aboutsummaryrefslogtreecommitdiffstats
path: root/src/views/Chores/MyChores.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/Chores/MyChores.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 '')
-rw-r--r--src/views/Chores/MyChores.jsx384
1 files changed, 384 insertions, 0 deletions
diff --git a/src/views/Chores/MyChores.jsx b/src/views/Chores/MyChores.jsx
new file mode 100644
index 0000000..98fd443
--- /dev/null
+++ b/src/views/Chores/MyChores.jsx
@@ -0,0 +1,384 @@
+import { Add, EditCalendar } from '@mui/icons-material'
+import {
+ Badge,
+ Box,
+ Checkbox,
+ CircularProgress,
+ Container,
+ IconButton,
+ List,
+ ListItem,
+ Menu,
+ MenuItem,
+ Snackbar,
+ Typography,
+} from '@mui/joy'
+import { useContext, useEffect, useRef, useState } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { UserContext } from '../../contexts/UserContext'
+import Logo from '../../Logo'
+import { GetAllUsers, GetChores, GetUserProfile } from '../../utils/Fetcher'
+import ChoreCard from './ChoreCard'
+
+const MyChores = () => {
+ const { userProfile, setUserProfile } = useContext(UserContext)
+ const [isSnackbarOpen, setIsSnackbarOpen] = useState(false)
+ const [snackBarMessage, setSnackBarMessage] = useState(null)
+ const [chores, setChores] = useState([])
+ const [filteredChores, setFilteredChores] = useState([])
+ const [selectedFilter, setSelectedFilter] = useState('All')
+ const [activeUserId, setActiveUserId] = useState(0)
+ const [performers, setPerformers] = useState([])
+ const [anchorEl, setAnchorEl] = useState(null)
+ const menuRef = useRef(null)
+ const Navigate = useNavigate()
+ const choreSorter = (a, b) => {
+ // 1. Handle null due dates (always last):
+ if (!a.nextDueDate && !b.nextDueDate) return 0 // Both null, no order
+ if (!a.nextDueDate) return 1 // a is null, comes later
+ if (!b.nextDueDate) return -1 // b is null, comes earlier
+
+ const aDueDate = new Date(a.nextDueDate)
+ const bDueDate = new Date(b.nextDueDate)
+ const now = new Date()
+
+ const oneDayInMs = 24 * 60 * 60 * 1000
+
+ // 2. Prioritize tasks due today +- 1 day:
+ const aTodayOrNear = Math.abs(aDueDate - now) <= oneDayInMs
+ const bTodayOrNear = Math.abs(bDueDate - now) <= oneDayInMs
+ if (aTodayOrNear && !bTodayOrNear) return -1 // a is closer
+ if (!aTodayOrNear && bTodayOrNear) return 1 // b is closer
+
+ // 3. Handle overdue tasks (excluding today +- 1):
+ const aOverdue = aDueDate < now && !aTodayOrNear
+ const bOverdue = bDueDate < now && !bTodayOrNear
+ if (aOverdue && !bOverdue) return -1 // a is overdue, comes earlier
+ if (!aOverdue && bOverdue) return 1 // b is overdue, comes earlier
+
+ // 4. Sort future tasks by due date:
+ return aDueDate - bDueDate // Sort ascending by due date
+ }
+
+ const handleSelectedFilter = selected => {
+ setFilteredChores(FILTERS[selected](chores))
+
+ setSelectedFilter(selected)
+ }
+
+ useEffect(() => {
+ if (userProfile === null) {
+ GetUserProfile()
+ .then(response => response.json())
+ .then(data => {
+ setUserProfile(data.res)
+ })
+ }
+ GetChores()
+ .then(response => response.json())
+ .then(data => {
+ data.res.sort(choreSorter)
+ setChores(data.res)
+
+ setFilteredChores(data.res)
+ })
+
+ GetAllUsers()
+ .then(response => response.json())
+ .then(data => {
+ setPerformers(data.res)
+ })
+
+ const currentUser = JSON.parse(localStorage.getItem('user'))
+ if (currentUser !== null) {
+ setActiveUserId(currentUser.id)
+ }
+ }, [])
+ useEffect(() => {
+ document.addEventListener('mousedown', handleMenuOutsideClick)
+ return () => {
+ document.removeEventListener('mousedown', handleMenuOutsideClick)
+ }
+ }, [anchorEl])
+ const handleMenuOutsideClick = event => {
+ if (
+ anchorEl &&
+ !anchorEl.contains(event.target) &&
+ !menuRef.current.contains(event.target)
+ ) {
+ handleFilterMenuClose()
+ }
+ }
+ const handleFilterMenuOpen = event => {
+ event.preventDefault()
+ setAnchorEl(event.currentTarget)
+ }
+
+ const handleFilterMenuClose = () => {
+ setAnchorEl(null)
+ }
+ const handleChoreUpdated = (updatedChore, event) => {
+ const newChores = chores.map(chore => {
+ if (chore.id === updatedChore.id) {
+ return updatedChore
+ }
+ return chore
+ })
+
+ const newFilteredChores = filteredChores.map(chore => {
+ if (chore.id === updatedChore.id) {
+ return updatedChore
+ }
+ return chore
+ })
+ setChores(newChores)
+ setFilteredChores(newFilteredChores)
+ switch (event) {
+ case 'completed':
+ setSnackBarMessage('Completed')
+ break
+ case 'skipped':
+ setSnackBarMessage('Skipped')
+ break
+ case 'rescheduled':
+ setSnackBarMessage('Rescheduled')
+ break
+ default:
+ setSnackBarMessage('Updated')
+ }
+ setIsSnackbarOpen(true)
+ }
+
+ const handleChoreDeleted = deletedChore => {
+ const newChores = chores.filter(chore => chore.id !== deletedChore.id)
+ const newFilteredChores = filteredChores.filter(
+ chore => chore.id !== deletedChore.id,
+ )
+ setChores(newChores)
+ setFilteredChores(newFilteredChores)
+ }
+
+ if (userProfile === null) {
+ return (
+ <Container className='flex h-full items-center justify-center'>
+ <Box className='flex flex-col items-center justify-center'>
+ <CircularProgress
+ color='success'
+ sx={{ '--CircularProgress-size': '200px' }}
+ >
+ <Logo />
+ </CircularProgress>
+ </Box>
+ </Container>
+ )
+ }
+
+ return (
+ <Container maxWidth='md'>
+ {/* <Typography level='h3' mb={1.5}>
+ My Chores
+ </Typography> */}
+ {/* <Sheet> */}
+ <List
+ orientation='horizontal'
+ wrap
+ sx={{
+ '--List-gap': '8px',
+ '--ListItem-radius': '20px',
+ '--ListItem-minHeight': '32px',
+ '--ListItem-gap': '4px',
+ mt: 0.2,
+ }}
+ >
+ {['All', 'Overdue', 'Due today', 'Due in week'].map(filter => (
+ <Badge
+ key={filter}
+ anchorOrigin={{
+ vertical: 'top',
+ horizontal: 'right',
+ }}
+ variant='outlined'
+ color={selectedFilter === filter ? 'primary' : 'neutral'}
+ badgeContent={FILTERS[filter](chores).length}
+ badgeInset={'5px'}
+ >
+ <ListItem key={filter}>
+ <Checkbox
+ key={'checkbox' + filter}
+ label={filter}
+ onClick={() => handleSelectedFilter(filter)}
+ checked={filter === selectedFilter}
+ disableIcon
+ overlay
+ size='sm'
+ />
+ </ListItem>
+ </Badge>
+ ))}
+
+ <ListItem onClick={handleFilterMenuOpen}>
+ <Checkbox key='checkboxAll' label='⋮' disableIcon overlay size='lg' />
+ </ListItem>
+ <Menu
+ ref={menuRef}
+ anchorEl={anchorEl}
+ open={Boolean(anchorEl)}
+ onClose={handleFilterMenuClose}
+ >
+ <MenuItem
+ onClick={() => {
+ setFilteredChores(
+ FILTERS['Assigned To Me'](chores, userProfile.id),
+ )
+ setSelectedFilter('Assigned To Me')
+ handleFilterMenuClose()
+ }}
+ >
+ Assigned to me
+ </MenuItem>
+ <MenuItem
+ onClick={() => {
+ setFilteredChores(
+ FILTERS['Created By Me'](chores, userProfile.id),
+ )
+ setSelectedFilter('Created By Me')
+ handleFilterMenuClose()
+ }}
+ >
+ Created by me
+ </MenuItem>
+ <MenuItem
+ onClick={() => {
+ setFilteredChores(FILTERS['No Due Date'](chores, userProfile.id))
+ setSelectedFilter('No Due Date')
+ handleFilterMenuClose()
+ }}
+ >
+ No Due Date
+ </MenuItem>
+ </Menu>
+ </List>
+ {/* </Sheet> */}
+ {filteredChores.length === 0 && (
+ <Box
+ sx={{
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ flexDirection: 'column',
+ height: '50vh',
+ }}
+ >
+ <EditCalendar
+ sx={{
+ fontSize: '4rem',
+ // color: 'text.disabled',
+ mb: 1,
+ }}
+ />
+ <Typography level='title-md' gutterBottom>
+ Nothing scheduled
+ </Typography>
+ </Box>
+ )}
+
+ {filteredChores.map(chore => (
+ <ChoreCard
+ key={chore.id}
+ chore={chore}
+ onChoreUpdate={handleChoreUpdated}
+ onChoreRemove={handleChoreDeleted}
+ performers={performers}
+ />
+ ))}
+
+ <Box
+ // variant='outlined'
+ sx={{
+ position: 'fixed',
+ bottom: 0,
+ left: 10,
+ p: 2, // padding
+ display: 'flex',
+ justifyContent: 'flex-end',
+ gap: 2,
+ 'z-index': 1000,
+ }}
+ >
+ <IconButton
+ color='primary'
+ variant='solid'
+ sx={{
+ borderRadius: '50%',
+ width: 50,
+ height: 50,
+ }}
+ // startDecorator={<Add />}
+ onClick={() => {
+ Navigate(`/chores/create`)
+ }}
+ >
+ <Add />
+ </IconButton>
+ </Box>
+ <Snackbar
+ open={isSnackbarOpen}
+ onClose={() => {
+ setIsSnackbarOpen(false)
+ }}
+ autoHideDuration={3000}
+ variant='soft'
+ color='success'
+ size='lg'
+ invertedColors
+ >
+ <Typography level='title-md'>{snackBarMessage}</Typography>
+ </Snackbar>
+ </Container>
+ )
+}
+
+const FILTERS = {
+ All: function (chores) {
+ return chores
+ },
+ Overdue: function (chores) {
+ return chores.filter(chore => {
+ if (chore.nextDueDate === null) return false
+ return new Date(chore.nextDueDate) < new Date()
+ })
+ },
+ 'Due today': function (chores) {
+ return chores.filter(chore => {
+ return (
+ new Date(chore.nextDueDate).toDateString() === new Date().toDateString()
+ )
+ })
+ },
+ 'Due in week': function (chores) {
+ return chores.filter(chore => {
+ return (
+ new Date(chore.nextDueDate) <
+ new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) &&
+ new Date(chore.nextDueDate) > new Date()
+ )
+ })
+ },
+ 'Created By Me': function (chores, userID) {
+ return chores.filter(chore => {
+ return chore.createdBy === userID
+ })
+ },
+ 'Assigned To Me': function (chores, userID) {
+ return chores.filter(chore => {
+ return chore.assignedTo === userID
+ })
+ },
+ 'No Due Date': function (chores, userID) {
+ return chores.filter(chore => {
+ return chore.nextDueDate === null
+ })
+ },
+}
+
+export default MyChores