Three views become two. The resource lanes were a separate tab, which meant reading a doctor's free hour on one screen and the laser's on another and matching them by eye — while in the resource-first model it is the device and the room that decide whether that hour is really free. They now sit under the same "زمانبندی" view, below the doctor's slots. Each lane says how much of its shift is still free, and that number respects capacity: a minute counts as busy only once the overlapping bookings reach the resource's capacity, so a three-bed room with two appointments is still open. Treating it otherwise would silently turn every multi-capacity resource into a single-capacity one. ResourceFreeTimeCalculator does the sweep and carries nine cases of its own. only_bookable=1 keeps resources with no service offering out of the view; they could only ever render an empty lane. On the seeded clinic that is five resources down to two. Two ruler defects the screenshot caught: hours rendered in Latin digits, and the last label was half-clipped by the container so 21 read as 2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { api, type ApiResponse } from '../lib/api';
|
|
|
|
export type ResourceTimelineItem = {
|
|
uuid: string;
|
|
starts_at: number;
|
|
ends_at: number;
|
|
status: 'booked' | 'hold';
|
|
segment_name: string | null;
|
|
appointment_id: number | null;
|
|
patient_name: string | null;
|
|
appointment_uuid: string | null;
|
|
appointment_status: string | null;
|
|
};
|
|
|
|
export type ResourceTimelineLane = {
|
|
uuid: string;
|
|
name: string;
|
|
type_name: string;
|
|
address_name: string | null;
|
|
capacity: number;
|
|
shifts: { start_minute: number; end_minute: number }[];
|
|
/** دقیقهٔ شیفت، دقیقهٔ پرشده (ظرفیتآگاه) و باقیماندهٔ آزاد. */
|
|
shift_minutes: number;
|
|
busy_minutes: number;
|
|
free_minutes: number;
|
|
items: ResourceTimelineItem[];
|
|
};
|
|
|
|
export type ResourceTimelineDay = {
|
|
date: number;
|
|
day_of_week: number;
|
|
resources: ResourceTimelineLane[];
|
|
};
|
|
|
|
/**
|
|
* «کدام منبع در این روز کِی گرفته است» — ورودی نمای منبعمحورِ صفحهٔ نوبتها.
|
|
*
|
|
* بازهها از جدول اشغال میآیند نه از خودِ نوبت: آمادهسازی و تمیزکاری دستگاه هم
|
|
* درونشان است و همان بازهای است که موتور جستجو اشغال میبیند.
|
|
*/
|
|
export function useResourceTimeline(date: string, options: { addressUuid?: string; onlyBookable?: boolean } = {}) {
|
|
const params = new URLSearchParams({ date });
|
|
if (options.addressUuid) params.set('address_uuid', options.addressUuid);
|
|
// منبعی که به هیچ سرویسی وصل نیست ردیفِ همیشهخالی است؛ در تایملاین نوبتها نمیآید.
|
|
if (options.onlyBookable) params.set('only_bookable', '1');
|
|
|
|
const query = useQuery({
|
|
queryKey: ['resource-timeline', date, options.addressUuid ?? null, options.onlyBookable ?? false],
|
|
queryFn: () => api.get<ApiResponse<ResourceTimelineDay>>(`/api/v1/resources/timeline?${params}`),
|
|
enabled: !!date,
|
|
});
|
|
|
|
return {
|
|
day: query.data?.data,
|
|
loading: query.isLoading,
|
|
error: query.isError ? ((query.error as Error)?.message || 'خطای نامشخص') : null,
|
|
};
|
|
}
|