diff --git a/assets/images/bed.svg b/assets/images/bed.svg new file mode 100644 index 000000000000..fd654c036a7c --- /dev/null +++ b/assets/images/bed.svg @@ -0,0 +1 @@ + diff --git a/assets/images/car-with-key.svg b/assets/images/car-with-key.svg new file mode 100644 index 000000000000..1586c0dfecfa --- /dev/null +++ b/assets/images/car-with-key.svg @@ -0,0 +1 @@ + diff --git a/assets/images/plane.svg b/assets/images/plane.svg new file mode 100644 index 000000000000..bf4d56875239 --- /dev/null +++ b/assets/images/plane.svg @@ -0,0 +1 @@ + diff --git a/src/CONST.ts b/src/CONST.ts index 6e5ddc92aa7a..d00cc5e04540 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -50,6 +50,7 @@ const KEYBOARD_SHORTCUT_NAVIGATION_TYPE = 'NAVIGATION_SHORTCUT'; const chatTypes = { POLICY_ANNOUNCE: 'policyAnnounce', POLICY_ADMINS: 'policyAdmins', + TRIP_ROOM: 'tripRoom', GROUP: 'group', DOMAIN_ALL: 'domainAll', POLICY_ROOM: 'policyRoom', @@ -697,6 +698,7 @@ const CONST = { TASK_COMPLETED: 'TASKCOMPLETED', TASK_EDITED: 'TASKEDITED', TASK_REOPENED: 'TASKREOPENED', + TRIPPREVIEW: 'TRIPPREVIEW', UNAPPROVED: 'UNAPPROVED', // OldDot Action UNHOLD: 'UNHOLD', UNSHARE: 'UNSHARE', // OldDot Action @@ -4740,6 +4742,12 @@ const CONST = { INITIAL_URL: 'INITIAL_URL', }, + RESERVATION_TYPE: { + CAR: 'car', + HOTEL: 'hotel', + FLIGHT: 'flight', + }, + DOT_SEPARATOR: '•', DEFAULT_TAX: { diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index bcc40947a83a..0995d7271ee6 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -19,6 +19,7 @@ import NotificationsAvatar from '@assets/images/avatars/notifications-avatar.svg import ActiveRoomAvatar from '@assets/images/avatars/room.svg'; import BackArrow from '@assets/images/back-left.svg'; import Bank from '@assets/images/bank.svg'; +import Bed from '@assets/images/bed.svg'; import Bell from '@assets/images/bell.svg'; import BellSlash from '@assets/images/bellSlash.svg'; import Bill from '@assets/images/bill.svg'; @@ -28,6 +29,7 @@ import Bug from '@assets/images/bug.svg'; import Building from '@assets/images/building.svg'; import Calendar from '@assets/images/calendar.svg'; import Camera from '@assets/images/camera.svg'; +import CarWithKey from '@assets/images/car-with-key.svg'; import Car from '@assets/images/car.svg'; import CardsAndDomains from '@assets/images/cards-and-domains.svg'; import Cash from '@assets/images/cash.svg'; @@ -124,6 +126,7 @@ import Paycheck from '@assets/images/paycheck.svg'; import Pencil from '@assets/images/pencil.svg'; import Phone from '@assets/images/phone.svg'; import Pin from '@assets/images/pin.svg'; +import Plane from '@assets/images/plane.svg'; import Play from '@assets/images/play.svg'; import Plus from '@assets/images/plus.svg'; import Printer from '@assets/images/printer.svg'; @@ -344,6 +347,9 @@ export { ChatBubbleUnread, ChatBubbleReply, Lightbulb, + Plane, + Bed, + CarWithKey, DocumentPlus, Clear, }; diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index b6f378763659..7fd5024f6734 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -1,6 +1,6 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import type {ImageContentFit} from 'expo-image'; -import type {ReactNode} from 'react'; +import type {ReactElement, ReactNode} from 'react'; import React, {forwardRef, useContext, useMemo} from 'react'; import type {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; @@ -105,6 +105,9 @@ type MenuItemBaseProps = { /** The fill color to pass into the secondary icon. */ secondaryIconFill?: string; + /** Whether the secondary icon should have hover style */ + isSecondaryIconHoverable?: boolean; + /** Icon Width */ iconWidth?: number; @@ -182,6 +185,12 @@ type MenuItemBaseProps = { /** Text to display for the item */ title?: string; + /** Component to display as the title */ + titleComponent?: ReactElement; + + /** Any additional styles to apply to the container for title components */ + titleContainerStyle?: StyleProp; + /** A right-aligned subtitle for this menu option */ subtitle?: string | number; @@ -300,6 +309,7 @@ function MenuItem( secondaryIcon, secondaryIconFill, iconType = CONST.ICON_TYPE_ICON, + isSecondaryIconHoverable = false, iconWidth, iconHeight, iconStyles, @@ -321,6 +331,8 @@ function MenuItem( focused = false, disabled = false, title, + titleComponent, + titleContainerStyle, subtitle, shouldShowBasicTitle, label, @@ -554,7 +566,7 @@ function MenuItem( )} {secondaryIcon && ( - + )} - + {!!description && shouldShowDescriptionOnTop && ( )} - - {!!title && (shouldRenderAsHTML || (shouldParseTitle && !!html.length)) && ( - - - - )} - {!shouldRenderAsHTML && !shouldParseTitle && !!title && ( - - {renderTitleContent()} - - )} - {shouldShowTitleIcon && titleIcon && ( - - - - )} - + {(!!title || !!shouldShowTitleIcon) && ( + + {!!title && (shouldRenderAsHTML || (shouldParseTitle && !!html.length)) && ( + + + + )} + {!shouldRenderAsHTML && !shouldParseTitle && !!title && ( + + {renderTitleContent()} + + )} + {shouldShowTitleIcon && titleIcon && ( + + + + )} + + )} {!!description && !shouldShowDescriptionOnTop && ( )} + {titleComponent} diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx new file mode 100644 index 000000000000..999ef4345e5b --- /dev/null +++ b/src/components/ReportActionItem/TripDetailsView.tsx @@ -0,0 +1,171 @@ +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +import Icon from '@components/Icon'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import SpacerView from '@components/SpacerView'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import DateUtils from '@libs/DateUtils'; +import variables from '@styles/variables'; +import * as Expensicons from '@src/components/Icon/Expensicons'; +import CONST from '@src/CONST'; +import * as ReportUtils from '@src/libs/ReportUtils'; +import * as TripReservationUtils from '@src/libs/TripReservationUtils'; +import type {Reservation, ReservationTimeDetails} from '@src/types/onyx/Transaction'; + +type TripDetailsViewProps = { + /** The active tripRoomReportID, used for Onyx subscription */ + tripRoomReportID?: string; + + /** Whether we should display the horizontal rule below the component */ + shouldShowHorizontalRule: boolean; +}; + +type ReservationViewProps = { + reservation: Reservation; +}; + +function ReservationView({reservation}: ReservationViewProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + + const reservationIcon = TripReservationUtils.getTripReservationIcon(reservation.type); + + const formatAirportInfo = (reservationTimeDetails: ReservationTimeDetails) => { + const longName = reservationTimeDetails?.longName ? `${reservationTimeDetails?.longName} ` : ''; + let shortName = reservationTimeDetails?.shortName ? `${reservationTimeDetails?.shortName}` : ''; + + shortName = longName && shortName ? `(${shortName})` : shortName; + + return `${longName}${shortName}`; + }; + + const getFormattedDate = () => { + switch (reservation.type) { + case CONST.RESERVATION_TYPE.FLIGHT: + return DateUtils.getFormattedTransportDate(new Date(reservation.start.date)); + case CONST.RESERVATION_TYPE.HOTEL: + case CONST.RESERVATION_TYPE.CAR: + return DateUtils.getFormattedReservationRangeDate(new Date(reservation.start.date), new Date(reservation.end.date)); + default: + return DateUtils.formatToLongDateWithWeekday(new Date(reservation.start.date)); + } + }; + + const formattedDate = getFormattedDate(); + + const bottomDescription = useMemo(() => { + const code = `${reservation.confirmations && reservation.confirmations?.length > 0 ? `${reservation.confirmations[0].value} • ` : ''}`; + if (reservation.type === CONST.RESERVATION_TYPE.FLIGHT) { + const longName = reservation.company?.longName ? `${reservation.company?.longName} • ` : ''; + const shortName = reservation?.company?.shortName ? `${reservation?.company?.shortName} ` : ''; + return `${code}${longName}${shortName}${reservation.route?.number}`; + } + if (reservation.type === CONST.RESERVATION_TYPE.HOTEL) { + return `${code}${reservation.start.address}`; + } + if (reservation.type === CONST.RESERVATION_TYPE.CAR) { + const vendor = reservation.vendor ? `${reservation.vendor} • ` : ''; + return `${vendor}${reservation.start.location}`; + } + return reservation.start.address ?? reservation.start.location; + }, [reservation]); + + const titleComponent = () => { + if (reservation.type === CONST.RESERVATION_TYPE.FLIGHT) { + return ( + + + {formatAirportInfo(reservation.start)} + + {formatAirportInfo(reservation.end)} + + {bottomDescription && {bottomDescription}} + + ); + } + + return ( + + + {reservation.type === CONST.RESERVATION_TYPE.CAR ? reservation.carInfo?.name : reservation.start.longName} + + {bottomDescription && {bottomDescription}} + + ); + }; + + return ( + {}} + iconHeight={20} + iconWidth={20} + iconStyles={[styles.tripReservationIconContainer, styles.mr3]} + secondaryIconFill={theme.icon} + hoverAndPressStyle={styles.hoveredComponentBG} + /> + ); +} + +function TripDetailsView({tripRoomReportID, shouldShowHorizontalRule}: TripDetailsViewProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const tripTransactions = ReportUtils.getTripTransactions(tripRoomReportID); + const reservations: Reservation[] = TripReservationUtils.getReservationsFromTripTransactions(tripTransactions); + + return ( + + + + + {translate('travel.tripSummary')} + + + + <> + {reservations.map((reservation) => ( + + + + ))} + + + + ); +} + +TripDetailsView.displayName = 'TripDetailsView'; + +export default TripDetailsView; diff --git a/src/languages/en.ts b/src/languages/en.ts index 8d1b24211583..75574961c1e8 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -224,6 +224,7 @@ export default { tomorrowAt: 'Tomorrow at', yesterdayAt: 'Yesterday at', conjunctionAt: 'at', + conjunctionTo: 'to', genericErrorMessage: 'Oops... something went wrong and your request could not be completed. Please try again later.', error: { invalidAmount: 'Invalid amount.', @@ -1878,6 +1879,13 @@ export default { agree: 'I agree to the travel ', error: 'You must accept the Terms & Conditions for travel to continue', }, + flight: 'Flight', + hotel: 'Hotel', + car: 'Car', + viewTrip: 'View trip', + trip: 'Trip', + tripSummary: 'Trip summary', + departs: 'Departs', }, workspace: { common: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 29f85edfc93e..39c6b6dc32b0 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -214,6 +214,7 @@ export default { tomorrowAt: 'Mañana a las', yesterdayAt: 'Ayer a las', conjunctionAt: 'a', + conjunctionTo: 'a', genericErrorMessage: 'Ups... algo no ha ido bien y la acción no se ha podido completar. Por favor, inténtalo más tarde.', error: { invalidAmount: 'Importe no válido.', @@ -1902,6 +1903,13 @@ export default { agree: 'Acepto los ', error: 'Debes aceptar los Términos y condiciones para que el viaje continúe', }, + flight: 'Vuelo', + hotel: 'Hotel', + car: 'Auto', + viewTrip: 'Ver viaje', + trip: 'Viaje', + tripSummary: 'Resumen del viaje', + departs: 'Sale', }, workspace: { common: { diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 8bd37ddd698d..50cb9a20dff6 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -15,8 +15,10 @@ import { isAfter, isBefore, isSameDay, + isSameMonth, isSameSecond, isSameYear, + isThisYear, isValid, parse, set, @@ -728,6 +730,73 @@ function getLastBusinessDayOfMonth(inputDate: Date): number { return getDate(currentDate); } +/** + * Returns a formatted date range from date 1 to date 2. + * Dates are formatted as follows: + * 1. When both dates refer to the same day: Mar 17 + * 2. When both dates refer to the same month: Mar 17-20 + * 3. When both dates refer to the same year: Feb 28 to Mar 1 + * 4. When the dates are from different years: Dec 28, 2023 to Jan 5, 2024 + */ +function getFormattedDateRange(date1: Date, date2: Date): string { + const {translateLocal} = Localize; + + if (isSameDay(date1, date2)) { + // Dates are from the same day + return format(date1, 'MMM d'); + } + if (isSameMonth(date1, date2)) { + // Dates in the same month and year, differ by days + return `${format(date1, 'MMM d')}-${format(date2, 'd')}`; + } + if (isSameYear(date1, date2)) { + // Dates are in the same year, differ by months + return `${format(date1, 'MMM d')} ${translateLocal('common.to').toLowerCase()} ${format(date2, 'MMM d')}`; + } + // Dates differ by years, months, days + return `${format(date1, 'MMM d, yyyy')} ${translateLocal('common.to').toLowerCase()} ${format(date2, 'MMM d, yyyy')}`; +} + +/** + * Returns a formatted date range from date 1 to date 2 of a reservation. + * Dates are formatted as follows: + * 1. When both dates refer to the same day and the current year: Sunday, Mar 17 + * 2. When both dates refer to the same day but not the current year: Wednesday, Mar 17, 2023 + * 3. When both dates refer to the current year: Sunday, Mar 17 to Wednesday, Mar 20 + * 4. When the dates are from different years or from a year which is not current: Wednesday, Mar 17, 2023 to Saturday, Jan 20, 2024 + */ +function getFormattedReservationRangeDate(date1: Date, date2: Date): string { + const {translateLocal} = Localize; + if (isSameDay(date1, date2) && isThisYear(date1)) { + // Dates are from the same day + return format(date1, 'EEEE, MMM d'); + } + if (isSameDay(date1, date2)) { + // Dates are from the same day but not this year + return format(date1, 'EEEE, MMM d, yyyy'); + } + if (isSameYear(date1, date2) && isThisYear(date1)) { + // Dates are in the current year, differ by months + return `${format(date1, 'EEEE, MMM d')} ${translateLocal('common.conjunctionTo')} ${format(date2, 'EEEE, MMM d')}`; + } + // Dates differ by years, months, days or only by months but the year is not current + return `${format(date1, 'EEEE, MMM d, yyyy')} ${translateLocal('common.conjunctionTo')} ${format(date2, 'EEEE, MMM d, yyyy')}`; +} + +/** + * Returns a formatted date of departure. + * Dates are formatted as follows: + * 1. When the date refers to the current day: Departs on Sunday, Mar 17 at 8:00 + * 2. When the date refers not to the current day: Departs on Wednesday, Mar 17, 2023 at 8:00 + */ +function getFormattedTransportDate(date: Date): string { + const {translateLocal} = Localize; + if (isThisYear(date)) { + return `${translateLocal('travel.departs')} ${format(date, 'EEEE, MMM d')} ${translateLocal('common.conjunctionAt')} ${format(date, 'HH:MM')}`; + } + return `${translateLocal('travel.departs')} ${format(date, 'EEEE, MMM d, yyyy')} ${translateLocal('common.conjunctionAt')} ${format(date, 'HH:MM')}`; +} + const DateUtils = { formatToDayOfWeek, formatToLongDateWithWeekday, @@ -768,6 +837,9 @@ const DateUtils = { formatToSupportedTimezone, enrichMoneyRequestTimestamp, getLastBusinessDayOfMonth, + getFormattedDateRange, + getFormattedReservationRangeDate, + getFormattedTransportDate, }; export default DateUtils; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 03538dc7a860..a005b3d23f2c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -905,6 +905,13 @@ function isInvoiceRoom(report: OnyxEntry): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; } +/** + * Checks if a report is a completed task report. + */ +function isTripRoom(report: OnyxEntry): boolean { + return isChatReport(report) && getChatType(report) === CONST.REPORT.CHAT_TYPE.TRIP_ROOM; +} + function isCurrentUserInvoiceReceiver(report: OnyxEntry): boolean { if (report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { return currentUserAccountID === report.invoiceReceiver.accountID; @@ -3190,6 +3197,11 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu if (ReportActionsUtils.isModifiedExpenseAction(parentReportAction)) { return ModifiedExpenseMessage.getForReportAction(report?.reportID, parentReportAction); } + + if (isTripRoom(report)) { + return report?.reportName ?? ''; + } + return parentReportActionMessage; } @@ -6586,6 +6598,13 @@ function shouldCreateNewMoneyRequestReport(existingIOUReport: OnyxEntry return !existingIOUReport || hasIOUWaitingOnCurrentUserBankAccount(chatReport) || !canAddOrDeleteTransactions(existingIOUReport); } +function getTripTransactions(tripRoomReportID: string | undefined): Transaction[] { + const tripTransactionReportIDs = Object.values(allReports ?? {}) + .filter((report) => report && report?.parentReportID === tripRoomReportID) + .map((report) => report?.reportID); + return tripTransactionReportIDs.flatMap((reportID) => TransactionUtils.getAllReportTransactions(reportID)); +} + /** * Checks if report contains actions with errors */ @@ -6931,6 +6950,7 @@ export { isCanceledTaskReport, isChatReport, isChatRoom, + isTripRoom, isChatThread, isChildReport, isClosedExpenseReportWithNoExpenses, @@ -7019,6 +7039,7 @@ export { updateOptimisticParentReportAction, updateReportPreview, temporary_getMoneyRequestOptions, + getTripTransactions, buildOptimisticInvoiceReport, getInvoiceChatByParticipants, shouldShowMerchantColumn, diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 79e73f1585d2..03e2be7b2db2 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -683,6 +683,10 @@ function isCustomUnitRateIDForP2P(transaction: OnyxEntry): boolean return transaction?.comment?.customUnit?.customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID; } +function hasReservationList(transaction: Transaction | undefined | null): boolean { + return !!transaction?.receipt?.reservationList && transaction?.receipt?.reservationList.length > 0; +} + /** * Get rate ID from the transaction object */ @@ -805,6 +809,7 @@ export { getWaypointIndex, waypointHasValidAddress, getRecentTransactions, + hasReservationList, hasViolation, hasNoticeTypeViolation, isCustomUnitRateIDForP2P, diff --git a/src/libs/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts new file mode 100644 index 000000000000..ead786b8eafd --- /dev/null +++ b/src/libs/TripReservationUtils.ts @@ -0,0 +1,27 @@ +import * as Expensicons from '@src/components/Icon/Expensicons'; +import CONST from '@src/CONST'; +import type {Reservation, ReservationType} from '@src/types/onyx/Transaction'; +import type Transaction from '@src/types/onyx/Transaction'; +import type IconAsset from '@src/types/utils/IconAsset'; + +function getTripReservationIcon(reservationType: ReservationType): IconAsset { + switch (reservationType) { + case CONST.RESERVATION_TYPE.FLIGHT: + return Expensicons.Plane; + case CONST.RESERVATION_TYPE.HOTEL: + return Expensicons.Bed; + case CONST.RESERVATION_TYPE.CAR: + return Expensicons.CarWithKey; + default: + return Expensicons.Luggage; + } +} + +function getReservationsFromTripTransactions(transactions: Transaction[]): Reservation[] { + return transactions + .map((item) => item?.receipt?.reservationList ?? []) + .filter((item) => item.length > 0) + .flat(); +} + +export {getTripReservationIcon, getReservationsFromTripTransactions}; diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 4b248bf14131..f87845d91254 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -29,6 +29,7 @@ import ReportPreview from '@components/ReportActionItem/ReportPreview'; import TaskAction from '@components/ReportActionItem/TaskAction'; import TaskPreview from '@components/ReportActionItem/TaskPreview'; import TaskView from '@components/ReportActionItem/TaskView'; +import TripDetailsView from '@components/ReportActionItem/TripDetailsView'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import SpacerView from '@components/SpacerView'; import Text from '@components/Text'; @@ -779,6 +780,19 @@ function ReportActionItem({ return {content}; }; + if (action.actionName === CONST.REPORT.ACTIONS.TYPE.TRIPPREVIEW) { + if (ReportUtils.isTripRoom(report)) { + return ( + + + + ); + } + } + if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { if (ReportActionsUtils.isTransactionThread(parentReportAction)) { const isReversedTransaction = ReportActionsUtils.isReversedTransaction(parentReportAction); @@ -845,6 +859,7 @@ function ReportActionItem({ ); } + if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report) || ReportUtils.isInvoiceReport(report)) { return ( diff --git a/src/styles/index.ts b/src/styles/index.ts index 7d4366b5723d..dd123986a929 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -386,6 +386,11 @@ const styles = (theme: ThemeColors) => lineHeight: variables.lineHeightNormal, }, + textSmall: { + fontFamily: FontUtils.fontFamily.platform.EXP_NEUE, + fontSize: variables.fontSizeSmall, + }, + textMicro: { fontFamily: FontUtils.fontFamily.platform.EXP_NEUE, fontSize: variables.fontSizeSmall, @@ -1377,6 +1382,10 @@ const styles = (theme: ThemeColors) => color: theme.textSupporting, }, + lh14: { + lineHeight: variables.lineHeightSmall, + }, + lh16: { lineHeight: 16, }, @@ -4925,6 +4934,15 @@ const styles = (theme: ThemeColors) => flex: 1, }, + tripReservationIconContainer: { + width: variables.avatarSizeNormal, + height: variables.avatarSizeNormal, + backgroundColor: theme.border, + borderRadius: variables.componentBorderRadiusXLarge, + alignItems: 'center', + justifyContent: 'center', + }, + textLineThrough: { textDecorationLine: 'line-through', }, diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index b079a64ebb4b..c62bd8f34f24 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -27,6 +27,7 @@ type OriginalMessageActionName = | 'ACTIONABLEMENTIONWHISPER' | 'ACTIONABLEREPORTMENTIONWHISPER' | 'ACTIONABLETRACKEXPENSEWHISPER' + | 'TRIPPREVIEW' | ValueOf; type OriginalMessageApproved = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.APPROVED; @@ -342,6 +343,15 @@ type OriginalMessageDismissedViolation = { }; }; +type OriginalMessageTripRoomPreview = { + actionName: typeof CONST.REPORT.ACTIONS.TYPE.TRIPPREVIEW; + originalMessage: { + linkedReportID: string; + lastModified?: string; + whisperedTo?: number[]; + }; +}; + type OriginalMessage = | OriginalMessageApproved | OriginalMessageIOU @@ -366,6 +376,7 @@ type OriginalMessage = | OriginalMessageReimbursementDequeued | OriginalMessageMoved | OriginalMessageMarkedReimbursed + | OriginalMessageTripRoomPreview | OriginalMessageActionableTrackedExpenseWhisper | OriginalMessageMergedWithCashTransaction | OriginalMessageDismissedViolation; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 460bd279048b..47ed18348275 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -82,6 +82,7 @@ type Receipt = { filename?: string; state?: ValueOf; type?: string; + reservationList?: Reservation[]; }; type Route = { @@ -110,6 +111,51 @@ type TaxRate = { data?: TaxRateData; }; +type Reservation = { + reservationID?: string; + start: ReservationTimeDetails; + end: ReservationTimeDetails; + type: ReservationType; + company?: Company; + confirmations?: ReservationConfirmation[]; + numPassengers?: number; + numberOfRooms?: number; + route?: { + airlineCode: string; + class?: string; + number: string; + }; + vendor?: string; + carInfo?: CarInfo; +}; + +type ReservationTimeDetails = { + date: string; + address?: string; + location?: string; + longName?: string; + shortName?: string; + timezoneOffset?: string; +}; + +type Company = { + longName: string; + shortName?: string; + phone?: string; +}; + +type ReservationConfirmation = { + name: string; + value: string; +}; + +type CarInfo = { + name?: string; + engine?: string; +}; + +type ReservationType = ValueOf; + type SplitShare = { amount: number; isModified?: boolean; @@ -278,6 +324,9 @@ export type { TransactionPendingFieldsKey, TransactionChanges, TaxRate, + Reservation, + ReservationTimeDetails, + ReservationType, ReceiptSource, TransactionCollectionDataSet, SplitShare,