Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import AccountAvatar from '@components/Avatar/connected/AccountAvatar';
import Table from '@components/Table';
import TextWithTooltip from '@components/TextWithTooltip';

import useThemeStyles from '@hooks/useThemeStyles';

import React from 'react';
import {View} from 'react-native';

import type {PaymentActivityRowData} from '.';

type PaymentActivityTableRowProps = {
/** The active user shown in the row */
item: PaymentActivityRowData;

/** The index of the row relative to all other rows */
rowIndex: number;
};

/** A read-only row showing an active user's avatar, name, and email. */
export default function PaymentActivityTableRow({item, rowIndex}: PaymentActivityTableRowProps) {
const styles = useThemeStyles();

const accessibilityLabel = [item.name, item.email].filter(Boolean).join(', ');

return (
<Table.Row
interactive={false}
rowIndex={rowIndex}
accessibilityLabel={accessibilityLabel}
>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.gap1]}>
<AccountAvatar
accountID={item.accountID}
fallbackDisplayName={item.name || item.email}
/>
<View style={[styles.flex1, styles.gap1]}>
<TextWithTooltip
shouldShowTooltip
text={item.name}
style={[styles.optionDisplayName, styles.pre]}
numberOfLines={1}
/>
<TextWithTooltip
shouldShowTooltip
text={item.email}
style={[styles.textLabelSupporting, styles.lh16, styles.pre]}
numberOfLines={1}
/>
</View>
</View>
</Table.Row>
);
}
95 changes: 95 additions & 0 deletions src/components/Tables/PaymentActivityTable/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type {CompareItemsCallback, IsItemInSearchCallback, TableColumn, TableData} from '@components/Table';
import Table, {composeTableListHeader} from '@components/Table';

import useLocalize from '@hooks/useLocalize';

import tokenizedSearch from '@libs/tokenizedSearch';

import CONST from '@src/CONST';

import type {ListRenderItemInfo} from '@shopify/flash-list';

import React from 'react';

import PaymentActivityTableRow from './PaymentActivityTableRow';

type PaymentActivityTableColumnKey = 'member';

type PaymentActivityRowData = TableData & {
accountID: number;

/** The active user's login (email/phone) */
login: string;

/** The active user's display name */
name: string;

/** The active user's formatted email/phone shown as the subtitle */
email: string;
};

type PaymentActivityTableProps = {
/** The active users billed for the month */
activeUsers: PaymentActivityRowData[];

/** Page-level content rendered above the table header inside the scrollable list */
headerComponent?: React.ReactElement;
};

function PaymentActivityTable({activeUsers, headerComponent}: PaymentActivityTableProps) {
const {translate, localeCompare} = useLocalize();

// The count always reflects every active user, not just the ones matching the search.
const activeUsersLabel = translate('subscription.paymentHistory.activeUsers', {count: activeUsers.length});

const columns: Array<TableColumn<PaymentActivityTableColumnKey>> = [
{
key: 'member',
label: activeUsersLabel,
sortable: false,
},
];

const compareItems: CompareItemsCallback<PaymentActivityRowData, PaymentActivityTableColumnKey> = (item1, item2) => localeCompare(item1.name.toLowerCase(), item2.name.toLowerCase());

const isItemInSearch: IsItemInSearchCallback<PaymentActivityRowData> = (item, searchValue) => {
const results = tokenizedSearch([item], searchValue, (option) => [option.name, option.email, option.login]);
return results.length > 0;
};

const renderItem = ({item, index}: ListRenderItemInfo<PaymentActivityRowData>) => (
<PaymentActivityTableRow
item={item}
rowIndex={index}
/>
);

// Short lists are easy to scan, so the search bar only shows once there are enough active users to search through.
const shouldShowSearchBar = activeUsers.length >= CONST.STANDARD_LIST_ITEM_LIMIT;
const tableHeaderComponent = composeTableListHeader(
headerComponent,
shouldShowSearchBar ? <Table.FilterBar label={translate('subscription.paymentHistory.findActiveUser')} /> : undefined,
);

return (
<Table
data={activeUsers}
columns={columns}
initialSortColumn="member"
title={activeUsersLabel}
renderItem={renderItem}
compareItems={compareItems}
isItemInSearch={isItemInSearch}
keyExtractor={(item) => item.keyForList}
>
<Table.ListHeader>{tableHeaderComponent}</Table.ListHeader>
<Table.NoResultsState />
<Table.Header />
<Table.Body />
</Table>
);
}

export default PaymentActivityTable;

