1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-21 15:54:22 +01:00

reactored payment-schedules-table to extract buttons to payment-schedule-item-actions

This commit is contained in:
Sylvain 2022-01-17 10:48:23 +01:00
parent a189190a8e
commit d8f27f0b1a
4 changed files with 125 additions and 118 deletions

View File

@ -4,14 +4,13 @@ import {
PaymentScheduleItem, PaymentScheduleItem,
PaymentScheduleItemState PaymentScheduleItemState
} from '../../models/payment-schedule'; } from '../../models/payment-schedule';
import React, { ReactElement, useEffect, useState } from 'react'; import React, { ReactElement, useState } from 'react';
import { FabButton } from '../base/fab-button'; import { FabButton } from '../base/fab-button';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { User, UserRole } from '../../models/user'; import { User, UserRole } from '../../models/user';
import PaymentScheduleAPI from '../../api/payment-schedule'; import PaymentScheduleAPI from '../../api/payment-schedule';
import { FabModal } from '../base/fab-modal'; import { FabModal } from '../base/fab-modal';
import FormatLib from '../../lib/format'; import FormatLib from '../../lib/format';
import { StripeElements } from '../payment/stripe/stripe-elements';
import { StripeConfirmModal } from '../payment/stripe/stripe-confirm-modal'; import { StripeConfirmModal } from '../payment/stripe/stripe-confirm-modal';
import { UpdateCardModal } from '../payment/update-card-modal'; import { UpdateCardModal } from '../payment/update-card-modal';
@ -28,13 +27,14 @@ interface PaymentScheduleItemActionsProps {
onSuccess: () => void, onSuccess: () => void,
onCardUpdateSuccess: () => void onCardUpdateSuccess: () => void
operator: User, operator: User,
displayOnceMap: Map<TypeOnce, Map<number, boolean>>, displayOnceMap: Map<TypeOnce, Map<number, number>>,
show: boolean,
} }
/** /**
* This component is responsible for rendering the actions buttons for a payment schedule item. * This component is responsible for rendering the actions buttons for a payment schedule item.
*/ */
export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProps> = ({ paymentScheduleItem, paymentSchedule, onError, onSuccess, onCardUpdateSuccess, displayOnceMap, operator }) => { export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProps> = ({ paymentScheduleItem, paymentSchedule, onError, onSuccess, onCardUpdateSuccess, displayOnceMap, operator, show }) => {
const { t } = useTranslation('shared'); const { t } = useTranslation('shared');
// is open, the modal dialog to cancel the associated subscription? // is open, the modal dialog to cancel the associated subscription?
@ -50,14 +50,6 @@ export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProp
// the user cannot confirm the action modal (3D secure), unless he has resolved the pending action // the user cannot confirm the action modal (3D secure), unless he has resolved the pending action
const [isConfirmActionDisabled, setConfirmActionDisabled] = useState<boolean>(true); const [isConfirmActionDisabled, setConfirmActionDisabled] = useState<boolean>(true);
useEffect(() => {
Object.keys(TypeOnce).forEach((type) => {
if (!displayOnceMap.has(type as TypeOnce)) {
displayOnceMap.set(type as TypeOnce, new Map<number, boolean>());
}
});
}, []);
/** /**
* Check if the current operator has administrative rights or is a normal member * Check if the current operator has administrative rights or is a normal member
*/ */
@ -82,10 +74,12 @@ export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProp
* Return a button to cancel the given subscription, if the user is privileged enough * Return a button to cancel the given subscription, if the user is privileged enough
*/ */
const cancelSubscriptionButton = (): ReactElement => { const cancelSubscriptionButton = (): ReactElement => {
if (isPrivileged() && !displayOnceMap.get(TypeOnce.SubscriptionCancel).get(paymentSchedule.id)) { const displayOnceStatus = displayOnceMap.get(TypeOnce.SubscriptionCancel).get(paymentSchedule.id);
displayOnceMap.get(TypeOnce.SubscriptionCancel).set(paymentSchedule.id, true); if (isPrivileged() && (!displayOnceStatus || displayOnceStatus === paymentScheduleItem.id)) {
displayOnceMap.get(TypeOnce.SubscriptionCancel).set(paymentSchedule.id, paymentScheduleItem.id);
return ( return (
<FabButton onClick={toggleCancelSubscriptionModal} <FabButton key={`cancel-subscription-${paymentSchedule.id}`}
onClick={toggleCancelSubscriptionModal}
icon={<i className="fas fa-times" />}> icon={<i className="fas fa-times" />}>
{t('app.shared.payment_schedule_item_actions.cancel_subscription')} {t('app.shared.payment_schedule_item_actions.cancel_subscription')}
</FabButton> </FabButton>
@ -99,7 +93,8 @@ export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProp
const confirmTransferButton = (): ReactElement => { const confirmTransferButton = (): ReactElement => {
if (isPrivileged()) { if (isPrivileged()) {
return ( return (
<FabButton onClick={toggleConfirmTransferModal} <FabButton key={`confirm-transfer-${paymentScheduleItem.id}`}
onClick={toggleConfirmTransferModal}
icon={<i className="fas fa-university"/>}> icon={<i className="fas fa-university"/>}>
{t('app.shared.payment_schedule_item_actions.confirm_payment')} {t('app.shared.payment_schedule_item_actions.confirm_payment')}
</FabButton> </FabButton>
@ -113,7 +108,8 @@ export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProp
const confirmCheckButton = (): ReactElement => { const confirmCheckButton = (): ReactElement => {
if (isPrivileged()) { if (isPrivileged()) {
return ( return (
<FabButton onClick={toggleConfirmCashingModal} <FabButton key={`confirm-check-${paymentScheduleItem.id}`}
onClick={toggleConfirmCashingModal}
icon={<i className="fas fa-check"/>}> icon={<i className="fas fa-check"/>}>
{t('app.shared.payment_schedule_item_actions.confirm_check')} {t('app.shared.payment_schedule_item_actions.confirm_check')}
</FabButton> </FabButton>
@ -126,7 +122,8 @@ export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProp
*/ */
const solveActionButton = (): ReactElement => { const solveActionButton = (): ReactElement => {
return ( return (
<FabButton onClick={toggleResolveActionModal} <FabButton key={`solve-action-${paymentScheduleItem.id}`}
onClick={toggleResolveActionModal}
icon={<i className="fas fa-wrench"/>}> icon={<i className="fas fa-wrench"/>}>
{t('app.shared.payment_schedule_item_actions.resolve_action')} {t('app.shared.payment_schedule_item_actions.resolve_action')}
</FabButton> </FabButton>
@ -137,10 +134,12 @@ export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProp
* Return a button to update the credit card associated with the payment schedule * Return a button to update the credit card associated with the payment schedule
*/ */
const updateCardButton = (): ReactElement => { const updateCardButton = (): ReactElement => {
if (!displayOnceMap.get(TypeOnce.CardUpdate).get(paymentSchedule.id)) { const displayOnceStatus = displayOnceMap.get(TypeOnce.SubscriptionCancel).get(paymentSchedule.id);
displayOnceMap.get(TypeOnce.CardUpdate).set(paymentSchedule.id, true); if (isPrivileged() && (!displayOnceStatus || displayOnceStatus === paymentScheduleItem.id)) {
displayOnceMap.get(TypeOnce.CardUpdate).set(paymentSchedule.id, paymentScheduleItem.id);
return ( return (
<FabButton onClick={toggleUpdateCardModal} <FabButton key={`update-card-${paymentSchedule.id}`}
onClick={toggleUpdateCardModal}
icon={<i className="fas fa-credit-card"/>}> icon={<i className="fas fa-credit-card"/>}>
{t('app.shared.payment_schedule_item_actions.update_card')} {t('app.shared.payment_schedule_item_actions.update_card')}
</FabButton> </FabButton>
@ -169,7 +168,7 @@ export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProp
*/ */
const errorActions = (): ReactElement => { const errorActions = (): ReactElement => {
// if the payment schedule is canceled/in error, the schedule is over, and we can't update the card // if the payment schedule is canceled/in error, the schedule is over, and we can't update the card
displayOnceMap.get(TypeOnce.CardUpdate).set(paymentSchedule.id, true); displayOnceMap.get(TypeOnce.CardUpdate).set(paymentSchedule.id, paymentScheduleItem.id);
if (isPrivileged()) { if (isPrivileged()) {
return cancelSubscriptionButton(); return cancelSubscriptionButton();
} else { } else {
@ -298,7 +297,7 @@ export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProp
}); });
}; };
if (!displayOnceMap.get(TypeOnce.CardUpdate) || !displayOnceMap.get(TypeOnce.SubscriptionCancel)) return null; if (!show) return null;
return ( return (
<span className="payment-schedule-item-actions"> <span className="payment-schedule-item-actions">
@ -311,58 +310,58 @@ export const PaymentScheduleItemActions: React.FC<PaymentScheduleItemActionsProp
{paymentScheduleItem.state === PaymentScheduleItemState.New && newActions()} {paymentScheduleItem.state === PaymentScheduleItemState.New && newActions()}
<div className="modals"> <div className="modals">
{/* Confirm the cashing of the current deadline by check */} {/* Confirm the cashing of the current deadline by check */}
<FabModal title={t('app.shared.schedules_table.confirm_check_cashing')} <FabModal title={t('app.shared.payment_schedule_item_actions.confirm_check_cashing')}
isOpen={showConfirmCashing} isOpen={showConfirmCashing}
toggleModal={toggleConfirmCashingModal} toggleModal={toggleConfirmCashingModal}
onConfirm={onCheckCashingConfirmed} onConfirm={onCheckCashingConfirmed}
closeButton={true} closeButton={true}
confirmButton={t('app.shared.schedules_table.confirm_button')}> confirmButton={t('app.shared.payment_schedule_item_actions.confirm_button')}>
<span> <span>
{t('app.shared.schedules_table.confirm_check_cashing_body', { {t('app.shared.payment_schedule_item_actions.confirm_check_cashing_body', {
AMOUNT: FormatLib.price(paymentScheduleItem.amount), AMOUNT: FormatLib.price(paymentScheduleItem.amount),
DATE: FormatLib.date(paymentScheduleItem.due_date) DATE: FormatLib.date(paymentScheduleItem.due_date)
})} })}
</span> </span>
</FabModal> </FabModal>
{/* Confirm the bank transfer for the current deadline */} {/* Confirm the bank transfer for the current deadline */}
<FabModal title={t('app.shared.schedules_table.confirm_bank_transfer')} <FabModal title={t('app.shared.payment_schedule_item_actions.confirm_bank_transfer')}
isOpen={showConfirmTransfer} isOpen={showConfirmTransfer}
toggleModal={toggleConfirmTransferModal} toggleModal={toggleConfirmTransferModal}
onConfirm={onTransferConfirmed} onConfirm={onTransferConfirmed}
closeButton={true} closeButton={true}
confirmButton={t('app.shared.schedules_table.confirm_button')}> confirmButton={t('app.shared.payment_schedule_item_actions.confirm_button')}>
<span> <span>
{t('app.shared.schedules_table.confirm_bank_transfer_body', { {t('app.shared.payment_schedule_item_actions.confirm_bank_transfer_body', {
AMOUNT: FormatLib.price(paymentScheduleItem.amount), AMOUNT: FormatLib.price(paymentScheduleItem.amount),
DATE: FormatLib.date(paymentScheduleItem.due_date) DATE: FormatLib.date(paymentScheduleItem.due_date)
})} })}
</span> </span>
</FabModal> </FabModal>
{/* Cancel the subscription */} {/* Cancel the subscription */}
<FabModal title={t('app.shared.schedules_table.cancel_subscription')} <FabModal title={t('app.shared.payment_schedule_item_actions.cancel_subscription')}
isOpen={showCancelSubscription} isOpen={showCancelSubscription}
toggleModal={toggleCancelSubscriptionModal} toggleModal={toggleCancelSubscriptionModal}
onConfirm={onCancelSubscriptionConfirmed} onConfirm={onCancelSubscriptionConfirmed}
closeButton={true} closeButton={true}
confirmButton={t('app.shared.schedules_table.confirm_button')}> confirmButton={t('app.shared.payment_schedule_item_actions.confirm_button')}>
{t('app.shared.schedules_table.confirm_cancel_subscription')} {t('app.shared.payment_schedule_item_actions.confirm_cancel_subscription')}
</FabModal> </FabModal>
<StripeElements> {/* 3D secure confirmation */}
{/* 3D secure confirmation */} <StripeConfirmModal isOpen={showResolveAction}
<StripeConfirmModal isOpen={showResolveAction} toggleModal={toggleResolveActionModal}
toggleModal={toggleResolveActionModal} onSuccess={afterConfirmAction}
onSuccess={afterConfirmAction} paymentScheduleItemId={paymentScheduleItem.id} />
paymentScheduleItemId={paymentScheduleItem.id} /> {/* Update credit card */}
{/* Update credit card */} <UpdateCardModal isOpen={showUpdateCard}
<UpdateCardModal isOpen={showUpdateCard} toggleModal={toggleUpdateCardModal}
toggleModal={toggleUpdateCardModal} operator={operator}
operator={operator} afterSuccess={handleCardUpdateSuccess}
afterSuccess={handleCardUpdateSuccess} onError={onError}
onError={onError} schedule={paymentSchedule}>
schedule={paymentSchedule}> </UpdateCardModal>
</UpdateCardModal>
</StripeElements>
</div> </div>
</span> </span>
); );
}; };
PaymentScheduleItemActions.defaultProps = { show: false };

View File

@ -10,6 +10,7 @@ import {
} from '../../models/payment-schedule'; } from '../../models/payment-schedule';
import FormatLib from '../../lib/format'; import FormatLib from '../../lib/format';
import { PaymentScheduleItemActions, TypeOnce } from './payment-schedule-item-actions'; import { PaymentScheduleItemActions, TypeOnce } from './payment-schedule-item-actions';
import { StripeElements } from '../payment/stripe/stripe-elements';
interface PaymentSchedulesTableProps { interface PaymentSchedulesTableProps {
paymentSchedules: Array<PaymentSchedule>, paymentSchedules: Array<PaymentSchedule>,
@ -29,7 +30,10 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
// for each payment schedule: are the details (all deadlines) shown or hidden? // for each payment schedule: are the details (all deadlines) shown or hidden?
const [showExpanded, setShowExpanded] = useState<Map<number, boolean>>(new Map()); const [showExpanded, setShowExpanded] = useState<Map<number, boolean>>(new Map());
// we want to display some buttons only once. This map keep track of the buttons that have been displayed. // we want to display some buttons only once. This map keep track of the buttons that have been displayed.
const [displayOnceMap] = useState<Map<TypeOnce, Map<number, boolean>>>(new Map()); const [displayOnceMap] = useState<Map<TypeOnce, Map<number, number>>>(new Map([
[TypeOnce.SubscriptionCancel, new Map()],
[TypeOnce.CardUpdate, new Map()]
]));
/** /**
* Check if the requested payment schedule is displayed with its deadlines (PaymentScheduleItem) or without them * Check if the requested payment schedule is displayed with its deadlines (PaymentScheduleItem) or without them
@ -107,69 +111,72 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
return ( return (
<div> <div>
<table className="schedules-table"> <StripeElements>
<thead> <table className="schedules-table">
<tr> <thead>
<th className="w-35" /> <tr>
<th className="w-200">{t('app.shared.schedules_table.schedule_num')}</th> <th className="w-35" />
<th className="w-200">{t('app.shared.schedules_table.date')}</th> <th className="w-200">{t('app.shared.schedules_table.schedule_num')}</th>
<th className="w-120">{t('app.shared.schedules_table.price')}</th> <th className="w-200">{t('app.shared.schedules_table.date')}</th>
{showCustomer && <th className="w-200">{t('app.shared.schedules_table.customer')}</th>} <th className="w-120">{t('app.shared.schedules_table.price')}</th>
<th className="w-200"/> {showCustomer && <th className="w-200">{t('app.shared.schedules_table.customer')}</th>}
</tr> <th className="w-200"/>
</thead> </tr>
<tbody> </thead>
{paymentSchedules.map(p => <tr key={p.id}> <tbody>
<td colSpan={showCustomer ? 6 : 5}> {paymentSchedules.map(p => <tr key={p.id}>
<table className="schedules-table-body"> <td colSpan={showCustomer ? 6 : 5}>
<tbody> <table className="schedules-table-body">
<tr> <tbody>
<td className="w-35 row-header" onClick={togglePaymentScheduleDetails(p.id)}>{expandCollapseIcon(p.id)}</td> <tr>
<td className="w-200">{p.reference}</td> <td className="w-35 row-header" onClick={togglePaymentScheduleDetails(p.id)}>{expandCollapseIcon(p.id)}</td>
<td className="w-200">{FormatLib.date(_.minBy(p.items, 'due_date').due_date)}</td> <td className="w-200">{p.reference}</td>
<td className="w-120">{FormatLib.price(p.total)}</td> <td className="w-200">{FormatLib.date(_.minBy(p.items, 'due_date').due_date)}</td>
{showCustomer && <td className="w-200">{p.user.name}</td>} <td className="w-120">{FormatLib.price(p.total)}</td>
<td className="w-200">{downloadScheduleButton(p.id)}</td> {showCustomer && <td className="w-200">{p.user.name}</td>}
</tr> <td className="w-200">{downloadScheduleButton(p.id)}</td>
<tr style={{ display: statusDisplay(p.id) }}> </tr>
<td className="w-35" /> <tr style={{ display: statusDisplay(p.id) }}>
<td colSpan={showCustomer ? 5 : 4}> <td className="w-35" />
<div> <td colSpan={showCustomer ? 5 : 4}>
<table className="schedule-items-table"> <div>
<thead> <table className="schedule-items-table">
<tr> <thead>
<th className="w-120">{t('app.shared.schedules_table.deadline')}</th> <tr>
<th className="w-120">{t('app.shared.schedules_table.amount')}</th> <th className="w-120">{t('app.shared.schedules_table.deadline')}</th>
<th className="w-200">{t('app.shared.schedules_table.state')}</th> <th className="w-120">{t('app.shared.schedules_table.amount')}</th>
<th className="w-200" /> <th className="w-200">{t('app.shared.schedules_table.state')}</th>
</tr> <th className="w-200" />
</thead> </tr>
<tbody> </thead>
{_.orderBy(p.items, 'due_date').map(item => <tr key={item.id}> <tbody>
<td>{FormatLib.date(item.due_date)}</td> {_.orderBy(p.items, 'due_date').map(item => <tr key={item.id}>
<td>{FormatLib.price(item.amount)}</td> <td>{FormatLib.date(item.due_date)}</td>
<td>{formatState(item, p)}</td> <td>{FormatLib.price(item.amount)}</td>
<td> <td>{formatState(item, p)}</td>
<PaymentScheduleItemActions paymentScheduleItem={item} <td>
paymentSchedule={p} <PaymentScheduleItemActions paymentScheduleItem={item}
onError={onError} paymentSchedule={p}
onSuccess={refreshSchedulesTable} onError={onError}
onCardUpdateSuccess={onCardUpdateSuccess} onSuccess={refreshSchedulesTable}
operator={operator} onCardUpdateSuccess={onCardUpdateSuccess}
displayOnceMap={displayOnceMap} /> operator={operator}
</td> displayOnceMap={displayOnceMap}
</tr>)} show={isExpanded(p.id)}/>
</tbody> </td>
</table> </tr>)}
</div> </tbody>
</td> </table>
</tr> </div>
</tbody> </td>
</table> </tr>
</td> </tbody>
</tr>)} </table>
</tbody> </td>
</table> </tr>)}
</tbody>
</table>
</StripeElements>
</div> </div>
); );
}; };

View File

@ -498,19 +498,20 @@ en:
method_card: "by card" method_card: "by card"
method_check: "by check" method_check: "by check"
method_transfer: "by transfer" method_transfer: "by transfer"
payment_schedule_item_actions:
download: "Download"
cancel_subscription: "Cancel the subscription"
confirm_payment: "Confirm payment" confirm_payment: "Confirm payment"
solve: "Solve" confirm_check: "Confirm cashing"
resolve_action: "Resolve the action"
update_card: "Update the card" update_card: "Update the card"
please_ask_reception: "For any questions, please contact the FabLab's reception."
confirm_button: "Confirm"
confirm_check_cashing: "Confirm the cashing of the check" 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_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_bank_transfer: "Confirm the bank transfer" confirm_bank_transfer: "Confirm the bank transfer"
confirm_bank_transfer_body: "You must confirm the receipt of {AMOUNT} for the deadline of {DATE}. By confirming the bank transfer, an invoice will be generated for this due date." confirm_bank_transfer_body: "You must confirm the receipt of {AMOUNT} for the deadline of {DATE}. By confirming the bank transfer, an invoice will be generated for this due date."
confirm_button: "Confirm"
resolve_action: "Resolve the action"
ok_button: "OK"
cancel_subscription: "Cancel the subscription"
confirm_cancel_subscription: "You're about to cancel this payment schedule and the related subscription. Are you sure?" confirm_cancel_subscription: "You're about to cancel this payment schedule and the related subscription. Are you sure?"
please_ask_reception: "For any questions, please contact the FabLab's reception."
payment_modal: payment_modal:
online_payment_disabled: "Online payment is not available. Please contact the FabLab's reception directly." online_payment_disabled: "Online payment is not available. Please contact the FabLab's reception directly."
unexpected_error: "An error occurred. Please report this issue to the Fab-Manager's team." unexpected_error: "An error occurred. Please report this issue to the Fab-Manager's team."

View File

@ -4,7 +4,7 @@
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"lib": ["es6", "dom", "es2015.collection", "es2015.iterable"], "lib": ["es6", "dom", "es2015.collection", "es2015.iterable"],
"module": "es6", "module": "ES2020",
"moduleResolution": "node", "moduleResolution": "node",
"sourceMap": true, "sourceMap": true,
"target": "es5", "target": "es5",