/opt/mawid/apps/dashboard/app/(app)/settings
Edit: /opt/mawid/apps/dashboard/app/(app)/settings/page.tsx (14530B)
'use client';
import { useEffect, useState } from 'react';
import { api, serviceName } from '@/lib/api';
import { useLang, type UiKey } from '@/lib/i18n';
import { Button, Card, Field, Input, Select, Spinner } from '@/components/ui';
type Interval = { start: string; end: string };
type WeekHours = Partial
>;
interface Clinic {
id: string;
name: string;
phone: string;
timezone: string;
defaultLanguage: string;
workingHours: WeekHours;
settings: {
reminderOffsetsHours?: number[];
waitlistHoldMinutes?: number;
ownerWaPhone?: string;
depositEnabled?: boolean;
};
}
interface ServiceRow {
id: string;
name: { ar?: string; tr?: string; en?: string };
durationMinutes: number;
price: string | number | null;
active: boolean;
}
interface StaffRow {
id: string;
name: string;
role: string;
active: boolean;
services: { id: string }[];
}
const DAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] as const;
export default function SettingsPage() {
const { lang } = useLang();
const [clinic, setClinic] = useState(null);
const [services, setServices] = useState([]);
const [staff, setStaff] = useState([]);
const [flash, setFlash] = useState(null);
async function loadAll() {
const [c, sv, st] = await Promise.all([
api('/clinic'),
api('/services'),
api('/staff'),
]);
setClinic(c);
setServices(sv);
setStaff(st);
}
useEffect(() => {
loadAll();
}, []);
function flashSaved(section: string) {
setFlash(section);
setTimeout(() => setFlash(null), 1500);
}
if (!clinic) return ;
async function saveClinic(patch: object, section: string) {
const updated = await api('/clinic', { method: 'PATCH', body: JSON.stringify(patch) });
setClinic(updated);
flashSaved(section);
}
return (
);
}
function ClinicInfoCard({
clinic,
onSave,
saved,
}: {
clinic: Clinic;
onSave: (patch: object, section: string) => Promise;
saved: boolean;
}) {
const { t } = useLang();
const [name, setName] = useState(clinic.name);
const [phone, setPhone] = useState(clinic.phone);
const [timezone, setTimezone] = useState(clinic.timezone);
const [defaultLanguage, setDefaultLanguage] = useState(clinic.defaultLanguage);
const [ownerWaPhone, setOwnerWaPhone] = useState(clinic.settings.ownerWaPhone ?? '');
const [offsets, setOffsets] = useState(
(clinic.settings.reminderOffsetsHours ?? [24, 2]).join(', '),
);
function save() {
const reminderOffsetsHours = offsets
.split(',')
.map((s) => Number(s.trim()))
.filter((n) => !Number.isNaN(n) && n > 0);
onSave(
{
name,
phone,
timezone,
defaultLanguage,
// settings are replaced wholesale — merge the existing object
settings: {
...clinic.settings,
ownerWaPhone: ownerWaPhone || undefined,
reminderOffsetsHours,
},
},
'info',
);
}
return (
{t('clinicInfo')}
setName(e.target.value)} />
setPhone(e.target.value)} dir="ltr" />
setTimezone(e.target.value)} dir="ltr" />
setOwnerWaPhone(e.target.value)} dir="ltr" />
setOffsets(e.target.value)} dir="ltr" />
);
}
function WorkingHoursCard({
clinic,
onSave,
saved,
}: {
clinic: Clinic;
onSave: (patch: object, section: string) => Promise;
saved: boolean;
}) {
const { t } = useLang();
const [hours, setHours] = useState(clinic.workingHours ?? {});
function setDay(day: string, intervals: Interval[]) {
setHours((prev) => ({ ...prev, [day]: intervals }));
}
return (
{t('workingHours')}
{DAYS.map((day) => {
const intervals = hours[day] ?? [];
return (
{t(`day_${day}` as UiKey)}
{intervals.map((interval, i) => (
setDay(
day,
intervals.map((v, j) => (j === i ? { ...v, start: e.target.value } : v)),
)
}
/>
–
setDay(
day,
intervals.map((v, j) => (j === i ? { ...v, end: e.target.value } : v)),
)
}
/>
))}
);
})}
);
}
function ServicesCard({
services,
lang,
onChanged,
}: {
services: ServiceRow[];
lang: string;
onChanged: () => void;
}) {
const { t } = useLang();
const [editing, setEditing] = useState>>({});
const [adding, setAdding] = useState(false);
const [newService, setNewService] = useState({ tr: '', ar: '', en: '', duration: 30, price: '' });
async function saveRow(row: ServiceRow) {
const patch = editing[row.id];
if (!patch) return;
await api(`/services/${row.id}`, {
method: 'PATCH',
body: JSON.stringify({
...(patch.name ? { name: { ...row.name, ...patch.name } } : {}),
...(patch.durationMinutes !== undefined ? { durationMinutes: patch.durationMinutes } : {}),
...(patch.active !== undefined ? { active: patch.active } : {}),
}),
});
setEditing((prev) => ({ ...prev, [row.id]: {} }));
onChanged();
}
async function addService() {
await api('/services', {
method: 'POST',
body: JSON.stringify({
name: { tr: newService.tr, ar: newService.ar, en: newService.en },
durationMinutes: newService.duration,
...(newService.price ? { price: Number(newService.price) } : {}),
}),
});
setAdding(false);
setNewService({ tr: '', ar: '', en: '', duration: 30, price: '' });
onChanged();
}
return (
{t('services')}
{adding && (
)}
);
}
function StaffCard({
staff,
services,
lang,
onChanged,
}: {
staff: StaffRow[];
services: ServiceRow[];
lang: string;
onChanged: () => void;
}) {
const { t } = useLang();
const [adding, setAdding] = useState(false);
const [newStaff, setNewStaff] = useState({ name: '', role: 'practitioner' });
async function toggleService(row: StaffRow, serviceId: string) {
const current = row.services.map((s) => s.id);
const next = current.includes(serviceId)
? current.filter((id) => id !== serviceId)
: [...current, serviceId];
await api(`/staff/${row.id}`, { method: 'PATCH', body: JSON.stringify({ serviceIds: next }) });
onChanged();
}
async function addStaff() {
await api('/staff', {
method: 'POST',
body: JSON.stringify({ name: newStaff.name, role: newStaff.role }),
});
setAdding(false);
setNewStaff({ name: '', role: 'practitioner' });
onChanged();
}
return (
{t('staffSection')}
{adding && (
setNewStaff({ ...newStaff, name: e.target.value })}
/>
setNewStaff({ ...newStaff, role: e.target.value })}
/>
)}
{staff.map((row) => (
))}
);
}