summaryrefslogtreecommitdiff
path: root/components/form-data
diff options
context:
space:
mode:
Diffstat (limited to 'components/form-data')
-rw-r--r--components/form-data/form-data-table.tsx10
-rw-r--r--components/form-data/import-excel-form.tsx312
-rw-r--r--components/form-data/spreadJS-dialog copy.tsx2
-rw-r--r--components/form-data/spreadJS-dialog.tsx71
4 files changed, 215 insertions, 180 deletions
diff --git a/components/form-data/form-data-table.tsx b/components/form-data/form-data-table.tsx
index 3d8b1438..09745bb0 100644
--- a/components/form-data/form-data-table.tsx
+++ b/components/form-data/form-data-table.tsx
@@ -99,7 +99,7 @@ export default function DynamicTable({
const router = useRouter();
const lng = (params?.lng as string) || "ko";
const { t } = useTranslation(lng, "engineering");
- const pathname = usePathname();
+
const [rowAction, setRowAction] =
React.useState<DataTableRowAction<GenericData> | null>(null);
@@ -115,14 +115,14 @@ export default function DynamicTable({
const [formStats, setFormStats] = React.useState<FormStatusByVendor | null>(null);
const [isLoadingStats, setIsLoadingStats] = React.useState(true);
- const isEVCPPath = pathname.includes('evcp');
+
React.useEffect(() => {
const fetchFormStats = async () => {
try {
setIsLoadingStats(true);
// getFormStatusByVendor 서버 액션 직접 호출
- const data = await getFormStatusByVendor(projectId, formCode);
+ const data = await getFormStatusByVendor(projectId, contractItemId, formCode);
if (data && data.length > 0) {
setFormStats(data[0]);
@@ -674,7 +674,7 @@ export default function DynamicTable({
return (
<>
- {!isEVCPPath && (
+
<div className="mb-6">
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-6">
{/* Tag Count */}
@@ -810,7 +810,7 @@ export default function DynamicTable({
</Card>
</div>
</div>
- )}
+
<ClientDataTable
data={tableData}
diff --git a/components/form-data/import-excel-form.tsx b/components/form-data/import-excel-form.tsx
index df6ab6c1..0e757891 100644
--- a/components/form-data/import-excel-form.tsx
+++ b/components/form-data/import-excel-form.tsx
@@ -56,22 +56,22 @@ interface GenericData {
* Check if a field is editable for a specific TAG_NO
*/
function isFieldEditable(
- column: DataTableColumnJSON,
- tagNo: string,
+ column: DataTableColumnJSON,
+ tagNo: string,
editableFieldsMap: Map<string, string[]>
): boolean {
// SHI-only fields (shi === "OUT" or shi === null) are never editable
if (column.shi === "OUT" || column.shi === null) return false;
-
+
// System fields are never editable
if (column.key === "TAG_NO" || column.key === "TAG_DESC" || column.key === "status") return false;
-
+
// If no editableFieldsMap provided, assume all non-SHI fields are editable
if (!editableFieldsMap || editableFieldsMap.size === 0) return true;
-
+
// If TAG_NO not in map, no fields are editable
if (!editableFieldsMap.has(tagNo)) return false;
-
+
// Check if this field is in the editable fields list for this TAG_NO
const editableFields = editableFieldsMap.get(tagNo) || [];
return editableFields.includes(column.key);
@@ -86,7 +86,7 @@ function createImportErrorSheet(workbook: ExcelJS.Workbook, errors: ImportError[
if (existingErrorSheet) {
workbook.removeWorksheet("Import_Errors");
}
-
+
const errorSheet = workbook.addWorksheet("Import_Errors");
// Add header error section if exists
@@ -114,7 +114,7 @@ function createImportErrorSheet(workbook: ExcelJS.Workbook, errors: ImportError[
// Data validation errors section
const startRow = errorSheet.rowCount + 1;
-
+
// Summary row
errorSheet.addRow([`DATA VALIDATION ERRORS: ${errors.length} errors found`]);
const summaryRow = errorSheet.getRow(startRow);
@@ -143,7 +143,7 @@ function createImportErrorSheet(workbook: ExcelJS.Workbook, errors: ImportError[
const headerRow = errorSheet.getRow(errorSheet.rowCount);
headerRow.font = { bold: true, color: { argb: "FFFFFFFF" } };
headerRow.alignment = { horizontal: "center" };
-
+
headerRow.eachCell((cell) => {
cell.fill = {
type: "pattern",
@@ -167,7 +167,7 @@ function createImportErrorSheet(workbook: ExcelJS.Workbook, errors: ImportError[
// Color code by error type
errorRow.eachCell((cell) => {
let bgColor = "FFFFFFFF"; // Default white
-
+
switch (error.errorType) {
case "MISSING_TAG_NO":
bgColor = "FFFFCCCC"; // Light red
@@ -285,8 +285,8 @@ export async function importExcelData({
saveAs(new Blob([outBuffer]), `import-error-report_${Date.now()}.xlsx`);
toast.error(`Header validation failed. ${headerErrors.length} errors found. Check downloaded error report.`);
- return {
- success: false,
+ return {
+ success: false,
error: "Header validation errors",
errorCount: headerErrors.length,
hasErrors: true
@@ -312,30 +312,28 @@ export async function importExcelData({
for (let rowNum = 2; rowNum <= lastRowNumber; rowNum++) {
const row = worksheet.getRow(rowNum);
const rowValues = row.values as ExcelJS.CellValue[];
- if (!rowValues || rowValues.length <= 1) continue; // Skip empty rows
+ // 실제 값이 있는지 확인 (빈 문자열이 아닌 실제 내용)
+ const hasAnyValue = rowValues && rowValues.slice(1).some(val =>
+ val !== undefined &&
+ val !== null &&
+ String(val).trim() !== ""
+ );
+
+ if (!hasAnyValue) {
+ console.log(`Row ${rowNum} is empty, skipping...`);
+ continue; // 완전히 빈 행은 건너뛰기
+ }
const rowObj: Record<string, any> = {};
const skippedFields: string[] = []; // 현재 행에서 건너뛴 필드들
let hasErrors = false;
-
+
// Get the TAG_NO first to identify existing data
const tagNoColIndex = keyToIndexMap.get("TAG_NO");
const tagNo = tagNoColIndex ? String(rowValues[tagNoColIndex] ?? "").trim() : "";
const existingRowData = existingDataMap.get(tagNo);
- // Validate TAG_NO first
- if (!tagNo) {
- validationErrors.push({
- tagNo: `Row-${rowNum}`,
- rowIndex: rowNum,
- columnKey: "TAG_NO",
- columnLabel: "TAG NO",
- errorType: "MISSING_TAG_NO",
- errorMessage: "TAG_NO is empty or missing",
- currentValue: tagNo,
- });
- hasErrors = true;
- } else if (!existingTagNumbers.has(tagNo)) {
+ if (!existingTagNumbers.has(tagNo)) {
validationErrors.push({
tagNo: tagNo,
rowIndex: rowNum,
@@ -355,7 +353,7 @@ export async function importExcelData({
// Check if this field is editable for this TAG_NO
const fieldEditable = isFieldEditable(col, tagNo, editableFieldsMap);
-
+
if (!fieldEditable) {
// If field is not editable, preserve existing value
if (existingRowData && existingRowData[col.key] !== undefined) {
@@ -373,7 +371,7 @@ export async function importExcelData({
break;
}
}
-
+
// Determine skip reason
let skipReason = "";
if (col.shi === "OUT" || col.shi === null) {
@@ -383,10 +381,10 @@ export async function importExcelData({
} else {
skipReason = "Not editable for this TAG";
}
-
+
// Log skipped field
skippedFields.push(`${col.label} (${skipReason})`);
-
+
// Check if Excel contains a value for a read-only field and warn
const cellValue = rowValues[colIndex] ?? "";
const stringVal = String(cellValue).trim();
@@ -403,7 +401,7 @@ export async function importExcelData({
});
hasErrors = true;
}
-
+
return; // Skip processing Excel value for this column
}
@@ -492,13 +490,13 @@ export async function importExcelData({
const outBuffer = await workbook.xlsx.writeBuffer();
saveAs(new Blob([outBuffer]), `import-error-report_${Date.now()}.xlsx`);
-
+
toast.error(
`Data validation failed. ${validationErrors.length} errors found across ${new Set(validationErrors.map(e => e.tagNo)).size} TAG(s). Check downloaded error report.`
);
-
- return {
- success: false,
+
+ return {
+ success: false,
error: "Data validation errors",
errorCount: validationErrors.length,
hasErrors: true,
@@ -510,14 +508,14 @@ export async function importExcelData({
// Create locally merged data for UI update
const mergedData = [...tableData];
const dataMap = new Map<string, GenericData>();
-
+
// Map existing data by TAG_NO
mergedData.forEach(item => {
if (item.TAG_NO) {
dataMap.set(item.TAG_NO, item);
}
});
-
+
// Update with imported data
importedData.forEach(item => {
if (item.TAG_NO) {
@@ -530,137 +528,137 @@ export async function importExcelData({
});
// If formCode and contractItemId are provided, save directly to DB
- // importExcelData 함수에서 DB 저장 부분
-if (formCode && contractItemId) {
- try {
- // 배치 업데이트 함수 호출
- const result = await updateFormDataBatchInDB(
- formCode,
- contractItemId,
- importedData // 모든 imported rows를 한번에 전달
- );
-
- if (result.success) {
- // 로컬 상태 업데이트
- if (onDataUpdate) {
- onDataUpdate(() => mergedData);
- }
-
- // 성공 메시지 구성
- const { updatedCount, notFoundTags } = result.data || {};
-
- let message = `Successfully updated ${updatedCount || importedData.length} rows`;
-
- // 건너뛴 필드가 있는 경우
- if (skippedFieldsLog.length > 0) {
- const totalSkippedFields = skippedFieldsLog.reduce((sum, log) => sum + log.fields.length, 0);
- message += ` (${totalSkippedFields} read-only fields preserved)`;
- }
-
- // 찾을 수 없는 TAG가 있는 경우
- if (notFoundTags && notFoundTags.length > 0) {
- console.warn("Tags not found in database:", notFoundTags);
- message += `. Warning: ${notFoundTags.length} tags not found in database`;
- }
-
- toast.success(message);
-
- return {
- success: true,
- importedCount: updatedCount || importedData.length,
- message: message,
- errorCount: 0,
- hasErrors: false,
- skippedFields: skippedFieldsLog,
- notFoundTags: notFoundTags
- };
-
- } else {
- // 배치 업데이트 실패
- console.error("Batch update failed:", result.message);
-
- // 부분 성공인 경우
- if (result.data?.updatedCount > 0) {
- // 부분적으로라도 업데이트된 경우 로컬 상태 업데이트
- if (onDataUpdate) {
- onDataUpdate(() => mergedData);
- }
-
- toast.warning(
- `Partially updated: ${result.data.updatedCount} of ${importedData.length} rows updated. ` +
- `${result.data.failedCount || 0} failed.`
+ // importExcelData 함수에서 DB 저장 부분
+ if (formCode && contractItemId) {
+ try {
+ // 배치 업데이트 함수 호출
+ const result = await updateFormDataBatchInDB(
+ formCode,
+ contractItemId,
+ importedData // 모든 imported rows를 한번에 전달
);
-
- return {
- success: true, // 부분 성공도 success로 처리
- importedCount: result.data.updatedCount,
- message: result.message,
- errorCount: result.data.failedCount || 0,
- hasErrors: true,
- skippedFields: skippedFieldsLog
- };
-
- } else {
- // 완전 실패
- toast.error(result.message || "Failed to update data to database");
-
+
+ if (result.success) {
+ // 로컬 상태 업데이트
+ if (onDataUpdate) {
+ onDataUpdate(() => mergedData);
+ }
+
+ // 성공 메시지 구성
+ const { updatedCount, notFoundTags } = result.data || {};
+
+ let message = `Successfully updated ${updatedCount || importedData.length} rows`;
+
+ // 건너뛴 필드가 있는 경우
+ if (skippedFieldsLog.length > 0) {
+ const totalSkippedFields = skippedFieldsLog.reduce((sum, log) => sum + log.fields.length, 0);
+ message += ` (${totalSkippedFields} read-only fields preserved)`;
+ }
+
+ // 찾을 수 없는 TAG가 있는 경우
+ if (notFoundTags && notFoundTags.length > 0) {
+ console.warn("Tags not found in database:", notFoundTags);
+ message += `. Warning: ${notFoundTags.length} tags not found in database`;
+ }
+
+ toast.success(message);
+
+ return {
+ success: true,
+ importedCount: updatedCount || importedData.length,
+ message: message,
+ errorCount: 0,
+ hasErrors: false,
+ skippedFields: skippedFieldsLog,
+ notFoundTags: notFoundTags
+ };
+
+ } else {
+ // 배치 업데이트 실패
+ console.error("Batch update failed:", result.message);
+
+ // 부분 성공인 경우
+ if (result.data?.updatedCount > 0) {
+ // 부분적으로라도 업데이트된 경우 로컬 상태 업데이트
+ if (onDataUpdate) {
+ onDataUpdate(() => mergedData);
+ }
+
+ toast.warning(
+ `Partially updated: ${result.data.updatedCount} of ${importedData.length} rows updated. ` +
+ `${result.data.failedCount || 0} failed.`
+ );
+
+ return {
+ success: true, // 부분 성공도 success로 처리
+ importedCount: result.data.updatedCount,
+ message: result.message,
+ errorCount: result.data.failedCount || 0,
+ hasErrors: true,
+ skippedFields: skippedFieldsLog
+ };
+
+ } else {
+ // 완전 실패
+ toast.error(result.message || "Failed to update data to database");
+
+ return {
+ success: false,
+ error: result.message,
+ errorCount: importedData.length,
+ hasErrors: true,
+ skippedFields: skippedFieldsLog
+ };
+ }
+ }
+
+ } catch (saveError) {
+ // 예외 발생 처리
+ console.error("Failed to save imported data:", saveError);
+
+ const errorMessage = saveError instanceof Error
+ ? saveError.message
+ : "Unknown error occurred";
+
+ toast.error(`Database update failed: ${errorMessage}`);
+
return {
success: false,
- error: result.message,
+ error: saveError,
+ message: errorMessage,
errorCount: importedData.length,
hasErrors: true,
skippedFields: skippedFieldsLog
};
}
+
+ } else {
+ // formCode나 contractItemId가 없는 경우 - 로컬 업데이트만
+ if (onDataUpdate) {
+ onDataUpdate(() => mergedData);
+ }
+
+ const successMessage = skippedFieldsLog.length > 0
+ ? `Imported ${importedData.length} rows successfully (read-only fields preserved)`
+ : `Imported ${importedData.length} rows successfully`;
+
+ toast.success(`${successMessage} (local only - no database connection)`);
+
+ return {
+ success: true,
+ importedCount: importedData.length,
+ message: "Data imported locally only",
+ errorCount: 0,
+ hasErrors: false,
+ skippedFields: skippedFieldsLog
+ };
}
-
- } catch (saveError) {
- // 예외 발생 처리
- console.error("Failed to save imported data:", saveError);
-
- const errorMessage = saveError instanceof Error
- ? saveError.message
- : "Unknown error occurred";
-
- toast.error(`Database update failed: ${errorMessage}`);
-
- return {
- success: false,
- error: saveError,
- message: errorMessage,
- errorCount: importedData.length,
- hasErrors: true,
- skippedFields: skippedFieldsLog
- };
- }
-
-} else {
- // formCode나 contractItemId가 없는 경우 - 로컬 업데이트만
- if (onDataUpdate) {
- onDataUpdate(() => mergedData);
- }
-
- const successMessage = skippedFieldsLog.length > 0
- ? `Imported ${importedData.length} rows successfully (read-only fields preserved)`
- : `Imported ${importedData.length} rows successfully`;
-
- toast.success(`${successMessage} (local only - no database connection)`);
-
- return {
- success: true,
- importedCount: importedData.length,
- message: "Data imported locally only",
- errorCount: 0,
- hasErrors: false,
- skippedFields: skippedFieldsLog
- };
-}
-
+
} catch (err) {
console.error("Excel import error:", err);
toast.error("Excel import failed.");
- return {
- success: false,
+ return {
+ success: false,
error: err,
errorCount: 1,
hasErrors: true
diff --git a/components/form-data/spreadJS-dialog copy.tsx b/components/form-data/spreadJS-dialog copy.tsx
index d001463e..8f7c3bc6 100644
--- a/components/form-data/spreadJS-dialog copy.tsx
+++ b/components/form-data/spreadJS-dialog copy.tsx
@@ -1389,7 +1389,7 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
<div className="flex items-center gap-4 text-sm">
<span className="font-medium text-blue-600">
Template Type: {
- templateType === 'SPREAD_LIST' ? 'List View (SPREAD_LIST)' :
+ template/home/ec2-user/evcp/components/form-dataType === 'SPREAD_LIST' ? 'List View (SPREAD_LIST)' :
templateType === 'SPREAD_ITEM' ? 'Item View (SPREAD_ITEM)' :
'Grid List View (GRD_LIST)'
}
diff --git a/components/form-data/spreadJS-dialog.tsx b/components/form-data/spreadJS-dialog.tsx
index af1a3dca..375c097c 100644
--- a/components/form-data/spreadJS-dialog.tsx
+++ b/components/form-data/spreadJS-dialog.tsx
@@ -176,13 +176,13 @@ export function TemplateViewDialog({
}, []);
const determineTemplateType = React.useCallback((template: TemplateItem): 'SPREAD_LIST' | 'SPREAD_ITEM' | 'GRD_LIST' | null => {
- if (template.TMPL_TYPE === "SPREAD_LIST" && (template.SPR_LST_SETUP?.CONTENT || template.SPR_ITM_LST_SETUP?.CONTENT)) {
+ if (template?.TMPL_TYPE === "SPREAD_LIST" && (template?.SPR_LST_SETUP?.CONTENT || template?.SPR_ITM_LST_SETUP?.CONTENT)) {
return 'SPREAD_LIST';
}
- if (template.TMPL_TYPE === "SPREAD_ITEM" && (template.SPR_LST_SETUP?.CONTENT || template.SPR_ITM_LST_SETUP?.CONTENT)) {
+ if (template?.TMPL_TYPE === "SPREAD_ITEM" && (template?.SPR_LST_SETUP?.CONTENT || template?.SPR_ITM_LST_SETUP?.CONTENT)) {
return 'SPREAD_ITEM';
}
- if (template.GRD_LST_SETUP && columnsJSON.length > 0) {
+ if (template?.GRD_LST_SETUP && columnsJSON.length > 0) {
return 'GRD_LIST';
}
return null;
@@ -221,10 +221,10 @@ export function TemplateViewDialog({
DATA_SHEETS: []
}
};
-
+
setAvailableTemplates([defaultGrdTemplate]);
- setSelectedTemplateId('DEFAULT_GRD_LIST');
- setTemplateType('GRD_LIST');
+ // setSelectedTemplateId('DEFAULT_GRD_LIST');
+ // setTemplateType('GRD_LIST');
console.log('📋 Created default GRD_LIST template');
}
return;
@@ -238,7 +238,7 @@ export function TemplateViewDialog({
}
const validTemplates = templates.filter(isValidTemplate);
-
+
// 유효한 템플릿이 없지만 columnsJSON이 있으면 기본 GRD_LIST 추가
if (validTemplates.length === 0 && columnsJSON && columnsJSON.length > 0) {
const defaultGrdTemplate: TemplateItem = {
@@ -261,11 +261,11 @@ export function TemplateViewDialog({
DATA_SHEETS: []
}
};
-
+
validTemplates.push(defaultGrdTemplate);
console.log('📋 Added default GRD_LIST template to empty template list');
}
-
+
setAvailableTemplates(validTemplates);
if (validTemplates.length > 0 && !selectedTemplateId) {
@@ -1251,13 +1251,13 @@ export function TemplateViewDialog({
}
});
- // 🔧 마지막에 activeSheetName으로 다시 전환
- if (activeSheetName && spread.getSheetFromName(activeSheetName)) {
- spread.setActiveSheet(activeSheetName);
- activeSheet = spread.getActiveSheet();
- }
+ // 🔧 마지막에 activeSheetName으로 다시 전환
+ if (activeSheetName && spread.getSheetFromName(activeSheetName)) {
+ spread.setActiveSheet(activeSheetName);
+ activeSheet = spread.getActiveSheet();
+ }
+
-
});
}
}
@@ -1292,6 +1292,32 @@ export function TemplateViewDialog({
}
}, [selectedTemplate, templateType, selectedRow, tableData, updateProgress, getSafeActiveSheet, createGrdListTableOptimized, setBatchValues, setBatchStyles, setupSheetProtectionAndEvents, setCellMappings, createCellStyle, isFieldEditable, columnsJSON, setupOptimizedListValidation, parseCellAddress, ensureRowCapacity, getCellAddress]);
+ React.useEffect(() => {
+ if (!selectedTemplateId) {
+ const only = availableTemplates[0];
+ const type = determineTemplateType(only);
+
+ // 선택되어 있지 않다면 자동 선택
+ if (selectedTemplateId !== only.TMPL_ID) {
+ setSelectedTemplateId(only.TMPL_ID);
+ setTemplateType(type);
+ }
+
+ // 이미 스프레드가 마운트되어 있다면 즉시 초기화(선택 변경만으로도 리렌더되지만 안전하게 보강)
+ if (currentSpread) {
+ initSpread(currentSpread, only);
+ }
+ }
+ }, [
+ availableTemplates,
+ selectedTemplateId,
+ currentSpread,
+ determineTemplateType,
+ initSpread,
+ setTemplateType,
+ setSelectedTemplateId
+ ]);
+
const handleSaveChanges = React.useCallback(async () => {
if (!currentSpread || !hasChanges) {
toast.info("No changes to save");
@@ -1454,6 +1480,8 @@ export function TemplateViewDialog({
const isDataValid = templateType === 'SPREAD_ITEM' ? !!selectedRow : tableData.length > 0;
const dataCount = templateType === 'SPREAD_ITEM' ? 1 : tableData.length;
+
+
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent
@@ -1463,7 +1491,8 @@ export function TemplateViewDialog({
<DialogTitle>SEDP Template - {formCode}</DialogTitle>
<DialogDescription>
<div className="space-y-3">
- {availableTemplates.length > 1 && (
+ {availableTemplates.length > 0 ? (
+ // 템플릿이 2개 이상일 때: Select 박스 표시
<div className="flex items-center gap-4">
<span className="text-sm font-medium">Template:</span>
<Select value={selectedTemplateId} onValueChange={handleTemplateChange}>
@@ -1479,7 +1508,15 @@ export function TemplateViewDialog({
</SelectContent>
</Select>
</div>
- )}
+ ) : availableTemplates.length === 1 ? (
+ // 템플릿이 정확히 1개일 때: 템플릿 이름을 텍스트로 표시
+ <div className="flex items-center gap-4">
+ <span className="text-sm font-medium">Template:</span>
+ <span className="text-sm text-blue-600 font-medium">
+ {availableTemplates[0].NAME} ({availableTemplates[0].TMPL_TYPE})
+ </span>
+ </div>
+ ) : null}
{selectedTemplate && (
<div className="flex items-center gap-4 text-sm">