trithuc-mvc-react 1.5.9 → 1.6.3

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/api/index.js CHANGED
@@ -60,3 +60,12 @@ export const exportExcel = async ({ tableName,data }) => {
60
60
  });
61
61
  return res.data;
62
62
  };
63
+
64
+ export const uploadFile = async (formData) => {
65
+ const res = await api.post("/Handler/fileUploader.ashx", formData, {
66
+ headers: {
67
+ "Content-Type": "multipart/form-data"
68
+ }
69
+ });
70
+ return res.data;
71
+ };
@@ -15,14 +15,14 @@ import { yupResolver } from "@hookform/resolvers/yup";
15
15
  EditorForm.propTypes = {
16
16
  fields: PropTypes.array
17
17
  };
18
- function EditorForm({ fields, submitRef }) {
18
+ function EditorForm({ fields, submitRef }) {
19
19
  const queryClient = useQueryClient();
20
20
  const { tableName, selectedEditItem, setOpenEditorDialog, validationSchema } = useDataTable();
21
21
 
22
22
  const methods = useForm({ defaultValues: {}, resolver: yupResolver(validationSchema) });
23
23
  const theme = useTheme();
24
24
  const downXl = useMediaQuery(theme.breakpoints.down("xl"));
25
- const elementSize = downXl ? "small": "medium";
25
+ const elementSize = downXl ? "small" : "medium";
26
26
  useEffect(() => {
27
27
  if (selectedEditItem) {
28
28
  methods.setValue("Id", selectedEditItem.Id);
@@ -37,6 +37,8 @@ function EditorForm({ fields, submitRef }) {
37
37
  methods.setValue(keyValueLabel, selectedEditItem[keyValueLabel]);
38
38
  } else if (type === "date") {
39
39
  methods.setValue(field, selectedEditItem[field]);
40
+ } else if (type === "file") {
41
+ methods.setValue(field, selectedEditItem[field] ? JSON.parse(selectedEditItem[field]) : []);
40
42
  } else {
41
43
  methods.setValue(field, selectedEditItem[field]);
42
44
  }
@@ -67,20 +69,22 @@ function EditorForm({ fields, submitRef }) {
67
69
  }
68
70
  });
69
71
  const onSubmit = (data) => {
70
- fields
71
- .filter(({ type }) => type === "date")
72
- .forEach(({ field }) => {
72
+ fields.reduce((data, { type, field, datas, keyValueLabel, keyValue, keyLabel }) => {
73
+ if (type === "date") {
73
74
  if (data[field]) {
74
75
  data[field] = moment(data[field]).toDate();
75
76
  }
76
- });
77
- fields
78
- .filter(({ type }) => type === "autocomplete")
79
- .forEach(({ field, datas, keyValueLabel, keyValue, keyLabel }) => {
77
+ } else if (type === "autocomplete") {
80
78
  if (data[field] && !data[keyValueLabel] && keyValueLabel) {
81
79
  data[keyValueLabel] = datas.find((item) => item[keyValue] == data[field])?.[keyLabel];
82
80
  }
83
- });
81
+ } else if (type === "file") {
82
+ if (data[field]) {
83
+ data[field] = JSON.stringify(data[field]);
84
+ }
85
+ }
86
+ return data;
87
+ }, data);
84
88
 
85
89
  saveMutation.mutate({
86
90
  tableName,
@@ -103,7 +107,7 @@ function EditorForm({ fields, submitRef }) {
103
107
  childrenFields,
104
108
  datas,
105
109
  loading = false,
106
- onChange = () => {},
110
+ onChange = () => { },
107
111
  keyLabel,
108
112
  keyValue,
109
113
  keyValueLabel,
@@ -19,6 +19,7 @@ import { DatePicker } from "@mui/x-date-pickers";
19
19
  import { useCallback, useEffect, } from "react";
20
20
  import moment from "moment/moment";
21
21
  import { DEFAULT_DATE_FORMAT } from "../../constants";
22
+ import UploadMultipleFile from "./upload/UploadMultipleFile";
22
23
 
23
24
 
24
25
  FormField.propTypes = {
@@ -235,7 +236,6 @@ function FormField({
235
236
  }}
236
237
  />
237
238
  );
238
-
239
239
  case "trangThaiXuLy":
240
240
  return (
241
241
  <Controller
@@ -262,6 +262,20 @@ function FormField({
262
262
  }}
263
263
  />
264
264
  );
265
+ case "file":
266
+ return (
267
+ <Controller
268
+ name={name}
269
+ control={control}
270
+ render={({ field, fieldState: { error } }) => {
271
+ return (
272
+ <>
273
+ <UploadMultipleFile {...field} name="DinhKem" />
274
+ </>
275
+ );
276
+ }}
277
+ />
278
+ );
265
279
  }
266
280
  }
267
281
 
@@ -32,6 +32,7 @@ DataManagement.propTypes = {
32
32
  editorFields: PropTypes.array,
33
33
  validationSchema: PropTypes.object,
34
34
  disableStatus: PropTypes.bool,
35
+ disableAdd: PropTypes.bool,
35
36
  statusKey: PropTypes.string,
36
37
  tableActions: PropTypes.array,
37
38
  disableEditor: PropTypes.bool,
@@ -50,6 +51,7 @@ function DataManagement({
50
51
  validationSchema = {},
51
52
  statusKey = "Status",
52
53
  disableStatus = false,
54
+ disableAdd = false,
53
55
  tableActions = [],
54
56
  disableEditor = false,
55
57
  onAddClick = () => {},
@@ -107,7 +109,7 @@ function DataManagement({
107
109
  validationSchema,
108
110
  statusKey,
109
111
  disableStatus,
110
-
112
+ disableAdd,
111
113
  tableActions
112
114
  };
113
115
  }, [tableName, selectedField, columns, selectedEditItem, dataSearch, setDataSearch, validationSchema, tableActions]);
@@ -119,7 +121,6 @@ function DataManagement({
119
121
  }, [Permission, setPermission]);
120
122
  const methods = useForm({ defaultValues: {} });
121
123
  const { reset, setValue } = methods;
122
-
123
124
  return (
124
125
  <>
125
126
  <DataTableContext.Provider value={values}>
@@ -148,20 +149,22 @@ function DataManagement({
148
149
 
149
150
  <ExportExcelButton tableName={tableName} data={dataSearch} size={elementSize} />
150
151
  {(!Permission || Permission.Create) && (
151
- <Button
152
- size={elementSize}
153
- variant="contained"
154
- startIcon={<Add />}
155
- onClick={(e) => {
156
- if (!disableEditor) {
157
- setOpenEditorDialog(true);
158
- setSelectedEditItem(null);
159
- }
160
- onAddClick(e);
161
- }}
162
- >
163
- Thêm
164
- </Button>
152
+ (!disableAdd) && (
153
+ <Button
154
+ size={elementSize}
155
+ variant="contained"
156
+ startIcon={<Add />}
157
+ onClick={(e) => {
158
+ if (!disableEditor) {
159
+ setOpenEditorDialog(true);
160
+ setSelectedEditItem(null);
161
+ }
162
+ onAddClick(e);
163
+ }}
164
+ >
165
+ Thêm
166
+ </Button>
167
+ )
165
168
  )}
166
169
  </Stack>
167
170
  </Stack>
@@ -0,0 +1,147 @@
1
+ import { isString } from 'lodash';
2
+ import PropTypes from 'prop-types';
3
+ import { Icon, addIcon } from '@iconify/react';
4
+ import { useDropzone } from 'react-dropzone';
5
+ import AddAPhotoIcon from "@mui/icons-material/AddAPhoto";
6
+ // material
7
+ import { alpha, styled } from '@mui/material/styles';
8
+ import { Box, Typography, Paper } from '@mui/material';
9
+ // utils
10
+
11
+ function fData(number) {
12
+ // return numeral(number).format("0.0 b");
13
+ return number;
14
+ }
15
+ // ----------------------------------------------------------------------
16
+
17
+ const RootStyle = styled('div')(({ theme }) => ({
18
+ width: 144,
19
+ height: 144,
20
+ margin: 'auto',
21
+ borderRadius: '50%',
22
+ padding: theme.spacing(1),
23
+ border: `1px dashed ${theme.palette.grey[500_32]}`
24
+ }));
25
+
26
+ const DropZoneStyle = styled('div')({
27
+ zIndex: 0,
28
+ width: '100%',
29
+ height: '100%',
30
+ outline: 'none',
31
+ display: 'flex',
32
+ overflow: 'hidden',
33
+ borderRadius: '50%',
34
+ position: 'relative',
35
+ alignItems: 'center',
36
+ justifyContent: 'center',
37
+ '& > *': { width: '100%', height: '100%' },
38
+ '&:hover': {
39
+ cursor: 'pointer',
40
+ '& .placeholder': {
41
+ zIndex: 9
42
+ }
43
+ }
44
+ });
45
+
46
+ const PlaceholderStyle = styled('div')(({ theme }) => ({
47
+ display: 'flex',
48
+ position: 'absolute',
49
+ alignItems: 'center',
50
+ flexDirection: 'column',
51
+ justifyContent: 'center',
52
+ color: theme.palette.text.secondary,
53
+ backgroundColor: theme.palette.background.neutral,
54
+ transition: theme.transitions.create('opacity', {
55
+ easing: theme.transitions.easing.easeInOut,
56
+ duration: theme.transitions.duration.shorter
57
+ }),
58
+ '&:hover': { opacity: 0.72 }
59
+ }));
60
+
61
+ // ------------------------------------------100/*/----------------------------
62
+
63
+ UploadAvatar.propTypes = {
64
+ error: PropTypes.bool,
65
+ file: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
66
+ caption: PropTypes.node,
67
+ sx: PropTypes.object
68
+ };
69
+ const ShowRejectionItems = ({ fileRejections }) => (
70
+ <Paper
71
+ variant="outlined"
72
+ sx={{
73
+ py: 1,
74
+ px: 2,
75
+ my: 2,
76
+ borderColor: "error.light",
77
+ bgcolor: (theme) => alpha(theme.palette.error.main, 0.08)
78
+ }}
79
+ >
80
+ {fileRejections.map(({ file, errors }) => {
81
+ const { path, size } = file;
82
+ return (
83
+ <Box key={path} sx={{ my: 1 }}>
84
+ <Typography variant="subtitle2" noWrap>
85
+ {path} - {fData(size)}
86
+ </Typography>
87
+ {errors.map((e) => (
88
+ <Typography key={e.code} variant="caption" component="p">
89
+ - {e.message}
90
+ </Typography>
91
+ ))}
92
+ </Box>
93
+ );
94
+ })}
95
+ </Paper>
96
+ );
97
+ export default function UploadAvatar({ error, file, caption, sx, ...other }) {
98
+ const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
99
+ multiple: false,
100
+ ...other
101
+ });
102
+
103
+
104
+ // console.log(file);
105
+ return (
106
+ <>
107
+ <RootStyle sx={sx}>
108
+ <DropZoneStyle
109
+ {...getRootProps()}
110
+ sx={{
111
+ ...(isDragActive && { opacity: 0.72 }),
112
+ ...((isDragReject || error) && {
113
+ color: "error.main",
114
+ borderColor: "error.light",
115
+ bgcolor: "error.lighter"
116
+ })
117
+ }}
118
+ >
119
+ <input {...getInputProps()} />
120
+
121
+ {file && <Box component="img" alt="avatar" src={isString(file) ? file : file.preview} sx={{ zIndex: 8, objectFit: "cover" }} />}
122
+
123
+ <PlaceholderStyle
124
+ className="placeholder"
125
+ sx={{
126
+ ...(file && {
127
+ opacity: 0,
128
+ color: "common.white",
129
+ bgcolor: "grey.900",
130
+ "&:hover": { opacity: 0.72 }
131
+ })
132
+ }}
133
+ >
134
+ <Box icon={AddAPhotoIcon} sx={{ width: 24, height: 24, mb: 1 }} >
135
+ <AddAPhotoIcon />
136
+ </Box>
137
+ <Typography variant="caption">Chọn hình</Typography>
138
+ </PlaceholderStyle>
139
+ </DropZoneStyle>
140
+ </RootStyle>
141
+
142
+ {caption}
143
+
144
+ {fileRejections.length > 0 && <ShowRejectionItems fileRejections={fileRejections} />}
145
+ </>
146
+ );
147
+ }
@@ -0,0 +1,237 @@
1
+ import { isString } from "lodash";
2
+ import PropTypes from "prop-types";
3
+ // import { Icon } from "@iconify/react";
4
+ import { useDropzone } from "react-dropzone";
5
+
6
+ import CloseIcon from "@mui/icons-material/Close";
7
+ import TextSnippetOutlinedIcon from "@mui/icons-material/TextSnippetOutlined";
8
+ // import { motion, AnimatePresence } from 'framer-motion';
9
+ // material
10
+ import { alpha, styled } from "@mui/material/styles";
11
+ import {
12
+ Box,
13
+ List,
14
+ Stack,
15
+ Paper,
16
+ Button,
17
+ ListItem,
18
+ Typography,
19
+ ListItemIcon,
20
+ ListItemText,
21
+ ListItemSecondaryAction,
22
+ IconButton,
23
+ Avatar,
24
+ Link
25
+ } from "@mui/material";
26
+ // utils
27
+ import { bytesToSize, fData } from "../../../utils";
28
+ import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined";
29
+ //
30
+ // import { MIconButton } from '../@material-extend';
31
+ // import { varFadeInRight } from "../animate";
32
+ // import { UploadIllustration } from '../../assets';
33
+
34
+ // ----------------------------------------------------------------------
35
+ let URL_APPLICATION = document.querySelector("#URL_APPLICATION").value;
36
+
37
+ const DropZoneStyle = styled("div")(({ theme }) => ({
38
+ outline: "none",
39
+ display: "flex",
40
+ textAlign: "center",
41
+ alignItems: "center",
42
+ flexDirection: "column",
43
+ justifyContent: "center",
44
+ padding: theme.spacing(5, 1),
45
+ borderRadius: theme.shape.borderRadius,
46
+ backgroundColor: theme.palette.background.neutral,
47
+ border: `1px dashed ${theme.palette.grey[500_32]}`,
48
+ "&:hover": { opacity: 0.72, cursor: "pointer" },
49
+ [theme.breakpoints.up("md")]: { textAlign: "left", flexDirection: "row" }
50
+ }));
51
+
52
+ // ----------------------------------------------------------------------
53
+
54
+ UploadMultiFile.propTypes = {
55
+ error: PropTypes.bool,
56
+ showPreview: PropTypes.bool,
57
+ files: PropTypes.array,
58
+ onRemove: PropTypes.func,
59
+ onRemoveAll: PropTypes.func,
60
+ sx: PropTypes.object
61
+ };
62
+
63
+ export default function UploadMultiFile({ error, showPreview = false, files, onRemove, onRemoveAll, sx, ...other }) {
64
+ const hasFile = files && files.length > 0;
65
+ const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
66
+ ...other
67
+ });
68
+
69
+ const ShowRejectionItems = () => (
70
+ <Paper
71
+ variant="outlined"
72
+ sx={{
73
+ py: 1,
74
+ px: 2,
75
+ mt: 3,
76
+ borderColor: "error.light",
77
+ bgcolor: (theme) => alpha(theme.palette.error.main, 0.08)
78
+ }}
79
+ >
80
+ {fileRejections.map(({ file, errors }) => {
81
+ const { path, size } = file;
82
+ return (
83
+ <Box key={path} sx={{ my: 1 }}>
84
+ <Typography variant="subtitle2" noWrap>
85
+ {path} - {fData(size)}
86
+ </Typography>
87
+ {errors.map((e) => (
88
+ <Typography key={e.code} variant="caption" component="p">
89
+ - {e.message}
90
+ </Typography>
91
+ ))}
92
+ </Box>
93
+ );
94
+ })}
95
+ </Paper>
96
+ );
97
+
98
+ return (
99
+ <Box sx={{ width: "100%", ...sx }}>
100
+ <DropZoneStyle
101
+ {...getRootProps()}
102
+ sx={{
103
+ ...(isDragActive && { opacity: 0.72 }),
104
+ ...((isDragReject || error) && {
105
+ color: "error.main",
106
+ borderColor: "error.light",
107
+ bgcolor: "error.lighter"
108
+ })
109
+ }}
110
+ >
111
+ <input {...getInputProps()} />
112
+
113
+ {/* <UploadIllustration sx={{ width: 220 }} /> */}
114
+ <Stack direction={"row"} alignItems={"center"}>
115
+ <Avatar>
116
+ <FileUploadOutlinedIcon />
117
+ </Avatar>
118
+ <Box sx={{ p: 1, ml: { md: 2 } }}>
119
+ <Typography gutterBottom variant="h5">
120
+ Click hoặc kéo và thả file
121
+ </Typography>
122
+
123
+ {/* <Typography variant="body2" sx={{ color: "text.secondary" }}>
124
+ Drop files here or click&nbsp;
125
+ <Typography variant="body2" component="span" sx={{ color: "primary.main", textDecoration: "underline" }}>
126
+ browse
127
+ </Typography>
128
+ &nbsp;thorough your machine
129
+ </Typography> */}
130
+ </Box>
131
+ </Stack>
132
+ </DropZoneStyle>
133
+
134
+ {fileRejections.length > 0 && <ShowRejectionItems />}
135
+
136
+ <List disablePadding sx={{ ...(hasFile && { my: 3 }) }}>
137
+ {files?.map((file) => {
138
+ let name, size;
139
+ if (file.tenFile) {
140
+ name = file.tenFile;
141
+ size = file.Size;
142
+ } else {
143
+ name = file.name;
144
+ size = bytesToSize(file.size);
145
+ }
146
+ const { preview, tenFile, Size } = file;
147
+ const key = isString(file) ? file : name;
148
+
149
+ if (showPreview) {
150
+ return (
151
+ <ListItem
152
+ key={key}
153
+ // {...varFadeInRight}
154
+ sx={{
155
+ p: 0,
156
+ m: 0.5,
157
+ width: 80,
158
+ height: 80,
159
+ borderRadius: 1.5,
160
+ overflow: "hidden",
161
+ position: "relative",
162
+ display: "inline-flex"
163
+ }}
164
+ >
165
+ <Paper
166
+ variant="outlined"
167
+ component="img"
168
+ src={isString(file) ? file : preview}
169
+ sx={{ width: "100%", height: "100%", objectFit: "cover", position: "absolute" }}
170
+ />
171
+ <Box sx={{ top: 6, right: 6, position: "absolute" }}>
172
+ <IconButton
173
+ size="small"
174
+ onClick={() => onRemove(file)}
175
+ sx={{
176
+ p: "2px",
177
+ color: "common.white",
178
+ bgcolor: (theme) => alpha(theme.palette.grey[900], 0.72),
179
+ "&:hover": {
180
+ bgcolor: (theme) => alpha(theme.palette.grey[900], 0.48)
181
+ }
182
+ }}
183
+ >
184
+ <CloseIcon />
185
+ {/* <Icon icon={closeFill} /> */}
186
+ </IconButton>
187
+ </Box>
188
+ </ListItem>
189
+ );
190
+ }
191
+
192
+ return (
193
+ <ListItem
194
+ key={key}
195
+ // {...varFadeInRight}
196
+ sx={{
197
+ my: 1,
198
+ py: 0.75,
199
+ px: 2,
200
+ borderRadius: 1,
201
+ border: (theme) => `solid 1px ${theme.palette.divider}`,
202
+ bgcolor: "background.paper"
203
+ }}
204
+ >
205
+ <ListItemIcon>
206
+ {/* <Icon icon={fileFill} width={28} height={28} /> */}
207
+ <TextSnippetOutlinedIcon color="info" />
208
+ </ListItemIcon>
209
+ <Link href={URL_APPLICATION + file?.urlFile}>
210
+ <ListItemText
211
+ primary={isString(file) ? file : name}
212
+ secondary={isString(file) ? "" : size}
213
+ primaryTypographyProps={{ variant: "subtitle2" }}
214
+ secondaryTypographyProps={{ variant: "caption" }}
215
+ />
216
+ </Link>
217
+ <ListItemSecondaryAction>
218
+ <IconButton edge="end" size="small" onClick={() => onRemove(file)}>
219
+ <CloseIcon />
220
+ </IconButton>
221
+ </ListItemSecondaryAction>
222
+ </ListItem>
223
+ );
224
+ })}
225
+ </List>
226
+
227
+ {hasFile && (
228
+ <Stack direction="row" justifyContent="flex-end">
229
+ <Button onClick={onRemoveAll} sx={{ mr: 1.5 }} color="error">
230
+ Gỡ hết
231
+ </Button>
232
+ {/* <Button variant="contained">Upload files</Button> */}
233
+ </Stack>
234
+ )}
235
+ </Box>
236
+ );
237
+ }
@@ -0,0 +1,60 @@
1
+ import { CardContent, CardHeader, FormHelperText, LinearProgress, Card } from "@mui/material";
2
+
3
+ import { Controller, useFormContext } from "react-hook-form";
4
+ import UploadMultiFile from "trithuc-mvc-react/components/DataManagement/upload/UploadMultiFile";
5
+ import { useCallback, useState } from "react";
6
+ import { saveFilesToServer } from "@/utils";
7
+
8
+ const UploadMultipleFile = ({ name, label }) => {
9
+ const { control, getValues, setValue } = useFormContext();
10
+ const [isLoadingUpfile, setIsLoadingUpfile] = useState(false);
11
+
12
+ const handleDrop = useCallback(
13
+ async (acceptedFiles) => {
14
+ const olds = getValues(name) ?? [];
15
+ setIsLoadingUpfile(true);
16
+ const data = await saveFilesToServer(acceptedFiles);
17
+ setValue(name, [...olds, ...data]);
18
+ setIsLoadingUpfile(false);
19
+ },
20
+ [setValue]
21
+ );
22
+ const handleRemoveAll = () => {
23
+ setValue(name, []);
24
+ };
25
+
26
+ const handleRemove = (file) => {
27
+ const filteredItems = getValues(name).filter((_file) => _file.urlFile !== file.urlFile);
28
+ setValue(name, filteredItems);
29
+ };
30
+
31
+ return (
32
+ <Controller
33
+ name={name}
34
+ control={control}
35
+ render={({ field, fieldState: { error } }) => {
36
+ return (
37
+ <>
38
+
39
+ <UploadMultiFile
40
+ maxSize={3145728}
41
+ accept="*"
42
+ files={field.value}
43
+ onDrop={handleDrop}
44
+ onRemove={handleRemove}
45
+ onRemoveAll={handleRemoveAll}
46
+ error={Boolean(error)}
47
+ />
48
+ {isLoadingUpfile && <LinearProgress />}
49
+ {error?.message && (
50
+ <FormHelperText error sx={{ px: 2 }}>
51
+ {error.message}
52
+ </FormHelperText>
53
+ )}
54
+ </>
55
+ );
56
+ }}
57
+ />
58
+ );
59
+ };
60
+ export default UploadMultipleFile ;
@@ -0,0 +1,129 @@
1
+ import { isString } from "lodash";
2
+ import PropTypes from "prop-types";
3
+ import { useDropzone } from "react-dropzone";
4
+ // material
5
+ import { alpha, styled } from "@mui/material/styles";
6
+ import { Paper, Box, Typography } from "@mui/material";
7
+ // utils
8
+ // import { fData } from '../../utils/formatNumber';
9
+ //
10
+ // import { UploadIllustration } from '../../assets';
11
+ import { fData } from "../../utils";
12
+
13
+ // ----------------------------------------------------------------------
14
+
15
+ const DropZoneStyle = styled("div")(({ theme }) => ({
16
+ outline: "none",
17
+ display: "flex",
18
+ overflow: "hidden",
19
+ textAlign: "center",
20
+ position: "relative",
21
+ alignItems: "center",
22
+ flexDirection: "column",
23
+ justifyContent: "center",
24
+ padding: theme.spacing(5, 0),
25
+ borderRadius: theme.shape.borderRadius,
26
+ transition: theme.transitions.create("padding"),
27
+ backgroundColor: theme.palette.background.neutral,
28
+ border: `1px dashed ${theme.palette.grey[500_32]}`,
29
+ "&:hover": {
30
+ opacity: 0.72,
31
+ cursor: "pointer"
32
+ },
33
+ [theme.breakpoints.up("md")]: { textAlign: "left", flexDirection: "row" }
34
+ }));
35
+
36
+ // ----------------------------------------------------------------------
37
+
38
+ UploadSingleFile.propTypes = {
39
+ error: PropTypes.bool,
40
+ file: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
41
+ sx: PropTypes.object
42
+ };
43
+ const ShowRejectionItems = ({ fileRejections }) => (
44
+ <Paper
45
+ variant="outlined"
46
+ sx={{
47
+ py: 1,
48
+ px: 2,
49
+ mt: 3,
50
+ borderColor: "error.light",
51
+ bgcolor: (theme) => alpha(theme.palette.error.main, 0.08)
52
+ }}
53
+ >
54
+ {fileRejections.map(({ file, errors }) => {
55
+ const { path, size } = file;
56
+ return (
57
+ <Box key={path} sx={{ my: 1 }}>
58
+ <Typography variant="subtitle2" noWrap>
59
+ {path} - {fData(size)}
60
+ </Typography>
61
+ {errors.map((e) => (
62
+ <Typography key={e.code} variant="caption" component="p">
63
+ - {e.message}
64
+ </Typography>
65
+ ))}
66
+ </Box>
67
+ );
68
+ })}
69
+ </Paper>
70
+ );
71
+ export default function UploadSingleFile({ error, file, sx, ...other }) {
72
+ const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
73
+ multiple: false,
74
+ ...other
75
+ });
76
+
77
+ return (
78
+ <Box sx={{ width: "100%", ...sx }}>
79
+ <DropZoneStyle
80
+ {...getRootProps()}
81
+ sx={{
82
+ ...(isDragActive && { opacity: 0.72 }),
83
+ ...((isDragReject || error) && {
84
+ color: "error.main",
85
+ borderColor: "error.light",
86
+ bgcolor: "error.lighter"
87
+ }),
88
+ ...(file && { padding: "12% 0" })
89
+ }}
90
+ >
91
+ <input {...getInputProps()} />
92
+
93
+ {/* <UploadIllustration sx={{ width: 220 }} /> */}
94
+
95
+ <Box sx={{ p: 3, ml: { md: 2 } }}>
96
+ <Typography gutterBottom variant="h5">
97
+ Drop or Select file
98
+ </Typography>
99
+
100
+ <Typography variant="body2" sx={{ color: "text.secondary" }}>
101
+ Drop files here or click&nbsp;
102
+ <Typography variant="body2" component="span" sx={{ color: "primary.main", textDecoration: "underline" }}>
103
+ browse
104
+ </Typography>
105
+ &nbsp;thorough your machine
106
+ </Typography>
107
+ </Box>
108
+
109
+ {file && (
110
+ <Box
111
+ component="img"
112
+ alt="file preview"
113
+ src={isString(file) ? file : file.preview}
114
+ sx={{
115
+ top: 8,
116
+ borderRadius: 1,
117
+ objectFit: "cover",
118
+ position: "absolute",
119
+ width: "calc(100% - 16px)",
120
+ height: "calc(100% - 16px)"
121
+ }}
122
+ />
123
+ )}
124
+ </DropZoneStyle>
125
+
126
+ {/* {fileRejections.length > 0 && <ShowRejectionItems fileRejections={fileRejections} />} */}
127
+ </Box>
128
+ );
129
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trithuc-mvc-react",
3
- "version": "1.5.9",
3
+ "version": "1.6.3",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -0,0 +1,11 @@
1
+ export default (bytes, decimals = 2) => {
2
+ if (bytes === 0) return '0 Bytes';
3
+
4
+ const k = 1024;
5
+ const dm = decimals < 0 ? 0 : decimals;
6
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
7
+
8
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
9
+
10
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
11
+ };
@@ -0,0 +1,47 @@
1
+ import moment from "moment";
2
+
3
+ /**
4
+ * Returns an object with default values for the given fields.
5
+ *
6
+ * @param {Array<{ fieldName: string, defaultValue?: string, type?: string }>} fields - The list of fields.
7
+ * @returns {Record<string, string[] | null | string>} - The object with default values.
8
+ */
9
+ export const getDefaultValues = (fields) => {
10
+ return fields.reduce((acc, { fieldName, defaultValue = "", type = "text" }) => {
11
+ switch (type) {
12
+ case "file":
13
+ acc[fieldName] = [];
14
+ break;
15
+ case "date":
16
+ acc[fieldName] = null;
17
+ break;
18
+ default:
19
+ acc[fieldName] = defaultValue;
20
+ break;
21
+ }
22
+ return acc;
23
+ }, {});
24
+ };
25
+
26
+ /**
27
+ * Converts field values in a form data object based on their type.
28
+ * @param {Array<{ fieldName: string, type: string }>} fields - The list of fields to handle.
29
+ * @param {Object} formData - The form data object to update.
30
+ * @returns {void}
31
+ */
32
+ export const handleFieldTypesBeforeSubmit = (fields, formData) => {
33
+ const updatedFormData = { ...formData };
34
+
35
+ for (const { fieldName, type } of fields) {
36
+ switch (type) {
37
+ case "date":
38
+ updatedFormData[fieldName] = moment(formData[fieldName]).toDate();
39
+ break;
40
+ case "file":
41
+ updatedFormData[fieldName] = JSON.stringify(formData[fieldName]);
42
+ break;
43
+ }
44
+ }
45
+
46
+ return updatedFormData;
47
+ };
package/utils/index.js ADDED
@@ -0,0 +1,32 @@
1
+ import moment from "moment";
2
+ import bytesToSize from "./bytesToSize";
3
+ import { uploadFile } from "trithuc-mvc-react/api";
4
+ export { bytesToSize };
5
+ export * from "./formFields";
6
+ export function fDateTime(date) {
7
+ return moment(date).format("DD/MM/yyyy HH:mm");
8
+ }
9
+
10
+ export function fData(number) {
11
+ // return numeral(number).format("0.0 b");
12
+ return number;
13
+ }
14
+
15
+ export const saveFilesToServer = async (files) => {
16
+ try {
17
+ const formData = new FormData();
18
+ files.forEach((file, i) => {
19
+ formData.append(`file${i}`, file);
20
+ });
21
+
22
+ const data = await uploadFile(formData);
23
+
24
+ return data.map((item) => {
25
+ return JSON.parse(item);
26
+ });
27
+ } catch (error) {
28
+ console.log(error);
29
+ }
30
+ };
31
+
32
+