aboutsummaryrefslogtreecommitdiffstats
path: root/src/views/ChoresOverview.jsx
diff options
context:
space:
mode:
authorLibravatar Mo Tarbin <mhed.t91@gmail.com>2024-06-30 18:55:39 -0400
committerLibravatar Mo Tarbin <mhed.t91@gmail.com>2024-06-30 18:55:39 -0400
commit2657469964e24ffbeb905024532120395f6e797c (patch)
tree2fe9db8a4ecfa92d854ca94f7586d81163c8bd25 /src/views/ChoresOverview.jsx
downloaddonetick-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.jsx354
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