Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4ab8d51
fix: Make Reject / Hold Messaging More Explicit
TaduJR Feb 8, 2026
577d269
fix: Use parent report to determine hold reason copy for submitter vs…
TaduJR Feb 8, 2026
e81fe75
fix: Add explainHoldApprover and update reasonPageDescription for all…
TaduJR Feb 8, 2026
d8c488f
fix: Determine submitter vs approver from selected transactions in bu…
TaduJR Feb 8, 2026
0f1aca2
chore: run prettier
TaduJR Feb 8, 2026
261aa31
fix: Use ownerAccountID selector and add explicit isApprover default
TaduJR Feb 9, 2026
c7650cf
refactor: Simplify isApprover computation and remove unnecessary useMemo
TaduJR Feb 9, 2026
5fc23b4
chore: run prettier
TaduJR Feb 9, 2026
862ad17
Merge branch 'main' of https://github.com/TaduJR/App into fix-Make-Re…
TaduJR Feb 13, 2026
22570e1
perf: Read allTransactions and allReportActions from Onyx inside hold…
TaduJR Feb 13, 2026
c25bf20
fix: Guard against undefined parentReportOwnerAccountID in isApprover…
TaduJR Feb 13, 2026
651bf9a
test: Add isApprover unit tests for HoldReasonFormView
TaduJR Feb 13, 2026
c9782fc
Merge branch 'main' of https://github.com/TaduJR/App into fix-Make-Re…
TaduJR Feb 18, 2026
dcbec2c
refactor: Rename isApprover prop to isSubmitter
TaduJR Feb 18, 2026
5c2cea5
Merge branch 'main' of https://github.com/TaduJR/App into fix-Make-Re…
TaduJR Feb 22, 2026
579e9e2
fix: Remove null guard and replace isCurrentUserSubmitter with direct…
TaduJR Feb 22, 2026
d78519c
Merge branch 'main' of https://github.com/TaduJR/App into fix-Make-Re…
TaduJR Feb 24, 2026
f2c5fdd
Merge branch 'main' of https://github.com/TaduJR/App into fix-Make-Re…
TaduJR Feb 25, 2026
67d85d5
fix: Remove canBeMissing option after upstream type change
TaduJR Feb 25, 2026
7ea1b5f
fix: Show generic submitter copy for mixed bulk hold selections per d…
TaduJR Feb 25, 2026
d778c7a
Merge branch 'main' of https://github.com/TaduJR/App into fix-Make-Re…
TaduJR Feb 26, 2026
94b5f9b
Merge branch 'main' of https://github.com/TaduJR/App into fix-Make-Re…
TaduJR Feb 26, 2026
6cb99a7
revert: Restore allTransactions and allReportActions as params
TaduJR Feb 26, 2026
74da034
fix: Remove unnecessary empty transactions check
TaduJR Feb 26, 2026
3c8c647
Merge branch 'main' of https://github.com/TaduJR/App into fix-Make-Re…
TaduJR Feb 26, 2026
3a5fd7a
chore: run prettier
TaduJR Feb 26, 2026
3702494
Merge branch 'main' of https://github.com/TaduJR/App into fix-Make-Re…
TaduJR Feb 26, 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
6 changes: 5 additions & 1 deletion src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1406,6 +1406,10 @@ const translations: TranslationDeepObject<typeof en> = {
one: 'Erkläre, warum du diese Ausgabe zurückhältst.',
other: 'Erklären Sie, warum Sie diese Ausgaben zurückhalten.',
}),
explainHoldApprover: () => ({
one: 'Erkläre, was du vor der Genehmigung dieser Ausgabe benötigst.',
other: 'Erkläre, was du vor der Genehmigung dieser Ausgaben benötigst.',
}),
retracted: 'zurückgezogen',
retract: 'Zurückziehen',
reopened: 'wiedereröffnet',
Expand Down Expand Up @@ -1498,7 +1502,7 @@ const translations: TranslationDeepObject<typeof en> = {
heldExpenseLeftBehindTitle: 'Zurückgehaltene Ausgaben werden ausgelassen, wenn du einen gesamten Bericht genehmigst.',
rejectExpenseTitle: 'Lehne eine Ausgabe ab, die du nicht genehmigen oder bezahlen möchtest.',
reasonPageTitle: 'Ausgabe ablehnen',
reasonPageDescription: 'Erkläre, warum du diese Ausgabe ablehnst.',
reasonPageDescription: 'Erkläre, warum du diese Ausgabe nicht genehmigen wirst.',
rejectReason: 'Ablehnungsgrund',
markAsResolved: 'Als gelöst markieren',
rejectedStatus: 'Diese Ausgabe wurde abgelehnt. Wir warten darauf, dass du die Probleme behebst und als gelöst markierst, um das Einreichen zu ermöglichen.',
Expand Down
6 changes: 5 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1424,6 +1424,10 @@ const translations = {
one: "Explain why you're holding this expense.",
other: "Explain why you're holding these expenses.",
}),
explainHoldApprover: () => ({
one: 'Explain what you need before approving this expense.',
other: 'Explain what you need before approving these expenses.',
}),
retracted: 'retracted',
retract: 'Retract',
reopened: 'reopened',
Expand Down Expand Up @@ -1516,7 +1520,7 @@ const translations = {
heldExpenseLeftBehindTitle: 'Held expenses are left behind when you approve an entire report.',
rejectExpenseTitle: "Reject an expense that you don't intend to approve or pay.",
reasonPageTitle: 'Reject expense',
reasonPageDescription: "Explain why you're rejecting this expense.",
reasonPageDescription: 'Explain why you will not approve this expense.',
rejectReason: 'Rejection reason',
markAsResolved: 'Mark as resolved',
rejectedStatus: 'This expense was rejected. Waiting on you to fix the issues and mark as resolved to enable submission.',
Expand Down
6 changes: 5 additions & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,10 @@ const translations: TranslationDeepObject<typeof en> = {
one: 'Explica la razón para retener esta solicitud.',
other: 'Explica la razón para retener estas solicitudes.',
}),
explainHoldApprover: () => ({
one: 'Explica qué necesitas antes de aprobar este gasto.',
other: 'Explica qué necesitas antes de aprobar estos gastos.',
}),
retract: 'Retractar',
reopened: 'reabrir',
reopenReport: 'Reabrir informe',
Expand Down Expand Up @@ -1349,7 +1353,7 @@ const translations: TranslationDeepObject<typeof en> = {
heldExpenseLeftBehindTitle: 'Los gastos retenidos se dejan atrás cuando apruebas un informe completo.',
rejectExpenseTitle: 'Rechaza un gasto que no tengos intención de aprobar o pagar.',
reasonPageTitle: 'Rechazar gasto',
reasonPageDescription: 'Explica por qué estás rechazando este gasto.',
reasonPageDescription: 'Explica por qué no aprobarás este gasto.',
rejectReason: 'Motivo del rechazo',
markAsResolved: 'Marcar como resuelto',
rejectedStatus: 'Este gasto fue rechazado. Estamos esperando que soluciones los problemas y lo marques como resuelto para poder enviarlo.',
Expand Down
6 changes: 5 additions & 1 deletion src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,10 @@ const translations: TranslationDeepObject<typeof en> = {
one: 'Expliquez pourquoi vous retenez cette dépense.',
other: 'Expliquez pourquoi vous retenez ces dépenses.',
}),
explainHoldApprover: () => ({
one: "Expliquez ce dont vous avez besoin avant d'approuver cette dépense.",
other: "Expliquez ce dont vous avez besoin avant d'approuver ces dépenses.",
}),
retracted: 'Retiré',
retract: 'Retirer',
reopened: 'rouvert',
Expand Down Expand Up @@ -1502,7 +1506,7 @@ const translations: TranslationDeepObject<typeof en> = {
heldExpenseLeftBehindTitle: 'Les dépenses en attente sont laissées de côté lorsque vous approuvez une note de frais entière.',
rejectExpenseTitle: 'Rejetez une dépense que vous n’avez pas l’intention d’approuver ou de payer.',
reasonPageTitle: 'Rejeter la dépense',
reasonPageDescription: 'Expliquez pourquoi vous rejetez cette dépense.',
reasonPageDescription: "Expliquez pourquoi vous n'approuverez pas cette dépense.",
rejectReason: 'Motif de rejet',
markAsResolved: 'Marquer comme résolu',
rejectedStatus: 'Cette dépense a été rejetée. Nous attendons que vous corrigiez les problèmes et la marquiez comme résolue pour permettre la soumission.',
Expand Down
6 changes: 5 additions & 1 deletion src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,10 @@ const translations: TranslationDeepObject<typeof en> = {
one: 'Spiega perché stai trattenendo questa spesa.',
other: 'Spiega perché stai trattenendo queste spese.',
}),
explainHoldApprover: () => ({
one: 'Spiega cosa ti serve prima di approvare questa spesa.',
other: 'Spiega cosa ti serve prima di approvare queste spese.',
}),
retracted: 'ritirato',
retract: 'Revoca',
reopened: 'riaperto',
Expand Down Expand Up @@ -1495,7 +1499,7 @@ const translations: TranslationDeepObject<typeof en> = {
heldExpenseLeftBehindTitle: 'Le spese in sospeso vengono escluse quando approvi l’intero report.',
rejectExpenseTitle: 'Rifiuta una spesa che non intendi approvare o pagare.',
reasonPageTitle: 'Rifiuta spesa',
reasonPageDescription: 'Spiega perché stai rifiutando questa spesa.',
reasonPageDescription: 'Spiega perché non approverai questa spesa.',
rejectReason: 'Motivo del rifiuto',
markAsResolved: 'Segna come risolto',
rejectedStatus: 'Questa spesa è stata rifiutata. In attesa che tu risolva i problemi e la contrassegni come risolta per consentirne l’invio.',
Expand Down
6 changes: 5 additions & 1 deletion src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,10 @@ const translations: TranslationDeepObject<typeof en> = {
one: 'この経費を保留している理由を説明してください。',
other: 'これらの経費を保留している理由を説明してください。',
}),
explainHoldApprover: () => ({
one: 'この経費を承認する前に必要なことを説明してください。',
other: 'これらの経費を承認する前に必要なことを説明してください。',
}),
retracted: '取り消し済み',
retract: '撤回',
reopened: '再開しました',
Expand Down Expand Up @@ -1485,7 +1489,7 @@ const translations: TranslationDeepObject<typeof en> = {
heldExpenseLeftBehindTitle: '保留中の経費は、レポート全体を承認してもそのまま残ります。',
rejectExpenseTitle: '承認や支払いを行う予定のない経費を却下します。',
reasonPageTitle: '経費を却下',
reasonPageDescription: 'この経費を却下する理由を説明してください。',
reasonPageDescription: 'この経費を承認しない理由を説明してください。',
rejectReason: '却下理由',
markAsResolved: '解決済みにする',
rejectedStatus: 'この経費は却下されました。問題を修正して解決済みにすると、再提出できるようになります。',
Expand Down
6 changes: 5 additions & 1 deletion src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1401,6 +1401,10 @@ const translations: TranslationDeepObject<typeof en> = {
one: 'Leg uit waarom je deze uitgave aanhoudt.',
other: 'Leg uit waarom je deze uitgaven vasthoudt.',
}),
explainHoldApprover: () => ({
one: 'Leg uit wat je nodig hebt voordat je deze uitgave goedkeurt.',
other: 'Leg uit wat je nodig hebt voordat je deze uitgaven goedkeurt.',
}),
retracted: 'ingetrokken',
retract: 'Intrekken',
reopened: 'heropend',
Expand Down Expand Up @@ -1493,7 +1497,7 @@ const translations: TranslationDeepObject<typeof en> = {
heldExpenseLeftBehindTitle: 'Vaste uitgaven blijven achter wanneer je een volledig rapport goedkeurt.',
rejectExpenseTitle: 'Wijs een uitgave af die je niet van plan bent goed te keuren of te betalen.',
reasonPageTitle: 'Uitgave afwijzen',
reasonPageDescription: 'Leg uit waarom je deze declaratie afwijst.',
reasonPageDescription: 'Leg uit waarom je deze uitgave niet zult goedkeuren.',
rejectReason: 'Reden van afwijzing',
markAsResolved: 'Markeren als opgelost',
rejectedStatus: 'Deze uitgave is afgewezen. We wachten tot jij de problemen oplost en als opgelost markeert, zodat je deze opnieuw kunt indienen.',
Expand Down
6 changes: 5 additions & 1 deletion src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,10 @@ const translations: TranslationDeepObject<typeof en> = {
one: 'Wyjaśnij, dlaczego wstrzymujesz ten wydatek.',
other: 'Wyjaśnij, dlaczego wstrzymujesz te wydatki.',
}),
explainHoldApprover: () => ({
one: 'Wyjaśnij, czego potrzebujesz przed zatwierdzeniem tego wydatku.',
other: 'Wyjaśnij, czego potrzebujesz przed zatwierdzeniem tych wydatków.',
}),
retracted: 'wycofano',
retract: 'Wycofaj',
reopened: 'ponownie otwarto',
Expand Down Expand Up @@ -1492,7 +1496,7 @@ const translations: TranslationDeepObject<typeof en> = {
heldExpenseLeftBehindTitle: 'Wstrzymane wydatki są pomijane, gdy zatwierdzasz cały raport.',
rejectExpenseTitle: 'Odrzuć wydatek, którego nie zamierzasz zatwierdzić ani opłacić.',
reasonPageTitle: 'Odrzuć wydatek',
reasonPageDescription: 'Wyjaśnij, dlaczego odrzucasz ten wydatek.',
reasonPageDescription: 'Wyjaśnij, dlaczego nie zatwierdzisz tego wydatku.',
rejectReason: 'Powód odrzucenia',
markAsResolved: 'Oznacz jako rozwiązane',
rejectedStatus: 'Ten wydatek został odrzucony. Czekamy, aż naprawisz problemy i oznaczysz go jako rozwiązany, aby umożliwić przesłanie.',
Expand Down
6 changes: 5 additions & 1 deletion src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1398,6 +1398,10 @@ const translations: TranslationDeepObject<typeof en> = {
one: 'Explique por que você está retendo esta despesa.',
other: 'Explique por que você está segurando essas despesas.',
}),
explainHoldApprover: () => ({
one: 'Explique o que você precisa antes de aprovar esta despesa.',
other: 'Explique o que você precisa antes de aprovar estas despesas.',
}),
retracted: 'retraído',
retract: 'Retrair',
reopened: 'reaberto',
Expand Down Expand Up @@ -1490,7 +1494,7 @@ const translations: TranslationDeepObject<typeof en> = {
heldExpenseLeftBehindTitle: 'Despesas retidas são deixadas para trás quando você aprova um relatório inteiro.',
rejectExpenseTitle: 'Rejeite uma despesa que você não pretende aprovar ou pagar.',
reasonPageTitle: 'Rejeitar despesa',
reasonPageDescription: 'Explique por que você está rejeitando esta despesa.',
reasonPageDescription: 'Explique por que você não aprovará esta despesa.',
rejectReason: 'Motivo da rejeição',
markAsResolved: 'Marcar como resolvido',
rejectedStatus: 'Esta despesa foi rejeitada. Estamos aguardando você corrigir os problemas e marcá-la como resolvida para permitir o envio.',
Expand Down
6 changes: 5 additions & 1 deletion src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1374,6 +1374,10 @@ const translations: TranslationDeepObject<typeof en> = {
one: '请解释你为何暂时保留这笔报销。',
other: '请说明你为何保留这些报销。',
}),
explainHoldApprover: () => ({
one: '请说明在批准这笔报销之前你需要什么。',
other: '请说明在批准这些报销之前你需要什么。',
}),
retracted: '已撤回',
retract: '撤回',
reopened: '已重新打开',
Expand Down Expand Up @@ -1464,7 +1468,7 @@ const translations: TranslationDeepObject<typeof en> = {
heldExpenseLeftBehindTitle: '当你批准整份报销报告时,已搁置的报销会被保留在报告之外。',
rejectExpenseTitle: '拒绝你不打算批准或报销的报销单。',
reasonPageTitle: '拒绝报销',
reasonPageDescription: '请说明你拒绝此报销的原因。',
reasonPageDescription: '请说明你为何不会批准这笔报销。',
rejectReason: '拒绝原因',
markAsResolved: '标记为已解决',
rejectedStatus: '该报销已被拒绝。请先修复问题并标记为已解决,然后才能提交。',
Expand Down
8 changes: 7 additions & 1 deletion src/pages/Search/SearchHoldReasonPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
import {useSearchActionsContext, useSearchStateContext} from '@components/Search/SearchContext';
import useAncestors from '@hooks/useAncestors';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import {clearErrorFields, clearErrors} from '@libs/actions/FormActions';
Expand All @@ -26,10 +27,14 @@ function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) {
const {backTo = '', reportID} = route.params ?? {};
Comment thread
TaduJR marked this conversation as resolved.
Comment thread
TaduJR marked this conversation as resolved.
const {selectedTransactionIDs, selectedTransactions, currentSearchHash} = useSearchStateContext();
const {clearSelectedTransactions} = useSearchActionsContext();
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION);
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const ancestors = useAncestors(report);

