commit 38b872bd8cd388cbb1d09369a7f5e78535dcc213 parent da73a14c6909150b5779574dfc0036b27a3752e8 Author: Sebastian <sebasjm@gmail.com> Date: Tue, 23 Mar 2021 18:04:04 -0300 added search by date Diffstat:
26 files changed, 975 insertions(+), 96 deletions(-)
diff --git a/packages/frontend/src/AdminRoutes.tsx b/packages/frontend/src/AdminRoutes.tsx @@ -27,10 +27,7 @@ export enum AdminPaths { instance_id_route = '/instance/:id/:rest*', } -interface Props { - // pushNotification: (n: Notification) => void; -} -export function AdminRoutes({ }: Props): VNode { +export function AdminRoutes(): VNode { const i18n = useMessageTemplate(); return <Router> diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx b/packages/frontend/src/ApplicationReadyRoutes.tsx @@ -28,9 +28,8 @@ import LoginPage from './paths/login'; import { INSTANCE_ID_LOOKUP } from './utils/constants'; import { NotYetReadyAppMenu, Menu, NotificationCard } from './components/menu'; import { useMessageTemplate } from 'preact-messages'; -interface Props { -} -export function ApplicationReadyRoutes({ }: Props): VNode { + +export function ApplicationReadyRoutes(): VNode { const i18n = useMessageTemplate(); const { url: backendURL, changeBackend, updateToken, clearAllTokens } = useBackendContext(); diff --git a/packages/frontend/src/components/exception/loading.tsx b/packages/frontend/src/components/exception/loading.tsx @@ -1,9 +1,32 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from "preact"; export function Loading(): VNode { - return <div class="columns is-centered is-vcentered" style={{height:'calc(100% - 3rem)',position:'absolute', width:'100%'}}> + return <div class="columns is-centered is-vcentered" style={{ height: 'calc(100% - 3rem)', position: 'absolute', width: '100%' }}> <div class="column is-one-fifth"> - <div class="lds-ring"><div></div><div></div><div></div><div></div></div> + <div class="lds-ring"> + <div /><div /><div /><div /> + </div> </div> </div> } \ No newline at end of file diff --git a/packages/frontend/src/components/form/DatePicker.tsx b/packages/frontend/src/components/form/DatePicker.tsx @@ -0,0 +1,329 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { h, Component } from "preact"; + +interface Props { + closeFunction?: () => void; + dateReceiver?: (d: Date) => void; + opened?: boolean; +} +interface State { + displayedMonth: number; + displayedYear: number; + selectYearMode: boolean; + currentDate: Date; +} + +// inspired by https://codepen.io/m4r1vs/pen/MOOxyE +export class DatePicker extends Component<Props, State> { + + closeDatePicker() { + this.props.closeFunction && this.props.closeFunction(); // Function gets passed by parent + } + + /** + * Gets fired when a day gets clicked. + * @param {object} e The event thrown by the <span /> element clicked + */ + dayClicked(e: any) { + + const element = e.target; // the actual element clicked + + if (element.innerHTML === '') return false; // don't continue if <span /> empty + + // get date from clicked element (gets attached when rendered) + const date = new Date(element.getAttribute('data-value')); + + // update the state + this.setState({ currentDate: date }); + this.passDateToParent(date) + } + + /** + * returns days in month as array + * @param {number} month the month to display + * @param {number} year the year to display + */ + getDaysByMonth(month: number, year: number) { + + const calendar = []; + + const date = new Date(year, month, 1); // month to display + + const firstDay = new Date(year, month, 1).getDay(); // first weekday of month + const lastDate = new Date(year, month + 1, 0).getDate(); // last date of month + + let day: number | null = 0; + + // the calendar is 7*6 fields big, so 42 loops + for (let i = 0; i < 42; i++) { + + if (i >= firstDay && day !== null) day = day + 1; + if (day !== null && day > lastDate) day = null; + + // append the calendar Array + calendar.push({ + day: (day === 0 || day === null) ? null : day, // null or number + date: (day === 0 || day === null) ? null : new Date(year, month, day), // null or Date() + today: (day === now.getDate() && month === now.getMonth() && year === now.getFullYear()) // boolean + }); + } + + return calendar; + } + + /** + * Display previous month by updating state + */ + displayPrevMonth() { + if (this.state.displayedMonth <= 0) { + this.setState({ + displayedMonth: 11, + displayedYear: this.state.displayedYear - 1 + }); + } + else { + this.setState({ + displayedMonth: this.state.displayedMonth - 1 + }); + } + } + + /** + * Display next month by updating state + */ + displayNextMonth() { + if (this.state.displayedMonth >= 11) { + this.setState({ + displayedMonth: 0, + displayedYear: this.state.displayedYear + 1 + }); + } + else { + this.setState({ + displayedMonth: this.state.displayedMonth + 1 + }); + } + } + + /** + * Display the selected month (gets fired when clicking on the date string) + */ + displaySelectedMonth() { + if (this.state.selectYearMode) { + this.toggleYearSelector(); + } + else { + if (!this.state.currentDate) return false; + this.setState({ + displayedMonth: this.state.currentDate.getMonth(), + displayedYear: this.state.currentDate.getFullYear() + }); + } + } + + toggleYearSelector() { + this.setState({ selectYearMode: !this.state.selectYearMode }); + } + + changeDisplayedYear(e: any) { + const element = e.target; + this.toggleYearSelector(); + this.setState({ displayedYear: parseInt(element.innerHTML, 10), displayedMonth: 0 }); + } + + /** + * Pass the selected date to parent when 'OK' is clicked + */ + passSavedDateDateToParent() { + this.passDateToParent(this.state.currentDate) + } + passDateToParent(date: Date) { + if (typeof this.props.dateReceiver === 'function') this.props.dateReceiver(date); + this.closeDatePicker(); + } + + componentDidUpdate() { + if (this.state.selectYearMode) { + document.getElementsByClassName('selected')[0].scrollIntoView(); // works in every browser incl. IE, replace with scrollIntoViewIfNeeded when browsers support it + } + } + + constructor() { + super(); + + this.closeDatePicker = this.closeDatePicker.bind(this); + this.dayClicked = this.dayClicked.bind(this); + this.displayNextMonth = this.displayNextMonth.bind(this); + this.displayPrevMonth = this.displayPrevMonth.bind(this); + this.getDaysByMonth = this.getDaysByMonth.bind(this); + this.changeDisplayedYear = this.changeDisplayedYear.bind(this); + this.passDateToParent = this.passDateToParent.bind(this); + this.toggleYearSelector = this.toggleYearSelector.bind(this); + this.displaySelectedMonth = this.displaySelectedMonth.bind(this); + + + this.state = { + currentDate: now, + displayedMonth: now.getMonth(), + displayedYear: now.getFullYear(), + selectYearMode: false + } + } + + render() { + + const { currentDate, displayedMonth, displayedYear, selectYearMode } = this.state; + + return ( + <div> + <div class={"datePicker " + (this.props.opened && "datePicker--opened")} > + + <div class="datePicker--titles"> + <h3 style={{ + color: selectYearMode ? 'rgba(255,255,255,.87)' : 'rgba(255,255,255,.57)' + }} onClick={this.toggleYearSelector}>{currentDate.getFullYear()}</h3> + <h2 style={{ + color: !selectYearMode ? 'rgba(255,255,255,.87)' : 'rgba(255,255,255,.57)' + }} onClick={this.displaySelectedMonth}> + {dayArr[currentDate.getDay()]}, {monthArrShort[currentDate.getMonth()]} {currentDate.getDate()} + </h2> + </div> + + {!selectYearMode && <nav> + <span onClick={this.displayPrevMonth} class="icon"><i style={{ transform: 'rotate(180deg)' }} class="mdi mdi-forward" /></span> + <h4>{monthArrShortFull[displayedMonth]} {displayedYear}</h4> + <span onClick={this.displayNextMonth} class="icon"><i class="mdi mdi-forward" /></span> + </nav>} + + <div class="datePicker--scroll"> + + {!selectYearMode && <div class="datePicker--calendar" > + + <div class="datePicker--dayNames"> + {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map(day => <span>{day}</span>)} + </div> + + <div onClick={this.dayClicked} class="datePicker--days"> + + {/* + Loop through the calendar object returned by getDaysByMonth(). + */} + + {this.getDaysByMonth(this.state.displayedMonth, this.state.displayedYear) + .map( + day => { + let selected = false; + + if (currentDate && day.date) selected = (currentDate.toLocaleDateString() === day.date.toLocaleDateString()); + + return (<span + class={(day.today ? 'datePicker--today ' : '') + (selected ? 'datePicker--selected' : '')} + disabled={!day.date} + data-value={day.date} + > + {day.day} + </span>) + } + ) + } + + </div> + + </div>} + + {selectYearMode && <div class="datePicker--selectYear"> + + {yearArr.map(year => ( + <span class={(year === displayedYear) ? 'selected' : ''} onClick={this.changeDisplayedYear}> + {year} + </span> + ))} + + </div>} + + {/* {!selectYearMode && <div class="datePicker--actions"> + <button onClick={this.closeDatePicker}>CANCEL</button> + <button onClick={this.passDateToParent}>OK</button> + </div>} */} + + </div> + </div> + + <div class="datePicker--background" onClick={this.closeDatePicker} style={{ + display: this.props.opened ? 'block' : 'none' + }} + /> + + </div> + ) + } +} + + +const monthArrShortFull = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' +] + +const monthArrShort = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec' +] + +const dayArr = [ + 'Sun', + 'Mon', + 'Tue', + 'Wed', + 'Thu', + 'Fri', + 'Sat' +] + +const now = new Date() + +const yearArr: number[] = [] + +for (let i = 2010; i <= now.getFullYear() + 10; i++) { + yearArr.push(i); +} diff --git a/packages/frontend/src/declaration.d.ts b/packages/frontend/src/declaration.d.ts @@ -20,7 +20,6 @@ */ - type HashCode = string; type EddsaPublicKey = string; type EddsaSignature = string; diff --git a/packages/frontend/src/hooks/backend.ts b/packages/frontend/src/hooks/backend.ts @@ -110,9 +110,8 @@ function fetcher(url: string, token: string, backend: string) { type YesOrNo = 'yes' | 'no'; -function orderFetcher(url: string, token: string, backend: string, paid?: YesOrNo, refunded?: YesOrNo, wired?: YesOrNo, date?: Date, start?: number, delta?: number) { - //%Y-%m-%d %H:%M:%S - return request(`${backend}${url}`, { token, params: { paid, refunded, wired, start, delta, date: date? format(date, 'yyyy-MM-dd HH:mm:ss'): undefined } }) +function orderFetcher(url: string, token: string, backend: string, paid?: YesOrNo, refunded?: YesOrNo, wired?: YesOrNo, date?: Date, delta?: number) { + return request(`${backend}${url}`, { token, params: { paid, refunded, wired, delta, date: date? format(date, 'yyyy-MM-dd HH:mm:ss'): undefined } }) } function transferFetcher(url: string, token: string, backend: string) { @@ -472,7 +471,7 @@ export interface InstanceOrderFilter { date?: Date; } -export function useInstanceOrders(args?: InstanceOrderFilter): HttpResponse<MerchantBackend.Orders.OrderHistory> { +export function useInstanceOrders(args: InstanceOrderFilter, updateFilter: (d:Date)=>void): HttpResponse<MerchantBackend.Orders.OrderHistory> { const { url: baseUrl, token: baseToken } = useBackendContext(); const { token: instanceToken, id, admin } = useInstanceContext(); @@ -482,18 +481,18 @@ export function useInstanceOrders(args?: InstanceOrderFilter): HttpResponse<Merc url: `${baseUrl}/instances/${id}`, token: instanceToken } - const [pageBefore, setPageBefore] = useState(0) + const [pageBefore, setPageBefore] = useState(1) const [pageAfter, setPageAfter] = useState(1) - const [start, setStart] = useState<number | undefined>(17); + const totalAfter = pageAfter * PAGE_SIZE; const totalBefore = pageBefore * PAGE_SIZE; const { data:beforeData, error:beforeError } = useSWR<MerchantBackend.Orders.OrderHistory, SwrError>( - [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired, args?.date, start, totalBefore], + [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired, args?.date, totalBefore], orderFetcher, ) const { data:afterData, error:afterError } = useSWR<MerchantBackend.Orders.OrderHistory, SwrError>( - [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired, args?.date, start ? start+1 : undefined, -totalAfter], + [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired, args?.date, -totalAfter], orderFetcher, ) @@ -513,9 +512,8 @@ export function useInstanceOrders(args?: InstanceOrderFilter): HttpResponse<Merc if (totalAfter < MAX_RESULT_SIZE) { setPageAfter(pageAfter + 1) } else { - const from = afterData?.orders?.[PAGE_SIZE]?.row_id - console.log('after',start,from) - if (from) setStart(from) + const from = afterData?.orders?.[PAGE_SIZE]?.timestamp?.t_ms + if (from) updateFilter(new Date(from)) } } @@ -523,9 +521,8 @@ export function useInstanceOrders(args?: InstanceOrderFilter): HttpResponse<Merc if (totalBefore < MAX_RESULT_SIZE) { setPageBefore(pageBefore + 1) } else { - const from = beforeData?.orders?.[PAGE_SIZE-1]?.row_id - console.log('prev',start,from) - if (from) setStart(from) + const from = beforeData?.orders?.[PAGE_SIZE-1]?.timestamp?.t_ms + if (from) updateFilter(new Date(from)) } } diff --git a/packages/frontend/src/paths/admin/create/CreatePage.tsx b/packages/frontend/src/paths/admin/create/CreatePage.tsx @@ -48,7 +48,7 @@ interface KeyValue { [key: string]: string; } -function with_defaults(id?:string): Partial<Entity> { +function with_defaults(id?: string): Partial<Entity> { return { id, default_pay_delay: { d_ms: 1000 }, @@ -87,7 +87,7 @@ export function CreatePage({ onCreate, isLoading, onBack, forceId }: Props): VNo <div class="column is-two-thirds"> <FormProvider<Entity> errors={errors} object={value} valueHandler={valueHandler} > - <InputWithAddon<Entity> name="id" addonBefore={`${backend.url}/private/instances/`} readonly={!!forceId}/> + <InputWithAddon<Entity> name="id" addonBefore={`${backend.url}/private/instances/`} readonly={!!forceId} /> <Input<Entity> name="name" /> diff --git a/packages/frontend/src/paths/admin/create/index.tsx b/packages/frontend/src/paths/admin/create/index.tsx @@ -119,12 +119,10 @@ function CreatedSuccessfully({ children, onConfirm }: CreatedSuccessfullyProps) </div> <footer class="card-footer"> <p class="card-footer-item" style={{ border: 'none' }}> - <span> - </span> + <span /> </p> <p class="card-footer-item" style={{ border: 'none' }}> - <span> - </span> + <span /> </p> <p class="card-footer-item"> <button class="button is-info" onClick={onConfirm}>Continue</button> diff --git a/packages/frontend/src/paths/instance/orders/create/index.tsx b/packages/frontend/src/paths/instance/orders/create/index.tsx @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; export default function ():VNode { diff --git a/packages/frontend/src/paths/instance/orders/list/Table.tsx b/packages/frontend/src/paths/instance/orders/list/Table.tsx @@ -65,9 +65,8 @@ export function CardTable({ instances, onCreate, onUpdate, onDelete, selected, o <header class="card-header"> <p class="card-header-title"><span class="icon"><i class="mdi mdi-cash-register" /></span><Message id="Orders" /></p> - <div class="card-header-icon" aria-label="more options"> - - </div> + <div class="card-header-icon" aria-label="more options" /> + <div class="card-header-icon" aria-label="more options"> <button class="button is-info" type="button" onClick={onCreate}> <span class="icon is-small" ><i class="mdi mdi-plus mdi-36px" /></span> diff --git a/packages/frontend/src/paths/instance/orders/list/index.tsx b/packages/frontend/src/paths/instance/orders/list/index.tsx @@ -1,13 +1,32 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; import { useState } from 'preact/hooks'; import { useConfigContext } from '../../../../context/backend'; import { MerchantBackend } from '../../../../declaration'; import { InstanceOrderFilter, SwrError, useInstanceOrders, useOrderMutateAPI, useProductMutateAPI } from '../../../../hooks/backend'; import { CardTable } from './Table'; -import { FormProvider, FormErrors } from "../../../../components/form/Field" -import { InputBoolean } from "../../../../components/form/InputBoolean"; -import { InputArray } from '../../../../components/form/InputArray'; -import { format, parse } from 'date-fns'; +import { format } from 'date-fns'; +import { DatePicker } from '../../../../components/form/DatePicker'; interface Props { onUnauthorized: () => VNode; @@ -16,18 +35,13 @@ interface Props { onCreate: () => void; } -// const fromBooleanToYesAndNo = { -// fromBoolean: (b?: boolean) => b === true ? 'yes' : (b === false ? 'no' : undefined), -// toBoolean: (b: string) => b === 'yes' ? true : (b === 'no' ? false : undefined) -// } export default function ({ onUnauthorized, onLoadError, onCreate, onNotFound }: Props): VNode { - // const [filter, setFilter] = useState<InstanceOrderFilter>({paid:'yes'}) - const date = parse('22/03/2021 11:13:25','dd/MM/yyyyy HH:mm:ss', new Date()) - const [filter, setFilter] = useState<InstanceOrderFilter>({}) - + const [filter, setFilter] = useState<InstanceOrderFilter>({paid:'yes'}) + const [pickDate, setPickDate] = useState(false) - const result = useInstanceOrders(filter) + const setNewDate = (date:Date) => setFilter(prev => ({...prev,date})) + const result = useInstanceOrders(filter, setNewDate) const { createOrder, deleteOrder } = useOrderMutateAPI() const { currency } = useConfigContext() @@ -46,23 +60,49 @@ export default function ({ onUnauthorized, onLoadError, onCreate, onNotFound }: filter: [] } - const VALUE_REGEX = /d/ - - const fields = ['wired','refunded','paid'] - const isPaidActive = filter.paid === 'yes' ? "is-active" : '' const isRefundedActive = filter.refunded === 'yes' ? "is-active" : '' const isNotWiredActive = filter.wired === 'no' ? "is-active" : '' const isAllActive = filter.paid === undefined && filter.refunded === undefined && filter.wired === undefined ? 'is-active' : '' return <section class="section is-main-section"> - <div class="tabs"> - <ul> - <li class={isPaidActive}><a onClick={() => setFilter({paid: 'yes'})}>Paid</a></li> - <li class={isRefundedActive}><a onClick={() => setFilter({refunded: 'yes'})}>Refunded</a></li> - <li class={isNotWiredActive}><a onClick={() => setFilter({wired: 'no'})}>Not wired</a></li> - <li class={isAllActive}><a onClick={() => setFilter({})}>All</a></li> - </ul> + + <DatePicker + opened={pickDate} + closeFunction={() => setPickDate(false)} + dateReceiver={setNewDate} + /> + + <div class="columns"> + <div class="column"> + <div class="tabs"> + <ul> + <li class={isPaidActive}><a onClick={() => setFilter({ paid: 'yes' })}>Paid</a></li> + <li class={isRefundedActive}><a onClick={() => setFilter({ refunded: 'yes' })}>Refunded</a></li> + <li class={isNotWiredActive}><a onClick={() => setFilter({ wired: 'no' })}>Not wired</a></li> + <li class={isAllActive}><a onClick={() => setFilter({})}>All</a></li> + </ul> + </div> + </div> + <div class="column "> + <div class="buttons is-right"> + <div class="field has-addons"> + { filter.date && <div class="control"> + <a class="button" onClick={() => { setFilter(prev => ({ ...prev, date:undefined }) ) }}> + <span class="icon"><i class="mdi mdi-close" /></span> + </a> + </div> } + <div class="control"> + <input class="input" type="text" readonly value={!filter.date ? '' : format(filter.date, 'yyyy/MM/dd')} placeholder="pick a date" /> + </div> + <div class="control"> + <a class="button" onClick={() => { setPickDate(true) }}> + <span class="icon"><i class="mdi mdi-calendar" /></span> + </a> + </div> + </div> + </div> + </div> </div> <CardTable instances={instances} diff --git a/packages/frontend/src/paths/instance/orders/update/index.tsx b/packages/frontend/src/paths/instance/orders/update/index.tsx @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; export default function ():VNode { diff --git a/packages/frontend/src/paths/instance/products/create/index.tsx b/packages/frontend/src/paths/instance/products/create/index.tsx @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; export default function ():VNode { diff --git a/packages/frontend/src/paths/instance/products/list/index.tsx b/packages/frontend/src/paths/instance/products/list/index.tsx @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; import { create } from 'yup/lib/Reference'; import { SwrError, useInstanceProducts, useProductMutateAPI } from '../../../../hooks/backend'; diff --git a/packages/frontend/src/paths/instance/products/update/index.tsx b/packages/frontend/src/paths/instance/products/update/index.tsx @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; export default function ():VNode { diff --git a/packages/frontend/src/paths/instance/tips/create/index.tsx b/packages/frontend/src/paths/instance/tips/create/index.tsx @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; export default function ():VNode { diff --git a/packages/frontend/src/paths/instance/tips/list/index.tsx b/packages/frontend/src/paths/instance/tips/list/index.tsx @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; import { Loading } from '../../../../components/exception/loading'; import { useConfigContext } from '../../../../context/backend'; diff --git a/packages/frontend/src/paths/instance/tips/update/index.tsx b/packages/frontend/src/paths/instance/tips/update/index.tsx @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; export default function ():VNode { diff --git a/packages/frontend/src/paths/instance/transfers/create/index.tsx b/packages/frontend/src/paths/instance/transfers/create/index.tsx @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; export default function ():VNode { diff --git a/packages/frontend/src/paths/instance/transfers/list/index.tsx b/packages/frontend/src/paths/instance/transfers/list/index.tsx @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; import { Loading } from '../../../../components/exception/loading'; import { useConfigContext } from '../../../../context/backend'; diff --git a/packages/frontend/src/paths/instance/transfers/update/index.tsx b/packages/frontend/src/paths/instance/transfers/update/index.tsx @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + import { h, VNode } from 'preact'; export default function ():VNode { diff --git a/packages/frontend/src/scss/_custom-calendar.scss b/packages/frontend/src/scss/_custom-calendar.scss @@ -0,0 +1,244 @@ +:root { + --primary-color: #673ab7; + --primary-color-light: #9a67ea; + --primary-color-dark: #320b86; + + --secondary-color: #ffc400; + --secondary-color-light: #fff64f; + --secondary-color-dark: #c79400; + + --primary-text-color-dark: rgba(0,0,0,.87); + --secondary-text-color-dark: rgba(0,0,0,.57); + --disabled-text-color-dark: rgba(0,0,0,.13); + + --primary-text-color-light: rgba(255,255,255,.87); + --secondary-text-color-light: rgba(255,255,255,.57); + --disabled-text-color-light: rgba(255,255,255,.13); + + --font-stack: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif; + + --primary-card-color: #fff; + --primary-background-color: #f2f2f2; + + --box-shadow-lvl-1: 0 1px 3px rgba(0, 0, 0, 0.12), + 0 1px 2px rgba(0, 0, 0, 0.24); + --box-shadow-lvl-2: 0 3px 6px rgba(0, 0, 0, 0.16), + 0 3px 6px rgba(0, 0, 0, 0.23); + --box-shadow-lvl-3: 0 10px 20px rgba(0, 0, 0, 0.19), + 0 6px 6px rgba(0, 0, 0, 0.23); + --box-shadow-lvl-4: 0 14px 28px rgba(0, 0, 0, 0.25), + 0 10px 10px rgba(0, 0, 0, 0.22); +} + + +.datePicker { + text-align: left; + background: var(--primary-card-color); + border-radius: 3px; + z-index: 200; + position: fixed; + height: auto; + max-height: 90vh; + width: 90vw; + max-width: 448px; + transform-origin: top left; + transition: transform .22s ease-in-out, opacity .22s ease-in-out; + top: 50%; + left: 50%; + opacity: 0; + transform: scale(0) translate(-50%, -50%); + user-select: none; + + &.datePicker--opened { + opacity: 1; + transform: scale(1) translate(-50%, -50%); + } + + .datePicker--titles { + border-top-left-radius: 3px; + border-top-right-radius: 3px; + padding: 24px; + height: 100px; + background: var(--primary-color); + + h2, h3 { + cursor: pointer; + color: #fff; + line-height: 1; + padding: 0; + margin: 0; + font-size: 32px; + } + + h3 { + color: rgba(255,255,255,.57); + font-size: 18px; + padding-bottom: 2px; + } + } + + nav { + padding: 20px; + height: 56px; + + h4 { + width: calc(100% - 60px); + text-align: center; + display: inline-block; + padding: 0; + font-size: 14px; + line-height: 24px; + margin: 0; + position: relative; + top: -9px; + color: var(--primary-text-color); + } + + i { + cursor: pointer; + color: var(--secondary-text-color); + font-size: 26px; + user-select: none; + border-radius: 50%; + + &:hover { + background: var(--disabled-text-color-dark); + } + } + } + + .datePicker--scroll { + overflow-y: auto; + max-height: calc(90vh - 56px - 100px); + } + + .datePicker--calendar { + padding: 0 20px; + + .datePicker--dayNames { + width: 100%; + display: grid; + text-align: center; + + // there's probably a better way to do this, but wanted to try out CSS grid + grid-template-columns: calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7); + + span { + color: var(--secondary-text-color-dark); + font-size: 14px; + line-height: 42px; + display: inline-grid; + } + } + + .datePicker--days { + width: 100%; + display: grid; + text-align: center; + grid-template-columns: calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7) calc(100% / 7); + + span { + color: var(--primary-text-color-dark); + line-height: 42px; + font-size: 14px; + display: inline-grid; + transition: color .22s; + height: 42px; + position: relative; + cursor: pointer; + user-select: none; + border-radius: 50%; + + &::before { + content: ''; + position: absolute; + z-index: -1; + height: 42px; + width: 42px; + left: calc(50% - 21px); + background: var(--primary-color); + border-radius: 50%; + transition: transform .22s, opacity .22s; + transform: scale(0); + opacity: 0; + } + + &[disabled=true] { + cursor: unset; + } + + &.datePicker--today { + font-weight: 700; + } + + &.datePicker--selected { + color: rgba(255,255,255,.87); + + &:before { + transform: scale(1); + opacity: 1; + } + } + } + } + } + + .datePicker--selectYear { + padding: 0 20px; + display: block; + width: 100%; + text-align: center; + max-height: 362px; + + span { + display: block; + width: 100%; + font-size: 24px; + margin: 20px auto; + cursor: pointer; + + &.selected { + font-size: 42px; + color: var(--primary-color); + } + } + } + + div.datePicker--actions { + width: 100%; + padding: 8px; + text-align: right; + + button { + margin-bottom: 0; + font-size: 15px; + cursor: pointer; + color: var(--primary-text-color); + border: none; + margin-left: 8px; + min-width: 64px; + line-height: 36px; + background-color: transparent; + appearance: none; + padding: 0 16px; + border-radius: 3px; + transition: background-color .13s; + + &:hover, &:focus { + outline: none; + background-color: var(--disabled-text-color-dark); + } + } + } +} + +.datePicker--background { + z-index: 199; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(0,0,0,.52); + animation: fadeIn .22s forwards; +} diff --git a/packages/frontend/src/scss/_loading.scss b/packages/frontend/src/scss/_loading.scss @@ -0,0 +1,35 @@ +.lds-ring { + display: inline-block; + position: relative; + width: 80px; + height: 80px; +} +.lds-ring div { + box-sizing: border-box; + display: block; + position: absolute; + width: 64px; + height: 64px; + margin: 8px; + border: 8px solid black; + border-radius: 50%; + animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: black transparent transparent transparent; +} +.lds-ring div:nth-child(1) { + animation-delay: -0.45s; +} +.lds-ring div:nth-child(2) { + animation-delay: -0.3s; +} +.lds-ring div:nth-child(3) { + animation-delay: -0.15s; +} +@keyframes lds-ring { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/packages/frontend/src/scss/main.scss b/packages/frontend/src/scss/main.scss @@ -41,6 +41,8 @@ @import "modal"; @import "footer"; @import "misc"; +@import "custom-calendar"; +@import "loading"; @import "fonts/nunito.css"; @import "icons/materialdesignicons-4.9.95.min.css"; @@ -108,42 +110,6 @@ tr:hover .right-sticky { background-color: hsl(0, 0%, 95%); } -.lds-ring { - display: inline-block; - position: relative; - width: 80px; - height: 80px; -} -.lds-ring div { - box-sizing: border-box; - display: block; - position: absolute; - width: 64px; - height: 64px; - margin: 8px; - border: 8px solid black; - border-radius: 50%; - animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; - border-color: black transparent transparent transparent; -} -.lds-ring div:nth-child(1) { - animation-delay: -0.45s; -} -.lds-ring div:nth-child(2) { - animation-delay: -0.3s; -} -.lds-ring div:nth-child(3) { - animation-delay: -0.15s; -} -@keyframes lds-ring { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - .content-full-size { height: calc(100% - 3rem); position: absolute; diff --git a/packages/frontend/src/utils/constants.ts b/packages/frontend/src/utils/constants.ts @@ -26,5 +26,8 @@ export const AMOUNT_REGEX=/^[a-zA-Z][a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/ export const INSTANCE_ID_LOOKUP = /^\/instances\/([^/]*)\/?$/ -export const PAGE_SIZE = 3 -export const MAX_RESULT_SIZE = 4; -\ No newline at end of file +// how much rows we add every time user hit load more +export const PAGE_SIZE = 10 +// how bigger can be the result set +// after this threshold, load more with move the cursor +export const MAX_RESULT_SIZE = 19; +\ No newline at end of file diff --git a/packages/frontend/src/utils/table.ts b/packages/frontend/src/utils/table.ts @@ -1,4 +1,23 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ export interface Actions<T extends WithId> { element: T;