The holiday model was already right — national holidays global, a per-tenant override in both directions, per-doctor and per-resource exceptions — but nothing could create a national holiday. The only writer was an import command, so the calendar the whole product inherits from had no owner. Three admin-only routes give it one. POST upserts, because `date` is unique and re-sending a day should rename it rather than surface a raw database error; PATCH takes only the title, because moving a date means a different holiday. The system admin has no work environment, so the list endpoint now returns the calendar with an empty `overrides` for that role instead of the 403 `pair()` would raise — the person who maintains the calendar has to be able to read it. Both holiday tabs — the doctor's and the resource's — now open with the official calendar above their own exceptions, from one shared card rather than two copies that would drift. Each row can be opted out of with a single click, which is the existing holiday-override endpoint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
85 lines
3.7 KiB
TypeScript
85 lines
3.7 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { useHolidays } from '../../hooks/useResourceCalendar';
|
|
import { formatDate, currentJalaliYear } from '../../lib/utils';
|
|
|
|
/**
|
|
* تعطیلات رسمیِ سال، همانجایی که تعطیلی اختصاصی تعریف میشود.
|
|
*
|
|
* تقویم رسمی یک بار مرکزی ثبت میشود و هر پزشک و هر منبع از آن ارث میبرد؛ اینجا فقط
|
|
* دیده میشود و — اگر این محیط آن روز باز باشد — با یک سوییچ خنثی میشود. ساخت و حذفِ
|
|
* خودِ تعطیلی کارِ مدیر سیستم است، نه کلینیک.
|
|
*
|
|
* یک کامپوننت برای دو مصرفکننده: تب تعطیلات پزشک و تب تعطیلات منبع. دو نسخه یعنی دو
|
|
* رفتار که با هم واگرا میشوند.
|
|
*/
|
|
export default function NationalHolidaysCard({ canUpdate, year = currentJalaliYear() }: {
|
|
canUpdate: boolean;
|
|
year?: number;
|
|
}) {
|
|
const { holidays, overrides, loading, setOverride, removeOverride } = useHolidays(year);
|
|
|
|
const overrideByDate = useMemo(
|
|
() => new Map(overrides.map((o) => [o.date, o])),
|
|
[overrides],
|
|
);
|
|
|
|
return (
|
|
<div className="card" style={{ padding: 14 }}>
|
|
<h2 className="section-title" style={{ margin: '0 0 4px' }}>تعطیلات رسمی {year}</h2>
|
|
<p style={{ fontSize: 12, color: 'var(--text-3)', margin: '0 0 10px', lineHeight: 1.9 }}>
|
|
اینها برای همهٔ پزشکان و منابع اعمال میشوند. اگر این محیط روزی را باز است،
|
|
همینجا استثنا بزنید — مدیریت کاملشان در{' '}
|
|
<Link to="/admin/holidays" style={{ color: 'var(--primary)' }}>تنظیمات ← تعطیلات رسمی</Link>.
|
|
</p>
|
|
|
|
{loading ? (
|
|
<span style={{ fontSize: 13, color: 'var(--text-3)' }}>در حال بارگذاری...</span>
|
|
) : holidays.length === 0 ? (
|
|
<p style={{ fontSize: 13, color: 'var(--text-3)', margin: 0 }}>
|
|
برای سال {year} تعطیلی رسمی ثبت نشده است.
|
|
</p>
|
|
) : (
|
|
<div style={{ display: 'grid', gap: 8 }}>
|
|
{holidays.map((h) => {
|
|
const override = overrideByDate.get(h.date);
|
|
const open = override?.is_working === true;
|
|
|
|
return (
|
|
<div key={h.uuid} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13 }}>
|
|
<span className={`badge ${open ? 'gray' : 'red'}`} style={{ fontSize: 11 }}>
|
|
{open ? 'باز' : 'تعطیل'}
|
|
</span>
|
|
<span style={{ flex: 1, color: 'var(--text-2)' }}>
|
|
{formatDate(h.date * 1000)} · {h.title}
|
|
</span>
|
|
{canUpdate && (
|
|
open ? (
|
|
<button
|
|
type="button"
|
|
className="btn secondary sm"
|
|
disabled={removeOverride.isPending}
|
|
onClick={() => override && removeOverride.mutate(override.uuid)}
|
|
>
|
|
تعطیل کن
|
|
</button>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
className="btn secondary sm"
|
|
disabled={setOverride.isPending}
|
|
onClick={() => setOverride.mutate({ date: h.date, is_working: true })}
|
|
>
|
|
این روز باز است
|
|
</button>
|
|
)
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|