Task #692: Auto-scroll bookings day grid to current time (red now-line)
- BookingDayGrid (artifacts/tx-os/src/pages/protocol.tsx) now renders inside its own scroll container (max-h-[60vh] overflow-y-auto overscroll-contain). - New scrollRef + useEffect: when the selected day is today and the now-line is within grid hours (showNow), sets scrollTop = nowTop - clientHeight/3 so the red line sits ~1/3 from the top; otherwise resets scroll to top. Effect deps: [dayKey, showNow, scrollSignal] — not re-run on minute ticks, so it never fights user scrolling. - "اليوم" button bumps a todayScrollSignal counter passed as optional scrollSignal prop, re-scrolling even when the selected day is already today. - Removed the previous outer max-h-[70vh] wrapper around BookingDayGrid to avoid nested scrollbars. - No changes to booking block layout, statuses, or list view. RTL-safe (logical properties untouched). - tsc --noEmit clean for tx-os; architect review addressed (60vh per spec).
This commit is contained in:
@@ -353,6 +353,7 @@ function BookingDayGrid({
|
||||
isAr,
|
||||
roomName,
|
||||
emptyText,
|
||||
scrollSignal = 0,
|
||||
}: {
|
||||
bookings: Booking[];
|
||||
selectedDay: Date;
|
||||
@@ -360,6 +361,7 @@ function BookingDayGrid({
|
||||
isAr: boolean;
|
||||
roomName: (id: number) => string;
|
||||
emptyText: string;
|
||||
scrollSignal?: number;
|
||||
}) {
|
||||
const hours: number[] = [];
|
||||
for (let h = GRID_HOUR_START; h <= GRID_HOUR_END; h++) hours.push(h);
|
||||
@@ -391,6 +393,25 @@ function BookingDayGrid({
|
||||
hour12: true,
|
||||
}).format(new Date(iso));
|
||||
|
||||
// The grid scrolls inside its own fixed-height container so we can jump
|
||||
// straight to the red "now" line without touching the page scroll. When
|
||||
// the selected day is today, position the line ~1/3 from the top of the
|
||||
// viewport; for other days start at the top.
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const dayKey = `${selectedDay.getFullYear()}-${selectedDay.getMonth()}-${selectedDay.getDate()}`;
|
||||
useEffect(() => {
|
||||
const el = scrollRef.current;
|
||||
if (!el) return;
|
||||
if (showNow) {
|
||||
el.scrollTop = Math.max(0, nowTop - el.clientHeight / 3);
|
||||
} else {
|
||||
el.scrollTop = 0;
|
||||
}
|
||||
// Re-run only when the day changes (or the now-line appears/disappears),
|
||||
// not on every minute tick — so we never fight the user's scrolling.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dayKey, showNow, scrollSignal]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{dayItems.length === 0 && (
|
||||
@@ -398,7 +419,11 @@ function BookingDayGrid({
|
||||
{emptyText}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex" style={{ height: gridHeight + 8 }}>
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="max-h-[60vh] overflow-y-auto overscroll-contain"
|
||||
>
|
||||
<div className="flex" style={{ height: gridHeight + 8 }}>
|
||||
<div className="relative w-14 shrink-0" style={{ height: gridHeight }}>
|
||||
{hours.map((h) => (
|
||||
<div
|
||||
@@ -474,6 +499,7 @@ function BookingDayGrid({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -628,6 +654,9 @@ export default function ProtocolPage() {
|
||||
setSelectedBookings(new Set());
|
||||
}, [debouncedBookingSearch, bookingsView, tab]);
|
||||
const [selectedDay, setSelectedDay] = useState<Date>(() => new Date());
|
||||
// Bumped by the "اليوم" button to re-scroll the day grid to the now-line
|
||||
// even when the selected day is already today.
|
||||
const [todayScrollSignal, setTodayScrollSignal] = useState(0);
|
||||
const [calendarMonth, setCalendarMonth] = useState<Date>(() => new Date());
|
||||
const now = useNow(60000);
|
||||
const bookedDates = useMemo(() => {
|
||||
@@ -1444,6 +1473,7 @@ export default function ProtocolPage() {
|
||||
const today = new Date();
|
||||
setSelectedDay(today);
|
||||
setCalendarMonth(today);
|
||||
setTodayScrollSignal((v) => v + 1);
|
||||
}}
|
||||
>
|
||||
<CalendarDays size={14} className="me-1" />
|
||||
@@ -1462,16 +1492,15 @@ export default function ProtocolPage() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-[70vh] overflow-y-auto">
|
||||
<BookingDayGrid
|
||||
bookings={bookings.data ?? []}
|
||||
selectedDay={selectedDay}
|
||||
now={now}
|
||||
isAr={isAr}
|
||||
roomName={roomName}
|
||||
emptyText={t("protocol.bookings.emptyDay")}
|
||||
/>
|
||||
</div>
|
||||
<BookingDayGrid
|
||||
bookings={bookings.data ?? []}
|
||||
selectedDay={selectedDay}
|
||||
now={now}
|
||||
isAr={isAr}
|
||||
roomName={roomName}
|
||||
emptyText={t("protocol.bookings.emptyDay")}
|
||||
scrollSignal={todayScrollSignal}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user