import * as React from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
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 { Input } from "@/components/ui/input";
import { Loader, RefreshCw, AlertCircle, CheckCircle, Info, EyeOff, ChevronDown, ChevronRight, Search } 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";
import { Card, CardContent } from "@/components/ui/card";
import { useTranslation } from "@/i18n/client"
import { useParams } from "next/navigation"
import { fetchTagDataFromSEDP } from "@/lib/forms-plant/sedp-actions";
interface SEDPCompareDialogProps {
isOpen: boolean;
onClose: () => void;
tableData: unknown[];
columnsJSON: DataTableColumnJSON[];
projectCode: string;
formCode: string;
projectType:string;
packageCode:string;
}
interface ComparisonResult {
tagNo: string;
tagDesc: string;
isMatching: boolean;
attributes: {
key: string;
label: string;
localValue: unknown;
sedpValue: unknown;
isMatching: boolean;
uom?: string;
}[];
}
// Component for formatting display value with UOM
const DisplayValue = ({ value, uom, isSedp = false }: { value: unknown; uom?: string; isSedp?: boolean }) => {
if (value === "" || value === null || value === undefined) {
return (empty);
}
// SEDP 값은 UOM을 표시하지 않음 (이미 포함되어 있다고 가정)
if (isSedp) {
return {value};
}
// 로컬 값은 UOM과 함께 표시
return (
{value}
{uom && {uom}}
);
};
export function SEDPCompareDialog({
isOpen,
onClose,
tableData,
columnsJSON,
projectCode,
formCode,
projectType,
packageCode
}: SEDPCompareDialogProps) {
const params = useParams() || {}
const lng = params.lng ? String(params.lng) : "ko"
const { t } = useTranslation(lng, "engineering")
// 범례 컴포넌트
const ColorLegend = () => {
return (
);
};
// 확장 가능한 차이점 표시 컴포넌트
const DifferencesCard = ({
attributes,
columnLabelMap,
showOnlyDifferences
}: {
attributes: ComparisonResult['attributes'];
columnLabelMap: Record;
showOnlyDifferences: boolean;
}) => {
const attributesToShow = showOnlyDifferences
? attributes.filter(attr => !attr.isMatching)
: attributes;
if (attributesToShow.length === 0) {
return (
{t("messages.allAttributesMatch")}
);
}
return (
{attributesToShow.map((attr) => (
{attr.label}
{attr.uom && ({attr.uom})}
{attr.isMatching ? (
) : (
)}
))}
);
};
const [isLoading, setIsLoading] = React.useState(false);
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);
const [searchTerm, setSearchTerm] = React.useState("");
const [expandedRows, setExpandedRows] = React.useState>(new Set());
// 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 and search results
const filteredResults = React.useMemo(() => {
let results = comparisonResults;
// Filter by tab
switch (activeTab) {
case "matching":
results = results.filter(r => r.isMatching);
break;
case "differences":
results = results.filter(r => !r.isMatching);
break;
case "all":
default:
break;
}
// Apply search filter
if (searchTerm.trim()) {
const search = searchTerm.toLowerCase();
results = results.filter(r =>
r.tagNo.toLowerCase().includes(search) ||
r.tagDesc.toLowerCase().includes(search)
);
}
return results;
}, [comparisonResults, activeTab, searchTerm]);
// Toggle row expansion
const toggleRowExpansion = (tagNo: string) => {
const newExpanded = new Set(expandedRows);
if (newExpanded.has(tagNo)) {
newExpanded.delete(tagNo);
} else {
newExpanded.add(tagNo);
}
setExpandedRows(newExpanded);
};
// Auto-expand rows with differences when switching to differences tab
React.useEffect(() => {
if (activeTab === "differences") {
const newExpanded = new Set();
filteredResults.filter(r => !r.isMatching).forEach(r => {
newExpanded.add(r.tagNo);
});
setExpandedRows(newExpanded);
}
}, [activeTab, filteredResults]);
const fetchAndCompareData = React.useCallback(async () => {
if (!projectCode || !formCode) {
toast.error("Project code or form code is missing");
return;
}
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();
const packageCodeAttId = projectType === "ship" ? "CM3003" : "ME5074";
const tagEntries = sedpTagEntries.filter(entry => {
if (Array.isArray(entry.ATTRIBUTES)) {
const packageCodeAttr = entry.ATTRIBUTES.find(attr => attr.ATT_ID === packageCodeAttId);
if (packageCodeAttr && packageCodeAttr.VALUE === packageCode) {
return true;
}
}
return false;
});
tagEntries.forEach((entry: Record) => {
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: Record) => {
attributesMap.set(attr.ATT_ID, attr.VALUE);
});
}
sedpTagMap.set(tagNo, {
tagDesc: entry.TAG_DESC,
attributes: attributesMap
});
});
// 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" && col.key !== "status"&& col.key !== "CLS_ID")
.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: 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 && missingCount === 0) {
toast.success(`All ${results.length} tags match with SEDP data`);
} 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'}`);
} finally {
setIsLoading(false);
}
}, [projectCode, formCode, tableData, columnsJSON, columnLabelMap, columnUomMap]);
// Fetch data when dialog opens
React.useEffect(() => {
if (isOpen) {
fetchAndCompareData();
}
}, [isOpen, fetchAndCompareData]);
return (
);
}