// ================================================================ // 2. PERIODIC EVALUATIONS FILTER SHEET // ================================================================ "use client" import { useEffect, useTransition, useState, useRef } from "react" import { useRouter, useParams } 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" // nanoid 생성기 const generateId = customAlphabet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 6) // 정기평가 필터 스키마 정의 const periodicEvaluationFilterSchema = z.object({ evaluationYear: z.string().optional(), evaluationPeriod: 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(), }) // 옵션 정의 const evaluationPeriodOptions = [ { value: "상반기", label: "상반기" }, { value: "하반기", label: "하반기" }, { value: "연간", label: "연간" }, ] const divisionOptions = [ { value: "PLANT", label: "해양" }, { value: "SHIP", label: "조선" }, ] const statusOptions = [ { value: "PENDING_SUBMISSION", label: "제출대기" }, { value: "SUBMITTED", label: "제출완료" }, { value: "IN_REVIEW", label: "검토중" }, { value: "REVIEW_COMPLETED", label: "검토완료" }, { value: "FINALIZED", label: "최종확정" }, ] const domesticForeignOptions = [ { value: "DOMESTIC", label: "내자" }, { value: "FOREIGN", label: "외자" }, ] const materialTypeOptions = [ { value: "EQUIPMENT", label: "기자재" }, { value: "BULK", label: "벌크" }, { value: "EQUIPMENT_BULK", label: "기자재/벌크" }, ] const documentsSubmittedOptions = [ { value: "true", label: "제출완료" }, { value: "false", label: "미제출" }, ] const gradeOptions = [ { value: "S", label: "S등급" }, { value: "A", label: "A등급" }, { value: "B", label: "B등급" }, { value: "C", label: "C등급" }, { value: "D", label: "D등급" }, ] type PeriodicEvaluationFilterFormValues = z.infer interface PeriodicEvaluationFilterSheetProps { isOpen: boolean; onClose: () => void; onSearch?: () => void; isLoading?: boolean; } export function PeriodicEvaluationFilterSheet({ isOpen, onClose, onSearch, isLoading = false }: PeriodicEvaluationFilterSheetProps) { const router = useRouter() const params = useParams(); const [isPending, startTransition] = useTransition() const [isInitializing, setIsInitializing] = useState(false) const lastAppliedFilters = useRef("") // nuqs로 URL 상태 관리 const [filters, setFilters] = useQueryState( "basicFilters", getFiltersStateParser().withDefault([]) ) const [joinOperator, setJoinOperator] = useQueryState( "basicJoinOperator", parseAsStringEnum(["and", "or"]).withDefault("and") ) // 폼 상태 초기화 const form = useForm({ resolver: zodResolver(periodicEvaluationFilterSchema), defaultValues: { evaluationYear: new Date().getFullYear().toString(), evaluationPeriod: "", division: "", status: "", domesticForeign: "", materialType: "", vendorCode: "", vendorName: "", documentsSubmitted: "", evaluationGrade: "", finalGrade: "", minTotalScore: "", maxTotalScore: "", }, }) // URL 필터에서 초기 폼 상태 설정 useEffect(() => { const currentFiltersString = JSON.stringify(filters); if (isOpen && filters && filters.length > 0 && currentFiltersString !== lastAppliedFilters.current) { setIsInitializing(true); const formValues = { ...form.getValues() }; let formUpdated = false; filters.forEach(filter => { if (filter.id in formValues) { // @ts-ignore - 동적 필드 접근 formValues[filter.id] = filter.value; formUpdated = true; } }); if (formUpdated) { form.reset(formValues); lastAppliedFilters.current = currentFiltersString; } setIsInitializing(false); } }, [filters, isOpen]) // 현재 적용된 필터 카운트 const getActiveFilterCount = () => { return filters?.length || 0 } // 폼 제출 핸들러 async function onSubmit(data: PeriodicEvaluationFilterFormValues) { if (isInitializing) return; startTransition(async () => { try { const newFilters = [] if (data.evaluationYear?.trim()) { newFilters.push({ id: "evaluationYear", value: parseInt(data.evaluationYear.trim()), type: "number", operator: "eq", rowId: generateId() }) } if (data.evaluationPeriod?.trim()) { newFilters.push({ id: "evaluationPeriod", value: data.evaluationPeriod.trim(), type: "select", 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() }) } // URL 업데이트 const currentUrl = new URL(window.location.href); const params = new URLSearchParams(currentUrl.search); params.delete('basicFilters'); params.delete('basicJoinOperator'); params.delete('page'); if (newFilters.length > 0) { params.set('basicFilters', JSON.stringify(newFilters)); params.set('basicJoinOperator', joinOperator); } params.set('page', '1'); const newUrl = `${currentUrl.pathname}?${params.toString()}`; window.location.href = newUrl; lastAppliedFilters.current = JSON.stringify(newFilters); if (onSearch) { onSearch(); } } catch (error) { console.error("정기평가 필터 적용 오류:", error); } }) } // 필터 초기화 핸들러 async function handleReset() { try { setIsInitializing(true); form.reset({ evaluationYear: new Date().getFullYear().toString(), evaluationPeriod: "", division: "", status: "", domesticForeign: "", materialType: "", vendorCode: "", vendorName: "", documentsSubmitted: "", evaluationGrade: "", finalGrade: "", minTotalScore: "", maxTotalScore: "", }); const currentUrl = new URL(window.location.href); const params = new URLSearchParams(currentUrl.search); params.delete('basicFilters'); params.delete('basicJoinOperator'); params.set('page', '1'); const newUrl = `${currentUrl.pathname}?${params.toString()}`; window.location.href = newUrl; lastAppliedFilters.current = ""; setIsInitializing(false); } catch (error) { console.error("정기평가 필터 초기화 오류:", error); setIsInitializing(false); } } if (!isOpen) { return null; } return (
{/* Filter Panel Header */}

정기평가 검색 필터

{getActiveFilterCount() > 0 && ( {getActiveFilterCount()}개 필터 적용됨 )}
{/* Join Operator Selection */}
{/* Scrollable content area */}
{/* 평가년도 */} ( 평가년도
{field.value && ( )}
)} /> {/* 평가기간 */} ( 평가기간 )} /> {/* 구분 */} ( 구분 )} /> {/* 진행상태 */} ( 진행상태 )} /> {/* 내외자 구분 */} ( 내외자 구분 )} /> {/* 자재구분 */} ( 자재구분 )} /> {/* 벤더 코드 */} ( 벤더 코드
{field.value && ( )}
)} /> {/* 벤더명 */} ( 벤더명
{field.value && ( )}
)} /> {/* 문서제출여부 */} ( 문서제출여부 )} /> {/* 평가등급 */} ( 평가등급 )} /> {/* 최종등급 */} ( 최종등급 )} /> {/* 점수 범위 */}
( 최소점수
{field.value && ( )}
)} /> ( 최대점수
{field.value && ( )}
)} />
{/* Fixed buttons at bottom */}
) }