diff --git a/src/components/learner-credit-management/AssignmentRowActionTableCell.jsx b/src/components/learner-credit-management/AssignmentRowActionTableCell.jsx
new file mode 100644
index 0000000000..1d771c27fa
--- /dev/null
+++ b/src/components/learner-credit-management/AssignmentRowActionTableCell.jsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {
+ Icon,
+ IconButton,
+ OverlayTrigger,
+ Stack,
+ Tooltip,
+} from '@edx/paragon';
+import { Mail, DoNotDisturbOn } from '@edx/paragon/icons';
+
+const AssignmentRowActionTableCell = ({ row }) => {
+ const cancelButtonMarginLeft = row.original.state === 'allocated' ? 'ml-2.5' : 'ml-auto';
+ return (
+
+ {row.original.state === 'allocated' && (
+ <>
+ Remind learner}
+ >
+ console.log(`Reminding ${row.original.uuid}`)}
+ data-testid={`remind-assignment-${row.original.uuid}`}
+ />
+
+
+ >
+ )}
+ Cancel assignment}
+ >
+ console.log(`Canceling ${row.original.uuid}`)}
+ data-testid={`cancel-assignment-${row.original.uuid}`}
+ />
+
+
+ );
+};
+
+AssignmentRowActionTableCell.propTypes = {
+ row: PropTypes.shape({
+ original: PropTypes.shape({
+ uuid: PropTypes.string.isRequired,
+ state: PropTypes.string.isRequired,
+ }).isRequired,
+ }).isRequired,
+};
+
+export default AssignmentRowActionTableCell;
diff --git a/src/components/learner-credit-management/AssignmentTableCancel.jsx b/src/components/learner-credit-management/AssignmentTableCancel.jsx
new file mode 100644
index 0000000000..37c3e8ef6d
--- /dev/null
+++ b/src/components/learner-credit-management/AssignmentTableCancel.jsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Button } from '@edx/paragon';
+import { DoNotDisturbOn } from '@edx/paragon/icons';
+
+const AssignmentTableCancelAction = ({ selectedFlatRows, ...rest }) => (
+ // eslint-disable-next-line no-console
+
+);
+
+AssignmentTableCancelAction.propTypes = {
+ selectedFlatRows: PropTypes.arrayOf(PropTypes.shape()),
+};
+
+AssignmentTableCancelAction.defaultProps = {
+ selectedFlatRows: [],
+};
+
+export default AssignmentTableCancelAction;
diff --git a/src/components/learner-credit-management/AssignmentTableRemind.jsx b/src/components/learner-credit-management/AssignmentTableRemind.jsx
new file mode 100644
index 0000000000..bee8cc9814
--- /dev/null
+++ b/src/components/learner-credit-management/AssignmentTableRemind.jsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Button } from '@edx/paragon';
+import { Mail } from '@edx/paragon/icons';
+
+const AssignmentTableRemindAction = ({ selectedFlatRows, ...rest }) => {
+ const hideRemindAction = selectedFlatRows.some(
+ row => row.original.state !== 'allocated',
+ );
+ if (hideRemindAction) {
+ return null;
+ }
+ return (
+ // eslint-disable-next-line no-console
+
+ );
+};
+
+AssignmentTableRemindAction.propTypes = {
+ selectedFlatRows: PropTypes.arrayOf(PropTypes.shape()),
+};
+
+AssignmentTableRemindAction.defaultProps = {
+ selectedFlatRows: [],
+};
+
+export default AssignmentTableRemindAction;
diff --git a/src/components/learner-credit-management/BudgetAssignmentsTable.jsx b/src/components/learner-credit-management/BudgetAssignmentsTable.jsx
index 301dfc99f8..cfc6603800 100644
--- a/src/components/learner-credit-management/BudgetAssignmentsTable.jsx
+++ b/src/components/learner-credit-management/BudgetAssignmentsTable.jsx
@@ -1,11 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { DataTable } from '@edx/paragon';
-
import TableTextFilter from './TableTextFilter';
import CustomDataTableEmptyState from './CustomDataTableEmptyState';
import AssignmentDetailsTableCell from './AssignmentDetailsTableCell';
import AssignmentStatusTableCell from './AssignmentStatusTableCell';
+import AssignmentRowActionTableCell from './AssignmentRowActionTableCell';
+import AssignmentTableRemindAction from './AssignmentTableRemind';
+import AssignmentTableCancelAction from './AssignmentTableCancel';
import { DEFAULT_PAGE, PAGE_SIZE, formatPrice } from './data';
import AssignmentRecentActionTableCell from './AssignmentRecentActionTableCell';
import AssignmentsTableRefreshAction from './AssignmentsTableRefreshAction';
@@ -19,6 +21,7 @@ const BudgetAssignmentsTable = ({
}) => (
,
]}
@@ -68,6 +78,10 @@ const BudgetAssignmentsTable = ({
itemCount={tableData?.count || 0}
pageCount={tableData?.numPages || 1}
EmptyTableComponent={CustomDataTableEmptyState}
+ bulkActions={[
+ ,
+ ,
+ ]}
/>
);
diff --git a/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx b/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx
index 3c5574ec22..462b32d46c 100644
--- a/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx
+++ b/src/components/learner-credit-management/tests/BudgetDetailPage.test.jsx
@@ -723,4 +723,117 @@ describe('', () => {
expect(screen.getByText('loading budget activity overview')).toBeInTheDocument();
});
+
+ it('displays remind row and bulk actions when allocated', async () => {
+ useParams.mockReturnValue({
+ budgetId: mockSubsidyAccessPolicyUUID,
+ activeTabKey: 'activity',
+ });
+ useOfferRedemptions.mockReturnValue({
+ isLoading: false,
+ offerRedemptions: mockEmptyOfferRedemptions,
+ fetchOfferRedemptions: jest.fn(),
+ });
+ useSubsidyAccessPolicy.mockReturnValue({
+ isInitialLoading: false,
+ data: mockAssignableSubsidyAccessPolicy,
+ });
+ useBudgetDetailActivityOverview.mockReturnValue({
+ isLoading: false,
+ data: {
+ contentAssignments: { count: 1 },
+ spentTransactions: { count: 0 },
+ },
+ });
+ useBudgetContentAssignments.mockReturnValue({
+ isLoading: false,
+ contentAssignments: {
+ count: 1,
+ results: [
+ {
+ uuid: 'test-uuid',
+ contentKey: mockCourseKey,
+ contentQuantity: -19900,
+ learnerState: 'active',
+ recentAction: { actionType: 'assigned', timestamp: '2023-10-27' },
+ actions: [],
+ state: 'allocated',
+ },
+ ],
+ numPages: 1,
+ currentPage: 1,
+ },
+ fetchContentAssignments: jest.fn(),
+ });
+ renderWithRouter();
+ const cancelRowAction = screen.getByTestId('cancel-assignment-test-uuid');
+ const remindRowAction = screen.getByTestId('remind-assignment-test-uuid');
+ expect(cancelRowAction).toBeInTheDocument();
+ expect(remindRowAction).toBeInTheDocument();
+
+ const checkBox = screen.getByTestId('datatable-select-column-checkbox-cell');
+ expect(checkBox).toBeInTheDocument();
+ userEvent.click(checkBox);
+ await waitFor(() => {
+ expect(screen.getByText('Remind (1)')).toBeInTheDocument();
+ });
+ await waitFor(() => {
+ expect(screen.getByText('Cancel (1)')).toBeInTheDocument();
+ });
+ });
+
+ it('hides remind row and bulk actions when allocated', () => {
+ useOfferRedemptions.mockReturnValue({
+ isLoading: false,
+ offerRedemptions: mockEmptyOfferRedemptions,
+ fetchOfferRedemptions: jest.fn(),
+ });
+ useBudgetDetailActivityOverview.mockReturnValue({
+ isLoading: false,
+ data: {
+ contentAssignments: { count: 1 },
+ spentTransactions: { count: 0 },
+ },
+ });
+ useParams.mockReturnValue({
+ budgetId: mockSubsidyAccessPolicyUUID,
+ activeTabKey: 'activity',
+ });
+ useSubsidyAccessPolicy.mockReturnValue({
+ isInitialLoading: false,
+ data: mockAssignableSubsidyAccessPolicy,
+ });
+ useBudgetDetailActivityOverview.mockReturnValue({
+ isLoading: false,
+ data: {
+ contentAssignments: { count: 1 },
+ spentTransactions: { count: 0 },
+ },
+ });
+ useBudgetContentAssignments.mockReturnValue({
+ isLoading: false,
+ contentAssignments: {
+ count: 1,
+ results: [
+ {
+ uuid: 'test-uuid',
+ contentKey: mockCourseKey,
+ contentQuantity: -19900,
+ learnerState: 'accepted',
+ recentAction: { actionType: 'assigned', timestamp: '2023-10-27' },
+ actions: [],
+ state: 'accepted',
+ },
+ ],
+ numPages: 1,
+ currentPage: 1,
+ },
+ fetchContentAssignments: jest.fn(),
+ });
+ renderWithRouter();
+ expect(screen.queryByTestId('remind-assignment-test-uuid')).not.toBeInTheDocument();
+ const checkBox = screen.getByTestId('datatable-select-column-checkbox-cell');
+ userEvent.click(checkBox);
+ expect(screen.queryByText('Remind (1)')).not.toBeInTheDocument();
+ });
});