Merge pull request #128 from donetick/ux-improvment-07-01-2026

Ux improvment 07 01 2026
This commit is contained in:
Mohamad Tarbin
2026-07-07 00:48:14 -04:00
committed by GitHub
6 changed files with 719 additions and 320 deletions

View File

@@ -25,6 +25,67 @@ export const ChoreStatus = Object.freeze({
PAUSED: 2,
PENDING_APPROVAL: 3,
})
const getDateGroupKey = dueDate =>
moment(dueDate).startOf('day').format('YYYY-MM-DD')
const getDateGroupName = dateKey =>
moment(dateKey, 'YYYY-MM-DD').format('dddd, MMM D')
const getDateGroupColor = dateKey => {
const today = moment().startOf('day')
const tomorrow = moment().add(1, 'day').startOf('day')
const groupDate = moment(dateKey, 'YYYY-MM-DD')
if (groupDate.isBefore(today)) {
return TASK_COLOR.OVERDUE
}
if (groupDate.isSame(today)) {
return TASK_COLOR.TODAY
}
if (groupDate.isSame(tomorrow)) {
return TASK_COLOR.TOMORROW
}
if (groupDate.isBefore(moment(today).add(8, 'days'))) {
return TASK_COLOR.NEXT_7_DAYS
}
if (groupDate.isSame(today, 'month')) {
return TASK_COLOR.LATER_THIS_MONTH
}
return TASK_COLOR.FUTURE
}
const buildActualDateGroups = chores => {
const groupedByDate = {}
const anytime = []
chores.forEach(chore => {
if (!chore.nextDueDate) {
anytime.push(chore)
return
}
const dateKey = getDateGroupKey(chore.nextDueDate)
if (!groupedByDate[dateKey]) {
groupedByDate[dateKey] = []
}
groupedByDate[dateKey].push(chore)
})
const dateGroups = Object.keys(groupedByDate)
.sort(
(a, b) =>
moment(a, 'YYYY-MM-DD').valueOf() - moment(b, 'YYYY-MM-DD').valueOf(),
)
.map(dateKey => ({
name: getDateGroupName(dateKey),
content: groupedByDate[dateKey],
color: getDateGroupColor(dateKey),
}))
return { dateGroups, anytime }
}
export const ChoresGrouper = (groupBy, chores, filter) => {
if (filter) {
chores = chores.filter(chore => filter(chore))
@@ -34,7 +95,7 @@ export const ChoresGrouper = (groupBy, chores, filter) => {
chores.sort(ChoreSorter)
var groups = []
switch (groupBy) {
case 'default':
case 'default': {
// same as due_date but hide empty groups: and if status is 1 or 2 have seperated catigory as Started:
var groupRaw = {
PendingApproval: [],
@@ -147,82 +208,21 @@ export const ChoresGrouper = (groupBy, chores, filter) => {
})
}
break
}
case 'due_date':
var groupRaw = {
Today: [],
Tomorrow: [],
'Next 7 Days': [],
'Later This Month': [],
Future: [],
Overdue: [],
Anytime: [],
}
chores.forEach(chore => {
if (chore.nextDueDate === null) {
groupRaw['Anytime'].push(chore)
} else if (new Date(chore.nextDueDate) < new Date()) {
groupRaw['Overdue'].push(chore)
} else if (
new Date(chore.nextDueDate).toDateString() ===
new Date().toDateString()
) {
groupRaw['Today'].push(chore)
} else if (
new Date(chore.nextDueDate).toDateString() ===
new Date(Date.now() + 24 * 60 * 60 * 1000).toDateString()
) {
groupRaw['Tomorrow'].push(chore)
} else if (
new Date(chore.nextDueDate) <
new Date(Date.now() + 8 * 24 * 60 * 60 * 1000) &&
new Date(chore.nextDueDate) >
new Date(Date.now() + 24 * 60 * 60 * 1000)
) {
groupRaw['Next 7 Days'].push(chore)
} else if (
new Date(chore.nextDueDate).getMonth() === new Date().getMonth() &&
new Date(chore.nextDueDate).getFullYear() === new Date().getFullYear()
) {
groupRaw['Later This Month'].push(chore)
} else {
groupRaw['Future'].push(chore)
}
})
groups = [
{
name: 'Overdue',
content: groupRaw['Overdue'],
color: TASK_COLOR.OVERDUE,
},
{ name: 'Today', content: groupRaw['Today'], color: TASK_COLOR.TODAY },
{
name: 'Tomorrow',
content: groupRaw['Tomorrow'],
color: TASK_COLOR.TOMORROW,
},
{
name: 'Next 7 Days',
content: groupRaw['Next 7 Days'],
color: TASK_COLOR.NEXT_7_DAYS,
},
{
name: 'Later This Month',
content: groupRaw['Later This Month'],
color: TASK_COLOR.LATER_THIS_MONTH,
},
{
name: 'Future',
content: groupRaw['Future'],
color: TASK_COLOR.FUTURE,
},
{
case 'due_date': {
var { dateGroups: dueDateGroups, anytime: dueAnytime } =
buildActualDateGroups(chores)
groups = [...dueDateGroups]
if (dueAnytime.length > 0) {
groups.push({
name: 'Anytime',
content: groupRaw['Anytime'],
content: dueAnytime,
color: TASK_COLOR.ANYTIME,
},
]
})
}
break
}
case 'priority':
groupRaw = {
p1: [],

View File

@@ -9,11 +9,11 @@ import {
import {
Box,
Chip,
IconButton,
Menu,
MenuItem,
Tooltip,
Typography,
IconButton,
} from '@mui/joy'
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
@@ -116,6 +116,8 @@ const CustomFilterChips = ({
px: 1.0,
py: 0.5,
height: 32,
display: 'flex',
alignItems: 'center',
opacity: hasWarning ? 0.7 : isActive ? 1 : 0.85,
...(hasCustomColor && {
@@ -185,6 +187,10 @@ const CustomFilterChips = ({
maxWidth: 100,
overflow: 'hidden',
textOverflow: 'ellipsis',
display: 'flex',
alignItems: 'center',
height: '100%',
lineHeight: 1,
...(hasCustomColor && {
color: textColor,
}),

View File

@@ -9,12 +9,10 @@ const MyChoreHeader = ({
tempFilter,
tempFilterMeta,
}) => {
if (
!activeFilterId &&
!tempFilter &&
(!selectedProject || selectedProject.id === 'default')
)
return null
const isVisible =
!!activeFilterId ||
!!tempFilter ||
(!!selectedProject && selectedProject.id !== 'default')
const renderIcon = () => {
if (tempFilter) {
@@ -53,18 +51,41 @@ const MyChoreHeader = ({
: activeFilter?.description || selectedProject?.description
return (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
{renderIcon()}
<Stack sx={{ flex: 1 }}>
<Typography level='h3' sx={{ fontWeight: 'lg', color: 'text.primary' }}>
{name}
</Typography>
{description && (
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
{description}
<Box
sx={{
overflow: 'hidden',
maxHeight: isVisible ? '120px' : '0',
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'translateY(0)' : 'translateY(-8px)',
transition:
'max-height 0.3s ease-in-out, opacity 0.3s ease-in-out, transform 0.3s ease-in-out',
marginBottom: isVisible ? 2 : 0,
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
{renderIcon()}
<Stack sx={{ flex: 1 }}>
<Typography
level='h3'
sx={{ fontWeight: 'lg', color: 'text.primary' }}
>
{name}
</Typography>
)}
</Stack>
<Box
sx={{
overflow: 'hidden',
maxHeight: description ? '40px' : '0',
opacity: description ? 1 : 0,
transition:
'max-height 0.3s ease-in-out, opacity 0.3s ease-in-out',
}}
>
<Typography level='body-sm' sx={{ color: 'text.secondary' }}>
{description}
</Typography>
</Box>
</Stack>
</Box>
</Box>
)
}

View File

@@ -12,6 +12,7 @@ import {
MarkChoreComplete,
NudgeChore,
RejectChore,
SaveChore,
SkipChore,
UndoChoreAction,
UpdateChoreAssignee,
@@ -664,6 +665,28 @@ export const useChoreActions = ({
}
break
case 'moveToProject': {
const project = extraData?.project
const projectId = project?.id === null ? null : project?.id
const updatedChore = { ...chore, projectId }
try {
const response = await SaveChore(updatedChore)
if (response.ok) {
updateChoreInState(updatedChore, 'moved-to-project')
showSuccess({
title: 'Task Moved',
message: `Task moved to ${project?.name || 'Default Project'}.`,
})
}
} catch (error) {
showError({
title: 'Failed to move task',
message: error?.message || 'Unable to move task to project',
})
}
break
}
case 'completeWithNote':
case 'completeWithPastDate':
case 'changeAssignee':

View File

@@ -1,52 +1,226 @@
import { HomeRounded, Login } from '@mui/icons-material'
import { Box, Button, CircularProgress, Container, Typography } from '@mui/joy'
import { Link } from 'react-router-dom'
import Logo from '../Logo' // Adjust the import path as necessary
import {
BugReportRounded,
CloudOffRounded,
ContentCopyRounded,
ErrorRounded,
ExpandMoreRounded,
HomeRounded,
LockRounded,
RefreshRounded,
SearchOffRounded,
} from '@mui/icons-material'
import { Box, Button, IconButton, Snackbar, Typography } from '@mui/joy'
import { useState } from 'react'
import { Link, useRouteError } from 'react-router-dom'
const getErrorKind = error => {
if (!error)
return { label: 'Unknown Error', color: 'danger', Icon: ErrorRounded }
const status = error?.status ?? error?.response?.status
if (status === 404)
return {
label: '404 · Not Found',
color: 'warning',
Icon: SearchOffRounded,
}
if (status === 401 || status === 403)
return {
label: `${status} · Unauthorized`,
color: 'warning',
Icon: LockRounded,
}
if (status >= 500)
return {
label: `${status} · Server Error`,
color: 'danger',
Icon: CloudOffRounded,
}
if (error?.name === 'TypeError')
return { label: 'Runtime Error', color: 'danger', Icon: BugReportRounded }
if (error?.name === 'SyntaxError')
return { label: 'Syntax Error', color: 'danger', Icon: BugReportRounded }
return { label: 'Unexpected Error', color: 'danger', Icon: ErrorRounded }
}
const safeMessage = error => {
const msg = error?.message ?? error?.statusText
if (!msg || msg === '[object Object]') return null
return msg
}
const buildErrorText = (error, url) => {
const lines = [
`URL: ${url}`,
`Time: ${new Date().toISOString()}`,
`Error: ${safeMessage(error) ?? String(error)}`,
]
if (error?.stack) lines.push(`\nStack:\n${error.stack}`)
return lines.join('\n')
}
const Error = () => {
const error = useRouteError()
const [showDetails, setShowDetails] = useState(false)
const [copied, setCopied] = useState(false)
const { color, Icon } = getErrorKind(error)
const message = safeMessage(error)
const url = window.location.href
const handleCopy = () => {
navigator.clipboard.writeText(buildErrorText(error, url)).then(() => {
setCopied(true)
})
}
return (
<Container className='flex h-full items-center justify-center'>
<Box
sx={{
position: 'relative',
minHeight: '100dvh',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
px: 3,
py: 6,
maxWidth: 440,
mx: 'auto',
overflow: 'hidden',
}}
>
{/* Decorative dots */}
<Box
className='flex flex-col items-center justify-center'
sx={{
minHeight: '80vh',
position: 'absolute',
top: '14%',
left: '6%',
width: 14,
height: 14,
borderRadius: '50%',
bgcolor: `${color}.100`,
opacity: 0.7,
}}
/>
<Box
sx={{
position: 'absolute',
top: '22%',
right: '8%',
width: 9,
height: 9,
borderRadius: '50%',
bgcolor: `${color}.200`,
}}
/>
<Box
sx={{
position: 'absolute',
bottom: '28%',
right: '6%',
width: 7,
height: 7,
borderRadius: '50%',
bgcolor: `${color}.100`,
}}
/>
{/* Icon with concentric rings */}
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mb: 4,
}}
>
<CircularProgress
value={100}
color='danger' // Set the color to 'error' for danger color
sx={{ '--CircularProgress-size': '200px' }}
>
<Logo />
</CircularProgress>
<Box
className='flex items-center gap-2'
sx={{
fontWeight: 700,
fontSize: 24,
mt: 2,
width: 172,
height: 172,
borderRadius: '50%',
border: '1.5px solid',
borderColor: `${color}.100`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
Ops, something went wrong
</Box>
<Typography level='body-md' fontWeight={500} textAlign={'center'}>
if you think this is a mistake, please contact us or{' '}
<a
href='https://github.com/donetick/donetick/issues/new'
style={{
textDecoration: 'underline',
<Box
sx={{
width: 128,
height: 128,
borderRadius: '50%',
border: '1.5px solid',
borderColor: `${color}.200`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
open issue here
</a>{' '}
</Typography>
<Box
sx={{
width: 84,
height: 84,
borderRadius: '50%',
bgcolor: `${color}.50`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Icon sx={{ fontSize: 42, color: `${color}.500` }} />
</Box>
</Box>
</Box>
</Box>
{/* Title */}
<Typography
level='h3'
fontWeight={700}
textAlign='center'
sx={{ mb: 1.5 }}
>
Something went wrong
</Typography>
{/* Error message */}
<Typography
level='body-sm'
textAlign='center'
sx={{
color: 'text.secondary',
mb: 4,
maxWidth: 320,
minHeight: '2.5em',
wordBreak: 'break-word',
}}
>
{message ??
'An unexpected error occurred. Try reloading — it usually fixes it.'}
</Typography>
{/* Primary CTA */}
<Button
variant='solid'
color='primary'
size='lg'
startDecorator={<RefreshRounded />}
onClick={() => window.location.reload()}
sx={{ width: '100%', mb: 2 }}
>
Try again
</Button>
{/* Secondary actions */}
<Box sx={{ display: 'flex', gap: 3, mb: 5 }}>
<Button
component={Link}
to='/chores'
variant='outlined'
color='primary'
sx={{ mt: 4 }}
variant='plain'
color='neutral'
size='lg'
startDecorator={<HomeRounded />}
>
@@ -55,16 +229,108 @@ const Error = () => {
<Button
component={Link}
to='/login'
variant='outlined'
color='primary'
sx={{ mt: 1 }}
variant='plain'
color='neutral'
size='lg'
startDecorator={<Login />}
>
Login
</Button>
</Box>
</Container>
{/* Report hint + collapsible details */}
<Box
sx={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Typography
level='body-xs'
textAlign='center'
sx={{ color: 'text.tertiary', mb: 1.5 }}
>
If this keeps happening,{' '}
<a
href='https://github.com/donetick/donetick/issues/new'
target='_blank'
rel='noopener noreferrer'
style={{ textDecoration: 'underline' }}
>
open an issue
</a>{' '}
and include the error details below.
</Typography>
{(error?.stack || message) && (
<>
<Button
variant='plain'
color='neutral'
size='sm'
onClick={() => setShowDetails(v => !v)}
endDecorator={
<ExpandMoreRounded
sx={{
transition: 'transform 0.2s',
transform: showDetails ? 'rotate(180deg)' : 'rotate(0deg)',
}}
/>
}
sx={{ mb: 1 }}
>
{showDetails ? 'Hide' : 'Show'} error details
</Button>
{showDetails && (
<Box
sx={{
position: 'relative',
bgcolor: 'background.level2',
borderRadius: 'sm',
p: 2,
width: '100%',
}}
>
<IconButton
size='sm'
variant='plain'
color='neutral'
onClick={handleCopy}
sx={{ position: 'absolute', top: 8, right: 8 }}
title='Copy to clipboard'
>
<ContentCopyRounded fontSize='small' />
</IconButton>
<Typography
level='body-xs'
sx={{
fontFamily: 'monospace',
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
pr: 4,
color: 'text.secondary',
}}
>
{buildErrorText(error, url)}
</Typography>
</Box>
)}
</>
)}
</Box>
<Snackbar
open={copied}
autoHideDuration={2500}
onClose={() => setCopied(false)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
size='sm'
>
Error details copied to clipboard
</Snackbar>
</Box>
)
}

View File

@@ -1,8 +1,10 @@
import {
Archive,
ArrowBack,
Cancel,
CopyAll,
Delete,
DriveFileMove,
Edit,
ManageSearch,
MoreTime,
@@ -20,10 +22,25 @@ import {
WbSunny,
Weekend,
} from '@mui/icons-material'
import { Divider, IconButton, Menu, MenuItem, Tooltip } from '@mui/joy'
import {
Avatar,
Divider,
IconButton,
ListItemContent,
ListItemDecorator,
Menu,
MenuItem,
Tooltip,
Typography,
} from '@mui/joy'
import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import LABEL_COLORS, {
getTextColorFromBackgroundColor,
} from '../../utils/Colors'
import { isOfficialDonetickInstanceSync } from '../../utils/FeatureToggle'
import { getIconComponent } from '../../utils/ProjectIcons'
import { useProjects } from '../Projects/ProjectQueries'
const ChoreActionMenu = ({
chore,
@@ -43,10 +60,11 @@ const ChoreActionMenu = ({
}) => {
const [anchorEl, setAnchorEl] = React.useState(null)
const [isOfficialInstance, setIsOfficialInstance] = useState(false)
const [showProjectPicker, setShowProjectPicker] = useState(false)
const menuRef = React.useRef(null)
const navigate = useNavigate()
const { data: projects = [] } = useProjects()
// Check if this is the official donetick.com instance
useEffect(() => {
try {
setIsOfficialInstance(isOfficialDonetickInstanceSync())
@@ -83,6 +101,12 @@ const ChoreActionMenu = ({
const handleMenuClose = () => {
setAnchorEl(null)
setShowProjectPicker(false)
}
const handleMoveToProject = project => {
onAction?.('moveToProject', chore, { project })
handleMenuClose()
}
const handleEdit = () => {
@@ -134,7 +158,6 @@ const ChoreActionMenu = ({
switch (option) {
case 'today': {
// Schedule for today at the next available slot: 9am, 12pm, 5pm, or now if after 5pm
const nowHour = now.getHours()
const scheduled = new Date(today)
if (nowHour < 9) {
@@ -144,7 +167,6 @@ const ChoreActionMenu = ({
} else if (nowHour < 17) {
scheduled.setHours(17, 0, 0, 0)
} else {
// After 5pm, use current time
scheduled.setHours(
now.getHours(),
now.getMinutes(),
@@ -163,7 +185,7 @@ const ChoreActionMenu = ({
case 'tomorrow': {
const tomorrow = new Date(today)
tomorrow.setDate(today.getDate() + 1)
tomorrow.setHours(12, 0, 0, 0) // Set to noon
tomorrow.setHours(12, 0, 0, 0)
return tomorrow
}
case 'tomorrow-afternoon': {
@@ -195,6 +217,18 @@ const ChoreActionMenu = ({
handleMenuClose()
}
const renderProjectAvatar = (color, icon) => {
const bg = color || LABEL_COLORS[0].value
const IconComponent = getIconComponent(icon || 'FolderOpen')
return (
<Avatar size='sm' sx={{ width: 22, height: 22, backgroundColor: bg }}>
<IconComponent
sx={{ fontSize: 13, color: getTextColorFromBackgroundColor(bg) }}
/>
</Avatar>
)
}
return (
<>
<IconButton
@@ -227,218 +261,267 @@ const ChoreActionMenu = ({
left: '50%',
}}
>
<MenuItem
onClick={e => {
e.stopPropagation()
onCompleteWithNote?.()
handleMenuClose()
}}
>
<NoteAdd />
Complete with note
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
onCompleteWithPastDate?.()
handleMenuClose()
}}
>
<Update />
Complete in past
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
handleSkip()
}}
>
<SwitchAccessShortcut />
Skip to next due date
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
onChangeAssignee?.()
handleMenuClose()
}}
>
<RecordVoiceOver />
Delegate to someone else
</MenuItem>
{isOfficialInstance && (
<MenuItem
onClick={e => {
e.stopPropagation()
onNudge?.()
handleMenuClose()
}}
>
<Notifications />
Send nudge
</MenuItem>
)}
<Divider />
<MenuItem
onClick={e => {
e.stopPropagation()
handleHistory()
}}
>
<ManageSearch />
History
</MenuItem>
<Divider />
<MenuItem
sx={{
display: 'flex',
justifyContent: 'space-around',
alignItems: 'center',
gap: 1,
cursor: 'default',
'&:hover': {
backgroundColor: 'transparent',
},
}}
onClick={e => e.stopPropagation()}
>
<Tooltip title='Today' placement='top'>
<IconButton
size='sm'
{showProjectPicker ? (
<>
<MenuItem
onClick={e => {
e.stopPropagation()
handleQuickSchedule('today')
setShowProjectPicker(false)
}}
sx={{ gap: 1 }}
>
<Today />
</IconButton>
</Tooltip>
<Tooltip title='Tomorrow' placement='top'>
<IconButton
size='sm'
<ArrowBack fontSize='small' />
<Typography level='body-sm' fontWeight={600}>
Move to project
</Typography>
</MenuItem>
<Divider />
<MenuItem
onClick={e => {
e.stopPropagation()
handleQuickSchedule('tomorrow')
handleMoveToProject({ id: null, name: 'Default Project' })
}}
>
<WbSunny />
</IconButton>
</Tooltip>
{/* <Tooltip title='Tomorrow afternoon' placement='top'>
<IconButton
size='sm'
<ListItemDecorator>
{renderProjectAvatar(LABEL_COLORS[0].value, 'FolderOpen')}
</ListItemDecorator>
<ListItemContent>
<Typography level='body-sm'>Default Project</Typography>
</ListItemContent>
</MenuItem>
{projects.map(project => (
<MenuItem
key={project.id}
onClick={e => {
e.stopPropagation()
handleMoveToProject(project)
}}
>
<ListItemDecorator>
{renderProjectAvatar(project.color, project.icon)}
</ListItemDecorator>
<ListItemContent>
<Typography level='body-sm'>{project.name}</Typography>
</ListItemContent>
</MenuItem>
))}
</>
) : (
<>
<MenuItem
onClick={e => {
e.stopPropagation()
handleQuickSchedule('tomorrow-afternoon')
onCompleteWithNote?.()
handleMenuClose()
}}
>
<WbTwilight />
</IconButton>
</Tooltip> */}
<Tooltip title='Weekend' placement='top'>
<IconButton
size='sm'
<NoteAdd />
Complete with note
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
handleQuickSchedule('weekend')
onCompleteWithPastDate?.()
handleMenuClose()
}}
>
<Weekend />
</IconButton>
</Tooltip>
<Tooltip title='Next week' placement='top'>
<IconButton
size='sm'
<Update />
Complete in past
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
handleQuickSchedule('next-week')
handleSkip()
}}
>
<NextWeek />
</IconButton>
</Tooltip>
<Tooltip title='Remove due date' placement='top'>
<IconButton
size='sm'
<SwitchAccessShortcut />
Skip to next due date
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
onChangeAssignee?.()
handleMenuClose()
}}
>
<RecordVoiceOver />
Delegate to someone else
</MenuItem>
{isOfficialInstance && (
<MenuItem
onClick={e => {
e.stopPropagation()
onNudge?.()
handleMenuClose()
}}
>
<Notifications />
Send nudge
</MenuItem>
)}
<Divider />
<MenuItem
onClick={e => {
e.stopPropagation()
handleHistory()
}}
>
<ManageSearch />
History
</MenuItem>
<Divider />
<MenuItem
sx={{
display: 'flex',
justifyContent: 'space-around',
alignItems: 'center',
gap: 1,
cursor: 'default',
'&:hover': {
backgroundColor: 'transparent',
},
}}
onClick={e => e.stopPropagation()}
>
<Tooltip title='Today' placement='top'>
<IconButton
size='sm'
onClick={e => {
e.stopPropagation()
handleQuickSchedule('today')
}}
>
<Today />
</IconButton>
</Tooltip>
<Tooltip title='Tomorrow' placement='top'>
<IconButton
size='sm'
onClick={e => {
e.stopPropagation()
handleQuickSchedule('tomorrow')
}}
>
<WbSunny />
</IconButton>
</Tooltip>
<Tooltip title='Weekend' placement='top'>
<IconButton
size='sm'
onClick={e => {
e.stopPropagation()
handleQuickSchedule('weekend')
}}
>
<Weekend />
</IconButton>
</Tooltip>
<Tooltip title='Next week' placement='top'>
<IconButton
size='sm'
onClick={e => {
e.stopPropagation()
handleQuickSchedule('next-week')
}}
>
<NextWeek />
</IconButton>
</Tooltip>
<Tooltip title='Remove due date' placement='top'>
<IconButton
size='sm'
color='neutral'
onClick={e => {
e.stopPropagation()
handleQuickSchedule('remove')
}}
>
<Cancel />
</IconButton>
</Tooltip>
</MenuItem>
<Divider />
<MenuItem
onClick={e => {
e.stopPropagation()
onChangeDueDate?.()
handleMenuClose()
}}
>
<MoreTime />
Change due date
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
onWriteNFC?.()
handleMenuClose()
}}
>
<Nfc />
Write to NFC
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
handleEdit()
}}
>
<Edit />
Edit
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
handleClone()
}}
>
<CopyAll />
Clone
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
handleView()
}}
>
<ViewCarousel />
View
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
handleArchive()
}}
color='neutral'
>
{chore.isActive ? <Archive /> : <Unarchive />}
{chore.isActive ? 'Archive' : 'Unarchive'}
</MenuItem>
{projects.length > 0 && (
<MenuItem
onClick={e => {
e.stopPropagation()
setShowProjectPicker(true)
}}
>
<DriveFileMove />
Move to project
</MenuItem>
)}
<Divider />
<MenuItem
onClick={e => {
e.stopPropagation()
handleQuickSchedule('remove')
handleDelete()
}}
color='danger'
>
<Cancel />
</IconButton>
</Tooltip>
</MenuItem>
<Divider />
<MenuItem
onClick={e => {
e.stopPropagation()
onChangeDueDate?.()
handleMenuClose()
}}
>
<MoreTime />
Change due date
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
onWriteNFC?.()
handleMenuClose()
}}
>
<Nfc />
Write to NFC
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
handleEdit()
}}
>
<Edit />
Edit
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
handleClone()
}}
>
<CopyAll />
Clone
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
handleView()
}}
>
<ViewCarousel />
View
</MenuItem>
<MenuItem
onClick={e => {
e.stopPropagation()
handleArchive()
}}
color='neutral'
>
{chore.isActive ? <Archive /> : <Unarchive />}
{chore.isActive ? 'Archive' : 'Unarchive'}
</MenuItem>
<Divider />
<MenuItem
onClick={e => {
e.stopPropagation()
handleDelete()
}}
color='danger'
>
<Delete />
Delete
</MenuItem>
<Delete />
Delete
</MenuItem>
</>
)}
</Menu>
</>
)