Merge pull request 'polish(ui): יישור 5 פריטי קטגוריה-A למוקאפי X17 המאושרים' (#279) from worktree-catA-polish into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 41s
G12 Leak-Guard / leak-guard (push) Successful in 3s
Lint — undefined names / undefined-names (push) Successful in 10s

This commit was merged in pull request #279.
This commit is contained in:
2026-06-16 19:16:40 +00:00
5 changed files with 73 additions and 18 deletions

View File

@@ -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>

View File

@@ -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)))}>

View File

@@ -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>
);
},
},

View File

@@ -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>
)}

View File

@@ -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>
);
}