Skip to content

Commit

Permalink
insert regions in batch
Browse files Browse the repository at this point in the history
  • Loading branch information
Janderson Souza Matias authored and Janderson Souza Matias committed Dec 17, 2023
1 parent b27d822 commit 112a087
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 5 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
"@fontsource/noto-sans": "^5.0.17",
"@hookform/resolvers": "^3.1.1",
"@superset-ui/embedded-sdk": "^0.1.0-alpha.9",
"@types/papaparse": "^5.3.14",
"@types/styled-system": "^5.1.16",
"axios": "^1.3.4",
"chart.js": "^4.4.0",
"date-fns": "^2.30.0",
"framer-motion": "^10.2.3",
"i18next": "^22.4.11",
"papaparse": "^5.4.1",
"qrcode": "^1.5.3",
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
Expand Down
16 changes: 15 additions & 1 deletion src/components/HeaderPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@ type Props = {
newButtonValue?: string | null;
onClickNew?: () => void;
onClickDownload?: () => void;
onClickImport?: () => void;
};

const HeaderPage: React.FC<Props> = ({ title, subtitle, newButtonValue, onClickNew, onClickDownload }) => {
const HeaderPage: React.FC<Props> = ({
title,
subtitle,
newButtonValue,
onClickImport,
onClickNew,
onClickDownload,
}) => {
const { t } = useTranslation();

return (
Expand All @@ -25,6 +33,12 @@ const HeaderPage: React.FC<Props> = ({ title, subtitle, newButtonValue, onClickN
</Text>
</VStack>
<HStack>
{onClickImport && (
<Button variant="solid" colorScheme="blue" onClick={onClickImport} gap="8px">
{t('common.import')}
</Button>
)}

{onClickDownload && (
<Button variant="solid" colorScheme="blue" onClick={onClickDownload} gap="8px">
{t('common.download')}
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/langs/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const enTranslation = {
generateQRCode: 'Generate QRCode',
cancel: 'Cancel',
download: 'Download data',
import: 'Import data',
'items-per-page': 'Items per page',
'all-time': 'All time',
'last-7-days': 'Last 7 days',
Expand Down Expand Up @@ -215,6 +216,7 @@ const enTranslation = {
'new-subregion': 'New district',
'new-sub-subregion': 'New municipality',
edit: 'Edit',
delete: 'Delete',
total_schools: 'Total school: {{ value }}',
placeholder: 'Region name',
0: {
Expand Down
156 changes: 156 additions & 0 deletions src/pages/Schools/SchoolImportModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import SchoolService, { SchoolBatchResponse } from '@/services/school';
import {
Box,
Button,
HStack,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Spinner,
Text,
VStack,
} from '@chakra-ui/react';
import Papa from 'papaparse';
import { useState } from 'react';

type Props = {
isOpen: boolean;
onClose: (reload?: boolean) => void;
};

const SchoolImportModal: React.FC<Props> = ({ isOpen, onClose }) => {
const [header, setHeader] = useState<string[]>([]);
const [arrayData, setArrayData] = useState<string[][]>([]);
const [isLoading, setIsLoading] = useState(false);
const [response, setResponse] = useState<SchoolBatchResponse>();

const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
Papa.parse<string[]>(file, {
header: false,
complete: function (results) {
const [header, ...data] = results.data;

setArrayData(data);
setHeader(header);
},
});
}
};

const startImport = async () => {
setIsLoading(true);
const response = await SchoolService.createSchoolBatch(arrayData);
setResponse(response);
setIsLoading(false);
};

const onCancel = () => {
onClose();
setArrayData([]);
setIsLoading(false);
setResponse(undefined);
};

const onFinish = () => {
onClose();
setArrayData([]);
setIsLoading(false);
setResponse(undefined);
};

return (
<Modal isOpen={isOpen} onClose={onClose} scrollBehavior="inside" size="5xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>Import file</ModalHeader>
<ModalCloseButton />
{isLoading ? (
<ModalBody>
<Spinner />
</ModalBody>
) : response ? (
<ModalBody>
<VStack w="full">
<Text fontSize={28} mb={8}>
{!!response.failItems?.length ? 'Items that failed to import' : 'All items was imported'}
</Text>
<HStack w="full" px={4} borderBottom="1px solid">
<Box fontWeight="bold" w="50px"></Box>
{header.map((_, index) => (
<Box flex={index === 1 ? 2 : 1} fontWeight="black">
{index === 0 ? 'Code' : index === 1 ? 'Name' : 'Region-' + (index - 2)}
</Box>
))}
</HStack>
{response?.failItems?.map((row, index) => (
<HStack w="full" bg={index % 2 ? '#f5f5f5' : '#ffffff'} px={4}>
<Box fontWeight="bold" w="50px">
{index + 1 + '.'}
</Box>
{row.map((item: string, itemIndex) => (
<Box flex={itemIndex === 1 ? 2 : 1}> {item}</Box>
))}
</HStack>
))}
</VStack>
</ModalBody>
) : (
<ModalBody>
{arrayData.length === 0 ? (
<input type="file" accept=".csv" onChange={handleFileUpload} />
) : (
<VStack w="full">
<HStack w="full" px={4} borderBottom="1px solid">
<Box fontWeight="bold" w="50px"></Box>
{header.map((_, index) => (
<Box flex={index === 1 ? 2 : 1} fontWeight="black">
{index === 0 ? 'Code' : index === 1 ? 'Name' : 'Region-' + (index - 2)}
</Box>
))}
</HStack>
{arrayData.map((row, index) => (
<HStack w="full" bg={index % 2 ? '#f5f5f5' : '#ffffff'} px={4}>
<Box fontWeight="bold" w="50px">
{index + 1 + '.'}
</Box>
{row.map((item: string, itemIndex) => (
<Box flex={itemIndex === 1 ? 2 : 1}> {item}</Box>
))}
</HStack>
))}
</VStack>
)}
</ModalBody>
)}

<ModalFooter>
{!!arrayData.length && <Text mr="auto">{'Total items: ' + arrayData.length}</Text>}
{!response && (
<Button variant="ghost" onClick={onCancel} isDisabled={isLoading}>
Cancel
</Button>
)}
{!isLoading && !response && (
<Button colorScheme="blue" mr={3} isDisabled={!arrayData.length} onClick={startImport}>
Start import
</Button>
)}

{!!response && (
<Button colorScheme="blue" mr={3} isDisabled={!arrayData.length} onClick={onFinish}>
Finish
</Button>
)}
</ModalFooter>
</ModalContent>
</Modal>
);
};

export default SchoolImportModal;
11 changes: 11 additions & 0 deletions src/pages/Schools/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import HeaderPage from '@/components/HeaderPage';
import { useTranslation } from 'react-i18next';
import SchoolForm from './SchoolForm';
import handleDownloadJSON from '@/common/download';
import SchoolImportModal from './SchoolImportModal';

const SchoolsPage: React.FC = () => {
const { t } = useTranslation();
Expand All @@ -28,6 +29,7 @@ const SchoolsPage: React.FC = () => {
const [isLoadingDelete, setIsLoadingDelete] = useState(false);
const [schoolToEdit, setSchoolToEdit] = useState<ISchool>();
const [schoolToDelete, setSchoolToDelete] = useState<ISchool>();
const [showImportModal, setShowImportModal] = useState(false);

useEffect(() => {
loadSchools();
Expand Down Expand Up @@ -67,13 +69,19 @@ const SchoolsPage: React.FC = () => {
setSchoolToDelete(undefined);
};

const onCloseImport = (reload?: boolean) => {
setShowImportModal(false);
if (reload) loadSchools();
};

return (
<Box p={4} minH="100vh" flex={1}>
<HeaderPage
subtitle={t('Navbar.data')}
title={t('Navbar.schools')}
newButtonValue={t('school.new-school')}
onClickNew={() => setNewSchool(true)}
onClickImport={() => setShowImportModal(true)}
onClickDownload={() => handleDownloadJSON(schools, t('Navbar.schools').toLowerCase().replace(' ', '-'))}
/>
<SchoolForm
Expand All @@ -89,6 +97,9 @@ const SchoolsPage: React.FC = () => {
) : (
<SchoolList schools={schools} handleEdit={setSchoolToEdit} handleDelete={setSchoolToDelete} />
)}

<SchoolImportModal isOpen={showImportModal} onClose={onCloseImport} />

<Modal isOpen={!!schoolToDelete} onClose={onCloseDeleteModal}>
<ModalOverlay />
<ModalContent>
Expand Down
68 changes: 64 additions & 4 deletions src/pages/Settings/Regions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,21 @@ import Icon from '@/components/Base/Icon';
import Loader from '@/components/Base/Loader';
import Menu from '@/components/Menu';
import { IRegion, IUser } from '@/types';
import { Center, HStack, Text, VStack, useTheme } from '@chakra-ui/react';
import {
Button,
Center,
HStack,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
VStack,
useTheme,
} from '@chakra-ui/react';
import RegionForm from './Form';
import { toast } from 'react-toastify';
import { useTranslation } from 'react-i18next';
Expand All @@ -13,6 +27,7 @@ const Regions = () => {
const theme = useTheme();
const { t } = useTranslation();
const [formIsOpen, setFormIsOpen] = useState(false);
const [deleteIsOpen, setDeleteIsOpen] = useState(false);
const [currentRegion, setCurrentRegion] = useState<IRegion>();
const [regions, setRegions] = useState({
isLoading: true,
Expand Down Expand Up @@ -43,9 +58,9 @@ const Regions = () => {
const menuOptions = [
{
label: t('settings.tabs.region.edit'),
handleClick: (user: IUser) => {
handleClick: (region: IRegion) => {
setFormIsOpen(true);
setCurrentRegion(user);
setCurrentRegion(region);
},
},
];
Expand All @@ -55,6 +70,18 @@ const Regions = () => {
setCurrentRegion(undefined);
};

const deleteRegion = async () => {
if (currentRegion?.id) {
setRegions({
isLoading: true,
data: [] as IRegion[],
});
setDeleteIsOpen(false);
await RegionService.deleteRegion(currentRegion.id);
refreshRegions();
}
};

return (
<>
<RegionForm
Expand Down Expand Up @@ -98,7 +125,23 @@ const Regions = () => {
</VStack>
</HStack>

<Menu items={menuOptions} currentItem={region} />
<Menu
items={[
...menuOptions,
...(!!region?.schoolsCount
? []
: [
{
label: t('settings.tabs.region.delete'),
handleClick: (region: IRegion) => {
setDeleteIsOpen(true);
setCurrentRegion(region);
},
},
]),
]}
currentItem={region}
/>
</HStack>
))}

Expand All @@ -109,6 +152,23 @@ const Regions = () => {
</>
)}
</VStack>

<Modal isOpen={!!deleteIsOpen} onClose={() => setDeleteIsOpen(false)}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Confirm Deletion</ModalHeader>
<ModalCloseButton />
<ModalBody>Are you sure you want to delete this region?</ModalBody>
<ModalFooter>
<Button variant="ghost" mr={3} onClick={() => setDeleteIsOpen(false)}>
Cancel
</Button>
<Button colorScheme="red" onClick={deleteRegion}>
Delete
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
Expand Down
1 change: 1 addition & 0 deletions src/services/region/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import _axios from '..';
import { IRegion } from '../../types';

export const RegionService = {
deleteRegion: async (regionId: string): Promise<IRegion[]> => (await _axios.delete(`region/${regionId}`)).data,
getRegions: async (): Promise<IRegion[]> => (await _axios.get('region')).data,
getRegionsTree: async (): Promise<IRegion[]> => (await _axios.get('region/tree')).data,
getRegion: async (id: string): Promise<IRegion> => (await _axios.get(`region/${id}`)).data,
Expand Down
Loading

0 comments on commit 112a087

Please sign in to comment.