feat: streamline dashboard actions by removing inline add-entry form and manual refresh
This commit is contained in:
96
src/App.js
96
src/App.js
@@ -28,6 +28,7 @@ function App() {
|
|||||||
const [dirtyDialogMessage, setDirtyDialogMessage] = useState('');
|
const [dirtyDialogMessage, setDirtyDialogMessage] = useState('');
|
||||||
const [pendingNavigation, setPendingNavigation] = useState(null);
|
const [pendingNavigation, setPendingNavigation] = useState(null);
|
||||||
const [dirtyDialogSaving, setDirtyDialogSaving] = useState(false);
|
const [dirtyDialogSaving, setDirtyDialogSaving] = useState(false);
|
||||||
|
const [confirmDialog, setConfirmDialog] = useState({ open: false });
|
||||||
|
|
||||||
const weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'];
|
const weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'];
|
||||||
const delay = useCallback((ms) => new Promise((resolve) => setTimeout(resolve, ms)), []);
|
const delay = useCallback((ms) => new Promise((resolve) => setTimeout(resolve, ms)), []);
|
||||||
@@ -543,6 +544,31 @@ function App() {
|
|||||||
setPendingNavigation(null);
|
setPendingNavigation(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const askConfirmation = useCallback(
|
||||||
|
(options = {}) =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
setConfirmDialog({
|
||||||
|
open: true,
|
||||||
|
title: options.title || 'Bitte bestätigen',
|
||||||
|
message: options.message || 'Bist du sicher?',
|
||||||
|
confirmLabel: options.confirmLabel || 'Ja',
|
||||||
|
cancelLabel: options.cancelLabel || 'Abbrechen',
|
||||||
|
confirmTone: options.confirmTone || 'primary',
|
||||||
|
resolve
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleConfirmDialog = useCallback((result) => {
|
||||||
|
setConfirmDialog((prev) => {
|
||||||
|
if (prev?.resolve) {
|
||||||
|
prev.resolve(result);
|
||||||
|
}
|
||||||
|
return { open: false };
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let ticker;
|
let ticker;
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
@@ -651,18 +677,30 @@ function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteEntry = (entryId) => {
|
const deleteEntry = async (entryId) => {
|
||||||
|
const confirmed = await askConfirmation({
|
||||||
|
title: 'Eintrag löschen',
|
||||||
|
message: 'Soll dieser Eintrag dauerhaft gelöscht werden?',
|
||||||
|
confirmLabel: 'Löschen',
|
||||||
|
confirmTone: 'danger'
|
||||||
|
});
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setIsDirty(true);
|
setIsDirty(true);
|
||||||
if (window.confirm('Soll dieser Eintrag dauerhaft gelöscht werden?')) {
|
|
||||||
const updatedConfig = config.filter((item) => item.id !== entryId);
|
const updatedConfig = config.filter((item) => item.id !== entryId);
|
||||||
setConfig(updatedConfig);
|
setConfig(updatedConfig);
|
||||||
setStatus('Eintrag gelöscht!');
|
setStatus('Eintrag gelöscht!');
|
||||||
setTimeout(() => setStatus(''), 3000);
|
setTimeout(() => setStatus(''), 3000);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const hideEntry = async (entryId) => {
|
const hideEntry = async (entryId) => {
|
||||||
if (!window.confirm('Soll dieser Betrieb ausgeblendet werden?')) {
|
const confirmed = await askConfirmation({
|
||||||
|
title: 'Betrieb ausblenden',
|
||||||
|
message: 'Soll dieser Betrieb ausgeblendet werden?',
|
||||||
|
confirmLabel: 'Ausblenden'
|
||||||
|
});
|
||||||
|
if (!confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsDirty(true);
|
setIsDirty(true);
|
||||||
@@ -749,9 +787,15 @@ function App() {
|
|||||||
if (existing && !existing.hidden) {
|
if (existing && !existing.hidden) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!window.confirm(`Soll der Betrieb "${store.name}" zur Liste hinzugefügt werden?`)) {
|
const confirmed = await askConfirmation({
|
||||||
|
title: 'Betrieb hinzufügen',
|
||||||
|
message: `Soll der Betrieb "${store.name}" zur Liste hinzugefügt werden?`,
|
||||||
|
confirmLabel: 'Hinzufügen'
|
||||||
|
});
|
||||||
|
if (!confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setIsDirty(true);
|
||||||
const message = existing ? 'Betrieb wieder eingeblendet.' : 'Betrieb zur Liste hinzugefügt.';
|
const message = existing ? 'Betrieb wieder eingeblendet.' : 'Betrieb zur Liste hinzugefügt.';
|
||||||
setIsDirty(true);
|
setIsDirty(true);
|
||||||
await persistConfigUpdate(
|
await persistConfigUpdate(
|
||||||
@@ -1409,6 +1453,16 @@ function App() {
|
|||||||
onCancel={handleDirtyCancel}
|
onCancel={handleDirtyCancel}
|
||||||
saving={dirtyDialogSaving}
|
saving={dirtyDialogSaving}
|
||||||
/>
|
/>
|
||||||
|
<ConfirmationDialog
|
||||||
|
open={!!confirmDialog.open}
|
||||||
|
title={confirmDialog.title}
|
||||||
|
message={confirmDialog.message}
|
||||||
|
confirmLabel={confirmDialog.confirmLabel}
|
||||||
|
cancelLabel={confirmDialog.cancelLabel}
|
||||||
|
confirmTone={confirmDialog.confirmTone}
|
||||||
|
onConfirm={() => handleConfirmDialog(true)}
|
||||||
|
onCancel={() => handleConfirmDialog(false)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
@@ -1508,6 +1562,38 @@ function DirtyNavigationDialog({ open, message, onSave, onDiscard, onCancel, sav
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ConfirmationDialog({ open, title, message, confirmLabel, cancelLabel, confirmTone = 'primary', onConfirm, onCancel }) {
|
||||||
|
if (!open) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const confirmClasses =
|
||||||
|
confirmTone === 'danger'
|
||||||
|
? 'bg-red-600 hover:bg-red-700 focus:ring-red-500'
|
||||||
|
: 'bg-blue-600 hover:bg-blue-700 focus:ring-blue-500';
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/60 px-4">
|
||||||
|
<div className="bg-white rounded-lg shadow-2xl max-w-md w-full p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-800 mb-2">{title || 'Bitte bestätigen'}</h3>
|
||||||
|
<p className="text-sm text-gray-600 mb-4">{message || 'Bist du sicher?'}</p>
|
||||||
|
<div className="flex flex-col gap-2 sm:flex-row sm:justify-end">
|
||||||
|
<button
|
||||||
|
onClick={onCancel}
|
||||||
|
className="bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded focus:outline-none focus:ring-2 focus:ring-gray-300"
|
||||||
|
>
|
||||||
|
{cancelLabel || 'Abbrechen'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={onConfirm}
|
||||||
|
className={`${confirmClasses} text-white px-4 py-2 rounded focus:outline-none focus:ring-2`}
|
||||||
|
>
|
||||||
|
{confirmLabel || 'Ja'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function formatEta(seconds) {
|
function formatEta(seconds) {
|
||||||
if (seconds == null || seconds === Infinity) {
|
if (seconds == null || seconds === Infinity) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user