feat: refactor ResourceExceptionsPanel and related hooks; remove unused availability logic and simplify resource detail management

This commit is contained in:
hamed
2026-08-03 10:36:13 +03:30
parent 00c275d618
commit 8509a04ae2
6 changed files with 99 additions and 221 deletions
+24 -36
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { PencilIcon } from '@heroicons/react/24/outline';
import PageHeader from '../components/ui/PageHeader';
import ConfirmDialog from '../components/ui/ConfirmDialog';
@@ -44,7 +44,6 @@ const SUBJECT_LABEL: Record<string, string> = {
*/
export default function ResourceDetailPage() {
const { resourceUuid } = useParams<{ resourceUuid: string }>();
const navigate = useNavigate();
const [urlState, setUrlState] = useUrlState({ tab: 'info' });
const tab = (TABS.some((t) => t.id === urlState.tab) ? urlState.tab : 'info') as TabId;
@@ -52,14 +51,14 @@ export default function ResourceDetailPage() {
const { addresses } = useAddresses();
const { types } = useResourceTypes();
const { skills } = useSkills();
const { update, remove, setSkills, setCategories } = useResources();
const { update, setSkills, setCategories } = useResources();
const { offerings, save: saveServices } = useResourceServices(resourceUuid);
const { items: serviceOptions } = useAllServiceItems();
const { can } = usePermissions();
const canUpdate = can('appointment_settings', 'update');
const [editOpen, setEditOpen] = useState(false);
const [confirmDelete, setConfirmDelete] = useState(false);
const [confirmDeactivate, setConfirmDeactivate] = useState(false);
// حالت‌های بارگذاری و نبودِ منبع هم داخل پوستهٔ تنظیمات می‌مانند، وگرنه منوی کناری
// یک لحظه می‌پرد و دوباره برمی‌گردد.
@@ -110,8 +109,12 @@ export default function ResourceDetailPage() {
resource={resource}
canUpdate={canUpdate}
toggling={update.isPending}
onToggleActive={() => update.mutate({ uuid: resource.uuid, d: { active: !resource.active } })}
onDelete={() => setConfirmDelete(true)}
// فعال کردن بی‌خطر است و مستقیم انجام می‌شود؛ غیرفعال کردن منبع را از
// نوبت‌دهی بیرون می‌برد، پس تأیید می‌گیرد.
onToggleActive={() => {
if (resource.active) setConfirmDeactivate(true);
else update.mutate({ uuid: resource.uuid, d: { active: true } });
}}
/>
)}
@@ -153,23 +156,20 @@ export default function ResourceDetailPage() {
{tab === 'blocks' && <ResourceBlocksPanel resourceUuid={resourceUuid} canUpdate={canUpdate} />}
{/* حذف موفق یعنی این صفحه دیگر منبعی ندارد؛ ماندن روی آن یک «منبع یافت نشد» است. */}
<ConfirmDialog
open={confirmDelete}
title="حذف منبع"
message={`آیا از حذف «${resource.name}» مطمئن هستید؟ این کار برگشت‌پذیر نیست.`}
confirmLabel="حذف"
open={confirmDeactivate}
title="غیرفعال کردن منبع"
message={`«${resource.name}» پس از غیرفعال شدن در جستجوی وقت و رزرو نوبت ظاهر نمی‌شود. نوبت‌های ثبت‌شده سرِ جایشان می‌مانند.`}
confirmLabel="غیرفعال کن"
danger
loading={remove.isPending}
loading={update.isPending}
onConfirm={() =>
remove.mutate(resource.uuid, {
onSuccess: () => {
setConfirmDelete(false);
navigate('/admin/resources');
},
})
update.mutate(
{ uuid: resource.uuid, d: { active: false } },
{ onSuccess: () => setConfirmDeactivate(false) },
)
}
onCancel={() => setConfirmDelete(false)}
onCancel={() => setConfirmDeactivate(false)}
/>
<ResourceFormModal
@@ -200,18 +200,16 @@ function Row({ label, children }: { label: string; children: React.ReactNode })
}
/**
* اطلاعات منبع + دو اقدامِ سطح-منبع.
* اطلاعات منبع + وضعیت فعال/غیرفعالش.
*
* فعال/غیرفعال و حذف پیش‌تر دکمه‌های ردیفِ جدول بودند. جدول حالا فقط فهرست است و
* هر اقدام کنارِ همان چیزی نشسته که تغییرش می‌دهد؛ حذف هم پایین و جدا از بقیه است
* تا با «ویرایش» و «غیرفعال» یک‌جا هم‌وزن دیده نشود.
* حذفِ منبع عمداً در رابط کاربری نیست: منبع به نوبت‌های ثبت‌شده گره خورده و راهِ
* درستِ «دیگر لازمش ندارم» غیرفعال کردن است، نه پاک کردن.
*/
function InfoTab({ resource, canUpdate, toggling, onToggleActive, onDelete }: {
function InfoTab({ resource, canUpdate, toggling, onToggleActive }: {
resource: ClinicResource;
canUpdate: boolean;
toggling: boolean;
onToggleActive: () => void;
onDelete: () => void;
}) {
const attributes = Object.entries(resource.attributes ?? {});
@@ -270,23 +268,13 @@ function InfoTab({ resource, canUpdate, toggling, onToggleActive, onDelete }: {
</div>
<button
type="button"
className={resource.active ? 'btn secondary' : 'btn primary'}
className={resource.active ? 'btn danger' : 'btn primary'}
disabled={toggling}
onClick={onToggleActive}
>
{resource.active ? 'غیرفعال کردن' : 'فعال کردن'}
</button>
</div>
{/* پس‌زمینه عمداً خنثی می‌ماند: `.btn.danger` خودش `--danger-bg` است و روی
ردیفِ هم‌رنگ نامرئی می‌شد. مرزِ قرمز به‌تنهایی هشدار را می‌رساند. */}
<div className="toggle-row" style={{ marginTop: 10, borderColor: 'var(--danger)' }}>
<div>
<div className="tr-title">حذف منبع</div>
<div className="tr-desc">برگشتپذیر نیست. اگر فقط موقتاً لازمش ندارید، غیرفعالش کنید.</div>
</div>
<button type="button" className="btn danger" onClick={onDelete}>حذف</button>
</div>
</div>
)}
</div>