export type {PaymentActivityRowData};
2 changes: 2 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11628,6 +11628,8 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
refundedAmountOn: ({amount, date}: {amount: string; date: string}) => `${amount} erstattet am ${date}`,
},
billedTo: 'Abgerechnet an',
activity: 'Aktivität',
findActiveUser: 'Aktiven Benutzer suchen',
viewReceipt: 'Beleg anzeigen',
viewInvoice: 'Rechnung anzeigen',
},
Expand Down
2 changes: 2 additions & 0 deletions src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11844,6 +11844,8 @@ ${reportName}`,
refundedAmountOn: ({amount, date}: {amount: string; date: string}) => `Επιστράφηκαν ${amount} στις ${date}`,
},
billedTo: 'Χρέωση σε',
activity: 'Δραστηριότητα',
findActiveUser: 'Εύρεση ενεργού χρήστη',
viewReceipt: 'Προβολή απόδειξης',
viewInvoice: 'Προβολή τιμολογίου',
},
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11897,6 +11897,8 @@ const translations = {
refundedAmountOn: ({amount, date}: PaymentDetailsPartialRefundParams) => `Refunded ${amount} on ${date}`,
},
billedTo: 'Billed to',
activity: 'Activity',
findActiveUser: 'Find an active user',
viewReceipt: 'View receipt',
viewInvoice: 'View invoice',
},
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11808,6 +11808,8 @@ ${reportName}`,
refundedAmountOn: ({amount, date}: {amount: string; date: string}) => `Reembolsado ${amount} el ${date}`,
},
billedTo: 'Facturado a',
activity: 'Actividad',
findActiveUser: 'Buscar un usuario activo',
viewReceipt: 'Ver recibo',
viewInvoice: 'Ver factura',
},
Expand Down
2 changes: 2 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11654,6 +11654,8 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
refundedAmountOn: ({amount, date}: {amount: string; date: string}) => `${amount} remboursé le ${date}`,
},
billedTo: 'Facturé à',
activity: 'Activité',
findActiveUser: 'Rechercher un utilisateur actif',
viewReceipt: 'Voir le reçu',
viewInvoice: 'Voir la facture',
},
Expand Down
2 changes: 2 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11574,6 +11574,8 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
refundedAmountOn: ({amount, date}: {amount: string; date: string}) => `Rimborsati ${amount} il ${date}`,
},
billedTo: 'Fatturato a',
activity: 'Attività',
findActiveUser: 'Trova un utente attivo',
viewReceipt: 'Visualizza ricevuta',
viewInvoice: 'Visualizza fattura',
},
Expand Down
2 changes: 2 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11408,6 +11408,8 @@ ${reportName}`,
refundedAmountOn: ({amount, date}: {amount: string; date: string}) => `${date} に ${amount} を返金`,
},
billedTo: '請求先',
activity: 'アクティビティ',
findActiveUser: 'アクティブユーザーを検索',
viewReceipt: '領収書を表示',
viewInvoice: '請求書を表示',
},
Expand Down
2 changes: 2 additions & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11539,6 +11539,8 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
refundedAmountOn: ({amount, date}: {amount: string; date: string}) => `${amount} terugbetaald op ${date}`,
},
billedTo: 'Gefactureerd aan',
activity: 'Activiteit',
findActiveUser: 'Zoek een actieve gebruiker',
viewReceipt: 'Bon bekijken',
viewInvoice: 'Factuur bekijken',
},
Expand Down
2 changes: 2 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11560,6 +11560,8 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
refundedAmountOn: ({amount, date}: {amount: string; date: string}) => `Zwrócono ${amount} ${date}`,
},
billedTo: 'Obciążono',
activity: 'Aktywność',
findActiveUser: 'Znajdź aktywnego użytkownika',
viewReceipt: 'Wyświetl paragon',
viewInvoice: 'Wyświetl fakturę',
},
Expand Down
2 changes: 2 additions & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11532,6 +11532,8 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
refundedAmountOn: ({amount, date}: {amount: string; date: string}) => `${amount} reembolsado em ${date}`,
},
billedTo: 'Cobrado de',
activity: 'Atividade',
findActiveUser: 'Encontrar um usuário ativo',
viewReceipt: 'Ver recibo',
viewInvoice: 'Ver fatura',
},
Expand Down
2 changes: 2 additions & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11115,6 +11115,8 @@ ${reportName}`,
refundedAmountOn: ({amount, date}: {amount: string; date: string}) => `${date} 已退款 ${amount}`,
},
billedTo: '账单对象',
activity: '活动',
findActiveUser: '查找活跃用户',
viewReceipt: '查看收据',
viewInvoice: '查看发票',
},
Expand Down
6 changes: 6 additions & 0 deletions src/libs/PaymentHistoryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ function getActiveUserCount(purchase: Purchase): number | undefined {
return emails.size > 0 ? emails.size : undefined;
}

/** Returns the account IDs of the users billed for a purchase, deduped since the same person can be billed under more than one workspace. */
function getActiveUserAccountIDs(purchase: Purchase | undefined): number[] {
return [...new Set(purchase?.activeUsers?.filter((accountID) => !!accountID))];
}

function compareNewestFirst(left: PaymentHistoryRow, right: PaymentHistoryRow): number {
if (left.created === right.created) {
return right.purchaseID - left.purchaseID;
Expand Down Expand Up @@ -370,6 +375,7 @@ function getReceiptFileName(source: string, purchaseID: number): string {

export {
formatDate,
getActiveUserAccountIDs,
getBadgeAppearance,
getBadgeText,
getPaymentDetailsBadge,
Expand Down
Loading
Loading