This commit is contained in:
Vula Builder
2026-09-15 18:02:54 +00:00
parent 4363bd5a8d
commit c823635186
+89
View File
@@ -0,0 +1,89 @@
'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>
)
}