const selectedTransactionsList = Object.values(selectedTransactions);
const isSubmitter = report ? report.ownerAccountID === currentUserAccountID : selectedTransactionsList.some((t) => t.ownerAccountID === currentUserAccountID);

const ancestors = useAncestors(report);
const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
Expand Down Expand Up @@ -91,6 +96,7 @@ function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) {
validate={validate}
expenseCount={expenseCount}
backTo={backTo}
isSubmitter={isSubmitter}
/>
);
}
Expand Down
7 changes: 5 additions & 2 deletions src/pages/iou/HoldReasonFormView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ type HoldReasonFormViewProps = {

/** Number of expenses being held (defaults to 1) */
expenseCount?: number;

Comment thread
TaduJR marked this conversation as resolved.
/** Whether the current user is the submitter of the expense */
isSubmitter?: boolean;
};

function HoldReasonFormView({backTo, validate, onSubmit, expenseCount = 1}: HoldReasonFormViewProps) {
function HoldReasonFormView({backTo, validate, onSubmit, expenseCount = 1, isSubmitter = true}: HoldReasonFormViewProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {inputCallbackRef} = useAutoFocusInput();
Expand All @@ -53,7 +56,7 @@ function HoldReasonFormView({backTo, validate, onSubmit, expenseCount = 1}: Hold
enabledWhenOffline
shouldHideFixErrorsAlert
>
<Text style={styles.mb6}>{translate('iou.explainHold', {count: expenseCount})}</Text>
<Text style={styles.mb6}>{translate(isSubmitter ? 'iou.explainHold' : 'iou.explainHoldApprover', {count: expenseCount})}</Text>
<View>
<InputWrapper
InputComponent={TextInput}
Expand Down
7 changes: 7 additions & 0 deletions src/pages/iou/HoldReasonPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {getReportOwnerAccountID} from '@selectors/Report';
import React, {useCallback, useEffect} from 'react';
import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider';
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
import useAncestors from '@hooks/useAncestors';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import {putOnHold} from '@libs/actions/IOU/Hold';
Expand All @@ -24,15 +26,19 @@ type HoldReasonPageProps =

function HoldReasonPage({route}: HoldReasonPageProps) {
const {translate} = useLocalize();
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();

Comment thread
TaduJR marked this conversation as resolved.
const {transactionID, reportID, backTo} = route.params;

const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const ancestors = useAncestors(report);

const [parentReportOwnerAccountID] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`, {selector: getReportOwnerAccountID});

// We first check if the report is part of a policy - if not, then it's a personal request (1:1 request)
// For personal requests, we need to allow both users to put the request on hold
const isWorkspaceRequest = isReportInGroupPolicy(report);
const isSubmitter = parentReportOwnerAccountID === currentUserAccountID;
const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID);

const {isDelegateAccessRestricted} = useDelegateNoAccessState();
Expand Down Expand Up @@ -85,6 +91,7 @@ function HoldReasonPage({route}: HoldReasonPageProps) {
onSubmit={onSubmit}
validate={validate}
backTo={backTo}
isSubmitter={isSubmitter}
/>
);
}
Expand Down
6 changes: 5 additions & 1 deletion src/selectors/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ function getReportPolicyID(report: OnyxEntry<Report>) {
return report?.policyID;
}

export {getArchiveReason, getReportPolicyID};
function getReportOwnerAccountID(report: OnyxEntry<Report>) {
Comment thread
TaduJR marked this conversation as resolved.
return report?.ownerAccountID;
}

export {getArchiveReason, getReportOwnerAccountID, getReportPolicyID};
Loading
Loading