From d54846c349483b68cb4e596145b1ba009a9f866c Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 4 Feb 2021 17:00:02 +0100 Subject: [PATCH] UI to confirm check cashing --- .../src/javascript/api/payment-schedule.ts | 7 ++- .../src/javascript/components/fab-button.tsx | 2 +- .../src/javascript/components/fab-modal.tsx | 14 +++-- .../components/payment-schedules-table.tsx | 62 ++++++++++++++++--- .../src/stylesheets/modules/fab-modal.scss | 18 +----- app/themes/casemate/style.scss.erb | 18 ++++++ config/locales/app.admin.en.yml | 3 + config/locales/app.admin.fr.yml | 3 + 8 files changed, 92 insertions(+), 35 deletions(-) diff --git a/app/frontend/src/javascript/api/payment-schedule.ts b/app/frontend/src/javascript/api/payment-schedule.ts index 44b4c1505..e149927e9 100644 --- a/app/frontend/src/javascript/api/payment-schedule.ts +++ b/app/frontend/src/javascript/api/payment-schedule.ts @@ -1,6 +1,6 @@ import apiClient from './api-client'; import { AxiosResponse } from 'axios'; -import { PaymentSchedule, PaymentScheduleIndexRequest } from '../models/payment-schedule'; +import { PaymentSchedule, PaymentScheduleIndexRequest, PaymentScheduleItem } from '../models/payment-schedule'; import wrapPromise, { IWrapPromise } from '../lib/wrap-promise'; export default class PaymentScheduleAPI { @@ -9,6 +9,11 @@ export default class PaymentScheduleAPI { return res?.data; } + async cashCheck(paymentScheduleItemId: number) { + const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/items/${paymentScheduleItemId}/cash_check`); + return res?.data; + } + static list (query: PaymentScheduleIndexRequest): IWrapPromise> { const api = new PaymentScheduleAPI(); return wrapPromise(api.list(query)); diff --git a/app/frontend/src/javascript/components/fab-button.tsx b/app/frontend/src/javascript/components/fab-button.tsx index 8e5a8552f..86b8e47eb 100644 --- a/app/frontend/src/javascript/components/fab-button.tsx +++ b/app/frontend/src/javascript/components/fab-button.tsx @@ -29,7 +29,7 @@ export const FabButton: React.FC = ({ onClick, icon, className, } return ( - diff --git a/app/frontend/src/javascript/components/fab-modal.tsx b/app/frontend/src/javascript/components/fab-modal.tsx index 0b5a12956..caf4dd67c 100644 --- a/app/frontend/src/javascript/components/fab-modal.tsx +++ b/app/frontend/src/javascript/components/fab-modal.tsx @@ -2,12 +2,13 @@ * This component is a template for a modal dialog that wraps the application style */ -import React, { ReactNode } from 'react'; +import React, { ReactNode, SyntheticEvent } from 'react'; import Modal from 'react-modal'; import { useTranslation } from 'react-i18next'; import { Loader } from './loader'; import CustomAssetAPI from '../api/custom-asset'; import { CustomAssetName } from '../models/custom-asset'; +import { FabButton } from './fab-button'; Modal.setAppElement('body'); @@ -25,12 +26,13 @@ interface FabModalProps { closeButton?: boolean, className?: string, width?: ModalSize, - customFooter?: ReactNode + customFooter?: ReactNode, + onConfirm?: (event: SyntheticEvent) => void } const blackLogoFile = CustomAssetAPI.get(CustomAssetName.LogoBlackFile); -export const FabModal: React.FC = ({ title, isOpen, toggleModal, children, confirmButton, className, width = 'sm', closeButton, customFooter }) => { +export const FabModal: React.FC = ({ title, isOpen, toggleModal, children, confirmButton, className, width = 'sm', closeButton, customFooter, onConfirm }) => { const { t } = useTranslation('shared'); const blackLogo = blackLogoFile.read(); @@ -56,7 +58,7 @@ export const FabModal: React.FC = ({ title, isOpen, toggleModal, } return ( - @@ -73,8 +75,8 @@ export const FabModal: React.FC = ({ title, isOpen, toggleModal,
- {hasCloseButton() &&} - {hasConfirmButton() && {confirmButton}} + {hasCloseButton() &&{t('app.shared.buttons.close')}} + {hasConfirmButton() && {confirmButton}} {hasCustomFooter() && customFooter}
diff --git a/app/frontend/src/javascript/components/payment-schedules-table.tsx b/app/frontend/src/javascript/components/payment-schedules-table.tsx index 7ccac319c..8f29e3b52 100644 --- a/app/frontend/src/javascript/components/payment-schedules-table.tsx +++ b/app/frontend/src/javascript/components/payment-schedules-table.tsx @@ -2,7 +2,7 @@ * This component shows a list of all payment schedules with their associated deadlines (aka. PaymentScheduleItem) and invoices */ -import React, { ReactEventHandler, useState } from 'react'; +import React, { ReactEventHandler, ReactNode, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Loader } from './loader'; import moment from 'moment'; @@ -10,6 +10,8 @@ import { IFablab } from '../models/fablab'; import _ from 'lodash'; import { PaymentSchedule, PaymentScheduleItem, PaymentScheduleItemState } from '../models/payment-schedule'; import { FabButton } from './fab-button'; +import { FabModal } from './fab-modal'; +import PaymentScheduleAPI from '../api/payment-schedule'; declare var Fablab: IFablab; @@ -21,13 +23,15 @@ interface PaymentSchedulesTableProps { const PaymentSchedulesTableComponent: React.FC = ({ paymentSchedules, showCustomer }) => { const { t } = useTranslation('admin'); - const [showExpanded, setShowExpanded] = useState({}); + const [showExpanded, setShowExpanded] = useState>(new Map()); + const [showConfirmCashing, setShowConfirmCashing] = useState(false); + const [tempDeadline, setTempDeadline] = useState(null); /** * Check if the requested payment schedule is displayed with its deadlines (PaymentScheduleItem) or without them */ const isExpanded = (paymentScheduleId: number): boolean => { - return showExpanded[paymentScheduleId]; + return showExpanded.get(paymentScheduleId); } /** @@ -71,9 +75,9 @@ const PaymentSchedulesTableComponent: React.FC = ({ const togglePaymentScheduleDetails = (paymentScheduleId: number): ReactEventHandler => { return (): void => { if (isExpanded(paymentScheduleId)) { - setShowExpanded(Object.assign({}, showExpanded, { [paymentScheduleId]: false })); + setShowExpanded((prev) => new Map(prev).set(paymentScheduleId, false)); } else { - setShowExpanded(Object.assign({}, showExpanded, { [paymentScheduleId]: true })); + setShowExpanded((prev) => new Map(prev).set(paymentScheduleId, true)); } } } @@ -146,14 +150,42 @@ const PaymentSchedulesTableComponent: React.FC = ({ const handleConfirmCheckPayment = (item: PaymentScheduleItem): ReactEventHandler => { return (): void => { - /* - TODO - - display confirmation modal - - create /api/payment_schedule/item/confirm_check endpoint and post to it - */ + setTempDeadline(item); + toggleConfirmCashingModal(); } } + const onCheckCashingConfirmed = (): void => { + const api = new PaymentScheduleAPI(); + api.cashCheck(tempDeadline.id).then(res => { + // TODO refresh display + }); + // TODO create /api/payment_schedule/item/confirm_check endpoint and post to it + } + + /** + * Show/hide the modal dialog that enable to confirm the cashing of the check for a given deadline. + */ + const toggleConfirmCashingModal = (): void => { + setShowConfirmCashing(!showConfirmCashing); + } + + /** + * Dynamically build the content of the modal depending on the currently selected deadline + */ + const cashingModalContent = (): ReactNode => { + if (tempDeadline) { + return ( + {t('app.admin.invoices.schedules_table.confirm_check_cashing_body', { + AMOUNT: formatPrice(tempDeadline.amount), + DATE: formatDate(tempDeadline.due_date) + })} + ); + } + + return ; + } + const handleSolveAction = (item: PaymentScheduleItem): ReactEventHandler => { return (): void => { /* @@ -239,6 +271,16 @@ const PaymentSchedulesTableComponent: React.FC = ({ )} +
+ + {cashingModalContent()} + +
); }; diff --git a/app/frontend/src/stylesheets/modules/fab-modal.scss b/app/frontend/src/stylesheets/modules/fab-modal.scss index f81344e64..132f71afc 100644 --- a/app/frontend/src/stylesheets/modules/fab-modal.scss +++ b/app/frontend/src/stylesheets/modules/fab-modal.scss @@ -69,23 +69,7 @@ border-top: 1px solid #e5e5e5; .modal-btn { - margin-bottom: 0; - margin-left: 5px; - display: inline-block; - font-weight: normal; - text-align: center; - white-space: nowrap; - vertical-align: middle; - touch-action: manipulation; - cursor: pointer; - background-image: none; - padding: 6px 12px; - font-size: 16px; - line-height: 1.5; - border-radius: 4px; - &--close { - @extend .modal-btn; color: black; background-color: #fbfbfb; border: 1px solid #c9c9c9; @@ -96,7 +80,7 @@ } &--confirm { - @extend .modal-btn; + margin-left: 0.5em; } } } diff --git a/app/themes/casemate/style.scss.erb b/app/themes/casemate/style.scss.erb index 5333982c8..83106afdb 100644 --- a/app/themes/casemate/style.scss.erb +++ b/app/themes/casemate/style.scss.erb @@ -309,3 +309,21 @@ section#cookies-modal div.cookies-consent .cookies-actions button.accept { } } } + +.fab-modal { + .fab-modal-footer { + .modal-btn--confirm { + & { + background-color: $secondary; + color: $secondary-text-color; + border-color: $secondary + } + + &:hover { + background-color: $secondary-dark !important; + border-color: $secondary-dark !important; + color: $secondary-text-color; + } + } + } +} diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 79a615f9a..8e3e1a83f 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -664,6 +664,9 @@ en: confirm_payment: "Confirm payment" solve: "Solve" update_card: "Update the card" + confirm_check_cashing: "Confirm the cashing of the check" + confirm_check_cashing_body: "You must cash a check of {AMOUNT} for the deadline of {DATE}. By confirming the cashing of the check, an invoice will be generated for this due date." + confirm_button: "Confirm" document_filters: reference: "Reference" customer: "Customer" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 6cef0a3c4..99a55b9c7 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -664,6 +664,9 @@ fr: confirm_payment: "Confirmer l'encaissement" solve: "Résoudre" update_card: "Mettre à jour la carte" + confirm_check_cashing: "Confirmer l'encaissement du chèque" + confirm_check_cashing_body: "Vous devez encaisser un chèque de {AMOUNT} pour l'échéance du {DATE}. En confirmant l'encaissement du chèque, une facture sera générée pour cette échéance." + confirm_button: "Confirmer" document_filters: reference: "Référence" customer: "Client"