diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 097811ab8d69..03e5a27de286 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -19,6 +19,9 @@ import {LOCALES} from './LOCALES'; const EMPTY_ARRAY = Object.freeze([]); const EMPTY_OBJECT = Object.freeze({}); +// Using 28 days to align with OldDot and because all months are guaranteed to be at least 28 days. +const MONTH_DAYS = Object.freeze([...Array(28).keys()].map((i) => i + 1)); + const DEFAULT_NUMBER_ID = 0; const CLOUDFRONT_DOMAIN = 'cloudfront.net'; const CLOUDFRONT_URL = `https://d2k5nsl2zxldvw.${CLOUDFRONT_DOMAIN}`; @@ -418,6 +421,7 @@ const CONST = { ORDINAL_DAY_OF_MONTH: 'do', MONTH_DAY_YEAR_ORDINAL_FORMAT: 'MMMM do, yyyy', SECONDS_PER_DAY: 24 * 60 * 60, + MONTH_DAYS, }, SMS: { DOMAIN: '@expensify.sms', @@ -3185,6 +3189,7 @@ const CONST = { AMEX_CUSTOM_FEED: 'AmexCustomFeed', SELECT_COUNTRY: 'SelectCountry', PLAID_CONNECTION: 'PlaidConnection', + SELECT_STATEMENT_CLOSE_DATE: 'SelectStatementCloseDate', }, CARD_TYPE: { AMEX: 'amex', @@ -3226,6 +3231,11 @@ const CONST = { RESTRICT: 'corporate', ALLOW: 'personal', }, + STATEMENT_CLOSE_DATE: { + LAST_DAY_OF_MONTH: 'lastDayOfMonth', + LAST_BUSINESS_DAY_OF_MONTH: 'lastBusinessDayOfMonth', + CUSTOM_DAY_OF_MONTH: 'customDayOfMonth', + }, CARD_LIST_THRESHOLD: 8, DEFAULT_EXPORT_TYPE: 'default', EXPORT_CARD_TYPES: { diff --git a/src/languages/de.ts b/src/languages/de.ts index d59419909190..1a3e78c99d76 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -4279,6 +4279,11 @@ const translations = { pleaseSelectFeedType: 'Bitte wählen Sie einen Feed-Typ aus, bevor Sie fortfahren.', }, }, + statementCloseDate: { + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_DAY_OF_MONTH]: 'Letzter Tag des Monats', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_BUSINESS_DAY_OF_MONTH]: 'Letzter Geschäftstag des Monats', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH]: 'Individueller Tag des Monats', + }, assignCard: 'Karte zuweisen', findCard: 'Karte finden', cardNumber: 'Kartennummer', @@ -4295,6 +4300,7 @@ const translations = { startDateDescription: 'Wir werden alle Transaktionen ab diesem Datum importieren. Wenn kein Datum angegeben ist, gehen wir so weit zurück, wie es Ihre Bank erlaubt.', fromTheBeginning: 'Von Anfang an', customStartDate: 'Benutzerdefiniertes Startdatum', + customCloseDate: 'Benutzerdefiniertes Abschlussdatum', letsDoubleCheck: 'Lassen Sie uns noch einmal überprüfen, ob alles richtig aussieht.', confirmationDescription: 'Wir werden sofort mit dem Import von Transaktionen beginnen.', cardholder: 'Karteninhaber', @@ -4519,6 +4525,7 @@ const translations = { removeCardFeedDescription: 'Möchten Sie diesen Karten-Feed wirklich entfernen? Dadurch werden alle Karten zugewiesen.', error: { feedNameRequired: 'Der Name des Karten-Feeds ist erforderlich', + statementCloseDateRequired: 'Bitte wählen Sie ein Abschlussdatum für den Kontoauszug aus.', }, corporate: 'Löschen von Transaktionen einschränken', personal: 'Löschen von Transaktionen erlauben', @@ -4545,6 +4552,8 @@ const translations = { expensifyCardBannerSubtitle: 'Genießen Sie Cashback bei jedem Einkauf in den USA, bis zu 50 % Rabatt auf Ihre Expensify-Rechnung, unbegrenzte virtuelle Karten und vieles mehr.', expensifyCardBannerLearnMoreButton: 'Erfahren Sie mehr', + statementCloseDateTitle: 'Datum des Rechnungsabschlusses', + statementCloseDateDescription: 'Teilen Sie uns mit, wann Ihre Kartenabrechnung geschlossen wird, und wir erstellen eine passende Abrechnung in Expensify.', }, workflows: { title: 'Workflows', diff --git a/src/languages/en.ts b/src/languages/en.ts index a62c49c7ce3e..6bb1bd0aaa3a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4261,6 +4261,11 @@ const translations = { pleaseSelectFeedType: 'Please select a feed type before continuing', }, }, + statementCloseDate: { + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_DAY_OF_MONTH]: 'Last day of the month', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_BUSINESS_DAY_OF_MONTH]: 'Last business day of the month', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH]: 'Custom day of month', + }, assignCard: 'Assign card', findCard: 'Find card', cardNumber: 'Card number', @@ -4277,6 +4282,7 @@ const translations = { startDateDescription: "We'll import all transaction from this date onwards. If no date is specified, we’ll go as far back as your bank allows.", fromTheBeginning: 'From the beginning', customStartDate: 'Custom start date', + customCloseDate: 'Custom close date', letsDoubleCheck: 'Let’s double check that everything looks right.', confirmationDescription: 'We’ll begin importing transactions immediately.', cardholder: 'Cardholder', @@ -4498,6 +4504,7 @@ const translations = { removeCardFeedDescription: 'Are you sure you want to remove this card feed? This will unassign all cards.', error: { feedNameRequired: 'Card feed name is required', + statementCloseDateRequired: 'Please select a statement close date.', }, corporate: 'Restrict deleting transactions', personal: 'Allow deleting transactions', @@ -4522,6 +4529,8 @@ const translations = { expensifyCardBannerTitle: 'Get the Expensify Card', expensifyCardBannerSubtitle: 'Enjoy cash back on every US purchase, up to 50% off your Expensify bill, unlimited virtual cards, and so much more.', expensifyCardBannerLearnMoreButton: 'Learn more', + statementCloseDateTitle: 'Statement close date', + statementCloseDateDescription: 'Let us know when your card statement closes, and we’ll create a matching statement in Expensify.', }, workflows: { title: 'Workflows', diff --git a/src/languages/es.ts b/src/languages/es.ts index 25cb362c4712..c196c8ab5f17 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4269,6 +4269,11 @@ const translations = { pleaseSelectFeedType: 'Seleccione un tipo de pienso antes de continuar', }, }, + statementCloseDate: { + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_DAY_OF_MONTH]: 'Último día del mes', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_BUSINESS_DAY_OF_MONTH]: 'Último día hábil del mes', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH]: 'Día personalizado del mes', + }, assignCard: 'Asignar tarjeta', findCard: 'Encontrar tarjeta', cardNumber: 'Número de la tarjeta', @@ -4285,6 +4290,7 @@ const translations = { startDateDescription: 'Importaremos todas las transacciones desde esta fecha en adelante. Si no se especifica una fecha, iremos tan atrás como lo permita tu banco.', fromTheBeginning: 'Desde el principio', customStartDate: 'Fecha de inicio personalizada', + customCloseDate: 'Fecha de cierre personalizada', letsDoubleCheck: 'Verifiquemos que todo esté bien.', confirmationDescription: 'Comenzaremos a importar transacciones inmediatamente.', cardholder: 'Titular de la tarjeta', @@ -4510,6 +4516,7 @@ const translations = { removeCardFeedDescription: '¿Estás seguro de que deseas eliminar esta fuente de tarjetas? Esto anulará la asignación de todas las tarjetas.', error: { feedNameRequired: 'Se requiere el nombre de la fuente de la tarjeta', + statementCloseDateRequired: 'Por favor, selecciona una fecha de cierre del estado de cuenta.', }, corporate: 'Restringir eliminación de transacciones', personal: 'Permitir eliminación de transacciones', @@ -4536,6 +4543,8 @@ const translations = { expensifyCardBannerSubtitle: 'Disfruta de una devolución en cada compra en Estados Unidos, hasta un 50% de descuento en tu factura de Expensify, tarjetas virtuales ilimitadas y mucho más.', expensifyCardBannerLearnMoreButton: 'Más información', + statementCloseDateTitle: 'Fecha de cierre del estado de cuenta', + statementCloseDateDescription: 'Indícanos cuándo cierra el estado de cuenta de tu tarjeta y crearemos uno correspondiente en Expensify.', }, workflows: { title: 'Flujos de trabajo', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 5fa1c2ecef7e..081c37a31b01 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -4288,6 +4288,11 @@ const translations = { pleaseSelectFeedType: 'Veuillez sélectionner un type de flux avant de continuer', }, }, + statementCloseDate: { + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_DAY_OF_MONTH]: 'Dernier jour du mois', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_BUSINESS_DAY_OF_MONTH]: 'Dernier jour ouvrable du mois', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH]: 'Jour personnalisé du mois', + }, assignCard: 'Attribuer la carte', findCard: 'Trouver la carte', cardNumber: 'Numéro de carte', @@ -4304,6 +4309,7 @@ const translations = { startDateDescription: "Nous importerons toutes les transactions à partir de cette date. Si aucune date n'est spécifiée, nous remonterons aussi loin que votre banque le permet.", fromTheBeginning: 'Depuis le début', customStartDate: 'Date de début personnalisé', + customCloseDate: 'Date de clôture personnalisée', letsDoubleCheck: 'Vérifions que tout est correct.', confirmationDescription: 'Nous commencerons à importer les transactions immédiatement.', cardholder: 'Titulaire de carte', @@ -4531,6 +4537,7 @@ const translations = { removeCardFeedDescription: 'Êtes-vous sûr de vouloir supprimer ce flux de cartes ? Cela désassignera toutes les cartes.', error: { feedNameRequired: 'Le nom du flux de carte est requis', + statementCloseDateRequired: 'Veuillez sélectionner une date de clôture du relevé.', }, corporate: 'Restreindre la suppression des transactions', personal: 'Autoriser la suppression des transactions', @@ -4558,6 +4565,8 @@ const translations = { expensifyCardBannerSubtitle: "Profitez de remises en argent sur chaque achat aux États-Unis, jusqu'à 50 % de réduction sur votre facture Expensify, des cartes virtuelles illimitées, et bien plus encore.", expensifyCardBannerLearnMoreButton: 'En savoir plus', + statementCloseDateTitle: 'Date de clôture du relevé', + statementCloseDateDescription: 'Indiquez-nous la date de clôture de votre relevé de carte et nous créerons un relevé correspondant dans Expensify.', }, workflows: { title: 'Workflows', diff --git a/src/languages/it.ts b/src/languages/it.ts index 461ba4c7a3ee..8a79252ffb84 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -4292,6 +4292,11 @@ const translations = { pleaseSelectFeedType: 'Si prega di selezionare un tipo di feed prima di continuare', }, }, + statementCloseDate: { + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_DAY_OF_MONTH]: 'Ultimo giorno del mese', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_BUSINESS_DAY_OF_MONTH]: 'Ultimo giorno lavorativo del mese', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH]: 'Giorno del mese personalizzato', + }, assignCard: 'Assegna carta', findCard: 'Trova carta', cardNumber: 'Numero di carta', @@ -4309,6 +4314,7 @@ const translations = { 'Importeremo tutte le transazioni da questa data in poi. Se non viene specificata alcuna data, risaliremo indietro fino a quanto consentito dalla tua banca.', fromTheBeginning: "Dall'inizio", customStartDate: 'Data di inizio personalizzata', + customCloseDate: 'Data di chiusura personalizzata', letsDoubleCheck: 'Verifichiamo che tutto sia corretto.', confirmationDescription: 'Inizieremo immediatamente a importare le transazioni.', cardholder: 'Titolare della carta', @@ -4532,6 +4538,7 @@ const translations = { removeCardFeedDescription: 'Sei sicuro di voler rimuovere questo feed di carte? Questo disassegnerà tutte le carte.', error: { feedNameRequired: 'Il nome del feed della carta è obbligatorio', + statementCloseDateRequired: "Selezionare una data di chiusura dell'estratto conto.", }, corporate: "Limita l'eliminazione delle transazioni", personal: "Consenti l'eliminazione delle transazioni", @@ -4558,6 +4565,8 @@ const translations = { expensifyCardBannerSubtitle: 'Goditi il cashback su ogni acquisto negli Stati Uniti, fino al 50% di sconto sulla tua fattura Expensify, carte virtuali illimitate e molto altro ancora.', expensifyCardBannerLearnMoreButton: 'Scopri di più', + statementCloseDateTitle: "Data di chiusura dell'estratto conto", + statementCloseDateDescription: "Comunicateci la data di chiusura dell'estratto conto della vostra carta e creeremo un estratto conto corrispondente in Expensify.", }, workflows: { title: 'Flussi di lavoro', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index f8f6b71e5d0f..65fd02149a8d 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -4277,6 +4277,11 @@ const translations = { pleaseSelectFeedType: '続行する前にフィードタイプを選択してください', }, }, + statementCloseDate: { + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_DAY_OF_MONTH]: '月の最終日', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_BUSINESS_DAY_OF_MONTH]: '月の最終営業日', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH]: 'カスタム月日', + }, assignCard: 'カードを割り当てる', findCard: 'カードを探す', cardNumber: 'カード番号', @@ -4293,6 +4298,7 @@ const translations = { startDateDescription: 'この日付以降のすべての取引をインポートします。日付が指定されていない場合は、銀行が許可する限り遡ります。', fromTheBeginning: '最初から', customStartDate: 'カスタム開始日', + customCloseDate: 'カスタムクローズ日', letsDoubleCheck: 'すべてが正しいかどうかをもう一度確認しましょう。', confirmationDescription: 'すぐに取引のインポートを開始します。', cardholder: 'カードホルダー', @@ -4515,6 +4521,7 @@ const translations = { removeCardFeedDescription: 'このカードフィードを削除してもよろしいですか?これにより、すべてのカードの割り当てが解除されます。', error: { feedNameRequired: 'カードフィード名は必須です', + statementCloseDateRequired: '明細書の締め日を選択してください。', }, corporate: '取引の削除を制限する', personal: '取引の削除を許可', @@ -4539,6 +4546,8 @@ const translations = { expensifyCardBannerTitle: 'Expensifyカードを取得する', expensifyCardBannerSubtitle: 'すべての米国での購入でキャッシュバックを楽しみ、Expensifyの請求書が最大50%オフ、無制限のバーチャルカードなど、さらに多くの特典があります。', expensifyCardBannerLearnMoreButton: '詳細を確認', + statementCloseDateTitle: '利用明細書の締め日', + statementCloseDateDescription: 'カード利用明細書の締め日をお知らせいただければ、Expensifyで一致する明細書を作成します。', }, workflows: { title: 'ワークフロー', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index d32c1927852e..b0e36467e276 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -4296,6 +4296,11 @@ const translations = { pleaseSelectFeedType: 'Selecteer een feedtype voordat u doorgaat.', }, }, + statementCloseDate: { + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_DAY_OF_MONTH]: 'Laatste dag van de maand', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_BUSINESS_DAY_OF_MONTH]: 'Laatste werkdag van de maand', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH]: 'Aangepaste dag van de maand', + }, assignCard: 'Kaart toewijzen', findCard: 'Kaart vinden', cardNumber: 'Kaartnummer', @@ -4312,6 +4317,7 @@ const translations = { startDateDescription: 'We importeren alle transacties vanaf deze datum. Als er geen datum is opgegeven, gaan we zo ver terug als uw bank toestaat.', fromTheBeginning: 'Vanaf het begin', customStartDate: 'Aangepaste startdatum', + customCloseDate: 'Aangepaste sluitingsdatum', letsDoubleCheck: 'Laten we dubbel controleren of alles er goed uitziet.', confirmationDescription: 'We beginnen onmiddellijk met het importeren van transacties.', cardholder: 'Kaart houder', @@ -4536,6 +4542,7 @@ const translations = { removeCardFeedDescription: 'Weet je zeker dat je deze kaartfeed wilt verwijderen? Dit zal alle kaarten deactiveren.', error: { feedNameRequired: 'Naam van de kaartfeed is vereist', + statementCloseDateRequired: 'Selecteer een datum waarop het afschrift moet worden gesloten.', }, corporate: 'Beperk het verwijderen van transacties', personal: 'Verwijderen van transacties toestaan', @@ -4560,6 +4567,8 @@ const translations = { expensifyCardBannerTitle: 'Verkrijg de Expensify Card', expensifyCardBannerSubtitle: 'Geniet van cashback op elke aankoop in de VS, tot 50% korting op je Expensify-rekening, onbeperkte virtuele kaarten en nog veel meer.', expensifyCardBannerLearnMoreButton: 'Meer informatie', + statementCloseDateTitle: 'Datum waarop rekeningafschrift wordt gesloten', + statementCloseDateDescription: 'Laat ons weten wanneer je rekeningafschrift wordt gesloten, dan maken we een bijpassend rekeningafschrift in Expensify.', }, workflows: { title: 'Workflows', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index d9cd66e30b6f..a112460924ff 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -4284,6 +4284,11 @@ const translations = { pleaseSelectFeedType: 'Proszę wybrać typ kanału przed kontynuowaniem.', }, }, + statementCloseDate: { + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_DAY_OF_MONTH]: 'Ostatni dzień miesiąca', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_BUSINESS_DAY_OF_MONTH]: 'Ostatni dzień roboczy miesiąca', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH]: 'Niestandardowy dzień miesiąca', + }, assignCard: 'Przypisz kartę', findCard: 'Znajdź kartę', cardNumber: 'Numer karty', @@ -4300,6 +4305,7 @@ const translations = { startDateDescription: 'Zaimportujemy wszystkie transakcje od tej daty. Jeśli nie określono daty, sięgniemy tak daleko wstecz, jak pozwala na to Twój bank.', fromTheBeginning: 'Od początku', customStartDate: 'Niestandardowa data rozpoczęcia', + customCloseDate: 'Niestandardowa data zamknięcia', letsDoubleCheck: 'Sprawdźmy jeszcze raz, czy wszystko wygląda dobrze.', confirmationDescription: 'Natychmiast rozpoczniemy importowanie transakcji.', cardholder: 'Posiadacz karty', @@ -4523,6 +4529,7 @@ const translations = { removeCardFeedDescription: 'Czy na pewno chcesz usunąć ten kanał kart? Spowoduje to odłączenie wszystkich kart.', error: { feedNameRequired: 'Nazwa kanału karty jest wymagana', + statementCloseDateRequired: 'Wybierz datę zamknięcia wyciągu.', }, corporate: 'Ogranicz usuwanie transakcji', personal: 'Zezwól na usuwanie transakcji', @@ -4549,6 +4556,8 @@ const translations = { expensifyCardBannerSubtitle: 'Ciesz się zwrotem gotówki przy każdym zakupie w USA, do 50% zniżki na rachunek Expensify, nielimitowanymi kartami wirtualnymi i wieloma innymi korzyściami.', expensifyCardBannerLearnMoreButton: 'Dowiedz się więcej', + statementCloseDateTitle: 'Data zamknięcia oświadczenia', + statementCloseDateDescription: 'Poinformuj nas o zamknięciu wyciągu z karty, a my utworzymy pasujący wyciąg w Expensify.', }, workflows: { title: 'Przepływy pracy', @@ -5893,9 +5902,9 @@ const translations = { subtitle: 'Zero wydatków. Maksymalny relaks. Dobra robota!', }, }, - unapproved: 'Não aprovado', - unapprovedCash: 'Dinheiro não aprovado', - unapprovedCompanyCards: 'Cartões de empresa não aprovados', + unapproved: 'Niezatwierdzony', + unapprovedCash: 'Niezatwierdzone środki pieniężne', + unapprovedCompanyCards: 'Niezatwierdzone karty firmowe', saveSearch: 'Zapisz wyszukiwanie', deleteSavedSearch: 'Usuń zapisaną wyszukiwarkę', deleteSavedSearchConfirm: 'Czy na pewno chcesz usunąć to wyszukiwanie?', @@ -5917,8 +5926,8 @@ const translations = { after: ({date}: OptionalParam = {}) => `After ${date ?? ''}`, on: ({date}: OptionalParam = {}) => `On ${date ?? ''}`, presets: { - [CONST.SEARCH.DATE_PRESETS.NEVER]: 'Nunca', - [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: 'No mês passado', + [CONST.SEARCH.DATE_PRESETS.NEVER]: 'Nigdy', + [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: 'Ostatni miesiąc', }, }, status: 'Status', @@ -5953,12 +5962,12 @@ const translations = { billable: 'Podlegające fakturowaniu', reimbursable: 'Podlegające zwrotowi', groupBy: { - reports: 'Relatório', - members: 'Membro', + reports: 'Raport', + members: 'Członek', cards: 'Karta', }, }, - groupBy: 'Agrupar por', + groupBy: 'Grupa według', moneyRequestReport: { emptyStateTitle: 'Ten raport nie zawiera wydatków.', emptyStateSubtitle: 'Możesz dodać wydatki do tego raportu, używając przycisku powyżej.', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 82c892821d4d..f277b8edbc6f 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -4290,6 +4290,11 @@ const translations = { pleaseSelectFeedType: 'Por favor, selecione um tipo de feed antes de continuar.', }, }, + statementCloseDate: { + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_DAY_OF_MONTH]: 'Último dia do mês', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_BUSINESS_DAY_OF_MONTH]: 'Último dia útil do mês', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH]: 'Dia personalizado do mês', + }, assignCard: 'Atribuir cartão', findCard: 'Encontrar cartão', cardNumber: 'Número do cartão', @@ -4306,6 +4311,7 @@ const translations = { startDateDescription: 'Importaremos todas as transações a partir desta data. Se nenhuma data for especificada, iremos o mais longe possível conforme permitido pelo seu banco.', fromTheBeginning: 'Desde o início', customStartDate: 'Data de início personalizada', + customCloseDate: 'Data de fechamento personalizada', letsDoubleCheck: 'Vamos verificar se tudo está correto.', confirmationDescription: 'Começaremos a importar transações imediatamente.', cardholder: 'Titular do cartão', @@ -4529,6 +4535,7 @@ const translations = { removeCardFeedDescription: 'Tem certeza de que deseja remover este feed de cartão? Isso desatribuirá todos os cartões.', error: { feedNameRequired: 'O nome do feed do cartão é obrigatório', + statementCloseDateRequired: 'Favor selecionar uma data de fechamento do extrato.', }, corporate: 'Restringir a exclusão de transações', personal: 'Permitir excluir transações', @@ -4554,6 +4561,8 @@ const translations = { expensifyCardBannerTitle: 'Obtenha o Cartão Expensify', expensifyCardBannerSubtitle: 'Aproveite o cashback em todas as compras nos EUA, até 50% de desconto na sua fatura do Expensify, cartões virtuais ilimitados e muito mais.', expensifyCardBannerLearnMoreButton: 'Saiba mais', + statementCloseDateTitle: 'Statement close date', + statementCloseDateDescription: 'Informe-nos quando o extrato do seu cartão for encerrado e criaremos um extrato correspondente na Expensify.', }, workflows: { title: 'Fluxos de Trabalho', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 2a3720c3c901..7ae877e2fc44 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -4223,6 +4223,11 @@ const translations = { pleaseSelectFeedType: '请在继续之前选择一个订阅类型', }, }, + statementCloseDate: { + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_DAY_OF_MONTH]: '本月最后一天', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.LAST_BUSINESS_DAY_OF_MONTH]: '本月最后一个工作日', + [CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH]: '本月自定义日期', + }, assignCard: '分配卡片', findCard: '查找卡片', cardNumber: '卡号', @@ -4239,6 +4244,7 @@ const translations = { startDateDescription: '我们将从此日期开始导入所有交易。如果未指定日期,我们将根据您的银行允许的最早日期进行导入。', fromTheBeginning: '从头开始', customStartDate: '自定义开始日期', + customCloseDate: '自定义关闭日期', letsDoubleCheck: '让我们仔细检查一下,确保一切正常。', confirmationDescription: '我们将立即开始导入交易。', cardholder: '持卡人', @@ -4456,6 +4462,7 @@ const translations = { removeCardFeedDescription: '您确定要移除此卡片源吗?这将取消分配所有卡片。', error: { feedNameRequired: '卡片摘要名称是必需的', + statementCloseDateRequired: '请选择报表关闭日期。', }, corporate: '限制删除交易', personal: '允许删除交易', @@ -4480,6 +4487,8 @@ const translations = { expensifyCardBannerTitle: '获取Expensify卡', expensifyCardBannerSubtitle: '享受每笔美国消费的现金返还,Expensify账单最高可享50%折扣,无限虚拟卡等更多优惠。', expensifyCardBannerLearnMoreButton: '了解更多', + statementCloseDateTitle: '对账单关闭日期', + statementCloseDateDescription: '让我们知道您的银行卡对账单何时关闭,我们将在 Expensify 中创建匹配的对账单。', }, workflows: { title: '工作流程', diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index 785d921583f2..7409a4ee8e72 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -21,7 +21,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Card, CardFeeds} from '@src/types/onyx'; import type {AssignCard, AssignCardData} from '@src/types/onyx/AssignCard'; -import type {AddNewCardFeedData, AddNewCardFeedStep, CompanyCardFeed} from '@src/types/onyx/CardFeeds'; +import type {AddNewCardFeedData, AddNewCardFeedStep, CardFeedDetails, CompanyCardFeed} from '@src/types/onyx/CardFeeds'; import type {OnyxData} from '@src/types/onyx/Request'; type AddNewCompanyCardFlowData = { @@ -58,7 +58,7 @@ function clearAddNewCardFlow() { }); } -function addNewCompanyCardsFeed(policyID: string | undefined, cardFeed: CompanyCardFeed, feedDetails: string, cardFeeds: OnyxEntry, lastSelectedFeed?: CompanyCardFeed) { +function addNewCompanyCardsFeed(policyID: string | undefined, cardFeed: CompanyCardFeed, feedDetails: CardFeedDetails, cardFeeds: OnyxEntry, lastSelectedFeed?: CompanyCardFeed) { const authToken = NetworkStore.getAuthToken(); const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID); @@ -131,7 +131,9 @@ function addNewCompanyCardsFeed(policyID: string | undefined, cardFeed: CompanyC policyID, authToken, feedType, - feedDetails, + feedDetails: Object.entries(feedDetails) + .map(([key, value]) => `${key}: ${value}`) + .join(', '), }; API.write(WRITE_COMMANDS.REQUEST_FEED_SETUP, parameters, {optimisticData, failureData, successData, finallyData}); diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList/CustomCloseDateSelectionList.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList/CustomCloseDateSelectionList.tsx new file mode 100644 index 000000000000..f8380d423d5f --- /dev/null +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList/CustomCloseDateSelectionList.tsx @@ -0,0 +1,97 @@ +import React, {useCallback, useMemo, useState} from 'react'; +import FormHelpMessage from '@components/FormHelpMessage'; +import SelectionList from '@components/SelectionList'; +import SingleSelectListItem from '@components/SelectionList/SingleSelectListItem'; +import type {ListItem} from '@components/SelectionList/types'; +import useDebouncedState from '@hooks/useDebouncedState'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; + +type CustomCloseDateListItem = ListItem & { + value: number; +}; + +type CustomCloseDateSelectionListProps = { + initiallySelectedDay: number | undefined; + onConfirmSelectedDay: (day: number) => void; +}; + +function CustomCloseDateSelectionList({initiallySelectedDay, onConfirmSelectedDay}: CustomCloseDateSelectionListProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const [selectedDay, setSelectedDay] = useState(initiallySelectedDay); + const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); + const [error, setError] = useState(undefined); + + const sections = useMemo(() => { + const data = CONST.DATE.MONTH_DAYS.reduce((days, dayValue) => { + const day = { + value: dayValue, + text: dayValue.toString(), + keyForList: dayValue.toString(), + isSelected: dayValue === selectedDay, + }; + + if (debouncedSearchValue) { + if (day.text.includes(debouncedSearchValue)) { + days.push(day); + } + } else { + days.push(day); + } + + return days; + }, []); + + return [{data, indexOffset: 0}]; + }, [selectedDay, debouncedSearchValue]); + + const selectDayAndClearError = useCallback((item: CustomCloseDateListItem) => { + setSelectedDay(item.value); + setError(undefined); + }, []); + + const confirmSelectedDay = useCallback(() => { + if (!selectedDay) { + setError(translate('workspace.moreFeatures.companyCards.error.statementCloseDateRequired')); + return; + } + + onConfirmSelectedDay(selectedDay); + }, [selectedDay, onConfirmSelectedDay, translate]); + + return ( + + {!!error && ( + + )} + + ); +} + +CustomCloseDateSelectionList.displayName = 'CustomCloseDateSelectionList'; + +export default CustomCloseDateSelectionList; diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList/index.tsx new file mode 100644 index 000000000000..b0391051ea68 --- /dev/null +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList/index.tsx @@ -0,0 +1,147 @@ +import React, {useCallback, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import FixedFooter from '@components/FixedFooter'; +import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; +import FormHelpMessage from '@components/FormHelpMessage'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import SingleSelectListItem from '@components/SelectionList/SingleSelectListItem'; +import type {ListItem} from '@components/SelectionList/types'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import type {CompanyCardStatementCloseDate} from '@src/types/onyx/CardFeeds'; +import CustomCloseDateSelectionList from './CustomCloseDateSelectionList'; + +type StatementCloseDateListItem = ListItem & { + value: CompanyCardStatementCloseDate; +}; + +type WorkspaceCompanyCardStatementCloseDateSelectionListProps = { + confirmText: string; + onSubmit: (statementCloseDate: CompanyCardStatementCloseDate, statementCustomCloseDate: number | undefined) => void; + onBackButtonPress: () => void; + enabledWhenOffline: boolean; +}; + +function WorkspaceCompanyCardStatementCloseDateSelectionList({confirmText, onSubmit, onBackButtonPress, enabledWhenOffline}: WorkspaceCompanyCardStatementCloseDateSelectionListProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const [selectedDate, setSelectedDate] = useState(undefined); + const [selectedCustomDate, setSelectedCustomDate] = useState(undefined); + const [error, setError] = useState(undefined); + + const [isChoosingCustomDate, setIsChoosingCustomDate] = useState(false); + + const title = useMemo( + () => (isChoosingCustomDate ? translate('workspace.companyCards.customCloseDate') : translate('workspace.moreFeatures.companyCards.statementCloseDateTitle')), + [translate, isChoosingCustomDate], + ); + + const goBack = useCallback(() => { + if (isChoosingCustomDate) { + setIsChoosingCustomDate(false); + return; + } + + onBackButtonPress(); + }, [isChoosingCustomDate, onBackButtonPress]); + + const selectDateAndClearError = useCallback((item: StatementCloseDateListItem) => { + setSelectedDate(item.value); + setError(undefined); + }, []); + + const selectCustomDateAndClearError = useCallback( + (day: number) => { + setSelectedCustomDate(day); + setError(undefined); + goBack(); + }, + [goBack], + ); + + const submit = useCallback(() => { + if (!selectedDate || (selectedDate === CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH && !selectedCustomDate)) { + setError(translate('workspace.moreFeatures.companyCards.error.statementCloseDateRequired')); + return; + } + + onSubmit(selectedDate, selectedDate === CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH ? selectedCustomDate : undefined); + }, [selectedDate, selectedCustomDate, onSubmit, translate]); + + return ( + + + {isChoosingCustomDate ? ( + + ) : ( + <> + + {translate('workspace.moreFeatures.companyCards.statementCloseDateDescription')} + + {Object.values(CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE)?.map((option) => ( + + ))} + {selectedDate === CONST.COMPANY_CARDS.STATEMENT_CLOSE_DATE.CUSTOM_DAY_OF_MONTH && ( + setIsChoosingCustomDate(true)} + viewMode={CONST.OPTION_MODE.COMPACT} + /> + )} + + + + {!!error && ( + + )} + + + + )} + + ); +} + +WorkspaceCompanyCardStatementCloseDateSelectionList.displayName = 'WorkspaceCompanyCardStatementCloseDateSelectionList'; + +export default WorkspaceCompanyCardStatementCloseDateSelectionList; diff --git a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx index 1d40658064bd..eb460cdc82e1 100644 --- a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx +++ b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx @@ -21,6 +21,7 @@ import PlaidConnectionStep from './PlaidConnectionStep'; import SelectBankStep from './SelectBankStep'; import SelectCountryStep from './SelectCountryStep'; import SelectFeedType from './SelectFeedType'; +import StatementCloseDateStep from './StatementCloseDateStep'; function AddNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) { const policyID = policy?.id; @@ -83,6 +84,8 @@ function AddNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) { return ; case CONST.COMPANY_CARDS.STEP.PLAID_CONNECTION: return ; + case CONST.COMPANY_CARDS.STEP.SELECT_STATEMENT_CLOSE_DATE: + return ; default: return isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) ? : ; } diff --git a/src/pages/workspace/companyCards/addNew/DetailsStep.tsx b/src/pages/workspace/companyCards/addNew/DetailsStep.tsx index a9e036894eac..ec2bb409665c 100644 --- a/src/pages/workspace/companyCards/addNew/DetailsStep.tsx +++ b/src/pages/workspace/companyCards/addNew/DetailsStep.tsx @@ -13,6 +13,7 @@ import TextInput from '@components/TextInput'; import TextLink from '@components/TextLink'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useCardFeeds from '@hooks/useCardFeeds'; +import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -35,6 +36,7 @@ function DetailsStep({policyID}: DetailsStepProps) { const theme = useTheme(); const styles = useThemeStyles(); const {inputCallbackRef} = useAutoFocusInput(); + const {isDevelopment} = useEnvironment(); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: false}); const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true}); @@ -46,17 +48,23 @@ function DetailsStep({policyID}: DetailsStepProps) { const bank = addNewCard?.data?.selectedBank; const isOtherBankSelected = bank === CONST.COMPANY_CARDS.BANKS.OTHER; + // s77rt remove DEV lock + const shouldSelectStatementCloseDate = isDevelopment; + const submit = (values: FormOnyxValues) => { if (!addNewCard?.data) { return; } - const feedDetails = Object.entries({ + const feedDetails = { ...values, bankName: addNewCard.data.bankName ?? 'Amex', - }) - .map(([key, value]) => `${key}: ${value}`) - .join(', '); + }; + + if (shouldSelectStatementCloseDate) { + setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_STATEMENT_CLOSE_DATE, data: {feedDetails}}); + return; + } addNewCompanyCardsFeed(policyID, addNewCard.data.feedType, feedDetails, cardFeeds, lastSelectedFeed); Navigation.goBack(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)); @@ -141,6 +149,7 @@ function DetailsStep({policyID}: DetailsStepProps) { role={CONST.ROLE.PRESENTATION} containerStyles={[styles.mb6]} ref={inputCallbackRef} + defaultValue={addNewCard?.data.feedDetails?.processorID} /> ); @@ -167,6 +178,7 @@ function DetailsStep({policyID}: DetailsStepProps) { role={CONST.ROLE.PRESENTATION} containerStyles={[styles.mb6]} ref={inputCallbackRef} + defaultValue={addNewCard?.data.feedDetails?.distributionID} /> ); case CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX: @@ -178,6 +190,7 @@ function DetailsStep({policyID}: DetailsStepProps) { role={CONST.ROLE.PRESENTATION} containerStyles={[styles.mb6]} ref={inputCallbackRef} + defaultValue={addNewCard?.data.feedDetails?.deliveryFileName} /> ); default: @@ -198,11 +211,10 @@ function DetailsStep({policyID}: DetailsStepProps) { /> diff --git a/src/pages/workspace/companyCards/addNew/StatementCloseDateStep.tsx b/src/pages/workspace/companyCards/addNew/StatementCloseDateStep.tsx new file mode 100644 index 000000000000..5894815168b2 --- /dev/null +++ b/src/pages/workspace/companyCards/addNew/StatementCloseDateStep.tsx @@ -0,0 +1,56 @@ +import React, {useCallback} from 'react'; +import useOnyx from 'react-native-onyx/dist/useOnyx'; +import useCardFeeds from '@hooks/useCardFeeds'; +import useLocalize from '@hooks/useLocalize'; +import {addNewCompanyCardsFeed, setAddNewCompanyCardStepAndData} from '@libs/actions/CompanyCards'; +import Navigation from '@libs/Navigation/Navigation'; +import WorkspaceCompanyCardStatementCloseDateSelectionList from '@pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {CompanyCardStatementCloseDate} from '@src/types/onyx/CardFeeds'; + +type StatementCloseDateStepProps = { + /** ID of the current policy */ + policyID: string | undefined; +}; + +function StatementCloseDateStep({policyID}: StatementCloseDateStepProps) { + const {translate} = useLocalize(); + + const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: false}); + const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true}); + + const [cardFeeds] = useCardFeeds(policyID); + + const submit = useCallback( + // s77rt make use of statementCloseDate / statementCustomCloseDate and remove disable lint rule + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (statementCloseDate: CompanyCardStatementCloseDate, statementCustomCloseDate: number | undefined) => { + if (!addNewCard?.data.feedDetails) { + return; + } + + addNewCompanyCardsFeed(policyID, addNewCard.data.feedType, addNewCard.data.feedDetails, cardFeeds, lastSelectedFeed); + Navigation.goBack(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)); + }, + [policyID, addNewCard, cardFeeds, lastSelectedFeed], + ); + + const goBack = useCallback(() => { + setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.CARD_DETAILS}); + }, []); + + return ( + + ); +} + +StatementCloseDateStep.displayName = 'StatementCloseDateStep'; + +export default StatementCloseDateStep; diff --git a/src/types/onyx/CardFeeds.ts b/src/types/onyx/CardFeeds.ts index 7941e8fd1cc8..48191bfabd7d 100644 --- a/src/types/onyx/CardFeeds.ts +++ b/src/types/onyx/CardFeeds.ts @@ -4,6 +4,9 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type * as OnyxCommon from './OnyxCommon'; +/** Card statement close date */ +type CompanyCardStatementCloseDate = ValueOf; + /** Card feed */ type CompanyCardFeed = ValueOf; @@ -17,6 +20,27 @@ type CardFeedProvider = | typeof CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX | typeof CONST.COMPANY_CARD.FEED_BANK_NAME.STRIPE; +/** Card feed details */ +type CardFeedDetails = { + /** Processor ID */ + processorID?: string; + + /** Financial institution (bank) ID */ + bankID?: string; + + /** Financial institution (bank) name */ + bankName?: string; + + /** Company ID */ + companyID?: string; + + /** Distribution ID */ + distributionID?: string; + + /** Delivery file name */ + deliveryFileName?: string; +}; + /** Custom card feed data */ type CustomCardFeedData = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** Whether any actions are pending */ @@ -103,6 +127,9 @@ type AddNewCardFeedData = { /** Card feed provider */ feedType: CardFeedProvider; + /** Card feed details */ + feedDetails?: CardFeedDetails; + /** Name of the card */ cardTitle: string; @@ -158,6 +185,7 @@ export type { AddNewCompanyCardFeed, AddNewCardFeedData, CompanyCardFeed, + CardFeedDetails, DirectCardFeedData, CardFeedProvider, CardFeedData, @@ -165,4 +193,5 @@ export type { CompanyCardNicknames, CompanyCardFeedWithNumber, FundID, + CompanyCardStatementCloseDate, };