feat(ui): Slot-Nummer nach links verschoben und Datumsauswahl horizontal ausgerichtet

- Slot-Nr.-Anzeige nach links im ActionBar verschoben.
- Datum-Labels („Von“ & „Bis“) und Eingabefelder horizontal ausgerichtet.
- Verbesserte UI/UX der Chart-Steuerungskomponenten.
This commit is contained in:
Ismail Ali
2025-03-14 21:30:21 +01:00
parent 162a0aa318
commit e0379c24a6
18 changed files with 14734 additions and 92 deletions

View File

@@ -11,6 +11,7 @@ import {
setChartOpen,
setFullScreen,
} from "../../../../../redux/slices/kabelueberwachungChartSlice";
import { resetBrushRange } from "../../../../../redux/slices/brushSlice";
interface ChartSwitcherProps {
isOpen: boolean;
@@ -30,6 +31,7 @@ const ChartSwitcher: React.FC<ChartSwitcherProps> = ({ isOpen, onClose }) => {
const handleClose = () => {
dispatch(setChartOpen(false));
dispatch(setFullScreen(false));
dispatch(resetBrushRange());
onClose();
};

View File

@@ -8,9 +8,20 @@ import {
} from "../../../../../redux/slices/kabelueberwachungChartSlice";
import "react-datepicker/dist/react-datepicker.css";
const DateRangePicker: React.FC = () => {
const dispatch = useDispatch();
// ✅ Props definieren
interface DateRangePickerProps {
setVonDatum: (date: Date) => void;
setBisDatum: (date: Date) => void;
minDate: string;
maxDate: string;
}
const DateRangePicker: React.FC<DateRangePickerProps> = ({
setVonDatum,
setBisDatum,
minDate,
maxDate,
}) => {
const reduxVonDatum = useSelector(
(state: RootState) => state.kabelueberwachungChart.vonDatum
);
@@ -19,37 +30,40 @@ const DateRangePicker: React.FC = () => {
);
return (
<div className="flex space-x-4">
<div>
<div className="flex space-x-4 items-center">
<div className="flex items-center space-x-2">
<label className="block text-sm font-semibold">Von</label>
<DatePicker
selected={reduxVonDatum ? new Date(reduxVonDatum) : new Date()}
onChange={(date) => {
if (date) {
dispatch(setVonDatum(date.toISOString().split("T")[0]));
setVonDatum(date);
}
}}
selectsStart
startDate={reduxVonDatum ? new Date(reduxVonDatum) : new Date()}
endDate={reduxBisDatum ? new Date(reduxBisDatum) : new Date()}
minDate={new Date(minDate)}
maxDate={new Date(maxDate)}
dateFormat="dd.MM.yyyy"
className="border px-2 py-1 rounded"
/>
</div>
<div>
<div className="flex items-center space-x-2">
<label className="block text-sm font-semibold">Bis</label>
<DatePicker
selected={reduxBisDatum ? new Date(reduxBisDatum) : new Date()}
onChange={(date) => {
if (date) {
dispatch(setBisDatum(date.toISOString().split("T")[0]));
setBisDatum(date);
}
}}
selectsEnd
startDate={reduxVonDatum ? new Date(reduxVonDatum) : new Date()}
endDate={reduxBisDatum ? new Date(reduxBisDatum) : new Date()}
minDate={reduxVonDatum ? new Date(reduxVonDatum) : new Date()}
minDate={new Date(minDate)}
maxDate={new Date(maxDate)}
dateFormat="dd.MM.yyyy"
className="border px-2 py-1 rounded"
/>

View File

@@ -23,22 +23,28 @@ const LoopChartActionBar: React.FC = () => {
selectedSlotType,
isChartOpen,
slotNumber,
loopMeasurementCurveChartData, // ⬅️ HIER FEHLTE DIESE ZEILE
loopMeasurementCurveChartData,
} = useSelector((state: RootState) => state.kabelueberwachungChart);
/**
* API-URL-Erstellung für Entwicklung und Produktion
*/
const getApiUrl = (mode: "DIA0" | "DIA1" | "DIA2", type: number) => {
const getApiUrl = (
mode: "DIA0" | "DIA1" | "DIA2",
type: number,
slotNumber: number
) => {
if (!slotNumber) {
console.error("⚠️ Slot-Nummer nicht gesetzt!");
return "";
}
// Dynamische Basis-URL abhängig von Umgebung
const typeFolder =
type === 3 ? "isolationswiderstand" : "schleifenwiderstand";
const baseUrl =
process.env.NODE_ENV === "development"
? `/CPLmockData/kuesChartData/${mode}_${type}.json`
? `/CPLmockData/kuesChartData/slot${slotNumber}/${typeFolder}/${mode}.json`
: `${window.location.origin}/CPL?seite.ACP&${mode}=${formatDate(
vonDatum
)};${formatDate(bisDatum)};${slotNumber};${type};`;
@@ -52,18 +58,21 @@ const LoopChartActionBar: React.FC = () => {
return `${dateParts[0]};${dateParts[1]};${dateParts[2]}`;
};
/**
* Funktion zum Laden der Messwerte
*/
/**
* Funktion zum Laden der Messwerte
*/
const handleFetchData = async () => {
const type = selectedSlotType === "schleifenwiderstand" ? 4 : 3;
const apiUrl = getApiUrl(selectedMode, type);
if (slotNumber === null) {
console.error("⚠️ Slot-Nummer nicht gesetzt!");
return;
}
const apiUrl = getApiUrl(selectedMode, type, slotNumber); // ✅ slotNumber ergänzt
if (!apiUrl) return;
try {
console.log("📡 API-Request an:", apiUrl); // Debugging
console.log("📡 API-Request an:", apiUrl);
const response = await fetch(apiUrl, {
method: "GET",
headers: { "Content-Type": "application/json" },
@@ -75,7 +84,7 @@ const LoopChartActionBar: React.FC = () => {
console.log("✅ Daten erfolgreich geladen:", jsonData);
if (Array.isArray(jsonData)) {
dispatch(setLoopMeasurementCurveChartData([...jsonData])); // Erzwingt eine neue Referenz
dispatch(setLoopMeasurementCurveChartData([...jsonData]));
// Falls das Chart zum ersten Mal geöffnet wird, setze vonDatum & bisDatum
if (!isChartOpen && jsonData.length > 0) {
@@ -83,22 +92,18 @@ const LoopChartActionBar: React.FC = () => {
const lastDate = new Date(jsonData[0].t);
dispatch(setVonDatum(firstDate.toISOString().split("T")[0]));
dispatch(setBisDatum(lastDate.toISOString().split("T")[0]));
dispatch(setChartOpen(true)); // Chart öffnen
dispatch(setChartOpen(true));
}
} else {
console.error("⚠️ Erwartetes Array, aber erhalten:", jsonData);
}
} catch (error) {
console.error("❌ Fehler beim Laden der Produktions-Daten:", error);
console.error("❌ Fehler beim Laden der Daten:", error);
}
};
const minDate = useMemo(() => {
if (
!loopMeasurementCurveChartData ||
loopMeasurementCurveChartData.length === 0
)
return "";
if (!loopMeasurementCurveChartData?.length) return "";
return new Date(
loopMeasurementCurveChartData[loopMeasurementCurveChartData.length - 1].t
)
@@ -107,77 +112,78 @@ const LoopChartActionBar: React.FC = () => {
}, [loopMeasurementCurveChartData]);
const maxDate = useMemo(() => {
if (
!loopMeasurementCurveChartData ||
loopMeasurementCurveChartData.length === 0
)
return "";
if (!loopMeasurementCurveChartData?.length) return "";
return new Date(loopMeasurementCurveChartData[0].t)
.toISOString()
.split("T")[0];
}, [loopMeasurementCurveChartData]);
// **Automatische Datenaktualisierung bei Auswahländerung**
// Automatische Datenaktualisierung bei Auswahländerung
useEffect(() => {
handleFetchData();
}, [selectedMode, selectedSlotType]); // Wird ausgeführt, wenn sich ein Dropdown ändert
// Wenn sich slotNumber, Zeitraum oder Auswahl ändert, neu laden:
}, [selectedMode, selectedSlotType, slotNumber, vonDatum, bisDatum]);
return (
<div className="flex justify-end items-center p-2 bg-gray-100 rounded-lg space-x-2">
{/* Datumsauswahl */}
<DateRangePicker
setVonDatum={(date) => {
const isoDate = date.toISOString().split("T")[0];
<div className="flex justify-between items-center p-2 bg-gray-100 rounded-lg space-x-2">
{/* Slot Nummer links positioniert */}
<div className="flex items-center">
<label className="text-sm font-semibold">
Slot-Nr.: {slotNumber ?? "-"}
</label>
</div>
if (isoDate < minDate || isoDate > maxDate) return;
<div className="flex items-center space-x-2">
<DateRangePicker
setVonDatum={(date) => {
const isoDate = date.toISOString().split("T")[0];
dispatch(setVonDatum(isoDate));
if (isoDate < minDate || isoDate > maxDate) return;
if (isoDate > bisDatum) {
dispatch(setBisDatum(isoDate)); // Sicherstellen, dass bisDatum >= vonDatum bleibt
}
}}
setBisDatum={(date) => {
const isoDate = date.toISOString().split("T")[0];
dispatch(setVonDatum(isoDate));
if (isoDate < minDate || isoDate > maxDate) return;
if (isoDate > bisDatum) dispatch(setBisDatum(isoDate));
}}
setBisDatum={(date) => {
const isoDate = date.toISOString().split("T")[0];
if (isoDate >= vonDatum) {
dispatch(setBisDatum(isoDate));
}
}}
minDate={minDate}
maxDate={maxDate}
/>
if (isoDate < minDate || isoDate > maxDate) return;
{/* Dropdown für DIA-Modus */}
<select
value={selectedMode}
onChange={(e) =>
dispatch(setSelectedMode(e.target.value as "DIA0" | "DIA1" | "DIA2"))
}
className="px-3 py-1 bg-white border rounded text-sm"
>
<option value="DIA0">Alle Messwerte</option>
<option value="DIA1">Stündliche Werte</option>
<option value="DIA2">Tägliche Werte</option>
</select>
if (isoDate >= vonDatum) dispatch(setBisDatum(isoDate));
}}
minDate={minDate}
maxDate={maxDate}
/>
{/* Dropdown für Slot-Typ */}
<select
value={selectedSlotType}
onChange={(e) =>
dispatch(
setSelectedSlotType(
e.target.value as "isolationswiderstand" | "schleifenwiderstand"
<select
value={selectedMode}
onChange={(e) =>
dispatch(
setSelectedMode(e.target.value as "DIA0" | "DIA1" | "DIA2")
)
)
}
className="px-3 py-1 bg-white border rounded text-sm"
>
<option value="schleifenwiderstand">Schleifenwiderstand</option>
<option value="isolationswiderstand">Isolationswiderstand</option>
</select>
}
className="px-3 py-1 bg-white border rounded text-sm"
>
<option value="DIA0">Alle Messwerte</option>
<option value="DIA1">Stündliche Werte</option>
<option value="DIA2">Tägliche Werte</option>
</select>
<select
value={selectedSlotType}
onChange={(e) =>
dispatch(
setSelectedSlotType(
e.target.value as "isolationswiderstand" | "schleifenwiderstand"
)
)
}
className="px-3 py-1 bg-white border rounded text-sm"
>
<option value="schleifenwiderstand">Schleifenwiderstand</option>
<option value="isolationswiderstand">Isolationswiderstand</option>
</select>
</div>
</div>
);
};

View File

@@ -89,7 +89,7 @@ const LoopMeasurementChart = () => {
messwertMinimum: eintrag.i,
messwertMaximum: eintrag.a,
messwert: eintrag.m ?? null,
messwertDurchschnitt: ["DIA1", "DIA2"].includes(selectedMode)
messwertDurchschnitt: ["DIA0", "DIA1", "DIA2"].includes(selectedMode)
? eintrag.g ?? null
: null,
}))
@@ -110,15 +110,22 @@ const LoopMeasurementChart = () => {
}, [formatierteDaten, brushRange.endIndex, dispatch]);
const handleBrushChange = useCallback(
({ startIndex, endIndex }) => {
({ startIndex, endIndex }: { startIndex?: number; endIndex?: number }) => {
if (startIndex === undefined || endIndex === undefined) return; // Verhindert Fehler
dispatch(
setBrushRange({
startIndex,
endIndex,
startDate: new Date(formatierteDaten[startIndex].zeit)
startDate: new Date(
formatierteDaten[startIndex]?.zeit || formatierteDaten[0].zeit
)
.toISOString()
.split("T")[0],
endDate: new Date(formatierteDaten[endIndex].zeit)
endDate: new Date(
formatierteDaten[endIndex]?.zeit ||
formatierteDaten[formatierteDaten.length - 1].zeit
)
.toISOString()
.split("T")[0],
})
@@ -126,6 +133,7 @@ const LoopMeasurementChart = () => {
},
[dispatch, formatierteDaten]
);
//--------------------------------------------------------------------------------
useEffect(() => {
if (formatierteDaten.length) {