mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
show more results for the payment schedules interface
This commit is contained in:
parent
eddf23622d
commit
d22d011a10
38
app/frontend/src/javascript/components/fab-button.tsx
Normal file
38
app/frontend/src/javascript/components/fab-button.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* This component is a template for a clickable button that wraps the application style
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { ReactNode, SyntheticEvent } from 'react';
|
||||||
|
|
||||||
|
interface FabButtonProps {
|
||||||
|
onClick?: (event: SyntheticEvent) => void,
|
||||||
|
icon?: ReactNode,
|
||||||
|
className?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const FabButton: React.FC<FabButtonProps> = ({ onClick, icon, className, children }) => {
|
||||||
|
/**
|
||||||
|
* Check if the current component was provided an icon to display
|
||||||
|
*/
|
||||||
|
const hasIcon = (): boolean => {
|
||||||
|
return !!icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the action of the button
|
||||||
|
*/
|
||||||
|
const handleClick = (e: SyntheticEvent): void => {
|
||||||
|
if (typeof onClick === 'function') {
|
||||||
|
onClick(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button onClick={handleClick} className={`fab-button ${className}`}>
|
||||||
|
{hasIcon() && <span className="fab-button--icon">{icon}</span>}
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -10,23 +10,63 @@ import { react2angular } from 'react2angular';
|
|||||||
import PaymentScheduleAPI from '../api/payment-schedule';
|
import PaymentScheduleAPI from '../api/payment-schedule';
|
||||||
import { DocumentFilters } from './document-filters';
|
import { DocumentFilters } from './document-filters';
|
||||||
import { PaymentSchedulesTable } from './payment-schedules-table';
|
import { PaymentSchedulesTable } from './payment-schedules-table';
|
||||||
|
import { FabButton } from './fab-button';
|
||||||
|
|
||||||
declare var Application: IApplication;
|
declare var Application: IApplication;
|
||||||
|
|
||||||
|
const PAGE_SIZE = 20;
|
||||||
const paymentSchedulesList = PaymentScheduleAPI.list({ query: { page: 1, size: 20 } });
|
const paymentSchedulesList = PaymentScheduleAPI.list({ query: { page: 1, size: 20 } });
|
||||||
|
|
||||||
const PaymentSchedulesList: React.FC = () => {
|
const PaymentSchedulesList: React.FC = () => {
|
||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
const [paymentSchedules, setPaymentSchedules] = useState(paymentSchedulesList.read());
|
const [paymentSchedules, setPaymentSchedules] = useState(paymentSchedulesList.read());
|
||||||
|
const [pageNumber, setPageNumber] = useState(1);
|
||||||
|
const [referenceFilter, setReferenceFilter] = useState(null);
|
||||||
|
const [customerFilter, setCustomerFilter] = useState(null);
|
||||||
|
const [dateFilter, setDateFilter] = useState(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch from the API the payments schedules matching the given filters and reset the results table with the new schedules.
|
||||||
|
*/
|
||||||
const handleFiltersChange = ({ reference, customer, date }): void => {
|
const handleFiltersChange = ({ reference, customer, date }): void => {
|
||||||
|
setReferenceFilter(reference);
|
||||||
|
setCustomerFilter(customer);
|
||||||
|
setDateFilter(date);
|
||||||
|
|
||||||
const api = new PaymentScheduleAPI();
|
const api = new PaymentScheduleAPI();
|
||||||
api.list({ query: { reference, customer, date, page: 1, size: 20 }}).then((res) => {
|
api.list({ query: { reference, customer, date, page: 1, size: PAGE_SIZE }}).then((res) => {
|
||||||
setPaymentSchedules(res);
|
setPaymentSchedules(res);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch from the API the next payment schedules to display, for the current filters, and append them to the current results table.
|
||||||
|
*/
|
||||||
|
const handleLoadMore = (): void => {
|
||||||
|
setPageNumber(pageNumber + 1);
|
||||||
|
|
||||||
|
const api = new PaymentScheduleAPI();
|
||||||
|
api.list({ query: { reference: referenceFilter, customer: customerFilter, date: dateFilter, page: pageNumber + 1, size: PAGE_SIZE }}).then((res) => {
|
||||||
|
const list = paymentSchedules.concat(res);
|
||||||
|
setPaymentSchedules(list);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current collection of payment schedules is empty or not.
|
||||||
|
*/
|
||||||
|
const hasSchedules = (): boolean => {
|
||||||
|
return paymentSchedules.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are some results for the current filters that aren't currently shown.
|
||||||
|
*/
|
||||||
|
const hasMoreSchedules = (): boolean => {
|
||||||
|
return hasSchedules() && paymentSchedules.length < paymentSchedules[0].max_length;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="payment-schedules-list">
|
<div className="payment-schedules-list">
|
||||||
<h3>
|
<h3>
|
||||||
@ -36,7 +76,11 @@ const PaymentSchedulesList: React.FC = () => {
|
|||||||
<div className="schedules-filters">
|
<div className="schedules-filters">
|
||||||
<DocumentFilters onFilterChange={handleFiltersChange} />
|
<DocumentFilters onFilterChange={handleFiltersChange} />
|
||||||
</div>
|
</div>
|
||||||
<PaymentSchedulesTable paymentSchedules={paymentSchedules} showCustomer={true} />
|
{!hasSchedules() && <div>{t('app.admin.invoices.payment_schedules.no_payment_schedules')}</div>}
|
||||||
|
{hasSchedules() && <div className="schedules-list">
|
||||||
|
<PaymentSchedulesTable paymentSchedules={paymentSchedules} showCustomer={true} />
|
||||||
|
{hasMoreSchedules() && <FabButton className="load-more" onClick={handleLoadMore}>{t('app.admin.invoices.payment_schedules.load_more')}</FabButton>}
|
||||||
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import moment from 'moment';
|
|||||||
import { IFablab } from '../models/fablab';
|
import { IFablab } from '../models/fablab';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { PaymentSchedule, PaymentScheduleItem, PaymentScheduleItemState } from '../models/payment-schedule';
|
import { PaymentSchedule, PaymentScheduleItem, PaymentScheduleItemState } from '../models/payment-schedule';
|
||||||
|
import { FabButton } from './fab-button';
|
||||||
|
|
||||||
declare var Fablab: IFablab;
|
declare var Fablab: IFablab;
|
||||||
|
|
||||||
@ -119,24 +120,24 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
|||||||
return downloadButton(TargetType.Invoice, item.invoice_id);
|
return downloadButton(TargetType.Invoice, item.invoice_id);
|
||||||
case PaymentScheduleItemState.Pending:
|
case PaymentScheduleItemState.Pending:
|
||||||
return (
|
return (
|
||||||
<button className="action-button" onClick={handleConfirmCheckPayment(item)}>
|
<FabButton onClick={handleConfirmCheckPayment(item)}
|
||||||
<i className="fas fa-money-check" />
|
icon={<i className="fas fa-money-check" />}>
|
||||||
{t('app.admin.invoices.schedules_table.confirm_payment')}
|
{t('app.admin.invoices.schedules_table.confirm_payment')}
|
||||||
</button>
|
</FabButton>
|
||||||
);
|
);
|
||||||
case PaymentScheduleItemState.RequireAction:
|
case PaymentScheduleItemState.RequireAction:
|
||||||
return (
|
return (
|
||||||
<button className="action-button" onClick={handleSolveAction(item)}>
|
<FabButton onClick={handleSolveAction(item)}
|
||||||
<i className="fas fa-wrench" />
|
icon={<i className="fas fa-wrench" />}>
|
||||||
{t('app.admin.invoices.schedules_table.solve')}
|
{t('app.admin.invoices.schedules_table.solve')}
|
||||||
</button>
|
</FabButton>
|
||||||
);
|
);
|
||||||
case PaymentScheduleItemState.RequirePaymentMethod:
|
case PaymentScheduleItemState.RequirePaymentMethod:
|
||||||
return (
|
return (
|
||||||
<button className="action-button" onClick={handleUpdateCard(item)}>
|
<FabButton onClick={handleUpdateCard(item)}
|
||||||
<i className="fas fa-credit-card" />
|
icon={<i className="fas fa-credit-card" />}>
|
||||||
{t('app.admin.invoices.schedules_table.update_card')}
|
{t('app.admin.invoices.schedules_table.update_card')}
|
||||||
</button>
|
</FabButton>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return <span />
|
return <span />
|
||||||
|
@ -27,6 +27,7 @@ export interface PaymentScheduleItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymentSchedule {
|
export interface PaymentSchedule {
|
||||||
|
max_length: number;
|
||||||
id: number,
|
id: number,
|
||||||
scheduled_type: string,
|
scheduled_type: string,
|
||||||
scheduled_id: number,
|
scheduled_id: number,
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
@import "modules/stripe";
|
@import "modules/stripe";
|
||||||
@import "modules/tour";
|
@import "modules/tour";
|
||||||
@import "modules/fab-modal";
|
@import "modules/fab-modal";
|
||||||
|
@import "modules/fab-button";
|
||||||
@import "modules/payment-schedule-summary";
|
@import "modules/payment-schedule-summary";
|
||||||
@import "modules/wallet-info";
|
@import "modules/wallet-info";
|
||||||
@import "modules/stripe-modal";
|
@import "modules/stripe-modal";
|
||||||
|
39
app/frontend/src/stylesheets/modules/fab-button.scss
Normal file
39
app/frontend/src/stylesheets/modules/fab-button.scss
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
.fab-button {
|
||||||
|
color: black;
|
||||||
|
background-color: #fbfbfb;
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
touch-action: manipulation;
|
||||||
|
cursor: pointer;
|
||||||
|
background-image: none;
|
||||||
|
border: 1px solid #c9c9c9;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
border-radius: 4px;
|
||||||
|
user-select: none;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
color: black;
|
||||||
|
border-color: #aaaaaa;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: black;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
border-color: #aaaaaa;
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--icon {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,11 @@
|
|||||||
.schedules-filters {
|
.schedules-filters {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.schedules-list {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.load-more {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -99,41 +99,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-button,
|
.download-button {
|
||||||
.action-button {
|
@extend .fab-button;
|
||||||
color: black;
|
|
||||||
background-color: #fbfbfb;
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
vertical-align: middle;
|
|
||||||
touch-action: manipulation;
|
|
||||||
cursor: pointer;
|
|
||||||
background-image: none;
|
|
||||||
border: 1px solid #c9c9c9;
|
|
||||||
padding: 6px 12px;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1.5;
|
|
||||||
border-radius: 4px;
|
|
||||||
user-select: none;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
color: black;
|
|
||||||
border-color: #aaaaaa;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
color: black;
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
border-color: #aaaaaa;
|
|
||||||
outline: 0;
|
|
||||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
|
||||||
}
|
|
||||||
|
|
||||||
& > i {
|
& > i {
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
max_schedules = @payment_schedules.except(:offset, :limit, :order).count
|
||||||
|
|
||||||
json.array! @payment_schedules do |ps|
|
json.array! @payment_schedules do |ps|
|
||||||
|
json.max_length max_schedules
|
||||||
json.extract! ps, :id, :reference, :created_at, :payment_method
|
json.extract! ps, :id, :reference, :created_at, :payment_method
|
||||||
json.total ps.total / 100.00
|
json.total ps.total / 100.00
|
||||||
json.chained_footprint ps.check_footprint
|
json.chained_footprint ps.check_footprint
|
||||||
|
@ -642,6 +642,8 @@ en:
|
|||||||
stripe_currency: "Stripe currency"
|
stripe_currency: "Stripe currency"
|
||||||
payment_schedules:
|
payment_schedules:
|
||||||
filter_schedules: "Filter schedules"
|
filter_schedules: "Filter schedules"
|
||||||
|
no_payment_schedules: "No payment schedules to display"
|
||||||
|
load_more: "Load more"
|
||||||
schedules_table:
|
schedules_table:
|
||||||
schedule_num: "Schedule #"
|
schedule_num: "Schedule #"
|
||||||
date: "Date"
|
date: "Date"
|
||||||
|
@ -642,6 +642,8 @@ fr:
|
|||||||
stripe_currency: "Devise Stripe"
|
stripe_currency: "Devise Stripe"
|
||||||
payment_schedules:
|
payment_schedules:
|
||||||
filter_schedules: "Filtrer les échéanciers"
|
filter_schedules: "Filtrer les échéanciers"
|
||||||
|
no_payment_schedules: "Pas d'échéancier à afficher"
|
||||||
|
load_more: "Voir plus"
|
||||||
schedules_table:
|
schedules_table:
|
||||||
schedule_num: "Échéancier n°"
|
schedule_num: "Échéancier n°"
|
||||||
date: "Date"
|
date: "Date"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user