340 lines
11 KiB
TypeScript
340 lines
11 KiB
TypeScript
import React from 'react';
|
|
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
|
import Ionicons from '@react-native-vector-icons/ionicons';
|
|
|
|
import { Button, Card, EmptyState, Loader, ScreenShell } from '../../../components';
|
|
import { useToast } from '../../../hooks/useToast';
|
|
import type { InsuranceStackParamList } from '../../../navigation/types';
|
|
import { useTheme } from '../../../theme';
|
|
import { formatCompactCurrency, formatCurrency, formatDate } from '../../../utils/formatters';
|
|
import { InsuranceStatusPill } from '../components/InsuranceStatusPill';
|
|
import { useGetInsurancePolicyDetailQuery } from '../services/insuranceApi';
|
|
|
|
type Props = NativeStackScreenProps<InsuranceStackParamList, 'InsurancePolicyDetails'>;
|
|
|
|
export default function InsurancePolicyDetailsScreen({ navigation, route }: Props) {
|
|
const theme = useTheme();
|
|
const toast = useToast();
|
|
const { data, isLoading, isError, refetch } = useGetInsurancePolicyDetailQuery(
|
|
route.params.policyId,
|
|
);
|
|
|
|
return (
|
|
<ScreenShell title="Policy Details" subtitle="Coverage, insurer, and servicing actions">
|
|
{isLoading ? (
|
|
<Card>
|
|
<Loader />
|
|
</Card>
|
|
) : null}
|
|
|
|
{isError || !data ? (
|
|
<EmptyState
|
|
title="Unable to load policy"
|
|
description="Please retry in a moment."
|
|
actionLabel="Retry"
|
|
onAction={refetch}
|
|
/>
|
|
) : (
|
|
<ScrollView contentContainerStyle={styles.scroll} showsVerticalScrollIndicator={false}>
|
|
<Card style={styles.heroCard}>
|
|
<View style={styles.heroHeader}>
|
|
<View style={styles.heroCopy}>
|
|
<Text style={[styles.heroTitle, { color: theme.colors.textPrimary }]}>
|
|
{data.planName}
|
|
</Text>
|
|
<Text style={[styles.heroMeta, { color: theme.colors.textSecondary }]}>
|
|
{data.policyNumber} · {data.insurer.name}
|
|
</Text>
|
|
</View>
|
|
<InsuranceStatusPill status={data.status} />
|
|
</View>
|
|
|
|
<View style={styles.metricGrid}>
|
|
<View style={[styles.metricCard, { backgroundColor: theme.colors.surfaceMuted }]}>
|
|
<Text style={[styles.metricLabel, { color: theme.colors.textSecondary }]}>
|
|
Coverage
|
|
</Text>
|
|
<Text style={[styles.metricValue, { color: theme.colors.textPrimary }]}>
|
|
{formatCompactCurrency(data.coverageAmount)}
|
|
</Text>
|
|
</View>
|
|
<View style={[styles.metricCard, { backgroundColor: theme.colors.surfaceMuted }]}>
|
|
<Text style={[styles.metricLabel, { color: theme.colors.textSecondary }]}>
|
|
Premium
|
|
</Text>
|
|
<Text style={[styles.metricValue, { color: theme.colors.textPrimary }]}>
|
|
{formatCurrency(data.premiumAmount)}
|
|
</Text>
|
|
<Text style={[styles.metricCaption, { color: theme.colors.textMuted }]}>
|
|
{data.premiumFrequency}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.infoList}>
|
|
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
|
Start date:{' '}
|
|
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
|
{formatDate(data.startDate)}
|
|
</Text>
|
|
</Text>
|
|
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
|
Expiry date:{' '}
|
|
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
|
{formatDate(data.expiryDate)}
|
|
</Text>
|
|
</Text>
|
|
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
|
Next premium due:{' '}
|
|
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
|
{formatDate(data.nextPremiumDueDate)}
|
|
</Text>
|
|
</Text>
|
|
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
|
Nominee:{' '}
|
|
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
|
{data.nomineeName} ({data.nomineeRelationship})
|
|
</Text>
|
|
</Text>
|
|
</View>
|
|
</Card>
|
|
|
|
<Card style={styles.actionCard}>
|
|
<View style={styles.actionRow}>
|
|
<View style={styles.actionCol}>
|
|
<Button
|
|
title="Pay Premium"
|
|
onPress={() =>
|
|
navigation.navigate('InsuranceRenewals', { policyId: data.id })
|
|
}
|
|
/>
|
|
</View>
|
|
<View style={styles.actionCol}>
|
|
<Button
|
|
title="File Claim"
|
|
variant="secondary"
|
|
onPress={() =>
|
|
navigation.navigate('InsuranceClaims', { policyId: data.id })
|
|
}
|
|
/>
|
|
</View>
|
|
</View>
|
|
<Button
|
|
title="Download Policy PDF"
|
|
variant="secondary"
|
|
onPress={() =>
|
|
toast(
|
|
'Policy download ready',
|
|
'success',
|
|
`${data.documents[0]?.name ?? 'Policy schedule'} is ready from the secure vault.`,
|
|
)
|
|
}
|
|
/>
|
|
</Card>
|
|
|
|
<Card style={styles.sectionCard}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
|
Benefits and inclusions
|
|
</Text>
|
|
{data.benefits.map((benefit) => (
|
|
<View key={benefit} style={styles.listRow}>
|
|
<Ionicons name="checkmark-circle" size={16} color={theme.colors.accent} />
|
|
<Text style={[styles.listText, { color: theme.colors.textPrimary }]}>
|
|
{benefit}
|
|
</Text>
|
|
</View>
|
|
))}
|
|
{data.inclusions.map((inclusion) => (
|
|
<View key={inclusion} style={styles.listRow}>
|
|
<Ionicons name="shield-checkmark" size={16} color={theme.colors.primary} />
|
|
<Text style={[styles.listText, { color: theme.colors.textPrimary }]}>
|
|
{inclusion}
|
|
</Text>
|
|
</View>
|
|
))}
|
|
</Card>
|
|
|
|
<Card style={styles.sectionCard}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
|
Exclusions
|
|
</Text>
|
|
{data.exclusions.map((exclusion) => (
|
|
<View key={exclusion} style={styles.listRow}>
|
|
<Ionicons name="close-circle" size={16} color={theme.colors.error} />
|
|
<Text style={[styles.listText, { color: theme.colors.textPrimary }]}>
|
|
{exclusion}
|
|
</Text>
|
|
</View>
|
|
))}
|
|
</Card>
|
|
|
|
<Card style={styles.sectionCard}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
|
Insurer details
|
|
</Text>
|
|
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
|
Claims ratio:{' '}
|
|
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
|
{data.insurer.claimSettlementRatio}%
|
|
</Text>
|
|
</Text>
|
|
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
|
Support:{' '}
|
|
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
|
{data.insurer.supportPhone}
|
|
</Text>
|
|
</Text>
|
|
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
|
Email:{' '}
|
|
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
|
{data.insurer.supportEmail}
|
|
</Text>
|
|
</Text>
|
|
<Text style={[styles.infoRow, { color: theme.colors.textSecondary }]}>
|
|
Address:{' '}
|
|
<Text style={[styles.infoStrong, { color: theme.colors.textPrimary }]}>
|
|
{data.insurer.address}
|
|
</Text>
|
|
</Text>
|
|
</Card>
|
|
|
|
<Card style={styles.sectionCard}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
|
Policy documents
|
|
</Text>
|
|
{data.documents.map((document) => (
|
|
<Pressable
|
|
key={document.id}
|
|
onPress={() =>
|
|
toast('Document available', 'info', `${document.name} is available in PDF.`)
|
|
}
|
|
style={[styles.documentRow, { backgroundColor: theme.colors.surfaceMuted }]}
|
|
>
|
|
<View style={styles.documentCopy}>
|
|
<Text style={[styles.documentName, { color: theme.colors.textPrimary }]}>
|
|
{document.name}
|
|
</Text>
|
|
<Text style={[styles.documentMeta, { color: theme.colors.textMuted }]}>
|
|
{document.mimeType} · {document.sizeKb} KB
|
|
</Text>
|
|
</View>
|
|
<Ionicons name="download-outline" size={18} color={theme.colors.primary} />
|
|
</Pressable>
|
|
))}
|
|
</Card>
|
|
</ScrollView>
|
|
)}
|
|
<View style={{ height: theme.spacing.xxl }} />
|
|
</ScreenShell>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
scroll: {
|
|
gap: 16,
|
|
paddingBottom: 32,
|
|
},
|
|
heroCard: {
|
|
gap: 14,
|
|
},
|
|
heroHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
gap: 12,
|
|
},
|
|
heroCopy: {
|
|
flex: 1,
|
|
gap: 2,
|
|
},
|
|
heroTitle: {
|
|
fontSize: 18,
|
|
fontWeight: '800',
|
|
},
|
|
heroMeta: {
|
|
fontSize: 12,
|
|
fontWeight: '600',
|
|
},
|
|
metricGrid: {
|
|
flexDirection: 'row',
|
|
gap: 12,
|
|
},
|
|
metricCard: {
|
|
flex: 1,
|
|
borderRadius: 16,
|
|
padding: 14,
|
|
gap: 4,
|
|
},
|
|
metricLabel: {
|
|
fontSize: 11,
|
|
fontWeight: '700',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.5,
|
|
},
|
|
metricValue: {
|
|
fontSize: 18,
|
|
fontWeight: '900',
|
|
},
|
|
metricCaption: {
|
|
fontSize: 11,
|
|
fontWeight: '600',
|
|
},
|
|
infoList: {
|
|
gap: 6,
|
|
},
|
|
infoRow: {
|
|
fontSize: 12,
|
|
lineHeight: 18,
|
|
},
|
|
infoStrong: {
|
|
fontWeight: '700',
|
|
},
|
|
actionCard: {
|
|
gap: 12,
|
|
},
|
|
actionRow: {
|
|
flexDirection: 'row',
|
|
gap: 12,
|
|
},
|
|
actionCol: {
|
|
flex: 1,
|
|
},
|
|
sectionCard: {
|
|
gap: 10,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 16,
|
|
fontWeight: '800',
|
|
},
|
|
listRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'flex-start',
|
|
gap: 8,
|
|
},
|
|
listText: {
|
|
flex: 1,
|
|
fontSize: 12,
|
|
lineHeight: 18,
|
|
fontWeight: '600',
|
|
},
|
|
documentRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
borderRadius: 14,
|
|
padding: 12,
|
|
},
|
|
documentCopy: {
|
|
flex: 1,
|
|
gap: 3,
|
|
},
|
|
documentName: {
|
|
fontSize: 14,
|
|
fontWeight: '700',
|
|
},
|
|
documentMeta: {
|
|
fontSize: 11,
|
|
fontWeight: '600',
|
|
},
|
|
});
|