diff --git a/web-ui/src/app/approvals/page.tsx b/web-ui/src/app/approvals/page.tsx index 0480560..664d83e 100644 --- a/web-ui/src/app/approvals/page.tsx +++ b/web-ui/src/app/approvals/page.tsx @@ -72,14 +72,27 @@ function ApprovalCard({ cat }: { cat: ApprovalCategory }) {

אין פריטים ממתינים ✓

) : cat.sample && cat.sample.length > 0 ? ( ) : null} diff --git a/web-ui/src/app/feedback/page.tsx b/web-ui/src/app/feedback/page.tsx new file mode 100644 index 0000000..ce4fc49 --- /dev/null +++ b/web-ui/src/app/feedback/page.tsx @@ -0,0 +1,252 @@ +"use client"; + +import { useMemo, useState } from "react"; +import Link from "next/link"; +import { Check, CheckCircle2 } from "lucide-react"; +import { toast } from "sonner"; +import { AppShell } from "@/components/app-shell"; +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + useFeedbackList, + useResolveFeedback, + CATEGORY_LABELS, + CATEGORY_COLORS, + BLOCK_LABELS, + type ChairFeedback, + type FeedbackCategory, +} from "@/lib/api/feedback"; + +/** + * מרכז הערות יו"ר — הדף המרכזי לטיפול בכל הערות דפנה שנרשמו על טיוטות + * (chair_feedback). מאגד הערות מכל התיקים במקום אחד, מאפשר סינון לפי + * "טרם יושמו" וקטגוריה, וסימון כל הערה כיושמה. מוזן מ-/api/feedback. + */ + +function formatDate(iso?: string | null): string { + if (!iso) return ""; + try { + return new Date(iso).toLocaleDateString("he-IL", { + day: "numeric", + month: "long", + year: "numeric", + }); + } catch { + return ""; + } +} + +function FeedbackCard({ fb }: { fb: ChairFeedback }) { + const resolve = useResolveFeedback(); + + const onResolve = () => { + resolve.mutate( + { feedbackId: fb.id, applied_to: [] }, + { + onSuccess: () => toast.success("ההערה סומנה כיושמה"), + onError: (e) => + toast.error(e instanceof Error ? e.message : "שגיאה בסימון"), + }, + ); + }; + + return ( + + +
+ + {CATEGORY_LABELS[fb.category]} + + + {BLOCK_LABELS[fb.block_id] ?? fb.block_id} + + {fb.case_number ? ( + + תיק {fb.case_number} + + ) : ( + ללא תיק + )} + + {formatDate(fb.created_at)} + +
+ +

+ {fb.feedback_text} +

+ + {fb.lesson_extracted ? ( +
+
לקח שהופק
+

+ {fb.lesson_extracted} +

+
+ ) : null} + +
+ {fb.resolved ? ( + + יושמה + + ) : ( + + )} +
+
+
+ ); +} + +type CatFilter = "all" | FeedbackCategory; + +export default function FeedbackPage() { + const [unresolvedOnly, setUnresolvedOnly] = useState(true); + const [category, setCategory] = useState("all"); + + const { data, isPending, error } = useFeedbackList({ + unresolved_only: unresolvedOnly, + category: category === "all" ? undefined : category, + }); + + const items = useMemo(() => data ?? [], [data]); + const unresolvedCount = useMemo( + () => items.filter((f) => !f.resolved).length, + [items], + ); + + const categories: { key: CatFilter; label: string }[] = [ + { key: "all", label: "הכל" }, + { key: "missing_content", label: CATEGORY_LABELS.missing_content }, + { key: "wrong_tone", label: CATEGORY_LABELS.wrong_tone }, + { key: "wrong_structure", label: CATEGORY_LABELS.wrong_structure }, + { key: "factual_error", label: CATEGORY_LABELS.factual_error }, + { key: "style", label: CATEGORY_LABELS.style }, + { key: "other", label: CATEGORY_LABELS.other }, + ]; + + return ( + +
+
+ +
+
+
+ שערים אנושיים · יו״ר הוועדה +
+

הערות יו״ר

+

+ כל ההערות שנרשמו על טיוטות — מכל התיקים. סמן כל הערה כיושמה + לאחר שהלקח הוטמע. +

+
+
+
+ {unresolvedCount} +
+
+ טרם יושמו +
+
+
+
+ +
+ + {/* Filters */} +
+
+ + +
+ +
+ {categories.map((c) => ( + + ))} +
+
+ + {error ? ( + + + שגיאה בטעינת ההערות. נסה לרענן. + + + ) : isPending ? ( +
+ {[0, 1, 2].map((i) => ( + + + + ))} +
+ ) : items.length === 0 ? ( +
+

אין הערות בקטגוריה זו.

+ {unresolvedOnly && ( +

כל ההערות יושמו ✓

+ )} +
+ ) : ( +
+ {items.map((fb) => ( + + ))} +
+ )} +
+
+ ); +} diff --git a/web-ui/src/components/app-shell.tsx b/web-ui/src/components/app-shell.tsx index 91cc20d..a243879 100644 --- a/web-ui/src/components/app-shell.tsx +++ b/web-ui/src/components/app-shell.tsx @@ -42,6 +42,7 @@ const NAV_GROUPS: NavGroup[] = [ items: [ { href: "/", label: "בית" }, { href: "/approvals", label: "מרכז אישורים" }, + { href: "/feedback", label: "הערות יו״ר" }, { href: "/archive", label: "ארכיון" }, ], }, diff --git a/web-ui/src/lib/api/chair.ts b/web-ui/src/lib/api/chair.ts index 50281d9..ee10ccc 100644 --- a/web-ui/src/lib/api/chair.ts +++ b/web-ui/src/lib/api/chair.ts @@ -11,7 +11,7 @@ import { apiRequest } from "./client"; */ export type ApprovalSeverity = "high" | "medium" | "low" | "ok"; -export type ApprovalSample = { text: string; source: string }; +export type ApprovalSample = { text: string; source: string; href?: string | null }; export type ApprovalCategory = { key: string; diff --git a/web/app.py b/web/app.py index b9195ba..f5511da 100644 --- a/web/app.py +++ b/web/app.py @@ -5058,17 +5058,21 @@ async def api_chair_pending(): "key": "chair_feedback", "label": "הערות יו\"ר שטרם יושמו", "description": "הערות שרשמת על טיוטות וטרם הופקו מהן לקחים/תיקונים.", "count": cf_count, "severity": "medium" if cf_count else "ok", - "href": "/", "sample": [{"text": (r["feedback_text"] or "")[:120], "source": r["case_number"]} for r in cf_sample], + "href": "/feedback", "sample": [{"text": (r["feedback_text"] or "")[:120], "source": r["case_number"]} for r in cf_sample], }) # 4) תיקים שנכשלו ב-QA qa_rows = await conn.fetch( "SELECT case_number, coalesce(title,'') AS title FROM cases WHERE status='qa_failed' ORDER BY updated_at DESC") + # Single failed case → link straight to it; multiple → home dashboard + # (the donut/table surface them). Each sample row links to its own case. + qa_href = f"/cases/{qa_rows[0]['case_number']}" if len(qa_rows) == 1 else "/" categories.append({ "key": "qa_failed", "label": "תיקים שנכשלו ב-QA", "description": "תיקים שבדיקת-האיכות חסמה — דורשים התייחסותך לפני המשך.", - "count": len(qa_rows), "severity": "high" if qa_rows else "ok", "href": "/", - "sample": [{"text": r["case_number"], "source": r["title"]} for r in qa_rows[:5]], + "count": len(qa_rows), "severity": "high" if qa_rows else "ok", "href": qa_href, + "sample": [{"text": r["case_number"], "source": r["title"], + "href": f"/cases/{r['case_number']}"} for r in qa_rows[:5]], }) total_pending = sum(c["count"] for c in categories)