Files
clinicpro/assets/admin/components/holidays/NationalHolidaysCard.tsx
T
hamedandClaude Opus 5 9ba4c8d948 feat(settings): manage resource schedules beside the doctors'
A resource carries its own working hours and holidays in the resource-first
model, so it belongs on the same settings page as a doctor's schedule rather
than on a page of its own. The page now has a scope switch — doctors or
resources — with the per-item tab bar below it, and both scopes reuse the
panels that already existed: ScheduleSection for a doctor, the working-hours
and exceptions panels for a resource. The selection lives in the query
string, so back and refresh return to the same tab.

The screenshot of the finished tab caught two real defects, both fixed here:

Dates in the resource panels and the holidays page read as year 57932.
formatDate already multiplies seconds by 1000, and five call sites passed
`x * 1000` on top of it. This predates the tab — the code was inherited from
the old calendar page — but it was invisible until a two-week preview was put
on screen.

The working-hours panel still told the user their hours were intersected with
the branch's. Branches are gone; the shift is the only source now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 15:49:01 +03:30

85 lines
3.8 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.toLocaleString('fa-IR', { useGrouping: false })}</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.toLocaleString('fa-IR', { useGrouping: false })} تعطیلی رسمی ثبت نشده است.
</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)} · {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>
);
}