Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3c9d13c
Add new search filter
jnowakow Mar 17, 2026
47f88de
Add navigation
jnowakow Mar 17, 2026
dc92c39
Add new page to RHP
jnowakow Mar 17, 2026
288e6fe
Merge branch 'main' into jnowakow/deleted-transactions-on-search
jnowakow Mar 18, 2026
4192d58
remove deleted RHP
jnowakow Mar 19, 2026
6b9527a
Update row UI for deleted transactions
jnowakow Mar 19, 2026
49b30a5
Support deleted transactions on search on web
jnowakow Mar 19, 2026
273f63e
fix deleted transactions on narrow screens
jnowakow Mar 19, 2026
28ce20c
Add isDeleted to memo deps
jnowakow Mar 19, 2026
70d637f
Prettier
jnowakow Mar 19, 2026
034fb9b
spell check
jnowakow Mar 19, 2026
6fff735
Add undelete action
jnowakow Mar 20, 2026
742d731
Correct badges colours
jnowakow Mar 20, 2026
19e05be
Fix opening undeleted transaction on search
jnowakow Mar 20, 2026
541ddd2
Merge branch 'main' into jnowakow/deleted-transactions-on-search
jnowakow Mar 23, 2026
a476024
Send transactionThreadReport when undeleting reports
jnowakow Mar 24, 2026
88107e5
Prevent RBR on Inbox after undeleting transaction
jnowakow Mar 24, 2026
a2d6d77
Merge branch 'main' of github.com:Expensify/App into jnowakow/deleted…
jnowakow Mar 24, 2026
61f2d03
Add translations
jnowakow Mar 24, 2026
10969ef
fix spanish translation
jnowakow Mar 24, 2026
b58bce0
reset mock to fix test
jnowakow Mar 24, 2026
cd023d7
Merge branch 'main' of github.com:Expensify/App into jnowakow/deleted…
jnowakow Mar 25, 2026
09be24a
Fix prettier
jnowakow Mar 25, 2026
489937d
Create utility hook for undeleting transactions
jnowakow Mar 25, 2026
cfb6d1a
apply feedback
jnowakow Mar 25, 2026
dab57fe
Merge branch 'main' of github.com:Expensify/App into jnowakow/deleted…
jnowakow Mar 25, 2026
0cac99d
fix linter
jnowakow Mar 25, 2026
b96f21d
Select single policy instead of collection
jnowakow Mar 25, 2026
0a5c224
undelete held expenses
jnowakow Mar 25, 2026
8d4504c
differentiate undeleted and unreported transactions optimistically
jnowakow Mar 25, 2026
7bf52c7
don't give button role to not clickable element
jnowakow Mar 26, 2026
aecbd3b
remove redundant memos and callbacks
jnowakow Mar 26, 2026
cc09a43
Don't give pressable role button if it does nothing
jnowakow Mar 27, 2026
12096d8
Update getReportStatusTranslation test
jnowakow Mar 27, 2026
82966ba
consistent handling of passing methods in TransactionListItem
jnowakow Mar 27, 2026
6bc5f05
Fix prettier
jnowakow Mar 27, 2026
f63f941
Merge branch 'main' of github.com:Expensify/App into jnowakow/deleted…
jnowakow Mar 27, 2026
9c13ce7
Restore useAllTransactions hook
jnowakow Mar 27, 2026
0b8ba6f
Fix offline support for undeleting transactions
jnowakow Mar 27, 2026
08a02f2
fix linter
jnowakow Mar 27, 2026
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
4 changes: 4 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,7 @@ const CONST = {
MAX_COUNT_BEFORE_FOCUS_UPDATE: 30,
MIN_INITIAL_REPORT_ACTION_COUNT: 15,
UNREPORTED_REPORT_ID: '0',
TRASH_REPORT_ID: '-1',
SPLIT_REPORT_ID: '-2',
SECONDARY_ACTIONS: {
SUBMIT: 'submit',
Expand Down Expand Up @@ -7345,6 +7346,7 @@ const CONST = {
DONE: 'done',
EXPORT_TO_ACCOUNTING: 'exportToAccounting',
PAID: 'paid',
UNDELETE: 'undelete',
},
HAS_VALUES: {
RECEIPT: 'receipt',
Expand All @@ -7365,6 +7367,7 @@ const CONST = {
REJECT: 'reject',
CHANGE_REPORT: 'changeReport',
SPLIT: 'split',
UNDELETE: 'undelete',
},
TRANSACTION_TYPE: {
CASH: 'cash',
Expand Down Expand Up @@ -7599,6 +7602,7 @@ const CONST = {
APPROVED: 'approved',
DONE: 'done',
PAID: 'paid',
DELETED: 'deleted',
},
EXPENSE_REPORT: {
ALL: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const actionTranslationsMap: Record<SearchTransactionAction, TranslationPaths> =
[CONST.SEARCH.ACTION_TYPES.EXPORT_TO_ACCOUNTING]: 'common.export',
[CONST.SEARCH.ACTION_TYPES.DONE]: 'common.done',
[CONST.SEARCH.ACTION_TYPES.PAID]: 'iou.settledExpensify',
[CONST.SEARCH.ACTION_TYPES.UNDELETE]: 'search.bulkActions.undelete',
};

export default actionTranslationsMap;
11 changes: 7 additions & 4 deletions src/components/Search/SearchList/ListItem/StatusCell.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useMemo} from 'react';
import React from 'react';
import {View} from 'react-native';
import StatusBadge from '@components/StatusBadge';
import useLocalize from '@hooks/useLocalize';
Expand All @@ -15,15 +15,18 @@ type StatusCellProps = {

/** Whether the report's state/status is pending */
isPending?: boolean;

/** Whether the transaction was deleted */
isDeleted?: boolean;
};

function StatusCell({stateNum, statusNum, isPending}: StatusCellProps) {
function StatusCell({stateNum, statusNum, isPending, isDeleted}: StatusCellProps) {
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();

const statusText = useMemo(() => getReportStatusTranslation({stateNum, statusNum, translate}), [stateNum, statusNum, translate]);
const reportStatusColorStyle = useMemo(() => getReportStatusColorStyle(theme, stateNum, statusNum), [theme, stateNum, statusNum]);
const statusText = getReportStatusTranslation({stateNum, statusNum, isDeleted, translate});
const reportStatusColorStyle = getReportStatusColorStyle(theme, stateNum, statusNum, isDeleted);

if (!statusText || !reportStatusColorStyle) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {handleActionButtonPress as handleActionButtonPressUtil} from '@libs/acti
import {syncMissingAttendeesViolation} from '@libs/AttendeeUtils';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {isInvoiceReport} from '@libs/ReportUtils';
import {isViolationDismissed, mergeProhibitedViolations, shouldShowViolation} from '@libs/TransactionUtils';
import {isDeletedTransaction as isDeletedTransactionUtil, isViolationDismissed, mergeProhibitedViolations, shouldShowViolation} from '@libs/TransactionUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -51,8 +51,10 @@ function TransactionListItem<TItem extends ListItem>({
customCardNames,
lastPaymentMethod,
personalPolicyID,
onUndelete,
}: TransactionListItemProps<TItem>) {
const transactionItem = item as unknown as TransactionListItemType;
const isDeletedTransaction = isDeletedTransactionUtil(transactionItem);
const styles = useThemeStyles();
const theme = useTheme();

Expand Down Expand Up @@ -163,6 +165,7 @@ function TransactionListItem<TItem extends ListItem>({
isDelegateAccessRestricted,
onDelegateAccessRestricted: showDelegateNoAccessModal,
personalPolicyID,
onUndelete: () => onUndelete?.(transactionItem.transactionID),
});
};

Expand All @@ -176,10 +179,10 @@ function TransactionListItem<TItem extends ListItem>({
<PressableWithFeedback
ref={pressableRef}
onLongPress={() => onLongPressRow?.(item)}
onPress={() => onSelectRow(item, transactionPreviewData)}
onPress={isDeletedTransaction ? undefined : () => onSelectRow(item, transactionPreviewData)}
disabled={isDisabled && !item.isSelected}
accessibilityLabel={item.text ?? ''}
role={getButtonRole(true)}
role={isDeletedTransaction ? getButtonRole(true) : 'none'}
isNested
onMouseDown={(e) => e.preventDefault()}
hoverStyle={[!item.isDisabled && styles.hoveredComponentBG, item.isSelected && styles.activeComponentBG]}
Expand All @@ -189,6 +192,7 @@ function TransactionListItem<TItem extends ListItem>({
style={[
pressableStyle,
isFocused && StyleUtils.getItemBackgroundColorStyle(!!item.isSelected, !!isFocused, !!item.isDisabled, theme.activeComponentBG, theme.hoverComponentBG),
isDeletedTransaction && styles.cursorDefault,
]}
onFocus={onFocus}
wrapperStyle={[styles.mb2, styles.mh5, styles.flex1, animatedHighlightStyle, styles.userSelectNone]}
Expand All @@ -199,7 +203,7 @@ function TransactionListItem<TItem extends ListItem>({
<UserInfoAndActionButtonRow
item={transactionItem}
handleActionButtonPress={handleActionButtonPress}
shouldShowUserInfo={!!transactionItem?.from}
shouldShowUserInfo={!isDeletedTransaction && !!transactionItem?.from}
isInMobileSelectionMode={shouldUseNarrowLayout && !!canSelectMultiple}
isDisabledItem={!!isDisabled}
/>
Expand Down Expand Up @@ -227,7 +231,7 @@ function TransactionListItem<TItem extends ListItem>({
checkboxSentryLabel={CONST.SENTRY_LABEL.SEARCH.TRANSACTION_LIST_ITEM_CHECKBOX}
style={[styles.p3, styles.pv2, shouldUseNarrowLayout ? styles.pt2 : {}]}
violations={transactionViolations}
onArrowRightPress={() => onSelectRow(item, transactionPreviewData)}
onArrowRightPress={isDeletedTransaction ? undefined : () => onSelectRow(item, transactionPreviewData)}
isHover={hovered}
customCardNames={customCardNames}
reportActions={exportedReportActions}
Expand Down
2 changes: 2 additions & 0 deletions src/components/Search/SearchList/ListItem/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,8 @@ type TransactionListItemProps<TItem extends ListItem> = ListItemProps<TItem> & {
lastPaymentMethod?: OnyxEntry<LastPaymentMethod>;
/** The user's personal policy ID */
personalPolicyID?: string;
/** Callback to undelete a transaction by its ID */
onUndelete?: (transactionID: string) => void;
};

type TransactionGroupListItemProps<TItem extends ListItem> = ListItemProps<TItem> & {
Expand Down
6 changes: 6 additions & 0 deletions src/components/Search/SearchList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import usePrevious from '@hooks/usePrevious';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSafeAreaPaddings from '@hooks/useSafeAreaPaddings';
import useThemeStyles from '@hooks/useThemeStyles';
import useUndeleteTransactions from '@hooks/useUndeleteTransactions';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import DateUtils from '@libs/DateUtils';
Expand Down Expand Up @@ -301,6 +302,9 @@ function SearchList({
const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID);
const [lastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD);
const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID);
const undeleteTransactions = useUndeleteTransactions();

const handleUndelete = (transactionID: string) => undeleteTransactions([transactionID]);

const route = useRoute();
const {getScrollOffset} = useContext(ScrollOffsetContext);
Expand Down Expand Up @@ -450,6 +454,7 @@ function SearchList({
customCardNames={customCardNames}
onFocus={onFocus}
newTransactionID={newTransactionID}
onUndelete={handleUndelete}
keyForList={item.keyForList}
/>
</Animated.View>
Expand Down Expand Up @@ -482,6 +487,7 @@ function SearchList({
personalPolicyID,
customCardNames,
selectedTransactions,
handleUndelete,
],
);

Expand Down
3 changes: 3 additions & 0 deletions src/components/TransactionItemRow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
getCreated as getTransactionCreated,
hasMissingSmartscanFields,
isAmountMissing,
isDeletedTransaction as isDeletedTransactionUtil,
isMerchantMissing,
isScanning,
isTimeRequest,
Expand Down Expand Up @@ -203,6 +204,7 @@ function TransactionItemRow({
const createdAt = getTransactionCreated(transactionItem);
const expensicons = useMemoizedLazyExpensifyIcons(['ArrowRight']);
const transactionThreadReportID = reportActions ? getIOUActionForTransactionID(reportActions, transactionItem.transactionID)?.childReportID : undefined;
const isDeletedTransaction = isDeletedTransactionUtil(transactionItem);

const isDateColumnWide = dateColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE;
const isSubmittedColumnWide = submittedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE;
Expand Down Expand Up @@ -615,6 +617,7 @@ function TransactionItemRow({
<StatusCell
stateNum={transactionItem.report?.stateNum}
statusNum={transactionItem.report?.statusNum}
isDeleted={isDeletedTransaction}
/>
</View>
);
Expand Down
25 changes: 24 additions & 1 deletion src/hooks/useSearchBulkActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import {
} from '@libs/ReportUtils';
import {navigateToSearchRHP, shouldShowDeleteOption} from '@libs/SearchUIUtils';
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
import {hasTransactionBeenRejected} from '@libs/TransactionUtils';
import {hasTransactionBeenRejected, isDeletedTransaction} from '@libs/TransactionUtils';
import variables from '@styles/variables';
import {canIOUBePaid, dismissRejectUseExplanation} from '@userActions/IOU';
import CONST from '@src/CONST';
Expand All @@ -67,6 +67,7 @@ import usePersonalPolicy from './usePersonalPolicy';
import useSelfDMReport from './useSelfDMReport';
import useTheme from './useTheme';
import useThemeStyles from './useThemeStyles';
import useUndeleteTransactions from './useUndeleteTransactions';

type SearchHeaderOptionValue = DeepValueOf<typeof CONST.SEARCH.BULK_ACTION_TYPES> | undefined;

Expand Down Expand Up @@ -103,6 +104,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const personalPolicy = usePersonalPolicy();
const [userBillingGraceEndPeriods] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const undeleteTransactions = useUndeleteTransactions();

// Cache the last search results that had data, so the merge option remains available
// while results are temporarily unset (e.g. during sorting/loading).
Expand Down Expand Up @@ -143,6 +145,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
'Exclamation',
'MoneyBag',
'ArrowSplit',
'RotateLeft',
'QBOSquare',
'XeroSquare',
'NetSuiteSquare',
Expand Down Expand Up @@ -681,6 +684,23 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
return CONST.EMPTY_ARRAY as unknown as Array<DropdownOption<SearchHeaderOptionValue>>;
}

const allSelectedAreDeleted = selectedTransactionsKeys.length > 0 && selectedTransactionsKeys.every((id) => isDeletedTransaction(selectedTransactions[id] ?? {}));

if (allSelectedAreDeleted) {
return [
{
icon: expensifyIcons.RotateLeft,
text: translate('search.bulkActions.undelete'),
value: CONST.SEARCH.BULK_ACTION_TYPES.UNDELETE,
shouldCloseModalOnSelect: true,
onSelected: () => {
undeleteTransactions(selectedTransactionsKeys);
clearSelectedTransactions();
},
},
];
}

const options: Array<DropdownOption<SearchHeaderOptionValue>> = [];
const isAnyTransactionOnHold = Object.values(selectedTransactions).some((transaction) => transaction.isHeld);

Expand Down Expand Up @@ -1153,6 +1173,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
expensifyIcons.DocumentMerge,
expensifyIcons.ArrowSplit,
expensifyIcons.Trashcan,
expensifyIcons.RotateLeft,
expensifyIcons.Exclamation,
translate,
areAllMatchingItemsSelected,
Expand Down Expand Up @@ -1194,6 +1215,8 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
firstTransaction,
firstTransactionPolicy,
handleDeleteSelectedTransactions,
undeleteTransactions,
currentUserPersonalDetails?.email,
theme.icon,
styles.colorMuted,
styles.fontWeightNormal,
Expand Down
33 changes: 33 additions & 0 deletions src/hooks/useUndeleteTransactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {changeTransactionsReport} from '@libs/actions/Transaction';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import useAllTransactions from './useAllTransactions';
import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
import useLocalize from './useLocalize';
import useOnyx from './useOnyx';
import usePermissions from './usePermissions';

function useUndeleteTransactions() {
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const allTransactions = useAllTransactions();
const {isBetaEnabled} = usePermissions();
const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
const {translate, toLocaleDigit} = useLocalize();
const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${personalPolicyID}`);

return (transactionIDs: string[]) => {
changeTransactionsReport({
transactionIDs,
isASAPSubmitBetaEnabled,
accountID: currentUserPersonalDetails.accountID ?? CONST.DEFAULT_NUMBER_ID,
email: currentUserPersonalDetails.email ?? '',
policy,
allTransactions,
translate,
toLocaleDigit,
});
};
}

export default useUndeleteTransactions;
2 changes: 2 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1605,6 +1605,7 @@ const translations: TranslationDeepObject<typeof en> = {
failedToApproveViaDEW: (reason: string) => `Genehmigung fehlgeschlagen. ${reason}`,
cannotDuplicateDistanceExpense:
'Sie können Entfernungsausgaben nicht über mehrere Arbeitsbereiche hinweg duplizieren, da sich die Sätze zwischen den Arbeitsbereichen unterscheiden können.',
deleted: 'Gelöscht',
},
transactionMerge: {
listPage: {
Expand Down Expand Up @@ -7327,6 +7328,7 @@ Fordern Sie Spesendetails wie Belege und Beschreibungen an, legen Sie Limits und
unhold: 'Zurückhalten aufheben',
reject: 'Ablehnen',
noOptionsAvailable: 'Für die ausgewählte Ausgabengruppe sind keine Optionen verfügbar.',
undelete: 'Wiederherstellen',
},
filtersHeader: 'Filter',
filters: {
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,7 @@ const translations = {
}),
settledExpensify: 'Paid',
done: 'Done',
deleted: 'Deleted',
settledElsewhere: 'Paid elsewhere',
individual: 'Individual',
business: 'Business',
Expand Down Expand Up @@ -7316,6 +7317,7 @@ const translations = {
hold: 'Hold',
unhold: 'Remove hold',
reject: 'Reject',
undelete: 'Undelete',
noOptionsAvailable: 'No options available for the selected group of expenses.',
},
filtersHeader: 'Filters',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,7 @@ const translations: TranslationDeepObject<typeof en> = {
}),
settledExpensify: 'Pagado',
done: 'Listo',
deleted: 'Eliminado',
settledElsewhere: 'Pagado de otra forma',
individual: 'Individual',
business: 'Empresa',
Expand Down Expand Up @@ -7218,6 +7219,7 @@ ${amount} para ${merchant} - ${date}`,
hold: 'Retener',
unhold: 'Desbloquear',
reject: 'Rechazar',
undelete: 'Restaurar',
noOptionsAvailable: 'No hay opciones disponibles para el grupo de gastos seleccionado.',
},
filtersHeader: 'Filtros',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1610,6 +1610,7 @@ const translations: TranslationDeepObject<typeof en> = {
`impossible d’approuver via les <a href="${CONST.CONFIGURE_EXPENSE_REPORT_RULES_HELP_URL}">règles de l’espace de travail</a>. ${reason}`,
failedToApproveViaDEW: (reason: string) => `échec de l’approbation. ${reason}`,
cannotDuplicateDistanceExpense: 'Vous ne pouvez pas dupliquer des dépenses de distance entre espaces de travail, car les taux peuvent différer d’un espace de travail à l’autre.',
deleted: 'Supprimé',
},
transactionMerge: {
listPage: {
Expand Down Expand Up @@ -7351,6 +7352,7 @@ Rendez obligatoires des informations de dépense comme les reçus et les descrip
unhold: 'Supprimer la mise en attente',
reject: 'Rejeter',
noOptionsAvailable: 'Aucune option n’est disponible pour le groupe de dépenses sélectionné.',
undelete: 'Restaurer',
},
filtersHeader: 'Filtres',
filters: {
Expand Down
2 changes: 2 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1603,6 +1603,7 @@ const translations: TranslationDeepObject<typeof en> = {
`approvazione non riuscita tramite le <a href="${CONST.CONFIGURE_EXPENSE_REPORT_RULES_HELP_URL}">regole dello spazio di lavoro</a>. ${reason}`,
failedToApproveViaDEW: (reason: string) => `approvazione non riuscita. ${reason}`,
cannotDuplicateDistanceExpense: 'Non puoi duplicare le spese chilometriche tra diversi spazi di lavoro perché le tariffe potrebbero essere diverse.',
deleted: 'Eliminato',
},
transactionMerge: {
listPage: {
Expand Down Expand Up @@ -7315,6 +7316,7 @@ Richiedi dettagli sulle spese come ricevute e descrizioni, imposta limiti e valo
unhold: 'Rimuovi blocco',
reject: 'Rifiuta',
noOptionsAvailable: 'Nessuna opzione disponibile per il gruppo di spese selezionato.',
undelete: 'Ripristina',
},
filtersHeader: 'Filtri',
filters: {
Expand Down
Loading
Loading