diff --git a/backend/api/bookability.py b/backend/api/bookability.py index 5197666..040d8c0 100644 --- a/backend/api/bookability.py +++ b/backend/api/bookability.py @@ -52,12 +52,14 @@ class DateRateInfo(BaseModel): tariffs: List[TariffInfo] tariff_count: int occupancy: Optional[OccupancyInfo] = None + valid_from: Optional[str] = None class RateMatrixResponse(BaseModel): categories: List[CategoryInfo] dates: List[str] matrix: Dict[str, Dict[str, DateRateInfo]] + date_last_updated: Dict[str, Optional[str]] = {} # ============================================ @@ -229,19 +231,32 @@ async def get_rate_matrix( # Preserve existing occupancy data existing_occ = matrix[cat_id][rate_date].occupancy + vf = row.valid_from.isoformat() if row.valid_from else None matrix[cat_id][rate_date] = DateRateInfo( rate_gross=float(row.rate_gross) if row.rate_gross else None, rate_net=float(row.rate_net) if row.rate_net else None, tariffs=tariffs_list, tariff_count=tariffs_data.get('tariff_count', len(tariffs_list)), - occupancy=existing_occ + occupancy=existing_occ, + valid_from=vf ) + # Per-date latest update time (max valid_from across all categories for each date) + date_last_updated: Dict[str, Optional[str]] = {} + for date_str in dates: + latest = None + for cat in categories: + vf = matrix.get(cat.category_id, {}).get(date_str, DateRateInfo(tariffs=[], tariff_count=0)).valid_from + if vf and (latest is None or vf > latest): + latest = vf + date_last_updated[date_str] = latest + return RateMatrixResponse( categories=categories, dates=dates, - matrix=matrix + matrix=matrix, + date_last_updated=date_last_updated ) diff --git a/frontend/src/pages/Bookability.tsx b/frontend/src/pages/Bookability.tsx index 2f45e12..e29d1ab 100644 --- a/frontend/src/pages/Bookability.tsx +++ b/frontend/src/pages/Bookability.tsx @@ -37,12 +37,14 @@ interface DateRateInfo { tariffs: TariffInfo[] tariff_count: number occupancy?: OccupancyInfo + valid_from?: string | null } interface RateMatrixData { categories: CategoryInfo[] dates: string[] matrix: Record> + date_last_updated?: Record } // Helper functions @@ -56,6 +58,16 @@ const formatDayOfWeek = (dateStr: string): string => { return date.toLocaleDateString('en-GB', { weekday: 'short' }) } +const formatLastUpdated = (isoStr: string | null | undefined): string => { + if (!isoStr) return '' + const d = new Date(isoStr) + const now = new Date() + const isToday = d.toDateString() === now.toDateString() + return isToday + ? d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' }) + : d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short' }) + ' ' + d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' }) +} + const isWeekend = (dateStr: string): boolean => { const date = new Date(dateStr + 'T00:00:00') const day = date.getDay() @@ -422,6 +434,11 @@ const Bookability: React.FC = () => { > {refreshingDate === dateStr ? '...' : '↻'} + {data.date_last_updated?.[dateStr] && ( + + {formatLastUpdated(data.date_last_updated[dateStr])} + + )} ))} @@ -853,6 +870,12 @@ const styles: Record = { fontSize: '13px', fontWeight: 600, }, + lastUpdated: { + fontSize: '9px', + color: 'var(--text-mid)', + opacity: 0.7, + lineHeight: 1, + }, weekendHeader: { background: 'var(--body-bg)', },