react-admin-base-bootstrap 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.yarnrc +1 -0
  2. package/assets/main.css +63 -0
  3. package/lib/esm/Components/ApiSelect.d.ts +3 -0
  4. package/lib/esm/Components/ApiSelect.js +82 -0
  5. package/lib/esm/Components/BootstrapDataTable.d.ts +12 -0
  6. package/lib/esm/Components/BootstrapDataTable.js +141 -0
  7. package/lib/esm/Components/BootstrapPagination.d.ts +9 -0
  8. package/lib/esm/Components/BootstrapPagination.js +13 -0
  9. package/lib/esm/Components/CRUD.d.ts +20 -0
  10. package/lib/esm/Components/CRUD.js +88 -0
  11. package/lib/esm/Components/CheckBox.d.ts +2 -0
  12. package/lib/esm/Components/CheckBox.js +7 -0
  13. package/lib/esm/Components/EntityEditor.d.ts +11 -0
  14. package/lib/esm/Components/EntityEditor.js +61 -0
  15. package/lib/esm/Components/ErrorBoundary.d.ts +12 -0
  16. package/lib/esm/Components/ErrorBoundary.js +25 -0
  17. package/lib/esm/Components/ExcelExportButton.d.ts +8 -0
  18. package/lib/esm/Components/ExcelExportButton.js +8 -0
  19. package/lib/esm/Components/ExternalLoginButton.d.ts +7 -0
  20. package/lib/esm/Components/ExternalLoginButton.js +12 -0
  21. package/lib/esm/Components/FilePickerCore.d.ts +16 -0
  22. package/lib/esm/Components/FilePickerCore.js +82 -0
  23. package/lib/esm/Components/GoToTop.d.ts +11 -0
  24. package/lib/esm/Components/GoToTop.js +34 -0
  25. package/lib/esm/Components/ImagePicker.d.ts +2 -0
  26. package/lib/esm/Components/ImagePicker.js +22 -0
  27. package/lib/esm/Components/LanguageProvider.d.ts +8 -0
  28. package/lib/esm/Components/LanguageProvider.js +42 -0
  29. package/lib/esm/Components/LoadingButton.d.ts +2 -0
  30. package/lib/esm/Components/LoadingButton.js +6 -0
  31. package/lib/esm/Components/MenuState.d.ts +3 -0
  32. package/lib/esm/Components/MenuState.js +20 -0
  33. package/lib/esm/Components/MultiFilePicker.d.ts +11 -0
  34. package/lib/esm/Components/MultiFilePicker.js +22 -0
  35. package/lib/esm/Components/SingleFilePicker.d.ts +12 -0
  36. package/lib/esm/Components/SingleFilePicker.js +20 -0
  37. package/lib/esm/Components/TopProgressBar.d.ts +3 -0
  38. package/lib/esm/Components/TopProgressBar.js +10 -0
  39. package/lib/esm/Components/Validator.d.ts +16 -0
  40. package/lib/esm/Components/Validator.js +33 -0
  41. package/lib/esm/i18n/de.json +34 -0
  42. package/lib/esm/i18n/en.json +35 -0
  43. package/lib/esm/i18n/tr.json +35 -0
  44. package/lib/esm/index.d.ts +19 -0
  45. package/lib/esm/index.js +19 -0
  46. package/package.json +55 -0
  47. package/src/Components/ApiSelect.tsx +122 -0
  48. package/src/Components/BootstrapDataTable.tsx +160 -0
  49. package/src/Components/BootstrapPagination.tsx +35 -0
  50. package/src/Components/CRUD.tsx +117 -0
  51. package/src/Components/CheckBox.tsx +9 -0
  52. package/src/Components/EntityEditor.tsx +67 -0
  53. package/src/Components/ErrorBoundary.tsx +29 -0
  54. package/src/Components/ExcelExportButton.tsx +11 -0
  55. package/src/Components/ExternalLoginButton.tsx +14 -0
  56. package/src/Components/FilePickerCore.tsx +87 -0
  57. package/src/Components/GoToTop.tsx +41 -0
  58. package/src/Components/ImagePicker.tsx +26 -0
  59. package/src/Components/LanguageProvider.tsx +61 -0
  60. package/src/Components/LoadingButton.tsx +7 -0
  61. package/src/Components/MenuState.tsx +24 -0
  62. package/src/Components/MultiFilePicker.tsx +43 -0
  63. package/src/Components/SingleFilePicker.tsx +40 -0
  64. package/src/Components/TopProgressBar.tsx +13 -0
  65. package/src/Components/Validator.tsx +55 -0
  66. package/src/i18n/de.json +34 -0
  67. package/src/i18n/en.json +35 -0
  68. package/src/i18n/tr.json +35 -0
  69. package/src/index.ts +42 -0
  70. package/tsconfig.json +23 -0
