Add tests and implementation for ServiceDetailPage and PriceInput components

- Implement PriceInput component tests to validate Persian and Arabic numeral handling, input formatting, and controlled behavior.
- Create ServiceDetailPage component with detailed service information, including pricing, insurance coverage, and editing capabilities.
- Add API tests for service item detail retrieval and coverage synchronization with insurance contracts.
- Ensure proper error handling and user feedback for service item retrieval and coverage management.
This commit is contained in:
hamed
2026-07-18 12:10:49 +03:30
parent f1d58e1604
commit 42d9ad26c5
27 changed files with 1758 additions and 284 deletions
@@ -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 { digitsOnly, parseUserNumberClamped } from '../lib/utils';
import Modal from './ui/Modal';
import PriceInput from './ui/PriceInput';
import type { ServiceItem } from '../types';
@@ -46,6 +47,8 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser
const existing = rows?.find((r) => r.service_item_uuid === item.uuid);
const [draft, setDraft] = useState<Draft>({ covered: true, coverage_percent: null, franchise_rials: null, ceiling_rials: null });
// متن خام فیلد درصد جدا از مقدار عددی نگه داشته می‌شود تا کاربر بتواند فیلد را خالی کند.
const [percentText, setPercentText] = useState('');
useEffect(() => {
setDraft(existing
@@ -56,6 +59,7 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser
ceiling_rials: existing.ceiling_rials,
}
: { covered: true, coverage_percent: null, franchise_rials: null, ceiling_rials: null });
setPercentText(existing?.coverage_percent == null ? '' : String(existing.coverage_percent));
}, [existing]);
const saveMut = useMutation({
@@ -129,9 +133,14 @@ function ContractCard({ contract, item }: { contract: TenantInsurance; item: Ser
<input
type="text" inputMode="numeric" dir="ltr" className="input"
style={{ height: 40, textAlign: 'left' }}
value={draft.coverage_percent ?? ''}
placeholder="ارث"
onChange={(e) => setDraft((d) => ({ ...d, coverage_percent: e.target.value === '' ? null : Number(e.target.value) }))}
value={percentText}
placeholder="ارث از قرارداد"
onChange={(e) => {
const digits = digitsOnly(e.target.value, 3);
setPercentText(digits);
setDraft((d) => ({ ...d, coverage_percent: parseUserNumberClamped(digits, 0, 100) }));
}}
onBlur={() => setPercentText(draft.coverage_percent == null ? '' : String(draft.coverage_percent))}
/>
</div>
<div style={{ minWidth: 0 }}>