- Automation rules propose suggestions instead of creating tasks; notify per rule config; corrected varroa measurements refresh or resolve their suggestion - Inspection values can be taken over into the colony card (occupiedFrames 0-40, strength deprecated); warning counts use the same visibility rule as the notification list - Biotech chains: swarm check after queen cells, queen acceptance check, split schedules (raise own, queen cell, queen) - Season autopilot core with A1 inspection due, A2 varroa measurement, A3 recheck after treatment; colony sync and daily step - Frontend: suggestions page with groups, bulk accept, undo, paging, focus management and permission gating - Frontend: varroa traffic light from seasonal bands, sample size, one range request for the colony chart - Notifications: mark-read answers 404 for rows the user may no longer see Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
/**
|
|
* The calendar day the beekeeper sees on their phone, as "YYYY-MM-DD".
|
|
*
|
|
* Not `toISOString().slice(0, 10)`: that is the UTC day, which in Germany is
|
|
* still yesterday until 01:00 (winter) or 02:00 (summer) - a form opened
|
|
* shortly after midnight would prefill the wrong date.
|
|
*/
|
|
export function toLocalIsoDate(date: Date): string {
|
|
const year = String(date.getFullYear()).padStart(4, "0");
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
|
|
/** Today's local calendar day, e.g. the default date of a new record. */
|
|
export function todayLocalIsoDate(): string {
|
|
return toLocalIsoDate(new Date());
|
|
}
|
|
|
|
const CALENDAR_DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
|
/**
|
|
* A day picked in a date field ("YYYY-MM-DD") as the API stores calendar
|
|
* days: midnight UTC of exactly that day, whatever the phone's time zone.
|
|
* Throws on anything that is not a real calendar day rather than sending a
|
|
* shifted or invalid date.
|
|
*/
|
|
export function toApiDate(calendarDay: string): string {
|
|
if (!CALENDAR_DAY.test(calendarDay)) {
|
|
throw new RangeError(`Kein Kalendertag: "${calendarDay}"`);
|
|
}
|
|
const iso = new Date(`${calendarDay}T00:00:00.000Z`).toISOString();
|
|
if (iso.slice(0, 10) !== calendarDay) {
|
|
throw new RangeError(`Kein Kalendertag: "${calendarDay}"`);
|
|
}
|
|
return iso;
|
|
}
|
|
|
|
const CALENDAR_DAY_LENGTH = 10;
|
|
|
|
/** The "YYYY-MM-DD" part of an ISO timestamp as written, without any time zone shift. */
|
|
export function toCalendarDay(isoTimestamp: string): string {
|
|
return isoTimestamp.slice(0, CALENDAR_DAY_LENGTH);
|
|
}
|
|
|
|
const BERLIN_DAY = new Intl.DateTimeFormat("en-CA", {
|
|
timeZone: "Europe/Berlin",
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
});
|
|
|
|
/** The Europe/Berlin calendar day of an instant, as "YYYY-MM-DD" (the backend's day). */
|
|
export function berlinCalendarDay(isoTimestamp: string): string {
|
|
return BERLIN_DAY.format(new Date(isoTimestamp));
|
|
}
|