From 2337cfd90c5ed2a744005f43eaa0b4fa3c3b8f42 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Tue, 14 Jul 2026 17:36:17 +0330 Subject: [PATCH] =?UTF-8?q?fix(services):=20restore=20separate=20insurance?= =?UTF-8?q?=20modal=20+=20fix=20=E2=8B=AE=20menu=20position?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - revert the previous merge of insurance coverage into the service edit modal (per user): 'پوشش بیمه' is again a ⋮ menu action opening its own ServiceInsuranceModal, kept within the services page - delete the inline ServiceInsuranceSection; restore ServiceInsuranceModal - fix ⋮ dropdown position: left:8 → insetInlineStart:8 so the menu anchors under the ⋮ button (which sits at inline-start/right in RTL) instead of the opposite side - keep the --primary-subtle → --primary-soft token fix in the modal - test: assert the ⋮ menu exposes ویرایش / تعرفه‌های سالانه / پوشش بیمه Co-Authored-By: Claude Opus 4.8 --- ...eSection.tsx => ServiceInsuranceModal.tsx} | 63 +++++++++---------- .../admin/pages/ClinicServicesPage.test.tsx | 15 ++--- assets/admin/pages/ClinicServicesPage.tsx | 23 ++----- 3 files changed, 39 insertions(+), 62 deletions(-) rename assets/admin/components/{ServiceInsuranceSection.tsx => ServiceInsuranceModal.tsx} (77%) diff --git a/assets/admin/components/ServiceInsuranceSection.tsx b/assets/admin/components/ServiceInsuranceModal.tsx similarity index 77% rename from assets/admin/components/ServiceInsuranceSection.tsx rename to assets/admin/components/ServiceInsuranceModal.tsx index 9bb7b4e5..c2fe241d 100644 --- a/assets/admin/components/ServiceInsuranceSection.tsx +++ b/assets/admin/components/ServiceInsuranceModal.tsx @@ -3,6 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { ShieldCheckIcon, CheckIcon } from '@heroicons/react/24/outline'; import { toast } from 'sonner'; import { api } from '../lib/api'; +import Modal from './ui/Modal'; import PriceInput from './ui/PriceInput'; import type { ServiceItem } from '../types'; @@ -129,7 +130,7 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser type="number" min={0} max={100} dir="ltr" className="input" style={{ height: 40, textAlign: 'left' }} value={draft.coverage_percent ?? ''} - placeholder="ارثی" + placeholder="ارث" onChange={(e) => setDraft((d) => ({ ...d, coverage_percent: e.target.value === '' ? null : Number(e.target.value) }))} /> @@ -158,7 +159,7 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser
مقدار خالی از قرارداد بیمه ارث می‌برد -
@@ -171,7 +172,7 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser {!draft.covered && !isLoading && (
این خدمت تحت این بیمه پوشش ندارد -
@@ -180,47 +181,41 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser ); } -/** - * Per-insurer coverage editor for a single saved service item, rendered inline - * inside the service edit modal (no dialog wrapper). Each contract saves on its - * own; independent of the service item's own save button. - */ -export default function ServiceInsuranceSection({ item }: { item: ServiceItem }) { +export default function ServiceInsuranceModal({ item, onClose }: { item: ServiceItem | null; onClose: () => void }) { const { data, isLoading } = useQuery<{ data: { data: TenantInsurance[] } }>({ queryKey: ['tenant-insurances'], queryFn: () => api.get('/api/v1/billing/tenant-insurances'), + enabled: !!item, }); const contracts = ((data as any)?.data?.data as TenantInsurance[] | undefined) ?? []; return ( -
-
- - پوشش بیمه -
-
- - درصد پوشش، فرانشیز و سقف هر بیمه‌گر برای این خدمت تعیین می‌شود. این تنظیمات مبنای محاسبه‌ی سهم بیمار و ساخت مطالبات بیمه است. -
- - {isLoading ? ( -
در حال بارگذاری...
- ) : contracts.length === 0 ? ( + +
- -
قرارداد بیمه‌ی فعالی ندارید
-
ابتدا از بخش بیمه‌ها یک بیمه را فعال کنید.
+ + درصد پوشش، فرانشیز و سقف هر بیمه‌گر برای این خدمت تعیین می‌شود. این تنظیمات مبنای محاسبه‌ی سهم بیمار و ساخت مطالبات بیمه است.
- ) : ( - contracts.map((c) => ) - )} -
+ + {isLoading ? ( +
در حال بارگذاری...
+ ) : contracts.length === 0 ? ( +
+ +
قرارداد بیمه‌ی فعالی ندارید
+
ابتدا از بخش بیمه‌ها یک بیمه را فعال کنید.
+
+ ) : ( + item && contracts.map((c) => ) + )} +
+ ); } diff --git a/assets/admin/pages/ClinicServicesPage.test.tsx b/assets/admin/pages/ClinicServicesPage.test.tsx index e716bc06..32b9765e 100644 --- a/assets/admin/pages/ClinicServicesPage.test.tsx +++ b/assets/admin/pages/ClinicServicesPage.test.tsx @@ -36,11 +36,6 @@ beforeEach(() => { { uuid: 'st1', full_name: 'مریم امینی', active: true }, { uuid: 'st2', full_name: 'سحر رحمانی', active: true }, ] }); - // coverage before tenant-insurances (coverage URL also contains "tenant-insurances") - if (url.includes('/service-coverage')) return Promise.resolve({ success: true, data: { data: [] } }); - if (url.includes('/tenant-insurances')) return Promise.resolve({ success: true, data: { data: [ - { uuid: 'ins1', insurance_name: 'تأمین اجتماعی', insurance_kind: 'basic', coverage_percent: 70 }, - ] } }); return Promise.resolve({ success: true, data: [] }); }); }); @@ -64,15 +59,13 @@ describe('ClinicServicesPage (خدمات)', () => { expect(screen.getByText('سرویس جدید')).toBeInTheDocument(); }); - it('embeds per-insurer coverage inline inside the service edit modal', async () => { + it('opens the ⋮ actions menu with insurance and tariff entries for a service', async () => { renderWithProviders(, { route: '/admin/clinic-services' }); fireEvent.click(await screen.findByText('کندلا ۲۰۲۱')); - // open the ⋮ actions menu, then ویرایش fireEvent.click(await screen.findByTitle('عملیات')); - fireEvent.click(await screen.findByText('ویرایش')); - // insurance section + contract render inside the same (edit) modal - expect(await screen.findByText('پوشش بیمه')).toBeInTheDocument(); - expect(await screen.findByText('تأمین اجتماعی')).toBeInTheDocument(); + expect(await screen.findByText('ویرایش')).toBeInTheDocument(); + expect(screen.getByText('تعرفه‌های سالانه')).toBeInTheDocument(); + expect(screen.getByText('پوشش بیمه')).toBeInTheDocument(); }); }); diff --git a/assets/admin/pages/ClinicServicesPage.tsx b/assets/admin/pages/ClinicServicesPage.tsx index 7f68aa37..61c6e13a 100644 --- a/assets/admin/pages/ClinicServicesPage.tsx +++ b/assets/admin/pages/ClinicServicesPage.tsx @@ -19,7 +19,7 @@ import Modal from '../components/ui/Modal'; import PriceInput from '../components/ui/PriceInput'; import ConfirmDialog from '../components/ui/ConfirmDialog'; import ServiceTariffModal from '../components/ServiceTariffModal'; -import ServiceInsuranceSection from '../components/ServiceInsuranceSection'; +import ServiceInsuranceModal from '../components/ServiceInsuranceModal'; import SearchableSelect from '../components/ui/SearchableSelect'; import FeatureGate from '../components/ui/FeatureGate'; @@ -78,6 +78,7 @@ function ClinicServicesPageInner() { const [itemModal, setItemModal] = useState<'create' | ServiceItem | null>(null); const [toggleItem, setToggleItem] = useState(null); const [tariffItem, setTariffItem] = useState(null); + const [insuranceItem, setInsuranceItem] = useState(null); const [search, setSearch] = useState(''); const [showInactive, setShowInactive] = useState(true); const [menuOpen, setMenuOpen] = useState(null); @@ -356,9 +357,10 @@ function ClinicServicesPageInner() { {menuOpen === item.uuid && ( <>
setMenuOpen(null)} /> -
+
+
@@ -572,25 +574,12 @@ function ClinicServicesPageInner() { )}
- - {/* پوشش بیمه — پیوسته داخل همان مودال، فقط برای سرویس ذخیره‌شده */} - {itemModal !== null && typeof itemModal === 'object' ? ( -
- -
- ) : ( -
- - برای تنظیم پوشش بیمه، ابتدا سرویس را ذخیره کنید و سپس آن را ویرایش کنید. -
- )} setTariffItem(null)} /> + setInsuranceItem(null)} /> + {/* Confirm حذف بخش */}