Add SmartTaskInput with Custom Renderer.
Change to use `@tanstack/react-query` Add Global ErrorProvider Add limited Support for offline version Fix issue with calendar display +1 day
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
import { KeyboardReturnOutlined } from '@mui/icons-material'
|
||||
import { Add } from '@mui/icons-material'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Chip,
|
||||
IconButton,
|
||||
Input,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
@@ -18,63 +17,20 @@ import * as chrono from 'chrono-node'
|
||||
import moment from 'moment'
|
||||
import { useContext, useEffect, useRef, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { CSSTransition } from 'react-transition-group'
|
||||
import { UserContext } from '../../contexts/UserContext'
|
||||
import { useCreateChore } from '../../queries/ChoreQueries'
|
||||
import { useAllUsers } from '../../queries/UserQueries'
|
||||
import useDebounce from '../../utils/Debounce'
|
||||
import { isPlusAccount } from '../../utils/Helpers'
|
||||
import { useLabels } from '../Labels/LabelQueries'
|
||||
import SmartTaskTitleInput from '../TestView/SmartTaskTitleInput'
|
||||
import { parseLabels, parsePriority, parseRepeatV2 } from './CustomParsers'
|
||||
import LearnMoreButton from './LearnMore'
|
||||
import SubTasks from './SubTask'
|
||||
const VALID_DAYS = {
|
||||
monday: 'Monday',
|
||||
mon: 'Monday',
|
||||
tuesday: 'Tuesday',
|
||||
tue: 'Tuesday',
|
||||
wednesday: 'Wednesday',
|
||||
wed: 'Wednesday',
|
||||
thursday: 'Thursday',
|
||||
thu: 'Thursday',
|
||||
friday: 'Friday',
|
||||
fri: 'Friday',
|
||||
saturday: 'Saturday',
|
||||
sat: 'Saturday',
|
||||
sunday: 'Sunday',
|
||||
sun: 'Sunday',
|
||||
}
|
||||
|
||||
const VALID_MONTHS = {
|
||||
january: 'January',
|
||||
jan: 'January',
|
||||
february: 'February',
|
||||
feb: 'February',
|
||||
march: 'March',
|
||||
mar: 'March',
|
||||
april: 'April',
|
||||
apr: 'April',
|
||||
may: 'May',
|
||||
june: 'June',
|
||||
jun: 'June',
|
||||
july: 'July',
|
||||
jul: 'July',
|
||||
august: 'August',
|
||||
aug: 'August',
|
||||
september: 'September',
|
||||
sep: 'September',
|
||||
october: 'October',
|
||||
oct: 'October',
|
||||
november: 'November',
|
||||
nov: 'November',
|
||||
december: 'December',
|
||||
dec: 'December',
|
||||
}
|
||||
|
||||
const ALL_MONTHS = Object.values(VALID_MONTHS).filter(
|
||||
(v, i, a) => a.indexOf(v) === i,
|
||||
)
|
||||
|
||||
const TaskInput = ({ autoFocus, onChoreUpdate }) => {
|
||||
const TaskInput = ({ autoFocus, onChoreUpdate, isModalOpen, onClose }) => {
|
||||
const { data: userLabels, isLoading: userLabelsLoading } = useLabels()
|
||||
const { data: allUsers, isLoading: isAllUserLoading } = useAllUsers()
|
||||
const createChoreMutation = useCreateChore()
|
||||
|
||||
const { userProfile } = useContext(UserContext)
|
||||
@@ -82,24 +38,29 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
|
||||
const [taskText, setTaskText] = useState('')
|
||||
const debounceParsing = useDebounce(taskText, 30)
|
||||
const [taskTitle, setTaskTitle] = useState('')
|
||||
const [openModal, setOpenModal] = useState(false)
|
||||
const [renderedParts, setRenderedParts] = useState([])
|
||||
|
||||
const textareaRef = useRef(null)
|
||||
const mainInputRef = useRef(null)
|
||||
const [priority, setPriority] = useState(0)
|
||||
const [dueDate, setDueDate] = useState(null)
|
||||
const [description, setDescription] = useState(null)
|
||||
const [assignedTo, setAssignedTo] = useState(userProfile?.id)
|
||||
const [assignees, setAssignees] = useState([])
|
||||
const [labelsV2, setLabelsV2] = useState([])
|
||||
const [frequency, setFrequency] = useState(null)
|
||||
const [frequencyHumanReadable, setFrequencyHumanReadable] = useState(null)
|
||||
const [subTasks, setSubTasks] = useState([])
|
||||
const [subTasks, setSubTasks] = useState(null)
|
||||
const [hasDescription, setHasDescription] = useState(false)
|
||||
const [hasSubTasks, setHasSubTasks] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (openModal && textareaRef.current) {
|
||||
if (isModalOpen && textareaRef.current) {
|
||||
textareaRef.current.focus()
|
||||
textareaRef.current.selectionStart = textareaRef.current.value?.length
|
||||
textareaRef.current.selectionEnd = textareaRef.current.value?.length
|
||||
}
|
||||
}, [openModal])
|
||||
}, [isModalOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (autoFocus > 0 && mainInputRef.current) {
|
||||
@@ -110,21 +71,19 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
|
||||
}, [autoFocus])
|
||||
|
||||
useEffect(() => {
|
||||
if (debounceParsing) {
|
||||
processText(debounceParsing)
|
||||
if (!isModalOpen || userLabelsLoading || isAllUserLoading) {
|
||||
return
|
||||
}
|
||||
}, [debounceParsing])
|
||||
|
||||
const handleEnterPressed = e => {
|
||||
if (e.key === 'Enter') {
|
||||
createChore()
|
||||
handleCloseModal()
|
||||
setTaskText('')
|
||||
}
|
||||
processText(taskText)
|
||||
}, [taskText, userLabelsLoading, isAllUserLoading])
|
||||
|
||||
const handleEnterPressed = () => {
|
||||
createChore()
|
||||
}
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setOpenModal(false)
|
||||
const handleCloseModal = forceRefetch => {
|
||||
onClose(forceRefetch)
|
||||
setTaskText('')
|
||||
setTaskTitle('')
|
||||
setDueDate(null)
|
||||
@@ -133,7 +92,9 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
|
||||
setPriority(0)
|
||||
setHasDescription(false)
|
||||
setDescription(null)
|
||||
setSubTasks([])
|
||||
setSubTasks(null)
|
||||
setHasSubTasks(false)
|
||||
setLabelsV2([])
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
@@ -142,245 +103,6 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
|
||||
setTaskText('')
|
||||
}
|
||||
|
||||
const parsePriority = inputSentence => {
|
||||
let sentence = inputSentence.toLowerCase()
|
||||
const priorityMap = {
|
||||
1: ['p1', 'priority 1', 'high priority', 'urgent', 'asap', 'important'],
|
||||
2: ['p2', 'priority 2', 'medium priority'],
|
||||
3: ['p3', 'priority 3', 'low priority'],
|
||||
4: ['p4', 'priority 4'],
|
||||
}
|
||||
|
||||
for (const [priority, terms] of Object.entries(priorityMap)) {
|
||||
if (terms.some(term => sentence.includes(term))) {
|
||||
return {
|
||||
result: priority,
|
||||
cleanedSentence: terms.reduce(
|
||||
(s, t) => s.replace(t, ''),
|
||||
inputSentence,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
return { result: 0, cleanedSentence: inputSentence }
|
||||
}
|
||||
const parseLabels = inputSentence => {
|
||||
let sentence = inputSentence.toLowerCase()
|
||||
const currentLabels = []
|
||||
// label will always be prefixed #:
|
||||
for (const label of userLabels) {
|
||||
if (sentence.includes(`#${label.name.toLowerCase()}`)) {
|
||||
currentLabels.push(label)
|
||||
sentence = sentence.replace(`#${label.name.toLowerCase()}`, '')
|
||||
}
|
||||
}
|
||||
if (currentLabels.length > 0) {
|
||||
return {
|
||||
result: currentLabels,
|
||||
cleanedSentence: sentence,
|
||||
}
|
||||
}
|
||||
return { result: null, cleanedSentence: sentence }
|
||||
}
|
||||
const parseAssignee = inputSentence => {
|
||||
let sentence = inputSentence.toLowerCase()
|
||||
const assigneeMap = {}
|
||||
}
|
||||
const parseRepeatV2 = inputSentence => {
|
||||
const sentence = inputSentence.toLowerCase()
|
||||
const result = {
|
||||
frequency: 1,
|
||||
frequencyType: null,
|
||||
frequencyMetadata: {
|
||||
days: [],
|
||||
months: [],
|
||||
unit: null,
|
||||
time: new Date().toISOString(),
|
||||
},
|
||||
}
|
||||
|
||||
const patterns = [
|
||||
{
|
||||
frequencyType: 'day_of_the_month:every',
|
||||
regex: /(\d+)(?:th|st|nd|rd)? of every month$/i,
|
||||
name: 'Every {day} of every month',
|
||||
},
|
||||
{
|
||||
frequencyType: 'daily',
|
||||
regex: /(every day|daily|everyday)$/i,
|
||||
name: 'Every day',
|
||||
},
|
||||
{
|
||||
frequencyType: 'daily:time',
|
||||
regex: /every (morning|noon|afternoon|evening|night)$/i,
|
||||
name: 'Every {time} daily',
|
||||
},
|
||||
{
|
||||
frequencyType: 'weekly',
|
||||
regex: /(every week|weekly)$/i,
|
||||
name: 'Every week',
|
||||
},
|
||||
{
|
||||
frequencyType: 'monthly',
|
||||
regex: /(every month|monthly)$/i,
|
||||
name: 'Every month',
|
||||
},
|
||||
{
|
||||
frequencyType: 'yearly',
|
||||
regex: /every year$/i,
|
||||
name: 'Every year',
|
||||
},
|
||||
{
|
||||
frequencyType: 'monthly',
|
||||
regex: /every (?:other )?month$/i,
|
||||
name: 'Bi Monthly',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
frequencyType: 'interval:2week',
|
||||
regex: /(bi-?weekly|every other week)/i,
|
||||
value: 2,
|
||||
name: 'Bi Weekly',
|
||||
},
|
||||
{
|
||||
frequencyType: 'interval',
|
||||
regex: /every (\d+) (days?|weeks?|months?|years?).*$/i,
|
||||
name: 'Every {frequency} {unit}',
|
||||
},
|
||||
{
|
||||
frequencyType: 'interval:every_other',
|
||||
regex: /every other (days?|weeks?|months?|years?)$/i,
|
||||
name: 'Every other {unit}',
|
||||
},
|
||||
{
|
||||
frequencyType: 'days_of_the_week',
|
||||
regex: /every ([\w, ]+(?:day)?(?:, [\w, ]+(?:day)?)*)$/i,
|
||||
name: 'Every {days}',
|
||||
},
|
||||
{
|
||||
frequencyType: 'day_of_the_month',
|
||||
regex: /(\d+)(?:st|nd|rd|th)? of ([\w ]+(?:(?:,| and |\s)[\w ]+)*)/i,
|
||||
name: 'Every {day} days of {months}',
|
||||
},
|
||||
]
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = sentence.match(pattern.regex)
|
||||
if (!match) continue
|
||||
|
||||
result.frequencyType = pattern.frequencyType
|
||||
const unitMap = {
|
||||
daily: 'days',
|
||||
weekly: 'weeks',
|
||||
monthly: 'months',
|
||||
yearly: 'years',
|
||||
}
|
||||
|
||||
switch (pattern.frequencyType) {
|
||||
case 'daily':
|
||||
case 'weekly':
|
||||
case 'monthly':
|
||||
case 'yearly':
|
||||
result.frequencyType = 'interval'
|
||||
result.frequency = pattern.value || 1
|
||||
result.frequencyMetadata.unit = unitMap[pattern.frequencyType]
|
||||
return {
|
||||
result,
|
||||
name: pattern.name,
|
||||
cleanedSentence: inputSentence.replace(match[0], '').trim(),
|
||||
}
|
||||
|
||||
case 'interval':
|
||||
result.frequency = parseInt(match[1], 10)
|
||||
result.frequencyMetadata.unit = match[2]
|
||||
return {
|
||||
result,
|
||||
name: pattern.name
|
||||
.replace('{frequency}', result.frequency)
|
||||
.replace('{unit}', result.frequencyMetadata.unit),
|
||||
cleanedSentence: inputSentence.replace(match[0], '').trim(),
|
||||
}
|
||||
|
||||
case 'days_of_the_week':
|
||||
result.frequencyMetadata.days = match[1]
|
||||
.toLowerCase()
|
||||
.split(/ and |,|\s/)
|
||||
.map(day => day.trim())
|
||||
.filter(day => VALID_DAYS[day])
|
||||
.map(day => VALID_DAYS[day])
|
||||
if (!result.frequencyMetadata.days.length)
|
||||
return { result: null, name: null, cleanedSentence: inputSentence }
|
||||
return {
|
||||
result,
|
||||
name: pattern.name.replace(
|
||||
'{days}',
|
||||
result.frequencyMetadata.days.join(', '),
|
||||
),
|
||||
cleanedSentence: inputSentence.replace(match[0], '').trim(),
|
||||
}
|
||||
|
||||
case 'day_of_the_month':
|
||||
result.frequency = parseInt(match[1], 10)
|
||||
result.frequencyMetadata.months = match[2]
|
||||
.toLowerCase()
|
||||
.split(/ and |,|\s/)
|
||||
.map(month => month.trim())
|
||||
.filter(month => VALID_MONTHS[month])
|
||||
.map(month => VALID_MONTHS[month])
|
||||
result.frequencyMetadata.unit = 'days'
|
||||
return {
|
||||
result,
|
||||
name: pattern.name
|
||||
.replace('{day}', result.frequency)
|
||||
.replace('{months}', result.frequencyMetadata.months.join(', ')),
|
||||
cleanedSentence: inputSentence.replace(match[0], '').trim(),
|
||||
}
|
||||
case 'interval:2week':
|
||||
result.frequency = 2
|
||||
result.frequencyMetadata.unit = 'weeks'
|
||||
result.frequencyType = 'interval'
|
||||
return {
|
||||
result,
|
||||
name: pattern.name,
|
||||
cleanedSentence: inputSentence.replace(match[0], '').trim(),
|
||||
}
|
||||
case 'daily:time':
|
||||
result.frequency = 1
|
||||
result.frequencyMetadata.unit = 'days'
|
||||
result.frequencyType = 'daily'
|
||||
return {
|
||||
result,
|
||||
name: pattern.name.replace('{time}', match[1]),
|
||||
// replace every x with ''
|
||||
|
||||
cleanedSentence: inputSentence.replace(match[0], '').trim(),
|
||||
}
|
||||
|
||||
case 'day_of_the_month:every':
|
||||
result.frequency = parseInt(match[1], 10)
|
||||
result.frequencyMetadata.months = ALL_MONTHS
|
||||
result.frequencyMetadata.unit = 'days'
|
||||
return {
|
||||
result,
|
||||
name: pattern.name
|
||||
.replace('{day}', result.frequency)
|
||||
.replace('{months}', result.frequencyMetadata.months.join(', ')),
|
||||
cleanedSentence: inputSentence.replace(match[0], '').trim(),
|
||||
}
|
||||
case 'interval:every_other':
|
||||
result.frequency = 2
|
||||
result.frequencyMetadata.unit = match[1]
|
||||
result.frequencyType = 'interval'
|
||||
return {
|
||||
result,
|
||||
name: pattern.name.replace('{unit}', result.frequencyMetadata.unit),
|
||||
cleanedSentence: inputSentence.replace(match[0], '').trim(),
|
||||
}
|
||||
}
|
||||
}
|
||||
return { result: null, name: null, cleanedSentence: inputSentence }
|
||||
}
|
||||
|
||||
const handleTextChange = e => {
|
||||
if (!e.target.value) {
|
||||
setTaskText('')
|
||||
@@ -394,18 +116,27 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
|
||||
}
|
||||
const processText = sentence => {
|
||||
let cleanedSentence = sentence
|
||||
const priority = parsePriority(cleanedSentence)
|
||||
const priority = parsePriority(sentence)
|
||||
if (priority.result) setPriority(priority.result)
|
||||
cleanedSentence = priority.cleanedSentence
|
||||
const labels = parseLabels(sentence, userLabels)
|
||||
if (labels.result) {
|
||||
cleanedSentence = labels.cleanedSentence
|
||||
setLabelsV2(labels.result)
|
||||
}
|
||||
|
||||
const repeat = parseRepeatV2(cleanedSentence)
|
||||
const repeat = parseRepeatV2(sentence)
|
||||
if (repeat.result) {
|
||||
setFrequency(repeat.result)
|
||||
setFrequencyHumanReadable(repeat.name)
|
||||
cleanedSentence = repeat.cleanedSentence
|
||||
}
|
||||
|
||||
const parsedDueDate = chrono.parse(cleanedSentence, new Date(), {
|
||||
// const assignees = parseAssignees(sentence)
|
||||
// if (assignees.result) {
|
||||
// cleanedSentence = assignees.cleanedSentence
|
||||
// set
|
||||
// }
|
||||
const parsedDueDate = chrono.parse(sentence, new Date(), {
|
||||
forwardDate: true,
|
||||
})
|
||||
if (parsedDueDate[0]?.index > -1) {
|
||||
@@ -428,14 +159,106 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (priority.result || parsedDueDate[0]?.index > -1 || repeat.result) {
|
||||
setOpenModal(true)
|
||||
}
|
||||
|
||||
setTaskText(sentence)
|
||||
setTaskTitle(cleanedSentence.trim())
|
||||
}
|
||||
const rendered = renderText(
|
||||
sentence,
|
||||
repeat.highlight,
|
||||
priority.highlight,
|
||||
labels.highlight,
|
||||
parsedDueDate && parsedDueDate[0]
|
||||
? {
|
||||
start: parsedDueDate[0].index,
|
||||
end: parsedDueDate[0].index + parsedDueDate[0].text.length,
|
||||
text: parsedDueDate[0].text,
|
||||
}
|
||||
: null,
|
||||
)
|
||||
|
||||
setRenderedParts(rendered)
|
||||
}
|
||||
const renderText = (
|
||||
sentence,
|
||||
repeatHighlight,
|
||||
priorityHighlight,
|
||||
labelsHighlight,
|
||||
dueDateHighlight,
|
||||
) => {
|
||||
const parts = []
|
||||
let lastIndex = 0
|
||||
|
||||
// Combine all highlight ranges and sort them by their start index
|
||||
const allHighlights = []
|
||||
if (repeatHighlight) {
|
||||
repeatHighlight.forEach(h => allHighlights.push({ ...h, type: 'repeat' }))
|
||||
}
|
||||
if (priorityHighlight) {
|
||||
priorityHighlight.forEach(h =>
|
||||
allHighlights.push({ ...h, type: 'priority' }),
|
||||
)
|
||||
}
|
||||
if (labelsHighlight) {
|
||||
labelsHighlight.forEach(h => allHighlights.push({ ...h, type: 'label' }))
|
||||
}
|
||||
if (dueDateHighlight) {
|
||||
allHighlights.push({ ...dueDateHighlight, type: 'dueDate' })
|
||||
}
|
||||
|
||||
allHighlights.sort((a, b) => a.start - b.start)
|
||||
|
||||
for (const highlight of allHighlights) {
|
||||
// Add the text before the highlight
|
||||
if (highlight.start > lastIndex) {
|
||||
parts.push(sentence.substring(lastIndex, highlight.start))
|
||||
}
|
||||
|
||||
// Determine the class name based on the highlight type
|
||||
let className = ''
|
||||
switch (highlight.type) {
|
||||
case 'repeat':
|
||||
className = 'highlight-repeat'
|
||||
break
|
||||
case 'priority':
|
||||
className = 'highlight-priority'
|
||||
break
|
||||
case 'label':
|
||||
className = 'highlight-label'
|
||||
break
|
||||
case 'dueDate':
|
||||
className = 'highlight-date'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
// Add the highlighted span
|
||||
parts.push(
|
||||
<span
|
||||
key={highlight.start}
|
||||
className={className}
|
||||
style={{
|
||||
// text underline:
|
||||
textDecoration: 'underline',
|
||||
// textDecorationColor: 'red',
|
||||
textDecorationThickness: '2px',
|
||||
textDecorationStyle: 'dashed',
|
||||
}}
|
||||
>
|
||||
{sentence.substring(highlight.start, highlight.end)}
|
||||
</span>,
|
||||
)
|
||||
|
||||
// Update the last index to the end of the current highlight
|
||||
lastIndex = highlight.end
|
||||
}
|
||||
|
||||
// Add any remaining text after the last highlight
|
||||
if (lastIndex < sentence.length) {
|
||||
parts.push(sentence.substring(lastIndex))
|
||||
}
|
||||
|
||||
return parts
|
||||
}
|
||||
const createChore = () => {
|
||||
const chore = {
|
||||
name: taskTitle,
|
||||
@@ -446,13 +269,13 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
|
||||
isRolling: false,
|
||||
notification: false,
|
||||
description: description || null,
|
||||
labelsV2: [],
|
||||
labelsV2: labelsV2,
|
||||
priority: priority ? Number(priority) : 0,
|
||||
status: 0,
|
||||
frequencyType: 'once',
|
||||
frequencyMetadata: {},
|
||||
notificationMetadata: {},
|
||||
subTasks: subTasks.length > 0 ? subTasks : null,
|
||||
subTasks: subTasks?.length > 0 ? subTasks : null,
|
||||
}
|
||||
|
||||
if (frequency) {
|
||||
@@ -469,78 +292,51 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
|
||||
chore.nextDueDate = new Date(dueDate).toUTCString()
|
||||
}
|
||||
|
||||
createChoreMutation.mutateAsync(chore).then(resp => {
|
||||
resp.json().then(data => {
|
||||
if (resp.status !== 200) {
|
||||
console.error('Error creating chore:', data)
|
||||
return
|
||||
} else {
|
||||
onChoreUpdate({ ...chore, id: data.res, nextDueDate: chore.dueDate })
|
||||
createChoreMutation
|
||||
.mutateAsync(chore)
|
||||
.then(resp => {
|
||||
resp.json().then(data => {
|
||||
if (resp.status !== 200) {
|
||||
console.error('Error creating chore:', data)
|
||||
return
|
||||
} else {
|
||||
onChoreUpdate({
|
||||
...chore,
|
||||
id: data.res,
|
||||
nextDueDate: chore.dueDate,
|
||||
})
|
||||
|
||||
handleCloseModal(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
if (error?.queued) {
|
||||
handleCloseModal(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if (userLabelsLoading || isAllUserLoading) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!openModal && (
|
||||
<CSSTransition in={!openModal} timeout={300} classNames='fade'>
|
||||
<Input
|
||||
autoFocus={autoFocus > 0}
|
||||
ref={mainInputRef}
|
||||
placeholder='Add a task...'
|
||||
value={taskText}
|
||||
onChange={handleTextChange}
|
||||
sx={{
|
||||
fontSize: '16px',
|
||||
mt: 1,
|
||||
mb: 1,
|
||||
borderRadius: 24,
|
||||
height: 24,
|
||||
borderColor: 'text.disabled',
|
||||
padding: 1,
|
||||
width: '100%',
|
||||
}}
|
||||
onKeyUp={handleEnterPressed}
|
||||
endDecorator={
|
||||
<IconButton
|
||||
variant='outlined'
|
||||
sx={{ borderRadius: 24, marginRight: -0.5 }}
|
||||
>
|
||||
<KeyboardReturnOutlined />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
</CSSTransition>
|
||||
)}
|
||||
|
||||
<Modal open={openModal} onClose={handleCloseModal}>
|
||||
<ModalOverflow>
|
||||
<ModalDialog>
|
||||
{/* <Button
|
||||
size='sm'
|
||||
onClick={() => navigate(`/chores/create`)}
|
||||
variant='outlined'
|
||||
sx={{ position: 'absolute', right: 20 }}
|
||||
startDecorator={<OpenInFull />}
|
||||
>
|
||||
Full Editor
|
||||
</Button> */}
|
||||
<Typography level='h4'>Create new task</Typography>
|
||||
<Chip startDecorator='🚧' variant='soft' color='warning' size='sm'>
|
||||
Experimental Feature
|
||||
</Chip>
|
||||
<Box>
|
||||
<Modal open={isModalOpen} onClose={handleCloseModal}>
|
||||
<ModalOverflow>
|
||||
<ModalDialog size='lg' sx={{ minWidth: '80%' }}>
|
||||
<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>
|
||||
<Input
|
||||
autoFocus
|
||||
ref={textareaRef}
|
||||
value={taskText}
|
||||
onChange={handleTextChange}
|
||||
onKeyUp={handleEnterPressed}
|
||||
placeholder='Type your full text here...'
|
||||
sx={{ width: '100%', fontSize: '16px' }}
|
||||
/>
|
||||
<LearnMoreButton
|
||||
content={
|
||||
<>
|
||||
@@ -585,58 +381,125 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
|
||||
}
|
||||
/>
|
||||
</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 : [],
|
||||
},
|
||||
'!': ['P1', 'P2', 'P3', 'P4'],
|
||||
'@': {
|
||||
// value: 'userId',
|
||||
// display: 'displayName',
|
||||
// options: allUsers ? allUsers : [],
|
||||
value: 'id',
|
||||
display: 'name',
|
||||
options: [
|
||||
{ id: userProfile.id, name: userProfile.displayName },
|
||||
],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* <Box>
|
||||
<Typography level='body-sm'>Title:</Typography>
|
||||
<Input
|
||||
value={taskTitle}
|
||||
onChange={e => setTaskTitle(e.target.value)}
|
||||
sx={{ width: '100%', fontSize: '16px' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{hasDescription ? (
|
||||
<Box>
|
||||
<Typography level='body-sm'>Description:</Typography>
|
||||
<Textarea
|
||||
minRows={2}
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
</Box> */}
|
||||
<Box>
|
||||
{!hasDescription && (
|
||||
<Button
|
||||
startDecorator={<Add />}
|
||||
variant='plain'
|
||||
size='sm'
|
||||
onClick={() => setHasDescription(true)}
|
||||
>
|
||||
Add Description
|
||||
Description
|
||||
</Button>
|
||||
)}
|
||||
<Typography level='body-sm'>Subtasks:</Typography>
|
||||
<SubTasks editMode={true} tasks={subTasks} setTasks={setSubTasks} />
|
||||
{!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>
|
||||
)}
|
||||
</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>
|
||||
{hasDescription && (
|
||||
<Box>
|
||||
<Typography level='body-sm'>Description:</Typography>
|
||||
<Textarea
|
||||
minRows={2}
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
</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
|
||||
@@ -646,55 +509,52 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
|
||||
sx={{ width: '100%', fontSize: '16px' }}
|
||||
/>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'start',
|
||||
gap: 2,
|
||||
}}
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'start',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<FormControl>
|
||||
<Typography level='body-sm'>Assignee</Typography>
|
||||
<Select value={'0'} disabled>
|
||||
<Option value='0'>Me</Option>
|
||||
{/* <Option value='1'>Other</Option> */}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Typography level='body-sm'>Frequency</Typography>
|
||||
<Input value={frequencyHumanReadable || 'Once'} variant='plain' />
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'end',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant='outlined'
|
||||
color='neutral'
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
<FormControl>
|
||||
<Typography level='body-sm'>Assignee</Typography>
|
||||
<Select value={'0'} disabled>
|
||||
<Option value='0'>Me</Option>
|
||||
{/* <Option value='1'>Other</Option> */}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Typography level='body-sm'>Frequency</Typography>
|
||||
<Input
|
||||
value={frequencyHumanReadable || 'Once'}
|
||||
variant='plain'
|
||||
/>
|
||||
</FormControl>
|
||||
</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>
|
||||
</ModalDialog>
|
||||
</ModalOverflow>
|
||||
</Modal>
|
||||
</>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='solid' color='primary' onClick={handleSubmit}>
|
||||
Create
|
||||
</Button>
|
||||
</Box>
|
||||
</ModalDialog>
|
||||
</ModalOverflow>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user