import React, { useMemo } from 'react'; import { useUrlState } from '../hooks/useUrlState'; import PageHeader from '../components/ui/PageHeader'; import DataTable, { type Column } from '../components/ui/DataTable'; import SearchableSelect from '../components/ui/SearchableSelect'; import PersianDateInput from '../components/ui/PersianDateInput'; import { Link } from 'react-router-dom'; import { isoToUnix, unixToIso } from '../lib/utils'; import { useBranches } from '../hooks/useBranches'; import { useResourceUtilization } from '../hooks/useReports'; import type { UtilizationRow } from '../types'; const RANGES = [ { value: '7', label: 'هفتهٔ گذشته' }, { value: '30', label: 'ماه گذشته' }, { value: '90', label: 'سه ماه گذشته' }, { value: 'custom', label: 'بازهٔ دلخواه' }, ]; function percent(value: number | null): string { return value === null ? '—' : `${Math.round(value * 100)}٪`; } /** * بهره‌وری منابع. * * ستون «نسبت کار مفید» مهم‌ترین ستون است: فاصله‌اش با «اشغال» همان چیزی است که تعریف * غلط بخش‌ها را لو می‌دهد. */ export default function ResourceUtilizationPage() { const { branches } = useBranches(); // بازه و شعبه در URL می‌نشینند نه در state: بازگشت از صفحهٔ منبع باید همان گزارش را // برگرداند، و لینکِ گزارش باید همان چیزی را نشان بدهد که فرستنده دیده. const [urlState, setUrlState] = useUrlState({ branch: '', days: '7', from: '', to: '' }); const branchUuid = urlState.branch; const days = urlState.days; const setBranchUuid = (v: string) => setUrlState({ branch: v }); const setDays = (v: string) => setUrlState({ days: v }); /** * بازهٔ آماده برای حالت عادی، بازهٔ دلخواه برای وقتی که کاربر دقیقاً می‌داند چه * می‌خواهد — مثلاً مقایسهٔ دو ماه مشخص با هم. */ const range = useMemo(() => { if (days === 'custom') { return { from: isoToUnix(urlState.from) ?? Math.floor(Date.now() / 1000) - 7 * 86400, to: isoToUnix(urlState.to) ?? Math.floor(Date.now() / 1000), }; } const to = Math.floor(Date.now() / 1000); return { from: to - Number(days) * 86400, to }; }, [days, urlState.from, urlState.to]); const { rows, loading } = useResourceUtilization(branchUuid || undefined, range.from, range.to); const columns: Column[] = [ { key: 'resource_name', header: 'منبع', render: (r) => (
{r.resource_name} {r.role}
), }, { key: 'available_minutes', header: 'در دسترس', render: (r) => {r.available_minutes} دقیقه, }, { key: 'occupied_minutes', header: 'اشغال', render: (r) => {r.occupied_minutes} دقیقه, }, { key: 'active_minutes', header: 'کار مفید', render: (r) => {r.active_minutes} دقیقه, }, { key: 'utilization', header: 'بهره‌وری', // `null` یعنی «تعریف‌نشده» نه «صفر»، و کار بعدی روشن است: تقویم منبع را بساز. // بدون لینک، کاربر عدد را می‌بیند و نمی‌داند کجا باید برود. render: (r) => r.utilization === null ? ( تنظیم تقویم ) : ( {percent(r.utilization)} ), }, { key: 'active_ratio', header: 'نسبت کار مفید', // توضیح باید در خودِ صفحه باشد، نه فقط در مستندات: کسی که گزارش را می‌خواند // مستندات را باز نمی‌کند. render: (r) => ( {percent(r.active_ratio)} ), }, { key: 'wasted_capacity', header: '', render: (r) => r.wasted_capacity ? ( ظرفیت هدررفته ) : null, }, ]; return (
setBranchUuid(String(v ?? ''))} options={branches.map((b) => ({ value: b.uuid, label: b.name || 'بدون نام' }))} placeholder="انتخاب شعبه" />
setDays(String(v ?? '7'))} options={RANGES} />
{days === 'custom' && ( <>
setUrlState({ from: v })} />
setUrlState({ to: v })} />
)}

«اشغال» شامل آماده‌سازی، تمیزکاری و بخش‌های انتظار است؛ «کار مفید» فقط زمانی که بیمار حاضر بوده. فاصلهٔ این دو نشان می‌دهد بخش‌های نوبت درست تعریف شده‌اند یا نه.

); }