From 39ed43048b69cd895d67a17e9ce75d5d4c1fc030 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Fri, 6 Feb 2026 15:00:40 +0200 Subject: [PATCH 01/11] PM-3698 - Allow Engagement payment approver to edit payments, hide "category" filter --- .../src/home/tabs/payments/PaymentsTab.tsx | 60 ++++++++++--------- .../lib/components/filter-bar/FilterBar.tsx | 2 +- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx index 92f6b14f0..732dbb658 100644 --- a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx +++ b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx @@ -17,6 +17,7 @@ import PaymentEditForm from '../../../lib/components/payment-edit/PaymentEdit' import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' import styles from './Payments.module.scss' +import { Filter } from '../../../lib/components/filter-bar/FilterBar' interface ListViewProps { // eslint-disable-next-line react/no-unused-prop-types @@ -287,9 +288,12 @@ const ListView: FC = (props: ListViewProps) => { const isEditingAllowed = (): boolean => ( props.profile.roles.includes('Payment Admin') || props.profile.roles.includes('Payment BA Admin') + || props.profile.roles.includes('Engagement Payment Approver') || props.profile.roles.includes('Payment Editor') ) + const isEngagementPaymentApprover = props.profile.roles.includes('Engagement Payment Approver'); + return ( <>
@@ -354,33 +358,35 @@ const ListView: FC = (props: ListViewProps) => { ], type: 'dropdown', }, - { - key: 'type', - label: 'Type', - options: [ - { - label: 'Task Payment', - value: 'TASK_PAYMENT', - }, - { - label: 'Contest Payment', - value: 'CONTEST_PAYMENT', - }, - { - label: 'Copilot Payment', - value: 'COPILOT_PAYMENT', - }, - { - label: 'Review Board Payment', - value: 'REVIEW_BOARD_PAYMENT', - }, - { - label: 'Engagement Payment', - value: 'ENGAGEMENT_PAYMENT', - }, - ], - type: 'dropdown', - }, + ...(isEngagementPaymentApprover ? [] : [ + { + key: 'category', + label: 'Type', + options: [ + { + label: 'Task Payment', + value: 'TASK_PAYMENT', + }, + { + label: 'Contest Payment', + value: 'CONTEST_PAYMENT', + }, + { + label: 'Copilot Payment', + value: 'COPILOT_PAYMENT', + }, + { + label: 'Review Board Payment', + value: 'REVIEW_BOARD_PAYMENT', + }, + { + label: 'Engagement Payment', + value: 'ENGAGEMENT_PAYMENT', + }, + ], + type: 'dropdown', + }, + ] as Filter[]), { key: 'date', label: 'Date', diff --git a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx index da5b4b023..78c3be35b 100644 --- a/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx +++ b/src/apps/wallet-admin/src/lib/components/filter-bar/FilterBar.tsx @@ -14,7 +14,7 @@ type FilterOptions = { value: string; }; -type Filter = { +export type Filter = { key: string; label: string; type: 'input' | 'dropdown' | 'member_autocomplete'; From d14ba856d902dc84e32adafec70a6a16ac500e1f Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Fri, 6 Feb 2026 16:02:29 +0200 Subject: [PATCH 02/11] PM-3698 - allow bulk update for EngagementPaymentApprover role --- .../src/home/tabs/payments/PaymentsTab.tsx | 72 ++++++++++++++++++- .../lib/components/filter-bar/FilterBar.tsx | 14 ++++ .../payments-table/PaymentTable.tsx | 62 ++++++++++++++++ 3 files changed, 147 insertions(+), 1 deletion(-) diff --git a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx index 732dbb658..4532ab647 100644 --- a/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx +++ b/src/apps/wallet-admin/src/home/tabs/payments/PaymentsTab.tsx @@ -4,7 +4,7 @@ import { toast } from 'react-toastify' import { AxiosError } from 'axios' import React, { FC, useCallback, useEffect } from 'react' -import { Collapsible, ConfirmModal, LoadingCircles } from '~/libs/ui' +import { Collapsible, ConfirmModal, LoadingCircles, InputText } from '~/libs/ui' import { UserProfile } from '~/libs/core' import { downloadBlob } from '~/libs/shared' @@ -293,6 +293,43 @@ const ListView: FC = (props: ListViewProps) => { ) const isEngagementPaymentApprover = props.profile.roles.includes('Engagement Payment Approver'); + const [bulkOpen, setBulkOpen] = React.useState(false) + const [bulkAuditNote, setBulkAuditNote] = React.useState('') + + const onBulkApprove = async (auditNote: string) => { + const ids = Object.keys(selectedPayments) + if (ids.length === 0) return + + toast.success('Starting bulk approve', { position: toast.POSITION.BOTTOM_RIGHT }) + + for (const id of ids) { + const updates: any = { + winningsId: id, + paymentStatus: 'OWED', + // attach audit note as part of the payload similar to single update + auditNote, + } + + try { + // awaiting sequentially to preserve order and server load control + // errors for individual items are caught and reported + // eslint-disable-next-line no-await-in-loop + const msg = await editPayment(updates) + toast.success(msg, { position: toast.POSITION.BOTTOM_RIGHT }) + } catch (err:any) { + if (err?.message) { + toast.error(`Failed to update payment ${id}: ${err.message}`, { position: toast.POSITION.BOTTOM_RIGHT }) + } else { + toast.error(`Failed to update payment ${id}`, { position: toast.POSITION.BOTTOM_RIGHT }) + } + } + } + + setBulkAuditNote('') + setBulkOpen(false) + setSelectedPayments({}) + await fetchWinnings() + } return ( <> @@ -304,6 +341,8 @@ const ListView: FC = (props: ListViewProps) => { Payment Listing}> setBulkOpen(true)} onExport={async () => { toast.success('Downloading payments report. This may take a few moments.', { position: toast.POSITION.BOTTOM_RIGHT }) downloadBlob( @@ -455,11 +494,13 @@ const ListView: FC = (props: ListViewProps) => { {isLoading && } {!isLoading && winnings.length > 0 && ( setSelectedPayments(selected)} onNextPageClick={async function onNextPageClicked() { if (pagination.currentPage === pagination.totalPages) { return @@ -519,6 +560,35 @@ const ListView: FC = (props: ListViewProps) => {
+ {bulkOpen && ( + 0} + open={bulkOpen} + > +
+

You are about to approve {Object.keys(selectedPayments).length} payments.

+ ) => setBulkAuditNote(e.target.value)} + /> +
+
+ )} {confirmFlow && ( void; onResetFilters?: () => void; onExport?: () => void; + selectedCount?: number; + onBulkClick?: () => void; } const FilterBar: React.FC = (props: FilterBarProps) => { const [selectedValue, setSelectedValue] = React.useState>(new Map()) const selectedMembers = useRef([]) + const renderDropdown = (index: number, filter: Filter): JSX.Element => ( = (props: FilterBarProps) => { size='lg' /> )} + {props.selectedCount && props.selectedCount > 0 && ( + <> +