Skip to content

Commit

Permalink
feat(trackersTabs): display trackers by 3 categories
Browse files Browse the repository at this point in the history
  • Loading branch information
Clm-Roig committed Mar 23, 2022
1 parent d09e245 commit 2bf3daa
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 59 deletions.
3 changes: 2 additions & 1 deletion src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import frLocale from 'date-fns/locale/fr';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { SnackbarKey, SnackbarProvider } from 'notistack';
import { LocalizationProvider } from '@mui/lab';
import { palette, typography } from '../config/CustomTheme';
import { components, palette, typography } from '../config/CustomTheme';
import { DRAWER_MENU_WIDTH } from '../config/Constants';
import AppBar from './AppBar';
import DrawerMenu from './DrawerMenu';
Expand All @@ -24,6 +24,7 @@ const MainContent = styled(Container)`

// Theme configuration
let theme = createTheme({
components,
palette: palette,
typography: typography
});
Expand Down
4 changes: 2 additions & 2 deletions src/app/CustomPersistGate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { PersistGate } from 'redux-persist/integration/react';
import FullScreenLoading from './FullScreenLoading';
import { persistor } from '../store/store';
import { useAppSelector } from './hooks';
import selectTrackers from '../store/trackers/trackers.selectors';
import { selectAllTrackers } from '../store/trackers/trackers.selectors';
import isATracker from '../utils/isATracker';
import { useNavigate } from 'react-router-dom';

Expand All @@ -12,7 +12,7 @@ interface Props {
}

const CustomPersistGate: FC<Props> = ({ children }) => {
const { trackers } = useAppSelector(selectTrackers);
const { trackers } = useAppSelector(selectAllTrackers);
const navigate = useNavigate();

const handleBeforeLift = () => {
Expand Down
25 changes: 25 additions & 0 deletions src/components/TabPanel/TabPanel.tsx
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;
21 changes: 21 additions & 0 deletions src/components/TrackerList/DateSelector.tsx
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;
54 changes: 14 additions & 40 deletions src/components/TrackerList/TrackerList.tsx
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;
22 changes: 22 additions & 0 deletions src/config/CustomTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@ const MIDDLE_BLUE = {
main: '#80CBC4'
};

export const components = {
MuiTabs: {
styleOverrides: {
indicator: {
backgroundColor: MIDDLE_BLUE.main
},
root: {
color: MIDDLE_BLUE.main
}
}
},
MuiTab: {
styleOverrides: {
root: {
'&.Mui-selected': {
color: MIDDLE_BLUE.main
}
}
}
}
};

export const palette = {
primary: CHARCOAL,
secondary: YELLOW_CRAYOLA,
Expand Down
2 changes: 1 addition & 1 deletion src/models/TrackerStatus.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
enum TrackerStatus {
active,
archived,
done,
over
}

Expand Down
71 changes: 68 additions & 3 deletions src/pages/Trackers.tsx
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;
4 changes: 2 additions & 2 deletions src/store/trackers/trackers.selectors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Tracker from '../../models/Tracker';
import TrackerStatus from '../../models/TrackerStatus';
import { createTestStore } from '../createTestStore';
import { RootState } from '../store';
import selectTrackers from './trackers.selectors';
import { selectAllTrackers } from './trackers.selectors';

describe('trackers selector', () => {
let state: RootState;
Expand Down Expand Up @@ -53,7 +53,7 @@ describe('trackers selector', () => {
]
}
};
const { error, status, trackers } = selectTrackers(stateWithTrackers);
const { error, status, trackers } = selectAllTrackers(stateWithTrackers);
expect(error).toEqual({});
expect(status).toEqual(SliceStatus.idle);
expect(trackers.length).toEqual(2);
Expand Down
101 changes: 91 additions & 10 deletions src/store/trackers/trackers.selectors.ts
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 };

0 comments on commit 2bf3daa

Please sign in to comment.