import { useRef, useState, useMemo } from "react"; import { Link } from "react-router-dom"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AlertTriangle, CheckCircle2, ChevronDown, ChevronLeft, ClipboardCheck, ExternalLink, Layers, Pencil, Plus, Search, ShieldAlert, Trash2, Wrench, X, } from "lucide-react"; import { createQcTemplate, deleteQcQuestion, deleteQcTemplate, getDefects, getQcTemplates, replaceQcTemplateQuestions, resolveDefect, updateQcTemplate, } from "../lib/api"; import type { Defect, QcPhase, QcQuestionInput, QcTemplate, QcTemplateQuestion } from "../lib/api"; import { Card, CardSkeleton, Chip, EmptyState, ErrorState, Modal, PageHeader, PanelsMenu, btnPrimary, btnSecondary, inputCls, ConfirmSheet, apiErrorToast, } from "../components/ui"; import { defectStatusMeta, formatDate, phaseTone, qcPhases, relativeTime, severityMeta } from "../lib/format"; import { useAuthStore } from "../store/auth"; import { Perm, hasPermission } from "../lib/roles"; // ---------------------------------------- // شناسه یکتای پایدار برای ردیف‌های ویرایشگر // ---------------------------------------- let keyCounter = 0; function newRowKey(): string { keyCounter += 1; if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") { return crypto.randomUUID(); } return `row-${keyCounter}-${Date.now()}`; } interface DraftQuestion { key: string; phase: QcPhase; text: string; required: boolean; } function draftFrom(questions: QcTemplateQuestion[]): DraftQuestion[] { return questions.map((q) => ({ key: newRowKey(), phase: q.phase, text: q.text, required: q.required, })); } function toPayload(rows: DraftQuestion[]): QcQuestionInput[] { const out: QcQuestionInput[] = []; for (const phase of qcPhases) { const inPhase = rows.filter((r) => r.phase === phase.value && r.text.trim()); inPhase.forEach((r, index) => { out.push({ phase: r.phase, text: r.text.trim(), required: r.required, sort_order: index }); }); } return out; } // ---------------------------------------- // ویرایشگر قالب QC // ---------------------------------------- function QcTemplateEditor({ template, onClose, }: { template: QcTemplate | null; onClose: () => void; }) { const queryClient = useQueryClient(); const [name, setName] = useState(() => template?.name ?? ""); const [activityType, setActivityType] = useState(() => template?.activity_type ?? ""); const [description, setDescription] = useState(() => template?.description ?? ""); const [rows, setRows] = useState(() => { const initial = template ? draftFrom(template.questions) : []; return initial.length > 0 ? initial : [{ key: newRowKey(), phase: "pre", text: "", required: true }]; }); const [formError, setFormError] = useState(null); const patchRow = (key: string, patch: Partial) => { setRows((prev) => prev.map((row) => (row.key === key ? { ...row, ...patch } : row))); }; const addRow = (phase: QcPhase = "pre") => { setRows((prev) => [...prev, { key: newRowKey(), phase, text: "", required: true }]); }; const removeRow = (key: string) => { setRows((prev) => prev.filter((row) => row.key !== key)); }; const invalidate = () => void queryClient.invalidateQueries({ queryKey: ["qc-templates"] }); const createMutation = useMutation({ mutationFn: (payload: { name: string; activity_type: string; description: string; questions: QcQuestionInput[] }) => createQcTemplate(payload), onSuccess: () => { invalidate(); onClose(); }, onError: (err) => apiErrorToast(err, "ایجاد قالب ناموفق بود"), }); const updateMutation = useMutation({ mutationFn: async (payload: { name: string; activity_type: string; description: string; questions: QcQuestionInput[]; }) => { if (!template) return; await updateQcTemplate(template.id, { name: payload.name, activity_type: payload.activity_type, description: payload.description, }); await replaceQcTemplateQuestions(template.id, payload.questions); }, onSuccess: () => { invalidate(); onClose(); }, onError: (err) => apiErrorToast(err, "ذخیره قالب ناموفق بود"), }); const pending = createMutation.isPending || updateMutation.isPending; const submit = () => { setFormError(null); const trimmedName = name.trim(); if (!trimmedName) { setFormError("نام قالب را وارد کنید"); return; } const questions = toPayload(rows); if (questions.length === 0) { setFormError("حداقل متن یک سؤال را وارد کنید"); return; } const payload = { name: trimmedName, activity_type: activityType.trim(), description: description.trim(), questions, }; if (template) updateMutation.mutate(payload); else createMutation.mutate(payload); }; const grouped = qcPhases.map((phase) => ({ phase: phase.value as QcPhase, label: phase.label, rows: rows.filter((r) => r.phase === phase.value), })); return ( setName(e.target.value)} className={inputCls} placeholder="مثال: کنترل کیفیت اجرای فونداسیون" /> setActivityType(e.target.value)} className={inputCls} placeholder="مثلاً: سازه، تأسیسات، نازک‌کاری" />