"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 { 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 { 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 for URL updates) */ 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 → callback + URL (동기적 처리) *****************************************************************************************/ async function onSubmit(data: PeriodicEvaluationFilterFormValues) { startTransition(() => { try { const newFilters = [] // 필터 생성 로직 if (data.evaluationYear?.trim()) { newFilters.push({ id: "evaluationYear", value: parseInt(data.evaluationYear.trim()), type: "number", operator: "eq", rowId: generateId() }) } if (data.division?.trim()) { newFilters.push({ id: "division", value: data.division.trim(), type: "select", operator: "eq", rowId: generateId() }) } if (data.status?.trim()) { newFilters.push({ id: "status", value: data.status.trim(), type: "select", operator: "eq", rowId: generateId() }) } if (data.domesticForeign?.trim()) { newFilters.push({ id: "domesticForeign", value: data.domesticForeign.trim(), type: "select", operator: "eq", rowId: generateId() }) } if (data.materialType?.trim()) { newFilters.push({ id: "materialType", value: data.materialType.trim(), type: "select", operator: "eq", rowId: generateId() }) } if (data.vendorCode?.trim()) { newFilters.push({ id: "vendorCode", value: data.vendorCode.trim(), type: "text", operator: "iLike", rowId: generateId() }) } if (data.vendorName?.trim()) { newFilters.push({ id: "vendorName", value: data.vendorName.trim(), type: "text", operator: "iLike", rowId: generateId() }) } if (data.documentsSubmitted?.trim()) { newFilters.push({ id: "documentsSubmitted", value: data.documentsSubmitted.trim() === "true", type: "boolean", operator: "eq", rowId: generateId() }) } if (data.evaluationGrade?.trim()) { newFilters.push({ id: "evaluationGrade", value: data.evaluationGrade.trim(), type: "select", operator: "eq", rowId: generateId() }) } if (data.finalGrade?.trim()) { newFilters.push({ id: "finalGrade", value: data.finalGrade.trim(), type: "select", operator: "eq", rowId: generateId() }) } if (data.minTotalScore?.trim()) { newFilters.push({ id: "totalScore", value: parseFloat(data.minTotalScore.trim()), type: "number", operator: "gte", rowId: generateId() }) } if (data.maxTotalScore?.trim()) { newFilters.push({ id: "totalScore", value: parseFloat(data.maxTotalScore.trim()), type: "number", operator: "lte", rowId: generateId() }) } console.log("=== 생성된 필터들 ===", newFilters); console.log("=== 조인 연산자 ===", joinOperator); // ✅ 부모 컴포넌트에 필터 전달 (동기적으로 즉시 호출) onFiltersApply(newFilters, joinOperator); console.log("=== 필터 적용 완료 ==="); } catch (error) { console.error("정기평가 필터 적용 오류:", error); } }); } /***************************************************************************************** * 4️⃣ Reset → clear form & URL (동기적 처리) *****************************************************************************************/ function handleReset() { // 1. 폼 초기화 form.reset({ evaluationYear: "", division: "", status: "", domesticForeign: "", materialType: "", vendorCode: "", vendorName: "", documentsSubmitted: "", evaluationGrade: "", finalGrade: "", minTotalScore: "", maxTotalScore: "", }); // 2. 조인 연산자 초기화 setJoinOperator("and"); // 3. URL 파라미터 초기화 (필터를 빈 배열로 설정) const currentUrl = new URL(window.location.href); const newSearchParams = new URLSearchParams(currentUrl.search); // 필터 관련 파라미터 초기화 newSearchParams.set("filters", JSON.stringify([])); newSearchParams.set("joinOperator", "and"); newSearchParams.set("page", "1"); newSearchParams.delete("search"); // 검색어 제거 // URL 업데이트 router.replace(`${currentUrl.pathname}?${newSearchParams.toString()}`); // 4. 빈 필터 배열 전달 (즉시 UI 업데이트를 위해) onFiltersApply([], "and"); console.log("=== 필터 완전 초기화 완료 ==="); } /***************************************************************************************** * 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 */}
); }