From 755187151baabef88759d216269401d0e306fe9c Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Sun, 24 Nov 2024 00:49:14 -0500 Subject: [PATCH 1/4] chore: Bump version to 0.1.80 in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c025855..95048d1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "donetick", "private": true, - "version": "0.1.79", + "version": "0.1.80", "type": "module", "lint-staged": { "*.{js,jsx,ts,tsx}": [ From 6c1df9cb369dd826cc3cfff8f02616c675466dc9 Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Thu, 28 Nov 2024 21:15:47 -0500 Subject: [PATCH 2/4] Add Color option for confirmation modal --- src/views/Modals/Inputs/ConfirmationModal.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/Modals/Inputs/ConfirmationModal.jsx b/src/views/Modals/Inputs/ConfirmationModal.jsx index 10f9bee..882522e 100644 --- a/src/views/Modals/Inputs/ConfirmationModal.jsx +++ b/src/views/Modals/Inputs/ConfirmationModal.jsx @@ -24,6 +24,7 @@ function ConfirmationModal({ config }) { }} fullWidth sx={{ mr: 1 }} + color={config.color ? config.color : 'primary'} > {config?.confirmText} From ce514cb43ad5f4f79542c11358204db66c60a882 Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Thu, 28 Nov 2024 21:18:25 -0500 Subject: [PATCH 3/4] Confirm deleting before delete the label --- src/views/Labels/LabelView.jsx | 45 ++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/views/Labels/LabelView.jsx b/src/views/Labels/LabelView.jsx index 9dd442a..62e699b 100644 --- a/src/views/Labels/LabelView.jsx +++ b/src/views/Labels/LabelView.jsx @@ -15,33 +15,55 @@ import { useLabels } from './LabelQueries' // import { useMutation, useQueryClient } from '@tanstack/react-query' import { Add } from '@mui/icons-material' import { useQueryClient } from 'react-query' +import { useNavigate } from 'react-router-dom' import { DeleteLabel } from '../../utils/Fetcher' +import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' const LabelView = () => { const { data: labels, isLabelsLoading, isError } = useLabels() const [userLabels, setUserLabels] = useState([labels]) 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 [confirmationModel, setConfirmationModel] = useState({}) + const Navigate = useNavigate() const handleAddLabel = () => { - setCurrentLabel(null) // Adding a new label + setCurrentLabel(null) setModalOpen(true) } const handleEditLabel = label => { - setCurrentLabel(label) // Editing an existing label + setCurrentLabel(label) 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 => { DeleteLabel(id).then(res => { - // Invalidate and refetch labels after deleting a label const updatedLabels = userLabels.filter(label => label.id !== id) setUserLabels(updatedLabels) queryClient.invalidateQueries('labels') }) - // Implement deletion logic here } const handleSaveLabel = newOrUpdatedLabel => { @@ -92,7 +114,13 @@ const LabelView = () => { {userLabels.map(label => ( - {label.name} + { + Navigate('/my/chores', { state: { label: label.id } }) + }} + > + {label.name} + { handleDeleteLabel(label.id)} + onClick={() => handleDeleteClicked(label.id)} color='danger' > @@ -151,7 +179,7 @@ const LabelView = () => { position: 'fixed', bottom: 0, left: 10, - p: 2, // padding + p: 2, display: 'flex', justifyContent: 'flex-end', gap: 2, @@ -171,6 +199,7 @@ const LabelView = () => { + ) } From 3467722c8989a3768fe1fa5b261976831640ccfd Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Thu, 28 Nov 2024 21:21:23 -0500 Subject: [PATCH 4/4] Add Ability to filter chores by priorities and labels, Add IconMenuButton --- src/views/ChoreEdit/ChoreView.jsx | 14 +-- src/views/Chores/ChoreCard.jsx | 23 +++- src/views/Chores/IconButtonWithMenu.jsx | 102 ++++++++++++++++++ src/views/Chores/MyChores.jsx | 137 +++++++++++++++++++----- 4 files changed, 241 insertions(+), 35 deletions(-) create mode 100644 src/views/Chores/IconButtonWithMenu.jsx diff --git a/src/views/ChoreEdit/ChoreView.jsx b/src/views/ChoreEdit/ChoreView.jsx index 8b2d2ed..3347ed7 100644 --- a/src/views/ChoreEdit/ChoreView.jsx +++ b/src/views/ChoreEdit/ChoreView.jsx @@ -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'} - {/* show each label : */} {chore?.labelsV2?.map((label, index) => ( { 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} @@ -512,16 +521,22 @@ const ChoreCard = ({ return ( { + // e.stopPropagation() + // onChipClick({ label: l }) + // }} // startDecorator={getIconForLabel(label)} > @@ -719,7 +734,9 @@ const ChoreCard = ({ handleAssigneChange(selected.id) }} /> - + {confirmModelConfig?.isOpen && ( + + )} { + 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 ( + <> + + {icon} + + + + {options?.map(item => ( + { + onItemSelect(item) + setSelectedItem?.selectedItem(item.name) + handleMenuClose() + }} + > + {useChips ? ( + + {item.name} + + ) : ( + <> + {item?.icon} + {item.name} + + )} + + ))} + {/* {selectedItem && selectedItem !== 'All' && ( + { + onItemSelect(null) + setSelectedItem?.setSelectedItem('All') + }} + > + Cancel All Filters + + )} */} + + + ) +} +export default IconButtonWithMenu diff --git a/src/views/Chores/MyChores.jsx b/src/views/Chores/MyChores.jsx index cc7ab58..eb93df6 100644 --- a/src/views/Chores/MyChores.jsx +++ b/src/views/Chores/MyChores.jsx @@ -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 */} {/* */} - {/* Search box to filter */} { justifyContent: 'space-between', alignContent: 'center', alignItems: 'center', - gap: 1, + gap: 0.5, }} > { ) } /> + } + options={Priorities} + selectedItem={selectedFilter} + onItemSelect={selected => { + handleLabelFiltering({ priority: selected.value }) + }} + mouseClickHandler={handleMenuOutsideClick} + isActive={selectedFilter.startsWith('Priority: ')} + /> + } + options={userLabels} + selectedItem={selectedFilter} + onItemSelect={selected => { + handleLabelFiltering({ label: selected }) + }} + isActive={selectedFilter.startsWith('Label: ')} + mouseClickHandler={handleMenuOutsideClick} + useChips + /> + { borderRadius: 24, }} > - {selectedFilter && selectedFilter != 'All' ? ( - - ) : ( - - )} + - {/* */} - - {Object.keys(FILTERS).map(filter => ( + {Object.keys(FILTERS).map((filter, index) => ( { const filterFunction = FILTERS[filter] const filteredChores = @@ -310,10 +348,40 @@ const MyChores = () => { ))} + {selectedFilter.startsWith('Label: ') || + (selectedFilter.startsWith('Priority: ') && ( + { + setFilteredChores(chores) + setSelectedFilter('All') + }} + > + Cancel All Filters + + ))} - + {selectedFilter !== 'All' && ( + { + setFilteredChores(chores) + setSelectedFilter('All') + }} + endDecorator={} + onClick={() => { + setFilteredChores(chores) + setSelectedFilter('All') + }} + > + Current Filter: {selectedFilter} + + )} {/* */} {filteredChores.length === 0 && ( { Nothing scheduled + {chores.length > 0 && ( + <> + + + )} )} @@ -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