From bc7d627f61a4d055b19d0679b3a4c128b7afcfda Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 28 May 2025 17:29:43 +0000 Subject: (대표님) admin / api / components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form-data/form-data-report-batch-dialog.tsx | 283 +++++++++++++-------- components/form-data/form-data-table-columns.tsx | 101 ++++++-- components/form-data/form-data-table.tsx | 60 ++++- 3 files changed, 309 insertions(+), 135 deletions(-) (limited to 'components/form-data') diff --git a/components/form-data/form-data-report-batch-dialog.tsx b/components/form-data/form-data-report-batch-dialog.tsx index ef921a91..53f8c489 100644 --- a/components/form-data/form-data-report-batch-dialog.tsx +++ b/components/form-data/form-data-report-batch-dialog.tsx @@ -51,6 +51,7 @@ import { import { Button } from "@/components/ui/button"; import { getReportTempList, getOrigin } from "@/lib/forms/services"; import { DataTableColumnJSON } from "./form-data-table-columns"; +import { PublishDialog } from "./publish-dialog"; const MAX_FILE_SIZE = 3000000; @@ -87,6 +88,10 @@ export const FormDataReportBatchDialog: FC = ({ const [selectTemp, setSelectTemp] = useState(""); const [selectedFiles, setSelectedFiles] = useState([]); const [isUploading, setIsUploading] = useState(false); + + // Add new state for publish dialog + const [publishDialogOpen, setPublishDialogOpen] = useState(false); + const [generatedFileBlob, setGeneratedFileBlob] = useState(null); useEffect(() => { updateReportTempList(packageId, formId, setTempList); @@ -125,48 +130,43 @@ export const FormDataReportBatchDialog: FC = ({ setSelectedFiles(updatedFiles); }; + // Create and download document const submitData = async () => { setIsUploading(true); try { const origin = await getOrigin(); - const targetFiles = selectedFiles[0]; const reportDatas = reportData.map((c) => { const reportValue = stringifyAllValues(c); - const reportValueMapping: { [key: string]: any } = {}; columnsJSON.forEach((c2) => { const { key } = c2; - - // const objKey = label.split(" ").join("_"); - reportValueMapping[key] = reportValue?.[key] ?? ""; }); return reportValueMapping; }); + const formData = new FormData(); formData.append("file", targetFiles); formData.append("customFileName", `${formCode}.pdf`); formData.append("reportDatas", JSON.stringify(reportDatas)); formData.append("reportTempPath", selectTemp); - const reqeustCreateReport = await fetch( + const requestCreateReport = await fetch( `${origin}/api/pdftron/createVendorDataReports`, { method: "POST", body: formData } ); - if (reqeustCreateReport.ok) { - const blob = await reqeustCreateReport.blob(); - + if (requestCreateReport.ok) { + const blob = await requestCreateReport.blob(); saveAs(blob, `${formCode}.pdf`); - toastMessage.success("Report 다운로드 완료!"); } else { - const err = await reqeustCreateReport.json(); + const err = await requestCreateReport.json(); console.error("에러:", err); throw new Error(err.message); } @@ -184,100 +184,179 @@ export const FormDataReportBatchDialog: FC = ({ } }; + // New function to prepare the file for publishing + const prepareFileForPublishing = async () => { + setIsUploading(true); + + try { + const origin = await getOrigin(); + const targetFiles = selectedFiles[0]; + + const reportDatas = reportData.map((c) => { + const reportValue = stringifyAllValues(c); + const reportValueMapping: { [key: string]: any } = {}; + + columnsJSON.forEach((c2) => { + const { key } = c2; + reportValueMapping[key] = reportValue?.[key] ?? ""; + }); + + return reportValueMapping; + }); + + const formData = new FormData(); + formData.append("file", targetFiles); + formData.append("customFileName", `${formCode}.pdf`); + formData.append("reportDatas", JSON.stringify(reportDatas)); + formData.append("reportTempPath", selectTemp); + + const requestCreateReport = await fetch( + `${origin}/api/pdftron/createVendorDataReports`, + { method: "POST", body: formData } + ); + + if (requestCreateReport.ok) { + const blob = await requestCreateReport.blob(); + setGeneratedFileBlob(blob); + setPublishDialogOpen(true); + toastMessage.success("문서가 생성되었습니다. 발행 정보를 입력해주세요."); + } else { + const err = await requestCreateReport.json(); + console.error("에러:", err); + throw new Error(err.message); + } + } catch (err) { + console.error(err); + toast({ + title: "Error", + description: "문서 생성 중 오류가 발생했습니다.", + variant: "destructive", + }); + } finally { + setIsUploading(false); + } + }; + return ( - - - - Vendor Document Create - - Vendor Document Template을 선택하신 후 갑지를 업로드하여 주시기 - 바랍니다. - - -
- - -
-
- - - {({ maxSize }) => ( - <> - - -
- -
- 파일을 여기에 드롭하세요 - - 또는 클릭하여 파일을 선택하세요. 최대 크기:{" "} - {maxSize ? prettyBytes(maxSize) : "무제한"} - + <> + + + + Vendor Document Create + + Vendor Document Template을 선택하신 후 갑지를 업로드하여 주시기 + 바랍니다. + + +
+ + +
+
+ + + {({ maxSize }) => ( + <> + + +
+ +
+ 파일을 여기에 드롭하세요 + + 또는 클릭하여 파일을 선택하세요. 최대 크기:{" "} + {maxSize ? prettyBytes(maxSize) : "무제한"} + +
-
- - - - )} - -
- - {selectedFiles.length > 0 && ( -
-
-
- 선택된 파일 ({selectedFiles.length}) -
- {selectedFiles.length}개 파일 -
- - - + + + + )} +
- )} - - - - - -
+ + {selectedFiles.length > 0 && ( +
+
+
+ 선택된 파일 ({selectedFiles.length}) +
+ {selectedFiles.length}개 파일 +
+ + + +
+ )} + + + {/* Add the new Publish button */} + + + + + + + {/* Add the PublishDialog component */} + + ); }; diff --git a/components/form-data/form-data-table-columns.tsx b/components/form-data/form-data-table-columns.tsx index a1fbcae1..de479efb 100644 --- a/components/form-data/form-data-table-columns.tsx +++ b/components/form-data/form-data-table-columns.tsx @@ -1,6 +1,7 @@ import type { ColumnDef, Row } from "@tanstack/react-table"; import { ClientDataTableColumnHeaderSimple } from "../client-data-table/data-table-column-simple-header"; import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; import { Ellipsis } from "lucide-react"; import { formatDate } from "@/lib/utils"; import { @@ -17,6 +18,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { toast } from 'sonner'; + /** row 액션 관련 타입 */ export interface DataTableRowAction { row: Row; @@ -39,8 +41,8 @@ export interface DataTableColumnJSON { uom?: string; uomId?: string; shi?: boolean; - } + /** * getColumns 함수에 필요한 props * - TData: 테이블에 표시할 행(Row)의 타입 @@ -52,20 +54,77 @@ interface GetColumnsProps { >; setReportData: React.Dispatch>; tempCount: number; + // 체크박스 선택 관련 props + selectedRows?: Record; + onRowSelectionChange?: (updater: Record | ((prev: Record) => Record)) => void; } /** * getColumns 함수 * 1) columnsJSON 배열을 순회하면서 accessorKey / header / cell 등을 설정 - * 2) 마지막에 "Action" 칼럼(예: update 버튼) 추가 + * 2) 체크박스 컬럼 추가 (showBatchSelection이 true일 때) + * 3) 마지막에 "Action" 칼럼(예: update 버튼) 추가 */ export function getColumns({ columnsJSON, setRowAction, setReportData, tempCount, + selectedRows = {}, + onRowSelectionChange, }: GetColumnsProps): ColumnDef[] { - // (1) 기본 컬럼들 + const columns: ColumnDef[] = []; + + // (1) 체크박스 컬럼 (항상 표시) + const selectColumn: ColumnDef = { + id: "select", + header: ({ table }) => ( + { + table.toggleAllPageRowsSelected(!!value); + + // 모든 행 선택/해제 + if (onRowSelectionChange) { + const allRowsSelection: Record = {}; + table.getRowModel().rows.forEach((row) => { + allRowsSelection[row.id] = !!value; + }); + onRowSelectionChange(allRowsSelection); + } + }} + aria-label="Select all" + className="translate-y-[2px]" + /> + ), + cell: ({ row }) => ( + { + row.toggleSelected(!!value); + + // 개별 행 선택 상태 업데이트 + if (onRowSelectionChange) { + onRowSelectionChange(prev => ({ + ...prev, + [row.id]: !!value + })); + } + }} + aria-label="Select row" + className="translate-y-[2px]" + /> + ), + enableSorting: false, + enableHiding: false, + size: 40, + }; + columns.push(selectColumn); + + // (2) 기본 컬럼들 const baseColumns: ColumnDef[] = columnsJSON.map((col) => ({ accessorKey: col.key, header: ({ column }) => ( @@ -82,7 +141,7 @@ export function getColumns({ maxWidth: col.key === "TAG_NO" ? 120 : 150, isReadOnly: col.shi === true, // shi 정보를 메타데이터에 저장 }, - // (2) 실제 셀(cell) 렌더링: type에 따라 분기 가능 + // (3) 실제 셀(cell) 렌더링: type에 따라 분기 가능 cell: ({ row }) => { const cellValue = row.getValue(col.key); @@ -109,14 +168,6 @@ export function getColumns({ ); - // case "date": - // // 예: 날짜 포맷팅 - // // 실제론 dayjs / date-fns 등으로 포맷 - // if (!cellValue) return
- // const dateString = cellValue as string - // if (!dateString) return null - // return formatDate(new Date(dateString)) - case "LIST": // 예: select인 경우 label만 표시 return ( @@ -144,7 +195,9 @@ export function getColumns({ }, })); - // (3) 액션 칼럼 - update 버튼 예시 + columns.push(...baseColumns); + + // (4) 액션 칼럼 - update 버튼 예시 const actionColumn: ColumnDef = { id: "update", header: "", @@ -162,12 +215,6 @@ export function getColumns({ { - // 행에 있는 모든 필드가 읽기 전용인지 확인할 수도 있습니다 (선택 사항) - // const allColumnsReadOnly = columnsJSON.every(col => col.shi === true); - // if(allColumnsReadOnly) { - // toast.info("이 항목은 읽기 전용입니다."); - // return; - // } setRowAction({ row, type: "update" }); }} > @@ -176,11 +223,11 @@ export function getColumns({ { if(tempCount > 0){ - const { original } = row; - setReportData([original]); - } else { - toast.error("업로드된 Template File이 없습니다."); - } + const { original } = row; + setReportData([original]); + } else { + toast.error("업로드된 Template File이 없습니다."); + } }} > Create Document @@ -192,6 +239,8 @@ export function getColumns({ enablePinning: true, }; - // (4) 최종 반환 - return [...baseColumns, actionColumn]; + columns.push(actionColumn); + + // (5) 최종 반환 + return columns; } \ No newline at end of file diff --git a/components/form-data/form-data-table.tsx b/components/form-data/form-data-table.tsx index 05278375..0a76e145 100644 --- a/components/form-data/form-data-table.tsx +++ b/components/form-data/form-data-table.tsx @@ -131,9 +131,14 @@ export default function DynamicTable({ React.useState | null>(null); const [tableData, setTableData] = React.useState(dataJSON); + // 배치 선택 관련 상태 + const [selectedRows, setSelectedRows] = React.useState>({}); + // Update tableData when dataJSON changes React.useEffect(() => { setTableData(dataJSON); + // 데이터가 변경되면 선택 상태 초기화 + setSelectedRows({}); }, [dataJSON]); // 폴링 상태 관리를 위한 ref @@ -207,9 +212,27 @@ export default function DynamicTable({ } }, [projectId]); + // 선택된 행들의 실제 데이터 가져오기 + const getSelectedRowsData = React.useCallback(() => { + const selectedIndices = Object.keys(selectedRows).filter(key => selectedRows[key]); + return selectedIndices.map(index => tableData[parseInt(index)]).filter(Boolean); + }, [selectedRows, tableData]); + + // 선택된 행 개수 계산 + const selectedRowCount = React.useMemo(() => { + return Object.values(selectedRows).filter(Boolean).length; + }, [selectedRows]); + const columns = React.useMemo( - () => getColumns({ columnsJSON, setRowAction, setReportData, tempCount }), - [columnsJSON, setRowAction, setReportData, tempCount] + () => getColumns({ + columnsJSON, + setRowAction, + setReportData, + tempCount, + selectedRows, + onRowSelectionChange: setSelectedRows + }), + [columnsJSON, setRowAction, setReportData, tempCount, selectedRows] ); function mapColumnTypeToAdvancedFilterType( @@ -518,13 +541,22 @@ export default function DynamicTable({ } } - // Handle batch document check + // Handle batch document with smart selection logic const handleBatchDocument = () => { - if (tempCount > 0) { - setBatchDownDialog(true); - } else { + if (tempCount === 0) { toast.error("업로드된 Template File이 없습니다."); + return; } + + // 선택된 항목이 있으면 선택된 것만, 없으면 전체 사용 + const selectedData = getSelectedRowsData(); + if (selectedData.length > 0) { + toast.info(`선택된 ${selectedData.length}개 항목으로 배치 문서를 생성합니다.`); + } else { + toast.info(`전체 ${tableData.length}개 항목으로 배치 문서를 생성합니다.`); + } + + setBatchDownDialog(true); }; return ( @@ -534,6 +566,15 @@ export default function DynamicTable({ columns={columns} advancedFilterFields={advancedFilterFields} > + {/* 선택된 항목 수 표시 (선택된 항목이 있을 때만) */} + {selectedRowCount > 0 && ( +
+

+ {selectedRowCount}개 항목이 선택되었습니다. 배치 문서는 선택된 항목만으로 생성됩니다. +

+
+ )} + {/* 버튼 그룹 */}
{/* 태그 관리 드롭다운 */} @@ -583,6 +624,11 @@ export default function DynamicTable({ Batch Document + {selectedRowCount > 0 && ( + + {selectedRowCount} + + )} @@ -748,7 +794,7 @@ export default function DynamicTable({ open={batchDownDialog} setOpen={setBatchDownDialog} columnsJSON={columnsJSON} - reportData={tableData} + reportData={selectedRowCount > 0 ? getSelectedRowsData() : tableData} packageId={contractItemId} formCode={formCode} formId={formId} -- cgit v1.2.3