diff --git a/app/frontend/src/javascript/components/fab-button.tsx b/app/frontend/src/javascript/components/fab-button.tsx new file mode 100644 index 000000000..8e5a8552f --- /dev/null +++ b/app/frontend/src/javascript/components/fab-button.tsx @@ -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 = ({ 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 ( + + ); +} + diff --git a/app/frontend/src/javascript/components/payment-schedules-list.tsx b/app/frontend/src/javascript/components/payment-schedules-list.tsx index b433d13dd..22840e743 100644 --- a/app/frontend/src/javascript/components/payment-schedules-list.tsx +++ b/app/frontend/src/javascript/components/payment-schedules-list.tsx @@ -10,23 +10,63 @@ import { react2angular } from 'react2angular'; import PaymentScheduleAPI from '../api/payment-schedule'; import { DocumentFilters } from './document-filters'; import { PaymentSchedulesTable } from './payment-schedules-table'; +import { FabButton } from './fab-button'; declare var Application: IApplication; +const PAGE_SIZE = 20; const paymentSchedulesList = PaymentScheduleAPI.list({ query: { page: 1, size: 20 } }); const PaymentSchedulesList: React.FC = () => { const { t } = useTranslation('admin'); 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 => { + setReferenceFilter(reference); + setCustomerFilter(customer); + setDateFilter(date); + 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); }); }; + /** + * 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 (

@@ -36,7 +76,11 @@ const PaymentSchedulesList: React.FC = () => {
- + {!hasSchedules() &&
{t('app.admin.invoices.payment_schedules.no_payment_schedules')}
} + {hasSchedules() &&
+ + {hasMoreSchedules() && {t('app.admin.invoices.payment_schedules.load_more')}} +
}

); } diff --git a/app/frontend/src/javascript/components/payment-schedules-table.tsx b/app/frontend/src/javascript/components/payment-schedules-table.tsx index 4c01603b6..7ccac319c 100644 --- a/app/frontend/src/javascript/components/payment-schedules-table.tsx +++ b/app/frontend/src/javascript/components/payment-schedules-table.tsx @@ -9,6 +9,7 @@ import moment from 'moment'; import { IFablab } from '../models/fablab'; import _ from 'lodash'; import { PaymentSchedule, PaymentScheduleItem, PaymentScheduleItemState } from '../models/payment-schedule'; +import { FabButton } from './fab-button'; declare var Fablab: IFablab; @@ -119,24 +120,24 @@ const PaymentSchedulesTableComponent: React.FC = ({ return downloadButton(TargetType.Invoice, item.invoice_id); case PaymentScheduleItemState.Pending: return ( - + ); case PaymentScheduleItemState.RequireAction: return ( - + ); case PaymentScheduleItemState.RequirePaymentMethod: return ( - + ); default: return diff --git a/app/frontend/src/javascript/models/payment-schedule.ts b/app/frontend/src/javascript/models/payment-schedule.ts index acf2cd1a8..f496e15e7 100644 --- a/app/frontend/src/javascript/models/payment-schedule.ts +++ b/app/frontend/src/javascript/models/payment-schedule.ts @@ -27,6 +27,7 @@ export interface PaymentScheduleItem { } export interface PaymentSchedule { + max_length: number; id: number, scheduled_type: string, scheduled_id: number, diff --git a/app/frontend/src/stylesheets/application.scss b/app/frontend/src/stylesheets/application.scss index da9a56466..2c2118252 100644 --- a/app/frontend/src/stylesheets/application.scss +++ b/app/frontend/src/stylesheets/application.scss @@ -22,6 +22,7 @@ @import "modules/stripe"; @import "modules/tour"; @import "modules/fab-modal"; +@import "modules/fab-button"; @import "modules/payment-schedule-summary"; @import "modules/wallet-info"; @import "modules/stripe-modal"; diff --git a/app/frontend/src/stylesheets/modules/fab-button.scss b/app/frontend/src/stylesheets/modules/fab-button.scss new file mode 100644 index 000000000..f9a151fd9 --- /dev/null +++ b/app/frontend/src/stylesheets/modules/fab-button.scss @@ -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; + } +} diff --git a/app/frontend/src/stylesheets/modules/payment-schedules-list.scss b/app/frontend/src/stylesheets/modules/payment-schedules-list.scss index 085a545b4..1641b1446 100644 --- a/app/frontend/src/stylesheets/modules/payment-schedules-list.scss +++ b/app/frontend/src/stylesheets/modules/payment-schedules-list.scss @@ -1,3 +1,11 @@ .schedules-filters { margin-bottom: 2em; } + +.schedules-list { + text-align: center; + + .load-more { + margin-top: 2em; + } +} diff --git a/app/frontend/src/stylesheets/modules/payment-schedules-table.scss b/app/frontend/src/stylesheets/modules/payment-schedules-table.scss index d27b12a02..86121e97a 100644 --- a/app/frontend/src/stylesheets/modules/payment-schedules-table.scss +++ b/app/frontend/src/stylesheets/modules/payment-schedules-table.scss @@ -99,41 +99,8 @@ } } - .download-button, - .action-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); - } + .download-button { + @extend .fab-button; & > i { margin-right: 0.5em; diff --git a/app/views/api/payment_schedules/list.json.jbuilder b/app/views/api/payment_schedules/list.json.jbuilder index 1c62f1723..41d80b46f 100644 --- a/app/views/api/payment_schedules/list.json.jbuilder +++ b/app/views/api/payment_schedules/list.json.jbuilder @@ -1,6 +1,9 @@ # frozen_string_literal: true +max_schedules = @payment_schedules.except(:offset, :limit, :order).count + json.array! @payment_schedules do |ps| + json.max_length max_schedules json.extract! ps, :id, :reference, :created_at, :payment_method json.total ps.total / 100.00 json.chained_footprint ps.check_footprint diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index cfcda3208..79a615f9a 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -642,6 +642,8 @@ en: stripe_currency: "Stripe currency" payment_schedules: filter_schedules: "Filter schedules" + no_payment_schedules: "No payment schedules to display" + load_more: "Load more" schedules_table: schedule_num: "Schedule #" date: "Date" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 6c1e20bf6..6cef0a3c4 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -642,6 +642,8 @@ fr: stripe_currency: "Devise Stripe" payment_schedules: filter_schedules: "Filtrer les échéanciers" + no_payment_schedules: "Pas d'échéancier à afficher" + load_more: "Voir plus" schedules_table: schedule_num: "Échéancier n°" date: "Date"