Merge pull request 'polish(ui): יישור 5 פריטי קטגוריה-A למוקאפי X17 המאושרים' (#279) from worktree-catA-polish into main
This commit was merged in pull request #279.
This commit is contained in:
@@ -168,9 +168,21 @@ export default function ArchivePage() {
|
||||
]);
|
||||
const [globalFilter, setGlobalFilter] = useState("");
|
||||
const [typeFilter, setTypeFilter] = useState<string>("all");
|
||||
const [yearFilter, setYearFilter] = useState<string>("all");
|
||||
|
||||
const rows = useMemo(() => data ?? [], [data]);
|
||||
|
||||
// years present in the archive (by archive date) — for the year filter (mockup 05)
|
||||
const years = useMemo(() => {
|
||||
const set = new Set<string>();
|
||||
for (const c of rows) {
|
||||
if (!c.archived_at) continue;
|
||||
const y = new Date(c.archived_at).getFullYear();
|
||||
if (!Number.isNaN(y)) set.add(String(y));
|
||||
}
|
||||
return [...set].sort((a, b) => Number(b) - Number(a));
|
||||
}, [rows]);
|
||||
|
||||
const table = useReactTable({
|
||||
data: rows,
|
||||
columns,
|
||||
@@ -192,10 +204,16 @@ export default function ArchivePage() {
|
||||
|
||||
// domain filter applied client-side (subtypeOf collapses בל"מ variants)
|
||||
const filteredRows = useMemo(() => {
|
||||
const all = table.getFilteredRowModel().rows;
|
||||
if (typeFilter === "all") return all;
|
||||
return all.filter((r) => subtypeOf(r.original) === typeFilter);
|
||||
}, [table, typeFilter, globalFilter, sorting, rows]);
|
||||
let all = table.getFilteredRowModel().rows;
|
||||
if (typeFilter !== "all") all = all.filter((r) => subtypeOf(r.original) === typeFilter);
|
||||
if (yearFilter !== "all") {
|
||||
all = all.filter((r) => {
|
||||
const iso = r.original.archived_at;
|
||||
return iso != null && String(new Date(iso).getFullYear()) === yearFilter;
|
||||
});
|
||||
}
|
||||
return all;
|
||||
}, [table, typeFilter, yearFilter, globalFilter, sorting, rows]);
|
||||
|
||||
const total = rows.length;
|
||||
const shown = filteredRows.length;
|
||||
@@ -242,6 +260,18 @@ export default function ArchivePage() {
|
||||
<option value="betterment_levy">היטל השבחה</option>
|
||||
<option value="compensation_197">פיצויים (ס׳ 197)</option>
|
||||
</select>
|
||||
{years.length > 0 ? (
|
||||
<select
|
||||
value={yearFilter}
|
||||
onChange={(e) => setYearFilter(e.target.value)}
|
||||
className="cursor-pointer text-[0.84rem] text-ink-soft bg-surface border border-rule rounded-lg px-3.5 py-2"
|
||||
>
|
||||
<option value="all">כל השנים</option>
|
||||
{years.map((y) => (
|
||||
<option key={y} value={y}>{y}</option>
|
||||
))}
|
||||
</select>
|
||||
) : null}
|
||||
<span className="ms-auto text-[0.82rem] text-ink-muted tabular-nums">
|
||||
מציג {shown} מתוך {total}
|
||||
</span>
|
||||
@@ -301,7 +331,7 @@ export default function ArchivePage() {
|
||||
<div className="text-gold text-2xl mb-2" aria-hidden>
|
||||
❦
|
||||
</div>
|
||||
{globalFilter || typeFilter !== "all"
|
||||
{globalFilter || typeFilter !== "all" || yearFilter !== "all"
|
||||
? "אין תיקים תואמים לחיפוש"
|
||||
: "אין תיקים בארכיון"}
|
||||
</TableCell>
|
||||
|
||||
@@ -264,9 +264,9 @@ function BurstControl({ s }: { s: OpsService }) {
|
||||
onChange={(e) => setUntil(e.target.value)}
|
||||
/>
|
||||
<div className="flex flex-wrap gap-1.5 pt-1">
|
||||
<button type="button" className="text-[0.72rem] text-gold-deep bg-gold-wash border border-rule rounded-full px-2.5 py-0.5 hover:border-gold"
|
||||
<button type="button" className="text-[0.72rem] text-gold-deep bg-gold-wash border border-gold rounded-full px-2.5 py-0.5 font-semibold hover:border-gold-deep"
|
||||
onClick={() => setUntil(toLocalInput(nextSaturday18()))}>
|
||||
שבת 18:00
|
||||
שבת 18:00 <span className="font-normal text-ink-muted">(ברירת-מחדל)</span>
|
||||
</button>
|
||||
<button type="button" className="text-[0.72rem] text-gold-deep bg-gold-wash border border-rule rounded-full px-2.5 py-0.5 hover:border-gold"
|
||||
onClick={() => setUntil(toLocalInput(new Date(Date.now() + 5 * 3600 * 1000)))}>
|
||||
|
||||
@@ -70,6 +70,17 @@ const HEARING_CLASS_STYLE: Record<HearingClass, string> = {
|
||||
none: "text-ink-light",
|
||||
};
|
||||
|
||||
/** Relative-time label beside the hearing date (mockup 04b `.hd-rel`). */
|
||||
function relativeHearing(iso?: string | null): string | null {
|
||||
const t = hearingMs(iso);
|
||||
if (t === null) return null;
|
||||
const days = Math.round((t - startOfToday()) / 86_400_000);
|
||||
if (days === 0) return "היום";
|
||||
if (days === 1) return "מחר";
|
||||
if (days === -1) return "אתמול";
|
||||
return days > 0 ? `בעוד ${days} ימים` : `לפני ${Math.abs(days)} ימים`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default ordering for the case list: the nearest upcoming hearing on top,
|
||||
* past hearings sinking below it (most-recently-passed first), and cases with
|
||||
@@ -143,10 +154,16 @@ const columns: ColumnDef<Case>[] = [
|
||||
sortingFn: hearingDateSort,
|
||||
cell: ({ row }) => {
|
||||
const klass = classifyHearing(row.original.hearing_date);
|
||||
const rel = relativeHearing(row.original.hearing_date);
|
||||
return (
|
||||
<div className="leading-tight">
|
||||
<span className={`tabular-nums text-sm ${HEARING_CLASS_STYLE[klass]}`}>
|
||||
{formatDate(row.original.hearing_date ?? undefined)}
|
||||
</span>
|
||||
{rel ? (
|
||||
<span className="block text-[0.7rem] text-ink-muted">{rel}</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -90,8 +90,8 @@ export function GoldenRatiosPanel() {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12 text-ink-faint">
|
||||
<Loader2 className="w-5 h-5 animate-spin ml-2" />
|
||||
<div className="flex items-center justify-center py-12 text-ink-muted">
|
||||
<Loader2 className="w-5 h-5 animate-spin me-2" />
|
||||
טוען...
|
||||
</div>
|
||||
);
|
||||
@@ -113,10 +113,10 @@ export function GoldenRatiosPanel() {
|
||||
{/* Table */}
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-ink-faint text-[11px]">
|
||||
<th className="text-right py-1">Section</th>
|
||||
<th className="text-center py-1">Min %</th>
|
||||
<th className="text-center py-1">Max %</th>
|
||||
<tr className="text-ink-muted text-[11px]">
|
||||
<th className="text-start py-1">בלוק</th>
|
||||
<th className="text-center py-1">מינ׳ %</th>
|
||||
<th className="text-center py-1">מקס׳ %</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -155,7 +155,7 @@ export function GoldenRatiosPanel() {
|
||||
disabled={!card.dirty || update.isPending}
|
||||
onClick={() => handleSave(card)}
|
||||
>
|
||||
{update.isPending ? <Loader2 className="w-3 h-3 animate-spin ml-1" /> : <Save className="w-3 h-3 ml-1" />}
|
||||
{update.isPending ? <Loader2 className="w-3 h-3 animate-spin me-1" /> : <Save className="w-3 h-3 me-1" />}
|
||||
שמור
|
||||
</Button>
|
||||
{card.isOverride && (
|
||||
@@ -165,7 +165,7 @@ export function GoldenRatiosPanel() {
|
||||
disabled={reset.isPending}
|
||||
onClick={() => handleReset(card)}
|
||||
>
|
||||
<RotateCcw className="w-3 h-3 ml-1" />
|
||||
<RotateCcw className="w-3 h-3 me-1" />
|
||||
איפוס
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -123,6 +123,14 @@ function PanelDeliberation({ round }: { round: NonNullable<Halacha["panel_round"
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{/* seedline (mockup 18): the chair's call IS the gold label the
|
||||
active-learning loop trains on — surfaced on every deliberation. */}
|
||||
<p
|
||||
className="border-t border-rule-soft bg-gold-wash/40 px-3 py-1.5 text-[0.64rem] text-ink-muted leading-snug"
|
||||
dir="rtl"
|
||||
>
|
||||
הכרעתך תיקלט כתווית-הזהב שממנה לומדת לולאת-הלמידה-הפעילה.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user