- Positive Pay on the 12 Oct /pps contract (account number, not account ID): OTP enrollment, cheque submit, saved cheques, detail and stop payment. Full flow passed on the phone against mobile-banking-backend.lincpay. - Statement & Mini Statement guide v1.0 (14 Oct): mini statement JSON on Home and the Statement screen; dated PDF export with the password rule. - Onboarding slides and splash screen, Inter font, shared Icon and TypewriterText components, nearby branches via OpenStreetMap. - Every feature switched on; the 14 Oct switch set is kept in featureConfig.stage-2026-10-14.ts. Loans run on demo data until the backend has /loans routes. - npm run android wraps run-android for shells spawned from VS Code. - OpenProject work packages updated to 14-10-2026 status, with screenshots of Positive Pay, statement, loans and cards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
154 lines
4.9 KiB
TypeScript
154 lines
4.9 KiB
TypeScript
import React from 'react';
|
|
import { View, Text, StyleSheet, FlatList } from 'react-native';
|
|
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
|
import { Icon } from '../../../components/Icon';
|
|
import Animated, { FadeInUp } from 'react-native-reanimated';
|
|
|
|
import { ScreenShell } from '../../../components';
|
|
import { useTheme, fontFamily } from '../../../theme';
|
|
import { useAppSelector } from '../../../app/hooks';
|
|
import { selectCustomerById } from '../store/agentSelectors';
|
|
import type { AgentCustomersStackParamList } from '../navigation/agentNavigationTypes';
|
|
import { formatAgentCurrency, formatDateTime } from '../utils/agentFormatters';
|
|
|
|
import { AgentEmptyState } from '../components/AgentEmptyState';
|
|
|
|
type Props = NativeStackScreenProps<AgentCustomersStackParamList, 'CustomerInstallmentHistory'>;
|
|
|
|
export default function CustomerInstallmentHistoryScreen({ route, navigation }: Props) {
|
|
const { customerId } = route.params;
|
|
const theme = useTheme();
|
|
|
|
const customer = useAppSelector(selectCustomerById(customerId));
|
|
|
|
if (!customer) {
|
|
return (
|
|
<ScreenShell title="Installment History">
|
|
<AgentEmptyState title="Error" description="Customer not found" />
|
|
</ScreenShell>
|
|
);
|
|
}
|
|
|
|
// Generate mock history based on totalCollected for demo purposes
|
|
const history = Array.from({ length: 10 }).map((_, i) => {
|
|
const isPaid = i < (customer.totalCollected / 500);
|
|
const date = new Date();
|
|
date.setDate(date.getDate() - (i * 30));
|
|
return {
|
|
id: `hist-${i}`,
|
|
amount: 500,
|
|
date: date.toISOString(),
|
|
status: isPaid ? 'paid' : 'pending',
|
|
receiptNo: isPaid ? `RCP-${date.getFullYear()}${String(date.getMonth()+1).padStart(2,'0')}${String(date.getDate()).padStart(2,'0')}-${1000+i}` : null,
|
|
};
|
|
});
|
|
|
|
const renderItem = ({ item, index }: { item: any, index: number }) => (
|
|
<Animated.View entering={FadeInUp.delay(index * 50)} style={[styles.card, { backgroundColor: theme.colors.surface, borderColor: theme.colors.cardBorder }]}>
|
|
<View style={styles.leftSide}>
|
|
<View style={[styles.iconFrame, { backgroundColor: item.status === 'paid' ? theme.tint(theme.colors.success, 0.08) : theme.tint(theme.colors.warning, 0.08) }]}>
|
|
<Icon
|
|
name={item.status === 'paid' ? 'checkmark-circle' : 'time'}
|
|
size={20}
|
|
color={item.status === 'paid' ? theme.colors.success : theme.colors.warning}
|
|
/>
|
|
</View>
|
|
<View>
|
|
<Text style={[styles.date, { color: theme.colors.textPrimary }]}>{formatDateTime(item.date)}</Text>
|
|
{item.receiptNo ? (
|
|
<Text style={[styles.receipt, { color: theme.colors.textMuted }]}>{item.receiptNo}</Text>
|
|
) : (
|
|
<Text style={[styles.receipt, { color: theme.colors.textMuted }]}>Pending Installment</Text>
|
|
)}
|
|
</View>
|
|
</View>
|
|
<View style={styles.rightSide}>
|
|
<Text style={[styles.amount, { color: theme.colors.textPrimary }]}>{formatAgentCurrency(item.amount)}</Text>
|
|
<Text style={[styles.status, { color: item.status === 'paid' ? theme.colors.success : theme.colors.warning }]}>
|
|
{item.status.toUpperCase()}
|
|
</Text>
|
|
</View>
|
|
</Animated.View>
|
|
);
|
|
|
|
return (
|
|
<ScreenShell title="Installment History" scrollable={false}>
|
|
<View style={[styles.header, { borderBottomColor: theme.colors.cardBorder }]}>
|
|
<Text style={[styles.customerName, { color: theme.colors.textPrimary }]}>{customer.name}</Text>
|
|
<Text style={[styles.accNumber, { color: theme.colors.textMuted }]}>
|
|
{customer.accounts[0]?.type} A/C: {customer.accounts[0]?.accountNumber}
|
|
</Text>
|
|
</View>
|
|
|
|
<FlatList
|
|
data={history}
|
|
keyExtractor={item => item.id}
|
|
renderItem={renderItem}
|
|
contentContainerStyle={styles.listContent}
|
|
showsVerticalScrollIndicator={false}
|
|
/>
|
|
</ScreenShell>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
header: {
|
|
padding: 16,
|
|
paddingTop: 8,
|
|
borderBottomWidth: 1,
|
|
},
|
|
customerName: {
|
|
fontSize: 18,
|
|
fontFamily: fontFamily.bold,
|
|
marginBottom: 4,
|
|
},
|
|
accNumber: {
|
|
fontSize: 14,
|
|
fontFamily: fontFamily.mono,
|
|
},
|
|
listContent: {
|
|
padding: 16,
|
|
},
|
|
card: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
padding: 16,
|
|
borderRadius: 16,
|
|
borderWidth: 1,
|
|
marginBottom: 12,
|
|
},
|
|
leftSide: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
},
|
|
iconFrame: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 20,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
date: {
|
|
fontSize: 14,
|
|
fontFamily: fontFamily.semibold,
|
|
marginBottom: 2,
|
|
},
|
|
receipt: {
|
|
fontSize: 11,
|
|
},
|
|
rightSide: {
|
|
alignItems: 'flex-end',
|
|
},
|
|
amount: {
|
|
fontSize: 15,
|
|
fontFamily: fontFamily.bold,
|
|
marginBottom: 2,
|
|
},
|
|
status: {
|
|
fontSize: 10,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
});
|