255 lines
9.9 KiB
TypeScript
255 lines
9.9 KiB
TypeScript
import React from 'react';
|
|
import { RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
|
import Ionicons from '@react-native-vector-icons/ionicons';
|
|
|
|
import type { LoanStackParamList } from '../../../navigation/types';
|
|
import { Button, Card, EmptyState, Loader, ScreenShell } from '../../../components';
|
|
import { useTheme } from '../../../theme';
|
|
import { formatCurrency, formatDate, formatCompactCurrency } from '../../../utils/formatters';
|
|
import { useLoanDashboard } from '../hooks/useLoanDashboard';
|
|
import { LoanSummaryHero } from '../components/LoanSummaryHero';
|
|
import { LoanAccountCard } from '../components/LoanAccountCard';
|
|
import { LoanFilterChip } from '../components/LoanFilterChip';
|
|
|
|
import { LoanNotificationItem } from '../components/LoanNotificationItem';
|
|
import { useGetLoanNotificationsQuery } from '../services/loanApi';
|
|
|
|
type Props = NativeStackScreenProps<LoanStackParamList, 'LoanDashboard'>;
|
|
|
|
export default function LoanDashboardScreen({ navigation }: Props) {
|
|
const theme = useTheme();
|
|
const {
|
|
activeLoans,
|
|
applications,
|
|
totalOutstanding,
|
|
nextEmi,
|
|
summary,
|
|
unreadNotifications,
|
|
isLoading,
|
|
isError,
|
|
refreshing,
|
|
onRefresh,
|
|
} = useLoanDashboard();
|
|
|
|
// Mock notifications for demonstration on dashboard
|
|
const { data: notifications } = useGetLoanNotificationsQuery();
|
|
const recentNotifications = (notifications ?? []).slice(0, 2);
|
|
|
|
return (
|
|
<ScreenShell title="Loan Management" subtitle="Manage active loans and apply for new ones" scrollable={false}>
|
|
<ScrollView
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={styles.scrollContent}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={refreshing}
|
|
onRefresh={onRefresh}
|
|
tintColor={theme.colors.primary}
|
|
colors={[theme.colors.primary]}
|
|
/>
|
|
}
|
|
>
|
|
{/* Hero Summary */}
|
|
<LoanSummaryHero totalOutstanding={totalOutstanding} nextEmi={nextEmi} summary={summary} />
|
|
|
|
{/* Notifications Section */}
|
|
{recentNotifications.length > 0 && (
|
|
<View style={styles.loanSection}>
|
|
<View style={styles.sectionHeaderRow}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>What's new</Text>
|
|
{unreadNotifications > 0 && (
|
|
<View style={[styles.unreadBadge, { backgroundColor: theme.colors.primary }]}>
|
|
<Text style={styles.unreadCount}>{unreadNotifications}</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
<Card style={styles.notificationCard}>
|
|
{recentNotifications.map((notif, index) => (
|
|
<View key={notif.id}>
|
|
<LoanNotificationItem notification={notif} />
|
|
{index < recentNotifications.length - 1 && (
|
|
<View style={[styles.divider, { backgroundColor: theme.colors.cardBorder }]} />
|
|
)}
|
|
</View>
|
|
))}
|
|
</Card>
|
|
</View>
|
|
)}
|
|
|
|
{/* Quick Actions */}
|
|
<Card style={styles.actionCard}>
|
|
<View style={styles.sectionHeader}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>Quick actions</Text>
|
|
<Text style={[styles.sectionSubtitle, { color: theme.colors.textSecondary }]}>
|
|
Apply for a new loan, calculate EMI, or manage payments
|
|
</Text>
|
|
</View>
|
|
<View style={styles.actionGrid}>
|
|
<View style={styles.actionRow}>
|
|
<View style={styles.actionCol}>
|
|
<Button
|
|
title="Apply Loan"
|
|
onPress={() => navigation.navigate('LoanTypesListing')}
|
|
leftAccessory={<Ionicons name="add-circle-outline" size={18} color={theme.colors.white} />}
|
|
/>
|
|
</View>
|
|
<View style={styles.actionCol}>
|
|
<Button
|
|
title="EMI Calculator"
|
|
variant="secondary"
|
|
onPress={() => navigation.navigate('LoanCalculator', {})}
|
|
leftAccessory={<Ionicons name="calculator-outline" size={18} color={theme.colors.textPrimary} />}
|
|
/>
|
|
</View>
|
|
</View>
|
|
<View style={styles.actionRow}>
|
|
<View style={styles.actionCol}>
|
|
<Button
|
|
title="Track Application"
|
|
variant="secondary"
|
|
onPress={() => navigation.navigate('LoanTracking')}
|
|
leftAccessory={<Ionicons name="locate-outline" size={18} color={theme.colors.textPrimary} />}
|
|
/>
|
|
</View>
|
|
<View style={styles.actionCol}>
|
|
<Button
|
|
title="Payment History"
|
|
variant="secondary"
|
|
onPress={() => navigation.navigate('LoanHistory')}
|
|
leftAccessory={<Ionicons name="time-outline" size={18} color={theme.colors.textPrimary} />}
|
|
/>
|
|
</View>
|
|
</View>
|
|
<Button
|
|
title="Documents"
|
|
variant="secondary"
|
|
onPress={() => navigation.navigate('LoanDocuments', {})}
|
|
leftAccessory={<Ionicons name="document-text-outline" size={18} color={theme.colors.textPrimary} />}
|
|
/>
|
|
</View>
|
|
</Card>
|
|
|
|
{/* Active Loans */}
|
|
<View style={styles.loanSection}>
|
|
<View style={styles.sectionHeaderRow}>
|
|
<View style={styles.sectionHeaderCopy}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>Active loans</Text>
|
|
<Text style={[styles.sectionSubtitle, { color: theme.colors.textSecondary }]}>
|
|
{activeLoans.length} loan{activeLoans.length !== 1 ? 's' : ''} in your portfolio
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{isLoading && !activeLoans.length ? (
|
|
<Card><Loader /></Card>
|
|
) : null}
|
|
|
|
{isError && !activeLoans.length ? (
|
|
<EmptyState
|
|
title="Unable to load loans"
|
|
description="Please pull to refresh or try again in a moment."
|
|
actionLabel="Refresh"
|
|
onAction={onRefresh}
|
|
/>
|
|
) : null}
|
|
|
|
{!isLoading && !isError && !activeLoans.length ? (
|
|
<EmptyState
|
|
title="No active loans"
|
|
description="Apply for a loan to finance your dreams with competitive interest rates."
|
|
actionLabel="Apply for Loan"
|
|
onAction={() => navigation.navigate('LoanTypesListing')}
|
|
/>
|
|
) : null}
|
|
|
|
{activeLoans.map((loan) => (
|
|
<LoanAccountCard
|
|
key={loan.id}
|
|
loan={loan}
|
|
onPress={() => navigation.navigate('ActiveLoanDetails', { loanId: loan.id })}
|
|
/>
|
|
))}
|
|
</View>
|
|
|
|
{/* Recent Applications */}
|
|
{applications.length > 0 && (
|
|
<View style={styles.loanSection}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>Recent applications</Text>
|
|
{applications.map((app) => (
|
|
<Card key={app.id} style={styles.applicationCard}>
|
|
<View style={styles.appHeader}>
|
|
<View style={styles.appHeaderLeft}>
|
|
<Text style={[styles.appType, { color: theme.colors.textPrimary }]}>{app.type}</Text>
|
|
<Text style={[styles.appAmount, { color: theme.colors.textSecondary }]}>
|
|
{formatCurrency(app.amount)} · {formatDate(app.appliedDate)}
|
|
</Text>
|
|
</View>
|
|
<View
|
|
style={[
|
|
styles.appStatusBadge,
|
|
{
|
|
backgroundColor: app.status === 'Approved' ? '#10B98114' :
|
|
app.status === 'Rejected' ? '#EF444414' : `${theme.colors.primary}14`,
|
|
},
|
|
]}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.appStatusText,
|
|
{
|
|
color: app.status === 'Approved' ? '#10B981' :
|
|
app.status === 'Rejected' ? '#EF4444' : theme.colors.primary,
|
|
},
|
|
]}
|
|
>
|
|
{app.status}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</Card>
|
|
))}
|
|
</View>
|
|
)}
|
|
|
|
<View style={styles.bottomSpacer} />
|
|
</ScrollView>
|
|
</ScreenShell>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
scrollContent: { gap: 18, paddingBottom: 24 },
|
|
actionCard: { gap: 16 },
|
|
sectionHeader: { gap: 4 },
|
|
sectionTitle: { fontSize: 18, fontWeight: '800' },
|
|
sectionSubtitle: { fontSize: 13, lineHeight: 18 },
|
|
actionGrid: { gap: 12 },
|
|
actionRow: { flexDirection: 'row', gap: 12 },
|
|
actionCol: { flex: 1 },
|
|
loanSection: { gap: 14 },
|
|
sectionHeaderRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
|
|
sectionHeaderCopy: { flex: 1, gap: 4 },
|
|
applicationCard: { gap: 8 },
|
|
unreadBadge: {
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 2,
|
|
borderRadius: 8,
|
|
marginLeft: 8,
|
|
},
|
|
unreadCount: {
|
|
color: '#FFF',
|
|
fontSize: 12,
|
|
fontWeight: '800',
|
|
},
|
|
notificationCard: { padding: 4, gap: 0 },
|
|
divider: { height: 1, marginHorizontal: 12 },
|
|
appHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
|
|
appHeaderLeft: { gap: 2 },
|
|
appType: { fontSize: 15, fontWeight: '700' },
|
|
appAmount: { fontSize: 12, fontWeight: '500' },
|
|
appStatusBadge: { paddingHorizontal: 10, paddingVertical: 5, borderRadius: 999 },
|
|
appStatusText: { fontSize: 12, fontWeight: '700' },
|
|
bottomSpacer: { height: 60 },
|
|
});
|