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.
- package/.yarnrc +1 -0
- package/assets/main.css +63 -0
- package/lib/esm/Components/ApiSelect.d.ts +3 -0
- package/lib/esm/Components/ApiSelect.js +82 -0
- package/lib/esm/Components/BootstrapDataTable.d.ts +12 -0
- package/lib/esm/Components/BootstrapDataTable.js +141 -0
- package/lib/esm/Components/BootstrapPagination.d.ts +9 -0
- package/lib/esm/Components/BootstrapPagination.js +13 -0
- package/lib/esm/Components/CRUD.d.ts +20 -0
- package/lib/esm/Components/CRUD.js +88 -0
- package/lib/esm/Components/CheckBox.d.ts +2 -0
- package/lib/esm/Components/CheckBox.js +7 -0
- package/lib/esm/Components/EntityEditor.d.ts +11 -0
- package/lib/esm/Components/EntityEditor.js +61 -0
- package/lib/esm/Components/ErrorBoundary.d.ts +12 -0
- package/lib/esm/Components/ErrorBoundary.js +25 -0
- package/lib/esm/Components/ExcelExportButton.d.ts +8 -0
- package/lib/esm/Components/ExcelExportButton.js +8 -0
- package/lib/esm/Components/ExternalLoginButton.d.ts +7 -0
- package/lib/esm/Components/ExternalLoginButton.js +12 -0
- package/lib/esm/Components/FilePickerCore.d.ts +16 -0
- package/lib/esm/Components/FilePickerCore.js +82 -0
- package/lib/esm/Components/GoToTop.d.ts +11 -0
- package/lib/esm/Components/GoToTop.js +34 -0
- package/lib/esm/Components/ImagePicker.d.ts +2 -0
- package/lib/esm/Components/ImagePicker.js +22 -0
- package/lib/esm/Components/LanguageProvider.d.ts +8 -0
- package/lib/esm/Components/LanguageProvider.js +42 -0
- package/lib/esm/Components/LoadingButton.d.ts +2 -0
- package/lib/esm/Components/LoadingButton.js +6 -0
- package/lib/esm/Components/MenuState.d.ts +3 -0
- package/lib/esm/Components/MenuState.js +20 -0
- package/lib/esm/Components/MultiFilePicker.d.ts +11 -0
- package/lib/esm/Components/MultiFilePicker.js +22 -0
- package/lib/esm/Components/SingleFilePicker.d.ts +12 -0
- package/lib/esm/Components/SingleFilePicker.js +20 -0
- package/lib/esm/Components/TopProgressBar.d.ts +3 -0
- package/lib/esm/Components/TopProgressBar.js +10 -0
- package/lib/esm/Components/Validator.d.ts +16 -0
- package/lib/esm/Components/Validator.js +33 -0
- package/lib/esm/i18n/de.json +34 -0
- package/lib/esm/i18n/en.json +35 -0
- package/lib/esm/i18n/tr.json +35 -0
- package/lib/esm/index.d.ts +19 -0
- package/lib/esm/index.js +19 -0
- package/package.json +55 -0
- package/src/Components/ApiSelect.tsx +122 -0
- package/src/Components/BootstrapDataTable.tsx +160 -0
- package/src/Components/BootstrapPagination.tsx +35 -0
- package/src/Components/CRUD.tsx +117 -0
- package/src/Components/CheckBox.tsx +9 -0
- package/src/Components/EntityEditor.tsx +67 -0
- package/src/Components/ErrorBoundary.tsx +29 -0
- package/src/Components/ExcelExportButton.tsx +11 -0
- package/src/Components/ExternalLoginButton.tsx +14 -0
- package/src/Components/FilePickerCore.tsx +87 -0
- package/src/Components/GoToTop.tsx +41 -0
- package/src/Components/ImagePicker.tsx +26 -0
- package/src/Components/LanguageProvider.tsx +61 -0
- package/src/Components/LoadingButton.tsx +7 -0
- package/src/Components/MenuState.tsx +24 -0
- package/src/Components/MultiFilePicker.tsx +43 -0
- package/src/Components/SingleFilePicker.tsx +40 -0
- package/src/Components/TopProgressBar.tsx +13 -0
- package/src/Components/Validator.tsx +55 -0
- package/src/i18n/de.json +34 -0
- package/src/i18n/en.json +35 -0
- package/src/i18n/tr.json +35 -0
- package/src/index.ts +42 -0
- 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">«</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">»</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
|
+
<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> ({ prettysize(uc.$size) })</span>
|
|
81
|
+
{ <CustomPreview value={uc} /> }
|
|
82
|
+
{ children && children(uc) }
|
|
83
|
+
</div>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null;
|
|
87
|
+
}
|