"use client"; import { useEffect, useTransition, useState, useRef } from "react"; import { useRouter } from "next/navigation"; import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { Search, X } from "lucide-react"; import { customAlphabet } from "nanoid"; import { parseAsStringEnum, useQueryState } from "nuqs"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { cn } from "@/lib/utils"; import { getFiltersStateParser } from "@/lib/parsers"; import { EVALUATION_TARGET_FILTER_OPTIONS } from "@/lib/evaluation-target-list/validation"; /***************************************************************************************** * UTILS & CONSTANTS *****************************************************************************************/ // nanoid generator (6‑chars [0-9a-zA-Z]) const generateId = customAlphabet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 6); // ── SELECT OPTIONS ────────────────────────────────────────────────────────────────────── const statusOptions = [ { value: "PENDING", label: "대상확정" }, { value: "PENDING_SUBMISSION", label: "자료접수중" }, { value: "SUBMITTED", label: "제출완료" }, { value: "IN_REVIEW", label: "평가중" }, { value: "REVIEW_COMPLETED", label: "평가완료" }, { value: "FINALIZED", label: "결과확정" }, ]; const documentsSubmittedOptions = [ { value: "true", label: "제출완료" }, { value: "false", label: "미제출" }, ]; const gradeOptions = [ { value: "A", label: "A등급" }, { value: "B", label: "B등급" }, { value: "C", label: "C등급" }, { value: "D", label: "D등급" }, ]; /***************************************************************************************** * ZOD SCHEMA & TYPES *****************************************************************************************/ const periodicEvaluationFilterSchema = z.object({ evaluationYear: z.string().optional(), division: z.string().optional(), status: z.string().optional(), domesticForeign: z.string().optional(), materialType: z.string().optional(), vendorCode: z.string().optional(), vendorName: z.string().optional(), documentsSubmitted: z.string().optional(), evaluationGrade: z.string().optional(), finalGrade: z.string().optional(), minTotalScore: z.string().optional(), maxTotalScore: z.string().optional(), }); export type PeriodicEvaluationFilterFormValues = z.infer< typeof periodicEvaluationFilterSchema >; /***************************************************************************************** * COMPONENT *****************************************************************************************/ interface PeriodicEvaluationFilterSheetProps { /** Slide‑over visibility */ isOpen: boolean; /** Close panel handler */ onClose: () => void; /** Show skeleton / spinner while outer data grid fetches */ isLoading?: boolean; /** Optional: fire immediately after URL is patched so parent can refetch */ onFiltersApply: (filters: any[], joinOperator: "and" | "or") => void; // ✅ 필터 전달 콜백 } export function PeriodicEvaluationFilterSheet({ isOpen, onClose, isLoading = false, onFiltersApply, }: PeriodicEvaluationFilterSheetProps) { /** Router (needed only for pathname) */ const router = useRouter(); /** Track pending state while we update URL */ const [isPending, startTransition] = useTransition(); const [joinOperator, setJoinOperator] = useState<"and" | "or">("and") /** React‑Hook‑Form */ const form = useForm({ resolver: zodResolver(periodicEvaluationFilterSchema), defaultValues: { evaluationYear: new Date().getFullYear().toString(), division: "", status: "", domesticForeign: "", materialType: "", vendorCode: "", vendorName: "", documentsSubmitted: "", evaluationGrade: "", finalGrade: "", minTotalScore: "", maxTotalScore: "", }, }); /***************************************************************************************** * 3️⃣ Submit → build filter array → push to URL (and reset page=1) *****************************************************************************************/ async function onSubmit(data: PeriodicEvaluationFilterFormValues) { startTransition(() => { try { const newFilters: any[] = []; const pushFilter = ( id: string, value: any, type: "text" | "select" | "number" | "boolean", operator: "eq" | "iLike" | "gte" | "lte" ) => { newFilters.push({ id, value, type, operator, rowId: generateId() }); }; if (data.evaluationYear?.trim()) pushFilter("evaluationYear", Number(data.evaluationYear), "number", "eq"); if (data.division?.trim()) pushFilter("division", data.division.trim(), "select", "eq"); if (data.status?.trim()) pushFilter("status", data.status.trim(), "select", "eq"); if (data.domesticForeign?.trim()) pushFilter("domesticForeign", data.domesticForeign.trim(), "select", "eq"); if (data.materialType?.trim()) pushFilter("materialType", data.materialType.trim(), "select", "eq"); if (data.vendorCode?.trim()) pushFilter("vendorCode", data.vendorCode.trim(), "text", "iLike"); if (data.vendorName?.trim()) pushFilter("vendorName", data.vendorName.trim(), "text", "iLike"); if (data.documentsSubmitted?.trim()) pushFilter( "documentsSubmitted", data.documentsSubmitted.trim() === "true", "boolean", "eq" ); if (data.evaluationGrade?.trim()) pushFilter("evaluationGrade", data.evaluationGrade.trim(), "select", "eq"); if (data.finalGrade?.trim()) pushFilter("finalGrade", data.finalGrade.trim(), "select", "eq"); if (data.minTotalScore?.trim()) pushFilter("totalScore", Number(data.minTotalScore), "number", "gte"); if (data.maxTotalScore?.trim()) pushFilter("totalScore", Number(data.maxTotalScore), "number", "lte"); setJoinOperator(joinOperator); onFiltersApply(newFilters, joinOperator); } catch (err) { // eslint-disable-next-line no-console console.error("정기평가 필터 적용 오류:", err); } }); } /***************************************************************************************** * 4️⃣ Reset → clear form & URL *****************************************************************************************/ async function handleReset() { form.reset({ evaluationYear: new Date().getFullYear().toString(), division: "", status: "", domesticForeign: "", materialType: "", vendorCode: "", vendorName: "", documentsSubmitted: "", evaluationGrade: "", finalGrade: "", minTotalScore: "", maxTotalScore: "", }); onFiltersApply([], "and"); setJoinOperator("and"); } /***************************************************************************************** * 5️⃣ RENDER *****************************************************************************************/ if (!isOpen) return null; return (
{/* Header */}

정기평가 검색 필터

{/* Join‑operator selector */}
{/* Form */}
{/* Scrollable area */}
{/* 평가년도 */} ( 평가년도
{field.value && ( )}
)} /> {/* 구분 */} ( 구분 )} /> {/* 진행상태 */} ( 진행상태 )} /> {/* 내외자 구분 */} ( 내외자 구분 )} /> {/* 자재구분 */} ( 자재구분 )} /> {/* 벤더 코드 */} ( 벤더 코드
{field.value && ( )}
)} /> {/* 벤더명 */} ( 벤더명
{field.value && ( )}
)} /> {/* 문서제출여부 */} ( 문서제출여부 )} /> {/* 평가등급 */} ( 평가등급 )} /> {/* 최종등급 */} ( 최종등급 )} /> {/* 점수 범위 */}
( 최소점수
{field.value && ( )}
)} /> ( 최대점수
{field.value && ( )}
)} />
{/* Footer buttons */}
); }