import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { AlertTriangle, Camera, Check, CheckCircle2, ClipboardCheck, FileDown, Minus, Plus, ShieldAlert, ShieldCheck, Trash2, X, } from "lucide-react"; import { addTaskQcQuestion, createDefect, createTaskQcDefinition, deleteTaskQcQuestion, getQcTemplates, getTaskQc, putTaskQcResult, resolveDefect, uploadFile, } from "../lib/api"; import type { AnswerValue, Defect, QcPhase, TaskDetail, TaskQcQuestion } from "../lib/api"; import { Chip, ProgressBar, Modal, btnPrimary, btnSecondary, inputCls, Combobox } from "./ui"; import { defectStatusMeta, relativeTime, severityMeta, severityOptions } from "../lib/format"; interface TaskQcSectionProps { task: TaskDetail; canExecuteQc: boolean; canDefineQc: boolean; } export function TaskQcSection({ task, canExecuteQc, canDefineQc }: TaskQcSectionProps) { const taskId = String(task.id || task.op_wp_id || ""); const queryClient = useQueryClient(); const [activePhase, setActivePhase] = useState("pre"); const [showTemplateModal, setShowTemplateModal] = useState(false); const [selectedTemplateId, setSelectedTemplateId] = useState(""); const [showAddQuestionModal, setShowAddQuestionModal] = useState(false); const [defectTargetQuestion, setDefectTargetQuestion] = useState(null); const [defectToResolve, setDefectToResolve] = useState(null); const [resolutionNotes, setResolutionNotes] = useState(""); // Load Task QC Data const qcQuery = useQuery({ queryKey: ["task-qc", taskId], queryFn: () => getTaskQc(taskId), }); const templatesQuery = useQuery({ queryKey: ["qc-templates"], queryFn: getQcTemplates, enabled: showTemplateModal, }); const qcState = qcQuery.data; const readiness = qcState?.readiness; const definitions = qcState?.definitions ?? []; const questions = definitions.flatMap((d) => d.questions); const selectedTemplate = (templatesQuery.data ?? []).find((t) => t.id === selectedTemplateId) ?? null; // Mutations const invalidateQc = () => { void queryClient.invalidateQueries({ queryKey: ["task-qc", taskId] }); void queryClient.invalidateQueries({ queryKey: ["task", taskId] }); void queryClient.invalidateQueries({ queryKey: ["tasks"] }); void queryClient.invalidateQueries({ queryKey: ["today"] }); }; const answerMutation = useMutation({ mutationFn: ({ questionId, answer, photoId }: { questionId: string; answer: AnswerValue | null; photoId?: string | null }) => putTaskQcResult(questionId, { answer, photo_id: photoId }), onSuccess: (_, variables) => { invalidateQc(); if (variables.answer === null) { toast.success("انتخاب بازرسی لغو شد"); } else { toast.success("نتیجه بازرسی ثبت شد"); } }, onError: (err: any) => { toast.error(err?.message || "خطا در ثبت نتیجه بازرسی"); }, }); const handleToggleAnswer = (questionId: string, currentAnswer: string | null | undefined, newAnswer: AnswerValue) => { if (!canExecuteQc || answerMutation.isPending) return; if (currentAnswer === newAnswer) { // کلیک مجدد: لغو انتخاب قبلی answerMutation.mutate({ questionId, answer: null }); } else { answerMutation.mutate({ questionId, answer: newAnswer }); } }; const applyTemplateMutation = useMutation({ mutationFn: (templateId: string) => createTaskQcDefinition(taskId, { template_id: templateId }), onSuccess: () => { invalidateQc(); setShowTemplateModal(false); setSelectedTemplateId(""); toast.success("چک‌لیست از قالب بارگذاری شد"); }, onError: (err: any) => { toast.error(err?.message || "خطا در بارگذاری قالب"); }, }); const addQuestionMutation = useMutation({ mutationFn: async ({ text, phase, required }: { text: string; phase: QcPhase; required: boolean }) => { let definitionId = definitions[0]?.id; if (!definitionId) { const newDef = await createTaskQcDefinition(taskId, { name: "چک‌لیست کنترل کیفیت" }); definitionId = newDef.id; } return addTaskQcQuestion(definitionId, { text, phase, required }); }, onSuccess: () => { invalidateQc(); setShowAddQuestionModal(false); toast.success("سؤال جدید به چک‌لیست اضافه شد"); }, onError: (err: any) => { toast.error(err?.message || "خطا در افزودن سؤال"); }, }); const deleteQuestionMutation = useMutation({ mutationFn: (questionId: string) => deleteTaskQcQuestion(questionId), onSuccess: () => { invalidateQc(); toast.success("سؤال با موفقیت حذف شد"); }, onError: (err: any) => { toast.error(err?.message || "خطا در حذف سؤال"); }, }); const resolveDefectMutation = useMutation({ mutationFn: ({ defectId, notes }: { defectId: string; notes: string }) => resolveDefect(defectId, notes), onSuccess: () => { invalidateQc(); setDefectToResolve(null); setResolutionNotes(""); toast.success("عیب به عنوان رفع‌شده ثبت گردید"); }, onError: (err: any) => { toast.error(err?.message || "خطا در رفع عیب"); }, }); // Calculate Progress const totalQuestions = readiness?.all.total ?? questions.length; const pendingCount = readiness?.all.pending ?? 0; const answeredCount = totalQuestions - pendingCount; const qcPercent = totalQuestions > 0 ? Math.round((answeredCount / totalQuestions) * 100) : 0; const currentPhaseQuestions = questions.filter((q) => q.phase === activePhase); // Group stats by phase const preStats = readiness?.pre; const execStats = readiness?.exec; return (
{/* 1. بنر وضعیت آمادگی شروع و اتمام کار */} {readiness && (
{!readiness.can_start ? (
شروع اجرای وظیفه نیازمند بازرسی پیش از اجرا است
{readiness.block_reasons.length > 0 && (
    {readiness.block_reasons.map((reason, idx) => (
  • {reason}
  • ))}
)}
) : readiness.all.total > 0 && readiness.all.pending === 0 && readiness.all.failed === 0 ? (
تمام بازرسی‌های کیفی با موفقیت تأیید شدند — پیشرفت ۱۰۰٪ مجاز است
) : (
آمادگی شروع تأیید شده است
{answeredCount} از {totalQuestions} بازرسی انجام شد
)} {/* نوار پیشرفت کلی QC */} {totalQuestions > 0 && (
پیشرفت بازرسی‌های کیفی {qcPercent}٪ ({answeredCount} از {totalQuestions})
)}
)} {/* 2. هدر ابزارهای چک‌لیست و تب‌های ۳ مرحله */}
چک‌لیست بازرسی کیفی
{canDefineQc && ( <> )}
{/* ۳ تب فازها: پیش از اجرا / حین اجرا / پس از اجرا */}
{/* لیست سؤالات فاز انتخاب‌شده */} {currentPhaseQuestions.length === 0 ? (

سؤالی در این مرحله تعریف نشده است.

{canDefineQc && ( )}
) : (
{currentPhaseQuestions.map((q, idx) => { const latestAns = q.result?.answer; const hasFailed = latestAns === "fail"; const isSatisfied = latestAns === "pass" || latestAns === "na"; return (
{idx + 1}

{q.text}

{q.result?.evaluated_by && (

بررسی توسط {q.result.evaluated_by} — {relativeTime(q.result.evaluated_at)}

)}
{canDefineQc && ( )}
{/* دکمه‌های پاسخ فوری ۳ گانه */}
وضعیت بازرسی:
{/* دکمه قبول / Pass */} {/* دکمه رد / Fail */} {/* دکمه مطرح نیست */}
{/* در صورت قبول بودن: اکشن آپلود عکس تأییدیه */} {latestAns === "pass" && (
این مورد تأیید شده است
{q.result?.photo_url && (
)}
)} {/* در صورت مردود بودن: اکشن ثبت عیب */} {hasFailed && (
این مورد مردود شده است
)}
); })}
)}
{/* 3. لیست عیوب و عدم انطباق‌های وظیفه */} {task.defects && task.defects.length > 0 && (
عدم انطباق‌ها و عیوب ثبت‌شده ({task.defects.length}) {task.open_defects != null && task.open_defects > 0 && ( {task.open_defects} مورد باز )}
{task.defects.map((defect) => { const isResolved = defect.status === "resolved"; const sev = severityMeta(defect.severity); const dst = defectStatusMeta(defect.status); return (
{defect.title}
{dst.label} {sev.label} {relativeTime(defect.created_at)}
{!isResolved && canExecuteQc && ( )}
{defect.corrective_action && (

اقدام اصلاحی: {defect.corrective_action}

)} {defect.resolution_notes && (

توضیح رفع: {defect.resolution_notes}

)}
); })}
)} {/* --- Modal: بارگذاری از قالب سراسری --- */} {showTemplateModal && ( { setShowTemplateModal(false); setSelectedTemplateId(""); }} >

با انتخاب قالب، تمام سؤالات آن در این وظیفه کپی می‌شوند و می‌توانید آزادانه آن‌ها را تغییر دهید.

{templatesQuery.isPending ? (

در حال دریافت قالب‌ها...

) : (templatesQuery.data ?? []).length === 0 ? (

هنوز هیچ قالبی در سیستم تعریف نشده است.

) : ( ({ value: tpl.id, label: tpl.name, hint: tpl.activity_type ? `${tpl.activity_type} (${tpl.questions.length} سؤال)` : `${tpl.questions.length} سؤال`, }))} /> )}
{selectedTemplate && (
{selectedTemplate.name} {selectedTemplate.questions.length} سؤال
{selectedTemplate.activity_type && (

نوع فعالیت: {selectedTemplate.activity_type}

)} {selectedTemplate.description && (

{selectedTemplate.description}

)}
پیش از اجرا: {selectedTemplate.questions.filter((q) => q.phase === "pre").length} حین اجرا: {selectedTemplate.questions.filter((q) => q.phase === "during").length} پس از اجرا: {selectedTemplate.questions.filter((q) => q.phase === "after").length}
)}
)} {/* --- Modal: افزودن سؤال جدید --- */} {showAddQuestionModal && ( setShowAddQuestionModal(false)} onSubmit={(data) => addQuestionMutation.mutate(data)} /> )} {/* --- Modal: ثبت عدم انطباق (عیب) --- */} {defectTargetQuestion && ( setDefectTargetQuestion(null)} onCreated={() => { setDefectTargetQuestion(null); invalidateQc(); }} /> )} {/* --- Modal: رفع عیب --- */} {defectToResolve && ( setDefectToResolve(null)}>

آیا عیب «{defectToResolve.title}» برطرف شده است؟