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:
@@ -7,12 +7,11 @@ import { toast } from "sonner";
|
||||
import { AppShell } from "@/components/app-shell";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
useFeedbackList,
|
||||
useResolveFeedback,
|
||||
useCreateFeedback,
|
||||
CATEGORY_LABELS,
|
||||
CATEGORY_COLORS,
|
||||
BLOCK_LABELS,
|
||||
type ChairFeedback,
|
||||
type FeedbackCategory,
|
||||
@@ -24,6 +23,16 @@ import {
|
||||
* "טרם יושמו" וקטגוריה, וסימון כל הערה כיושמה. מוזן מ-/api/feedback.
|
||||
*/
|
||||
|
||||
// category chip styling per mockup 06 (.c-missing / .c-tone / .c-struct / .c-fact / .c-style)
|
||||
const CAT_CHIP: Record<FeedbackCategory, string> = {
|
||||
missing_content: "bg-warn-bg text-warn",
|
||||
wrong_tone: "bg-info-bg text-info",
|
||||
wrong_structure: "bg-gold-wash text-gold-deep border border-rule",
|
||||
factual_error: "bg-danger-bg text-danger",
|
||||
style: "bg-rule-soft text-ink-soft",
|
||||
other: "bg-rule-soft text-ink-soft",
|
||||
};
|
||||
|
||||
function formatDate(iso?: string | null): string {
|
||||
if (!iso) return "";
|
||||
try {
|
||||
@@ -57,47 +66,51 @@ function FeedbackCard({ fb }: { fb: ChairFeedback }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={`bg-surface border-rule shadow-sm ${fb.resolved ? "opacity-60" : ""}`}>
|
||||
<CardContent className="px-5 py-4 space-y-2.5">
|
||||
<div className="flex items-start gap-2 flex-wrap">
|
||||
<Badge variant="outline" className={`text-[0.7rem] ${CATEGORY_COLORS[fb.category]}`}>
|
||||
<Card className={`bg-surface border-rule shadow-sm ${fb.resolved ? "opacity-[0.78]" : ""}`}>
|
||||
<CardContent className="px-[18px] py-4 space-y-2.5">
|
||||
{/* meta row — where · category chip · when (mockup 06 .meta) */}
|
||||
<div className="flex items-center gap-2.5 flex-wrap">
|
||||
<span className="font-semibold text-navy text-[0.84rem]">
|
||||
{fb.case_number ? (
|
||||
<Link
|
||||
href={`/cases/${encodeURIComponent(fb.case_number)}`}
|
||||
className="hover:text-gold-deep"
|
||||
>
|
||||
ערר {fb.case_number}
|
||||
</Link>
|
||||
) : (
|
||||
"ללא תיק"
|
||||
)}
|
||||
<span className="text-ink-muted font-normal"> · {BLOCK_LABELS[fb.block_id] ?? fb.block_id}</span>
|
||||
</span>
|
||||
<span
|
||||
className={`inline-block rounded-full px-2.5 py-0.5 text-[0.72rem] font-semibold whitespace-nowrap ${CAT_CHIP[fb.category]}`}
|
||||
>
|
||||
{CATEGORY_LABELS[fb.category]}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-[0.7rem]">
|
||||
{BLOCK_LABELS[fb.block_id] ?? fb.block_id}
|
||||
</Badge>
|
||||
{fb.case_number ? (
|
||||
<Link
|
||||
href={`/cases/${encodeURIComponent(fb.case_number)}`}
|
||||
className="text-[0.72rem] text-gold-deep hover:underline"
|
||||
>
|
||||
תיק {fb.case_number}
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-[0.72rem] text-ink-muted">ללא תיק</span>
|
||||
)}
|
||||
<span className="ms-auto text-[0.72rem] text-ink-muted">
|
||||
</span>
|
||||
<span className="ms-auto text-[0.72rem] text-ink-muted whitespace-nowrap">
|
||||
{formatDate(fb.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-navy text-sm leading-relaxed m-0 whitespace-pre-wrap" dir="rtl">
|
||||
<p className="text-ink-soft text-sm leading-relaxed m-0 whitespace-pre-wrap" dir="rtl">
|
||||
{fb.feedback_text}
|
||||
</p>
|
||||
|
||||
{fb.lesson_extracted ? (
|
||||
<div className="rounded-md bg-gold-wash/40 border-s-[3px] border-gold ps-3 pe-3 py-2">
|
||||
<div className="text-[0.68rem] text-gold-deep mb-0.5">לקח שהופק</div>
|
||||
<p className="text-ink-soft text-[0.82rem] leading-relaxed m-0 whitespace-pre-wrap italic" dir="rtl">
|
||||
{fb.lesson_extracted}
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="text-[0.82rem] text-ink-muted italic leading-relaxed m-0 whitespace-pre-wrap border-s-[3px] border-gold ps-2.5"
|
||||
dir="rtl"
|
||||
>
|
||||
לקח שחולץ: {fb.lesson_extracted}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="flex items-center justify-end pt-1 border-t border-rule-soft">
|
||||
{/* action row — gold CTA / applied pill (mockup 06 .actrow) */}
|
||||
<div className="flex items-center justify-start pt-1">
|
||||
{fb.resolved ? (
|
||||
<span className="flex items-center gap-1 text-[0.78rem] text-emerald-700">
|
||||
<CheckCircle2 className="w-3.5 h-3.5" /> יושמה
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full bg-success-bg text-success text-[0.76rem] font-semibold px-3 py-1">
|
||||
<CheckCircle2 className="w-3.5 h-3.5" /> יושם
|
||||
</span>
|
||||
) : (
|
||||
<Button
|
||||
@@ -118,6 +131,118 @@ function FeedbackCard({ fb }: { fb: ChairFeedback }) {
|
||||
|
||||
type CatFilter = "all" | FeedbackCategory;
|
||||
|
||||
const BLOCK_OPTIONS = [
|
||||
"block-vav",
|
||||
"block-zayin",
|
||||
"block-chet",
|
||||
"block-tet",
|
||||
"block-yod",
|
||||
"block-yod-alef",
|
||||
];
|
||||
|
||||
function AddFeedbackForm() {
|
||||
const create = useCreateFeedback();
|
||||
const [caseNumber, setCaseNumber] = useState("");
|
||||
const [blockId, setBlockId] = useState("block-yod");
|
||||
const [category, setCategory] = useState<FeedbackCategory>("missing_content");
|
||||
const [text, setText] = useState("");
|
||||
|
||||
const onSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!text.trim()) {
|
||||
toast.error("יש להזין את תוכן ההערה");
|
||||
return;
|
||||
}
|
||||
create.mutate(
|
||||
{
|
||||
case_number: caseNumber.trim() || undefined,
|
||||
block_id: blockId,
|
||||
category,
|
||||
feedback_text: text.trim(),
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success("ההערה נשמרה");
|
||||
setCaseNumber("");
|
||||
setText("");
|
||||
},
|
||||
onError: (err) =>
|
||||
toast.error(err instanceof Error ? err.message : "שגיאה בשמירה"),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const fieldCls =
|
||||
"w-full text-[0.84rem] text-ink bg-parchment border border-rule rounded-md px-3 py-2";
|
||||
const labelCls = "block text-[0.76rem] text-ink-muted font-medium mt-2.5 mb-1";
|
||||
|
||||
return (
|
||||
<Card className="bg-surface border-rule shadow-sm lg:sticky lg:top-6">
|
||||
<CardContent className="px-5 py-4">
|
||||
<h3 className="text-navy text-base font-semibold mb-3.5">הוסף הערה</h3>
|
||||
<form onSubmit={onSubmit}>
|
||||
<label className={labelCls} htmlFor="fb-case">מספר ערר</label>
|
||||
<input
|
||||
id="fb-case"
|
||||
type="text"
|
||||
value={caseNumber}
|
||||
onChange={(e) => setCaseNumber(e.target.value)}
|
||||
placeholder="לדוגמה: 1126-08-25"
|
||||
className={fieldCls}
|
||||
dir="rtl"
|
||||
/>
|
||||
|
||||
<label className={labelCls} htmlFor="fb-block">בלוק</label>
|
||||
<select
|
||||
id="fb-block"
|
||||
value={blockId}
|
||||
onChange={(e) => setBlockId(e.target.value)}
|
||||
className={`${fieldCls} cursor-pointer`}
|
||||
>
|
||||
{BLOCK_OPTIONS.map((b) => (
|
||||
<option key={b} value={b}>
|
||||
{BLOCK_LABELS[b]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<label className={labelCls} htmlFor="fb-cat">קטגוריה</label>
|
||||
<select
|
||||
id="fb-cat"
|
||||
value={category}
|
||||
onChange={(e) => setCategory(e.target.value as FeedbackCategory)}
|
||||
className={`${fieldCls} cursor-pointer`}
|
||||
>
|
||||
{(Object.keys(CATEGORY_LABELS) as FeedbackCategory[]).map((c) => (
|
||||
<option key={c} value={c}>
|
||||
{CATEGORY_LABELS[c]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<label className={labelCls} htmlFor="fb-text">תוכן ההערה</label>
|
||||
<textarea
|
||||
id="fb-text"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
placeholder="מה צריך לתקן ולמה…"
|
||||
className={`${fieldCls} min-h-[90px] resize-y leading-relaxed`}
|
||||
dir="rtl"
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={create.isPending}
|
||||
className="w-full mt-4 bg-gold text-white hover:bg-gold-deep border-transparent"
|
||||
>
|
||||
{create.isPending ? "שומר…" : "שמור הערה"}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FeedbackPage() {
|
||||
const [unresolvedOnly, setUnresolvedOnly] = useState(true);
|
||||
const [category, setCategory] = useState<CatFilter>("all");
|
||||
@@ -159,14 +284,19 @@ export default function FeedbackPage() {
|
||||
<div className="text-[0.75rem] uppercase tracking-[0.12em] text-gold-deep">
|
||||
שערים אנושיים · יו״ר הוועדה
|
||||
</div>
|
||||
<h1 className="text-navy mb-0">הערות יו״ר</h1>
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<h1 className="text-navy mb-0">הערות יו״ר</h1>
|
||||
<span className="inline-block rounded-full bg-gold-wash border border-rule text-ink-muted text-[0.76rem] px-3 py-0.5">
|
||||
נגיש גם מתוך מרכז האישורים
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-ink-muted text-sm mt-1 max-w-2xl leading-relaxed">
|
||||
כל ההערות שנרשמו על טיוטות — מכל התיקים. סמן כל הערה כיושמה
|
||||
לאחר שהלקח הוטמע.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-end">
|
||||
<div className="text-3xl font-semibold text-navy leading-none">
|
||||
<div className="text-3xl font-semibold text-navy leading-none tabular-nums">
|
||||
{unresolvedCount}
|
||||
</div>
|
||||
<div className="text-[0.72rem] uppercase tracking-[0.08em] text-ink-muted mt-1">
|
||||
@@ -178,15 +308,35 @@ export default function FeedbackPage() {
|
||||
|
||||
<div className="h-[2px] bg-gradient-to-l from-transparent via-gold to-transparent" />
|
||||
|
||||
{/* Filters */}
|
||||
{/* category filter chips (mockup 06 .chips) + resolved-state toggle */}
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{categories.map((c) => {
|
||||
const on = category === c.key;
|
||||
return (
|
||||
<button
|
||||
key={c.key}
|
||||
type="button"
|
||||
onClick={() => setCategory(c.key)}
|
||||
className={`text-[0.8rem] font-medium px-3.5 py-1.5 rounded-full border transition-colors ${
|
||||
on
|
||||
? "bg-navy text-white border-navy"
|
||||
: "bg-surface text-ink-soft border-rule hover:bg-rule-soft"
|
||||
}`}
|
||||
>
|
||||
{c.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 ms-auto">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setUnresolvedOnly(true)}
|
||||
className={`text-[0.78rem] px-3 py-1.5 rounded border transition-colors ${
|
||||
unresolvedOnly
|
||||
? "bg-navy text-parchment border-navy"
|
||||
? "bg-navy text-white border-navy"
|
||||
: "bg-surface text-ink-muted border-rule hover:bg-rule-soft"
|
||||
}`}
|
||||
>
|
||||
@@ -197,60 +347,50 @@ export default function FeedbackPage() {
|
||||
onClick={() => setUnresolvedOnly(false)}
|
||||
className={`text-[0.78rem] px-3 py-1.5 rounded border transition-colors ${
|
||||
!unresolvedOnly
|
||||
? "bg-navy text-parchment border-navy"
|
||||
? "bg-navy text-white border-navy"
|
||||
: "bg-surface text-ink-muted border-rule hover:bg-rule-soft"
|
||||
}`}
|
||||
>
|
||||
הכל
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 flex-wrap ms-auto">
|
||||
{categories.map((c) => (
|
||||
<button
|
||||
key={c.key}
|
||||
type="button"
|
||||
onClick={() => setCategory(c.key)}
|
||||
className={`text-[0.74rem] px-2.5 py-1 rounded border transition-colors ${
|
||||
category === c.key
|
||||
? "bg-gold-wash text-gold-deep border-gold/40"
|
||||
: "bg-surface text-ink-muted border-rule hover:bg-rule-soft"
|
||||
}`}
|
||||
>
|
||||
{c.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<Card className="bg-surface border-rule">
|
||||
<CardContent className="px-6 py-5 text-ink-muted text-sm">
|
||||
שגיאה בטעינת ההערות. נסה לרענן.
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : isPending ? (
|
||||
<div className="space-y-3">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<Card key={i} className="bg-surface border-rule shadow-sm">
|
||||
<CardContent className="px-5 py-4 h-28 animate-pulse" />
|
||||
{/* two-column body — feedback list + sticky add-form rail (mockup 06 .wrap grid) */}
|
||||
<div className="grid gap-6 lg:grid-cols-[1fr_340px] items-start">
|
||||
<div>
|
||||
{error ? (
|
||||
<Card className="bg-surface border-rule">
|
||||
<CardContent className="px-6 py-5 text-ink-muted text-sm">
|
||||
שגיאה בטעינת ההערות. נסה לרענן.
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : items.length === 0 ? (
|
||||
<div className="text-center text-ink-muted py-16">
|
||||
<p className="text-lg">אין הערות בקטגוריה זו.</p>
|
||||
{unresolvedOnly && (
|
||||
<p className="text-sm mt-2">כל ההערות יושמו ✓</p>
|
||||
) : isPending ? (
|
||||
<div className="space-y-3.5">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<Card key={i} className="bg-surface border-rule shadow-sm">
|
||||
<CardContent className="px-[18px] py-4 h-28 animate-pulse" />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : items.length === 0 ? (
|
||||
<div className="text-center text-ink-muted py-16">
|
||||
<p className="text-lg">אין הערות בקטגוריה זו.</p>
|
||||
{unresolvedOnly && (
|
||||
<p className="text-sm mt-2">כל ההערות יושמו ✓</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3.5">
|
||||
{items.map((fb) => (
|
||||
<FeedbackCard key={fb.id} fb={fb} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{items.map((fb) => (
|
||||
<FeedbackCard key={fb.id} fb={fb} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AddFeedbackForm />
|
||||
</div>
|
||||
</section>
|
||||
</AppShell>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user