diff --git a/src/apps/onboarding/src/redux/actions/member.ts b/src/apps/onboarding/src/redux/actions/member.ts index 836759f96..7b1f23b50 100644 --- a/src/apps/onboarding/src/redux/actions/member.ts +++ b/src/apps/onboarding/src/redux/actions/member.ts @@ -212,6 +212,7 @@ const createWorksPayloadData: any = (works: WorkInfo[]) => { const data: any = works.map(work => { const { companyName, + company, position, industry, otherIndustry, @@ -222,10 +223,12 @@ const createWorksPayloadData: any = (works: WorkInfo[]) => { city, associatedSkills, }: any = work + const normalizedCompanyName: string = _.trim(companyName || company || '') return { associatedSkills: Array.isArray(associatedSkills) ? associatedSkills : undefined, cityName: city, - companyName: companyName || '', + company: normalizedCompanyName || undefined, + companyName: normalizedCompanyName, description: description || undefined, // eslint-disable-next-line unicorn/no-null endDate: endDate ? endDate.toISOString() : null, 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..b679bc89f 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, InputText, LoadingCircles } from '~/libs/ui' import { UserProfile } from '~/libs/core' import { downloadBlob } from '~/libs/shared' @@ -13,6 +13,7 @@ import { Winning, WinningDetail } from '../../../lib/models/WinningDetail' import { FilterBar, formatIOSDateString, PaymentView } from '../../../lib' import { ConfirmFlowData } from '../../../lib/models/ConfirmFlowData' import { PaginationInfo } from '../../../lib/models/PaginationInfo' +import { Filter } from '../../../lib/components/filter-bar/FilterBar' import PaymentEditForm from '../../../lib/components/payment-edit/PaymentEdit' import PaymentsTable from '../../../lib/components/payments-table/PaymentTable' @@ -69,6 +70,7 @@ const ListView: FC = (props: ListViewProps) => { const [isConfirmFormValid, setIsConfirmFormValid] = React.useState(false) const [winnings, setWinnings] = React.useState>([]) const [selectedPayments, setSelectedPayments] = React.useState<{ [paymentId: string]: Winning }>({}) + const selectedPaymentsCount = Object.keys(selectedPayments).length const [isLoading, setIsLoading] = React.useState(false) const [filters, setFilters] = React.useState>({}) const [pagination, setPagination] = React.useState({ @@ -290,6 +292,50 @@ const ListView: FC = (props: ListViewProps) => { || props.profile.roles.includes('Payment Editor') ) + const isEngagementPaymentApprover = props.profile.roles.includes('Engagement Payment Approver') + const [bulkOpen, setBulkOpen] = React.useState(false) + const [bulkAuditNote, setBulkAuditNote] = React.useState('') + + const onBulkApprove = useCallback(async (auditNote: string) => { + const ids = Object.keys(selectedPayments) + if (ids.length === 0) return + + toast.success('Starting bulk approve', { position: toast.POSITION.BOTTOM_RIGHT }) + + let successfullyUpdated = 0 + for (const id of ids) { + const updates: any = { + auditNote, + paymentStatus: 'OWED', + winningsId: id, + } + + 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 + await editPayment(updates) + successfullyUpdated += 1 + } catch (err:any) { + const paymentName = selectedPayments[id]?.handle || id + if (err?.message) { + toast.error(`Failed to update payment ${paymentName} (${id}): ${err.message}`, { position: toast.POSITION.BOTTOM_RIGHT }) + } else { + toast.error(`Failed to update payment ${paymentName} (${id})`, { position: toast.POSITION.BOTTOM_RIGHT }) + } + } + } + + if (successfullyUpdated === ids.length) { + toast.success(`Successfully updated ${successfullyUpdated} winnings`, { position: toast.POSITION.BOTTOM_RIGHT }) + } + + setBulkAuditNote('') + setBulkOpen(false) + setSelectedPayments({}) + await fetchWinnings() + }, [selectedPayments, fetchWinnings]) + return ( <>
@@ -300,6 +346,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( @@ -354,33 +402,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', @@ -449,11 +499,13 @@ const ListView: FC = (props: ListViewProps) => { {isLoading && } {!isLoading && winnings.length > 0 && ( setSelectedPayments(selected)} onNextPageClick={async function onNextPageClicked() { if (pagination.currentPage === pagination.totalPages) { return @@ -513,6 +565,44 @@ const ListView: FC = (props: ListViewProps) => {
+ {bulkOpen && ( + 1 ? 'Bulk ' : ''}Approve Payment${selectedPaymentsCount > 1 ? 's' : ''}`} + action='Approve' + onClose={function onClose() { + setBulkAuditNote('') + setBulkOpen(false) + }} + onConfirm={function onConfirm() { + onBulkApprove(bulkAuditNote) + }} + canSave={bulkAuditNote.trim().length > 0} + open={bulkOpen} + > +
+

+ You are about to approve + {' '} + {selectedPaymentsCount} + {' '} + payment + {selectedPaymentsCount > 1 ? 's' : ''} + . +

+
+ ) => setBulkAuditNote(e.target.value)} + /> +
+
+ )} {confirmFlow && ( void; onResetFilters?: () => void; onExport?: () => void; + selectedCount?: number; + onBulkClick?: () => void; } const FilterBar: React.FC = (props: FilterBarProps) => { @@ -120,6 +122,17 @@ const FilterBar: React.FC = (props: FilterBarProps) => { size='lg' /> )} + {!!props.selectedCount && props.selectedCount > 0 && ( + <> +