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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user