ui for files manipulation
This commit is contained in:
parent
318f6f9d8f
commit
f24c394388
100
src/pages/files/add.jsx
Normal file
100
src/pages/files/add.jsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { Group, Modal, Text, rem, Button, List } from '@mantine/core';
|
||||||
|
import { IconUpload, IconPhoto, IconX } from '@tabler/icons-react';
|
||||||
|
import { Dropzone, IMAGE_MIME_TYPE, MIME_TYPES } from '@mantine/dropzone';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import API from '../../services/api';
|
||||||
|
import setNotification from '../errors/error-notification';
|
||||||
|
|
||||||
|
const ModalAddFile = ({ opened, handler, addFiles }) => {
|
||||||
|
const [files, setFiles] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const validate = (object) => {
|
||||||
|
setIsLoading(false);
|
||||||
|
setFiles([]);
|
||||||
|
addFiles(object);
|
||||||
|
handler();
|
||||||
|
};
|
||||||
|
|
||||||
|
const addFilesToList = (files) => {
|
||||||
|
files.forEach((file) => {
|
||||||
|
setFiles((prev) => [...prev, file]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
const formData = new FormData();
|
||||||
|
files.forEach((file) => formData.append('file', file));
|
||||||
|
API.upload(formData)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
validate(files);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setNotification(true, err.message);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal.Root opened={opened} onClose={() => validate([])}>
|
||||||
|
<Modal.Overlay />
|
||||||
|
<Modal.Content>
|
||||||
|
<Modal.Header>
|
||||||
|
<Modal.Title>
|
||||||
|
<Text fw={700} fz="lg">
|
||||||
|
Add File
|
||||||
|
</Text>
|
||||||
|
</Modal.Title>
|
||||||
|
<Modal.CloseButton />
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => addFilesToList(files)}
|
||||||
|
onReject={(files) => setNotification(true, `Rejected files: ${files.map((file) => file.name)}`)}
|
||||||
|
maxSize={1024 ** 3} // 1 GB
|
||||||
|
accept={[IMAGE_MIME_TYPE, MIME_TYPES.mp4]}
|
||||||
|
>
|
||||||
|
<Group position="center" spacing="xl" style={{ minHeight: rem(220), pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size="3.2rem" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size="3.2rem" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size="3.2rem" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag images here or click to select files
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" color="dimmed" inline mt={7}>
|
||||||
|
Attach as many files as you like, each file should not exceed 1 GB
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
<List>
|
||||||
|
{files.map((file, index) => (
|
||||||
|
<List.Item key={index}>{file.name}</List.Item>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button variant="light" color="red" onClick={() => validate([])}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="light" color="green" loading={isLoading} onClick={handleSubmit}>
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalAddFile;
|
130
src/pages/files/file-selector.jsx
Normal file
130
src/pages/files/file-selector.jsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { Modal, Button, Text, Group, Grid, TextInput, ScrollArea } from '@mantine/core';
|
||||||
|
import { IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import ModalAddFile from './add';
|
||||||
|
import SelectorItem from '../../components/select-item';
|
||||||
|
import API from '../../services/api';
|
||||||
|
import setNotification from '../errors/error-notification';
|
||||||
|
|
||||||
|
const ModalFileSelector = ({ opened, handleClose, handleSubmit, ...props }) => {
|
||||||
|
const [files, setFiles] = useState([]);
|
||||||
|
const [showAdd, setShowAdd] = useState(false);
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
let resfiless = [];
|
||||||
|
|
||||||
|
const toggleShowAdd = () => setShowAdd(!showAdd);
|
||||||
|
|
||||||
|
const clickHandler = (files) => {
|
||||||
|
if (props.multi) {
|
||||||
|
// eslint-disable-next-line eqeqeq
|
||||||
|
const indexfiles = resfiless.findIndex((item) => item._id == files._id);
|
||||||
|
if (indexfiles === -1) {
|
||||||
|
resfiless.push(files);
|
||||||
|
} else {
|
||||||
|
resfiless.splice(indexfiles, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleSubmitLocal(files);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitLocal = (files) => {
|
||||||
|
handleSubmit(props.multi ? resfiless : files);
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
API.getFiles()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
setFiles(res.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setNotification(true, err.response.data.error);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (search.length >= 2) {
|
||||||
|
API.searchfiles(search)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
setFiles(res.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setNotification(true, err.response.data.error);
|
||||||
|
});
|
||||||
|
} else if (search.length === 0) {
|
||||||
|
API.getFiles()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
setFiles(res.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setNotification(true, err.response.data.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [search]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal.Root opened={opened} onClose={handleClose} size="lg">
|
||||||
|
<Modal.Overlay />
|
||||||
|
<Modal.Content>
|
||||||
|
<Modal.Header>
|
||||||
|
<Modal.Title>
|
||||||
|
<Text fw={700} fz="lg">
|
||||||
|
Select file{props.multi ? 's' : ''}
|
||||||
|
</Text>
|
||||||
|
</Modal.Title>
|
||||||
|
<Modal.CloseButton />
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Type to Search"
|
||||||
|
mb="sm"
|
||||||
|
radius="md"
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
icon={<IconSearch size="1rem" stroke={1.5} />}
|
||||||
|
/>
|
||||||
|
<ScrollArea h={430} offsetScrollbars>
|
||||||
|
<Grid columns={3}>
|
||||||
|
{files.map((file) => (
|
||||||
|
<SelectorItem file={file} clickHandler={clickHandler} key={file.id} />
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</ScrollArea>
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
{props.multi && (
|
||||||
|
<>
|
||||||
|
<Button variant="light" color="gray" onClick={() => setShowAdd(true)}>
|
||||||
|
Add file(s)
|
||||||
|
</Button>
|
||||||
|
<Button variant="light" color="red" onClick={handleClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" variant="light" color="green" onClick={() => handleSubmitLocal()}>
|
||||||
|
Validate
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
{showAdd && (
|
||||||
|
<ModalAddFile
|
||||||
|
opened={showAdd}
|
||||||
|
handler={toggleShowAdd}
|
||||||
|
addFiles={(files) => setFiles((prev) => [...prev, files])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalFileSelector;
|
37
src/pages/files/file-view.jsx
Normal file
37
src/pages/files/file-view.jsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Card, Divider, Text, Title, Image, Badge, Button, Group } from '@mantine/core';
|
||||||
|
|
||||||
|
import API from '../../services/api';
|
||||||
|
|
||||||
|
const FileView = ({ file, onSelect, onDelete, ...props }) => {
|
||||||
|
const deleteHandler = async () => {
|
||||||
|
try {
|
||||||
|
await API.deleteFile(file.id);
|
||||||
|
onDelete(file.id);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card shadow="sm" padding="md" withBorder>
|
||||||
|
<Card.Section>
|
||||||
|
<Image src={'/api/file/' + file?.id ?? ''} alt={file?.name ?? ''} withPlaceholder fit="contain" />
|
||||||
|
</Card.Section>
|
||||||
|
<Text>{file?.name ?? 'File Name'}</Text>
|
||||||
|
<Group position="center" grow>
|
||||||
|
{!props.noSelect ? (
|
||||||
|
<Button color="green" mt="sm" variant="light" onClick={() => onSelect(file?.id)}>
|
||||||
|
Select
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
<Button color="red" mt="sm" variant="light">
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileView;
|
67
src/pages/files/index.jsx
Normal file
67
src/pages/files/index.jsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Button, Paper, Grid, Text, Title, Group, List, Image, ScrollArea, Center } from '@mantine/core';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import API from '../../services/api';
|
||||||
|
import setNotification from '../errors/error-notification';
|
||||||
|
import ModalAddFile from './add';
|
||||||
|
import FileView from '../files/file-view';
|
||||||
|
|
||||||
|
const Files = () => {
|
||||||
|
const [showAddFile, setShowAddFile] = useState(false);
|
||||||
|
const [files, setFiles] = useState([]);
|
||||||
|
const toggleShowAddFile = () => setShowAddFile(!showAddFile);
|
||||||
|
|
||||||
|
const handleAddFiles = (files) => {
|
||||||
|
console.log(files);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
API.getFiles()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
setFiles(res.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setNotification(true, err.response.data.error);
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Group position="apart" mt="md">
|
||||||
|
<Title m="md" order={2}>
|
||||||
|
Files
|
||||||
|
</Title>
|
||||||
|
<Button variant="light" mt="sm" onClick={toggleShowAddFile}>
|
||||||
|
Add file
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper p="xs" radius="sm" shadow="sm" withBorder my="md">
|
||||||
|
<ScrollArea.Autosize mah={700} offsetScrollbars>
|
||||||
|
<Title order={3}>Files</Title>
|
||||||
|
<Grid columns={12}>
|
||||||
|
{files.map((file) => (
|
||||||
|
<Grid.Col xl={2} lg={3} md={3} sm={4} xs={4} key={file.id}>
|
||||||
|
<FileView key={file.id} file={file} noSelect />
|
||||||
|
</Grid.Col>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</ScrollArea.Autosize>
|
||||||
|
<Center>
|
||||||
|
<Button onClick={() => {}} mt="md">
|
||||||
|
Load More
|
||||||
|
</Button>
|
||||||
|
</Center>
|
||||||
|
</Paper>
|
||||||
|
<ModalAddFile
|
||||||
|
opened={showAddFile}
|
||||||
|
handler={toggleShowAddFile}
|
||||||
|
addFiles={(files) => handleAddFiles(files)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Files;
|
Loading…
Reference in New Issue
Block a user