Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ jobs:
- name: Run Linter
run: npm run lint
- name: Run Type Check
run: npm run type-check
run: npm run type-check
53 changes: 24 additions & 29 deletions frontend/src/components/ExportButton/ExportButton.tsx

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exporting doesn't work for me b/c of the aforementioned errors but this code looks good to me !

Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import React, { useState, useRef } from 'react';
import { CSVLink } from 'react-csv';
import { download } from '../../icons/other';
import { Button } from '../FormElements/FormElements';
import styles from './exportButton.module.css';
Expand All @@ -19,30 +17,34 @@ const ExportButton = ({
csvCols,
filename,
}: clickHandler) => {
const [downloadData, setDownloadData] = useState<string>('');
const { showToast } = useToast();
const csvLink = useRef<
CSVLink & HTMLAnchorElement & { link: HTMLAnchorElement }
>(null);

const downloadCSV = () => {
axios
.get(endpoint, {
const downloadCSV = async () => {
try {
// fetch csv string from stats table in backend
const res = await axios.get(endpoint, {
responseType: 'text',
transformResponse: [(data) => data],
})
.then((res) => res.data)
.then((data) => {
if (data === '') {
setDownloadData(csvCols);
} else {
setDownloadData(data);
}
if (csvLink.current) {
csvLink.current.link.click();
}
})
.then(() => showToast(toastMsg, ToastStatus.SUCCESS));
});
const data = res.data || csvCols;

// generate a download link and initiate the download
const blob = new Blob([data], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
Comment thread
anika-4444 marked this conversation as resolved.
link.href = url;
link.download = filename || 'download.csv';
document.body.appendChild(link);
link.click();

//cleanup
document.body.removeChild(link);
URL.revokeObjectURL(url);

showToast(toastMsg, ToastStatus.SUCCESS);
} catch (error) {
showToast('Failed to download CSV', ToastStatus.ERROR);
}
};

return (
Expand All @@ -54,13 +56,6 @@ const ExportButton = ({
>
<img src={download} alt="capacity icon" /> Export
</Button>
<CSVLink
data={downloadData}
filename={filename}
className={styles.hidden}
ref={csvLink}
target="_blank"
/>
</>
);
};
Expand Down
235 changes: 235 additions & 0 deletions frontend/src/components/Modal/StatsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Modal from '@mui/material/Modal';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { useEmployees } from 'context/EmployeesContext';
import moment from 'moment';
import { Driver } from 'types';
import ExportButton from 'components/ExportButton/ExportButton';
import dayjs from 'dayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import styles from '../Modal/modal.module.css';
import { useState } from 'react';
import { useEffect } from 'react';
import { Button } from 'components/FormElements/FormElements';

const theme = createTheme();

type StatsModalProps = {
initStartDate: string;
initEndDate: string;
};

const StatsModal = ({ initStartDate, initEndDate }: StatsModalProps) => {
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const { drivers } = useEmployees();
const [startDate, setStartDate] = useState(initStartDate);
const [endDate, setEndDate] = useState(initEndDate);
const today = moment();

useEffect(() => {
if (open) {
setStartDate(initStartDate);
setEndDate(initEndDate);
}
}, [initStartDate, initEndDate, open]);

//Creates csv column string
const generateCols = () => {
const cols =
'Date,Daily Total,Daily Ride Count,Day No Shows,Day Cancels,Night Ride Count, Night No Shows, Night Cancels';
const finalCols = drivers.reduce(
(acc: string, curr: Driver) =>
`${acc},${curr.firstName} ${curr.lastName.substring(0, 1)}.`,
cols
);
return finalCols;
};

//logic for handling when start and end dates are for shortcut buttons
const handleShortcut = (shortcut: string) => {
const today = dayjs();
if (shortcut === 'this week') {
const newStartDate = today.startOf('week').format('YYYY-MM-DD'); //beginning of week to today
const newEndDate = today.format('YYYY-MM-DD');
return { newStartDate, newEndDate };
} else if (shortcut === 'last week') {
const prevWeek = today.subtract(7, 'day'); //seven days ago to today
const newStartDate = prevWeek.startOf('week').format('YYYY-MM-DD');
const newEndDate = prevWeek.endOf('week').format('YYYY-MM-DD');
return { newStartDate, newEndDate };
} else if (shortcut === 'last 7') {
const newStartDate = today.subtract(7, 'day').format('YYYY-MM-DD'); //beginning of last week to end of last week
const newEndDate = today.format('YYYY-MM-DD');
return { newStartDate, newEndDate };
} else if (shortcut === 'curr month') {
const newStartDate = today.startOf('month').format('YYYY-MM-DD'); //beginning of month to today
const newEndDate = today.format('YYYY-MM-DD');
return { newStartDate, newEndDate };
} else if (shortcut === 'curr year') {
const newStartDate = today.startOf('year').format('YYYY-MM-DD'); //beginning of year to today
const newEndDate = today.format('YYYY-MM-DD');
return { newStartDate, newEndDate };
}
};

return (
<ThemeProvider theme={theme}>
<div style={{ alignContent: 'space-between' }}>
<Button onClick={handleOpen}>Export Data</Button>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
slotProps={{
backdrop: {
className: styles.background,
},
}}
>
<Box className={styles.modal}>
<h3 className={styles.title} id="modal-modal-title">
Export Statistics
</h3>
<div className={styles.colBlock}>
<div className={styles.rowBlock}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to rethink how you handle the modal here. Using it on the website, I was frustrated that the date range I had set for the main analytics view didn't carry over to the default values on this modal. Maybe it could be a prop for the export button?

label="Start Date"
value={dayjs(startDate)}
onChange={(newValue) => {
if (newValue) {
setStartDate(newValue.format('YYYY-MM-DD'));
}
}}
maxDate={dayjs(today.format('YYYY-MM-DD'))} //data validation
/>

<DatePicker
label="End Date"
value={dayjs(endDate)}
onChange={(newValue) => {
if (newValue) {
setEndDate(newValue.format('YYYY-MM-DD'));
}
}}
minDate={dayjs(startDate)} //data validation
maxDate={dayjs(today.format('YYYY-MM-DD'))}
/>
</LocalizationProvider>
</div>
</div>

<div className={styles.btnList}>
<button
className={styles.btnListItem}
onClick={(event) => {
const newStartDate =
handleShortcut('this week')?.newStartDate ??
today.format('YYYY-MM-DD');
const newEndDate =
handleShortcut('this week')?.newEndDate ??
today.format('YYYY-MM-DD');
setStartDate(newStartDate);
setEndDate(newEndDate);
}}
>
This Week
</button>

<button
className={styles.btnListItem}
onClick={(event) => {
const newStartDate =
handleShortcut('last week')?.newStartDate ??
today.format('YYYY-MM-DD');
const newEndDate =
handleShortcut('last week')?.newEndDate ??
today.format('YYYY-MM-DD');
setStartDate(newStartDate);
setEndDate(newEndDate);
}}
>
Last Week{' '}
</button>

<button
className={styles.btnListItem}
onClick={(event) => {
const newStartDate =
handleShortcut('last 7')?.newStartDate ??
today.format('YYYY-MM-DD');
const newEndDate =
handleShortcut('last 7')?.newEndDate ??
today.format('YYYY-MM-DD');
setStartDate(newStartDate);
setEndDate(newEndDate);
}}
>
Last 7 Days
</button>
<button
className={styles.btnListItem}
onClick={(event) => {
const newStartDate =
handleShortcut('curr month')?.newStartDate ??
today.format('YYYY-MM-DD');
const newEndDate =
handleShortcut('curr month')?.newEndDate ??
today.format('YYYY-MM-DD');
setStartDate(newStartDate);
setEndDate(newEndDate);
}}
>
Current Month
</button>
<button
className={styles.btnListItem}
onClick={(event) => {
const newStartDate =
handleShortcut('curr year')?.newStartDate ??
today.format('YYYY-MM-DD');
const newEndDate =
handleShortcut('curr year')?.newEndDate ??
today.format('YYYY-MM-DD');
setStartDate(newStartDate);
setEndDate(newEndDate);
}}
>
Year to Date
</button>
</div>

<div
className={styles.buttonContainer}
style={{ marginTop: '10px' }}
>
<button
type="button"
className={styles.closeBtn}
onClick={handleClose}
>
Cancel
</button>
<div className={styles.submit}>
<ExportButton
toastMsg={`${startDate} to ${endDate} data has been downloaded.`}
endpoint={`/api/stats/download?from=${startDate}&to=${endDate}`}
csvCols={generateCols()}
filename={`${startDate}_${endDate}_analytics.csv`}
/>
</div>
</div>
</Box>
</Modal>
</div>
</ThemeProvider>
);
};

export default StatsModal;
39 changes: 39 additions & 0 deletions frontend/src/components/Modal/modal.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,45 @@
margin-bottom: 1.5rem;
}

.colBlock {
display: flex;
flex-direction: column;
justify-content: space-between;
flex-wrap: wrap;
align-content: center;
gap: 15px;
margin-top: 15px;
}

.rowBlock {
display: flex;
flex-direction: row;
justify-content: space-evenly;
gap: 15px;
}

.btnList {
display: grid;
grid-template-columns: auto auto auto;
gap: 10px;
padding: 5px;
margin-top: 15px;
}

.btnListItem {
padding: 8px 16px;
background-color: #d4d4d4;
border-radius: 25px;
text-align: center;
font-size: 14px;
min-height: auto;
border: #d4d4d4;
}

.btnListItem:focus {
box-shadow: 0 0 0 3px #0075db;
}

/* Common input styles */
.inputContainer input[type='text'],
.inputContainer input[type='tel'],
Expand Down
Loading
Loading