Update Label to confirm deletion

Add More Filter to chores
Improve the Priority
This commit is contained in:
Mo Tarbin 2024-11-29 19:05:30 -05:00
commit d216a30eec
7 changed files with 280 additions and 44 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "donetick", "name": "donetick",
"private": true, "private": true,
"version": "0.1.79", "version": "0.1.80",
"type": "module", "type": "module",
"lint-staged": { "lint-staged": {
"*.{js,jsx,ts,tsx}": [ "*.{js,jsx,ts,tsx}": [

View file

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

View file

@ -48,6 +48,7 @@ import {
UpdateChoreAssignee, UpdateChoreAssignee,
} from '../../utils/Fetcher' } from '../../utils/Fetcher'
import { getTextColorFromBackgroundColor } from '../../utils/LabelColors' import { getTextColorFromBackgroundColor } from '../../utils/LabelColors'
import Priorities from '../../utils/Priorities'
import { Fetch } from '../../utils/TokenManager' import { Fetch } from '../../utils/TokenManager'
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
import DateModal from '../Modals/Inputs/DateModal' import DateModal from '../Modals/Inputs/DateModal'
@ -62,6 +63,7 @@ const ChoreCard = ({
userLabels, userLabels,
sx, sx,
viewOnly, viewOnly,
onChipClick,
}) => { }) => {
const [activeUserId, setActiveUserId] = React.useState(0) const [activeUserId, setActiveUserId] = React.useState(0)
const [isChangeDueDateModalOpen, setIsChangeDueDateModalOpen] = const [isChangeDueDateModalOpen, setIsChangeDueDateModalOpen] =
@ -504,6 +506,13 @@ const ChoreCard = ({
? 'warning' ? 'warning'
: 'neutral' : 'neutral'
} }
startDecorator={
Priorities.find(p => p.value === chore.priority)?.icon
}
onClick={e => {
e.stopPropagation()
onChipClick({ priority: chore.priority })
}}
> >
P{chore.priority} P{chore.priority}
</Chip> </Chip>
@ -512,16 +521,22 @@ const ChoreCard = ({
return ( return (
<Chip <Chip
variant='solid' variant='solid'
key={l.id} key={`chorecard-${chore.id}-label-${l.id}`}
color='primary' color='primary'
sx={{ sx={{
position: 'relative', position: 'relative',
ml: index === 0 ? 0 : 0.5, ml: index === 0 ? 0 : 0.5,
top: 2, top: 2,
zIndex: 1, zIndex: 1,
backgroundColor: l?.color, backgroundColor: `${l?.color} !important`,
color: getTextColorFromBackgroundColor(l?.color), color: getTextColorFromBackgroundColor(l?.color),
// apply background color for th clickable button:
}} }}
// onClick={e => {
// e.stopPropagation()
// onChipClick({ label: l })
// }}
// startDecorator={getIconForLabel(label)} // startDecorator={getIconForLabel(label)}
> >
@ -719,7 +734,9 @@ const ChoreCard = ({
handleAssigneChange(selected.id) handleAssigneChange(selected.id)
}} }}
/> />
{confirmModelConfig?.isOpen && (
<ConfirmationModal config={confirmModelConfig} /> <ConfirmationModal config={confirmModelConfig} />
)}
<TextModal <TextModal
isOpen={isCompleteWithNoteModalOpen} isOpen={isCompleteWithNoteModalOpen}
title='Add note to attach to this completion:' 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, CancelRounded,
EditCalendar, EditCalendar,
FilterAlt, FilterAlt,
FilterAltOff, PriorityHigh,
Style,
} from '@mui/icons-material' } from '@mui/icons-material'
import { import {
Box, Box,
Button,
Chip, Chip,
Container, Container,
IconButton, IconButton,
@ -23,9 +25,11 @@ import { useNavigate } from 'react-router-dom'
import { UserContext } from '../../contexts/UserContext' import { UserContext } from '../../contexts/UserContext'
import { useChores } from '../../queries/ChoreQueries' import { useChores } from '../../queries/ChoreQueries'
import { GetAllUsers, GetUserProfile } from '../../utils/Fetcher' import { GetAllUsers, GetUserProfile } from '../../utils/Fetcher'
import Priorities from '../../utils/Priorities'
import LoadingComponent from '../components/Loading' import LoadingComponent from '../components/Loading'
import { useLabels } from '../Labels/LabelQueries' import { useLabels } from '../Labels/LabelQueries'
import ChoreCard from './ChoreCard' import ChoreCard from './ChoreCard'
import IconButtonWithMenu from './IconButtonWithMenu'
const MyChores = () => { const MyChores = () => {
const { userProfile, setUserProfile } = useContext(UserContext) const { userProfile, setUserProfile } = useContext(UserContext)
@ -122,6 +126,28 @@ const MyChores = () => {
const handleFilterMenuClose = () => { const handleFilterMenuClose = () => {
setAnchorEl(null) 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 handleChoreUpdated = (updatedChore, event) => {
const newChores = chores.map(chore => { const newChores = chores.map(chore => {
if (chore.id === updatedChore.id) { if (chore.id === updatedChore.id) {
@ -175,13 +201,16 @@ const MyChores = () => {
chores.map(c => ({ chores.map(c => ({
...c, ...c,
raw_label: c.labelsV2 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(' '), .join(' '),
})), })),
searchOptions, searchOptions,
) )
const handleSearchChange = e => { const handleSearchChange = e => {
if (selectedFilter !== 'All') {
setSelectedFilter('All')
}
const search = e.target.value const search = e.target.value
if (search === '') { if (search === '') {
setFilteredChores(chores) setFilteredChores(chores)
@ -209,7 +238,6 @@ const MyChores = () => {
My Chores My Chores
</Typography> */} </Typography> */}
{/* <Sheet> */} {/* <Sheet> */}
{/* Search box to filter */} {/* Search box to filter */}
<Box <Box
sx={{ sx={{
@ -217,7 +245,7 @@ const MyChores = () => {
justifyContent: 'space-between', justifyContent: 'space-between',
alignContent: 'center', alignContent: 'center',
alignItems: 'center', alignItems: 'center',
gap: 1, gap: 0.5,
}} }}
> >
<Input <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 <IconButton
onClick={handleFilterMenuOpen} onClick={handleFilterMenuOpen}
variant='outlined' variant='outlined'
color={ color={
selectedFilter && selectedFilter != 'All' ? 'primary' : 'neutral' selectedFilter && FILTERS[selectedFilter] && selectedFilter != 'All'
? 'primary'
: 'neutral'
} }
size='sm' size='sm'
sx={{ sx={{
@ -257,40 +311,24 @@ const MyChores = () => {
borderRadius: 24, borderRadius: 24,
}} }}
> >
{selectedFilter && selectedFilter != 'All' ? (
<FilterAltOff />
) : (
<FilterAlt /> <FilterAlt />
)}
</IconButton> </IconButton>
<List <List
orientation='horizontal' orientation='horizontal'
wrap wrap
sx={{ sx={{
// '--List-gap': '8px',
// '--ListItem-radius': '20px',
// '--ListItem-minHeight': '32px',
// '--ListItem-gap': '4px',
mt: 0.2, mt: 0.2,
}} }}
> >
{/* <Checkbox
key='checkboxAll'
label=''
disableIcon
overlay
size='sm'
/> */}
<Menu <Menu
ref={menuRef} ref={menuRef}
anchorEl={anchorEl} anchorEl={anchorEl}
open={Boolean(anchorEl)} open={Boolean(anchorEl)}
onClose={handleFilterMenuClose} onClose={handleFilterMenuClose}
> >
{Object.keys(FILTERS).map(filter => ( {Object.keys(FILTERS).map((filter, index) => (
<MenuItem <MenuItem
key={filter} key={`filter-list-${filter}-${index}`}
onClick={() => { onClick={() => {
const filterFunction = FILTERS[filter] const filterFunction = FILTERS[filter]
const filteredChores = const filteredChores =
@ -310,10 +348,40 @@ const MyChores = () => {
</Chip> </Chip>
</MenuItem> </MenuItem>
))} ))}
{selectedFilter.startsWith('Label: ') ||
(selectedFilter.startsWith('Priority: ') && (
<MenuItem
key={`filter-list-cancel-all-filters`}
onClick={() => {
setFilteredChores(chores)
setSelectedFilter('All')
}}
>
Cancel All Filters
</MenuItem>
))}
</Menu> </Menu>
</List> </List>
</Box> </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> */} {/* </Sheet> */}
{filteredChores.length === 0 && ( {filteredChores.length === 0 && (
<Box <Box
@ -335,6 +403,17 @@ const MyChores = () => {
<Typography level='title-md' gutterBottom> <Typography level='title-md' gutterBottom>
Nothing scheduled Nothing scheduled
</Typography> </Typography>
{chores.length > 0 && (
<>
<Button
onClick={() => setFilteredChores(chores)}
variant='outlined'
color='neutral'
>
Reset filters
</Button>
</>
)}
</Box> </Box>
)} )}
@ -346,6 +425,7 @@ const MyChores = () => {
onChoreRemove={handleChoreDeleted} onChoreRemove={handleChoreDeleted}
performers={performers} performers={performers}
userLabels={userLabels} 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) { 'Created By Me': function (chores, userID) {
return chores.filter(chore => { return chores.filter(chore => {
return chore.createdBy === userID return chore.createdBy === userID

View file

@ -15,33 +15,55 @@ import { useLabels } from './LabelQueries'
// import { useMutation, useQueryClient } from '@tanstack/react-query' // import { useMutation, useQueryClient } from '@tanstack/react-query'
import { Add } from '@mui/icons-material' import { Add } from '@mui/icons-material'
import { useQueryClient } from 'react-query' import { useQueryClient } from 'react-query'
import { useNavigate } from 'react-router-dom'
import { DeleteLabel } from '../../utils/Fetcher' import { DeleteLabel } from '../../utils/Fetcher'
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
const LabelView = () => { const LabelView = () => {
const { data: labels, isLabelsLoading, isError } = useLabels() const { data: labels, isLabelsLoading, isError } = useLabels()
const [userLabels, setUserLabels] = useState([labels]) const [userLabels, setUserLabels] = useState([labels])
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false)
const [currentLabel, setCurrentLabel] = useState(null) // Label being edited or null for new label const [currentLabel, setCurrentLabel] = useState(null)
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [confirmationModel, setConfirmationModel] = useState({})
const Navigate = useNavigate()
const handleAddLabel = () => { const handleAddLabel = () => {
setCurrentLabel(null) // Adding a new label setCurrentLabel(null)
setModalOpen(true) setModalOpen(true)
} }
const handleEditLabel = label => { const handleEditLabel = label => {
setCurrentLabel(label) // Editing an existing label setCurrentLabel(label)
setModalOpen(true) setModalOpen(true)
} }
const handleDeleteClicked = id => {
setConfirmationModel({
isOpen: true,
title: 'Delete Label',
message:
'Are you sure you want to delete this label? This will remove the label from all tasks.',
confirmText: 'Delete',
color: 'danger',
cancelText: 'Cancel',
onClose: confirmed => {
if (confirmed) {
handleDeleteLabel(id)
}
setConfirmationModel({})
},
})
}
const handleDeleteLabel = id => { const handleDeleteLabel = id => {
DeleteLabel(id).then(res => { DeleteLabel(id).then(res => {
// Invalidate and refetch labels after deleting a label
const updatedLabels = userLabels.filter(label => label.id !== id) const updatedLabels = userLabels.filter(label => label.id !== id)
setUserLabels(updatedLabels) setUserLabels(updatedLabels)
queryClient.invalidateQueries('labels') queryClient.invalidateQueries('labels')
}) })
// Implement deletion logic here
} }
const handleSaveLabel = newOrUpdatedLabel => { const handleSaveLabel = newOrUpdatedLabel => {
@ -92,7 +114,13 @@ const LabelView = () => {
<tbody> <tbody>
{userLabels.map(label => ( {userLabels.map(label => (
<tr key={label.id}> <tr key={label.id}>
<td>{label.name}</td> <td
onClick={() => {
Navigate('/my/chores', { state: { label: label.id } })
}}
>
{label.name}
</td>
<td <td
style={{ style={{
// center without display flex: // center without display flex:
@ -121,7 +149,7 @@ const LabelView = () => {
<EditIcon /> <EditIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => handleDeleteLabel(label.id)} onClick={() => handleDeleteClicked(label.id)}
color='danger' color='danger'
> >
<DeleteIcon /> <DeleteIcon />
@ -151,7 +179,7 @@ const LabelView = () => {
position: 'fixed', position: 'fixed',
bottom: 0, bottom: 0,
left: 10, left: 10,
p: 2, // padding p: 2,
display: 'flex', display: 'flex',
justifyContent: 'flex-end', justifyContent: 'flex-end',
gap: 2, gap: 2,
@ -171,6 +199,7 @@ const LabelView = () => {
<Add /> <Add />
</IconButton> </IconButton>
</Box> </Box>
<ConfirmationModal config={confirmationModel} />
</Container> </Container>
) )
} }

View file

@ -24,6 +24,7 @@ function ConfirmationModal({ config }) {
}} }}
fullWidth fullWidth
sx={{ mr: 1 }} sx={{ mr: 1 }}
color={config.color ? config.color : 'primary'}
> >
{config?.confirmText} {config?.confirmText}
</Button> </Button>