Files
CBS-Mobile-App/src/modules/agent/screens/CustomerInstallmentHistoryScreen.tsx
T
Abin ToppoandClaude Opus 5.5 f960a119e8 Add Positive Pay, statement guide v1.0, onboarding, and switch every feature on
- 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>
2026-10-14 18:18:40 +05:30

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,
},
});