feat: add auto deactivate option for slot plans

Add an autoDeactivate flag to pickup configuration entries, expose it as a dashboard checkbox beside the active state, and keep existing entries defaulting to automatic deactivation unless explicitly disabled.
This commit is contained in:
2026-06-14 21:23:47 +02:00
parent bd41e8c079
commit 3f1892b951
5 changed files with 68 additions and 4 deletions

View File

@@ -1,3 +1,3 @@
chore: sync running docker state feat: add auto deactivate option for slot plans
Commit the app code currently running in Docker, including scheduler, store status, journal reminder, maintenance mode, and Foodsharing API check updates. Remove generated runtime logs from version control and ignore local runtime captures. Add an autoDeactivate flag to pickup configuration entries, expose it as a dashboard checkbox beside the active state, and keep existing entries defaulting to automatic deactivation unless explicitly disabled.

View File

@@ -248,6 +248,7 @@ function mergeStoresIntoConfig(config = [], stores = []) {
id, id,
label: store.name || `Store ${id}`, label: store.name || `Store ${id}`,
active: false, active: false,
autoDeactivate: true,
checkProfileId: true, checkProfileId: true,
onlyNotify: false, onlyNotify: false,
hidden: hideByDefault hidden: hideByDefault

View File

@@ -685,6 +685,17 @@ function deactivateEntry(session, entry, options = {}) {
return changed; return changed;
} }
function shouldAutoDeactivateEntry(entry) {
return entry?.autoDeactivate !== false;
}
function deactivateEntryAfterSuccess(session, entry, options = {}) {
if (!shouldAutoDeactivateEntry(entry)) {
return false;
}
return deactivateEntry(session, entry, options);
}
function isEntryActiveInConfig(profileId, entryId) { function isEntryActiveInConfig(profileId, entryId) {
if (!profileId || !entryId) { if (!profileId || !entryId) {
return false; return false;
@@ -853,7 +864,7 @@ async function processBooking(session, entry, pickup) {
booked: false, booked: false,
storeId: entry.id storeId: entry.id
}); });
deactivateEntry(session, entry); deactivateEntryAfterSuccess(session, entry);
return; return;
} }
@@ -883,7 +894,7 @@ async function processBooking(session, entry, pickup) {
booked: true, booked: true,
storeId: entry.id storeId: entry.id
}); });
deactivateEntry(session, entry); deactivateEntryAfterSuccess(session, entry);
} catch (error) { } catch (error) {
console.error(`[ERROR] Buchung für ${storeName} am ${readableDate} fehlgeschlagen:`, error.message); console.error(`[ERROR] Buchung für ${storeName} am ${readableDate} fehlgeschlagen:`, error.message);
try { try {

View File

@@ -529,6 +529,15 @@ function App() {
); );
}; };
const handleToggleAutoDeactivate = (entryId) => {
setIsDirty(true);
setConfig((prev) =>
prev.map((item) =>
item.id === entryId ? { ...item, autoDeactivate: item.autoDeactivate === false } : item
)
);
};
const handleToggleProfileCheck = (entryId) => { const handleToggleProfileCheck = (entryId) => {
setIsDirty(true); setIsDirty(true);
setConfig((prev) => setConfig((prev) =>
@@ -693,6 +702,7 @@ function App() {
id: storeId, id: storeId,
label: store.name || `Store ${storeId}`, label: store.name || `Store ${storeId}`,
active: false, active: false,
autoDeactivate: true,
checkProfileId: true, checkProfileId: true,
onlyNotify: false, onlyNotify: false,
skipDormantCheck: false, skipDormantCheck: false,
@@ -813,6 +823,7 @@ function App() {
config={config} config={config}
registeredPickups={registeredPickups} registeredPickups={registeredPickups}
onToggleActive={handleToggleActive} onToggleActive={handleToggleActive}
onToggleAutoDeactivate={handleToggleAutoDeactivate}
onToggleProfileCheck={handleToggleProfileCheck} onToggleProfileCheck={handleToggleProfileCheck}
onToggleOnlyNotify={handleToggleOnlyNotify} onToggleOnlyNotify={handleToggleOnlyNotify}
onToggleDormantSkip={handleToggleDormantSkip} onToggleDormantSkip={handleToggleDormantSkip}

View File

@@ -202,6 +202,7 @@ const DashboardView = ({
config, config,
registeredPickups, registeredPickups,
onToggleActive, onToggleActive,
onToggleAutoDeactivate,
onToggleProfileCheck, onToggleProfileCheck,
onToggleOnlyNotify, onToggleOnlyNotify,
onToggleDormantSkip, onToggleDormantSkip,
@@ -388,6 +389,44 @@ const DashboardView = ({
enableSorting: false, enableSorting: false,
enableColumnFilter: false enableColumnFilter: false
}), }),
columnHelper.accessor((row) => row.autoDeactivate !== false, {
id: 'autoDeactivate',
header: ({ column }) => (
<div>
<span className="font-semibold text-sm">Automatisch deaktivieren</span>
<ColumnSelectFilter
column={column}
options={[
{ value: 'true', label: 'Ja' },
{ value: 'false', label: 'Nein' }
]}
/>
</div>
),
cell: ({ row }) => (
<div className="text-center">
<input
type="checkbox"
className="h-5 w-5"
checked={row.original.autoDeactivate !== false}
onChange={() => onToggleAutoDeactivate(row.original.id)}
title="Nach erfolgreicher Slot-Aktion automatisch deaktivieren"
/>
</div>
),
filterFn: (row, columnId, value) => {
if (value === undefined) {
return true;
}
const boolValue = value === 'true';
return row.getValue(columnId) === boolValue;
},
sortingFn: (rowA, rowB, columnId) => {
const a = rowA.getValue(columnId);
const b = rowB.getValue(columnId);
return Number(b) - Number(a);
}
}),
columnHelper.accessor('label', { columnHelper.accessor('label', {
header: ({ column }) => ( header: ({ column }) => (
<div> <div>
@@ -649,7 +688,9 @@ const DashboardView = ({
onDeleteEntry, onDeleteEntry,
onHideEntry, onHideEntry,
onRangePickerRequest, onRangePickerRequest,
onToggleAutoDeactivate,
onToggleActive, onToggleActive,
onToggleDormantSkip,
onToggleOnlyNotify, onToggleOnlyNotify,
onToggleProfileCheck, onToggleProfileCheck,
onWeekdayChange, onWeekdayChange,