diff options
author | Mo Tarbin <mhed.t91@gmail.com> | 2024-06-30 18:55:39 -0400 |
---|---|---|
committer | Mo Tarbin <mhed.t91@gmail.com> | 2024-06-30 18:55:39 -0400 |
commit | 2657469964e24ffbeb905024532120395f6e797c (patch) | |
tree | 2fe9db8a4ecfa92d854ca94f7586d81163c8bd25 /src/views/ChoresOverview.jsx | |
download | donetick-frontend-2657469964e24ffbeb905024532120395f6e797c.tar.gz donetick-frontend-2657469964e24ffbeb905024532120395f6e797c.tar.bz2 donetick-frontend-2657469964e24ffbeb905024532120395f6e797c.zip |
move to Donetick Org, First commit frontend
Diffstat (limited to 'src/views/ChoresOverview.jsx')
-rw-r--r-- | src/views/ChoresOverview.jsx | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/src/views/ChoresOverview.jsx b/src/views/ChoresOverview.jsx new file mode 100644 index 0000000..396ab0d --- /dev/null +++ b/src/views/ChoresOverview.jsx @@ -0,0 +1,354 @@ +import { + Adjust, + CancelRounded, + CheckBox, + Edit, + HelpOutline, + History, + QueryBuilder, + SearchRounded, + Warning, +} from '@mui/icons-material' +import { + Avatar, + Button, + ButtonGroup, + Chip, + Container, + Grid, + IconButton, + Input, + Table, + Tooltip, + Typography, +} from '@mui/joy' + +import moment from 'moment' +import { useEffect, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { API_URL } from '../Config' +import { GetAllUsers } from '../utils/Fetcher' +import { Fetch } from '../utils/TokenManager' +import DateModal from './Modals/Inputs/DateModal' +// import moment from 'moment' + +// enum for chore status: +const CHORE_STATUS = { + NO_DUE_DATE: 'No due date', + DUE_SOON: 'Soon', + DUE_NOW: 'Due', + OVER_DUE: 'Overdue', +} + +const ChoresOverview = () => { + const [chores, setChores] = useState([]) + const [filteredChores, setFilteredChores] = useState([]) + const [performers, setPerformers] = useState([]) + const [activeUserId, setActiveUserId] = useState(null) + const [isDateModalOpen, setIsDateModalOpen] = useState(false) + const [choreId, setChoreId] = useState(null) + const [search, setSearch] = useState('') + const Navigate = useNavigate() + + const getChoreStatus = chore => { + if (chore.nextDueDate === null) { + return CHORE_STATUS.NO_DUE_DATE + } + const dueDate = new Date(chore.nextDueDate) + const now = new Date() + const diff = dueDate - now + if (diff < 0) { + return CHORE_STATUS.OVER_DUE + } + if (diff > 1000 * 60 * 60 * 24) { + return CHORE_STATUS.DUE_NOW + } + if (diff > 0) { + return CHORE_STATUS.DUE_SOON + } + return CHORE_STATUS.NO_DUE_DATE + } + const getChoreStatusColor = chore => { + switch (getChoreStatus(chore)) { + case CHORE_STATUS.NO_DUE_DATE: + return 'neutral' + case CHORE_STATUS.DUE_SOON: + return 'success' + case CHORE_STATUS.DUE_NOW: + return 'primary' + case CHORE_STATUS.OVER_DUE: + return 'warning' + default: + return 'neutral' + } + } + const getChoreStatusIcon = chore => { + switch (getChoreStatus(chore)) { + case CHORE_STATUS.NO_DUE_DATE: + return <HelpOutline /> + case CHORE_STATUS.DUE_SOON: + return <QueryBuilder /> + case CHORE_STATUS.DUE_NOW: + return <Adjust /> + case CHORE_STATUS.OVER_DUE: + return <Warning /> + default: + return <HelpOutline /> + } + } + useEffect(() => { + // fetch chores: + Fetch(`${API_URL}/chores/`) + .then(response => response.json()) + .then(data => { + const filteredData = data.res.filter( + chore => chore.assignedTo === activeUserId || chore.assignedTo === 0, + ) + setChores(data.res) + setFilteredChores(data.res) + }) + GetAllUsers() + .then(response => response.json()) + .then(data => { + setPerformers(data.res) + }) + const user = JSON.parse(localStorage.getItem('user')) + if (user != null && user.id > 0) { + setActiveUserId(user.id) + } + }, []) + + return ( + <Container> + <Typography level='h4' mb={1.5}> + Chores Overviews + </Typography> + {/* <SummaryCard /> */} + <Grid container> + <Grid + item + sm={6} + alignSelf={'flex-start'} + minWidth={100} + display='flex' + gap={2} + > + <Input + placeholder='Search' + value={search} + onChange={e => { + if (e.target.value === '') { + setFilteredChores(chores) + } + setSearch(e.target.value) + const newChores = chores.filter(chore => { + return chore.name.includes(e.target.value) + }) + setFilteredChores(newChores) + }} + endDecorator={ + search !== '' ? ( + <Button + variant='text' + onClick={() => { + setSearch('') + setFilteredChores(chores) + }} + > + <CancelRounded /> + </Button> + ) : ( + <Button variant='text'> + <SearchRounded /> + </Button> + ) + } + ></Input> + </Grid> + <Grid item sm={6} justifyContent={'flex-end'} display={'flex'} gap={2}> + <Button + onClick={() => { + Navigate(`/chores/create`) + }} + > + New Chore + </Button> + </Grid> + </Grid> + + <Table> + <thead> + <tr> + {/* first column has minium size because its icon */} + <th style={{ width: 100 }}>Due</th> + <th>Chore</th> + <th>Assignee</th> + <th>Due</th> + <th>Action</th> + </tr> + </thead> + <tbody> + {filteredChores.map(chore => ( + <tr key={chore.id}> + {/* cirular icon if the chore is due will be red else yellow: */} + <td> + <Chip color={getChoreStatusColor(chore)}> + {getChoreStatus(chore)} + </Chip> + </td> + <td + onClick={() => { + Navigate(`/chores/${chore.id}/edit`) + }} + > + {chore.name || '--'} + </td> + <td> + {chore.assignedTo > 0 ? ( + <Tooltip + title={ + performers.find(p => p.id === chore.assignedTo) + ?.displayName + } + size='sm' + > + <Chip + startDecorator={ + <Avatar color='primary'> + { + performers.find(p => p.id === chore.assignedTo) + ?.displayName[0] + } + </Avatar> + } + > + {performers.find(p => p.id === chore.assignedTo)?.name} + </Chip> + </Tooltip> + ) : ( + <Chip + color='warning' + startDecorator={<Avatar color='primary'>?</Avatar>} + > + Unassigned + </Chip> + )} + </td> + <td> + <Tooltip + title={ + chore.nextDueDate === null + ? 'no due date' + : moment(chore.nextDueDate).format('YYYY-MM-DD') + } + size='sm' + > + <Typography> + {chore.nextDueDate === null + ? '--' + : moment(chore.nextDueDate).fromNow()} + </Typography> + </Tooltip> + </td> + + <td> + <ButtonGroup + // display='flex' + // // justifyContent='space-around' + // alignItems={'center'} + // gap={0.5} + > + <IconButton + variant='outlined' + size='sm' + // sx={{ borderRadius: '50%' }} + onClick={() => { + Fetch(`${API_URL}/chores/${chore.id}/do`, { + method: 'POST', + }).then(response => { + if (response.ok) { + response.json().then(data => { + const newChore = data.res + const newChores = [...chores] + const index = newChores.findIndex( + c => c.id === chore.id, + ) + newChores[index] = newChore + setChores(newChores) + setFilteredChores(newChores) + }) + } + }) + }} + aria-setsize={2} + > + <CheckBox /> + </IconButton> + <IconButton + variant='outlined' + size='sm' + // sx={{ borderRadius: '50%' }} + onClick={() => { + setChoreId(chore.id) + setIsDateModalOpen(true) + }} + aria-setsize={2} + > + <History /> + </IconButton> + <IconButton + variant='outlined' + size='sm' + // sx={{ + // borderRadius: '50%', + // }} + onClick={() => { + Navigate(`/chores/${chore.id}/edit`) + }} + > + <Edit /> + </IconButton> + </ButtonGroup> + </td> + </tr> + ))} + </tbody> + </Table> + <DateModal + isOpen={isDateModalOpen} + key={choreId} + title={`Change due date`} + onClose={() => { + setIsDateModalOpen(false) + }} + onSave={date => { + if (activeUserId === null) { + alert('Please select a performer') + return + } + fetch( + `${API_URL}/chores/${choreId}/do?performer=${activeUserId}&completedDate=${new Date( + date, + ).toISOString()}`, + { + method: 'POST', + }, + ).then(response => { + if (response.ok) { + response.json().then(data => { + const newChore = data.res + const newChores = [...chores] + const index = newChores.findIndex(c => c.id === chore.id) + newChores[index] = newChore + setChores(newChores) + setFilteredChores(newChores) + }) + } + }) + }} + /> + </Container> + ) +} + +export default ChoresOverview |