Add Ability to filter chores by priorities and labels, Add IconMenuButton
This commit is contained in:
parent
ce514cb43a
commit
3467722c89
4 changed files with 241 additions and 35 deletions
|
@ -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}
|
||||
|
|
|
@ -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:'
|
||||
|
|
102
src/views/Chores/IconButtonWithMenu.jsx
Normal file
102
src/views/Chores/IconButtonWithMenu.jsx
Normal 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
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue