Refactor modals to use FadeModal component for consistent styling and improved user experience
- fix https://github.com/donetick/donetick/issues/222 - Replaced Modal and ModalDialog with FadeModal in LabelModal, PasswordChangeModal, SelectModal, TextModal, UserModal, WriteNFCModal, RedeemPointsModal, and AddTaskModal. - Updated modal structure and layout to maintain functionality while enhancing visual consistency. - Removed unused imports and commented-out code for cleaner codebase.
This commit is contained in:
82
src/components/common/FadeModal.jsx
Normal file
82
src/components/common/FadeModal.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Modal, ModalDialog, ModalOverflow } from '@mui/joy'
|
||||
|
||||
/**
|
||||
* FadeModal component with consistent fade-in/out animations
|
||||
* Can be used as a drop-in replacement for Joy UI's Modal component
|
||||
*/
|
||||
const FadeModal = ({
|
||||
open,
|
||||
onClose,
|
||||
children,
|
||||
size = 'md',
|
||||
fullWidth = false,
|
||||
backdropBlur = true,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
sx={{
|
||||
'& .MuiModal-backdrop': {
|
||||
backdropFilter: backdropBlur ? 'blur(3px)' : 'none',
|
||||
},
|
||||
}}
|
||||
keepMounted
|
||||
// These transition properties create a smooth fade + slide effect
|
||||
transition={{
|
||||
mount: { opacity: 1, transform: 'translateY(0px)' },
|
||||
unmount: { opacity: 0, transform: 'translateY(20px)' },
|
||||
duration: 250, // Animation duration in ms
|
||||
easing: {
|
||||
enter: 'cubic-bezier(0.34, 1.56, 0.64, 1)', // Slight overshoot for natural feel
|
||||
exit: 'cubic-bezier(0.4, 0, 0.2, 1)', // Standard ease out
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<ModalOverflow>
|
||||
<ModalDialog
|
||||
size={size}
|
||||
sx={{
|
||||
minWidth: fullWidth ? '100%' : 'auto',
|
||||
animation: open
|
||||
? 'modalFadeIn 0.35s forwards'
|
||||
: 'modalFadeOut 0.25s forwards',
|
||||
'@keyframes modalFadeIn': {
|
||||
from: { opacity: 0, transform: 'translateY(8px)' },
|
||||
to: { opacity: 1, transform: 'translateY(0)' },
|
||||
},
|
||||
'@keyframes modalFadeOut': {
|
||||
from: { opacity: 1, transform: 'translateY(0)' },
|
||||
to: { opacity: 0, transform: 'translateY(8px)' },
|
||||
},
|
||||
// Add staggered animation for child elements
|
||||
'& > *': {
|
||||
opacity: 0,
|
||||
animation: open
|
||||
? 'contentFadeIn 0.35s forwards'
|
||||
: 'contentFadeOut 0.2s forwards',
|
||||
},
|
||||
// Stagger child animations
|
||||
'& > *:nth-of-type(1)': { animationDelay: '0.05s' },
|
||||
'& > *:nth-of-type(2)': { animationDelay: '0.1s' },
|
||||
'& > *:nth-of-type(3)': { animationDelay: '0.15s' },
|
||||
'& > *:nth-of-type(4)': { animationDelay: '0.2s' },
|
||||
'& > *:nth-of-type(5)': { animationDelay: '0.25s' },
|
||||
'@keyframes contentFadeIn': {
|
||||
to: { opacity: 1 },
|
||||
},
|
||||
'@keyframes contentFadeOut': {
|
||||
to: { opacity: 0 },
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ModalDialog>
|
||||
</ModalOverflow>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default FadeModal
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Sheet,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import Cookies from 'js-cookie'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
@@ -27,8 +28,8 @@ import { apiManager, isTokenValid } from '../../utils/TokenManager'
|
||||
import MFAVerificationModal from './MFAVerificationModal'
|
||||
|
||||
const LoginView = () => {
|
||||
// Only fetch user profile if token is valid to prevent unnecessary queries
|
||||
// const { data: userProfileData } = useUserProfile()
|
||||
// Use React Query client directly to invalidate the user profile query
|
||||
const queryClient = useQueryClient()
|
||||
const [userProfile, setUserProfile] = useState(null)
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
@@ -78,11 +79,19 @@ const LoginView = () => {
|
||||
// Normal login without MFA
|
||||
localStorage.setItem('ca_token', data.token)
|
||||
localStorage.setItem('ca_expiration', data.expire)
|
||||
|
||||
// Refetch user profile after successful login
|
||||
queryClient.refetchQueries(['userProfile'])
|
||||
|
||||
const redirectUrl = Cookies.get('ca_redirect')
|
||||
if (redirectUrl) {
|
||||
|
||||
if (redirectUrl && redirectUrl !== '/') {
|
||||
console.log('Redirecting to', redirectUrl)
|
||||
|
||||
Cookies.remove('ca_redirect')
|
||||
Navigate(redirectUrl)
|
||||
} else {
|
||||
Cookies.remove('ca_redirect')
|
||||
Navigate('/my/chores')
|
||||
}
|
||||
})
|
||||
@@ -143,6 +152,9 @@ const LoginView = () => {
|
||||
localStorage.setItem('ca_token', data.token)
|
||||
localStorage.setItem('ca_expiration', data.expire)
|
||||
|
||||
// Refetch user profile after successful OAuth login
|
||||
queryClient.invalidateQueries(['userProfile'])
|
||||
|
||||
const redirectUrl = Cookies.get('ca_redirect')
|
||||
if (redirectUrl) {
|
||||
Cookies.remove('ca_redirect')
|
||||
@@ -161,17 +173,17 @@ const LoginView = () => {
|
||||
})
|
||||
}
|
||||
const getUserProfileAndNavigateToHome = () => {
|
||||
// Refetch user profile after login
|
||||
// refetchUserProfile().then(() => {
|
||||
// // check if redirect url is set in cookie:
|
||||
const redirectUrl = Cookies.get('ca_redirect')
|
||||
if (redirectUrl) {
|
||||
Cookies.remove('ca_redirect')
|
||||
Navigate(redirectUrl)
|
||||
} else {
|
||||
Navigate('/my/chores')
|
||||
}
|
||||
// })
|
||||
// Refetch user profile after login using React Query
|
||||
queryClient.invalidateQueries(['userProfile']).then(() => {
|
||||
// check if redirect url is set in cookie:
|
||||
const redirectUrl = Cookies.get('ca_redirect')
|
||||
if (redirectUrl) {
|
||||
Cookies.remove('ca_redirect')
|
||||
Navigate(redirectUrl)
|
||||
} else {
|
||||
Navigate('/my/chores')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleMFASuccess = data => {
|
||||
@@ -180,6 +192,9 @@ const LoginView = () => {
|
||||
setMfaModalOpen(false)
|
||||
setMfaSessionToken('')
|
||||
|
||||
// Refetch user profile after MFA success
|
||||
queryClient.invalidateQueries(['userProfile'])
|
||||
|
||||
const redirectUrl = Cookies.get('ca_redirect')
|
||||
if (redirectUrl) {
|
||||
Cookies.remove('ca_redirect')
|
||||
|
||||
@@ -5,13 +5,12 @@ import {
|
||||
Button,
|
||||
Input,
|
||||
Link,
|
||||
Modal,
|
||||
ModalClose,
|
||||
ModalDialog,
|
||||
Stack,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { useState } from 'react'
|
||||
import FadeModal from '../../components/common/FadeModal'
|
||||
import { VerifyMFA } from '../../utils/Fetcher'
|
||||
|
||||
const MFAVerificationModal = ({
|
||||
@@ -70,90 +69,88 @@ const MFAVerificationModal = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={handleClose}>
|
||||
<ModalDialog size='sm' sx={{ maxWidth: 400 }}>
|
||||
<ModalClose />
|
||||
<FadeModal open={open} onClose={handleClose} size='sm'>
|
||||
<ModalClose />
|
||||
|
||||
<Box className='mb-4 text-center'>
|
||||
<Security sx={{ fontSize: 48, color: 'primary.main', mb: 2 }} />
|
||||
<Typography level='h4' sx={{ mb: 1 }}>
|
||||
Two-Factor Authentication
|
||||
</Typography>
|
||||
<Typography level='body-md' sx={{ color: 'text.secondary' }}>
|
||||
Enter the verification code from your authenticator app
|
||||
<Box className='mb-4 text-center'>
|
||||
<Security sx={{ fontSize: 48, color: 'primary.main', mb: 2 }} />
|
||||
<Typography level='h4' sx={{ mb: 1 }}>
|
||||
Two-Factor Authentication
|
||||
</Typography>
|
||||
<Typography level='body-md' sx={{ color: 'text.secondary' }}>
|
||||
Enter the verification code from your authenticator app
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Stack spacing={3}>
|
||||
<Box>
|
||||
<Typography level='body-sm' sx={{ mb: 1 }}>
|
||||
{isBackupCode ? 'Backup Code' : 'Verification Code'}
|
||||
</Typography>
|
||||
<Input
|
||||
placeholder={
|
||||
isBackupCode ? 'Enter backup code' : 'Enter 6-digit code'
|
||||
}
|
||||
value={verificationCode}
|
||||
onChange={e => setVerificationCode(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
fontSize: '1.1em',
|
||||
letterSpacing: isBackupCode ? 'normal' : '0.1em',
|
||||
}}
|
||||
slotProps={{
|
||||
input: {
|
||||
maxLength: isBackupCode ? 50 : 6,
|
||||
pattern: isBackupCode ? undefined : '[0-9]*',
|
||||
},
|
||||
}}
|
||||
startDecorator={<Smartphone />}
|
||||
autoFocus
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Stack spacing={3}>
|
||||
<Box>
|
||||
<Typography level='body-sm' sx={{ mb: 1 }}>
|
||||
{isBackupCode ? 'Backup Code' : 'Verification Code'}
|
||||
</Typography>
|
||||
<Input
|
||||
placeholder={
|
||||
isBackupCode ? 'Enter backup code' : 'Enter 6-digit code'
|
||||
}
|
||||
value={verificationCode}
|
||||
onChange={e => setVerificationCode(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
fontSize: '1.1em',
|
||||
letterSpacing: isBackupCode ? 'normal' : '0.1em',
|
||||
}}
|
||||
slotProps={{
|
||||
input: {
|
||||
maxLength: isBackupCode ? 50 : 6,
|
||||
pattern: isBackupCode ? undefined : '[0-9]*',
|
||||
},
|
||||
}}
|
||||
startDecorator={<Smartphone />}
|
||||
autoFocus
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{error && (
|
||||
<Alert color='danger' size='sm'>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button
|
||||
color='primary'
|
||||
loading={loading}
|
||||
onClick={handleVerify}
|
||||
disabled={!verificationCode.trim()}
|
||||
size='lg'
|
||||
>
|
||||
Verify & Sign In
|
||||
</Button>
|
||||
|
||||
<Box className='text-center'>
|
||||
<Link
|
||||
component='button'
|
||||
type='button'
|
||||
onClick={() => {
|
||||
setIsBackupCode(!isBackupCode)
|
||||
setVerificationCode('')
|
||||
setError('')
|
||||
}}
|
||||
sx={{ fontSize: 'sm' }}
|
||||
>
|
||||
{isBackupCode
|
||||
? 'Use authenticator app instead'
|
||||
: "Can't access your authenticator? Use a backup code"}
|
||||
</Link>
|
||||
</Box>
|
||||
|
||||
<Alert color='neutral' size='sm'>
|
||||
<Typography level='body-xs'>
|
||||
Having trouble? Make sure your authenticator app is synced and try
|
||||
again. Each backup code can only be used once.
|
||||
</Typography>
|
||||
{error && (
|
||||
<Alert color='danger' size='sm'>
|
||||
{error}
|
||||
</Alert>
|
||||
</Stack>
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
<Button
|
||||
color='primary'
|
||||
loading={loading}
|
||||
onClick={handleVerify}
|
||||
disabled={!verificationCode.trim()}
|
||||
size='lg'
|
||||
>
|
||||
Verify & Sign In
|
||||
</Button>
|
||||
|
||||
<Box className='text-center'>
|
||||
<Link
|
||||
component='button'
|
||||
type='button'
|
||||
onClick={() => {
|
||||
setIsBackupCode(!isBackupCode)
|
||||
setVerificationCode('')
|
||||
setError('')
|
||||
}}
|
||||
sx={{ fontSize: 'sm' }}
|
||||
>
|
||||
{isBackupCode
|
||||
? 'Use authenticator app instead'
|
||||
: "Can't access your authenticator? Use a backup code"}
|
||||
</Link>
|
||||
</Box>
|
||||
|
||||
<Alert color='neutral' size='sm'>
|
||||
<Typography level='body-xs'>
|
||||
Having trouble? Make sure your authenticator app is synced and try
|
||||
again. Each backup code can only be used once.
|
||||
</Typography>
|
||||
</Alert>
|
||||
</Stack>
|
||||
</FadeModal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
FormLabel,
|
||||
Input,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { Box, Button, FormLabel, Input, Typography } from '@mui/joy'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import FadeModal from '../../components/common/FadeModal'
|
||||
import ConfirmationModal from './Inputs/ConfirmationModal'
|
||||
|
||||
function EditHistoryModal({ config, historyRecord }) {
|
||||
@@ -29,93 +22,91 @@ function EditHistoryModal({ config, historyRecord }) {
|
||||
const [notes, setNotes] = useState(historyRecord.notes)
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
|
||||
return (
|
||||
<Modal open={config?.isOpen} onClose={config?.onClose}>
|
||||
<ModalDialog>
|
||||
<Typography level='h4' mb={1}>
|
||||
Edit History
|
||||
</Typography>
|
||||
<FormLabel>Due Date</FormLabel>
|
||||
<Input
|
||||
type='datetime-local'
|
||||
value={dueDate}
|
||||
onChange={e => {
|
||||
setDueDate(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<FormLabel>Completed Date</FormLabel>
|
||||
<Input
|
||||
type='datetime-local'
|
||||
value={completedDate}
|
||||
onChange={e => {
|
||||
setCompletedDate(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<FormLabel>Note</FormLabel>
|
||||
<Input
|
||||
fullWidth
|
||||
multiline
|
||||
label='Additional Notes'
|
||||
placeholder='Additional Notes'
|
||||
value={notes}
|
||||
onChange={e => {
|
||||
if (e.target.value.trim() === '') {
|
||||
setNotes(null)
|
||||
return
|
||||
}
|
||||
setNotes(e.target.value)
|
||||
}}
|
||||
size='md'
|
||||
sx={{
|
||||
mb: 1,
|
||||
}}
|
||||
/>
|
||||
<FadeModal open={config?.isOpen} onClose={config?.onClose}>
|
||||
<Typography level='h4' mb={1}>
|
||||
Edit History
|
||||
</Typography>
|
||||
<FormLabel>Due Date</FormLabel>
|
||||
<Input
|
||||
type='datetime-local'
|
||||
value={dueDate}
|
||||
onChange={e => {
|
||||
setDueDate(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<FormLabel>Completed Date</FormLabel>
|
||||
<Input
|
||||
type='datetime-local'
|
||||
value={completedDate}
|
||||
onChange={e => {
|
||||
setCompletedDate(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<FormLabel>Note</FormLabel>
|
||||
<Input
|
||||
fullWidth
|
||||
multiline
|
||||
label='Additional Notes'
|
||||
placeholder='Additional Notes'
|
||||
value={notes}
|
||||
onChange={e => {
|
||||
if (e.target.value.trim() === '') {
|
||||
setNotes(null)
|
||||
return
|
||||
}
|
||||
setNotes(e.target.value)
|
||||
}}
|
||||
size='md'
|
||||
sx={{
|
||||
mb: 1,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 3 button save , cancel and delete */}
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button
|
||||
onClick={() =>
|
||||
config.onSave({
|
||||
id: historyRecord.id,
|
||||
performedAt: moment(completedDate).toISOString(),
|
||||
dueDate: moment(dueDate).toISOString(),
|
||||
notes,
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button onClick={config.onClose} variant='outlined'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsDeleteModalOpen(true)
|
||||
}}
|
||||
variant='outlined'
|
||||
color='danger'
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Box>
|
||||
<ConfirmationModal
|
||||
config={{
|
||||
isOpen: isDeleteModalOpen,
|
||||
onClose: isConfirm => {
|
||||
if (isConfirm) {
|
||||
config.onDelete(historyRecord.id)
|
||||
}
|
||||
setIsDeleteModalOpen(false)
|
||||
},
|
||||
title: 'Delete History',
|
||||
message: 'Are you sure you want to delete this history?',
|
||||
confirmText: 'Delete',
|
||||
cancelText: 'Cancel',
|
||||
{/* 3 button save , cancel and delete */}
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button
|
||||
onClick={() =>
|
||||
config.onSave({
|
||||
id: historyRecord.id,
|
||||
performedAt: moment(completedDate).toISOString(),
|
||||
dueDate: moment(dueDate).toISOString(),
|
||||
notes,
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button onClick={config.onClose} variant='outlined'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsDeleteModalOpen(true)
|
||||
}}
|
||||
/>
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
variant='outlined'
|
||||
color='danger'
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Box>
|
||||
<ConfirmationModal
|
||||
config={{
|
||||
isOpen: isDeleteModalOpen,
|
||||
onClose: isConfirm => {
|
||||
if (isConfirm) {
|
||||
config.onDelete(historyRecord.id)
|
||||
}
|
||||
setIsDeleteModalOpen(false)
|
||||
},
|
||||
title: 'Delete History',
|
||||
message: 'Are you sure you want to delete this history?',
|
||||
confirmText: 'Delete',
|
||||
cancelText: 'Cancel',
|
||||
}}
|
||||
/>
|
||||
</FadeModal>
|
||||
)
|
||||
}
|
||||
export default EditHistoryModal
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Box, Button, Modal, ModalDialog, Typography } from '@mui/joy'
|
||||
import React from 'react'
|
||||
import { Box, Button, Typography } from '@mui/joy'
|
||||
import FadeModal from '../../../components/common/FadeModal'
|
||||
|
||||
function ConfirmationModal({ config }) {
|
||||
const handleAction = isConfirmed => {
|
||||
@@ -7,38 +7,41 @@ function ConfirmationModal({ config }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={config?.isOpen} onClose={config?.onClose}>
|
||||
<ModalDialog>
|
||||
<Typography level='h4' mb={1}>
|
||||
{config?.title}
|
||||
</Typography>
|
||||
<FadeModal
|
||||
open={config?.isOpen}
|
||||
onClose={config?.onClose}
|
||||
size='sm'
|
||||
unmountDelay={250}
|
||||
>
|
||||
<Typography level='h4' mb={1}>
|
||||
{config?.title}
|
||||
</Typography>
|
||||
|
||||
<Typography level='body-md' gutterBottom>
|
||||
{config?.message}
|
||||
</Typography>
|
||||
<Typography level='body-md' gutterBottom>
|
||||
{config?.message}
|
||||
</Typography>
|
||||
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleAction(true)
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ mr: 1 }}
|
||||
color={config.color ? config.color : 'primary'}
|
||||
>
|
||||
{config?.confirmText}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleAction(false)
|
||||
}}
|
||||
variant='outlined'
|
||||
>
|
||||
{config?.cancelText}
|
||||
</Button>
|
||||
</Box>
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleAction(true)
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ mr: 1 }}
|
||||
color={config.color ? config.color : 'primary'}
|
||||
>
|
||||
{config?.confirmText}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleAction(false)
|
||||
}}
|
||||
variant='outlined'
|
||||
>
|
||||
{config?.cancelText}
|
||||
</Button>
|
||||
</Box>
|
||||
</FadeModal>
|
||||
)
|
||||
}
|
||||
export default ConfirmationModal
|
||||
|
||||
@@ -4,14 +4,13 @@ import {
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Input,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
Option,
|
||||
Select,
|
||||
Textarea,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { useEffect, useState } from 'react'
|
||||
import FadeModal from '../../../components/common/FadeModal'
|
||||
|
||||
function CreateThingModal({ isOpen, onClose, onSave, currentThing }) {
|
||||
const [name, setName] = useState(currentThing?.name || '')
|
||||
@@ -59,87 +58,80 @@ function CreateThingModal({ isOpen, onClose, onSave, currentThing }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onClose={onClose}>
|
||||
<ModalDialog>
|
||||
{/* <ModalClose /> */}
|
||||
<Typography level='h4'>
|
||||
{currentThing?.id ? 'Edit' : 'Create'} Thing
|
||||
</Typography>
|
||||
<FadeModal open={isOpen} onClose={onClose}>
|
||||
<Typography level='h4'>
|
||||
{currentThing?.id ? 'Edit' : 'Create'} Thing
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<Typography>Name</Typography>
|
||||
<Textarea
|
||||
placeholder='Thing name'
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
sx={{ minWidth: 300 }}
|
||||
/>
|
||||
<FormHelperText color='danger'>{errors.name}</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Typography>Type</Typography>
|
||||
<Select value={type} sx={{ minWidth: 300 }}>
|
||||
{['text', 'number', 'boolean'].map(type => (
|
||||
<Option value={type} key={type} onClick={() => setType(type)}>
|
||||
{type.charAt(0).toUpperCase() + type.slice(1)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<FormHelperText color='danger'>{errors.type}</FormHelperText>
|
||||
</FormControl>
|
||||
{type === 'text' && (
|
||||
<FormControl>
|
||||
<Typography>Name</Typography>
|
||||
<Textarea
|
||||
placeholder='Thing name'
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
<Typography>Value</Typography>
|
||||
<Input
|
||||
placeholder='Thing value'
|
||||
value={state || ''}
|
||||
onChange={e => setState(e.target.value)}
|
||||
sx={{ minWidth: 300 }}
|
||||
/>
|
||||
<FormHelperText color='danger'>{errors.name}</FormHelperText>
|
||||
<FormHelperText color='danger'>{errors.state}</FormHelperText>
|
||||
</FormControl>
|
||||
)}
|
||||
{type === 'number' && (
|
||||
<FormControl>
|
||||
<Typography>Type</Typography>
|
||||
<Select value={type} sx={{ minWidth: 300 }}>
|
||||
{['text', 'number', 'boolean'].map(type => (
|
||||
<Option value={type} key={type} onClick={() => setType(type)}>
|
||||
{type.charAt(0).toUpperCase() + type.slice(1)}
|
||||
<Typography>Value</Typography>
|
||||
<Input
|
||||
placeholder='Thing value'
|
||||
type='number'
|
||||
value={state || ''}
|
||||
onChange={e => {
|
||||
setState(e.target.value)
|
||||
}}
|
||||
sx={{ minWidth: 300 }}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
{type === 'boolean' && (
|
||||
<FormControl>
|
||||
<Typography>Value</Typography>
|
||||
<Select sx={{ minWidth: 300 }} value={state}>
|
||||
{['true', 'false'].map(value => (
|
||||
<Option value={value} key={value} onClick={() => setState(value)}>
|
||||
{value.charAt(0).toUpperCase() + value.slice(1)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<FormHelperText color='danger'>{errors.type}</FormHelperText>
|
||||
</FormControl>
|
||||
{type === 'text' && (
|
||||
<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>
|
||||
)}
|
||||
{type === 'number' && (
|
||||
<FormControl>
|
||||
<Typography>Value</Typography>
|
||||
<Input
|
||||
placeholder='Thing value'
|
||||
type='number'
|
||||
value={state || ''}
|
||||
onChange={e => {
|
||||
setState(e.target.value)
|
||||
}}
|
||||
sx={{ minWidth: 300 }}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
{type === 'boolean' && (
|
||||
<FormControl>
|
||||
<Typography>Value</Typography>
|
||||
<Select sx={{ minWidth: 300 }} value={state}>
|
||||
{['true', 'false'].map(value => (
|
||||
<Option
|
||||
value={value}
|
||||
key={value}
|
||||
onClick={() => setState(value)}
|
||||
>
|
||||
{value.charAt(0).toUpperCase() + value.slice(1)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</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 CreateThingModal
|
||||
|
||||
@@ -3,13 +3,12 @@ import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
Option,
|
||||
Select,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { useEffect, useState } from 'react'
|
||||
import FadeModal from '../../../components/common/FadeModal'
|
||||
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useNotification } from '../../../service/NotificationProvider.jsx'
|
||||
@@ -58,29 +57,9 @@ function LabelModal({ isOpen, onClose, label }) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Mutation for saving labels
|
||||
// const saveLabelMutation = useMutation(
|
||||
// newLabel =>
|
||||
// label
|
||||
// ? UpdateLabel({ id: label.id, ...newLabel })
|
||||
// : CreateLabel(newLabel),
|
||||
// {
|
||||
// onSuccess: () => {
|
||||
// queryClient.invalidateQueries('labels')
|
||||
// onClose()
|
||||
// },
|
||||
// onError: () => {
|
||||
// setError('Failed to save label. Please try again.')
|
||||
// },
|
||||
// },
|
||||
// )
|
||||
|
||||
const handleSave = () => {
|
||||
if (!validateLabel()) return
|
||||
const saveLabel = label?.id && label.id !== -1 ? UpdateLabel : CreateLabel
|
||||
// ? { id: label.id, name: labelName, color }
|
||||
// : { name: labelName, color }
|
||||
// saveLabelMutation.mutate({ name: labelName, color })
|
||||
saveLabel({
|
||||
id: label?.id,
|
||||
name: labelName,
|
||||
@@ -110,79 +89,77 @@ function LabelModal({ isOpen, onClose, label }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onClose={onClose}>
|
||||
<ModalDialog>
|
||||
<Typography level='title-md' mb={1}>
|
||||
{label ? 'Edit Label' : 'Add Label'}
|
||||
<FadeModal open={isOpen} onClose={onClose}>
|
||||
<Typography level='title-md' mb={1}>
|
||||
{label ? 'Edit Label' : 'Add Label'}
|
||||
</Typography>
|
||||
|
||||
<FormControl>
|
||||
<Typography gutterBottom level='body-sm' alignSelf='start'>
|
||||
Name
|
||||
</Typography>
|
||||
<Input
|
||||
fullWidth
|
||||
id='labelName'
|
||||
value={labelName}
|
||||
onChange={e => setLabelName(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<Typography gutterBottom level='body-sm' alignSelf='start'>
|
||||
Name
|
||||
</Typography>
|
||||
<Input
|
||||
fullWidth
|
||||
id='labelName'
|
||||
value={labelName}
|
||||
onChange={e => setLabelName(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Typography gutterBottom level='body-sm' alignSelf='start'>
|
||||
Color
|
||||
</Typography>
|
||||
<Select
|
||||
value={color}
|
||||
onChange={(e, value) => value && setColor(value)}
|
||||
renderValue={selected => (
|
||||
<Typography
|
||||
startDecorator={
|
||||
<Box
|
||||
className='size-4'
|
||||
borderRadius={10}
|
||||
sx={{ background: selected.value }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{selected.label}
|
||||
</Typography>
|
||||
)}
|
||||
>
|
||||
{LABEL_COLORS.map(val => (
|
||||
<Option key={val.value} value={val.value}>
|
||||
<Box className='flex items-center justify-between'>
|
||||
<Box
|
||||
width={20}
|
||||
height={20}
|
||||
borderRadius={10}
|
||||
sx={{ background: val.value }}
|
||||
/>
|
||||
<Typography sx={{ ml: 1 }} variant='caption'>
|
||||
{val.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<Typography gutterBottom level='body-sm' alignSelf='start'>
|
||||
Color
|
||||
</Typography>
|
||||
<Select
|
||||
value={color}
|
||||
onChange={(e, value) => value && setColor(value)}
|
||||
renderValue={selected => (
|
||||
<Typography
|
||||
startDecorator={
|
||||
<Box
|
||||
className='size-4'
|
||||
borderRadius={10}
|
||||
sx={{ background: selected.value }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{selected.label}
|
||||
</Typography>
|
||||
)}
|
||||
>
|
||||
{LABEL_COLORS.map(val => (
|
||||
<Option key={val.value} value={val.value}>
|
||||
<Box className='flex items-center justify-between'>
|
||||
<Box
|
||||
width={20}
|
||||
height={20}
|
||||
borderRadius={10}
|
||||
sx={{ background: val.value }}
|
||||
/>
|
||||
<Typography sx={{ ml: 1 }} variant='caption'>
|
||||
{val.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{error && (
|
||||
<Typography color='warning' level='body-sm'>
|
||||
{error}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Typography color='warning' level='body-sm'>
|
||||
{error}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Box display='flex' justifyContent='space-around' mt={1}>
|
||||
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
|
||||
{label ? 'Save Changes' : 'Add Label'}
|
||||
</Button>
|
||||
<Button onClick={onClose} variant='outlined'>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
<Box display='flex' justifyContent='space-around' mt={1}>
|
||||
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
|
||||
{label ? 'Save Changes' : 'Add Label'}
|
||||
</Button>
|
||||
<Button onClick={onClose} variant='outlined'>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</FadeModal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,10 @@ import {
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Input,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import React, { useEffect } from 'react'
|
||||
import FadeModal from '../../../components/common/FadeModal'
|
||||
|
||||
function PassowrdChangeModal({ isOpen, onClose }) {
|
||||
const [password, setPassword] = React.useState('')
|
||||
@@ -40,78 +39,76 @@ function PassowrdChangeModal({ isOpen, onClose }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onClose={onClose}>
|
||||
<ModalDialog>
|
||||
<Typography level='h4' mb={1}>
|
||||
<FadeModal open={isOpen} onClose={onClose}>
|
||||
<Typography level='h4' mb={1}>
|
||||
Change Password
|
||||
</Typography>
|
||||
|
||||
<Typography level='body-md' gutterBottom>
|
||||
Please enter your new password.
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<Typography level='body2' alignSelf={'start'}>
|
||||
New Password
|
||||
</Typography>
|
||||
<Input
|
||||
margin='normal'
|
||||
required
|
||||
fullWidth
|
||||
name='password'
|
||||
label='Password'
|
||||
type='password'
|
||||
id='password'
|
||||
value={password}
|
||||
onChange={e => {
|
||||
setPasswordTouched(true)
|
||||
setPassword(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<Typography level='body2' alignSelf={'start'}>
|
||||
Confirm Password
|
||||
</Typography>
|
||||
<Input
|
||||
margin='normal'
|
||||
required
|
||||
fullWidth
|
||||
name='confirmPassword'
|
||||
label='confirmPassword'
|
||||
type='password'
|
||||
id='confirmPassword'
|
||||
value={confirmPassword}
|
||||
onChange={e => {
|
||||
setConfirmPasswordTouched(true)
|
||||
setConfirmPassword(e.target.value)
|
||||
}}
|
||||
/>
|
||||
|
||||
<FormHelperText>{passwordError}</FormHelperText>
|
||||
</FormControl>
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button
|
||||
disabled={passwordError != null}
|
||||
onClick={() => {
|
||||
handleAction(true)
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
Change Password
|
||||
</Typography>
|
||||
|
||||
<Typography level='body-md' gutterBottom>
|
||||
Please enter your new password.
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<Typography level='body2' alignSelf={'start'}>
|
||||
New Password
|
||||
</Typography>
|
||||
<Input
|
||||
margin='normal'
|
||||
required
|
||||
fullWidth
|
||||
name='password'
|
||||
label='Password'
|
||||
type='password'
|
||||
id='password'
|
||||
value={password}
|
||||
onChange={e => {
|
||||
setPasswordTouched(true)
|
||||
setPassword(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<Typography level='body2' alignSelf={'start'}>
|
||||
Confirm Password
|
||||
</Typography>
|
||||
<Input
|
||||
margin='normal'
|
||||
required
|
||||
fullWidth
|
||||
name='confirmPassword'
|
||||
label='confirmPassword'
|
||||
type='password'
|
||||
id='confirmPassword'
|
||||
value={confirmPassword}
|
||||
onChange={e => {
|
||||
setConfirmPasswordTouched(true)
|
||||
setConfirmPassword(e.target.value)
|
||||
}}
|
||||
/>
|
||||
|
||||
<FormHelperText>{passwordError}</FormHelperText>
|
||||
</FormControl>
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button
|
||||
disabled={passwordError != null}
|
||||
onClick={() => {
|
||||
handleAction(true)
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
Change Password
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleAction(false)
|
||||
}}
|
||||
variant='outlined'
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleAction(false)
|
||||
}}
|
||||
variant='outlined'
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</FadeModal>
|
||||
)
|
||||
}
|
||||
export default PassowrdChangeModal
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
Option,
|
||||
Select,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { Box, Button, Option, Select, Typography } from '@mui/joy'
|
||||
import React from 'react'
|
||||
import FadeModal from '../../../components/common/FadeModal'
|
||||
|
||||
function SelectModal({ isOpen, onClose, onSave, options, title, displayKey,placeholder }) {
|
||||
function SelectModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
options,
|
||||
title,
|
||||
displayKey,
|
||||
placeholder,
|
||||
}) {
|
||||
const [selected, setSelected] = React.useState(null)
|
||||
const handleSave = () => {
|
||||
onSave(options.find(item => item.id === selected))
|
||||
@@ -17,33 +18,31 @@ function SelectModal({ isOpen, onClose, onSave, options, title, displayKey,place
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onClose={onClose}>
|
||||
<ModalDialog>
|
||||
<Typography variant='h4'>{title}</Typography>
|
||||
<Select placeholder={placeholder}>
|
||||
{options.map((item, index) => (
|
||||
<Option
|
||||
value={item.id}
|
||||
key={item[displayKey]}
|
||||
onClick={() => {
|
||||
setSelected(item.id)
|
||||
}}
|
||||
>
|
||||
{item[displayKey]}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<FadeModal open={isOpen} onClose={onClose}>
|
||||
<Typography variant='h4'>{title}</Typography>
|
||||
<Select placeholder={placeholder}>
|
||||
{options.map((item, index) => (
|
||||
<Option
|
||||
value={item.id}
|
||||
key={item[displayKey]}
|
||||
onClick={() => {
|
||||
setSelected(item.id)
|
||||
}}
|
||||
>
|
||||
{item[displayKey]}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<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>
|
||||
<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 SelectModal
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Box, Button, Modal, ModalDialog, Textarea, Typography } from '@mui/joy'
|
||||
import { Box, Button, Textarea, Typography } from '@mui/joy'
|
||||
import { useState } from 'react'
|
||||
import FadeModal from '../../../components/common/FadeModal'
|
||||
|
||||
function TextModal({
|
||||
isOpen,
|
||||
@@ -18,29 +19,26 @@ function TextModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onClose={onClose}>
|
||||
<ModalDialog>
|
||||
{/* <ModalClose /> */}
|
||||
<Typography variant='h4'>{title}</Typography>
|
||||
<Textarea
|
||||
placeholder='Type in here…'
|
||||
value={text}
|
||||
onChange={e => setText(e.target.value)}
|
||||
minRows={2}
|
||||
maxRows={4}
|
||||
sx={{ minWidth: 300 }}
|
||||
/>
|
||||
<FadeModal open={isOpen} onClose={onClose}>
|
||||
<Typography variant='h4'>{title}</Typography>
|
||||
<Textarea
|
||||
placeholder='Type in here…'
|
||||
value={text}
|
||||
onChange={e => setText(e.target.value)}
|
||||
minRows={2}
|
||||
maxRows={4}
|
||||
sx={{ minWidth: 300 }}
|
||||
/>
|
||||
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
|
||||
{okText ? okText : 'Save'}
|
||||
</Button>
|
||||
<Button onClick={onClose} variant='outlined'>
|
||||
{cancelText ? cancelText : 'Cancel'}
|
||||
</Button>
|
||||
</Box>
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
|
||||
{okText ? okText : 'Save'}
|
||||
</Button>
|
||||
<Button onClick={onClose} variant='outlined'>
|
||||
{cancelText ? cancelText : 'Cancel'}
|
||||
</Button>
|
||||
</Box>
|
||||
</FadeModal>
|
||||
)
|
||||
}
|
||||
export default TextModal
|
||||
|
||||
@@ -1,57 +1,44 @@
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
List,
|
||||
ListItem,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
ModalOverflow,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { Avatar, Box, Button, List, ListItem, Typography } from '@mui/joy'
|
||||
import FadeModal from '../../../components/common/FadeModal'
|
||||
|
||||
const UserModal = ({ isOpen, performers = [], onSelect, onClose }) => {
|
||||
return (
|
||||
<Modal open={isOpen} onClose={onClose}>
|
||||
<ModalOverflow>
|
||||
<ModalDialog size='md' sx={{ minWidth: 360 }}>
|
||||
<Typography level='h4' sx={{ mb: 2 }}>
|
||||
Select User
|
||||
</Typography>
|
||||
<List sx={{ mb: 2 }}>
|
||||
{performers.map(user => (
|
||||
<ListItem
|
||||
key={user.id}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.04)',
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
onSelect(user)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Avatar
|
||||
size='lg'
|
||||
src={user.image || user.avatar}
|
||||
alt={user.displayName || user.name}
|
||||
/>
|
||||
<Typography>{user.displayName || user.name}</Typography>
|
||||
</Box>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||
<Button variant='outlined' color='neutral' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</ModalDialog>
|
||||
</ModalOverflow>
|
||||
</Modal>
|
||||
<FadeModal open={isOpen} onClose={onClose} size='md' fullWidth>
|
||||
<Typography level='h4' sx={{ mb: 2 }}>
|
||||
Select User
|
||||
</Typography>
|
||||
<List sx={{ mb: 2 }}>
|
||||
{performers.map(user => (
|
||||
<ListItem
|
||||
key={user.id}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.04)',
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
onSelect(user)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Avatar
|
||||
size='lg'
|
||||
src={user.image || user.avatar}
|
||||
alt={user.displayName || user.name}
|
||||
/>
|
||||
<Typography>{user.displayName || user.name}</Typography>
|
||||
</Box>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||
<Button variant='outlined' color='neutral' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</FadeModal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { CopyAll } from '@mui/icons-material'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Input,
|
||||
ListItem,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import React, { useState } from 'react'
|
||||
import { Box, Button, Checkbox, Input, ListItem, Typography } from '@mui/joy'
|
||||
import { useState } from 'react'
|
||||
import FadeModal from '../../../components/common/FadeModal'
|
||||
|
||||
function WriteNFCModal({ config }) {
|
||||
const [nfcStatus, setNfcStatus] = useState('idle') // 'idle', 'writing', 'success', 'error'
|
||||
@@ -60,63 +52,61 @@ function WriteNFCModal({ config }) {
|
||||
return url
|
||||
}
|
||||
return (
|
||||
<Modal open={config?.isOpen} onClose={handleClose}>
|
||||
<ModalDialog>
|
||||
<Typography level='h4' mb={1}>
|
||||
{nfcStatus === 'success' ? 'Success!' : 'Write to NFC'}
|
||||
</Typography>
|
||||
<FadeModal open={config?.isOpen} onClose={handleClose}>
|
||||
<Typography level='h4' mb={1}>
|
||||
{nfcStatus === 'success' ? 'Success!' : 'Write to NFC'}
|
||||
</Typography>
|
||||
|
||||
{nfcStatus === 'success' ? (
|
||||
{nfcStatus === 'success' ? (
|
||||
<Typography level='body-md' gutterBottom>
|
||||
URL written to NFC tag successfully!
|
||||
</Typography>
|
||||
) : (
|
||||
<>
|
||||
<Typography level='body-md' gutterBottom>
|
||||
URL written to NFC tag successfully!
|
||||
{nfcStatus === 'error'
|
||||
? errorMessage
|
||||
: 'Press the button below to write to NFC.'}
|
||||
</Typography>
|
||||
) : (
|
||||
<>
|
||||
<Typography level='body-md' gutterBottom>
|
||||
{nfcStatus === 'error'
|
||||
? errorMessage
|
||||
: 'Press the button below to write to NFC.'}
|
||||
</Typography>
|
||||
<Input
|
||||
value={getURL()}
|
||||
fullWidth
|
||||
readOnly
|
||||
label='URL'
|
||||
sx={{ mt: 1 }}
|
||||
endDecorator={
|
||||
<CopyAll
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(getURL())
|
||||
alert('URL copied to clipboard!')
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ListItem>
|
||||
<Checkbox
|
||||
checked={isAutoCompleteWhenScan}
|
||||
onChange={e => setIsAutoCompleteWhenScan(e.target.checked)}
|
||||
label='Auto-complete when scanned'
|
||||
<Input
|
||||
value={getURL()}
|
||||
fullWidth
|
||||
readOnly
|
||||
label='URL'
|
||||
sx={{ mt: 1 }}
|
||||
endDecorator={
|
||||
<CopyAll
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(getURL())
|
||||
alert('URL copied to clipboard!')
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button
|
||||
onClick={() => writeToNFC(getURL())}
|
||||
fullWidth
|
||||
sx={{ mr: 1 }}
|
||||
disabled={nfcStatus === 'writing'}
|
||||
>
|
||||
Write NFC
|
||||
</Button>
|
||||
<Button onClick={requestNFCAccess} variant='outlined'>
|
||||
Request Access
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
}
|
||||
/>
|
||||
<ListItem>
|
||||
<Checkbox
|
||||
checked={isAutoCompleteWhenScan}
|
||||
onChange={e => setIsAutoCompleteWhenScan(e.target.checked)}
|
||||
label='Auto-complete when scanned'
|
||||
/>
|
||||
</ListItem>
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button
|
||||
onClick={() => writeToNFC(getURL())}
|
||||
fullWidth
|
||||
sx={{ mr: 1 }}
|
||||
disabled={nfcStatus === 'writing'}
|
||||
>
|
||||
Write NFC
|
||||
</Button>
|
||||
<Button onClick={requestNFCAccess} variant='outlined'>
|
||||
Request Access
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</FadeModal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Input,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { Box, Button, FormLabel, IconButton, Input, Typography } from '@mui/joy'
|
||||
import { useEffect, useState } from 'react'
|
||||
import FadeModal from '../../components/common/FadeModal'
|
||||
|
||||
function RedeemPointsModal({ config }) {
|
||||
useEffect(() => {
|
||||
@@ -20,71 +12,69 @@ function RedeemPointsModal({ config }) {
|
||||
const predefinedPoints = [1, 5, 10, 25]
|
||||
|
||||
return (
|
||||
<Modal open={config?.isOpen} onClose={config?.onClose}>
|
||||
<ModalDialog>
|
||||
<Typography level='h4' mb={1}>
|
||||
Redeem Points
|
||||
</Typography>
|
||||
<FormLabel>
|
||||
Points to Redeem ({config.available ? config.available : 0} points
|
||||
available)
|
||||
</FormLabel>
|
||||
<Input
|
||||
type='number'
|
||||
value={points}
|
||||
slotProps={{
|
||||
input: { min: 0, max: config.available ? config.available : 0 },
|
||||
}}
|
||||
onChange={e => {
|
||||
if (e.target.value > config.available) {
|
||||
setPoints(config.available)
|
||||
return
|
||||
}
|
||||
setPoints(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<FormLabel>Or select from predefined points:</FormLabel>
|
||||
<Box display='flex' justifyContent='space-evenly' mb={1}>
|
||||
{predefinedPoints.map(point => (
|
||||
<IconButton
|
||||
variant='outlined'
|
||||
disabled={points + point > config.available}
|
||||
sx={{ borderRadius: '50%' }}
|
||||
key={point}
|
||||
onClick={() => {
|
||||
const newPoints = points + point
|
||||
if (newPoints > config.available) {
|
||||
setPoints(config.available)
|
||||
return
|
||||
}
|
||||
setPoints(newPoints)
|
||||
}}
|
||||
>
|
||||
{point}
|
||||
</IconButton>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* 3 button save , cancel and delete */}
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button
|
||||
onClick={() =>
|
||||
config.onSave({
|
||||
points: Number(points),
|
||||
userId: config.user.userId,
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
sx={{ mr: 1 }}
|
||||
<FadeModal open={config?.isOpen} onClose={config?.onClose}>
|
||||
<Typography level='h4' mb={1}>
|
||||
Redeem Points
|
||||
</Typography>
|
||||
<FormLabel>
|
||||
Points to Redeem ({config.available ? config.available : 0} points
|
||||
available)
|
||||
</FormLabel>
|
||||
<Input
|
||||
type='number'
|
||||
value={points}
|
||||
slotProps={{
|
||||
input: { min: 0, max: config.available ? config.available : 0 },
|
||||
}}
|
||||
onChange={e => {
|
||||
if (e.target.value > config.available) {
|
||||
setPoints(config.available)
|
||||
return
|
||||
}
|
||||
setPoints(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<FormLabel>Or select from predefined points:</FormLabel>
|
||||
<Box display='flex' justifyContent='space-evenly' mb={1}>
|
||||
{predefinedPoints.map(point => (
|
||||
<IconButton
|
||||
variant='outlined'
|
||||
disabled={points + point > config.available}
|
||||
sx={{ borderRadius: '50%' }}
|
||||
key={point}
|
||||
onClick={() => {
|
||||
const newPoints = points + point
|
||||
if (newPoints > config.available) {
|
||||
setPoints(config.available)
|
||||
return
|
||||
}
|
||||
setPoints(newPoints)
|
||||
}}
|
||||
>
|
||||
Redeem
|
||||
</Button>
|
||||
<Button onClick={config.onClose} variant='outlined'>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
{point}
|
||||
</IconButton>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* 3 button save , cancel and delete */}
|
||||
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||
<Button
|
||||
onClick={() =>
|
||||
config.onSave({
|
||||
points: Number(points),
|
||||
userId: config.user.userId,
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
Redeem
|
||||
</Button>
|
||||
<Button onClick={config.onClose} variant='outlined'>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</FadeModal>
|
||||
)
|
||||
}
|
||||
export default RedeemPointsModal
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import { Add, EditNotifications } from '@mui/icons-material'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Chip,
|
||||
Input,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
ModalOverflow,
|
||||
Option,
|
||||
Select,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { Box, Button, Chip, Input, Option, Select, Typography } from '@mui/joy'
|
||||
import { FormControl } from '@mui/material'
|
||||
import * as chrono from 'chrono-node'
|
||||
import moment from 'moment'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import FadeModal from '../../components/common/FadeModal'
|
||||
import { useCreateChore } from '../../queries/ChoreQueries'
|
||||
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
||||
import { isPlusAccount } from '../../utils/Helpers'
|
||||
@@ -399,101 +389,100 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={isModalOpen} onClose={handleCloseModal}>
|
||||
<ModalOverflow>
|
||||
<ModalDialog size='lg' sx={{ minWidth: '100%' }}>
|
||||
<Typography level='h4'>Create new task</Typography>
|
||||
<Chip startDecorator='🚧' variant='soft' color='warning' size='sm'>
|
||||
Experimental Feature
|
||||
</Chip>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography level='body-sm'>Task in a sentence:</Typography>
|
||||
<LearnMoreButton
|
||||
content={
|
||||
<>
|
||||
<Typography level='body-sm' sx={{ mb: 1 }}>
|
||||
This feature lets you create a task simply by typing a
|
||||
sentence. It attempt parses the sentence to identify the
|
||||
task's due date, priority, and frequency.
|
||||
</Typography>
|
||||
<FadeModal
|
||||
open={isModalOpen}
|
||||
onClose={handleCloseModal}
|
||||
size='lg'
|
||||
fullWidth={true}
|
||||
>
|
||||
<Typography level='h4'>Create new task</Typography>
|
||||
<Chip startDecorator='🚧' variant='soft' color='warning' size='sm'>
|
||||
Experimental Feature
|
||||
</Chip>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography level='body-sm'>Task in a sentence:</Typography>
|
||||
<LearnMoreButton
|
||||
content={
|
||||
<>
|
||||
<Typography level='body-sm' sx={{ mb: 1 }}>
|
||||
This feature lets you create a task simply by typing a
|
||||
sentence. It attempt parses the sentence to identify the
|
||||
task's due date, priority, and frequency.
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
level='body-sm'
|
||||
sx={{ fontWeight: 'bold', mt: 2 }}
|
||||
>
|
||||
Examples:
|
||||
</Typography>
|
||||
<Typography level='body-sm' sx={{ fontWeight: 'bold', mt: 2 }}>
|
||||
Examples:
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
level='body-sm'
|
||||
component='ul'
|
||||
sx={{ pl: 2, mt: 1, listStyle: 'disc' }}
|
||||
>
|
||||
<li>
|
||||
<strong>Priority:</strong>For highest priority any of
|
||||
the following keyword <em>P1</em>, <em>Urgent</em>,{' '}
|
||||
<em>Important</em>, or <em>ASAP</em>. For lower
|
||||
priorities, use <em>P2</em>, <em>P3</em>, or <em>P4</em>
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Due date:</strong> Specify dates with phrases
|
||||
like <em>tomorrow</em>, <em>next week</em>,{' '}
|
||||
<em>Monday</em>, or <em>August 1st at 12pm</em>.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Frequency:</strong> Set recurring tasks with
|
||||
terms like <em>daily</em>, <em>weekly</em>,{' '}
|
||||
<em>monthly</em>, <em>yearly</em>, or patterns such as{' '}
|
||||
<em>every Tuesday and Thursday</em>.
|
||||
</li>
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Typography
|
||||
level='body-sm'
|
||||
component='ul'
|
||||
sx={{ pl: 2, mt: 1, listStyle: 'disc' }}
|
||||
>
|
||||
<li>
|
||||
<strong>Priority:</strong>For highest priority any of the
|
||||
following keyword <em>P1</em>, <em>Urgent</em>,{' '}
|
||||
<em>Important</em>, or <em>ASAP</em>. For lower priorities,
|
||||
use <em>P2</em>, <em>P3</em>, or <em>P4</em>.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Due date:</strong> Specify dates with phrases like{' '}
|
||||
<em>tomorrow</em>, <em>next week</em>, <em>Monday</em>, or{' '}
|
||||
<em>August 1st at 12pm</em>.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Frequency:</strong> Set recurring tasks with terms
|
||||
like <em>daily</em>, <em>weekly</em>, <em>monthly</em>,{' '}
|
||||
<em>yearly</em>, or patterns such as{' '}
|
||||
<em>every Tuesday and Thursday</em>.
|
||||
</li>
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<SmartTaskTitleInput
|
||||
autoFocus
|
||||
value={taskText}
|
||||
placeholder='Type your full text here...'
|
||||
onChange={text => {
|
||||
setTaskText(text)
|
||||
}}
|
||||
customRenderer={renderedParts}
|
||||
onEnterPressed={handleEnterPressed}
|
||||
suggestions={{
|
||||
'#': {
|
||||
value: 'id',
|
||||
display: 'name',
|
||||
options: userLabels ? userLabels : [],
|
||||
},
|
||||
'!': {
|
||||
value: 'id',
|
||||
display: 'name',
|
||||
options: [
|
||||
{ id: '1', name: 'P1' },
|
||||
{ id: '2', name: 'P2' },
|
||||
{ id: '3', name: 'P3' },
|
||||
{ id: '4', name: 'P4' },
|
||||
],
|
||||
},
|
||||
'@': {
|
||||
value: 'userId',
|
||||
display: 'displayName',
|
||||
options: circleMembers?.res || [],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* <Box>
|
||||
<SmartTaskTitleInput
|
||||
autoFocus
|
||||
value={taskText}
|
||||
placeholder='Type your full text here...'
|
||||
onChange={text => {
|
||||
setTaskText(text)
|
||||
}}
|
||||
customRenderer={renderedParts}
|
||||
onEnterPressed={handleEnterPressed}
|
||||
suggestions={{
|
||||
'#': {
|
||||
value: 'id',
|
||||
display: 'name',
|
||||
options: userLabels ? userLabels : [],
|
||||
},
|
||||
'!': {
|
||||
value: 'id',
|
||||
display: 'name',
|
||||
options: [
|
||||
{ id: '1', name: 'P1' },
|
||||
{ id: '2', name: 'P2' },
|
||||
{ id: '3', name: 'P3' },
|
||||
{ id: '4', name: 'P4' },
|
||||
],
|
||||
},
|
||||
'@': {
|
||||
value: 'userId',
|
||||
display: 'displayName',
|
||||
options: circleMembers?.res || [],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* <Box>
|
||||
<Typography level='body-sm'>Title:</Typography>
|
||||
<Input
|
||||
value={taskTitle}
|
||||
@@ -501,126 +490,122 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => {
|
||||
sx={{ width: '100%', fontSize: '16px' }}
|
||||
/>
|
||||
</Box> */}
|
||||
<Box>
|
||||
{!hasDescription && (
|
||||
<Button
|
||||
startDecorator={<Add />}
|
||||
variant='plain'
|
||||
size='sm'
|
||||
onClick={() => setHasDescription(true)}
|
||||
>
|
||||
Description
|
||||
</Button>
|
||||
)}
|
||||
{!hasSubTasks && (
|
||||
<Button
|
||||
startDecorator={<Add />}
|
||||
variant='plain'
|
||||
size='sm'
|
||||
onClick={() => setHasSubTasks(true)}
|
||||
>
|
||||
Subtasks
|
||||
</Button>
|
||||
)}
|
||||
{!dueDate && (
|
||||
<Button
|
||||
startDecorator={<Add />}
|
||||
variant='plain'
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
setDueDate(
|
||||
moment().add(1, 'day').format('YYYY-MM-DDTHH:00:00'),
|
||||
)
|
||||
}}
|
||||
>
|
||||
Due Date
|
||||
</Button>
|
||||
)}
|
||||
{!hasNotifications && dueDate && (
|
||||
<Button
|
||||
startDecorator={<EditNotifications />}
|
||||
variant='plain'
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
setHasNotifications(true)
|
||||
setFrequencyHumanReadable('Once')
|
||||
setFrequency(null)
|
||||
setDueDate(
|
||||
moment().add(1, 'day').format('YYYY-MM-DDTHH:00:00'),
|
||||
)
|
||||
}}
|
||||
>
|
||||
Edit Notifications
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{hasDescription && (
|
||||
<Box>
|
||||
<Typography level='body-sm'>Description:</Typography>
|
||||
<div>
|
||||
<RichTextEditor
|
||||
onChange={setDescription}
|
||||
entityType={'chore_description'}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
{hasSubTasks && (
|
||||
<Box>
|
||||
<Typography level='body-sm'>Subtasks:</Typography>
|
||||
<SubTasks
|
||||
editMode={true}
|
||||
tasks={subTasks ? subTasks : []}
|
||||
setTasks={setSubTasks}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 2,
|
||||
<Box>
|
||||
{!hasDescription && (
|
||||
<Button
|
||||
startDecorator={<Add />}
|
||||
variant='plain'
|
||||
size='sm'
|
||||
onClick={() => setHasDescription(true)}
|
||||
>
|
||||
Description
|
||||
</Button>
|
||||
)}
|
||||
{!hasSubTasks && (
|
||||
<Button
|
||||
startDecorator={<Add />}
|
||||
variant='plain'
|
||||
size='sm'
|
||||
onClick={() => setHasSubTasks(true)}
|
||||
>
|
||||
Subtasks
|
||||
</Button>
|
||||
)}
|
||||
{!dueDate && (
|
||||
<Button
|
||||
startDecorator={<Add />}
|
||||
variant='plain'
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
setDueDate(moment().add(1, 'day').format('YYYY-MM-DDTHH:00:00'))
|
||||
}}
|
||||
>
|
||||
<FormControl>
|
||||
<Typography level='body-sm'>Priority</Typography>
|
||||
<Select
|
||||
defaultValue={0}
|
||||
value={priority}
|
||||
onChange={(e, value) => setPriority(value)}
|
||||
>
|
||||
<Option value='0'>No Priority</Option>
|
||||
<Option value='1'>P1</Option>
|
||||
<Option value='2'>P2</Option>
|
||||
<Option value='3'>P3</Option>
|
||||
<Option value='4'>P4</Option>
|
||||
</Select>
|
||||
</FormControl>
|
||||
{dueDate && (
|
||||
<FormControl>
|
||||
<Typography level='body-sm'>Due Date</Typography>
|
||||
<Input
|
||||
type='datetime-local'
|
||||
value={dueDate}
|
||||
onChange={e => setDueDate(e.target.value)}
|
||||
sx={{ width: '100%', fontSize: '16px' }}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'start',
|
||||
gap: 2,
|
||||
Due Date
|
||||
</Button>
|
||||
)}
|
||||
{!hasNotifications && dueDate && (
|
||||
<Button
|
||||
startDecorator={<EditNotifications />}
|
||||
variant='plain'
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
setHasNotifications(true)
|
||||
setFrequencyHumanReadable('Once')
|
||||
setFrequency(null)
|
||||
setDueDate(moment().add(1, 'day').format('YYYY-MM-DDTHH:00:00'))
|
||||
}}
|
||||
>
|
||||
{/* <FormControl>
|
||||
Edit Notifications
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{hasDescription && (
|
||||
<Box>
|
||||
<Typography level='body-sm'>Description:</Typography>
|
||||
<div>
|
||||
<RichTextEditor
|
||||
onChange={setDescription}
|
||||
entityType={'chore_description'}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
{hasSubTasks && (
|
||||
<Box>
|
||||
<Typography level='body-sm'>Subtasks:</Typography>
|
||||
<SubTasks
|
||||
editMode={true}
|
||||
tasks={subTasks ? subTasks : []}
|
||||
setTasks={setSubTasks}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<FormControl>
|
||||
<Typography level='body-sm'>Priority</Typography>
|
||||
<Select
|
||||
defaultValue={0}
|
||||
value={priority}
|
||||
onChange={(e, value) => setPriority(value)}
|
||||
>
|
||||
<Option value='0'>No Priority</Option>
|
||||
<Option value='1'>P1</Option>
|
||||
<Option value='2'>P2</Option>
|
||||
<Option value='3'>P3</Option>
|
||||
<Option value='4'>P4</Option>
|
||||
</Select>
|
||||
</FormControl>
|
||||
{dueDate && (
|
||||
<FormControl>
|
||||
<Typography level='body-sm'>Due Date</Typography>
|
||||
<Input
|
||||
type='datetime-local'
|
||||
value={dueDate}
|
||||
onChange={e => setDueDate(e.target.value)}
|
||||
sx={{ width: '100%', fontSize: '16px' }}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'start',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{/* <FormControl>
|
||||
<Typography level='body-sm'>Assignees</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
{assignees.length > 0 ? (
|
||||
@@ -641,58 +626,51 @@ const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => {
|
||||
)}
|
||||
</Box>
|
||||
</FormControl> */}
|
||||
{hasNotifications && dueDate && (
|
||||
<Box
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography level='body-sm'>Notification Schedule</Typography>
|
||||
<Box sx={{ p: 0.5 }}>
|
||||
<NotificationTemplate
|
||||
onChange={metadata => {
|
||||
if (
|
||||
metadata.notifications !==
|
||||
notificationMetadata.templates
|
||||
) {
|
||||
const newNotificaitonMetadata = {
|
||||
...notificationMetadata,
|
||||
templates: metadata.notifications,
|
||||
}
|
||||
setNotificationMetadata(newNotificaitonMetadata)
|
||||
}
|
||||
}}
|
||||
value={notificationMetadata}
|
||||
showTimeline={false}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{hasNotifications && dueDate && (
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'end',
|
||||
gap: 1,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant='outlined'
|
||||
color='neutral'
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='solid' color='primary' onClick={handleSubmit}>
|
||||
Create
|
||||
</Button>
|
||||
<Typography level='body-sm'>Notification Schedule</Typography>
|
||||
<Box sx={{ p: 0.5 }}>
|
||||
<NotificationTemplate
|
||||
onChange={metadata => {
|
||||
if (
|
||||
metadata.notifications !== notificationMetadata.templates
|
||||
) {
|
||||
const newNotificaitonMetadata = {
|
||||
...notificationMetadata,
|
||||
templates: metadata.notifications,
|
||||
}
|
||||
setNotificationMetadata(newNotificaitonMetadata)
|
||||
}
|
||||
}}
|
||||
value={notificationMetadata}
|
||||
showTimeline={false}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</ModalDialog>
|
||||
</ModalOverflow>
|
||||
</Modal>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'end',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Button variant='outlined' color='neutral' onClick={handleCloseModal}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='solid' color='primary' onClick={handleSubmit}>
|
||||
Create
|
||||
</Button>
|
||||
</Box>
|
||||
</FadeModal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user