Skip to content

Commit 8e38bd4

Browse files
committed
feat: Enable status filter and sorting in Approved Requests table
- Enable Status column filtering - Map filters to latest_action_status / latest_action_status__in - Map sorting to latest_action_status and latest_action_time
1 parent ccbb977 commit 8e38bd4

11 files changed

+293
-129
lines changed

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

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,28 +62,11 @@ const BudgetDetailApprovedRequestHeader = () => {
6262
const BudgetDetailApprovedRequest = ({ enterpriseId }) => {
6363
const { isLoading, bnrRequests, fetchApprovedRequests } = useBnrSubsidyRequests({ enterpriseId });
6464

65-
// Transform the data to match the expected table format
66-
// Generate status counts from the actual results data
67-
const statusCounts = {};
68-
(bnrRequests.results || []).forEach((request) => {
69-
const { lastActionStatus } = request;
70-
if (lastActionStatus) {
71-
statusCounts[lastActionStatus] = (statusCounts[lastActionStatus] || 0) + 1;
72-
}
73-
});
74-
75-
const requestStatusCounts = Object.entries(statusCounts).map(
76-
([lastActionStatus, count]) => ({
77-
lastActionStatus,
78-
count,
79-
}),
80-
);
81-
8265
const approvedRequests = {
8366
count: bnrRequests.itemCount || 0,
8467
numPages: bnrRequests.pageCount || 1,
8568
results: bnrRequests.results || [],
86-
requestStatusCounts,
69+
requestStatusCounts: bnrRequests.learnerRequestStateCounts || [],
8770
};
8871

8972
return (

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

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import RequestAmountTableCell from './RequestAmountTableCell';
1010
import RequestRecentActionTableCell from './RequestRecentActionTableCell';
1111
import ApprovedRequestActionsTableCell from './ApprovedRequestActionsTableCell';
1212
import ApprovedRequestsTableRefreshAction from './ApprovedRequestsTableRefreshAction';
13-
import { DEFAULT_PAGE, PAGE_SIZE, REQUEST_STATUSES } from './data';
13+
import { DEFAULT_PAGE, PAGE_SIZE } from './data';
14+
import { transformLearnerRequestStateCounts } from './data/utils';
1415

1516
const FilterStatus = (rest) => (
1617
<DataTable.FilterStatus showFilteredFields={false} {...rest} />
@@ -29,18 +30,7 @@ const BudgetDetailApprovedRequestTable = ({
2930
fetchTableData,
3031
}) => {
3132
const intl = useIntl();
32-
const statusFilterChoices = tableData.requestStatusCounts
33-
? tableData.requestStatusCounts
34-
.filter(({ lastActionStatus }) => {
35-
const displayName = REQUEST_STATUSES[lastActionStatus];
36-
return !!displayName;
37-
})
38-
.map(({ lastActionStatus, count }) => ({
39-
name: REQUEST_STATUSES[lastActionStatus],
40-
number: count,
41-
value: lastActionStatus,
42-
}))
43-
: [];
33+
const statusFilterChoices = transformLearnerRequestStateCounts(tableData.requestStatusCounts);
4434

4535
const approvedRequestsTableData = (() => ({
4636
tableActions: [
@@ -51,8 +41,7 @@ const BudgetDetailApprovedRequestTable = ({
5141

5242
return (
5343
<DataTable
54-
// Temporarily disabling sorting for release
55-
isSortable={false}
44+
isSortable
5645
manualSortBy
5746
isPaginated
5847
manualPagination
@@ -93,13 +82,12 @@ const BudgetDetailApprovedRequestTable = ({
9382
description:
9483
'Column header for the status column in the approved requests table',
9584
}),
96-
accessor: 'lastActionStatus',
85+
accessor: 'learnerRequestState',
9786
Cell: RequestStatusTableCell,
9887
Filter: CheckboxFilter,
9988
filter: 'includesValue',
10089
filterChoices: statusFilterChoices,
101-
// Temporarily disabling filters for release
102-
disableFilters: true,
90+
disableFilters: false,
10391
},
10492
{
10593
Header: intl.formatMessage({
@@ -157,7 +145,7 @@ BudgetDetailApprovedRequestTable.propTypes = {
157145
results: PropTypes.arrayOf(PropTypes.shape()),
158146
requestStatusCounts: PropTypes.arrayOf(
159147
PropTypes.shape({
160-
requestStatus: PropTypes.string.isRequired,
148+
learnerRequestState: PropTypes.string.isRequired,
161149
count: PropTypes.number.isRequired,
162150
}),
163151
),

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const BudgetDetailRequestsTabContent = ({ enterpriseId }) => {
1515
const {
1616
isLoading,
1717
bnrRequests,
18-
requestsOverview,
1918
fetchBnrRequests,
2019
refreshRequests,
2120
} = useBnrSubsidyRequests({ enterpriseId });
@@ -35,7 +34,9 @@ const BudgetDetailRequestsTabContent = ({ enterpriseId }) => {
3534
itemCount={bnrRequests.itemCount}
3635
data={bnrRequests.results}
3736
fetchData={fetchBnrRequests}
38-
requestStatusFilterChoices={requestsOverview}
37+
tableData={{
38+
requestStatusCounts: bnrRequests.learnerRequestStateCounts || [],
39+
}}
3940
onApprove={(row) => {
4041
setSelectedRequest(row);
4142
setIsApproveModalOpen(true);

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

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,45 @@
1-
import React from 'react';
21
import PropTypes from 'prop-types';
32
import { REQUEST_RECENT_ACTIONS } from './data';
43

54
const RequestRecentActionTableCell = ({ row }) => {
65
const { original } = row;
76
const {
8-
requestDate,
9-
requestStatus,
107
latestAction,
118
lastActionDate,
129
} = original;
1310

14-
const formatRequest = () => {
15-
const hasRemindedAction = latestAction && latestAction.recentAction === REQUEST_RECENT_ACTIONS.reminded;
16-
// Using reminded action if the latest action is 'reminded' else fall back to requestStatus
17-
const status = hasRemindedAction ? latestAction.recentAction : requestStatus;
18-
const formattedActionType = `${status.charAt(0).toUpperCase()}${status.slice(1)}`;
19-
20-
const formattedActionTimestamp = hasRemindedAction ? lastActionDate : requestDate;
21-
22-
return { formattedActionType, formattedActionTimestamp };
11+
const formatRecentActionDisplay = () => {
12+
// Check if latestAction exists and has recentAction property
13+
if (!latestAction || !latestAction.recentAction) {
14+
return {
15+
actionType: 'No action',
16+
timestamp: lastActionDate || 'N/A',
17+
};
18+
}
19+
20+
const actionType = latestAction.recentAction;
21+
return {
22+
actionType: `${REQUEST_RECENT_ACTIONS[actionType].charAt(0).toUpperCase()}${REQUEST_RECENT_ACTIONS[actionType].slice(1)}`,
23+
timestamp: lastActionDate,
24+
};
2325
};
2426

25-
const { formattedActionType, formattedActionTimestamp } = formatRequest();
27+
const { actionType, timestamp } = formatRecentActionDisplay();
2628

2729
return (
2830
<span>
29-
{formattedActionType}: {formattedActionTimestamp}
31+
{actionType}: {timestamp}
3032
</span>
3133
);
3234
};
3335

3436
RequestRecentActionTableCell.propTypes = {
3537
row: PropTypes.shape({
3638
original: PropTypes.shape({
37-
requestDate: PropTypes.string.isRequired,
38-
requestStatus: PropTypes.string.isRequired,
3939
latestAction: PropTypes.shape({
40-
recentAction: PropTypes.string.isRequired,
41-
}).isRequired,
42-
lastActionDate: PropTypes.string.isRequired,
40+
recentAction: PropTypes.string,
41+
}),
42+
lastActionDate: PropTypes.string,
4343
}).isRequired,
4444
}).isRequired,
4545
};

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

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,24 @@ import FailedCancellation from './request-status-chips/FailedCancellation';
77
import FailedRedemption from './request-status-chips/FailedRedemption';
88
import { capitalizeFirstLetter } from '../../utils';
99
import {
10-
REQUEST_ERROR_STATES, REQUEST_RECENT_ACTIONS, useBudgetId, useSubsidyAccessPolicy,
10+
REQUEST_ERROR_STATES, useBudgetId, useSubsidyAccessPolicy,
11+
LEARNER_CREDIT_REQUEST_STATES, LEARNER_CREDIT_REQUEST_STATE_LABELS,
1112
} from './data';
1213

1314
const RequestStatusTableCell = ({ enterpriseId, row }) => {
1415
const { original } = row;
1516
const {
16-
learnerEmail,
17-
lastActionStatus,
17+
email: learnerEmail,
18+
learnerRequestState,
1819
lastActionErrorReason,
19-
requestStatus,
2020
} = original;
2121

22-
// Use lastActionStatus if available, otherwise fall back to requestStatus
23-
// There is a lot of complexity around status field inconsistencies across different data sources,
24-
// but these inconsistencies can only be resolved by API improvements.
2522
const { subsidyAccessPolicyId } = useBudgetId();
2623
const { data: subsidyAccessPolicy } = useSubsidyAccessPolicy(
2724
subsidyAccessPolicyId,
2825
);
2926
const sharedTrackEventMetadata = {
30-
learnerEmail,
31-
requestStatus,
27+
learnerRequestState,
3228
subsidyAccessPolicy,
3329
};
3430

@@ -39,50 +35,66 @@ const RequestStatusTableCell = ({ enterpriseId, row }) => {
3935
});
4036
};
4137

42-
// TODO: Consolidate status handling in future API improvements
43-
// Currently we check both `lastActionErrorReason` and `lastActionStatus` which creates
44-
// confusion since status information comes from two different sources. The API should
45-
// be updated to return a single, unified status field to simplify this logic.
46-
if (lastActionErrorReason === REQUEST_ERROR_STATES.failed_cancellation) {
47-
return (
48-
<FailedCancellation
49-
learnerEmail={learnerEmail}
50-
trackEvent={sendGenericTrackEvent}
51-
/>
38+
const sendErrorStateTrackEvent = (eventName, eventMetadata = {}) => {
39+
const errorReasonMetadata = {
40+
erroredAction: {
41+
errorReason: lastActionErrorReason || null,
42+
},
43+
};
44+
const errorStateMetadata = {
45+
...sharedTrackEventMetadata,
46+
...errorReasonMetadata,
47+
...eventMetadata,
48+
};
49+
sendEnterpriseTrackEvent(
50+
enterpriseId,
51+
eventName,
52+
errorStateMetadata,
5253
);
54+
};
55+
56+
// Learner request state is not available, so don't display anything.
57+
if (!learnerRequestState) {
58+
return null;
5359
}
5460

55-
if (lastActionErrorReason === REQUEST_ERROR_STATES.failed_redemption) {
61+
// Handle specific learner request states with appropriate chips
62+
if (learnerRequestState === LEARNER_CREDIT_REQUEST_STATES.waiting) {
5663
return (
57-
<FailedRedemption trackEvent={sendGenericTrackEvent} />
64+
<WaitingForLearner learnerEmail={learnerEmail} trackEvent={sendGenericTrackEvent} />
5865
);
5966
}
6067

61-
if (lastActionStatus === REQUEST_RECENT_ACTIONS.reminded || requestStatus === REQUEST_RECENT_ACTIONS.approved) {
68+
if (learnerRequestState === LEARNER_CREDIT_REQUEST_STATES.failed) {
69+
// Determine which failure chip to display based on the error reason
70+
if (lastActionErrorReason === REQUEST_ERROR_STATES.failed_cancellation) {
71+
return <FailedCancellation trackEvent={sendErrorStateTrackEvent} />;
72+
}
73+
if (lastActionErrorReason === REQUEST_ERROR_STATES.failed_redemption) {
74+
return <FailedRedemption trackEvent={sendErrorStateTrackEvent} />;
75+
}
76+
// For other failure cases, display a generic failed chip
6277
return (
63-
<WaitingForLearner
64-
learnerEmail={learnerEmail}
65-
trackEvent={sendGenericTrackEvent}
66-
/>
78+
<Chip variant="dark">
79+
{LEARNER_CREDIT_REQUEST_STATE_LABELS.failed}
80+
</Chip>
6781
);
6882
}
6983

70-
return (
71-
<Chip>
72-
{`${capitalizeFirstLetter(requestStatus)}`}
73-
</Chip>
74-
);
84+
// For all other states, display the appropriate label
85+
const displayLabel = LEARNER_CREDIT_REQUEST_STATE_LABELS[learnerRequestState]
86+
|| capitalizeFirstLetter(learnerRequestState);
87+
88+
return <Chip>{displayLabel}</Chip>;
7589
};
7690

7791
RequestStatusTableCell.propTypes = {
7892
enterpriseId: PropTypes.string.isRequired,
7993
row: PropTypes.shape({
8094
original: PropTypes.shape({
81-
requestStatus: PropTypes.string,
82-
learnerEmail: PropTypes.string,
83-
lastActionStatus: PropTypes.string,
95+
email: PropTypes.string,
96+
learnerRequestState: PropTypes.string,
8497
lastActionErrorReason: PropTypes.string,
85-
recentAction: PropTypes.string,
8698
}).isRequired,
8799
}).isRequired,
88100
};

src/components/learner-credit-management/data/constants.js

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -180,19 +180,7 @@ export const REQUEST_RECENT_ACTIONS = {
180180
expired: 'expired',
181181
reversed: 'reversed',
182182
reminded: 'reminded',
183-
};
184-
185-
export const REQUEST_STATUSES = {
186-
requested: 'Requested',
187-
pending: 'Pending',
188-
approved: 'Waiting For Learner',
189-
declined: 'Declined',
190-
error: 'Errored',
191-
accepted: 'Redeemed By Learner',
192-
cancelled: 'Cancelled',
193-
expired: 'Expired',
194-
reversed: 'Refunded',
195-
reminded: 'Waiting For Learner',
183+
failed: 'failed',
196184
};
197185

198186
export const REQUEST_ERROR_REASON = {
@@ -210,3 +198,27 @@ export const REQUEST_ERROR_STATES = {
210198
failed_redemption: 'failed_redemption',
211199
failed_reversal: 'failed_reversal',
212200
};
201+
202+
// Learner credit request state constants based on the API payload
203+
export const LEARNER_CREDIT_REQUEST_STATES = {
204+
requested: 'requested',
205+
waiting: 'waiting',
206+
failed: 'failed',
207+
notifying: 'notifying',
208+
accepted: 'accepted',
209+
cancelled: 'cancelled',
210+
expired: 'expired',
211+
reversed: 'reversed',
212+
};
213+
214+
// Human-readable labels for learner credit request states
215+
export const LEARNER_CREDIT_REQUEST_STATE_LABELS = {
216+
[LEARNER_CREDIT_REQUEST_STATES.requested]: 'Requested',
217+
[LEARNER_CREDIT_REQUEST_STATES.waiting]: 'Waiting For Learner',
218+
[LEARNER_CREDIT_REQUEST_STATES.failed]: 'Failed',
219+
[LEARNER_CREDIT_REQUEST_STATES.notifying]: 'Notifying',
220+
[LEARNER_CREDIT_REQUEST_STATES.accepted]: 'Redeemed By Learner',
221+
[LEARNER_CREDIT_REQUEST_STATES.cancelled]: 'Cancelled',
222+
[LEARNER_CREDIT_REQUEST_STATES.expired]: 'Expired',
223+
[LEARNER_CREDIT_REQUEST_STATES.reversed]: 'Refunded',
224+
};

0 commit comments

Comments
 (0)