@@ -0,0 +1,122 @@
1
+ /** @jsx jsx */
2
+ import { jsx } from '@emotion/react';
3
+ import React, { useCallback, useMemo, useState } from 'react';
4
+ import { useFetch } from 'react-admin-base';
5
+ import { FormattedMessage, useIntl } from 'react-intl';
6
+ import Select, { components } from "react-select";
7
+ import CreatableSelect from 'react-select/creatable';
8
+
9
+ function Option(props) {
10
+ return <components.Option {...props}>
11
+ { (props.selectProps.children && props.selectProps.children(props.data)) || (props.data.__isNew__ ? <FormattedMessage
12
+ id="CREATE_VALUE"
13
+ values={{ text: props.children }}
14
+ /> : props.children) }
15
+ </components.Option>
16
+ }
17
+
18
+ function SingleValue(props) {
19
+ return <components.SingleValue {...props}>
20
+ { (props.selectProps.children && props.selectProps.children(props.data)) || (props.data.__isNew__ ? <FormattedMessage
21
+ id="CREATE_VALUE"
22
+ values={{ text: props.children }}
23
+ /> : props.children) }
24
+ </components.SingleValue>
25
+ }
26
+
27
+ function EditOrAddIndicator(props) {
28
+ const { className, cx, getStyles, innerProps } = props;
29
+
30
+ return (
31
+ <div
32
+ {...innerProps}
33
+ className={cx(
34
+ {
35
+ indicator: true,
36
+ 'clear-indicator': true
37
+ },
38
+ className
39
+ )}
40
+ css={getStyles('clearIndicator', props)}
41
+ onMouseDown={e => {
42
+ e.stopPropagation();
43
+ e.preventDefault();
44
+ props.selectProps.onAddOrEdit();
45
+ }}
46
+ >
47
+ <i className={"fas " + (props.hasValue ? 'fa-pencil-alt' : 'fa-plus')} />
48
+ </div>);
49
+ }
50
+
51
+ function IndicatorsContainer(props) {
52
+ return <components.IndicatorsContainer {...props}>
53
+ { props.selectProps.onAddOrEdit && <React.Fragment>
54
+ <EditOrAddIndicator {...props} />
55
+ <components.IndicatorSeparator {...props} />
56
+ </React.Fragment> }
57
+ {props.children}
58
+ </components.IndicatorsContainer>;
59
+ }
60
+
61
+ const Components = { Option, SingleValue, IndicatorsContainer };
62
+
63
+ export default function ApiSelect(props) {
64
+ const { disabled, url, getOptionLabel, getOptionValue, idKey, nameKey, filter, group, onCreateOption, getNewOptionData, isMulti, onChange, value, placeholder, staticOptions } = props;
65
+ const intl = useIntl();
66
+ const [ search, setSearch ] = useState('');
67
+ const params = useMemo(() => ({ query: search }), [search]);
68
+ const [ isMenuOpen, setIsMenuOpen ] = useState(false);
69
+ const [ hasMenuOpen, setHasMenuOpen ] = useState(false);
70
+ const [ data, loading ] = useFetch((hasMenuOpen && url) || false, params);
71
+ const [ creating, setCreating ] = useState(false);
72
+
73
+ let options = staticOptions || (data && data.data) || data;
74
+ if (group) {
75
+ options = group(options || []);
76
+ }
77
+
78
+ const handleCreateOption = useCallback(async function(input) {
79
+ setCreating(true);
80
+ try {
81
+ let option = await onCreateOption(input);
82
+ if (isMulti) {
83
+ onChange((value || []).concat([option]));
84
+ } else {
85
+ onChange(option);
86
+ }
87
+ } finally {
88
+ setCreating(false);
89
+ }
90
+ }, [onCreateOption, setCreating, isMulti, value]);
91
+
92
+ const onMenuOpen = useCallback(function() {
93
+ setIsMenuOpen(true);
94
+ setHasMenuOpen(true);
95
+ }, [ setIsMenuOpen, setHasMenuOpen ]);
96
+
97
+ const onMenuClose = useCallback(function() {
98
+ setIsMenuOpen(false);
99
+ }, [ setIsMenuOpen ]);
100
+
101
+ const Component = onCreateOption ? CreatableSelect : Select;
102
+
103
+ return <Component
104
+ {...props}
105
+ onCreateOption={(onCreateOption && handleCreateOption) || null}
106
+ getNewOptionData={onCreateOption ? getNewOptionData ? getNewOptionData : (inputValue) =>( { [nameKey || 'name']: inputValue, __isNew__: true }) : null}
107
+ inputValue={search}
108
+ onInputChange={a => setSearch(a)}
109
+ components={Components}
110
+ isLoading={!!loading || creating}
111
+ getOptionLabel={getOptionLabel || ((row:any) => row[nameKey || 'name'])}
112
+ getOptionValue={getOptionValue || ((row:any) => row[idKey || 'id'])}
113
+ isDisabled={!!disabled || creating}
114
+ isClearable
115
+ isSearchable
116
+ placeholder={placeholder || intl.formatMessage({ id: 'SELECT' })}
117
+ options={!options ? [] : ((filter && options.filter(filter)) || options)}
118
+ isMenuOpen={isMenuOpen}
119
+ onMenuOpen={onMenuOpen}
120
+ onMenuClose={onMenuClose}
121
+ />;
122
+ }
@@ -0,0 +1,160 @@
1
+ import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
2
+ import { DataContextProvider, useAuth, useDataTable } from 'react-admin-base';
3
+ import { FormattedMessage, useIntl } from "react-intl";
4
+ import { Link } from 'react-router-dom';
5
+ import { Alert, Button, Card, CardFooter, CardHeader, Col, Input, Row, Table } from 'reactstrap';
6
+ import Swal from 'sweetalert2';
7
+ import BootstrapPagination from "./BootstrapPagination";
8
+
9
+ const DataTableContext = React.createContext(null as any);
10
+
11
+ export function useDataTableContext() {
12
+ return useContext(DataTableContext);
13
+ }
14
+
15
+ export function Actions({edit, del, rowSpan, children} : {edit: any, del: any, rowSpan?:any, children?: any}) {
16
+ const [api] = useAuth();
17
+ const [, setParams] = useContext(DataTableContext);
18
+ const intl = useIntl();
19
+ const [ loading, setLoading ] = useState(false);
20
+
21
+ const delFnc = useCallback(async function (e) {
22
+ e && e.preventDefault();
23
+
24
+ const val = await Swal.fire({
25
+ title: intl.formatMessage({ id: 'ACTIONS.DELETE.TITLE' }),
26
+ text: intl.formatMessage({ id: 'ACTIONS.DELETE.TEXT' }),
27
+ icon: 'warning',
28
+ showCancelButton: true,
29
+ confirmButtonColor: '#3085d6',
30
+ cancelButtonColor: '#d33',
31
+ confirmButtonText: intl.formatMessage({ id: 'ACTIONS.DELETE.CONFIRM' })
32
+ });
33
+
34
+ if (val.value) {
35
+ setLoading(true);
36
+ try {
37
+ await api.tokenized.delete(del);
38
+ setParams({}, true);
39
+ } finally {
40
+ setLoading(false);
41
+ }
42
+ }
43
+ }, [api, del, setParams, intl, setLoading]);
44
+
45
+ return <td className="min" rowSpan={rowSpan}>
46
+ {edit && <Link to={edit}><Button outline color="primary"><i className="fa fa-pencil-alt"/></Button></Link>}
47
+ {edit && del && ' '}
48
+ {del && <Button color="danger" outline onClick={delFnc} disabled={loading}><i className={loading ? "fa fa-spin fa-spinner" : "fa fa-trash"} /></Button>}
49
+ {children}
50
+ </td>;
51
+ }
52
+
53
+ export function IdColumn() {
54
+ return <Column className="min text-center" sort="id"><i className="fas fa-hashtag"/></Column>;
55
+ }
56
+
57
+ export function ActionsColumn() {
58
+ return <Column className="min text-center"><i className="fas fa-water"/></Column>;
59
+ }
60
+
61
+ export function Column(props) {
62
+ const [params, setParams] = useContext(DataTableContext);
63
+ const {sort} = props;
64
+ return <th
65
+ {...props}
66
+ style={props.sort && {cursor: 'pointer'}}
67
+ onClick={props.sort && (() => setParams({
68
+ sort: sort,
69
+ desc: params.sort === sort ? !params.desc : true
70
+ }))}
71
+ >{props.children} {sort && params.sort && params.sort === sort ? params.desc ?
72
+ <i className="fa fa-sort-down"/> :
73
+ <i className="fa fa-sort-up"/> : ''}
74
+ </th>;
75
+ }
76
+
77
+ export default function BootstrapTable({url, bordered, noStrip, defaultParams, add, children, innerRef, body}: any) {
78
+ var state = useState({sort: 'id', ...defaultParams});
79
+ const [params, setParams] = state;
80
+ const [page, lastPage, setPage, data, itemPerPage, setItemPerPage] = useDataTable(url, params, body);
81
+ const intl = useIntl();
82
+ const [ api ] = useAuth();
83
+
84
+ var ref = useRef(defaultParams);
85
+ useEffect(function () {
86
+ if (ref.current !== defaultParams) {
87
+ ref.current = defaultParams;
88
+ setParams(defaultParams);
89
+ }
90
+ }, [defaultParams, ref, setParams]);
91
+
92
+ useEffect(function () {
93
+ if (innerRef) {
94
+ innerRef.current = setParams;
95
+
96
+ return function () {
97
+ innerRef.current = null;
98
+ };
99
+ }
100
+ }, [setParams, innerRef]);
101
+
102
+ const fetchData = useCallback(async function(extraParams) {
103
+ if (body) {
104
+ const data = await api.tokenized.post(url, body, { params: { ...params, ...(extraParams || {}) } });
105
+ return data.data;
106
+ }
107
+
108
+ const data = await api.tokenized.get(url, { params: { ...params, ...(extraParams || {}) } });
109
+ return data.data;
110
+ }, [api, url, params, body]);
111
+
112
+ return <Card>
113
+ <DataTableContext.Provider value={state}>
114
+ <DataContextProvider value={fetchData}>
115
+ <CardHeader>
116
+ <Row>
117
+ {add && <Col xs="12" md="2"><Link to={add} className="btn btn-primary font-xl btn-lg d-block"><i className="fa fa-plus"/></Link></Col>}
118
+ <Col md="2">
119
+ <Input type="select" bsSize="lg" value={itemPerPage.toString()} onChange={a => setItemPerPage(+a.currentTarget.value)}>
120
+ <option value="1">1</option>
121
+ <option value="20">20</option>
122
+ <option value="50">50</option>
123
+ <option value="100">100</option>
124
+ <option value="150">150</option>
125
+ <option value="200">200</option>
126
+ <option value="-1">{intl.formatMessage({id: "ALL"})}</option>
127
+ </Input>
128
+ </Col>
129
+ {children[2]}
130
+ <Col md="3" className="ms-auto">
131
+ <Input
132
+ placeholder={intl.formatMessage({id: "SEARCH"})} type="text"
133
+ value={params.query || ''}
134
+ bsSize="lg"
135
+ onChange={e => setParams({...params, query: e.currentTarget.value})}
136
+ />
137
+ </Col>
138
+ </Row>
139
+ </CardHeader>
140
+ {data === null ? <Alert className="text-center mb-0 " color="warning"><i className="fas fa-spinner fa-lg fa-spin"></i></Alert> : !data.length ? <Alert className="text-center" color="danger">
141
+ <i className="far fa-times-circle fa-lg"></i> <FormattedMessage id="NO_DATA_IS_AVAILABLE"/>
142
+ </Alert> : <Table hover bordered={bordered} striped={!noStrip} responsive size="md" className="mb-0 dataTable">
143
+ {children[0]}
144
+ <tbody>
145
+ {data && data.map(children[1].props.children)}
146
+ </tbody>
147
+ </Table>}
148
+ { lastPage > 1 && <CardFooter>
149
+ <nav>
150
+ <BootstrapPagination
151
+ currentPage={page}
152
+ pageCount={lastPage}
153
+ onPageChange={index => setPage(index)}
154
+ />
155
+ </nav>
156
+ </CardFooter> }
157
+ </DataContextProvider>
158
+ </DataTableContext.Provider>
159
+ </Card>;
160
+ }
@@ -0,0 +1,35 @@
1
+
2
+ import React from "react";
3
+ import { PaginatorCore } from 'react-admin-base';
4
+ import { Pagination, PaginationItem, PaginationLink } from "reactstrap";
5
+
6
+ type BootstrapPaginationProps = {
7
+ className?: string;
8
+ currentPage: number;
9
+ pageCount: number;
10
+ onPageChange: (page: number) => void;
11
+ };
12
+
13
+ export default function BootstrapPagination({ className, currentPage, pageCount, onPageChange }: BootstrapPaginationProps) {
14
+ if (pageCount <= 1)
15
+ return null;
16
+
17
+ return <Pagination listClassName="mb-0" className={className}>
18
+ <PaginatorCore
19
+ activePage={currentPage}
20
+ pageCount={pageCount}
21
+ showPages={15}
22
+ prevPage={index => <PaginationItem onClick={() => onPageChange(index)}><PaginationLink previous tag="button">&laquo;</PaginationLink></PaginationItem>}
23
+
24
+ page={index => <PaginationItem key={index} active={currentPage === index}>
25
+ <PaginationLink tag="button" onClick={() => onPageChange(index)}>{index}</PaginationLink>
26
+ </PaginationItem>}
27
+
28
+ nextPage={index => <PaginationItem
29
+ onClick={() => onPageChange(index)}><PaginationLink next tag="button">&raquo;</PaginationLink></PaginationItem>}
30
+
31
+ dots={index => <PaginationItem
32
+ onClick={() => onPageChange(index)} active={currentPage === index}><PaginationLink tag="button">{index}</PaginationLink></PaginationItem>}
33
+ />
34
+ </Pagination>;
35
+ }
@@ -0,0 +1,117 @@
1
+ import React, { useCallback, useContext, useRef, useState } from 'react';
2
+ import { ValidatorProvider } from "react-admin-base";
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { Redirect, Route } from 'react-router-dom';
5
+ import { Alert, Button, Form, Modal, ModalFooter, ModalHeader } from "reactstrap";
6
+ import LoadingButton from '../Components/LoadingButton';
7
+ import BootstrapDataTable, { Actions } from './BootstrapDataTable';
8
+
9
+ type ModalEntityEditorParams = {
10
+ entity: any;
11
+ title?: string;
12
+ size?: string;
13
+ url?: string;
14
+ onReload?: any;
15
+ disabled?: Boolean;
16
+ children: React.ReactNode;
17
+ }
18
+
19
+ export function ModalEntityEditor({ entity, title, size, url, onReload, disabled, children } : ModalEntityEditorParams) {
20
+ const [ , , save, loading ] = entity;
21
+
22
+ const [ open, setOpen ] = useState(true);
23
+ const [ saved, setSaved ] = useState(false);
24
+ const [ error, setError ] = useState<any>(false);
25
+
26
+ const onSubmit = useCallback(async function(e) {
27
+ e.stopPropagation();
28
+ e.preventDefault();
29
+
30
+
31
+ if (saved) {
32
+ setSaved(false);
33
+ }
34
+
35
+ if (error) {
36
+ setError(null);
37
+ }
38
+
39
+ try {
40
+ let data = await save();
41
+ if (onReload) {
42
+ await onReload(data);
43
+ }
44
+
45
+ if (url) {
46
+ setSaved(true);
47
+ }
48
+ } catch(e) {
49
+ console.error(e);
50
+ setError((e.response && e.response.data && e.response.data.message) || (error.data && error.data.message) || e.data || e.message || e);
51
+ }
52
+ finally
53
+ {
54
+
55
+ }
56
+ }, [save, saved, error, onReload, url]);
57
+
58
+ return <>
59
+ { (saved || !open) && url && <Redirect to={url} />}
60
+ <Modal isOpen size={size} toggle={() => url ? setOpen(false) : onReload(null)} fade={false}>
61
+ { title && <ModalHeader toggle={() => url ? setOpen(false) : onReload(null)}>
62
+ <b>{ title }</b>
63
+ </ModalHeader> }
64
+ <ValidatorProvider>
65
+ <Form onSubmit={onSubmit} disabled={!!loading || disabled}>
66
+ <fieldset disabled={!!loading || !!disabled}>
67
+ { children }
68
+ </fieldset>
69
+ <ModalFooter>
70
+ { error && <Alert color="danger" toggle={() => setError(null)} style={{ display: 'block', width: '100%' }}>{ error }</Alert>}
71
+ <LoadingButton block loading={loading} type="submit" color="primary">
72
+ <i className="fas fa-save" />{' '}<FormattedMessage id="ENTITY.SAVE" />
73
+ </LoadingButton>
74
+ <Button block outline color="danger" onClick={(e) => { e.preventDefault(); (url ? setOpen(false) : onReload(null)); }}>
75
+ <i className="fas fa-times-circle" />{' '}<FormattedMessage id="ENTITY.CANCEL" />
76
+ </Button>
77
+ </ModalFooter>
78
+ </Form>
79
+ </ValidatorProvider>
80
+ </Modal>
81
+ </>;
82
+ }
83
+
84
+ const UrlContext = React.createContext(null as any);
85
+
86
+ type CrudActionProps = {
87
+ id: any;
88
+ edit?: Boolean;
89
+ del: string;
90
+ children?: React.ReactNode;
91
+ };
92
+
93
+ export function CRUDActions({ id, edit, del, children }: CrudActionProps) {
94
+ const url = useContext(UrlContext);
95
+
96
+ return <Actions edit={edit && (url + "/" + id + "/edit")} del={del}>
97
+ { children }
98
+ </Actions>;
99
+ }
100
+
101
+ export default function CRUD(props) {
102
+ const ref = useRef(null as any);
103
+ const { id, url, apiUrl, Component, defaultParams, noAdd } = props;
104
+
105
+ var reload = useCallback(async function() {
106
+ if (ref.current) {
107
+ ref.current({});
108
+ }
109
+ }, [ref]);
110
+
111
+ return <UrlContext.Provider value={url}>
112
+ { !noAdd && <Route path={url+"/create"} render={props => <Component url={url} onReload={reload} {...(defaultParams || {})} />} /> }
113
+ <Route path={url+"/:id/edit"} render={props => <Component url={url} onReload={reload} id={props.match.params.id} {...(defaultParams || {})} />} />
114
+ { id && <Route path={url+"/edit"} render={props => <Component url={url} onReload={reload} id={id} {...(defaultParams || {})} />} />}
115
+ <BootstrapDataTable innerRef={ref} add={!noAdd && (url+"/create")} {...props} url={apiUrl || url} />
116
+ </UrlContext.Provider>;
117
+ }
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { FormGroup, Input, Label } from "reactstrap";
3
+
4
+ export default function CheckBox(props: any) {
5
+ return <FormGroup check>
6
+ <Input type={props.type || "checkbox"} {...props} label={undefined} />
7
+ <Label check for={props.id}>{props.children || props.label}</Label>
8
+ </FormGroup>;
9
+ }
@@ -0,0 +1,67 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { ValidatorProvider } from "react-admin-base";
3
+ import { FormattedMessage } from "react-intl";
4
+ import { Alert, Form } from 'reactstrap';
5
+ import LoadingButton from "../Components/LoadingButton";
6
+ import { ValidationErrors } from './Validator';
7
+
8
+ type EntityEditorParams = {
9
+ entity: any;
10
+ onSave?: any;
11
+ saveButtonClassName?: string;
12
+ saveButtonText?: string;
13
+ disabled?: Boolean;
14
+ children: React.ReactNode;
15
+ };
16
+
17
+ export default function EntityEditor({ entity, disabled, children, onSave, saveButtonClassName, saveButtonText }: EntityEditorParams) {
18
+ const [ _1, _2, save, loading ] = entity;
19
+
20
+ const [ saved, setSaved ] = useState(false);
21
+ const [ error, setError ] = useState<any>(false);
22
+
23
+ const onSubmit = useCallback(async function(e) {
24
+ e.preventDefault();
25
+ e.stopPropagation();
26
+
27
+ if (saved) {
28
+ setSaved(false);
29
+ }
30
+
31
+ if (error) {
32
+ setError(null);
33
+ }
34
+
35
+ try {
36
+ await save();
37
+ onSave && onSave();
38
+ setSaved(true);
39
+ } catch(e) {
40
+ console.error(e);
41
+ setError((e.response && e.response.data && e.response.data.message) || (error.data && error.data.message) || e.data || e.message || e);
42
+ }
43
+ finally
44
+ {
45
+
46
+ }
47
+ }, [save, saved, error]);
48
+
49
+ const savedAlert = saved && <Alert color="success" toggle={() => setSaved(false)}><i className="fas fa-check-circle me-2" /><FormattedMessage id="ENTITY.SAVED" /></Alert>;
50
+
51
+ return <ValidatorProvider>
52
+ <Form onSubmit={onSubmit} disabled={!!loading || !!disabled}>
53
+ { loading && <Alert color="warning" toggle={() => setSaved(false)}>
54
+ <i className="fa fa-spin fa-spinner" />{' '}
55
+ <FormattedMessage id="PLEASE_WAIT"/>
56
+ </Alert>}
57
+ { savedAlert }
58
+ { error && <Alert color="danger" toggle={() => setError(null)}>{ error }</Alert>}
59
+ <fieldset disabled={!!loading || !!disabled}>
60
+ { children }
61
+ </fieldset>
62
+ <ValidationErrors />
63
+ { savedAlert }
64
+ <LoadingButton className={saveButtonClassName || "col-md-12"} loading={loading} type="submit" color="primary">{ saveButtonText || <FormattedMessage id="ENTITY.SAVE" /> } <i className="fas fa-save fa-lg"></i></LoadingButton>
65
+ </Form>
66
+ </ValidatorProvider>;
67
+ }
@@ -0,0 +1,29 @@
1
+
2
+ import { Alert } from 'reactstrap';
3
+ import React from 'react';
4
+
5
+ function ErrorHandler({ error }) {
6
+ return <Alert color="danger">
7
+ <i className="fas fa-exclamation-circle me-1" />
8
+ { error && ((error.response && error.response.data && error.response.data.message) || error.message) }
9
+ </Alert>;
10
+ }
11
+
12
+ export default class ErrorBoundary extends React.Component {
13
+ state = { hasError: false, error: null };
14
+
15
+ static getDerivedStateFromError(error) {
16
+ return {
17
+ hasError: true,
18
+ error
19
+ };
20
+ }
21
+
22
+ render() {
23
+ if (this.state.hasError) {
24
+ return <ErrorHandler error={this.state.error} />;
25
+ }
26
+
27
+ return this.props.children;
28
+ }
29
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import {useExporter} from 'react-admin-base';
3
+ import {Button, Col} from "reactstrap";
4
+
5
+ export default function ExcelExportButton({name, header, params, map, extra}) {
6
+ const [ handleExport, loading ] = useExporter(header, params, map, extra);
7
+
8
+ return <Col><Button className="w-100 d-block" type="button" size="lg" color="success" outline disabled={!!loading} onClick={() => handleExport(name)}>
9
+ {loading ? <i className="fas fa-spin fa-spinner"/> : <i className="fas fa-file-excel"/>}
10
+ </Button></Col>
11
+ }
@@ -0,0 +1,14 @@
1
+
2
+ import React from 'react';
3
+ import { useApp, useAuth, useLogin } from 'react-admin-base';
4
+ import { FormattedMessage } from 'react-intl';
5
+
6
+ export default function ExternalLoginButton({ id, icon, name, url }) {
7
+ const { endpoint } = useApp();
8
+ const [{ client_id }] = useAuth();
9
+ const { login } = useLogin();
10
+
11
+ return <a className={"btn btn-primary d-block w-100 btn-social-"+id} href={endpoint + url + '?client_id=' + client_id + '&callback_url=' + encodeURIComponent(window.location.origin + login)}>
12
+ <i className={"fab fa-" + (icon || id) + " me-2"} /> <FormattedMessage id="SIGN_IN_USING" values={{ provider: <b>{name}</b> }} />
13
+ </a>;
14
+ }
@@ -0,0 +1,87 @@
1
+
2
+ import React, { useCallback } from 'react';
3
+ import prettysize from 'prettysize';
4
+ import {Button} from "reactstrap";
5
+ import { useApp, useFilePicker, usePreviewComponent } from "react-admin-base";
6
+ import {FormattedMessage} from "react-intl";
7
+
8
+ var photo_ext = ["png", "jpg", "jpeg", "svg"];
9
+
10
+ function is_photo(name) {
11
+ return photo_ext.indexOf(name.split('.')[1]) !== -1;
12
+ }
13
+
14
+ function is_absolute(url) {
15
+ if (url.indexOf("blob:") === 0)
16
+ return false;
17
+
18
+ var pat = /^https?:\/\//i;
19
+ return !pat.test(url);
20
+ }
21
+
22
+ export function Relative({ children }) {
23
+ const app = useApp();
24
+
25
+ if (!children)
26
+ return null;
27
+
28
+ const { src, href } = children.props;
29
+
30
+ return React.cloneElement(children, {
31
+ src: src && (is_absolute(src) ? app.endpoint.replace(/\/$/, "") + src : src),
32
+ href: href && (is_absolute(href) ? app.endpoint.replace(/\/$/, "") + href : href)
33
+ });
34
+ }
35
+
36
+ export function Preview({ value }) {
37
+ var name = value.$name;
38
+ var src = value.$blob_url || value.$src;
39
+
40
+ if (is_photo(name)) {
41
+ return <div className="mt-2">
42
+ <Relative>
43
+ <img src={src} style={{ maxHeight: '150px', maxWidth: '100%' }} alt={name} />
44
+ </Relative>
45
+ </div>;
46
+ }
47
+
48
+ return null;
49
+ }
50
+
51
+ export default function FilePickerCore({ disabled, className, accepts, value, onNotify, children, transform } : { disabled?: any, className?: any, accepts?: any, value?:any, onNotify?:any, children?:any, transform?:any }) {
52
+ const [ uc, cancel, pick ] = useFilePicker(value, onNotify);
53
+ const CustomPreview = usePreviewComponent() || Preview;
54
+
55
+ const chooseFile = useCallback(function(e) {
56
+ e.preventDefault();
57
+ pick(accepts, transform);
58
+ }, [pick, accepts, transform]);
59
+
60
+ if(uc.$state === 1) { // dosya sec
61
+ return <div className={className}>
62
+ <Button color="primary" size="sm" onClick={chooseFile} disabled={disabled}><i className="fa fa-upload" /> <FormattedMessage id="CHOOSE_FILE" /></Button>
63
+ { children && children(null) }
64
+ </div>;
65
+ } else if(uc.$state === 2) { // yukleniyor
66
+ return <div className={className}>
67
+ <Button color="danger" outline size="sm" onClick={cancel}><i className="fa fa-times-circle" /></Button>
68
+ {' '}
69
+ <Button disabled size="sm" outline color="dark">
70
+ <i className="fa fa-spinner fa-spin" />
71
+ <span style={{ width: '35px', display: 'inline-block' }}>{ Math.round(uc.$pr * 1000) / 10 }%</span>
72
+ { uc.name }
73
+ </Button>
74
+ { children && children(uc) }
75
+ </div>;
76
+ } else if(uc.$state === 3) { // yuklendi
77
+ return <div className={className}>
78
+ { !disabled && <Button color="danger" outline size="sm" onClick={cancel}><i className="fa fa-trash" /></Button> }
79
+ &nbsp;<Relative><a href={uc.$src} target="_blank" className="btn btn-sm btn-outline-primary"><i className="fa fa-download" /> { uc.name }</a></Relative>
80
+ <span>&nbsp;({ prettysize(uc.$size) })</span>
81
+ { <CustomPreview value={uc} /> }
82
+ { children && children(uc) }
83
+ </div>;
84
+ }
85
+
86
+ return null;
87
+ }