diff --git a/src/utils/Debounce.jsx b/src/utils/Debounce.jsx new file mode 100644 index 0000000..3ef84ab --- /dev/null +++ b/src/utils/Debounce.jsx @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react' + +function useDebounce(value, delay) { + const [debouncedValue, setDebouncedValue] = useState(value) + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value) + }, delay) + + return () => { + clearTimeout(handler) + } + }, [value, delay]) + + return debouncedValue +} + +export default useDebounce diff --git a/src/views/Chores/ChoreCard.jsx b/src/views/Chores/ChoreCard.jsx index a9df0a1..002b396 100644 --- a/src/views/Chores/ChoreCard.jsx +++ b/src/views/Chores/ChoreCard.jsx @@ -300,6 +300,12 @@ const ChoreCard = ({ } } const getRecurrentChipText = chore => { + // if chore.frequencyMetadata is type string then parse it otherwise assigned to the metadata: + const metadata = + typeof chore.frequencyMetadata === 'string' + ? JSON.parse(chore.frequencyMetadata) + : chore.frequencyMetadata + const dayOfMonthSuffix = n => { if (n >= 11 && n <= 13) { return 'th' @@ -330,7 +336,7 @@ const ChoreCard = ({ } else if (chore.frequencyType === 'yearly') { return 'Yearly' } else if (chore.frequencyType === 'days_of_the_week') { - let days = JSON.parse(chore.frequencyMetadata).days + let days = metadata.days if (days.length > 4) { const allDays = [ 'Sunday', @@ -354,7 +360,7 @@ const ChoreCard = ({ return days.join(', ') } } else if (chore.frequencyType === 'day_of_the_month') { - let months = JSON.parse(chore.frequencyMetadata).months + let months = metadata.months if (months.length > 6) { const allMonths = [ 'January', @@ -385,16 +391,14 @@ const ChoreCard = ({ except ${notSelectedShortMonths.join(', ')}` return result } else { - let freqData = JSON.parse(chore.frequencyMetadata) + let freqData = metadata const months = freqData.months.map(m => moment().month(m).format('MMM')) return `${chore.frequency}${dayOfMonthSuffix( chore.frequency, )} of ${months.join(', ')}` } } else if (chore.frequencyType === 'interval') { - return `Every ${chore.frequency} ${ - JSON.parse(chore.frequencyMetadata).unit - }` + return `Every ${chore.frequency} ${metadata.unit}` } else { return chore.frequencyType } diff --git a/src/views/components/AddTaskModal.jsx b/src/views/components/AddTaskModal.jsx index 3e2b4b2..fcc1bf5 100644 --- a/src/views/components/AddTaskModal.jsx +++ b/src/views/components/AddTaskModal.jsx @@ -19,7 +19,9 @@ 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 useDebounce from '../../utils/Debounce' import { CreateChore } from '../../utils/Fetcher' +import { useLabels } from '../Labels/LabelQueries' import LearnMoreButton from './LearnMore' const VALID_DAYS = { monday: 'Monday', @@ -69,9 +71,11 @@ const ALL_MONTHS = Object.values(VALID_MONTHS).filter( ) const TaskInput = ({ autoFocus, onChoreUpdate }) => { + const { data: userLabels, isLoading: userLabelsLoading } = useLabels() const { userProfile } = useContext(UserContext) const navigate = useNavigate() const [taskText, setTaskText] = useState('') + const debounceParsing = useDebounce(taskText, 300) const [taskTitle, setTaskTitle] = useState('') const [openModal, setOpenModal] = useState(false) const textareaRef = useRef(null) @@ -98,6 +102,12 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => { } }, [autoFocus]) + useEffect(() => { + if (debounceParsing) { + processText(debounceParsing) + } + }, [debounceParsing]) + const handleEnterPressed = e => { if (e.key === 'Enter') { createChore() @@ -139,7 +149,28 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => { } 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 = { @@ -284,8 +315,6 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => { .replace('{months}', result.frequencyMetadata.months.join(', ')), cleanedSentence: inputSentence.replace(match[0], '').trim(), } - - case 'interval:every_other': case 'interval:2week': result.frequency = 2 result.frequencyMetadata.unit = 'weeks' @@ -307,6 +336,15 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => { .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 } @@ -321,8 +359,10 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => { setPriority(0) return } - - let cleanedSentence = e.target.value + setTaskText(e.target.value) + } + const processText = sentence => { + let cleanedSentence = sentence const priority = parsePriority(cleanedSentence) if (priority.result) setPriority(priority.result) cleanedSentence = priority.cleanedSentence @@ -344,11 +384,24 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => { cleanedSentence = cleanedSentence.replace(parsedDueDate[0].text, '') } + if (repeat.result) { + // if repeat has result the cleaned sentence will remove the date related info which mean + // we need to reparse the date again to get the correct due date: + const parsedDueDate = chrono.parse(sentence, new Date(), { + forwardDate: true, + }) + if (parsedDueDate[0]?.index > -1) { + setDueDate( + moment(parsedDueDate[0].start.date()).format('YYYY-MM-DDTHH:mm:ss'), + ) + } + } + if (priority.result || parsedDueDate[0]?.index > -1 || repeat.result) { setOpenModal(true) } - setTaskText(e.target.value) + setTaskText(sentence) setTaskTitle(cleanedSentence.trim()) } @@ -504,6 +557,7 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => { Priority + diff --git a/src/views/components/CalendarView.jsx b/src/views/components/CalendarView.jsx index 43e44b8..33ed5d4 100644 --- a/src/views/components/CalendarView.jsx +++ b/src/views/components/CalendarView.jsx @@ -132,12 +132,7 @@ const CalendarView = ({ chores }) => { mb: 0.4, py: 1, px: 1, - - // backgroundColor: getAssigneeColor( - // chore.assignedTo, - // userProfile, - // ), - // everything show in one row: + cursor: 'pointer', }} >