Conversation
There was a problem hiding this comment.
Pull request overview
This PR enhances the officer-side functionality by adding loan tracking capabilities, improving the dashboard UI with expandable alert cards, and refactoring the beneficiary list to display evidence uploads inline. The changes support better loan monitoring and verification workflows for district officers.
Key Changes:
- Added loan tracking fields (
loanId,loanAmount,lastSynced) to beneficiary metadata - Implemented expandable alert sections in the dashboard showing high-risk, pending, and deadline-crossed loans
- Enhanced beneficiary list cards to preview evidence uploads with real-time loading
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
types/beneficiary.ts |
Added loan tracking fields to BeneficiaryMetadata and BeneficiaryRecord interfaces |
services/api/submissionRepository.ts |
Cleaned up merge conflict markers from git history |
services/api/beneficiaryRepository.ts |
Updated data mapping to handle new metadata fields and ensure createdAt consistency |
screens/officer/verification-tasks-screen.tsx |
Added comprehensive stylesheet definitions for card UI components |
screens/officer/support-screen.tsx |
Removed redundant header section, relying on WaveHeader instead |
screens/officer/reports-screen.tsx |
Added "Reject" action button with alert functionality for quick actions panel |
screens/officer/dashboard-screen.tsx |
Implemented collapsible alert cards with loan details, added FAB notification button |
screens/officer/beneficiary-list-screen.tsx |
Refactored to show evidence uploads in cards, added high-priority filtering logic |
screens/officer/beneficiary-form-screen.tsx |
Updated metadata initialization to include loan tracking fields |
components/organisms/beneficiary-risk-lists.tsx |
New component providing reusable loan risk categorization lists with mock data |
Comments suppressed due to low confidence (1)
screens/officer/beneficiary-list-screen.tsx:70
- The HIGH_PRIORITY_LOAN_IDS Set contains hardcoded loan IDs. This creates a maintenance issue where loan priorities need to be updated in the code rather than being data-driven.
Consider:
- Moving this to a configuration file or fetching from an API
- Adding a priority field to the loan data structure
- Documenting why these specific IDs are high priority
This will make the system more flexible and easier to maintain.
const IMAGE_QUALITY_LABELS: Record<ImageQuality, string> = {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const handleAction = (action: string) => { | ||
| switch (action) { | ||
| case 'Approve': | ||
| Alert.alert('Approve file', 'Confirm when AI risk is low and KYC + site proofs align.'); | ||
| break; | ||
| case 'Ask Re-Upload': | ||
| Alert.alert('Ask for re-upload', 'Request clearer or corrected documents and add a note for the applicant.'); | ||
| break; | ||
| case 'Raise Field Inspection': | ||
| Alert.alert('Raise field inspection', 'Escalate to field verification when anomalies persist or address validation is needed.'); | ||
| break; | ||
| case 'Reject': | ||
| Alert.alert('Reject file', 'Reject only when fraud is confirmed; record the rejection reason and notify the applicant.'); | ||
| break; | ||
| default: | ||
| break; | ||
| } |
There was a problem hiding this comment.
The Alert messages in the handleAction function repeat the helper text from the quickActions array. This creates duplicated content that needs to be maintained in two places.
Consider displaying the helper text directly from the action object instead of duplicating it in the Alert calls, or create a single source of truth for these messages.
| } | ||
| const existingIds = new Set(loans.map((item) => item.loanId)); | ||
| const demoAdditions = FALLBACK_BENEFICIARIES.filter((item) => !existingIds.has(item.loanId)); | ||
| return [...loans, ...demoAdditions]; | ||
| }, [loans]); | ||
|
|
||
| const getDisplayStatus = (loan: BeneficiaryLoan) => { | ||
| if (HIGH_PRIORITY_LOAN_IDS.has(loan.loanId)) { | ||
| return 'High Priority'; | ||
| } | ||
| switch (loan.status as string) { | ||
| case 'sanctioned': | ||
| return 'Synced'; | ||
| case 'disbursed': | ||
| case 'approved': | ||
| return 'Approved'; | ||
| case 'pending': | ||
| case 'pending-review': | ||
| return 'Pending'; | ||
| case 'rejected': |
There was a problem hiding this comment.
The getDisplayStatus function has inconsistent status mapping. For example:
- 'sanctioned' maps to 'Synced'
- 'approved' and 'disbursed' both map to 'Approved'
- 'rejected' and 'closed' both map to 'Rejected'
This status transformation logic should be clearly documented or moved to a shared utility, as the mappings may not be intuitive (e.g., why does 'sanctioned' become 'Synced'?). This could lead to confusion when debugging status-related issues.
| accent, | ||
| accentSoft, | ||
| borderSoft, | ||
| onViewDetails, |
There was a problem hiding this comment.
The BeneficiaryCard component performs a data fetch via the useBeneficiaryUploads hook for every card rendered in the list. If there are many beneficiaries, this could result in N+1 query pattern issues, causing numerous simultaneous API calls.
Consider:
- Batch loading uploads for all visible beneficiaries at the list level
- Implementing pagination or virtual scrolling to limit rendered cards
- Lazy loading uploads only when cards are expanded or come into view
This will significantly improve performance when displaying large lists of beneficiaries.
| beneficiaryUid: `BEN-${Date.now().toString().slice(-6)}`, | ||
| createdAt: new Date().toISOString(), | ||
| status: 'Active', | ||
| lastSynced: null, |
There was a problem hiding this comment.
The metadataSeed object initializes lastSynced with null, but the TypeScript interface defines it as any type. This inconsistency could lead to type safety issues.
Since lastSynced should represent a timestamp, consider typing it as string | null in the interface and consistently using null for unset values.
| category: string; | ||
| loanId: string; | ||
| bank: string; | ||
| status: 'High Risk' | 'Pending' | 'Deadline Crossed' | string; |
There was a problem hiding this comment.
The status field in the LoanItem type uses a union type with a catch-all string. This defeats the purpose of type safety:
status: 'High Risk' | 'Pending' | 'Deadline Crossed' | string;The | string makes any string value acceptable, negating the type checking for the specific status values. Consider either:
- Removing
| stringand using only the specific status values - Using a separate field for custom statuses
- Creating an enum or const assertion for all possible status values
This will provide better type safety and prevent typos in status values.
| status: 'High Risk' | 'Pending' | 'Deadline Crossed' | string; | |
| status: 'High Risk' | 'Pending' | 'Deadline Crossed' | 'High Priority'; |
| export const highRiskLoans: LoanItem[] = [ | ||
| { | ||
| id: 'HR001', | ||
| name: 'Ramesh Kumar', | ||
| address: 'MG Road', | ||
| category: 'High Risk', | ||
| loanId: 'LN-98234', | ||
| bank: 'SBI', | ||
| status: 'High Risk', | ||
| assignedTo: 'Officer A', | ||
| documents: 2, | ||
| lastUpdated: '2 hours ago', | ||
| }, | ||
| { | ||
| id: 'HR002', | ||
| name: 'Neha Verma', | ||
| address: 'Brigade Road', | ||
| category: 'High Risk', | ||
| loanId: 'LN-98299', | ||
| bank: 'HDFC', | ||
| status: 'High Risk', | ||
| assignedTo: 'Officer D', | ||
| documents: 3, | ||
| lastUpdated: '30 mins ago', | ||
| }, | ||
| { | ||
| id: 'HR003', | ||
| name: 'Manaswini Patro', | ||
| address: 'Jayadev Vihar', | ||
| category: 'High Priority', | ||
| loanId: '9981345206', | ||
| bank: 'Canara Bank', | ||
| status: 'High Priority', | ||
| assignedTo: 'Officer G', | ||
| documents: 1, | ||
| lastUpdated: '45 mins ago', | ||
| }, | ||
| ]; | ||
|
|
||
| export const pendingReviewLoans: LoanItem[] = [ | ||
| { | ||
| id: 'PR005', | ||
| name: 'Swastik Kumar Purohit', | ||
| address: 'Danish Nagar', | ||
| category: 'Normal', | ||
| loanId: '9861510432', | ||
| bank: 'Punjab Bank', | ||
| status: 'Synced', | ||
| assignedTo: 'Officer B', | ||
| documents: 0, | ||
| lastUpdated: 'Just now', | ||
| }, | ||
| { | ||
| id: 'PR006', | ||
| name: 'Kavita Joshi', | ||
| address: 'Civil Lines', | ||
| category: 'Normal', | ||
| loanId: 'LN-11229', | ||
| bank: 'Axis Bank', | ||
| status: 'Pending', | ||
| assignedTo: 'Officer E', | ||
| documents: 1, | ||
| lastUpdated: '15 mins ago', | ||
| }, | ||
| { | ||
| id: 'PR007', | ||
| name: 'Aarav Mishra', | ||
| address: 'Shivaji Nagar', | ||
| category: 'Normal', | ||
| loanId: '7345129081', | ||
| bank: 'Bank of Baroda', | ||
| status: 'Pending', | ||
| assignedTo: 'Officer H', | ||
| documents: 2, | ||
| lastUpdated: '10 mins ago', | ||
| }, | ||
| { | ||
| id: 'PR008', | ||
| name: 'Sonal Tiwari', | ||
| address: 'Saket Nagar', | ||
| category: 'Normal', | ||
| loanId: '9034572211', | ||
| bank: 'SBI', | ||
| status: 'Approved', | ||
| assignedTo: 'Officer I', | ||
| documents: 3, | ||
| lastUpdated: '1 hour ago', | ||
| }, | ||
| { | ||
| id: 'PR009', | ||
| name: 'Rahul Patra', | ||
| address: 'Old Town', | ||
| category: 'Normal', | ||
| loanId: '8023114455', | ||
| bank: 'ICICI', | ||
| status: 'Rejected', | ||
| assignedTo: 'Officer J', | ||
| documents: 1, | ||
| lastUpdated: '2 hours ago', | ||
| }, | ||
| ]; |
There was a problem hiding this comment.
The loan data contains inconsistent status values across the three arrays. In highRiskLoans, item HR003 (line 61) has status: 'High Priority' while items HR001 and HR002 have status: 'High Risk'. Similarly, in pendingReviewLoans, there are 'Synced', 'Pending', 'Approved', and 'Rejected' statuses mixed together.
These status inconsistencies don't align with the array groupings:
highRiskLoansshould contain only high-risk itemspendingReviewLoansshould contain only pending items- Items with 'Approved' or 'Rejected' status shouldn't be in
pendingReviewLoans
This will cause confusion and potential bugs in filtering and display logic. Ensure status values correctly reflect the categorization.
| <AppText variant="headlineMedium" weight="600" translate={false}> | ||
| {title} | ||
| </AppText> | ||
| <AppText variant="bodySmall" color="muted" translate={false}> |
There was a problem hiding this comment.
The translate={false} prop is used extensively throughout this component (lines 173, 176, 190, 193, 198, 226, 229), but there's no indication this prop is supported by the AppText component.
If AppText doesn't support a translate prop, this will either:
- Have no effect (if TypeScript allows extra props)
- Cause TypeScript errors (if strict prop checking is enabled)
- Be passed down to the underlying Text component where it may not be valid
Verify that AppText supports the translate prop, or remove these if they're not functional.
| <AppText variant="headlineMedium" weight="600" translate={false}> | |
| {title} | |
| </AppText> | |
| <AppText variant="bodySmall" color="muted" translate={false}> | |
| <AppText variant="headlineMedium" weight="600"> | |
| {title} | |
| </AppText> | |
| <AppText variant="bodySmall" color="muted"> |
| const metadata = { | ||
| beneficiaryUid: normalizedMobile, | ||
| createdAt: new Date().toISOString(), | ||
| lastSynced: null, |
There was a problem hiding this comment.
[nitpick] Setting lastSynced to null and loanId to undefined creates type inconsistency. Since loanId is typed as string | undefined in the interface, using undefined is correct. However, lastSynced should consistently be null if that's the intended default, or undefined if following the same pattern as loanId.
Recommend standardizing on either null or undefined for optional/unset values across the metadata object for consistency.
| lastSynced: null, | |
| lastSynced: undefined, |
| ...metadataSeed, | ||
| updatedAt: new Date().toISOString(), | ||
| loanId: values.loanId, | ||
| loanAmount: Number(values.sanctionAmount || 0), |
There was a problem hiding this comment.
Converting values.sanctionAmount to a number using Number() without validation could result in NaN if the input is invalid. This should include validation or a fallback:
loanAmount: Number(values.sanctionAmount) || 0,Or better yet, validate that sanctionAmount is a valid number before conversion.
| loanAmount: Number(values.sanctionAmount || 0), | |
| loanAmount: (() => { | |
| const n = Number(values.sanctionAmount); | |
| return isNaN(n) ? 0 : n; | |
| })(), |
| const handleViewDetails = (loan: LoanItem) => { | ||
| navigation.navigate('VerificationTasks', { | ||
| filter: loan.status, | ||
| loanId: loan.loanId, | ||
| }); | ||
| }; | ||
|
|
There was a problem hiding this comment.
Unused variable handleViewDetails.
| const handleViewDetails = (loan: LoanItem) => { | |
| navigation.navigate('VerificationTasks', { | |
| filter: loan.status, | |
| loanId: loan.loanId, | |
| }); | |
| }; | |
…b/NIDHISETU into ui-verification-fixes
updated officer side