import { useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
BanknotesIcon, ShieldCheckIcon, ClockIcon, UsersIcon, PencilIcon,
CalendarDaysIcon, WrenchScrewdriverIcon, CubeIcon,
} from '@heroicons/react/24/outline';
import { toast } from 'sonner';
import { api } from '../lib/api';
import type { ApiResponse } from '../lib/api';
import type { ServiceItem } from '../types';
import { formatRial, formatNumber, formatYear, formatDateTime } from '../lib/utils';
import PageHeader from '../components/ui/PageHeader';
import FeatureGate from '../components/ui/FeatureGate';
import ConfirmDialog from '../components/ui/ConfirmDialog';
import ServiceTariffModal from '../components/ServiceTariffModal';
import ServiceInsuranceModal from '../components/ServiceInsuranceModal';
import ServiceItemFormModal from '../components/ServiceItemFormModal';
interface Tariff {
uuid: string;
year: number;
price_rials: number;
is_active: boolean;
}
interface TariffList {
current_year: number;
default_price_rials: number;
data: Tariff[];
}
interface TenantInsurance {
uuid: string;
insurance_name: string | null;
insurance_kind: 'basic' | 'supplementary' | null;
coverage_percent: number;
}
interface CoverageRow {
service_item_uuid: string | null;
covered: boolean;
coverage_percent: number | null;
franchise_rials: number | null;
ceiling_rials: number | null;
}
const TABS = [
{ id: 'info', label: 'اطلاعات سرویس' },
{ id: 'tariffs', label: 'تعرفهها' },
{ id: 'insurance', label: 'بیمهها' },
] as const;
type TabId = typeof TABS[number]['id'];
const KIND = {
basic: { label: 'پایه', cls: 'blue' },
supplementary: { label: 'تکمیلی', cls: 'violet' },
} as const;
function Row({ icon, label, children }: { icon: React.ReactNode; label: string; children: React.ReactNode }) {
return (
{icon} {label}
{children}
);
}
function InfoTab({ item }: { item: ServiceItem }) {
const members = item.staff_members ?? (item.staff ? [item.staff] : []);
return (
|
} label="قیمت پایه">
{formatRial(item.price_rials)}
|
} label="بخش">
{item.section_name ?? '—'}
|
} label="زمان متوسط">
{item.duration_minutes
?
{formatNumber(Number(item.duration_minutes))} دقیقه
: '—'}
|
} label="نمایش در نوبتدهی">
{item.bookable
?
فعال
:
غیرفعال}
|
} label="پوشش بیمه">
{item.insurance_covered
?
تحت پوشش
: '—'}
|
} label="پرسنل مسئول">
{members.length > 0 ? (
{members.map((m) => (
{m.full_name}
))}
) : '—'}
|
} label="تاریخ ایجاد">
{item.created_at ? formatDateTime(item.created_at) : '—'}
آخرین ویرایش
{item.updated_at ? formatDateTime(item.updated_at) : '—'}
);
}
function TariffsTab({ item, onManage }: { item: ServiceItem; onManage: () => void }) {
const { data, isLoading } = useQuery>({
queryKey: ['service-tariffs', item.uuid],
queryFn: () => api.get(`/api/v1/service-items/${item.uuid}/tariffs`),
});
const list = data?.data;
const tariffs = list?.data ?? [];
return (
تعرفههای سالانه
قیمت هر سال شمسی؛ قیمت سال جاری مبنای صورتحساب است.
{isLoading ? (
در حال بارگذاری...
) : tariffs.length === 0 ? (
) : (
{tariffs.map((t) => (
{formatYear(t.year)}
{t.year === list?.current_year && سال جاری}
{!t.is_active && غیرفعال}
{formatRial(t.price_rials)}
))}
)}
);
}
function InsuranceTab({ item, onManage }: { item: ServiceItem; onManage: () => void }) {
const { data: contractsData, isLoading } = useQuery<{ data: { data: TenantInsurance[] } }>({
queryKey: ['tenant-insurances'],
queryFn: () => api.get('/api/v1/billing/tenant-insurances'),
});
const contracts = (contractsData as any)?.data?.data as TenantInsurance[] | undefined ?? [];
return (
بیمههای مرتبط
درصد پوشش، فرانشیز و سقف هر بیمهگر برای این خدمت.
{isLoading ? (
در حال بارگذاری...
) : contracts.length === 0 ? (
قرارداد بیمهی فعالی ندارید
) : (
{contracts.map((c) => (
))}
)}
);
}
function ContractCoverageRow({ contract, itemUuid }: { contract: TenantInsurance; itemUuid: string }) {
const { data } = useQuery<{ data: { data: CoverageRow[] } }>({
queryKey: ['service-coverage', contract.uuid],
queryFn: () => api.get(`/api/v1/billing/tenant-insurances/${contract.uuid}/service-coverage`),
});
const row = ((data as any)?.data?.data as CoverageRow[] | undefined)
?.find((r) => r.service_item_uuid === itemUuid);
const kind = contract.insurance_kind ? KIND[contract.insurance_kind] : null;
const percent = row?.coverage_percent ?? contract.coverage_percent;
return (
{contract.insurance_name ?? 'بیمه'}
{kind && {kind.label}}
{row && !row.covered ? (
بدون پوشش
) : (
پوشش {formatNumber(percent)}٪
{row?.franchise_rials ? فرانشیز {formatRial(row.franchise_rials)} : null}
{row?.ceiling_rials ? سقف {formatRial(row.ceiling_rials)} : null}
{!row && (ارث از قرارداد)}
)}
);
}
function ServiceDetailPageInner() {
const { uuid } = useParams<{ uuid: string }>();
const navigate = useNavigate();
const qc = useQueryClient();
const [tab, setTab] = useState('info');
const [editOpen, setEditOpen] = useState(false);
const [tariffOpen, setTariffOpen] = useState(false);
const [insuranceOpen, setInsuranceOpen] = useState(false);
const [toggleOpen, setToggleOpen] = useState(false);
const { data, isLoading, isError } = useQuery>({
queryKey: ['service-item', uuid],
queryFn: () => api.get(`/api/v1/service-item/${uuid}`),
enabled: !!uuid,
});
const item = data?.data;
const toggleActive = useMutation({
mutationFn: () => api.patch(`/api/v1/service-item/${uuid}`, { active: !item!.active }),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['service-item', uuid] });
qc.invalidateQueries({ queryKey: ['service-items'] });
setToggleOpen(false);
toast.success(item!.active ? 'سرویس غیرفعال شد' : 'سرویس فعال شد');
},
onError: (e: Error) => { toast.error(e.message); setToggleOpen(false); },
});
if (isLoading) {
return در حال بارگذاری...
;
}
if (isError || !item) {
return (
سرویس یافت نشد
);
}
return (
}
/>
{item.active ? 'فعال' : 'غیرفعال'}
{TABS.map((t) => (
))}
{tab === 'info' && }
{tab === 'tariffs' && setTariffOpen(true)} />}
{tab === 'insurance' && setInsuranceOpen(true)} />}
setEditOpen(false)}
onManageInsurance={() => setInsuranceOpen(true)}
/>
setTariffOpen(false)} />
setInsuranceOpen(false)} />
toggleActive.mutate()}
onCancel={() => setToggleOpen(false)}
loading={toggleActive.isPending}
/>
);
}
export default function ServiceDetailPage() {
return (
);
}