Files
clinicpro/assets/admin/components/appointments/ResourceDayPanel.tsx
T
hamed 4f69bc9044 feat: Implement resource booking functionality
- Add service timeline builder for appointments to manage available slots.
- Create a hook to fetch resource booking services with effective durations.
- Develop ResourceBookingSlotController to handle API requests for resource booking slots.
- Implement ResourceBookingSlotService to calculate available time slots based on resource occupancy and service durations.
- Add tests for resource appointment creation and booking slot functionality to ensure correct behavior and edge cases.
2026-08-03 14:34:23 +03:30

80 lines
3.1 KiB
TypeScript

import React from 'react';
import { useQuery } from '@tanstack/react-query';
import { api } from '../../lib/api';
import type { ApiResponse } from '../../lib/api';
import TurnsTimeline from './TurnsTimeline';
import { buildServiceTimeline } from './serviceTimeline';
import type { TimelineSlot } from './types';
import type { Appointment, ClinicResource } from '../../types';
interface DaySlotsData {
windows: { start: number; end: number; start_time: string; end_time: string }[];
empty_reason: string | null;
}
/**
* نمای روزِ یک منبع — همان تایم‌لاینِ نوبت‌دهی سرویسیِ پزشک، با تقویم خودِ منبع.
*
* منبع اسلاتِ ثابت ندارد: بازهٔ کاری از شیفت خودش می‌آید و طول هر نوبت از سرویس‌هایش،
* پس ردیف‌ها «نوبت‌های رزروشده + بازه‌های خالیِ بینشان»اند. کامپوننت تایم‌لاین یکی
* است تا کارت، وضعیت و عملیاتِ نوبت در هر دو نما یک چیز باشند.
*/
export default function ResourceDayPanel({
resource, date, appointments, loading, canCreate, queryKey, onBook, onView,
}: {
resource: ClinicResource;
/** روزِ نمایش، ISO `Y-m-d`. */
date: string;
appointments: Appointment[];
loading: boolean;
canCreate: boolean;
queryKey: unknown[];
onBook: (slot: TimelineSlot | null) => void;
onView: (appointment: Appointment) => void;
}) {
const dayQuery = useQuery<ApiResponse<DaySlotsData>>({
queryKey: ['resource-day-slots', resource.uuid, date],
queryFn: () => api.get(`/api/v1/resource/${resource.uuid}/day-slots?date=${date}`),
});
const data = dayQuery.data?.data;
const windows = data?.windows ?? [];
const slots = React.useMemo(
() => buildServiceTimeline(windows, appointments),
[windows, appointments],
);
const workingRange = windows.length > 0
? { start: windows[0].start_time, end: windows[windows.length - 1].end_time }
: null;
return (
<div>
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8,
padding: '8px 12px', marginBottom: 12, borderRadius: 'var(--r-sm)',
background: 'var(--surface-2)', border: '1px solid var(--border)',
fontSize: 12.5, color: 'var(--text-2)',
}}>
<span>نوبت‌دهی سرویسی نوبت‌ها بر اساس مدت سرویس چیده می‌شوند.</span>
{workingRange && (
<span dir="ltr" style={{ color: 'var(--text-3)', flexShrink: 0 }}>
ساعت کاری: {workingRange.start} - {workingRange.end}
</span>
)}
</div>
<TurnsTimeline
slots={canCreate ? slots : slots.filter(s => s.appointment !== null)}
loading={loading || dayQuery.isLoading}
queryKey={queryKey}
onView={onView}
onBook={onBook}
emptyReason={data?.empty_reason ?? null}
errorMessage={dayQuery.isError ? ((dayQuery.error as Error)?.message || 'خطای نامشخص') : null}
/>
</div>
);
}