refactor(services): merge insurance coverage into the service edit modal

- extract ContractCard + contracts list from ServiceInsuranceModal into a
  new inline ServiceInsuranceSection (no dialog wrapper; buttons type=button
  so they don't submit the parent service form)
- ClinicServicesPage: drop the separate 'پوشش بیمه' menu action and its
  standalone modal; render coverage inline under the service fields in the
  edit modal (edit only — needs the item uuid; create shows a hint)
- delete now-unused ServiceInsuranceModal.tsx
- fix undefined --primary-subtle token → --primary-soft
- test: coverage renders inline inside the service edit modal

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-14 15:58:32 +03:30
co-authored by Claude Opus 4.8
parent be72756058
commit c79720ee79
3 changed files with 70 additions and 37 deletions
@@ -3,7 +3,6 @@ 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';
@@ -85,7 +84,7 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser
}}>
<div style={{
width: 32, height: 32, borderRadius: 9, flexShrink: 0,
display: 'grid', placeItems: 'center', background: 'var(--primary-subtle)',
display: 'grid', placeItems: 'center', background: 'var(--primary-soft)',
}}>
<ShieldCheckIcon style={{ width: 18, color: 'var(--primary)' }} />
</div>
@@ -130,7 +129,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) }))}
/>
</div>
@@ -159,7 +158,7 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser
</div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, marginTop: 12 }}>
<span style={{ fontSize: 11, color: 'var(--text-3)' }}>مقدار خالی از قرارداد بیمه ارث میبرد</span>
<button className="btn primary sm" disabled={saveMut.isPending} onClick={() => saveMut.mutate()}>
<button type="button" className="btn primary sm" disabled={saveMut.isPending} onClick={() => saveMut.mutate()}>
{saveMut.isPending ? '...' : 'ذخیره'}
</button>
</div>
@@ -172,7 +171,7 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser
{!draft.covered && !isLoading && (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, padding: '12px 14px' }}>
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>این خدمت تحت این بیمه پوشش ندارد</span>
<button className="btn sm" disabled={saveMut.isPending} onClick={() => saveMut.mutate()}>
<button type="button" className="btn sm" disabled={saveMut.isPending} onClick={() => saveMut.mutate()}>
{saveMut.isPending ? '...' : 'ذخیره'}
</button>
</div>
@@ -181,41 +180,47 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser
);
}
export default function ServiceInsuranceModal({ item, onClose }: { item: ServiceItem | null; onClose: () => void }) {
/**
* 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 }) {
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 (
<Modal open={!!item} onClose={onClose} title={`پوشش بیمه — ${item?.name ?? ''}`} size="md">
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
<div style={{
display: 'flex', gap: 8, padding: '11px 13px', borderRadius: 'var(--r-sm)',
background: 'var(--primary-subtle)', fontSize: 12, color: 'var(--text-2)', lineHeight: 1.7,
}}>
<ShieldCheckIcon style={{ width: 16, flexShrink: 0, marginTop: 1, color: 'var(--primary)' }} />
<span>درصد پوشش، فرانشیز و سقف هر بیمهگر برای این خدمت تعیین میشود. این تنظیمات مبنای محاسبهی سهم بیمار و ساخت مطالبات بیمه است.</span>
</div>
{isLoading ? (
<div className="muted" style={{ fontSize: 13, padding: '8px 0' }}>در حال بارگذاری...</div>
) : contracts.length === 0 ? (
<div style={{
border: '2px dashed var(--border)', borderRadius: 'var(--r)',
padding: '36px 16px', textAlign: 'center',
}}>
<ShieldCheckIcon style={{ width: 34, margin: '0 auto 10px', display: 'block', opacity: 0.35 }} />
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-2)' }}>قرارداد بیمهی فعالی ندارید</div>
<div style={{ fontSize: 12, color: 'var(--text-3)', marginTop: 4 }}>ابتدا از بخش بیمهها یک بیمه را فعال کنید.</div>
</div>
) : (
item && contracts.map((c) => <ContractCard key={c.uuid} contract={c} item={item} />)
)}
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<ShieldCheckIcon style={{ width: 17, color: 'var(--primary)' }} />
<b style={{ fontSize: 14 }}>پوشش بیمه</b>
</div>
</Modal>
<div style={{
display: 'flex', gap: 8, padding: '11px 13px', borderRadius: 'var(--r-sm)',
background: 'var(--primary-soft)', fontSize: 12, color: 'var(--text-2)', lineHeight: 1.7,
}}>
<ShieldCheckIcon style={{ width: 16, flexShrink: 0, marginTop: 1, color: 'var(--primary)' }} />
<span>درصد پوشش، فرانشیز و سقف هر بیمهگر برای این خدمت تعیین میشود. این تنظیمات مبنای محاسبهی سهم بیمار و ساخت مطالبات بیمه است.</span>
</div>
{isLoading ? (
<div className="muted" style={{ fontSize: 13, padding: '8px 0' }}>در حال بارگذاری...</div>
) : contracts.length === 0 ? (
<div style={{
border: '2px dashed var(--border)', borderRadius: 'var(--r)',
padding: '28px 16px', textAlign: 'center',
}}>
<ShieldCheckIcon style={{ width: 32, margin: '0 auto 10px', display: 'block', opacity: 0.35 }} />
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-2)' }}>قرارداد بیمهی فعالی ندارید</div>
<div style={{ fontSize: 12, color: 'var(--text-3)', marginTop: 4 }}>ابتدا از بخش بیمهها یک بیمه را فعال کنید.</div>
</div>
) : (
contracts.map((c) => <ContractCard key={c.uuid} contract={c} item={item} />)
)}
</div>
);
}