Skip to content

Commit c26c034

Browse files
committed
feat: support new latest_action API structure with nested field filtering
- Remove getLastActionStatus function, use API values directly - Fix errorReason field access after camelCase transformation - Add nested field filtering/sorting (action_status, action_error_reason) - Implement configuration-driven filtering for maintainability - Update status mappings (reminded vs waiting_for_learner) - Fix BnrRequestStatusCell error display (failed_approval → "Failed: Approval") - Add React.memo optimizations and fix deprecation warnings - Update tests for new API structure Maintains backward compatibility while enabling enhanced filtering capabilities.
1 parent bbb39a2 commit c26c034

File tree

7 files changed

+94
-42
lines changed

7 files changed

+94
-42
lines changed

src/components/learner-credit-management/ApprovedRequestActionsTableCell.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const ApprovedRequestActionsTableCell = ({ row }) => {
1010

1111
// Check if the cancel and remind button should be shown for this row
1212
const shouldShowShowActionButtons = (
13-
(original.lastActionStatus === 'waiting_for_learner' || original.requestStatus === 'approved')
13+
(original.lastActionStatus === 'reminded' || original.requestStatus === 'approved')
1414
);
1515

1616
// Don't render dropdown if no actions are available

src/components/learner-credit-management/BudgetDetailApprovedRequestTable.jsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,14 @@ const FilterStatus = (rest) => (
1717
);
1818

1919
const getRequestStatusDisplayName = (status) => {
20-
if (status === 'waiting_for_learner') {
21-
return 'Waiting for learner';
22-
}
20+
const statusDisplayMap = {
21+
reminded: 'Waiting for learner',
22+
approved: 'Approved',
23+
pending: 'Pending',
24+
refunded: 'Refunded',
25+
};
2326

24-
if (status === 'refunded') {
25-
return 'Refunded';
26-
}
27-
28-
if (status === 'failed_cancellation') {
29-
return 'Failed cancellation';
30-
}
31-
32-
return status
27+
return statusDisplayMap[status] || status
3328
.split('_')
3429
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
3530
.join(' ');

src/components/learner-credit-management/RequestStatusTableCell.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ const RequestStatusTableCell = ({ enterpriseId, row }) => {
4040
// Currently we check both `lastActionErrorReason` and `lastActionStatus` which creates
4141
// confusion since status information comes from two different sources. The API should
4242
// be updated to return a single, unified status field to simplify this logic.
43-
if (lastActionErrorReason === 'Failed: Cancellation') {
43+
44+
if (lastActionErrorReason === 'failed_cancellation') {
4445
return (
4546
<FailedCancellation
4647
learnerEmail={learnerEmail}
@@ -49,7 +50,7 @@ const RequestStatusTableCell = ({ enterpriseId, row }) => {
4950
);
5051
}
5152

52-
if (lastActionStatus === 'waiting_for_learner' || requestStatus === 'approved') {
53+
if (lastActionStatus === 'reminded' || requestStatus === 'approved') {
5354
return (
5455
<WaitingForLearner
5556
learnerEmail={learnerEmail}

src/components/learner-credit-management/data/hooks/tests/useBnrSubsidyRequests.test.jsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const mockApiResponse = {
3939
created: '2023-10-27T10:00:00Z',
4040
state: 'approved',
4141
latestAction: {
42-
status: 'waiting for learner',
42+
status: 'reminded',
4343
errorReason: null,
4444
created: '2023-10-27T10:00:00Z',
4545
},
@@ -71,11 +71,11 @@ const expectedTransformedData = [
7171
amount: 100,
7272
requestDate: 'Oct 27, 2023',
7373
requestStatus: 'approved',
74-
lastActionStatus: 'waiting_for_learner',
74+
lastActionStatus: 'reminded',
7575
lastActionDate: 'Oct 27, 2023',
7676
lastActionErrorReason: null,
7777
latestAction: {
78-
status: 'waiting for learner',
78+
status: 'reminded',
7979
errorReason: null,
8080
created: '2023-10-27T10:00:00Z',
8181
},
@@ -90,10 +90,10 @@ const expectedTransformedData = [
9090
requestStatus: 'approved',
9191
lastActionStatus: 'refunded',
9292
lastActionDate: 'Oct 27, 2023',
93-
lastActionErrorReason: 'Payment failed',
93+
lastActionErrorReason: 'failed_cancellation',
9494
latestAction: {
9595
status: 'refunded',
96-
errorReason: 'Payment failed',
96+
errorReason: 'failed_cancellation',
9797
created: '2023-10-27T11:00:00Z',
9898
},
9999
},
@@ -247,8 +247,8 @@ describe('useBnrSubsidyRequests', () => {
247247
{
248248
page: 1,
249249
page_size: 25,
250-
search: '[email protected]',
251250
state: 'approved,pending',
251+
search: '[email protected]',
252252
},
253253
);
254254
});
@@ -528,10 +528,24 @@ describe('applyFiltersToOptions', () => {
528528
});
529529
});
530530

531-
it('should not apply status filter when array is empty', () => {
531+
it('should apply nested field filters correctly', () => {
532+
const options = {};
533+
applyFiltersToOptions([
534+
{ id: 'lastActionStatus', value: ['reminded', 'approved'] },
535+
{ id: 'lastActionErrorReason', value: ['failed_cancellation'] },
536+
], options);
537+
expect(options).toEqual({
538+
action_status: 'reminded,approved',
539+
action_error_reason: 'failed_cancellation',
540+
});
541+
});
542+
543+
it('should not apply filters when values are empty', () => {
532544
const options = {};
533545
applyFiltersToOptions([
534546
{ id: 'requestStatus', value: [] },
547+
{ id: 'requestDetails', value: '' },
548+
{ id: 'lastActionStatus', value: [] },
535549
], options);
536550
expect(options).toEqual({});
537551
});

src/components/learner-credit-management/data/hooks/useBnrSubsidyRequests.js

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const API_FIELDS_BY_TABLE_COLUMN_ACCESSOR = {
2121
amount: 'amount',
2222
requestDate: 'requestDate',
2323
requestStatus: 'requestStatus',
24+
lastActionStatus: 'latest_action__status',
25+
lastActionErrorReason: 'latest_action__error_reason',
26+
recentAction: 'latest_action__recent_action',
2427
};
2528

2629
export const applySortByToOptions = (sortBy, options) => {
@@ -43,27 +46,47 @@ export const applySortByToOptions = (sortBy, options) => {
4346
}
4447
};
4548

49+
// Configuration mapping for filter field transformations
50+
const FILTER_FIELD_CONFIG = {
51+
requestDetails: {
52+
apiParam: 'search',
53+
transform: (value) => value,
54+
},
55+
requestStatus: {
56+
apiParam: 'state',
57+
transform: (value) => (Array.isArray(value) ? value.join(',') : value),
58+
},
59+
lastActionStatus: {
60+
apiParam: 'action_status',
61+
transform: (value) => (Array.isArray(value) ? value.join(',') : value),
62+
},
63+
lastActionErrorReason: {
64+
apiParam: 'action_error_reason',
65+
transform: (value) => (Array.isArray(value) ? value.join(',') : value),
66+
},
67+
};
68+
4669
export const applyFiltersToOptions = (filters, options) => {
4770
if (!filters || filters.length === 0) {
4871
return;
4972
}
50-
const emailSearchQuery = filters.find(filter => filter.id === 'requestDetails')?.value;
51-
const statusFilter = filters.find(filter => filter.id === 'requestStatus')?.value;
5273

53-
if (emailSearchQuery) {
54-
Object.assign(options, {
55-
search: emailSearchQuery,
56-
});
57-
}
74+
filters.forEach(filter => {
75+
const config = FILTER_FIELD_CONFIG[filter.id];
5876

59-
if (statusFilter && statusFilter.length > 0) {
60-
Object.assign(options, {
61-
state: statusFilter.join(','),
62-
});
63-
}
64-
};
77+
if (config && filter.value) {
78+
if (Array.isArray(filter.value) && filter.value.length === 0) {
79+
return;
80+
}
81+
if (typeof filter.value === 'string' && !filter.value.trim()) {
82+
return;
83+
}
6584

66-
const getLastActionStatus = (latestAction) => latestAction?.status?.toLowerCase().replace(/\s+/g, '_');
85+
const transformedValue = config.transform(filter.value);
86+
Object.assign(options, { [config.apiParam]: transformedValue });
87+
}
88+
});
89+
};
6790

6891
// Transform API response data to match DataTable the requirements
6992
const transformApiDataToTableData = (apiResults) => apiResults.map((item) => {
@@ -85,8 +108,8 @@ const transformApiDataToTableData = (apiResults) => apiResults.map((item) => {
85108
amount: item?.coursePrice || 0,
86109
requestDate,
87110
requestStatus: item?.state,
88-
lastActionStatus: getLastActionStatus(item?.latestAction),
89-
lastActionErrorReason: item?.latestAction?.errorReason,
111+
lastActionStatus: item?.latestAction?.status, // Direct assignment, no transformation
112+
lastActionErrorReason: item?.latestAction?.errorReason, // Use error_reason field
90113
lastActionDate,
91114
latestAction: item?.latestAction,
92115
};

src/components/learner-credit-management/requests-tab/BnrRequestStatusCell.jsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@ const BnrRequestStatusCell = ({ row }) => {
1111
const recentAction = latestAction?.recentAction;
1212
const [target, setTarget] = useState(null);
1313
const [isModalOpen, setIsModalOpen] = useState(false);
14+
15+
// Format error reasons for display
16+
const formatErrorReason = (reason) => {
17+
if (!reason) {
18+
return null;
19+
}
20+
21+
const errorReasonMap = {
22+
failed_approval: 'Failed: Approval',
23+
failed_cancellation: 'Failed: Cancellation',
24+
failed_system: 'Failed: System Error',
25+
failed_payment: 'Failed: Payment',
26+
failed_enrollment: 'Failed: Enrollment',
27+
};
28+
29+
return errorReasonMap[reason]
30+
|| reason.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(': ');
31+
};
1432
const getStatusConfig = useMemo(() => {
1533
const statusConfigs = {
1634
requested: {
@@ -44,7 +62,8 @@ const BnrRequestStatusCell = ({ row }) => {
4462
};
4563

4664
// Determine what to display in the chip
47-
const displayText = errorReason || getStatusConfig.label;
65+
const formattedErrorReason = formatErrorReason(errorReason);
66+
const displayText = formattedErrorReason || getStatusConfig.label;
4867
const displayIcon = errorReason ? Error : getStatusConfig.icon;
4968
const displayVariant = errorReason ? 'dark' : '';
5069
const isClickable = !!errorReason;
@@ -66,7 +85,7 @@ const BnrRequestStatusCell = ({ row }) => {
6685

6786
{errorReason && (
6887
<RequestFailureModal
69-
errorReason={errorReason}
88+
errorReason={formattedErrorReason}
7089
isOpen={isModalOpen}
7190
onClose={closeModal}
7291
target={target}

src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1894,12 +1894,12 @@ describe('<BudgetDetailPage />', () => {
18941894
const mockRequestsWithDifferentStatuses = [
18951895
{
18961896
...mockApprovedRequest,
1897-
lastActionStatus: 'waiting_for_learner',
1897+
lastActionStatus: 'reminded',
18981898
},
18991899
{
19001900
...createMockApprovedRequest(),
19011901
lastActionStatus: 'refunded',
1902-
lastActionErrorReason: 'Failed: Cancellation',
1902+
lastActionErrorReason: 'failed_cancellation',
19031903
},
19041904
{
19051905
...createMockApprovedRequest(),

0 commit comments

Comments
 (0)