Files
vula-f7951bab/src/components/BusinessHours.tsx
T
Vula Builder 339cbc898c Deploy
2026-10-11 14:20:23 +00:00

90 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useState } from 'react'
import { useVulaContent } from '@/hooks/useVulaContent'
import { Clock } from 'lucide-react'
const DAY_ORDER = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']
type HourRow = { day: string; open: string; close: string; closed: boolean; note: string }
/**
* The weekday where the business is. Not the server's — it runs UTC, and for
* the first two hours of every South African day it would still say yesterday.
*/
export function todayIn(at: Date = new Date()): string {
return new Intl.DateTimeFormat('en-US', { weekday: 'long', timeZone: 'Africa/Johannesburg' }).format(at)
}
/**
* The practice's opening hours, from the one place they are edited.
*
* `compact` is the footer form — no heading, tighter rows — so a footer can
* show real hours without anyone typing them in by hand again.
*/
export default function BusinessHours({ compact = false, title = 'Opening hours' }: { compact?: boolean; title?: string }) {
const { items, isLoading } = useVulaContent('business_hours')
// Chosen after the page arrives. Rendered on the server, "today" is whenever
// the page was built, not when a patient opens it.
const [today, setToday] = useState('')
useEffect(() => { setToday(todayIn()) }, [])
const rows: HourRow[] = (items ?? [])
.map((item) => {
const d = (item.data ?? {}) as Record<string, unknown>
return {
day: String(d.day_of_week ?? ''),
open: String(d.open_time ?? ''),
close: String(d.close_time ?? ''),
closed: Boolean(d.is_closed),
note: String(d.note ?? ''),
}
})
.filter((r) => r.day)
.sort((a, b) => DAY_ORDER.indexOf(a.day) - DAY_ORDER.indexOf(b.day))
// Nothing rather than an empty frame. Seven rows reading "Day —" is what the
// AI-written version did, and it looks like a broken website rather than a
// business that has not filled its hours in yet.
if (isLoading || rows.length === 0) return null
const line = (r: HourRow) =>
r.closed ? 'Closed' : r.note ? r.note : r.open && r.close ? `${r.open} – ${r.close}` : '—'
if (compact) {
return (
<ul className="space-y-1 text-sm">
{rows.map((r) => (
<li key={r.day} className={'flex justify-between gap-4' + (r.day === today ? ' font-semibold' : '')}>
<span className={r.day === today ? 'text-foreground' : 'text-muted-foreground'}>{r.day.slice(0, 3)}</span>
<span className={r.closed ? 'text-muted-foreground' : ''}>{line(r)}</span>
</li>
))}
</ul>
)
}
return (
<div data-vula-cms="business_hours" className="rounded-2xl border border-border bg-card p-6">
<div className="flex items-center gap-3 mb-4">
<span className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-primary">
<Clock className="h-5 w-5" />
</span>
<h3 className="text-lg font-semibold">{title}</h3>
</div>
<ul className="divide-y divide-border">
{rows.map((r) => (
<li key={r.day} className={'flex items-center justify-between gap-4 py-2.5 text-sm' + (r.day === today ? ' -mx-3 rounded-lg bg-muted px-3' : '')}>
<span className="font-medium">
{r.day}
{r.day === today && (
<span className="ml-2 rounded-full bg-primary/10 px-2 py-0.5 text-xs font-semibold text-primary">Today</span>
)}
</span>
<span className={r.closed ? 'text-muted-foreground' : 'text-foreground'}>{line(r)}</span>
</li>
))}
</ul>
</div>
)
}