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:
@@ -1,95 +1,64 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Plug, HardDrive, Database, FileText } from "lucide-react";
|
||||
import { AppShell } from "@/components/app-shell";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { useSkills, type Skill } from "@/lib/api/skills";
|
||||
|
||||
function formatSize(bytes: number | null) {
|
||||
if (bytes == null) return "—";
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
function formatChars(skill: Skill): string {
|
||||
// Mockup column = "גודל (תווים)" — the DB markdown char count, grouped.
|
||||
const n = skill.db_markdown_chars ?? 0;
|
||||
return n.toLocaleString("en-US");
|
||||
}
|
||||
|
||||
function StatusDot({ tone }: { tone: string }) {
|
||||
return <span className={`h-1.5 w-1.5 rounded-full ${tone}`} aria-hidden />;
|
||||
function formatUpdated(iso: string | null): string {
|
||||
if (!iso) return "—";
|
||||
try {
|
||||
// ISO-style date to match the mockup (2026-06-09), tabular.
|
||||
return new Date(iso).toISOString().slice(0, 10);
|
||||
} catch {
|
||||
return "—";
|
||||
}
|
||||
}
|
||||
|
||||
function statusBadge(s: Skill) {
|
||||
if (s.not_in_db) {
|
||||
return (
|
||||
<Badge variant="outline" className="gap-1.5 bg-warn-bg text-warn border-warn/40">
|
||||
<StatusDot tone="bg-warn" />לא סונכרן
|
||||
</Badge>
|
||||
);
|
||||
/**
|
||||
* Sync chip — colored dot + label, faithful to mockup 14:
|
||||
* - מסונכרן (success) when present in DB + on disk
|
||||
* - DB בלבד (info) when in DB but no disk copy
|
||||
* - לא מסונכרן (warn) when missing from the DB
|
||||
*/
|
||||
function SyncChip({ skill }: { skill: Skill }) {
|
||||
let tone: { wrap: string; dot: string };
|
||||
let label: string;
|
||||
if (skill.not_in_db) {
|
||||
tone = { wrap: "bg-warn-bg text-warn", dot: "bg-warn" };
|
||||
label = "לא מסונכרן";
|
||||
} else if (skill.db_markdown_chars > 0 && skill.disk_exists) {
|
||||
tone = { wrap: "bg-success-bg text-success", dot: "bg-success" };
|
||||
label = "מסונכרן";
|
||||
} else if (skill.db_markdown_chars > 0) {
|
||||
tone = { wrap: "bg-info-bg text-info", dot: "bg-info" };
|
||||
label = "DB בלבד";
|
||||
} else {
|
||||
tone = { wrap: "bg-rule-soft text-ink-muted", dot: "bg-ink-muted" };
|
||||
label = "לא ידוע";
|
||||
}
|
||||
if (s.db_markdown_chars > 0 && s.disk_exists) {
|
||||
return (
|
||||
<Badge variant="outline" className="gap-1.5 bg-success-bg text-success border-success/40">
|
||||
<StatusDot tone="bg-success" />מסונכרן
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
if (s.db_markdown_chars > 0) {
|
||||
return (
|
||||
<Badge variant="outline" className="gap-1.5 bg-info-bg text-info border-info/40">
|
||||
<StatusDot tone="bg-info" />DB בלבד
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
return <Badge variant="outline">לא ידוע</Badge>;
|
||||
}
|
||||
|
||||
function SkillCard({ skill }: { skill: Skill }) {
|
||||
const fileCount = skill.file_inventory?.length ?? 0;
|
||||
return (
|
||||
<Card className="bg-surface border-rule shadow-sm hover:shadow-md transition-shadow">
|
||||
<CardContent className="px-5 py-4">
|
||||
<div className="flex items-start justify-between gap-3 mb-2">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Plug className="w-4 h-4 text-gold-deep shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<h3 className="text-navy font-semibold text-base mb-0 truncate">
|
||||
{skill.name || skill.slug}
|
||||
</h3>
|
||||
<code className="text-[0.72rem] text-ink-muted tabular-nums">
|
||||
{skill.slug}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
{statusBadge(skill)}
|
||||
</div>
|
||||
<dl className="grid grid-cols-3 gap-2 text-[0.72rem] text-ink-muted mt-3">
|
||||
<div className="flex items-center gap-1">
|
||||
<FileText className="w-3 h-3" />
|
||||
<span className="tabular-nums">{fileCount}</span>
|
||||
<span>קבצים</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Database className="w-3 h-3" />
|
||||
<span className="tabular-nums">
|
||||
{(skill.db_markdown_chars / 1000).toFixed(1)}K
|
||||
</span>
|
||||
<span>תווים</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<HardDrive className="w-3 h-3" />
|
||||
<span className="tabular-nums">
|
||||
{formatSize(skill.disk_skill_md_bytes)}
|
||||
</span>
|
||||
</div>
|
||||
</dl>
|
||||
{skill.updated_at && (
|
||||
<p className="text-[0.7rem] text-ink-light mt-2">
|
||||
עודכן: {new Date(skill.updated_at).toLocaleDateString("he-IL")}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<span
|
||||
className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-[3px] text-[0.75rem] font-semibold ${tone.wrap}`}
|
||||
>
|
||||
<span className={`h-1.5 w-1.5 rounded-full ${tone.dot}`} aria-hidden />
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,9 +74,9 @@ export default function SkillsPage() {
|
||||
<span aria-hidden> · </span>
|
||||
<span className="text-navy">מיומנויות</span>
|
||||
</nav>
|
||||
<h1 className="text-navy mb-0">מיומנויות Paperclip</h1>
|
||||
<h1 className="text-navy mb-0">מיומנויות</h1>
|
||||
<p className="text-ink-muted text-sm mt-1 max-w-2xl">
|
||||
רשימת ה-skills המותקנים במערכת Paperclip ומצב הסנכרון שלהם בין ה-DB
|
||||
סקילים מותקנים בפלטפורמה — שם, גודל, מועד עדכון ומצב הסנכרון בין ה-DB
|
||||
לדיסק.
|
||||
</p>
|
||||
</header>
|
||||
@@ -115,28 +84,69 @@ export default function SkillsPage() {
|
||||
<div className="h-[2px] bg-gradient-to-l from-transparent via-gold to-transparent" />
|
||||
|
||||
{error ? (
|
||||
<Card className="bg-danger-bg border-danger/40">
|
||||
<CardContent className="px-6 py-6 text-center text-danger">
|
||||
{error.message}
|
||||
</CardContent>
|
||||
<Card className="bg-danger-bg border-danger/40 px-6 py-6 text-center text-danger">
|
||||
{error.message}
|
||||
</Card>
|
||||
) : isPending ? (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<Skeleton key={i} className="h-32 w-full rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
<Skeleton className="h-80 w-full rounded-lg" />
|
||||
) : data?.length === 0 ? (
|
||||
<Card className="bg-surface border-rule">
|
||||
<CardContent className="px-6 py-12 text-center text-ink-muted">
|
||||
<div className="text-gold text-3xl mb-2" aria-hidden>❦</div>
|
||||
אין skills מותקנים
|
||||
</CardContent>
|
||||
<Card className="bg-surface border-rule px-6 py-12 text-center text-ink-muted">
|
||||
<div className="text-gold text-3xl mb-2" aria-hidden>❦</div>
|
||||
אין skills מותקנים
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{data?.map((s) => <SkillCard key={s.slug} skill={s} />)}
|
||||
</div>
|
||||
<Card className="bg-surface border-rule shadow-sm overflow-hidden p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-parchment hover:bg-parchment border-rule">
|
||||
<TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3.5">
|
||||
סלאג
|
||||
</TableHead>
|
||||
<TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3.5">
|
||||
שם תצוגה
|
||||
</TableHead>
|
||||
<TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3.5">
|
||||
גודל (תווים)
|
||||
</TableHead>
|
||||
<TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3.5">
|
||||
עודכן
|
||||
</TableHead>
|
||||
<TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3.5">
|
||||
סנכרון
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.map((s) => (
|
||||
<TableRow
|
||||
key={s.slug}
|
||||
className="border-rule-soft hover:bg-gold-wash"
|
||||
>
|
||||
<TableCell className="px-5 py-4">
|
||||
<code
|
||||
className="font-mono text-[0.81rem] font-semibold text-navy"
|
||||
dir="ltr"
|
||||
>
|
||||
{s.slug}
|
||||
</code>
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4 font-semibold text-navy text-[0.9rem]">
|
||||
{s.name || s.slug}
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4 text-ink-soft tabular-nums">
|
||||
{formatChars(s)}
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4 text-ink-muted text-[0.81rem] tabular-nums">
|
||||
{formatUpdated(s.updated_at)}
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4">
|
||||
<SyncChip skill={s} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
)}
|
||||
</section>
|
||||
</AppShell>
|
||||
|
||||
Reference in New Issue
Block a user