diff --git a/web-ui/src/app/approvals/page.tsx b/web-ui/src/app/approvals/page.tsx index 0d78299..25dee49 100644 --- a/web-ui/src/app/approvals/page.tsx +++ b/web-ui/src/app/approvals/page.tsx @@ -17,13 +17,6 @@ import { * פסיקה חסרה, הערות שטרם יושמו, ותיקים שנכשלו ב-QA. המטרה: * שאף פריט הדורש את אישורך לא יישכח. הנתונים נשלפים חי מ-/api/chair/pending. */ -const SEVERITY_BADGE: Record = { - high: "bg-gold text-navy border-transparent", - medium: "bg-gold-wash text-gold-deep border-gold/40", - low: "bg-rule-soft text-ink-muted border-rule", - ok: "bg-emerald-50 text-emerald-800 border-emerald-300/60", -}; - // Severity expressed as a colored dot next to the title (matches the approved // IA-redesign mockup): high=danger, medium=warn, low=info, ok=success. const SEVERITY_DOT: Record = { @@ -49,67 +42,84 @@ function formatDate(iso?: string | null): string { function ApprovalCard({ cat }: { cat: ApprovalCategory }) { const cleared = cat.count === 0; return ( - - -
+ + + {/* top row — severity dot · title+age · big count number (mockup 01) */} +
-

{cat.label}

+
+

{cat.label}

+
+ {cleared ? ( + + תור נקי + + ) : cat.oldest_at ? ( + <>הוותיק ביותר — {formatDate(cat.oldest_at)} + ) : ( + cat.description + )} +
+
{cat.count}
-

- {cat.description} -

- - {cat.oldest_at && cat.count > 0 ? ( -

- הישן ביותר ממתין מ־{formatDate(cat.oldest_at)} -

+ {/* description kept (subtle) when the age line took the title slot */} + {!cleared && cat.oldest_at ? ( +

{cat.description}

) : null} {cat.extra ? ( -

+

סך {cat.extra.total} שאילתות · {cat.extra.reviewed} אושרו על־ידך

) : null} - {cleared ? ( -

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

- ) : cat.sample && cat.sample.length > 0 ? ( -
    + {!cleared && cat.sample && cat.sample.length > 0 ? ( +
      {cat.sample.map((s, i) => { - const body = ( - <> - {s.text || "—"} + const row = ( +
      + {s.text || "—"} {s.source ? ( - · {s.source} + + {s.source} + ) : null} - +
      ); return ( -
    • +
    • {s.href ? ( - - {body} + + {row} ) : ( - body + row )}
    • ); })}
    + ) : cleared ? ( +

    + אין פריטים הממתינים להתייחסות. כל התיקים עברו בדיקת-איכות. +

    ) : null} -
    + {/* foot — gold CTA when actionable, quiet outline when cleared */} +
    {cat.href ? ( cleared ? ( - {hasAnalysis && ( - + )} + + {hasAnalysis && ( + + )} +
    + + {uploadMsg && ( +

    + {uploadMsg.text} +

    + )} + + {/* mockup 03: stage indicators — informational pointers, not actions */} +
    +
    + הרץ למידת-קול — ממתין להעלאת הסופי +
    +
    + הרץ אימות-הלכות — ממתין להעלאת הסופי +
    +
    + + - )} - {hasAnalysis && ( - - )} - -
    + + ); } @@ -145,6 +210,18 @@ export default function ComposePage({ } } const practiceArea = caseQuery.data?.practice_area ?? null; + const subtype = subtypeLabel(caseQuery.data?.appeal_subtype); + const parties = (() => { + const c = caseQuery.data; + if (!c) return null; + const app = c.appellants?.length ? c.appellants.join(", ") : null; + const resp = c.respondents?.length ? c.respondents.join(", ") : null; + const out: string[] = []; + if (app) out.push(`עוררים: ${app}`); + if (resp) out.push(`משיבה: ${resp}`); + return out.length ? out.join(" · ") : c.title || null; + })(); + const documents = caseQuery.data?.documents ?? []; const isNotFound = analysis.error instanceof Error && @@ -152,121 +229,84 @@ export default function ComposePage({ return ( -
    - {/* Header strip */} -
    -
    - -

    ניתוח משפטי וכתיבת עמדה

    - {caseQuery.data?.title && ( -

    - {caseQuery.data.title} -

    - )} -
    - analysis.refetch()} /> + {/* ── Case header band (mockup 03) — parchment strip, full-bleed to the + AppShell
    edges (which pads px-10 py-10) ── */} +
    + +
    +

    ערר {caseNumber}

    + + {subtype && ( + + {subtype} + + )} + {/* INV-G10: source-of-truth pill — the blocks are the canonical text */} + + מקור-אמת: בלוקים +
    + {parties &&

    {parties}

    } +
    -
    - - {analysis.isPending ? ( - - - - - - - - - ) : isNotFound ? ( - - -
    -

    - טרם בוצע ניתוח משפטי לתיק זה -

    -

    - לאחר שקובץ analysis-and-research.md ייווצר, תוכלי - לערוך כאן את עמדת הוועדה לכל טענת סף וסוגיה. -

    -
    -
    - ) : analysis.error ? ( - - -

    {analysis.error.message}

    -
    -
    - ) : analysis.data ? ( -
    - {/* Case-level general precedents */} - - -

    פסיקה כללית לדיון

    -

    - ציטוטים התומכים בעמדה באופן רוחבי — ישולבו בפתיחת בלוק י (דיון). -

    - -
    -
    - - {/* Threshold claims */} - {analysis.data.threshold_claims && - analysis.data.threshold_claims.length > 0 && ( -
    -
    -

    טענות סף

    - - {analysis.data.threshold_claims.length} - -
    -
    - {analysis.data.threshold_claims.map((tc) => ( - - ))} -
    -
    - )} - - {/* Issues */} - {analysis.data.issues && analysis.data.issues.length > 0 && ( + {analysis.isPending ? ( + + + + + + + + + ) : isNotFound ? ( + + +
    +

    + טרם בוצע ניתוח משפטי לתיק זה +

    +

    + לאחר שקובץ analysis-and-research.md ייווצר, תוכלי + לערוך כאן את עמדת הוועדה לכל טענת סף וסוגיה. +

    +
    +
    + ) : analysis.error ? ( + + +

    {analysis.error.message}

    +
    +
    + ) : analysis.data ? ( + /* ── Two-column workspace: main editor list + 320px side rail ──────── */ +
    + {/* MAIN — the block/subsection editor list */} +
    + {/* Threshold claims */} + {analysis.data.threshold_claims && + analysis.data.threshold_claims.length > 0 && (
    -

    סוגיות להכרעה

    +

    טענות סף

    - {analysis.data.issues.length} + {analysis.data.threshold_claims.length}
    -
    - {analysis.data.issues.map((iss) => ( +
    + {analysis.data.threshold_claims.map((tc) => ( ))} @@ -274,8 +314,31 @@ export default function ComposePage({
    )} - {(!analysis.data.threshold_claims?.length && - !analysis.data.issues?.length) && ( + {/* Issues */} + {analysis.data.issues && analysis.data.issues.length > 0 && ( +
    +
    +

    סוגיות להכרעה

    + + {analysis.data.issues.length} + +
    +
    + {analysis.data.issues.map((iss) => ( + + ))} +
    +
    + )} + + {!analysis.data.threshold_claims?.length && + !analysis.data.issues?.length && ( לא נמצאו טענות סף או סוגיות בניתוח זה. @@ -283,42 +346,82 @@ export default function ComposePage({ )} - {/* Background prose — moved below the issues so it reads as - supporting context after the chair has seen the main - decision points, not as a wall of text beside them. */} - - -

    רקע לניתוח

    - - - - + {/* Background prose — supporting context after the decision points */} + + +

    רקע לניתוח

    + + + + +
    +
    + + {analysis.data.conclusions?.trim() && ( + + +

    מסקנות

    +
    - - {analysis.data.conclusions?.trim() && ( - - -

    מסקנות

    - -
    -
    - )} + )}
    - ) : null} -
    + + {/* SIDE RAIL — documents · attached precedents · finish-and-transfer */} + +
+ ) : null} ); } diff --git a/web-ui/src/app/cases/[caseNumber]/page.tsx b/web-ui/src/app/cases/[caseNumber]/page.tsx index f252362..81553ec 100644 --- a/web-ui/src/app/cases/[caseNumber]/page.tsx +++ b/web-ui/src/app/cases/[caseNumber]/page.tsx @@ -45,10 +45,10 @@ export default function CaseDetailPage({ ? EXPECTED_OUTCOME_LABELS[data.expected_outcome] ?? data.expected_outcome : null; - return ( - -
- {error ? ( + if (error) { + return ( + +

שגיאה בטעינת התיק

@@ -58,130 +58,168 @@ export default function CaseDetailPage({
+
+
+ ); + } + + const tabsList = ( + + {[ + ["overview", "סקירה"], + ["arguments", "טיעונים"], + ["decision", "ההחלטה"], + ["drafts", "טיוטות והערות"], + ["agents", "סוכנים"], + ].map(([value, label]) => ( + + {label} + + ))} + + ); + + const bandActions = ( + <> + {data && } + + + ); + + return ( + + + {/* parchment band — header (title/chips/parties/actions) + tab strip */} + {isPending ? ( +
+ + + +
) : ( - <> - {isPending ? ( - - - - - + + )} + + {/* two-column wrap — main tab content (1fr) + rail (340px) */} +
+
+ + +
+ סקירת התיק +
+ +
+

תוצאה צפויה

+

+ {expectedOutcomeLabel ?? "לא נקבעה תוצאה צפויה."} +

+
+
+
+
בעיבוד
+
+ {data?.processing_count ?? 0} +
+
+
+ {canStartWorkflow && ( +
+ +
+ )}
- ) : ( - - )} -
+ + + {/* gold CTA — open the decision editor (mockup .cta) */} + + + + - -
- - סקירה - - טיעונים - - - ההחלטה - - - טיוטות והערות - - - סוכנים - - -
- - {data && } - -
-
- - -
-

תוצאה צפויה

-

- {expectedOutcomeLabel ?? "לא נקבעה תוצאה צפויה."} -

-
-
-

סיכום מהיר

-
-
בעיבוד
-
- {data?.processing_count ?? 0} -
-
-
- {canStartWorkflow && ( -
- -
- )} - -
- - - - - - - - - - - - - - - - -
+
+
- - - -

שלב בתהליך

- - - + + + + -
- - )} -
+ + + + + + + + + + + + + + + + + + + + {/* rail — status timeline + status controls (mockup .rail) */} +
+ +
+ סטטוס התיק +
+ + + + + + +
+
+ +
); } diff --git a/web-ui/src/app/digests/page.tsx b/web-ui/src/app/digests/page.tsx index ee55e43..2caee2d 100644 --- a/web-ui/src/app/digests/page.tsx +++ b/web-ui/src/app/digests/page.tsx @@ -2,11 +2,11 @@ import Link from "next/link"; import { AppShell } from "@/components/app-shell"; -import { Card, CardContent } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Badge } from "@/components/ui/badge"; import { DigestListPanel } from "@/components/digests/digest-list-panel"; import { DigestSearchPanel } from "@/components/digests/digest-search-panel"; +import { DigestUploadDialog } from "@/components/digests/digest-upload-dialog"; import { useDigestPending } from "@/lib/api/digests"; /** @@ -38,44 +38,68 @@ export default function DigestsPage() { return (
-
-
); diff --git a/web-ui/src/app/feedback/page.tsx b/web-ui/src/app/feedback/page.tsx index df624ad..b89c65d 100644 --- a/web-ui/src/app/feedback/page.tsx +++ b/web-ui/src/app/feedback/page.tsx @@ -7,12 +7,11 @@ 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, + useCreateFeedback, CATEGORY_LABELS, - CATEGORY_COLORS, BLOCK_LABELS, type ChairFeedback, type FeedbackCategory, @@ -24,6 +23,16 @@ import { * "טרם יושמו" וקטגוריה, וסימון כל הערה כיושמה. מוזן מ-/api/feedback. */ +// category chip styling per mockup 06 (.c-missing / .c-tone / .c-struct / .c-fact / .c-style) +const CAT_CHIP: Record = { + missing_content: "bg-warn-bg text-warn", + wrong_tone: "bg-info-bg text-info", + wrong_structure: "bg-gold-wash text-gold-deep border border-rule", + factual_error: "bg-danger-bg text-danger", + style: "bg-rule-soft text-ink-soft", + other: "bg-rule-soft text-ink-soft", +}; + function formatDate(iso?: string | null): string { if (!iso) return ""; try { @@ -57,47 +66,51 @@ function FeedbackCard({ fb }: { fb: ChairFeedback }) { }; return ( - - -
- + + + {/* meta row — where · category chip · when (mockup 06 .meta) */} +
+ + {fb.case_number ? ( + + ערר {fb.case_number} + + ) : ( + "ללא תיק" + )} + · {BLOCK_LABELS[fb.block_id] ?? fb.block_id} + + {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} -

-
+

+ לקח שחולץ: {fb.lesson_extracted} +

) : null} -
+ {/* action row — gold CTA / applied pill (mockup 06 .actrow) */} +
{fb.resolved ? ( - - יושמה + + יושם ) : (