feat: Enhance UserPoints component with improved filtering and layout

- Updated UserPoints component to include a more user-friendly filter bar with enhanced styling.
- Added a summary section to display the current filter context.
- Refactored user selection and time period filtering logic for better clarity and performance.
- Improved the layout of points cards and history sections for better visual hierarchy.
- Integrated a bar chart for visual representation of points over time.
- Updated redeem points functionality with better user feedback.

feat: Add keyboard shortcuts in AddTaskModal for improved usability

- Implemented keyboard shortcuts for adding descriptions, subtasks, and due dates.
- Enhanced user experience by providing visual hints for keyboard shortcuts.
- Refactored task creation logic to streamline the process.

fix: Refactor ChoreActionMenu to handle mouse events and improve accessibility

- Added mouse enter and leave event handlers for better interaction feedback.
- Adjusted menu positioning for improved usability.

refactor: Update RichTextEditor to support focus handling from parent components

- Converted RichTextEditor to use forwardRef for better integration with parent components.
- Exposed focus and blur methods for external control.
- Improved image upload handling with better error management.

fix: Adjust SubTask component to handle Enter key behavior correctly

- Modified key event handling to prevent unintended task creation when holding meta or ctrl keys.
- Added autoFocus prop to new task input for better user experience.
This commit is contained in:
Mo Tarbin
2025-07-11 20:10:28 -04:00
parent c2f5569010
commit 953c62cc66
42 changed files with 6262 additions and 1887 deletions

View File

