import { Add, CancelRounded, EditCalendar } from '@mui/icons-material' import { Badge, Box, Checkbox, CircularProgress, Container, IconButton, Input, List, ListItem, Menu, MenuItem, Snackbar, Typography, } from '@mui/joy' import Fuse from 'fuse.js' 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 [searchTerm, setSearchTerm] = useState('') 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) } const searchOptions = { // keys to search in keys: ['name', 'labels'], includeScore: true, // Optional: if you want to see how well each result matched the search term isCaseSensitive: false, findAllMatches: true, } const fuse = new Fuse(chores, searchOptions) const handleSearchChange = e => { const search = e.target.value if (search === '') { setFilteredChores(chores) setSearchTerm('') return } const term = search.toLowerCase() setSearchTerm(term) setFilteredChores(fuse.search(term).map(result => result.item)) } if (userProfile === null) { return ( ) } return ( {/* My Chores */} {/* */} {['All', 'Overdue', 'Due today', 'Due in week'].map(filter => ( handleSelectedFilter(filter)} checked={filter === selectedFilter} disableIcon overlay size='sm' /> ))} { setFilteredChores( FILTERS['Assigned To Me'](chores, userProfile.id), ) setSelectedFilter('Assigned To Me') handleFilterMenuClose() }} > Assigned to me { setFilteredChores( FILTERS['Created By Me'](chores, userProfile.id), ) setSelectedFilter('Created By Me') handleFilterMenuClose() }} > Created by me { setFilteredChores(FILTERS['No Due Date'](chores, userProfile.id)) setSelectedFilter('No Due Date') handleFilterMenuClose() }} > No Due Date {/* Search box to filter */} { setSearchTerm('') setFilteredChores(chores) }} /> ) } /> {/* */} {filteredChores.length === 0 && ( Nothing scheduled )} {filteredChores.map(chore => ( ))} } onClick={() => { Navigate(`/chores/create`) }} > { setIsSnackbarOpen(false) }} autoHideDuration={3000} variant='soft' color='success' size='lg' invertedColors > {snackBarMessage} ) } 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