Files
clinicpro/assets/admin/components/ResourceUtilizationChart.tsx
hamedandClaude Opus 5 2db7a500e6 feat(reports): restore the documented sample threshold, and draw the chart
MIN_SAMPLE goes back to the specified 10. The reason it had been lowered to 3
was real — a small clinic saw an empty report — but the fix was wrong: three
samples do not make an average, and calling that "accurate" is worse than
saying nothing.

Rows below the threshold are now returned rather than dropped, with severity
null and below_min_sample true. That refuses both mistakes: it claims no
severity it cannot support, and it does not show a small clinic an empty page
that implies everything is fine. They sort after the usable rows and render
faded with a "small sample" badge.

The utilization page gets its Recharts bar chart. The table stays underneath —
six numeric columns are not something a chart answers — but the one question
the table is bad at, "which resource is behind", is exactly what a chart is
for. Colours come from the design tokens rather than hex, which is where a
chart usually breaks in dark mode, and a resource with no calendar is left out
entirely: null is not zero, and a zero bar would be a lie.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:01:40 +03:30

69 lines
2.8 KiB
TypeScript

import React from 'react';
import { Bar, BarChart, CartesianGrid, Cell, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
import type { UtilizationRow } from '../types';
/**
* بهره‌وری هر منبع در یک نگاه.
*
* جدول شش ستون عدد دارد و برای مقایسه ساخته نشده؛ چشم نمی‌تواند بگوید کدام منبع
* عقب است. نمودار دقیقاً همان یک سؤال را جواب می‌دهد و بقیه‌اش زیرش در جدول می‌ماند.
*
* منبعِ بدون تقویم اینجا **نمی‌آید**: `null` صفر نیست و ستون صفر دروغ می‌گوید.
*/
export default function ResourceUtilizationChart({ rows }: { rows: UtilizationRow[] }) {
const data = rows
.filter((r) => r.utilization !== null)
.map((r) => ({
name: r.resource_name,
percent: Math.round((r.utilization ?? 0) * 100),
wasted: r.wasted_capacity,
}));
if (data.length === 0) return null;
return (
<div className="card card-pad" style={{ marginBottom: 16 }}>
<h3 style={{ fontSize: 14, margin: '0 0 12px' }}>بهره‌وری منابع</h3>
<div style={{ width: '100%', height: Math.max(180, data.length * 42) }}>
<ResponsiveContainer>
<BarChart data={data} layout="vertical" margin={{ right: 16, left: 8 }}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" horizontal={false} />
<XAxis type="number" domain={[0, 100]} unit="٪" stroke="var(--text-3)" fontSize={12} />
<YAxis
type="category"
dataKey="name"
width={120}
stroke="var(--text-3)"
fontSize={12}
orientation="right"
/>
<Tooltip
formatter={(v) => [`${Number(v ?? 0)}٪`, 'بهره‌وری'] as [string, string]}
contentStyle={{
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 8,
fontSize: 12,
}}
/>
<Bar dataKey="percent" radius={[0, 6, 6, 0]}>
{/* رنگ از توکن‌ها می‌آید نه از hex — دارک‌مود همین‌جا شکسته می‌شد. */}
{data.map((row) => (
<Cell
key={row.name}
fill={row.wasted ? 'var(--danger)' : 'var(--primary)'}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
<p style={{ fontSize: 12, color: 'var(--text-3)', margin: '8px 0 0', lineHeight: 1.8 }}>
منبعی که تقویم ندارد در نمودار نمی‌آید بهره‌وری‌اش صفر نیست، تعریف‌نشده است.
</p>
</div>
);
}