feat: refactor ResourceExceptionsPanel and related hooks; remove unused availability logic and simplify resource detail management
This commit is contained in:
@@ -1,106 +1,21 @@
|
||||
import React from 'react';
|
||||
import { useResourceAvailability } from '../../hooks/useResourceCalendar';
|
||||
import { formatDate, formatNumber } from '../../lib/utils';
|
||||
import { DAY_LABELS } from './ResourceWorkingHoursPanel';
|
||||
import NationalHolidaysCard from '../holidays/NationalHolidaysCard';
|
||||
import ResourceExceptionsCard from './ResourceExceptionsCard';
|
||||
import type { ResourceAvailability } from '../../types';
|
||||
|
||||
/** چرا یک روز خالی است — بدون ترجمه، پاسخ خام سرور به کاربر نشان داده میشد. */
|
||||
const REASON_LABELS: Record<string, string> = {
|
||||
national_holiday: 'تعطیل رسمی',
|
||||
tenant_holiday: 'تعطیلی این محیط',
|
||||
no_shift: 'شیفتی تعریف نشده',
|
||||
exception: 'مرخصی یا سرویس',
|
||||
resource_inactive: 'منبع غیرفعال است',
|
||||
address_inactive: 'محل نوبتدهی غیرفعال است',
|
||||
};
|
||||
|
||||
/** نیمهشبِ امروز بهصورت timestamp ثانیهای. */
|
||||
function todayMidnight(): number {
|
||||
const d = new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
return Math.floor(d.getTime() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* تقویمِ استثناهای یک منبع: تعطیلات رسمی، مرخصی و سرویس، و پیشنمایش دو هفته.
|
||||
* تقویمِ استثناهای یک منبع: تعطیلات رسمی، و مرخصی و سرویس.
|
||||
*
|
||||
* این ترکیب مخصوص صفحهٔ جزئیات منبع است، جایی که کل صفحه دربارهٔ همان یک منبع است
|
||||
* و پیشنمایش پاسخِ «بعد از این تنظیمات، چه ساعتی باز میماند؟» را میدهد. صفحهٔ
|
||||
* تنظیمات نوبتدهی همان دو کارت را بدون پیشنمایش و بهصورت تب میچیند.
|
||||
* دو ورودیِ همخانواده که کنار هم میمانند — هر دو روزی را از تقویم منبع کم میکنند،
|
||||
* یکی سراسری و یکی مخصوص همین منبع.
|
||||
*/
|
||||
export default function ResourceExceptionsPanel({ resourceUuid, canUpdate }: {
|
||||
resourceUuid?: string;
|
||||
canUpdate: boolean;
|
||||
}) {
|
||||
const previewFrom = todayMidnight();
|
||||
const previewTo = previewFrom + 13 * 86400;
|
||||
const { availability } = useResourceAvailability(resourceUuid, previewFrom, previewTo);
|
||||
|
||||
return (
|
||||
<div className="wh-two-col">
|
||||
<div style={{ display: 'grid', gap: 'var(--gap)' }}>
|
||||
<NationalHolidaysCard canUpdate={canUpdate} />
|
||||
<ResourceExceptionsCard resourceUuid={resourceUuid} canUpdate={canUpdate} />
|
||||
</div>
|
||||
|
||||
<PreviewCard availability={availability} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* پیشنمایش دو هفته.
|
||||
*
|
||||
* ۱۴ سطرِ یکنواختِ «شیفتی تعریف نشده» فقط نویز است و با یک حالت خالیِ راهحلدار
|
||||
* جمع میشود. شرط عمداً تنگ است: اگر حتی یک روز به دلیل دیگری (تعطیل رسمی،
|
||||
* مرخصی) بسته باشد سطرها میمانند، چون آن دلیل خودش اطلاعات است — نه نویز.
|
||||
*/
|
||||
function PreviewCard({ availability }: { availability?: ResourceAvailability }) {
|
||||
const days = availability?.days ?? [];
|
||||
const nothingConfigured = days.length > 0
|
||||
&& days.every((d) => d.intervals.length === 0
|
||||
&& d.reasons.length > 0
|
||||
&& d.reasons.every((r) => r === 'no_shift'));
|
||||
|
||||
return (
|
||||
<div className="card card-pad" style={{ position: 'sticky', top: 'var(--gap)' }}>
|
||||
<h2 style={{ fontSize: 16, fontWeight: 700, color: 'var(--text)' }}>پیشنمایش دو هفته</h2>
|
||||
<p className="field-hint" style={{ marginTop: 4, marginBottom: 14 }}>
|
||||
ساعت <strong>خام</strong> — نوبتهای ثبتشده هنوز از آن کسر نشدهاند.
|
||||
</p>
|
||||
|
||||
{days.length === 0 ? (
|
||||
<div className="skeleton" style={{ height: 120, borderRadius: 'var(--r-sm)' }} />
|
||||
) : nothingConfigured ? (
|
||||
<p style={{ fontSize: 13, color: 'var(--text-3)', margin: 0, lineHeight: 1.9 }}>
|
||||
در دو هفتهٔ آینده هیچ ساعتی باز نیست. اول در «شیفت هفتگی» روزهای کاری را تعریف کنید.
|
||||
</p>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gap: 7 }}>
|
||||
{days.map((day) => (
|
||||
<div
|
||||
key={day.date}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
gap: 8, fontSize: 13,
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'var(--text-2)' }}>
|
||||
{DAY_LABELS[day.day_of_week]} · {formatDate(day.date)}
|
||||
</span>
|
||||
{day.intervals.length === 0 ? (
|
||||
<span className="badge gray" style={{ fontSize: 11 }}>
|
||||
{day.reasons.map((r) => REASON_LABELS[r] ?? r).join('، ') || '—'}
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ fontWeight: 700 }}>{formatNumber(day.total_minutes)} دقیقه</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<NationalHolidaysCard canUpdate={canUpdate} />
|
||||
<ResourceExceptionsCard resourceUuid={resourceUuid} canUpdate={canUpdate} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user