@@ -1,10 +1,73 @@
import { Box, Button, Typography } from '@mui/joy'
import { useCallback, useEffect, useState } from 'react'
import FadeModal from '../../../components/common/FadeModal'
import KeyboardShortcutHint from '../../../components/common/KeyboardShortcutHint'
function ConfirmationModal({ config }) {
const handleAction = isConfirmed => {
config.onClose(isConfirmed)
}
const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false)
const handleAction = useCallback(
isConfirmed => {
config.onClose(isConfirmed)
},
[config],
)
// Keyboard shortcuts for confirmation modal
useEffect(() => {
const handleKeyDown = event => {
if (!config?.isOpen) return
// Show keyboard shortcuts when Ctrl/Cmd is pressed
if (event.ctrlKey || event.metaKey) {
setShowKeyboardShortcuts(true)
}
// Ctrl/Cmd + Y for confirm
if ((event.ctrlKey || event.metaKey) && event.key === 'y') {
event.preventDefault()
handleAction(true)
return
}
// Ctrl/Cmd + X for cancel
if ((event.ctrlKey || event.metaKey) && event.key === 'x') {
event.preventDefault()
handleAction(false)
return
}
// Escape key for cancel
if (event.key === 'Escape') {
event.preventDefault()
handleAction(false)
return
}
// Enter key for confirm
if (event.key === 'Enter') {
event.preventDefault()
handleAction(true)
return
}
}
const handleKeyUp = event => {
if (!event.ctrlKey && !event.metaKey) {
setShowKeyboardShortcuts(false)
}
}
if (config?.isOpen) {
document.addEventListener('keydown', handleKeyDown)
document.addEventListener('keyup', handleKeyUp)
}
return () => {
document.removeEventListener('keydown', handleKeyDown)
document.removeEventListener('keyup', handleKeyUp)
}
}, [config?.isOpen, handleAction])
return (
<FadeModal
@@ -21,22 +84,28 @@ function ConfirmationModal({ config }) {
{config?.message}
</Typography>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Box display={'flex'} justifyContent={'space-around'} mt={1} gap={1}>
<Button
onClick={() => {
handleAction(true)
}}
fullWidth
sx={{ mr: 1 }}
color={config.color ? config.color : 'primary'}
endDecorator={
<KeyboardShortcutHint shortcut='Y' show={showKeyboardShortcuts} />
}
>
{config?.confirmText}
</Button>
<Button
onClick={() => {
handleAction(false)
}}
variant='outlined'
endDecorator={
<KeyboardShortcutHint shortcut='X' show={showKeyboardShortcuts} />
}
>
{config?.cancelText}
</Button>

View File

@@ -1,13 +1,6 @@
import React, { useState } from 'react'
import {
Modal,
Button,
Input,
ModalDialog,
ModalClose,
Box,
Typography,
} from '@mui/joy'
import { Box, Button, Input, Typography } from '@mui/joy'
import { useState } from 'react'
import FadeModal from '../../../components/common/FadeModal'
function DateModal({ isOpen, onClose, onSave, current, title }) {
const [date, setDate] = useState(
@@ -20,26 +13,23 @@ function DateModal({ isOpen, onClose, onSave, current, title }) {
}
return (
<Modal open={isOpen} onClose={onClose}>
<ModalDialog>
{/* <ModalClose /> */}
<Typography variant='h4'>{title}</Typography>
<Input
sx={{ mt: 3 }}
type='date'
value={date}
onChange={e => setDate(e.target.value)}
/>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
Save
</Button>
<Button onClick={onClose} variant='outlined'>
Cancel
</Button>
</Box>
</ModalDialog>
</Modal>
<FadeModal open={isOpen} onClose={onClose}>
<Typography variant='h4'>{title}</Typography>
<Input
sx={{ mt: 3 }}
type='date'
value={date}
onChange={e => setDate(e.target.value)}
/>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
Save
</Button>
<Button onClick={onClose} variant='outlined'>
Cancel
</Button>
</Box>
</FadeModal>
)
}
export default DateModal

View File

@@ -4,11 +4,10 @@ import {
FormControl,
FormHelperText,
Input,
Modal,
ModalDialog,
Typography,
} from '@mui/joy'
import { useState } from 'react'
import FadeModal from '../../../components/common/FadeModal'
function EditThingStateModal({ isOpen, onClose, onSave, currentThing }) {
const [state, setState] = useState(currentThing?.state || '')
@@ -39,31 +38,29 @@ function EditThingStateModal({ isOpen, onClose, onSave, currentThing }) {
}
return (
<Modal open={isOpen} onClose={onClose}>
<ModalDialog>
<Typography level='h4'>Update state</Typography>
<FadeModal open={isOpen} onClose={onClose}>
<Typography level='h4'>Update state</Typography>
<FormControl>
<Typography>Value</Typography>
<Input
placeholder='Thing value'
value={state || ''}
onChange={e => setState(e.target.value)}
sx={{ minWidth: 300 }}
/>
<FormHelperText color='danger'>{errors.state}</FormHelperText>
</FormControl>
<FormControl>
<Typography>Value</Typography>
<Input
placeholder='Thing value'
value={state || ''}
onChange={e => setState(e.target.value)}
sx={{ minWidth: 300 }}
/>
<FormHelperText color='danger'>{errors.state}</FormHelperText>
</FormControl>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{currentThing?.id ? 'Update' : 'Create'}
</Button>
<Button onClick={onClose} variant='outlined'>
{currentThing?.id ? 'Cancel' : 'Close'}
</Button>
</Box>
</ModalDialog>
</Modal>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{currentThing?.id ? 'Update' : 'Create'}
</Button>
<Button onClick={onClose} variant='outlined'>
{currentThing?.id ? 'Cancel' : 'Close'}
</Button>
</Box>
</FadeModal>
)
}
export default EditThingStateModal

View File

@@ -345,104 +345,220 @@ const TimerEditModal = ({ isOpen, onClose, choreId, onTimerUpdate }) => {
}}
>
{/* Active Time */}
<Box
<Card
variant='soft'
sx={{
textAlign: 'center',
p: 2,
borderRadius: 'md',
border: '1px solid',
borderColor: 'success.500',
boxShadow: 1,
px: 2,
py: 1,
minHeight: 90,
height: '100%',
justifyContent: 'start',
}}
>
<Typography
level='h4'
<Box
sx={{
color: 'success.600',
fontWeight: 'bold',
display: 'flex',
alignItems: 'center',
justifyContent: 'start',
mb: 0.5,
}}
>
{formatDuration(calculateCurrentActiveDuration())}
</Typography>
<Typography level='body-xs' sx={{ color: 'text.secondary' }}>
Active Work
</Typography>
</Box>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
backgroundColor: 'success.500',
mr: 1,
}}
/>
<Typography
level='body-md'
sx={{
fontWeight: '500',
color: 'text.primary',
}}
>
Active Work
</Typography>
</Box>
<Box>
<Typography
level='h4'
sx={{
color: 'success.600',
fontWeight: 'bold',
lineHeight: 1.5,
}}
>
{formatDuration(calculateCurrentActiveDuration())}
</Typography>
</Box>
</Card>
{/* Idle Time */}
<Box
<Card
variant='soft'
sx={{
textAlign: 'center',
p: 2,
borderRadius: 'md',
border: '1px solid',
borderColor: 'warning.500',
boxShadow: 1,
px: 2,
py: 1,
minHeight: 90,
height: '100%',
justifyContent: 'start',
}}
>
<Typography
level='h4'
<Box
sx={{
color: 'warning.600',
fontWeight: 'bold',
display: 'flex',
alignItems: 'center',
justifyContent: 'start',
mb: 0.5,
}}
>
{formatDuration(calculateIdleTime())}
</Typography>
<Typography level='body-xs' sx={{ color: 'text.secondary' }}>
Break Time
</Typography>
</Box>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
backgroundColor: 'warning.500',
mr: 1,
}}
/>
<Typography
level='body-md'
sx={{
fontWeight: '500',
color: 'text.primary',
}}
>
Break Time
</Typography>
</Box>
<Box>
<Typography
level='h4'
sx={{
color: 'warning.600',
fontWeight: 'bold',
lineHeight: 1.5,
}}
>
{formatDuration(calculateIdleTime())}
</Typography>
</Box>
</Card>
{/* Total Sessions */}
<Box
<Card
variant='soft'
sx={{
textAlign: 'center',
p: 2,
borderRadius: 'md',
border: '1px solid',
borderColor: 'primary.500',
boxShadow: 1,
px: 2,
py: 1,
minHeight: 90,
height: '100%',
justifyContent: 'start',
}}
>
<Typography
level='h4'
<Box
sx={{
color: 'primary.600',
fontWeight: 'bold',
display: 'flex',
alignItems: 'center',
justifyContent: 'start',
mb: 0.5,
}}
>
{timerData.pauseLog?.length || 0}
</Typography>
<Typography level='body-xs' sx={{ color: 'text.secondary' }}>
Work Sessions
</Typography>
</Box>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
backgroundColor: 'primary.500',
mr: 1,
}}
/>
<Typography
level='body-md'
sx={{
fontWeight: '500',
color: 'text.primary',
}}
>
Work Sessions
</Typography>
</Box>
<Box>
<Typography
level='h4'
sx={{
color: 'primary.600',
fontWeight: 'bold',
lineHeight: 1.5,
}}
>
{timerData.pauseLog?.length || 0}
</Typography>
</Box>
</Card>
{/* Total Session Time */}
<Box
<Card
variant='soft'
sx={{
textAlign: 'center',
p: 2,
borderRadius: 'md',
border: '1px solid',
borderColor: 'neutral.500',
boxShadow: 1,
px: 2,
py: 1,
minHeight: 90,
height: '100%',
justifyContent: 'start',
}}
>
<Typography
level='h4'
<Box
sx={{
color: 'neutral.700',
fontWeight: 'bold',
display: 'flex',
alignItems: 'center',
justifyContent: 'start',
mb: 0.5,
}}
>
{formatTime(calculateTotalDuration())}
</Typography>
<Typography level='body-xs' sx={{ color: 'text.secondary' }}>
Total Time
</Typography>
</Box>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
backgroundColor: 'neutral.500',
mr: 1,
}}
/>
<Typography
level='body-md'
sx={{
fontWeight: '500',
color: 'text.primary',
}}
>
Total Time
</Typography>
</Box>
<Box>
<Typography
level='h4'
sx={{
color: 'neutral.700',
fontWeight: 'bold',
lineHeight: 1.5,
}}
>
{formatTime(calculateTotalDuration())}
</Typography>
</Box>
</Card>
</Box>
{/* Progress Bar */}