diff --git a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.spec.tsx b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.spec.tsx index de61dffad..127bd1134 100644 --- a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.spec.tsx +++ b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.spec.tsx @@ -221,10 +221,10 @@ describe('PaymentsListView', () => { jest.clearAllMocks() }) - it('defaults the engagement approver view to the On Hold (Admin) status filter', async () => { + it('defaults the approver view to the On Hold (Admin) status filter and both allowed categories', async () => { render( , ) @@ -232,14 +232,13 @@ describe('PaymentsListView', () => { expect(mockedGetPayments) .toHaveBeenLastCalledWith(10, 0, { - category: ['ENGAGEMENT_PAYMENT'], + categories: ['TASK_PAYMENT', 'ENGAGEMENT_PAYMENT'], status: ['ON_HOLD_ADMIN'], }) expect(mockFilterBar) .toHaveBeenCalled() expect(mockFilterBar.mock.calls.at(-1)?.[0].selectedValueOverrides) .toEqual(expect.objectContaining({ - category: 'ENGAGEMENT_PAYMENT', status: 'ON_HOLD_ADMIN', })) }) @@ -266,7 +265,7 @@ describe('PaymentsListView', () => { it('applies the default approver status after switching from admin view', async () => { render( , ) @@ -275,19 +274,18 @@ describe('PaymentsListView', () => { expect(mockedGetPayments) .toHaveBeenLastCalledWith(10, 0, {}) - fireEvent.click(screen.getByRole('button', { name: 'Engagement Approver View' })) + fireEvent.click(screen.getByRole('button', { name: 'Approver View' })) await screen.findByText('No payments match your filters.') expect(mockedGetPayments) .toHaveBeenLastCalledWith(10, 0, { - category: ['ENGAGEMENT_PAYMENT'], + categories: ['TASK_PAYMENT', 'ENGAGEMENT_PAYMENT'], status: ['ON_HOLD_ADMIN'], }) expect(mockFilterBar.mock.calls.at(-1)?.[0].selectedValueOverrides) .toEqual(expect.objectContaining({ - category: 'ENGAGEMENT_PAYMENT', status: 'ON_HOLD_ADMIN', })) }) @@ -314,7 +312,7 @@ describe('PaymentsListView', () => { it('lets an explicit status filter override the default approver status', async () => { render( , ) @@ -327,14 +325,13 @@ describe('PaymentsListView', () => { await waitFor(() => { expect(mockedGetPayments) .toHaveBeenLastCalledWith(10, 0, { - category: ['ENGAGEMENT_PAYMENT'], + categories: ['TASK_PAYMENT', 'ENGAGEMENT_PAYMENT'], status: ['PAID'], }) }) expect(mockFilterBar.mock.calls.at(-1)?.[0].selectedValueOverrides) .toEqual(expect.objectContaining({ - category: 'ENGAGEMENT_PAYMENT', status: 'PAID', })) }) @@ -367,7 +364,7 @@ describe('PaymentsListView', () => { })) }) - it('lets engagement approvers reject selected on hold admin payments with an audit note', async () => { + it('lets approvers reject selected on hold admin payments with an audit note', async () => { mockedGetPayments.mockResolvedValue(paymentsResponse as any) mockedGetMemberHandle.mockResolvedValue(new Map([ [111, 'sathya22in'], @@ -376,7 +373,7 @@ describe('PaymentsListView', () => { render( , ) diff --git a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.tsx b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.tsx index af3992aef..7a7430579 100644 --- a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.tsx +++ b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsListView.tsx @@ -19,11 +19,13 @@ import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' import styles from './Payments.module.scss' -type PaymentRoleView = 'admin' | 'engagementApprover' | 'wiproTaasAdmin' +type PaymentRoleView = 'admin' | 'paymentApprover' | 'wiproTaasAdmin' type SelectedPaymentAction = 'approve' | 'reject' +const taskPaymentCategory = 'TASK_PAYMENT' const engagementPaymentCategory = 'ENGAGEMENT_PAYMENT' const restrictedRoleDefaultStatus = 'ON_HOLD_ADMIN' +const approverAllowedCategories = [taskPaymentCategory, engagementPaymentCategory] const taasPaymentCategory = 'TAAS_PAYMENT' const topgearPaymentCategory = 'TOPGEAR_PAYMENT' const defaultPageSize = 10 @@ -172,8 +174,8 @@ const PaymentsListView: FC = (props: PaymentsListViewProp const isWiproTaasAdmin = hasRole('Wipro TaaS Admin') const hasPaymentAdminRole = hasRole('Payment Admin') const isPaymentAdmin = hasPaymentAdminRole || isWiproTaasAdmin - const isEngagementPaymentApprover = hasRole('Engagement Payment Approver') - const canToggleRoleView = isPaymentAdmin && (isEngagementPaymentApprover) + const isPaymentApprover = hasRole('Payment Approver') + const canToggleRoleView = isPaymentAdmin && isPaymentApprover const [confirmFlow, setConfirmFlow] = React.useState(undefined) const [isConfirmFormValid, setIsConfirmFormValid] = React.useState(false) const [winnings, setWinnings] = React.useState>([]) @@ -181,21 +183,17 @@ const PaymentsListView: FC = (props: PaymentsListViewProp const selectedPaymentsCount = Object.keys(selectedPayments).length const [isLoading, setIsLoading] = React.useState(false) const [paymentRoleView, setPaymentRoleView] = React.useState( - isPaymentAdmin ? 'admin' : 'engagementApprover', + isPaymentAdmin ? 'admin' : 'paymentApprover', ) - const isEngagementApproverView = isEngagementPaymentApprover && ( - !isPaymentAdmin || paymentRoleView === 'engagementApprover' + const isApproverView = isPaymentApprover && ( + !isPaymentAdmin || paymentRoleView === 'paymentApprover' ) - const restrictedCategory = isEngagementApproverView - ? engagementPaymentCategory - : (isWiproTaasAdmin && !hasPaymentAdminRole ? taasPaymentCategory : undefined) - const restrictedDefaultStatus = isEngagementApproverView ? restrictedRoleDefaultStatus : undefined - const isRestrictedApproverView = isEngagementApproverView + const restrictedCategory = isWiproTaasAdmin && !hasPaymentAdminRole ? taasPaymentCategory : undefined + const restrictedDefaultStatus = isApproverView ? restrictedRoleDefaultStatus : undefined + const isRestrictedApproverView = isApproverView const [filters, setFilters] = React.useState>({}) - // Remove the old hasSelectedStatusFilter declaration as we handle it directly below - const appliedFilters = React.useMemo>(() => { // Strip 'all' sentinel values — never forward them to the API const activeFilters = Object.fromEntries( @@ -203,50 +201,77 @@ const PaymentsListView: FC = (props: PaymentsListViewProp .filter(([, v]) => v.length > 0 && v[0] !== 'all'), ) - if (!restrictedCategory) { - return activeFilters + if (restrictedCategory) { + // WiproTaasAdmin scoped to a single category + let statusFilter: Record = {} + if (filters.status && filters.status[0] !== 'all') { + statusFilter = { status: activeFilters.status } + } + + return { + ...activeFilters, + category: [restrictedCategory], + ...statusFilter, + } } - // Determine the correct status filter to append - let statusFilter: Record = {} + if (isApproverView) { + // Payment Approver: restrict to allowed categories, default status ON_HOLD_ADMIN + let statusFilter: Record = {} + if (filters.status && filters.status[0] !== 'all') { + statusFilter = { status: activeFilters.status } + } else if (!filters.status && restrictedDefaultStatus) { + statusFilter = { status: [restrictedDefaultStatus] } + } - if (filters.status && filters.status[0] !== 'all') { - // 1. User explicitly chose a specific status (e.g. 'OWED') - statusFilter = { status: activeFilters.status } - } else if (!filters.status && restrictedDefaultStatus) { - // 2. Initial load (filters.status is undefined), apply restricted default - statusFilter = { status: [restrictedDefaultStatus] } - } - // 3. If user explicitly selected 'all' (filters.status[0] === 'all'), - // statusFilter remains empty, allowing the API to return all statuses. + let categoryFilter: Record = {} + if ( + activeFilters.category + && approverAllowedCategories.includes(activeFilters.category[0]) + ) { + categoryFilter = { category: activeFilters.category } + } else if (!filters.category || filters.category[0] === 'all') { + categoryFilter = { categories: ([] as string[]).concat(approverAllowedCategories) } + } - return { - ...activeFilters, - category: [restrictedCategory], - ...statusFilter, + const rest = { ...activeFilters } + delete rest.category + + return { + ...rest, + ...categoryFilter, + ...statusFilter, + } } - }, [filters, restrictedCategory, restrictedDefaultStatus]) + + return activeFilters + }, [filters, restrictedCategory, restrictedDefaultStatus, isApproverView]) const hasActiveFilters = React.useMemo( () => Object.entries(appliedFilters) - .some(([key, value]) => key !== 'category' && value.length > 0), + .some(([key, value]) => key !== 'category' && key !== 'categories' && value.length > 0), [appliedFilters], ) const selectedValueOverrides = React.useMemo>(() => { - if (!restrictedCategory) { - return {} as Record + if (restrictedCategory) { + const statusOverride = filters.status?.[0] !== 'all' ? filters.status?.[0] : undefined + + return { + category: restrictedCategory, + ...(statusOverride ? { status: statusOverride } : {}), + } } - // Reflect the user's explicit status choice in the dropdown display. - // Do not inject restrictedDefaultStatus here — it applies to the API query - // via appliedFilters but must not override the dropdown's "All" default. - const statusOverride = filters.status?.[0] !== 'all' ? filters.status?.[0] : undefined + if (isApproverView) { + const statusOverride = filters.status?.[0] !== 'all' ? filters.status?.[0] : undefined - return { - category: restrictedCategory, - ...(statusOverride ? { status: statusOverride } : {}), + return { + ...(statusOverride ? { status: statusOverride } : {}), + } } - }, [filters.status, restrictedCategory]) + + return {} as Record + }, [filters.status, restrictedCategory, isApproverView]) const defaultDropdownValues = React.useMemo>(() => { const defaults: Record = {} @@ -561,14 +586,14 @@ const PaymentsListView: FC = (props: PaymentsListViewProp > Admin View - {isEngagementPaymentApprover && ( + {isPaymentApprover && ( )} @@ -638,7 +663,27 @@ const PaymentsListView: FC = (props: PaymentsListViewProp ], type: 'dropdown', }, - ...(isRestrictedApproverView || (isWiproTaasAdmin && !hasPaymentAdminRole) ? [] : [ + ...(isWiproTaasAdmin && !hasPaymentAdminRole ? [] : isApproverView ? [ + { + key: 'category', + label: 'Payment Type', + options: [ + { + label: 'All', + value: 'all', + }, + { + label: 'Task Payments', + value: taskPaymentCategory, + }, + { + label: 'Engagement Payments', + value: engagementPaymentCategory, + }, + ], + type: 'dropdown', + }, + ] as Filter[] : [ { key: 'category', label: 'Type', diff --git a/src/apps/wallet-admin/src/lib/services/wallet.ts b/src/apps/wallet-admin/src/lib/services/wallet.ts index 7d8ff6653..78445ebae 100644 --- a/src/apps/wallet-admin/src/lib/services/wallet.ts +++ b/src/apps/wallet-admin/src/lib/services/wallet.ts @@ -357,10 +357,12 @@ export async function exportSearchResults( ): Promise { const url = `${baseUrl}/admin/winnings/export` - const filteredFilters: Record = {} + const filteredFilters: Record = {} for (const key in filters) { - if (filters[key].length > 0 && key !== 'pageSize') { + if (['categories'].includes(key)) { + filteredFilters[key] = filters[key] + } else if (filters[key].length > 0 && key !== 'pageSize') { filteredFilters[key] = filters[key][0] } } @@ -397,10 +399,12 @@ export async function fetchWinnings( winnings: WinningDetail[], pagination: PaginationInfo }> { - const filteredFilters: Record = {} + const filteredFilters: Record = {} for (const key in filters) { - if (filters[key].length > 0 && key !== 'pageSize') { + if (['categories'].includes(key)) { + filteredFilters[key] = filters[key] + } else if (filters[key].length > 0 && key !== 'pageSize') { filteredFilters[key] = filters[key][0] } }