M39: harden application and acceptance gates
This commit is contained in:
@@ -3,6 +3,7 @@ import { Link, useSearchParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { api } from "../api/client";
|
||||
import type { Booking, Page } from "../api/types";
|
||||
import { brusselsDayStartIso, brusselsNextDayStartIso, toBrusselsDateTimeLocal } from "../i18n/brusselsDateTime";
|
||||
import { useLocaleFormat } from "../i18n/format";
|
||||
import { StatusBadge } from "../components/Badge";
|
||||
import { EmptyState, ErrorState, LoadingState, PageHeader } from "../components/PageChrome";
|
||||
@@ -10,15 +11,9 @@ import { Pagination } from "../components/Pagination";
|
||||
|
||||
const STATUS_OPTIONS = ["reserved", "active", "returned", "cancelled", "blocked"];
|
||||
|
||||
function localDateString(date = new Date()): string {
|
||||
const offset = date.getTimezoneOffset() * 60_000;
|
||||
return new Date(date.getTime() - offset).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function nextLocalDay(value: string): string {
|
||||
const date = new Date(`${value}T12:00:00`);
|
||||
date.setDate(date.getDate() + 1);
|
||||
return localDateString(date);
|
||||
/** Today's Brussels calendar day (YYYY-MM-DD) -- the operational "today", not the browser's. */
|
||||
function brusselsToday(): string {
|
||||
return toBrusselsDateTimeLocal(new Date()).slice(0, 10);
|
||||
}
|
||||
|
||||
export function Bookings() {
|
||||
@@ -41,7 +36,7 @@ export function Bookings() {
|
||||
if (value === null || value === "") next.delete(key);
|
||||
else next.set(key, String(value));
|
||||
});
|
||||
setSearchParams(next);
|
||||
setSearchParams(next, { replace: true });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -50,14 +45,19 @@ export function Bookings() {
|
||||
const params = new URLSearchParams({ page: String(page), page_size: "25" });
|
||||
if (status) params.set("status", status);
|
||||
if (query) params.set("query", query);
|
||||
if (startsFrom) params.set("starts_from", new Date(`${startsFrom}T00:00:00`).toISOString());
|
||||
if (startsTo) params.set("starts_to", new Date(`${nextLocalDay(startsTo)}T00:00:00`).toISOString());
|
||||
// Date filters are Brussels calendar days regardless of the browser's own time zone.
|
||||
if (startsFrom) params.set("starts_from", brusselsDayStartIso(startsFrom));
|
||||
if (startsTo) params.set("starts_to", brusselsNextDayStartIso(startsTo));
|
||||
if (location) params.set("location", location);
|
||||
params.set("sort", sort);
|
||||
const controller = new AbortController();
|
||||
api
|
||||
.get<Page<Booking>>(`/api/v1/bookings?${params.toString()}`)
|
||||
.get<Page<Booking>>(`/api/v1/bookings?${params.toString()}`, { signal: controller.signal })
|
||||
.then(setBookings)
|
||||
.catch(() => setError(t("list.unavailable")));
|
||||
.catch(() => {
|
||||
if (!controller.signal.aborted) setError(t("list.unavailable"));
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [page, query, status, startsFrom, startsTo, location, sort, t]);
|
||||
|
||||
return (
|
||||
@@ -82,8 +82,8 @@ export function Bookings() {
|
||||
<label>{t("list.sortLabel")}<select value={sort} onChange={(event) => updateFilters({ sort: event.target.value, page: 1 })}><option value="operational">{t("list.sortOperational")}</option><option value="starts_asc">{t("list.sortAscending")}</option><option value="starts_desc">{t("list.sortDescending")}</option></select></label>
|
||||
</form>
|
||||
<div className="filter-presets" aria-label={t("list.presetsLabel")}>
|
||||
<button type="button" onClick={() => updateFilters({ from: localDateString(), to: localDateString(), status: null, page: 1 })}>{t("list.todayPreset")}</button>
|
||||
<button type="button" onClick={() => updateFilters({ from: localDateString(), to: null, status: "reserved", sort: "starts_asc", page: 1 })}>{t("list.upcomingPreset")}</button>
|
||||
<button type="button" onClick={() => updateFilters({ from: brusselsToday(), to: brusselsToday(), status: null, page: 1 })}>{t("list.todayPreset")}</button>
|
||||
<button type="button" onClick={() => updateFilters({ from: brusselsToday(), to: null, status: "reserved", sort: "starts_asc", page: 1 })}>{t("list.upcomingPreset")}</button>
|
||||
<button type="button" onClick={() => setSearchParams(new URLSearchParams())}>{t("list.clearFilters")}</button>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user