Add Ability to filter chores by priorities and labels, Add IconMenuButton

This commit is contained in:
Mo Tarbin 2024-11-28 21:21:23 -05:00
parent ce514cb43a
commit 3467722c89
4 changed files with 241 additions and 35 deletions

View file

@ -103,12 +103,13 @@ const ChoreView = () => {
}
}, [chore, performers])
const handleUpdatePriority = priority => {
if (priority.value === 0) {
setChorePriority(null)
} else {
setChorePriority(priority)
}
UpdateChorePriority(choreId, priority.value)
UpdateChorePriority(choreId, priority.value).then(response => {
if (response.ok) {
response.json().then(data => {
setChorePriority(priority)
})
}
})
}
const generateInfoCards = chore => {
const cards = [
@ -265,7 +266,6 @@ const ChoreView = () => {
? `Due at ${moment(chore.nextDueDate).format('MM/DD/YYYY hh:mm A')}`
: 'N/A'}
</Chip>
{/* show each label : */}
{chore?.labelsV2?.map((label, index) => (
<Chip
key={index}

View file

@ -48,6 +48,7 @@ import {
UpdateChoreAssignee,
} from '../../utils/Fetcher'
import { getTextColorFromBackgroundColor } from '../../utils/LabelColors'
import Priorities from '../../utils/Priorities'
import { Fetch } from '../../utils/TokenManager'
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
import DateModal from '../Modals/Inputs/DateModal'
@ -62,6 +63,7 @@ const ChoreCard = ({
userLabels,
sx,
viewOnly,
onChipClick,
}) => {
const [activeUserId, setActiveUserId] = React.useState(0)
const [isChangeDueDateModalOpen, setIsChangeDueDateModalOpen] =
@ -504,6 +506,13 @@ const ChoreCard = ({
? 'warning'
: 'neutral'
}
startDecorator={
Priorities.find(p => p.value === chore.priority)?.icon
}
onClick={e => {
e.stopPropagation()
onChipClick({ priority: chore.priority })
}}
>
P{chore.priority}
</Chip>
@ -512,16 +521,22 @@ const ChoreCard = ({
return (
<Chip
variant='solid'
key={l.id}
key={`chorecard-${chore.id}-label-${l.id}`}
color='primary'
sx={{
position: 'relative',
ml: index === 0 ? 0 : 0.5,
top: 2,
zIndex: 1,
backgroundColor: l?.color,
backgroundColor: `${l?.color} !important`,
color: getTextColorFromBackgroundColor(l?.color),
// apply background color for th clickable button:
}}
// onClick={e => {
// e.stopPropagation()
// onChipClick({ label: l })
// }}
// startDecorator={getIconForLabel(label)}
>
@ -719,7 +734,9 @@ const ChoreCard = ({
handleAssigneChange(selected.id)
}}
/>
<ConfirmationModal config={confirmModelConfig} />
{confirmModelConfig?.isOpen && (
<ConfirmationModal config={confirmModelConfig} />
)}
<TextModal
isOpen={isCompleteWithNoteModalOpen}
title='Add note to attach to this completion:'

View file

@ -0,0 +1,102 @@
import { Chip, Menu, MenuItem } from '@mui/joy'
import IconButton from '@mui/joy/IconButton'
import React, { useEffect, useRef, useState } from 'react'
import { getTextColorFromBackgroundColor } from '../../utils/LabelColors'
const IconButtonWithMenu = ({
key,
icon,
options,
onItemSelect,
selectedItem,
setSelectedItem,
isActive,
useChips,
}) => {
const [anchorEl, setAnchorEl] = useState(null)
const menuRef = useRef(null)
const handleMenuOpen = event => {
setAnchorEl(event.currentTarget)
}
const handleMenuClose = () => {
setAnchorEl(null)
}
useEffect(() => {
document.addEventListener('mousedown', handleMenuOutsideClick)
return () => {
document.removeEventListener('mousedown', handleMenuOutsideClick)
}
}, [anchorEl])
const handleMenuOutsideClick = event => {
if (menuRef.current && !menuRef.current.contains(event.target)) {
handleMenuClose()
}
}
return (
<>
<IconButton
onClick={handleMenuOpen}
variant='outlined'
color={isActive ? 'primary' : 'neutral'}
size='sm'
sx={{
height: 24,
borderRadius: 24,
}}
>
{icon}
</IconButton>
<Menu
key={key}
ref={menuRef}
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
>
{options?.map(item => (
<MenuItem
key={`${key}-${item?.id}`}
onClick={() => {
onItemSelect(item)
setSelectedItem?.selectedItem(item.name)
handleMenuClose()
}}
>
{useChips ? (
<Chip
sx={{
backgroundColor: item.color ? item.color : null,
color: getTextColorFromBackgroundColor(item.color),
}}
>
{item.name}
</Chip>
) : (
<>
{item?.icon}
{item.name}
</>
)}
</MenuItem>
))}
{/* {selectedItem && selectedItem !== 'All' && (
<MenuItem
id={`${id}cancel-all-filters`}
onClick={() => {
onItemSelect(null)
setSelectedItem?.setSelectedItem('All')
}}
>
Cancel All Filters
</MenuItem>
)} */}
</Menu>
</>
)
}
export default IconButtonWithMenu

View file

@ -3,10 +3,12 @@ import {
CancelRounded,
EditCalendar,
FilterAlt,
FilterAltOff,
PriorityHigh,
Style,
} from '@mui/icons-material'
import {
Box,
Button,
Chip,
Container,
IconButton,
@ -23,9 +25,11 @@ import { useNavigate } from 'react-router-dom'
import { UserContext } from '../../contexts/UserContext'
import { useChores } from '../../queries/ChoreQueries'
import { GetAllUsers, 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'
const MyChores = () => {
const { userProfile, setUserProfile } = useContext(UserContext)
@ -122,6 +126,28 @@ const MyChores = () => {
const handleFilterMenuClose = () => {
setAnchorEl(null)
}
const handleLabelFiltering = chipClicked => {
console.log('chipClicked', chipClicked)
if (chipClicked.label) {
const label = chipClicked.label
const labelFiltered = [...chores].filter(chore =>
chore.labelsV2.some(l => l.id === label.id),
)
console.log('labelFiltered', labelFiltered)
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)
}
}
const handleChoreUpdated = (updatedChore, event) => {
const newChores = chores.map(chore => {
if (chore.id === updatedChore.id) {
@ -175,13 +201,16 @@ const MyChores = () => {
chores.map(c => ({
...c,
raw_label: c.labelsV2
.map(l => userLabels.find(x => (x.id = l.id)).name)
.map(l => userLabels.find(x => x.id === l.id).name)
.join(' '),
})),
searchOptions,
)
const handleSearchChange = e => {
if (selectedFilter !== 'All') {
setSelectedFilter('All')
}
const search = e.target.value
if (search === '') {
setFilteredChores(chores)
@ -209,7 +238,6 @@ const MyChores = () => {
My Chores
</Typography> */}
{/* <Sheet> */}
{/* Search box to filter */}
<Box
sx={{
@ -217,7 +245,7 @@ const MyChores = () => {
justifyContent: 'space-between',
alignContent: 'center',
alignItems: 'center',
gap: 1,
gap: 0.5,
}}
>
<Input
@ -245,11 +273,37 @@ const MyChores = () => {
)
}
/>
<IconButtonWithMenu
key={'icon-menu-labels-filter'}
icon={<PriorityHigh />}
options={Priorities}
selectedItem={selectedFilter}
onItemSelect={selected => {
handleLabelFiltering({ priority: selected.value })
}}
mouseClickHandler={handleMenuOutsideClick}
isActive={selectedFilter.startsWith('Priority: ')}
/>
<IconButtonWithMenu
key={'icon-menu-labels-filter'}
icon={<Style />}
options={userLabels}
selectedItem={selectedFilter}
onItemSelect={selected => {
handleLabelFiltering({ label: selected })
}}
isActive={selectedFilter.startsWith('Label: ')}
mouseClickHandler={handleMenuOutsideClick}
useChips
/>
<IconButton
onClick={handleFilterMenuOpen}
variant='outlined'
color={
selectedFilter && selectedFilter != 'All' ? 'primary' : 'neutral'
selectedFilter && FILTERS[selectedFilter] && selectedFilter != 'All'
? 'primary'
: 'neutral'
}
size='sm'
sx={{
@ -257,40 +311,24 @@ const MyChores = () => {
borderRadius: 24,
}}
>
{selectedFilter && selectedFilter != 'All' ? (
<FilterAltOff />
) : (
<FilterAlt />
)}
<FilterAlt />
</IconButton>
<List
orientation='horizontal'
wrap
sx={{
// '--List-gap': '8px',
// '--ListItem-radius': '20px',
// '--ListItem-minHeight': '32px',
// '--ListItem-gap': '4px',
mt: 0.2,
}}
>
{/* <Checkbox
key='checkboxAll'
label=''
disableIcon
overlay
size='sm'
/> */}
<Menu
ref={menuRef}
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleFilterMenuClose}
>
{Object.keys(FILTERS).map(filter => (
{Object.keys(FILTERS).map((filter, index) => (
<MenuItem
key={filter}
key={`filter-list-${filter}-${index}`}
onClick={() => {
const filterFunction = FILTERS[filter]
const filteredChores =
@ -310,10 +348,40 @@ const MyChores = () => {
</Chip>
</MenuItem>
))}
{selectedFilter.startsWith('Label: ') ||
(selectedFilter.startsWith('Priority: ') && (
<MenuItem
key={`filter-list-cancel-all-filters`}
onClick={() => {
setFilteredChores(chores)
setSelectedFilter('All')
}}
>
Cancel All Filters
</MenuItem>
))}
</Menu>
</List>
</Box>
{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>
)}
{/* </Sheet> */}
{filteredChores.length === 0 && (
<Box
@ -335,6 +403,17 @@ const MyChores = () => {
<Typography level='title-md' gutterBottom>
Nothing scheduled
</Typography>
{chores.length > 0 && (
<>
<Button
onClick={() => setFilteredChores(chores)}
variant='outlined'
color='neutral'
>
Reset filters
</Button>
</>
)}
</Box>
)}
@ -346,6 +425,7 @@ const MyChores = () => {
onChoreRemove={handleChoreDeleted}
performers={performers}
userLabels={userLabels}
onChipClick={handleLabelFiltering}
/>
))}
@ -421,6 +501,13 @@ const FILTERS = {
)
})
},
'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