import React, { useEffect, useMemo, useState } from 'react'; import { PlusIcon, TrashIcon } from '@heroicons/react/24/outline'; import { useResourceCalendar } from '../../hooks/useResourceCalendar'; /** ۰ = شنبه — همان قرارداد بک‌اند و ساعت کاری شعبه. */ export const DAY_LABELS = ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه']; const MINUTES_IN_DAY = 1440; type Draft = { start: string; end: string; endOfDay: boolean }; function toTime(minute: number): string { return `${String(Math.floor(minute / 60)).padStart(2, '0')}:${String(minute % 60).padStart(2, '0')}`; } function toMinutes(time: string): number | null { const m = /^(\d{1,2}):(\d{2})$/.exec(time.trim()); if (!m) return null; const minutes = Number(m[1]) * 60 + Number(m[2]); return minutes >= 0 && minutes <= MINUTES_IN_DAY ? minutes : null; } /** * شیفت هفتگی یک منبع — روزهای کاری و ساعت هر روز. * * ساعت واقعی منبع تقاطع این شیفت‌ها با ساعت کاری شعبه است، نه خودشان؛ پس شیفتِ * بیرون از ساعت شعبه ذخیره می‌شود ولی در دسترس‌پذیری اثری ندارد. */ export default function ResourceWorkingHoursPanel({ resourceUuid, canUpdate }: { resourceUuid?: string; canUpdate: boolean; }) { const { calendar, loading, save } = useResourceCalendar(resourceUuid); const [draft, setDraft] = useState>({}); const [error, setError] = useState(null); useEffect(() => { if (!calendar) return; const next: Record = {}; DAY_LABELS.forEach((_, day) => { next[day] = (calendar.days[String(day)] ?? []).map((r) => ({ start: toTime(r.start_minute), end: r.end_minute === MINUTES_IN_DAY ? '23:59' : toTime(r.end_minute), endOfDay: r.end_minute === MINUTES_IN_DAY, })); }); setDraft(next); }, [calendar]); const totalShifts = useMemo( () => Object.values(draft).reduce((sum, rows) => sum + rows.length, 0), [draft], ); const editRange = (day: number, index: number, patch: Partial) => setDraft((d) => ({ ...d, [day]: (d[day] ?? []).map((r, i) => (i === index ? { ...r, ...patch } : r)) })); const submit = () => { const days: Record = {}; for (const [dayKey, rows] of Object.entries(draft)) { const parsed: { start_minute: number; end_minute: number }[] = []; for (const row of rows) { const start = toMinutes(row.start); const end = row.endOfDay ? MINUTES_IN_DAY : toMinutes(row.end); if (start === null || end === null) { setError(`ساعت روز ${DAY_LABELS[Number(dayKey)]} را به شکل ۰۹:۰۰ وارد کنید`); return; } if (end <= start) { setError(`در روز ${DAY_LABELS[Number(dayKey)]} پایان شیفت باید بعد از شروع آن باشد`); return; } parsed.push({ start_minute: start, end_minute: end }); } days[dayKey] = parsed; } setError(null); save.mutate(days); }; return (

روزهای کاری و ساعت هر روز. ساعت واقعی از تقاطع این شیفت‌ها با ساعت کاری شعبه به‌دست می‌آید و تعطیلات و مرخصی از آن کسر می‌شود. {totalShifts > 0 && <> · {totalShifts} شیفت}

{canUpdate && ( )}
{error && (
{error}
)} {loading ? (
در حال بارگذاری...
) : (
{DAY_LABELS.map((label, day) => { const rows = draft[day] ?? []; return (
{label} {rows.length === 0 && بدون شیفت}
{canUpdate && ( )}
{rows.map((row, index) => (
editRange(day, index, { start: e.target.value })} style={{ width: 116 }} /> تا {row.endOfDay ? ( ۲۴:۰۰ ) : ( editRange(day, index, { end: e.target.value })} style={{ width: 116 }} /> )} {canUpdate && ( )}
))}
); })}
)}
); }