From 4b76297a7b9f36fdbffe58b152e5ba418b0e6237 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 12 May 2025 11:32:59 +0000 Subject: (대표님) S-EDP 관련 components/form-data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/form-data/sedp-compare-dialog.tsx | 434 ++++++++++++++++++--------- 1 file changed, 290 insertions(+), 144 deletions(-) (limited to 'components/form-data/sedp-compare-dialog.tsx') diff --git a/components/form-data/sedp-compare-dialog.tsx b/components/form-data/sedp-compare-dialog.tsx index 461a3630..37fe18ed 100644 --- a/components/form-data/sedp-compare-dialog.tsx +++ b/components/form-data/sedp-compare-dialog.tsx @@ -3,11 +3,12 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from " import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Loader, RefreshCw, AlertCircle, CheckCircle, Info } from "lucide-react"; +import { Loader, RefreshCw, AlertCircle, CheckCircle, Info, EyeOff } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { toast } from "sonner"; import { DataTableColumnJSON } from "./form-data-table-columns"; import { ExcelDownload } from "./sedp-excel-download"; +import { Switch } from "../ui/switch"; interface SEDPCompareDialogProps { isOpen: boolean; @@ -38,12 +39,12 @@ const DisplayValue = ({ value, uom, isSedp = false }: { value: any; uom?: string if (value === "" || value === null || value === undefined) { return (empty); } - + // SEDP 값은 UOM을 표시하지 않음 (이미 포함되어 있다고 가정) if (isSedp) { return {value}; } - + // 로컬 값은 UOM과 함께 표시 return ( @@ -88,27 +89,72 @@ export function SEDPCompareDialog({ const [comparisonResults, setComparisonResults] = React.useState([]); const [activeTab, setActiveTab] = React.useState("all"); const [isExporting, setIsExporting] = React.useState(false); - + const [missingTags, setMissingTags] = React.useState<{ + localOnly: { tagNo: string; tagDesc: string }[]; + sedpOnly: { tagNo: string; tagDesc: string }[]; + }>( + { localOnly: [], sedpOnly: [] } + ); + // 추가: 차이점만 표시하는 옵션 + const [showOnlyDifferences, setShowOnlyDifferences] = React.useState(true); + // Stats for summary const totalTags = comparisonResults.length; const matchingTags = comparisonResults.filter(r => r.isMatching).length; const nonMatchingTags = totalTags - matchingTags; + const totalMissingTags = missingTags.localOnly.length + missingTags.sedpOnly.length; // Get column label map and UOM map for better display const { columnLabelMap, columnUomMap } = React.useMemo(() => { const labelMap: Record = {}; const uomMap: Record = {}; - + columnsJSON.forEach(col => { labelMap[col.key] = col.displayLabel || col.label; if (col.uom) { uomMap[col.key] = col.uom; } }); - + return { columnLabelMap: labelMap, columnUomMap: uomMap }; }, [columnsJSON]); + // Filter results based on active tab + const filteredResults = React.useMemo(() => { + switch (activeTab) { + case "matching": + return comparisonResults.filter(r => r.isMatching); + case "differences": + return comparisonResults.filter(r => !r.isMatching); + case "all": + default: + return comparisonResults; + } + }, [comparisonResults, activeTab]); + + // 변경: 표시할 컬럼 결정 (차이가 있는 컬럼만 or 모든 컬럼) + const columnsToDisplay = React.useMemo(() => { + // 기본 컬럼 (TAG_NO, TAG_DESC 제외) + const columns = columnsJSON.filter(col => col.key !== "TAG_NO" && col.key !== "TAG_DESC"); + + if (!showOnlyDifferences) { + return columns; // 모든 컬럼 표시 + } + + // 하나라도 차이가 있는 속성만 필터링 + const columnsWithDifferences = new Set(); + comparisonResults.forEach(result => { + result.attributes.forEach(attr => { + if (!attr.isMatching) { + columnsWithDifferences.add(attr.key); + } + }); + }); + + // 차이가 있는 컬럼만 반환 + return columns.filter(col => columnsWithDifferences.has(col.key)); + }, [columnsJSON, comparisonResults, showOnlyDifferences]); + const fetchAndCompareData = React.useCallback(async () => { if (!projectCode || !formCode) { toast.error("Project code or form code is missing"); @@ -117,110 +163,120 @@ export function SEDPCompareDialog({ try { setIsLoading(true); - + // Fetch data from SEDP API const sedpData = await fetchTagDataFromSEDP(projectCode, formCode); - + // Get the table name from the response const tableName = Object.keys(sedpData)[0]; const sedpTagEntries = sedpData[tableName] || []; - + // Create a map of SEDP data by TAG_NO for quick lookup const sedpTagMap = new Map(); sedpTagEntries.forEach((entry: any) => { const tagNo = entry.TAG_NO; const attributesMap = new Map(); - + // Convert attributes array to map for easier access if (Array.isArray(entry.ATTRIBUTES)) { entry.ATTRIBUTES.forEach((attr: any) => { attributesMap.set(attr.ATT_ID, attr.VALUE); }); } - + sedpTagMap.set(tagNo, { tagDesc: entry.TAG_DESC, attributes: attributesMap }); }); - - // Compare with local table data - const results: ComparisonResult[] = tableData.map(localItem => { - const tagNo = localItem.TAG_NO; - const sedpItem = sedpTagMap.get(tagNo); - - // If tag not found in SEDP data - if (!sedpItem) { + + // Create sets for finding missing tags + const localTagNos = new Set(tableData.map(item => item.TAG_NO)); + const sedpTagNos = new Set(sedpTagMap.keys()); + + // Find missing tags + const localOnlyTags = tableData + .filter(item => !sedpTagMap.has(item.TAG_NO)) + .map(item => ({ tagNo: item.TAG_NO, tagDesc: item.TAG_DESC || "" })); + + const sedpOnlyTags = Array.from(sedpTagMap.entries()) + .filter(([tagNo]) => !localTagNos.has(tagNo)) + .map(([tagNo, data]) => ({ tagNo, tagDesc: data.tagDesc || "" })); + + setMissingTags({ + localOnly: localOnlyTags, + sedpOnly: sedpOnlyTags + }); + + // Compare with local table data (only for tags that exist in both systems) + const results: ComparisonResult[] = tableData + .filter(localItem => sedpTagMap.has(localItem.TAG_NO)) + .map(localItem => { + const tagNo = localItem.TAG_NO; + const sedpItem = sedpTagMap.get(tagNo); + + // Compare attributes + const attributeComparisons = columnsJSON + .filter(col => col.key !== "TAG_NO" && col.key !== "TAG_DESC") + .map(col => { + const localValue = localItem[col.key]; + const sedpValue = sedpItem.attributes.get(col.key); + const uom = columnUomMap[col.key]; + + // Compare values (with type handling) + let isMatching = false; + + // Special case: Empty SEDP value and 0 local value + if ((sedpValue === "" || sedpValue === null || sedpValue === undefined) && + (localValue === 0 || localValue === "0")) { + isMatching = true; + } else { + // Standard string comparison for other cases + const normalizedLocal = localValue === undefined || localValue === null ? "" : String(localValue).trim(); + const normalizedSedp = sedpValue === undefined || sedpValue === null ? "" : String(sedpValue).trim(); + isMatching = normalizedLocal === normalizedSedp; + } + + return { + key: col.key, + label: columnLabelMap[col.key] || col.key, + localValue, + sedpValue, + isMatching, + uom + }; + }); + + // Item is matching if all attributes match + const isItemMatching = attributeComparisons.every(attr => attr.isMatching); + return { tagNo, tagDesc: localItem.TAG_DESC || "", - isMatching: false, - attributes: columnsJSON - .filter(col => col.key !== "TAG_NO" && col.key !== "TAG_DESC") - .map(col => ({ - key: col.key, - label: columnLabelMap[col.key] || col.key, - localValue: localItem[col.key], - sedpValue: null, - isMatching: false, - uom: columnUomMap[col.key] - })) + isMatching: isItemMatching, + attributes: attributeComparisons }; - } - - // Compare attributes - const attributeComparisons = columnsJSON - .filter(col => col.key !== "TAG_NO" && col.key !== "TAG_DESC") - .map(col => { - const localValue = localItem[col.key]; - const sedpValue = sedpItem.attributes.get(col.key); - const uom = columnUomMap[col.key]; - - // Compare values (with type handling) - let isMatching = false; - - // 문자열 비교 - // Normalize empty values - const normalizedLocal = localValue === undefined || localValue === null ? "" : String(localValue).trim(); - const normalizedSedp = sedpValue === undefined || sedpValue === null ? "" : String(sedpValue).trim(); - isMatching = normalizedLocal === normalizedSedp; - - - return { - key: col.key, - label: columnLabelMap[col.key] || col.key, - localValue, - sedpValue, - isMatching, - uom - }; - }); - - // Item is matching if all attributes match - const isItemMatching = attributeComparisons.every(attr => attr.isMatching); - - return { - tagNo, - tagDesc: localItem.TAG_DESC || "", - isMatching: isItemMatching, - attributes: attributeComparisons - }; - }); - + }); + setComparisonResults(results); - + // Show summary in toast const matchCount = results.filter(r => r.isMatching).length; const nonMatchCount = results.length - matchCount; - + const missingCount = localOnlyTags.length + sedpOnlyTags.length; + + if (missingCount > 0) { + toast.error(`Found ${missingCount} missing tags between systems`); + } + if (nonMatchCount > 0) { toast.warning(`Found ${nonMatchCount} tags with differences`); - } else if (results.length > 0) { + } else if (results.length > 0 && missingCount === 0) { toast.success(`All ${results.length} tags match with SEDP data`); - } else { + } else if (results.length === 0 && missingCount === 0) { toast.info("No tags to compare"); } - + } catch (error) { console.error("SEDP comparison error:", error); toast.error(`Failed to compare with SEDP: ${error instanceof Error ? error.message : 'Unknown error'}`); @@ -228,41 +284,38 @@ export function SEDPCompareDialog({ setIsLoading(false); } }, [projectCode, formCode, tableData, columnsJSON, fetchTagDataFromSEDP, columnLabelMap, columnUomMap]); - + // Fetch data when dialog opens React.useEffect(() => { if (isOpen) { fetchAndCompareData(); } }, [isOpen, fetchAndCompareData]); - - // Filter results based on active tab - const filteredResults = React.useMemo(() => { - switch (activeTab) { - case "matching": - return comparisonResults.filter(r => r.isMatching); - case "differences": - return comparisonResults.filter(r => !r.isMatching); - case "all": - default: - return comparisonResults; - } - }, [comparisonResults, activeTab]); return ( - - SEDP 데이터 비교 + SEDP 데이터 비교 +
+
+ + +
- - {matchingTags} / {totalTags} 일치 + + {matchingTags} / {totalTags} 일치 {totalMissingTags > 0 ? `(${totalMissingTags} 누락)` : ''} -
- +
- + {/* 범례 추가 */}
- + 전체 태그 ({totalTags}) 차이 있음 ({nonMatchingTags}) 일치함 ({matchingTags}) + 0 ? "text-red-500" : ""}> + 누락된 태그 ({totalMissingTags}) + - + {isLoading ? (
데이터 비교 중...
+ ) : activeTab === "missing" ? ( + // Missing tags tab content +
+ {missingTags.localOnly.length > 0 && ( +
+

로컬에만 있는 태그 ({missingTags.localOnly.length})

+ + + + Tag Number + Tag Description + + + + {missingTags.localOnly.map((tag) => ( + + {tag.tagNo} + {tag.tagDesc} + + ))} + +
+
+ )} + + {missingTags.sedpOnly.length > 0 && ( +
+

SEDP에만 있는 태그 ({missingTags.sedpOnly.length})

+ + + + Tag Number + Tag Description + + + + {missingTags.sedpOnly.map((tag) => ( + + {tag.tagNo} + {tag.tagDesc} + + ))} + +
+
+ )} + + {totalMissingTags === 0 && ( +
+ 모든 태그가 양쪽 시스템에 존재합니다 +
+ )} +
) : filteredResults.length > 0 ? ( - - - - Tag Number - Tag Description - 상태 - 차이점 - - - - {filteredResults.map((result) => { - // Find differences to display - const differences = result.attributes.filter(attr => !attr.isMatching); - - return ( + // 개선된 테이블 구조 +
+
+ + + Tag Number + Tag Description + 상태 + + {/* 동적으로 속성 열 헤더 생성 */} + {columnsToDisplay.length > 0 ? ( + columnsToDisplay.map(col => ( + + {columnLabelMap[col.key] || col.key} + {columnUomMap[col.key] && ( + + ({columnUomMap[col.key]}) + + )} + + )) + ) : ( + +
+ + 차이가 있는 항목이 없습니다 +
+
+ )} +
+
+ + {filteredResults.map((result) => ( - {result.tagNo} - {result.tagDesc} - + + {result.tagNo} + + + {result.tagDesc} + + {result.isMatching ? ( @@ -326,30 +457,44 @@ export function SEDPCompareDialog({ )} - - {differences.length > 0 ? ( -
- {differences.map((diff) => ( -
- {diff.label}: - - - - - - -
- ))} -
- ) : ( + + {/* 각 속성에 대한 셀 동적 생성 */} + {columnsToDisplay.length > 0 ? ( + columnsToDisplay.map(col => { + const attr = result.attributes.find(a => a.key === col.key); + + if (!attr) return -; + + return ( + + {attr.isMatching ? ( + + ) : ( +
+
+ +
+
+ +
+
+ )} +
+ ); + }) + ) : ( + 모든 값이 일치합니다 - )} - +
+ )}
- ); - })} -
-
+ ))} + + + ) : (
현재 필터에 맞는 태그가 없습니다 @@ -357,12 +502,13 @@ export function SEDPCompareDialog({ )} - + -- cgit v1.2.3