From 52dea342e393e7921d461046a328e097a5c1c71f Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Thu, 30 Apr 2026 13:50:34 +0530 Subject: [PATCH 1/5] PM-4961 View Billing Accountdetails --- src/apps/reports/src/config/routes.config.ts | 1 + .../src/lib/components/NavTabs/NavTabs.tsx | 10 +- src/apps/reports/src/lib/services/index.ts | 5 + .../src/lib/services/reports.service.ts | 50 ++ .../src/pages/reports/BillingAccountsPage.tsx | 3 + .../src/pages/reports/ReportsPage.module.scss | 187 ++++++- .../reports/src/pages/reports/ReportsPage.tsx | 499 +++++++++++++++--- src/apps/reports/src/reports-app.routes.tsx | 9 + 8 files changed, 683 insertions(+), 81 deletions(-) create mode 100644 src/apps/reports/src/pages/reports/BillingAccountsPage.tsx diff --git a/src/apps/reports/src/config/routes.config.ts b/src/apps/reports/src/config/routes.config.ts index fb7d07091..914a95cd9 100644 --- a/src/apps/reports/src/config/routes.config.ts +++ b/src/apps/reports/src/config/routes.config.ts @@ -10,3 +10,4 @@ export const rootRoute: string export const reportsPageRouteId = 'reports' export const bulkMemberLookupRouteId = 'bulk-member-lookup' +export const billingAccountsPageRouteId = 'billing-accounts' diff --git a/src/apps/reports/src/lib/components/NavTabs/NavTabs.tsx b/src/apps/reports/src/lib/components/NavTabs/NavTabs.tsx index cb0c2e34a..2a73aa4fe 100644 --- a/src/apps/reports/src/lib/components/NavTabs/NavTabs.tsx +++ b/src/apps/reports/src/lib/components/NavTabs/NavTabs.tsx @@ -15,7 +15,11 @@ import classNames from 'classnames' import { useClickOutside } from '~/libs/shared/lib/hooks' import { TabsNavItem } from '~/libs/ui' -import { bulkMemberLookupRouteId, reportsPageRouteId } from '../../../config/routes.config' +import { + billingAccountsPageRouteId, + bulkMemberLookupRouteId, + reportsPageRouteId, +} from '../../../config/routes.config' import styles from './NavTabs.module.scss' @@ -34,6 +38,10 @@ const NavTabs: FC = () => { id: bulkMemberLookupRouteId, title: 'Bulk Member Lookup', }, + { + id: billingAccountsPageRouteId, + title: 'Billing Accounts', + }, ], []) const activeTabPathName: string = useMemo(() => { diff --git a/src/apps/reports/src/lib/services/index.ts b/src/apps/reports/src/lib/services/index.ts index b2c6dc18b..567bbcf6a 100644 --- a/src/apps/reports/src/lib/services/index.ts +++ b/src/apps/reports/src/lib/services/index.ts @@ -2,6 +2,7 @@ export { downloadBlobFile, downloadReportAsCsv, downloadReportAsJson, + fetchReportJson, fetchReportsIndex, postReportAsCsv, postReportAsJson, @@ -10,8 +11,12 @@ export { } from './reports.service' export type { + BillingAccountDetail, + BillingAccountProfileResponse, + BillingAccountsViewData, ReportDefinition, ReportGroup, ReportParameter, ReportsIndexResponse, + SfdcBillingAccountPaymentRow, } from './reports.service' diff --git a/src/apps/reports/src/lib/services/reports.service.ts b/src/apps/reports/src/lib/services/reports.service.ts index d752087c8..97930cec0 100644 --- a/src/apps/reports/src/lib/services/reports.service.ts +++ b/src/apps/reports/src/lib/services/reports.service.ts @@ -28,6 +28,46 @@ export type ReportGroup = { export type ReportsIndexResponse = Record +export type BillingAccountDetail = { + name: string + description: string | null + subcontractingEndCustomer: string | null + status: string + startDate: string | null + endDate: string | null + budget: string | number + markup: string | number +} + +export type SfdcBillingAccountPaymentRow = { + paymentId: string + paymentDate: string + billingAccountId: string + paymentStatus: string + challengeFee: string | number + paymentAmount: string | number + challengeId: string + category: string + isTask: boolean + challengeName: string | null + challengeStatus: string | null + winnerHandle: string + winnerId: string + winnerFirstName: string + winnerLastName: string +} + +/** Response from GET /sfdc/billing-accounts */ +export type BillingAccountProfileResponse = { + billingAccount: BillingAccountDetail | null +} + +/** Billing Accounts in-app view: profile + rows from GET /sfdc/payments */ +export type BillingAccountsViewData = { + billingAccount: BillingAccountDetail | null + payments: SfdcBillingAccountPaymentRow[] +} + const reportsDownloadClient: AxiosInstance = xhrCreateInstance() const buildReportUrl = (path: string): string => { @@ -137,6 +177,16 @@ export const downloadReportAsJson = (path: string): Promise => ( downloadReportBlob(path, 'application/json') ) +export const fetchReportJson = async (path: string): Promise => { + if (!path) { + throw new Error('Report path is required') + } + + const normalizedPath = path.startsWith('/') ? path : `/${path}` + const url = `${EnvironmentConfig.API.V6}/reports${normalizedPath}` + return xhrGetAsync(url, reportsDownloadClient) +} + export const downloadReportAsCsv = (path: string): Promise => ( downloadReportBlob(path, 'text/csv') ) diff --git a/src/apps/reports/src/pages/reports/BillingAccountsPage.tsx b/src/apps/reports/src/pages/reports/BillingAccountsPage.tsx new file mode 100644 index 000000000..fc399e312 --- /dev/null +++ b/src/apps/reports/src/pages/reports/BillingAccountsPage.tsx @@ -0,0 +1,3 @@ +import { BillingAccountsPage } from './ReportsPage' + +export default BillingAccountsPage diff --git a/src/apps/reports/src/pages/reports/ReportsPage.module.scss b/src/apps/reports/src/pages/reports/ReportsPage.module.scss index e804f2221..4b60a8919 100644 --- a/src/apps/reports/src/pages/reports/ReportsPage.module.scss +++ b/src/apps/reports/src/pages/reports/ReportsPage.module.scss @@ -26,26 +26,109 @@ gap: 4px; } +.filtersPanel { + margin-top: 12px; + padding: 16px; + border: 1px solid #e4e6e9; + border-radius: 8px; + background: #fcfcfd; +} + .params { display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 16px; - margin-top: 12px; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 24px 20px; + margin-top: 0; + align-items: start; +} + +@media (max-width: 1200px) { + .params { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 760px) { + .params { + grid-template-columns: 1fr; + } +} + +.paramCard { + display: grid; + grid-template-rows: auto 40px auto; + row-gap: 8px; + align-content: start; +} + +.paramHeader { + min-height: 28px; +} + +.paramTitleRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.paramHeaderActions { + display: flex; + align-items: center; + gap: 8px; } .paramLabel { font-weight: 600; + color: #2f3338; +} + +.paramTypePill { + font-size: 11px; + color: #5e6369; + background: #f3f4f6; + border-radius: 999px; + padding: 2px 8px; + white-space: nowrap; } .paramMeta { color: #6b6f75; font-size: 12px; + line-height: 1.35; + min-height: 40px; + overflow: hidden; + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; } -.paramHint { +.actionsBar { + display: flex; + justify-content: flex-start; + margin-top: 18px; + padding-top: 14px; + border-top: 1px solid #eceef1; +} + +.paramInfoButton { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border: 0; + padding: 0; + border-radius: 50%; + background: transparent; color: #6b6f75; - font-size: 12px; - font-style: italic; + cursor: pointer; + + svg { + width: 16px; + height: 16px; + } } .reportTitle { @@ -94,3 +177,95 @@ font-style: italic; color: #6b6f75; } + +.billingSummary { + margin-top: 8px; + padding: 16px; + border: 1px solid #e4e6e9; + border-radius: 6px; + background: #fafbfc; +} + +.billingSummaryTitle { + font-weight: 600; + margin-bottom: 12px; +} + +.billingDetailGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 12px 24px; +} + +.billingDetailItem { + display: flex; + flex-direction: column; + gap: 2px; +} + +.billingDetailLabel { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.04em; + color: #6b6f75; +} + +.billingDetailValue { + font-size: 14px; + color: #1a1d21; + word-break: break-word; +} + +.billingMissingNotice { + margin-top: 8px; + padding: 12px; + border-radius: 4px; + background: #f3f4f6; + color: #494f55; + font-size: 14px; +} + +.paymentsSection { + margin-top: 24px; +} + +.paymentsSectionTitle { + font-weight: 600; + margin-bottom: 8px; +} + +.tableWrap { + overflow-x: auto; + border: 1px solid #e4e6e9; + border-radius: 6px; +} + +.paymentsTable { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +.paymentsTable th, +.paymentsTable td { + padding: 8px 10px; + text-align: left; + border-bottom: 1px solid #e4e6e9; + vertical-align: top; +} + +.paymentsTable th { + background: #f3f4f6; + font-weight: 600; + white-space: nowrap; +} + +.paymentsTable tbody tr:last-child td { + border-bottom: none; +} + +.paymentsEmpty { + margin-top: 8px; + color: #6b6f75; + font-style: italic; +} diff --git a/src/apps/reports/src/pages/reports/ReportsPage.tsx b/src/apps/reports/src/pages/reports/ReportsPage.tsx index b00f5cf2f..e37bdfd87 100644 --- a/src/apps/reports/src/pages/reports/ReportsPage.tsx +++ b/src/apps/reports/src/pages/reports/ReportsPage.tsx @@ -1,19 +1,32 @@ import { ChangeEvent, FC, useCallback, useEffect, useMemo, useState } from 'react' import { NavigateFunction, useNavigate } from 'react-router-dom' -import { Button, InputSelect, InputSelectOption, InputText, LoadingSpinner, PageTitle } from '~/libs/ui' +import { + Button, + IconOutline, + InputSelect, + InputSelectOption, + InputText, + LoadingSpinner, + PageTitle, + Tooltip, +} from '~/libs/ui' import { bulkMemberLookupRouteId } from '../../config/routes.config' import { handleError } from '../../lib/utils' import { + BillingAccountProfileResponse, + BillingAccountsViewData, downloadBlobFile, downloadReportAsCsv, downloadReportAsJson, + fetchReportJson, fetchReportsIndex, ReportDefinition, ReportGroup, ReportParameter, ReportsIndexResponse, + SfdcBillingAccountPaymentRow, } from '../../lib/services' import { getReportParameterValidationError } from './reports-page.validation' @@ -21,6 +34,199 @@ import styles from './ReportsPage.module.scss' const pageTitle = 'Reports' const bulkMembersByHandlesPath = '/identity/users-by-handles' +const BILLING_ACCOUNTS_REPORT_PATH = '/sfdc/billing-accounts' +const SFDC_PAYMENTS_REPORT_PATH = '/sfdc/payments' +const BILLING_ACCOUNTS_REPORT_DEFINITION: ReportDefinition = { + name: 'Billing Accounts', + path: BILLING_ACCOUNTS_REPORT_PATH, + description: 'View billing-account details and SFDC payments by billing account ID.', + method: 'GET', + parameters: [ + { + name: 'billingAccountId', + type: 'string', + description: 'Billing account ID', + required: true, + location: 'query', + }, + { + name: 'startDate', + type: 'date', + description: 'Optional start date for payment filtering (ISO 8601)', + location: 'query', + }, + { + name: 'endDate', + type: 'date', + description: 'Optional end date for payment filtering (ISO 8601)', + location: 'query', + }, + ], +} + +type ReportsPageTab = 'reports' | 'billingAccounts' + +const buildSfdcPaymentsQueryPath = ( + billingAccountId: string, + startDate?: string, + endDate?: string, +): string => { + const query = new URLSearchParams() + query.append('billingAccountIds', billingAccountId.trim()) + const start = startDate?.trim() + const end = endDate?.trim() + + if (start) { + query.append('startDate', start) + } + + if (end) { + query.append('endDate', end) + } + + return `${SFDC_PAYMENTS_REPORT_PATH}?${query.toString()}` +} + +const formatReportCell = (value: unknown): string => { + if (value === null || value === undefined || value === '') { + return '—' + } + + if (typeof value === 'boolean') { + return value ? 'Yes' : 'No' + } + + return String(value) +} + +const formatPaymentDate = (iso: string): string => { + const parsed = Date.parse(iso) + + if (Number.isNaN(parsed)) { + return iso + } + + return new Date(parsed).toLocaleString() +} + +const PAYMENT_TABLE_COLUMNS: { key: keyof SfdcBillingAccountPaymentRow; label: string }[] = [ + { key: 'paymentId', label: 'Payment ID' }, + { key: 'paymentDate', label: 'Payment date' }, + { key: 'billingAccountId', label: 'Billing account ID' }, + { key: 'paymentStatus', label: 'Status' }, + { key: 'challengeFee', label: 'Challenge fee' }, + { key: 'paymentAmount', label: 'Payment amount' }, + { key: 'challengeId', label: 'Challenge ID' }, + { key: 'category', label: 'Category' }, + { key: 'isTask', label: 'Task' }, + { key: 'challengeName', label: 'Challenge name' }, + { key: 'challengeStatus', label: 'Challenge status' }, + { key: 'winnerHandle', label: 'Winner handle' }, + { key: 'winnerId', label: 'Winner ID' }, + { key: 'winnerFirstName', label: 'Winner first name' }, + { key: 'winnerLastName', label: 'Winner last name' }, +] + +const BillingAccountReportResults = ({ data }: { data: BillingAccountsViewData }): JSX.Element => { + const { billingAccount, payments } = data + + return ( +
+
+
Billing account
+ {billingAccount ? ( +
+
+ Name + {billingAccount.name} +
+
+ Description + + {formatReportCell(billingAccount.description)} + +
+
+ Subcontracting end customer + + {formatReportCell(billingAccount.subcontractingEndCustomer)} + +
+
+ Status + {billingAccount.status} +
+
+ Start date + + {billingAccount.startDate + ? formatPaymentDate(String(billingAccount.startDate)) + : '—'} + +
+
+ End date + + {billingAccount.endDate + ? formatPaymentDate(String(billingAccount.endDate)) + : '—'} + +
+
+ Budget + + {formatReportCell(billingAccount.budget)} + +
+
+ Markup + + {formatReportCell(billingAccount.markup)} + +
+
+ ) : ( +
+ No billing account profile was found for this ID. Payments for this account may still + appear below. +
+ )} +
+ +
+
Payments
+ {payments.length === 0 ? ( +
No payments matched the selected filters.
+ ) : ( +
+ + + + {PAYMENT_TABLE_COLUMNS.map(col => ( + + ))} + + + + {payments.map(row => ( + + {PAYMENT_TABLE_COLUMNS.map(col => ( + + ))} + + ))} + +
{col.label}
+ {col.key === 'paymentDate' + ? formatPaymentDate(String(row[col.key])) + : formatReportCell(row[col.key])} +
+
+ )} +
+
+ ) +} const buildDownloadName = ( name: string, @@ -48,12 +254,21 @@ const formatMethod = (method?: string): string => ( method ? method.toUpperCase() : 'GET' ) +const formatParameterLabel = (name: string): string => ( + name + .replace(/([a-z0-9])([A-Z])/g, '$1 $2') + .replace(/Ids\b/g, 'IDs') + .replace(/^./, char => char.toUpperCase()) +) + type ReportActionsProps = { handleCsvDownload: () => void handleJsonDownload: () => void + handleResetFilters: () => void handleOpenBulkMemberLookup: () => void isDownloadDisabled: boolean isHandleLookupPostReport: boolean + isResetDisabled: boolean isPostReport: boolean } @@ -94,6 +309,13 @@ const ReportActions = (props: ReportActionsProps): JSX.Element => { > Download as CSV + ) } @@ -125,50 +347,69 @@ const SelectedReportSection = (props: SelectedReportSectionProps): JSX.Element = - {(props.selectedReport.parameters?.length ?? 0) > 0 && ( -
- {props.selectedReport.parameters?.map(parameter => ( -
-
- {parameter.name} - {parameter.required ? ' *' : ''} -
- {parameter.description && ( -
{parameter.description}
- )} -
- Location: - {' '} - {parameter.location || 'query'} - {' '} - • Type: - {' '} - {parameter.type} -
- {parameter.type.endsWith('[]') && ( -
- Use comma-separated values for lists. + {(props.selectedReport.parameters?.length ?? 0) > 0 ? ( +
+
+ {props.selectedReport.parameters?.map(parameter => ( +
+
+
+
+ {formatParameterLabel(parameter.name)} + {parameter.required ? ' *' : ''} +
+
+
{parameter.type}
+ + + +
+
- )} - {props.renderParameterInput(parameter)} -
- ))} +
+ {parameter.description || '\u00A0'} +
+ {props.renderParameterInput(parameter)} +
+ ))} +
+
+ {props.reportActions} +
+ ) : ( + props.reportActions )} - - {props.reportActions} ) } -export const ReportsPage: FC = () => { +type ReportsPageContentProps = { + initialTab: ReportsPageTab +} + +const ReportsPageContent: FC = ({ initialTab }) => { const navigate: NavigateFunction = useNavigate() + const [activeTab] = useState(initialTab) const [reportsIndex, setReportsIndex] = useState({}) const [selectedBasePath, setSelectedBasePath] = useState('') const [selectedReportPath, setSelectedReportPath] = useState('') const [isLoading, setIsLoading] = useState(false) const [downloadingFormat, setDownloadingFormat] = useState<'json' | 'csv' | undefined>(undefined) const [parameterValues, setParameterValues] = useState>({}) + const [billingAccountViewData, setBillingAccountViewData] = useState< + BillingAccountsViewData | undefined + >(undefined) + const [isBillingAccountViewLoading, setIsBillingAccountViewLoading] = useState(false) useEffect(() => { let isMounted = true @@ -230,15 +471,21 @@ export const ReportsPage: FC = () => { selectedGroup?.reports?.find(report => report.path === selectedReportPath) ), [selectedGroup, selectedReportPath]) + const selectedReportForForm = activeTab === 'billingAccounts' + ? BILLING_ACCOUNTS_REPORT_DEFINITION + : selectedReport + const handleBasePathChange = useCallback((event: ChangeEvent) => { setSelectedBasePath(event.target.value) setSelectedReportPath('') setParameterValues({}) + setBillingAccountViewData(undefined) }, []) const handleReportChange = useCallback((event: ChangeEvent) => { setSelectedReportPath(event.target.value) setParameterValues({}) + setBillingAccountViewData(undefined) }, []) const handleParameterChange = useCallback((event: ChangeEvent) => { @@ -291,7 +538,7 @@ export const ReportsPage: FC = () => { }, [parameterValues]) const parameterErrors = useMemo>(() => ( - (selectedReport?.parameters ?? []).reduce>((errors, parameter) => { + (selectedReportForForm?.parameters ?? []).reduce>((errors, parameter) => { const error = getReportParameterValidationError(parameter, parameterValues[parameter.name]) if (error) { @@ -300,12 +547,55 @@ export const ReportsPage: FC = () => { return errors }, {}) - ), [parameterValues, selectedReport]) + ), [parameterValues, selectedReportForForm]) const hasInvalidParameterValues = useMemo(() => ( Object.keys(parameterErrors).length > 0 ), [parameterErrors]) + const handleBillingAccountView = useCallback(async () => { + if (activeTab !== 'billingAccounts' || hasInvalidParameterValues) { + return + } + + const billingAccountId = parameterValues.billingAccountId?.trim() + + if (!billingAccountId) { + return + } + + try { + setIsBillingAccountViewLoading(true) + const profileQuery = new URLSearchParams({ billingAccountId }) + const profilePath = `${BILLING_ACCOUNTS_REPORT_PATH}?${profileQuery.toString()}` + const paymentsPath = buildSfdcPaymentsQueryPath( + billingAccountId, + parameterValues.startDate, + parameterValues.endDate, + ) + + const paymentsPromise = fetchReportJson(paymentsPath) + const profilePromise = fetchReportJson(profilePath) + .catch(() => ({ billingAccount: null })) + const [profile, payments] = await Promise.all([profilePromise, paymentsPromise]) + + setBillingAccountViewData({ + billingAccount: profile.billingAccount, + payments, + }) + } catch (error) { + handleError(error) + } finally { + setIsBillingAccountViewLoading(false) + } + }, [ + activeTab, + hasInvalidParameterValues, + parameterValues.billingAccountId, + parameterValues.endDate, + parameterValues.startDate, + ]) + const handleDownload = useCallback(async (format: 'json' | 'csv') => { if (!selectedReport || hasInvalidParameterValues) { return @@ -338,18 +628,24 @@ export const ReportsPage: FC = () => { navigate(bulkMemberLookupRouteId) }, [navigate]) + const handleResetFilters = useCallback(() => { + setParameterValues({}) + setBillingAccountViewData(undefined) + }, []) + const isDownloading = downloadingFormat !== undefined + const isBusy = isDownloading || isBillingAccountViewLoading const requiredParamsMissing = useMemo(() => { - const params = selectedReport?.parameters ?? [] + const params = selectedReportForForm?.parameters ?? [] return params.some(param => param.required && !(parameterValues[param.name]?.trim())) - }, [parameterValues, selectedReport]) + }, [parameterValues, selectedReportForForm]) const hasUnresolvedPathParams = useMemo(() => ( - (selectedReport?.parameters ?? []) + (selectedReportForForm?.parameters ?? []) .filter(param => param.location === 'path') .some(param => !parameterValues[param.name]?.trim()) - ), [parameterValues, selectedReport]) + ), [parameterValues, selectedReportForForm]) const isPostReport = selectedReport?.method?.toUpperCase() === 'POST' const isHandleLookupPostReport = isPostReport && selectedReport.path === bulkMembersByHandlesPath @@ -360,6 +656,14 @@ export const ReportsPage: FC = () => { || hasInvalidParameterValues || hasUnresolvedPathParams + const billingAccountViewDisabled = !selectedReportForForm + || isDownloading + || isBillingAccountViewLoading + || requiredParamsMissing + || hasInvalidParameterValues + || hasUnresolvedPathParams + const isResetDisabled = Object.keys(parameterValues).length === 0 + const handleJsonDownload = useCallback(() => { handleDownload('json') }, [handleDownload]) @@ -368,20 +672,41 @@ export const ReportsPage: FC = () => { handleDownload('csv') }, [handleDownload]) + const billingAccountReportActions = ( +
+ + +
+ ) + const reportActions = ( ) const renderParameterInput = useCallback((parameter: ReportParameter) => { const commonProps = { - label: parameter.name, + label: formatParameterLabel(parameter.name), name: parameter.name, placeholder: parameter.type === 'date' ? 'YYYY-MM-DD' @@ -435,14 +760,18 @@ export const ReportsPage: FC = () => { return ( <> - {isDownloading && ( - + {isBusy && ( + )}
{pageTitle}

- Select a base path to view the available reports. After choosing a report, provide any - required parameters and download the data as JSON or CSV directly from the reports API. + {activeTab === 'reports' + ? 'Select a base path to view available reports. Choose a report, fill required parameters, and download JSON or CSV from the reports API.' + : 'Enter a billing account ID and optional start/end dates, then click View to load billing account payment data.'}

{isLoading ? ( @@ -451,42 +780,56 @@ export const ReportsPage: FC = () => {
) : ( <> - {basePathOptions.length ? ( -
- - - {selectedGroup && ( - + {activeTab === 'reports' ? ( + <> + {basePathOptions.length ? ( +
+ + + {selectedGroup && ( + + )} +
+ ) : ( +
+ No reports are currently available. +
)} -
+ + + ) : ( -
- No reports are currently available. -
+ )} - + {activeTab === 'billingAccounts' && billingAccountViewData ? ( + + ) : undefined} )}
@@ -494,4 +837,12 @@ export const ReportsPage: FC = () => { ) } +export const ReportsPage: FC = () => ( + +) + +export const BillingAccountsPage: FC = () => ( + +) + export default ReportsPage diff --git a/src/apps/reports/src/reports-app.routes.tsx b/src/apps/reports/src/reports-app.routes.tsx index df4b56ecd..01c720710 100644 --- a/src/apps/reports/src/reports-app.routes.tsx +++ b/src/apps/reports/src/reports-app.routes.tsx @@ -11,6 +11,7 @@ import { } from '~/libs/core' import { + billingAccountsPageRouteId, bulkMemberLookupRouteId, reportsPageRouteId, rootRoute, @@ -21,6 +22,9 @@ const ReportsPage: LazyLoadedComponent = lazyLoad( () => import('./pages/reports/ReportsPage'), 'ReportsPage', ) +const BillingAccountsPage: LazyLoadedComponent = lazyLoad( + () => import('./pages/reports/BillingAccountsPage'), +) const BulkMemberLookupPage: LazyLoadedComponent = lazyLoad( () => import('./pages/bulk-member-lookup/BulkMemberLookupPage'), 'BulkMemberLookupPage', @@ -43,6 +47,11 @@ export const reportsRoutes: ReadonlyArray = [ element: , route: reportsPageRouteId, }, + { + authRequired: true, + element: , + route: billingAccountsPageRouteId, + }, { authRequired: true, element: , From 333abcc64fd73c4f0a7ca5171d34c44bad141fc1 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Thu, 30 Apr 2026 14:04:28 +0530 Subject: [PATCH 2/5] fix linting --- .../reports/src/pages/reports/ReportsPage.tsx | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/apps/reports/src/pages/reports/ReportsPage.tsx b/src/apps/reports/src/pages/reports/ReportsPage.tsx index e37bdfd87..7c8c824df 100644 --- a/src/apps/reports/src/pages/reports/ReportsPage.tsx +++ b/src/apps/reports/src/pages/reports/ReportsPage.tsx @@ -37,31 +37,31 @@ const bulkMembersByHandlesPath = '/identity/users-by-handles' const BILLING_ACCOUNTS_REPORT_PATH = '/sfdc/billing-accounts' const SFDC_PAYMENTS_REPORT_PATH = '/sfdc/payments' const BILLING_ACCOUNTS_REPORT_DEFINITION: ReportDefinition = { - name: 'Billing Accounts', - path: BILLING_ACCOUNTS_REPORT_PATH, description: 'View billing-account details and SFDC payments by billing account ID.', method: 'GET', + name: 'Billing Accounts', parameters: [ { - name: 'billingAccountId', - type: 'string', description: 'Billing account ID', - required: true, location: 'query', + name: 'billingAccountId', + required: true, + type: 'string', }, { - name: 'startDate', - type: 'date', description: 'Optional start date for payment filtering (ISO 8601)', location: 'query', + name: 'startDate', + type: 'date', }, { - name: 'endDate', - type: 'date', description: 'Optional end date for payment filtering (ISO 8601)', location: 'query', + name: 'endDate', + type: 'date', }, ], + path: BILLING_ACCOUNTS_REPORT_PATH, } type ReportsPageTab = 'reports' | 'billingAccounts' @@ -106,7 +106,8 @@ const formatPaymentDate = (iso: string): string => { return iso } - return new Date(parsed).toLocaleString() + return new Date(parsed) + .toLocaleString() } const PAYMENT_TABLE_COLUMNS: { key: keyof SfdcBillingAccountPaymentRow; label: string }[] = [ @@ -127,8 +128,11 @@ const PAYMENT_TABLE_COLUMNS: { key: keyof SfdcBillingAccountPaymentRow; label: s { key: 'winnerLastName', label: 'Winner last name' }, ] -const BillingAccountReportResults = ({ data }: { data: BillingAccountsViewData }): JSX.Element => { - const { billingAccount, payments } = data +const BillingAccountReportResults = ( + props: { data: BillingAccountsViewData }, +): JSX.Element => { + const billingAccount: BillingAccountsViewData['billingAccount'] = props.data.billingAccount + const payments: BillingAccountsViewData['payments'] = props.data.payments return (
@@ -261,6 +265,8 @@ const formatParameterLabel = (name: string): string => ( .replace(/^./, char => char.toUpperCase()) ) +const EMPTY_BILLING_ACCOUNT_PROFILE_RESPONSE = {} as BillingAccountProfileResponse + type ReportActionsProps = { handleCsvDownload: () => void handleJsonDownload: () => void @@ -361,7 +367,10 @@ const SelectedReportSection = (props: SelectedReportSectionProps): JSX.Element =
{parameter.type}
-
- {parameter.description || '\u00A0'} -
{props.renderParameterInput(parameter)} ))} From 6cc4b306f583510460f9cbd4941128aca19d5c97 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Thu, 30 Apr 2026 21:50:33 +0530 Subject: [PATCH 4/5] Fix devin feedback --- src/apps/reports/src/lib/services/reports.service.ts | 4 ++-- src/apps/reports/src/pages/reports/ReportsPage.tsx | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/apps/reports/src/lib/services/reports.service.ts b/src/apps/reports/src/lib/services/reports.service.ts index 97930cec0..a6c4ed649 100644 --- a/src/apps/reports/src/lib/services/reports.service.ts +++ b/src/apps/reports/src/lib/services/reports.service.ts @@ -59,12 +59,12 @@ export type SfdcBillingAccountPaymentRow = { /** Response from GET /sfdc/billing-accounts */ export type BillingAccountProfileResponse = { - billingAccount: BillingAccountDetail | null + billingAccount?: BillingAccountDetail } /** Billing Accounts in-app view: profile + rows from GET /sfdc/payments */ export type BillingAccountsViewData = { - billingAccount: BillingAccountDetail | null + billingAccount?: BillingAccountDetail payments: SfdcBillingAccountPaymentRow[] } diff --git a/src/apps/reports/src/pages/reports/ReportsPage.tsx b/src/apps/reports/src/pages/reports/ReportsPage.tsx index 53ca22dc4..96792adf3 100644 --- a/src/apps/reports/src/pages/reports/ReportsPage.tsx +++ b/src/apps/reports/src/pages/reports/ReportsPage.tsx @@ -265,7 +265,9 @@ const formatParameterLabel = (name: string): string => ( .replace(/^./, char => char.toUpperCase()) ) -const EMPTY_BILLING_ACCOUNT_PROFILE_RESPONSE = {} as BillingAccountProfileResponse +const EMPTY_BILLING_ACCOUNT_PROFILE_RESPONSE: BillingAccountProfileResponse = { + billingAccount: undefined, +} type ReportActionsProps = { handleCsvDownload: () => void @@ -369,7 +371,7 @@ const SelectedReportSection = (props: SelectedReportSectionProps): JSX.Element = From 331f6f31a7ce5c0618d57bff3d19b791f6579506 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Thu, 30 Apr 2026 21:59:47 +0530 Subject: [PATCH 5/5] Fix tooltip content --- src/apps/reports/src/pages/reports/ReportsPage.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/apps/reports/src/pages/reports/ReportsPage.tsx b/src/apps/reports/src/pages/reports/ReportsPage.tsx index 96792adf3..b27b76195 100644 --- a/src/apps/reports/src/pages/reports/ReportsPage.tsx +++ b/src/apps/reports/src/pages/reports/ReportsPage.tsx @@ -369,10 +369,14 @@ const SelectedReportSection = (props: SelectedReportSectionProps): JSX.Element =
{parameter.type}
+ {parameter.description || 'No description available'} +
+ {`Location: ${parameter.location || 'query'} + (${parameter.name})`} + + )} place='top' >