import { Add, CancelRounded, EditCalendar, ExpandCircleDown, Grain, PriorityHigh, Search, Sort, Style, Unarchive, } from '@mui/icons-material' import { Accordion, AccordionDetails, AccordionGroup, Box, Button, Chip, Container, Divider, IconButton, Input, List, 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 { useChores } from '../../queries/ChoreQueries' import { GetAllUsers, GetArchivedChores, GetChores, GetUserProfile, } from '../../utils/Fetcher' import Priorities from '../../utils/Priorities' import LoadingComponent from '../components/Loading' import { useLabels } from '../Labels/LabelQueries' import ChoreCard from './ChoreCard' import IconButtonWithMenu from './IconButtonWithMenu' import { ChoreFilters, ChoresGrouper, ChoreSorter } from '../../utils/Chores' import TaskInput from '../components/AddTaskModal' import { canScheduleNotification, scheduleChoreNotification, } from './LocalNotificationScheduler' import NotificationAccessSnackbar from './NotificationAccessSnackbar' import Sidepanel from './Sidepanel' import SortAndGrouping from './SortAndGrouping' const MyChores = () => { const { userProfile, setUserProfile } = useContext(UserContext) const [isSnackbarOpen, setIsSnackbarOpen] = useState(false) const [snackBarMessage, setSnackBarMessage] = useState(null) const [chores, setChores] = useState([]) const [archivedChores, setArchivedChores] = useState(null) const [filteredChores, setFilteredChores] = useState([]) const [searchFilter, setSearchFilter] = useState('All') const [choreSections, setChoreSections] = useState([]) const [activeTextField, setActiveTextField] = useState('task') const [taskInputFocus, setTaskInputFocus] = useState(0) const searchInputRef = useRef() const [searchInputFocus, setSearchInputFocus] = useState(0) const [selectedChoreSection, setSelectedChoreSection] = useState( localStorage.getItem('selectedChoreSection') || 'due_date', ) const [openChoreSections, setOpenChoreSections] = useState( JSON.parse(localStorage.getItem('openChoreSections')) || {}, ) const [selectedChoreFilter, setSelectedChoreFilter] = useState( localStorage.getItem('selectedChoreFilter') || 'anyone', ) const [searchTerm, setSearchTerm] = useState('') const [performers, setPerformers] = useState([]) const [anchorEl, setAnchorEl] = useState(null) const menuRef = useRef(null) const Navigate = useNavigate() const { data: userLabels, isLoading: userLabelsLoading } = useLabels() const { data: choresData, isLoading: choresLoading } = useChores() useEffect(() => { Promise.all([GetChores(), GetAllUsers(), GetUserProfile()]).then( responses => { const [choresResponse, usersResponse, userProfileResponse] = responses if (!choresResponse.ok) { throw new Error(choresResponse.statusText) } if (!usersResponse.ok) { throw new Error(usersResponse.statusText) } if (!userProfileResponse.ok) { throw new Error(userProfileResponse.statusText) } Promise.all([ choresResponse.json(), usersResponse.json(), userProfileResponse.json(), ]).then(data => { const [choresData, usersData, userProfileData] = data setUserProfile(userProfileData.res) choresData.res.sort(ChoreSorter) setChores(choresData.res) setFilteredChores(choresData.res) setPerformers(usersData.res) if (canScheduleNotification()) { scheduleChoreNotification( choresData.res, userProfileData.res, usersData.res, ) } }) }, ) // GetAllUsers() // .then(response => response.json()) // .then(data => { // setPerformers(data.res) // }) // GetUserProfile().then(response => response.json()).then(data => { // setUserProfile(data.res) // }) // const currentUser = JSON.parse(localStorage.getItem('user')) // if (currentUser !== null) { // setActiveUserId(currentUser.id) // } }, []) useEffect(() => { if (choresData) { const sortedChores = choresData.res.sort(ChoreSorter) setChores(sortedChores) setFilteredChores(sortedChores) const sections = ChoresGrouper(selectedChoreSection, sortedChores) setChoreSections(sections) if (localStorage.getItem('openChoreSections') === null) { setSelectedChoreSectionWithCache(selectedChoreSection) setOpenChoreSections( Object.keys(sections).reduce((acc, key) => { acc[key] = true return acc }, {}), ) } } }, [choresData, choresLoading]) useEffect(() => { document.addEventListener('mousedown', handleMenuOutsideClick) return () => { document.removeEventListener('mousedown', handleMenuOutsideClick) } }, [anchorEl]) useEffect(() => { if (searchInputFocus > 0 && searchInputRef.current) { searchInputRef.current.focus() searchInputRef.current.selectionStart = searchInputRef.current.value?.length searchInputRef.current.selectionEnd = searchInputRef.current.value?.length } }, [searchInputFocus]) const setSelectedChoreSectionWithCache = value => { setSelectedChoreSection(value) localStorage.setItem('selectedChoreSection', value) } const setOpenChoreSectionsWithCache = value => { setOpenChoreSections(value) localStorage.setItem('openChoreSections', JSON.stringify(value)) } const setSelectedChoreFilterWithCache = value => { setSelectedChoreFilter(value) localStorage.setItem('selectedChoreFilter', JSON.stringify(value)) } const updateChores = newChore => { const newChores = chores newChores.push(newChore) setChores(newChores) setFilteredChores(newChores) setChoreSections(ChoresGrouper(selectedChoreSection, newChores)) setSearchFilter('All') } 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 handleLabelFiltering = chipClicked => { if (chipClicked.label) { const label = chipClicked.label const labelFiltered = [...chores].filter(chore => chore.labelsV2.some( l => l.id === label.id && l.created_by === label.created_by, ), ) setFilteredChores(labelFiltered) setSearchFilter('Label: ' + label.name) } else if (chipClicked.priority) { const priority = chipClicked.priority const priorityFiltered = chores.filter( chore => chore.priority === priority, ) setFilteredChores(priorityFiltered) setSearchFilter('Priority: ' + priority) } } const handleChoreUpdated = (updatedChore, event) => { var newChores = chores.map(chore => { if (chore.id === updatedChore.id) { return updatedChore } return chore }) var newFilteredChores = filteredChores.map(chore => { if (chore.id === updatedChore.id) { return updatedChore } return chore }) if ( event === 'archive' || (event === 'completed' && updatedChore.frequencyType === 'once') ) { newChores = newChores.filter(chore => chore.id !== updatedChore.id) newFilteredChores = newFilteredChores.filter( chore => chore.id !== updatedChore.id, ) if (archivedChores !== null) { setArchivedChores([...archivedChores, updatedChore]) } } if (event === 'unarchive') { newChores.push(updatedChore) newFilteredChores.push(updatedChore) setArchivedChores( archivedChores.filter(chore => chore.id !== updatedChore.id), ) } setChores(newChores) setFilteredChores(newFilteredChores) setChoreSections(ChoresGrouper(selectedChoreSection, newChores)) switch (event) { case 'completed': setSnackBarMessage('Completed') break case 'skipped': setSnackBarMessage('Skipped') break case 'rescheduled': setSnackBarMessage('Rescheduled') break case 'unarchive': setSnackBarMessage('Unarchive') break case 'archive': setSnackBarMessage('Archived') 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) setChoreSections(ChoresGrouper(selectedChoreSection, newChores)) } const searchOptions = { // keys to search in keys: ['name', 'raw_label'], 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.map(c => ({ ...c, raw_label: c.labelsV2.map(c => c.name).join(' '), })), searchOptions, ) const handleSearchChange = e => { if (searchFilter !== 'All') { setSearchFilter('All') } 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 || userLabelsLoading || performers.length === 0 || choresLoading ) { return } return (
{activeTextField == 'task' && ( )} {activeTextField == 'search' && ( 0} placeholder='Search' value={searchTerm} fullWidth sx={{ mt: 1, mb: 1, borderRadius: 24, height: 24, borderColor: 'text.disabled', padding: 1, }} onChange={handleSearchChange} endDecorator={ searchTerm && ( { setSearchTerm('') setFilteredChores(chores) }} /> ) } /> )} {activeTextField != 'task' && ( { setActiveTextField('task') setTaskInputFocus(taskInputFocus + 1) }} > )} {activeTextField != 'search' && ( { setActiveTextField('search') setSearchInputFocus(searchInputFocus + 1) searchInputRef?.current?.focus() }} > )} } selectedItem={selectedChoreSection} selectedFilter={selectedChoreFilter} setFilter={filter => { setSelectedChoreFilterWithCache(filter) const section = ChoresGrouper( selectedChoreSection, chores, ChoreFilters(userProfile)[filter], ) setChoreSections(section) setOpenChoreSectionsWithCache( // open all sections by default Object.keys(section).reduce((acc, key) => { acc[key] = true return acc }, {}), ) }} onItemSelect={selected => { const section = ChoresGrouper(selected.value, chores) setChoreSections(section) setSelectedChoreSectionWithCache(selected.value) setOpenChoreSectionsWithCache( // open all sections by default Object.keys(section).reduce((acc, key) => { acc[key] = true return acc }, {}), ) setFilteredChores(chores) setSearchFilter('All') }} mouseClickHandler={handleMenuOutsideClick} /> {activeTextField === 'search' && (
} options={Priorities} selectedItem={searchFilter} onItemSelect={selected => { handleLabelFiltering({ priority: selected.value }) }} mouseClickHandler={handleMenuOutsideClick} isActive={searchFilter.startsWith('Priority: ')} /> } options={userLabels} selectedItem={searchFilter} onItemSelect={selected => { handleLabelFiltering({ label: selected }) }} isActive={searchFilter.startsWith('Label: ')} mouseClickHandler={handleMenuOutsideClick} useChips /> {Object.keys(FILTERS).map((filter, index) => ( { const filterFunction = FILTERS[filter] const filteredChores = filterFunction.length === 2 ? filterFunction(chores, userProfile.id) : filterFunction(chores) setFilteredChores(filteredChores) setSearchFilter(filter) handleFilterMenuClose() }} > {filter} {FILTERS[filter].length === 2 ? FILTERS[filter](chores, userProfile.id).length : FILTERS[filter](chores).length} ))} {searchFilter.startsWith('Label: ') || (searchFilter.startsWith('Priority: ') && ( { setFilteredChores(chores) setSearchFilter('All') }} > Cancel All Filters ))}
)} {searchFilter !== 'All' && ( { setFilteredChores(chores) setSearchFilter('All') }} endDecorator={} onClick={() => { setFilteredChores(chores) setSearchFilter('All') }} > Current Filter: {searchFilter} )} {filteredChores.length === 0 && ( Nothing scheduled {chores.length > 0 && ( <> )} )} {(searchTerm?.length > 0 || searchFilter !== 'All') && filteredChores.map(chore => ( ))} {searchTerm.length === 0 && searchFilter === 'All' && ( {choreSections.map((section, index) => { if (section.content.length === 0) return null return ( { if (openChoreSections[index]) { const newOpenChoreSections = { ...openChoreSections, } delete newOpenChoreSections[index] setOpenChoreSectionsWithCache(newOpenChoreSections) } else { setOpenChoreSectionsWithCache({ ...openChoreSections, [index]: true, }) } }} endDecorator={ openChoreSections[index] ? ( ) : ( ) } startDecorator={ <> {section?.content?.length} } > {section.name} {section.content?.map(chore => ( ))} ) })} )} {archivedChores === null && ( )} {archivedChores !== null && ( <> {archivedChores?.length} } > Archived {archivedChores?.map(chore => ( ))} )} { 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() ) }) }, 'Due Later': function (chores) { return chores.filter(chore => { return ( new Date(chore.nextDueDate) > new Date(Date.now() + 24 * 60 * 60 * 1000) ) }) }, '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