-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(trackersTabs): display trackers by 3 categories
- Loading branch information
Showing
10 changed files
with
248 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import * as React from 'react'; | ||
import Box from '@mui/material/Box'; | ||
|
||
interface TabPanelProps { | ||
children?: React.ReactNode; | ||
index: number; | ||
value: number; | ||
} | ||
|
||
function TabPanel(props: TabPanelProps) { | ||
const { children, value, index, ...other } = props; | ||
|
||
return ( | ||
<div | ||
role="tabpanel" | ||
hidden={value !== index} | ||
id={`simple-tabpanel-${index}`} | ||
aria-labelledby={`simple-tab-${index}`} | ||
{...other}> | ||
{value === index && <Box>{children}</Box>} | ||
</div> | ||
); | ||
} | ||
|
||
export default TabPanel; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { Box, IconButton, Typography } from '@mui/material'; | ||
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; | ||
import ChevronRightIcon from '@mui/icons-material/ChevronRight'; | ||
|
||
import formatDate from '../../utils/formatDate'; | ||
|
||
const DateSelector = () => { | ||
return ( | ||
<Box sx={{ alignItems: 'center', display: 'flex', justifyContent: 'center', width: '100%' }}> | ||
<IconButton> | ||
<ChevronLeftIcon color="primary" fontSize="large" /> | ||
</IconButton> | ||
<Typography variant="h5">{formatDate(new Date())}</Typography> | ||
<IconButton> | ||
<ChevronRightIcon color="primary" fontSize="large" /> | ||
</IconButton> | ||
</Box> | ||
); | ||
}; | ||
|
||
export default DateSelector; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,21 @@ | ||
import { Box, CircularProgress, IconButton, Typography } from '@mui/material'; | ||
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; | ||
import ChevronRightIcon from '@mui/icons-material/ChevronRight'; | ||
|
||
import { useAppSelector } from '../../app/hooks'; | ||
import formatDate from '../../utils/formatDate'; | ||
import SliceStatus from '../../models/SliceStatus'; | ||
import selectTrackers from '../../store/trackers/trackers.selectors'; | ||
import { FC } from 'react'; | ||
import TrackerCard from '../TrackerCard/TrackerCard'; | ||
import AddTrackerCard from './AddTrackerCard'; | ||
import Tracker from '../../models/Tracker'; | ||
import { CardProps } from '@mui/material'; | ||
|
||
function TrackerList() { | ||
const { status, trackers } = useAppSelector(selectTrackers); | ||
const cardSxProp = { mt: 2, bgcolor: 'secondary.main' }; | ||
interface Props { | ||
trackers: Tracker[]; | ||
cardProps?: CardProps; | ||
} | ||
|
||
const TrackerList: FC<Props> = ({ trackers, cardProps }) => { | ||
return ( | ||
<Box> | ||
{status === SliceStatus.loading && <CircularProgress />} | ||
|
||
<Box sx={{ alignItems: 'center', display: 'flex', justifyContent: 'center', width: '100%' }}> | ||
<IconButton> | ||
<ChevronLeftIcon color="primary" fontSize="large" /> | ||
</IconButton> | ||
<Typography variant="h5">{formatDate(new Date())}</Typography> | ||
<IconButton> | ||
<ChevronRightIcon color="primary" fontSize="large" /> | ||
</IconButton> | ||
</Box> | ||
|
||
{((trackers && trackers.length === 0) || !trackers) && ( | ||
<Typography align="center">{"Vous n'avez pas encore de trackers."}</Typography> | ||
)} | ||
|
||
<AddTrackerCard cardProps={{ sx: cardSxProp }} /> | ||
|
||
{trackers && trackers.length > 0 && ( | ||
<> | ||
{trackers.map((t) => ( | ||
<TrackerCard tracker={t} key={t.id} cardProps={{ sx: cardSxProp }} /> | ||
))} | ||
</> | ||
)} | ||
</Box> | ||
<> | ||
{trackers.map((t) => ( | ||
<TrackerCard tracker={t} key={t.id} cardProps={cardProps} /> | ||
))} | ||
</> | ||
); | ||
} | ||
}; | ||
|
||
export default TrackerList; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
enum TrackerStatus { | ||
active, | ||
archived, | ||
done, | ||
over | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,75 @@ | ||
import { useState } from 'react'; | ||
import { Alert, Box, Tab, Tabs, Typography } from '@mui/material'; | ||
import BallotIcon from '@mui/icons-material/Ballot'; | ||
import CheckIcon from '@mui/icons-material/Check'; | ||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; | ||
|
||
import { useAppSelector } from '../app/hooks'; | ||
import DateSelector from '../components/TrackerList/DateSelector'; | ||
import TrackerList from '../components/TrackerList/TrackerList'; | ||
import AddTrackerCard from '../components/TrackerList/AddTrackerCard'; | ||
import { | ||
selectDoneTrackers, | ||
selectHiddenTrackers, | ||
selectTodoTrackers | ||
} from '../store/trackers/trackers.selectors'; | ||
import TabPanel from '../components/TabPanel/TabPanel'; | ||
|
||
function Trackers() { | ||
const { trackers: doneTrackers } = useAppSelector(selectDoneTrackers); | ||
const { trackers: hiddenTrackers } = useAppSelector(selectHiddenTrackers); | ||
const { trackers: todoTrackers } = useAppSelector(selectTodoTrackers); | ||
const [selectedTab, setSelectedTab] = useState(0); | ||
const handleTabChange = (event: React.SyntheticEvent, newTab: number) => { | ||
setSelectedTab(newTab); | ||
}; | ||
|
||
const cardSxProp = { mb: 2, bgcolor: 'secondary.main' }; | ||
|
||
return ( | ||
<> | ||
<TrackerList /> | ||
</> | ||
<Box> | ||
<DateSelector /> | ||
|
||
{doneTrackers.length + hiddenTrackers.length + todoTrackers.length === 0 && ( | ||
<Typography align="center">{"Vous n'avez pas encore de trackers."}</Typography> | ||
)} | ||
|
||
<Tabs | ||
aria-label="icon label tabs" | ||
centered | ||
onChange={handleTabChange} | ||
sx={{ mb: 2 }} | ||
TabIndicatorProps={{ style: { backgroundColor: 'main.accent' } }} | ||
variant="fullWidth" | ||
value={selectedTab}> | ||
<Tab icon={<BallotIcon />} label="À FAIRE" /> | ||
<Tab icon={<CheckIcon />} label="FAIT(S)" /> | ||
<Tab icon={<VisibilityOffIcon />} label="MASQUÉ(S)" /> | ||
</Tabs> | ||
|
||
<TabPanel value={selectedTab} index={0}> | ||
<AddTrackerCard cardProps={{ sx: cardSxProp }} /> | ||
{todoTrackers.length === 0 ? ( | ||
<Alert severity="info">{"Vous n'avez aucun tracker à compléter aujourd'hui."}</Alert> | ||
) : ( | ||
<TrackerList trackers={todoTrackers} cardProps={{ sx: cardSxProp }} /> | ||
)} | ||
</TabPanel> | ||
<TabPanel value={selectedTab} index={1}> | ||
{doneTrackers.length === 0 ? ( | ||
<Alert severity="info">{"Vous n'avez complété aucun tracker aujourd'hui."}</Alert> | ||
) : ( | ||
<TrackerList trackers={doneTrackers} cardProps={{ sx: cardSxProp }} /> | ||
)} | ||
</TabPanel> | ||
<TabPanel value={selectedTab} index={2}> | ||
{hiddenTrackers.length === 0 ? ( | ||
<Alert severity="info">{"Vous n'avez aucun tracker masqué aujourd'hui."}</Alert> | ||
) : ( | ||
<TrackerList trackers={hiddenTrackers} cardProps={{ sx: cardSxProp }} /> | ||
)} | ||
</TabPanel> | ||
</Box> | ||
); | ||
} | ||
export default Trackers; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,107 @@ | ||
import { RootState } from '../store'; | ||
import Tracker from '../../models/Tracker'; | ||
import { addDays, differenceInDays } from 'date-fns'; | ||
import { addDays, differenceInDays, isSameDay } from 'date-fns'; | ||
import TrackerStatus from '../../models/TrackerStatus'; | ||
|
||
const selectTrackers = (state: RootState) => { | ||
const prevTrackers = state.trackers.trackers; | ||
const newTrackers = prevTrackers.map((t) => { | ||
const computeRemainingDays = (beginDate: string, duration: number) => { | ||
const estimatedEndDateObj = addDays(new Date(beginDate), duration); | ||
const difference = differenceInDays(estimatedEndDateObj, new Date()); | ||
return difference; | ||
}; | ||
|
||
const computeNewStatus = (tracker: Tracker) => { | ||
const { remainingDays, entries, status } = tracker; | ||
let newStatus = status; | ||
// End tracker if needed | ||
if (remainingDays !== undefined && remainingDays < 0) { | ||
newStatus = TrackerStatus.over; | ||
} | ||
// Mark Tracker as done if there is a completion for today | ||
if (entries.some((e) => isSameDay(new Date(e.date), new Date()))) { | ||
newStatus = TrackerStatus.done; | ||
} | ||
|
||
return newStatus; | ||
}; | ||
|
||
const formatTrackers = (trackers: Tracker[]) => { | ||
const newTrackers = trackers.map((t) => { | ||
let trackerObj = t as Tracker; | ||
const { beginDate, duration } = trackerObj; | ||
const { beginDate, dateHidden, duration } = trackerObj; | ||
// Delete dateHidden if it is not today | ||
if (dateHidden) { | ||
if (!isSameDay(new Date(dateHidden), new Date())) { | ||
trackerObj.dateHidden = undefined; | ||
} | ||
} | ||
// Remaining Days | ||
if (beginDate && duration) { | ||
const estimatedEndDateObj = addDays(new Date(beginDate), duration); | ||
const difference = differenceInDays(estimatedEndDateObj, new Date()); | ||
trackerObj = { | ||
...trackerObj, | ||
remainingDays: difference | ||
remainingDays: computeRemainingDays(beginDate, duration) | ||
}; | ||
} | ||
|
||
trackerObj = { | ||
...trackerObj, | ||
status: computeNewStatus(trackerObj) | ||
}; | ||
|
||
return trackerObj; | ||
}); | ||
return newTrackers; | ||
}; | ||
|
||
const removeOverTrackers = (trackers: Tracker[]) => | ||
trackers.filter((t) => t.status !== TrackerStatus.over); | ||
|
||
const removeDoneTrackers = (trackers: Tracker[]) => | ||
trackers.filter((t) => t.status !== TrackerStatus.done); | ||
|
||
const removeHiddenTrackers = (trackers: Tracker[]) => | ||
trackers.filter((t) => t.dateHidden === undefined); | ||
|
||
const selectHiddenTrackers = (state: RootState) => { | ||
const newTrackers = removeOverTrackers( | ||
formatTrackers(state.trackers.trackers).filter((t) => t.dateHidden !== undefined) | ||
); | ||
return { | ||
...state.trackers, | ||
trackers: newTrackers | ||
}; | ||
}; | ||
|
||
const selectDoneTrackers = (state: RootState) => { | ||
const newTrackers = formatTrackers(state.trackers.trackers).filter( | ||
(t) => t.status === TrackerStatus.done | ||
); | ||
|
||
return { | ||
...state.trackers, | ||
trackers: newTrackers | ||
}; | ||
}; | ||
|
||
const selectTodoTrackers = (state: RootState) => { | ||
const newTrackers = removeOverTrackers( | ||
removeHiddenTrackers( | ||
removeDoneTrackers( | ||
formatTrackers(state.trackers.trackers).filter((t) => t.dateHidden === undefined) | ||
) | ||
) | ||
); | ||
return { | ||
...state.trackers, | ||
trackers: newTrackers | ||
}; | ||
}; | ||
|
||
const selectAllTrackers = (state: RootState) => { | ||
const newTrackers = formatTrackers(state.trackers.trackers); | ||
return { | ||
...state.trackers, | ||
trackers: newTrackers ? [...newTrackers] : prevTrackers | ||
trackers: newTrackers | ||
}; | ||
}; | ||
|
||
export default selectTrackers; | ||
export { selectAllTrackers, selectDoneTrackers, selectHiddenTrackers, selectTodoTrackers }; |