summaryrefslogtreecommitdiff
path: root/components/po-rfq/rfq-filter-sheet.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-05-28 00:18:16 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-05-28 00:18:16 +0000
commit748bb1720fd81e97a84c3e92f89d606e976b52e3 (patch)
treea7f7f377035cd04912fe0541368884f976f4ee6d /components/po-rfq/rfq-filter-sheet.tsx
parent9e280704988fdeffa05c1d8cbb731722f666c6af (diff)
(대표님) 컴포넌트 파트 커밋
Diffstat (limited to 'components/po-rfq/rfq-filter-sheet.tsx')
-rw-r--r--components/po-rfq/rfq-filter-sheet.tsx643
1 files changed, 0 insertions, 643 deletions
diff --git a/components/po-rfq/rfq-filter-sheet.tsx b/components/po-rfq/rfq-filter-sheet.tsx
deleted file mode 100644
index 31f02442..00000000
--- a/components/po-rfq/rfq-filter-sheet.tsx
+++ /dev/null
@@ -1,643 +0,0 @@
-"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 { CalendarIcon, ChevronRight, 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 { DateRangePicker } from "../date-range-picker"
-import { Badge } from "@/components/ui/badge"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
-import { cn } from "@/lib/utils"
-import { useTranslation } from '@/i18n/client'
-import { getFiltersStateParser } from "@/lib/parsers"
-
-// nanoid 생성기
-const generateId = customAlphabet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 6)
-
-// 필터 스키마 정의 (RFQFilterBox와 동일)
-const filterSchema = z.object({
- picCode: z.string().optional(),
- projectCode: z.string().optional(),
- rfqCode: z.string().optional(),
- itemCode: z.string().optional(),
- majorItemMaterialCode: z.string().optional(),
- status: z.string().optional(),
- dateRange: z.object({
- from: z.date().optional(),
- to: z.date().optional(),
- }).optional(),
-})
-
-// 상태 옵션 정의
-const statusOptions = [
- { value: "RFQ Created", label: "RFQ Created" },
- { value: "RFQ Vendor Assignned", label: "RFQ Vendor Assignned" },
- { value: "RFQ Sent", label: "RFQ Sent" },
- { value: "Quotation Analysis", label: "Quotation Analysis" },
- { value: "PO Transfer", label: "PO Transfer" },
- { value: "PO Create", label: "PO Create" },
-]
-
-type FilterFormValues = z.infer<typeof filterSchema>
-
-interface RFQFilterSheetProps {
- isOpen: boolean;
- onClose: () => void;
- onSearch?: () => void;
- isLoading?: boolean;
-}
-
-// Updated component for inline use (not a sheet anymore)
-export function RFQFilterSheet({
- isOpen,
- onClose,
- onSearch,
- isLoading = false
-}: RFQFilterSheetProps) {
- const router = useRouter()
- const params = useParams();
- const lng = params ? (params.lng as string) : 'ko';
- const { t } = useTranslation(lng);
-
- const [isPending, startTransition] = useTransition()
-
- // 초기화 상태 추가 - 폼 초기화 중에는 상태 변경을 방지
- const [isInitializing, setIsInitializing] = useState(false)
- // 마지막으로 적용된 필터를 추적하기 위한 ref
- const lastAppliedFilters = useRef<string>("")
-
- // nuqs로 URL 상태 관리 - 파라미터명을 'basicFilters'로 변경
- const [filters, setFilters] = useQueryState(
- "basicFilters",
- getFiltersStateParser().withDefault([])
- )
-
- // joinOperator 설정
- const [joinOperator, setJoinOperator] = useQueryState(
- "basicJoinOperator",
- parseAsStringEnum(["and", "or"]).withDefault("and")
- )
-
- // 현재 URL의 페이지 파라미터도 가져옴
- const [page, setPage] = useQueryState("page", { defaultValue: "1" })
-
- // 폼 상태 초기화
- const form = useForm<FilterFormValues>({
- resolver: zodResolver(filterSchema),
- defaultValues: {
- picCode: "",
- projectCode: "",
- rfqCode: "",
- itemCode: "",
- majorItemMaterialCode: "",
- status: "",
- dateRange: {
- from: undefined,
- to: undefined,
- },
- },
- })
-
- // 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 === "rfqSendDate" && Array.isArray(filter.value) && filter.value.length > 0) {
- formValues.dateRange = {
- from: filter.value[0] ? new Date(filter.value[0]) : undefined,
- to: filter.value[1] ? new Date(filter.value[1]) : undefined,
- };
- formUpdated = true;
- } else if (filter.id in formValues) {
- // @ts-ignore - 동적 필드 접근
- formValues[filter.id] = filter.value;
- formUpdated = true;
- }
- });
-
- // 폼 값이 변경된 경우에만 reset으로 한 번에 업데이트
- if (formUpdated) {
- form.reset(formValues);
- lastAppliedFilters.current = currentFiltersString;
- }
-
- setIsInitializing(false);
- }
- }, [filters, isOpen]) // form 의존성 제거
-
- // 현재 적용된 필터 카운트
- const getActiveFilterCount = () => {
- return filters?.length || 0
- }
-
- // 폼 제출 핸들러 - 개선된 버전
- async function onSubmit(data: FilterFormValues) {
- // 초기화 중이면 제출 방지
- if (isInitializing) return;
-
- startTransition(async () => {
- try {
- // 필터 배열 생성
- const newFilters = []
-
- if (data.picCode?.trim()) {
- newFilters.push({
- id: "picCode",
- value: data.picCode.trim(),
- type: "text",
- operator: "iLike",
- rowId: generateId()
- })
- }
-
- if (data.projectCode?.trim()) {
- newFilters.push({
- id: "projectCode",
- value: data.projectCode.trim(),
- type: "text",
- operator: "iLike",
- rowId: generateId()
- })
- }
-
- if (data.rfqCode?.trim()) {
- newFilters.push({
- id: "rfqCode",
- value: data.rfqCode.trim(),
- type: "text",
- operator: "iLike",
- rowId: generateId()
- })
- }
-
- if (data.itemCode?.trim()) {
- newFilters.push({
- id: "itemCode",
- value: data.itemCode.trim(),
- type: "text",
- operator: "iLike",
- rowId: generateId()
- })
- }
-
- if (data.majorItemMaterialCode?.trim()) {
- newFilters.push({
- id: "majorItemMaterialCode",
- value: data.majorItemMaterialCode.trim(),
- type: "text",
- operator: "iLike",
- rowId: generateId()
- })
- }
-
- if (data.status?.trim()) {
- newFilters.push({
- id: "status",
- value: data.status.trim(),
- type: "select",
- operator: "eq",
- rowId: generateId()
- })
- }
-
- // Add date range to params if it exists
- if (data.dateRange?.from) {
- newFilters.push({
- id: "rfqSendDate",
- value: [
- data.dateRange.from.toISOString().split('T')[0],
- data.dateRange.to ? data.dateRange.to.toISOString().split('T')[0] : undefined
- ].filter(Boolean),
- type: "date",
- operator: "isBetween",
- rowId: generateId()
- })
- }
-
- console.log("기본 필터 적용:", newFilters);
-
- // 마지막 적용된 필터 업데이트
- lastAppliedFilters.current = JSON.stringify(newFilters);
-
- // 먼저 필터를 설정
- await setFilters(newFilters.length > 0 ? newFilters : null);
-
- // 그 다음 페이지를 1로 설정
- await setPage("1");
-
- // 필터 업데이트 후 조회 핸들러 호출 (제공된 경우)
- if (onSearch) {
- onSearch();
- }
- } catch (error) {
- console.error("필터 적용 오류:", error);
- }
- })
- }
-
- // 필터 초기화 핸들러 - 개선된 버전
- async function handleReset() {
- try {
- setIsInitializing(true);
-
- form.reset({
- picCode: "",
- projectCode: "",
- rfqCode: "",
- itemCode: "",
- majorItemMaterialCode: "",
- status: "",
- dateRange: { from: undefined, to: undefined },
- });
-
- // 필터와 조인 연산자를 초기화
- await setFilters(null);
- await setJoinOperator("and");
- await setPage("1");
-
- // 마지막 적용된 필터 초기화
- lastAppliedFilters.current = "";
-
- console.log("필터 초기화 완료");
- setIsInitializing(false);
- } catch (error) {
- console.error("필터 초기화 오류:", error);
- setIsInitializing(false);
- }
- }
-
- // Don't render if not open (for side panel use)
- if (!isOpen) {
- return null;
- }
-
- return (
- <div className="flex flex-col h-full max-h-full bg-[#F5F7FB]">
- {/* 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>
-
- {/* Join Operator Selection - 보더 제거, 배경 색상 적용 */}
- <div className="px-6 shrink-0">
- <label className="text-sm font-medium">조건 결합 방식</label>
- <Select
- value={joinOperator}
- onValueChange={(value: "and" | "or") => setJoinOperator(value)}
- disabled={isInitializing}
- >
- <SelectTrigger className="h-8 w-[180px] mt-2 bg-white">
- <SelectValue placeholder="조건 결합 방식" />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="and">모든 조건 충족 (AND)</SelectItem>
- <SelectItem value="or">하나라도 충족 (OR)</SelectItem>
- </SelectContent>
- </Select>
- </div>
-
- <Form {...form}>
- <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col h-full min-h-0">
- {/* Scrollable content area - 헤더와 버튼 사이에서 스크롤 */}
- <div className="flex-1 min-h-0 overflow-y-auto px-6 pb-4">
- <div className="space-y-6 pt-4">
- {/* 발주 담당 */}
- <FormField
- control={form.control}
- name="picCode"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t("발주담당")}</FormLabel>
- <FormControl>
- <div className="relative">
- <Input
- placeholder={t("발주담당 입력")}
- {...field}
- className={cn(field.value && "pr-8", "bg-white")}
- disabled={isInitializing}
- />
- {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("picCode", "");
- }}
- disabled={isInitializing}
- >
- <X className="size-3.5" />
- <span className="sr-only">Clear</span>
- </Button>
- )}
- </div>
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- {/* 프로젝트 코드 */}
- <FormField
- control={form.control}
- name="projectCode"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t("프로젝트 코드")}</FormLabel>
- <FormControl>
- <div className="relative">
- <Input
- placeholder={t("프로젝트 코드 입력")}
- {...field}
- className={cn(field.value && "pr-8", "bg-white")}
- disabled={isInitializing}
- />
- {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("projectCode", "");
- }}
- disabled={isInitializing}
- >
- <X className="size-3.5" />
- <span className="sr-only">Clear</span>
- </Button>
- )}
- </div>
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- {/* RFQ NO. */}
- <FormField
- control={form.control}
- name="rfqCode"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t("RFQ NO.")}</FormLabel>
- <FormControl>
- <div className="relative">
- <Input
- placeholder={t("RFQ 번호 입력")}
- {...field}
- className={cn(field.value && "pr-8", "bg-white")}
- disabled={isInitializing}
- />
- {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("rfqCode", "");
- }}
- disabled={isInitializing}
- >
- <X className="size-3.5" />
- <span className="sr-only">Clear</span>
- </Button>
- )}
- </div>
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- {/* 자재그룹 */}
- <FormField
- control={form.control}
- name="itemCode"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t("자재그룹")}</FormLabel>
- <FormControl>
- <div className="relative">
- <Input
- placeholder={t("자재그룹 입력")}
- {...field}
- className={cn(field.value && "pr-8", "bg-white")}
- disabled={isInitializing}
- />
- {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("itemCode", "");
- }}
- disabled={isInitializing}
- >
- <X className="size-3.5" />
- <span className="sr-only">Clear</span>
- </Button>
- )}
- </div>
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- {/* 자재코드 */}
- <FormField
- control={form.control}
- name="majorItemMaterialCode"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t("자재코드")}</FormLabel>
- <FormControl>
- <div className="relative">
- <Input
- placeholder={t("자재코드 입력")}
- {...field}
- className={cn(field.value && "pr-8", "bg-white")}
- disabled={isInitializing}
- />
- {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("majorItemMaterialCode", "");
- }}
- disabled={isInitializing}
- >
- <X className="size-3.5" />
- <span className="sr-only">Clear</span>
- </Button>
- )}
- </div>
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
-
- {/* Status */}
- <FormField
- control={form.control}
- name="status"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t("Status")}</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={t("Select status")} />
- {field.value && (
- <Button
- type="button"
- variant="ghost"
- size="icon"
- className="h-4 w-4 -mr-2"
- onClick={(e) => {
- e.stopPropagation();
- form.setValue("status", "");
- }}
- disabled={isInitializing}
- >
- <X className="size-3" />
- <span className="sr-only">Clear</span>
- </Button>
- )}
- </div>
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- {statusOptions.map(option => (
- <SelectItem key={option.value} value={option.value}>
- {option.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- <FormMessage />
- </FormItem>
- )}
- />
-
- {/* RFQ 전송일 */}
- <FormField
- control={form.control}
- name="dateRange"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t("RFQ 전송일")}</FormLabel>
- <FormControl>
- <div className="relative">
- <DateRangePicker
- triggerSize="default"
- triggerClassName="w-full bg-white"
- align="start"
- showClearButton={true}
- placeholder={t("RFQ 전송일 범위를 고르세요")}
- value={field.value || undefined}
- onChange={field.onChange}
- disabled={isInitializing}
- />
- {(field.value?.from || field.value?.to) && (
- <Button
- type="button"
- variant="ghost"
- size="icon"
- className="absolute right-10 top-0 h-full px-2"
- onClick={(e) => {
- e.stopPropagation();
- form.setValue("dateRange", { from: undefined, to: undefined });
- }}
- disabled={isInitializing}
- >
- <X className="size-3.5" />
- <span className="sr-only">Clear</span>
- </Button>
- )}
- </div>
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- </div>
- </div>
-
- {/* Fixed buttons at bottom - 보더 제거, 배경 색상 적용 */}
- <div className="p-4 shrink-0">
- <div className="flex gap-2 justify-end">
- <Button
- type="button"
- variant="outline"
- onClick={handleReset}
- disabled={isPending || getActiveFilterCount() === 0 || isInitializing}
- className="px-4"
- >
- {t("초기화")}
- </Button>
- <Button
- type="submit"
- variant="samsung"
- disabled={isPending || isLoading || isInitializing}
- className="px-4"
- >
- <Search className="size-4 mr-2" />
- {isPending || isLoading ? t("조회 중...") : t("조회")}
- </Button>
- </div>
- </div>
- </form>
- </Form>
- </div>
- )
-} \ No newline at end of file