Files
clinicpro/assets/admin/components/appointments/TurnsViewToggle.tsx
T
hamedandClaude Opus 5 444ebc897a feat(appointments): a resource-first view on the timeline
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>
2026-08-02 14:14:10 +03:30

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>
);
}