diff options
Diffstat (limited to 'lib/evaluation/table/evaluation-filter-sheet.tsx')
| -rw-r--r-- | lib/evaluation/table/evaluation-filter-sheet.tsx | 616 |
1 files changed, 203 insertions, 413 deletions
diff --git a/lib/evaluation/table/evaluation-filter-sheet.tsx b/lib/evaluation/table/evaluation-filter-sheet.tsx index 7f4de6a6..8f435e36 100644 --- a/lib/evaluation/table/evaluation-filter-sheet.tsx +++ b/lib/evaluation/table/evaluation-filter-sheet.tsx @@ -1,19 +1,15 @@ -// ================================================================ -// 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" +"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, @@ -21,50 +17,28 @@ import { FormItem, FormLabel, FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" -import { Badge } from "@/components/ui/badge" +} 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" +} 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"; -// nanoid 생성기 -const generateId = customAlphabet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 6) +/***************************************************************************************** + * UTILS & CONSTANTS + *****************************************************************************************/ -// 정기평가 필터 스키마 정의 -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(), -}) +// nanoid generator (6‑chars [0-9a-zA-Z]) +const generateId = customAlphabet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 6); -// 옵션 정의 -const evaluationPeriodOptions = [ - { value: "상반기", label: "상반기" }, - { value: "하반기", label: "하반기" }, - { value: "연간", label: "연간" }, -] - -const divisionOptions = [ - { value: "PLANT", label: "해양" }, - { value: "SHIP", label: "조선" }, -] +// ── SELECT OPTIONS ────────────────────────────────────────────────────────────────────── const statusOptions = [ { value: "PENDING", label: "대상확정" }, @@ -73,70 +47,76 @@ const statusOptions = [ { 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: "A", label: "A등급" }, { value: "B", label: "B등급" }, { value: "C", label: "C등급" }, { value: "D", label: "D등급" }, -] +]; -type PeriodicEvaluationFilterFormValues = z.infer<typeof periodicEvaluationFilterSchema> +/***************************************************************************************** + * 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; - onSearch?: () => 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, - onSearch, - isLoading = false + isLoading = false, + onFiltersApply, }: PeriodicEvaluationFilterSheetProps) { - const router = useRouter() - const params = useParams(); - - const [isPending, startTransition] = useTransition() - const [isInitializing, setIsInitializing] = useState(false) - const lastAppliedFilters = useRef<string>("") + /** Router (needed only for pathname) */ + const router = useRouter(); - // nuqs로 URL 상태 관리 - const [filters, setFilters] = useQueryState( - "basicFilters", - getFiltersStateParser().withDefault([]) - ) + /** Track pending state while we update URL */ + const [isPending, startTransition] = useTransition(); + const [joinOperator, setJoinOperator] = useState<"and" | "or">("and") - const [joinOperator, setJoinOperator] = useQueryState( - "basicJoinOperator", - parseAsStringEnum(["and", "or"]).withDefault("and") - ) - // 폼 상태 초기화 + /** React‑Hook‑Form */ const form = useForm<PeriodicEvaluationFilterFormValues>({ resolver: zodResolver(periodicEvaluationFilterSchema), defaultValues: { evaluationYear: new Date().getFullYear().toString(), - evaluationPeriod: "", division: "", status: "", domesticForeign: "", @@ -149,273 +129,130 @@ export function PeriodicEvaluationFilterSheet({ 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 - } - // 폼 제출 핸들러 + /***************************************************************************************** + * 3️⃣ Submit → build filter array → push to URL (and reset page=1) + *****************************************************************************************/ async function onSubmit(data: PeriodicEvaluationFilterFormValues) { - if (isInitializing) return; - - startTransition(async () => { + startTransition(() => { try { - const newFilters = [] + const newFilters: any[] = []; - if (data.evaluationYear?.trim()) { - newFilters.push({ - id: "evaluationYear", - value: parseInt(data.evaluationYear.trim()), - type: "number", - operator: "eq", - rowId: generateId() - }) - } + 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.evaluationPeriod?.trim()) { - newFilters.push({ - id: "evaluationPeriod", - value: data.evaluationPeriod.trim(), - type: "select", - operator: "eq", - rowId: generateId() - }) - } + if (data.evaluationYear?.trim()) + pushFilter("evaluationYear", Number(data.evaluationYear), "number", "eq"); - if (data.division?.trim()) { - newFilters.push({ - id: "division", - value: data.division.trim(), - type: "select", - operator: "eq", - rowId: generateId() - }) - } + if (data.division?.trim()) + pushFilter("division", data.division.trim(), "select", "eq"); - if (data.status?.trim()) { - newFilters.push({ - id: "status", - value: data.status.trim(), - type: "select", - operator: "eq", - rowId: generateId() - }) - } + if (data.status?.trim()) + pushFilter("status", data.status.trim(), "select", "eq"); - if (data.domesticForeign?.trim()) { - newFilters.push({ - id: "domesticForeign", - value: data.domesticForeign.trim(), - type: "select", - operator: "eq", - rowId: generateId() - }) - } + if (data.domesticForeign?.trim()) + pushFilter("domesticForeign", data.domesticForeign.trim(), "select", "eq"); - if (data.materialType?.trim()) { - newFilters.push({ - id: "materialType", - value: data.materialType.trim(), - type: "select", - operator: "eq", - rowId: generateId() - }) - } + if (data.materialType?.trim()) + pushFilter("materialType", data.materialType.trim(), "select", "eq"); - if (data.vendorCode?.trim()) { - newFilters.push({ - id: "vendorCode", - value: data.vendorCode.trim(), - type: "text", - operator: "iLike", - rowId: generateId() - }) - } + if (data.vendorCode?.trim()) + pushFilter("vendorCode", data.vendorCode.trim(), "text", "iLike"); - if (data.vendorName?.trim()) { - newFilters.push({ - id: "vendorName", - value: data.vendorName.trim(), - type: "text", - operator: "iLike", - rowId: generateId() - }) - } + if (data.vendorName?.trim()) + pushFilter("vendorName", data.vendorName.trim(), "text", "iLike"); - if (data.documentsSubmitted?.trim()) { - newFilters.push({ - id: "documentsSubmitted", - value: data.documentsSubmitted.trim() === "true", - type: "boolean", - operator: "eq", - rowId: generateId() - }) - } + if (data.documentsSubmitted?.trim()) + pushFilter( + "documentsSubmitted", + data.documentsSubmitted.trim() === "true", + "boolean", + "eq" + ); - if (data.evaluationGrade?.trim()) { - newFilters.push({ - id: "evaluationGrade", - value: data.evaluationGrade.trim(), - type: "select", - operator: "eq", - rowId: generateId() - }) - } + if (data.evaluationGrade?.trim()) + pushFilter("evaluationGrade", data.evaluationGrade.trim(), "select", "eq"); - if (data.finalGrade?.trim()) { - newFilters.push({ - id: "finalGrade", - value: data.finalGrade.trim(), - type: "select", - operator: "eq", - rowId: generateId() - }) - } + if (data.finalGrade?.trim()) + pushFilter("finalGrade", data.finalGrade.trim(), "select", "eq"); - if (data.minTotalScore?.trim()) { - newFilters.push({ - id: "totalScore", - value: parseFloat(data.minTotalScore.trim()), - type: "number", - operator: "gte", - rowId: generateId() - }) - } + if (data.minTotalScore?.trim()) + pushFilter("totalScore", Number(data.minTotalScore), "number", "gte"); - if (data.maxTotalScore?.trim()) { - newFilters.push({ - id: "totalScore", - value: parseFloat(data.maxTotalScore.trim()), - type: "number", - operator: "lte", - rowId: generateId() - }) - } + if (data.maxTotalScore?.trim()) + pushFilter("totalScore", Number(data.maxTotalScore), "number", "lte"); - // 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; + setJoinOperator(joinOperator); - lastAppliedFilters.current = JSON.stringify(newFilters); - if (onSearch) { - onSearch(); - } - } catch (error) { - console.error("정기평가 필터 적용 오류:", error); + onFiltersApply(newFilters, joinOperator); + } catch (err) { + // eslint-disable-next-line no-console + console.error("정기평가 필터 적용 오류:", err); } - }) + }); } - // 필터 초기화 핸들러 + /***************************************************************************************** + * 4️⃣ Reset → clear form & URL + *****************************************************************************************/ 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'); + form.reset({ + evaluationYear: new Date().getFullYear().toString(), + division: "", + status: "", + domesticForeign: "", + materialType: "", + vendorCode: "", + vendorName: "", + documentsSubmitted: "", + evaluationGrade: "", + finalGrade: "", + minTotalScore: "", + maxTotalScore: "", + }); - const newUrl = `${currentUrl.pathname}?${params.toString()}`; - window.location.href = newUrl; + onFiltersApply([], "and"); + setJoinOperator("and"); - lastAppliedFilters.current = ""; - setIsInitializing(false); - } catch (error) { - console.error("정기평가 필터 초기화 오류:", error); - setIsInitializing(false); - } } - if (!isOpen) { - return null; - } + /***************************************************************************************** + * 5️⃣ RENDER + *****************************************************************************************/ + if (!isOpen) return null; return ( - <div className="flex flex-col h-full max-h-full bg-[#F5F7FB] px-6 sm:px-8" style={{backgroundColor:"#F5F7FB", paddingLeft:"2rem", paddingRight:"2rem"}}> - {/* Filter Panel Header */} - <div className="flex items-center justify-between px-6 min-h-[60px] shrink-0"> - <h3 className="text-lg font-semibold whitespace-nowrap">정기평가 검색 필터</h3> + <div + className="flex h-full max-h-full flex-col px-6 sm:px-8" + style={{ backgroundColor: "#F5F7FB", paddingLeft: "2rem", paddingRight: "2rem" }} + > + {/* Header */} + <div className="flex shrink-0 min-h-[60px] items-center justify-between px-6"> + <h3 className="whitespace-nowrap text-lg font-semibold">정기평가 검색 필터</h3> <div className="flex items-center gap-2"> - {getActiveFilterCount() > 0 && ( - <Badge variant="secondary" className="px-2 py-1"> - {getActiveFilterCount()}개 필터 적용됨 - </Badge> - )} + <Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8"> + <X className="size-4" /> + </Button> </div> </div> - {/* Join Operator Selection */} - <div className="px-6 shrink-0"> + {/* Join‑operator selector */} + <div className="shrink-0 px-6"> <label className="text-sm font-medium">조건 결합 방식</label> <Select value={joinOperator} - onValueChange={(value: "and" | "or") => setJoinOperator(value)} - disabled={isInitializing} + onValueChange={(v: "and" | "or") => setJoinOperator(v)} > - <SelectTrigger className="h-8 w-[180px] mt-2 bg-white"> + <SelectTrigger className="mt-2 h-8 w-[180px] bg-white"> <SelectValue placeholder="조건 결합 방식" /> </SelectTrigger> <SelectContent> @@ -425,12 +262,12 @@ export function PeriodicEvaluationFilterSheet({ </Select> </div> + {/* Form */} <Form {...form}> - <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col h-full min-h-0"> - {/* Scrollable content area */} + <form onSubmit={form.handleSubmit(onSubmit)} className="flex min-h-0 flex-col h-full"> + {/* Scrollable area */} <div className="flex-1 min-h-0 overflow-y-auto px-6 pb-4"> <div className="space-y-4 pt-2"> - {/* 평가년도 */} <FormField control={form.control} @@ -444,20 +281,20 @@ export function PeriodicEvaluationFilterSheet({ type="number" placeholder="평가년도 입력" {...field} - className={cn(field.value && "pr-8", "bg-white")} disabled={isInitializing} + className={cn(field.value && "pr-8", "bg-white")} /> {field.value && ( <Button type="button" variant="ghost" size="icon" - className="absolute right-0 top-0 h-full px-2" onClick={(e) => { e.stopPropagation(); form.setValue("evaluationYear", ""); }} disabled={isInitializing} + className="absolute right-0 top-0 h-full px-2" > <X className="size-3.5" /> </Button> @@ -469,52 +306,6 @@ export function PeriodicEvaluationFilterSheet({ )} /> - {/* 평가기간 */} - {/* <FormField - control={form.control} - name="evaluationPeriod" - render={({ field }) => ( - <FormItem> - <FormLabel>평가기간</FormLabel> - <Select - value={field.value} - onValueChange={field.onChange} - disabled={isInitializing} - > - <FormControl> - <SelectTrigger className={cn(field.value && "pr-8", "bg-white")}> - <div className="flex justify-between w-full"> - <SelectValue placeholder="평가기간 선택" /> - {field.value && ( - <Button - type="button" - variant="ghost" - size="icon" - className="h-4 w-4 -mr-2" - onClick={(e) => { - e.stopPropagation(); - form.setValue("evaluationPeriod", ""); - }} - disabled={isInitializing} - > - <X className="size-3" /> - </Button> - )} - </div> - </SelectTrigger> - </FormControl> - <SelectContent> - {evaluationPeriodOptions.map(option => ( - <SelectItem key={option.value} value={option.value}> - {option.label} - </SelectItem> - ))} - </SelectContent> - </Select> - <FormMessage /> - </FormItem> - )} - /> */} {/* 구분 */} <FormField @@ -530,14 +321,14 @@ export function PeriodicEvaluationFilterSheet({ > <FormControl> <SelectTrigger className={cn(field.value && "pr-8", "bg-white")}> - <div className="flex justify-between w-full"> + <div className="flex w-full justify-between"> <SelectValue placeholder="구분 선택" /> {field.value && ( <Button type="button" variant="ghost" size="icon" - className="h-4 w-4 -mr-2" + className="-mr-2 h-4 w-4" onClick={(e) => { e.stopPropagation(); form.setValue("division", ""); @@ -551,9 +342,9 @@ export function PeriodicEvaluationFilterSheet({ </SelectTrigger> </FormControl> <SelectContent> - {divisionOptions.map(option => ( - <SelectItem key={option.value} value={option.value}> - {option.label} + {EVALUATION_TARGET_FILTER_OPTIONS.DIVISIONS.map((opt) => ( + <SelectItem key={opt.value} value={opt.value}> + {opt.label} </SelectItem> ))} </SelectContent> @@ -577,14 +368,14 @@ export function PeriodicEvaluationFilterSheet({ > <FormControl> <SelectTrigger className={cn(field.value && "pr-8", "bg-white")}> - <div className="flex justify-between w-full"> + <div className="flex w-full justify-between"> <SelectValue placeholder="진행상태 선택" /> {field.value && ( <Button type="button" variant="ghost" size="icon" - className="h-4 w-4 -mr-2" + className="-mr-2 h-4 w-4" onClick={(e) => { e.stopPropagation(); form.setValue("status", ""); @@ -598,9 +389,9 @@ export function PeriodicEvaluationFilterSheet({ </SelectTrigger> </FormControl> <SelectContent> - {statusOptions.map(option => ( - <SelectItem key={option.value} value={option.value}> - {option.label} + {statusOptions.map((opt) => ( + <SelectItem key={opt.value} value={opt.value}> + {opt.label} </SelectItem> ))} </SelectContent> @@ -624,14 +415,14 @@ export function PeriodicEvaluationFilterSheet({ > <FormControl> <SelectTrigger className={cn(field.value && "pr-8", "bg-white")}> - <div className="flex justify-between w-full"> + <div className="flex w-full justify-between"> <SelectValue placeholder="내외자 구분 선택" /> {field.value && ( <Button type="button" variant="ghost" size="icon" - className="h-4 w-4 -mr-2" + className="-mr-2 h-4 w-4" onClick={(e) => { e.stopPropagation(); form.setValue("domesticForeign", ""); @@ -645,9 +436,9 @@ export function PeriodicEvaluationFilterSheet({ </SelectTrigger> </FormControl> <SelectContent> - {domesticForeignOptions.map(option => ( - <SelectItem key={option.value} value={option.value}> - {option.label} + {EVALUATION_TARGET_FILTER_OPTIONS.DOMESTIC_FOREIGN.map((opt) => ( + <SelectItem key={opt.value} value={opt.value}> + {opt.label} </SelectItem> ))} </SelectContent> @@ -671,14 +462,14 @@ export function PeriodicEvaluationFilterSheet({ > <FormControl> <SelectTrigger className={cn(field.value && "pr-8", "bg-white")}> - <div className="flex justify-between w-full"> + <div className="flex w-full justify-between"> <SelectValue placeholder="자재구분 선택" /> {field.value && ( <Button type="button" variant="ghost" size="icon" - className="h-4 w-4 -mr-2" + className="-mr-2 h-4 w-4" onClick={(e) => { e.stopPropagation(); form.setValue("materialType", ""); @@ -692,9 +483,9 @@ export function PeriodicEvaluationFilterSheet({ </SelectTrigger> </FormControl> <SelectContent> - {materialTypeOptions.map(option => ( - <SelectItem key={option.value} value={option.value}> - {option.label} + {EVALUATION_TARGET_FILTER_OPTIONS.MATERIAL_TYPES.map((opt) => ( + <SelectItem key={opt.value} value={opt.value}> + {opt.label} </SelectItem> ))} </SelectContent> @@ -716,8 +507,8 @@ export function PeriodicEvaluationFilterSheet({ <Input placeholder="벤더 코드 입력" {...field} - className={cn(field.value && "pr-8", "bg-white")} disabled={isInitializing} + className={cn(field.value && "pr-8", "bg-white")} /> {field.value && ( <Button @@ -753,8 +544,8 @@ export function PeriodicEvaluationFilterSheet({ <Input placeholder="벤더명 입력" {...field} - className={cn(field.value && "pr-8", "bg-white")} disabled={isInitializing} + className={cn(field.value && "pr-8", "bg-white")} /> {field.value && ( <Button @@ -792,14 +583,14 @@ export function PeriodicEvaluationFilterSheet({ > <FormControl> <SelectTrigger className={cn(field.value && "pr-8", "bg-white")}> - <div className="flex justify-between w-full"> + <div className="flex w-full justify-between"> <SelectValue placeholder="문서제출여부 선택" /> {field.value && ( <Button type="button" variant="ghost" size="icon" - className="h-4 w-4 -mr-2" + className="-mr-2 h-4 w-4" onClick={(e) => { e.stopPropagation(); form.setValue("documentsSubmitted", ""); @@ -813,9 +604,9 @@ export function PeriodicEvaluationFilterSheet({ </SelectTrigger> </FormControl> <SelectContent> - {documentsSubmittedOptions.map(option => ( - <SelectItem key={option.value} value={option.value}> - {option.label} + {documentsSubmittedOptions.map((opt) => ( + <SelectItem key={opt.value} value={opt.value}> + {opt.label} </SelectItem> ))} </SelectContent> @@ -839,14 +630,14 @@ export function PeriodicEvaluationFilterSheet({ > <FormControl> <SelectTrigger className={cn(field.value && "pr-8", "bg-white")}> - <div className="flex justify-between w-full"> + <div className="flex w-full justify-between"> <SelectValue placeholder="평가등급 선택" /> {field.value && ( <Button type="button" variant="ghost" size="icon" - className="h-4 w-4 -mr-2" + className="-mr-2 h-4 w-4" onClick={(e) => { e.stopPropagation(); form.setValue("evaluationGrade", ""); @@ -860,9 +651,9 @@ export function PeriodicEvaluationFilterSheet({ </SelectTrigger> </FormControl> <SelectContent> - {gradeOptions.map(option => ( - <SelectItem key={option.value} value={option.value}> - {option.label} + {gradeOptions.map((opt) => ( + <SelectItem key={opt.value} value={opt.value}> + {opt.label} </SelectItem> ))} </SelectContent> @@ -886,14 +677,14 @@ export function PeriodicEvaluationFilterSheet({ > <FormControl> <SelectTrigger className={cn(field.value && "pr-8", "bg-white")}> - <div className="flex justify-between w-full"> + <div className="flex w-full justify-between"> <SelectValue placeholder="최종등급 선택" /> {field.value && ( <Button type="button" variant="ghost" size="icon" - className="h-4 w-4 -mr-2" + className="-mr-2 h-4 w-4" onClick={(e) => { e.stopPropagation(); form.setValue("finalGrade", ""); @@ -907,9 +698,9 @@ export function PeriodicEvaluationFilterSheet({ </SelectTrigger> </FormControl> <SelectContent> - {gradeOptions.map(option => ( - <SelectItem key={option.value} value={option.value}> - {option.label} + {gradeOptions.map((opt) => ( + <SelectItem key={opt.value} value={opt.value}> + {opt.label} </SelectItem> ))} </SelectContent> @@ -934,8 +725,8 @@ export function PeriodicEvaluationFilterSheet({ step="0.1" placeholder="최소" {...field} - className={cn(field.value && "pr-8", "bg-white")} disabled={isInitializing} + className={cn(field.value && "pr-8", "bg-white")} /> {field.value && ( <Button @@ -972,8 +763,8 @@ export function PeriodicEvaluationFilterSheet({ step="0.1" placeholder="최대" {...field} - className={cn(field.value && "pr-8", "bg-white")} disabled={isInitializing} + className={cn(field.value && "pr-8", "bg-white")} /> {field.value && ( <Button @@ -997,18 +788,17 @@ export function PeriodicEvaluationFilterSheet({ )} /> </div> - </div> </div> - {/* Fixed buttons at bottom */} - <div className="p-4 shrink-0"> - <div className="flex gap-2 justify-end"> + {/* Footer buttons */} + <div className="shrink-0 p-4"> + <div className="flex justify-end gap-2"> <Button type="button" variant="outline" onClick={handleReset} - disabled={isPending || getActiveFilterCount() === 0 || isInitializing} + disabled={isPending } className="px-4" > 초기화 @@ -1016,10 +806,10 @@ export function PeriodicEvaluationFilterSheet({ <Button type="submit" variant="samsung" - disabled={isPending || isLoading || isInitializing} + disabled={isPending || isLoading } className="px-4" > - <Search className="size-4 mr-2" /> + <Search className="mr-2 size-4" /> {isPending || isLoading ? "조회 중..." : "조회"} </Button> </div> @@ -1027,5 +817,5 @@ export function PeriodicEvaluationFilterSheet({ </form> </Form> </div> - ) -}
\ No newline at end of file + ); +} |
