179 lines
6.4 KiB
TypeScript
179 lines
6.4 KiB
TypeScript
import * as React from "react";
|
|
import { useState, useEffect } from "react";
|
|
import { makeStyles } from "@fluentui/react-components";
|
|
import { SheetInfo, SheetMappingStatus, ConsolidateSettings } from "../models";
|
|
import SheetSelector from "./SheetSelector";
|
|
import ColumnMapper from "./ColumnMapper";
|
|
import StatusNotifier from "./StatusNotifier";
|
|
import { getAvailableSheets, detectHeadersAndColumns, detectHeadersForSingleSheetRow, consolidateData } from "../excelLogic";
|
|
|
|
interface AppProps {
|
|
title: string;
|
|
}
|
|
|
|
declare const DEV_MODE: boolean;
|
|
|
|
const useStyles = makeStyles({
|
|
root: {
|
|
minHeight: "100vh",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
},
|
|
});
|
|
|
|
const App: React.FC<AppProps> = () => {
|
|
const styles = useStyles();
|
|
|
|
type WizardStep = "select_sheets" | "map_columns" | "done";
|
|
|
|
const [step, setStep] = useState<WizardStep>("select_sheets");
|
|
const [sheets, setSheets] = useState<SheetInfo[]>([]);
|
|
const [selectedSheetIds, setSelectedSheetIds] = useState<string[]>([]);
|
|
const [sheetMappings, setSheetMappings] = useState<SheetMappingStatus[]>([]);
|
|
|
|
const [status, setStatus] = useState<"idle" | "success" | "warning" | "error">("idle");
|
|
const [statusMessage, setStatusMessage] = useState("");
|
|
const [isConsolidating, setIsConsolidating] = useState(false);
|
|
|
|
const [settings, setSettings] = useState<ConsolidateSettings>({
|
|
colorNew: "#d4edda", // light green
|
|
colorChanged: "#fff3cd", // light yellow/orange
|
|
colorDeleted: "#f8d7da", // light red
|
|
colorDuplicate: "#ffe6cc", // light orange for duplicates
|
|
});
|
|
|
|
useEffect(() => {
|
|
// Load internal sheets on mount
|
|
getAvailableSheets().then(setSheets).catch(err => {
|
|
setStatus("error");
|
|
setStatusMessage("Fehler beim Laden der Arbeitsblätter: " + String(err));
|
|
});
|
|
}, []);
|
|
|
|
const handleExternalSheetsLoaded = (newExternalSheets: SheetInfo[]) => {
|
|
setSheets(prev => [...prev, ...newExternalSheets]);
|
|
};
|
|
|
|
const handleNextToMapping = async () => {
|
|
const selectedSheets = sheets.filter(s => selectedSheetIds.includes(s.id));
|
|
if (selectedSheets.length === 0) return;
|
|
|
|
try {
|
|
const mappings = await detectHeadersAndColumns(selectedSheets);
|
|
setSheetMappings(mappings);
|
|
setStep("map_columns");
|
|
setStatus("idle");
|
|
setStatusMessage("");
|
|
} catch (err) {
|
|
setStatus("error");
|
|
setStatusMessage("Fehler beim Analysieren der Blätter: " + String(err));
|
|
}
|
|
};
|
|
|
|
const handleHeaderRowChange = async (sheetName: string, newRowIndex: number) => {
|
|
try {
|
|
// Wir müssen das richtige SheetInfo Objekt finden (für isExternal check)
|
|
const sheetInfo = sheets.find(s => s.name === sheetName);
|
|
if (!sheetInfo) return;
|
|
|
|
// Re-detect columns for exactly this row
|
|
const newMapping = await detectHeadersForSingleSheetRow(sheetInfo, newRowIndex);
|
|
setSheetMappings(prev => prev.map(m => m.sheetName === sheetName ? newMapping : m));
|
|
} catch (err) {
|
|
setStatus("error");
|
|
setStatusMessage("Fehler beim Neuladen der Zeile " + newRowIndex + " für Blatt " + sheetName);
|
|
}
|
|
};
|
|
|
|
const handleMappingChange = (sheetName: string, targetCol: string, sourceColIndex: number) => {
|
|
setSheetMappings(prev => prev.map(sheet => {
|
|
if (sheet.sheetName !== sheetName) return sheet;
|
|
|
|
const newMappings = sheet.mappings.map(m =>
|
|
m.targetColumn === targetCol ? { ...m, sourceColumnIndex: sourceColIndex } : m
|
|
);
|
|
return { ...sheet, mappings: newMappings };
|
|
}));
|
|
};
|
|
|
|
const handleConsolidate = async () => {
|
|
setIsConsolidating(true);
|
|
setStatus("idle");
|
|
try {
|
|
const rowsCount = await consolidateData(sheetMappings, settings);
|
|
setStatus("success");
|
|
setStatusMessage(`Erfolgreich! Es wurden ${rowsCount} Zeilen aus ${sheetMappings.length} Blättern zusammengefasst.`);
|
|
setStep("done");
|
|
} catch (err: any) {
|
|
setStatus("error");
|
|
setStatusMessage(err.message || "Fehler bei der Konsolidierung: " + String(err));
|
|
} finally {
|
|
setIsConsolidating(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className={styles.root}>
|
|
{typeof DEV_MODE !== "undefined" && DEV_MODE && (
|
|
<div style={{ backgroundColor: "#ffc107", color: "#000", textAlign: "center", padding: "4px", fontWeight: "bold", fontSize: "12px" }}>
|
|
DEV ENVIRONMENT
|
|
</div>
|
|
)}
|
|
<StatusNotifier status={status} message={statusMessage} />
|
|
|
|
{step === "select_sheets" && (
|
|
<SheetSelector
|
|
sheets={sheets}
|
|
selectedSheetIds={selectedSheetIds}
|
|
onSelectionChange={setSelectedSheetIds}
|
|
onNext={handleNextToMapping}
|
|
onExternalSheetsLoaded={handleExternalSheetsLoaded}
|
|
/>
|
|
)}
|
|
|
|
{step === "map_columns" && (
|
|
<ColumnMapper
|
|
sheetMappings={sheetMappings}
|
|
settings={settings}
|
|
onSettingsChange={setSettings}
|
|
onHeaderRowChange={handleHeaderRowChange}
|
|
onMappingChange={handleMappingChange}
|
|
onBack={() => setStep("select_sheets")}
|
|
onConsolidate={handleConsolidate}
|
|
isConsolidating={isConsolidating}
|
|
/>
|
|
)}
|
|
|
|
{step === "done" && (
|
|
<div style={{ padding: "10px", textAlign: "center", marginTop: "40px" }}>
|
|
<h2>Fertig!</h2>
|
|
<p>Die Daten wurden in die 'Gesamtliste' geschrieben.</p>
|
|
<button
|
|
style={{ padding: "8px 16px", marginTop: "10px", cursor: "pointer" }}
|
|
onClick={() => {
|
|
setStep("select_sheets");
|
|
setSelectedSheetIds([]);
|
|
setStatus("idle");
|
|
}}
|
|
>
|
|
Neuen Durchlauf starten
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Footer Area with Links and Copyright */}
|
|
<div style={{ marginTop: "auto", padding: "10px", textAlign: "center", fontSize: "12px", borderTop: "1px solid #eee", width: "100%", boxSizing: "border-box" }}>
|
|
<div style={{ marginBottom: "8px" }}>
|
|
<a href="/manifest.prod.xml" download style={{ color: "#0078d4", textDecoration: "none", marginRight: "16px" }}>Prod-Manifest (.xml)</a>
|
|
<a href="/docs/" target="_blank" rel="noopener noreferrer" style={{ color: "#0078d4", textDecoration: "none" }}>Dokumentation</a>
|
|
</div>
|
|
<div style={{ color: "#666" }}>
|
|
© {new Date().getFullYear()} Toni Martin - SAT Elektrotechnik GmbH. Alle Rechte vorbehalten.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default App;
|