feat(ui): IA redesign → production · יישום נאמן של 16 הדפים הנותרים למוקאפים
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
תיקון הגישה: יישום מלא ונאמן של עיצוב-המוקאפים המאושרים (Claude Design) על כל הדפים — שינוי-הרכב אמיתי פר-מוקאפ, לא ליטוש-טוקנים. כל hook/query/mutation/טאב/ טופס/נתון נשמר (אומת: tsc נקי + בדיקת-נוכחות hooks קריטיים; 0 פונקציונליות נמחקה). דפים (← מוקאפ): - בית — לוח: KPI + "תיקים לפי סטטוס" (bars) + כרטיס-אישורים + CTA כפול. - ארכיון — filter-bar שטוח + טבלה נקייה + צ'יפי-סוג/תוצאה. - הערות יו״ר — פריסה דו-טורית + טופס-הוספה חי + כרטיסי-הערה. - ספריית-פסיקה — tabs קו-תחתון + כרטיסי-תוצאה halacha/קטע + AuthorityBadge. - דף-תקדים — באנר-meta parchment + דו-טורי + provenance pills. - פסיקה-חסרה — pill פתוחים + צ'יפי-סטטוס + CTA העלאה. - יומונים — אזור-העלאה מקווקו + כרטיסי-digest + "ממתין" כתווית פסיבית. - גרף — פאנל-צד שכבות/אנליטיקה + canvas parchment. - אימון-סגנון — פורטרט: banner + KPI + אנטומיה + ביטויי-חתימה. - מתודולוגיה — עורך-צ'קליסט + "חל על:" + canon chip. - מיומנויות/סקריפטים — טבלאות אמיתיות + צ'יפי-סטטוס. - הגדרות — sidenav דו-טורי + env-rows עם "ממתין ל-redeploy". - דף-תיק — באנר-תיק parchment + tabs + timeline + "פתח עורך החלטה". - תפעול — SectionHeaders + טבלת-שירותים + כרטיסי-שער gold-wash. - compose — באנר-תיק + SOT pill + פריסה דו-טורית + "השלמה והעברה". תיקונים שלי אחרי הסוכנים: documents-panel (הוצאת רכיב Shell מ-render — React Compiler), scripts useMemo deps. /approvals כבר נבנה מחדש נאמנה (commit קודם). בדיקות: npx tsc --noEmit ✓ · eslint ✓ (לבד מ-learning-panel:109 קיים-מראש). שימור-פונקציונליות אומת. CI Docker build = שער סופי לפני deploy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,12 @@ function mb(bytes: number): string {
|
||||
return `${Math.round((bytes || 0) / 1024 / 1024)}MB`;
|
||||
}
|
||||
|
||||
// mockup 02: every region opens with a navy section heading (h2, 18px) — the
|
||||
// page reads as a sequence of titled sections rather than a stack of cards.
|
||||
function SectionHeader({ children }: { children: React.ReactNode }) {
|
||||
return <h2 className="text-navy text-lg font-semibold mb-3 mt-2">{children}</h2>;
|
||||
}
|
||||
|
||||
function ago(ms: number): string {
|
||||
if (!ms) return "—";
|
||||
const secs = Math.floor((Date.now() - ms) / 1000);
|
||||
@@ -189,64 +195,91 @@ function ServicesPanel({ data }: { data: OperationsSnapshot }) {
|
||||
return (
|
||||
<Card className="bg-surface border-rule shadow-sm">
|
||||
<CardContent className="px-6 py-5">
|
||||
<h2 className="text-navy text-lg mb-1">ניהול תהליכי-רקע (pm2)</h2>
|
||||
<p className="text-ink-muted text-xs mb-4">
|
||||
כמו "שירותים" ב-Windows. דמון = שירות רץ-תמיד (הפעל-מחדש/עצור/הפעל).
|
||||
תזמון (cron) = רץ לפי לוח-זמנים ("הרץ עכשיו" להרצה מיידית, ומתג
|
||||
הפעלה/כיבוי של התזמון).
|
||||
ניהול תהליכי-רקע (pm2) — כמו "שירותים" ב-Windows. דמון = שירות
|
||||
רץ-תמיד (הפעל-מחדש/עצור/הפעל). תזמון (cron) = רץ לפי לוח-זמנים
|
||||
("הרץ עכשיו" להרצה מיידית, ומתג הפעלה/כיבוי של התזמון).
|
||||
</p>
|
||||
{data.services_error ? (
|
||||
<p className="text-sm text-destructive">{data.services_error}</p>
|
||||
) : data.services.length === 0 ? (
|
||||
<p className="text-sm text-ink-muted">אין שירותים.</p>
|
||||
) : (
|
||||
<div className="grid gap-2">
|
||||
{data.services.map((s: OpsService) => {
|
||||
const isCron = !!s.cron;
|
||||
return (
|
||||
<div
|
||||
key={s.name}
|
||||
className="flex items-center justify-between gap-3 rounded-md border border-rule-soft bg-rule-soft/30 px-3 py-2"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{isCron ? (
|
||||
<Badge
|
||||
variant={s.disabled ? "destructive" : "default"}
|
||||
className="font-normal"
|
||||
>
|
||||
{s.disabled ? "כבוי" : "פעיל (מתוזמן)"}
|
||||
</Badge>
|
||||
) : (
|
||||
<StatusBadge value={s.status} />
|
||||
)}
|
||||
{s.cron ? (
|
||||
<span className="text-[0.7rem] text-ink-muted font-mono" dir="ltr">
|
||||
{s.cron}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="text-[0.8rem] text-navy truncate mt-0.5">
|
||||
{SERVICE_LABELS[s.name] ?? s.name}
|
||||
</div>
|
||||
<div className="text-[0.66rem] text-ink-muted flex items-center gap-2 flex-wrap">
|
||||
<span className="font-mono" dir="ltr">
|
||||
{s.name}
|
||||
</span>
|
||||
<span>{mb(s.memory_bytes)}</span>
|
||||
<span>↻{s.restarts}</span>
|
||||
<span>{isCron ? `ריצה אחרונה ${ago(s.uptime_ms)}` : ago(s.uptime_ms)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ServiceControls
|
||||
s={s}
|
||||
busy={busy}
|
||||
onAction={(a) => action.mutate({ name: s.name, action: a })}
|
||||
onToggle={(disabled) => toggle.mutate({ name: s.name, disabled })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
/* mockup 02: services as a table — שירות · סטטוס · זמן-ריצה · זיכרון · controls */
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b border-rule-soft text-ink-muted">
|
||||
<th className="text-start font-medium text-xs py-2 pe-3">שירות</th>
|
||||
<th className="text-start font-medium text-xs py-2 px-3">סטטוס</th>
|
||||
<th className="text-start font-medium text-xs py-2 px-3 whitespace-nowrap">
|
||||
זמן-ריצה
|
||||
</th>
|
||||
<th className="text-start font-medium text-xs py-2 px-3 whitespace-nowrap">
|
||||
זיכרון / ↻
|
||||
</th>
|
||||
<th className="py-2 ps-3" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.services.map((s: OpsService) => {
|
||||
const isCron = !!s.cron;
|
||||
return (
|
||||
<tr
|
||||
key={s.name}
|
||||
className="border-b border-rule-soft last:border-0 align-top"
|
||||
>
|
||||
<td className="py-2.5 pe-3">
|
||||
<div className="text-navy font-semibold text-[0.82rem]">
|
||||
{SERVICE_LABELS[s.name] ?? s.name}
|
||||
</div>
|
||||
<div className="text-[0.66rem] text-ink-muted font-mono" dir="ltr">
|
||||
{s.name}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-2.5 px-3">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{isCron ? (
|
||||
<Badge
|
||||
variant={s.disabled ? "destructive" : "default"}
|
||||
className="font-normal"
|
||||
>
|
||||
{s.disabled ? "כבוי" : "פעיל (מתוזמן)"}
|
||||
</Badge>
|
||||
) : (
|
||||
<StatusBadge value={s.status} />
|
||||
)}
|
||||
{s.cron ? (
|
||||
<span
|
||||
className="text-[0.66rem] text-ink-muted font-mono"
|
||||
dir="ltr"
|
||||
>
|
||||
{s.cron}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-2.5 px-3 text-[0.78rem] text-ink-soft whitespace-nowrap tabular-nums">
|
||||
{isCron ? `אחרונה ${ago(s.uptime_ms)}` : ago(s.uptime_ms)}
|
||||
</td>
|
||||
<td className="py-2.5 px-3 text-[0.78rem] text-ink-soft whitespace-nowrap tabular-nums">
|
||||
{mb(s.memory_bytes)} · ↻{s.restarts}
|
||||
</td>
|
||||
<td className="py-2.5 ps-3">
|
||||
<div className="flex justify-end">
|
||||
<ServiceControls
|
||||
s={s}
|
||||
busy={busy}
|
||||
onAction={(a) => action.mutate({ name: s.name, action: a })}
|
||||
onToggle={(disabled) => toggle.mutate({ name: s.name, disabled })}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
@@ -336,6 +369,7 @@ function PipelineCard({
|
||||
children,
|
||||
href,
|
||||
hrefLabel,
|
||||
gate = false,
|
||||
}: {
|
||||
title: string;
|
||||
desc: string;
|
||||
@@ -344,9 +378,18 @@ function PipelineCard({
|
||||
// (/approvals), never duplicated here. /operations only monitors.
|
||||
href?: string;
|
||||
hrefLabel?: string;
|
||||
// mockup 02: gate cards get a gold-wash treatment + gold border so the
|
||||
// human-gates read as distinct from the automatic pipelines.
|
||||
gate?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Card className="bg-surface border-rule shadow-sm">
|
||||
<Card
|
||||
className={
|
||||
gate
|
||||
? "bg-gold-wash border-gold/40 shadow-sm"
|
||||
: "bg-surface border-rule shadow-sm"
|
||||
}
|
||||
>
|
||||
<CardContent className="px-5 py-4 space-y-2.5">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
@@ -356,7 +399,7 @@ function PipelineCard({
|
||||
{href && (
|
||||
<Link
|
||||
href={href}
|
||||
className="shrink-0 text-[0.72rem] text-gold-deep hover:underline whitespace-nowrap"
|
||||
className="shrink-0 text-[0.72rem] text-gold-deep font-semibold hover:underline whitespace-nowrap"
|
||||
>
|
||||
{hrefLabel ?? "לטיפול ←"}
|
||||
</Link>
|
||||
@@ -456,30 +499,29 @@ function LiveAgentsPanel() {
|
||||
return (
|
||||
<Card className="bg-surface border-rule shadow-sm">
|
||||
<CardContent className="px-6 py-5">
|
||||
<div className="flex items-center justify-between gap-3 mb-1 flex-wrap">
|
||||
<h2 className="text-navy text-lg mb-0">סוכנים פעילים</h2>
|
||||
{data ? (
|
||||
<div className="flex items-center gap-2 text-[0.72rem]">
|
||||
{/* ADM-6 (INV-IA5): counts sum only the companies that loaded.
|
||||
When a company errored, mark the totals as a floor ("+") so
|
||||
the operator isn't shown a shrunken depth as if complete. */}
|
||||
<Badge variant="default" className="font-normal">
|
||||
רצים {data.running}{data.errors.length > 0 ? "+" : ""}
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="font-normal">
|
||||
בתור {data.queued}{data.errors.length > 0 ? "+" : ""}
|
||||
</Badge>
|
||||
{data.errors.length > 0 ? (
|
||||
<span
|
||||
className="text-warn"
|
||||
title={`ספירה חלקית — חברות שלא נטענו: ${data.errors.join(" · ")}`}
|
||||
>
|
||||
⚠ חלקי
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{/* mockup 02: status pills row (running / queued) + company hint */}
|
||||
{data ? (
|
||||
<div className="flex items-center gap-2 text-[0.72rem] mb-3 flex-wrap">
|
||||
{/* ADM-6 (INV-IA5): counts sum only the companies that loaded.
|
||||
When a company errored, mark the totals as a floor ("+") so
|
||||
the operator isn't shown a shrunken depth as if complete. */}
|
||||
<Badge variant="default" className="font-normal">
|
||||
רצים {data.running}{data.errors.length > 0 ? "+" : ""}
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="font-normal">
|
||||
בתור {data.queued}{data.errors.length > 0 ? "+" : ""}
|
||||
</Badge>
|
||||
{data.errors.length > 0 ? (
|
||||
<span
|
||||
className="text-warn"
|
||||
title={`ספירה חלקית — חברות שלא נטענו: ${data.errors.join(" · ")}`}
|
||||
>
|
||||
⚠ חלקי
|
||||
</span>
|
||||
) : null}
|
||||
<span className="text-ink-muted ms-auto">חברות: CMP · CMPA</span>
|
||||
</div>
|
||||
) : null}
|
||||
<p className="text-ink-muted text-xs mb-4">
|
||||
מי מבין סוכני-הוועדה עובד כרגע ומה הפלט שלו — כולל עבודה שלא קשורה לתיק (כמו
|
||||
ריקון תור הלכות ע״י ה-CEO). עצירה היא מבוקרת דרך הפלטפורמה (לא kill).
|
||||
@@ -597,10 +639,14 @@ export default function OperationsPage() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<SectionHeader>סוכנים פעילים</SectionHeader>
|
||||
<LiveAgentsPanel />
|
||||
|
||||
<SectionHeader>שירותים</SectionHeader>
|
||||
<ServicesPanel data={data} />
|
||||
|
||||
<SectionHeader>צינורות-עבודה</SectionHeader>
|
||||
{/* Automatic pipelines — uniform stat cards */}
|
||||
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
<PipelineCard
|
||||
title="אחזור פסיקה (X13)"
|
||||
@@ -633,10 +679,14 @@ export default function OperationsPage() {
|
||||
לפסיקה
|
||||
</p>
|
||||
</PipelineCard>
|
||||
</div>
|
||||
|
||||
{/* mockup 02: human-gate pointer cards — gold-wash, "לתיבת-האישורים ←" */}
|
||||
<div className="grid gap-4 md:grid-cols-2 mt-4">
|
||||
<PipelineCard
|
||||
gate
|
||||
title="אישור הלכות (שער יו״ר)"
|
||||
desc="הלכות שחולצו, ממתינות להכרעת דפנה — שער-אנושי, לא תהליך"
|
||||
desc="שער-אנושי, לא תהליך — הפעולה ב-/approvals"
|
||||
href="/approvals"
|
||||
hrefLabel="לתיבת-האישורים ←"
|
||||
>
|
||||
@@ -644,6 +694,7 @@ export default function OperationsPage() {
|
||||
</PipelineCard>
|
||||
|
||||
<PipelineCard
|
||||
gate
|
||||
title="פסיקה חסרה"
|
||||
desc="פערים בקורפוס — נסגרים אוטומטית כשנקלטים"
|
||||
href="/approvals"
|
||||
@@ -653,6 +704,7 @@ export default function OperationsPage() {
|
||||
</PipelineCard>
|
||||
</div>
|
||||
|
||||
<SectionHeader>אחזורים אחרונים</SectionHeader>
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
<Card className="bg-surface border-rule shadow-sm">
|
||||
<CardContent className="px-5 py-4">
|
||||
@@ -712,9 +764,9 @@ export default function OperationsPage() {
|
||||
|
||||
{/* INV-IA4: the former /diagnostics surface, folded in here — one
|
||||
monitoring intent, one surface. /diagnostics now redirects here. */}
|
||||
<div className="space-y-3 pt-2">
|
||||
<h2 className="text-navy text-lg mb-0">בריאות-מערכת</h2>
|
||||
<p className="text-ink-muted text-sm">
|
||||
<div className="pt-2">
|
||||
<SectionHeader>בריאות-מערכת</SectionHeader>
|
||||
<p className="text-ink-muted text-sm mb-3">
|
||||
מצב ה-DB, תורי-אישור, ומסמכים שנכשלו/תקועים. (אוחד מ״אבחון״.)
|
||||
</p>
|
||||
<SystemHealthSection />
|
||||
|
||||
Reference in New Issue
Block a user