diff options
Diffstat (limited to 'components/form-data')
| -rw-r--r-- | components/form-data/form-data-table-columns.tsx | 47 | ||||
| -rw-r--r-- | components/form-data/form-data-table.tsx | 20 | ||||
| -rw-r--r-- | components/form-data/import-excel-form.tsx | 117 |
3 files changed, 147 insertions, 37 deletions
diff --git a/components/form-data/form-data-table-columns.tsx b/components/form-data/form-data-table-columns.tsx index de479efb..b088276e 100644 --- a/components/form-data/form-data-table-columns.tsx +++ b/components/form-data/form-data-table-columns.tsx @@ -57,6 +57,7 @@ interface GetColumnsProps<TData> { // 체크박스 선택 관련 props selectedRows?: Record<string, boolean>; onRowSelectionChange?: (updater: Record<string, boolean> | ((prev: Record<string, boolean>) => Record<string, boolean>)) => void; + editableFieldsMap?: Map<string, string[]>; // 새로 추가 } /** @@ -72,6 +73,7 @@ export function getColumns<TData extends object>({ tempCount, selectedRows = {}, onRowSelectionChange, + editableFieldsMap = new Map(), // 새로 추가 }: GetColumnsProps<TData>): ColumnDef<TData>[] { const columns: ColumnDef<TData>[] = []; @@ -139,42 +141,64 @@ export function getColumns<TData extends object>({ minWidth: 80, paddingFactor: 1.2, maxWidth: col.key === "TAG_NO" ? 120 : 150, - isReadOnly: col.shi === true, // shi 정보를 메타데이터에 저장 + isReadOnly: col.shi === true, }, - // (3) 실제 셀(cell) 렌더링: type에 따라 분기 가능 + cell: ({ row }) => { const cellValue = row.getValue(col.key); - // shi 속성이 true인 경우 적용할 스타일 - const isReadOnly = col.shi === true; - const readOnlyClass = isReadOnly ? "read-only-cell" : ""; + // 기본 읽기 전용 여부 (shi 속성 기반) + let isReadOnly = col.shi === true; - // 읽기 전용 셀의 스타일 (인라인 스타일과 클래스 동시 적용) + // 동적 읽기 전용 여부 계산 + if (!isReadOnly && col.key !== 'TAG_NO' && col.key !== 'TAG_DESC') { + const tagNo = row.getValue('TAG_NO') as string; + if (tagNo && editableFieldsMap.has(tagNo)) { + const editableFields = editableFieldsMap.get(tagNo) || []; + // 해당 TAG의 편집 가능 필드 목록에 없으면 읽기 전용 + isReadOnly = !editableFields.includes(col.key); + } else { + // TAG_NO가 없거나 editableFieldsMap에 없으면 읽기 전용 + isReadOnly = true; + } + } + + const readOnlyClass = isReadOnly ? "read-only-cell" : ""; const cellStyle = isReadOnly ? { backgroundColor: '#f5f5f5', color: '#666', cursor: 'not-allowed' } : {}; + // 툴팁 메시지 설정 + let tooltipMessage = ""; + if (isReadOnly) { + if (col.shi === true) { + tooltipMessage = "SHI 전용 필드입니다"; + } else if (col.key === 'TAG_NO' || col.key === 'TAG_DESC') { + tooltipMessage = "기본 필드는 수정할 수 없습니다"; + } else { + tooltipMessage = "이 TAG 클래스에서는 편집할 수 없는 필드입니다"; + } + } + // 데이터 타입별 처리 switch (col.type) { case "NUMBER": - // 예: number인 경우 콤마 등 표시 return ( <div className={readOnlyClass} style={cellStyle} - title={isReadOnly ? "읽기 전용 필드입니다" : ""} + title={tooltipMessage} > {cellValue ? Number(cellValue).toLocaleString() : ""} </div> ); case "LIST": - // 예: select인 경우 label만 표시 return ( <div className={readOnlyClass} style={cellStyle} - title={isReadOnly ? "읽기 전용 필드입니다" : ""} + title={tooltipMessage} > {String(cellValue ?? "")} </div> @@ -186,7 +210,7 @@ export function getColumns<TData extends object>({ <div className={readOnlyClass} style={cellStyle} - title={isReadOnly ? "읽기 전용 필드입니다" : ""} + title={tooltipMessage} > {String(cellValue ?? "")} </div> @@ -196,7 +220,6 @@ export function getColumns<TData extends object>({ })); columns.push(...baseColumns); - // (4) 액션 칼럼 - update 버튼 예시 const actionColumn: ColumnDef<TData> = { id: "update", diff --git a/components/form-data/form-data-table.tsx b/components/form-data/form-data-table.tsx index 0a76e145..6de6dd0b 100644 --- a/components/form-data/form-data-table.tsx +++ b/components/form-data/form-data-table.tsx @@ -110,6 +110,7 @@ export interface DynamicTableProps { formName?: string; objectCode?: string; mode: "IM" | "ENG"; // 모드 속성 + editableFieldsMap?: Map<string, string[]>; // 새로 추가 } export default function DynamicTable({ @@ -121,6 +122,7 @@ export default function DynamicTable({ projectId, mode = "IM", // 기본값 설정 formName = `${formCode}`, // Default form name based on formCode + editableFieldsMap = new Map(), // 새로 추가 }: DynamicTableProps) { const params = useParams(); const router = useRouter(); @@ -230,7 +232,8 @@ export default function DynamicTable({ setReportData, tempCount, selectedRows, - onRowSelectionChange: setSelectedRows + onRowSelectionChange: setSelectedRows, + editableFieldsMap }), [columnsJSON, setRowAction, setReportData, tempCount, selectedRows] ); @@ -397,26 +400,30 @@ export default function DynamicTable({ async function handleImportExcel(e: React.ChangeEvent<HTMLInputElement>) { const file = e.target.files?.[0]; if (!file) return; - + try { setIsImporting(true); - // Call the updated importExcelData function with direct save capability + // Call the updated importExcelData function with editableFieldsMap const result = await importExcelData({ file, tableData, columnsJSON, - formCode, // Pass formCode for direct save - contractItemId, // Pass contractItemId for direct save + formCode, + contractItemId, + editableFieldsMap, // 추가: 편집 가능 필드 정보 전달 onPendingChange: setIsImporting, onDataUpdate: (newData) => { - // This is called only after successful DB save setTableData(Array.isArray(newData) ? newData : newData(tableData)); } }); // If import and save was successful, refresh the page if (result.success) { + // Show additional info about skipped fields if any + if (result.skippedFields && result.skippedFields.length > 0) { + console.log("Import completed with some fields skipped:", result.skippedFields); + } router.refresh(); } } catch (error) { @@ -428,7 +435,6 @@ export default function DynamicTable({ setIsImporting(false); } } - // SEDP Send handler (with confirmation) function handleSEDPSendClick() { if (tableData.length === 0) { diff --git a/components/form-data/import-excel-form.tsx b/components/form-data/import-excel-form.tsx index d425a909..f32e44d8 100644 --- a/components/form-data/import-excel-form.tsx +++ b/components/form-data/import-excel-form.tsx @@ -1,17 +1,18 @@ -// lib/excelUtils.ts (continued) import ExcelJS from "exceljs"; import { saveAs } from "file-saver"; import { toast } from "sonner"; import { DataTableColumnJSON } from "./form-data-table-columns"; import { updateFormDataInDB } from "@/lib/forms/services"; import { decryptWithServerAction } from "../drm/drmUtils"; -// Assuming the previous types are defined above + +// Enhanced options interface with editableFieldsMap export interface ImportExcelOptions { file: File; tableData: GenericData[]; columnsJSON: DataTableColumnJSON[]; - formCode?: string; // Optional - provide to enable direct DB save - contractItemId?: number; // Optional - provide to enable direct DB save + formCode?: string; + contractItemId?: number; + editableFieldsMap?: Map<string, string[]>; // 새로 추가 onPendingChange?: (isPending: boolean) => void; onDataUpdate?: (updater: ((prev: GenericData[]) => GenericData[]) | GenericData[]) => void; } @@ -21,6 +22,7 @@ export interface ImportExcelResult { importedCount?: number; error?: any; message?: string; + skippedFields?: { tagNo: string, fields: string[] }[]; // 건너뛴 필드 정보 } export interface ExportExcelOptions { @@ -30,7 +32,6 @@ export interface ExportExcelOptions { onPendingChange?: (isPending: boolean) => void; } -// For typing consistency interface GenericData { [key: string]: any; } @@ -41,6 +42,7 @@ export async function importExcelData({ columnsJSON, formCode, contractItemId, + editableFieldsMap = new Map(), // 기본값으로 빈 Map onPendingChange, onDataUpdate }: ImportExcelOptions): Promise<ImportExcelResult> { @@ -59,7 +61,6 @@ export async function importExcelData({ }); const workbook = new ExcelJS.Workbook(); - // const arrayBuffer = await file.arrayBuffer(); const arrayBuffer = await decryptWithServerAction(file); await workbook.xlsx.load(arrayBuffer); @@ -127,6 +128,7 @@ export async function importExcelData({ const importedData: GenericData[] = []; const lastRowNumber = worksheet.lastRow?.number || 1; let errorCount = 0; + const skippedFieldsLog: { tagNo: string, fields: string[] }[] = []; // 건너뛴 필드 로그 // Process each data row for (let rowNum = 2; rowNum <= lastRowNumber; rowNum++) { @@ -135,21 +137,51 @@ export async function importExcelData({ if (!rowValues || rowValues.length <= 1) continue; // Skip empty rows let errorMessage = ""; + let warningMessage = ""; const rowObj: Record<string, any> = {}; + const skippedFields: string[] = []; // 현재 행에서 건너뛴 필드들 - // Get the TAG_NO first to identify existing data + // Get the TAG_NO first to identify existing data and editable fields const tagNoColIndex = keyToIndexMap.get("TAG_NO"); const tagNo = tagNoColIndex ? String(rowValues[tagNoColIndex] ?? "").trim() : ""; const existingRowData = existingDataMap.get(tagNo); + + // Get editable fields for this specific TAG + const editableFields = editableFieldsMap.has(tagNo) ? editableFieldsMap.get(tagNo)! : []; // Process each column columnsJSON.forEach((col) => { const colIndex = keyToIndexMap.get(col.key); if (colIndex === undefined) return; - // Check if this column should be ignored (col.shi === true) + // Determine if this field is editable + let isFieldEditable = true; + let skipReason = ""; + + // 1. Check if this is a SHI-only field if (col.shi === true) { - // Use existing value instead of Excel value + isFieldEditable = false; + skipReason = "SHI-only field"; + } + // 2. Check if this field is editable based on TAG class attributes + else if (col.key !== "TAG_NO" && col.key !== "TAG_DESC") { + // For non-basic fields, check if they're in the editable list + if (tagNo && editableFieldsMap.has(tagNo)) { + if (!editableFields.includes(col.key)) { + isFieldEditable = false; + skipReason = "Not editable for this TAG class"; + } + } else if (tagNo) { + // If TAG exists but no editable fields info, treat as not editable + isFieldEditable = false; + skipReason = "No editable fields info for this TAG"; + } + } + // 3. TAG_NO and TAG_DESC are always considered basic fields + // (They should be editable, but you might want to add specific logic here) + + // If field is not editable, use existing value or default + if (!isFieldEditable) { if (existingRowData && existingRowData[col.key] !== undefined) { rowObj[col.key] = existingRowData[col.key]; } else { @@ -165,9 +197,13 @@ export async function importExcelData({ break; } } + + // Log skipped field + skippedFields.push(`${col.label} (${skipReason})`); return; // Skip processing Excel value for this column } + // Process Excel value for editable fields const cellValue = rowValues[colIndex] ?? ""; let stringVal = String(cellValue).trim(); @@ -212,6 +248,15 @@ export async function importExcelData({ } }); + // Log skipped fields for this TAG + if (skippedFields.length > 0) { + skippedFieldsLog.push({ + tagNo: tagNo, + fields: skippedFields + }); + warningMessage += `Skipped ${skippedFields.length} non-editable fields. `; + } + // Validate TAG_NO const tagNum = rowObj["TAG_NO"]; if (!tagNum) { @@ -225,10 +270,23 @@ export async function importExcelData({ row.getCell(lastColIndex).value = errorMessage.trim(); errorCount++; } else { + // Add warning message to Excel if there are skipped fields + if (warningMessage) { + row.getCell(lastColIndex).value = `WARNING: ${warningMessage.trim()}`; + } importedData.push(rowObj); } } + // Show summary of skipped fields + if (skippedFieldsLog.length > 0) { + const totalSkippedFields = skippedFieldsLog.reduce((sum, log) => sum + log.fields.length, 0); + console.log("Skipped fields summary:", skippedFieldsLog); + toast.info( + `${totalSkippedFields} non-editable fields were skipped across ${skippedFieldsLog.length} rows. Check console for details.` + ); + } + // If there are validation errors, download error report and exit if (errorCount > 0) { const outBuffer = await workbook.xlsx.writeBuffer(); @@ -236,7 +294,11 @@ export async function importExcelData({ toast.error( `There are ${errorCount} error row(s). Please check downloaded file.` ); - return { success: false, error: "Data validation errors" }; + return { + success: false, + error: "Data validation errors", + skippedFields: skippedFieldsLog + }; } // If we reached here, all data is valid @@ -310,13 +372,15 @@ export async function importExcelData({ return { success: true, importedCount: successCount, - message: `Partially successful: ${successCount} rows updated, ${errorCount} errors` + message: `Partially successful: ${successCount} rows updated, ${errorCount} errors`, + skippedFields: skippedFieldsLog }; } else { return { success: false, error: "All updates failed", - message: errors.join("\n") + message: errors.join("\n"), + skippedFields: skippedFieldsLog }; } } @@ -326,16 +390,25 @@ export async function importExcelData({ onDataUpdate(() => mergedData); } - toast.success(`Successfully updated ${successCount} rows`); + const successMessage = skippedFieldsLog.length > 0 + ? `Successfully updated ${successCount} rows (some non-editable fields were preserved)` + : `Successfully updated ${successCount} rows`; + + toast.success(successMessage); return { success: true, importedCount: successCount, - message: "All data imported and saved to database" + message: "All data imported and saved to database", + skippedFields: skippedFieldsLog }; } catch (saveError) { console.error("Failed to save imported data:", saveError); toast.error("Failed to save imported data to database"); - return { success: false, error: saveError }; + return { + success: false, + error: saveError, + skippedFields: skippedFieldsLog + }; } } else { // Fall back to just updating local state if DB parameters aren't provided @@ -343,8 +416,16 @@ export async function importExcelData({ onDataUpdate(() => mergedData); } - toast.success(`Imported ${importedData.length} rows successfully (local only)`); - return { success: true, importedCount: importedData.length }; + const successMessage = skippedFieldsLog.length > 0 + ? `Imported ${importedData.length} rows successfully (some fields preserved)` + : `Imported ${importedData.length} rows successfully`; + + toast.success(`${successMessage} (local only)`); + return { + success: true, + importedCount: importedData.length, + skippedFields: skippedFieldsLog + }; } } catch (err) { @@ -354,4 +435,4 @@ export async function importExcelData({ } finally { if (onPendingChange) onPendingChange(false); } -}
\ No newline at end of file +} |
