/* global React */ const { useState: useStateC, useEffect: useEffectC, useMemo: useMemoC } = React; // Shared helpers ============================================================ window.LQ_fmtINR = (n) => '₹' + (Number(n) || 0).toLocaleString('en-IN'); window.LQ_fmtDate = (s) => { if (!s) return ''; const d = new Date(s); if (isNaN(d)) return s; return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); }; window.LQ_fmtDateTime = (s) => { if (!s) return ''; const d = new Date(s.replace(' ', 'T')); if (isNaN(d)) return s; return d.toLocaleString('en-IN', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit', hour12: true }); }; window.LQ_phone = (m) => (m || '').replace(/[^\d+]/g, ''); window.LQ_waLink = (mobile, msg) => `https://wa.me/${(mobile || '').replace(/^\+/, '').replace(/[^\d]/g, '')}?text=${encodeURIComponent(msg || '')}`; // ============================================================================= // DASHBOARD // ============================================================================= window.DashboardSection = function DashboardSection({ user }) { const [data, setData] = useStateC(null); const [err, setErr] = useStateC(''); useEffectC(() => { window.LQ.request('/dashboard').then(setData).catch((e) => setErr(e.message)); }, []); if (err) return
{err}
; if (!data) return
Loading dashboard…
; const stats = data.stats || {}; const cards = [ { label: 'Today\'s Appointments', value: stats.today_appointments, sub: 'Pending or confirmed' }, { label: 'Pending Orders', value: stats.pending_orders, sub: 'Need processing or shipping' }, { label: 'Refill Due', value: stats.refill_due, sub: 'Clients to remind', highlight: stats.refill_due > 0 }, { label: 'Low Stock', value: stats.low_stock, sub: 'Press-ons running low', highlight: stats.low_stock > 0 }, { label: 'Total Clients', value: stats.total_clients, sub: 'Lifetime' }, { label: 'Completed This Week', value: stats.completed_this_week, sub: 'Mon onwards' }, ]; return ( <>

Welcome back, {user.name.split(' ')[0]}.

{cards.map((c) => (
{c.label}
{c.value ?? 0}
{c.sub}
))}

Upcoming Appointments

{!data.upcoming || data.upcoming.length === 0 ? (
No upcoming bookings.
) : ( )}

Recent Orders

{!data.recent_orders || data.recent_orders.length === 0 ? (
No orders yet.
) : ( )}
); }; // ============================================================================= // APPOINTMENTS // ============================================================================= const SERVICES = ['Gel Polish', 'Nail Art', 'Nail Extensions', 'Gel Extensions', 'Acrylic Extensions', 'Permanent Extensions', 'Manicure', 'Pedicure', 'Builder Gel', 'Refill']; const TIME_SLOTS = ['10:00 AM', '11:00 AM', '12:00 PM', '1:00 PM', '2:00 PM', '3:00 PM', '4:00 PM', '5:00 PM', '6:00 PM', '7:00 PM', '8:00 PM']; window.AppointmentsSection = function AppointmentsSection() { const [list, setList] = useStateC([]); const [loading, setLoading] = useStateC(true); const [filterDate, setFilterDate] = useStateC(''); const [filterStatus, setFilterStatus] = useStateC(''); const [showAdd, setShowAdd] = useStateC(false); const [editing, setEditing] = useStateC(null); function load() { setLoading(true); const qs = filterStatus ? '?status=' + filterStatus : ''; window.LQ.request('/appointments' + qs).then((r) => { setList(r || []); setLoading(false); }).catch(() => setLoading(false)); } useEffectC(load, [filterStatus]); const filtered = useMemoC(() => { return filterDate ? list.filter((a) => a.appt_date === filterDate) : list; }, [list, filterDate]); async function setStatus(id, status) { await window.LQ.request(`/appointments/${id}`, { method: 'PATCH', body: { status } }); load(); } async function del(id) { if (!confirm('Delete this appointment?')) return; await window.LQ.request(`/appointments/${id}`, { method: 'DELETE' }); load(); } function downloadIcs(id) { window.LQ.downloadFile(`/ics/${id}`, `appointment-${id}.ics`); } function downloadAllIcs() { window.LQ.downloadFile('/ics', 'lacquery-appointments.ics'); } return ( <>
setFilterDate(e.target.value)} /> {filterDate && }
{loading ?
Loading…
: filtered.length === 0 ?
No appointments found.
:
{filtered.map((a) => ( ))}
NameServiceDate · TimeSourceStatusActions
{a.name}
{a.mobile}
{a.service}
{window.LQ_fmtDate(a.appt_date)}
{a.appt_time}
{a.source} {a.status}
{a.status === 'pending' && } {(a.status === 'pending' || a.status === 'confirmed') && <> } WA
} {(showAdd || editing) && ( { setShowAdd(false); setEditing(null); }} onSaved={() => { setShowAdd(false); setEditing(null); load(); }} /> )} ); }; window.AppointmentModal = function AppointmentModal({ appt, onClose, onSaved }) { const isEdit = !!appt; const today = new Date().toISOString().slice(0, 10); const [form, setForm] = useStateC({ name: appt?.name || '', mobile: appt?.mobile || '', email: appt?.email || '', service: appt?.service || SERVICES[0], appt_date: appt?.appt_date || today, appt_time: appt?.appt_time || TIME_SLOTS[2], guests: appt?.guests || 1, status: appt?.status || 'pending', source: appt?.source || 'walk_in', price: appt?.price || '', notes: appt?.notes || '', }); const [saving, setSaving] = useStateC(false); const [err, setErr] = useStateC(''); async function save(e) { e.preventDefault(); setSaving(true); setErr(''); try { const body = { ...form }; if (body.price === '') body.price = null; else body.price = Number(body.price); body.guests = Number(body.guests); if (isEdit) await window.LQ.request(`/appointments/${appt.id}`, { method: 'PATCH', body }); else await window.LQ.request('/appointments', { method: 'POST', body }); onSaved(); } catch (e) { setErr(e.message); } finally { setSaving(false); } } function set(k, v) { setForm((f) => ({ ...f, [k]: v })); } return (
e.stopPropagation()}>

{isEdit ? 'Edit Appointment' : 'New / Walk-in Appointment'}

set('name', e.target.value)} />
set('mobile', e.target.value)} placeholder="+91…" />
set('email', e.target.value)} />
set('appt_date', e.target.value)} />
set('guests', e.target.value)} />
set('price', e.target.value)} placeholder="e.g. 1499" />