Add BottomSheetModal component and implement calendar view in MyChores
This commit is contained in:
224
src/components/common/BottomSheetModal.jsx
Normal file
224
src/components/common/BottomSheetModal.jsx
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
import { Close } from '@mui/icons-material'
|
||||||
|
import { IconButton, Modal, Sheet, Typography } from '@mui/joy'
|
||||||
|
import { forwardRef, useEffect, useState } from 'react'
|
||||||
|
import { Z_INDEX } from '../../constants/zIndex'
|
||||||
|
|
||||||
|
const BottomSheetModal = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
children,
|
||||||
|
title,
|
||||||
|
height = 'auto',
|
||||||
|
maxHeight = '90vh',
|
||||||
|
expandedHeight = '95vh',
|
||||||
|
backdropBlur = true,
|
||||||
|
showHandle = true,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false)
|
||||||
|
const [isClosing, setIsClosing] = useState(false)
|
||||||
|
const [internalOpen, setInternalOpen] = useState(open)
|
||||||
|
|
||||||
|
// Handle opening
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setInternalOpen(true)
|
||||||
|
setIsClosing(false)
|
||||||
|
}
|
||||||
|
}, [open])
|
||||||
|
|
||||||
|
// Handle closing with animation
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open && internalOpen) {
|
||||||
|
setIsClosing(true)
|
||||||
|
// Wait for animation to complete before hiding modal
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setInternalOpen(false)
|
||||||
|
setIsClosing(false)
|
||||||
|
setIsExpanded(false)
|
||||||
|
}, 250) // Match transition duration
|
||||||
|
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}
|
||||||
|
}, [open, internalOpen])
|
||||||
|
|
||||||
|
// Handle toggle expansion
|
||||||
|
const handleToggleExpansion = () => {
|
||||||
|
setIsExpanded(prev => !prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close on escape key
|
||||||
|
useEffect(() => {
|
||||||
|
const handleEscape = event => {
|
||||||
|
if (event.key === 'Escape' && internalOpen) {
|
||||||
|
onClose?.()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (internalOpen) {
|
||||||
|
document.addEventListener('keydown', handleEscape)
|
||||||
|
// Prevent body scroll when modal is open
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleEscape)
|
||||||
|
document.body.style.overflow = 'unset'
|
||||||
|
}
|
||||||
|
}, [internalOpen, onClose])
|
||||||
|
|
||||||
|
// Calculate current height
|
||||||
|
const currentHeight = isExpanded ? expandedHeight : height
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={internalOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
sx={{
|
||||||
|
'& .MuiModal-backdrop': {
|
||||||
|
backdropFilter: backdropBlur ? 'blur(3px)' : 'none',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
},
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
keepMounted
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Sheet
|
||||||
|
ref={ref}
|
||||||
|
sx={{
|
||||||
|
zIndex: Z_INDEX.MODAL_CONTENT,
|
||||||
|
width: '100%',
|
||||||
|
height: currentHeight,
|
||||||
|
maxHeight: isExpanded ? expandedHeight : maxHeight,
|
||||||
|
borderTopLeftRadius: 16,
|
||||||
|
borderTopRightRadius: 16,
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
p: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
transition:
|
||||||
|
'height 0.3s cubic-bezier(0.32, 0.72, 0, 1), max-height 0.3s cubic-bezier(0.32, 0.72, 0, 1), transform 0.3s cubic-bezier(0.32, 0.72, 0, 1)',
|
||||||
|
transform:
|
||||||
|
open && !isClosing ? 'translateY(0)' : 'translateY(100%)',
|
||||||
|
// Handle safe area on mobile devices
|
||||||
|
paddingBottom: 'env(safe-area-inset-bottom)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header Section with drag handle, title, and close button */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
backgroundColor: 'inherit',
|
||||||
|
borderTopLeftRadius: 16,
|
||||||
|
borderTopRightRadius: 16,
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Close button positioned absolutely in top-right */}
|
||||||
|
{showCloseButton && (
|
||||||
|
<IconButton
|
||||||
|
variant='soft'
|
||||||
|
color='neutral'
|
||||||
|
size='sm'
|
||||||
|
onClick={onClose}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 8,
|
||||||
|
right: 16,
|
||||||
|
zIndex: 1,
|
||||||
|
borderRadius: '50%',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
backgroundColor: 'neutral.softBg',
|
||||||
|
color: 'neutral.softColor',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'neutral.softHoverBg',
|
||||||
|
transform: 'scale(1.05)',
|
||||||
|
},
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Close fontSize='small' />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Drag Handle */}
|
||||||
|
{showHandle && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '12px 0 8px 0',
|
||||||
|
cursor: 'pointer',
|
||||||
|
userSelect: 'none',
|
||||||
|
}}
|
||||||
|
onClick={handleToggleExpansion}
|
||||||
|
title={isExpanded ? 'Collapse' : 'Expand'}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 30,
|
||||||
|
height: 4,
|
||||||
|
borderRadius: 2,
|
||||||
|
backgroundColor: 'var(--joy-palette-neutral-300)',
|
||||||
|
transition: 'background-color 0.2s ease',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Title Row */}
|
||||||
|
{title && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: showHandle ? '0 20px 16px 20px' : '16px 20px 16px 20px',
|
||||||
|
paddingRight: showCloseButton ? '60px' : '20px', // Add space for close button
|
||||||
|
minHeight: 24,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
level='title-lg'
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* Content area */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
overflow: 'auto',
|
||||||
|
padding: '0 20px 20px 20px',
|
||||||
|
minHeight: 0, // Important for flex child with overflow
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Sheet>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
BottomSheetModal.displayName = 'BottomSheetModal'
|
||||||
|
|
||||||
|
export default BottomSheetModal
|
||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
Add,
|
Add,
|
||||||
Archive,
|
Archive,
|
||||||
Bolt,
|
Bolt,
|
||||||
|
CalendarMonth,
|
||||||
CancelRounded,
|
CancelRounded,
|
||||||
CheckBox,
|
CheckBox,
|
||||||
CheckBoxOutlineBlank,
|
CheckBoxOutlineBlank,
|
||||||
@@ -51,12 +52,15 @@ import CompactChoreCard from './CompactChoreCard'
|
|||||||
import IconButtonWithMenu from './IconButtonWithMenu'
|
import IconButtonWithMenu from './IconButtonWithMenu'
|
||||||
import MultiSelectHelp from './MultiSelectHelp'
|
import MultiSelectHelp from './MultiSelectHelp'
|
||||||
|
|
||||||
|
import { useMediaQuery } from '@mui/material'
|
||||||
import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint'
|
import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint'
|
||||||
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
|
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
|
||||||
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
||||||
import { ChoreFilters, ChoresGrouper, ChoreSorter } from '../../utils/Chores'
|
import { ChoreFilters, ChoresGrouper, ChoreSorter } from '../../utils/Chores'
|
||||||
import { DeleteChore, MarkChoreComplete, SkipChore } from '../../utils/Fetcher'
|
import { DeleteChore, MarkChoreComplete, SkipChore } from '../../utils/Fetcher'
|
||||||
import TaskInput from '../components/AddTaskModal'
|
import TaskInput from '../components/AddTaskModal'
|
||||||
|
import CalendarDual from '../components/CalendarDual'
|
||||||
|
import CalendarMonthly from '../components/CalendarMonthly.jsx'
|
||||||
import {
|
import {
|
||||||
canScheduleNotification,
|
canScheduleNotification,
|
||||||
scheduleChoreNotification,
|
scheduleChoreNotification,
|
||||||
@@ -68,6 +72,7 @@ import SortAndGrouping from './SortAndGrouping'
|
|||||||
const MyChores = () => {
|
const MyChores = () => {
|
||||||
const { data: userProfile, isLoading: isUserProfileLoading } =
|
const { data: userProfile, isLoading: isUserProfileLoading } =
|
||||||
useUserProfile()
|
useUserProfile()
|
||||||
|
const isLargeScreen = useMediaQuery(theme => theme.breakpoints.up('md'))
|
||||||
const { showSuccess, showError, showWarning } = useNotification()
|
const { showSuccess, showError, showWarning } = useNotification()
|
||||||
const { impersonatedUser } = useImpersonateUser()
|
const { impersonatedUser } = useImpersonateUser()
|
||||||
const [chores, setChores] = useState([])
|
const [chores, setChores] = useState([])
|
||||||
@@ -93,9 +98,10 @@ const MyChores = () => {
|
|||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [performers, setPerformers] = useState([])
|
const [performers, setPerformers] = useState([])
|
||||||
const [anchorEl, setAnchorEl] = useState(null)
|
const [anchorEl, setAnchorEl] = useState(null)
|
||||||
const [isCompactView, setIsCompactView] = useState(
|
const [viewMode, setViewMode] = useState(
|
||||||
localStorage.getItem('choreCardViewMode') === 'compact',
|
localStorage.getItem('choreCardViewMode') || 'default',
|
||||||
)
|
)
|
||||||
|
const [selectedCalendarDate, setSelectedCalendarDate] = useState(new Date())
|
||||||
const menuRef = useRef(null)
|
const menuRef = useRef(null)
|
||||||
const Navigate = useNavigate()
|
const Navigate = useNavigate()
|
||||||
const { data: userLabels, isLoading: userLabelsLoading } = useLabels()
|
const { data: userLabels, isLoading: userLabelsLoading } = useLabels()
|
||||||
@@ -385,17 +391,27 @@ const MyChores = () => {
|
|||||||
const setSelectedChoreFilterWithCache = value => {
|
const setSelectedChoreFilterWithCache = value => {
|
||||||
setSelectedChoreFilter(value)
|
setSelectedChoreFilter(value)
|
||||||
localStorage.setItem('selectedChoreFilter', value)
|
localStorage.setItem('selectedChoreFilter', value)
|
||||||
|
// Clear selected calendar date when filters change
|
||||||
|
setSelectedCalendarDate(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleViewMode = () => {
|
const toggleViewMode = () => {
|
||||||
const newMode = !isCompactView
|
const modes = ['default', 'compact', 'calendar']
|
||||||
setIsCompactView(newMode)
|
const currentIndex = modes.indexOf(viewMode)
|
||||||
localStorage.setItem('choreCardViewMode', newMode ? 'compact' : 'default')
|
const nextIndex = (currentIndex + 1) % modes.length
|
||||||
|
const newMode = modes[nextIndex]
|
||||||
|
setViewMode(newMode)
|
||||||
|
localStorage.setItem('choreCardViewMode', newMode)
|
||||||
|
|
||||||
|
// Clear selected calendar date when switching away from calendar view
|
||||||
|
if (newMode !== 'calendar') {
|
||||||
|
setSelectedCalendarDate(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to render the appropriate card component
|
// Helper function to render the appropriate card component
|
||||||
const renderChoreCard = (chore, key) => {
|
const renderChoreCard = (chore, key) => {
|
||||||
const CardComponent = isCompactView ? CompactChoreCard : ChoreCard
|
const CardComponent = viewMode === 'compact' ? CompactChoreCard : ChoreCard
|
||||||
return (
|
return (
|
||||||
<CardComponent
|
<CardComponent
|
||||||
key={key || chore.id}
|
key={key || chore.id}
|
||||||
@@ -413,6 +429,25 @@ const MyChores = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to get filtered chores for display
|
||||||
|
const getFilteredChores = () => {
|
||||||
|
if (searchTerm?.length > 0 || searchFilter !== 'All') {
|
||||||
|
return filteredChores
|
||||||
|
}
|
||||||
|
return chores.filter(ChoreFilters(userProfile)[selectedChoreFilter])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get chores for a specific date
|
||||||
|
const getChoresForDate = date => {
|
||||||
|
const filteredChores = getFilteredChores()
|
||||||
|
return filteredChores.filter(chore => {
|
||||||
|
if (!chore.nextDueDate) return false
|
||||||
|
const choreDate = new Date(chore.nextDueDate).toLocaleDateString()
|
||||||
|
const selectedDate = date.toLocaleDateString()
|
||||||
|
return choreDate === selectedDate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const updateChores = newChore => {
|
const updateChores = newChore => {
|
||||||
const newChores = chores
|
const newChores = chores
|
||||||
newChores.push(newChore)
|
newChores.push(newChore)
|
||||||
@@ -463,6 +498,8 @@ const MyChores = () => {
|
|||||||
setFilteredChores(priorityFiltered)
|
setFilteredChores(priorityFiltered)
|
||||||
setSearchFilter('Priority: ' + priority)
|
setSearchFilter('Priority: ' + priority)
|
||||||
}
|
}
|
||||||
|
// Clear selected calendar date when filters change
|
||||||
|
setSelectedCalendarDate(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChoreUpdated = (updatedChore, event) => {
|
const handleChoreUpdated = (updatedChore, event) => {
|
||||||
@@ -607,18 +644,24 @@ const MyChores = () => {
|
|||||||
if (search === '') {
|
if (search === '') {
|
||||||
setFilteredChores(chores)
|
setFilteredChores(chores)
|
||||||
setSearchTerm('')
|
setSearchTerm('')
|
||||||
|
// Clear selected calendar date when search changes
|
||||||
|
setSelectedCalendarDate(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const term = search.toLowerCase()
|
const term = search.toLowerCase()
|
||||||
setSearchTerm(term)
|
setSearchTerm(term)
|
||||||
setFilteredChores(fuse.search(term).map(result => result.item))
|
setFilteredChores(fuse.search(term).map(result => result.item))
|
||||||
|
// Clear selected calendar date when search changes
|
||||||
|
setSelectedCalendarDate(null)
|
||||||
}
|
}
|
||||||
const handleSearchClose = () => {
|
const handleSearchClose = () => {
|
||||||
setSearchTerm('')
|
setSearchTerm('')
|
||||||
setFilteredChores(chores)
|
setFilteredChores(chores)
|
||||||
// remove the focus from the search input:
|
// remove the focus from the search input:
|
||||||
setSearchInputFocus(0)
|
setSearchInputFocus(0)
|
||||||
|
// Clear selected calendar date when search closes
|
||||||
|
setSelectedCalendarDate(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-select helper functions
|
// Multi-select helper functions
|
||||||
@@ -1065,10 +1108,20 @@ const MyChores = () => {
|
|||||||
}}
|
}}
|
||||||
onClick={toggleViewMode}
|
onClick={toggleViewMode}
|
||||||
title={
|
title={
|
||||||
isCompactView ? 'Switch to Card View' : 'Switch to Compact View'
|
viewMode === 'default'
|
||||||
|
? 'Switch to Compact View'
|
||||||
|
: viewMode === 'compact'
|
||||||
|
? 'Switch to Calendar View'
|
||||||
|
: 'Switch to Card View'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isCompactView ? <ViewModule /> : <ViewAgenda />}
|
{viewMode === 'default' ? (
|
||||||
|
<ViewAgenda />
|
||||||
|
) : viewMode === 'compact' ? (
|
||||||
|
<CalendarMonth />
|
||||||
|
) : (
|
||||||
|
<ViewModule />
|
||||||
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
{/* Multi-select Toggle Button */}
|
{/* Multi-select Toggle Button */}
|
||||||
@@ -1579,79 +1632,153 @@ const MyChores = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{(searchTerm?.length > 0 || searchFilter !== 'All') &&
|
{(searchTerm?.length > 0 || searchFilter !== 'All') &&
|
||||||
|
viewMode !== 'calendar' &&
|
||||||
filteredChores.map(chore =>
|
filteredChores.map(chore =>
|
||||||
renderChoreCard(chore, `filtered-${chore.id}`),
|
renderChoreCard(chore, `filtered-${chore.id}`),
|
||||||
)}
|
)}
|
||||||
{searchTerm.length === 0 && searchFilter === 'All' && (
|
{viewMode === 'calendar' && (
|
||||||
<AccordionGroup transition='0.2s ease' disableDivider>
|
<>
|
||||||
{choreSections.map((section, index) => {
|
{/* Calendar Monthly View */}
|
||||||
if (section.content.length === 0) return null
|
<Box sx={{ mb: 2 }}>
|
||||||
return (
|
{isLargeScreen ? (
|
||||||
<Accordion
|
<CalendarDual
|
||||||
key={section.name + index}
|
chores={getFilteredChores()}
|
||||||
sx={{
|
onDateChange={date => {
|
||||||
my: 0,
|
setSelectedCalendarDate(date)
|
||||||
px: 0,
|
|
||||||
}}
|
}}
|
||||||
expanded={Boolean(openChoreSections[index])}
|
/>
|
||||||
>
|
) : (
|
||||||
<Divider orientation='horizontal'>
|
<div className='calendar-dual'>
|
||||||
<Chip
|
<CalendarMonthly
|
||||||
variant='soft'
|
chores={getFilteredChores()}
|
||||||
color='neutral'
|
onDateChange={date => {
|
||||||
size='md'
|
setSelectedCalendarDate(date)
|
||||||
onClick={() => {
|
|
||||||
if (openChoreSections[index]) {
|
|
||||||
const newOpenChoreSections = {
|
|
||||||
...openChoreSections,
|
|
||||||
}
|
|
||||||
delete newOpenChoreSections[index]
|
|
||||||
setOpenChoreSectionsWithCache(newOpenChoreSections)
|
|
||||||
} else {
|
|
||||||
setOpenChoreSectionsWithCache({
|
|
||||||
...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',
|
|
||||||
['& > *']: {
|
|
||||||
// px: 0.5,
|
|
||||||
px: 0.5,
|
|
||||||
// pr: 0,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
{section.content?.map(chore => renderChoreCard(chore))}
|
</div>
|
||||||
</AccordionDetails>
|
)}
|
||||||
</Accordion>
|
</Box>
|
||||||
)
|
|
||||||
})}
|
{/* Selected Date Tasks */}
|
||||||
</AccordionGroup>
|
{selectedCalendarDate && (
|
||||||
|
<Box sx={{ mt: 2 }}>
|
||||||
|
<Typography level='title-md' gutterBottom>
|
||||||
|
Tasks for {selectedCalendarDate.toLocaleDateString()}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 1,
|
||||||
|
maxHeight: '400px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getChoresForDate(selectedCalendarDate).length === 0 ? (
|
||||||
|
<Typography
|
||||||
|
level='body-sm'
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
py: 2,
|
||||||
|
color: 'text.tertiary',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No tasks scheduled for this date
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
getChoresForDate(selectedCalendarDate).map(chore => (
|
||||||
|
<CompactChoreCard
|
||||||
|
key={`calendar-${chore.id}`}
|
||||||
|
chore={chore}
|
||||||
|
onChoreUpdate={handleChoreUpdated}
|
||||||
|
onChoreRemove={handleChoreDeleted}
|
||||||
|
performers={performers}
|
||||||
|
userLabels={userLabels}
|
||||||
|
onChipClick={handleLabelFiltering}
|
||||||
|
// Multi-select props
|
||||||
|
isMultiSelectMode={isMultiSelectMode}
|
||||||
|
isSelected={selectedChores.has(chore.id)}
|
||||||
|
onSelectionToggle={() => toggleChoreSelection(chore.id)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
{searchTerm.length === 0 &&
|
||||||
|
searchFilter === 'All' &&
|
||||||
|
viewMode !== 'calendar' && (
|
||||||
|
<AccordionGroup transition='0.2s ease' disableDivider>
|
||||||
|
{choreSections.map((section, index) => {
|
||||||
|
if (section.content.length === 0) return null
|
||||||
|
return (
|
||||||
|
<Accordion
|
||||||
|
key={section.name + index}
|
||||||
|
sx={{
|
||||||
|
my: 0,
|
||||||
|
px: 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]
|
||||||
|
setOpenChoreSectionsWithCache(newOpenChoreSections)
|
||||||
|
} else {
|
||||||
|
setOpenChoreSectionsWithCache({
|
||||||
|
...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',
|
||||||
|
['& > *']: {
|
||||||
|
// px: 0.5,
|
||||||
|
px: 0.5,
|
||||||
|
// pr: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{section.content?.map(chore => renderChoreCard(chore))}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</AccordionGroup>
|
||||||
|
)}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
// center the button
|
// center the button
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Box, Typography } from '@mui/joy'
|
import { Box, Typography } from '@mui/joy'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Calendar from 'react-calendar'
|
import Calendar from 'react-calendar'
|
||||||
// import 'react-calendar/dist/Calendar.css'
|
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
||||||
import { getPriorityColor, TASK_COLOR } from '../../utils/Colors'
|
import { getPriorityColor, TASK_COLOR } from '../../utils/Colors'
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { CalendarMonth } from '@mui/icons-material'
|
|||||||
import { Avatar, Box, Chip, Grid, Typography } from '@mui/joy'
|
import { Avatar, Box, Chip, Grid, Typography } from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
// import 'react-calendar/dist/Calendar.css'
|
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
||||||
import { getPriorityColor, TASK_COLOR } from '../../utils/Colors'
|
import { getPriorityColor, TASK_COLOR } from '../../utils/Colors'
|
||||||
|
|||||||
Reference in New Issue
Block a user