diff --git a/AGENTS.md b/AGENTS.md index 2214f50..8cf991a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,6 +29,7 @@ ## Commit & Pull Request Guidelines - Follow conventional commits (e.g., `feat: add mqtt auth fields`, `fix: debounce config saves`) and keep subjects ≤72 characters. +- After every change, refresh `.commitmessage` with the final commit text and ensure it is staged (e.g., `git add -f .commitmessage`) so tooling can reuse it automatically. - Reference issues or MQTT topics impacted inside the body, and describe user-visible changes plus verification steps. - PRs must include: summary of API/UI changes, screenshots or JSON samples when modifying config shape, notes on new env vars, and confirmation that `npm test` and `npm run build` succeed. diff --git a/services/pickupScheduler.js b/services/pickupScheduler.js index b96b0d3..cca5604 100644 --- a/services/pickupScheduler.js +++ b/services/pickupScheduler.js @@ -78,17 +78,41 @@ async function ensureSession(session) { } } -function matchesDesiredDate(pickupDate, desiredDate) { - if (!desiredDate) { +function toDateValue(input) { + if (!input) { + return null; + } + const date = input instanceof Date ? new Date(input.getTime()) : new Date(input); + if (Number.isNaN(date.getTime())) { + return null; + } + return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime(); +} + +function matchesDesiredDate(pickupDate, desiredDate, desiredDateRange) { + const pickupValue = toDateValue(pickupDate); + if (pickupValue === null) { + return false; + } + + const hasRange = Boolean(desiredDateRange && (desiredDateRange.start || desiredDateRange.end)); + if (hasRange) { + const startValue = toDateValue(desiredDateRange.start); + const endValue = toDateValue(desiredDateRange.end); + if (startValue !== null && pickupValue < startValue) { + return false; + } + if (endValue !== null && pickupValue > endValue) { + return false; + } return true; } - const desired = new Date(desiredDate); - return ( - pickupDate.getFullYear() === desired.getFullYear() && - pickupDate.getMonth() === desired.getMonth() && - pickupDate.getDate() === desired.getDate() - ); + const desiredValue = toDateValue(desiredDate); + if (desiredValue === null) { + return true; + } + return pickupValue === desiredValue; } function matchesDesiredWeekday(pickupDate, desiredWeekday) { @@ -158,7 +182,7 @@ async function checkEntry(sessionId, entry, settings) { pickups.forEach((pickup) => { const pickupDate = new Date(pickup.date); - if (!matchesDesiredDate(pickupDate, entry.desiredDate)) { + if (!matchesDesiredDate(pickupDate, entry.desiredDate, entry.desiredDateRange)) { return; } if (!matchesDesiredWeekday(pickupDate, desiredWeekday)) { diff --git a/src/App.js b/src/App.js index 1df8a6c..641afe2 100644 --- a/src/App.js +++ b/src/App.js @@ -748,6 +748,9 @@ function App() { if (value && updated.desiredDate) { delete updated.desiredDate; } + if (value && updated.desiredDateRange) { + delete updated.desiredDateRange; + } return updated; }) ); @@ -764,6 +767,49 @@ function App() { if (value && updated.desiredWeekday) { delete updated.desiredWeekday; } + if (value && updated.desiredDateRange) { + delete updated.desiredDateRange; + } + return updated; + }) + ); + }; + + const handleDateRangeChange = (entryId, field, value) => { + setIsDirty(true); + setConfig((prev) => + prev.map((item) => { + if (item.id !== entryId) { + return item; + } + const nextRange = { + start: item.desiredDateRange?.start || null, + end: item.desiredDateRange?.end || null + }; + if (field === 'start') { + nextRange.start = value || null; + if (nextRange.end && value && value > nextRange.end) { + nextRange.end = value; + } + } else if (field === 'end') { + nextRange.end = value || null; + if (nextRange.start && value && value < nextRange.start) { + nextRange.start = value; + } + } + const hasRange = Boolean(nextRange.start || nextRange.end); + const updated = { ...item }; + if (hasRange) { + updated.desiredDateRange = nextRange; + if (updated.desiredWeekday) { + delete updated.desiredWeekday; + } + if (updated.desiredDate) { + delete updated.desiredDate; + } + } else if (updated.desiredDateRange) { + delete updated.desiredDateRange; + } return updated; }) ); @@ -1165,19 +1211,22 @@ function App() { Nur benachrichtigen Wochentag Spezifisches Datum + Datumsbereich Aktionen {visibleConfig.length === 0 && ( - + Keine sichtbaren Einträge. Nutze „Verfügbare Betriebe“, um Betriebe hinzuzufügen oder ausgeblendete Einträge zurückzuholen. )} - {visibleConfig.map((item, index) => ( - + {visibleConfig.map((item, index) => { + const hasDateRange = Boolean(item.desiredDateRange?.start || item.desiredDateRange?.end); + return ( + handleWeekdayChange(item.id, e.target.value)} className="border rounded p-2 w-full bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" - disabled={item.desiredDate} + disabled={item.desiredDate || hasDateRange} > {weekdays.map((day) => ( @@ -1230,9 +1279,34 @@ function App() { value={item.desiredDate || ''} onChange={(e) => handleDateChange(item.id, e.target.value)} className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" - disabled={item.desiredWeekday} + disabled={item.desiredWeekday || hasDateRange} /> + +
+
+ + handleDateRangeChange(item.id, 'start', e.target.value)} + className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + disabled={item.desiredWeekday || item.desiredDate} + /> +
+
+ + handleDateRangeChange(item.id, 'end', e.target.value)} + className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + disabled={item.desiredWeekday || item.desiredDate} + min={item.desiredDateRange?.start || undefined} + /> +
+
+
diff --git a/src/PickupConfigEditor.js b/src/PickupConfigEditor.js index d2c7a3e..97051d2 100644 --- a/src/PickupConfigEditor.js +++ b/src/PickupConfigEditor.js @@ -94,6 +94,9 @@ const PickupConfigEditor = () => { if (newConfig[index].desiredDate) { delete newConfig[index].desiredDate; } + if (newConfig[index].desiredDateRange) { + delete newConfig[index].desiredDateRange; + } setConfig(newConfig); }; @@ -104,6 +107,41 @@ const PickupConfigEditor = () => { if (newConfig[index].desiredWeekday) { delete newConfig[index].desiredWeekday; } + if (newConfig[index].desiredDateRange) { + delete newConfig[index].desiredDateRange; + } + setConfig(newConfig); + }; + + const handleDateRangeChange = (index, field, value) => { + const newConfig = [...config]; + const nextRange = { + start: newConfig[index].desiredDateRange?.start || null, + end: newConfig[index].desiredDateRange?.end || null + }; + if (field === 'start') { + nextRange.start = value || null; + if (nextRange.end && value && value > nextRange.end) { + nextRange.end = value; + } + } else if (field === 'end') { + nextRange.end = value || null; + if (nextRange.start && value && value < nextRange.start) { + nextRange.start = value; + } + } + const hasRange = Boolean(nextRange.start || nextRange.end); + if (hasRange) { + newConfig[index].desiredDateRange = nextRange; + if (newConfig[index].desiredWeekday) { + delete newConfig[index].desiredWeekday; + } + if (newConfig[index].desiredDate) { + delete newConfig[index].desiredDate; + } + } else if (newConfig[index].desiredDateRange) { + delete newConfig[index].desiredDateRange; + } setConfig(newConfig); }; @@ -139,10 +177,13 @@ const PickupConfigEditor = () => { Nur benachrichtigen Wochentag Spezifisches Datum + Datumsbereich - {config.map((item, index) => ( + {config.map((item, index) => { + const hasDateRange = Boolean(item.desiredDateRange?.start || item.desiredDateRange?.end); + return ( { value={item.desiredWeekday || ''} onChange={(e) => handleWeekdayChange(index, e.target.value)} className="border rounded p-1 w-full" - disabled={item.desiredDate} + disabled={item.desiredDate || hasDateRange} > {weekdays.map((day) => ( @@ -192,11 +233,37 @@ const PickupConfigEditor = () => { value={item.desiredDate || ''} onChange={(e) => handleDateChange(index, e.target.value)} className="border rounded p-1 w-full" - disabled={item.desiredWeekday} + disabled={item.desiredWeekday || hasDateRange} /> + +
+
+ + handleDateRangeChange(index, 'start', e.target.value)} + className="border rounded p-1 w-full" + disabled={item.desiredWeekday || item.desiredDate} + /> +
+
+ + handleDateRangeChange(index, 'end', e.target.value)} + className="border rounded p-1 w-full" + disabled={item.desiredWeekday || item.desiredDate} + min={item.desiredDateRange?.start || undefined} + /> +
+
+ - ))} + ); + })}