Task #666: Today & Book buttons in room-booking calendar view

Added two quick actions inside the Protocol bookings "Calendar" view's day
panel header (artifacts/tx-os/src/pages/protocol.tsx):
- "Today" (اليوم): outline button that resets selectedDay to new Date(), so
  the month picker, day time-grid, and red now-line all snap to today.
- "Book" (حجز): primary button gated by the same c?.canRequest capability as
  the existing toolbar new-booking button. Opens the new-booking dialog
  pre-filled with the currently selected day.

Implementation:
- Extended bookingDialog state with an optional `day?: Date`.
- BookingDialog now accepts an `initialDay?: Date` prop; for new (non-edit)
  bookings it pre-fills startsAt=09:00 and endsAt=10:00 on that day via a
  local dayAt(hour) helper (reuses toLocalInput). Edit flow unchanged.
- Added i18n keys protocol.bookings.today / protocol.bookings.book to both
  ar.json and en.json. Buttons use logical spacing (me-1) so they mirror in
  Arabic RTL.

No backend/API changes. List/Calendar toggle unaffected. tx-os typecheck
passes; HMR clean, no console errors. Screenshot tool unavailable (no
artifact registered in this repo), verified via typecheck + logs.
This commit is contained in:
Riyadh
2026-07-07 10:34:16 +00:00
parent 2bea68c92d
commit a8f4693fce
3 changed files with 48 additions and 9 deletions
+2
View File
@@ -1723,6 +1723,8 @@
"endsAt": "إلى",
"viewList": "قائمة",
"viewCalendar": "تقويم",
"today": "اليوم",
"book": "حجز",
"emptyDay": "لا توجد حجوزات في هذا اليوم.",
"shareLinkTitle": "رابط الحجز العام (للمشاركة مع الضيوف)",
"copyLink": "نسخ الرابط",
+2
View File
@@ -1596,6 +1596,8 @@
"endsAt": "To",
"viewList": "List",
"viewCalendar": "Calendar",
"today": "Today",
"book": "Book",
"emptyDay": "No bookings on this day.",
"shareLinkTitle": "Public booking link (share with guests)",
"copyLink": "Copy link",
+44 -9
View File
@@ -556,6 +556,7 @@ export default function ProtocolPage() {
const [bookingDialog, setBookingDialog] = useState<{
open: boolean;
edit?: Booking;
day?: Date;
}>({ open: false });
const [rejectTarget, setRejectTarget] = useState<{
kind: "booking" | "issue" | "external";
@@ -978,13 +979,36 @@ export default function ProtocolPage() {
</div>
</div>
<div className="min-w-0 flex-1 rounded-xl border border-slate-200 bg-white p-3">
<div className="mb-3 text-sm font-semibold text-slate-600">
{selectedDay.toLocaleDateString(isAr ? "ar" : "en", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
})}
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
<div className="text-sm font-semibold text-slate-600">
{selectedDay.toLocaleDateString(isAr ? "ar" : "en", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
})}
</div>
<div className="flex items-center gap-2">
<Button
size="sm"
variant="outline"
onClick={() => setSelectedDay(new Date())}
>
<CalendarDays size={14} className="me-1" />
{t("protocol.bookings.today")}
</Button>
{c?.canRequest && (
<Button
size="sm"
onClick={() =>
setBookingDialog({ open: true, day: selectedDay })
}
>
<Plus size={14} className="me-1" />
{t("protocol.bookings.book")}
</Button>
)}
</div>
</div>
<div className="max-h-[70vh] overflow-y-auto">
<BookingDayGrid
@@ -1364,6 +1388,7 @@ export default function ProtocolPage() {
<BookingDialog
rooms={rooms.data ?? []}
edit={bookingDialog.edit}
initialDay={bookingDialog.day}
nameOf={nameOf}
onClose={() => setBookingDialog({ open: false })}
onSaved={() => {
@@ -1709,6 +1734,7 @@ function DialogShell({
function BookingDialog({
rooms,
edit,
initialDay,
nameOf,
onClose,
onSaved,
@@ -1716,21 +1742,30 @@ function BookingDialog({
}: {
rooms: Room[];
edit?: Booking;
initialDay?: Date;
nameOf: (ar: string, en: string) => string;
onClose: () => void;
onSaved: () => void;
onError: (e: unknown) => void;
}) {
const { t } = useTranslation();
const dayAt = (hour: number) => {
if (!initialDay) return "";
const d = new Date(initialDay);
d.setHours(hour, 0, 0, 0);
return toLocalInput(d.toISOString());
};
const [title, setTitle] = useState(edit?.title ?? "");
const [roomId, setRoomId] = useState<string>(
edit ? String(edit.roomId) : rooms[0] ? String(rooms[0].id) : "",
);
const [requesterName, setRequesterName] = useState(edit?.requesterName ?? "");
const [startsAt, setStartsAt] = useState(
edit ? toLocalInput(edit.startsAt) : "",
edit ? toLocalInput(edit.startsAt) : dayAt(9),
);
const [endsAt, setEndsAt] = useState(
edit ? toLocalInput(edit.endsAt) : dayAt(10),
);
const [endsAt, setEndsAt] = useState(edit ? toLocalInput(edit.endsAt) : "");
const [notes, setNotes] = useState(edit?.notes ?? "");
const mut = useMutation({