feat(plans): כפתור "משוך מ-מנהל-התכנון" בטופס-התכנית (Phase C טריגר 1)
טריגר 1 הידני: בטופס PlanForm, כפתור "משוך מ-מנהל-התכנון" — היו"ר מקליד מספר-תכנית, לוחץ, והשדות (שם/תאריך-רשומות/י"פ/סוג/ייעוד) מתמלאים מ-mavat דרך POST /api/plans/fetch (#292). היו"ר בודק ושומר — שער-היו"ר נשמר (שום שמירה אוטומטית). - plans.ts: useFetchPlan + PlanFetchResult. - PlanForm: כפתור עם spinner (~דקה, דפדפן חי), מילוי-שדות (מחליף בערך-mavat היכן שקיים, שומר ערך-יו"ר היכן ש-mavat ריק), קישור-מקור "מקור: מנהל-התכנון" בתצוגה-המקדימה (פרובננס INV-AH). עבר שער-עיצוב (מוקאפ 22-plans-review מאושר). ההוק ידני (לא תלוי types שנוצרים). tsc ✅ lint ✅ (0 errors). INV-AH: source_url מוצג; שדה-חסר ריק לא מומצא. G10: מילוי-טופס בלבד, שמירה דרך plan_upsert הקיים. G2: צורך את /api/plans/fetch (#292). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
Check, X, Edit2, AlertTriangle, Plus, GitMerge, Save, Search, Undo2, BadgeCheck,
|
||||
DownloadCloud, Loader2, ExternalLink,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -13,7 +14,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
usePlansAll, usePlanDuplicates, useUpsertPlan, useUpdatePlan,
|
||||
useReviewPlan, useMergePlans, type Plan, type PlanEdit,
|
||||
useReviewPlan, useMergePlans, useFetchPlan, type Plan, type PlanEdit,
|
||||
} from "@/lib/api/plans";
|
||||
|
||||
/* Strip bidi marks (mirror of the halacha panel's cleanCitation). */
|
||||
@@ -401,14 +402,47 @@ function PlanForm({
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const [f, setF] = useState<EditForm>(initial);
|
||||
const [sourceUrl, setSourceUrl] = useState("");
|
||||
const upsert = useUpsertPlan();
|
||||
const update = useUpdatePlan();
|
||||
const fetchMavat = useFetchPlan();
|
||||
const busy = upsert.isPending || update.isPending;
|
||||
|
||||
function set<K extends keyof EditForm>(k: K, v: string) {
|
||||
setF((prev) => ({ ...prev, [k]: v }));
|
||||
}
|
||||
|
||||
// Pull identity + validity from the official source (mavat) into the fields.
|
||||
// Fills each field mavat returns (keeps a chair value only where mavat is
|
||||
// empty); the chair still reviews + saves. Slow — a real browser on the host.
|
||||
async function pullFromMavat() {
|
||||
const num = f.plan_number.trim();
|
||||
if (!num) {
|
||||
toast.error("הקלד מספר-תכנית למשיכה");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const r = await fetchMavat.mutateAsync(num);
|
||||
setF((prev) => ({
|
||||
plan_number: r.plan_number || prev.plan_number,
|
||||
display_name: r.display_name || prev.display_name,
|
||||
plan_type: r.plan_type || prev.plan_type,
|
||||
gazette_date: r.gazette_date || prev.gazette_date,
|
||||
yalkut_number: r.yalkut_number || prev.yalkut_number,
|
||||
purpose: r.purpose || prev.purpose,
|
||||
}));
|
||||
setSourceUrl(r.source_url || "");
|
||||
toast.success(
|
||||
r.yalkut_number || r.gazette_date
|
||||
? "נמשך ממנהל-התכנון — בדוק ואשר"
|
||||
: "נמצא במנהל-התכנון אך ללא תוקף מפורסם — השלם ידנית",
|
||||
);
|
||||
} catch (e) {
|
||||
const msg = (e as { body?: { detail?: string } })?.body?.detail;
|
||||
toast.error(msg || "שגיאה במשיכה ממנהל-התכנון");
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (!f.plan_number.trim()) {
|
||||
toast.error("חובה מספר-תכנית");
|
||||
@@ -455,6 +489,28 @@ function PlanForm({
|
||||
<h3 className="text-navy text-sm font-bold m-0">{title}</h3>
|
||||
<p className="text-ink-muted text-xs mt-0.5">{subtitle}</p>
|
||||
</div>
|
||||
|
||||
{/* pull validity from the official source (mavat) into the fields below */}
|
||||
<div className="flex items-center gap-3 flex-wrap rounded-md border border-[#cdd9e8] bg-info-bg px-3 py-2.5">
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-navy text-white hover:bg-navy-soft shrink-0"
|
||||
disabled={fetchMavat.isPending}
|
||||
onClick={pullFromMavat}
|
||||
>
|
||||
{fetchMavat.isPending ? (
|
||||
<><Loader2 className="size-4 animate-spin" /> מושך…</>
|
||||
) : (
|
||||
<><DownloadCloud className="size-4" /> משוך מ-מנהל-התכנון</>
|
||||
)}
|
||||
</Button>
|
||||
<span className="text-[0.72rem] text-ink-soft leading-relaxed flex-1 min-w-[220px]">
|
||||
{fetchMavat.isPending
|
||||
? "מושך ממנהל-התכנון — עד כדקה (דפדפן חי)…"
|
||||
: 'הקלד מספר-תכנית ולחץ — שם, תאריך-רשומות, מס\' ילקוט (י"פ), סוג וייעוד יימשכו מהמקור הרשמי. שדה שהמקור אינו חושף יישאר ריק — לא מומצא.'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{field("plan_number", "מספר תכנית", 'מזהה בלבד (ללא המילה "תכנית")')}
|
||||
{field("display_name", "שם תצוגה")}
|
||||
@@ -468,6 +524,14 @@ function PlanForm({
|
||||
תצוגה מקדימה — כך ייכתב בבלוק ט:
|
||||
</span>
|
||||
<span className="text-[0.88rem] leading-relaxed">{previewCitation(f)}</span>
|
||||
{sourceUrl && (
|
||||
<a
|
||||
href={sourceUrl} target="_blank" rel="noopener noreferrer"
|
||||
className="mt-2 inline-flex items-center gap-1 text-[0.7rem] text-[#365070] hover:underline"
|
||||
>
|
||||
<ExternalLink className="size-3" /> מקור: מנהל-התכנון
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 justify-end">
|
||||
<Button variant="ghost" size="sm" className="me-auto text-ink-muted" onClick={onClose}>
|
||||
|
||||
Reference in New Issue
Block a user