2024-06-30 18:55:39 -04:00
|
|
|
import {
|
2024-09-06 01:22:10 -04:00
|
|
|
Add,
|
|
|
|
CancelRounded,
|
|
|
|
EditCalendar,
|
2024-12-11 20:46:02 -05:00
|
|
|
ExpandCircleDown,
|
2024-09-06 01:22:10 -04:00
|
|
|
FilterAlt,
|
2024-11-28 21:21:23 -05:00
|
|
|
PriorityHigh,
|
2024-12-22 15:37:45 -05:00
|
|
|
Sort,
|
2024-11-28 21:21:23 -05:00
|
|
|
Style,
|
2024-12-15 18:10:50 -05:00
|
|
|
Unarchive,
|
2024-09-06 01:22:10 -04:00
|
|
|
} from '@mui/icons-material'
|
|
|
|
import {
|
2024-12-11 20:46:02 -05:00
|
|
|
Accordion,
|
|
|
|
AccordionDetails,
|
|
|
|
AccordionGroup,
|
2024-06-30 18:55:39 -04:00
|
|
|
Box,
|
2024-11-28 21:21:23 -05:00
|
|
|
Button,
|
2024-09-06 01:22:10 -04:00
|
|
|
Chip,
|
2024-06-30 18:55:39 -04:00
|
|
|
Container,
|
2024-12-11 20:46:02 -05:00
|
|
|
Divider,
|
2024-06-30 18:55:39 -04:00
|
|
|
IconButton,
|
2024-07-06 03:49:51 -04:00
|
|
|
Input,
|
2024-06-30 18:55:39 -04:00
|
|
|
List,
|
|
|
|
Menu,
|
|
|
|
MenuItem,
|
|
|
|
Snackbar,
|
|
|
|
Typography,
|
|
|
|
} from '@mui/joy'
|
2024-07-06 03:49:51 -04:00
|
|
|
import Fuse from 'fuse.js'
|
2024-06-30 18:55:39 -04:00
|
|
|
import { useContext, useEffect, useRef, useState } from 'react'
|
|
|
|
import { useNavigate } from 'react-router-dom'
|
|
|
|
import { UserContext } from '../../contexts/UserContext'
|
2024-11-23 20:23:59 -05:00
|
|
|
import { useChores } from '../../queries/ChoreQueries'
|
2024-12-15 18:10:50 -05:00
|
|
|
import {
|
|
|
|
GetAllUsers,
|
|
|
|
GetArchivedChores,
|
2024-12-26 02:13:47 -05:00
|
|
|
GetChores,
|
2024-12-15 18:10:50 -05:00
|
|
|
GetUserProfile,
|
|
|
|
} from '../../utils/Fetcher'
|
2024-11-28 21:21:23 -05:00
|
|
|
import Priorities from '../../utils/Priorities'
|
2024-07-16 19:37:18 -04:00
|
|
|
import LoadingComponent from '../components/Loading'
|
2024-11-23 20:23:59 -05:00
|
|
|
import { useLabels } from '../Labels/LabelQueries'
|
2024-06-30 18:55:39 -04:00
|
|
|
import ChoreCard from './ChoreCard'
|
2024-11-28 21:21:23 -05:00
|
|
|
import IconButtonWithMenu from './IconButtonWithMenu'
|
2024-06-30 18:55:39 -04:00
|
|
|
|
2024-12-26 02:13:47 -05:00
|
|
|
import { canScheduleNotification, scheduleChoreNotification } from './LocalNotificationScheduler'
|
|
|
|
import NotificationAccessSnackbar from './NotificationAccessSnackbar'
|
|
|
|
|
2024-06-30 18:55:39 -04:00
|
|
|
const MyChores = () => {
|
|
|
|
const { userProfile, setUserProfile } = useContext(UserContext)
|
|
|
|
const [isSnackbarOpen, setIsSnackbarOpen] = useState(false)
|
|
|
|
const [snackBarMessage, setSnackBarMessage] = useState(null)
|
|
|
|
const [chores, setChores] = useState([])
|
2024-12-15 18:10:50 -05:00
|
|
|
const [archivedChores, setArchivedChores] = useState(null)
|
2024-06-30 18:55:39 -04:00
|
|
|
const [filteredChores, setFilteredChores] = useState([])
|
|
|
|
const [selectedFilter, setSelectedFilter] = useState('All')
|
2024-12-11 20:46:02 -05:00
|
|
|
const [choreSections, setChoreSections] = useState([])
|
2024-12-22 15:37:45 -05:00
|
|
|
const [selectedChoreSection, setSelectedChoreSection] = useState('due_date')
|
2024-12-11 20:46:02 -05:00
|
|
|
const [openChoreSections, setOpenChoreSections] = useState({})
|
2024-07-06 03:49:51 -04:00
|
|
|
const [searchTerm, setSearchTerm] = useState('')
|
2024-06-30 18:55:39 -04:00
|
|
|
const [performers, setPerformers] = useState([])
|
|
|
|
const [anchorEl, setAnchorEl] = useState(null)
|
|
|
|
const menuRef = useRef(null)
|
|
|
|
const Navigate = useNavigate()
|
2024-11-23 20:23:59 -05:00
|
|
|
const { data: userLabels, isLoading: userLabelsLoading } = useLabels()
|
|
|
|
const { data: choresData, isLoading: choresLoading } = useChores()
|
2024-06-30 18:55:39 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-12-11 20:46:02 -05:00
|
|
|
const sectionSorter = (t, chores) => {
|
|
|
|
// sort by priority then due date:
|
|
|
|
chores.sort((a, b) => {
|
|
|
|
// no priority is lowest priority:
|
|
|
|
if (a.priority === 0) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
if (a.priority !== b.priority) {
|
|
|
|
return a.priority - b.priority
|
|
|
|
}
|
|
|
|
if (a.nextDueDate === null) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
if (b.nextDueDate === null) {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
return new Date(a.nextDueDate) - new Date(b.nextDueDate)
|
|
|
|
})
|
|
|
|
|
|
|
|
var groups = []
|
|
|
|
switch (t) {
|
|
|
|
case 'due_date':
|
|
|
|
var groupRaw = {
|
|
|
|
Today: [],
|
|
|
|
'In a week': [],
|
|
|
|
'This month': [],
|
|
|
|
Later: [],
|
|
|
|
Overdue: [],
|
|
|
|
Anytime: [],
|
|
|
|
}
|
|
|
|
chores.forEach(chore => {
|
|
|
|
if (chore.nextDueDate === null) {
|
|
|
|
groupRaw['Anytime'].push(chore)
|
|
|
|
} else if (new Date(chore.nextDueDate) < new Date()) {
|
|
|
|
groupRaw['Overdue'].push(chore)
|
|
|
|
} else if (
|
|
|
|
new Date(chore.nextDueDate).toDateString() ===
|
|
|
|
new Date().toDateString()
|
|
|
|
) {
|
|
|
|
groupRaw['Today'].push(chore)
|
|
|
|
} else if (
|
|
|
|
new Date(chore.nextDueDate) <
|
|
|
|
new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) &&
|
|
|
|
new Date(chore.nextDueDate) > new Date()
|
|
|
|
) {
|
|
|
|
groupRaw['In a week'].push(chore)
|
|
|
|
} else if (
|
|
|
|
new Date(chore.nextDueDate).getMonth() === new Date().getMonth()
|
|
|
|
) {
|
|
|
|
groupRaw['This month'].push(chore)
|
|
|
|
} else {
|
|
|
|
groupRaw['Later'].push(chore)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
groups = [
|
|
|
|
{ name: 'Overdue', content: groupRaw['Overdue'] },
|
|
|
|
{ name: 'Today', content: groupRaw['Today'] },
|
|
|
|
{ name: 'In a week', content: groupRaw['In a week'] },
|
|
|
|
{ name: 'This month', content: groupRaw['This month'] },
|
|
|
|
{ name: 'Later', content: groupRaw['Later'] },
|
|
|
|
{ name: 'Anytime', content: groupRaw['Anytime'] },
|
|
|
|
]
|
|
|
|
break
|
|
|
|
case 'priority':
|
|
|
|
groupRaw = {
|
|
|
|
p1: [],
|
|
|
|
p2: [],
|
|
|
|
p3: [],
|
|
|
|
p4: [],
|
|
|
|
no_priority: [],
|
|
|
|
}
|
|
|
|
chores.forEach(chore => {
|
|
|
|
switch (chore.priority) {
|
|
|
|
case 1:
|
|
|
|
groupRaw['p1'].push(chore)
|
|
|
|
break
|
|
|
|
case 2:
|
|
|
|
groupRaw['p2'].push(chore)
|
|
|
|
break
|
|
|
|
case 3:
|
|
|
|
groupRaw['p3'].push(chore)
|
|
|
|
break
|
|
|
|
case 4:
|
|
|
|
groupRaw['p4'].push(chore)
|
|
|
|
break
|
2024-12-22 11:03:06 -05:00
|
|
|
default:
|
|
|
|
groupRaw['no_priority'].push(chore)
|
|
|
|
break
|
2024-12-11 20:46:02 -05:00
|
|
|
}
|
|
|
|
})
|
2024-12-22 11:03:06 -05:00
|
|
|
groups = [
|
|
|
|
{ name: 'Priority 1', content: groupRaw['p1'] },
|
|
|
|
{ name: 'Priority 2', content: groupRaw['p2'] },
|
|
|
|
{ name: 'Priority 3', content: groupRaw['p3'] },
|
|
|
|
{ name: 'Priority 4', content: groupRaw['p4'] },
|
|
|
|
{ name: 'No Priority', content: groupRaw['no_priority'] },
|
|
|
|
]
|
2024-12-11 20:46:02 -05:00
|
|
|
break
|
|
|
|
case 'labels':
|
|
|
|
groupRaw = {}
|
2024-12-22 11:03:06 -05:00
|
|
|
var labels = {}
|
2024-12-11 20:46:02 -05:00
|
|
|
chores.forEach(chore => {
|
|
|
|
chore.labelsV2.forEach(label => {
|
2024-12-22 11:03:06 -05:00
|
|
|
labels[label.id] = label
|
2024-12-11 20:46:02 -05:00
|
|
|
if (groupRaw[label.id] === undefined) {
|
|
|
|
groupRaw[label.id] = []
|
|
|
|
}
|
|
|
|
groupRaw[label.id].push(chore)
|
|
|
|
})
|
|
|
|
})
|
2024-12-22 11:03:06 -05:00
|
|
|
groups = Object.keys(groupRaw).map(key => {
|
|
|
|
return {
|
|
|
|
name: labels[key].name,
|
|
|
|
content: groupRaw[key],
|
|
|
|
}
|
|
|
|
})
|
|
|
|
groups.sort((a, b) => {
|
|
|
|
a.name < b.name ? 1 : -1
|
|
|
|
})
|
2024-12-11 20:46:02 -05:00
|
|
|
}
|
|
|
|
return groups
|
|
|
|
}
|
2024-06-30 18:55:39 -04:00
|
|
|
useEffect(() => {
|
|
|
|
|
2024-12-26 02:13:47 -05:00
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-06-30 18:55:39 -04:00
|
|
|
|
2024-12-26 02:13:47 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
// }
|
2024-06-30 18:55:39 -04:00
|
|
|
}, [])
|
2024-11-23 20:23:59 -05:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (choresData) {
|
|
|
|
const sortedChores = choresData.res.sort(choreSorter)
|
|
|
|
setChores(sortedChores)
|
|
|
|
setFilteredChores(sortedChores)
|
2024-12-11 20:46:02 -05:00
|
|
|
const sections = sectionSorter('due_date', sortedChores)
|
|
|
|
setChoreSections(sections)
|
|
|
|
setOpenChoreSections(
|
|
|
|
Object.keys(sections).reduce((acc, key) => {
|
|
|
|
acc[key] = true
|
|
|
|
return acc
|
|
|
|
}, {}),
|
|
|
|
)
|
2024-11-23 20:23:59 -05:00
|
|
|
}
|
|
|
|
}, [choresData, choresLoading])
|
|
|
|
|
2024-06-30 18:55:39 -04:00
|
|
|
useEffect(() => {
|
|
|
|
document.addEventListener('mousedown', handleMenuOutsideClick)
|
|
|
|
return () => {
|
|
|
|
document.removeEventListener('mousedown', handleMenuOutsideClick)
|
|
|
|
}
|
|
|
|
}, [anchorEl])
|
2024-12-15 18:10:50 -05:00
|
|
|
|
2024-06-30 18:55:39 -04:00
|
|
|
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)
|
|
|
|
}
|
2024-11-28 21:21:23 -05:00
|
|
|
|
|
|
|
const handleLabelFiltering = chipClicked => {
|
|
|
|
if (chipClicked.label) {
|
|
|
|
const label = chipClicked.label
|
|
|
|
const labelFiltered = [...chores].filter(chore =>
|
2024-12-05 21:28:15 -05:00
|
|
|
chore.labelsV2.some(
|
|
|
|
l => l.id === label.id && l.created_by === label.created_by,
|
|
|
|
),
|
2024-11-28 21:21:23 -05:00
|
|
|
)
|
|
|
|
setFilteredChores(labelFiltered)
|
|
|
|
setSelectedFilter('Label: ' + label.name)
|
|
|
|
} else if (chipClicked.priority) {
|
|
|
|
const priority = chipClicked.priority
|
|
|
|
const priorityFiltered = chores.filter(
|
|
|
|
chore => chore.priority === priority,
|
|
|
|
)
|
|
|
|
setFilteredChores(priorityFiltered)
|
|
|
|
setSelectedFilter('Priority: ' + priority)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-30 18:55:39 -04:00
|
|
|
const handleChoreUpdated = (updatedChore, event) => {
|
2024-12-21 00:15:44 -05:00
|
|
|
var newChores = chores.map(chore => {
|
2024-06-30 18:55:39 -04:00
|
|
|
if (chore.id === updatedChore.id) {
|
|
|
|
return updatedChore
|
|
|
|
}
|
|
|
|
return chore
|
|
|
|
})
|
|
|
|
|
2024-12-21 00:15:44 -05:00
|
|
|
var newFilteredChores = filteredChores.map(chore => {
|
2024-06-30 18:55:39 -04:00
|
|
|
if (chore.id === updatedChore.id) {
|
|
|
|
return updatedChore
|
|
|
|
}
|
|
|
|
return chore
|
|
|
|
})
|
2024-12-21 00:15:44 -05:00
|
|
|
if (event === 'archive') {
|
|
|
|
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),
|
|
|
|
)
|
|
|
|
}
|
2024-06-30 18:55:39 -04:00
|
|
|
setChores(newChores)
|
|
|
|
setFilteredChores(newFilteredChores)
|
2024-12-15 18:10:50 -05:00
|
|
|
setChoreSections(sectionSorter('due_date', newChores))
|
2024-12-21 00:15:44 -05:00
|
|
|
|
2024-06-30 18:55:39 -04:00
|
|
|
switch (event) {
|
|
|
|
case 'completed':
|
|
|
|
setSnackBarMessage('Completed')
|
|
|
|
break
|
|
|
|
case 'skipped':
|
|
|
|
setSnackBarMessage('Skipped')
|
|
|
|
break
|
|
|
|
case 'rescheduled':
|
|
|
|
setSnackBarMessage('Rescheduled')
|
|
|
|
break
|
2024-12-21 00:15:44 -05:00
|
|
|
case 'unarchive':
|
|
|
|
setSnackBarMessage('Unarchive')
|
|
|
|
break
|
|
|
|
case 'archive':
|
|
|
|
setSnackBarMessage('Archived')
|
|
|
|
break
|
2024-06-30 18:55:39 -04:00
|
|
|
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)
|
2024-12-15 18:10:50 -05:00
|
|
|
setChoreSections(sectionSorter('due_date', newChores))
|
2024-06-30 18:55:39 -04:00
|
|
|
}
|
|
|
|
|
2024-07-06 03:49:51 -04:00
|
|
|
const searchOptions = {
|
|
|
|
// keys to search in
|
2024-11-23 20:23:59 -05:00
|
|
|
keys: ['name', 'raw_label'],
|
2024-07-06 03:49:51 -04:00
|
|
|
includeScore: true, // Optional: if you want to see how well each result matched the search term
|
|
|
|
isCaseSensitive: false,
|
|
|
|
findAllMatches: true,
|
|
|
|
}
|
2024-11-23 20:23:59 -05:00
|
|
|
|
|
|
|
const fuse = new Fuse(
|
|
|
|
chores.map(c => ({
|
|
|
|
...c,
|
2024-12-05 21:28:15 -05:00
|
|
|
raw_label: c.labelsV2.map(c => c.name).join(' '),
|
2024-11-23 20:23:59 -05:00
|
|
|
})),
|
|
|
|
searchOptions,
|
|
|
|
)
|
2024-07-06 03:49:51 -04:00
|
|
|
|
|
|
|
const handleSearchChange = e => {
|
2024-11-28 21:21:23 -05:00
|
|
|
if (selectedFilter !== 'All') {
|
|
|
|
setSelectedFilter('All')
|
|
|
|
}
|
2024-07-06 03:49:51 -04:00
|
|
|
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))
|
|
|
|
}
|
2024-12-26 02:13:47 -05:00
|
|
|
|
2024-11-23 20:23:59 -05:00
|
|
|
if (
|
|
|
|
userProfile === null ||
|
|
|
|
userLabelsLoading ||
|
|
|
|
performers.length === 0 ||
|
|
|
|
choresLoading
|
2024-12-26 02:13:47 -05:00
|
|
|
) {
|
2024-07-16 19:37:18 -04:00
|
|
|
return <LoadingComponent />
|
2024-06-30 18:55:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Container maxWidth='md'>
|
2024-09-06 01:22:10 -04:00
|
|
|
<Box
|
2024-06-30 18:55:39 -04:00
|
|
|
sx={{
|
2024-09-06 01:22:10 -04:00
|
|
|
display: 'flex',
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
alignContent: 'center',
|
|
|
|
alignItems: 'center',
|
2024-11-28 21:21:23 -05:00
|
|
|
gap: 0.5,
|
2024-06-30 18:55:39 -04:00
|
|
|
}}
|
|
|
|
>
|
2024-07-06 03:49:51 -04:00
|
|
|
<Input
|
|
|
|
placeholder='Search'
|
|
|
|
value={searchTerm}
|
2024-09-06 01:22:10 -04:00
|
|
|
fullWidth
|
2024-07-06 03:49:51 -04:00
|
|
|
sx={{
|
|
|
|
mt: 1,
|
|
|
|
mb: 1,
|
2024-09-06 01:22:10 -04:00
|
|
|
borderRadius: 24,
|
|
|
|
height: 24,
|
2024-07-06 03:49:51 -04:00
|
|
|
borderColor: 'text.disabled',
|
|
|
|
padding: 1,
|
|
|
|
}}
|
|
|
|
onChange={handleSearchChange}
|
|
|
|
endDecorator={
|
2024-07-06 13:27:41 -04:00
|
|
|
searchTerm && (
|
|
|
|
<CancelRounded
|
|
|
|
onClick={() => {
|
|
|
|
setSearchTerm('')
|
|
|
|
setFilteredChores(chores)
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)
|
2024-07-06 03:49:51 -04:00
|
|
|
}
|
|
|
|
/>
|
2024-11-28 21:21:23 -05:00
|
|
|
<IconButtonWithMenu
|
|
|
|
icon={<PriorityHigh />}
|
2024-12-22 15:37:45 -05:00
|
|
|
title='Filter by Priority'
|
2024-11-28 21:21:23 -05:00
|
|
|
options={Priorities}
|
|
|
|
selectedItem={selectedFilter}
|
|
|
|
onItemSelect={selected => {
|
|
|
|
handleLabelFiltering({ priority: selected.value })
|
|
|
|
}}
|
|
|
|
mouseClickHandler={handleMenuOutsideClick}
|
|
|
|
isActive={selectedFilter.startsWith('Priority: ')}
|
|
|
|
/>
|
|
|
|
<IconButtonWithMenu
|
|
|
|
icon={<Style />}
|
2024-12-05 21:28:15 -05:00
|
|
|
// TODO : this need simplification we want to display both user labels and chore labels
|
|
|
|
// that why we are merging them here.
|
|
|
|
// we also filter out the labels that user created as those will be part of user labels
|
2024-12-22 15:37:45 -05:00
|
|
|
title='Filter by Label'
|
2024-12-05 21:28:15 -05:00
|
|
|
options={[
|
|
|
|
...userLabels,
|
|
|
|
...chores
|
|
|
|
.map(c => c.labelsV2)
|
|
|
|
.flat()
|
|
|
|
.filter(l => l.created_by !== userProfile.id)
|
|
|
|
.map(l => {
|
|
|
|
// if user created it don't show it:
|
|
|
|
return {
|
|
|
|
...l,
|
|
|
|
name: l.name + ' (Shared Label)',
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
]}
|
2024-11-28 21:21:23 -05:00
|
|
|
selectedItem={selectedFilter}
|
|
|
|
onItemSelect={selected => {
|
|
|
|
handleLabelFiltering({ label: selected })
|
|
|
|
}}
|
|
|
|
isActive={selectedFilter.startsWith('Label: ')}
|
|
|
|
mouseClickHandler={handleMenuOutsideClick}
|
|
|
|
useChips
|
|
|
|
/>
|
|
|
|
|
2024-09-06 01:22:10 -04:00
|
|
|
<IconButton
|
|
|
|
onClick={handleFilterMenuOpen}
|
|
|
|
variant='outlined'
|
|
|
|
color={
|
2024-11-28 21:21:23 -05:00
|
|
|
selectedFilter && FILTERS[selectedFilter] && selectedFilter != 'All'
|
|
|
|
? 'primary'
|
|
|
|
: 'neutral'
|
2024-09-06 01:22:10 -04:00
|
|
|
}
|
|
|
|
size='sm'
|
|
|
|
sx={{
|
|
|
|
height: 24,
|
|
|
|
borderRadius: 24,
|
|
|
|
}}
|
|
|
|
>
|
2024-11-28 21:21:23 -05:00
|
|
|
<FilterAlt />
|
2024-09-06 01:22:10 -04:00
|
|
|
</IconButton>
|
|
|
|
<List
|
|
|
|
orientation='horizontal'
|
|
|
|
wrap
|
|
|
|
sx={{
|
|
|
|
mt: 0.2,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Menu
|
|
|
|
ref={menuRef}
|
|
|
|
anchorEl={anchorEl}
|
|
|
|
open={Boolean(anchorEl)}
|
|
|
|
onClose={handleFilterMenuClose}
|
|
|
|
>
|
2024-11-28 21:21:23 -05:00
|
|
|
{Object.keys(FILTERS).map((filter, index) => (
|
2024-09-06 01:22:10 -04:00
|
|
|
<MenuItem
|
2024-11-28 21:21:23 -05:00
|
|
|
key={`filter-list-${filter}-${index}`}
|
2024-09-06 01:22:10 -04:00
|
|
|
onClick={() => {
|
|
|
|
const filterFunction = FILTERS[filter]
|
|
|
|
const filteredChores =
|
|
|
|
filterFunction.length === 2
|
|
|
|
? filterFunction(chores, userProfile.id)
|
|
|
|
: filterFunction(chores)
|
|
|
|
setFilteredChores(filteredChores)
|
|
|
|
setSelectedFilter(filter)
|
|
|
|
handleFilterMenuClose()
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{filter}
|
|
|
|
<Chip color={selectedFilter === filter ? 'primary' : 'neutral'}>
|
|
|
|
{FILTERS[filter].length === 2
|
|
|
|
? FILTERS[filter](chores, userProfile.id).length
|
|
|
|
: FILTERS[filter](chores).length}
|
|
|
|
</Chip>
|
|
|
|
</MenuItem>
|
|
|
|
))}
|
2024-11-28 21:21:23 -05:00
|
|
|
{selectedFilter.startsWith('Label: ') ||
|
|
|
|
(selectedFilter.startsWith('Priority: ') && (
|
|
|
|
<MenuItem
|
|
|
|
key={`filter-list-cancel-all-filters`}
|
|
|
|
onClick={() => {
|
|
|
|
setFilteredChores(chores)
|
|
|
|
setSelectedFilter('All')
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
Cancel All Filters
|
|
|
|
</MenuItem>
|
|
|
|
))}
|
2024-09-06 01:22:10 -04:00
|
|
|
</Menu>
|
|
|
|
</List>
|
2024-12-22 15:37:45 -05:00
|
|
|
<Divider orientation='vertical' />
|
|
|
|
<IconButtonWithMenu
|
|
|
|
title='Group by'
|
|
|
|
icon={<Sort />}
|
|
|
|
options={[
|
|
|
|
{ name: 'Due Date', value: 'due_date' },
|
|
|
|
{ name: 'Priority', value: 'priority' },
|
|
|
|
{ name: 'Labels', value: 'labels' },
|
|
|
|
]}
|
|
|
|
selectedItem={selectedChoreSection}
|
|
|
|
onItemSelect={selected => {
|
|
|
|
const section = sectionSorter(selected.value, chores)
|
|
|
|
setChoreSections(section)
|
|
|
|
setSelectedChoreSection(selected.value)
|
|
|
|
setFilteredChores(chores)
|
|
|
|
setSelectedFilter('All')
|
|
|
|
}}
|
|
|
|
mouseClickHandler={handleMenuOutsideClick}
|
|
|
|
/>
|
2024-07-06 03:49:51 -04:00
|
|
|
</Box>
|
2024-11-28 21:21:23 -05:00
|
|
|
{selectedFilter !== 'All' && (
|
|
|
|
<Chip
|
|
|
|
level='title-md'
|
|
|
|
gutterBottom
|
|
|
|
color='warning'
|
|
|
|
label={selectedFilter}
|
|
|
|
onDelete={() => {
|
|
|
|
setFilteredChores(chores)
|
|
|
|
setSelectedFilter('All')
|
|
|
|
}}
|
|
|
|
endDecorator={<CancelRounded />}
|
|
|
|
onClick={() => {
|
|
|
|
setFilteredChores(chores)
|
|
|
|
setSelectedFilter('All')
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
Current Filter: {selectedFilter}
|
|
|
|
</Chip>
|
|
|
|
)}
|
2024-06-30 18:55:39 -04:00
|
|
|
{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>
|
2024-11-28 21:21:23 -05:00
|
|
|
{chores.length > 0 && (
|
|
|
|
<>
|
|
|
|
<Button
|
2024-12-11 20:46:02 -05:00
|
|
|
onClick={() => {
|
|
|
|
setFilteredChores(chores)
|
|
|
|
setSearchTerm('')
|
|
|
|
}}
|
2024-11-28 21:21:23 -05:00
|
|
|
variant='outlined'
|
|
|
|
color='neutral'
|
|
|
|
>
|
|
|
|
Reset filters
|
|
|
|
</Button>
|
|
|
|
</>
|
|
|
|
)}
|
2024-06-30 18:55:39 -04:00
|
|
|
</Box>
|
|
|
|
)}
|
2024-12-11 20:46:02 -05:00
|
|
|
{(searchTerm?.length > 0 || selectedFilter !== 'All') &&
|
|
|
|
filteredChores.map(chore => (
|
|
|
|
<ChoreCard
|
|
|
|
key={`filtered-${chore.id} `}
|
|
|
|
chore={chore}
|
|
|
|
onChoreUpdate={handleChoreUpdated}
|
|
|
|
onChoreRemove={handleChoreDeleted}
|
|
|
|
performers={performers}
|
|
|
|
userLabels={userLabels}
|
|
|
|
onChipClick={handleLabelFiltering}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
{searchTerm.length === 0 && selectedFilter === 'All' && (
|
|
|
|
<AccordionGroup transition='0.2s ease' disableDivider>
|
|
|
|
{choreSections.map((section, index) => {
|
|
|
|
if (section.content.length === 0) return null
|
|
|
|
return (
|
|
|
|
<Accordion
|
|
|
|
title={section.name}
|
|
|
|
key={section.name + index}
|
|
|
|
sx={{
|
|
|
|
my: 0,
|
|
|
|
}}
|
|
|
|
expanded={Boolean(openChoreSections[index])}
|
|
|
|
>
|
|
|
|
<Divider orientation='horizontal'>
|
|
|
|
<Chip
|
|
|
|
variant='soft'
|
|
|
|
color='neutral'
|
|
|
|
size='md'
|
|
|
|
onClick={() => {
|
|
|
|
if (openChoreSections[index]) {
|
|
|
|
const newOpenChoreSections = { ...openChoreSections }
|
|
|
|
delete newOpenChoreSections[index]
|
|
|
|
setOpenChoreSections(newOpenChoreSections)
|
|
|
|
} else {
|
|
|
|
setOpenChoreSections({
|
|
|
|
...openChoreSections,
|
|
|
|
[index]: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
endDecorator={
|
|
|
|
openChoreSections[index] ? (
|
|
|
|
<ExpandCircleDown
|
|
|
|
color='primary'
|
|
|
|
sx={{ transform: 'rotate(180deg)' }}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<ExpandCircleDown color='primary' />
|
|
|
|
)
|
|
|
|
}
|
|
|
|
startDecorator={
|
|
|
|
<>
|
|
|
|
<Chip color='primary' size='sm' variant='soft'>
|
|
|
|
{section?.content?.length}
|
|
|
|
</Chip>
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
{section.name}
|
|
|
|
</Chip>
|
|
|
|
</Divider>
|
|
|
|
<AccordionDetails
|
|
|
|
sx={{
|
|
|
|
flexDirection: 'column',
|
|
|
|
my: 0,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{section.content?.map(chore => (
|
|
|
|
<ChoreCard
|
|
|
|
key={chore.id}
|
|
|
|
chore={chore}
|
|
|
|
onChoreUpdate={handleChoreUpdated}
|
|
|
|
onChoreRemove={handleChoreDeleted}
|
|
|
|
performers={performers}
|
|
|
|
userLabels={userLabels}
|
|
|
|
onChipClick={handleLabelFiltering}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</AccordionDetails>
|
|
|
|
</Accordion>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</AccordionGroup>
|
|
|
|
)}
|
2024-12-15 18:10:50 -05:00
|
|
|
<Box
|
|
|
|
sx={{
|
|
|
|
// center the button
|
|
|
|
justifyContent: 'center',
|
|
|
|
mt: 2,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{archivedChores === null && (
|
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
|
|
|
<Button
|
|
|
|
sx={{}}
|
|
|
|
onClick={() => {
|
|
|
|
GetArchivedChores()
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(data => {
|
|
|
|
setArchivedChores(data.res)
|
|
|
|
})
|
|
|
|
}}
|
|
|
|
variant='outlined'
|
|
|
|
color='neutral'
|
|
|
|
startDecorator={<Unarchive />}
|
|
|
|
>
|
|
|
|
Show Archived
|
|
|
|
</Button>
|
|
|
|
</Box>
|
|
|
|
)}
|
|
|
|
{archivedChores !== null && (
|
|
|
|
<>
|
|
|
|
<Divider orientation='horizontal'>
|
|
|
|
<Chip
|
|
|
|
variant='soft'
|
|
|
|
color='danger'
|
|
|
|
size='md'
|
|
|
|
startDecorator={
|
|
|
|
<>
|
|
|
|
<Chip color='danger' size='sm' variant='plain'>
|
|
|
|
{archivedChores?.length}
|
|
|
|
</Chip>
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
Archived
|
|
|
|
</Chip>
|
|
|
|
</Divider>
|
|
|
|
|
|
|
|
{archivedChores?.map(chore => (
|
|
|
|
<ChoreCard
|
|
|
|
key={chore.id}
|
|
|
|
chore={chore}
|
|
|
|
onChoreUpdate={handleChoreUpdated}
|
|
|
|
onChoreRemove={handleChoreDeleted}
|
|
|
|
performers={performers}
|
|
|
|
userLabels={userLabels}
|
|
|
|
onChipClick={handleLabelFiltering}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</Box>
|
2024-06-30 18:55:39 -04:00
|
|
|
<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,
|
|
|
|
}}
|
|
|
|
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>
|
2024-12-26 02:13:47 -05:00
|
|
|
<NotificationAccessSnackbar />
|
2024-06-30 18:55:39 -04:00
|
|
|
</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()
|
|
|
|
)
|
|
|
|
})
|
|
|
|
},
|
2024-11-28 21:21:23 -05:00
|
|
|
'Due Later': function (chores) {
|
|
|
|
return chores.filter(chore => {
|
|
|
|
return (
|
|
|
|
new Date(chore.nextDueDate) > new Date(Date.now() + 24 * 60 * 60 * 1000)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
},
|
2024-06-30 18:55:39 -04:00
|
|
|
'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
|