- 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.
80 lines
3.1 KiB
TypeScript
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>
|
|
);
|
|
}
|