feat(case-ui): חלוקת רשימת-המסמכים בטאב-הסקירה ל-4 קבוצות
חלוקה פנימית (לנוחות בלבד, לא משנה doc_type) של רשימת מסמכי-התיק:
1. עיקריים (appeal/response/appraisal) · 2. נלווים · 3. פרוטוקול ועדת הערר ·
4. לאחר הדיון. הסיווג נגזר בצד-הלקוח מ-doc_type + שני דגלי-metadata.
שני פקדים חדשים בעורך-התיוג:
- מתג "התקבל אחרי הדיון" → metadata.is_post_hearing (כבר נצרך ע"י בלוק-ח);
גובר על הסיווג-לפי-סוג ומעביר לקבוצה 4.
- בורר "פרוטוקול של: ועדת הערר / ועדה מקומית-מחוזית" → metadata.protocol_scope
(נראה רק כשהסוג protocol); ברירת-מחדל appeal→קבוצה 3, lower→קבוצה 2.
Backend: PATCH /documents/{id} + MCP document_update מקבלים is_post_hearing
ו-protocol_scope, נשמרים ב-metadata JSONB (אותו מסלול כמו appraiser_side).
עיצוב אושר ב-Claude Design (X17): 18k-case-documents-grouped.html.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,13 @@ import { casesKeys } from "@/lib/api/cases";
|
||||
import type { CaseDetail, CaseDocument } from "@/lib/api/cases";
|
||||
import { DocumentTypeEditor } from "@/components/cases/document-type-editor";
|
||||
import { formatDateShort as formatDate } from "@/lib/format-date";
|
||||
import {
|
||||
documentGroup,
|
||||
DOC_GROUP_ORDER,
|
||||
DOC_GROUP_LABELS,
|
||||
DOC_GROUP_HINTS,
|
||||
type DocGroup,
|
||||
} from "@/lib/doc-types";
|
||||
|
||||
/*
|
||||
* Document list for the case detail "מסמכים" tab. Uses the real document
|
||||
@@ -224,6 +231,13 @@ function DocumentRow({
|
||||
const canPreview =
|
||||
doc.extraction_status === "completed" || doc.extraction_status === "proofread";
|
||||
|
||||
const meta = doc.metadata as
|
||||
| { appraiser_side?: string; is_post_hearing?: boolean; protocol_scope?: string }
|
||||
| undefined;
|
||||
const isPostHearing = meta?.is_post_hearing === true;
|
||||
const protocolScope = meta?.protocol_scope;
|
||||
const isProtocol = doc.doc_type === "protocol";
|
||||
|
||||
return (
|
||||
<>
|
||||
<li className="py-3 flex items-start gap-3 hover:bg-gold-wash/30 transition-colors px-2 -mx-2 rounded group">
|
||||
@@ -246,15 +260,24 @@ function DocumentRow({
|
||||
{doc.created_at && <span>{formatDate(doc.created_at)}</span>}
|
||||
</div>
|
||||
</button>
|
||||
{isProtocol && (
|
||||
<span className="shrink-0 self-center rounded-full border border-rule bg-parchment px-2 py-0.5 text-[0.62rem] font-medium text-navy whitespace-nowrap">
|
||||
{protocolScope === "lower" ? "ועדה מקומית" : "ועדת הערר"}
|
||||
</span>
|
||||
)}
|
||||
{isPostHearing && (
|
||||
<span className="shrink-0 self-center rounded-full border border-info/40 bg-info-bg px-2 py-0.5 text-[0.62rem] font-medium text-info whitespace-nowrap">
|
||||
לאחר הדיון
|
||||
</span>
|
||||
)}
|
||||
{doc.doc_type && (
|
||||
<DocumentTypeEditor
|
||||
caseNumber={caseNumber}
|
||||
docId={doc.id}
|
||||
docType={doc.doc_type}
|
||||
appraiserSide={
|
||||
(doc.metadata as { appraiser_side?: string } | undefined)
|
||||
?.appraiser_side
|
||||
}
|
||||
appraiserSide={meta?.appraiser_side}
|
||||
isPostHearing={isPostHearing}
|
||||
protocolScope={protocolScope}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
@@ -306,6 +329,30 @@ function DocumentsShell({ count, children }: { count: number; children: ReactNod
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Group section header ──────────────────────────────────────────
|
||||
* The list is split into four convenience buckets (see documentGroup).
|
||||
* The number is the fixed 1-4 position in DOC_GROUP_ORDER so a group keeps
|
||||
* its identity even when earlier groups are empty and hidden. */
|
||||
function GroupHeader({ group, count }: { group: DocGroup; count: number }) {
|
||||
const index = DOC_GROUP_ORDER.indexOf(group) + 1;
|
||||
return (
|
||||
<div className="flex items-center gap-2.5 px-2 -mx-2 py-2 border-b border-rule-soft bg-parchment/50">
|
||||
<span className="w-[19px] h-[19px] shrink-0 rounded-full bg-navy text-cream text-[0.66rem] font-bold flex items-center justify-center tabular-nums">
|
||||
{index}
|
||||
</span>
|
||||
<span className="text-[0.8rem] font-bold text-navy shrink-0">
|
||||
{DOC_GROUP_LABELS[group]}
|
||||
</span>
|
||||
<span className="shrink-0 text-[0.62rem] font-bold text-gold-deep bg-gold-wash border border-rule rounded-full px-1.5 tabular-nums">
|
||||
{count}
|
||||
</span>
|
||||
<span className="text-[0.66rem] text-ink-muted font-medium truncate">
|
||||
{DOC_GROUP_HINTS[group]}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocumentsPanel({
|
||||
data,
|
||||
}: {
|
||||
@@ -329,6 +376,16 @@ export function DocumentsPanel({
|
||||
(a, b) => statusOrder(a.extraction_status) - statusOrder(b.extraction_status),
|
||||
);
|
||||
|
||||
// Bucket into the four overview groups. Iterating `sorted` preserves the
|
||||
// within-group status ordering. Grouping is display-only — see documentGroup.
|
||||
const grouped = new Map<DocGroup, CaseDocument[]>();
|
||||
for (const doc of sorted) {
|
||||
const g = documentGroup(doc.doc_type, doc.metadata);
|
||||
const bucket = grouped.get(g);
|
||||
if (bucket) bucket.push(doc);
|
||||
else grouped.set(g, [doc]);
|
||||
}
|
||||
|
||||
const done = docs.filter(
|
||||
(d) => d.extraction_status === "completed" || d.extraction_status === "proofread",
|
||||
).length;
|
||||
@@ -378,12 +435,21 @@ export function DocumentsPanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="max-h-[70vh] overflow-y-auto overflow-x-hidden" dir="rtl">
|
||||
<ul className="divide-y divide-rule" dir="rtl">
|
||||
{sorted.map((doc) => (
|
||||
<DocumentRow key={doc.id} doc={doc} caseNumber={caseNumber} />
|
||||
))}
|
||||
</ul>
|
||||
<div className="max-h-[70vh] overflow-y-auto overflow-x-hidden space-y-2" dir="rtl">
|
||||
{DOC_GROUP_ORDER.map((group) => {
|
||||
const groupDocs = grouped.get(group);
|
||||
if (!groupDocs || groupDocs.length === 0) return null;
|
||||
return (
|
||||
<section key={group}>
|
||||
<GroupHeader group={group} count={groupDocs.length} />
|
||||
<ul className="divide-y divide-rule" dir="rtl">
|
||||
{groupDocs.map((doc) => (
|
||||
<DocumentRow key={doc.id} doc={doc} caseNumber={caseNumber} />
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</DocumentsShell>
|
||||
|
||||
Reference in New Issue
Block a user