import React, { useMemo } from 'react'; import { Link } from 'react-router-dom'; import { formatNumber } from '../../lib/utils'; import type { ResourceTimelineLane, ResourceTimelineItem } from '../../hooks/useResourceTimeline'; const HOUR = 60; const LANE_HEIGHT = 46; const LABEL_WIDTH = 168; function hhmm(timestamp: number): string { const d = new Date(timestamp * 1000); return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; } /** ساعتِ خطکش با ارقام فارسی — بقیهٔ پنل هم فارسی است. */ function hourLabel(minute: number): string { return String(Math.floor(minute / 60)).padStart(2, '0').replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[Number(d)]); } function minuteOfDay(timestamp: number, dayStart: number): number { return Math.round((timestamp - dayStart) / 60); } /** * بازهٔ ساعتی که نشان داده میشود: از شیفتها و اشغالهای همان روز درمیآید، نه ۰ تا ۲۴. * * نمایش کل شبانهروز یعنی کلینیکی که ۱۶ تا ۲۱ کار میکند، پنجششمِ عرض صفحهاش خالی * بماند و بلوکها آنقدر باریک شوند که خوانده نشوند. */ function windowOf(lanes: ResourceTimelineLane[], dayStart: number): { from: number; to: number } { let from = 24 * HOUR; let to = 0; lanes.forEach((lane) => { lane.shifts.forEach((s) => { from = Math.min(from, s.start_minute); to = Math.max(to, s.end_minute); }); lane.items.forEach((i) => { from = Math.min(from, minuteOfDay(i.starts_at, dayStart)); to = Math.max(to, minuteOfDay(i.ends_at, dayStart)); }); }); if (from >= to) return { from: 8 * HOUR, to: 20 * HOUR }; // به ساعت گرد میشود تا خطکش بالای نمودار عدد کامل نشان دهد. return { from: Math.floor(from / HOUR) * HOUR, to: Math.ceil(to / HOUR) * HOUR }; } /** * تایملاین منبعمحورِ یک روز — یک ردیف بهازای هر منبع. * * نمای پزشکمحور نمیگوید چرا ساعتی پر است؛ در مدل منبعمحور یک نوبت میتواند همزمان * اتاق و دستگاه را بگیرد و همان است که ظرفیت را تمام میکند. اینجا هر بلوک یک ردیفِ * اشغال است، پس یک نوبتِ چندبخشی روی چند ردیف دیده میشود. */ export default function ResourceTimeline({ lanes, dayStart, loading, error }: { lanes: ResourceTimelineLane[]; dayStart: number; loading: boolean; error: string | null; }) { const { from, to } = useMemo(() => windowOf(lanes, dayStart), [lanes, dayStart]); const span = to - from; const hours = useMemo( () => Array.from({ length: Math.floor(span / HOUR) + 1 }, (_, i) => from + i * HOUR), [from, span], ); if (loading) { return