Rebuild the /admin/appointments page visual layer to match the tauri
clinic-pro-tauri "turns" design while keeping all existing data wiring and
backend endpoints unchanged (add/edit/move/transfer-reserve/replace already
supported via PATCH /api/v1/appointment/{uuid} and POST /api/v1/my/appointment).
Frontend (assets/admin):
- Sidebar: نوبتها becomes an expandable parent with sub-items
«نوبت های تایید شده» (/admin/appointments) and «افزودن نوبت»
(/admin/appointments/new); auto-expands on active child. Applied to
admin/clinic/doctor/secretary roles. Adds nav-subitem styling.
- New presentational components under components/appointments/: tauri status
palette (turnStatus), TurnsStatInfo, TurnsViewToggle (sliding), DoctorTabs
(underline), TurnsTimeline (marker rail + status cards, empty slot → افزودن
نوبت), TurnsTable.
- AppointmentsPage recomposed with the new components (stats bar, doctor tabs,
view toggle, timeline/table), preserving queries, filters, pagination,
quick-book modal and the row actions menu.
- AppointmentCreatePage: full-page create form (CreateTurn layout) at
/admin/appointments/new, reusing POST /api/v1/my|admin/appointment.
Tests: TurnsStatInfo, TurnsTimeline, Sidebar (expandable), AppointmentsPage,
AppointmentCreatePage. Backend move/reserve/replace verified green via existing
tests/Appointment/AppointmentUpdateTest + AppointmentWorkflowFieldsTest.
No API endpoints changed → no docs/api change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
62 lines
2.5 KiB
TypeScript
62 lines
2.5 KiB
TypeScript
import { UsersIcon, UserPlusIcon, ClockIcon, XCircleIcon } from '@heroicons/react/24/outline';
|
|
|
|
/**
|
|
* نوار آمار نوبتها — بازسازیِ `TurnsStatInfo.jsx` طرح tauri (کارت سفید با
|
|
* جداکنندههای عمودی و چهار آمار). رنگها از `data/turnsHeaderStats.json` مبدأ.
|
|
*/
|
|
export interface TurnsStats {
|
|
total: number;
|
|
completed: number;
|
|
waiting: number;
|
|
cancelled: number;
|
|
}
|
|
|
|
interface StatDef {
|
|
title: string;
|
|
value: number;
|
|
Icon: React.ElementType;
|
|
iconColor: string;
|
|
iconBg: string;
|
|
}
|
|
|
|
function BadgeIcon({ Icon, color, bg }: { Icon: React.ElementType; color: string; bg: string }) {
|
|
return (
|
|
<div style={{
|
|
width: 44, height: 44, borderRadius: 10, background: bg,
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
|
|
}}>
|
|
<Icon style={{ width: 22, height: 22, color }} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function TurnsStatInfo({ stats }: { stats: TurnsStats }) {
|
|
const items: StatDef[] = [
|
|
{ title: 'کل نوبت های امروز', value: stats.total, Icon: UsersIcon, iconColor: '#494cb3', iconBg: '#e9ebfb' },
|
|
{ title: 'نوبت های انجام شده', value: stats.completed, Icon: UserPlusIcon, iconColor: '#0d9f43', iconBg: '#e7f6ed' },
|
|
{ title: 'بیماران در انتظار', value: stats.waiting, Icon: ClockIcon, iconColor: '#e0a838', iconBg: '#fff2dc' },
|
|
{ title: 'نوبت های لغو شده', value: stats.cancelled, Icon: XCircleIcon, iconColor: '#ee5c6d', iconBg: '#fad6da' },
|
|
];
|
|
|
|
return (
|
|
<div style={{
|
|
display: 'flex', alignItems: 'center', background: 'var(--surface)',
|
|
border: '1px solid var(--border)', borderRadius: 'var(--r)',
|
|
padding: '14px 20px', marginBottom: 16, gap: 4,
|
|
}}>
|
|
{items.map((it, i) => (
|
|
<div key={it.title} style={{ display: 'flex', alignItems: 'center', flex: 1, gap: 10 }}>
|
|
{i > 0 && <div style={{ width: 1, height: 48, background: 'var(--border)', marginInlineEnd: 8 }} />}
|
|
<BadgeIcon Icon={it.Icon} color={it.iconColor} bg={it.iconBg} />
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
<span style={{ fontSize: 12, color: 'var(--text-2)', whiteSpace: 'nowrap' }}>{it.title}</span>
|
|
<span style={{ fontSize: 18, fontWeight: 700, color: 'var(--text)' }}>
|
|
{it.value.toLocaleString('fa-IR')}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|