Skip to content

Commit 8c054c2

Browse files
committed
Add basic version of MoneyRequestReportView with basic transaction list and chat
1 parent 1743a93 commit 8c054c2

16 files changed

+557
-118
lines changed

src/CONST.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,11 @@ const CONST = {
13221322
},
13231323
THREAD_DISABLED: ['CREATED'],
13241324
},
1325+
TRANSACTION_LIST: {
1326+
COLUMNS: {
1327+
COMMENTS: 'comments',
1328+
},
1329+
},
13251330
CANCEL_PAYMENT_REASONS: {
13261331
ADMIN: 'CANCEL_REASON_ADMIN',
13271332
},
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React from 'react';
2+
import {View} from 'react-native';
3+
import type {SearchColumnType, SortOrder} from '@components/Search/types';
4+
import SortableTableHeader from '@components/SelectionList/SortableTableHeader';
5+
import useResponsiveLayout from '@hooks/useResponsiveLayout';
6+
import useThemeStyles from '@hooks/useThemeStyles';
7+
import CONST from '@src/CONST';
8+
import type {TranslationPaths} from '@src/languages/types';
9+
10+
type ColumnConfig = {
11+
columnName: SearchColumnType | typeof CONST.REPORT.TRANSACTION_LIST.COLUMNS.COMMENTS;
12+
translationKey: TranslationPaths | undefined;
13+
isColumnSortable?: boolean;
14+
};
15+
16+
const columnConfig: ColumnConfig[] = [
17+
{
18+
columnName: CONST.SEARCH.TABLE_COLUMNS.RECEIPT,
19+
translationKey: 'common.receipt',
20+
isColumnSortable: false,
21+
},
22+
{
23+
columnName: CONST.SEARCH.TABLE_COLUMNS.TYPE,
24+
translationKey: 'common.type',
25+
isColumnSortable: false,
26+
},
27+
{
28+
columnName: CONST.SEARCH.TABLE_COLUMNS.DATE,
29+
translationKey: 'common.date',
30+
},
31+
{
32+
columnName: CONST.SEARCH.TABLE_COLUMNS.MERCHANT,
33+
translationKey: 'common.merchant',
34+
},
35+
{
36+
columnName: CONST.SEARCH.TABLE_COLUMNS.CATEGORY,
37+
translationKey: 'common.category',
38+
},
39+
{
40+
columnName: CONST.SEARCH.TABLE_COLUMNS.TAG,
41+
translationKey: 'common.tag',
42+
},
43+
{
44+
columnName: CONST.REPORT.TRANSACTION_LIST.COLUMNS.COMMENTS,
45+
translationKey: undefined, // comments have no title displayed
46+
isColumnSortable: false,
47+
},
48+
{
49+
columnName: CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT,
50+
translationKey: 'common.total',
51+
},
52+
];
53+
54+
type SearchTableHeaderProps = {
55+
sortBy?: SearchColumnType;
56+
sortOrder?: SortOrder;
57+
onSortPress: (column: SearchColumnType | typeof CONST.REPORT.TRANSACTION_LIST.COLUMNS.COMMENTS, order: SortOrder) => void;
58+
shouldShowSorting: boolean;
59+
};
60+
61+
// At this moment with new Report View we have no extra logic for displaying columns
62+
const shouldShowColumn = () => true;
63+
64+
function MoneyRequestReportTableHeader({sortBy, sortOrder, onSortPress, shouldShowSorting}: SearchTableHeaderProps) {
65+
const styles = useThemeStyles();
66+
const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
67+
const displayNarrowVersion = isMediumScreenWidth || shouldUseNarrowLayout;
68+
69+
if (displayNarrowVersion) {
70+
return;
71+
}
72+
73+
return (
74+
<View style={[styles.ph8, styles.pv3]}>
75+
<SortableTableHeader
76+
columns={columnConfig}
77+
shouldShowColumn={shouldShowColumn}
78+
dateColumnSize="normal"
79+
shouldShowSorting={shouldShowSorting}
80+
sortBy={sortBy}
81+
sortOrder={sortOrder}
82+
onSortPress={onSortPress}
83+
/>
84+
</View>
85+
);
86+
}
87+
88+
MoneyRequestReportTableHeader.displayName = 'MoneyRequestReportTableHeader';
89+
90+
export default MoneyRequestReportTableHeader;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from 'react';
2+
import {View} from 'react-native';
3+
import type {OnyxEntry} from 'react-native-onyx';
4+
import TransactionItemRow from '@components/TransactionItemRow';
5+
import useThemeStyles from '@hooks/useThemeStyles';
6+
import type * as OnyxTypes from '@src/types/onyx';
7+
import MoneyRequestReportTableHeader from './MoneyRequestReportTableHeader';
8+
9+
type MoneyRequestReportTransactionListProps = {
10+
/** The report */
11+
report: OnyxEntry<OnyxTypes.Report>;
12+
13+
/** List of transactions belonging to one report */
14+
transactions: OnyxTypes.Transaction[];
15+
};
16+
17+
/**
18+
* TODO
19+
* This component is under construction and not yet displayed to any users.
20+
*/
21+
function MoneyRequestReportTransactionList({report, transactions}: MoneyRequestReportTransactionListProps) {
22+
const styles = useThemeStyles();
23+
24+
return (
25+
<>
26+
<MoneyRequestReportTableHeader
27+
shouldShowSorting
28+
sortBy="date"
29+
sortOrder="desc"
30+
onSortPress={() => {}}
31+
/>
32+
<View style={[styles.pv2, styles.ph5]}>
33+
{transactions.map((transaction) => {
34+
return (
35+
<View style={[styles.flex1, styles.mb2]}>
36+
<TransactionItemRow
37+
transactionItem={transaction}
38+
isSelected={false}
39+
shouldShowTooltip
40+
shouldUseNarrowLayout={false}
41+
/>
42+
</View>
43+
);
44+
})}
45+
</View>
46+
</>
47+
);
48+
}
49+
50+
MoneyRequestReportTransactionList.displayName = 'MoneyRequestReportTransactionList';
51+
52+
export default MoneyRequestReportTransactionList;
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import type {ListRenderItemInfo} from '@react-native/virtualized-lists/Lists/VirtualizedList';
2+
import React, {useCallback, useMemo} from 'react';
3+
import {View} from 'react-native';
4+
import type {OnyxEntry} from 'react-native-onyx';
5+
import {useOnyx} from 'react-native-onyx';
6+
import FlatList from '@components/FlatList';
7+
import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView';
8+
import usePaginatedReportActions from '@hooks/usePaginatedReportActions';
9+
import useThemeStyles from '@hooks/useThemeStyles';
10+
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
11+
import {
12+
getMostRecentIOURequestActionID,
13+
getOneTransactionThreadReportID,
14+
getSortedReportActionsForDisplay,
15+
isConsecutiveActionMadeByPreviousActor,
16+
isConsecutiveChronosAutomaticTimerAction,
17+
isDeletedParentAction,
18+
shouldReportActionBeVisible,
19+
} from '@libs/ReportActionsUtils';
20+
import {canUserPerformWriteAction, chatIncludesChronosWithID} from '@libs/ReportUtils';
21+
import ReportActionsListItemRenderer from '@pages/home/report/ReportActionsListItemRenderer';
22+
import CONST from '@src/CONST';
23+
import ONYXKEYS from '@src/ONYXKEYS';
24+
import type * as OnyxTypes from '@src/types/onyx';
25+
import type Transaction from '@src/types/onyx/Transaction';
26+
import {isEmptyObject} from '@src/types/utils/EmptyObject';
27+
import MoneyRequestReportTransactionList from './MoneyRequestReportTransactionList';
28+
29+
type TemporaryMoneyRequestReportViewProps = {
30+
/** The report */
31+
report: OnyxEntry<OnyxTypes.Report>;
32+
};
33+
34+
function getParentReportAction(parentReportActions: OnyxEntry<OnyxTypes.ReportActions>, parentReportActionID: string | undefined): OnyxEntry<OnyxTypes.ReportAction> {
35+
if (!parentReportActions || !parentReportActionID) {
36+
return;
37+
}
38+
return parentReportActions[parentReportActionID];
39+
}
40+
41+
/**
42+
* TODO
43+
* This component is under construction and not yet displayed to any users.
44+
*/
45+
function MoneyRequestReportView({report}: TemporaryMoneyRequestReportViewProps) {
46+
const styles = useThemeStyles();
47+
48+
const reportID = report?.reportID;
49+
50+
// const [accountManagerReportID] = useOnyx(ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID);
51+
// const [accountManagerReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(accountManagerReportID)}`);
52+
// const [userLeavingStatus = false] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`);
53+
const [reportOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {allowStaleData: true});
54+
// const [reportNameValuePairsOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`, {allowStaleData: true});
55+
const [parentReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(reportOnyx?.parentReportID)}`, {
56+
canEvict: false,
57+
selector: (parentReportActions) => getParentReportAction(parentReportActions, reportOnyx?.parentReportActionID),
58+
});
59+
60+
const {reportActions, linkedAction, sortedAllReportActions, hasNewerActions, hasOlderActions} = usePaginatedReportActions(reportID);
61+
62+
const mostRecentIOUReportActionID = useMemo(() => getMostRecentIOURequestActionID(reportActions), [reportActions]);
63+
64+
const transactionThreadReportID = getOneTransactionThreadReportID(reportID, reportActions ?? [], false);
65+
const [transactionThreadReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID ?? CONST.DEFAULT_NUMBER_ID}`, {
66+
selector: (actions: OnyxEntry<OnyxTypes.ReportActions>) => getSortedReportActionsForDisplay(actions, canUserPerformWriteAction(report), true),
67+
});
68+
69+
const [transactions = {}] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION);
70+
71+
const isOffline = false;
72+
73+
const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID ?? CONST.DEFAULT_NUMBER_ID}`);
74+
75+
const actionsChatItem = reportActions.filter((ra) => {
76+
return ra.actionName !== 'IOU' && ra.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED;
77+
});
78+
79+
// // Get a sorted array of reportActions for both the current report and the transaction thread report associated with this report (if there is one)
80+
// // so that we display transaction-level and report-level report actions in order in the one-transaction view
81+
// const reportActions2 = useMemo(
82+
// () => (reportActionsToDisplay ? getCombinedReportActions(reportActionsToDisplay, transactionThreadReportID ?? null, transactionThreadReportActions ?? []) : []),
83+
// [reportActionsToDisplay, transactionThreadReportActions, transactionThreadReportID],
84+
// );
85+
86+
/* Thread's divider line should hide when the first chat in the thread is marked as unread.
87+
* This is so that it will not be conflicting with header's separator line.
88+
*/
89+
// const shouldHideThreadDividerLine = useMemo(
90+
// (): boolean => getFirstVisibleReportActionID(reportActions, isOffline) === unreadMarkerReportActionID,
91+
// [reportActions, isOffline, unreadMarkerReportActionID],
92+
// );
93+
94+
const parentReportActionForTransactionThread = useMemo(
95+
() =>
96+
isEmptyObject(transactionThreadReportActions)
97+
? undefined
98+
: (reportActions?.find((action) => action.reportActionID === transactionThreadReport?.parentReportActionID) as OnyxEntry<OnyxTypes.ReportAction>),
99+
[reportActions, transactionThreadReportActions, transactionThreadReport?.parentReportActionID],
100+
);
101+
102+
const canPerformWriteAction = canUserPerformWriteAction(report);
103+
const visibleReportActions = useMemo(() => {
104+
const filteredActions = actionsChatItem.filter(
105+
(reportAction) =>
106+
(isOffline || isDeletedParentAction(reportAction) || reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || reportAction.errors) &&
107+
shouldReportActionBeVisible(reportAction, reportAction.reportActionID, canPerformWriteAction),
108+
);
109+
110+
return [...filteredActions].toReversed();
111+
}, [actionsChatItem, isOffline, canPerformWriteAction]);
112+
113+
const transactionList = Object.values(transactions).filter((transaction): transaction is Transaction => {
114+
return transaction?.reportID === reportID;
115+
});
116+
117+
const renderItem = useCallback(
118+
({item: reportAction, index}: ListRenderItemInfo<OnyxTypes.ReportAction>) => (
119+
<ReportActionsListItemRenderer
120+
reportAction={reportAction}
121+
parentReportAction={parentReportAction}
122+
parentReportActionForTransactionThread={parentReportActionForTransactionThread}
123+
index={index}
124+
report={report}
125+
transactionThreadReport={transactionThreadReport}
126+
displayAsGroup={
127+
!isConsecutiveChronosAutomaticTimerAction(visibleReportActions, index, chatIncludesChronosWithID(reportAction?.reportID)) &&
128+
isConsecutiveActionMadeByPreviousActor(visibleReportActions, index)
129+
}
130+
mostRecentIOUReportActionID={mostRecentIOUReportActionID}
131+
shouldHideThreadDividerLine
132+
shouldDisplayNewMarker={false}
133+
// shouldHideThreadDividerLine={shouldHideThreadDividerLine}
134+
// shouldDisplayNewMarker={reportAction.reportActionID === unreadMarkerReportActionID}
135+
// shouldUseThreadDividerLine={shouldUseThreadDividerLine}
136+
shouldDisplayReplyDivider={visibleReportActions.length > 1}
137+
isFirstVisibleReportAction={false}
138+
/>
139+
),
140+
[
141+
report,
142+
visibleReportActions,
143+
mostRecentIOUReportActionID,
144+
parentReportAction,
145+
transactionThreadReport,
146+
parentReportActionForTransactionThread,
147+
// unreadMarkerReportActionID,
148+
// shouldHideThreadDividerLine,
149+
// shouldUseThreadDividerLine,
150+
// firstVisibleReportActionID,
151+
],
152+
);
153+
154+
const listHeaderComponent = (
155+
<MoneyRequestReportTransactionList
156+
transactions={transactionList}
157+
report={report}
158+
/>
159+
);
160+
161+
return (
162+
<View style={styles.flex1}>
163+
{report ? (
164+
<FlatList
165+
accessibilityLabel="Test"
166+
testID="report-actions-list"
167+
style={styles.overscrollBehaviorContain}
168+
data={visibleReportActions}
169+
renderItem={renderItem}
170+
keyExtractor={(item) => item.reportActionID}
171+
initialNumToRender={10}
172+
// onEndReached={onEndReached}
173+
onEndReachedThreshold={0.75}
174+
// onStartReached={onStartReached}
175+
onStartReachedThreshold={0.75}
176+
ListHeaderComponent={listHeaderComponent}
177+
keyboardShouldPersistTaps="handled"
178+
// onLayout={onLayoutInner}
179+
// onContentSizeChange={onContentSizeChangeInner}
180+
// onScroll={trackVerticalScrolling}
181+
// onScrollToIndexFailed={onScrollToIndexFailed}
182+
// extraData={extraData}
183+
// key={listID}
184+
// shouldEnableAutoScrollToTopThreshold={shouldEnableAutoScrollToTopThreshold}
185+
// initialScrollKey={reportActionID}
186+
/>
187+
) : (
188+
<ReportActionsSkeletonView />
189+
)}
190+
</View>
191+
);
192+
}
193+
194+
MoneyRequestReportView.displayName = 'MoneyRequestReportView';
195+
196+
export default MoneyRequestReportView;

src/components/Search/index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import SelectionListWithModal from '@components/SelectionListWithModal';
1010
import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton';
1111
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
1212
import useNetwork from '@hooks/useNetwork';
13+
import usePermissions from '@hooks/usePermissions';
1314
import usePrevious from '@hooks/usePrevious';
1415
import useResponsiveLayout from '@hooks/useResponsiveLayout';
1516
import useSearchHighlightAndScroll from '@hooks/useSearchHighlightAndScroll';
@@ -150,6 +151,7 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS
150151
const previousReportActions = usePrevious(reportActions);
151152
const shouldGroupByReports = groupBy === CONST.SEARCH.GROUP_BY.REPORTS;
152153

154+
const {canUseTableReportView} = usePermissions();
153155
const canSelectMultiple = isSmallScreenWidth ? !!selectionMode?.isEnabled : true;
154156

155157
useEffect(() => {
@@ -421,6 +423,11 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS
421423

422424
const backTo = Navigation.getActiveRoute();
423425

426+
if (canUseTableReportView && isReportListItemType(item)) {
427+
Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID, backTo}));
428+
return;
429+
}
430+
424431
if (isReportActionListItemType(item)) {
425432
const reportActionID = item.reportActionID;
426433
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, reportActionID, backTo}));

src/components/SelectionList/Search/ReportListItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ function ReportListItem<TItem extends ListItem>({
196196
</View>
197197
</View>
198198
{isLargeScreenWidth && (
199-
<View style={StyleUtils.getSearchTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.ACTION)}>
199+
<View style={StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.ACTION)}>
200200
<ActionCell
201201
action={reportItem.action}
202202
goToItem={handleOnButtonPress}

0 commit comments

Comments
 (0)