mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 09:23:51 +00:00
3128f5a195
* refactor: add typescript dependencies * chore: update chakra-ui * refactor: typescript config * refactor: convert <MenuBar> component to typescript * refactor: convert <TooltipActionBtn> component to typescript * refactor: improve UX in file upload * refactor: upgrade dependencies * refactor: prepare data provider * refactor: prevent importing bad fields * refactor: extract merge to provider * refactor(upload): parser merges only given fields * refactor(upload): add event fields to excel * refactor(upload): improve styling on modal open * style: improve styling in menu * style: prevent global pollution * feat(upload): add upload options * fix: avoid potential bug in log queue
138 lines
4.2 KiB
TypeScript
138 lines
4.2 KiB
TypeScript
import { ChangeEvent, useCallback, useContext, useRef, useState } from 'react';
|
|
import { Button } from '@chakra-ui/button';
|
|
import {
|
|
Checkbox,
|
|
FormControl,
|
|
FormErrorMessage,
|
|
FormHelperText,
|
|
FormLabel,
|
|
Input,
|
|
Modal,
|
|
ModalBody,
|
|
ModalCloseButton,
|
|
ModalContent,
|
|
ModalFooter,
|
|
ModalHeader,
|
|
ModalOverlay,
|
|
Progress,
|
|
} from '@chakra-ui/react';
|
|
import { IoCloseSharp } from '@react-icons/all-files/io5/IoCloseSharp';
|
|
import { useQueryClient } from '@tanstack/react-query';
|
|
|
|
import { EVENTS_TABLE } from '../../api/apiConstants';
|
|
import { uploadEvents } from '../../api/ontimeApi';
|
|
import { LoggingContext } from '../../context/LoggingContext';
|
|
import TooltipActionBtn from '../buttons/TooltipActionBtn';
|
|
|
|
import { validateFile } from './utils';
|
|
|
|
import style from './UploadModal.module.scss';
|
|
|
|
interface UploadModalProps {
|
|
onClose: () => void;
|
|
isOpen: boolean;
|
|
}
|
|
|
|
export default function UploadModal({ onClose, isOpen }: UploadModalProps) {
|
|
const queryClient = useQueryClient();
|
|
const { emitError } = useContext(LoggingContext);
|
|
const [errors, setErrors] = useState<string[]>([]);
|
|
const [file, setFile] = useState<File | null>(null);
|
|
const [progress, setProgress] = useState(0);
|
|
const overrideOptionRef = useRef<HTMLInputElement>(null);
|
|
|
|
const handleFile = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
|
const fileUploaded = event?.target?.files?.[0];
|
|
if (!fileUploaded) return;
|
|
|
|
const validate = validateFile(fileUploaded);
|
|
setErrors(validate.errors);
|
|
|
|
if (validate.isValid) {
|
|
setFile(fileUploaded);
|
|
} else {
|
|
setFile(null);
|
|
}
|
|
}, []);
|
|
|
|
const handleUpload = useCallback(async () => {
|
|
if (file) {
|
|
try {
|
|
await uploadEvents(file, setProgress, { onlyEvents: overrideOptionRef?.current?.checked });
|
|
} catch (error) {
|
|
emitError(`Failed uploading file: ${error}`);
|
|
} finally {
|
|
await queryClient.invalidateQueries(EVENTS_TABLE);
|
|
setFile(null);
|
|
}
|
|
}
|
|
}, [emitError, file, queryClient]);
|
|
|
|
return (
|
|
<Modal
|
|
onClose={onClose}
|
|
isOpen={isOpen}
|
|
closeOnOverlayClick={false}
|
|
motionPreset='slideInBottom'
|
|
size='xl'
|
|
scrollBehavior='inside'
|
|
>
|
|
<ModalOverlay />
|
|
<ModalContent>
|
|
<ModalHeader>File upload</ModalHeader>
|
|
<ModalCloseButton />
|
|
<ModalBody className={style.modalBody}>
|
|
<FormControl isInvalid={errors.length > 0}>
|
|
<FormLabel>Select file to upload</FormLabel>
|
|
<Input type='file' onChange={handleFile} accept='.json, .xlsx' />
|
|
{errors.length === 0 ? (
|
|
<FormHelperText>.XLSX .JSON with max 1MB</FormHelperText>
|
|
) : (
|
|
<FormErrorMessage className={style.flexColumnLeft}>
|
|
{errors.map((error) => (
|
|
<span key={error}>{error}</span>
|
|
))}
|
|
</FormErrorMessage>
|
|
)}
|
|
</FormControl>
|
|
<div className={style.options}>
|
|
<b>Options</b>
|
|
<Checkbox ref={overrideOptionRef}>Import only events</Checkbox>
|
|
<span className={style.notes}>This will prevent overriding user settings</span>
|
|
</div>
|
|
{file && (
|
|
<div className={style.info}>
|
|
<span>File ready to upload</span>
|
|
<TooltipActionBtn
|
|
clickHandler={() => setFile(null)}
|
|
tooltip='Cancel'
|
|
aria-label='Cancel'
|
|
className={style.corner}
|
|
size='sm'
|
|
variant='ghosted'
|
|
icon={<IoCloseSharp />}
|
|
/>
|
|
<ul className={style.infoList}>
|
|
<li>{file.name}</li>
|
|
<li>{`${(file.size / 1024).toFixed(2)}kb`}</li>
|
|
<li>{file.type}</li>
|
|
</ul>
|
|
</div>
|
|
)}
|
|
<Progress value={progress} />
|
|
</ModalBody>
|
|
<ModalFooter>
|
|
<Button
|
|
colorScheme='blue'
|
|
disabled={!file || errors.length > 0}
|
|
onClick={handleUpload}
|
|
isLoading={progress < 0 && progress >= 100}
|
|
>
|
|
Upload
|
|
</Button>
|
|
</ModalFooter>
|
|
</ModalContent>
|
|
</Modal>
|
|
);
|
|
}
|