Skip to content

Commit

Permalink
feat: add import events through excel
Browse files Browse the repository at this point in the history
  • Loading branch information
D3nnis38 committed Aug 14, 2024
1 parent 327f95e commit fc6b500
Show file tree
Hide file tree
Showing 18 changed files with 334 additions and 4 deletions.
7 changes: 7 additions & 0 deletions src/api/event/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ export const exportEvents = (filter?: string) => client.get<Blob>(
responseType: 'blob',
},
);
export const importEvents = (file: File, password: string) => {
const formData = new FormData();
formData.append('file', file);
formData.append('password', password);
return client.post('/zones/events/import', formData);
};
export const postImportEventsPassword = (password: string) => client.post('/zones/events/import/password', { password });
Binary file added src/assets/Template.xlsx
Binary file not shown.
9 changes: 7 additions & 2 deletions src/components/Link/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import * as Styled from './styled';
type LinkProps = {
label: string;
href: string;
download?: string | null;
};

export const Link: FC<LinkProps> = ({ label, href }) => (
<Styled.Link href={href}>{label}</Styled.Link>
export const Link: FC<LinkProps> = ({ label, href, download }) => (
<Styled.Link href={href} download={download}>{label}</Styled.Link>
);

Link.defaultProps = {
download: null,
};
1 change: 1 addition & 0 deletions src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const Menu = () => {
<Styled.Text onClick={() => navigate('/account')}>Konto</Styled.Text>
<Styled.Text onClick={() => navigate('/account/zones')}>Hantera zoner</Styled.Text>
<Styled.Text onClick={() => navigate('/account/zones/create')}>Lägg till zoner</Styled.Text>
<Styled.Text onClick={() => navigate('/account/events/import')}>Importera data</Styled.Text>
</Styled.SectionTwo>
<Styled.SectionThree>
<Styled.Text onClick={() => logOutAndRedirect()}>Logga ut</Styled.Text>
Expand Down
9 changes: 9 additions & 0 deletions src/components/SideBar/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ export const SideBar = () => {
>
Lägg till zoner
</Button>
<Button
onClick={handleNavigate('/account/events/import')}
buttonSize={ButtonSize.SMALL}
secondary={activePath === '/account/events/import'}
tertiary={activePath !== '/account/events/import'}
type="button"
>
Importera data
</Button>
</Styled.SideBarContainer>
);
};
8 changes: 7 additions & 1 deletion src/hooks/useEventApi.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { getEvents, exportEvents, getGroupedEvents } from 'api/event';
import {
getEvents, exportEvents, getGroupedEvents, importEvents, postImportEventsPassword,
} from 'api/event';

export const useEventApi = () => {
const getAllEvents = (filter?: string) => getEvents(filter);
const exportAllEvents = (filter?: string) => exportEvents(filter);
const getAllGroupedEvents = (filter?: string) => getGroupedEvents(filter);
const importEventsPassword = (password: string) => postImportEventsPassword(password);
const importEventsByExcel = (file: File, password: string) => importEvents(file, password);

return {
getAllEvents,
exportAllEvents,
getAllGroupedEvents,
importEventsPassword,
importEventsByExcel,
};
};
2 changes: 2 additions & 0 deletions src/modules/Account/AccountRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ const AccountSettings = lazy(() => import('./AccountSettings'));
const ZonesSettings = lazy(() => import('./ZonesSettings'));
const CreateZones = lazy(() => import('./CreateZones'));
const EditZone = lazy(() => import('./EditZone'));
const ImportEvents = lazy(() => import('./ImportEvents'));

export const AccountRouter = () => (
<DeliveryLayout>
<Routes>
<Route path="/zones/create" element={<CreateZones />} />
<Route path="/zones/:id/edit" element={<EditZone />} />
<Route path="/zones" element={<ZonesSettings />} />
<Route path="/events/import" element={<ImportEvents />} />
<Route path="/" element={<AccountSettings />} />
</Routes>
</DeliveryLayout>
Expand Down
30 changes: 30 additions & 0 deletions src/modules/Account/ImportEvents/ImportEvents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useEffect } from 'react';
import { useAuth } from 'hooks/useAuth';
import { SideBar } from 'components';
import { ImportEventsForm } from './components';
import * as Styled from './styled';

export const ImportEvents = () => {
const { hasToken } = useAuth();
const isAuthenticated = hasToken();

useEffect(() => {
if (!isAuthenticated) {
window.location.href = '/auth/login';
}
}, [isAuthenticated]);

if (!isAuthenticated) {
return null;
}

return (
<Styled.ContentContainer>
<SideBar />
<Styled.FormContainer>
<Styled.Header>Importera data</Styled.Header>
<ImportEventsForm />
</Styled.FormContainer>
</Styled.ContentContainer>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* eslint-disable react/jsx-props-no-spreading */
import { useDropzone } from 'react-dropzone';
import {
Input, Button,
Link,
} from 'components';
import * as Styled from './styled';
import { useImportEventsForm } from './hooks';

export const ImportEventsForm = () => {
const {
onDrop,
passwordIsCorrect,
setPassword,
password,
submitFileForm,
submitPasswordForm,
apiErrorText,
isLoading,
} = useImportEventsForm();

const {
getRootProps,
getInputProps,
isFocused,
isDragAccept,
isDragReject,
isDragActive,
} = useDropzone({
onDrop,
accept: {
'application/json': ['.xlsx'],
},
maxFiles: 1,
});

return (
<Styled.ContentContainer>
{passwordIsCorrect ? (
<form onSubmit={submitFileForm}>
<Styled.DropArea {...getRootProps({ isFocused, isDragAccept, isDragReject })}>
<input {...getInputProps()} />
{
isDragActive
? <p>Ladda upp zoner...</p>
: <p>Dra zoner eller klicka för att ladda upp</p>
}
</Styled.DropArea>
<Link href="/src/assets/Template.xlsx" download="mall.xlsx" label="Ladda ner mall" />
<Styled.ButtonContainer>
<Button onClick={submitFileForm} type="button" disabled={isLoading}>Importera</Button>
</Styled.ButtonContainer>
</form>
) : (
<form>
<Input
label="Lösenord"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Lösenord"
/>
<Styled.ButtonContainer>
<Button onClick={submitPasswordForm} type="button" disabled={isLoading}>Verifiera</Button>
</Styled.ButtonContainer>
</form>
)}
{apiErrorText && <Styled.ErrorText>{apiErrorText}</Styled.ErrorText>}
</Styled.ContentContainer>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useImportEventsForm';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useImportEventsForm';
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { AxiosError } from 'axios';
import { useState, useCallback } from 'react';
import { useEventApi } from 'hooks/useEventApi';

export const useImportEventsForm = () => {
const [isLoading, setIsLoading] = useState(false);
const [importFile, setImportFile] = useState<File | null>(null);
const [password, setPassword] = useState<string>('');
const [apiErrorText, setApiErrorText] = useState<string>('');
const [passwordIsCorrect, setPasswordIsCorrect] = useState<boolean>(false);

const { importEventsPassword, importEventsByExcel } = useEventApi();

const onDrop = useCallback((acceptedFiles: any) => {
const file = acceptedFiles[0];
setImportFile(file);
}, []);

const submitFileForm = async () => {
setIsLoading(true);
try {
await importEventsByExcel(importFile as File, password);
setApiErrorText('');
} catch (error: AxiosError | any) {
if (error) {
setApiErrorText(error.response.statusText);
}
}
setIsLoading(false);
};

const submitPasswordForm = async () => {
setIsLoading(true);
try {
await importEventsPassword(password);
setPasswordIsCorrect(true);
setApiErrorText('');
} catch (error: AxiosError | any) {
if (error) {
if (error.response.status === 401) {
setApiErrorText(
'Felaktigt lösenord',
);
} else {
setApiErrorText(error.response.statusText);
}
}
}
setIsLoading(false);
};

return {
isLoading,
onDrop,
submitFileForm,
submitPasswordForm,
apiErrorText,
setPassword,
passwordIsCorrect,
password,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ImportEventsForm';
104 changes: 104 additions & 0 deletions src/modules/Account/ImportEvents/components/ImportEventsForm/styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import styled from 'styled-components';

const getColor = (props: any) => {
if (props.isDragAccept) {
return '#00e676';
}
if (props.isDragReject) {
return '#ff1744';
}
if (props.isFocused) {
return '#2196f3';
}
return '#eeeeee';
};

export const ContentContainer = styled.div`
margin-top: var(--spacing-xxs);
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
width: 100%;
gap: var(--spacing-xs);
`;

export const DropArea = styled.div`
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
border-width: 2px;
border-radius: 2px;
border-color: ${(props) => getColor(props)};
border-style: dashed;
background-color: #fafafa;
color: #bdbdbd;
outline: none;
transition: border .24s ease-in-out;
`;

export const SplitContainer = styled.div`
display: flex;
flex-direction: row;
gap: var(--spacing-xs);
width: 100%;
margin-bottom: var(--spacing-xxs);
border-bottom: 1px solid var(--color-gray-12);
padding-bottom: var(--spacing-xs);
`;

export const MapContainer = styled.div`
width: 45%;
height: 375px;
`;

export const InputContainer = styled.div`
width: 55%;
display: flex;
flex-direction: column;
gap: var(--spacing-xxxs);
`;

export const ButtonContainer = styled.div`
display: flex;
justify-content: flex-end;
margin-top: var(--spacing-xs);
gap: var(--spacing-xs);
`;

export const ErrorText = styled.p`
font-size: var(--font-size-body-xs);
color: var(--color-red-1);
line-height: var(--line-height-xxs);
font-weight: var(--font-weight-500);
margin-top: 10px;
`;

export const List = styled.ul`
list-style: none;
margin-bottom: var(--spacing-xxs);
width: 321px;
@media (max-width: 768px) {
width: 238px;
}
position: absolute;
background-color: var(--color-white);
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
padding: var(--spacing-xxs);
padding-bottom: 0;
border-radius: var(--border-radius-sm);
`;

export const ListItem = styled.li`
border-bottom: 1px solid var(--color-gray-12);
cursor: pointer;
margin-bottom: var(--spacing-xxs);
&:hover {
color: var(--color-gray-2);
transition: color 0.2s ease-in-out;
}
&:last-child {
border-bottom: none;
}
`;
1 change: 1 addition & 0 deletions src/modules/Account/ImportEvents/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ImportEventsForm';
3 changes: 3 additions & 0 deletions src/modules/Account/ImportEvents/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ImportEvents } from './ImportEvents';

export default ImportEvents;
22 changes: 22 additions & 0 deletions src/modules/Account/ImportEvents/styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import styled from 'styled-components';

export const ContentContainer = styled.div`
padding: 36px 32px;
padding-bottom: 64px;
`;

export const FormContainer = styled.div`
width: 600px;
margin: 24px auto 32px;
@media (max-width: 768px) {
width: 450px;
}
`;

export const Header = styled.h1`
font-size: var(--font-size-heading-xs);
font-weight: var(--font-weight-800);
line-height: var(--line-height-xxxl);
margin-bottom: var(--spacing-xxs);
font-family: var(--font-family);
`;
Loading

0 comments on commit fc6b500

Please sign in to comment.