Deploy
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user