The appointments page only ever showed one doctor's row, but in the resource-first model a single appointment can hold a room and a device at the same time, and that — not the doctor's schedule — is what runs the capacity out. An hour could look free on the doctor's lane while the only alexandrite laser was already taken. A third view, "منابع", draws one lane per resource for the selected day. Blocks come from resource_occupancy rather than the appointment: that range includes the device's setup and cleanup minutes and is the same range the availability engine treats as busy. A multi-segment appointment therefore shows up on every resource it holds, and each block links to the appointment it belongs to. GET /api/v1/resources/timeline keeps a fixed query count — one for occupancy, one for shifts, one for the patient names — instead of one per resource. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
2.4 KiB
TypeScript
65 lines
2.4 KiB
TypeScript
/**
|
|
* سوییچ کشویی نمای نوبتها — بازسازی `TurnsViewModeToggle.jsx` طرح tauri
|
|
* (پسزمینهٔ لغزنده، متن فعال #5559ce).
|
|
*
|
|
* نمای «منابع» بعداً اضافه شد چون در مدل منبعمحور، پرشدن یک ساعت را دستگاه و اتاق
|
|
* تعیین میکنند نه فقط برنامهٔ پزشک.
|
|
*/
|
|
export type TurnsViewMode = 'table' | 'timeline' | 'resources';
|
|
|
|
const MODES: { id: TurnsViewMode; label: string }[] = [
|
|
{ id: 'table', label: 'نمایش جدولی' },
|
|
{ id: 'timeline', label: 'زمانبندی' },
|
|
{ id: 'resources', label: 'منابع' },
|
|
];
|
|
|
|
export default function TurnsViewToggle({
|
|
viewMode, onChange,
|
|
}: {
|
|
viewMode: TurnsViewMode;
|
|
onChange: (m: TurnsViewMode) => void;
|
|
}) {
|
|
const index = Math.max(MODES.findIndex((m) => m.id === viewMode), 0);
|
|
const width = 100 / MODES.length;
|
|
|
|
return (
|
|
<div
|
|
role="tablist"
|
|
aria-label="نمای نوبتها"
|
|
style={{
|
|
position: 'relative', width: 272, height: 44, display: 'flex', overflow: 'hidden',
|
|
border: '1px solid var(--border)', borderRadius: 'var(--r-sm)', background: 'var(--surface)',
|
|
}}
|
|
>
|
|
{/* پسزمینهٔ لغزنده — RTL، پس اولین حالت سمت راست است */}
|
|
<div style={{
|
|
position: 'absolute', top: 0, right: 0, height: '100%', width: `${width}%`,
|
|
background: 'var(--primary-soft)', transition: 'transform .3s var(--ease)',
|
|
transform: `translateX(${index * -100}%)`,
|
|
}} />
|
|
|
|
{MODES.map((m, i) => (
|
|
<div key={m.id} style={{ display: 'contents' }}>
|
|
{i > 0 && (
|
|
<div style={{ position: 'relative', zIndex: 1, width: 1, height: '100%', background: 'var(--border)' }} />
|
|
)}
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
aria-selected={viewMode === m.id}
|
|
onClick={() => onChange(m.id)}
|
|
style={{
|
|
position: 'relative', zIndex: 1, flex: 1, height: '100%', border: 'none',
|
|
background: 'transparent', cursor: 'pointer', fontFamily: 'inherit',
|
|
fontSize: 13, fontWeight: 500,
|
|
color: viewMode === m.id ? 'var(--primary)' : 'var(--text-3)',
|
|
}}
|
|
>
|
|
{m.label}
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|