From 0174ad394032a8dad81107341f477d6d23a3c04c Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 24 Oct 2025 09:52:35 +0000 Subject: (최겸) 구매 피드백 수정(PQ, 실사 등)-1024 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pq/helper.ts | 22 +- lib/pq/pq-review-table-new/pq-filter-sheet.tsx | 247 ++++++++++++++++++--- lib/pq/pq-review-table-new/user-combobox.tsx | 2 +- .../pq-review-table-new/vendors-table-columns.tsx | 114 ++++++---- .../vendors-table-toolbar-actions.tsx | 26 ++- lib/pq/pq-review-table-new/vendors-table.tsx | 17 +- lib/pq/service.ts | 80 ++++--- lib/site-visit/client-site-visit-wrapper.tsx | 6 +- lib/site-visit/service.ts | 48 ++-- lib/vendor-data-plant/services copy.ts | 99 +++++++++ .../table/investigation-table-columns.tsx | 7 +- lib/vendor-regular-registrations/service.ts | 6 +- lib/vendors/table/request-pq-dialog.tsx | 25 ++- lib/vendors/table/vendors-table.tsx | 1 + 14 files changed, 542 insertions(+), 158 deletions(-) create mode 100644 lib/vendor-data-plant/services copy.ts (limited to 'lib') diff --git a/lib/pq/helper.ts b/lib/pq/helper.ts index 81ee5db2..d3970fd7 100644 --- a/lib/pq/helper.ts +++ b/lib/pq/helper.ts @@ -1,11 +1,12 @@ -import { - vendorPQSubmissions, - vendors, - projects, - users, - vendorInvestigations +import { + vendorPQSubmissions, + vendors, + projects, + users, + vendorInvestigations } from "@/db/schema" import { CustomColumnMapping } from "../filter-columns" +import { sql } from "drizzle-orm" /** * Helper function to create custom column mapping for PQ submissions @@ -41,6 +42,12 @@ export function createPQFilterMapping(): CustomColumnMapping { investigationStatus: { table: vendorInvestigations, column: "investigationStatus" }, investigationAddress: { table: vendorInvestigations, column: "investigationAddress" }, qmManagerId: { table: vendorInvestigations, column: "qmManagerId" }, + + // pqItems JSON 검색 (첫 번째 아이템의 itemCode 또는 itemName) + pqItems: { + table: vendorPQSubmissions, + column: "pqItems" + }, } } @@ -90,5 +97,8 @@ export function createPQDirectColumnMapping(): CustomColumnMapping { investigationStatus: vendorInvestigations.investigationStatus, investigationAddress: vendorInvestigations.investigationAddress, qmManagerId: vendorInvestigations.qmManagerId, + + // pqItems JSON 검색 (첫 번째 아이템의 itemCode 또는 itemName) + pqItems: vendorPQSubmissions.pqItems, } } \ No newline at end of file diff --git a/lib/pq/pq-review-table-new/pq-filter-sheet.tsx b/lib/pq/pq-review-table-new/pq-filter-sheet.tsx index ff1b890b..fe525a46 100644 --- a/lib/pq/pq-review-table-new/pq-filter-sheet.tsx +++ b/lib/pq/pq-review-table-new/pq-filter-sheet.tsx @@ -7,7 +7,7 @@ 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 { parseAsStringEnum, useQueryState, parseAsString } from "nuqs" import { Button } from "@/components/ui/button" import { @@ -40,8 +40,11 @@ const pqFilterSchema = z.object({ requesterName: z.string().optional(), pqNumber: z.string().optional(), vendorName: z.string().optional(), + vendorCode: z.string().optional(), + type: z.string().optional(), + projectName: z.string().optional(), status: z.string().optional(), - evaluationResult: z.string().optional(), + pqItems: z.string().optional(), createdAtRange: z.object({ from: z.date().optional(), to: z.date().optional(), @@ -57,13 +60,13 @@ const pqStatusOptions = [ { value: "REJECTED", label: "거부됨" }, ] -// 평가 결과 옵션 정의 -const evaluationResultOptions = [ - { value: "APPROVED", label: "승인" }, - { value: "SUPPLEMENT", label: "보완" }, - { value: "REJECTED", label: "불가" }, +// PQ 유형 옵션 정의 +const pqTypeOptions = [ + { value: "GENERAL", label: "일반 PQ" }, + { value: "PROJECT", label: "프로젝트 PQ" }, ] + type PQFilterFormValues = z.infer interface PQFilterSheetProps { @@ -106,6 +109,10 @@ export function PQFilterSheet({ // 현재 URL의 페이지 파라미터도 가져옴 const [page, setPage] = useQueryState("page", { defaultValue: "1" }) + // DateRangePicker의 from/to 파라미터 직접 읽기 + const [fromParam, setFromParam] = useQueryState("from", parseAsString.withDefault("")) + const [toParam, setToParam] = useQueryState("to", parseAsString.withDefault("")) + // 폼 상태 초기화 const form = useForm({ resolver: zodResolver(pqFilterSchema), @@ -113,8 +120,11 @@ export function PQFilterSheet({ requesterName: "", pqNumber: "", vendorName: "", + vendorCode: "", + type: "", + projectName: "", status: "", - evaluationResult: "", + pqItems: "", createdAtRange: { from: undefined, to: undefined, @@ -128,12 +138,22 @@ export function PQFilterSheet({ const currentFiltersString = JSON.stringify(filters); // 패널이 열렸고, 필터가 있고, 마지막에 적용된 필터와 다를 때만 업데이트 - if (isOpen && filters && filters.length > 0 && currentFiltersString !== lastAppliedFilters.current) { + if (isOpen && (filters && filters.length > 0 || fromParam || toParam) && currentFiltersString !== lastAppliedFilters.current) { setIsInitializing(true); const formValues = { ...form.getValues() }; let formUpdated = false; + // DateRangePicker의 from/to 파라미터 처리 + if (fromParam || toParam) { + formValues.createdAtRange = { + from: fromParam ? new Date(fromParam) : undefined, + to: toParam ? new Date(toParam) : undefined, + }; + formUpdated = true; + } + + // 기존 필터 처리 filters.forEach(filter => { if (filter.id === "createdAt" && Array.isArray(filter.value) && filter.value.length > 0) { formValues.createdAtRange = { @@ -156,7 +176,7 @@ export function PQFilterSheet({ setIsInitializing(false); } - }, [filters, isOpen]) + }, [filters, fromParam, toParam, isOpen]) // 현재 적용된 필터 카운트 const getActiveFilterCount = () => { @@ -171,7 +191,13 @@ async function onSubmit(data: PQFilterFormValues) { startTransition(async () => { try { // 필터 배열 생성 - const newFilters = [] + const newFilters: Array<{ + id: string; + value: string | string[]; + type: string; + operator: string; + rowId: string; + }> = [] if (data.requesterName?.trim()) { newFilters.push({ @@ -203,6 +229,36 @@ async function onSubmit(data: PQFilterFormValues) { }) } + if (data.vendorCode?.trim()) { + newFilters.push({ + id: "vendorCode", + value: data.vendorCode.trim(), + type: "text", + operator: "iLike", + rowId: generateId() + }) + } + + if (data.type?.trim()) { + newFilters.push({ + id: "type", + value: data.type.trim(), + type: "select", + operator: "eq", + rowId: generateId() + }) + } + + if (data.projectName?.trim()) { + newFilters.push({ + id: "projectName", + value: data.projectName.trim(), + type: "text", + operator: "iLike", + rowId: generateId() + }) + } + if (data.status?.trim()) { newFilters.push({ id: "status", @@ -213,24 +269,24 @@ async function onSubmit(data: PQFilterFormValues) { }) } - if (data.evaluationResult?.trim()) { + if (data.pqItems?.trim()) { newFilters.push({ - id: "evaluationResult", - value: data.evaluationResult.trim(), - type: "select", - operator: "eq", + id: "pqItems", + value: data.pqItems.trim(), + type: "text", + operator: "iLike", rowId: generateId() }) } - // 생성일 범위 추가 + // PQ 전송일 범위 추가 (createdAt) if (data.createdAtRange?.from) { newFilters.push({ id: "createdAt", value: [ data.createdAtRange.from.toISOString().split('T')[0], - data.createdAtRange.to ? data.createdAtRange.to.toISOString().split('T')[0] : undefined - ].filter(Boolean), + data.createdAtRange.to?.toISOString().split('T')[0] + ].filter((v): v is string => v !== undefined), type: "date", operator: "isBetween", rowId: generateId() @@ -289,8 +345,11 @@ async function onSubmit(data: PQFilterFormValues) { requesterName: "", pqNumber: "", vendorName: "", + vendorCode: "", + type: "", + projectName: "", status: "", - evaluationResult: "", + pqItems: "", createdAtRange: { from: undefined, to: undefined }, }); @@ -373,11 +432,11 @@ async function onSubmit(data: PQFilterFormValues) { name="requesterName" render={({ field }) => ( - 요청자명 + PQ/실사 요청자
( - PQ 번호 + PQ No.
( - 협력업체명 + 협력업체
{/* PQ 상태 */} - ( @@ -527,15 +586,15 @@ async function onSubmit(data: PQFilterFormValues) { )} - /> + /> */} - {/* 평가 결과 */} + {/* PQ 유형 */} ( - 평가 결과 + PQ 유형 + {field.value && ( + + )} +
+
+ +
+ )} + /> */} + + {/* 협력업체 코드 */} + ( + + 협력업체 코드 + +
+ + {field.value && ( + + )} +
+
+ +
+ )} + /> + + {/* 실사품목 */} + ( + + 실사품목 + +
+ + {field.value && ( + + )} +
+
+ +
+ )} + /> + + {/* PQ 전송일 */} ( - PQ 생성일 + PQ 전송일 범위
diff --git a/lib/pq/pq-review-table-new/user-combobox.tsx b/lib/pq/pq-review-table-new/user-combobox.tsx index 560f675a..3e0264ed 100644 --- a/lib/pq/pq-review-table-new/user-combobox.tsx +++ b/lib/pq/pq-review-table-new/user-combobox.tsx @@ -87,7 +87,7 @@ export function UserCombobox({ {users.map((user) => ( { onChange(user.id) setOpen(false) diff --git a/lib/pq/pq-review-table-new/vendors-table-columns.tsx b/lib/pq/pq-review-table-new/vendors-table-columns.tsx index e3687f52..ffa15e56 100644 --- a/lib/pq/pq-review-table-new/vendors-table-columns.tsx +++ b/lib/pq/pq-review-table-new/vendors-table-columns.tsx @@ -122,25 +122,54 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef // ---------------------------------------------------------------- const selectColumn: ColumnDef = { id: "select", - header: ({ table }) => ( - table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - className="translate-y-0.5" - /> - ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label="Select row" - className="translate-y-0.5" - /> - ), + header: ({ table }) => { + const selectedRows = table.getSelectedRowModel().rows; + const isAllSelected = table.getIsAllPageRowsSelected(); + const isSomeSelected = table.getIsSomePageRowsSelected(); + + return ( + { + if (value) { + // 전체 선택: 첫 번째 행만 선택 + table.toggleAllRowsSelected(false); + if (table.getRowModel().rows.length > 0) { + table.getRowModel().rows[0].toggleSelected(true); + } + } else { + // 전체 해제 + table.toggleAllRowsSelected(false); + } + }} + aria-label="Select all" + className="translate-y-0.5" + /> + ); + }, + cell: ({ row, table }) => { + const selectedRows = table.getSelectedRowModel().rows; + const isCurrentlySelected = row.getIsSelected(); + + return ( + { + if (value) { + // 체크하려는 경우: 이미 선택된 행이 있으면 모두 해제하고 현재 행만 선택 + table.toggleAllRowsSelected(false); + row.toggleSelected(true); + } else { + // 체크 해제하는 경우 + row.toggleSelected(false); + } + }} + aria-label="Select row" + className="translate-y-0.5" + /> + ); + }, size: 40, enableSorting: false, enableHiding: false, @@ -172,7 +201,7 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef cell: ({ row }) => (
{row.getValue("vendorName")} - {row.original.vendorCode ? row.original.vendorCode : "-"}/{row.original.taxId} + {/* {row.original.vendorCode ? row.original.vendorCode : "-"}/{row.original.taxId} */}
), enableSorting: true, @@ -408,6 +437,26 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef ) }, } + const investigationRequestedAtColumn: ColumnDef = { + accessorKey: "investigationRequestedAt", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const investigation = row.original.investigation; + + if (!investigation || !investigation.requestedAt) { + return -; + } + const dateVal = investigation.requestedAt + + return ( +
+ {dateVal ? formatDate(dateVal, 'KR') : "-"} +
+ ) + }, + } const investigationNotesColumn: ColumnDef = { accessorKey: "investigationNotes", @@ -484,26 +533,7 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef } - const investigationRequestedAtColumn: ColumnDef = { - accessorKey: "investigationRequestedAt", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - const investigation = row.original.investigation; - - if (!investigation || !investigation.requestedAt) { - return -; - } - const dateVal = investigation.requestedAt - return ( -
- {dateVal ? formatDate(dateVal, 'KR') : "-"} -
- ) - }, - } const investigationForecastedAtColumn: ColumnDef = { @@ -651,9 +681,9 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef )} - {/* 방문실사 버튼 - 제품검사평가 또는 방문실사평가인 경우에만 표시 */} - {pq.investigation && - (pq.investigation.investigationMethod === "PRODUCT_INSPECTION" || + {/* 방문실사 버튼 - PQ가 승인됨 상태이고 제품검사평가 또는 방문실사평가인 경우에만 표시 */} + {pq.status === "APPROVED" && pq.investigation && + (pq.investigation.investigationMethod === "PRODUCT_INSPECTION" || pq.investigation.investigationMethod === "SITE_VISIT_EVAL") && ( <> { variant="outline" size="sm" onClick={() => setIsCancelDialogOpen(true)} - disabled={isLoading || selectedRows.length === 0} + disabled={ + isLoading || + selectedRows.length === 0 || + !selectedRows.every(row => row.original.investigation?.investigationStatus === "PLANNED") + } className="gap-2" >