- Implemented BlogBodySanitizer to clean HTML content before saving articles, ensuring security against XSS attacks. - Added tests for BlogBodySanitizer to verify that unsafe tags and attributes are stripped from the content. - Introduced ApiLeastPrivilegeTest to ensure that unauthorized users cannot access sensitive API routes, maintaining strict access control.
85 lines
3.8 KiB
TypeScript
85 lines
3.8 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import { Link } from 'react-router';
|
|
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>
|
|
);
|
|
}
|