From df8a43f8b24fe458b127419b0ba862b7f1a87f66 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Tue, 4 Aug 2026 20:18:38 +0330 Subject: [PATCH] feat(resource): remove ResourceCategoriesPanel and related functionality from ResourceDetailPage --- .../resources/ResourceCategoriesPanel.tsx | 94 ------------------- assets/admin/hooks/useResources.ts | 10 +- .../admin/pages/ResourceDetailPage.test.tsx | 17 ++-- assets/admin/pages/ResourceDetailPage.tsx | 22 +---- docs/api/resource.md | 6 ++ 5 files changed, 17 insertions(+), 132 deletions(-) delete mode 100644 assets/admin/components/resources/ResourceCategoriesPanel.tsx diff --git a/assets/admin/components/resources/ResourceCategoriesPanel.tsx b/assets/admin/components/resources/ResourceCategoriesPanel.tsx deleted file mode 100644 index eedbd7fb..00000000 --- a/assets/admin/components/resources/ResourceCategoriesPanel.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { Link } from 'react-router-dom'; -import SearchableSelect from '../ui/SearchableSelect'; -import { useCatalogCategories } from '../../hooks/useCatalogCategories'; -import type { CatalogCategory, ClinicResource } from '../../types'; - -type Flat = { uuid: string; name: string; depth: number }; - -function flatten(nodes: CatalogCategory[], depth = 0): Flat[] { - return nodes.flatMap((n) => [ - { uuid: n.uuid, name: n.name, depth }, - ...flatten(n.children ?? [], depth + 1), - ]); -} - -/** - * دسته‌بندی یک منبع — فقط **انتخاب** از کاتالوگ سراسری. - * - * ساختن دسته اینجا عمداً ممکن نیست؛ تنها جای ساخت «تنظیمات ← دسته‌بندی‌ها» است، وگرنه - * هر کاربر نسخهٔ خودش از «تمام بدن» را می‌سازد. - */ -export default function ResourceCategoriesPanel({ resource, canUpdate, saving, onSave }: { - resource: ClinicResource | null; - canUpdate: boolean; - saving: boolean; - onSave: (categoryUuids: string[]) => void; -}) { - const { tree, loading } = useCatalogCategories(); - const [chosen, setChosen] = useState([]); - - useEffect(() => { - setChosen((resource?.categories ?? []).map((c) => c.uuid)); - }, [resource]); - - const all = useMemo(() => flatten(tree), [tree]); - const picked = new Set(chosen); - const available = all.filter((c) => !picked.has(c.uuid)); - const nameOf = (uuid: string) => all.find((c) => c.uuid === uuid)?.name ?? uuid; - - return ( -
-

- دسته‌بندی سراسری است و اینجا فقط انتخاب می‌شود. برای ساخت یا ویرایش به{' '} - تنظیمات ← دسته‌بندی‌ها{' '} - بروید. -

- - {loading ? ( - در حال بارگذاری... - ) : chosen.length === 0 ? ( -

این منبع هیچ دسته‌بندی‌ای ندارد.

- ) : ( -
- {chosen.map((uuid) => ( - - {nameOf(uuid)} - {canUpdate && ( - - )} - - ))} -
- )} - - {canUpdate && available.length > 0 && ( -
- - ({ value: c.uuid, label: '— '.repeat(c.depth) + c.name }))} - value={null} - onChange={(v) => v && setChosen((c) => [...c, String(v)])} - placeholder="یک دسته‌بندی انتخاب کنید" - height={38} - /> -
- )} - - {canUpdate && ( -
- -
- )} -
- ); -} diff --git a/assets/admin/hooks/useResources.ts b/assets/admin/hooks/useResources.ts index c629b181..57af90f5 100644 --- a/assets/admin/hooks/useResources.ts +++ b/assets/admin/hooks/useResources.ts @@ -85,18 +85,10 @@ export function useResources(filters: ResourceFilters = {}) { onError: (e) => fail(e, 'ذخیرهٔ مهارت‌ها ناموفق بود'), }); - /** دستهٔ منبع از کاتالوگ سراسری می‌آید؛ ساختنش فقط در «تنظیمات ← دسته‌بندی‌ها» ممکن است. */ - const setCategories = useMutation({ - mutationFn: ({ uuid, categoryUuids }: { uuid: string; categoryUuids: string[] }) => - api.put>(`/api/v1/resource/${uuid}/categories`, { category_uuids: categoryUuids }), - onSuccess: () => { toast.success('دسته‌بندی‌های منبع ذخیره شد'); invalidate(); }, - onError: (e) => fail(e, 'ذخیرهٔ دسته‌بندی‌ها ناموفق بود'), - }); - return { resources: query.data?.data ?? [], loading: query.isLoading, - create, update, remove, setSkills, setCategories, + create, update, remove, setSkills, }; } diff --git a/assets/admin/pages/ResourceDetailPage.test.tsx b/assets/admin/pages/ResourceDetailPage.test.tsx index 5c2f13c3..8cb77a7c 100644 --- a/assets/admin/pages/ResourceDetailPage.test.tsx +++ b/assets/admin/pages/ResourceDetailPage.test.tsx @@ -188,17 +188,18 @@ describe('ResourceDetailPage', () => { expect(screen.queryByRole('button', { name: /کپی/ })).not.toBeInTheDocument(); }); - it('تب دسته‌بندی فقط انتخاب می‌دهد، نه ساخت', async () => { + /** + * دسته‌بندی از این صفحه برداشته شد: چیزی که کاربر می‌نوشت هیچ‌جا خوانده نمی‌شد و + * «کدام منبع این سرویس را می‌دهد» را تب سرویس‌ها صریح‌تر جواب می‌دهد. + */ + it('تب دسته‌بندی وجود ندارد و لینک قدیمی به تب اطلاعات می‌افتد', async () => { mockApi(); - const user = userEvent.setup(); - renderPage(); + renderPage('/admin/resources/r1?tab=categories'); + // نبودِ تب وقتی معنا دارد که صفحه واقعاً رندر شده باشد. await waitFor(() => expect(screen.getByText('ظرفیت هم‌زمان')).toBeInTheDocument()); - await user.click(screen.getByRole('button', { name: 'دسته‌بندی‌ها' })); - - await waitFor(() => expect(screen.getByText(/فقط انتخاب می‌شود/)).toBeInTheDocument()); - expect(screen.queryByRole('button', { name: /افزودن دسته‌بندی جدید/ })).not.toBeInTheDocument(); - expect(screen.getByText('تنظیمات ← دسته‌بندی‌ها')).toHaveAttribute('href', '/admin/service-categories'); + expect(screen.queryByRole('button', { name: 'دسته‌بندی‌ها' })).not.toBeInTheDocument(); + expect(screen.queryByText('دست')).not.toBeInTheDocument(); }); /** تب استثناها فقط دو ورودی دارد: تعطیلات رسمی، و مرخصی و سرویس. */ diff --git a/assets/admin/pages/ResourceDetailPage.tsx b/assets/admin/pages/ResourceDetailPage.tsx index 7132e2bd..3bebdf6c 100644 --- a/assets/admin/pages/ResourceDetailPage.tsx +++ b/assets/admin/pages/ResourceDetailPage.tsx @@ -9,7 +9,6 @@ import ResourceWorkingHoursPanel from '../components/resources/ResourceWorkingHo import ResourceExceptionsPanel from '../components/resources/ResourceExceptionsPanel'; import ResourceServicesPanel from '../components/resources/ResourceServicesPanel'; import ResourceSkillsPanel from '../components/resources/ResourceSkillsPanel'; -import ResourceCategoriesPanel from '../components/resources/ResourceCategoriesPanel'; import ResourceBlocksPanel from '../components/resources/ResourceBlocksPanel'; import { useUrlState } from '../hooks/useUrlState'; import { usePermissions } from '../hooks/usePermissions'; @@ -23,7 +22,6 @@ const TABS = [ { id: 'exceptions', label: 'تعطیلات و استثنا' }, { id: 'services', label: 'سرویس‌ها' }, { id: 'skills', label: 'مهارت‌ها' }, - { id: 'categories', label: 'دسته‌بندی‌ها' }, { id: 'blocks', label: 'غیرفعال‌سازی موقت' }, ] as const; type TabId = typeof TABS[number]['id']; @@ -49,7 +47,7 @@ export default function ResourceDetailPage() { const { resource, loading } = useResourceDetail(resourceUuid); const { types } = useResourceTypes(); const { skills } = useSkills(); - const { update, setSkills, setCategories } = useResources(); + const { update, setSkills } = useResources(); const { offerings, save: saveServices } = useResourceServices(resourceUuid); const { items: serviceOptions } = useAllServiceItems(); const { can } = usePermissions(); @@ -143,15 +141,6 @@ export default function ResourceDetailPage() { )} - {tab === 'categories' && ( - setCategories.mutate({ uuid: resource.uuid, categoryUuids })} - /> - )} - {tab === 'blocks' && } )} - - {(resource.categories ?? []).length === 0 ? '—' : ( - - {(resource.categories ?? []).map((c) => ( - {c.name} - ))} - - )} - {attributes.map(([key, value]) => ( {String(value)} ))} diff --git a/docs/api/resource.md b/docs/api/resource.md index ced42237..7d938ea9 100644 --- a/docs/api/resource.md +++ b/docs/api/resource.md @@ -559,6 +559,12 @@ مجوز: `appointment_settings.update`. +> **از ۲۰۲۶-۰۸ در پنل ادمین سطحی ندارد.** تب «دسته‌بندی‌ها»ی صفحهٔ منبع برداشته شد؛ اندپوینت و +> جدول و فیلد `categories` در پاسخ سرِ جایشان‌اند، ولی هیچ صفحه‌ای آن‌ها را نمی‌نویسد و نمی‌خواند. +> تنها اثر رفتاریِ این داده، ترتیبِ `findEligible` است که مصرف‌کننده‌اش (`AppointmentPlanBuilder`) +> فقط `count` و `max` می‌گیرد — یعنی امروز روی هیچ خروجی‌ای اثر ندارد. «کدام منبع این سرویس را +> می‌دهد» را `ResourceServiceOffering` صریح و به‌صورت فیلتر جواب می‌دهد. + دستهٔ منبع از **کاتالوگ سراسری** انتخاب می‌شود (`CatalogCategory`) — همان دسته‌هایی که سرویس هم از آن‌ها استفاده می‌کند. ساخت دسته اینجا ممکن نیست؛ فقط در «تنظیمات ← دسته‌بندی‌ها» ([`clinic-services.md`](clinic-services.md)).