diff --git a/src/App.js b/src/App.js index eb90662679b003941ada3ceeefe7bde28f0e3e64..b174c33c21467046a245b58ab16bf3c6da497763 100644 --- a/src/App.js +++ b/src/App.js @@ -1,70 +1,81 @@ import { createBrowserHistory } from 'history'; -import React, { Component } from "react"; +import React, { Component } from 'react'; import { Admin, Resource } from 'react-admin'; import ReactDOM from 'react-dom'; import Layout from './layout/Layout'; import Login from './layout/Login'; -import { customRoutes } from "./layout/Routes"; -import Theme from "./layout/Theme"; +import { customRoutes } from './layout/Routes'; +import Theme from './layout/Theme'; import Dashboard from './pages/Dashboard'; import AuthProvider from './providers/AuthProvider'; import DataProvider from './providers/DataProvider'; -import I18nProvider from "./providers/I18nProvider"; -import accounts from "./resources/Accounts"; +import I18nProvider from './providers/I18nProvider'; +import accounts from './resources/Accounts'; import accountscounts from './resources/AccountsCounts'; -import archived_members from "./resources/ArchivedMembers"; -import automated_transactions from "./resources/AutomatedTransactions"; +import archived_members from './resources/ArchivedMembers'; +import automated_transactions from './resources/AutomatedTransactions'; import events from './resources/Events'; import members from './resources/Members'; -import movements from "./resources/Movements"; +import movements from './resources/Movements'; import participations from './resources/Participations'; import people from './resources/People'; -import personal_accounts from "./resources/PersonalAccounts"; -import personal_transactions from "./resources/PersonalTransactions"; +import personal_accounts from './resources/PersonalAccounts'; +import personal_transactions from './resources/PersonalTransactions'; import products from './resources/Products'; -import productscategories from "./resources/ProductsCategories"; +import productscategories from './resources/ProductsCategories'; import productscounts from './resources/ProductsCounts'; import purchases from './resources/Purchases'; -import sales from "./resources/Sales"; -import transactions from "./resources/Transactions"; -import transactionscategories from "./resources/TransactionsCategories"; -import transferts from "./resources/Transferts"; -import users from "./resources/Users"; +import sales from './resources/Sales'; +import transactions from './resources/Transactions'; +import transactionscategories from './resources/TransactionsCategories'; +import transferts from './resources/Transferts'; +import users from './resources/Users'; const history = createBrowserHistory(); export default class App extends Component { - render() { - return ( - <Admin customRoutes={customRoutes} i18nProvider={I18nProvider} loginPage={Login} history={history} dashboard={Dashboard} theme={Theme} layout={Layout} dataProvider={DataProvider} authProvider={AuthProvider} title="Seb" disableTelemetry> - <Resource name="permissions" /> - <Resource name="profile" /> - <Resource name="tokens" /> - <Resource name="accounts" {...accounts} /> - <Resource name="accounts_counts" {...accountscounts} /> - <Resource name="people" {...people} /> - <Resource name="members" {...members} /> - <Resource name="transactions" {...transactions} /> - <Resource name="users" {...users} /> - <Resource name="products_categories" {...productscategories} /> - <Resource name="products_counts" {...productscounts} /> - <Resource name="transactions_categories" {...transactionscategories} /> - <Resource name="products" {...products} /> - <Resource name="movements" {...movements} /> - <Resource name="sales" {...sales} /> - <Resource name="purchases" {...purchases} /> - <Resource name="transferts" {...transferts} /> - <Resource name="archived_members" {...archived_members} /> - <Resource name="automated_transactions" {...automated_transactions} /> - <Resource name="personal_accounts" {...personal_accounts} /> - <Resource name="personal_transactions" {...personal_transactions} /> - <Resource name="events" {...events} /> - <Resource name="participations" {...participations} /> - </Admin> - ); - } -}; + render() { + return ( + <Admin + customRoutes={customRoutes} + i18nProvider={I18nProvider} + loginPage={Login} + history={history} + dashboard={Dashboard} + theme={Theme} + layout={Layout} + dataProvider={DataProvider} + authProvider={AuthProvider} + title="Seb" + disableTelemetry> + <Resource name="permissions" /> + <Resource name="profile" /> + <Resource name="tokens" /> + <Resource name="accounts" {...accounts} /> + <Resource name="accounts_counts" {...accountscounts} /> + <Resource name="people" {...people} /> + <Resource name="members" {...members} /> + <Resource name="transactions" {...transactions} /> + <Resource name="users" {...users} /> + <Resource name="products_categories" {...productscategories} /> + <Resource name="products_counts" {...productscounts} /> + <Resource name="transactions_categories" {...transactionscategories} /> + <Resource name="products" {...products} /> + <Resource name="movements" {...movements} /> + <Resource name="sales" {...sales} /> + <Resource name="purchases" {...purchases} /> + <Resource name="transferts" {...transferts} /> + <Resource name="archived_members" {...archived_members} /> + <Resource name="automated_transactions" {...automated_transactions} /> + <Resource name="personal_accounts" {...personal_accounts} /> + <Resource name="personal_transactions" {...personal_transactions} /> + <Resource name="events" {...events} /> + <Resource name="participations" {...participations} /> + </Admin> + ); + } +} if (document.getElementById('app')) { - ReactDOM.render(<App />, document.getElementById('app')); + ReactDOM.render(<App />, document.getElementById('app')); } diff --git a/src/components/ArchiveButton.js b/src/components/ArchiveButton.js index cefbe7aaea579c4a0c55b5204a29791dc6a4b91e..49059530f56b2f65b9556a5ff21171880b108c3e 100644 --- a/src/components/ArchiveButton.js +++ b/src/components/ArchiveButton.js @@ -1,23 +1,24 @@ import ArchiveIcon from '@material-ui/icons/Archive'; -import * as React from "react"; +import * as React from 'react'; import { Button, useDataProvider, useRefresh, useResourceContext, useTranslate } from 'react-admin'; -const ArchiveButton = (props) => { - const dataProvider = useDataProvider(); - const name = useResourceContext(); - const refresh = useRefresh(); - const translate = useTranslate(); +const ArchiveButton = () => { + const dataProvider = useDataProvider(); + const name = useResourceContext(); + const refresh = useRefresh(); + const translate = useTranslate(); - return ( - <Button - onClick={() => { - dataProvider.archive(name).then(() => { - refresh(); - }); - }} - label={translate('actions.archive')} - ><ArchiveIcon /></Button> - ); + return ( + <Button + onClick={() => { + dataProvider.archive(name).then(() => { + refresh(); + }); + }} + label={translate('actions.archive')}> + <ArchiveIcon /> + </Button> + ); }; export { ArchiveButton }; diff --git a/src/components/Breadcrumb.js b/src/components/Breadcrumb.js index bdd26c3539e862eeeb6b40628345a3c65a63c561..0bbae9f08282fac00c4bf271c1693f5bcde312aa 100644 --- a/src/components/Breadcrumb.js +++ b/src/components/Breadcrumb.js @@ -6,41 +6,37 @@ import { useTranslate } from 'react-admin'; import { Link, useLocation } from 'react-router-dom'; const BreadCrumb = () => { - const location = useLocation(); - let path = location.pathname.split('/'); - const translate = useTranslate(); + const location = useLocation(); + let path = location.pathname.split('/'); + const translate = useTranslate(); - if (path.length === 2 && path[1] === "") { - return ( - <MuiBreadcrumbs aria-label="breadcrumb" style={{ paddingBottom: '5px' }}> - <Typography color="inherit"> - {translate('menu.left.dashboard')} - </Typography> - </MuiBreadcrumbs> - ); - } - - path.shift(); - let last = path.pop(); - let link = ""; + if (path.length === 2 && path[1] === '') { return ( - <MuiBreadcrumbs aria-label="breadcrumb" style={{ paddingBottom: '5px' }}> - <MuiLink component={Link} color="inherit" to="/"> - {translate('menu.left.dashboard')} - </MuiLink> - {path.map((val, key) => { - link += "/" + val; - return ( - <MuiLink component={Link} color="inherit" to={link} key={link}> - {(isNaN(val) ? translate('menu.left.' + val) : val)} - </MuiLink> - ); - })} - <Typography color="textPrimary"> - {(isNaN(last) ? translate('menu.left.' + last) : last)} - </Typography> - </MuiBreadcrumbs> + <MuiBreadcrumbs aria-label="breadcrumb" style={{ paddingBottom: '5px' }}> + <Typography color="inherit">{translate('menu.left.dashboard')}</Typography> + </MuiBreadcrumbs> ); + } + + path.shift(); + let last = path.pop(); + let link = ''; + return ( + <MuiBreadcrumbs aria-label="breadcrumb" style={{ paddingBottom: '5px' }}> + <MuiLink component={Link} color="inherit" to="/"> + {translate('menu.left.dashboard')} + </MuiLink> + {path.map((val) => { + link += '/' + val; + return ( + <MuiLink component={Link} color="inherit" to={link} key={link}> + {isNaN(val) ? translate('menu.left.' + val) : val} + </MuiLink> + ); + })} + <Typography color="textPrimary">{isNaN(last) ? translate('menu.left.' + last) : last}</Typography> + </MuiBreadcrumbs> + ); }; -export default BreadCrumb; \ No newline at end of file +export default BreadCrumb; diff --git a/src/components/DateField.js b/src/components/DateField.js index 05b30f9c84c5cefaffee63b4a4bee57d1719ca69..f91ef792e18a031be9d2d1c5b9689fa2b2a0ab46 100644 --- a/src/components/DateField.js +++ b/src/components/DateField.js @@ -1,8 +1,8 @@ -import { DateField as RADateField } from "react-admin"; +import { DateField as RADateField } from 'react-admin'; const DateField = RADateField; -DateField.defaultProps.locales = "fr-FR"; +DateField.defaultProps.locales = 'fr-FR'; DateField.defaultProps.showTime = true; export default DateField; diff --git a/src/components/DateTimeInput.js b/src/components/DateTimeInput.js index 13ca3e80adba96d2e95b669a1e95071471dfe89e..6e6e143f4596a4fa2cc66303468733e9a3cab967 100644 --- a/src/components/DateTimeInput.js +++ b/src/components/DateTimeInput.js @@ -1,20 +1,20 @@ /** * https://github.com/vascofg/react-admin-date-inputs - * + * * MIT License - * + * * Copyright (c) 2017 Alexey Simakov - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -26,104 +26,93 @@ import DateFnsUtils from '@date-io/date-fns'; import { DatePicker, DateTimePicker, MuiPickersUtilsProvider, TimePicker } from '@material-ui/pickers'; -import format from "date-fns/format"; -import frLocale from "date-fns/locale/fr"; +import format from 'date-fns/format'; +import frLocale from 'date-fns/locale/fr'; import PropTypes from 'prop-types'; import { FieldTitle, useInput } from 'ra-core'; import React, { useCallback } from 'react'; class LocalizedUtils extends DateFnsUtils { - getDatePickerHeaderText(date) { - return format(date, "d MMM", { locale: this.locale }); - } + getDatePickerHeaderText(date) { + return format(date, 'd MMM', { locale: this.locale }); + } - getDateTimePickerHeaderText(date) { - return format(date, "d MMM", { locale: this.locale }); - } + getDateTimePickerHeaderText(date) { + return format(date, 'd MMM', { locale: this.locale }); + } } const Picker = ({ PickerComponent, ...fieldProps }) => { + const { options, label, source, resource, className, isRequired, providerOptions, disabled } = fieldProps; - const { - options, - label, - source, - resource, - className, - isRequired, - providerOptions, - disabled, - } = fieldProps; + const { input, meta } = useInput({ source }); - const { input, meta } = useInput({ source }); + const { touched, error } = meta; - const { touched, error } = meta; - - const handleChange = useCallback(value => { - Date.parse(value) ? input.onChange(value.toISOString()) : input.onChange(null); - }, [input]); + const handleChange = useCallback( + (value) => { + Date.parse(value) ? input.onChange(value.toISOString()) : input.onChange(null); + }, + [input] + ); - return ( - <div className="picker"> - <MuiPickersUtilsProvider {...providerOptions} locale={frLocale}> - <PickerComponent - {...options} - label={<FieldTitle - label={label} - source={source} - resource={resource} - isRequired={isRequired} - />} - margin="normal" - error={!!(touched && error)} - helperText={touched && error} - className={className} - value={input.value ? new Date(input.value) : null} - onChange={date => handleChange(date)} - onBlur={() => input.onBlur(input.value ? new Date(input.value).toISOString() : null)} - disabled={disabled} - format="dd/MM/yyyy, HH:mm:ss" - ampm={false} - inputVariant="filled" - /> - </MuiPickersUtilsProvider> - </div> - ) -} + return ( + <div className="picker"> + <MuiPickersUtilsProvider {...providerOptions} locale={frLocale}> + <PickerComponent + {...options} + label={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />} + margin="normal" + error={!!(touched && error)} + helperText={touched && error} + className={className} + value={input.value ? new Date(input.value) : null} + onChange={(date) => handleChange(date)} + onBlur={() => input.onBlur(input.value ? new Date(input.value).toISOString() : null)} + disabled={disabled} + format="dd/MM/yyyy, HH:mm:ss" + ampm={false} + inputVariant="filled" + /> + </MuiPickersUtilsProvider> + </div> + ); +}; Picker.propTypes = { - input: PropTypes.object, - isRequired: PropTypes.bool, - label: PropTypes.string, - meta: PropTypes.object, - options: PropTypes.object, - resource: PropTypes.string, - source: PropTypes.string, - labelTime: PropTypes.string, - className: PropTypes.string, - providerOptions: PropTypes.shape({ - utils: PropTypes.func, - locale: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - }), - disabled: PropTypes.bool + input: PropTypes.object, + isRequired: PropTypes.bool, + label: PropTypes.string, + meta: PropTypes.object, + options: PropTypes.object, + resource: PropTypes.string, + source: PropTypes.string, + labelTime: PropTypes.string, + className: PropTypes.string, + providerOptions: PropTypes.shape({ + utils: PropTypes.func, + locale: PropTypes.oneOfType([PropTypes.object, PropTypes.string]) + }), + disabled: PropTypes.bool, + PickerComponent: PropTypes.element }; Picker.defaultProps = { - input: {}, - isRequired: false, - meta: { touched: false, error: false }, - options: {}, - resource: '', - source: '', - labelTime: '', - className: '', - disabled: false, - providerOptions: { - utils: LocalizedUtils, - locale: undefined, - }, + input: {}, + isRequired: false, + meta: { touched: false, error: false }, + options: {}, + resource: '', + source: '', + labelTime: '', + className: '', + disabled: false, + providerOptions: { + utils: LocalizedUtils, + locale: undefined + } }; -export const DateInput = props => <Picker PickerComponent={DatePicker} {...props} /> -export const TimeInput = props => <Picker PickerComponent={TimePicker} {...props} /> -export const DateTimeInput = props => <Picker PickerComponent={DateTimePicker} {...props} /> +export const DateInput = (props) => <Picker PickerComponent={DatePicker} {...props} />; +export const TimeInput = (props) => <Picker PickerComponent={TimePicker} {...props} />; +export const DateTimeInput = (props) => <Picker PickerComponent={DateTimePicker} {...props} />; diff --git a/src/components/DialogForm.js b/src/components/DialogForm.js index b0ddc920aa419d3c30aebb1eae6dee65ab257c60..c98905f5bbe22a1430021d8e97f98a2449863802 100644 --- a/src/components/DialogForm.js +++ b/src/components/DialogForm.js @@ -1,159 +1,221 @@ import { Dialog, DialogContent, DialogTitle, useMediaQuery, useTheme } from '@material-ui/core'; import IconButton from '@material-ui/core/IconButton'; -import { styled, withStyles } from "@material-ui/core/styles"; +import { styled, withStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import CloseIcon from '@material-ui/icons/Close'; -import { spacing } from "@material-ui/system"; -import * as React from "react"; -import { Create, DeleteButton, Edit, SaveButton, Show, Toolbar, useGetResourceLabel, useNotify, useRedirect, useRefresh, useResourceContext, useResourceDefinition, useTranslate } from 'react-admin'; +import { spacing } from '@material-ui/system'; +import * as React from 'react'; +import { + Create, + DeleteButton, + Edit, + SaveButton, + Show, + Toolbar, + useGetResourceLabel, + useNotify, + useRedirect, + useRefresh, + useResourceContext, + useResourceDefinition, + useTranslate +} from 'react-admin'; import { Route, withRouter } from 'react-router-dom'; const StyledDeleteButton = styled(DeleteButton)(spacing); -const CustomToolbar = props => ( - <Toolbar {...props}> - <SaveButton color="secondary" /> - <StyledDeleteButton size="medium" ml="auto" undoable={false} /> - </Toolbar> +const CustomToolbar = (props) => ( + <Toolbar {...props}> + <SaveButton color="secondary" /> + <StyledDeleteButton size="medium" ml="auto" undoable={false} /> + </Toolbar> ); const styles = (theme) => ({ - root: { - margin: 0, - padding: theme.spacing(2), - }, - closeButton: { - position: 'absolute', - right: theme.spacing(1), - top: theme.spacing(1), - color: theme.palette.grey[500], - }, + root: { + margin: 0, + padding: theme.spacing(2) + }, + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500] + } }); const MyDialogTitle = withStyles(styles)((props) => { - const { children, classes, onClose, ...other } = props; - return ( - <DialogTitle disableTypography className={classes.root} {...other}> - <Typography variant="h6">{children}</Typography> - {onClose ? ( - <IconButton aria-label="close" className={classes.closeButton} onClick={onClose}> - <CloseIcon /> - </IconButton> - ) : null} - </DialogTitle> - ); + const { children, classes, onClose, ...other } = props; + return ( + <DialogTitle disableTypography className={classes.root} {...other}> + <Typography variant="h6">{children}</Typography> + {onClose ? ( + <IconButton aria-label="close" className={classes.closeButton} onClick={onClose}> + <CloseIcon /> + </IconButton> + ) : null} + </DialogTitle> + ); }); -const CreateDialog = withRouter(({ history, handleClose, staticContext, syncWithLocation, children, ...props }) => { - const name = useResourceContext(); - const resource = useResourceDefinition(props); - const label = useGetResourceLabel(); - const theme = useTheme(); - const fullScreen = useMediaQuery(theme.breakpoints.down('md')); - const redirect = useRedirect(); - const translate = useTranslate(); - const notify = useNotify(); - const refresh = useRefresh(); +const CreateDialog = withRouter(({ history, children, ...props }) => { + const name = useResourceContext(); + const resource = useResourceDefinition(props); + const label = useGetResourceLabel(); + const theme = useTheme(); + const fullScreen = useMediaQuery(theme.breakpoints.down('md')); + const redirect = useRedirect(); + const translate = useTranslate(); + const notify = useNotify(); + const refresh = useRefresh(); - return ( - <Route path={`/${name}/create`} exact render={() => { - const handleClose = () => { - if (history.length === 1) { - redirect(`/${name}`); - } else { - history.goBack(); - } - }; - return ( - <Dialog fullScreen={fullScreen} open maxWidth="md" onClose={handleClose} fullWidth={true} scroll="body"> - {resource.hasCreate === undefined ? handleClose() : <> - <DialogContent> - <MyDialogTitle onClose={handleClose}>{translate('ra.page.create', { name: label(name, 1) })}</MyDialogTitle> - <Create title=" " {...props} onSuccess={() => { - notify('ra.notification.created', 'info', { smart_count: 1 }); - redirect(`/${name}`); - refresh(); - }}> - {React.cloneElement(children, { toolbar: (<CustomToolbar />) })} - </Create> - </DialogContent> - </>} - </Dialog> - ); - }} /> - ); + return ( + <Route + path={`/${name}/create`} + exact + render={() => { + const handleClose = () => { + if (history.length === 1) { + redirect(`/${name}`); + } else { + history.goBack(); + } + }; + return ( + <Dialog fullScreen={fullScreen} open maxWidth="md" onClose={handleClose} fullWidth={true} scroll="body"> + {resource.hasCreate === undefined ? ( + handleClose() + ) : ( + <> + <DialogContent> + <MyDialogTitle onClose={handleClose}>{translate('ra.page.create', { name: label(name, 1) })}</MyDialogTitle> + <Create + title=" " + {...props} + onSuccess={() => { + notify('ra.notification.created', 'info', { + smart_count: 1 + }); + redirect(`/${name}`); + refresh(); + }}> + {React.cloneElement(children, { + toolbar: <CustomToolbar /> + })} + </Create> + </DialogContent> + </> + )} + </Dialog> + ); + }} + /> + ); }); -const EditDialog = withRouter(({ history, handleClose, staticContext, syncWithLocation, children, ...props }) => { - const name = useResourceContext(); - const resource = useResourceDefinition(props); - const label = useGetResourceLabel(); - const theme = useTheme(); - const fullScreen = useMediaQuery(theme.breakpoints.down('md')); - const redirect = useRedirect(); - const translate = useTranslate(); +const EditDialog = withRouter(({ history, children, ...props }) => { + const name = useResourceContext(); + const resource = useResourceDefinition(props); + const label = useGetResourceLabel(); + const theme = useTheme(); + const fullScreen = useMediaQuery(theme.breakpoints.down('md')); + const redirect = useRedirect(); + const translate = useTranslate(); - return ( - <Route path={`/${name}/:id`} render={({ match }) => { - const handleClose = () => { - if (history.length === 1) { - redirect(`/${name}`); - } else { - history.goBack(); - } - }; - const isMatch = match && match.params && match.params.id !== 'create'; - return ( - <Dialog fullScreen={fullScreen} open={isMatch} maxWidth="md" onClose={handleClose} fullWidth={true} scroll="body"> - {isMatch ? ( - (resource.hasEdit === undefined ? handleClose() : <> - <DialogContent> - <MyDialogTitle onClose={handleClose}>{translate('ra.page.edit', { name: label(name, 1), id: isMatch ? match.params.id : null })}</MyDialogTitle> - <Edit actions={null} id={isMatch ? match.params.id : null} title=" " {...props}> - {React.cloneElement(children, { toolbar: (<CustomToolbar />) })} - </Edit> - </DialogContent> - </>) - ) : ""} - </Dialog> - ); - }} exact /> - ); + return ( + <Route + path={`/${name}/:id`} + render={({ match }) => { + const handleClose = () => { + if (history.length === 1) { + redirect(`/${name}`); + } else { + history.goBack(); + } + }; + const isMatch = match && match.params && match.params.id !== 'create'; + return ( + <Dialog fullScreen={fullScreen} open={isMatch} maxWidth="md" onClose={handleClose} fullWidth={true} scroll="body"> + {isMatch ? ( + resource.hasEdit === undefined ? ( + handleClose() + ) : ( + <> + <DialogContent> + <MyDialogTitle onClose={handleClose}> + {translate('ra.page.edit', { + name: label(name, 1), + id: isMatch ? match.params.id : null + })} + </MyDialogTitle> + <Edit actions={null} id={isMatch ? match.params.id : null} title=" " {...props}> + {React.cloneElement(children, { + toolbar: <CustomToolbar /> + })} + </Edit> + </DialogContent> + </> + ) + ) : ( + '' + )} + </Dialog> + ); + }} + exact + /> + ); }); -const ShowDialog = withRouter(({ history, handleClose, staticContext, syncWithLocation, children, hasEdit, ...props }) => { - const name = useResourceContext(); - const resource = useResourceDefinition(props); - const label = useGetResourceLabel(); - const theme = useTheme(); - const fullScreen = useMediaQuery(theme.breakpoints.down('md')); - const redirect = useRedirect(); - const translate = useTranslate(); +const ShowDialog = withRouter(({ history, children, ...props }) => { + const name = useResourceContext(); + const resource = useResourceDefinition(props); + const label = useGetResourceLabel(); + const theme = useTheme(); + const fullScreen = useMediaQuery(theme.breakpoints.down('md')); + const redirect = useRedirect(); + const translate = useTranslate(); - return ( - <Route path={`/${name}/:id/show`} render={({ match }) => { - const handleClose = () => { - if (history.length === 1) { - redirect(`/${name}`); - } else { - history.goBack(); - } - }; - const isMatch = match && match.params && match.params.id !== 'create'; - return ( - <Dialog fullScreen={fullScreen} open={isMatch} maxWidth="md" onClose={handleClose} fullWidth={true} scroll="body"> - {isMatch ? ( - (resource.hasShow ? <> - <DialogContent> - <MyDialogTitle onClose={handleClose}>{translate('ra.page.show', { name: label(name, 1), id: isMatch ? match.params.id : null })}</MyDialogTitle> - <Show actions={null} id={isMatch ? match.params.id : null} basePath={props.basePath} resource={name} title=" " {...props} > - {children} - </Show> - </DialogContent> - </> : handleClose())) : ""} - </Dialog> - ); - }} exact /> - ); + return ( + <Route + path={`/${name}/:id/show`} + render={({ match }) => { + const handleClose = () => { + if (history.length === 1) { + redirect(`/${name}`); + } else { + history.goBack(); + } + }; + const isMatch = match && match.params && match.params.id !== 'create'; + return ( + <Dialog fullScreen={fullScreen} open={isMatch} maxWidth="md" onClose={handleClose} fullWidth={true} scroll="body"> + {isMatch ? ( + resource.hasShow ? ( + <> + <DialogContent> + <MyDialogTitle onClose={handleClose}> + {translate('ra.page.show', { + name: label(name, 1), + id: isMatch ? match.params.id : null + })} + </MyDialogTitle> + <Show actions={null} id={isMatch ? match.params.id : null} basePath={props.basePath} resource={name} title=" " {...props}> + {children} + </Show> + </DialogContent> + </> + ) : ( + handleClose() + ) + ) : ( + '' + )} + </Dialog> + ); + }} + exact + /> + ); }); export { CreateDialog, EditDialog, ShowDialog }; diff --git a/src/components/MoneyField.js b/src/components/MoneyField.js index 6b9a9d5c16f329743457bcee3089896d963d8ad1..4efe627a0ce3f19f360945c961e4ee132754ea81 100644 --- a/src/components/MoneyField.js +++ b/src/components/MoneyField.js @@ -1,32 +1,54 @@ import get from 'lodash/get'; import PropTypes from 'prop-types'; -import * as React from "react"; +import * as React from 'react'; import { FunctionField, Labeled } from 'react-admin'; -const MoneyField = ({ source, noLabel, currency, digits, ...props }) => { - return ( - noLabel ? - <FunctionField {...props} style={{ width: '100%', whiteSpace: 'nowrap', textAlign: 'right', display: 'inline-block' }} render={record => (Number(get(record, source)).toLocaleString('fr-FR', { currency: currency, currencyDisplay: 'symbol', style: 'currency' }))} /> - : - <Labeled source={source} {...props}> - <FunctionField {...props} render={record => (Number(get(record, source)).toLocaleString('fr-FR', { currency: currency, currencyDisplay: 'symbol', style: 'currency' }))} /> - </Labeled> - ); -} +const MoneyField = ({ source, noLabel, currency, ...props }) => { + return noLabel ? ( + <FunctionField + {...props} + style={{ + width: '100%', + whiteSpace: 'nowrap', + textAlign: 'right', + display: 'inline-block' + }} + render={(record) => + Number(get(record, source)).toLocaleString('fr-FR', { + currency: currency, + currencyDisplay: 'symbol', + style: 'currency' + }) + } + /> + ) : ( + <Labeled source={source} {...props}> + <FunctionField + {...props} + render={(record) => + Number(get(record, source)).toLocaleString('fr-FR', { + currency: currency, + currencyDisplay: 'symbol', + style: 'currency' + }) + } + /> + </Labeled> + ); +}; MoneyField.propTypes = { - label: PropTypes.string, - noLabel: PropTypes.bool, - record: PropTypes.object, - currency: PropTypes.string, - source: PropTypes.string.isRequired, - digits: PropTypes.number + label: PropTypes.string, + noLabel: PropTypes.bool, + record: PropTypes.object, + currency: PropTypes.string, + source: PropTypes.string.isRequired }; MoneyField.defaultProps = { - currency: "EUR", - noLabel: false, - digits: 2 -} + currency: 'EUR', + noLabel: false, + digits: 2 +}; export default MoneyField; diff --git a/src/components/MoneyInput.js b/src/components/MoneyInput.js index 21ba6bb31c2bfecfadf453837b0558d09b11ed66..049f8c36834faaeacbe0c48c64b6e36f7900eeb2 100644 --- a/src/components/MoneyInput.js +++ b/src/components/MoneyInput.js @@ -1,26 +1,26 @@ import InputAdornment from '@material-ui/core/InputAdornment'; import PropTypes from 'prop-types'; -import * as React from "react"; +import * as React from 'react'; import { TextInput } from 'react-admin'; const MoneyInput = ({ currency, ...props }) => ( - <TextInput - InputProps={{ - endAdornment: <InputAdornment position="end">{currency}</InputAdornment>, - }} - {...props} - /> + <TextInput + InputProps={{ + endAdornment: <InputAdornment position="end">{currency}</InputAdornment> + }} + {...props} + /> ); MoneyInput.propTypes = { - label: PropTypes.string, - record: PropTypes.object, - currency: PropTypes.string, - source: PropTypes.string.isRequired + label: PropTypes.string, + record: PropTypes.object, + currency: PropTypes.string, + source: PropTypes.string.isRequired }; MoneyInput.defaultProps = { - currency: "€" -} + currency: '€' +}; -export default MoneyInput; \ No newline at end of file +export default MoneyInput; diff --git a/src/components/MultiProductCountInput.js b/src/components/MultiProductCountInput.js index 7f68defefe4f60237c1866e36ec42c7fe40d9bd5..4007d543c7d3e1b430ba7af4c41356ee73328433 100644 --- a/src/components/MultiProductCountInput.js +++ b/src/components/MultiProductCountInput.js @@ -1,221 +1,329 @@ - -import { Button, Grid, IconButton, InputAdornment, makeStyles, MenuItem, Paper, TextField, Typography } from "@material-ui/core"; +import { Button, Grid, IconButton, InputAdornment, makeStyles, MenuItem, Paper, TextField, Typography } from '@material-ui/core'; import AddIcon from '@material-ui/icons/Add'; import ClearIcon from '@material-ui/icons/Clear'; import FilterListIcon from '@material-ui/icons/FilterList'; import RemoveIcon from '@material-ui/icons/Remove'; -import React, { useEffect, useState } from "react"; +import PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; import { Error, Labeled, Loading, useInput, useQuery, useTranslate } from 'react-admin'; const useStyles = makeStyles((theme) => ({ - root: { - flexGrow: 1, - }, - paper: { - padding: theme.spacing(2), - textAlign: 'center', - color: theme.palette.text.secondary, - backgroundColor: '#545454' - }, + root: { + flexGrow: 1 + }, + paper: { + padding: theme.spacing(2), + textAlign: 'center', + color: theme.palette.text.secondary, + backgroundColor: '#545454' + } })); const MultiProductCountItem = ({ product, filterCategory, filterName, countZero, updatePrice, price, showcount, ...props }) => { - const classes = useStyles(); - const [count, setCount] = useState(countZero ? "" : 0); - - const addCount = ((x) => { - let c = count; - - if (countZero) { - if (c === "") { - c = 0; - } - } - - if (c + x < 0) { - setCount(countZero ? "" : 0); - updatePrice(product, countZero ? "" : 0); - return; - } - - setCount(c + x); - updatePrice(product, c + x); - }); - - useEffect(() => { - setCount(countZero ? "" : 0); - updatePrice(product, countZero ? "" : 0); - }, [props.refresh]); // eslint-disable-line - - return ( - ((filterCategory === "" || filterCategory === product.category_id) && (filterName === "" || product.name.toLowerCase().includes(filterName.toLowerCase()))) ? - <Grid item xs={12} sm={6} md={4} lg={3} xl={2}> - <Paper className={classes.paper}> - <Typography variant="h6" gutterBottom>{product.name}</Typography> - {price || showcount ? - <Typography gutterBottom> - {Number(product.price).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' })} - {price && showcount ? " | " : ''} - {Number(product.count).toLocaleString('fr-FR', { style: 'decimal' })} - </Typography> - : ''} - <Grid container style={{ justifyContent: "center" }}> - <Grid item className={classes.rows}> - <IconButton aria-label="sub" onClick={(e) => { addCount(e.shiftKey ? -10 : -1) }} tabIndex="-1"> - <RemoveIcon /> - </IconButton> - </Grid> - <Grid item container alignItems="center" className={classes.rows} xs={4}> - <Grid item> - <TextField - type="text" - inputProps={{ style: { textAlign: 'center' } }} - value={count} - InputLabelProps={{ - shrink: true, - }} - onChange={(e) => { - let val = e.target.value; - if (val === "") { - setCount(countZero ? "" : 0); - updatePrice(product, countZero ? "" : 0); - return; - } - val = parseInt(val); - if (!isNaN(val) && val >= 0) { - setCount(val); - updatePrice(product, val); - } - }} - /> - </Grid> - </Grid> - <Grid item className={classes.rows}> - <IconButton aria-label="add" onClick={(e) => { addCount(e.shiftKey ? 10 : 1) }} tabIndex="-1"> - <AddIcon /> - </IconButton> - </Grid> - </Grid> - </Paper> + const classes = useStyles(); + const [count, setCount] = useState(countZero ? '' : 0); + + const addCount = (x) => { + let c = count; + + if (countZero) { + if (c === '') { + c = 0; + } + } + + if (c + x < 0) { + setCount(countZero ? '' : 0); + updatePrice(product, countZero ? '' : 0); + return; + } + + setCount(c + x); + updatePrice(product, c + x); + }; + + useEffect(() => { + setCount(countZero ? '' : 0); + updatePrice(product, countZero ? '' : 0); + }, [props.refresh]); // eslint-disable-line + + return (filterCategory === '' || filterCategory === product.category_id) && + (filterName === '' || product.name.toLowerCase().includes(filterName.toLowerCase())) ? ( + <Grid item xs={12} sm={6} md={4} lg={3} xl={2}> + <Paper className={classes.paper}> + <Typography variant="h6" gutterBottom> + {product.name} + </Typography> + {price || showcount ? ( + <Typography gutterBottom> + {Number(product.price).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + })} + {price && showcount ? ' | ' : ''} + {Number(product.count).toLocaleString('fr-FR', { + style: 'decimal' + })} + </Typography> + ) : ( + '' + )} + <Grid container style={{ justifyContent: 'center' }}> + <Grid item className={classes.rows}> + <IconButton + aria-label="sub" + onClick={(e) => { + addCount(e.shiftKey ? -10 : -1); + }} + tabIndex="-1"> + <RemoveIcon /> + </IconButton> + </Grid> + <Grid item container alignItems="center" className={classes.rows} xs={4}> + <Grid item> + <TextField + type="text" + inputProps={{ style: { textAlign: 'center' } }} + value={count} + InputLabelProps={{ + shrink: true + }} + onChange={(e) => { + let val = e.target.value; + if (val === '') { + setCount(countZero ? '' : 0); + updatePrice(product, countZero ? '' : 0); + return; + } + val = parseInt(val); + if (!isNaN(val) && val >= 0) { + setCount(val); + updatePrice(product, val); + } + }} + /> </Grid> - : "" - ); + </Grid> + <Grid item className={classes.rows}> + <IconButton + aria-label="add" + onClick={(e) => { + addCount(e.shiftKey ? 10 : 1); + }} + tabIndex="-1"> + <AddIcon /> + </IconButton> + </Grid> + </Grid> + </Paper> + </Grid> + ) : ( + '' + ); }; +MultiProductCountItem.propTypes = { + product: PropTypes.object, + filterName: PropTypes.string, + filterCategory: PropTypes.string, + countZero: PropTypes.bool, + updatePrice: PropTypes.func, + price: PropTypes.bool, + showcount: PropTypes.bool +}; const MultiProductCountInput = ({ total, children, countZero, onlysalable, priceChanged, ...props }) => { - const { data: products_data, loading: products_loading, error: products_error } = useQuery({ - type: 'getAll', - resource: 'products', - payload: {} - }); - - const { data: categories_data, loading: categories_loading, error: categories_error } = useQuery({ - type: 'getAll', - resource: 'products_categories', - payload: {} - }); - - const { - input: { name, onChange } - } = useInput(props); - - const translate = useTranslate(); - const [filterName, setFilterName] = useState(""); - const [filterCategory, setFilterCategory] = useState(""); - - const [counts, setCounts] = useState({}); - const [price, setPrice] = useState(0); - const [refresh, doRefresh] = useState(0); - const updatePrice = (product, count) => { - let c = counts; - - if (count === "" && countZero) { - delete c[product.id]; - } else { - c[product.id] = { count: count, price: product.price }; - } - - setCounts(c); - - let p = []; - for (let id in c) { - if (c[id].count > 0 || countZero) { - p.push({ 'id': parseInt(id), 'count': counts[id].count }); - } - } - - if (p.length === 0) { - onChange(undefined); - } else { - onChange(p); - } - - let moula = 0; - for (let elem in c) { - moula += c[elem].count * c[elem].price; - } - if (priceChanged !== undefined) { - priceChanged(moula); - } - setPrice(moula); - }; - - if (products_loading || categories_loading) return <Loading />; - if (products_error) return <Error error={products_error} />; - if (categories_error) return <Error error={categories_error} />; - if ((!products_data) || (!categories_data)) return null; - - return ( - <Labeled label={name}> - <Grid container> - <Grid container item xs={12}> - <Grid item xs={12} md style={{ padding: '8px 4px' }}> - <TextField size="small" value={filterName} onChange={(e) => { setFilterName(e.target.value) }} variant="filled" type="text" label={translate('inputs.multiproductcount.filters.name')} InputProps={{ - endAdornment: ( - <InputAdornment position="end"> - <FilterListIcon /> - </InputAdornment> - ), - }} /> - </Grid> - <Grid item xs={12} md style={{ padding: '8px 4px' }}> - <TextField select variant="filled" size="small" type="text" label={translate('inputs.multiproductcount.filters.category')} value={filterCategory} onChange={(e) => { setFilterCategory(e.target.value) }} InputProps={{ - endAdornment: ( - <InputAdornment position="end" style={{ marginRight: "20px" }} > - <FilterListIcon /> - </InputAdornment> - ), - }}> - <MenuItem key={""} value={""}><span style={{ color: 'transparent' }}>{translate('inputs.multiproductcount.filters.none')}</span></MenuItem> - {categories_data.map((cat) => ( - <MenuItem key={cat.id} value={cat.id}> - {cat.name} - </MenuItem> - ))} - </TextField> - </Grid> - <Grid item xs={12} md style={{ padding: '8px 4px', display: 'flex', justifyContent: 'end', flexGrow: 0 }}> - <Button color="primary" startIcon={<ClearIcon />} onClick={() => doRefresh(prev => prev + 1)}>{translate('actions.clear')}</Button> - </Grid> - </Grid> - <Grid container item xs={12} spacing={2} style={{ height: 'calc(100vh - 420px)', minHeight: '400px', overflowY: 'scroll', width: 'auto', margin: '0' }}> - {products_data.map((val, key) => { - if ((!onlysalable || onlysalable) && val.salable) - return React.cloneElement(React.Children.only(children), { key: key, product: val, refresh: refresh, filterCategory: filterCategory, filterName: filterName, updatePrice: updatePrice, countZero: countZero }); - else - return "" - })} - </Grid> - {total ? ( - <Grid item xs={12} style={{ padding: '8px 4px' }}> - <TextField value={Number(price).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' })} disabled variant="filled" type="text" label={translate('inputs.multiproductcount.price')} /> - </Grid> - ) : ''} - </Grid> - </Labeled> - ); -} + const { + data: products_data, + loading: products_loading, + error: products_error + } = useQuery({ + type: 'getAll', + resource: 'products', + payload: {} + }); -export { MultiProductCountInput, MultiProductCountItem }; + const { + data: categories_data, + loading: categories_loading, + error: categories_error + } = useQuery({ + type: 'getAll', + resource: 'products_categories', + payload: {} + }); + + const { + input: { name, onChange } + } = useInput(props); + + const translate = useTranslate(); + const [filterName, setFilterName] = useState(''); + const [filterCategory, setFilterCategory] = useState(''); + const [counts, setCounts] = useState({}); + const [price, setPrice] = useState(0); + const [refresh, doRefresh] = useState(0); + const updatePrice = (product, count) => { + let c = counts; + + if (count === '' && countZero) { + delete c[product.id]; + } else { + c[product.id] = { count: count, price: product.price }; + } + + setCounts(c); + + let p = []; + for (let id in c) { + if (c[id].count > 0 || countZero) { + p.push({ id: parseInt(id), count: counts[id].count }); + } + } + + if (p.length === 0) { + onChange(undefined); + } else { + onChange(p); + } + + let moula = 0; + for (let elem in c) { + moula += c[elem].count * c[elem].price; + } + if (priceChanged !== undefined) { + priceChanged(moula); + } + setPrice(moula); + }; + + if (products_loading || categories_loading) return <Loading />; + if (products_error) return <Error error={products_error} />; + if (categories_error) return <Error error={categories_error} />; + if (!products_data || !categories_data) return null; + + return ( + <Labeled label={name}> + <Grid container> + <Grid container item xs={12}> + <Grid item xs={12} md style={{ padding: '8px 4px' }}> + <TextField + size="small" + value={filterName} + onChange={(e) => { + setFilterName(e.target.value); + }} + variant="filled" + type="text" + label={translate('inputs.multiproductcount.filters.name')} + InputProps={{ + endAdornment: ( + <InputAdornment position="end"> + <FilterListIcon /> + </InputAdornment> + ) + }} + /> + </Grid> + <Grid item xs={12} md style={{ padding: '8px 4px' }}> + <TextField + select + variant="filled" + size="small" + type="text" + label={translate('inputs.multiproductcount.filters.category')} + value={filterCategory} + onChange={(e) => { + setFilterCategory(e.target.value); + }} + InputProps={{ + endAdornment: ( + <InputAdornment position="end" style={{ marginRight: '20px' }}> + <FilterListIcon /> + </InputAdornment> + ) + }}> + <MenuItem key={''} value={''}> + <span style={{ color: 'transparent' }}>{translate('inputs.multiproductcount.filters.none')}</span> + </MenuItem> + {categories_data.map((cat) => ( + <MenuItem key={cat.id} value={cat.id}> + {cat.name} + </MenuItem> + ))} + </TextField> + </Grid> + <Grid + item + xs={12} + md + style={{ + padding: '8px 4px', + display: 'flex', + justifyContent: 'end', + flexGrow: 0 + }}> + <Button color="primary" startIcon={<ClearIcon />} onClick={() => doRefresh((prev) => prev + 1)}> + {translate('actions.clear')} + </Button> + </Grid> + </Grid> + <Grid + container + item + xs={12} + spacing={2} + style={{ + height: 'calc(100vh - 420px)', + minHeight: '400px', + overflowY: 'scroll', + width: 'auto', + margin: '0' + }}> + {products_data.map((val, key) => { + if ((!onlysalable || onlysalable) && val.salable) + return React.cloneElement(React.Children.only(children), { + key: key, + product: val, + refresh: refresh, + filterCategory: filterCategory, + filterName: filterName, + updatePrice: updatePrice, + countZero: countZero + }); + else return ''; + })} + </Grid> + {total ? ( + <Grid item xs={12} style={{ padding: '8px 4px' }}> + <TextField + value={Number(price).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + })} + disabled + variant="filled" + type="text" + label={translate('inputs.multiproductcount.price')} + /> + </Grid> + ) : ( + '' + )} + </Grid> + </Labeled> + ); +}; + +MultiProductCountInput.propTypes = { + total: PropTypes.bool, + children: PropTypes.any, + countZero: PropTypes.bool, + onlysalable: PropTypes.bool, + priceChanged: PropTypes.func +}; + +export { MultiProductCountInput, MultiProductCountItem }; diff --git a/src/components/PaymentInput.js b/src/components/PaymentInput.js index 51aad16c1343ced2dc1e98a3879db02c5de43463..bb3df8541b0bb516f17791d78f4ec0691f15f5d4 100644 --- a/src/components/PaymentInput.js +++ b/src/components/PaymentInput.js @@ -1,23 +1,44 @@ -import { TextField as MuiTextField } from "@material-ui/core"; -import React from "react"; +import { TextField as MuiTextField } from '@material-ui/core'; +import PropTypes from 'prop-types'; +import React from 'react'; import { FormDataConsumer, SelectInput, useTranslate } from 'react-admin'; -import PersonalAccountSelector from "../components/PersonalAccountSelector"; +import PersonalAccountSelector from '../components/PersonalAccountSelector'; const PaymentInput = ({ price, optional = false }) => { - const translate = useTranslate(); - return (<> - <MuiTextField value={Number(price).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' })} disabled variant="filled" type="text" label={translate('inputs.multiproductcount.price')} /> - <SelectInput source="payment" label="Moyen de paiement" allowEmpty={optional} choices={[ - { id: 'cash', name: 'Liquide (Caisse)' }, - { id: 'card', name: 'Carte Bancaire' }, - { id: 'account', name: 'Compte personel' }, - ]} initialValue={optional ? null : 'cash'} /> - <FormDataConsumer> - {({ formData, ...rest }) => formData.payment === 'account' && - <PersonalAccountSelector source="token" label="Compte" /> - } - </FormDataConsumer> - </>); + const translate = useTranslate(); + return ( + <> + <MuiTextField + value={Number(price).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + })} + disabled + variant="filled" + type="text" + label={translate('inputs.multiproductcount.price')} + /> + <SelectInput + source="payment" + label="Moyen de paiement" + allowEmpty={optional} + choices={[ + { id: 'cash', name: 'Liquide (Caisse)' }, + { id: 'card', name: 'Carte Bancaire' }, + { id: 'account', name: 'Compte personel' } + ]} + initialValue={optional ? null : 'cash'} + /> + <FormDataConsumer>{({ formData }) => formData.payment === 'account' && <PersonalAccountSelector source="token" label="Compte" />}</FormDataConsumer> + </> + ); +}; + +PaymentInput.propTypes = { + price: PropTypes.number, + optional: PropTypes.bool, + rest: PropTypes.any }; export default PaymentInput; diff --git a/src/components/PersonalAccountSelector.js b/src/components/PersonalAccountSelector.js index 6ac33d33509d7870ffd7b3ee639f91a7759f112a..5cbcb3563ea76ef753b1e898a867f32feb14cdfa 100644 --- a/src/components/PersonalAccountSelector.js +++ b/src/components/PersonalAccountSelector.js @@ -1,94 +1,103 @@ -import { TextField as MuiTextField } from "@material-ui/core"; +import { TextField as MuiTextField } from '@material-ui/core'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import PhotoCameraIcon from '@material-ui/icons/PhotoCamera'; -import React, { useEffect, useState } from "react"; +import PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; import { Labeled, Loading, useInput, useMutation, useNotify } from 'react-admin'; import QrReader from 'react-qr-reader'; const PersonalAccountSelector = ({ label, ...props }) => { - const { input: { name, onChange, value, ...rest }, meta: { touched, error }, isRequired } = useInput(props); + const { + input: { name, onChange, value, ...rest }, + meta: { touched, error }, + isRequired + } = useInput(props); - const [load_account, { loading, data, error: query_error }] = useMutation({ - type: 'getOne', - resource: 'personal_accounts', - payload: { id: value } - }); + const [load_account, { loading, data, error: query_error }] = useMutation({ + type: 'getOne', + resource: 'personal_accounts', + payload: { id: value } + }); - useEffect(() => { - if (value !== "") - load_account(); - }, [value, load_account]); + useEffect(() => { + if (value !== '') load_account(); + }, [value, load_account]); - const notify = useNotify(); - useEffect(() => { - if (query_error) - notify('ra.notification.item_doesnt_exist', 'error'); - }, [query_error, notify]); + const notify = useNotify(); + useEffect(() => { + if (query_error) notify('ra.notification.item_doesnt_exist', 'error'); + }, [query_error, notify]); - const [scanning, setScanning] = useState(false); + const [scanning, setScanning] = useState(false); - const scanClicked = () => { - setScanning(!scanning); - }; - const onScan = (data) => { - if (data) { - let s = data.toString().match("(?:https?:\\/\\/esc\\.gg\\/|core:\\/\\/)([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}).*"); - if (s !== null) { - onChange(s[1]); - setScanning(false); - } - } - }; + const scanClicked = () => { + setScanning(!scanning); + }; + const onScan = (data) => { + if (data) { + let s = data.toString().match('(?:https?:\\/\\/esc\\.gg\\/|core:\\/\\/)([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}).*'); + if (s !== null) { + onChange(s[1]); + setScanning(false); + } + } + }; - return ( - <> - <Labeled label={label}> - <Grid container spacing={3}> - <Grid item md> - <MuiTextField - disabled - name={name} - onChange={onChange} - error={!!(touched && error)} - helperText={touched && error} - required={isRequired} - value={value} - {...rest} - /> - </Grid> - <Grid item container md={2} style={{ justifyContent: "flex-end" }}> - <Grid item> - <Button fullWidth variant="contained" endIcon={<PhotoCameraIcon />} color="primary" onClick={scanClicked}> - Scan - </Button> - </Grid> - </Grid> - </Grid> - </Labeled> - {scanning ? ( - <QrReader delay={200} style={{ width: '100%' }} onScan={onScan} onError={console.error} /> - ) : ""} + return ( + <> + <Labeled label={label}> + <Grid container spacing={3}> + <Grid item md> + <MuiTextField + disabled + name={name} + onChange={onChange} + error={!!(touched && error)} + helperText={touched && error} + required={isRequired} + value={value} + {...rest} + /> + </Grid> + <Grid item container md={2} style={{ justifyContent: 'flex-end' }}> + <Grid item> + <Button fullWidth variant="contained" endIcon={<PhotoCameraIcon />} color="primary" onClick={scanClicked}> + Scan + </Button> + </Grid> + </Grid> + </Grid> + </Labeled> + {scanning ? <QrReader delay={200} style={{ width: '100%' }} onScan={onScan} onError={console.error} /> : ''} - {loading ? ( - <Loading /> - ) : (data === undefined || data === null ? "" : (<> - <Labeled label="Nom"> - <MuiTextField - disabled - value={data.person.fullname} - /> - </Labeled> - <Labeled label="Solde"> - <MuiTextField - disabled - value={Number(data.balance).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' })} - /> - </Labeled> - </>) - )} + {loading ? ( + <Loading /> + ) : data === undefined || data === null ? ( + '' + ) : ( + <> + <Labeled label="Nom"> + <MuiTextField disabled value={data.person.fullname} /> + </Labeled> + <Labeled label="Solde"> + <MuiTextField + disabled + value={Number(data.balance).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + })} + /> + </Labeled> </> - ); -} + )} + </> + ); +}; + +PersonalAccountSelector.propTypes = { + label: PropTypes.string +}; -export default PersonalAccountSelector; \ No newline at end of file +export default PersonalAccountSelector; diff --git a/src/components/QRInput.js b/src/components/QRInput.js index 3afa9a5a3986882e52a6cafa97c5eb6aaaeb5218..487107f601bf0954ef11ebd13ddfefec7e29ab75 100644 --- a/src/components/QRInput.js +++ b/src/components/QRInput.js @@ -1,59 +1,67 @@ - import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import TextField from '@material-ui/core/TextField'; import PhotoCameraIcon from '@material-ui/icons/PhotoCamera'; -import { useState } from 'react'; +import PropTypes from 'prop-types'; +import React, { useState } from 'react'; import { Labeled, useInput } from 'react-admin'; import QrReader from 'react-qr-reader'; const QRInput = ({ label, regexp, ...props }) => { - const { input: { name, onChange, value, ...rest }, meta: { touched, error }, isRequired } = useInput(props); + const { + input: { name, onChange, value, ...rest }, + meta: { touched, error }, + isRequired + } = useInput(props); + + const [scanning, setScanning] = useState(false); + + const scanClicked = () => { + setScanning(!scanning); + }; + const onScan = (data) => { + if (data) { + let s = data.toString().match(regexp); + if (s !== null) { + onChange(s[1]); + setScanning(false); + } + } + }; - const [scanning, setScanning] = useState(false); + return ( + <> + <Labeled label={label}> + <Grid container spacing={3}> + <Grid item md> + <TextField + disabled + name={name} + onChange={onChange} + error={!!(touched && error)} + helperText={touched && error} + required={isRequired} + value={value} + {...rest} + /> + </Grid> + <Grid item container md={2} style={{ justifyContent: 'flex-end' }}> + <Grid item> + <Button fullWidth variant="contained" endIcon={<PhotoCameraIcon />} color="primary" onClick={scanClicked}> + Scan + </Button> + </Grid> + </Grid> + </Grid> + </Labeled> + {scanning ? <QrReader delay={200} style={{ width: '100%' }} onScan={onScan} onError={console.error} /> : ''} + </> + ); +}; - const scanClicked = () => { - setScanning(!scanning); - }; - const onScan = (data) => { - if (data) { - let s = data.toString().match(regexp); - if (s !== null) { - onChange(s[1]); - setScanning(false); - } - } - }; +QRInput.propTypes = { + label: PropTypes.string, + regexp: PropTypes.string +}; - return ( - <> - <Labeled label={label}> - <Grid container spacing={3}> - <Grid item md> - <TextField - disabled - name={name} - onChange={onChange} - error={!!(touched && error)} - helperText={touched && error} - required={isRequired} - value={value} - {...rest} - /> - </Grid> - <Grid item container md={2} style={{ justifyContent: "flex-end" }}> - <Grid item> - <Button fullWidth variant="contained" endIcon={<PhotoCameraIcon />} color="primary" onClick={scanClicked}> - Scan - </Button> - </Grid> - </Grid> - </Grid> - </Labeled> - {scanning ? ( - <QrReader delay={200} style={{ width: '100%' }} onScan={onScan} onError={console.error} /> - ) : ""} - </> - ); -} -export default QRInput; \ No newline at end of file +export default QRInput; diff --git a/src/components/RecalculateButton.js b/src/components/RecalculateButton.js index 305e3478d0635b3988d22f4119f398de6cdd8628..8d5259f26a5c45719b04663b87fb7a419d3fe322 100644 --- a/src/components/RecalculateButton.js +++ b/src/components/RecalculateButton.js @@ -1,24 +1,24 @@ import RefreshIcon from '@material-ui/icons/Refresh'; -import * as React from "react"; +import * as React from 'react'; import { Button, useDataProvider, useRefresh, useResourceContext, useTranslate } from 'react-admin'; +const RecalculateButton = () => { + const dataProvider = useDataProvider(); + const name = useResourceContext(); + const refresh = useRefresh(); + const translate = useTranslate(); -const RecalculateButton = (props) => { - const dataProvider = useDataProvider(); - const name = useResourceContext(); - const refresh = useRefresh(); - const translate = useTranslate(); - - return ( - <Button - onClick={() => { - dataProvider.reload(name).then(() => { - refresh(); - }); - }} - label={translate('actions.recalculate')} - ><RefreshIcon /></Button> - ); + return ( + <Button + onClick={() => { + dataProvider.reload(name).then(() => { + refresh(); + }); + }} + label={translate('actions.recalculate')}> + <RefreshIcon /> + </Button> + ); }; export { RecalculateButton }; diff --git a/src/index.js b/src/index.js index 57a1af86d0e96ddbd88d917a03ccd5cb8e913503..69cba98701e4974461a0929f99ce9cc6b0548af7 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ import App from './App'; import reportWebVitals from './reportWebVitals'; import './sass/app.scss'; -axios.defaults.baseURL = process.env.REACT_APP_SEB_API; +axios.defaults.baseURL = process.env.REACT_APP_SEB_API; // eslint-disable-line ReactDOM.render( <React.StrictMode> diff --git a/src/layout/AppBar.js b/src/layout/AppBar.js index 1a62b6ae9f174c3a67f42d7883219165f8b42c7f..00ff9616449b603362822ce0e12e9c7f2c3d297b 100644 --- a/src/layout/AppBar.js +++ b/src/layout/AppBar.js @@ -2,5 +2,5 @@ import React from 'react'; import { AppBar } from 'react-admin'; import UserMenu from './UserMenu'; -const MyAppBar = props => <AppBar {...props} userMenu={<UserMenu />} />; -export default MyAppBar; \ No newline at end of file +const MyAppBar = (props) => <AppBar {...props} userMenu={<UserMenu />} />; +export default MyAppBar; diff --git a/src/layout/Layout.js b/src/layout/Layout.js index f6cf779bbbf3f96046924edc5d8a7a122e92e418..0bf1536cab44c1dcc358d6260dd3ec1f9207c09f 100644 --- a/src/layout/Layout.js +++ b/src/layout/Layout.js @@ -1,14 +1,19 @@ - +import PropTypes from 'prop-types'; +import React from 'react'; import { Layout } from 'react-admin'; import BreadCrumb from '../components/Breadcrumb'; import AppBar from './AppBar'; import { Menu } from './Menu'; const MyLayout = ({ children, ...props }) => ( - <Layout {...props} menu={Menu} appBar={AppBar} > - <BreadCrumb /> - {children} - </Layout> + <Layout {...props} menu={Menu} appBar={AppBar}> + <BreadCrumb /> + {children} + </Layout> ); -export default MyLayout; \ No newline at end of file +MyLayout.propTypes = { + children: PropTypes.node +}; + +export default MyLayout; diff --git a/src/layout/Login.js b/src/layout/Login.js index 82826207a350776387c3fc6d35feb92fa170803c..3c10cf2627b583613de614f7705142ac9dd93317 100644 --- a/src/layout/Login.js +++ b/src/layout/Login.js @@ -8,177 +8,192 @@ import { Field, Form } from 'react-final-form'; import { Link as RouterLink } from 'react-router-dom'; const useStyles = makeStyles( - (theme) => ({ - form: { - padding: '0 1em 1em 1em', - }, - input: { - marginTop: '1em', - }, - button: { - width: '100%', - }, - icon: { - marginRight: theme.spacing(1), - }, - copying: { - position: 'fixed', - bottom: 0, - left: 0, - right: 0, - zIndex: 1000, - textAlign: 'center', - color: 'white' - }, - link: { - color: "#ade6fd" - } - }), - { name: 'RaLoginForm' } + (theme) => ({ + form: { + padding: '0 1em 1em 1em' + }, + input: { + marginTop: '1em' + }, + button: { + width: '100%' + }, + icon: { + marginRight: theme.spacing(1) + }, + copying: { + position: 'fixed', + bottom: 0, + left: 0, + right: 0, + zIndex: 1000, + textAlign: 'center', + color: 'white' + }, + link: { + color: '#ade6fd' + } + }), + { name: 'RaLoginForm' } ); const Input = ({ - meta: { touched, error }, // eslint-disable-line react/prop-types - input: inputProps, // eslint-disable-line react/prop-types - ...props -}) => ( - <TextField - error={!!(touched && error)} - helperText={touched && error} - {...inputProps} - {...props} - fullWidth - /> -); + meta: { touched, error }, // eslint-disable-line react/prop-types + input: inputProps, // eslint-disable-line react/prop-types + ...props +}) => <TextField error={!!(touched && error)} helperText={touched && error} {...inputProps} {...props} fullWidth />; const MyLoginPage = (props) => { - const inputRef = React.useRef(); - const [loading, setLoading] = useSafeSetState(false); - const [twoFA, setTwoFA] = useSafeSetState(false); - const login = useLogin(); - const translate = useTranslate(); - const notify = useNotify(); - const classes = useStyles(props); + const inputRef = React.useRef(); + const [loading, setLoading] = useSafeSetState(false); + const [twoFA, setTwoFA] = useSafeSetState(false); + const login = useLogin(); + const translate = useTranslate(); + const notify = useNotify(); + const classes = useStyles(props); - const submit = values => { - setLoading(true); - if (twoFA) { - axios.post('/two-factor-challenge', { - code: values.code, - recovery_code: values.code - }).then(response => { - setLoading(false); - let data = response.data; - if (data?.two_factor === true) { - setTwoFA(true); - } else { - login(data, "/"); - } - }).catch(error => { - setLoading(false); + const submit = (values) => { + setLoading(true); + if (twoFA) { + axios + .post('/two-factor-challenge', { + code: values.code, + recovery_code: values.code + }) + .then((response) => { + setLoading(false); + let data = response.data; + if (data?.two_factor === true) { + setTwoFA(true); + } else { + login(data, '/'); + } + }) + .catch((error) => { + setLoading(false); - if (error?.response?.data?.message) { - let message = ""; - if (error?.response?.data?.errors !== undefined) { - for (let ms in error.response.data.errors) { - for (let m of error.response.data.errors[ms]) { - message += m + "\n"; - } - } - } else { - message = error?.response?.data?.message; - } - notify(message, "warning"); - } else { - notify(error?.message, "warning"); + if (error?.response?.data?.message) { + let message = ''; + if (error?.response?.data?.errors !== undefined) { + for (let ms in error.response.data.errors) { + for (let m of error.response.data.errors[ms]) { + message += m + '\n'; } - }); - } else { - axios.get('/csrf-cookie').then(response => { - axios.post('/login', values).then(response => { - setLoading(false); - let data = response.data; - if (data?.two_factor === true) { - setTwoFA(true); - inputRef.current.focus(); - } else { - login(data, "/"); - } - }).catch(error => { - setLoading(false); - if (error?.response?.data?.message) { - let message = ""; - if (error?.response?.data?.errors !== undefined) { - for (let ms in error.response.data.errors) { - for (let m of error.response.data.errors[ms]) { - message += m + "\n"; - } - } - } else { - message = error?.response?.data?.message; - } - notify(message, 'warning'); - } else { - notify('Error', 'warning'); - } - }); - }); - } - }; - - return ( - <> - <Login> - <Form onSubmit={submit} render={({ handleSubmit }) => (twoFA ? - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - <div className={classes.input}> - <Field inputRef={inputRef} id="code" name="code" component={Input} label={translate('ra.auth.2fa.verification_code')} disabled={loading} /> - </div> - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.2fa.login')} - </Button> - </CardActions> - </form> - : - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - <div className={classes.input}> - <Field autoFocus id="username" name="username" component={Input} label={translate('ra.auth.username')} disabled={loading} /> - </div> - <div className={classes.input}> - <Field id="password" name="password" component={Input} label={translate('ra.auth.password')} type="password" - disabled={loading} autoComplete="current-password" helperText={ - <Link component={RouterLink} to="/forgot-password" className={classes.link}>{translate('ra.auth.forgot')}</Link> - } /> - </div> + } + } else { + message = error?.response?.data?.message; + } + notify(message, 'warning'); + } else { + notify(error?.message, 'warning'); + } + }); + } else { + axios.get('/csrf-cookie').then(() => { + axios + .post('/login', values) + .then((response) => { + setLoading(false); + let data = response.data; + if (data?.two_factor === true) { + setTwoFA(true); + inputRef.current.focus(); + } else { + login(data, '/'); + } + }) + .catch((error) => { + setLoading(false); + if (error?.response?.data?.message) { + let message = ''; + if (error?.response?.data?.errors !== undefined) { + for (let ms in error.response.data.errors) { + for (let m of error.response.data.errors[ms]) { + message += m + '\n'; + } + } + } else { + message = error?.response?.data?.message; + } + notify(message, 'warning'); + } else { + notify('Error', 'warning'); + } + }); + }); + } + }; - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.sign_in')} - </Button> - </CardActions> - </form> - )} /> - </Login> - <div className={classes.copying}> - <Typography> - Seb {process.env.REACT_APP_VERSION ?? "DEV"}<br/> - {translate('copying.bottommessage')} <Link to="/copying" component={RouterLink} className={classes.link}>GNU AGPLv3</Link><br/> - <a className={classes.link} href="/doc/privacy-policy.pdf">Politique de Confidentialité</a> - </Typography> - </div> - </> - ); + return ( + <> + <Login> + <Form + onSubmit={submit} + render={({ handleSubmit }) => + twoFA ? ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}> + <div className={classes.input}> + <Field inputRef={inputRef} id="code" name="code" component={Input} label={translate('ra.auth.2fa.verification_code')} disabled={loading} /> + </div> + </div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.2fa.login')} + </Button> + </CardActions> + </form> + ) : ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}> + <div className={classes.input}> + <Field autoFocus id="username" name="username" component={Input} label={translate('ra.auth.username')} disabled={loading} /> + </div> + <div className={classes.input}> + <Field + id="password" + name="password" + component={Input} + label={translate('ra.auth.password')} + type="password" + disabled={loading} + autoComplete="current-password" + helperText={ + <Link component={RouterLink} to="/forgot-password" className={classes.link}> + {translate('ra.auth.forgot')} + </Link> + } + /> + </div> + </div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.sign_in')} + </Button> + </CardActions> + </form> + ) + } + /> + </Login> + <div className={classes.copying}> + <Typography> + Seb {process.env.REACT_APP_VERSION ?? 'DEV' /* eslint-disable-line */} + <br /> + {translate('copying.bottommessage')}{' '} + <Link to="/copying" component={RouterLink} className={classes.link}> + GNU AGPLv3 + </Link> + <br /> + <a className={classes.link} href="/doc/privacy-policy.pdf"> + Politique de Confidentialité + </a> + </Typography> + </div> + </> + ); }; -export default MyLoginPage; \ No newline at end of file +export default MyLoginPage; diff --git a/src/layout/Menu.js b/src/layout/Menu.js index ac0c7da5f46b7a7044c6ac4df244beb999a0ec50..2113cb99cdcaffcaf4c186989e5e569f85821d8a 100644 --- a/src/layout/Menu.js +++ b/src/layout/Menu.js @@ -1,4 +1,3 @@ - import { useMediaQuery } from '@material-ui/core'; import Collapse from '@material-ui/core/Collapse'; import ListItemIcon from '@material-ui/core/ListItemIcon'; @@ -24,6 +23,7 @@ import MoneyIcon from '@material-ui/icons/Money'; import PersonAddIcon from '@material-ui/icons/PersonAdd'; import ShoppingCartIcon from '@material-ui/icons/ShoppingCart'; import SwapHorizIcon from '@material-ui/icons/SwapHoriz'; +import PropTypes from 'prop-types'; import * as React from 'react'; import { useState } from 'react'; import { MenuItemLink, usePermissions, useTranslate } from 'react-admin'; @@ -32,114 +32,159 @@ import { useLocation } from 'react-router-dom'; import theme from './Theme'; function permMatch(userperms, elemperm) { - const [resource, access] = elemperm.split("."); + const [resource, access] = elemperm.split('.'); - return userperms.includes("*.*") || userperms.includes(resource + ".*") || userperms.includes("*." + access) || userperms.includes(elemperm); + return userperms.includes('*.*') || userperms.includes(resource + '.*') || userperms.includes('*.' + access) || userperms.includes(elemperm); } function hasPerm(permissions, perm) { - if (permissions === undefined) - return false; + if (permissions === undefined) return false; - if (permissions.includes("*.*")) { - return true; - } + if (permissions.includes('*.*')) { + return true; + } - if (Array.isArray(perm)) { - for (let p of perm) { - if (permMatch(permissions, p)) { - return true; - } - } - } else { - if (permMatch(permissions, perm)) - return true; + if (Array.isArray(perm)) { + for (let p of perm) { + if (permMatch(permissions, p)) { + return true; + } } - return false; + } else { + if (permMatch(permissions, perm)) return true; + } + return false; } const Accordeon = (props) => { - const { permissions: perms } = usePermissions(); - const [open, setOpen] = useState(props.open); + const { permissions: perms } = usePermissions(); + const [open, setOpen] = useState(props.open); - return ( - <>{hasPerm(perms, props.permissions) ? ( - <> - <Tooltip title={props.title} placement="right"> - <MenuItem button onClick={() => setOpen(!open)} className={"RaMenuItemLink-root-36 MuiMenuItem-root"}> - <ListItemIcon className={"RaMenuItemLink-icon-38"}> - {open ? <ExpandLess /> : <ExpandMore />} - </ListItemIcon> - <ListItemText primary={props.title} /> - </MenuItem> - </Tooltip> - <Collapse in={open} style={{ minHeight: "auto" }}> - {props.children} - </Collapse> - </>) : ''} + return ( + <> + {hasPerm(perms, props.permissions) ? ( + <> + <Tooltip title={props.title} placement="right"> + <MenuItem button onClick={() => setOpen(!open)} className={'RaMenuItemLink-root-36 MuiMenuItem-root'}> + <ListItemIcon className={'RaMenuItemLink-icon-38'}>{open ? <ExpandLess /> : <ExpandMore />}</ListItemIcon> + <ListItemText primary={props.title} /> + </MenuItem> + </Tooltip> + <Collapse in={open} style={{ minHeight: 'auto' }}> + {props.children} + </Collapse> </> - ); -} + ) : ( + '' + )} + </> + ); +}; + +Accordeon.propTypes = { + open: PropTypes.bool, + permissions: [PropTypes.string], + title: PropTypes.string, + children: PropTypes.node, + to: PropTypes.string +}; const Item = (props) => { - const { permissions } = usePermissions(); - const open = useSelector(state => state.admin.ui.sidebarOpen); - const location = useLocation(); - const regex = new RegExp(`^${props.to}\\/|^${props.to}$`); + const { permissions } = usePermissions(); + const open = useSelector((state) => state.admin.ui.sidebarOpen); + const location = useLocation(); + const regex = new RegExp(`^${props.to}\\/|^${props.to}$`); - return ( - <>{ - (!('permissions' in props) || hasPerm(permissions, props.permissions)) && - <MenuItemLink theme={theme} {...props} sidebarIsOpen={open} selected={regex.test(location.pathname)} /> - }</> - ); -} + return ( + <> + {(!('permissions' in props) || hasPerm(permissions, props.permissions)) && ( + <MenuItemLink theme={theme} {...props} sidebarIsOpen={open} selected={regex.test(location.pathname)} /> + )} + </> + ); +}; -const Menu = ({ onMenuClick, logout }) => { - const isXSmall = useMediaQuery(theme => theme.breakpoints.down('xs')); - const translate = useTranslate(); +Item.propTypes = { + to: PropTypes.string, + permissions: [PropTypes.string] +}; - return ( - <> - <Item to="/" primaryText={translate('menu.left.dashboard')} leftIcon={<DashboardIcon />} /> - <Item to="/sales/create" permissions="sales.create" primaryText={translate('menu.left.sell')} leftIcon={<LocalOfferIcon />} /> - <Item to="/purchases/create" permissions="purchases.create" primaryText={translate('menu.left.buy')} leftIcon={<ShoppingCartIcon />} /> - <Item to="/accounts_counts/create" permissions="accounts_counts.create" primaryText={translate('menu.left.count_money')} leftIcon={<MoneyIcon />} /> - <Item to="/products_counts/create" permissions="products_counts.create" primaryText={translate('menu.left.count_stocks')} leftIcon={<BarChartIcon />} /> - <Item to="/participations/create" permissions="participations.create" primaryText={translate('menu.left.add_participant')} leftIcon={<EventIcon />} /> - <Accordeon open={false} title={translate('menu.left.humans')} permissions={["people.show", "members.show", "users.show"]}> - <Item to="/people" permissions="people.show" primaryText={translate('menu.left.people')} leftIcon={<EmojiPeopleIcon />} /> - <Item to="/members" permissions="members.show" primaryText={translate('menu.left.members')} leftIcon={<GroupIcon />} /> - <Item to="/users" permissions="users.show" primaryText={translate('menu.left.users')} leftIcon={<AccountBoxIcon />} /> - <Item to="/personal_accounts" permissions="personal_accounts.show" primaryText={translate('menu.left.personal_accounts')} leftIcon={<AccountBalanceIcon />} /> - <Item to="/personal_transactions" permissions="personal_transactions.show" primaryText={translate('menu.left.personal_transactions')} leftIcon={<SwapHorizIcon />} /> - </Accordeon> - <Accordeon open={false} title={translate('menu.left.stocks')} permissions={["products.show", "products_categories.show", "movements.show", "products_counts.show"]}> - <Item to="/products" permissions="products.show" primaryText={translate('menu.left.products')} leftIcon={<LocalCafeIcon />} /> - <Item to="/products_categories" permissions="products_categories.show" primaryText={translate('menu.left.categories')} leftIcon={<CategoryIcon />} /> - <Item to="/products_counts" permissions="products_counts.show" primaryText={translate('menu.left.products_counts')} leftIcon={<BarChartIcon />} /> - <Item to="/movements" permissions="movements.show" primaryText={translate('menu.left.movements')} leftIcon={<SwapHorizIcon />} /> - </Accordeon> - <Accordeon open={false} title={translate('menu.left.accounting')} permissions={["accounts.show", "accounts_counts.show", "transactions.show", "transactions_categories.show", "sales.show", "purchases.show"]}> - <Item to="/accounts" permissions="accounts.show" primaryText={translate('menu.left.accounts')} leftIcon={<AccountBalanceIcon />} /> - <Item to="/accounts_counts" permissions="accounts_counts.show" primaryText={translate('menu.left.accounts_counts')} leftIcon={<MoneyIcon />} /> - <Item to="/transactions" permissions="transactions.show" primaryText={translate('menu.left.transactions')} leftIcon={<SwapHorizIcon />} /> - <Item to="/automated_transactions" permissions="automated_transactions.show" primaryText={translate('menu.left.automated_transactions')} leftIcon={<AutorenewIcon />} /> - <Item to="/transactions_categories" permissions="transactions_categories.show" primaryText={translate('menu.left.categories')} leftIcon={<CategoryIcon />} /> - <Item to="/sales" permissions="sales.show" primaryText={translate('menu.left.sales')} leftIcon={<LocalOfferIcon />} /> - <Item to="/purchases" permissions="purchases.show" primaryText={translate('menu.left.purchases')} leftIcon={<ShoppingCartIcon />} /> - <Item to="/transferts" permissions="transferts.show" primaryText={translate('menu.left.transferts')} leftIcon={<ImportExportIcon />} /> - </Accordeon> - <Accordeon open={false} title={translate('menu.left.events')} permissions={["events.show", "participations.show"]}> - <Item to="/events" permissions="events.show" primaryText={translate('menu.left.events')} leftIcon={<EventIcon />} /> - <Item to="/participations" permissions="participations.show" primaryText={translate('menu.left.participations')} leftIcon={<PersonAddIcon />} /> - </Accordeon> - <Accordeon open={false} title={translate('menu.left.archives')} permissions={["archived_members.show"]}> - <Item to="/archived_members" permissions="archived_members.show" primaryText={translate('menu.left.archived_members')} leftIcon={<GroupIcon />} /> - </Accordeon> - {isXSmall && <Item to="/profile" primaryText={translate('menu.left.profile')} leftIcon={<AccountCircleIcon />} />} - {isXSmall && logout} - </> - ); +const Menu = ({ logout }) => { + const isXSmall = useMediaQuery((theme) => theme.breakpoints.down('xs')); + const translate = useTranslate(); + + return ( + <> + <Item to="/" primaryText={translate('menu.left.dashboard')} leftIcon={<DashboardIcon />} /> + <Item to="/sales/create" permissions="sales.create" primaryText={translate('menu.left.sell')} leftIcon={<LocalOfferIcon />} /> + <Item to="/purchases/create" permissions="purchases.create" primaryText={translate('menu.left.buy')} leftIcon={<ShoppingCartIcon />} /> + <Item to="/accounts_counts/create" permissions="accounts_counts.create" primaryText={translate('menu.left.count_money')} leftIcon={<MoneyIcon />} /> + <Item to="/products_counts/create" permissions="products_counts.create" primaryText={translate('menu.left.count_stocks')} leftIcon={<BarChartIcon />} /> + <Item to="/participations/create" permissions="participations.create" primaryText={translate('menu.left.add_participant')} leftIcon={<EventIcon />} /> + <Accordeon open={false} title={translate('menu.left.humans')} permissions={['people.show', 'members.show', 'users.show']}> + <Item to="/people" permissions="people.show" primaryText={translate('menu.left.people')} leftIcon={<EmojiPeopleIcon />} /> + <Item to="/members" permissions="members.show" primaryText={translate('menu.left.members')} leftIcon={<GroupIcon />} /> + <Item to="/users" permissions="users.show" primaryText={translate('menu.left.users')} leftIcon={<AccountBoxIcon />} /> + <Item + to="/personal_accounts" + permissions="personal_accounts.show" + primaryText={translate('menu.left.personal_accounts')} + leftIcon={<AccountBalanceIcon />} + /> + <Item + to="/personal_transactions" + permissions="personal_transactions.show" + primaryText={translate('menu.left.personal_transactions')} + leftIcon={<SwapHorizIcon />} + /> + </Accordeon> + <Accordeon + open={false} + title={translate('menu.left.stocks')} + permissions={['products.show', 'products_categories.show', 'movements.show', 'products_counts.show']}> + <Item to="/products" permissions="products.show" primaryText={translate('menu.left.products')} leftIcon={<LocalCafeIcon />} /> + <Item to="/products_categories" permissions="products_categories.show" primaryText={translate('menu.left.categories')} leftIcon={<CategoryIcon />} /> + <Item to="/products_counts" permissions="products_counts.show" primaryText={translate('menu.left.products_counts')} leftIcon={<BarChartIcon />} /> + <Item to="/movements" permissions="movements.show" primaryText={translate('menu.left.movements')} leftIcon={<SwapHorizIcon />} /> + </Accordeon> + <Accordeon + open={false} + title={translate('menu.left.accounting')} + permissions={['accounts.show', 'accounts_counts.show', 'transactions.show', 'transactions_categories.show', 'sales.show', 'purchases.show']}> + <Item to="/accounts" permissions="accounts.show" primaryText={translate('menu.left.accounts')} leftIcon={<AccountBalanceIcon />} /> + <Item to="/accounts_counts" permissions="accounts_counts.show" primaryText={translate('menu.left.accounts_counts')} leftIcon={<MoneyIcon />} /> + <Item to="/transactions" permissions="transactions.show" primaryText={translate('menu.left.transactions')} leftIcon={<SwapHorizIcon />} /> + <Item + to="/automated_transactions" + permissions="automated_transactions.show" + primaryText={translate('menu.left.automated_transactions')} + leftIcon={<AutorenewIcon />} + /> + <Item + to="/transactions_categories" + permissions="transactions_categories.show" + primaryText={translate('menu.left.categories')} + leftIcon={<CategoryIcon />} + /> + <Item to="/sales" permissions="sales.show" primaryText={translate('menu.left.sales')} leftIcon={<LocalOfferIcon />} /> + <Item to="/purchases" permissions="purchases.show" primaryText={translate('menu.left.purchases')} leftIcon={<ShoppingCartIcon />} /> + <Item to="/transferts" permissions="transferts.show" primaryText={translate('menu.left.transferts')} leftIcon={<ImportExportIcon />} /> + </Accordeon> + <Accordeon open={false} title={translate('menu.left.events')} permissions={['events.show', 'participations.show']}> + <Item to="/events" permissions="events.show" primaryText={translate('menu.left.events')} leftIcon={<EventIcon />} /> + <Item to="/participations" permissions="participations.show" primaryText={translate('menu.left.participations')} leftIcon={<PersonAddIcon />} /> + </Accordeon> + <Accordeon open={false} title={translate('menu.left.archives')} permissions={['archived_members.show']}> + <Item to="/archived_members" permissions="archived_members.show" primaryText={translate('menu.left.archived_members')} leftIcon={<GroupIcon />} /> + </Accordeon> + {isXSmall && <Item to="/profile" primaryText={translate('menu.left.profile')} leftIcon={<AccountCircleIcon />} />} + {isXSmall && logout} + </> + ); +}; + +Menu.propTypes = { + logout: PropTypes.any }; + export { Menu }; diff --git a/src/layout/Routes.js b/src/layout/Routes.js index ea51511382151de1d65d6466f9c2b50ca4a61a8e..5b5e6c9efe38c03f2f7a27f89cb85a43b18fc561 100644 --- a/src/layout/Routes.js +++ b/src/layout/Routes.js @@ -1,28 +1,28 @@ -import * as React from "react"; +import * as React from 'react'; import { RouteWithoutLayout } from 'react-admin'; import { Route } from 'react-router-dom'; import personal_refills from '../resources/PersonalRefills'; -import Profile from "../resources/Profile"; +import Profile from '../resources/Profile'; import ActivateAccount from './forms/ActivateAccount'; import ChangePassword from './forms/ChangePassword'; -import Copying from "./forms/Copying"; +import Copying from './forms/Copying'; import ForgotPassword from './forms/ForgotPassword'; import ResetPassword from './forms/ResetPassword'; -import TokenClear from "./forms/TokenClear"; +import TokenClear from './forms/TokenClear'; import TokenNew from './forms/TokenNew'; -import TwoFactorDisable from "./forms/TwoFactorDisable"; +import TwoFactorDisable from './forms/TwoFactorDisable'; import TwoFactorEnable from './forms/TwoFactorEnable'; export const customRoutes = [ - <Route exact path="/profile" component={Profile} />, - <RouteWithoutLayout exact path="/change-password" component={ChangePassword} />, - <RouteWithoutLayout exact path="/forgot-password" component={ForgotPassword} />, - <RouteWithoutLayout exact path="/reset-password/:token" component={ResetPassword} />, - <RouteWithoutLayout exact path="/enable-account/:token" component={ActivateAccount} />, - <RouteWithoutLayout exact path="/two-factor/enable" component={TwoFactorEnable} />, - <RouteWithoutLayout exact path="/two-factor/disable" component={TwoFactorDisable} />, - <RouteWithoutLayout exact path="/tokens/create" component={TokenNew} />, - <RouteWithoutLayout exact path="/tokens/clear" component={TokenClear} />, - <RouteWithoutLayout exact path="/copying" component={Copying} />, - <Route exact path="/personal_accounts/refill" component={personal_refills.create} /> -]; \ No newline at end of file + <Route key={0} exact path="/profile" component={Profile} />, + <RouteWithoutLayout key={1} exact path="/change-password" component={ChangePassword} />, + <RouteWithoutLayout key={2} exact path="/forgot-password" component={ForgotPassword} />, + <RouteWithoutLayout key={3} exact path="/reset-password/:token" component={ResetPassword} />, + <RouteWithoutLayout key={4} exact path="/enable-account/:token" component={ActivateAccount} />, + <RouteWithoutLayout key={5} exact path="/two-factor/enable" component={TwoFactorEnable} />, + <RouteWithoutLayout key={6} exact path="/two-factor/disable" component={TwoFactorDisable} />, + <RouteWithoutLayout key={7} exact path="/tokens/create" component={TokenNew} />, + <RouteWithoutLayout key={8} exact path="/tokens/clear" component={TokenClear} />, + <RouteWithoutLayout key={9} exact path="/copying" component={Copying} />, + <Route key={10} exact path="/personal_accounts/refill" component={personal_refills.create} /> +]; diff --git a/src/layout/Theme.js b/src/layout/Theme.js index ca9ed7ac37dfc36e9d392e4072ef80d1823eda40..948e1fd55c814953c6231c4625d3809e29efea92 100644 --- a/src/layout/Theme.js +++ b/src/layout/Theme.js @@ -2,116 +2,116 @@ import merge from 'lodash/merge'; import { defaultTheme } from 'react-admin'; export default merge(defaultTheme, { - palette: { - primary: { - main: '#ade6fd', - light: '#', - dark: '#303030' - }, - secondary: { - main: "#fb963a", - dark: "#f77c36", - light: "#ffc646" - }, - error: { - main: "#ffadad" - }, - type: 'dark', + palette: { + primary: { + main: '#ade6fd', + light: '#', + dark: '#303030' }, - shadows: [ - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none", - "none" - ], - overrides: { - RaLayout: { - content: { - "padding-right": "5px", - "padding-top": "5px" - } - }, - RaFormInput: { - input: { - width: "100%" - } - }, - RaAutocompleteArrayInput: { - inputRoot: { - width: "100%" - } - }, - MuiFormControl: { - root: { - width: "100%" - } - }, - MuiTableHead: { - root: { - "border-radius": "4px 4px 0 0" - } - }, - RaToolbar: { - toolbar: { - "background-color": "#545454" - } - }, - RaCreate: { - main: { - "margin-top": "0px !important" - } - }, - MuiList: { - root: { - "background": "#545454" - } - }, - MuiDialogTitle: { - root: { - "padding-top": "0px !important", - "padding-bottom": "0px !important" - } - }, - RaEdit: { - noActions: { - "margin-top": 0 - } - }, - MuiDialogContent: { - root: { - padding: "8px, 0 !important" - } - }, - MuiListItemIcon: { - root: { - "min-width": null - } - }, - RaLabeled: { - value: { - padding: 0 - } - } + secondary: { + main: '#fb963a', + dark: '#f77c36', + light: '#ffc646' + }, + error: { + main: '#ffadad' + }, + type: 'dark' + }, + shadows: [ + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none', + 'none' + ], + overrides: { + RaLayout: { + content: { + 'padding-right': '5px', + 'padding-top': '5px' + } + }, + RaFormInput: { + input: { + width: '100%' + } + }, + RaAutocompleteArrayInput: { + inputRoot: { + width: '100%' + } + }, + MuiFormControl: { + root: { + width: '100%' + } + }, + MuiTableHead: { + root: { + 'border-radius': '4px 4px 0 0' + } + }, + RaToolbar: { + toolbar: { + 'background-color': '#545454' + } + }, + RaCreate: { + main: { + 'margin-top': '0px !important' + } + }, + MuiList: { + root: { + background: '#545454' + } + }, + MuiDialogTitle: { + root: { + 'padding-top': '0px !important', + 'padding-bottom': '0px !important' + } + }, + RaEdit: { + noActions: { + 'margin-top': 0 + } + }, + MuiDialogContent: { + root: { + padding: '8px, 0 !important' + } + }, + MuiListItemIcon: { + root: { + 'min-width': null + } + }, + RaLabeled: { + value: { + padding: 0 + } } -}); \ No newline at end of file + } +}); diff --git a/src/layout/UserMenu.js b/src/layout/UserMenu.js index 36ab9a17a7870e061872a0a93094dda3ff4c2f4b..560b00af895e82c9fa0ca4d2df13e1d52c7f24e8 100644 --- a/src/layout/UserMenu.js +++ b/src/layout/UserMenu.js @@ -1,136 +1,133 @@ -import { - Button, Menu -} from '@material-ui/core'; +import { Button, Menu } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import AccountCircle from '@material-ui/icons/AccountCircle'; import CopyrightIcon from '@material-ui/icons/Copyright'; import SettingsIcon from '@material-ui/icons/Settings'; +import PropTypes from 'prop-types'; import React, { Children, cloneElement, isValidElement, useEffect, useState } from 'react'; import { MenuItemLink, useGetIdentity, useTranslate } from 'react-admin'; import { connect } from 'react-redux'; const useStyles = makeStyles( - theme => ({ - user: {}, - userButton: { - textTransform: 'none', - }, - avatar: { - width: theme.spacing(4), - height: theme.spacing(4), - }, - }), - { name: 'RaUserMenu' } + (theme) => ({ + user: {}, + userButton: { + textTransform: 'none' + }, + avatar: { + width: theme.spacing(4), + height: theme.spacing(4) + } + }), + { name: 'RaUserMenu' } ); const AnchorOrigin = { - vertical: 'bottom', - horizontal: 'right', + vertical: 'bottom', + horizontal: 'right' }; const TransformOrigin = { - vertical: 'top', - horizontal: 'right', + vertical: 'top', + horizontal: 'right' }; const CustomUserMenu = (props) => { - const [anchorEl, setAnchorEl] = useState(null); - const translate = useTranslate(); - const { loaded, identity } = useGetIdentity(); - const [fullname, setFullname] = useState(null); - const classes = useStyles(props); - - const { - children, - label = 'ra.auth.user_menu', - logout, - } = props; - - const open = Boolean(anchorEl); - - const handleMenu = event => setAnchorEl(event.currentTarget); - const handleClose = () => setAnchorEl(null); - - useEffect(() => { - const handler = (e) => { - setFullname(e.detail.email); - }; - - document.addEventListener('profileUpdated', handler); - - return () => { - document.removeEventListener('profileUpdated', handler); - } - }); - - if (!logout && !children) return null; - - return ( - <div className={classes.user}> - {loaded ? ( - <Button - aria-label={label && translate(label, { _: label })} - className={classes.userButton} - color="inherit" - startIcon={<AccountCircle />} - onClick={handleMenu}> - {fullname === null ? identity?.fullName : fullname} - </Button> - ) : ""} - <Menu - id="menu-appbar" - disableScrollLock - anchorEl={anchorEl} - anchorOrigin={AnchorOrigin} - transformOrigin={TransformOrigin} - getContentAnchorEl={null} - open={open} - onClose={handleClose}> - {Children.map(children, menuItem => - isValidElement(menuItem) - ? cloneElement(menuItem, { - onClick: handleClose, - }) - : null - )} - {logout} - </Menu> - </div> - ); + const [anchorEl, setAnchorEl] = useState(null); + const translate = useTranslate(); + const { loaded, identity } = useGetIdentity(); + const [fullname, setFullname] = useState(null); + const classes = useStyles(props); + + const { children, label = 'ra.auth.user_menu', logout } = props; + + const open = Boolean(anchorEl); + + const handleMenu = (event) => setAnchorEl(event.currentTarget); + const handleClose = () => setAnchorEl(null); + + useEffect(() => { + const handler = (e) => { + setFullname(e.detail.email); + }; + + document.addEventListener('profileUpdated', handler); + + return () => { + document.removeEventListener('profileUpdated', handler); + }; + }); + + if (!logout && !children) return null; + + return ( + <div className={classes.user}> + {loaded ? ( + <Button + aria-label={label && translate(label, { _: label })} + className={classes.userButton} + color="inherit" + startIcon={<AccountCircle />} + onClick={handleMenu}> + {fullname === null ? identity?.fullName : fullname} + </Button> + ) : ( + '' + )} + <Menu + id="menu-appbar" + disableScrollLock + anchorEl={anchorEl} + anchorOrigin={AnchorOrigin} + transformOrigin={TransformOrigin} + getContentAnchorEl={null} + open={open} + onClose={handleClose}> + {Children.map(children, (menuItem) => + isValidElement(menuItem) + ? cloneElement(menuItem, { + onClick: handleClose + }) + : null + )} + {logout} + </Menu> + </div> + ); +}; + +CustomUserMenu.propTypes = { + children: PropTypes.node, + label: PropTypes.string, + logout: PropTypes.any }; const MyUserMenuView = (props) => { - const { profile, ...alt } = props; - const translate = useTranslate(); - - return ( - <CustomUserMenu label={profile ? profile.fullName : ''} {...alt}> - <MenuItemLink - to="/profile" - primaryText={translate("menu.user.profile")} - leftIcon={<SettingsIcon />} - /> - <MenuItemLink - to="/copying" - primaryText={translate("menu.user.copying")} - leftIcon={<CopyrightIcon />} - /> - </CustomUserMenu> - ); + const { profile, ...alt } = props; + const translate = useTranslate(); + + return ( + <CustomUserMenu label={profile ? profile.fullName : ''} {...alt}> + <MenuItemLink to="/profile" primaryText={translate('menu.user.profile')} leftIcon={<SettingsIcon />} /> + <MenuItemLink to="/copying" primaryText={translate('menu.user.copying')} leftIcon={<CopyrightIcon />} /> + </CustomUserMenu> + ); }; -const mapStateToProps = state => { - const resource = 'profile'; - const id = 'me'; - const profileState = state.admin.resources[resource]; +MyUserMenuView.propTypes = { + profile: PropTypes.any +}; - return { - profile: profileState ? profileState.data[id] : null - }; +const mapStateToProps = (state) => { + const resource = 'profile'; + const id = 'me'; + const profileState = state.admin.resources[resource]; + + return { + profile: profileState ? profileState.data[id] : null + }; }; -const MyUserMenu = connect( - mapStateToProps -)(MyUserMenuView); +const MyUserMenu = connect(mapStateToProps)(MyUserMenuView); -export default MyUserMenu; \ No newline at end of file +export default MyUserMenu; diff --git a/src/layout/forms/ActivateAccount.js b/src/layout/forms/ActivateAccount.js index 08a185beeeeeb0861fc8d554cc7f6a36ff0df2da..c69f33d32e902b72615540bab42564354de88822 100644 --- a/src/layout/forms/ActivateAccount.js +++ b/src/layout/forms/ActivateAccount.js @@ -1,6 +1,7 @@ import { Button, CardActions, CircularProgress, TextField } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import axios from 'axios'; +import PropTypes from 'prop-types'; import { useNotify, useSafeSetState, useTranslate } from 'ra-core'; import React from 'react'; import { Login, useRedirect } from 'react-admin'; @@ -8,97 +9,113 @@ import { Field, Form } from 'react-final-form'; import { useLocation, useParams } from 'react-router-dom'; const useStyles = makeStyles( - (theme) => ({ - form: { - padding: '0 1em 1em 1em', - }, - input: { - marginTop: '1em', - }, - button: { - width: '100%', - }, - icon: { - marginRight: theme.spacing(1), - }, - }), - { name: 'RaLoginForm' } + (theme) => ({ + form: { + padding: '0 1em 1em 1em' + }, + input: { + marginTop: '1em' + }, + button: { + width: '100%' + }, + icon: { + marginRight: theme.spacing(1) + } + }), + { name: 'RaLoginForm' } ); function useQuery() { - return new URLSearchParams(useLocation().search); + return new URLSearchParams(useLocation().search); } const Input = ({ meta: { touched, error }, input: inputProps, ...props }) => ( - <TextField error={!!(touched && error)} helperText={touched && error} {...inputProps} {...props} fullWidth /> + <TextField error={!!(touched && error)} helperText={touched && error} {...inputProps} {...props} fullWidth /> ); +Input.propTypes = { + meta: PropTypes.any, + input: PropTypes.any +}; + const ActivateAccount = (props) => { - const [loading, setLoading] = useSafeSetState(false); - const translate = useTranslate(); - const notify = useNotify(); - const redirect = useRedirect(); - const classes = useStyles(props); - let query = useQuery(); - let { token } = useParams(); - let email = query.get("email"); + const [loading, setLoading] = useSafeSetState(false); + const translate = useTranslate(); + const notify = useNotify(); + const redirect = useRedirect(); + const classes = useStyles(props); + let query = useQuery(); + let { token } = useParams(); + let email = query.get('email'); - const submit = ({ password, password_confirmation }) => { - setLoading(true); - return axios.post('/reset-password', { - 'token': token, - 'email': email, - 'password': password, - 'password_confirmation': password_confirmation - }).then(response => { - setLoading(false); - notify(response.data.message) - redirect("/login"); - }).catch(error => { - setLoading(false); + const submit = ({ password, password_confirmation }) => { + setLoading(true); + return axios + .post('/reset-password', { + token: token, + email: email, + password: password, + password_confirmation: password_confirmation + }) + .then((response) => { + setLoading(false); + notify(response.data.message); + redirect('/login'); + }) + .catch((error) => { + setLoading(false); - if (error?.response?.data?.message) { - let message = ""; - if (error?.response?.data?.errors !== undefined) { - for (let ms in error.response.data.errors) { - for (let m of error.response.data.errors[ms]) { - message += m + "\n"; - } - } - } else { - message = error?.response?.data?.message; - } - notify(message, "warning"); - } else { - notify(error?.message, "warning"); + if (error?.response?.data?.message) { + let message = ''; + if (error?.response?.data?.errors !== undefined) { + for (let ms in error.response.data.errors) { + for (let m of error.response.data.errors[ms]) { + message += m + '\n'; + } } - }); - }; + } else { + message = error?.response?.data?.message; + } + notify(message, 'warning'); + } else { + notify(error?.message, 'warning'); + } + }); + }; - return ( - <Login> - <Form onSubmit={submit} render={({ handleSubmit }) => ( - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - <div className={classes.input}> - <Field id="password" name="password" component={Input} label={translate('ra.auth.password')} type="password" disabled={loading} /> - </div> - <div className={classes.input}> - <Field id="password_confirmation" name="password_confirmation" component={Input} label={translate('ra.auth.password_confirmation')} type="password" disabled={loading} /> - </div> - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.set_password')} - </Button> - </CardActions> - </form> - )} /> - </Login> - ); + return ( + <Login> + <Form + onSubmit={submit} + render={({ handleSubmit }) => ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}> + <div className={classes.input}> + <Field id="password" name="password" component={Input} label={translate('ra.auth.password')} type="password" disabled={loading} /> + </div> + <div className={classes.input}> + <Field + id="password_confirmation" + name="password_confirmation" + component={Input} + label={translate('ra.auth.password_confirmation')} + type="password" + disabled={loading} + /> + </div> + </div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.set_password')} + </Button> + </CardActions> + </form> + )} + /> + </Login> + ); }; -export default ActivateAccount; \ No newline at end of file +export default ActivateAccount; diff --git a/src/layout/forms/ChangePassword.js b/src/layout/forms/ChangePassword.js index 8ffbd99eb2c1965e28293dfb4201c8e05b0d88de..1dee442d8dbf835c465548db60da2ac54162a232 100644 --- a/src/layout/forms/ChangePassword.js +++ b/src/layout/forms/ChangePassword.js @@ -9,109 +9,121 @@ import { Link as RouterLink } from 'react-router-dom'; import BaseForm from './Form'; const useStyles = makeStyles( - (theme) => ({ - form: { - padding: '0 1em 1em 1em', - }, - input: { - marginTop: '1em', - }, - button: { - width: '100%', - }, - icon: { - marginRight: theme.spacing(1), - }, - }), - { name: 'RaLoginForm' } + (theme) => ({ + form: { + padding: '0 1em 1em 1em' + }, + input: { + marginTop: '1em' + }, + button: { + width: '100%' + }, + icon: { + marginRight: theme.spacing(1) + } + }), + { name: 'RaLoginForm' } ); const Input = ({ - meta: { touched, error }, // eslint-disable-line react/prop-types - input: inputProps, // eslint-disable-line react/prop-types - ...props -}) => ( - <TextField - error={!!(touched && error)} - helperText={touched && error} - {...inputProps} - {...props} - fullWidth - /> -); + meta: { touched, error }, // eslint-disable-line react/prop-types + input: inputProps, // eslint-disable-line react/prop-types + ...props +}) => <TextField error={!!(touched && error)} helperText={touched && error} {...inputProps} {...props} fullWidth />; const ChangePassword = (props) => { - useAuthenticated(); - const [loading, setLoading] = useSafeSetState(false); - const translate = useTranslate(); - const notify = useNotify(); - const redirect = useRedirect(); - const classes = useStyles(props); + useAuthenticated(); + const [loading, setLoading] = useSafeSetState(false); + const translate = useTranslate(); + const notify = useNotify(); + const redirect = useRedirect(); + const classes = useStyles(props); - const submit = ({ current_password, password, password_confirmation }) => { - setLoading(true); - axios.get('/csrf-cookie').then(response => { - return axios.put('/user/password', { - 'current_password': current_password, - 'password': password, - 'password_confirmation': password_confirmation - }).then(response => { - setLoading(false); - notify('ra.auth.password_changed'); - redirect("/profile"); - }).catch(error => { - setLoading(false); + const submit = ({ current_password, password, password_confirmation }) => { + setLoading(true); + axios.get('/csrf-cookie').then(() => { + return axios + .put('/user/password', { + current_password: current_password, + password: password, + password_confirmation: password_confirmation + }) + .then(() => { + setLoading(false); + notify('ra.auth.password_changed'); + redirect('/profile'); + }) + .catch((error) => { + setLoading(false); - if (error?.response?.data?.message) { - let message = ""; - if (error?.response?.data?.errors !== undefined) { - for (let ms in error.response.data.errors) { - for (let m of error.response.data.errors[ms]) { - message += m + "\n"; - } - } - } else { - message = error?.response?.data?.message; - } - notify(message, "warning"); - } else { - notify(error?.message, "warning"); + if (error?.response?.data?.message) { + let message = ''; + if (error?.response?.data?.errors !== undefined) { + for (let ms in error.response.data.errors) { + for (let m of error.response.data.errors[ms]) { + message += m + '\n'; } - }); + } + } else { + message = error?.response?.data?.message; + } + notify(message, 'warning'); + } else { + notify(error?.message, 'warning'); + } }); - }; - - return ( - <BaseForm> - <Form onSubmit={submit} render={({ handleSubmit }) => ( - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - <div className={classes.input}> - <Field id="current_password" name="current_password" component={Input} label={translate('ra.auth.current_password')} - type="password" disabled={loading} autoComplete="current-password" /> - </div> - <div className={classes.input}> - <Field id="password" name="password" component={Input} label={translate('ra.auth.new_password')} type="password" - disabled={loading} /> - </div> - <div className={classes.input}> - <Field id="password_confirmation" name="password_confirmation" component={Input} label={translate('ra.auth.password_confirmation')} type="password" - disabled={loading} helperText={<Link component={RouterLink} to="/profile">{translate('ra.auth.back')}</Link>} /> - </div> + }); + }; - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.change_password')} - </Button> - </CardActions> - </form> - )} /> - </BaseForm> - ); + return ( + <BaseForm> + <Form + onSubmit={submit} + render={({ handleSubmit }) => ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}> + <div className={classes.input}> + <Field + id="current_password" + name="current_password" + component={Input} + label={translate('ra.auth.current_password')} + type="password" + disabled={loading} + autoComplete="current-password" + /> + </div> + <div className={classes.input}> + <Field id="password" name="password" component={Input} label={translate('ra.auth.new_password')} type="password" disabled={loading} /> + </div> + <div className={classes.input}> + <Field + id="password_confirmation" + name="password_confirmation" + component={Input} + label={translate('ra.auth.password_confirmation')} + type="password" + disabled={loading} + helperText={ + <Link component={RouterLink} to="/profile"> + {translate('ra.auth.back')} + </Link> + } + /> + </div> + </div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.change_password')} + </Button> + </CardActions> + </form> + )} + /> + </BaseForm> + ); }; -export default ChangePassword; \ No newline at end of file +export default ChangePassword; diff --git a/src/layout/forms/Copying.js b/src/layout/forms/Copying.js index bc01886e7a1b741c54b27edde6e2775268cb5b10..9503c7a3032fbcf187da90d9c18253342d9b0882 100644 --- a/src/layout/forms/Copying.js +++ b/src/layout/forms/Copying.js @@ -7,65 +7,106 @@ import { withRouter } from 'react-router-dom'; import theme from '../Theme'; const useStyles = makeStyles( - (theme) => ({ - form: { - padding: '1em', - textAlign: 'justify' - }, - input: { - marginTop: '1em', - }, - icon: { - marginRight: theme.spacing(1), - }, - }), - { name: 'RaLoginForm' } + (theme) => ({ + form: { + padding: '1em', + textAlign: 'justify' + }, + input: { + marginTop: '1em' + }, + icon: { + marginRight: theme.spacing(1) + } + }), + { name: 'RaLoginForm' } ); const Copying = withRouter(({ history: { goBack }, ...props }) => { - const classes = useStyles(props); - const muiTheme = useMemo(() => createMuiTheme(theme), []); - const translate = useTranslate(); + const classes = useStyles(props); + const muiTheme = useMemo(() => createMuiTheme(theme), []); + const translate = useTranslate(); - return ( - <div style={{ backgroundImage: 'radial-gradient(circle at 50% 14em, #313264 0%, #00023b 60%, #00023b 100%)', backgroundAttachment: 'fixed', minHeight: '100vh', paddingTop: '1em', paddingBottom: '1em' }}> - <ThemeProvider theme={muiTheme}> - <Container maxWidth="md" style={{ backgroundColor: "#424242", color: "#ffffff" }}> - <div className={classes.form}> - <Typography variant="h4" component="h1" gutterBottom>{translate('copying.title')} - Seb {process.env.REACT_APP_VERSION ?? "DEV"}</Typography> - <Typography variant="h5" component="h2" gutterBottom>{translate('copying.notice')}</Typography> - <Typography gutterBottom> - {translate('copying.agpl.line1')}<br /> - {translate('copying.agpl.line2')}<br /> - {translate('copying.agpl.line3.start')} <a href="https://git.unistra.fr/amicale-core/seb">{translate('copying.agpl.line3.here')}</a> {translate('copying.agpl.line3.continue')} <a href="https://git.unistra.fr/amicale-core/seb/-/blob/master/LICENSE.md">{translate('copying.agpl.line3.there')}</a>{translate('copying.agpl.line3.end')} - </Typography> - <Typography variant="h5" component="h2" gutterBottom>{translate('copying.dependencies')}</Typography> - <Typography gutterBottom> - {translate('copying.dependencies2')} - </Typography> - <ul> - <li><a href="https://github.com/laravel/laravel">Laravel</a> - {translate('copying.released')} <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a></li> - <li><a href="https://github.com/marmelab/react-admin">React-admin</a> - {translate('copying.released')} <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a></li> - <li><a href="https://github.com/facebook/react">React</a> - {translate('copying.released')} <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a></li> - <li><a href="https://github.com/axios/axios">Axios</a> - {translate('copying.released')} <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a></li> - <li><a href="https://github.com/jashkenas/underscore">Underscore</a> - {translate('copying.released')} <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a></li> - <li><a href="https://github.com/kybarg/react-qr-scanner">react-qr-scanner</a> - {translate('copying.released')} <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a></li> - </ul> - <Typography variant="h5" component="h2" gutterBottom>{translate('copying.contributors')}</Typography> - <ul> - <li><a href="https://github.com/M4xi1m3/">Maxime "M4x1m3" Friess</a> <a href="mailto:maxime.friess@pm.me"><maxime.friess@pm.me></a> </li> - <li><a href="https://github.com/StelFux/">Charles Lesecq</a> <a href="mailto:charles.lesecq@etu.unistra.fr"><charles.lesecq@etu.unistra.fr></a> </li> - </ul> - </div> - <TopToolbar style={{ paddingTop: 0 }}> - <Button className={classes.button} color="primary" onClick={goBack} > - {translate('ra.auth.back')} - </Button> - </TopToolbar> - </Container> - </ThemeProvider> - </div> - ); + return ( + <div + style={{ + backgroundImage: 'radial-gradient(circle at 50% 14em, #313264 0%, #00023b 60%, #00023b 100%)', + backgroundAttachment: 'fixed', + minHeight: '100vh', + paddingTop: '1em', + paddingBottom: '1em' + }}> + <ThemeProvider theme={muiTheme}> + <Container maxWidth="md" style={{ backgroundColor: '#424242', color: '#ffffff' }}> + <div className={classes.form}> + <Typography variant="h4" component="h1" gutterBottom> + {translate('copying.title')} - Seb {process.env.REACT_APP_VERSION ?? 'DEV' /* eslint-disable-line */} + </Typography> + <Typography variant="h5" component="h2" gutterBottom> + {translate('copying.notice')} + </Typography> + <Typography gutterBottom> + {translate('copying.agpl.line1')} + <br /> + {translate('copying.agpl.line2')} + <br /> + {translate('copying.agpl.line3.start')} <a href="https://git.unistra.fr/amicale-core/seb">{translate('copying.agpl.line3.here')}</a>{' '} + {translate('copying.agpl.line3.continue')}{' '} + <a href="https://git.unistra.fr/amicale-core/seb/-/blob/master/LICENSE.md">{translate('copying.agpl.line3.there')}</a> + {translate('copying.agpl.line3.end')} + </Typography> + <Typography variant="h5" component="h2" gutterBottom> + {translate('copying.dependencies')} + </Typography> + <Typography gutterBottom>{translate('copying.dependencies2')}</Typography> + <ul> + <li> + <a href="https://github.com/laravel/laravel">Laravel</a> - {translate('copying.released')}{' '} + <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a> + </li> + <li> + <a href="https://github.com/marmelab/react-admin">React-admin</a> - {translate('copying.released')}{' '} + <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a> + </li> + <li> + <a href="https://github.com/facebook/react">React</a> - {translate('copying.released')}{' '} + <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a> + </li> + <li> + <a href="https://github.com/axios/axios">Axios</a> - {translate('copying.released')}{' '} + <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a> + </li> + <li> + <a href="https://github.com/jashkenas/underscore">Underscore</a> - {translate('copying.released')}{' '} + <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a> + </li> + <li> + <a href="https://github.com/kybarg/react-qr-scanner">react-qr-scanner</a> - {translate('copying.released')}{' '} + <a href="https://opensource.org/licenses/MIT">{translate('copying.license.mit')}</a> + </li> + </ul> + <Typography variant="h5" component="h2" gutterBottom> + {translate('copying.contributors')} + </Typography> + <ul> + <li> + <a href="https://github.com/M4xi1m3/">Maxime "M4x1m3" Friess</a> <a href="mailto:maxime.friess@pm.me"><maxime.friess@pm.me></a>{' '} + </li> + <li> + <a href="https://github.com/StelFux/">Charles Lesecq</a>{' '} + <a href="mailto:charles.lesecq@etu.unistra.fr"><charles.lesecq@etu.unistra.fr></a>{' '} + </li> + </ul> + </div> + <TopToolbar style={{ paddingTop: 0 }}> + <Button className={classes.button} color="primary" onClick={goBack}> + {translate('ra.auth.back')} + </Button> + </TopToolbar> + </Container> + </ThemeProvider> + </div> + ); }); -export default Copying; \ No newline at end of file +export default Copying; diff --git a/src/layout/forms/ForgotPassword.js b/src/layout/forms/ForgotPassword.js index 90fbdc9a7d6ad3fe046792fb4e2752fc1582516e..04ad485728c1caef17865e929e31df804cada8ae 100644 --- a/src/layout/forms/ForgotPassword.js +++ b/src/layout/forms/ForgotPassword.js @@ -1,6 +1,7 @@ import { Button, CardActions, CircularProgress, Link, TextField } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import axios from 'axios'; +import PropTypes from 'prop-types'; import { useNotify, useSafeSetState, useTranslate } from 'ra-core'; import React from 'react'; import { Login } from 'react-admin'; @@ -8,86 +9,105 @@ import { Field, Form } from 'react-final-form'; import { Link as RouterLink } from 'react-router-dom'; const useStyles = makeStyles( - (theme) => ({ - form: { - padding: '0 1em 1em 1em', - }, - input: { - marginTop: '1em', - }, - button: { - width: '100%', - }, - icon: { - marginRight: theme.spacing(1), - }, - }), - { name: 'RaLoginForm' } + (theme) => ({ + form: { + padding: '0 1em 1em 1em' + }, + input: { + marginTop: '1em' + }, + button: { + width: '100%' + }, + icon: { + marginRight: theme.spacing(1) + } + }), + { name: 'RaLoginForm' } ); const Input = ({ meta: { touched, error }, input: inputProps, ...props }) => ( - <TextField error={!!(touched && error)} helperText={touched && error} {...inputProps} {...props} fullWidth /> + <TextField error={!!(touched && error)} helperText={touched && error} {...inputProps} {...props} fullWidth /> ); +Input.propTypes = { + meta: PropTypes.any, + input: PropTypes.any +}; + const ForgotPassword = (props) => { - const [loading, setLoading] = useSafeSetState(false); - const translate = useTranslate(); - const notify = useNotify(); - const classes = useStyles(props); + const [loading, setLoading] = useSafeSetState(false); + const translate = useTranslate(); + const notify = useNotify(); + const classes = useStyles(props); - const submit = ({ email }) => { - setLoading(true); - axios.get('/csrf-cookie').then(response => { - return axios.post('/forgot-password', { - 'email': email - }).then(response => { - setLoading(false); - notify(response.data.message) - }).catch(error => { - setLoading(false); + const submit = ({ email }) => { + setLoading(true); + axios.get('/csrf-cookie').then(() => { + return axios + .post('/forgot-password', { + email: email + }) + .then((response) => { + setLoading(false); + notify(response.data.message); + }) + .catch((error) => { + setLoading(false); - if (error?.response?.data?.message) { - let message = ""; - if (error?.response?.data?.errors !== undefined) { - for (let ms in error.response.data.errors) { - for (let m of error.response.data.errors[ms]) { - message += m + "\n"; - } - } - } else { - message = error?.response?.data?.message; - } - notify(message, "warning"); - } else { - notify(error?.message, "warning"); + if (error?.response?.data?.message) { + let message = ''; + if (error?.response?.data?.errors !== undefined) { + for (let ms in error.response.data.errors) { + for (let m of error.response.data.errors[ms]) { + message += m + '\n'; } - }); + } + } else { + message = error?.response?.data?.message; + } + notify(message, 'warning'); + } else { + notify(error?.message, 'warning'); + } }); - }; + }); + }; - return ( - <Login> - <Form onSubmit={submit} render={({ handleSubmit }) => ( - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - <div className={classes.input}> - <Field autoFocus id="email" name="email" component={Input} label={translate('ra.auth.email')} disabled={loading} helperText={ - <Link component={RouterLink} to="/login">{translate('ra.auth.back')}</Link> - } /> - </div> - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.reset_password')} - </Button> - </CardActions> - </form> - )} /> - </Login> - ); + return ( + <Login> + <Form + onSubmit={submit} + render={({ handleSubmit }) => ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}> + <div className={classes.input}> + <Field + autoFocus + id="email" + name="email" + component={Input} + label={translate('ra.auth.email')} + disabled={loading} + helperText={ + <Link component={RouterLink} to="/login"> + {translate('ra.auth.back')} + </Link> + } + /> + </div> + </div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.reset_password')} + </Button> + </CardActions> + </form> + )} + /> + </Login> + ); }; -export default ForgotPassword; \ No newline at end of file +export default ForgotPassword; diff --git a/src/layout/forms/Form.js b/src/layout/forms/Form.js index 034a1c1f905e2c7d324d5e7768bfba11828d0d96..1e1619ab094777a77f9a521b0323339c282c6500 100644 --- a/src/layout/forms/Form.js +++ b/src/layout/forms/Form.js @@ -4,119 +4,104 @@ import LockIcon from '@material-ui/icons/Lock'; import { ThemeProvider } from '@material-ui/styles'; import classnames from 'classnames'; import PropTypes from 'prop-types'; -import React, { - createElement, useEffect, - useMemo, useRef -} from 'react'; +import React, { createElement, useEffect, useMemo, useRef } from 'react'; import { Notification as DefaultNotification } from 'react-admin'; import defaultTheme from '../Theme'; const useStyles = makeStyles( - (theme) => ({ - main: { - display: 'flex', - flexDirection: 'column', - minHeight: '100vh', - height: '1px', - alignItems: 'center', - justifyContent: 'flex-start', - backgroundRepeat: 'no-repeat', - backgroundSize: 'cover', - backgroundImage: - 'radial-gradient(circle at 50% 14em, #313264 0%, #00023b 60%, #00023b 100%)', - }, - card: { - minWidth: 300, - marginTop: '6em', - }, - avatar: { - margin: '1em', - display: 'flex', - justifyContent: 'center', - }, - icon: { - backgroundColor: theme.palette.secondary[500], - }, - }), - { name: 'RaLogin' } + (theme) => ({ + main: { + display: 'flex', + flexDirection: 'column', + minHeight: '100vh', + height: '1px', + alignItems: 'center', + justifyContent: 'flex-start', + backgroundRepeat: 'no-repeat', + backgroundSize: 'cover', + backgroundImage: 'radial-gradient(circle at 50% 14em, #313264 0%, #00023b 60%, #00023b 100%)' + }, + card: { + minWidth: 300, + marginTop: '6em' + }, + avatar: { + margin: '1em', + display: 'flex', + justifyContent: 'center' + }, + icon: { + backgroundColor: theme.palette.secondary[500] + } + }), + { name: 'RaLogin' } ); -const Form = props => { - const { - theme, - title, - classes: classesOverride, - className, - children, - notification, - staticContext, - backgroundImage, - noLock, - ...rest - } = props; - const containerRef = useRef(); - const classes = useStyles(props); - const muiTheme = useMemo(() => createMuiTheme(theme), [theme]); - let backgroundImageLoaded = false; +const Form = (props) => { + const { theme, className, children, notification, backgroundImage, noLock, ...rest } = props; + const containerRef = useRef(); + const classes = useStyles(props); + const muiTheme = useMemo(() => createMuiTheme(theme), [theme]); + let backgroundImageLoaded = false; - const updateBackgroundImage = () => { - if (!backgroundImageLoaded && containerRef.current) { - containerRef.current.style.backgroundImage = `url(${backgroundImage})`; - backgroundImageLoaded = true; - } - }; + const updateBackgroundImage = () => { + if (!backgroundImageLoaded && containerRef.current) { + containerRef.current.style.backgroundImage = `url(${backgroundImage})`; + backgroundImageLoaded = true; + } + }; - // Load background image asynchronously to speed up time to interactive - const lazyLoadBackgroundImage = () => { - if (backgroundImage) { - const img = new Image(); - img.onload = updateBackgroundImage; - img.src = backgroundImage; - } - }; + // Load background image asynchronously to speed up time to interactive + const lazyLoadBackgroundImage = () => { + if (backgroundImage) { + const img = new Image(); + img.onload = updateBackgroundImage; + img.src = backgroundImage; + } + }; - useEffect(() => { - if (!backgroundImageLoaded) { - lazyLoadBackgroundImage(); - } - }); + useEffect(() => { + if (!backgroundImageLoaded) { + lazyLoadBackgroundImage(); + } + }); - return ( - <ThemeProvider theme={muiTheme}> - <div - className={classnames(classes.main, className)} - {...rest} - ref={containerRef} - > - <Card className={classes.card}> - {(noLock === true ? "" : - <div className={classes.avatar}> - <Avatar className={classes.icon}> - <LockIcon /> - </Avatar> - </div> - )} - {children} - </Card> - {notification ? createElement(notification) : null} + return ( + <ThemeProvider theme={muiTheme}> + <div className={classnames(classes.main, className)} {...rest} ref={containerRef}> + <Card className={classes.card}> + {noLock === true ? ( + '' + ) : ( + <div className={classes.avatar}> + <Avatar className={classes.icon}> + <LockIcon /> + </Avatar> </div> - </ThemeProvider> - ); + )} + {children} + </Card> + {notification ? createElement(notification) : null} + </div> + </ThemeProvider> + ); }; Form.propTypes = { - backgroundImage: PropTypes.string, - children: PropTypes.node, - classes: PropTypes.object, - className: PropTypes.string, - theme: PropTypes.object, - staticContext: PropTypes.object, + backgroundImage: PropTypes.string, + children: PropTypes.node, + classes: PropTypes.object, + className: PropTypes.string, + theme: PropTypes.object, + staticContext: PropTypes.object, + notification: PropTypes.any, + noLock: PropTypes.any }; Form.defaultProps = { - theme: defaultTheme, - //children: <DefaultLoginForm />, - notification: DefaultNotification, + theme: defaultTheme, + //children: <DefaultLoginForm />, + notification: DefaultNotification }; export default Form; diff --git a/src/layout/forms/ResetPassword.js b/src/layout/forms/ResetPassword.js index c45c0c9de738f4fe8aa5ea7f78ebf9b425c14336..c6a6a64b2f612b5b7c93059b741a8c08d966fbd1 100644 --- a/src/layout/forms/ResetPassword.js +++ b/src/layout/forms/ResetPassword.js @@ -1,6 +1,7 @@ import { Button, CardActions, CircularProgress, TextField } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import axios from 'axios'; +import PropTypes from 'prop-types'; import { useNotify, useSafeSetState, useTranslate } from 'ra-core'; import React from 'react'; import { Login, useRedirect } from 'react-admin'; @@ -8,97 +9,113 @@ import { Field, Form } from 'react-final-form'; import { useLocation, useParams } from 'react-router-dom'; const useStyles = makeStyles( - (theme) => ({ - form: { - padding: '0 1em 1em 1em', - }, - input: { - marginTop: '1em', - }, - button: { - width: '100%', - }, - icon: { - marginRight: theme.spacing(1), - }, - }), - { name: 'RaLoginForm' } + (theme) => ({ + form: { + padding: '0 1em 1em 1em' + }, + input: { + marginTop: '1em' + }, + button: { + width: '100%' + }, + icon: { + marginRight: theme.spacing(1) + } + }), + { name: 'RaLoginForm' } ); function useQuery() { - return new URLSearchParams(useLocation().search); + return new URLSearchParams(useLocation().search); } const Input = ({ meta: { touched, error }, input: inputProps, ...props }) => ( - <TextField error={!!(touched && error)} helperText={touched && error} {...inputProps} {...props} fullWidth /> + <TextField error={!!(touched && error)} helperText={touched && error} {...inputProps} {...props} fullWidth /> ); +Input.propTypes = { + meta: PropTypes.any, + input: PropTypes.any +}; + const ResetPassword = (props) => { - const [loading, setLoading] = useSafeSetState(false); - const translate = useTranslate(); - const notify = useNotify(); - const redirect = useRedirect(); - const classes = useStyles(props); - let query = useQuery(); - let { token } = useParams(); - let email = query.get("email"); + const [loading, setLoading] = useSafeSetState(false); + const translate = useTranslate(); + const notify = useNotify(); + const redirect = useRedirect(); + const classes = useStyles(props); + let query = useQuery(); + let { token } = useParams(); + let email = query.get('email'); - const submit = ({ password, password_confirmation }) => { - setLoading(true); - return axios.post('/reset-password', { - 'token': token, - 'email': email, - 'password': password, - 'password_confirmation': password_confirmation - }).then(response => { - setLoading(false); - notify(response.data.message) - redirect("/login"); - }).catch(error => { - setLoading(false); + const submit = ({ password, password_confirmation }) => { + setLoading(true); + return axios + .post('/reset-password', { + token: token, + email: email, + password: password, + password_confirmation: password_confirmation + }) + .then((response) => { + setLoading(false); + notify(response.data.message); + redirect('/login'); + }) + .catch((error) => { + setLoading(false); - if (error?.response?.data?.message) { - let message = ""; - if (error?.response?.data?.errors !== undefined) { - for (let ms in error.response.data.errors) { - for (let m of error.response.data.errors[ms]) { - message += m + "\n"; - } - } - } else { - message = error?.response?.data?.message; - } - notify(message); - } else { - notify(error?.message); + if (error?.response?.data?.message) { + let message = ''; + if (error?.response?.data?.errors !== undefined) { + for (let ms in error.response.data.errors) { + for (let m of error.response.data.errors[ms]) { + message += m + '\n'; + } } - }); - }; + } else { + message = error?.response?.data?.message; + } + notify(message); + } else { + notify(error?.message); + } + }); + }; - return ( - <Login> - <Form onSubmit={submit} render={({ handleSubmit }) => ( - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - <div className={classes.input}> - <Field id="password" name="password" component={Input} label={translate('ra.auth.password')} type="password" disabled={loading} /> - </div> - <div className={classes.input}> - <Field id="password_confirmation" name="password_confirmation" component={Input} label={translate('ra.auth.password_confirmation')} type="password" disabled={loading} /> - </div> - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.reset_password')} - </Button> - </CardActions> - </form> - )} /> - </Login> - ); + return ( + <Login> + <Form + onSubmit={submit} + render={({ handleSubmit }) => ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}> + <div className={classes.input}> + <Field id="password" name="password" component={Input} label={translate('ra.auth.password')} type="password" disabled={loading} /> + </div> + <div className={classes.input}> + <Field + id="password_confirmation" + name="password_confirmation" + component={Input} + label={translate('ra.auth.password_confirmation')} + type="password" + disabled={loading} + /> + </div> + </div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.reset_password')} + </Button> + </CardActions> + </form> + )} + /> + </Login> + ); }; -export default ResetPassword; \ No newline at end of file +export default ResetPassword; diff --git a/src/layout/forms/TokenClear.js b/src/layout/forms/TokenClear.js index 80e89d3a2d5d62eac8f4f7c87322c052b4f90a73..bc7e2609e83417261a9d9906417fd7b03c5848a2 100644 --- a/src/layout/forms/TokenClear.js +++ b/src/layout/forms/TokenClear.js @@ -8,82 +8,91 @@ import { Form } from 'react-final-form'; import BaseForm from './Form'; const useStyles = makeStyles( - (theme) => ({ - form: { - padding: '0 1em 1em 1em', - textAlign: 'center', - width: '420px' - }, - input: { - marginTop: '1em', - }, - button: { - width: '100%', - }, - icon: { - marginRight: theme.spacing(1), - }, - }), - { name: 'RaLoginForm' } + (theme) => ({ + form: { + padding: '0 1em 1em 1em', + textAlign: 'center', + width: '420px' + }, + input: { + marginTop: '1em' + }, + button: { + width: '100%' + }, + icon: { + marginRight: theme.spacing(1) + } + }), + { name: 'RaLoginForm' } ); const TokenClear = (props) => { - useAuthenticated(); - const [loading, setLoading] = useSafeSetState(false); - const translate = useTranslate(); - const notify = useNotify(); - const redirect = useRedirect(); - const classes = useStyles(props); + useAuthenticated(); + const [loading, setLoading] = useSafeSetState(false); + const translate = useTranslate(); + const notify = useNotify(); + const redirect = useRedirect(); + const classes = useStyles(props); - const submit = (values) => { - setLoading(true); - return axios.delete('/tokens').then(response => { - notify("ra.auth.token.clear.cleared"); - setLoading(false); - redirect("/profile"); - }).catch(error => { - setLoading(false); + const submit = () => { + setLoading(true); + return axios + .delete('/tokens') + .then(() => { + notify('ra.auth.token.clear.cleared'); + setLoading(false); + redirect('/profile'); + }) + .catch((error) => { + setLoading(false); - if (error?.response?.data?.message) { - let message = ""; - if (error?.response?.data?.errors !== undefined) { - for (let ms in error.response.data.errors) { - for (let m of error.response.data.errors[ms]) { - message += m + "\n"; - } - } - } else { - message = error?.response?.data?.message; - } - notify(message, "warning"); - } else { - notify(error?.message, "warning"); + if (error?.response?.data?.message) { + let message = ''; + if (error?.response?.data?.errors !== undefined) { + for (let ms in error.response.data.errors) { + for (let m of error.response.data.errors[ms]) { + message += m + '\n'; + } } - }); - }; + } else { + message = error?.response?.data?.message; + } + notify(message, 'warning'); + } else { + notify(error?.message, 'warning'); + } + }); + }; - return ( - <BaseForm> - <Form onSubmit={submit} render={({ handleSubmit }) => ( - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - {translate('ra.auth.token.clear.ask')} - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.token.clear.clear')} - </Button> - <Button variant="contained" onClick={() => { redirect('/profile') }} color="secondary" disabled={loading} className={classes.button} > - {translate('ra.auth.cancel')} - </Button> - </CardActions> - </form> - )} /> - </BaseForm> - ); + return ( + <BaseForm> + <Form + onSubmit={submit} + render={({ handleSubmit }) => ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}>{translate('ra.auth.token.clear.ask')}</div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.token.clear.clear')} + </Button> + <Button + variant="contained" + onClick={() => { + redirect('/profile'); + }} + color="secondary" + disabled={loading} + className={classes.button}> + {translate('ra.auth.cancel')} + </Button> + </CardActions> + </form> + )} + /> + </BaseForm> + ); }; -export default TokenClear; \ No newline at end of file +export default TokenClear; diff --git a/src/layout/forms/TokenNew.js b/src/layout/forms/TokenNew.js index facf459defbd65e50eb313012237a2757b0b6fec..2d6f3557d06dbca68483a6dbd29569d63550fb46 100644 --- a/src/layout/forms/TokenNew.js +++ b/src/layout/forms/TokenNew.js @@ -8,141 +8,152 @@ import { Field, Form } from 'react-final-form'; import BaseForm from './Form'; const useStyles = makeStyles( - (theme) => ({ - form: { - padding: '0 1em 1em 1em', - textAlign: 'center', - width: '420px' - }, - input: { - marginTop: '1em', - }, - button: { - width: '100%', - }, - icon: { - marginRight: theme.spacing(1), - }, - }), - { name: 'RaLoginForm' } + (theme) => ({ + form: { + padding: '0 1em 1em 1em', + textAlign: 'center', + width: '420px' + }, + input: { + marginTop: '1em' + }, + button: { + width: '100%' + }, + icon: { + marginRight: theme.spacing(1) + } + }), + { name: 'RaLoginForm' } ); const Input = ({ - meta: { touched, error }, // eslint-disable-line react/prop-types - input: inputProps, // eslint-disable-line react/prop-types - ...props -}) => ( - <TextField - error={!!(touched && error)} - helperText={touched && error} - {...inputProps} - {...props} - fullWidth - /> -); + meta: { touched, error }, // eslint-disable-line react/prop-types + input: inputProps, // eslint-disable-line react/prop-types + ...props +}) => <TextField error={!!(touched && error)} helperText={touched && error} {...inputProps} {...props} fullWidth />; const TokenNew = (props) => { - useAuthenticated(); - const [loading, setLoading] = useSafeSetState(false); - const [step, setStep] = useSafeSetState(0); - const [qrcode, setQrcode] = useSafeSetState(""); - const [token, setToken] = useSafeSetState(""); - const translate = useTranslate(); - const notify = useNotify(); - const redirect = useRedirect(); - const classes = useStyles(props); + useAuthenticated(); + const [loading, setLoading] = useSafeSetState(false); + const [step, setStep] = useSafeSetState(0); + const [qrcode, setQrcode] = useSafeSetState(''); + const [token, setToken] = useSafeSetState(''); + const translate = useTranslate(); + const notify = useNotify(); + const redirect = useRedirect(); + const classes = useStyles(props); - const submit = (values) => { - setLoading(true); - switch (step) { - case 0: - return axios.post('/tokens', values).then(response => { - setLoading(false); - setQrcode(response.data.qrcode); - setToken(response.data.token); - setStep(1); - }).catch(error => { - setLoading(false); + const submit = (values) => { + setLoading(true); + switch (step) { + case 0: + return axios + .post('/tokens', values) + .then((response) => { + setLoading(false); + setQrcode(response.data.qrcode); + setToken(response.data.token); + setStep(1); + }) + .catch((error) => { + setLoading(false); + + if (error?.response?.data?.message) { + let message = ''; + if (error?.response?.data?.errors !== undefined) { + for (let ms in error.response.data.errors) { + for (let m of error.response.data.errors[ms]) { + message += m + '\n'; + } + } + } else { + message = error?.response?.data?.message; + } + notify(message, 'warning'); + } else { + notify(error?.message, 'warning'); + } + }); + case 1: + setLoading(false); + redirect('/profile'); + break; + default: + break; + } + }; - if (error?.response?.data?.message) { - let message = ""; - if (error?.response?.data?.errors !== undefined) { - for (let ms in error.response.data.errors) { - for (let m of error.response.data.errors[ms]) { - message += m + "\n"; - } - } - } else { - message = error?.response?.data?.message; - } - notify(message, "warning"); - } else { - notify(error?.message, "warning"); - } - }); + return ( + <BaseForm> + <Form + onSubmit={submit} + render={({ handleSubmit }) => { + switch (step) { + case 0: + return ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}> + {translate('ra.auth.token.new.ask')} + <div className={classes.input}> + <Field id="name" name="name" component={Input} label={translate('ra.auth.token.new.name')} type="text" disabled={loading} /> + </div> + </div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.token.new.create')} + </Button> + <Button + variant="contained" + onClick={() => { + redirect('/profile'); + }} + color="secondary" + disabled={loading} + className={classes.button}> + {translate('ra.auth.cancel')} + </Button> + </CardActions> + </form> + ); case 1: - setLoading(false); - redirect('/profile'); - break; + return ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}> + {translate('ra.auth.token.new.scan')} + <div + style={{ + display: 'flex', + justifyContent: 'center', + marginTop: '1em', + marginBottom: '1em' + }}> + <div + dangerouslySetInnerHTML={{ __html: qrcode }} + style={{ + border: '8px solid white', + width: '208px', + height: '208px' + }}></div> + </div> + <Box fontFamily="monospace">{token}</Box> + </div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.token.new.validate')} + </Button> + </CardActions> + </form> + ); default: - break; - } - }; - - return ( - <BaseForm> - <Form onSubmit={submit} render={({ handleSubmit }) => { - switch (step) { - case 0: - return ( - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - {translate('ra.auth.token.new.ask')} - <div className={classes.input}> - <Field id="name" name="name" component={Input} label={translate('ra.auth.token.new.name')} type="text" disabled={loading} /> - </div> - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.token.new.create')} - </Button> - <Button variant="contained" onClick={() => { redirect('/profile') }} color="secondary" disabled={loading} className={classes.button} > - {translate('ra.auth.cancel')} - </Button> - </CardActions> - </form> - ); - case 1: - return ( - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - {translate('ra.auth.token.new.scan')} - <div style={{ display: "flex", justifyContent: "center", marginTop: "1em", marginBottom: "1em" }}> - <div dangerouslySetInnerHTML={{ __html: qrcode }} style={{ border: "8px solid white", width: "208px", height: "208px" }}></div> - </div> - <Box fontFamily="monospace"> - {token} - </Box> - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.token.new.validate')} - </Button> - </CardActions> - </form> - ); - default: - break; - } - }} /> - </BaseForm> - ); + break; + } + }} + /> + </BaseForm> + ); }; -export default TokenNew; \ No newline at end of file +export default TokenNew; diff --git a/src/layout/forms/TwoFactorDisable.js b/src/layout/forms/TwoFactorDisable.js index a6beda87a8828911249da0cb9dc28292934bdbc3..765dbc58b2eb2d4b0e76c10531ffb1cac07a7a95 100644 --- a/src/layout/forms/TwoFactorDisable.js +++ b/src/layout/forms/TwoFactorDisable.js @@ -8,82 +8,91 @@ import { Form } from 'react-final-form'; import BaseForm from './Form'; const useStyles = makeStyles( - (theme) => ({ - form: { - padding: '0 1em 1em 1em', - textAlign: 'center', - width: '420px' - }, - input: { - marginTop: '1em', - }, - button: { - width: '100%', - }, - icon: { - marginRight: theme.spacing(1), - }, - }), - { name: 'RaLoginForm' } + (theme) => ({ + form: { + padding: '0 1em 1em 1em', + textAlign: 'center', + width: '420px' + }, + input: { + marginTop: '1em' + }, + button: { + width: '100%' + }, + icon: { + marginRight: theme.spacing(1) + } + }), + { name: 'RaLoginForm' } ); const TwoFactorDisable = (props) => { - useAuthenticated(); - const [loading, setLoading] = useSafeSetState(false); - const translate = useTranslate(); - const notify = useNotify(); - const redirect = useRedirect(); - const classes = useStyles(props); + useAuthenticated(); + const [loading, setLoading] = useSafeSetState(false); + const translate = useTranslate(); + const notify = useNotify(); + const redirect = useRedirect(); + const classes = useStyles(props); - const submit = (values) => { - setLoading(true); - return axios.delete('/user/two-factor-authentication').then(response => { - notify("ra.auth.2fa.disabled"); - setLoading(false); - redirect("/profile"); - }).catch(error => { - setLoading(false); + const submit = () => { + setLoading(true); + return axios + .delete('/user/two-factor-authentication') + .then(() => { + notify('ra.auth.2fa.disabled'); + setLoading(false); + redirect('/profile'); + }) + .catch((error) => { + setLoading(false); - if (error?.response?.data?.message) { - let message = ""; - if (error?.response?.data?.errors !== undefined) { - for (let ms in error.response.data.errors) { - for (let m of error.response.data.errors[ms]) { - message += m + "\n"; - } - } - } else { - message = error?.response?.data?.message; - } - notify(message, "warning"); - } else { - notify(error?.message, "warning"); + if (error?.response?.data?.message) { + let message = ''; + if (error?.response?.data?.errors !== undefined) { + for (let ms in error.response.data.errors) { + for (let m of error.response.data.errors[ms]) { + message += m + '\n'; + } } - }); - }; + } else { + message = error?.response?.data?.message; + } + notify(message, 'warning'); + } else { + notify(error?.message, 'warning'); + } + }); + }; - return ( - <BaseForm> - <Form onSubmit={submit} render={({ handleSubmit }) => ( - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - {translate('ra.auth.2fa.askdisable')} - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.2fa.disable')} - </Button> - <Button variant="contained" onClick={() => { redirect('/profile') }} color="secondary" disabled={loading} className={classes.button} > - {translate('ra.auth.cancel')} - </Button> - </CardActions> - </form> - )} /> - </BaseForm> - ); + return ( + <BaseForm> + <Form + onSubmit={submit} + render={({ handleSubmit }) => ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}>{translate('ra.auth.2fa.askdisable')}</div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.2fa.disable')} + </Button> + <Button + variant="contained" + onClick={() => { + redirect('/profile'); + }} + color="secondary" + disabled={loading} + className={classes.button}> + {translate('ra.auth.cancel')} + </Button> + </CardActions> + </form> + )} + /> + </BaseForm> + ); }; -export default TwoFactorDisable; \ No newline at end of file +export default TwoFactorDisable; diff --git a/src/layout/forms/TwoFactorEnable.js b/src/layout/forms/TwoFactorEnable.js index 397d2299f368034e822cb971ae0e2320ca7e92b0..7d3856b3527d293b2fd0b63d3072ee0fa073e2e3 100644 --- a/src/layout/forms/TwoFactorEnable.js +++ b/src/layout/forms/TwoFactorEnable.js @@ -8,126 +8,148 @@ import { Form } from 'react-final-form'; import BaseForm from './Form'; const useStyles = makeStyles( - (theme) => ({ - form: { - padding: '0 1em 1em 1em', - textAlign: 'center', - width: '420px' - }, - input: { - marginTop: '1em', - }, - button: { - width: '100%', - }, - icon: { - marginRight: theme.spacing(1), - }, - }), - { name: 'RaLoginForm' } + (theme) => ({ + form: { + padding: '0 1em 1em 1em', + textAlign: 'center', + width: '420px' + }, + input: { + marginTop: '1em' + }, + button: { + width: '100%' + }, + icon: { + marginRight: theme.spacing(1) + } + }), + { name: 'RaLoginForm' } ); const TwoFactorEnable = (props) => { - useAuthenticated(); - const [loading, setLoading] = useSafeSetState(false); - const [step, setStep] = useSafeSetState(0); - const [qrcode, setQrcode] = useSafeSetState(""); - const translate = useTranslate(); - const notify = useNotify(); - const redirect = useRedirect(); - const classes = useStyles(props); + useAuthenticated(); + const [loading, setLoading] = useSafeSetState(false); + const [step, setStep] = useSafeSetState(0); + const [qrcode, setQrcode] = useSafeSetState(''); + const translate = useTranslate(); + const notify = useNotify(); + const redirect = useRedirect(); + const classes = useStyles(props); - const submit = (values) => { - setLoading(true); - switch (step) { - case 0: - return axios.post('/user/two-factor-authentication').then(response => { - return axios.get('/user/two-factor-qr-code').then(response => { - setLoading(false); - setQrcode(response.data.svg); - setStep(1); - }).catch(error => { - setLoading(false); - console.log(error); - notify(error?.response?.data?.message); - }); - }).catch(error => { - setLoading(false); - - if (error?.response?.data?.message) { - let message = ""; - if (error?.response?.data?.errors !== undefined) { - for (let ms in error.response.data.errors) { - for (let m of error.response.data.errors[ms]) { - message += m + "\n"; - } - } - } else { - message = error?.response?.data?.message; - } - notify(message, "warning"); - } else { - notify(error?.message, "warning"); - } - }); - case 1: + const submit = () => { + setLoading(true); + switch (step) { + case 0: + return axios + .post('/user/two-factor-authentication') + .then(() => { + return axios + .get('/user/two-factor-qr-code') + .then((response) => { setLoading(false); - notify('ra.auth.2fa.success'); - redirect('/profile'); - break; - default: - break; - } - }; + setQrcode(response.data.svg); + setStep(1); + }) + .catch((error) => { + setLoading(false); + console.log(error); + notify(error?.response?.data?.message); + }); + }) + .catch((error) => { + setLoading(false); - return ( - <BaseForm> - <Form onSubmit={submit} render={({ handleSubmit }) => { - switch (step) { - case 0: - return ( - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - {translate('ra.auth.2fa.ask')} - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.2fa.enable')} - </Button> - <Button variant="contained" onClick={() => { redirect('/profile') }} color="secondary" disabled={loading} className={classes.button} > - {translate('ra.auth.cancel')} - </Button> - </CardActions> - </form> - ); - case 1: - return ( - <form onSubmit={handleSubmit} noValidate> - <div className={classes.form}> - {translate('ra.auth.2fa.scan')} - <div style={{ display: "flex", justifyContent: "center", marginTop: "1em", marginBottom: "1em" }}> - <div dangerouslySetInnerHTML={{ __html: qrcode }} style={{ border: "8px solid white", width: "208px", height: "208px" }}></div> - </div> - </div> - <CardActions> - <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button} > - {loading && ( - <CircularProgress className={classes.icon} size={18} thickness={2} /> - )} - {translate('ra.auth.2fa.validate')} - </Button> - </CardActions> - </form> - ); - default: - break; + if (error?.response?.data?.message) { + let message = ''; + if (error?.response?.data?.errors !== undefined) { + for (let ms in error.response.data.errors) { + for (let m of error.response.data.errors[ms]) { + message += m + '\n'; + } } - }} /> - </BaseForm> - ); + } else { + message = error?.response?.data?.message; + } + notify(message, 'warning'); + } else { + notify(error?.message, 'warning'); + } + }); + case 1: + setLoading(false); + notify('ra.auth.2fa.success'); + redirect('/profile'); + break; + default: + break; + } + }; + + return ( + <BaseForm> + <Form + onSubmit={submit} + render={({ handleSubmit }) => { + switch (step) { + case 0: + return ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}>{translate('ra.auth.2fa.ask')}</div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.2fa.enable')} + </Button> + <Button + variant="contained" + onClick={() => { + redirect('/profile'); + }} + color="secondary" + disabled={loading} + className={classes.button}> + {translate('ra.auth.cancel')} + </Button> + </CardActions> + </form> + ); + case 1: + return ( + <form onSubmit={handleSubmit} noValidate> + <div className={classes.form}> + {translate('ra.auth.2fa.scan')} + <div + style={{ + display: 'flex', + justifyContent: 'center', + marginTop: '1em', + marginBottom: '1em' + }}> + <div + dangerouslySetInnerHTML={{ __html: qrcode }} + style={{ + border: '8px solid white', + width: '208px', + height: '208px' + }}></div> + </div> + </div> + <CardActions> + <Button variant="contained" type="submit" color="secondary" disabled={loading} className={classes.button}> + {loading && <CircularProgress className={classes.icon} size={18} thickness={2} />} + {translate('ra.auth.2fa.validate')} + </Button> + </CardActions> + </form> + ); + default: + break; + } + }} + /> + </BaseForm> + ); }; -export default TwoFactorEnable; \ No newline at end of file +export default TwoFactorEnable; diff --git a/src/pages/Count.js b/src/pages/Count.js index e31803b3b7283f522e57c84b203467c21485ee83..8e4d491f7892574c59266a6ba646f5b78d3e6778 100644 --- a/src/pages/Count.js +++ b/src/pages/Count.js @@ -10,191 +10,249 @@ import AddIcon from '@material-ui/icons/Add'; import ClearIcon from '@material-ui/icons/Clear'; import RemoveIcon from '@material-ui/icons/Remove'; import SaveIcon from '@material-ui/icons/Save'; -import { useEffect, useState } from "react"; +import PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; import { Title, useDataProvider, useNotify, useTranslate } from 'react-admin'; const useStyles = makeStyles((theme) => ({ - root: { - flexGrow: 1, - }, - paper: { - padding: theme.spacing(2), - textAlign: 'center', - color: theme.palette.text.secondary, - backgroundColor: '#545454' - }, + root: { + flexGrow: 1 + }, + paper: { + padding: theme.spacing(2), + textAlign: 'center', + color: theme.palette.text.secondary, + backgroundColor: '#545454' + } })); const Item = ({ value, updatePrice, ...props }) => { - const classes = useStyles(); - const [count, setCount] = useState(0); - - const addCount = ((x) => { - - if (count + x < 0) { - setCount(0); - updatePrice(value, 0); - return; - } - - setCount(count + x); - updatePrice(value, count + x); - }); - - useEffect(() => { - setCount(0); - updatePrice(value, 0); - }, [props.refresh, updatePrice, value]); - - return ( - <Grid item xs={12} sm={6} md={4} lg={3} xl={2}> - <Paper className={classes.paper}> - <Typography variant="h6" gutterBottom>{Number(value).toFixed(2)} €</Typography> - <Grid container style={{ justifyContent: "center" }}> - <Grid item className={classes.rows}> - <IconButton aria-label="sub" onClick={(e) => { addCount(e.shiftKey ? -10 : -1) }}> - <RemoveIcon /> - </IconButton> - </Grid> - <Grid item container alignItems="center" className={classes.rows} xs={4}> - <Grid item> - <TextField - id="standard-number" - type="text" - inputProps={{ min: 0, style: { textAlign: 'center' } }} - value={count} - InputLabelProps={{ - shrink: true, - }} - onChange={(e) => { - let val = e.target.value; - if (val === "") - val = "0"; - val = parseInt(val); - if (!isNaN(val) && val >= 0) { - setCount(val); - updatePrice(value, val); - } - }} - /> - </Grid> - </Grid> - <Grid item className={classes.rows}> - <IconButton aria-label="add" onClick={(e) => { addCount(e.shiftKey ? 10 : 1) }}> - <AddIcon /> - </IconButton> - </Grid> - </Grid> - </Paper> + const classes = useStyles(); + const [count, setCount] = useState(0); + + const addCount = (x) => { + if (count + x < 0) { + setCount(0); + updatePrice(value, 0); + return; + } + + setCount(count + x); + updatePrice(value, count + x); + }; + + useEffect(() => { + setCount(0); + updatePrice(value, 0); + }, [props.refresh, updatePrice, value]); + + return ( + <Grid item xs={12} sm={6} md={4} lg={3} xl={2}> + <Paper className={classes.paper}> + <Typography variant="h6" gutterBottom> + {Number(value).toFixed(2)} € + </Typography> + <Grid container style={{ justifyContent: 'center' }}> + <Grid item className={classes.rows}> + <IconButton + aria-label="sub" + onClick={(e) => { + addCount(e.shiftKey ? -10 : -1); + }}> + <RemoveIcon /> + </IconButton> + </Grid> + <Grid item container alignItems="center" className={classes.rows} xs={4}> + <Grid item> + <TextField + id="standard-number" + type="text" + inputProps={{ min: 0, style: { textAlign: 'center' } }} + value={count} + InputLabelProps={{ + shrink: true + }} + onChange={(e) => { + let val = e.target.value; + if (val === '') val = '0'; + val = parseInt(val); + if (!isNaN(val) && val >= 0) { + setCount(val); + updatePrice(value, val); + } + }} + /> + </Grid> + </Grid> + <Grid item className={classes.rows}> + <IconButton + aria-label="add" + onClick={(e) => { + addCount(e.shiftKey ? 10 : 1); + }}> + <AddIcon /> + </IconButton> + </Grid> </Grid> - ); + </Paper> + </Grid> + ); +}; + +Item.propTypes = { + refresh: PropTypes.any, + updatePrice: PropTypes.func, + value: PropTypes.any }; const Count = () => { - let values = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500]; - - const dataProvider = useDataProvider(); - const [counts, setCounts] = useState({}); - const [accounts, setAccounts] = useState([]); - const [selectAccount, setSelectAccount] = useState(""); - const [selectType, setSelectType] = useState("cash"); - const [price, setPrice] = useState(0); - const [refresh, doRefresh] = useState(0); - const notify = useNotify(); - const translate = useTranslate(); - - useEffect(() => { - dataProvider.getList('accounts', { pagination: { perPage: 100000, page: 1 }, sort: { field: 'id', order: 'asc' } }).then(({ data }) => { - setAccounts(data); - }).catch(error => { - console.error(error); - }); - }, [dataProvider]); - - const updatePrice = (value, count) => { - let c = counts; - c[value] = count; - setCounts(c); - let moula = 0; - for (let elem in c) { - moula += c[elem] * parseFloat(elem); - } - setPrice(moula); - }; + let values = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500]; + + const dataProvider = useDataProvider(); + const [counts, setCounts] = useState({}); + const [accounts, setAccounts] = useState([]); + const [selectAccount, setSelectAccount] = useState(''); + const [selectType, setSelectType] = useState('cash'); + const [price, setPrice] = useState(0); + const [refresh, doRefresh] = useState(0); + const notify = useNotify(); + const translate = useTranslate(); - let items = values.map((val) => { - return ( - <Item key={val} value={val} refresh={refresh} updatePrice={updatePrice} /> - ); - }); - - const save = () => { - let data = { - type: selectType, - account_id: selectAccount - }; - - if (selectType === "cash") { - data["data"] = counts; - } else { - data["data"] = { amount: price }; - } - - dataProvider.create('accounts_counts', { data: data }).then((response) => { - doRefresh(prev => prev + 1); - notify('Count saved!'); - }).catch((error) => { - notify(error.message, 'warning'); - }) + useEffect(() => { + dataProvider + .getList('accounts', { + pagination: { perPage: 100000, page: 1 }, + sort: { field: 'id', order: 'asc' } + }) + .then(({ data }) => { + setAccounts(data); + }) + .catch((error) => { + console.error(error); + }); + }, [dataProvider]); + + const updatePrice = (value, count) => { + let c = counts; + c[value] = count; + setCounts(c); + let moula = 0; + for (let elem in c) { + moula += c[elem] * parseFloat(elem); + } + setPrice(moula); + }; + + let items = values.map((val) => { + return <Item key={val} value={val} refresh={refresh} updatePrice={updatePrice} />; + }); + + const save = () => { + let data = { + type: selectType, + account_id: selectAccount }; - return ( - <> - <Grid container> - <Grid xs={12} item className="MuiToolbar-root MuiToolbar-regular RaTopToolbar-root-56" style={{ flexGrow: 1, display: 'flex', justifyContent: 'end' }}> - {(selectType === "cash" ? <Button color="primary" startIcon={<ClearIcon />} onClick={() => doRefresh(prev => prev + 1)}>{translate('actions.clear')}</Button> : "")} - <Button color="primary" startIcon={<SaveIcon />} onClick={save}>{translate('ra.action.save')}</Button> - </Grid> - <Grid item xs={12} md={6} lg={4} style={{ padding: '0 4px' }}> - <TextField select variant="filled" type="text" label={translate('sell.account')} value={selectAccount} onChange={(e) => { setSelectAccount(e.target.value) }}> - <MenuItem key={""} value={""}><span style={{ color: 'transparent' }}>{translate('sell.none')}</span></MenuItem> - {accounts.map((account) => ( - <MenuItem key={account.id} value={account.id}> - {account.name} - </MenuItem> - ))} - </TextField> - </Grid> - <Grid item xs={12} md={6} lg={4} style={{ padding: '0 4px' }}> - <TextField select variant="filled" type="text" label={translate('sell.type')} value={selectType} onChange={(e) => { setSelectType(e.target.value) }}> - <MenuItem key="cash" value="cash"> - {translate('sell.cash')} - </MenuItem> - <MenuItem key="value" value="value"> - {translate('sell.value')} - </MenuItem> - </TextField> - </Grid> - <Grid item xs={12} lg={4} style={{ padding: '0 4px' }}> - {(selectType === "cash" ? - <TextField value={Number(price).toFixed(2) + " €"} disabled variant="filled" type="text" label={translate('sell.balance')} /> - : - <TextField value={price} onChange={(e) => { setPrice(e.target.value) }} variant="filled" type="text" label={translate('sell.balance')} /> - )} - </Grid> + if (selectType === 'cash') { + data['data'] = counts; + } else { + data['data'] = { amount: price }; + } + + dataProvider + .create('accounts_counts', { data: data }) + .then(() => { + doRefresh((prev) => prev + 1); + notify('Count saved!'); + }) + .catch((error) => { + notify(error.message, 'warning'); + }); + }; + + return ( + <> + <Grid container> + <Grid xs={12} item className="MuiToolbar-root MuiToolbar-regular RaTopToolbar-root-56" style={{ flexGrow: 1, display: 'flex', justifyContent: 'end' }}> + {selectType === 'cash' ? ( + <Button color="primary" startIcon={<ClearIcon />} onClick={() => doRefresh((prev) => prev + 1)}> + {translate('actions.clear')} + </Button> + ) : ( + '' + )} + <Button color="primary" startIcon={<SaveIcon />} onClick={save}> + {translate('ra.action.save')} + </Button> + </Grid> + <Grid item xs={12} md={6} lg={4} style={{ padding: '0 4px' }}> + <TextField + select + variant="filled" + type="text" + label={translate('sell.account')} + value={selectAccount} + onChange={(e) => { + setSelectAccount(e.target.value); + }}> + <MenuItem key={''} value={''}> + <span style={{ color: 'transparent' }}>{translate('sell.none')}</span> + </MenuItem> + {accounts.map((account) => ( + <MenuItem key={account.id} value={account.id}> + {account.name} + </MenuItem> + ))} + </TextField> + </Grid> + <Grid item xs={12} md={6} lg={4} style={{ padding: '0 4px' }}> + <TextField + select + variant="filled" + type="text" + label={translate('sell.type')} + value={selectType} + onChange={(e) => { + setSelectType(e.target.value); + }}> + <MenuItem key="cash" value="cash"> + {translate('sell.cash')} + </MenuItem> + <MenuItem key="value" value="value"> + {translate('sell.value')} + </MenuItem> + </TextField> + </Grid> + <Grid item xs={12} lg={4} style={{ padding: '0 4px' }}> + {selectType === 'cash' ? ( + <TextField value={Number(price).toFixed(2) + ' €'} disabled variant="filled" type="text" label={translate('sell.balance')} /> + ) : ( + <TextField + value={price} + onChange={(e) => { + setPrice(e.target.value); + }} + variant="filled" + type="text" + label={translate('sell.balance')} + /> + )} + </Grid> + </Grid> + <Card> + <Title title={translate('menu.left.count_money')} /> + {selectType === 'cash' ? ( + <CardContent> + <Grid container spacing={3}> + {items} </Grid> - <Card> - <Title title={translate('menu.left.count_money')} /> - {(selectType === "cash" ? - <CardContent> - <Grid container spacing={3} > - {items} - </Grid> - </CardContent> - : "")} - </Card> - </> - ); + </CardContent> + ) : ( + '' + )} + </Card> + </> + ); }; export default Count; diff --git a/src/pages/Dashboard.js b/src/pages/Dashboard.js index f0da60f9b558592600ecc4a0356d1efe92018d25..af675ab570d64a383453c566acddca8183a0d75f 100644 --- a/src/pages/Dashboard.js +++ b/src/pages/Dashboard.js @@ -1,5 +1,6 @@ import { Card, CardContent, CardHeader, Grid, makeStyles, Tab, Table, TableBody, TableCell, TableRow, Tabs, Typography } from '@material-ui/core'; -import React from "react"; +import PropTypes from 'prop-types'; +import React from 'react'; import { Error, Loading, Title, usePermissions, useQuery, useTranslate } from 'react-admin'; import ColorProvider from '../providers/ColorProvider'; import AccountsStatistics from './statistics/AccountsStatistics'; @@ -7,174 +8,220 @@ import ProductsStatistics from './statistics/ProductsStatistics'; import SellersStatistics from './statistics/SellersStatistics'; const useStyles = makeStyles({ - good: { - color: "#4caf50" - }, - bad: { - color: "#f44336" - } + good: { + color: '#4caf50' + }, + bad: { + color: '#f44336' + } }); const AccountsPanel = ({ data }) => { - const translate = useTranslate(); - const styles = useStyles(); - - return ('accounts' in data && 'stocks_value' in data ? ( - <Grid item xs={12} lg={4} xl={3}> - <Card style={{ height: '100%' }}> - <CardContent> - {data['accounts'].length === 0 && !('stocks_value' in data) ? translate('dashboard.alerts.none') : ( - <Grid container style={{ justifyContent: "space-between" }}> - {data['accounts'].length !== 0 ? ( - data['accounts'].map((value, i) => { - return ( - <Grid item xs={12} sm={6} md={4} lg={12} key={i}> - <Typography variant="h6">{value.name}</Typography> - <Typography variant="h4" classes={{ root: value.balance > 0 ? styles.good : styles.bad }}>{value.balance > 0 ? '+' : ''}{Number(value.balance).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' })}</Typography> - </Grid> - ); - }) - ) : ''} - {('stocks_value' in data) ? ( - <Grid item xs={12} sm={6} md={4} lg={12}> - <Typography variant="h6">{translate('dashboard.accounts.stocks_value')}</Typography> - <Typography variant="h4" classes={{ root: data.stocks_value > 0 ? styles.good : styles.bad }}>{data.stocks_value > 0 ? '+' : ''}{Number(data.stocks_value).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' })}</Typography> - </Grid> - ) : ''} - </Grid> - )} - </CardContent> - </Card> - </Grid> - ) : <></>); + const translate = useTranslate(); + const styles = useStyles(); + + return 'accounts' in data && 'stocks_value' in data ? ( + <Grid item xs={12} lg={4} xl={3}> + <Card style={{ height: '100%' }}> + <CardContent> + {data['accounts'].length === 0 && !('stocks_value' in data) ? ( + translate('dashboard.alerts.none') + ) : ( + <Grid container style={{ justifyContent: 'space-between' }}> + {data['accounts'].length !== 0 + ? data['accounts'].map((value, i) => { + return ( + <Grid item xs={12} sm={6} md={4} lg={12} key={i}> + <Typography variant="h6">{value.name}</Typography> + <Typography + variant="h4" + classes={{ + root: value.balance > 0 ? styles.good : styles.bad + }}> + {value.balance > 0 ? '+' : ''} + {Number(value.balance).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + })} + </Typography> + </Grid> + ); + }) + : ''} + {'stocks_value' in data ? ( + <Grid item xs={12} sm={6} md={4} lg={12}> + <Typography variant="h6">{translate('dashboard.accounts.stocks_value')}</Typography> + <Typography + variant="h4" + classes={{ + root: data.stocks_value > 0 ? styles.good : styles.bad + }}> + {data.stocks_value > 0 ? '+' : ''} + {Number(data.stocks_value).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + })} + </Typography> + </Grid> + ) : ( + '' + )} + </Grid> + )} + </CardContent> + </Card> + </Grid> + ) : ( + <></> + ); +}; + +AccountsPanel.propTypes = { + data: PropTypes.any }; const ProductsAlertsPanel = ({ data }) => { - const translate = useTranslate(); - - return ('products_alerts' in data ? ( - <Grid item xs={12} lg xl> - <Card style={{ height: '100%' }}> - <CardHeader title={translate('dashboard.alerts.title')} /> - <CardContent> - {data['products_alerts'].length === 0 ? translate('dashboard.alerts.none') : ( - - <Table size="small"> - <TableBody> - <TableRow> - <TableCell>{translate('dashboard.alerts.id')}</TableCell> - <TableCell>{translate('dashboard.alerts.name')}</TableCell> - <TableCell>{translate('dashboard.alerts.count')}</TableCell> - <TableCell>{translate('dashboard.alerts.treshold')}</TableCell> - </TableRow> - {data['products_alerts'].map((value) => ( - <TableRow key={value.id}> - <TableCell>{value.id}</TableCell> - <TableCell component="th" scope="row"> - {value.name} - </TableCell> - <TableCell component="th" scope="row"> - {value.count} - </TableCell> - <TableCell component="th" scope="row"> - {value.alert_level} - </TableCell> - </TableRow> - ))} - </TableBody> - </Table> - )} - </CardContent> - </Card> - </Grid > - ) : ''); + const translate = useTranslate(); + + return 'products_alerts' in data ? ( + <Grid item xs={12} lg xl> + <Card style={{ height: '100%' }}> + <CardHeader title={translate('dashboard.alerts.title')} /> + <CardContent> + {data['products_alerts'].length === 0 ? ( + translate('dashboard.alerts.none') + ) : ( + <Table size="small"> + <TableBody> + <TableRow> + <TableCell>{translate('dashboard.alerts.id')}</TableCell> + <TableCell>{translate('dashboard.alerts.name')}</TableCell> + <TableCell>{translate('dashboard.alerts.count')}</TableCell> + <TableCell>{translate('dashboard.alerts.treshold')}</TableCell> + </TableRow> + {data['products_alerts'].map((value) => ( + <TableRow key={value.id}> + <TableCell>{value.id}</TableCell> + <TableCell component="th" scope="row"> + {value.name} + </TableCell> + <TableCell component="th" scope="row"> + {value.count} + </TableCell> + <TableCell component="th" scope="row"> + {value.alert_level} + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + )} + </CardContent> + </Card> + </Grid> + ) : ( + '' + ); +}; + +ProductsAlertsPanel.propTypes = { + data: PropTypes.any }; function TabPanel(props) { - const { children, value, index, ...other } = props; - - return ( - <div - role="tabpanel" - hidden={value !== index} - id={`simple-tabpanel-${index}`} - aria-labelledby={`simple-tab-${index}`} - {...other} - > - {value === index && ( - <>{children}</> - )} - </div> - ); + const { children, value, index, ...other } = props; + + return ( + <div role="tabpanel" hidden={value !== index} id={`simple-tabpanel-${index}`} aria-labelledby={`simple-tab-${index}`} {...other}> + {value === index && <>{children}</>} + </div> + ); } +TabPanel.propTypes = { + children: PropTypes.any, + value: PropTypes.number, + index: PropTypes.number +}; + function permMatch(userperms, elemperm) { - if (userperms === undefined) - return false; + if (userperms === undefined) return false; - const [resource, access] = elemperm.split("."); + const [resource, access] = elemperm.split('.'); - return userperms.includes("*.*") || userperms.includes(resource + ".*") || userperms.includes("*." + access) || userperms.includes(elemperm); + return userperms.includes('*.*') || userperms.includes(resource + '.*') || userperms.includes('*.' + access) || userperms.includes(elemperm); } -const StatisticsPanel = (props) => { - const [value, setValue] = React.useState(0); - const { permissions } = usePermissions(); - - const handleChange = (event, newValue) => { - setValue(newValue); - }; - - let i = 0; - - return (permMatch(permissions, "accounts.show") || permMatch(permissions, "sales.show") || permMatch(permissions, "users.show")) ? ( - <Grid item xs={12}> - <Card style={{ height: '100%' }}> - <Tabs - value={value} - onChange={handleChange} - indicatorColor="primary" - textColor="primary"> - {permMatch(permissions, "accounts.show") ? (<Tab label="Comptes" />) : ""} - {permMatch(permissions, "sales.show") ? (<Tab label="Stocks" />) : ""} - {permMatch(permissions, "users.show") ? (<Tab label="Vendeurs" />) : ""} - </Tabs> - {permMatch(permissions, "accounts.show") ? (<TabPanel value={value} index={i++}> - <AccountsStatistics /> - </TabPanel>) : ""} - {permMatch(permissions, "sales.show") ? (<TabPanel value={value} index={i++}> - <ProductsStatistics /> - </TabPanel>) : ""} - {permMatch(permissions, "users.show") ? (<TabPanel value={value} index={i++}> - <SellersStatistics /> - </TabPanel>) : ""} - </Card> - </Grid> - ) : ""; +const StatisticsPanel = () => { + const [value, setValue] = React.useState(0); + const { permissions } = usePermissions(); + + const handleChange = (event, newValue) => { + setValue(newValue); + }; + + let i = 0; + + return permMatch(permissions, 'accounts.show') || permMatch(permissions, 'sales.show') || permMatch(permissions, 'users.show') ? ( + <Grid item xs={12}> + <Card style={{ height: '100%' }}> + <Tabs value={value} onChange={handleChange} indicatorColor="primary" textColor="primary"> + {permMatch(permissions, 'accounts.show') ? <Tab label="Comptes" /> : ''} + {permMatch(permissions, 'sales.show') ? <Tab label="Stocks" /> : ''} + {permMatch(permissions, 'users.show') ? <Tab label="Vendeurs" /> : ''} + </Tabs> + {permMatch(permissions, 'accounts.show') ? ( + <TabPanel value={value} index={i++}> + <AccountsStatistics /> + </TabPanel> + ) : ( + '' + )} + {permMatch(permissions, 'sales.show') ? ( + <TabPanel value={value} index={i++}> + <ProductsStatistics /> + </TabPanel> + ) : ( + '' + )} + {permMatch(permissions, 'users.show') ? ( + <TabPanel value={value} index={i++}> + <SellersStatistics /> + </TabPanel> + ) : ( + '' + )} + </Card> + </Grid> + ) : ( + '' + ); }; const Dashboard = () => { - const translate = useTranslate(); - ColorProvider.shuffle(300); - - const { data, loading, error } = useQuery({ - type: 'getOne', - resource: 'dashboard', - payload: { id: 'home' } - }); - - if (loading) return <Loading />; - if (error) return <Error error={error} />; - if (!data) return null; - - return ( - <Grid container spacing={1}> - <Title title={translate('dashboard.welcome')} /> - {localStorage.getItem("dash.showAccounts") !== "false" && <AccountsPanel data={data} />} - {localStorage.getItem("dash.showAlerts") !== "false" && <ProductsAlertsPanel data={data} />} - {localStorage.getItem("dash.showGraphs") !== "false" && <StatisticsPanel />} - </Grid > - ); + const translate = useTranslate(); + ColorProvider.shuffle(300); + + const { data, loading, error } = useQuery({ + type: 'getOne', + resource: 'dashboard', + payload: { id: 'home' } + }); + + if (loading) return <Loading />; + if (error) return <Error error={error} />; + if (!data) return null; + + return ( + <Grid container spacing={1}> + <Title title={translate('dashboard.welcome')} /> + {localStorage.getItem('dash.showAccounts') !== 'false' && <AccountsPanel data={data} />} + {localStorage.getItem('dash.showAlerts') !== 'false' && <ProductsAlertsPanel data={data} />} + {localStorage.getItem('dash.showGraphs') !== 'false' && <StatisticsPanel />} + </Grid> + ); }; export default Dashboard; diff --git a/src/pages/statistics/AccountsStatistics.js b/src/pages/statistics/AccountsStatistics.js index f7a228ba1276dafc25241322cabc3dc8952312cd..c634d0a40cdc7ff3e30c60834ce519d568588766 100644 --- a/src/pages/statistics/AccountsStatistics.js +++ b/src/pages/statistics/AccountsStatistics.js @@ -1,109 +1,144 @@ import { CardHeader, Grid } from '@material-ui/core'; import { format } from 'date-fns'; -import { default as React } from "react"; +import PropTypes from 'prop-types'; +import { default as React } from 'react'; import { Error, Loading, useQuery, useTranslate } from 'react-admin'; import { CartesianGrid, Cell, Legend, Line, LineChart, Pie, PieChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; import ColorProvider from '../../providers/ColorProvider'; -const dateFormatter = date => { - return format(new Date(date * 1000), "dd/MM/yyyy"); +const dateFormatter = (date) => { + return format(new Date(date * 1000), 'dd/MM/yyyy'); }; -const formatter = (value, name, props) => { - return (value >= 0 ? '+' : '') + Number(value).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' }); +const formatter = (value) => { + return ( + (value >= 0 ? '+' : '') + + Number(value).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + }) + ); }; const CategoriesPiePanel = ({ data }) => { - const translate = useTranslate(); + const translate = useTranslate(); - return ('categories' in data ? ( - <> - <Grid item xs={12} md={6} lg={4}> - <CardHeader title={translate('dashboard.categories_positive.title')} /> - <ResponsiveContainer width="100%" height={200}> - <PieChart width={400} height={200}> - <Tooltip itemStyle={{ color: 'white !important' }} formatter={formatter} /> - <Pie dataKey={'value'} label={(d) => (d.name)} data={data.categories_positive} cx="50%" cy="50%" innerRadius={30} outerRadius={50}> - {data.categories_positive.map((entry, index) => ( - <Cell key={`cell-${index}`} fill={ColorProvider.randomSColor()} /> - ))} - </Pie> - </PieChart> - </ResponsiveContainer> - </Grid> - <Grid item xs={12} md={6} lg={4}> - <CardHeader title={translate('dashboard.categories_negative.title')} /> - <ResponsiveContainer width="100%" height={200}> - <PieChart width={400} height={200}> - <Tooltip itemStyle={{ color: 'white !important' }} formatter={formatter} /> - <Pie dataKey={'value'} label={(d) => (d.name)} data={data.categories_negative} cx="50%" cy="50%" innerRadius={30} outerRadius={50}> - {data.categories_negative.map((entry, index) => ( - <Cell key={`cell-${index}`} fill={ColorProvider.randomSColor()} /> - ))} - </Pie> - </PieChart> - </ResponsiveContainer> - </Grid> - <Grid item xs={12} md={6} lg={4}> - <CardHeader title={translate('dashboard.categories.title')} /> - <ResponsiveContainer width="100%" height={200}> - <PieChart width={400} height={200}> - <Tooltip itemStyle={{ color: 'white !important' }} formatter={formatter} /> - <Pie dataKey={'value'} label={(d) => (d.name)} data={data.categories} cx="50%" cy="50%" innerRadius={30} outerRadius={50}> - {data.categories.map((entry, index) => ( - <Cell key={`cell-${index}`} fill={ColorProvider.randomSColor()} /> - ))} - </Pie> - </PieChart> - </ResponsiveContainer> - </Grid> - </> - ) : ''); + return 'categories' in data ? ( + <> + <Grid item xs={12} md={6} lg={4}> + <CardHeader title={translate('dashboard.categories_positive.title')} /> + <ResponsiveContainer width="100%" height={200}> + <PieChart width={400} height={200}> + <Tooltip itemStyle={{ color: 'white !important' }} formatter={formatter} /> + <Pie dataKey={'value'} label={(d) => d.name} data={data.categories_positive} cx="50%" cy="50%" innerRadius={30} outerRadius={50}> + {data.categories_positive.map((entry, index) => ( + <Cell key={`cell-${index}`} fill={ColorProvider.randomSColor()} /> + ))} + </Pie> + </PieChart> + </ResponsiveContainer> + </Grid> + <Grid item xs={12} md={6} lg={4}> + <CardHeader title={translate('dashboard.categories_negative.title')} /> + <ResponsiveContainer width="100%" height={200}> + <PieChart width={400} height={200}> + <Tooltip itemStyle={{ color: 'white !important' }} formatter={formatter} /> + <Pie dataKey={'value'} label={(d) => d.name} data={data.categories_negative} cx="50%" cy="50%" innerRadius={30} outerRadius={50}> + {data.categories_negative.map((entry, index) => ( + <Cell key={`cell-${index}`} fill={ColorProvider.randomSColor()} /> + ))} + </Pie> + </PieChart> + </ResponsiveContainer> + </Grid> + <Grid item xs={12} md={6} lg={4}> + <CardHeader title={translate('dashboard.categories.title')} /> + <ResponsiveContainer width="100%" height={200}> + <PieChart width={400} height={200}> + <Tooltip itemStyle={{ color: 'white !important' }} formatter={formatter} /> + <Pie dataKey={'value'} label={(d) => d.name} data={data.categories} cx="50%" cy="50%" innerRadius={30} outerRadius={50}> + {data.categories.map((entry, index) => ( + <Cell key={`cell-${index}`} fill={ColorProvider.randomSColor()} /> + ))} + </Pie> + </PieChart> + </ResponsiveContainer> + </Grid> + </> + ) : ( + '' + ); +}; + +CategoriesPiePanel.propTypes = { + data: PropTypes.any }; const TransactionsGraphPanel = ({ data }) => { - const translate = useTranslate(); + const translate = useTranslate(); + + let start = new Date(); + start.setFullYear(start.getFullYear() - 1); - let start = new Date(); - start.setFullYear(start.getFullYear() - 1); + return 'transactions' in data ? ( + <Grid item xs={12}> + <CardHeader title={translate('dashboard.transactions.title')} /> + <ResponsiveContainer width={'99%'} height={400}> + <LineChart data={data.transactions.data} width={600} height={300} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}> + <XAxis + domain={[Math.floor(start.getTime() / 1000), Math.floor(Date.now() / 1000)]} + type="number" + dataKey={data.transactions.date_field} + tickFormatter={dateFormatter} + /> + <Tooltip labelFormatter={dateFormatter} formatter={formatter} /> + <Legend verticalAlign="top" /> + <YAxis /> + {Object.keys(data.transactions.accounts).map((key) => { + return ( + <Line + stroke={ColorProvider.randomSColor()} + connectNulls + type="monotone" + dataKey={key} + name={data.transactions.accounts[key]} + key={key} + strokeWidth={2} + dot={false} + /> + ); + })} + <CartesianGrid stroke="#505050" strokeDasharray="5 5" /> + </LineChart> + </ResponsiveContainer> + </Grid> + ) : ( + '' + ); +}; - return ('transactions' in data ? ( - <Grid item xs={12}> - <CardHeader title={translate('dashboard.transactions.title')} /> - <ResponsiveContainer width={'99%'} height={400}> - <LineChart data={data.transactions.data} width={600} height={300} - margin={{ top: 10, right: 30, left: 0, bottom: 0 }}> - <XAxis domain={[Math.floor(start.getTime() / 1000), Math.floor(Date.now() / 1000)]} type="number" dataKey={data.transactions.date_field} tickFormatter={dateFormatter} /> - <Tooltip labelFormatter={dateFormatter} formatter={formatter} /> - <Legend verticalAlign="top" /> - <YAxis /> - {Object.keys(data.transactions.accounts).map((key) => { - return <Line stroke={ColorProvider.randomSColor()} connectNulls type="monotone" dataKey={key} name={data.transactions.accounts[key]} key={key} strokeWidth={2} dot={false} />; - })} - <CartesianGrid stroke="#505050" strokeDasharray="5 5" /> - </LineChart> - </ResponsiveContainer> - </Grid> - ) : ''); -} +TransactionsGraphPanel.propTypes = { + data: PropTypes.any +}; -const AccountsStatistics = (props) => { - const { data, loading, error } = useQuery({ - type: 'getOne', - resource: 'dashboard', - payload: { id: 'accounts' } - }); +const AccountsStatistics = () => { + const { data, loading, error } = useQuery({ + type: 'getOne', + resource: 'dashboard', + payload: { id: 'accounts' } + }); - if (loading) return <Loading />; - if (error) return <Error error={error} />; - if (!data) return null; + if (loading) return <Loading />; + if (error) return <Error error={error} />; + if (!data) return null; - return ( - <Grid container> - <TransactionsGraphPanel data={data} /> - <CategoriesPiePanel data={data} /> - </Grid> - ); -} + return ( + <Grid container> + <TransactionsGraphPanel data={data} /> + <CategoriesPiePanel data={data} /> + </Grid> + ); +}; -export default AccountsStatistics; \ No newline at end of file +export default AccountsStatistics; diff --git a/src/pages/statistics/ProductsStatistics.js b/src/pages/statistics/ProductsStatistics.js index d68657b32f70519ad622c1e70e7927972d2e0361..9688182a555a6638a54326d3af7733c19934375d 100644 --- a/src/pages/statistics/ProductsStatistics.js +++ b/src/pages/statistics/ProductsStatistics.js @@ -1,69 +1,73 @@ import { CardHeader, Grid } from '@material-ui/core'; import _ from 'lodash'; -import { default as React } from "react"; +import PropTypes from 'prop-types'; +import { default as React } from 'react'; import { Error, Loading, useQuery, useTranslate } from 'react-admin'; import { Bar, BarChart, Cell, LabelList, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; import ColorProvider from '../../providers/ColorProvider'; const renderCustomizedLabel = (props) => { - const { - x, y, width, height, value, - } = props; + const { x, y, width, height, value } = props; - const fireOffset = width < 100; - const offset = fireOffset ? -180 : 5; - return ( - <text x={x + width - offset} y={y + height - (fireOffset ? 4 : 3)} fill={fireOffset ? "#fff" : "#303030"} textAnchor="end"> - {value} - </text> - ); + const fireOffset = width < 100; + const offset = fireOffset ? -180 : 5; + return ( + <text x={x + width - offset} y={y + height - (fireOffset ? 4 : 3)} fill={fireOffset ? '#fff' : '#303030'} textAnchor="end"> + {value} + </text> + ); }; const BestProductsBarPanel = ({ data }) => { - const translate = useTranslate(); - let products = []; + const translate = useTranslate(); + let products = []; - if ('products' in data) - products = _.orderBy(data.products, 'value', 'desc'); + if ('products' in data) products = _.orderBy(data.products, 'value', 'desc'); - return ('products' in data ? ( - <> - <Grid item xs={12}> - <CardHeader title={translate('dashboard.products.title')} /> - <ResponsiveContainer width={'99%'} height={products.length * 20 + 40}> - <BarChart width={100} height={800} data={products} layout="vertical"> - <XAxis type="number" /> - <YAxis type="category" dataKey="name" tick={false} /> - <Tooltip itemStyle={{ color: 'white !important' }} /> - <Bar dataKey="value" label={(d) => (d.name)} barSize={16}> - {products.map((entry, index) => ( - <Cell key={`cell-${index}`} fill={ColorProvider.randomSColor()} /> - ))} - <LabelList dataKey="name" content={renderCustomizedLabel} position="insideRight" style={{ fill: "white" }} /> - </Bar> - </BarChart> - </ResponsiveContainer> - </Grid> - </> - ) : ''); + return 'products' in data ? ( + <> + <Grid item xs={12}> + <CardHeader title={translate('dashboard.products.title')} /> + <ResponsiveContainer width={'99%'} height={products.length * 20 + 40}> + <BarChart width={100} height={800} data={products} layout="vertical"> + <XAxis type="number" /> + <YAxis type="category" dataKey="name" tick={false} /> + <Tooltip itemStyle={{ color: 'white !important' }} /> + <Bar dataKey="value" label={(d) => d.name} barSize={16}> + {products.map((entry, index) => ( + <Cell key={`cell-${index}`} fill={ColorProvider.randomSColor()} /> + ))} + <LabelList dataKey="name" content={renderCustomizedLabel} position="insideRight" style={{ fill: 'white' }} /> + </Bar> + </BarChart> + </ResponsiveContainer> + </Grid> + </> + ) : ( + '' + ); }; -const ProductsStatistics = (props) => { - const { data, loading, error } = useQuery({ - type: 'getOne', - resource: 'dashboard', - payload: { id: 'products' } - }); +BestProductsBarPanel.propTypes = { + data: PropTypes.any +}; + +const ProductsStatistics = () => { + const { data, loading, error } = useQuery({ + type: 'getOne', + resource: 'dashboard', + payload: { id: 'products' } + }); - if (loading) return <Loading />; - if (error) return <Error error={error} />; - if (!data) return null; + if (loading) return <Loading />; + if (error) return <Error error={error} />; + if (!data) return null; - return ( - <Grid container> - <BestProductsBarPanel data={data} /> - </Grid> - ); -} + return ( + <Grid container> + <BestProductsBarPanel data={data} /> + </Grid> + ); +}; -export default ProductsStatistics; \ No newline at end of file +export default ProductsStatistics; diff --git a/src/pages/statistics/SellersStatistics.js b/src/pages/statistics/SellersStatistics.js index 189e799c7ffd0646b822e5b2a68715cebb66db6b..e96f16c8b315748a028bc497c5742a018e412bd5 100644 --- a/src/pages/statistics/SellersStatistics.js +++ b/src/pages/statistics/SellersStatistics.js @@ -1,69 +1,73 @@ import { CardHeader, Grid } from '@material-ui/core'; import _ from 'lodash'; -import { default as React } from "react"; +import PropTypes from 'prop-types'; +import { default as React } from 'react'; import { Error, Loading, useQuery, useTranslate } from 'react-admin'; import { Bar, BarChart, Cell, LabelList, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; import ColorProvider from '../../providers/ColorProvider'; const renderCustomizedLabel = (props) => { - const { - x, y, width, height, value, - } = props; + const { x, y, width, height, value } = props; - const fireOffset = width < 100; - const offset = fireOffset ? -180 : 5; - return ( - <text x={x + width - offset} y={y + height - (fireOffset ? 4 : 3)} fill={fireOffset ? "#fff" : "#303030"} textAnchor="end"> - {value} - </text> - ); + const fireOffset = width < 100; + const offset = fireOffset ? -180 : 5; + return ( + <text x={x + width - offset} y={y + height - (fireOffset ? 4 : 3)} fill={fireOffset ? '#fff' : '#303030'} textAnchor="end"> + {value} + </text> + ); }; const BestSellersBarPanel = ({ data }) => { - const translate = useTranslate(); - let sellers = []; + const translate = useTranslate(); + let sellers = []; - if ('sellers' in data) - sellers = _.orderBy(data.sellers, 'value', 'desc'); + if ('sellers' in data) sellers = _.orderBy(data.sellers, 'value', 'desc'); - return ('sellers' in data ? ( - <> - <Grid item xs={12}> - <CardHeader title={translate('dashboard.sellers.title')} /> - <ResponsiveContainer width={'99%'} height={sellers.length * 20 + 40}> - <BarChart width={100} height={800} data={sellers} layout="vertical"> - <XAxis type="number" /> - <YAxis type="category" dataKey="name" tick={false} /> - <Tooltip itemStyle={{ color: 'white !important' }} /> - <Bar dataKey="value" label={(d) => (d.name)} barSize={16}> - {sellers.map((entry, index) => ( - <Cell key={`cell-${index}`} fill={ColorProvider.randomSColor()} /> - ))} - <LabelList dataKey="name" content={renderCustomizedLabel} position="insideRight" style={{ fill: "white" }} /> - </Bar> - </BarChart> - </ResponsiveContainer> - </Grid> - </> - ) : ''); + return 'sellers' in data ? ( + <> + <Grid item xs={12}> + <CardHeader title={translate('dashboard.sellers.title')} /> + <ResponsiveContainer width={'99%'} height={sellers.length * 20 + 40}> + <BarChart width={100} height={800} data={sellers} layout="vertical"> + <XAxis type="number" /> + <YAxis type="category" dataKey="name" tick={false} /> + <Tooltip itemStyle={{ color: 'white !important' }} /> + <Bar dataKey="value" label={(d) => d.name} barSize={16}> + {sellers.map((entry, index) => ( + <Cell key={`cell-${index}`} fill={ColorProvider.randomSColor()} /> + ))} + <LabelList dataKey="name" content={renderCustomizedLabel} position="insideRight" style={{ fill: 'white' }} /> + </Bar> + </BarChart> + </ResponsiveContainer> + </Grid> + </> + ) : ( + '' + ); }; -const SellersStatistics = (props) => { - const { data, loading, error } = useQuery({ - type: 'getOne', - resource: 'dashboard', - payload: { id: 'sellers' } - }); +BestSellersBarPanel.propTypes = { + data: PropTypes.any +}; + +const SellersStatistics = () => { + const { data, loading, error } = useQuery({ + type: 'getOne', + resource: 'dashboard', + payload: { id: 'sellers' } + }); - if (loading) return <Loading />; - if (error) return <Error error={error} />; - if (!data) return null; + if (loading) return <Loading />; + if (error) return <Error error={error} />; + if (!data) return null; - return ( - <Grid container> - <BestSellersBarPanel data={data} /> - </Grid> - ); -} + return ( + <Grid container> + <BestSellersBarPanel data={data} /> + </Grid> + ); +}; -export default SellersStatistics; \ No newline at end of file +export default SellersStatistics; diff --git a/src/providers/AuthProvider.js b/src/providers/AuthProvider.js index fa411ff2f4b5c7fbf3436f834e8212d140b04bc4..7489fc88783428e860d6de153baa2303fe4c1516 100644 --- a/src/providers/AuthProvider.js +++ b/src/providers/AuthProvider.js @@ -1,102 +1,108 @@ import axios from 'axios'; class AuthProvider { - /** - * Authenticates an user - */ - login({ user }) { - let data = user; - data["fullName"] = data.email; - localStorage.setItem('user', JSON.stringify(data)); - localStorage.setItem('logged', "1"); - return Promise.resolve(); - } + /** + * Authenticates an user + */ + login({ user }) { + let data = user; + data['fullName'] = data.email; + localStorage.setItem('user', JSON.stringify(data)); + localStorage.setItem('logged', '1'); + return Promise.resolve(); + } - /** - * Gets the identity of an user - */ - getIdentity() { - if (localStorage.getItem('user')) { - return Promise.resolve(JSON.parse(localStorage.getItem('user'))); - } else { - return Promise.reject(); - } + /** + * Gets the identity of an user + */ + getIdentity() { + if (localStorage.getItem('user')) { + return Promise.resolve(JSON.parse(localStorage.getItem('user'))); + } else { + return Promise.reject(); } + } - /** - * Updates the email of the user in the localstorage - */ - updateEmail(newEmail) { - if (localStorage.getItem('user')) { - const data = JSON.parse(localStorage.getItem('user')); - data.email = newEmail; - data.fullName = newEmail; - localStorage.setItem('user', JSON.stringify(data)); - return Promise.resolve(); - } else { - return Promise.reject(); - } + /** + * Updates the email of the user in the localstorage + */ + updateEmail(newEmail) { + if (localStorage.getItem('user')) { + const data = JSON.parse(localStorage.getItem('user')); + data.email = newEmail; + data.fullName = newEmail; + localStorage.setItem('user', JSON.stringify(data)); + return Promise.resolve(); + } else { + return Promise.reject(); } + } - /** - * Check if the error is an authentication error - */ - checkError(error) { - const status = error?.status; - if (status === 401 || status === 403 || error.message === "Unauthenticated.") { - localStorage.setItem('logged', "0"); - localStorage.removeItem('user'); - return Promise.reject(); - } - return Promise.resolve(); + /** + * Check if the error is an authentication error + */ + checkError(error) { + const status = error?.status; + if (status === 401 || status === 403 || error.message === 'Unauthenticated.') { + localStorage.setItem('logged', '0'); + localStorage.removeItem('user'); + return Promise.reject(); } + return Promise.resolve(); + } - /** - * Check if we are authenticated - */ - checkAuth(params) { - if (localStorage.getItem('user')) { - return Promise.resolve(); - } else if (localStorage.getItem('logged') === "0") { - return Promise.reject(); - } else { - return axios.get('/profile/me').then(response => { - let data = response.data.data; - data["fullName"] = data.email; - localStorage.setItem('user', JSON.stringify(data)); - return Promise.resolve(); - }).catch(error => { - console.log(error); - localStorage.setItem('logged', "0"); - localStorage.removeItem('user'); - return Promise.reject(error); - }); - } - } - - /** - * Log out - */ - logout() { - return axios.post('/logout').then(response => { - localStorage.removeItem('user'); - localStorage.setItem('logged', "0"); - return Promise.resolve(); - }).catch(error => { - return Promise.reject(); + /** + * Check if we are authenticated + */ + checkAuth() { + if (localStorage.getItem('user')) { + return Promise.resolve(); + } else if (localStorage.getItem('logged') === '0') { + return Promise.reject(); + } else { + return axios + .get('/profile/me') + .then((response) => { + let data = response.data.data; + data['fullName'] = data.email; + localStorage.setItem('user', JSON.stringify(data)); + return Promise.resolve(); + }) + .catch((error) => { + console.log(error); + localStorage.setItem('logged', '0'); + localStorage.removeItem('user'); + return Promise.reject(error); }); } + } + + /** + * Log out + */ + logout() { + return axios + .post('/logout') + .then(() => { + localStorage.removeItem('user'); + localStorage.setItem('logged', '0'); + return Promise.resolve(); + }) + .catch(() => { + return Promise.reject(); + }); + } - /** - * Get a list of the permissions - */ - getPermissions(params) { - if (localStorage.getItem('user')) { - return Promise.resolve(JSON.parse(localStorage.getItem('user'))["permissions"]); - } else { - return Promise.resolve([]); - } + /** + * Get a list of the permissions + */ + getPermissions() { + if (localStorage.getItem('user')) { + return Promise.resolve(JSON.parse(localStorage.getItem('user'))['permissions']); + } else { + return Promise.resolve([]); } + } } export default new AuthProvider(); diff --git a/src/providers/ColorProvider.js b/src/providers/ColorProvider.js index 57a43be6e3806c3c43be25e907545ab9d19f32c7..756c7d35366b2b683e4852e7fb64e553e8ba546c 100644 --- a/src/providers/ColorProvider.js +++ b/src/providers/ColorProvider.js @@ -1,4 +1,3 @@ - import amber from '@material-ui/core/colors/amber'; import blue from '@material-ui/core/colors/blue'; // import blueGrey from '@material-ui/core/colors/blueGrey'; @@ -21,60 +20,60 @@ import yellow from '@material-ui/core/colors/yellow'; import _ from 'lodash'; const colors = { - 'amber': amber, - 'blue': blue, - // 'blueGrey': blueGrey, - // 'brown': brown, - 'cyan': cyan, - 'deepOrange': deepOrange, - 'deepPurple': deepPurple, - 'green': green, - // 'grey': grey, - 'indigo': indigo, - 'lightBlue': lightBlue, - 'lightGreen': lightGreen, - 'lime': lime, - 'orange': orange, - 'pink': pink, - 'purple': purple, - 'red': red, - 'teal': teal, - 'yellow': yellow -} + amber: amber, + blue: blue, + // 'blueGrey': blueGrey, + // 'brown': brown, + cyan: cyan, + deepOrange: deepOrange, + deepPurple: deepPurple, + green: green, + // 'grey': grey, + indigo: indigo, + lightBlue: lightBlue, + lightGreen: lightGreen, + lime: lime, + orange: orange, + pink: pink, + purple: purple, + red: red, + teal: teal, + yellow: yellow +}; const randomProperty = (obj) => { - var keys = Object.keys(obj); - return obj[keys[keys.length * Math.random() << 0]]; + var keys = Object.keys(obj); + return obj[keys[(keys.length * Math.random()) << 0]]; }; class ColorProvider { - constructor() { - this.shuffled_colors = ['#FF0000']; - this.counter = 0; - } + constructor() { + this.shuffled_colors = ['#FF0000']; + this.counter = 0; + } - randomColor(shade) { - return randomProperty(colors)[shade]; - } + randomColor(shade) { + return randomProperty(colors)[shade]; + } - randomSColor() { - this.counter = (this.counter + 1) % this.shuffled_colors.length; - return this.shuffled_colors[this.counter]; - } + randomSColor() { + this.counter = (this.counter + 1) % this.shuffled_colors.length; + return this.shuffled_colors[this.counter]; + } - shuffledColors(shade) { - let color_arr = []; - for (let key in colors) { - color_arr.push(colors[key][shade]); - } - return _.shuffle(color_arr); + shuffledColors(shade) { + let color_arr = []; + for (let key in colors) { + color_arr.push(colors[key][shade]); } + return _.shuffle(color_arr); + } - shuffle(shade) { - this.shuffled_colors = this.shuffledColors(shade); - this.counter = 0; - } + shuffle(shade) { + this.shuffled_colors = this.shuffledColors(shade); + this.counter = 0; + } } const instance = new ColorProvider(); -export default instance; \ No newline at end of file +export default instance; diff --git a/src/providers/DataProvider.js b/src/providers/DataProvider.js index d4eb36ba19feb10dc78a1ac5198d0be4d61ed025..75139cfea47b723d61fead4bf7af1491ed4639a3 100644 --- a/src/providers/DataProvider.js +++ b/src/providers/DataProvider.js @@ -2,145 +2,189 @@ import axios from 'axios'; import _ from 'underscore'; class DataProvider { - reload(resource) { - return axios.get('/' + resource + '/reload').then(response => { - return Promise.resolve({ 'data': {} }); - }).catch(this.__handleError); + reload(resource) { + return axios + .get('/' + resource + '/reload') + .then(() => { + return Promise.resolve({ data: {} }); + }) + .catch(this.__handleError); + } + + archive(resource) { + return axios + .get('/' + resource + '/archive') + .then(() => { + return Promise.resolve({ data: {} }); + }) + .catch(this.__handleError); + } + + getList(resource, params) { + let filter = ''; + for (let key in params.filter) { + if (params.filter[key] === true) params.filter[key] = 1; + if (params.filter[key] === false) params.filter[key] = 0; + + filter += '&filter[' + encodeURIComponent(key) + ']=' + encodeURIComponent(params.filter[key]); } - archive(resource) { - return axios.get('/' + resource + '/archive').then(response => { - return Promise.resolve({ 'data': {} }); - }).catch(this.__handleError); + return axios + .get( + '/' + + resource + + '?per_page=' + + params.pagination.perPage + + '&page=' + + params.pagination.page + + '&order_by=' + + params.sort.field + + '&order_sort=' + + params.sort.order + + filter + ) + .then((response) => { + return response.data; + }) + .catch(this.__handleError); + } + + getAll(resource, params) { + let filter = ''; + for (let key in params.filter) { + if (params.filter[key] === true) params.filter[key] = 1; + if (params.filter[key] === false) params.filter[key] = 0; + + filter += '?filter[' + encodeURIComponent(key) + ']=' + encodeURIComponent(params.filter[key]); } - getList(resource, params) { - let filter = ""; - for (let key in params.filter) { - if (params.filter[key] === true) params.filter[key] = 1; - if (params.filter[key] === false) params.filter[key] = 0; - - filter += "&filter[" + encodeURIComponent(key) + "]=" + encodeURIComponent(params.filter[key]); - } - - return axios.get('/' + resource + - '?per_page=' + params.pagination.perPage + - '&page=' + params.pagination.page + - '&order_by=' + params.sort.field + - '&order_sort=' + params.sort.order + - filter).then(response => { - return response.data; - }).catch(this.__handleError); - } - - getAll(resource, params) { - let filter = ""; - for (let key in params.filter) { - if (params.filter[key] === true) params.filter[key] = 1; - if (params.filter[key] === false) params.filter[key] = 0; - - filter += "?filter[" + encodeURIComponent(key) + "]=" + encodeURIComponent(params.filter[key]); - } - - return axios.get('/' + resource + - filter).then(response => { - return response.data; - }).catch(this.__handleError); - } - - getOne(resource, { id }) { - return axios.get('/' + resource + '/' + id).then(response => { - return response.data; - }).catch(this.__handleError); + return axios + .get('/' + resource + filter) + .then((response) => { + return response.data; + }) + .catch(this.__handleError); + } + + getOne(resource, { id }) { + return axios + .get('/' + resource + '/' + id) + .then((response) => { + return response.data; + }) + .catch(this.__handleError); + } + + export(resource, { id }) { + return axios + .get('/' + resource + '/' + id + '/export') + .then((response) => { + return response.data; + }) + .catch(this.__handleError); + } + + getMany(resource, { ids }) { + let parameters = ''; + for (let i = 0; i < ids.length; i++) { + if (i === 0) { + parameters += '?ids[]=' + ids[i]; + } else { + parameters += '&ids[]=' + ids[i]; + } } - export(resource, { id }) { - return axios.get('/' + resource + '/' + id + '/export').then(response => { - return response.data; - }).catch(this.__handleError); - } - - getMany(resource, { ids }) { - let parameters = ""; - for (let i = 0; i < ids.length; i++) { - if (i === 0) { - parameters += "?ids[]=" + ids[i]; - } else { - parameters += "&ids[]=" + ids[i]; - } - } - - return axios.get('/' + resource + parameters).then(response => { - return response.data; - }).catch(this.__handleError); + return axios + .get('/' + resource + parameters) + .then((response) => { + return response.data; + }) + .catch(this.__handleError); + } + + deleteMany(resource, { ids }) { + let parameters = ''; + for (let i = 0; i < ids.length; i++) { + if (i === 0) { + parameters += '?ids[]=' + ids[i]; + } else { + parameters += '&ids[]=' + ids[i]; + } } - deleteMany(resource, { ids }) { - let parameters = ""; - for (let i = 0; i < ids.length; i++) { - if (i === 0) { - parameters += "?ids[]=" + ids[i]; - } else { - parameters += "&ids[]=" + ids[i]; - } - } - - return axios.delete('/' + resource + parameters).then(response => { - return response.data; - }).catch(this.__handleError); - } - - create(resource, { data }) { - return axios.post('/' + resource, data).then(response => { - return response.data; - }).catch(this.__handleError); - } - - delete(resource, { id }) { - return axios.delete('/' + resource + "/" + id).then(response => { - return response.data; - }).catch(this.__handleError); - } - - update(resource, { id, data, previousData }) { - let diff = _.omit(data, function (v, k) { return previousData[k] === v; }); - - return axios.put('/' + resource + "/" + id, diff).then(response => { - return response.data; - }).catch(this.__handleError); - } - - updateMany(resource, { ids, data }) { - let parameters = ""; - for (let i = 0; i < ids.length; i++) { - if (i === 0) { - parameters += "?ids[]=" + ids[i]; - } else { - parameters += "&ids[]=" + ids[i]; - } - } - - return axios.put('/' + resource + parameters, data).then(response => { - return response.data; - }).catch(this.__handleError); + return axios + .delete('/' + resource + parameters) + .then((response) => { + return response.data; + }) + .catch(this.__handleError); + } + + create(resource, { data }) { + return axios + .post('/' + resource, data) + .then((response) => { + return response.data; + }) + .catch(this.__handleError); + } + + delete(resource, { id }) { + return axios + .delete('/' + resource + '/' + id) + .then((response) => { + return response.data; + }) + .catch(this.__handleError); + } + + update(resource, { id, data, previousData }) { + let diff = _.omit(data, function (v, k) { + return previousData[k] === v; + }); + + return axios + .put('/' + resource + '/' + id, diff) + .then((response) => { + return response.data; + }) + .catch(this.__handleError); + } + + updateMany(resource, { ids, data }) { + let parameters = ''; + for (let i = 0; i < ids.length; i++) { + if (i === 0) { + parameters += '?ids[]=' + ids[i]; + } else { + parameters += '&ids[]=' + ids[i]; + } } - __handleError(error) { - if (error?.response?.data?.message) { - let message = ""; - if (error?.response?.data?.errors !== undefined) { - for (let ms in error.response.data.errors) { - for (let m of error.response.data.errors[ms]) { - message += m + "\n"; - } - } - } else { - message = error?.response?.data?.message; - } - return Promise.reject(new Error(message)); + return axios + .put('/' + resource + parameters, data) + .then((response) => { + return response.data; + }) + .catch(this.__handleError); + } + + __handleError(error) { + if (error?.response?.data?.message) { + let message = ''; + if (error?.response?.data?.errors !== undefined) { + for (let ms in error.response.data.errors) { + for (let m of error.response.data.errors[ms]) { + message += m + '\n'; + } } - return Promise.reject(new Error(error?.message)); + } else { + message = error?.response?.data?.message; + } + return Promise.reject(new Error(message)); } + return Promise.reject(new Error(error?.message)); + } } -export default new DataProvider(); \ No newline at end of file +export default new DataProvider(); diff --git a/src/providers/I18nProvider.js b/src/providers/I18nProvider.js index f7bbeb9c3265276319b3505a0a54b230e99e51b1..333c14d3c7047cf398fdf48283aad561430350bb 100644 --- a/src/providers/I18nProvider.js +++ b/src/providers/I18nProvider.js @@ -4,512 +4,515 @@ import polyglotI18nProvider from 'ra-i18n-polyglot'; import frenchMessages from 'ra-language-french'; const messages = { - 'fr': merge(frenchMessages, { - ra: { - auth: { - email: "Email", - forgot: "Mot de passe oublié ?", - back: "Retour", - cancel: "Annuler", - reset_password: "Réinitaliser le mot de passe", - set_password: "Définir le mot de passe", - password_confirmation: "Confirmation", - new_password: "Nouveau mot de passe", - current_password: "Mot de passe actuel", - change_password: "Changer de mot de passe", - password_changed: "Mot de passe modifié!", - "2fa": { - ask: "Voulez-vous vraiment activer l'Authentification à deux Facteurs ?", - enable: "Activer l'A2F", - scan: "Veuillez scanner le QR code dans votre application d'A2F", - verify: "Entrez un code d'A2F pour valider", - verification_code: "Code d'A2F", - success: "L'Authentification à deux Facteurs et maintenant active.", - validate: "Terminé", - disabled: "L'Authentification à deux Facteurs a été désactivée.", - disable: "Désactiver l'A2F", - askdisable: "Voulez-vous vraiment désactiver l'Authentification à deux Facteurs ?", - login: "Continue" - }, - token: { - new: { - ask: "Voulez vous vraiment créer un nouveau token d'API ?", - create: "Créer", - name: "Nom du token", - scan: "Voici votre nouveau token. Vous pouvez maintenant le scanner dans l'App ou le noter. Gardez le précieusement : vous ne le reverez jamais.", - validate: "Terminé" - }, - clear: { - ask: "Voulez-vous vraiment effacer tous les tokens ? Attention: Vous devrez générer un nouveau token pour vous connecter dans l'Application.", - clear: "Effacer", - cleared: "Vos tokens ont étés effacés" - } - } - }, - notification: { - exported: "Élement exporté!" - } + fr: merge(frenchMessages, { + ra: { + auth: { + email: 'Email', + forgot: 'Mot de passe oublié ?', + back: 'Retour', + cancel: 'Annuler', + reset_password: 'Réinitaliser le mot de passe', + set_password: 'Définir le mot de passe', + password_confirmation: 'Confirmation', + new_password: 'Nouveau mot de passe', + current_password: 'Mot de passe actuel', + change_password: 'Changer de mot de passe', + password_changed: 'Mot de passe modifié!', + '2fa': { + ask: "Voulez-vous vraiment activer l'Authentification à deux Facteurs ?", + enable: "Activer l'A2F", + scan: "Veuillez scanner le QR code dans votre application d'A2F", + verify: "Entrez un code d'A2F pour valider", + verification_code: "Code d'A2F", + success: "L'Authentification à deux Facteurs et maintenant active.", + validate: 'Terminé', + disabled: "L'Authentification à deux Facteurs a été désactivée.", + disable: "Désactiver l'A2F", + askdisable: "Voulez-vous vraiment désactiver l'Authentification à deux Facteurs ?", + login: 'Continue' }, - copying: { - title: "Licence et attributions", - notice: "Avis de droit d'auteur", - agpl: { - line1: "Copyright © 2021 - Association Amicale des Étudiants et Anciens Étudiants du Département Informatique de l'IUT Robert Schuman et al.", - line2: "Seb est un logiciel libre : vous pouvez le redistribuer et/ou le modifier selon les termes de la Licence Publique Générale GNU Affero telle que publiée par la Free Software Foundation, soit la version 3 de la Licence, soit (à votre choix) toute version ultérieure. ", - line3: { - start: "Ce programme est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE ; sans même la garantie implicite de QUALITÉ MARCHANDE ou D'ADAPTATION À UN USAGE PARTICULIER. Conformément aux exigences de la licence susmentionnée, vous pouvez obtenir une copie du code source de Seb", - here: "ici", - continue: "et lire la licence dans son intégralité (en anglais)", - there: "là ", - end: "." - } - }, - dependencies: "Dependences", - dependencies2: "Seb n'est pas construit à partir de zéro. Nos dépendances les plus notables sont : ", - released: "Distribué sous", - license: { - mit: "Licence MIT" - }, - contributors: "Contributeurs", - bottommessage: "Copyright © 2021 Amicale CORE - Publié sous licence" + token: { + new: { + ask: "Voulez vous vraiment créer un nouveau token d'API ?", + create: 'Créer', + name: 'Nom du token', + scan: "Voici votre nouveau token. Vous pouvez maintenant le scanner dans l'App ou le noter. Gardez le précieusement : vous ne le reverez jamais.", + validate: 'Terminé' + }, + clear: { + ask: "Voulez-vous vraiment effacer tous les tokens ? Attention: Vous devrez générer un nouveau token pour vous connecter dans l'Application.", + clear: 'Effacer', + cleared: 'Vos tokens ont étés effacés' + } + } + }, + notification: { + exported: 'Élement exporté!' + } + }, + copying: { + title: 'Licence et attributions', + notice: "Avis de droit d'auteur", + agpl: { + line1: "Copyright © 2021 - Association Amicale des Étudiants et Anciens Étudiants du Département Informatique de l'IUT Robert Schuman et al.", + line2: + 'Seb est un logiciel libre : vous pouvez le redistribuer et/ou le modifier selon les termes de la Licence Publique Générale GNU Affero telle que publiée par la Free Software Foundation, soit la version 3 de la Licence, soit (à votre choix) toute version ultérieure. ', + line3: { + start: + "Ce programme est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE ; sans même la garantie implicite de QUALITÉ MARCHANDE ou D'ADAPTATION À UN USAGE PARTICULIER. Conformément aux exigences de la licence susmentionnée, vous pouvez obtenir une copie du code source de Seb", + here: 'ici', + continue: 'et lire la licence dans son intégralité (en anglais)', + there: 'là ', + end: '.' + } + }, + dependencies: 'Dependences', + dependencies2: "Seb n'est pas construit à partir de zéro. Nos dépendances les plus notables sont : ", + released: 'Distribué sous', + license: { + mit: 'Licence MIT' + }, + contributors: 'Contributeurs', + bottommessage: 'Copyright © 2021 Amicale CORE - Publié sous licence' + }, + menu: { + user: { + copying: 'Redistribution', + profile: 'Profil' + }, + left: { + dashboard: 'Accueil', + sell: 'Vendre', + buy: 'Acheter', + count_money: 'Compter les comptes', + count_stocks: 'Compter les stocks', + add_participant: 'Ajouter un participant', + members: 'Membres', + stocks: 'Stocks', + products: 'Produits', + categories: 'Catégories', + products_counts: 'Comptages de stocks', + movements: 'Mouvements de stocks', + accounting: 'Comptabilité', + accounts: 'Comptes', + accounts_counts: 'Comptages de comptes', + automated_transactions: 'Transactions Automatiques', + transactions: 'Transactions', + transferts: 'Virements', + sales: 'Ventes', + purchases: 'Achats', + users: 'Utilisateurs', + logout: 'Déconnexion', + create: 'Créer', + show: 'Voir', + products_categories: 'Catégories de Produits', + transactions_categories: 'Catégories de Transactions', + profile: 'Profil', + archives: 'Archives', + archived_members: 'Membres Archivés', + humans: 'Gens', + people: 'Personnes', + personal_accounts: 'Comptes Personnels', + personal_transactions: 'Transactions Personnelles', + personal_refills: 'Recharge Compte Perso.', + refill: 'Recharger', + events: 'Évènements', + participations: 'Participants' + } + }, + resources: { + personal_refills: { + name: 'Recharger Compte Perso.', + fields: { + amount: 'Montant' + } + }, + personal_transactions: { + name: 'Transaction Personnelle |||| Transactions Personnelles', + fields: { + id: '#', + amount: 'Montant', + personal_account_id: 'Compte Personel', + transaction_id: 'Transaction', + user_id: 'Créateur', + created_at: 'Créé le', + updated_at: 'Modifié le' + } + }, + personal_accounts: { + name: 'Compte Personnel |||| Comptes Personnels', + fields: { + id: '#', + person_id: 'Personne', + balance: 'Solde', + created_at: 'Créé le', + updated_at: 'Modifié le' + } + }, + members: { + name: 'Membre |||| Membres', + fields: { + id: '#', + paid: 'Payé', + person_id: 'Personne', + transaction: 'Transaction', + created_at: 'Créé le', + updated_at: 'Modifié le' }, - menu: { - user: { - copying: "Redistribution", - profile: "Profil" - }, - left: { - dashboard: "Accueil", - sell: "Vendre", - buy: "Acheter", - count_money: "Compter les comptes", - count_stocks: "Compter les stocks", - add_participant: "Ajouter un participant", - members: "Membres", - stocks: "Stocks", - products: "Produits", - categories: "Catégories", - products_counts: "Comptages de stocks", - movements: "Mouvements de stocks", - accounting: "Comptabilité", - accounts: "Comptes", - accounts_counts: "Comptages de comptes", - automated_transactions: "Transactions Automatiques", - transactions: "Transactions", - transferts: "Virements", - sales: "Ventes", - purchases: "Achats", - users: "Utilisateurs", - logout: "Déconnexion", - create: "Créer", - show: "Voir", - products_categories: "Catégories de Produits", - transactions_categories: "Catégories de Transactions", - profile: "Profil", - archives: "Archives", - archived_members: "Membres Archivés", - humans: "Gens", - people: "Personnes", - personal_accounts: "Comptes Personnels", - personal_transactions: "Transactions Personnelles", - personal_refills: "Recharge Compte Perso.", - refill: "Recharger", - events: "Évènements", - participations: "Participants" - } + mark_payed: 'Marqué comme payé' + }, + people: { + name: 'Personne |||| Personnes', + fields: { + firstname: 'Prénom', + lastname: 'Nom', + discord_id: 'ID Discord', + created_at: 'Créé le', + updated_at: 'Modifié le' + } + }, + archived_members: { + name: 'Membre Archivé |||| Membres Archivés', + fields: { + id: '#', + person_id: 'Personne', + paid: 'Payé', + transaction: 'Transaction', + created_at: 'Créé le', + updated_at: 'Modifié le', + school_year: 'Année Scolaire' + } + }, + products: { + name: 'Produit |||| Produits', + fields: { + id: '#', + name: 'Nom', + category_id: 'Catégorie', + price: 'Prix', + count: 'Nombre', + alert_level: "Niveau d'alerte", + created_at: 'Créé le', + updated_at: 'Modifié le', + salable: 'Vendable' }, - resources: { - personal_refills: { - name: 'Recharger Compte Perso.', - fields: { - amount: 'Montant' - } - }, - personal_transactions: { - name: 'Transaction Personnelle |||| Transactions Personnelles', - fields: { - id: '#', - amount: 'Montant', - personal_account_id: 'Compte Personel', - transaction_id: 'Transaction', - user_id: 'Créateur', - created_at: 'Créé le', - updated_at: 'Modifié le' - } - }, - personal_accounts: { - name: 'Compte Personnel |||| Comptes Personnels', - fields: { - id: '#', - person_id: 'Personne', - balance: 'Solde', - created_at: 'Créé le', - updated_at: 'Modifié le' - } - }, - members: { - name: 'Membre |||| Membres', - fields: { - id: '#', - paid: 'Payé', - person_id: 'Personne', - transaction: 'Transaction', - created_at: 'Créé le', - updated_at: 'Modifié le' - }, - mark_payed: "Marqué comme payé" - }, - people: { - name: 'Personne |||| Personnes', - fields: { - firstname: 'Prénom', - lastname: 'Nom', - discord_id: 'ID Discord', - created_at: 'Créé le', - updated_at: 'Modifié le' - } - }, - archived_members: { - name: 'Membre Archivé |||| Membres Archivés', - fields: { - id: '#', - person_id: 'Personne', - paid: 'Payé', - transaction: 'Transaction', - created_at: 'Créé le', - updated_at: 'Modifié le', - school_year: 'Année Scolaire' - } - }, - products: { - name: 'Produit |||| Produits', - fields: { - id: '#', - name: 'Nom', - category_id: 'Catégorie', - price: 'Prix', - count: 'Nombre', - alert_level: 'Niveau d\'alerte', - created_at: 'Créé le', - updated_at: 'Modifié le', - salable: 'Vendable' - }, - mark_salabe: 'Marquer comme Vendable', - mark_unsalable: 'Marquer comme Invendable' - }, - products_categories: { - name: 'Catégorie de Produits |||| Catégories de Produits', - fields: { - id: '#', - name: 'Nom', - created_at: 'Créé le', - updated_at: 'Modifié le' - } - }, - products_counts: { - name: 'Comptage de Stocks |||| Compatages de Stocks', - fields: { - id: '#', - movement_id: 'Mouvement', - 'movement.user_id': 'Créateur', - created_at: 'Créé le', - updated_at: 'Modifié le', - data: 'Produits' - } - }, - movements: { - name: 'Mouvement |||| Mouvements', - fields: { - id: '#', - name: 'Nom', - rectification: 'Réctification', - user_id: 'Créateur', - created_at: 'Créé le', - updated_at: 'Modifié le', - product_id: 'Produit', - 'product.name': 'Nom', - count: 'Différence', - products: 'Produits', - diff: 'Différence' - } - }, - accounts: { - name: 'Compte |||| Comptes', - fields: { - id: '#', - name: 'Nom', - iban: 'IBAN', - bic: 'BIC', - balance: 'Solde', - created_at: 'Créé le', - updated_at: 'Modifié le' - } - }, - accounts_counts: { - name: 'Comptage de Comptes |||| Compatages de Comptes', - fields: { - id: '#', - type: 'Type', - balance: 'Solde', - transaction: 'Transaction', - 'transaction.account_id': 'Compte', - 'transaction.user_id': 'Créateur', - created_at: 'Créé le', - updated_at: 'Modifié le', - data: 'Données', - value: 'Valeur', - count: 'Nombre', - total: 'Total' - } - }, - transactions: { - name: 'Transaction |||| Transactions', - fields: { - id: '#', - name: 'Nom', - amount: 'Montant', - rectification: 'Réctification', - account_id: 'Compte', - category_id: 'Catégorie', - user_id: 'Créateur', - created_at: 'Créé le', - updated_at: 'Modifié le', - } - }, - automated_transactions: { - name: 'Transaction Automatique |||| Transactions Automatiques', - fields: { - id: '#', - name: 'Nom', - amount: 'Montant', - rectification: 'Réctification', - account_id: 'Compte', - category_id: 'Catégorie', - user_id: 'Créateur', - created_at: 'Créé le', - updated_at: 'Modifié le', - frequency: 'Fréquence', - day: 'Jour' - }, - frequencies: { - yearly: 'Annuelle', - monthly: 'Mensuelle', - weekly: 'Hébdomadaire', - dayly: 'Journalière' - } - }, - transferts: { - name: 'Virement |||| Virements', - fields: { - id: '#', - amount: 'Montant', - 'sub_transaction.user_id': 'Créateur', - 'add_transaction.amount': 'Montant', - 'sub_transaction.account_id': 'Depuis', - 'add_transaction.account_id': 'Vers', - sub_transaction_id: 'Transaction soustraction', - add_transaction_id: 'Transaction addition', - from_account_id: 'Depuis', - to_account_id: 'Vers', - created_at: 'Créé le', - updated_at: 'Modifié le' - } - }, - transactions_categories: { - name: 'Catégorie de Transactions |||| Catégories de Transactions', - fields: { - id: '#', - name: 'Nom', - created_at: 'Créé le', - updated_at: 'Modifié le', - } - }, - sales: { - name: 'Vente |||| Ventes', - fields: { - id: '#', - created_at: 'Créé le', - updated_at: 'Modifié le', - 'transaction.amount': 'Montant', - 'transaction.user_id': 'Créateur', - movement_id: 'Mouvement', - transaction_id: 'Transaction', - 'movement.products': 'Produits', - person_id: 'Personne', - product_id: 'Produit', - count: 'Différence' - } - }, - events: { - name: "Évènement |||| Évènements", - fields: { - id: '#', - name: 'Nom', - location: "Lieux", - inscriptions_closed_at: "Fermeture des inscriptions", - start: "Début", - end: "Fin", - price: "Prix", - price_member: "Prix (membre)", - category_id: 'Catégorie', - created_at: 'Créé le', - updated_at: 'Modifié le', - data: "Structure des données", - type: "Type" - } - }, - purchases: { - name: 'Achat |||| Achats', - fields: { - id: '#', - name: 'Nom', - created_at: 'Créé le', - updated_at: 'Modifié le', - 'transaction.amount': 'Montant', - 'transaction.user_id': 'Créateur', - movement_id: 'Mouvement', - transaction_id: 'Transaction', - 'movement_id.products': 'Produits', - 'movement.products': 'Produits', - product_id: 'Produit', - count: 'Différence', - account_id: 'Compte', - category_id: 'Catégorie', - amount: 'Montant', - has_products: 'L\'achat concerne des produits' - } - }, - users: { - name: 'Utilisateur |||| Utilisateurs', - fields: { - id: '#', - username: 'Login', - password: 'Mot de passe', - person_id: 'Personne', - email: 'Adresse email', - permissions: 'Permissions', - created_at: 'Créé le', - updated_at: 'Modifié le', - password_changed_at: 'MDP changé le' - } - }, - participations: { - name: 'Participation |||| Participations', - fields: { - id: "#", - created_at: 'Créé le', - updated_at: 'Modifié le', - event_id: "Évènement", - person_id: "Personne", - transaction_id: "Transaction" - } - }, - profile: { - name: 'Profile', - fields: { - username: 'Login', - firstname: 'Prénom', - lastname: 'Nom', - email: 'Adresse email' - }, - me: "Mon Profil", - permissions: { - title: "Permissions", - }, - password: { - title: "Mot de passe", - last_change: "Dernier changement", - never: "Jamais", - change: 'Changer le mot de passe' - }, - '2fa': { - title: 'Authentification à deux Facteurs', - enable: 'Activer l\'A2F', - disable: 'Désactiver l\'A2F', - enabled: 'L\'A2F est activée', - disabled: 'L\'A2F est désactivée', - status: 'Status de l\'A2F', - recovery: 'Codes de récupération', - recovery_message: 'Ces codes vous permettent de récupérer votre compte en ça de perte de votre périphérique d\'A2F. Veuillez les conserver précieusement.' - }, - tokens: { - title: 'Tokens d\'API', - id: '#', - name: 'Nom', - last_used_at: 'Dernière utilisation', - created_at: 'Créé le', - clear: 'Effacer les tokens', - new: 'Nouveau token', - none: 'Vous n\'avez pas de tokens d\'API' - } - } + mark_salabe: 'Marquer comme Vendable', + mark_unsalable: 'Marquer comme Invendable' + }, + products_categories: { + name: 'Catégorie de Produits |||| Catégories de Produits', + fields: { + id: '#', + name: 'Nom', + created_at: 'Créé le', + updated_at: 'Modifié le' + } + }, + products_counts: { + name: 'Comptage de Stocks |||| Compatages de Stocks', + fields: { + id: '#', + movement_id: 'Mouvement', + 'movement.user_id': 'Créateur', + created_at: 'Créé le', + updated_at: 'Modifié le', + data: 'Produits' + } + }, + movements: { + name: 'Mouvement |||| Mouvements', + fields: { + id: '#', + name: 'Nom', + rectification: 'Réctification', + user_id: 'Créateur', + created_at: 'Créé le', + updated_at: 'Modifié le', + product_id: 'Produit', + 'product.name': 'Nom', + count: 'Différence', + products: 'Produits', + diff: 'Différence' + } + }, + accounts: { + name: 'Compte |||| Comptes', + fields: { + id: '#', + name: 'Nom', + iban: 'IBAN', + bic: 'BIC', + balance: 'Solde', + created_at: 'Créé le', + updated_at: 'Modifié le' + } + }, + accounts_counts: { + name: 'Comptage de Comptes |||| Compatages de Comptes', + fields: { + id: '#', + type: 'Type', + balance: 'Solde', + transaction: 'Transaction', + 'transaction.account_id': 'Compte', + 'transaction.user_id': 'Créateur', + created_at: 'Créé le', + updated_at: 'Modifié le', + data: 'Données', + value: 'Valeur', + count: 'Nombre', + total: 'Total' + } + }, + transactions: { + name: 'Transaction |||| Transactions', + fields: { + id: '#', + name: 'Nom', + amount: 'Montant', + rectification: 'Réctification', + account_id: 'Compte', + category_id: 'Catégorie', + user_id: 'Créateur', + created_at: 'Créé le', + updated_at: 'Modifié le' + } + }, + automated_transactions: { + name: 'Transaction Automatique |||| Transactions Automatiques', + fields: { + id: '#', + name: 'Nom', + amount: 'Montant', + rectification: 'Réctification', + account_id: 'Compte', + category_id: 'Catégorie', + user_id: 'Créateur', + created_at: 'Créé le', + updated_at: 'Modifié le', + frequency: 'Fréquence', + day: 'Jour' }, - actions: { - recalculate: "Recalculer", - clear: "Réinitialiser", - archive: "Archiver" + frequencies: { + yearly: 'Annuelle', + monthly: 'Mensuelle', + weekly: 'Hébdomadaire', + dayly: 'Journalière' + } + }, + transferts: { + name: 'Virement |||| Virements', + fields: { + id: '#', + amount: 'Montant', + 'sub_transaction.user_id': 'Créateur', + 'add_transaction.amount': 'Montant', + 'sub_transaction.account_id': 'Depuis', + 'add_transaction.account_id': 'Vers', + sub_transaction_id: 'Transaction soustraction', + add_transaction_id: 'Transaction addition', + from_account_id: 'Depuis', + to_account_id: 'Vers', + created_at: 'Créé le', + updated_at: 'Modifié le' + } + }, + transactions_categories: { + name: 'Catégorie de Transactions |||| Catégories de Transactions', + fields: { + id: '#', + name: 'Nom', + created_at: 'Créé le', + updated_at: 'Modifié le' + } + }, + sales: { + name: 'Vente |||| Ventes', + fields: { + id: '#', + created_at: 'Créé le', + updated_at: 'Modifié le', + 'transaction.amount': 'Montant', + 'transaction.user_id': 'Créateur', + movement_id: 'Mouvement', + transaction_id: 'Transaction', + 'movement.products': 'Produits', + person_id: 'Personne', + product_id: 'Produit', + count: 'Différence' + } + }, + events: { + name: 'Évènement |||| Évènements', + fields: { + id: '#', + name: 'Nom', + location: 'Lieux', + inscriptions_closed_at: 'Fermeture des inscriptions', + start: 'Début', + end: 'Fin', + price: 'Prix', + price_member: 'Prix (membre)', + category_id: 'Catégorie', + created_at: 'Créé le', + updated_at: 'Modifié le', + data: 'Structure des données', + type: 'Type' + } + }, + purchases: { + name: 'Achat |||| Achats', + fields: { + id: '#', + name: 'Nom', + created_at: 'Créé le', + updated_at: 'Modifié le', + 'transaction.amount': 'Montant', + 'transaction.user_id': 'Créateur', + movement_id: 'Mouvement', + transaction_id: 'Transaction', + 'movement_id.products': 'Produits', + 'movement.products': 'Produits', + product_id: 'Produit', + count: 'Différence', + account_id: 'Compte', + category_id: 'Catégorie', + amount: 'Montant', + has_products: "L'achat concerne des produits" + } + }, + users: { + name: 'Utilisateur |||| Utilisateurs', + fields: { + id: '#', + username: 'Login', + password: 'Mot de passe', + person_id: 'Personne', + email: 'Adresse email', + permissions: 'Permissions', + created_at: 'Créé le', + updated_at: 'Modifié le', + password_changed_at: 'MDP changé le' + } + }, + participations: { + name: 'Participation |||| Participations', + fields: { + id: '#', + created_at: 'Créé le', + updated_at: 'Modifié le', + event_id: 'Évènement', + person_id: 'Personne', + transaction_id: 'Transaction' + } + }, + profile: { + name: 'Profile', + fields: { + username: 'Login', + firstname: 'Prénom', + lastname: 'Nom', + email: 'Adresse email' }, - dashboard: { - welcome: "Bienvenue sur Sebâ„¢", - statistics: { - title: "Statistiques" - }, - products: { - title: "Classement des produits" - }, - sellers: { - title: "Classement des vendeurs" - }, - alerts: { - none: "Pas d'alertes", - title: "Alertes de stocks", - id: "#", - name: "Nom", - count: "Nombre", - treshold: "Seuil d'alerte" - }, - accounts: { - none: "Pas de comptes", - title: 'Comptes', - stocks_value: 'Valeur Stocks' - }, - transactions: { - title: "Graphe des comptes" - }, - categories_positive: { - title: "Répartitions des bénéfices" - }, - categories_negative: { - title: "Répartitions des dépenses" - }, - categories: { - title: "Répartition de la valeur" - } + me: 'Mon Profil', + permissions: { + title: 'Permissions' }, - sell: { - account: "Compte", - type: 'Type', - cash: 'Liquide', - value: 'Valeur absolue', - balance: 'Solde' + password: { + title: 'Mot de passe', + last_change: 'Dernier changement', + never: 'Jamais', + change: 'Changer le mot de passe' }, - inputs: { - multiproductcount: { - filters: { - name: 'Nom', - category: 'Catégorie', - none: 'Aucun' - }, - price: 'Total' - } + '2fa': { + title: 'Authentification à deux Facteurs', + enable: "Activer l'A2F", + disable: "Désactiver l'A2F", + enabled: "L'A2F est activée", + disabled: "L'A2F est désactivée", + status: "Status de l'A2F", + recovery: 'Codes de récupération', + recovery_message: + "Ces codes vous permettent de récupérer votre compte en ça de perte de votre périphérique d'A2F. Veuillez les conserver précieusement." + }, + tokens: { + title: "Tokens d'API", + id: '#', + name: 'Nom', + last_used_at: 'Dernière utilisation', + created_at: 'Créé le', + clear: 'Effacer les tokens', + new: 'Nouveau token', + none: "Vous n'avez pas de tokens d'API" } - }), + } + }, + actions: { + recalculate: 'Recalculer', + clear: 'Réinitialiser', + archive: 'Archiver' + }, + dashboard: { + welcome: 'Bienvenue sur Sebâ„¢', + statistics: { + title: 'Statistiques' + }, + products: { + title: 'Classement des produits' + }, + sellers: { + title: 'Classement des vendeurs' + }, + alerts: { + none: "Pas d'alertes", + title: 'Alertes de stocks', + id: '#', + name: 'Nom', + count: 'Nombre', + treshold: "Seuil d'alerte" + }, + accounts: { + none: 'Pas de comptes', + title: 'Comptes', + stocks_value: 'Valeur Stocks' + }, + transactions: { + title: 'Graphe des comptes' + }, + categories_positive: { + title: 'Répartitions des bénéfices' + }, + categories_negative: { + title: 'Répartitions des dépenses' + }, + categories: { + title: 'Répartition de la valeur' + } + }, + sell: { + account: 'Compte', + type: 'Type', + cash: 'Liquide', + value: 'Valeur absolue', + balance: 'Solde' + }, + inputs: { + multiproductcount: { + filters: { + name: 'Nom', + category: 'Catégorie', + none: 'Aucun' + }, + price: 'Total' + } + } + }) }; // TODO: Make this a setting const locale = 'fr'; -axios.defaults.params = {} +axios.defaults.params = {}; axios.defaults.params['lang'] = locale; -export default polyglotI18nProvider(locale => messages[locale], locale); +export default polyglotI18nProvider((locale) => messages[locale], locale); diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js index 5253d3ad9e6be6690549cb255f5952337b02401d..532f29b0bb61f0b2e42e33a909b205a13b243020 100644 --- a/src/reportWebVitals.js +++ b/src/reportWebVitals.js @@ -1,4 +1,4 @@ -const reportWebVitals = onPerfEntry => { +const reportWebVitals = (onPerfEntry) => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); diff --git a/src/resources/Accounts.js b/src/resources/Accounts.js index 9baa7599f68892f4e007e38f4332591d386f9a5f..6c6bd179302ad83c5ffabb08d3820469e4e1e0f2 100644 --- a/src/resources/Accounts.js +++ b/src/resources/Accounts.js @@ -1,88 +1,103 @@ import { useMediaQuery } from '@material-ui/core'; -import * as React from "react"; -import { CreateButton, Datagrid, EditButton, ExportButton, FilterButton, List, SimpleForm, SimpleList, SimpleShowLayout, TextField, TextInput, TopToolbar } from 'react-admin'; +import * as React from 'react'; +import { + CreateButton, + Datagrid, + EditButton, + ExportButton, + FilterButton, + List, + SimpleForm, + SimpleList, + SimpleShowLayout, + TextField, + TextInput, + TopToolbar +} from 'react-admin'; import DateField from '../components/DateField'; import DateInput from '../components/DateInput'; import { CreateDialog, EditDialog, ShowDialog } from '../components/DialogForm'; -import MoneyField from "../components/MoneyField"; -import MoneyInput from "../components/MoneyInput"; +import MoneyField from '../components/MoneyField'; +import MoneyInput from '../components/MoneyInput'; import { RecalculateButton } from '../components/RecalculateButton'; -const AccountsFilters = [ - <TextInput source="name" />, - <TextInput source="iban" />, - <TextInput source="bic" />, -]; +const AccountsFilters = [<TextInput key={0} source="name" />, <TextInput key={1} source="iban" />, <TextInput key={2} source="bic" />]; -const AccountsListActions = ({ basePath, ...props }) => ( - <TopToolbar> - <FilterButton /> - <RecalculateButton /> - <CreateButton /> - <ExportButton /> - </TopToolbar> +const AccountsListActions = () => ( + <TopToolbar> + <FilterButton /> + <RecalculateButton /> + <CreateButton /> + <ExportButton /> + </TopToolbar> ); const Accounts = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={AccountsFilters} actions={<AccountsListActions />}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <TextField source="name" /> - <TextField source="iban" /> - <TextField source="bic" /> - <MoneyField noLabel={true} source="balance" /> - <EditButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => record.name} - secondaryText={record => Number(record.balance).toLocaleString('fr-FR', { currency: "EUR", currencyDisplay: 'symbol', style: 'currency' })} - tertiaryText={record => "#" + record.id} - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <TextInput source="name" /> - <TextInput source="iban" /> - <TextInput source="bic" /> - </SimpleForm> - </CreateDialog> - <EditDialog {...props}> - <SimpleForm redirect="list"> - <TextInput disabled source="id" /> - <TextInput source="name" /> - <TextInput source="iban" /> - <TextInput source="bic" /> - <MoneyInput disabled source="balance" /> - <DateInput disabled source="created_at" /> - <DateInput disabled source="updated_at" /> - </SimpleForm> - </EditDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="name" /> - <TextField source="iban" /> - <TextField source="bic" /> - <MoneyField source="balance" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={AccountsFilters} actions={<AccountsListActions />}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="name" /> + <TextField source="iban" /> + <TextField source="bic" /> + <MoneyField noLabel={true} source="balance" /> + <EditButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => record.name} + secondaryText={(record) => + Number(record.balance).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + }) + } + tertiaryText={(record) => '#' + record.id} + /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <TextInput source="name" /> + <TextInput source="iban" /> + <TextInput source="bic" /> + </SimpleForm> + </CreateDialog> + <EditDialog {...props}> + <SimpleForm redirect="list"> + <TextInput disabled source="id" /> + <TextInput source="name" /> + <TextInput source="iban" /> + <TextInput source="bic" /> + <MoneyInput disabled source="balance" /> + <DateInput disabled source="created_at" /> + <DateInput disabled source="updated_at" /> + </SimpleForm> + </EditDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <TextField source="name" /> + <TextField source="iban" /> + <TextField source="bic" /> + <MoneyField source="balance" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const accounts = { - list: Accounts, - create: Accounts, - edit: Accounts, - show: Accounts + list: Accounts, + create: Accounts, + edit: Accounts, + show: Accounts }; export default accounts; diff --git a/src/resources/AccountsCounts.js b/src/resources/AccountsCounts.js index ca7ed3b633cfbfbe8d7ba66603e888f113f10343..0b882931e307849b39be106775bc80b8aa790585 100644 --- a/src/resources/AccountsCounts.js +++ b/src/resources/AccountsCounts.js @@ -1,122 +1,149 @@ import { Table, TableBody, TableCell, TableRow, useMediaQuery } from '@material-ui/core'; -import * as React from "react"; +import PropTypes from 'prop-types'; +import * as React from 'react'; import { Datagrid, Labeled, List, ReferenceField, ShowButton, SimpleList, SimpleShowLayout, TextField, useRecordContext, useTranslate } from 'react-admin'; import DateField from '../components/DateField'; import { ShowDialog } from '../components/DialogForm'; -import MoneyField from "../components/MoneyField"; +import MoneyField from '../components/MoneyField'; import Count from '../pages/Count'; const CountField = (props) => { - const record = useRecordContext(props); - const translate = useTranslate(); - const { source } = props; + const record = useRecordContext(props); + const translate = useTranslate(); + const { source } = props; - return record['type'] === 'cash' ? ( - <Labeled source={source} {...props}><> - <Table size="small"> - <TableBody> - <TableRow> - <TableCell>{translate('resources.accounts_counts.fields.count')}</TableCell> - <TableCell align="right">{translate('resources.accounts_counts.fields.value')}</TableCell> - <TableCell align="right">{translate('resources.accounts_counts.fields.total')}</TableCell> - </TableRow> - {Object.keys(record[source]).map((key) => ( - <TableRow key={key}> - <TableCell>{record[source][key]}</TableCell> - <TableCell align="right" component="th" scope="row"> - {Number(key).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' })} - </TableCell> - <TableCell align="right" component="th" scope="row"> - {Number(parseFloat(key) * parseInt(record[source][key])).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' })} - </TableCell> - </TableRow> - ))} - <TableRow key={'total'}> - <TableCell></TableCell> - <TableCell align="right" component="th" scope="row"></TableCell> - <TableCell align="right" component="th" scope="row"> - {Number(record['balance']).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' })} - </TableCell> - </TableRow> - </TableBody> - </Table> - </></Labeled> - ) : (<> - <MoneyField {...props} source="balance" /> - </>); + return record['type'] === 'cash' ? ( + <Labeled source={source} {...props}> + <> + <Table size="small"> + <TableBody> + <TableRow> + <TableCell>{translate('resources.accounts_counts.fields.count')}</TableCell> + <TableCell align="right">{translate('resources.accounts_counts.fields.value')}</TableCell> + <TableCell align="right">{translate('resources.accounts_counts.fields.total')}</TableCell> + </TableRow> + {Object.keys(record[source]).map((key) => ( + <TableRow key={key}> + <TableCell>{record[source][key]}</TableCell> + <TableCell align="right" component="th" scope="row"> + {Number(key).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + })} + </TableCell> + <TableCell align="right" component="th" scope="row"> + {Number(parseFloat(key) * parseInt(record[source][key])).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + })} + </TableCell> + </TableRow> + ))} + <TableRow key={'total'}> + <TableCell></TableCell> + <TableCell align="right" component="th" scope="row"></TableCell> + <TableCell align="right" component="th" scope="row"> + {Number(record['balance']).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + })} + </TableCell> + </TableRow> + </TableBody> + </Table> + </> + </Labeled> + ) : ( + <> + <MoneyField {...props} source="balance" /> + </> + ); }; -const AccountsCounts = (props) => { +CountField.propTypes = { + source: PropTypes.number +}; - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} bulkActionButtons={false}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <TextField source="type" /> - <MoneyField noLabel={true} source="balance" /> - <ReferenceField source="transaction_id" reference="transactions" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField label="Compte" source="transaction_id" reference="transactions" link={false}> - <ReferenceField source="account_id" reference="accounts" link="show"> - <TextField source="name" /> - </ReferenceField> - </ReferenceField> - <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link={false}> - <ReferenceField source="user_id" reference="users" link="show"> - <TextField source="username" /> - </ReferenceField> - </ReferenceField> - <DateField source="created_at" /> - <ShowButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => - <ReferenceField record={record} label="Compte" source="transaction_id" reference="transactions" link={false}> - <ReferenceField source="account_id" reference="accounts" link={false}> - <TextField source="name" /> - </ReferenceField> - </ReferenceField>} - tertiaryText={record => Number(record.balance).toLocaleString('fr-FR', { currency: "EUR", currencyDisplay: 'symbol', style: 'currency' })} - secondaryText={record => new Date(record.created_at).toLocaleString()} - linkType="show" - /> - )} - </List> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="type" /> - <CountField source="data" /> - <ReferenceField source="transaction_id" reference="transactions" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField label="Compte" source="transaction_id" reference="transactions" link="show"> - <ReferenceField source="account_id" reference="accounts" link="show"> - <TextField source="name" /> - </ReferenceField> - </ReferenceField> - <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link="show"> - <ReferenceField source="user_id" reference="users" link="show"> - <TextField source="username" /> - </ReferenceField> - </ReferenceField> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); +const AccountsCounts = (props) => { + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} bulkActionButtons={false}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="type" /> + <MoneyField noLabel={true} source="balance" /> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField label="Compte" source="transaction_id" reference="transactions" link={false}> + <ReferenceField source="account_id" reference="accounts" link="show"> + <TextField source="name" /> + </ReferenceField> + </ReferenceField> + <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link={false}> + <ReferenceField source="user_id" reference="users" link="show"> + <TextField source="username" /> + </ReferenceField> + </ReferenceField> + <DateField source="created_at" /> + <ShowButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => ( + <ReferenceField record={record} label="Compte" source="transaction_id" reference="transactions" link={false}> + <ReferenceField source="account_id" reference="accounts" link={false}> + <TextField source="name" /> + </ReferenceField> + </ReferenceField> + )} + tertiaryText={(record) => + Number(record.balance).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + }) + } + secondaryText={(record) => new Date(record.created_at).toLocaleString()} + linkType="show" + /> + )} + </List> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <TextField source="type" /> + <CountField source="data" /> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField label="Compte" source="transaction_id" reference="transactions" link="show"> + <ReferenceField source="account_id" reference="accounts" link="show"> + <TextField source="name" /> + </ReferenceField> + </ReferenceField> + <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link="show"> + <ReferenceField source="user_id" reference="users" link="show"> + <TextField source="username" /> + </ReferenceField> + </ReferenceField> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const accounts_counts = { - list: AccountsCounts, - show: AccountsCounts, - create: Count + list: AccountsCounts, + show: AccountsCounts, + create: Count }; export default accounts_counts; diff --git a/src/resources/ArchivedMembers.js b/src/resources/ArchivedMembers.js index 0707aa6bcc824a95f8fbb3063118989bcf152aac..6a9f0f7f1698d5b19f5affbe88cb07f7c9d25585 100644 --- a/src/resources/ArchivedMembers.js +++ b/src/resources/ArchivedMembers.js @@ -1,76 +1,93 @@ import { useMediaQuery } from '@material-ui/core'; -import * as React from "react"; -import { AutocompleteInput, BooleanField, BooleanInput, Datagrid, ExportButton, FilterButton, FunctionField, List, ReferenceField, ReferenceInput, ShowButton, SimpleList, SimpleShowLayout, TextField, TopToolbar } from 'react-admin'; +import * as React from 'react'; +import { + AutocompleteInput, + BooleanField, + BooleanInput, + Datagrid, + ExportButton, + FilterButton, + FunctionField, + List, + ReferenceField, + ReferenceInput, + ShowButton, + SimpleList, + SimpleShowLayout, + TextField, + TopToolbar +} from 'react-admin'; import DateField from '../components/DateField'; import { ShowDialog } from '../components/DialogForm'; const ArchivedMembersFilters = [ - <ReferenceInput source="person_id" reference="people" filterToQuery={searchText => ({ fullname: searchText })}> - <AutocompleteInput optionText="fullname" /> - </ReferenceInput>, - <BooleanInput source="paid" /> + <ReferenceInput key={0} source="person_id" reference="people" filterToQuery={(searchText) => ({ fullname: searchText })}> + <AutocompleteInput optionText="fullname" /> + </ReferenceInput>, + <BooleanInput key={1} source="paid" /> ]; -const ArchivedMembersListActions = ({ basePath, ...props }) => ( - <TopToolbar> - <FilterButton /> - <ExportButton /> - </TopToolbar> +const ArchivedMembersListActions = () => ( + <TopToolbar> + <FilterButton /> + <ExportButton /> + </TopToolbar> ); const ArchivedMembers = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={ArchivedMembersFilters} bulkActionButtons={false} actions={<ArchivedMembersListActions />}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <ReferenceField source="person_id" reference="people" link="show" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <BooleanField source="paid" /> - <ReferenceField source="transaction_id" reference="transactions" link="show" > - <FunctionField render={r => "#" + r.id} /> - </ReferenceField> - <TextField source="school_year" /> - <ShowButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => - <ReferenceField record={record} source="person_id" reference="people" link={false} > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField>} - tertiaryText={record => <BooleanField record={record} source="paid" />} - secondaryText={record => record.school_year} - linkType="show" - /> - )} - </List> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <ReferenceField source="person_id" reference="people" link="show" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <BooleanField source="paid" /> - <ReferenceField source="transaction_id" reference="transactions" link="show" > - <FunctionField render={r => "#" + r.id} /> - </ReferenceField> - <TextField source="school_year" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={ArchivedMembersFilters} bulkActionButtons={false} actions={<ArchivedMembersListActions />}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <ReferenceField source="person_id" reference="people" link="show"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <BooleanField source="paid" /> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <FunctionField render={(r) => '#' + r.id} /> + </ReferenceField> + <TextField source="school_year" /> + <ShowButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => ( + <ReferenceField record={record} source="person_id" reference="people" link={false}> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + )} + tertiaryText={(record) => <BooleanField record={record} source="paid" />} + secondaryText={(record) => record.school_year} + linkType="show" + /> + )} + </List> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <ReferenceField source="person_id" reference="people" link="show"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <BooleanField source="paid" /> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <FunctionField render={(r) => '#' + r.id} /> + </ReferenceField> + <TextField source="school_year" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const archived_members = { - list: ArchivedMembers, - create: ArchivedMembers, - show: ArchivedMembers + list: ArchivedMembers, + create: ArchivedMembers, + show: ArchivedMembers }; export default archived_members; diff --git a/src/resources/AutomatedTransactions.js b/src/resources/AutomatedTransactions.js index 29abc9b18f242870d6a972653ae2b6303227388e..0c649590165c83334f1d699c7052c9525ad25bc1 100644 --- a/src/resources/AutomatedTransactions.js +++ b/src/resources/AutomatedTransactions.js @@ -1,149 +1,235 @@ import { useMediaQuery } from '@material-ui/core'; -import * as React from "react"; -import { AutocompleteInput, BooleanField, BooleanInput, Datagrid, DateInput, EditButton, List, NumberField, NumberInput, ReferenceField, ReferenceInput, SelectField, SelectInput, SimpleForm, SimpleList, SimpleShowLayout, TextField, TextInput, useTranslate } from 'react-admin'; +import * as React from 'react'; +import { + AutocompleteInput, + BooleanField, + BooleanInput, + Datagrid, + DateInput, + EditButton, + List, + NumberField, + NumberInput, + ReferenceField, + ReferenceInput, + SelectField, + SelectInput, + SimpleForm, + SimpleList, + SimpleShowLayout, + TextField, + TextInput, + useTranslate +} from 'react-admin'; import DateField from '../components/DateField'; import { CreateDialog, EditDialog, ShowDialog } from '../components/DialogForm'; -import MoneyField from "../components/MoneyField"; -import MoneyInput from "../components/MoneyInput"; +import MoneyField from '../components/MoneyField'; +import MoneyInput from '../components/MoneyInput'; const AutomatedTransactionsFilters = [ - <TextInput source="name" />, - <ReferenceInput source="account_id" reference="accounts" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput>, - <ReferenceInput source="category_id" reference="transactions_categories" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput>, - <ReferenceInput source="user_id" reference="users" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="username" /> - </ReferenceInput> + <TextInput key={0} source="name" />, + <ReferenceInput key={1} source="account_id" reference="accounts" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput>, + <ReferenceInput key={2} source="category_id" reference="transactions_categories" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput>, + <ReferenceInput key={3} source="user_id" reference="users" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="username" /> + </ReferenceInput> ]; const AutomatedTransactions = (props) => { - const translate = useTranslate(); + const translate = useTranslate(); - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); - const l = { - yearly: "année", - monthly: "mois", - weekly: "semaine" - } + const l = { + yearly: 'année', + monthly: 'mois', + weekly: 'semaine' + }; - return ( - <> - <List {...props} filters={AutomatedTransactionsFilters}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <TextField source="name" /> - <MoneyField noLabel={true} source="amount" /> - <BooleanField source="rectification" /> - <ReferenceField source="account_id" reference="accounts"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="category_id" reference="transactions_categories"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - <SelectField source="frequency" choices={[ - { id: 'yearly', name: translate('resources.automated_transactions.frequencies.yearly') }, - { id: 'monthly', name: translate('resources.automated_transactions.frequencies.monthly') }, - { id: 'weekly', name: translate('resources.automated_transactions.frequencies.weekly') }, - { id: 'dayly', name: translate('resources.automated_transactions.frequencies.dayly') } - ]} /> - <NumberField source="day" /> - <DateField source="created_at" /> - <EditButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => record.name} - tertiaryText={record => Number(record.amount).toLocaleString('fr-FR', { currency: "EUR", currencyDisplay: 'symbol', style: 'currency' })} - secondaryText={record => record.frequency === 'dayly' ? 'Tous les jours' : `Le ${record.day} de chaque ${l[record.frequency]}`} - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <TextInput source="name" /> - <MoneyInput source="amount" /> - <BooleanInput source="rectification" /> - <ReferenceInput source="account_id" reference="accounts" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <ReferenceInput source="category_id" reference="transactions_categories" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <SelectInput source="frequency" choices={[ - { id: 'yearly', name: translate('resources.automated_transactions.frequencies.yearly') }, - { id: 'monthly', name: translate('resources.automated_transactions.frequencies.monthly') }, - { id: 'weekly', name: translate('resources.automated_transactions.frequencies.weekly') }, - { id: 'dayly', name: translate('resources.automated_transactions.frequencies.dayly') } - ]} /> - <NumberInput source="day" /> - </SimpleForm> - </CreateDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="name" /> - <MoneyField source="amount" /> - <BooleanField source="rectification" /> - <ReferenceField source="account_id" reference="accounts"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="category_id" reference="transactions_categories"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - <SelectField source="frequency" choices={[ - { id: 'yearly', name: translate('resources.automated_transactions.frequencies.yearly') }, - { id: 'monthly', name: translate('resources.automated_transactions.frequencies.monthly') }, - { id: 'weekly', name: translate('resources.automated_transactions.frequencies.weekly') }, - { id: 'dayly', name: translate('resources.automated_transactions.frequencies.dayly') } - ]} /> - <NumberField source="day" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - <EditDialog {...props}> - <SimpleForm redirect="list"> - <TextInput disabled source="id" /> - <TextInput source="name" /> - <MoneyInput source="amount" /> - <BooleanInput source="rectification" /> - <ReferenceInput source="account_id" reference="accounts" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <ReferenceInput source="category_id" reference="transactions_categories" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <SelectInput source="frequency" choices={[ - { id: 'yearly', name: translate('resources.automated_transactions.frequencies.yearly') }, - { id: 'monthly', name: translate('resources.automated_transactions.frequencies.monthly') }, - { id: 'weekly', name: translate('resources.automated_transactions.frequencies.weekly') }, - { id: 'dayly', name: translate('resources.automated_transactions.frequencies.dayly') } - ]} /> - <NumberInput source="day" /> - <DateInput disabled source="created_at" /> - <DateInput disabled source="updated_at" /> - </SimpleForm> - </EditDialog> - </> - ); + return ( + <> + <List {...props} filters={AutomatedTransactionsFilters}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="name" /> + <MoneyField noLabel={true} source="amount" /> + <BooleanField source="rectification" /> + <ReferenceField source="account_id" reference="accounts"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="category_id" reference="transactions_categories"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + <SelectField + source="frequency" + choices={[ + { + id: 'yearly', + name: translate('resources.automated_transactions.frequencies.yearly') + }, + { + id: 'monthly', + name: translate('resources.automated_transactions.frequencies.monthly') + }, + { + id: 'weekly', + name: translate('resources.automated_transactions.frequencies.weekly') + }, + { + id: 'dayly', + name: translate('resources.automated_transactions.frequencies.dayly') + } + ]} + /> + <NumberField source="day" /> + <DateField source="created_at" /> + <EditButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => record.name} + tertiaryText={(record) => + Number(record.amount).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + }) + } + secondaryText={(record) => (record.frequency === 'dayly' ? 'Tous les jours' : `Le ${record.day} de chaque ${l[record.frequency]}`)} + /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <TextInput source="name" /> + <MoneyInput source="amount" /> + <BooleanInput source="rectification" /> + <ReferenceInput source="account_id" reference="accounts" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <ReferenceInput source="category_id" reference="transactions_categories" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <SelectInput + source="frequency" + choices={[ + { + id: 'yearly', + name: translate('resources.automated_transactions.frequencies.yearly') + }, + { + id: 'monthly', + name: translate('resources.automated_transactions.frequencies.monthly') + }, + { + id: 'weekly', + name: translate('resources.automated_transactions.frequencies.weekly') + }, + { + id: 'dayly', + name: translate('resources.automated_transactions.frequencies.dayly') + } + ]} + /> + <NumberInput source="day" /> + </SimpleForm> + </CreateDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <TextField source="name" /> + <MoneyField source="amount" /> + <BooleanField source="rectification" /> + <ReferenceField source="account_id" reference="accounts"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="category_id" reference="transactions_categories"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + <SelectField + source="frequency" + choices={[ + { + id: 'yearly', + name: translate('resources.automated_transactions.frequencies.yearly') + }, + { + id: 'monthly', + name: translate('resources.automated_transactions.frequencies.monthly') + }, + { + id: 'weekly', + name: translate('resources.automated_transactions.frequencies.weekly') + }, + { + id: 'dayly', + name: translate('resources.automated_transactions.frequencies.dayly') + } + ]} + /> + <NumberField source="day" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + <EditDialog {...props}> + <SimpleForm redirect="list"> + <TextInput disabled source="id" /> + <TextInput source="name" /> + <MoneyInput source="amount" /> + <BooleanInput source="rectification" /> + <ReferenceInput source="account_id" reference="accounts" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <ReferenceInput source="category_id" reference="transactions_categories" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <SelectInput + source="frequency" + choices={[ + { + id: 'yearly', + name: translate('resources.automated_transactions.frequencies.yearly') + }, + { + id: 'monthly', + name: translate('resources.automated_transactions.frequencies.monthly') + }, + { + id: 'weekly', + name: translate('resources.automated_transactions.frequencies.weekly') + }, + { + id: 'dayly', + name: translate('resources.automated_transactions.frequencies.dayly') + } + ]} + /> + <NumberInput source="day" /> + <DateInput disabled source="created_at" /> + <DateInput disabled source="updated_at" /> + </SimpleForm> + </EditDialog> + </> + ); }; const automated_transactions = { - list: AutomatedTransactions, - create: AutomatedTransactions, - show: AutomatedTransactions, - edit: AutomatedTransactions + list: AutomatedTransactions, + create: AutomatedTransactions, + show: AutomatedTransactions, + edit: AutomatedTransactions }; export default automated_transactions; diff --git a/src/resources/Events.js b/src/resources/Events.js index d0dc5cd02d431164346627f8de62ce3de8134813..5e7f6253d2783d451d8cd0963d3d33a135f829c3 100644 --- a/src/resources/Events.js +++ b/src/resources/Events.js @@ -1,101 +1,115 @@ import { useMediaQuery } from '@material-ui/core'; -import * as React from "react"; -import { AutocompleteInput, Datagrid, DateField, List, ReferenceField, ReferenceInput, ShowButton, SimpleForm, SimpleList, SimpleShowLayout, TextField, TextInput } from 'react-admin'; -import { JsonField, JsonInput } from "react-admin-json-view"; +import * as React from 'react'; +import { + AutocompleteInput, + Datagrid, + DateField, + List, + ReferenceField, + ReferenceInput, + ShowButton, + SimpleForm, + SimpleList, + SimpleShowLayout, + TextField, + TextInput +} from 'react-admin'; +import { JsonField, JsonInput } from 'react-admin-json-view'; import { DateTimeInput } from '../components/DateTimeInput'; import { CreateDialog, ShowDialog } from '../components/DialogForm'; import MoneyField from '../components/MoneyField'; import MoneyInput from '../components/MoneyInput'; const EventsFilters = [ - <TextInput source="name" />, - <TextInput source="location" />, - <ReferenceInput source="category_id" reference="transactions_categories" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> + <TextInput key={0} source="name" />, + <TextInput key={1} source="location" />, + <ReferenceInput key={2} source="category_id" reference="transactions_categories" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> ]; const Events = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={EventsFilters} bulkActionButtons={false}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <TextField source="name" /> - <TextField source="location" /> - <DateField source="start" /> - <ShowButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => record.name} - secondaryText={record => record.location + " - " + new Date(record.start).toLocaleDateString()} - tertiaryText={record => "#" + record.id} - linkType="show" - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <TextInput source="name" /> - <TextInput source="location" /> - <DateTimeInput source="start" /> - <DateTimeInput source="end" /> - <DateTimeInput source="inscriptions_closed_at" /> - <JsonInput - source="data" - reactJsonOptions={{ - theme: "monokai", - displayObjectSize: false, - displayDataTypes: false, - enableClipboard: false, - defaultValue: { - name: "", type: "" - } - }} - initialValue={[]} - /> - <MoneyInput source="price" /> - <MoneyInput source="price_member" /> - </SimpleForm> - </CreateDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="name" /> - <TextField source="location" /> - <DateField source="start" /> - <DateField source="end" /> - <DateField source="inscriptions_closed_at" /> - <MoneyField source="price" /> - <MoneyField source="price_member" /> - <ReferenceField source="category_id" reference="transactions_categories"> - <TextField source="name" /> - </ReferenceField> - <JsonField - source="data" - addLabel - reactJsonOptions={{ - theme: "monokai", - displayObjectSize: false, - displayDataTypes: false, - enableClipboard: false - }} - /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={EventsFilters} bulkActionButtons={false}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="name" /> + <TextField source="location" /> + <DateField source="start" /> + <ShowButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => record.name} + secondaryText={(record) => record.location + ' - ' + new Date(record.start).toLocaleDateString()} + tertiaryText={(record) => '#' + record.id} + linkType="show" + /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <TextInput source="name" /> + <TextInput source="location" /> + <DateTimeInput source="start" /> + <DateTimeInput source="end" /> + <DateTimeInput source="inscriptions_closed_at" /> + <JsonInput + source="data" + reactJsonOptions={{ + theme: 'monokai', + displayObjectSize: false, + displayDataTypes: false, + enableClipboard: false, + defaultValue: { + name: '', + type: '' + } + }} + initialValue={[]} + /> + <MoneyInput source="price" /> + <MoneyInput source="price_member" /> + </SimpleForm> + </CreateDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <TextField source="name" /> + <TextField source="location" /> + <DateField source="start" /> + <DateField source="end" /> + <DateField source="inscriptions_closed_at" /> + <MoneyField source="price" /> + <MoneyField source="price_member" /> + <ReferenceField source="category_id" reference="transactions_categories"> + <TextField source="name" /> + </ReferenceField> + <JsonField + source="data" + addLabel + reactJsonOptions={{ + theme: 'monokai', + displayObjectSize: false, + displayDataTypes: false, + enableClipboard: false + }} + /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const events = { - list: Events, - create: Events, - show: Events + list: Events, + create: Events, + show: Events }; export default events; diff --git a/src/resources/Members.js b/src/resources/Members.js index df31b8aa95952742f028a0fc3b26f7d21fcc2823..05cc2338ff5cef32f3c9e59cfc4f3a309b724d41 100644 --- a/src/resources/Members.js +++ b/src/resources/Members.js @@ -1,106 +1,135 @@ import { useMediaQuery } from '@material-ui/core'; import AttachMoneyIcon from '@material-ui/icons/AttachMoney'; -import * as React from "react"; +import * as React from 'react'; import { Fragment } from 'react'; -import { AutocompleteInput, BooleanField, BooleanInput, BulkDeleteButton, BulkUpdateButton, CreateButton, Datagrid, EditButton, ExportButton, FilterButton, FunctionField, List, ReferenceField, ReferenceInput, SimpleForm, SimpleList, SimpleShowLayout, TextField, TextInput, TopToolbar, useTranslate } from 'react-admin'; +import { + AutocompleteInput, + BooleanField, + BooleanInput, + BulkDeleteButton, + BulkUpdateButton, + CreateButton, + Datagrid, + EditButton, + ExportButton, + FilterButton, + FunctionField, + List, + ReferenceField, + ReferenceInput, + SimpleForm, + SimpleList, + SimpleShowLayout, + TextField, + TextInput, + TopToolbar, + useTranslate +} from 'react-admin'; import { ArchiveButton } from '../components/ArchiveButton'; import DateField from '../components/DateField'; import DateInput from '../components/DateInput'; import { CreateDialog, EditDialog, ShowDialog } from '../components/DialogForm'; const MembersFilters = [ - <ReferenceInput source="person_id" reference="people" filterToQuery={searchText => ({ fullname: searchText, is_member: true })}> - <AutocompleteInput optionText="fullname" /> - </ReferenceInput>, - <BooleanInput source="paid" /> + <ReferenceInput key={0} source="person_id" reference="people" filterToQuery={(searchText) => ({ fullname: searchText, is_member: true })}> + <AutocompleteInput optionText="fullname" /> + </ReferenceInput>, + <BooleanInput key={1} source="paid" /> ]; -const MembersListActions = ({ basePath, ...props }) => ( - <TopToolbar> - <FilterButton /> - <ArchiveButton /> - <CreateButton /> - <ExportButton /> - </TopToolbar> +const MembersListActions = () => ( + <TopToolbar> + <FilterButton /> + <ArchiveButton /> + <CreateButton /> + <ExportButton /> + </TopToolbar> ); -const MembersBulkActionButtons = props => { - const translate = useTranslate(); - return ( - <Fragment> - <BulkUpdateButton {...props} label={translate('resources.members.mark_payed')} data={{ "paid": true }} icon={<AttachMoneyIcon />} /> - <BulkDeleteButton {...props} /> - </Fragment> - ); +const MembersBulkActionButtons = (props) => { + const translate = useTranslate(); + return ( + <Fragment> + <BulkUpdateButton {...props} label={translate('resources.members.mark_payed')} data={{ paid: true }} icon={<AttachMoneyIcon />} /> + <BulkDeleteButton {...props} /> + </Fragment> + ); }; const Members = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={MembersFilters} bulkActionButtons={<MembersBulkActionButtons />} actions={<MembersListActions />}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <ReferenceField source="person_id" reference="people" link="show" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <BooleanField source="paid" /> - <ReferenceField source="transaction_id" reference="transactions" link="show" > - <FunctionField render={r => "#" + r.id} /> - </ReferenceField> - <EditButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => - <ReferenceField record={record} source="person_id" reference="people" link={false} > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField>} - tertiaryText={record => <BooleanField record={record} source="paid" />} - linkType="show" - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <ReferenceInput source="person_id" reference="people" filterToQuery={searchText => ({ fullname: searchText, is_member: false })}> - <AutocompleteInput optionText="fullname" /> - </ReferenceInput> - <BooleanInput source="paid" /> - </SimpleForm> - </CreateDialog> - <EditDialog {...props}> - <SimpleForm redirect="list"> - <TextInput disabled source="id" /> - <ReferenceField source="person_id" reference="people" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <BooleanInput source="paid" /> - <DateInput disabled source="created_at" /> - <DateInput disabled source="updated_at" /> - </SimpleForm> - </EditDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <ReferenceField source="person_id" reference="people" link="show" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <BooleanField source="paid" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={MembersFilters} bulkActionButtons={<MembersBulkActionButtons />} actions={<MembersListActions />}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <ReferenceField source="person_id" reference="people" link="show"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <BooleanField source="paid" /> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <FunctionField render={(r) => '#' + r.id} /> + </ReferenceField> + <EditButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => ( + <ReferenceField record={record} source="person_id" reference="people" link={false}> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + )} + tertiaryText={(record) => <BooleanField record={record} source="paid" />} + linkType="show" + /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <ReferenceInput + source="person_id" + reference="people" + filterToQuery={(searchText) => ({ + fullname: searchText, + is_member: false + })}> + <AutocompleteInput optionText="fullname" /> + </ReferenceInput> + <BooleanInput source="paid" /> + </SimpleForm> + </CreateDialog> + <EditDialog {...props}> + <SimpleForm redirect="list"> + <TextInput disabled source="id" /> + <ReferenceField source="person_id" reference="people"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <BooleanInput source="paid" /> + <DateInput disabled source="created_at" /> + <DateInput disabled source="updated_at" /> + </SimpleForm> + </EditDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <ReferenceField source="person_id" reference="people" link="show"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <BooleanField source="paid" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const members = { - list: Members, - create: Members, - edit: Members, - show: Members + list: Members, + create: Members, + edit: Members, + show: Members }; export default members; diff --git a/src/resources/Movements.js b/src/resources/Movements.js index 458108f09816693d58ea5ae85eafcd7a10fb36b6..70e69d20d870a9f37811f14e52e0209193bbe0d4 100644 --- a/src/resources/Movements.js +++ b/src/resources/Movements.js @@ -1,79 +1,95 @@ import { useMediaQuery } from '@material-ui/core'; -import React from "react"; -import { ArrayField, ArrayInput, AutocompleteInput, BooleanField, BooleanInput, Datagrid, List, NumberInput, ReferenceField, ReferenceInput, ShowButton, SimpleForm, SimpleFormIterator, SimpleList, SimpleShowLayout, TextField, TextInput } from 'react-admin'; +import React from 'react'; +import { + ArrayField, + ArrayInput, + AutocompleteInput, + BooleanField, + BooleanInput, + Datagrid, + List, + NumberInput, + ReferenceField, + ReferenceInput, + ShowButton, + SimpleForm, + SimpleFormIterator, + SimpleList, + SimpleShowLayout, + TextField, + TextInput +} from 'react-admin'; import DateField from '../components/DateField'; import { CreateDialog, ShowDialog } from '../components/DialogForm'; -const MovementsFilters = [ - <TextInput source="name" /> -]; +const MovementsFilters = [<TextInput key={0} source="name" />]; const Movements = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={MovementsFilters} bulkActionButtons={false} > - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <TextField source="name" /> - <BooleanField source="rectification" /> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - <DateField source="created_at" /> - <ShowButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => record.name} - tertiaryText={record => "#" + record.id} - secondaryText={record => new Date(record.created_at).toLocaleString()} - linkType="show" - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <TextInput source="name" /> - <BooleanInput source="rectification" /> - <ArrayInput source="products"> - <SimpleFormIterator> - <ReferenceInput source="id" reference="products" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <NumberInput source="diff" /> - </SimpleFormIterator> - </ArrayInput> - </SimpleForm> - </CreateDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="name" /> - <ArrayField source="products" > - <Datagrid> - <ReferenceField source="product_id" reference="products" link="edit"> - <TextField source="name" /> - </ReferenceField> - <TextField source="count" /> - </Datagrid> - </ArrayField> - <BooleanField source="rectification" /> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - <DateField source="created_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={MovementsFilters} bulkActionButtons={false}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="name" /> + <BooleanField source="rectification" /> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + <DateField source="created_at" /> + <ShowButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => record.name} + tertiaryText={(record) => '#' + record.id} + secondaryText={(record) => new Date(record.created_at).toLocaleString()} + linkType="show" + /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <TextInput source="name" /> + <BooleanInput source="rectification" /> + <ArrayInput source="products"> + <SimpleFormIterator> + <ReferenceInput source="id" reference="products" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <NumberInput source="diff" /> + </SimpleFormIterator> + </ArrayInput> + </SimpleForm> + </CreateDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <TextField source="name" /> + <ArrayField source="products"> + <Datagrid> + <ReferenceField source="product_id" reference="products" link="edit"> + <TextField source="name" /> + </ReferenceField> + <TextField source="count" /> + </Datagrid> + </ArrayField> + <BooleanField source="rectification" /> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + <DateField source="created_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; -const movements = { - list: Movements, - create: Movements, - show: Movements +const movements = { + list: Movements, + create: Movements, + show: Movements }; export default movements; diff --git a/src/resources/Participations.js b/src/resources/Participations.js index 12882b4c808e9cc0c43fb31a157eeff97e844efb..b10e804b012c3227ea3cdff3601c88ddf4b3750b 100644 --- a/src/resources/Participations.js +++ b/src/resources/Participations.js @@ -1,301 +1,422 @@ import { capitalize, FormControl, FormControlLabel, InputLabel, MenuItem, Select, Switch, TextField as MuiTextField, useMediaQuery } from '@material-ui/core'; import CheckIcon from '@material-ui/icons/Check'; import CloseIcon from '@material-ui/icons/Close'; -import React, { useEffect, useState } from "react"; -import { AutocompleteInput, BooleanField, Create, Datagrid, DateField, Edit, EditButton, Error, FormDataConsumer, FormTab, FunctionField, Labeled, List, Loading, ReferenceField, ReferenceInput, ShowButton, SimpleList, SimpleShowLayout, TabbedForm, TextField, useInput, useNotify, useQuery, useRefresh, useTranslate } from 'react-admin'; +import PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; +import { + AutocompleteInput, + BooleanField, + Create, + Datagrid, + DateField, + Edit, + EditButton, + Error, + FormDataConsumer, + FormTab, + FunctionField, + Labeled, + List, + Loading, + ReferenceField, + ReferenceInput, + ShowButton, + SimpleList, + SimpleShowLayout, + TabbedForm, + TextField, + useInput, + useNotify, + useQuery, + useRefresh, + useTranslate +} from 'react-admin'; import { ShowDialog } from '../components/DialogForm'; -import PaymentInput from "../components/PaymentInput"; +import PaymentInput from '../components/PaymentInput'; const ParticipationFilters = [ - <ReferenceInput source="event_id" reference="events" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput>, - <ReferenceInput source="person_id" reference="people" filterToQuery={searchText => ({ fullname: searchText, has_account: true })}> - <AutocompleteInput optionText="fullname" /> - </ReferenceInput> + <ReferenceInput key={0} source="event_id" reference="events" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput>, + <ReferenceInput + key={1} + source="person_id" + reference="people" + filterToQuery={(searchText) => ({ + fullname: searchText, + has_account: true + })}> + <AutocompleteInput optionText="fullname" /> + </ReferenceInput> ]; const EventDataField = (props) => { - const { data, loading, error } = useQuery({ - type: 'getOne', - resource: 'events', - payload: { id: props.record.event_id } - }); + const { data, loading, error } = useQuery({ + type: 'getOne', + resource: 'events', + payload: { id: props.record.event_id } + }); - if (loading) return <Loading />; - if (error) return <Error />; - if (!data) return null; + if (loading) return <Loading />; + if (error) return <Error />; + if (!data) return null; - return data.data.map((val, i) => { - if (val.type === "bool") { - return (<Labeled key={i} label={capitalize(val.name)}> - <BooleanField source={"data." + val.name} record={props.record} /> - </Labeled>); - } else { - return (<Labeled key={i} label={capitalize(val.name)}> - <TextField source={"data." + val.name} label={val.name} record={props.record} /> - </Labeled>); - } - }); + return data.data.map((val, i) => { + if (val.type === 'bool') { + return ( + <Labeled key={i} label={capitalize(val.name)}> + <BooleanField source={'data.' + val.name} record={props.record} /> + </Labeled> + ); + } else { + return ( + <Labeled key={i} label={capitalize(val.name)}> + <TextField source={'data.' + val.name} label={val.name} record={props.record} /> + </Labeled> + ); + } + }); +}; + +EventDataField.propTypes = { + record: PropTypes.any }; const MultiButton = (props) => { - if (props?.record?.transaction_id === null || props?.record?.transaction_id === undefined) { - return <EditButton {...props} />; - } else { - return <ShowButton {...props} />; - } -} + if (props?.record?.transaction_id === null || props?.record?.transaction_id === undefined) { + return <EditButton {...props} />; + } else { + return <ShowButton {...props} />; + } +}; + +MultiButton.propTypes = { + record: PropTypes.any +}; const Participation = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={ParticipationFilters} bulkActionButtons={false}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <ReferenceField source="event_id" reference="events" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="person_id" reference="people" link="show" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <ReferenceField source="transaction_id" reference="transactions" link="show"> - <TextField source="name" /> - </ReferenceField> - <MultiButton /> - </Datagrid> - ) : ( - <SimpleList - secondaryText={record => - <ReferenceField record={record} source="event_id" reference="events" link={false}> - <TextField source="name" /> - </ReferenceField>} - primaryText={record => - <ReferenceField record={record} source="person_id" reference="people" link={false} > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField>} - tertiaryText={record => (record.transaction_id === null ? <CloseIcon /> : <CheckIcon />)} - linkType="show" - /> - )} - </List> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <ReferenceField source="event_id" reference="events" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="person_id" reference="people" link="show" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <ReferenceField source="transaction_id" reference="transactions" link="show"> - <TextField source="name" /> - </ReferenceField> - <EventDataField /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={ParticipationFilters} bulkActionButtons={false}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <ReferenceField source="event_id" reference="events" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="person_id" reference="people" link="show"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <TextField source="name" /> + </ReferenceField> + <MultiButton /> + </Datagrid> + ) : ( + <SimpleList + secondaryText={(record) => ( + <ReferenceField record={record} source="event_id" reference="events" link={false}> + <TextField source="name" /> + </ReferenceField> + )} + primaryText={(record) => ( + <ReferenceField record={record} source="person_id" reference="people" link={false}> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + )} + tertiaryText={(record) => (record.transaction_id === null ? <CloseIcon /> : <CheckIcon />)} + linkType="show" + /> + )} + </List> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <ReferenceField source="event_id" reference="events" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="person_id" reference="people" link="show"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <TextField source="name" /> + </ReferenceField> + <EventDataField /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const eventPrice = (event, person, data) => { - const member = person.is_member; - let price = member ? event.price_member : event.price; + const member = person.is_member; + let price = member ? event.price_member : event.price; - for (let dat of event.data) { - if (dat.type === "boolean") { - if (data[dat.name] === true) { - price += member ? dat.price_member : dat.price; - } - } else if (dat.type === "select") { - for (let val of dat.values) { - if (data[dat.name] === val.name) { - price += member ? val.price_member : val.price; - break; - } - } + for (let dat of event.data) { + if (dat.type === 'boolean') { + if (data[dat.name] === true) { + price += member ? dat.price_member : dat.price; + } + } else if (dat.type === 'select') { + for (let val of dat.values) { + if (data[dat.name] === val.name) { + price += member ? val.price_member : val.price; + break; } + } } + } - return price; -} + return price; +}; const EventDataInput = ({ eventId, personId, priceChanged, ...props }) => { - // TODO: Calculate and report price - const { - input: { name, onChange, initialValue }, - meta: { touched, error }, - isRequired - } = useInput(props); - console.log(initialValue); + // TODO: Calculate and report price + const { + input: { onChange, initialValue } + } = useInput(props); + console.log(initialValue); - const [value, setValue] = useState({}); + const [value, setValue] = useState({}); - const { data: event, loading: loading1, error: error1 } = useQuery({ - type: 'getOne', - resource: 'events', - payload: { id: eventId } - }); + const { + data: event, + loading: loading1, + error: error1 + } = useQuery({ + type: 'getOne', + resource: 'events', + payload: { id: eventId } + }); - const { data: person, loading: loading2, error: error2 } = useQuery({ - type: 'getOne', - resource: 'people', - payload: { id: personId } - }); + const { + data: person, + loading: loading2, + error: error2 + } = useQuery({ + type: 'getOne', + resource: 'people', + payload: { id: personId } + }); - const onChangeWrapper = (name) => { - return (e) => { - let val = { ...value }; - val[name] = e.target.type === 'checkbox' ? e.target.checked : e.target.value; - setValue(val); - onChange(val); - } - } + const onChangeWrapper = (name) => { + return (e) => { + let val = { ...value }; + val[name] = e.target.type === 'checkbox' ? e.target.checked : e.target.value; + setValue(val); + onChange(val); + }; + }; - useEffect(() => { - if (priceChanged !== undefined && event !== undefined && person !== undefined && value !== undefined) - priceChanged(eventPrice(event, person, value)); - }, [event, person, value]); + useEffect(() => { + if (priceChanged !== undefined && event !== undefined && person !== undefined && value !== undefined) priceChanged(eventPrice(event, person, value)); + }, [event, person, value]); // eslint-disable-line - useEffect(() => { - let val = {}; + useEffect(() => { + let val = {}; - event?.data?.map((v) => { - if (v.type === "boolean") { - val[v.name] = false; - } else if (v.type === "select") { - val[v.name] = v.values[0]?.name; - } else { - val[v.name] = ""; - } - }); + event?.data?.forEach((v) => { + if (v.type === 'boolean') { + val[v.name] = false; + } else if (v.type === 'select') { + val[v.name] = v.values[0]?.name; + } else { + val[v.name] = ''; + } + }); - setValue(val); - }, [event]); + setValue(val); + }, [event]); - if (loading1 || loading2) return <Loading />; - if (error1 || error2) return <Error />; - if ((!event) || (!person)) return null; + if (loading1 || loading2) return <Loading />; + if (error1 || error2) return <Error />; + if (!event || !person) return null; - return event.data.map((structure, index) => { - if (structure.type === "boolean") { + return event.data.map((structure, index) => { + if (structure.type === 'boolean') { + return ( + <FormControlLabel + key={index} + margin="normal" + control={<Switch checked={value[structure.name] ?? false} onChange={onChangeWrapper(structure.name)} name={structure.name} />} + label={capitalize(structure.name)} + /> + ); + } else if (structure.type === 'select') { + return ( + <FormControl key={index} variant="filled" margin="normal"> + <InputLabel id={'data-' + index}>{capitalize(structure.name)}</InputLabel> + <Select labelId={'data-' + index} id={'data-select-' + index} value={value[structure.name] ?? ''} onChange={onChangeWrapper(structure.name)}> + {structure.values.map((item, i) => { + return ( + <MenuItem value={item.name} key={i}> + {capitalize(item.name)} + </MenuItem> + ); + })} + </Select> + </FormControl> + ); + } else { + return ( + <MuiTextField + key={index} + name={structure.name} + label={capitalize(structure.name)} + onChange={onChangeWrapper(structure.name)} + value={value[structure.name] ?? ''} + variant="filled" + margin="normal" + size="small" + /> + ); + } + }); +}; - return <FormControlLabel key={index} margin="normal" - control={<Switch checked={value[structure.name] ?? false} onChange={onChangeWrapper(structure.name)} name={structure.name} />} - label={capitalize(structure.name)} - />; - } else if (structure.type === "select") { - return <FormControl key={index} variant="filled" margin="normal"> - <InputLabel id={"data-" + index}>{capitalize(structure.name)}</InputLabel> - <Select - labelId={"data-" + index} - id={"data-select-" + index} - value={value[structure.name] ?? ""} - onChange={onChangeWrapper(structure.name)} - > - {structure.values.map((item, i) => { - return <MenuItem value={item.name} key={i}>{capitalize(item.name)}</MenuItem> - })} - </Select> - </FormControl>; - } else { - return <MuiTextField key={index} name={structure.name} label={capitalize(structure.name)} onChange={onChangeWrapper(structure.name)} value={value[structure.name] ?? ""} variant="filled" margin="normal" size="small" />; - } - }); +EventDataInput.propTypes = { + eventId: PropTypes.number, + personId: PropTypes.number, + priceChanged: PropTypes.func }; -const EditParticipation = props => { - const translate = useTranslate(); - const refresh = useRefresh(); - const notify = useNotify(); +const EditParticipation = (props) => { + const translate = useTranslate(); + const refresh = useRefresh(); + const notify = useNotify(); - const [price, setPrice] = useState(0); + const [price, setPrice] = useState(0); - return <> - <Edit {...props} onSuccess={() => { - notify('ra.notification.created', 'info', { smart_count: 1 }); - refresh(); + return ( + <> + <Edit + {...props} + onSuccess={() => { + notify('ra.notification.created', 'info', { smart_count: 1 }); + refresh(); }}> - <TabbedForm syncWithLocation={false}> - <FormTab label="Produits"> - <ReferenceField source="event_id" reference="events" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="person_id" reference="people" link="show" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <ReferenceField source="transaction_id" reference="transactions" link="show"> - <TextField source="name" /> - </ReferenceField> - <FormDataConsumer> - {({ formData, ...rest }) => { - if (formData.event_id === undefined || formData.person_id === undefined || (formData.transaction_id !== null && formData.transaction_id !== undefined)) - return null; - return <EventDataInput priceChanged={setPrice} source="data" key={formData.event_id + " " + formData.person_id} eventId={formData.event_id} personId={formData.person_id} />; - }} - </FormDataConsumer> - <MuiTextField value={Number(price).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' })} disabled variant="filled" type="text" label={translate('inputs.multiproductcount.price')} /> - </FormTab> - <FormTab label="Paiement"> - <FormDataConsumer> - {({ formData, ...rest }) => { - if (formData.transaction_id !== null && formData.transaction_id !== undefined) - return null; - return <PaymentInput price={price} optional />; - }} - </FormDataConsumer> - </FormTab> - </TabbedForm> - </Edit> + <TabbedForm syncWithLocation={false}> + <FormTab label="Produits"> + <ReferenceField source="event_id" reference="events" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="person_id" reference="people" link="show"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <TextField source="name" /> + </ReferenceField> + <FormDataConsumer> + {({ formData }) => { + if ( + formData.event_id === undefined || + formData.person_id === undefined || + (formData.transaction_id !== null && formData.transaction_id !== undefined) + ) + return null; + return ( + <EventDataInput + priceChanged={setPrice} + source="data" + key={formData.event_id + ' ' + formData.person_id} + eventId={formData.event_id} + personId={formData.person_id} + /> + ); + }} + </FormDataConsumer> + <MuiTextField + value={Number(price).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + })} + disabled + variant="filled" + type="text" + label={translate('inputs.multiproductcount.price')} + /> + </FormTab> + <FormTab label="Paiement"> + <FormDataConsumer> + {({ formData }) => { + if (formData.transaction_id !== null && formData.transaction_id !== undefined) return null; + return <PaymentInput price={price} optional />; + }} + </FormDataConsumer> + </FormTab> + </TabbedForm> + </Edit> </> + ); }; -const AddParticipation = props => { - const refresh = useRefresh(); - const notify = useNotify(); - const translate = useTranslate(); +const AddParticipation = (props) => { + const refresh = useRefresh(); + const notify = useNotify(); + const translate = useTranslate(); - const [price, setPrice] = useState(0); + const [price, setPrice] = useState(0); - return <> - <Create {...props} onSuccess={() => { - notify('ra.notification.created', 'info', { smart_count: 1 }); - refresh(); + return ( + <> + <Create + {...props} + onSuccess={() => { + notify('ra.notification.created', 'info', { smart_count: 1 }); + refresh(); }}> - <TabbedForm syncWithLocation={false}> - <FormTab label="Produits"> - <ReferenceInput source="event_id" reference="events" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <ReferenceInput source="person_id" reference="people" filterToQuery={searchText => ({ fullname: searchText })}> - <AutocompleteInput optionText="fullname" /> - </ReferenceInput> - <FormDataConsumer> - {({ formData, ...rest }) => { - if (formData.event_id === undefined || formData.person_id === undefined) - return null; - return <EventDataInput priceChanged={setPrice} source="data" key={formData.event_id + " " + formData.person_id} eventId={formData.event_id} personId={formData.person_id} />; - }} - </FormDataConsumer> - <MuiTextField value={Number(price).toLocaleString('fr-FR', { currency: 'EUR', currencyDisplay: 'symbol', style: 'currency' })} disabled variant="filled" type="text" label={translate('inputs.multiproductcount.price')} /> - </FormTab> - <FormTab label="Paiement"> - <PaymentInput price={price} optional /> - </FormTab> - </TabbedForm> - </Create> + <TabbedForm syncWithLocation={false}> + <FormTab label="Produits"> + <ReferenceInput source="event_id" reference="events" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <ReferenceInput source="person_id" reference="people" filterToQuery={(searchText) => ({ fullname: searchText })}> + <AutocompleteInput optionText="fullname" /> + </ReferenceInput> + <FormDataConsumer> + {({ formData }) => { + if (formData.event_id === undefined || formData.person_id === undefined) return null; + return ( + <EventDataInput + priceChanged={setPrice} + source="data" + key={formData.event_id + ' ' + formData.person_id} + eventId={formData.event_id} + personId={formData.person_id} + /> + ); + }} + </FormDataConsumer> + <MuiTextField + value={Number(price).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + })} + disabled + variant="filled" + type="text" + label={translate('inputs.multiproductcount.price')} + /> + </FormTab> + <FormTab label="Paiement"> + <PaymentInput price={price} optional /> + </FormTab> + </TabbedForm> + </Create> </> + ); }; const participations = { - list: Participation, - create: AddParticipation, - edit: EditParticipation, - show: Participation + list: Participation, + create: AddParticipation, + edit: EditParticipation, + show: Participation }; export default participations; diff --git a/src/resources/People.js b/src/resources/People.js index 02f4d1a249f50c4710d3f43d413dd2535a353120..e4e02e489f4bdbb1b2c30021c3209849f4db2565 100644 --- a/src/resources/People.js +++ b/src/resources/People.js @@ -1,105 +1,108 @@ import { useMediaQuery } from '@material-ui/core'; import GetAppIcon from '@material-ui/icons/GetApp'; -import * as React from "react"; +import PropTypes from 'prop-types'; +import * as React from 'react'; import { Button, Datagrid, EditButton, List, SimpleForm, SimpleList, SimpleShowLayout, TextField, TextInput, useDataProvider, useNotify } from 'react-admin'; import DateField from '../components/DateField'; import DateInput from '../components/DateInput'; import { CreateDialog, EditDialog, ShowDialog } from '../components/DialogForm'; -const PeopleFilters = [ - <TextInput source="firstname" />, - <TextInput source="lastname" />, - <TextInput source="discord_id" /> -]; +const PeopleFilters = [<TextInput key={0} source="firstname" />, <TextInput key={1} source="lastname" />, <TextInput key={2} source="discord_id" />]; const download = (data, filename, type) => { - let file = new Blob([data], { type: type }); - let a = document.createElement("a"), - url = URL.createObjectURL(file); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - setTimeout(function () { - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - }, 0); -} + let file = new Blob([data], { type: type }); + let a = document.createElement('a'), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(function () { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); +}; const PeopleExportButton = ({ record, ...rest }) => { + const dataProvider = useDataProvider(); + const notify = useNotify(); - const dataProvider = useDataProvider(); - const notify = useNotify(); - - const onClick = () => { - dataProvider.export('people', { id: record.id }).then((response) => { - download(JSON.stringify(response), "export.json", "application/json"); - notify('ra.notification.exported'); - }); - } - + const onClick = () => { + dataProvider.export('people', { id: record.id }).then((response) => { + download(JSON.stringify(response), 'export.json', 'application/json'); + notify('ra.notification.exported'); + }); + }; - return ( - <Button label="Exporter" {...rest} onClick={onClick}> - <GetAppIcon /> - </Button> - ) + return ( + <Button label="Exporter" {...rest} onClick={onClick}> + <GetAppIcon /> + </Button> + ); }; const People = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={PeopleFilters}> - {isDesktop ? (<Datagrid> - <TextField source="id" /> - <TextField source="firstname" /> - <TextField source="lastname" /> - <TextField source="discord_id" /> - <EditButton /> - <PeopleExportButton /> - </Datagrid>) : (<SimpleList - primaryText={record => record.firstname + " " + record.lastname} - secondaryText={record => record.discord_id} - tertiaryText={record => "#" + record.id} - />)} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <TextInput source="firstname" /> - <TextInput source="lastname" /> - <TextInput source="discord_id" /> - </SimpleForm> - </CreateDialog> - <EditDialog {...props}> - <SimpleForm redirect="list"> - <TextInput disabled source="id" /> - <TextInput source="firstname" /> - <TextInput source="lastname" /> - <TextInput source="discord_id" /> - <DateInput disabled source="created_at" /> - <DateInput disabled source="updated_at" /> - </SimpleForm> - </EditDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="firstname" /> - <TextField source="lastname" /> - <TextField source="discord_id" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={PeopleFilters}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="firstname" /> + <TextField source="lastname" /> + <TextField source="discord_id" /> + <EditButton /> + <PeopleExportButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => record.firstname + ' ' + record.lastname} + secondaryText={(record) => record.discord_id} + tertiaryText={(record) => '#' + record.id} + /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <TextInput source="firstname" /> + <TextInput source="lastname" /> + <TextInput source="discord_id" /> + </SimpleForm> + </CreateDialog> + <EditDialog {...props}> + <SimpleForm redirect="list"> + <TextInput disabled source="id" /> + <TextInput source="firstname" /> + <TextInput source="lastname" /> + <TextInput source="discord_id" /> + <DateInput disabled source="created_at" /> + <DateInput disabled source="updated_at" /> + </SimpleForm> + </EditDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <TextField source="firstname" /> + <TextField source="lastname" /> + <TextField source="discord_id" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); +}; + +PeopleExportButton.propTypes = { + record: PropTypes.any }; const people = { - list: People, - create: People, - edit: People, - show: People + list: People, + create: People, + edit: People, + show: People }; export default people; diff --git a/src/resources/PersonalAccounts.js b/src/resources/PersonalAccounts.js index 20128984cffad04bc95263527713997aebb4c252..149b97044496d7f22b0217113939523f8cd1e789 100644 --- a/src/resources/PersonalAccounts.js +++ b/src/resources/PersonalAccounts.js @@ -1,88 +1,131 @@ import { useMediaQuery } from '@material-ui/core'; import AttachMoneyIcon from '@material-ui/icons/AttachMoney'; -import * as React from "react"; -import { AutocompleteInput, Button, CreateButton, Datagrid, ExportButton, FilterButton, FunctionField, List, ReferenceField, ReferenceInput, ShowButton, SimpleForm, SimpleList, SimpleShowLayout, TextField, TopToolbar, useRedirect } from 'react-admin'; +import * as React from 'react'; +import { + AutocompleteInput, + Button, + CreateButton, + Datagrid, + ExportButton, + FilterButton, + FunctionField, + List, + ReferenceField, + ReferenceInput, + ShowButton, + SimpleForm, + SimpleList, + SimpleShowLayout, + TextField, + TopToolbar, + useRedirect +} from 'react-admin'; import DateField from '../components/DateField'; import { CreateDialog, ShowDialog } from '../components/DialogForm'; -import MoneyField from "../components/MoneyField"; -import QRInput from "../components/QRInput"; +import MoneyField from '../components/MoneyField'; +import QRInput from '../components/QRInput'; const PersonalAccountsFilters = [ - <ReferenceInput source="person_id" reference="people" filterToQuery={searchText => ({ fullname: searchText, has_account: true })}> - <AutocompleteInput optionText="fullname" /> - </ReferenceInput> + <ReferenceInput + key={0} + source="person_id" + reference="people" + filterToQuery={(searchText) => ({ + fullname: searchText, + has_account: true + })}> + <AutocompleteInput optionText="fullname" /> + </ReferenceInput> ]; -const PersonalAccountsListActions = ({ basePath, ...props }) => { - const redirect = useRedirect(); +const PersonalAccountsListActions = () => { + const redirect = useRedirect(); - return ( - <TopToolbar> - <Button - onClick={() => { - redirect('/personal_accounts/refill'); - }} - label="Recharger" - ><AttachMoneyIcon /></Button> - <FilterButton /> - <CreateButton /> - <ExportButton /> - </TopToolbar> - ); -} + return ( + <TopToolbar> + <Button + onClick={() => { + redirect('/personal_accounts/refill'); + }} + label="Recharger"> + <AttachMoneyIcon /> + </Button> + <FilterButton /> + <CreateButton /> + <ExportButton /> + </TopToolbar> + ); +}; const PersonalAccounts = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={PersonalAccountsFilters} actions={<PersonalAccountsListActions />}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <ReferenceField source="person_id" reference="people" link="show" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <MoneyField noLabel={true} source="balance" /> - <ShowButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => - <ReferenceField record={record} source="person_id" reference="people" link={false} > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField>} - tertiaryText={record => Number(record.balance).toLocaleString('fr-FR', { currency: "EUR", currencyDisplay: 'symbol', style: 'currency' })} - linkType="show" - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <ReferenceInput source="person_id" reference="people" filterToQuery={searchText => ({ fullname: searchText, has_account: false })}> - <AutocompleteInput optionText="fullname" /> - </ReferenceInput> - <QRInput source="token" label="Scan Carte Étudiant" regexp="(?:https?:\/\/esc\.gg\/|core:\/\/)([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}).*" /> - </SimpleForm> - </CreateDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <ReferenceField source="person_id" reference="people" link="show" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <MoneyField noLabel={false} source="balance" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={PersonalAccountsFilters} actions={<PersonalAccountsListActions />}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <ReferenceField source="person_id" reference="people" link="show"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <MoneyField noLabel={true} source="balance" /> + <ShowButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => ( + <ReferenceField record={record} source="person_id" reference="people" link={false}> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + )} + tertiaryText={(record) => + Number(record.balance).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + }) + } + linkType="show" + /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <ReferenceInput + source="person_id" + reference="people" + filterToQuery={(searchText) => ({ + fullname: searchText, + has_account: false + })}> + <AutocompleteInput optionText="fullname" /> + </ReferenceInput> + <QRInput + source="token" + label="Scan Carte Étudiant" + regexp="(?:https?:\/\/esc\.gg\/|core:\/\/)([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}).*" + /> + </SimpleForm> + </CreateDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <ReferenceField source="person_id" reference="people" link="show"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <MoneyField noLabel={false} source="balance" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const personal_accounts = { - list: PersonalAccounts, - create: PersonalAccounts, - show: PersonalAccounts + list: PersonalAccounts, + create: PersonalAccounts, + show: PersonalAccounts }; export default personal_accounts; diff --git a/src/resources/PersonalRefills.js b/src/resources/PersonalRefills.js index fb73166ff673d002910f1fbd8f027790a7750609..4c6aaca565f4be0e68d30b5d0bfc0c3cec1325db 100644 --- a/src/resources/PersonalRefills.js +++ b/src/resources/PersonalRefills.js @@ -1,31 +1,40 @@ -import React from "react"; +import React from 'react'; import { Create, SelectInput, SimpleForm, useNotify, useRefresh } from 'react-admin'; -import MoneyInput from "../components/MoneyInput"; -import PersonalAccountSelector from "../components/PersonalAccountSelector"; +import MoneyInput from '../components/MoneyInput'; +import PersonalAccountSelector from '../components/PersonalAccountSelector'; -const PersonalRefills = props => { - const refresh = useRefresh(); - const notify = useNotify(); +const PersonalRefills = (props) => { + const refresh = useRefresh(); + const notify = useNotify(); - return ( - <Create {...props} onSuccess={() => { - notify('ra.notification.created', 'info', { smart_count: 1 }); - refresh(); - }} basePath="/personal_accounts" resource="personal_refills"> - <SimpleForm> - <PersonalAccountSelector source="token" label="Compte" /> - <MoneyInput source="amount" /> - <SelectInput source="payment" label="Moyen de paiement" allowEmpty={false} choices={[ - { id: 'cash', name: 'Liquide (Caisse)' }, - { id: 'card', name: 'Carte Bancaire' } - ]} /> - </SimpleForm> - </Create> - ); + return ( + <Create + {...props} + onSuccess={() => { + notify('ra.notification.created', 'info', { smart_count: 1 }); + refresh(); + }} + basePath="/personal_accounts" + resource="personal_refills"> + <SimpleForm> + <PersonalAccountSelector source="token" label="Compte" /> + <MoneyInput source="amount" /> + <SelectInput + source="payment" + label="Moyen de paiement" + allowEmpty={false} + choices={[ + { id: 'cash', name: 'Liquide (Caisse)' }, + { id: 'card', name: 'Carte Bancaire' } + ]} + /> + </SimpleForm> + </Create> + ); }; const personal_refills = { - create: PersonalRefills + create: PersonalRefills }; export default personal_refills; diff --git a/src/resources/PersonalTransactions.js b/src/resources/PersonalTransactions.js index f1f6ae4ff74d0ee63f6b11649965b4af7b790f65..9315ad5582da502f1759b5b77aa5717925c78754 100644 --- a/src/resources/PersonalTransactions.js +++ b/src/resources/PersonalTransactions.js @@ -1,87 +1,95 @@ import { useMediaQuery } from '@material-ui/core'; -import * as React from "react"; +import * as React from 'react'; import { AutocompleteInput, Datagrid, List, ReferenceField, ReferenceInput, ShowButton, SimpleList, SimpleShowLayout, TextField } from 'react-admin'; import DateField from '../components/DateField'; import { ShowDialog } from '../components/DialogForm'; -import MoneyField from "../components/MoneyField"; +import MoneyField from '../components/MoneyField'; const PersonalTransactionsFilters = [ - <ReferenceInput source="personal_account_id" reference="personal_accounts"> - <ReferenceInput source="person_id" reference="people" filterToQuery={searchText => ({ fullname: searchText })}> - <AutocompleteInput optionText="fullname" /> - </ReferenceInput> - </ReferenceInput>, - <ReferenceInput source="user_id" reference="users" filterToQuery={searchText => ({ username: searchText })}> - <AutocompleteInput optionText="username" /> + <ReferenceInput key={0} source="personal_account_id" reference="personal_accounts"> + <ReferenceInput source="person_id" reference="people" filterToQuery={(searchText) => ({ fullname: searchText })}> + <AutocompleteInput optionText="fullname" /> </ReferenceInput> + </ReferenceInput>, + <ReferenceInput key={1} source="user_id" reference="users" filterToQuery={(searchText) => ({ username: searchText })}> + <AutocompleteInput optionText="username" /> + </ReferenceInput> ]; const PersonalTransactions = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={PersonalTransactionsFilters} bulkActionButtons={false}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <MoneyField noLabel={true} source="amount" /> - <ReferenceField source="transaction_id" reference="transactions" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="personal_account_id" reference="personal_accounts" link={false}> - <ReferenceField source="person_id" reference="people" link="show"> - <TextField source="fullname" /> - </ReferenceField> - </ReferenceField> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - <DateField source="created_at" /> - <ShowButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => - <ReferenceField record={record} source="transaction_id" reference="transactions" link={false} > - <TextField source="name" /> - </ReferenceField>} - secondaryText={record => - <ReferenceField record={record} source="personal_account_id" reference="personal_accounts"> - <ReferenceField source="person_id" reference="people" link={false}> - <TextField source="fullname" /> - </ReferenceField> - </ReferenceField>} - tertiaryText={record => Number(record.amount).toLocaleString('fr-FR', { currency: "EUR", currencyDisplay: 'symbol', style: 'currency' })} - linkType="show" - /> - )} - </List> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <MoneyField source="amount" /> - <ReferenceField source="personal_account_id" reference="personal_accounts"> - <ReferenceField source="person_id" reference="people" link="show"> - <TextField source="fullname" /> - </ReferenceField> - </ReferenceField> - <ReferenceField source="transaction_id" reference="transactions" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="user_id" reference="users" link="show"> - <TextField source="username" /> - </ReferenceField> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={PersonalTransactionsFilters} bulkActionButtons={false}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <MoneyField noLabel={true} source="amount" /> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="personal_account_id" reference="personal_accounts" link={false}> + <ReferenceField source="person_id" reference="people" link="show"> + <TextField source="fullname" /> + </ReferenceField> + </ReferenceField> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + <DateField source="created_at" /> + <ShowButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => ( + <ReferenceField record={record} source="transaction_id" reference="transactions" link={false}> + <TextField source="name" /> + </ReferenceField> + )} + secondaryText={(record) => ( + <ReferenceField record={record} source="personal_account_id" reference="personal_accounts"> + <ReferenceField source="person_id" reference="people" link={false}> + <TextField source="fullname" /> + </ReferenceField> + </ReferenceField> + )} + tertiaryText={(record) => + Number(record.amount).toLocaleString('fr-FR', { + currency: 'EUR', + currencyDisplay: 'symbol', + style: 'currency' + }) + } + linkType="show" + /> + )} + </List> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <MoneyField source="amount" /> + <ReferenceField source="personal_account_id" reference="personal_accounts"> + <ReferenceField source="person_id" reference="people" link="show"> + <TextField source="fullname" /> + </ReferenceField> + </ReferenceField> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="user_id" reference="users" link="show"> + <TextField source="username" /> + </ReferenceField> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const personal_transactions = { - list: PersonalTransactions, - show: PersonalTransactions + list: PersonalTransactions, + show: PersonalTransactions }; export default personal_transactions; diff --git a/src/resources/Products.js b/src/resources/Products.js index 3d36e275c922a53555ceaebea13d8fcab05105ce..1ae88970e5f9af93c715393d924c98884e6db7f3 100644 --- a/src/resources/Products.js +++ b/src/resources/Products.js @@ -1,126 +1,151 @@ import { useMediaQuery } from '@material-ui/core'; import RemoveShoppingCartIcon from '@material-ui/icons/RemoveShoppingCart'; import ShoppingCartIcon from '@material-ui/icons/ShoppingCart'; -import * as React from "react"; -import { AutocompleteInput, BooleanField, BooleanInput, BulkDeleteButton, BulkUpdateButton, CreateButton, Datagrid, EditButton, ExportButton, FilterButton, List, NullableBooleanInput, NumberField, NumberInput, ReferenceField, ReferenceInput, SimpleForm, SimpleList, SimpleShowLayout, TextField, TextInput, TopToolbar, useTranslate } from 'react-admin'; +import * as React from 'react'; +import { + AutocompleteInput, + BooleanField, + BooleanInput, + BulkDeleteButton, + BulkUpdateButton, + CreateButton, + Datagrid, + EditButton, + ExportButton, + FilterButton, + List, + NullableBooleanInput, + NumberField, + NumberInput, + ReferenceField, + ReferenceInput, + SimpleForm, + SimpleList, + SimpleShowLayout, + TextField, + TextInput, + TopToolbar, + useTranslate +} from 'react-admin'; import DateField from '../components/DateField'; import DateInput from '../components/DateInput'; import { CreateDialog, EditDialog, ShowDialog } from '../components/DialogForm'; -import MoneyField from "../components/MoneyField"; -import MoneyInput from "../components/MoneyInput"; +import MoneyField from '../components/MoneyField'; +import MoneyInput from '../components/MoneyInput'; import { RecalculateButton } from '../components/RecalculateButton'; const ProductsFilters = [ - <TextInput source="name" />, - <ReferenceInput source="category_id" reference="products_categories" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput>, - <NullableBooleanInput source="alerts" />, - <NullableBooleanInput source="salable" /> + <TextInput key={0} source="name" />, + <ReferenceInput key={1} source="category_id" reference="products_categories" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput>, + <NullableBooleanInput key={2} source="alerts" />, + <NullableBooleanInput key={3} source="salable" /> ]; -const ProductsListActions = ({ basePath, ...props }) => ( - <TopToolbar> - <FilterButton /> - <RecalculateButton /> - <CreateButton /> - <ExportButton /> - </TopToolbar> +const ProductsListActions = () => ( + <TopToolbar> + <FilterButton /> + <RecalculateButton /> + <CreateButton /> + <ExportButton /> + </TopToolbar> ); -const ProductsBulkActionButtons = props => { - const translate = useTranslate(); - return ( - <React.Fragment> - <BulkUpdateButton {...props} label={translate('resources.products.mark_salabe')} data={{ "salable": true }} icon={<ShoppingCartIcon />} /> - <BulkUpdateButton {...props} label={translate('resources.products.mark_unsalable')} data={{ "salable": false }} icon={<RemoveShoppingCartIcon />} /> - <BulkDeleteButton {...props} /> - </React.Fragment> - ); +const ProductsBulkActionButtons = (props) => { + const translate = useTranslate(); + return ( + <React.Fragment> + <BulkUpdateButton {...props} label={translate('resources.products.mark_salabe')} data={{ salable: true }} icon={<ShoppingCartIcon />} /> + <BulkUpdateButton {...props} label={translate('resources.products.mark_unsalable')} data={{ salable: false }} icon={<RemoveShoppingCartIcon />} /> + <BulkDeleteButton {...props} /> + </React.Fragment> + ); }; const Products = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={ProductsFilters} bulkActionButtons={<ProductsBulkActionButtons />} actions={<ProductsListActions />}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <TextField source="name" /> - <ReferenceField source="category_id" reference="products_categories" > - <TextField source="name" /> - </ReferenceField> - <MoneyField noLabel={true} source="price" /> - <NumberField source="count" /> - <NumberField source="alert_level" /> - <BooleanField source="salable" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - <EditButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => record.name} - secondaryText={record => - <ReferenceField record={record} source="category_id" reference="products_categories" link={false}> - <TextField source="name" /> - </ReferenceField>} - tertiaryText={record => record.count} - linkType="edit" - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <TextInput source="name" /> - <ReferenceInput source="category_id" reference="products_categories" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <MoneyInput source="price" /> - <NumberInput source="alert_level" /> - <BooleanInput source="salable" defaultValue={true} /> - </SimpleForm> - </CreateDialog> - <EditDialog {...props}> - <SimpleForm redirect="list"> - <TextInput disabled source="id" /> - <TextInput source="name" /> - <ReferenceInput source="category_id" reference="products_categories" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <MoneyInput source="price" /> - <NumberInput disabled source="count" /> - <NumberInput source="alert_level" /> - <BooleanInput source="salable" /> - <DateInput disabled source="created_at" /> - <DateInput disabled source="updated_at" /> - </SimpleForm> - </EditDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="name" /> - <ReferenceField source="category_id" reference="products_categories" > - <TextField source="name" /> - </ReferenceField> - <MoneyField source="price" /> - <NumberField source="count" /> - <NumberField source="alert_level" /> - <BooleanField source="salable" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={ProductsFilters} bulkActionButtons={<ProductsBulkActionButtons />} actions={<ProductsListActions />}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="name" /> + <ReferenceField source="category_id" reference="products_categories"> + <TextField source="name" /> + </ReferenceField> + <MoneyField noLabel={true} source="price" /> + <NumberField source="count" /> + <NumberField source="alert_level" /> + <BooleanField source="salable" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + <EditButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => record.name} + secondaryText={(record) => ( + <ReferenceField record={record} source="category_id" reference="products_categories" link={false}> + <TextField source="name" /> + </ReferenceField> + )} + tertiaryText={(record) => record.count} + linkType="edit" + /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <TextInput source="name" /> + <ReferenceInput source="category_id" reference="products_categories" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <MoneyInput source="price" /> + <NumberInput source="alert_level" /> + <BooleanInput source="salable" defaultValue={true} /> + </SimpleForm> + </CreateDialog> + <EditDialog {...props}> + <SimpleForm redirect="list"> + <TextInput disabled source="id" /> + <TextInput source="name" /> + <ReferenceInput source="category_id" reference="products_categories" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <MoneyInput source="price" /> + <NumberInput disabled source="count" /> + <NumberInput source="alert_level" /> + <BooleanInput source="salable" /> + <DateInput disabled source="created_at" /> + <DateInput disabled source="updated_at" /> + </SimpleForm> + </EditDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <TextField source="name" /> + <ReferenceField source="category_id" reference="products_categories"> + <TextField source="name" /> + </ReferenceField> + <MoneyField source="price" /> + <NumberField source="count" /> + <NumberField source="alert_level" /> + <BooleanField source="salable" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const products = { - list: Products, - create: Products, - edit: Products, - show: Products + list: Products, + create: Products, + edit: Products, + show: Products }; export default products; diff --git a/src/resources/ProductsCategories.js b/src/resources/ProductsCategories.js index d3da82a246a7b2904ca3dfe1c223c84ed9ff9e4f..8dada504572c77a5b62675e78e9b7e1ed1158049 100644 --- a/src/resources/ProductsCategories.js +++ b/src/resources/ProductsCategories.js @@ -1,65 +1,59 @@ import { useMediaQuery } from '@material-ui/core'; -import * as React from "react"; +import * as React from 'react'; import { Datagrid, EditButton, List, SimpleForm, SimpleList, SimpleShowLayout, TextField, TextInput } from 'react-admin'; import DateField from '../components/DateField'; import DateInput from '../components/DateInput'; import { CreateDialog, EditDialog, ShowDialog } from '../components/DialogForm'; -const ProductsCategoriesFilters = [ - <TextInput source="name" /> -]; +const ProductsCategoriesFilters = [<TextInput key={0} source="name" />]; const ProductsCategories = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={ProductsCategoriesFilters}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <TextField source="name" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - <EditButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => record.name} - tertiaryText={record => "#" + record.id} - linkType="edit" - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <TextInput source="name" /> - </SimpleForm> - </CreateDialog> - <EditDialog {...props}> - <SimpleForm redirect="list"> - <TextInput disabled source="id" /> - <TextInput source="name" /> - <DateInput disabled source="created_at" /> - <DateInput disabled source="updated_at" /> - </SimpleForm> - </EditDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="name" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={ProductsCategoriesFilters}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="name" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + <EditButton /> + </Datagrid> + ) : ( + <SimpleList primaryText={(record) => record.name} tertiaryText={(record) => '#' + record.id} linkType="edit" /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <TextInput source="name" /> + </SimpleForm> + </CreateDialog> + <EditDialog {...props}> + <SimpleForm redirect="list"> + <TextInput disabled source="id" /> + <TextInput source="name" /> + <DateInput disabled source="created_at" /> + <DateInput disabled source="updated_at" /> + </SimpleForm> + </EditDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <TextField source="name" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const products_categories = { - list: ProductsCategories, - create: ProductsCategories, - edit: ProductsCategories, - show: ProductsCategories + list: ProductsCategories, + create: ProductsCategories, + edit: ProductsCategories, + show: ProductsCategories }; export default products_categories; diff --git a/src/resources/ProductsCounts.js b/src/resources/ProductsCounts.js index 0210daa6cd5df2820a623ad1d0898603eb096232..0a5ebda5b15bca1dcec22f0510c3b3fd93bf0175 100644 --- a/src/resources/ProductsCounts.js +++ b/src/resources/ProductsCounts.js @@ -1,83 +1,87 @@ import { useMediaQuery } from '@material-ui/core'; -import * as React from "react"; +import * as React from 'react'; import { ArrayField, Create, Datagrid, List, ReferenceField, ShowButton, SimpleForm, SimpleList, SimpleShowLayout, TextField } from 'react-admin'; import DateField from '../components/DateField'; import { ShowDialog } from '../components/DialogForm'; -import { MultiProductCountInput, MultiProductCountItem } from "../components/MultiProductCountInput"; +import { MultiProductCountInput, MultiProductCountItem } from '../components/MultiProductCountInput'; const ProductsCounts = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} bulkActionButtons={false}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <ReferenceField source="movement_id" reference="movements" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField label="Créateur" source="movement_id" reference="movements" link={false}> - <ReferenceField source="user_id" reference="users" link="show"> - <TextField source="username" /> - </ReferenceField> - </ReferenceField> - <DateField source="created_at" /> - <ShowButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => <ReferenceField record={record} source="movement_id" reference="movements" link={false}> - <TextField source="name" /> - </ReferenceField>} - secondaryText={record => new Date(record.created_at).toLocaleString()} - tertiaryText={record => "#" + record.id} - linkType="show" - /> - )} - </List> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <ArrayField source="data"> - <Datagrid> - <ReferenceField source="id" reference="products" link="show"> - <TextField source="name" /> - </ReferenceField> - <TextField source="count" /> - </Datagrid> - </ArrayField> - <ReferenceField source="movement_id" reference="movements" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField label="Créateur" source="movement_id" reference="movements" link="show"> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - </ReferenceField> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} bulkActionButtons={false}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <ReferenceField source="movement_id" reference="movements" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField label="Créateur" source="movement_id" reference="movements" link={false}> + <ReferenceField source="user_id" reference="users" link="show"> + <TextField source="username" /> + </ReferenceField> + </ReferenceField> + <DateField source="created_at" /> + <ShowButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => ( + <ReferenceField record={record} source="movement_id" reference="movements" link={false}> + <TextField source="name" /> + </ReferenceField> + )} + secondaryText={(record) => new Date(record.created_at).toLocaleString()} + tertiaryText={(record) => '#' + record.id} + linkType="show" + /> + )} + </List> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <ArrayField source="data"> + <Datagrid> + <ReferenceField source="id" reference="products" link="show"> + <TextField source="name" /> + </ReferenceField> + <TextField source="count" /> + </Datagrid> + </ArrayField> + <ReferenceField source="movement_id" reference="movements" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField label="Créateur" source="movement_id" reference="movements" link="show"> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + </ReferenceField> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const CountProducts = (props) => { - return <> - <Create {...props}> - <SimpleForm> - <MultiProductCountInput countZero source="data"> - <MultiProductCountItem /> - </MultiProductCountInput> - </SimpleForm> - </Create> - </>; + return ( + <> + <Create {...props}> + <SimpleForm> + <MultiProductCountInput countZero source="data"> + <MultiProductCountItem /> + </MultiProductCountInput> + </SimpleForm> + </Create> + </> + ); }; const products_counts = { - list: ProductsCounts, - show: ProductsCounts, - create: CountProducts + list: ProductsCounts, + show: ProductsCounts, + create: CountProducts }; export default products_counts; diff --git a/src/resources/Profile.js b/src/resources/Profile.js index 4e6884bc62dc9d8689de79f066f1309067a96f3d..20bb234e1fea4c6a41d4a5c13d9cf5173d5f3002 100644 --- a/src/resources/Profile.js +++ b/src/resources/Profile.js @@ -1,5 +1,18 @@ -import { Button as MuiButton, Dialog, DialogActions as MuiDialogActions, DialogContent as MuiDialogContent, DialogTitle, FormControlLabel, FormGroup, Grid, IconButton, Switch, Typography, withStyles } from '@material-ui/core'; -import { makeStyles, styled } from "@material-ui/core/styles"; +import { + Button as MuiButton, + Dialog, + DialogActions as MuiDialogActions, + DialogContent as MuiDialogContent, + DialogTitle, + FormControlLabel, + FormGroup, + Grid, + IconButton, + Switch, + Typography, + withStyles +} from '@material-ui/core'; +import { makeStyles, styled } from '@material-ui/core/styles'; import { fade } from '@material-ui/core/styles/colorManipulator'; import AddIcon from '@material-ui/icons/Add'; import ClearIcon from '@material-ui/icons/Clear'; @@ -9,337 +22,471 @@ import LockIcon from '@material-ui/icons/Lock'; import LockOpenIcon from '@material-ui/icons/LockOpen'; import SecurityIcon from '@material-ui/icons/Security'; import VpnKeyIcon from '@material-ui/icons/VpnKey'; -import { spacing } from "@material-ui/system"; +import { spacing } from '@material-ui/system'; import axios from 'axios'; +import PropTypes from 'prop-types'; import React, { useEffect, useState } from 'react'; -import { ArrayField, Button, ChipField, Datagrid, Edit, Labeled, ReferenceArrayField, SaveButton, SimpleForm, SingleFieldList, TextField, TextInput, Toolbar, useAuthProvider, useDeleteWithConfirmController, useNotify, useRecordContext, useRedirect, useRefresh, useTranslate, useUpdateLoading } from 'react-admin'; +import { + ArrayField, + Button, + ChipField, + Datagrid, + Edit, + Labeled, + ReferenceArrayField, + SaveButton, + SimpleForm, + SingleFieldList, + TextField, + TextInput, + Toolbar, + useAuthProvider, + useDeleteWithConfirmController, + useNotify, + useRecordContext, + useRedirect, + useRefresh, + useTranslate, + useUpdateLoading +} from 'react-admin'; import DateField from '../components/DateField'; const StyledButton = styled(Button)(spacing); const StyledGrid = styled(Grid)(spacing); const ChangePasswordField = (props) => { - const { source } = props; - const record = useRecordContext(props); - const redirect = useRedirect(); - const translate = useTranslate(); - return <> - <StyledGrid container mb={2} alignItems="center"> - <Grid item xs> - {record[source] !== null ? - <Labeled label={translate('resources.profile.password.last_change')} {...props}> - <DateField {...props} /> - </Labeled> - : - <Labeled label={translate('resources.profile.password.last_change')} {...props}> - <><Typography component="span" variant="body2">{translate('resources.profile.password.never')}</Typography></> - </Labeled> - } - </Grid> - <Grid item> - <StyledButton size="medium" ml="auto" label={translate('resources.profile.password.change')} onClick={() => redirect("/change-password")}><VpnKeyIcon /></StyledButton> - </Grid> - </StyledGrid> - </>; + const { source } = props; + const record = useRecordContext(props); + const redirect = useRedirect(); + const translate = useTranslate(); + return ( + <> + <StyledGrid container mb={2} alignItems="center"> + <Grid item xs> + {record[source] !== null ? ( + <Labeled label={translate('resources.profile.password.last_change')} {...props}> + <DateField {...props} /> + </Labeled> + ) : ( + <Labeled label={translate('resources.profile.password.last_change')} {...props}> + <> + <Typography component="span" variant="body2"> + {translate('resources.profile.password.never')} + </Typography> + </> + </Labeled> + )} + </Grid> + <Grid item> + <StyledButton size="medium" ml="auto" label={translate('resources.profile.password.change')} onClick={() => redirect('/change-password')}> + <VpnKeyIcon /> + </StyledButton> + </Grid> + </StyledGrid> + </> + ); }; +ChangePasswordField.propTypes = { + source: PropTypes.string +}; const styles = (theme) => ({ - root: { - margin: 0, - padding: theme.spacing(2), - }, - closeButton: { - position: 'absolute', - right: theme.spacing(1), - top: theme.spacing(1), - color: theme.palette.grey[500], - }, + root: { + margin: 0, + padding: theme.spacing(2) + }, + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500] + } }); const MyDialogTitle = withStyles(styles)((props) => { - const { children, classes, onClose, ...other } = props; - return ( - <DialogTitle disableTypography className={classes.root} {...other}> - <Typography variant="h6" style={{ marginTop: '1rem', marginBottom: '1rem' }}>{children}</Typography> - {onClose ? ( - <IconButton aria-label="close" className={classes.closeButton} onClick={onClose}> - <CloseIcon /> - </IconButton> - ) : null} - </DialogTitle> - ); + const { children, classes, onClose, ...other } = props; + return ( + <DialogTitle disableTypography className={classes.root} {...other}> + <Typography variant="h6" style={{ marginTop: '1rem', marginBottom: '1rem' }}> + {children} + </Typography> + {onClose ? ( + <IconButton aria-label="close" className={classes.closeButton} onClick={onClose}> + <CloseIcon /> + </IconButton> + ) : null} + </DialogTitle> + ); }); const DialogContent = withStyles((theme) => ({ - root: { - padding: theme.spacing(2), - }, + root: { + padding: theme.spacing(2) + } }))(MuiDialogContent); const DialogActions = withStyles((theme) => ({ - root: { - margin: 0, - padding: theme.spacing(1), - }, + root: { + margin: 0, + padding: theme.spacing(1) + } }))(MuiDialogActions); const TwoFactorAuthField = (props) => { - const classes = useStyles(props); - const notify = useNotify(); - const { source } = props; - const record = useRecordContext(props); - const redirect = useRedirect(); - const [open, setOpen] = useState(false); - const [codes, setCodes] = useState([]); - const { startLoading, stopLoading } = useUpdateLoading(); - const translate = useTranslate(); + const classes = useStyles(props); + const notify = useNotify(); + const { source } = props; + const record = useRecordContext(props); + const redirect = useRedirect(); + const [open, setOpen] = useState(false); + const [codes, setCodes] = useState([]); + const { startLoading, stopLoading } = useUpdateLoading(); + const translate = useTranslate(); - const handleClickOpen = () => { - setOpen(true); - }; - const handleClose = () => { - setOpen(false); - }; + const handleClickOpen = () => { + setOpen(true); + }; + const handleClose = () => { + setOpen(false); + }; - return <> - <StyledGrid container mb={2} alignItems="center"> - <Grid item xs> - <Labeled label={translate('resources.profile.2fa.status')} {...props}> - {record[source] ? ( - <div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}> - <DoneIcon /> - <span style={{ marginLeft: '8px' }}>{translate('resources.profile.2fa.enabled')}</span> - </div> - ) : ( - <div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}> - <ClearIcon /> - <span style={{ marginLeft: '8px' }}>{translate('resources.profile.2fa.disabled')}</span> - </div> - )} - </Labeled> - </Grid> - <Grid item> - {record[source] ? (<> - <StyledButton size="medium" ml="auto" label={translate('resources.profile.2fa.recovery')} onClick={() => { - startLoading(); - return axios.get('/user/two-factor-recovery-codes').then(response => { - stopLoading(); - setCodes(response.data); - handleClickOpen(); - }).catch(error => { - stopLoading(); - console.log(error); - notify(error?.response?.data?.message); - }); - }}><SecurityIcon /></StyledButton> - <StyledButton className={classes.deleteButton} size="medium" ml="auto" label={translate('resources.profile.2fa.disable')} onClick={() => redirect("/two-factor/disable")}><LockOpenIcon /></StyledButton> - </>) : ( - <StyledButton size="medium" ml="auto" label={translate('resources.profile.2fa.enable')} onClick={() => redirect("/two-factor/enable")}><LockIcon /></StyledButton> - )} - </Grid> - </StyledGrid> - {record[source] ? - <Dialog onClose={handleClose} aria-labelledby="customized-dialog-title" open={open}> - <MyDialogTitle id="customized-dialog-title" onClose={handleClose}> - {translate('resources.profile.2fa.recovery')} - </MyDialogTitle> - <DialogContent> - <Typography gutterBottom style={{ marginBottom: '16px' }}> - {translate('resources.profile.2fa.recovery_message')} - </Typography> - {codes.map((code, key) => ( - <Typography style={{ fontFamily: 'monospace', textAlign: 'center' }} gutterBottom key={key}> - {code} - </Typography> - ))} - </DialogContent> - <DialogActions> - <MuiButton autoFocus onClick={() => { - startLoading(); - return axios.post('/user/two-factor-recovery-codes').then(response => { - return axios.get('/user/two-factor-recovery-codes').then(response => { - stopLoading(); - setCodes(response.data); - }).catch(error => { - stopLoading(); - console.log(error); - notify(error?.response?.data?.message); - }); - }).catch(error => { - stopLoading(); - console.log(error); - notify(error?.response?.data?.message); - }); - }} color="primary"> - Regen - </MuiButton> - <MuiButton autoFocus onClick={handleClose} color="primary"> - Close - </MuiButton> - </DialogActions> - </Dialog> : "" - } - </>; + return ( + <> + <StyledGrid container mb={2} alignItems="center"> + <Grid item xs> + <Labeled label={translate('resources.profile.2fa.status')} {...props}> + {record[source] ? ( + <div + style={{ + display: 'flex', + alignItems: 'center', + flexWrap: 'wrap' + }}> + <DoneIcon /> + <span style={{ marginLeft: '8px' }}>{translate('resources.profile.2fa.enabled')}</span> + </div> + ) : ( + <div + style={{ + display: 'flex', + alignItems: 'center', + flexWrap: 'wrap' + }}> + <ClearIcon /> + <span style={{ marginLeft: '8px' }}>{translate('resources.profile.2fa.disabled')}</span> + </div> + )} + </Labeled> + </Grid> + <Grid item> + {record[source] ? ( + <> + <StyledButton + size="medium" + ml="auto" + label={translate('resources.profile.2fa.recovery')} + onClick={() => { + startLoading(); + return axios + .get('/user/two-factor-recovery-codes') + .then((response) => { + stopLoading(); + setCodes(response.data); + handleClickOpen(); + }) + .catch((error) => { + stopLoading(); + console.log(error); + notify(error?.response?.data?.message); + }); + }}> + <SecurityIcon /> + </StyledButton> + <StyledButton + className={classes.deleteButton} + size="medium" + ml="auto" + label={translate('resources.profile.2fa.disable')} + onClick={() => redirect('/two-factor/disable')}> + <LockOpenIcon /> + </StyledButton> + </> + ) : ( + <StyledButton size="medium" ml="auto" label={translate('resources.profile.2fa.enable')} onClick={() => redirect('/two-factor/enable')}> + <LockIcon /> + </StyledButton> + )} + </Grid> + </StyledGrid> + {record[source] ? ( + <Dialog onClose={handleClose} aria-labelledby="customized-dialog-title" open={open}> + <MyDialogTitle id="customized-dialog-title" onClose={handleClose}> + {translate('resources.profile.2fa.recovery')} + </MyDialogTitle> + <DialogContent> + <Typography gutterBottom style={{ marginBottom: '16px' }}> + {translate('resources.profile.2fa.recovery_message')} + </Typography> + {codes.map((code, key) => ( + <Typography style={{ fontFamily: 'monospace', textAlign: 'center' }} gutterBottom key={key}> + {code} + </Typography> + ))} + </DialogContent> + <DialogActions> + <MuiButton + autoFocus + onClick={() => { + startLoading(); + return axios + .post('/user/two-factor-recovery-codes') + .then(() => { + return axios + .get('/user/two-factor-recovery-codes') + .then((response) => { + stopLoading(); + setCodes(response.data); + }) + .catch((error) => { + stopLoading(); + console.log(error); + notify(error?.response?.data?.message); + }); + }) + .catch((error) => { + stopLoading(); + console.log(error); + notify(error?.response?.data?.message); + }); + }} + color="primary"> + Regen + </MuiButton> + <MuiButton autoFocus onClick={handleClose} color="primary"> + Close + </MuiButton> + </DialogActions> + </Dialog> + ) : ( + '' + )} + </> + ); +}; + +TwoFactorAuthField.propTypes = { + source: PropTypes.string }; const useStyles = makeStyles( - theme => ({ - deleteButton: { - color: theme.palette.error.main, - '&:hover': { - backgroundColor: fade(theme.palette.error.main, 0.12), - // Reset on mouse devices - '@media (hover: none)': { - backgroundColor: 'transparent', - }, - }, - }, - }), - { name: 'RaDeleteWithUndoButton' } + (theme) => ({ + deleteButton: { + color: theme.palette.error.main, + '&:hover': { + backgroundColor: fade(theme.palette.error.main, 0.12), + // Reset on mouse devices + '@media (hover: none)': { + backgroundColor: 'transparent' + } + } + } + }), + { name: 'RaDeleteWithUndoButton' } ); -const TokenField = ({ onClick, ...props }) => { - const classes = useStyles(props); - const record = useRecordContext(props); - const translate = useTranslate(); +const TokenField = (props) => { + const classes = useStyles(props); + const record = useRecordContext(props); + const translate = useTranslate(); - - const { loading, handleDelete } = useDeleteWithConfirmController({ - resource: "tokens", - record: record, - basePath: "profile", - mutationMode: "pessimistic" - }); - return ( - <> - <div style={{ width: '100%', display: 'flex', alignItems: 'end' }}> - <StyledButton size="medium" ml="auto" className={classes.deleteButton} label={translate('ra.action.delete')} onClick={handleDelete} disabled={loading}> - <ClearIcon /> - </StyledButton> - </div> - </> - ); + const { loading, handleDelete } = useDeleteWithConfirmController({ + resource: 'tokens', + record: record, + basePath: 'profile', + mutationMode: 'pessimistic' + }); + return ( + <> + <div style={{ width: '100%', display: 'flex', alignItems: 'end' }}> + <StyledButton size="medium" ml="auto" className={classes.deleteButton} label={translate('ra.action.delete')} onClick={handleDelete} disabled={loading}> + <ClearIcon /> + </StyledButton> + </div> + </> + ); }; const TokensField = (props) => { - const redirect = useRedirect(); - const classes = useStyles(props); - const translate = useTranslate(); + const redirect = useRedirect(); + const classes = useStyles(props); + const translate = useTranslate(); - return (<> - <StyledGrid container mb={2} alignItems="center"> - <Grid item xs> - <Typography variant="h5" component="h2">{translate('resources.profile.tokens.title')}</Typography> - </Grid> - <Grid item> - <StyledButton size="medium" ml="auto" label={translate('resources.profile.tokens.new')} onClick={() => redirect("/tokens/create")}><AddIcon /></StyledButton> - <StyledButton className={classes.deleteButton} size="medium" ml="auto" label={translate('resources.profile.tokens.clear')} onClick={() => redirect("/tokens/clear")}><ClearIcon /></StyledButton> - </Grid> - </StyledGrid> - <> - {props.record.tokens.length === 0 ? <Typography>{translate('resources.profile.tokens.none')}</Typography> : - <ArrayField source="tokens" {...props}> - <Datagrid> - <TextField source="id" label={translate('resources.profile.tokens.id')} /> - <TextField source="name" label={translate('resources.profile.tokens.name')} /> - <DateField source="last_used_at" label={translate('resources.profile.tokens.last_used_at')} /> - <DateField source="created_at" label={translate('resources.profile.tokens.created_at')} /> - <TokenField source="id" label="" /> - </Datagrid> - </ArrayField> - } - </> - </>); + return ( + <> + <StyledGrid container mb={2} alignItems="center"> + <Grid item xs> + <Typography variant="h5" component="h2"> + {translate('resources.profile.tokens.title')} + </Typography> + </Grid> + <Grid item> + <StyledButton size="medium" ml="auto" label={translate('resources.profile.tokens.new')} onClick={() => redirect('/tokens/create')}> + <AddIcon /> + </StyledButton> + <StyledButton + className={classes.deleteButton} + size="medium" + ml="auto" + label={translate('resources.profile.tokens.clear')} + onClick={() => redirect('/tokens/clear')}> + <ClearIcon /> + </StyledButton> + </Grid> + </StyledGrid> + <> + {props.record.tokens.length === 0 ? ( + <Typography>{translate('resources.profile.tokens.none')}</Typography> + ) : ( + <ArrayField source="tokens" {...props}> + <Datagrid> + <TextField source="id" label={translate('resources.profile.tokens.id')} /> + <TextField source="name" label={translate('resources.profile.tokens.name')} /> + <DateField source="last_used_at" label={translate('resources.profile.tokens.last_used_at')} /> + <DateField source="created_at" label={translate('resources.profile.tokens.created_at')} /> + <TokenField source="id" label="" /> + </Datagrid> + </ArrayField> + )} + </> + </> + ); }; +TokensField.propTypes = { + record: PropTypes.any +}; const ProfileEditToolbar = (props) => { + return ( + <Toolbar {...props}> + <SaveButton disabled={props.pristine} color="secondary" /> + </Toolbar> + ); +}; - return ( - <Toolbar {...props} > - <SaveButton disabled={props.pristine} color="secondary" /> - </Toolbar> - ); +ProfileEditToolbar.propTypes = { + pristine: PropTypes.bool }; -const DashboardSettings = (props) => { - const [showAccounts, setShowAccounts] = useState(localStorage.getItem("dash.showAccounts") !== "false"); - const [showAlerts, setShowAlerts] = useState(localStorage.getItem("dash.showAlerts") !== "false"); - const [showGraphs, setShowGraphs] = useState(localStorage.getItem("dash.showGraphs") !== "false"); +const DashboardSettings = () => { + const [showAccounts, setShowAccounts] = useState(localStorage.getItem('dash.showAccounts') !== 'false'); + const [showAlerts, setShowAlerts] = useState(localStorage.getItem('dash.showAlerts') !== 'false'); + const [showGraphs, setShowGraphs] = useState(localStorage.getItem('dash.showGraphs') !== 'false'); - useEffect(() => localStorage.setItem("dash.showAccounts", showAccounts), [showAccounts]); - useEffect(() => localStorage.setItem("dash.showAlerts", showAlerts), [showAlerts]); - useEffect(() => localStorage.setItem("dash.showGraphs", showGraphs), [showGraphs]); + useEffect(() => localStorage.setItem('dash.showAccounts', showAccounts), [showAccounts]); + useEffect(() => localStorage.setItem('dash.showAlerts', showAlerts), [showAlerts]); + useEffect(() => localStorage.setItem('dash.showGraphs', showGraphs), [showGraphs]); - return ( - <StyledGrid container mt={2} mb={2} alignItems="center"> - <Grid item xs={12}> - <Typography variant="h5" component="h2">Accueil</Typography> - </Grid> - <Grid item xs={12}> - <FormGroup column> - <FormControlLabel - control={<Switch color="primary" checked={showAccounts} onChange={() => setShowAccounts(!showAccounts)} name="showAccounts" />} - label="Afficher les comptes" - /> - <FormControlLabel - control={<Switch color="primary" checked={showAlerts} onChange={() => setShowAlerts(!showAlerts)} name="showAccounts" />} - label="Afficher les alertes de stock" - /> - <FormControlLabel - control={<Switch color="primary" checked={showGraphs} onChange={() => setShowGraphs(!showGraphs)} name="showAccounts" />} - label="Afficher les graphiques" - /> - </FormGroup> - </Grid> - </StyledGrid> - ); -} + return ( + <StyledGrid container mt={2} mb={2} alignItems="center"> + <Grid item xs={12}> + <Typography variant="h5" component="h2"> + Accueil + </Typography> + </Grid> + <Grid item xs={12}> + <FormGroup column> + <FormControlLabel + control={<Switch color="primary" checked={showAccounts} onChange={() => setShowAccounts(!showAccounts)} name="showAccounts" />} + label="Afficher les comptes" + /> + <FormControlLabel + control={<Switch color="primary" checked={showAlerts} onChange={() => setShowAlerts(!showAlerts)} name="showAccounts" />} + label="Afficher les alertes de stock" + /> + <FormControlLabel + control={<Switch color="primary" checked={showGraphs} onChange={() => setShowGraphs(!showGraphs)} name="showAccounts" />} + label="Afficher les graphiques" + /> + </FormGroup> + </Grid> + </StyledGrid> + ); +}; -const ProfileEdit = ({ staticContext, ...props }) => { - const translate = useTranslate(); - const notify = useNotify(); - const authProvider = useAuthProvider(); - const refresh = useRefresh(); +const ProfileEdit = (props) => { + const translate = useTranslate(); + const notify = useNotify(); + const authProvider = useAuthProvider(); + const refresh = useRefresh(); - const onSuccess = ({ data }) => { - notify('ra.notification.updated', 'info', { smart_count: 1 }, false); - document.dispatchEvent(new CustomEvent('profileUpdated', { detail: data })); - authProvider.updateEmail(data.email); - refresh(); - }; + const onSuccess = ({ data }) => { + notify('ra.notification.updated', 'info', { smart_count: 1 }, false); + document.dispatchEvent(new CustomEvent('profileUpdated', { detail: data })); + authProvider.updateEmail(data.email); + refresh(); + }; - return ( - <Edit onSuccess={onSuccess} mutationMode="pessimistic" height={1} id="me" resource="profile" basePath="/profile" redirect={false} title={translate('resources.profile.me')} {...props} > - <SimpleForm redirect={false} toolbar={<ProfileEditToolbar />}> - <><Typography variant="h5" component="h2">{translate('resources.profile.name')}</Typography></> - <TextInput source="username" /> - <>{((props) => ( - <Grid container spacing={3}> - <Grid item xs={12} md={6}> - <TextInput {...props} source="firstname" label={translate('resources.profile.fields.firstname')} /> - </Grid> - <Grid item xs={12} md={6}> - <TextInput {...props} source="lastname" label={translate('resources.profile.fields.lastname')} /> - </Grid> - </Grid> - ))()}</> - <TextInput source="email" /> - <><Typography variant="h5" component="h2">{translate('resources.profile.permissions.title')}</Typography></> - <><ReferenceArrayField reference="permissions" source="permissions" style={{ marginBottom: '2px', marginTop: '2px' }}> - <SingleFieldList linkType={false}> - <ChipField source="name" /> - </SingleFieldList> - </ReferenceArrayField></> - <><Typography variant="h5" component="h2">{translate('resources.profile.password.title')}</Typography></> - <ChangePasswordField source="password_changed_at" /> - <><Typography variant="h5" component="h2">{translate('resources.profile.2fa.title')}</Typography></> - <TwoFactorAuthField source="two_factor" /> - <TokensField /> - <DashboardSettings /> - </SimpleForm> - </Edit> - ); + return ( + <Edit + onSuccess={onSuccess} + mutationMode="pessimistic" + height={1} + id="me" + resource="profile" + basePath="/profile" + redirect={false} + title={translate('resources.profile.me')} + {...props}> + <SimpleForm redirect={false} toolbar={<ProfileEditToolbar />}> + <> + <Typography variant="h5" component="h2"> + {translate('resources.profile.name')} + </Typography> + </> + <TextInput source="username" /> + <> + {((props) => ( + <Grid container spacing={3}> + <Grid item xs={12} md={6}> + <TextInput {...props} source="firstname" label={translate('resources.profile.fields.firstname')} /> + </Grid> + <Grid item xs={12} md={6}> + <TextInput {...props} source="lastname" label={translate('resources.profile.fields.lastname')} /> + </Grid> + </Grid> + ))()} + </> + <TextInput source="email" /> + <> + <Typography variant="h5" component="h2"> + {translate('resources.profile.permissions.title')} + </Typography> + </> + <> + <ReferenceArrayField reference="permissions" source="permissions" style={{ marginBottom: '2px', marginTop: '2px' }}> + <SingleFieldList linkType={false}> + <ChipField source="name" /> + </SingleFieldList> + </ReferenceArrayField> + </> + <> + <Typography variant="h5" component="h2"> + {translate('resources.profile.password.title')} + </Typography> + </> + <ChangePasswordField source="password_changed_at" /> + <> + <Typography variant="h5" component="h2"> + {translate('resources.profile.2fa.title')} + </Typography> + </> + <TwoFactorAuthField source="two_factor" /> + <TokensField /> + <DashboardSettings /> + </SimpleForm> + </Edit> + ); }; export default ProfileEdit; diff --git a/src/resources/Purchases.js b/src/resources/Purchases.js index 666e5757ccec5ec28dfbbd75de9815ee795397f4..3d448eda195fad46d1365215f036d3ccfff0eff1 100644 --- a/src/resources/Purchases.js +++ b/src/resources/Purchases.js @@ -1,107 +1,129 @@ import { useMediaQuery } from '@material-ui/core'; -import React from "react"; -import { ArrayField, AutocompleteInput, BooleanInput, Create, Datagrid, FormDataConsumer, List, ReferenceField, ReferenceInput, ShowButton, SimpleForm, SimpleList, SimpleShowLayout, TextField, TextInput } from 'react-admin'; +import React from 'react'; +import { + ArrayField, + AutocompleteInput, + BooleanInput, + Create, + Datagrid, + FormDataConsumer, + List, + ReferenceField, + ReferenceInput, + ShowButton, + SimpleForm, + SimpleList, + SimpleShowLayout, + TextField, + TextInput +} from 'react-admin'; import DateField from '../components/DateField'; import { ShowDialog } from '../components/DialogForm'; -import MoneyField from "../components/MoneyField"; +import MoneyField from '../components/MoneyField'; import MoneyInput from '../components/MoneyInput'; -import { MultiProductCountInput, MultiProductCountItem } from "../components/MultiProductCountInput"; +import { MultiProductCountInput, MultiProductCountItem } from '../components/MultiProductCountInput'; const Purchases = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} bulkActionButtons={false} > - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <TextField source="name" /> - <DateField source="created_at" /> - <ReferenceField label="Montant" source="transaction_id" reference="transactions" link="show"> - <MoneyField source="amount" noLabel={true} /> - </ReferenceField> - <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link={false}> - <ReferenceField source="user_id" reference="users" link="show"> - <TextField source="username" /> - </ReferenceField> - </ReferenceField> - <ReferenceField source="movement_id" reference="movements" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="transaction_id" reference="transactions" link="show"> - <TextField source="name" /> - </ReferenceField> - <ShowButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => record.name} - secondaryText={record => new Date(record.created_at).toLocaleString()} - tertiaryText={record => <ReferenceField record={record} label="Montant" source="transaction_id" reference="transactions" link={false}> - <MoneyField source="amount" noLabel={true} /> - </ReferenceField>} - linkType="show" - /> - )} - </List> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="name" /> - <ReferenceField label="Montant" source="transaction_id" reference="transactions" link="show"> - <MoneyField source="amount" noLabel="true" /> - </ReferenceField> - <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link="show"> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - </ReferenceField> - <ReferenceField label="Produits" source="movement_id" reference="movements" link="show"> - <ArrayField source="products" > - <Datagrid> - <ReferenceField source="product_id" reference="products" link="edit"> - <TextField source="name" /> - </ReferenceField> - <TextField source="count" /> - </Datagrid> - </ArrayField> - </ReferenceField> - <DateField source="created_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} bulkActionButtons={false}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="name" /> + <DateField source="created_at" /> + <ReferenceField label="Montant" source="transaction_id" reference="transactions" link="show"> + <MoneyField source="amount" noLabel={true} /> + </ReferenceField> + <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link={false}> + <ReferenceField source="user_id" reference="users" link="show"> + <TextField source="username" /> + </ReferenceField> + </ReferenceField> + <ReferenceField source="movement_id" reference="movements" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <TextField source="name" /> + </ReferenceField> + <ShowButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => record.name} + secondaryText={(record) => new Date(record.created_at).toLocaleString()} + tertiaryText={(record) => ( + <ReferenceField record={record} label="Montant" source="transaction_id" reference="transactions" link={false}> + <MoneyField source="amount" noLabel={true} /> + </ReferenceField> + )} + linkType="show" + /> + )} + </List> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <TextField source="name" /> + <ReferenceField label="Montant" source="transaction_id" reference="transactions" link="show"> + <MoneyField source="amount" noLabel="true" /> + </ReferenceField> + <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link="show"> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + </ReferenceField> + <ReferenceField label="Produits" source="movement_id" reference="movements" link="show"> + <ArrayField source="products"> + <Datagrid> + <ReferenceField source="product_id" reference="products" link="edit"> + <TextField source="name" /> + </ReferenceField> + <TextField source="count" /> + </Datagrid> + </ArrayField> + </ReferenceField> + <DateField source="created_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const Buy = (props) => { - return <> - <Create {...props}> - <SimpleForm> - <TextInput source="name" /> - <ReferenceInput source="account_id" reference="accounts" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <ReferenceInput source="category_id" reference="transactions_categories" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <MoneyInput source="amount" /> - <BooleanInput source="has_products" /> - <FormDataConsumer> - {({ formData, ...rest }) => formData.has_products && - <MultiProductCountInput source="products" {...rest}> - <MultiProductCountItem /> - </MultiProductCountInput> - } - </FormDataConsumer> - </SimpleForm> - </Create> - </>; + return ( + <> + <Create {...props}> + <SimpleForm> + <TextInput source="name" /> + <ReferenceInput source="account_id" reference="accounts" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <ReferenceInput source="category_id" reference="transactions_categories" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <MoneyInput source="amount" /> + <BooleanInput source="has_products" /> + <FormDataConsumer> + {({ formData, ...rest }) => + formData.has_products && ( + <MultiProductCountInput source="products" {...rest}> + <MultiProductCountItem /> + </MultiProductCountInput> + ) + } + </FormDataConsumer> + </SimpleForm> + </Create> + </> + ); }; const purchases = { - list: Purchases, - create: Buy, - show: Purchases + list: Purchases, + create: Buy, + show: Purchases }; export default purchases; diff --git a/src/resources/Sales.js b/src/resources/Sales.js index 59cd08b7049cf5a5ca78df75ece1d13f7354ec23..abf2b0b73f7332645c0e65b5c08c1c0c1c6eb945 100644 --- a/src/resources/Sales.js +++ b/src/resources/Sales.js @@ -1,113 +1,145 @@ -import { useMediaQuery } from "@material-ui/core"; -import React, { useState } from "react"; -import { ArrayField, AutocompleteInput, Create, Datagrid, FormTab, FunctionField, List, ReferenceField, ReferenceInput, ShowButton, SimpleList, SimpleShowLayout, TabbedForm, TextField, useNotify, useRefresh } from 'react-admin'; +import { useMediaQuery } from '@material-ui/core'; +import React, { useState } from 'react'; +import { + ArrayField, + AutocompleteInput, + Create, + Datagrid, + FormTab, + FunctionField, + List, + ReferenceField, + ReferenceInput, + ShowButton, + SimpleList, + SimpleShowLayout, + TabbedForm, + TextField, + useNotify, + useRefresh +} from 'react-admin'; import DateField from '../components/DateField'; import { ShowDialog } from '../components/DialogForm'; -import MoneyField from "../components/MoneyField"; -import { MultiProductCountInput, MultiProductCountItem } from "../components/MultiProductCountInput"; -import PaymentInput from "../components/PaymentInput"; +import MoneyField from '../components/MoneyField'; +import { MultiProductCountInput, MultiProductCountItem } from '../components/MultiProductCountInput'; +import PaymentInput from '../components/PaymentInput'; const SalesFilters = [ - <ReferenceInput source="person_id" reference="people" filterToQuery={searchText => ({ fullname: searchText, has_account: true })}> - <AutocompleteInput optionText="fullname" /> - </ReferenceInput> + <ReferenceInput + key={0} + source="person_id" + reference="people" + filterToQuery={(searchText) => ({ + fullname: searchText, + has_account: true + })}> + <AutocompleteInput optionText="fullname" /> + </ReferenceInput> ]; const Sales = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={SalesFilters} bulkActionButtons={false} > - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <DateField source="created_at" /> - <ReferenceField label="Montant" source="transaction_id" reference="transactions" link="show"> - <MoneyField source="amount" noLabel={true} /> - </ReferenceField> - <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link={false}> - <ReferenceField source="user_id" reference="users" link="show"> - <TextField source="username" /> - </ReferenceField> - </ReferenceField> - <ReferenceField source="movement_id" reference="movements" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="transaction_id" reference="transactions" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="person_id" reference="people" link="show" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <ShowButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => <ReferenceField record={record} source="movement_id" reference="movements" link={false}> - <TextField source="name" /> - </ReferenceField>} - secondaryText={record => new Date(record.created_at).toLocaleString()} - tertiaryText={record => <ReferenceField record={record} label="Montant" source="transaction_id" reference="transactions" link={false}> - <MoneyField source="amount" noLabel={true} /> - </ReferenceField>} - linkType="show" - /> - )} - </List> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <ReferenceField label="Montant" source="transaction_id" reference="transactions" link="show"> - <MoneyField source="amount" noLabel={true} /> - </ReferenceField> - <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link="show"> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - </ReferenceField> - <ArrayField source="movement.products" > - <Datagrid> - <TextField source="product_id" /> - <TextField source="product.name" /> - <TextField source="count" /> - </Datagrid> - </ArrayField> - <DateField source="created_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={SalesFilters} bulkActionButtons={false}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <DateField source="created_at" /> + <ReferenceField label="Montant" source="transaction_id" reference="transactions" link="show"> + <MoneyField source="amount" noLabel={true} /> + </ReferenceField> + <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link={false}> + <ReferenceField source="user_id" reference="users" link="show"> + <TextField source="username" /> + </ReferenceField> + </ReferenceField> + <ReferenceField source="movement_id" reference="movements" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="transaction_id" reference="transactions" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="person_id" reference="people" link="show"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <ShowButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => ( + <ReferenceField record={record} source="movement_id" reference="movements" link={false}> + <TextField source="name" /> + </ReferenceField> + )} + secondaryText={(record) => new Date(record.created_at).toLocaleString()} + tertiaryText={(record) => ( + <ReferenceField record={record} label="Montant" source="transaction_id" reference="transactions" link={false}> + <MoneyField source="amount" noLabel={true} /> + </ReferenceField> + )} + linkType="show" + /> + )} + </List> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <ReferenceField label="Montant" source="transaction_id" reference="transactions" link="show"> + <MoneyField source="amount" noLabel={true} /> + </ReferenceField> + <ReferenceField label="Créateur" source="transaction_id" reference="transactions" link="show"> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + </ReferenceField> + <ArrayField source="movement.products"> + <Datagrid> + <TextField source="product_id" /> + <TextField source="product.name" /> + <TextField source="count" /> + </Datagrid> + </ArrayField> + <DateField source="created_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; -const Sell = props => { - const refresh = useRefresh(); - const notify = useNotify(); +const Sell = (props) => { + const refresh = useRefresh(); + const notify = useNotify(); - const [price, setPrice] = useState(0); + const [price, setPrice] = useState(0); - return <> - <Create {...props} onSuccess={() => { - notify('ra.notification.created', 'info', { smart_count: 1 }); - refresh(); + return ( + <> + <Create + {...props} + onSuccess={() => { + notify('ra.notification.created', 'info', { smart_count: 1 }); + refresh(); }}> - <TabbedForm syncWithLocation={false}> - <FormTab label="Produits"> - <MultiProductCountInput source="products" total onlysalable priceChanged={(p) => setPrice(p)}> - <MultiProductCountItem price showcount /> - </MultiProductCountInput> - </FormTab> - <FormTab label="Paiement"> - <PaymentInput price={price} /> - </FormTab> - </TabbedForm> - </Create> + <TabbedForm syncWithLocation={false}> + <FormTab label="Produits"> + <MultiProductCountInput source="products" total onlysalable priceChanged={(p) => setPrice(p)}> + <MultiProductCountItem price showcount /> + </MultiProductCountInput> + </FormTab> + <FormTab label="Paiement"> + <PaymentInput price={price} /> + </FormTab> + </TabbedForm> + </Create> </> + ); }; const sales = { - list: Sales, - create: Sell, - show: Sales + list: Sales, + create: Sell, + show: Sales }; export default sales; diff --git a/src/resources/Transactions.js b/src/resources/Transactions.js index cacfb654862aa3d0c47ce84b1019caa99abfb9c6..0ac945b5a86af16f128465014bd94b9a9aa6822f 100644 --- a/src/resources/Transactions.js +++ b/src/resources/Transactions.js @@ -1,96 +1,110 @@ import { useMediaQuery } from '@material-ui/core'; -import * as React from "react"; -import { AutocompleteInput, BooleanField, BooleanInput, Datagrid, List, ReferenceField, ReferenceInput, ShowButton, SimpleForm, SimpleList, SimpleShowLayout, TextField, TextInput } from 'react-admin'; +import * as React from 'react'; +import { + AutocompleteInput, + BooleanField, + BooleanInput, + Datagrid, + List, + ReferenceField, + ReferenceInput, + ShowButton, + SimpleForm, + SimpleList, + SimpleShowLayout, + TextField, + TextInput +} from 'react-admin'; import DateField from '../components/DateField'; import { CreateDialog, ShowDialog } from '../components/DialogForm'; -import MoneyField from "../components/MoneyField"; -import MoneyInput from "../components/MoneyInput"; +import MoneyField from '../components/MoneyField'; +import MoneyInput from '../components/MoneyInput'; const TransactionsFilters = [ - <TextInput source="name" />, - <ReferenceInput source="account_id" reference="accounts" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput>, - <ReferenceInput source="category_id" reference="transactions_categories" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput>, - <ReferenceInput source="user_id" reference="users" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="username" /> - </ReferenceInput> + <TextInput key={0} source="name" />, + <ReferenceInput key={1} source="account_id" reference="accounts" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput>, + <ReferenceInput key={2} source="category_id" reference="transactions_categories" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput>, + <ReferenceInput key={3} source="user_id" reference="users" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="username" /> + </ReferenceInput> ]; const Transactions = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={TransactionsFilters} bulkActionButtons={false}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <TextField source="name" /> - <MoneyField noLabel={true} source="amount" /> - <BooleanField source="rectification" /> - <ReferenceField source="account_id" reference="accounts"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="category_id" reference="transactions_categories"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - <DateField source="created_at" /> - <ShowButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => record.name} - secondaryText={record => new Date(record.created_at).toLocaleDateString()} - tertiaryText={record => <MoneyField record={record} source="amount" noLabel={true} />} - linkType="show" - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <TextInput source="name" /> - <MoneyInput source="amount" /> - <BooleanInput source="rectification" /> - <ReferenceInput source="account_id" reference="accounts" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <ReferenceInput source="category_id" reference="transactions_categories" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - </SimpleForm> - </CreateDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="name" /> - <MoneyField source="amount" /> - <BooleanField source="rectification" /> - <ReferenceField source="account_id" reference="accounts"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="category_id" reference="transactions_categories"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={TransactionsFilters} bulkActionButtons={false}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="name" /> + <MoneyField noLabel={true} source="amount" /> + <BooleanField source="rectification" /> + <ReferenceField source="account_id" reference="accounts"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="category_id" reference="transactions_categories"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + <DateField source="created_at" /> + <ShowButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => record.name} + secondaryText={(record) => new Date(record.created_at).toLocaleDateString()} + tertiaryText={(record) => <MoneyField record={record} source="amount" noLabel={true} />} + linkType="show" + /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <TextInput source="name" /> + <MoneyInput source="amount" /> + <BooleanInput source="rectification" /> + <ReferenceInput source="account_id" reference="accounts" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <ReferenceInput source="category_id" reference="transactions_categories" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + </SimpleForm> + </CreateDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <TextField source="name" /> + <MoneyField source="amount" /> + <BooleanField source="rectification" /> + <ReferenceField source="account_id" reference="accounts"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="category_id" reference="transactions_categories"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const transactions = { - list: Transactions, - create: Transactions, - show: Transactions + list: Transactions, + create: Transactions, + show: Transactions }; export default transactions; diff --git a/src/resources/TransactionsCategories.js b/src/resources/TransactionsCategories.js index f3c82cb6414e869d3df2c0e284a8a62df46651b1..2f58a63989a8fa7fbac9ce6cddcefb480d4603d7 100644 --- a/src/resources/TransactionsCategories.js +++ b/src/resources/TransactionsCategories.js @@ -1,65 +1,59 @@ import { useMediaQuery } from '@material-ui/core'; -import * as React from "react"; +import * as React from 'react'; import { Datagrid, EditButton, List, SimpleForm, SimpleList, SimpleShowLayout, TextField, TextInput } from 'react-admin'; import DateField from '../components/DateField'; import DateInput from '../components/DateInput'; import { CreateDialog, EditDialog, ShowDialog } from '../components/DialogForm'; -const TransactionsCategoriesFilters = [ - <TextInput source="name" /> -]; +const TransactionsCategoriesFilters = [<TextInput key={0} source="name" />]; const TransactionsCategories = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={TransactionsCategoriesFilters}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <TextField source="name" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - <EditButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => record.name} - tertiaryText={record => "#" + record.id} - linkType="edit" - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <TextInput source="name" /> - </SimpleForm> - </CreateDialog> - <EditDialog {...props}> - <SimpleForm redirect="list"> - <TextInput disabled source="id" /> - <TextInput source="name" /> - <DateInput disabled source="created_at" /> - <DateInput disabled source="updated_at" /> - </SimpleForm> - </EditDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="name" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={TransactionsCategoriesFilters}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="name" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + <EditButton /> + </Datagrid> + ) : ( + <SimpleList primaryText={(record) => record.name} tertiaryText={(record) => '#' + record.id} linkType="edit" /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <TextInput source="name" /> + </SimpleForm> + </CreateDialog> + <EditDialog {...props}> + <SimpleForm redirect="list"> + <TextInput disabled source="id" /> + <TextInput source="name" /> + <DateInput disabled source="created_at" /> + <DateInput disabled source="updated_at" /> + </SimpleForm> + </EditDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <TextField source="name" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const transactions_categories = { - list: TransactionsCategories, - create: TransactionsCategories, - edit: TransactionsCategories, - show: TransactionsCategories + list: TransactionsCategories, + create: TransactionsCategories, + edit: TransactionsCategories, + show: TransactionsCategories }; export default transactions_categories; diff --git a/src/resources/Transferts.js b/src/resources/Transferts.js index 842f58559f7f087cabfc998fd13ccb8c03c27c7f..5aff32e5049d9d4ac0e4e4e2ba1cdbce5fd94bf6 100644 --- a/src/resources/Transferts.js +++ b/src/resources/Transferts.js @@ -1,113 +1,128 @@ import { useMediaQuery } from '@material-ui/core'; -import * as React from "react"; -import { AutocompleteInput, Datagrid, List, ReferenceField, ReferenceInput, ShowButton, SimpleForm, SimpleList, SimpleShowLayout, TextField } from 'react-admin'; +import * as React from 'react'; +import { + AutocompleteInput, + Datagrid, + List, + ReferenceField, + ReferenceInput, + ShowButton, + SimpleForm, + SimpleList, + SimpleShowLayout, + TextField +} from 'react-admin'; import DateField from '../components/DateField'; import { CreateDialog, ShowDialog } from '../components/DialogForm'; -import MoneyField from "../components/MoneyField"; -import MoneyInput from "../components/MoneyInput"; +import MoneyField from '../components/MoneyField'; +import MoneyInput from '../components/MoneyInput'; const Transferts = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} bulkActionButtons={false}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <ReferenceField label="Depuis" source="sub_transaction_id" reference="transactions" link="show"> - <ReferenceField source="account_id" reference="accounts"> - <TextField source="name" /> - </ReferenceField> - </ReferenceField> - <ReferenceField label="Vers" source="add_transaction_id" reference="transactions" link="show"> - <ReferenceField source="account_id" reference="accounts"> - <TextField source="name" /> - </ReferenceField> - </ReferenceField> - <ReferenceField label="Montant" source="add_transaction_id" reference="transactions" link="show"> - <MoneyField source="amount" noLabel={true} /> - </ReferenceField> - <ReferenceField label="Créateur" source="add_transaction_id" reference="transactions" link="show"> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - </ReferenceField> - <DateField source="created_at" /> - <ShowButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => ( - <> - <ReferenceField record={record} label="Depuis" source="sub_transaction_id" reference="transactions" link={false}> - <ReferenceField source="account_id" reference="accounts" link={false}> - <TextField source="name" /> - </ReferenceField> - </ReferenceField> → <ReferenceField record={record} label="Vers" source="add_transaction_id" reference="transactions" link={false}> - <ReferenceField source="account_id" reference="accounts" link={false}> - <TextField source="name" /> - </ReferenceField> - </ReferenceField> - </> - )} - secondaryText={record => new Date(record.created_at).toLocaleDateString()} - tertiaryText={record => <ReferenceField record={record} label="Montant" source="add_transaction_id" reference="transactions" link={false}> - <MoneyField source="amount" noLabel={true} /> - </ReferenceField>} - linkType="show" - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <MoneyInput source="amount" /> - <ReferenceInput source="from_account_id" reference="accounts" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - <ReferenceInput source="to_account_id" reference="accounts" filterToQuery={searchText => ({ name: searchText })}> - <AutocompleteInput optionText="name" /> - </ReferenceInput> - </SimpleForm> - </CreateDialog> - <ShowDialog> - <SimpleShowLayout> - <TextField source="id" /> - <ReferenceField label="Depuis" source="sub_transaction_id" reference="transactions" link="show"> - <ReferenceField source="account_id" reference="accounts"> - <TextField source="name" /> - </ReferenceField> - </ReferenceField> - <ReferenceField label="Transaction soustraction" source="sub_transaction_id" reference="transactions" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField label="Vers" source="add_transaction_id" reference="transactions" link="show"> - <ReferenceField source="account_id" reference="accounts"> - <TextField source="name" /> - </ReferenceField> - </ReferenceField> - <ReferenceField label="Transaction addition" source="add_transaction_id" reference="transactions" link="show"> - <TextField source="name" /> - </ReferenceField> - <ReferenceField label="Montant" source="add_transaction_id" reference="transactions" link="show"> - <MoneyField source="amount" noLabel={true} /> - </ReferenceField> - <ReferenceField label="Créateur" source="add_transaction_id" reference="transactions" link="show"> - <ReferenceField source="user_id" reference="users"> - <TextField source="username" /> - </ReferenceField> - </ReferenceField> - <DateField source="created_at" /> - <DateField source="updated_at" /> - </SimpleShowLayout> - </ShowDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} bulkActionButtons={false}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <ReferenceField label="Depuis" source="sub_transaction_id" reference="transactions" link="show"> + <ReferenceField source="account_id" reference="accounts"> + <TextField source="name" /> + </ReferenceField> + </ReferenceField> + <ReferenceField label="Vers" source="add_transaction_id" reference="transactions" link="show"> + <ReferenceField source="account_id" reference="accounts"> + <TextField source="name" /> + </ReferenceField> + </ReferenceField> + <ReferenceField label="Montant" source="add_transaction_id" reference="transactions" link="show"> + <MoneyField source="amount" noLabel={true} /> + </ReferenceField> + <ReferenceField label="Créateur" source="add_transaction_id" reference="transactions" link="show"> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + </ReferenceField> + <DateField source="created_at" /> + <ShowButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => ( + <> + <ReferenceField record={record} label="Depuis" source="sub_transaction_id" reference="transactions" link={false}> + <ReferenceField source="account_id" reference="accounts" link={false}> + <TextField source="name" /> + </ReferenceField> + </ReferenceField>{' '} + →{' '} + <ReferenceField record={record} label="Vers" source="add_transaction_id" reference="transactions" link={false}> + <ReferenceField source="account_id" reference="accounts" link={false}> + <TextField source="name" /> + </ReferenceField> + </ReferenceField> + </> + )} + secondaryText={(record) => new Date(record.created_at).toLocaleDateString()} + tertiaryText={(record) => ( + <ReferenceField record={record} label="Montant" source="add_transaction_id" reference="transactions" link={false}> + <MoneyField source="amount" noLabel={true} /> + </ReferenceField> + )} + linkType="show" + /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <MoneyInput source="amount" /> + <ReferenceInput source="from_account_id" reference="accounts" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + <ReferenceInput source="to_account_id" reference="accounts" filterToQuery={(searchText) => ({ name: searchText })}> + <AutocompleteInput optionText="name" /> + </ReferenceInput> + </SimpleForm> + </CreateDialog> + <ShowDialog> + <SimpleShowLayout> + <TextField source="id" /> + <ReferenceField label="Depuis" source="sub_transaction_id" reference="transactions" link="show"> + <ReferenceField source="account_id" reference="accounts"> + <TextField source="name" /> + </ReferenceField> + </ReferenceField> + <ReferenceField label="Transaction soustraction" source="sub_transaction_id" reference="transactions" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField label="Vers" source="add_transaction_id" reference="transactions" link="show"> + <ReferenceField source="account_id" reference="accounts"> + <TextField source="name" /> + </ReferenceField> + </ReferenceField> + <ReferenceField label="Transaction addition" source="add_transaction_id" reference="transactions" link="show"> + <TextField source="name" /> + </ReferenceField> + <ReferenceField label="Montant" source="add_transaction_id" reference="transactions" link="show"> + <MoneyField source="amount" noLabel={true} /> + </ReferenceField> + <ReferenceField label="Créateur" source="add_transaction_id" reference="transactions" link="show"> + <ReferenceField source="user_id" reference="users"> + <TextField source="username" /> + </ReferenceField> + </ReferenceField> + <DateField source="created_at" /> + <DateField source="updated_at" /> + </SimpleShowLayout> + </ShowDialog> + </> + ); }; const transferts = { - list: Transferts, - create: Transferts, - show: Transferts + list: Transferts, + create: Transferts, + show: Transferts }; export default transferts; diff --git a/src/resources/Users.js b/src/resources/Users.js index 747d03ee690aa46e72c65f299967b87e6eb751bc..ea8c82ce741304c7a75e10d7171dbd41df5e468b 100644 --- a/src/resources/Users.js +++ b/src/resources/Users.js @@ -1,80 +1,106 @@ import { useMediaQuery } from '@material-ui/core'; -import * as React from "react"; -import { AutocompleteArrayInput, AutocompleteInput, Datagrid, EditButton, FunctionField, List, ReferenceArrayInput, ReferenceField, ReferenceInput, SimpleForm, SimpleList, TextField, TextInput } from 'react-admin'; +import * as React from 'react'; +import { + AutocompleteArrayInput, + AutocompleteInput, + Datagrid, + EditButton, + FunctionField, + List, + ReferenceArrayInput, + ReferenceField, + ReferenceInput, + SimpleForm, + SimpleList, + TextField, + TextInput +} from 'react-admin'; import DateField from '../components/DateField'; import DateInput from '../components/DateInput'; import { CreateDialog, EditDialog } from '../components/DialogForm'; -const UsersFilters = [ - <TextInput label="Username" source="username" />, - <TextInput label="Email" source="email" /> -]; +const UsersFilters = [<TextInput key={0} label="Username" source="username" />, <TextInput key={1} label="Email" source="email" />]; const Users = (props) => { - const isDesktop = useMediaQuery(theme => theme.breakpoints.up('md')); - return ( - <> - <List {...props} filters={UsersFilters}> - {isDesktop ? ( - <Datagrid> - <TextField source="id" /> - <TextField source="username" /> - <ReferenceField source="person_id" reference="people" link="show" > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField> - <TextField source="email" /> - <DateField source="created_at" /> - <DateField source="updated_at" /> - <DateField source="password_changed_at" /> - <EditButton /> - </Datagrid> - ) : ( - <SimpleList - primaryText={record => <ReferenceField record={record} source="person_id" reference="people" link={false} > - <FunctionField render={r => r.firstname + " " + r.lastname} /> - </ReferenceField>} - secondaryText={record => record.username} - tertiaryText={record => "#" + record.id} - linkType="show" - /> - )} - </List> - <CreateDialog {...props}> - <SimpleForm redirect="list"> - <TextInput source="username" /> - <ReferenceInput source="person_id" reference="people" filterToQuery={searchText => ({ fullname: searchText, has_account: false })}> - <AutocompleteInput optionText="fullname" /> - </ReferenceInput> - <TextInput source="email" /> - <ReferenceArrayInput label="Permissions" reference="permissions" source="permissions"> - <AutocompleteArrayInput /> - </ReferenceArrayInput> - </SimpleForm> - </CreateDialog> - <EditDialog {...props}> - <SimpleForm redirect="list"> - <TextInput source="username" /> - <ReferenceInput disabled source="person_id" reference="people" filterToQuery={searchText => ({ fullname: searchText, has_account: false })}> - <AutocompleteInput optionText="fullname" /> - </ReferenceInput> - <TextInput source="email" /> - <ReferenceArrayInput label="Permissions" reference="permissions" source="permissions"> - <AutocompleteArrayInput /> - </ReferenceArrayInput> - <DateInput disabled source="created_at" /> - <DateInput disabled source="updated_at" /> - <DateInput disabled source="password_changed_at" /> - </SimpleForm> - </EditDialog> - </> - ); + const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md')); + return ( + <> + <List {...props} filters={UsersFilters}> + {isDesktop ? ( + <Datagrid> + <TextField source="id" /> + <TextField source="username" /> + <ReferenceField source="person_id" reference="people" link="show"> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + <TextField source="email" /> + <DateField source="created_at" /> + <DateField source="updated_at" /> + <DateField source="password_changed_at" /> + <EditButton /> + </Datagrid> + ) : ( + <SimpleList + primaryText={(record) => ( + <ReferenceField record={record} source="person_id" reference="people" link={false}> + <FunctionField render={(r) => r.firstname + ' ' + r.lastname} /> + </ReferenceField> + )} + secondaryText={(record) => record.username} + tertiaryText={(record) => '#' + record.id} + linkType="show" + /> + )} + </List> + <CreateDialog {...props}> + <SimpleForm redirect="list"> + <TextInput source="username" /> + <ReferenceInput + source="person_id" + reference="people" + filterToQuery={(searchText) => ({ + fullname: searchText, + has_account: false + })}> + <AutocompleteInput optionText="fullname" /> + </ReferenceInput> + <TextInput source="email" /> + <ReferenceArrayInput label="Permissions" reference="permissions" source="permissions"> + <AutocompleteArrayInput /> + </ReferenceArrayInput> + </SimpleForm> + </CreateDialog> + <EditDialog {...props}> + <SimpleForm redirect="list"> + <TextInput source="username" /> + <ReferenceInput + disabled + source="person_id" + reference="people" + filterToQuery={(searchText) => ({ + fullname: searchText, + has_account: false + })}> + <AutocompleteInput optionText="fullname" /> + </ReferenceInput> + <TextInput source="email" /> + <ReferenceArrayInput label="Permissions" reference="permissions" source="permissions"> + <AutocompleteArrayInput /> + </ReferenceArrayInput> + <DateInput disabled source="created_at" /> + <DateInput disabled source="updated_at" /> + <DateInput disabled source="password_changed_at" /> + </SimpleForm> + </EditDialog> + </> + ); }; const users = { - list: Users, - create: Users, - edit: Users, - show: Users + list: Users, + create: Users, + edit: Users, + show: Users }; export default users; diff --git a/src/sass/_variables.scss b/src/sass/_variables.scss index 0407ab577327b92faf6bbb6cb6391587d9db6d1a..0f89db084267c79e2e05ab8d34a355f69faa4c43 100644 --- a/src/sass/_variables.scss +++ b/src/sass/_variables.scss @@ -2,7 +2,7 @@ $body-bg: #f8fafc; // Typography -$font-family-sans-serif: 'Nunito', sans-serif; +$font-family-sans-serif: "Nunito", sans-serif; $font-size-base: 0.9rem; $line-height-base: 1.6; diff --git a/src/sass/app.scss b/src/sass/app.scss index af96f2e3f854756128d06eff6997aed687da49c8..c476742f0ef33a8a41d46ae4b8a8c12960556956 100644 --- a/src/sass/app.scss +++ b/src/sass/app.scss @@ -8,58 +8,58 @@ @import "~sass-reset"; .MuiCollapse-wrapper .MuiListItemIcon-root { - margin-right: 16px; + margin-right: 16px; } .MuiButtonBase-root .MuiListItemIcon-root { - width: 40px; + width: 40px; } li.RaMenuItemLink-root-36 { - height: 36px; + height: 36px; } a.MuiButton-contained:hover { - color: #000; - background-color: #74d6fc; + color: #000; + background-color: #74d6fc; } a:hover { - color: #74d6fc; + color: #74d6fc; } .MuiDrawer-root { - a:hover, - li:hover { - color: #fb963a; - } + a:hover, + li:hover { + color: #fb963a; + } } .MuiTable-root { - background: #303030; + background: #303030; } .MuiTableBody-root, .MuiDrawer-root { - background: #424242; + background: #424242; } @media only screen and (max-width: 959px) { - .MuiDrawer-root { - background: none; - } + .MuiDrawer-root { + background: none; + } } .MuiGrid-spacing-xs-3 > .MuiGrid-item { - width: 100%; + width: 100%; } .MuiSnackbarContent-message { - white-space: pre; + white-space: pre; } .recharts-default-tooltip { - background-color: #545454 !important; - border-radius: 4px; - border: none !important; + background-color: #545454 !important; + border-radius: 4px; + border: none !important; }