feat(api): enhance appointment retrieval and add today stats endpoint

This commit is contained in:
hamed
2026-06-11 14:32:10 +03:30
parent 45ee725820
commit 704075770b
2 changed files with 112 additions and 36 deletions
+24 -13
View File
@@ -13,6 +13,8 @@ import { useAuthStore } from '../stores/authStore';
import AppointmentStatusDropdown from '../components/ui/AppointmentStatusDropdown';
import PersianCalendar from '../components/ui/PersianCalendar';
const EMPTY_ARR: Appointment[] = [];
// ─────────────────────────────────────────────────────────────────────────────
// Status config
// ─────────────────────────────────────────────────────────────────────────────
@@ -36,12 +38,12 @@ function statusMeta(s: string) {
// ─────────────────────────────────────────────────────────────────────────────
function IconTotal() {
const dots: [number, number][] = [];
for (const x of [10, 16, 22, 28, 34]) for (const y of [10, 16, 22, 28, 34]) dots.push([x, y]);
return (
<svg width="44" height="44" viewBox="0 0 44 44" fill="none">
<circle cx="22" cy="22" r="22" fill="#ede9fe" />
{[8,14,20,26].map(x => [8,14,20,26].map(y => (
<circle key={`${x}${y}`} cx={x} cy={y} r="2" fill="#7c3aed" opacity="0.7" />
)))}
{dots.map(([x, y]) => <circle key={`${x}-${y}`} cx={x} cy={y} r="1.8" fill="#7c3aed" opacity="0.6" />)}
</svg>
);
}
@@ -82,10 +84,14 @@ function IconCancelled() {
interface TodayStats { total: number; completed: number; waiting: number; cancelled: number; }
function StatsBar({ date }: { date: string }) {
function StatsBar({ date, isAdmin }: { date: string; isAdmin: boolean }) {
const { data } = useQuery<ApiResponse<TodayStats>>({
queryKey: ['appt-today-stats', date],
queryFn: () => api.get(`/api/v1/admin/appointments/today-stats?date=${date}`),
queryKey: ['appt-today-stats', date, isAdmin],
queryFn: () => api.get(
isAdmin
? `/api/v1/admin/appointments/today-stats?date=${date}`
: `/api/v1/my/appointments/today-stats?date=${date}`
),
});
const s = data?.data ?? { total: 0, completed: 0, waiting: 0, cancelled: 0 };
const stats = [
@@ -337,8 +343,11 @@ function NewAppointmentModal({
const [mobile, setMobile] = useState('');
const qc = useQueryClient();
const role = useAuthStore(s => s.primaryRole);
const createEndpoint = role === 'admin' ? '/api/v1/admin/appointment' : '/api/v1/appointment';
const mutation = useMutation({
mutationFn: () => api.post('/api/v1/admin/appointment', {
mutationFn: () => api.post(createEndpoint, {
doctor_uuid: slot.doctor_uuid,
slot_start: slot.start,
slot_end: slot.end,
@@ -405,7 +414,8 @@ function NewAppointmentModal({
// ─────────────────────────────────────────────────────────────────────────────
export default function AppointmentsPage() {
const { primaryRole, dbUuid } = useAuthStore(s => ({ primaryRole: s.primaryRole, dbUuid: s.dbUuid }));
const primaryRole = useAuthStore(s => s.primaryRole);
const dbUuid = useAuthStore(s => s.dbUuid);
const isAdmin = primaryRole === 'admin';
const isClinic = primaryRole === 'clinic';
const isDoctor = primaryRole === 'doctor';
@@ -418,15 +428,16 @@ export default function AppointmentsPage() {
const qc = useQueryClient();
// ── Appointments query
const apptQueryKey = ['admin-appointments', selectedDate, selectedDoctorUuid];
const apptEndpoint = isAdmin ? '/api/v1/admin/appointments' : '/api/v1/my/appointments';
const apptQueryKey = ['appointments', apptEndpoint, selectedDate, selectedDoctorUuid];
const apptParams = new URLSearchParams({ date: selectedDate, limit: '500' });
if (selectedDoctorUuid) apptParams.set('doctor_uuid', selectedDoctorUuid);
const apptQuery = useQuery<PaginatedResponse<Appointment>>({
queryKey: apptQueryKey,
queryFn: () => api.get(`/api/v1/admin/appointments?${apptParams}`),
queryFn: () => api.get(`${apptEndpoint}?${apptParams}`),
});
const appointments: Appointment[] = apptQuery.data?.data ?? [];
const appointments: Appointment[] = apptQuery.data?.data ?? EMPTY_ARR;
// ── Unique doctors from results (for clinic tabs)
const doctors = React.useMemo(() => {
@@ -451,7 +462,7 @@ export default function AppointmentsPage() {
// ── Merge slots + appointments for schedule view
const mergedSlots: SlotItem[] = React.useMemo(() => {
if (viewMode !== 'schedule') return [];
const rawSlots: any[] = slotsQuery.data?.data ?? [];
const rawSlots: any[] = (slotsQuery.data?.data as any)?.slots ?? [];
const apptByStart = new Map<number, Appointment>();
appointments.forEach(a => apptByStart.set(a.slot_start, a));
@@ -484,7 +495,7 @@ export default function AppointmentsPage() {
return (
<div style={{ padding: '20px 24px' }}>
{/* Stats Bar */}
{isAdmin && <StatsBar date={selectedDate} />}
<StatsBar date={selectedDate} isAdmin={isAdmin} />
{/* Doctor tabs (clinic with ≥2 doctors) */}
{showDoctorTabs && (