summaryrefslogtreecommitdiff
path: root/components/form-data/form-data-table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/form-data/form-data-table.tsx')
-rw-r--r--components/form-data/form-data-table.tsx265
1 files changed, 172 insertions, 93 deletions
diff --git a/components/form-data/form-data-table.tsx b/components/form-data/form-data-table.tsx
index 6de6dd0b..9a438957 100644
--- a/components/form-data/form-data-table.tsx
+++ b/components/form-data/form-data-table.tsx
@@ -13,21 +13,22 @@ import {
} from "./form-data-table-columns";
import type { DataTableAdvancedFilterField } from "@/types/table";
import { Button } from "../ui/button";
-import {
- Download,
- Loader,
- Save,
- Upload,
- Plus,
- Tag,
- TagsIcon,
- FileText,
+import {
+ Download,
+ Loader,
+ Save,
+ Upload,
+ Plus,
+ Tag,
+ TagsIcon,
+ FileText,
FileSpreadsheet,
FileOutput,
- Clipboard,
+ Clipboard,
Send,
GitCompareIcon,
- RefreshCcw
+ RefreshCcw,
+ Trash2
} from "lucide-react";
import { toast } from "sonner";
import {
@@ -54,16 +55,17 @@ import { exportExcelData } from "./export-excel-form";
import { SEDPConfirmationDialog, SEDPStatusDialog } from "./sedp-components";
import { SEDPCompareDialog } from "./sedp-compare-dialog";
import { getSEDPToken } from "@/lib/sedp/sedp-token";
+import { DeleteFormDataDialog } from "./delete-form-data-dialog"; // 새로 추가
async function fetchTagDataFromSEDP(projectCode: string, formCode: string): Promise<any> {
try {
// Get the token
const apiKey = await getSEDPToken();
-
+
// Define the API base URL
const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api';
-
+
// Make the API call
const response = await fetch(
`${SEDP_API_BASE_URL}/Data/GetPubData`,
@@ -82,12 +84,12 @@ async function fetchTagDataFromSEDP(projectCode: string, formCode: string): Prom
})
}
);
-
+
if (!response.ok) {
const errorText = await response.text();
throw new Error(`SEDP API request failed: ${response.status} ${response.statusText} - ${errorText}`);
}
-
+
const data = await response.json();
return data;
} catch (error: any) {
@@ -119,7 +121,7 @@ export default function DynamicTable({
contractItemId,
formCode,
formId,
- projectId,
+ projectId,
mode = "IM", // 기본값 설정
formName = `${formCode}`, // Default form name based on formCode
editableFieldsMap = new Map(), // 새로 추가
@@ -134,19 +136,21 @@ export default function DynamicTable({
const [tableData, setTableData] = React.useState<GenericData[]>(dataJSON);
// 배치 선택 관련 상태
- const [selectedRows, setSelectedRows] = React.useState<Record<string, boolean>>({});
+ const [selectedRowsData, setSelectedRowsData] = React.useState<GenericData[]>([]);
+ const [clearSelection, setClearSelection] = React.useState(false);
+ // 삭제 관련 상태 간소화
+ const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
+ const [deleteTarget, setDeleteTarget] = React.useState<GenericData[]>([]);
// Update tableData when dataJSON changes
React.useEffect(() => {
setTableData(dataJSON);
- // 데이터가 변경되면 선택 상태 초기화
- setSelectedRows({});
}, [dataJSON]);
-
+
// 폴링 상태 관리를 위한 ref
const pollingRef = React.useRef<NodeJS.Timeout | null>(null);
const [syncId, setSyncId] = React.useState<string | null>(null);
-
+
// Separate loading states for different operations
const [isSyncingTags, setIsSyncingTags] = React.useState(false);
const [isImporting, setIsImporting] = React.useState(false);
@@ -154,10 +158,10 @@ export default function DynamicTable({
const [isSaving, setIsSaving] = React.useState(false);
const [isSendingSEDP, setIsSendingSEDP] = React.useState(false);
const [isLoadingTags, setIsLoadingTags] = React.useState(false);
-
+
// Any operation in progress
const isAnyOperationPending = isSyncingTags || isImporting || isExporting || isSaving || isSendingSEDP || isLoadingTags;
-
+
// SEDP dialogs state
const [sedpConfirmOpen, setSedpConfirmOpen] = React.useState(false);
const [sedpStatusOpen, setSedpStatusOpen] = React.useState(false);
@@ -168,11 +172,11 @@ export default function DynamicTable({
errorCount: 0,
totalCount: 0
});
-
+
// SEDP compare dialog state
const [sedpCompareOpen, setSedpCompareOpen] = React.useState(false);
const [projectCode, setProjectCode] = React.useState<string>('');
-
+
const [tempUpDialog, setTempUpDialog] = React.useState(false);
const [reportData, setReportData] = React.useState<GenericData[]>([]);
const [batchDownDialog, setBatchDownDialog] = React.useState(false);
@@ -216,26 +220,22 @@ export default function DynamicTable({
// 선택된 행들의 실제 데이터 가져오기
const getSelectedRowsData = React.useCallback(() => {
- const selectedIndices = Object.keys(selectedRows).filter(key => selectedRows[key]);
- return selectedIndices.map(index => tableData[parseInt(index)]).filter(Boolean);
- }, [selectedRows, tableData]);
+ return selectedRowsData;
+ }, [selectedRowsData]);
// 선택된 행 개수 계산
const selectedRowCount = React.useMemo(() => {
- return Object.values(selectedRows).filter(Boolean).length;
- }, [selectedRows]);
+ return selectedRowsData.length;
+ }, [selectedRowsData]);
const columns = React.useMemo(
- () => getColumns<GenericData>({
- columnsJSON,
- setRowAction,
- setReportData,
+ () => getColumns<GenericData>({
+ columnsJSON,
+ setRowAction,
+ setReportData,
tempCount,
- selectedRows,
- onRowSelectionChange: setSelectedRows,
- editableFieldsMap
}),
- [columnsJSON, setRowAction, setReportData, tempCount, selectedRows]
+ [columnsJSON, setRowAction, setReportData, tempCount]
);
function mapColumnTypeToAdvancedFilterType(
@@ -297,30 +297,30 @@ export default function DynamicTable({
setIsSyncingTags(false);
}
}
-
+
// ENG 모드: 태그 가져오기 함수
const handleGetTags = async () => {
try {
setIsLoadingTags(true);
-
+
// API 엔드포인트 호출 - 작업 시작만 요청
const response = await fetch('/api/cron/form-tags/start', {
method: 'POST',
- body: JSON.stringify({ projectCode ,formCode ,contractItemId })
+ body: JSON.stringify({ projectCode, formCode, contractItemId })
});
-
+
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to start tag import');
}
-
+
const data = await response.json();
-
+
// 작업 ID 저장
if (data.syncId) {
setSyncId(data.syncId);
toast.info('Tag import started. This may take a while...');
-
+
// 상태 확인을 위한 폴링 시작
startPolling(data.syncId);
} else {
@@ -336,49 +336,49 @@ export default function DynamicTable({
setIsLoadingTags(false);
}
};
-
+
const startPolling = (id: string) => {
// 이전 폴링이 있다면 제거
if (pollingRef.current) {
clearInterval(pollingRef.current);
}
-
+
// 5초마다 상태 확인
pollingRef.current = setInterval(async () => {
try {
const response = await fetch(`/api/cron/form-tags/status?id=${id}`);
-
+
if (!response.ok) {
throw new Error('Failed to get tag import status');
}
-
+
const data = await response.json();
-
+
if (data.status === 'completed') {
// 폴링 중지
if (pollingRef.current) {
clearInterval(pollingRef.current);
pollingRef.current = null;
}
-
+
router.refresh();
-
+
// 상태 초기화
setIsLoadingTags(false);
setSyncId(null);
-
+
// 성공 메시지 표시
toast.success(
`Tags imported successfully! ${data.result?.processedCount || 0} items processed.`
);
-
+
} else if (data.status === 'failed') {
// 에러 처리
if (pollingRef.current) {
clearInterval(pollingRef.current);
pollingRef.current = null;
}
-
+
setIsLoadingTags(false);
setSyncId(null);
toast.error(data.error || 'Import failed');
@@ -395,15 +395,16 @@ export default function DynamicTable({
}
}, 5000); // 5초마다 체크
};
-
- // Excel Import - Modified to directly save to DB
+
+ // Excel Import - Fixed version with proper loading state management
async function handleImportExcel(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
-
+
try {
- setIsImporting(true);
-
+ // Don't set setIsImporting here - let importExcelData handle it completely
+ // setIsImporting(true); // Remove this line
+
// Call the updated importExcelData function with editableFieldsMap
const result = await importExcelData({
file,
@@ -411,28 +412,37 @@ export default function DynamicTable({
columnsJSON,
formCode,
contractItemId,
- editableFieldsMap, // 추가: 편집 가능 필드 정보 전달
- onPendingChange: setIsImporting,
+ // editableFieldsMap, // 추가: 편집 가능 필드 정보 전달
+ onPendingChange: setIsImporting, // Let importExcelData handle loading state
onDataUpdate: (newData) => {
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();
+
+ // Ensure loading state is cleared before refresh
+ setIsImporting(false);
+
+ // Add a small delay to ensure state update is processed
+ setTimeout(() => {
+ router.refresh();
+ }, 100);
}
} catch (error) {
console.error("Import failed:", error);
toast.error("Failed to import Excel data");
+ // Ensure loading state is cleared on error
+ setIsImporting(false);
} finally {
// Always clear the file input value
e.target.value = "";
- setIsImporting(false);
+ // Don't set setIsImporting(false) here since we handle it above
}
}
// SEDP Send handler (with confirmation)
@@ -441,23 +451,23 @@ export default function DynamicTable({
toast.error("No data to send to SEDP");
return;
}
-
+
// Open confirmation dialog
setSedpConfirmOpen(true);
}
-
+
// Handle SEDP compare button click
function handleSEDPCompareClick() {
if (tableData.length === 0) {
toast.error("No data to compare with SEDP");
return;
}
-
+
if (!projectCode) {
toast.error("Project code is not available");
return;
}
-
+
// Open compare dialog
setSedpCompareOpen(true);
}
@@ -466,7 +476,7 @@ export default function DynamicTable({
async function handleSEDPSendConfirmed() {
try {
setIsSendingSEDP(true);
-
+
// Validate data
const invalidData = tableData.filter((item) => !item.TAG_NO?.trim());
if (invalidData.length > 0) {
@@ -479,13 +489,14 @@ export default function DynamicTable({
const sedpResult = await sendFormDataToSEDP(
formCode, // Send formCode instead of formName
projectId, // Project ID
+ contractItemId,
tableData, // Table data
columnsJSON // Column definitions
);
// Close confirmation dialog
setSedpConfirmOpen(false);
-
+
// Set status data based on result
if (sedpResult.success) {
setSedpStatusData({
@@ -504,16 +515,16 @@ export default function DynamicTable({
totalCount: tableData.length
});
}
-
+
// Open status dialog to show result
setSedpStatusOpen(true);
-
+
// Refresh the route to get fresh data
router.refresh();
-
+
} catch (err: any) {
console.error("SEDP error:", err);
-
+
// Set error status
setSedpStatusData({
status: 'error',
@@ -522,16 +533,16 @@ export default function DynamicTable({
errorCount: tableData.length,
totalCount: tableData.length
});
-
+
// Close confirmation and open status
setSedpConfirmOpen(false);
setSedpStatusOpen(true);
-
+
} finally {
setIsSendingSEDP(false);
}
}
-
+
// Template Export
async function handleExportExcel() {
try {
@@ -561,24 +572,76 @@ export default function DynamicTable({
} else {
toast.info(`전체 ${tableData.length}개 항목으로 배치 문서를 생성합니다.`);
}
-
+
setBatchDownDialog(true);
};
+ // 개별 행 삭제 핸들러
+ const handleDeleteRow = (rowData: GenericData) => {
+ setDeleteTarget([rowData]);
+ setDeleteDialogOpen(true);
+ };
+
+ // 배치 삭제 핸들러
+ const handleBatchDelete = () => {
+ const selectedData = getSelectedRowsData();
+ if (selectedData.length === 0) {
+ toast.error("삭제할 항목을 선택해주세요.");
+ return;
+ }
+
+ setDeleteTarget(selectedData);
+ setDeleteDialogOpen(true);
+ };
+
+ // 삭제 성공 후 처리
+ const handleDeleteSuccess = () => {
+ // 로컬 상태에서 삭제된 항목들 제거
+ const tagNosToDelete = deleteTarget
+ .map(item => item.TAG_NO)
+ .filter(Boolean);
+
+ setTableData(prev =>
+ prev.filter(item => !tagNosToDelete.includes(item.TAG_NO))
+ );
+
+ // 선택 상태 초기화
+ setSelectedRowsData([]);
+ setClearSelection(prev => !prev); // ClientDataTable의 선택 상태 초기화
+
+ // 삭제 타겟 초기화
+ setDeleteTarget([]);
+ };
+
+ // rowAction 처리 부분 수정
+ React.useEffect(() => {
+ if (rowAction?.type === "delete") {
+ handleDeleteRow(rowAction.row.original);
+ setRowAction(null); // 액션 초기화
+ }
+ }, [rowAction]);
+
+
return (
<>
<ClientDataTable
data={tableData}
columns={columns}
advancedFilterFields={advancedFilterFields}
+ autoSizeColumns
+ onSelectedRowsChange={setSelectedRowsData}
+ clearSelection={clearSelection}
>
{/* 선택된 항목 수 표시 (선택된 항목이 있을 때만) */}
{selectedRowCount > 0 && (
- <div className="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-md">
- <p className="text-sm text-blue-700">
- {selectedRowCount}개 항목이 선택되었습니다. 배치 문서는 선택된 항목만으로 생성됩니다.
- </p>
- </div>
+ <Button
+ variant="destructive"
+ size="sm"
+ onClick={handleBatchDelete}
+ >
+ <Trash2 className="mr-2 size-4" />
+ Delete ({selectedRowCount})
+ </Button>
)}
{/* 버튼 그룹 */}
@@ -613,7 +676,7 @@ export default function DynamicTable({
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
-
+
{/* 리포트 관리 드롭다운 */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -707,6 +770,7 @@ export default function DynamicTable({
</ClientDataTable>
{/* Modal dialog for tag update */}
+ {/* Modal dialog for tag update */}
<UpdateTagSheet
open={rowAction?.type === "update"}
onOpenChange={(open) => {
@@ -716,21 +780,36 @@ export default function DynamicTable({
rowData={rowAction?.row.original ?? null}
formCode={formCode}
contractItemId={contractItemId}
+ editableFieldsMap={editableFieldsMap} // 새로 추가
onUpdateSuccess={(updatedValues) => {
// Update the specific row in tableData when a single row is updated
if (rowAction?.row.original?.TAG_NO) {
const tagNo = rowAction.row.original.TAG_NO;
- setTableData(prev =>
- prev.map(item =>
+ setTableData(prev =>
+ prev.map(item =>
item.TAG_NO === tagNo ? updatedValues : item
)
);
}
}}
/>
-
+ <DeleteFormDataDialog
+ formData={deleteTarget}
+ formCode={formCode}
+ contractItemId={contractItemId}
+ open={deleteDialogOpen}
+ onOpenChange={(open) => {
+ if (!open) {
+ setDeleteDialogOpen(false);
+ setDeleteTarget([]);
+ }
+ }}
+ onSuccess={handleDeleteSuccess}
+ showTrigger={false}
+ />
+
{/* Dialog for adding tags */}
- <AddFormTagDialog
+ <AddFormTagDialog
projectId={projectId}
formCode={formCode}
formName={`Form ${formCode}`}
@@ -738,7 +817,7 @@ export default function DynamicTable({
open={addTagDialogOpen}
onOpenChange={setAddTagDialogOpen}
/>
-
+
{/* SEDP Confirmation Dialog */}
<SEDPConfirmationDialog
isOpen={sedpConfirmOpen}
@@ -748,7 +827,7 @@ export default function DynamicTable({
tagCount={tableData.length}
isLoading={isSendingSEDP}
/>
-
+
{/* SEDP Status Dialog */}
<SEDPStatusDialog
isOpen={sedpStatusOpen}
@@ -759,7 +838,7 @@ export default function DynamicTable({
errorCount={sedpStatusData.errorCount}
totalCount={sedpStatusData.totalCount}
/>
-
+
{/* SEDP Compare Dialog */}
<SEDPCompareDialog
isOpen={sedpCompareOpen}
@@ -770,7 +849,7 @@ export default function DynamicTable({
formCode={formCode}
fetchTagDataFromSEDP={fetchTagDataFromSEDP}
/>
-
+
{/* Other dialogs */}
{tempUpDialog && (
<FormDataReportTempUploadDialog