Files
Pickup-Config/src/PickupConfigEditor.js

422 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react';
import { DateRange } from 'react-date-range';
import { format, parseISO, isValid, startOfDay } from 'date-fns';
import { de } from 'date-fns/locale';
import 'react-date-range/dist/styles.css';
import 'react-date-range/dist/theme/default.css';
const parseDateValue = (value) => {
if (!value) {
return null;
}
const parsed = parseISO(value);
return isValid(parsed) ? parsed : null;
};
const formatDateValue = (date) => {
if (!(date instanceof Date) || !isValid(date)) {
return null;
}
return format(date, 'yyyy-MM-dd');
};
const formatRangeLabel = (start, end) => {
const startDate = parseDateValue(start);
const endDate = parseDateValue(end);
if (startDate && endDate) {
const startLabel = format(startDate, 'dd.MM.yyyy', { locale: de });
const endLabel = format(endDate, 'dd.MM.yyyy', { locale: de });
if (startLabel === endLabel) {
return startLabel;
}
return `${startLabel} ${endLabel}`;
}
if (startDate) {
return format(startDate, 'dd.MM.yyyy', { locale: de });
}
return 'Zeitraum auswählen';
};
const buildSelectionRange = (start, end, minDate) => {
const minimum = minDate || startOfDay(new Date());
let startDate = parseDateValue(start) || parseDateValue(end) || minimum;
let endDate = parseDateValue(end) || parseDateValue(start) || startDate;
if (startDate < minimum) {
startDate = minimum;
}
if (endDate < minimum) {
endDate = startDate;
}
return {
startDate,
endDate,
key: 'selection'
};
};
const sortEntriesByLabel = (entries = []) => {
return [...entries].sort((a, b) =>
(a.label || '').localeCompare(b.label || '', 'de', { sensitivity: 'base' })
);
};
const PickupConfigEditor = () => {
const [config, setConfig] = useState([]);
const [loading, setLoading] = useState(true);
const [status, setStatus] = useState('');
const [error, setError] = useState('');
const [activeRangePicker, setActiveRangePicker] = useState(null);
const minSelectableDate = startOfDay(new Date());
// Simulierte API-Endpunkte - diese müssen in Ihrer tatsächlichen Implementierung angepasst werden
const API_URL = '/api/iobroker/pickup-config';
useEffect(() => {
// Beim Laden der Komponente die aktuelle Konfiguration abrufen
fetchConfig();
}, []);
useEffect(() => {
if (!activeRangePicker) {
return;
}
const entry = config.find((item) => item.id === activeRangePicker);
if (!entry || entry.desiredWeekday) {
setActiveRangePicker(null);
}
}, [activeRangePicker, config]);
const fetchConfig = async () => {
setLoading(true);
setError('');
try {
// In einer echten Implementierung würden Sie Ihre API aufrufen
// Hier wird die statische Konfiguration verwendet
// const response = await fetch(API_URL);
// const data = await response.json();
// Simulierte Verzögerung und Antwort mit der statischen Konfiguration
setTimeout(() => {
const staticConfig = [
{ id: "63448", active: false, checkProfileId: true, onlyNotify: true, label: "Penny Baden-Oos" },
{ id: "44975", active: false, checkProfileId: true, onlyNotify: false, label: "Aldi Kuppenheim", desiredWeekday: "Samstag" },
{ id: "44972", active: false, checkProfileId: true, onlyNotify: false, label: "Aldi Biblisweg", desiredWeekday: "Dienstag" },
{
id: "44975",
active: false,
checkProfileId: true,
onlyNotify: false,
label: "Aldi Kuppenheim",
desiredDateRange: { start: "2025-05-18", end: "2025-05-18" }
},
{ id: "33875", active: false, checkProfileId: true, onlyNotify: false, label: "Cap Markt", desiredWeekday: "Donnerstag" },
{ id: "42322", active: false, checkProfileId: false, onlyNotify: false, label: "Edeka Haueneberstein" },
{ id: "51450", active: false, checkProfileId: true, onlyNotify: false, label: "Hornbach Grünwinkel" }
];
setConfig(sortEntriesByLabel(staticConfig));
setLoading(false);
}, 500);
} catch (err) {
setError('Fehler beim Laden der Konfiguration: ' + err.message);
setLoading(false);
}
};
const saveConfig = async () => {
setStatus('Speichere...');
setError('');
try {
// API-Aufruf zum Speichern der Konfiguration in ioBroker
// In einer echten Implementierung würden Sie Ihre API aufrufen
// const response = await fetch(API_URL, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify(config),
// });
// Simulierte Verzögerung zum Darstellen des Speichervorgangs
setTimeout(() => {
setStatus('Konfiguration erfolgreich gespeichert!');
setTimeout(() => setStatus(''), 3000);
}, 1000);
} catch (err) {
setError('Fehler beim Speichern: ' + err.message);
}
};
const handleToggleActive = (index) => {
const newConfig = [...config];
newConfig[index].active = !newConfig[index].active;
setConfig(newConfig);
};
const handleToggleProfileCheck = (index) => {
const newConfig = [...config];
newConfig[index].checkProfileId = !newConfig[index].checkProfileId;
setConfig(newConfig);
};
const handleToggleOnlyNotify = (index) => {
const newConfig = [...config];
newConfig[index].onlyNotify = !newConfig[index].onlyNotify;
setConfig(newConfig);
};
const handleWeekdayChange = (index, value) => {
const newConfig = [...config];
const entryId = newConfig[index]?.id;
newConfig[index].desiredWeekday = value;
if (newConfig[index].desiredDateRange) {
delete newConfig[index].desiredDateRange;
}
setConfig(newConfig);
if (value && entryId) {
setActiveRangePicker((prev) => (prev === entryId ? null : prev));
}
};
const handleDateRangeSelection = (entryId, startDate, endDate) => {
const startValue = formatDateValue(startDate);
const endValue = formatDateValue(endDate);
setConfig((prev) =>
prev.map((item) => {
if (item.id !== entryId) {
return item;
}
const updated = { ...item };
if (startValue || endValue) {
updated.desiredDateRange = {
start: startValue || endValue,
end: endValue || startValue
};
if (updated.desiredWeekday) {
delete updated.desiredWeekday;
}
} else if (updated.desiredDateRange) {
delete updated.desiredDateRange;
}
if (updated.desiredDate) {
delete updated.desiredDate;
}
return updated;
})
);
};
const activeRangeEntry = activeRangePicker
? config.find((item) => item.id === activeRangePicker) || null
: null;
if (loading) {
return <div className="text-center p-8">Lade Konfiguration...</div>;
}
const weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'];
return (
<div className="p-4 max-w-4xl mx-auto">
<h1 className="text-2xl font-bold mb-6">ioBroker Abholung-Konfiguration</h1>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
{status && (
<div className="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
{status}
</div>
)}
<div className="overflow-x-auto">
<table className="min-w-full bg-white border border-gray-200">
<thead>
<tr className="bg-gray-100">
<th className="px-4 py-2">Aktiv</th>
<th className="px-4 py-2">Geschäft</th>
<th className="px-4 py-2">Profil prüfen</th>
<th className="px-4 py-2">Nur benachrichtigen</th>
<th className="px-4 py-2">Wochentag</th>
<th className="px-4 py-2">Datum / Zeitraum</th>
</tr>
</thead>
<tbody>
{config.map((item, index) => {
const normalizedRange = item.desiredDateRange
? { ...item.desiredDateRange }
: item.desiredDate
? { start: item.desiredDate, end: item.desiredDate }
: null;
const rangeStart = normalizedRange?.start || '';
const rangeEnd = normalizedRange?.end || '';
const hasDateRange = Boolean(rangeStart || rangeEnd);
return (
<tr key={index} className={index % 2 === 0 ? 'bg-gray-50' : 'bg-white'}>
<td className="px-4 py-2 text-center">
<input
type="checkbox"
checked={item.active}
onChange={() => handleToggleActive(index)}
className="h-5 w-5"
/>
</td>
<td className="px-4 py-2">
<span className="font-medium">{item.label}</span>
</td>
<td className="px-4 py-2 text-center">
<input
type="checkbox"
checked={item.checkProfileId}
onChange={() => handleToggleProfileCheck(index)}
className="h-5 w-5"
/>
</td>
<td className="px-4 py-2 text-center">
<input
type="checkbox"
checked={item.onlyNotify}
onChange={() => handleToggleOnlyNotify(index)}
className="h-5 w-5"
/>
</td>
<td className="px-4 py-2">
<select
value={item.desiredWeekday || ''}
onChange={(e) => handleWeekdayChange(index, e.target.value)}
className="border rounded p-1 w-full"
disabled={hasDateRange}
>
<option value="">Kein Wochentag</option>
{weekdays.map((day) => (
<option key={day} value={day}>{day}</option>
))}
</select>
</td>
<td className="px-4 py-2">
<button
type="button"
onClick={() => {
if (item.desiredWeekday) {
return;
}
setActiveRangePicker(item.id);
}}
disabled={Boolean(item.desiredWeekday)}
className={`w-full border rounded p-2 text-left transition focus:outline-none focus:ring-2 focus:ring-blue-500 ${
item.desiredWeekday
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'bg-white hover:border-blue-400'
}`}
>
<span className="block text-sm text-gray-700">{formatRangeLabel(rangeStart, rangeEnd)}</span>
<span className="block text-xs text-gray-500">Klicke zum Auswählen</span>
</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<div className="mt-6 flex justify-between">
<button
onClick={fetchConfig}
className="bg-gray-500 hover:bg-gray-600 text-white py-2 px-4 rounded"
>
Zurücksetzen
</button>
<button
onClick={saveConfig}
className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded"
>
In ioBroker speichern
</button>
</div>
<div className="mt-8 p-4 border rounded bg-gray-50">
<h2 className="text-lg font-bold mb-2">Aktuelle JSON-Konfiguration:</h2>
<pre className="bg-gray-100 p-4 rounded overflow-x-auto">
{JSON.stringify(config, null, 2)}
</pre>
</div>
{activeRangeEntry && !activeRangeEntry.desiredWeekday && (
<div
className="fixed inset-0 z-40 flex items-center justify-center bg-black bg-opacity-40 px-4"
onClick={() => setActiveRangePicker(null)}
>
<div
className="bg-white rounded-2xl shadow-2xl w-full max-w-lg"
onClick={(event) => event.stopPropagation()}
>
<div className="px-5 pt-5 pb-3 border-b">
<p className="text-xs uppercase tracking-wide text-gray-500">Zeitraum auswählen für</p>
<p className="text-lg font-semibold text-gray-900">
{activeRangeEntry.label || `Store ${activeRangeEntry.id}`}
</p>
</div>
<div className="px-2 py-4">
<DateRange
onChange={(ranges) => {
const { startDate, endDate } = ranges.selection;
handleDateRangeSelection(activeRangeEntry.id, startDate, endDate);
}}
moveRangeOnFirstSelection={false}
ranges={[
buildSelectionRange(
activeRangeEntry.desiredDateRange?.start,
activeRangeEntry.desiredDateRange?.end,
minSelectableDate
)
]}
rangeColors={['#2563EB']}
months={1}
direction="horizontal"
showDateDisplay={false}
locale={de}
minDate={minSelectableDate}
/>
</div>
<div className="flex items-center justify-between px-5 py-3 border-t bg-gray-50 rounded-b-2xl">
<button
type="button"
className="text-sm text-gray-600 hover:text-gray-900"
onClick={() => {
handleDateRangeSelection(activeRangeEntry.id, null, null);
setActiveRangePicker(null);
}}
>
Zurücksetzen
</button>
<div className="flex items-center gap-3">
<button
type="button"
className="text-sm text-gray-600 hover:text-gray-900"
onClick={() => setActiveRangePicker(null)}
>
Abbrechen
</button>
<button
type="button"
className="text-sm font-semibold text-white bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-md"
onClick={() => setActiveRangePicker(null)}
>
Fertig
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default PickupConfigEditor;