Custom Notification
@@ -372,7 +380,7 @@ const NotificationSetting = () => {
-
+
{notificationTarget === '1' && (
<>
diff --git a/src/views/User/UserActivities.jsx b/src/views/User/UserActivities.jsx
index 62ee3e1..1738950 100644
--- a/src/views/User/UserActivities.jsx
+++ b/src/views/User/UserActivities.jsx
@@ -232,7 +232,7 @@ const UserActivites = () => {
}
const generateChoreDuePieChartData = chores => {
- const groups = ChoresGrouper('due_date', chores)
+ const groups = ChoresGrouper('due_date', chores, null)
return groups
.map(group => {
return {
@@ -245,7 +245,7 @@ const UserActivites = () => {
.filter(item => item.value > 0)
}
const generateChorePriorityPieChartData = chores => {
- const groups = ChoresGrouper('priority', chores)
+ const groups = ChoresGrouper('priority', chores, null)
return groups
.map(group => {
return {
diff --git a/src/views/components/AddTaskModal.jsx b/src/views/components/AddTaskModal.jsx
index 30bbba9..99cfa68 100644
--- a/src/views/components/AddTaskModal.jsx
+++ b/src/views/components/AddTaskModal.jsx
@@ -76,12 +76,12 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
const { userProfile } = useContext(UserContext)
const navigate = useNavigate()
const [taskText, setTaskText] = useState('')
- const debounceParsing = useDebounce(taskText, 300)
+ const debounceParsing = useDebounce(taskText, 30)
const [taskTitle, setTaskTitle] = useState('')
const [openModal, setOpenModal] = useState(false)
const textareaRef = useRef(null)
const mainInputRef = useRef(null)
- const [priority, setPriority] = useState('0')
+ const [priority, setPriority] = useState(0)
const [dueDate, setDueDate] = useState(null)
const [description, setDescription] = useState(null)
const [frequency, setFrequency] = useState(null)
@@ -436,7 +436,6 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
priority: priority || 0,
status: 0,
frequencyType: 'once',
- notificationMetadata: {},
}
if (frequency) {
@@ -451,7 +450,12 @@ const TaskInput = ({ autoFocus, onChoreUpdate }) => {
CreateChore(chore).then(resp => {
resp.json().then(data => {
- onChoreUpdate({ ...chore, id: data.res, nextDueDate: chore.dueDate })
+ if (resp.status !== 200) {
+ console.error('Error creating chore:', data)
+ return
+ } else {
+ onChoreUpdate({ ...chore, id: data.res, nextDueDate: chore.dueDate })
+ }
})
})
}
diff --git a/src/views/components/SubTask.jsx b/src/views/components/SubTask.jsx
index efc1b21..676dbc2 100644
--- a/src/views/components/SubTask.jsx
+++ b/src/views/components/SubTask.jsx
@@ -6,7 +6,15 @@ import {
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
-import { Add, Delete, DragIndicator, Edit } from '@mui/icons-material'
+import {
+ ChevronRight,
+ Delete,
+ DragIndicator,
+ Edit,
+ ExpandMore,
+ KeyboardReturn,
+ PlaylistAdd,
+} from '@mui/icons-material'
import {
Box,
Checkbox,
@@ -19,23 +27,41 @@ import {
import React, { useState } from 'react'
import { CompleteSubTask } from '../../utils/Fetcher'
-function SortableItem({ task, index, handleToggle, handleDelete, editMode }) {
+function SortableItem({
+ task,
+ index,
+ handleToggle,
+ handleDelete,
+ handleAddSubtask,
+ allTasks,
+ setTasks,
+ level = 0,
+ editMode,
+}) {
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id: task.id })
+ const [isEditing, setIsEditing] = useState(false)
+ const [editedText, setEditedText] = useState(task.name)
+ const [expanded, setExpanded] = useState(false)
+ const [showAddSubtask, setShowAddSubtask] = useState(false)
+ const [newSubtask, setNewSubtask] = useState('')
+
+ // Find child tasks
+ const childTasks = allTasks.filter(t => t.parentId === task.id)
+ const hasChildren = childTasks.length > 0
+
const style = {
transform: CSS.Transform.toString(transform),
transition,
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
- flexDirection: { xs: 'column', sm: 'row' }, // Responsive style
+ flexDirection: { xs: 'column', sm: 'row' },
touchAction: 'none',
+ paddingLeft: `${level * 24}px`,
}
- const [isEditing, setIsEditing] = useState(false)
- const [editedText, setEditedText] = useState(task.name)
-
const handleEdit = () => {
setIsEditing(true)
}
@@ -43,146 +69,332 @@ function SortableItem({ task, index, handleToggle, handleDelete, editMode }) {
const handleSave = () => {
setIsEditing(false)
task.name = editedText
+ // Update the task in the parent component
+ setTasks(prevTasks =>
+ prevTasks.map(t => (t.id === task.id ? { ...t, name: editedText } : t)),
+ )
+ }
+
+ const handleExpandClick = () => {
+ setExpanded(!expanded)
+ }
+
+ const handleAddSubtaskClick = () => {
+ setShowAddSubtask(!showAddSubtask)
+ }
+
+ const submitNewSubtask = () => {
+ if (!newSubtask.trim()) return
+
+ handleAddSubtask(task.id, newSubtask)
+ setNewSubtask('')
+ setShowAddSubtask(false)
+ setExpanded(true) // Auto-expand to show the new subtask
+ }
+
+ const handleKeyPress = event => {
+ if (event.key === 'Enter') {
+ submitNewSubtask()
+ }
}
return (
-
- {editMode && (
-
-
-
- )}
-
- {!editMode && (
- handleToggle(task.id)}
- overlay={!editMode}
- />
+ <>
+
+ {editMode && (
+
+
+
)}
+
+ {hasChildren && (
+
+ {expanded ? : }
+
+ )}
+
+ {!hasChildren && level > 0 && (
+ // Spacer for alignment not sure of better way for now it's good
+ )}
+
- {isEditing ? (
+ {!editMode && (
+ handleToggle(task.id)}
+ />
+ )}
+ {
+ if (!editMode) {
+ handleToggle(task.id)
+ }
+ }}
+ >
+ {isEditing ? (
+ setEditedText(e.target.value)}
+ onBlur={handleSave}
+ onKeyPress={e => {
+ if (e.key === 'Enter') {
+ handleSave()
+ }
+ }}
+ autoFocus
+ />
+ ) : (
+
+ {task.name}
+
+ )}
+ {task.completedAt && (
+
+ {new Date(task.completedAt).toLocaleString()}
+
+ )}
+
+
+
+
+ {editMode && (
+ <>
+
+
+
+
+
+
+ handleDelete(task.id)}
+ >
+
+
+ >
+ )}
+
+
+
+ {/* Add subtask input field */}
+ {showAddSubtask && (
+
+
setEditedText(e.target.value)}
- onBlur={handleSave}
- onKeyPress={e => {
- if (e.key === 'Enter') {
- handleSave()
- }
- }}
+ placeholder='Add new subtask...'
+ value={newSubtask}
+ onChange={e => setNewSubtask(e.target.value)}
+ onKeyPress={handleKeyPress}
+ sx={{ flex: 1 }}
autoFocus
/>
- ) : (
-
- {task.name}
-
- )}
- {task.completedAt && (
-
- {new Date(task.completedAt).toLocaleString()}
-
- )}
-
-
- {editMode && (
-
-
-
-
- handleDelete(task.id)}
- >
-
-
+
+
+
+
+
+ )}
+
+ {/* Child tasks */}
+ {hasChildren && expanded && (
+
+ {childTasks
+ .sort((a, b) => a.orderId - b.orderId)
+ .map((childTask, childIndex) => (
+
+ ))}
)}
-
+ >
)
}
-const SubTasks = ({ editMode = true, choreId = 0, tasks, setTasks }) => {
+const SubTasks = ({ editMode = true, choreId = 0, tasks = [], setTasks }) => {
const [newTask, setNewTask] = useState('')
+ const topLevelTasks = tasks.filter(task => task.parentId === null)
+
const handleToggle = taskId => {
const updatedTask = tasks.find(task => task.id === taskId)
- updatedTask.completedAt = updatedTask.completedAt
+ const newCompletedAt = updatedTask.completedAt
? null
: new Date().toISOString()
+ // Update the task
const updatedTasks = tasks.map(task =>
- task.id === taskId ? updatedTask : task,
+ task.id === taskId ? { ...task, completedAt: newCompletedAt } : task,
)
- CompleteSubTask(
- taskId,
- Number(choreId),
- updatedTask.completedAt ? new Date().toISOString() : null,
- ).then(res => {
+
+ // If completing a task, also complete all child tasks
+ if (newCompletedAt) {
+ const completeChildren = parentId => {
+ const children = updatedTasks.filter(t => t.parentId === parentId)
+ children.forEach(child => {
+ const index = updatedTasks.findIndex(t => t.id === child.id)
+ if (index !== -1) {
+ updatedTasks[index] = {
+ ...updatedTasks[index],
+ completedAt: newCompletedAt,
+ }
+ completeChildren(child.id) // Recursively complete grandchildren
+ }
+ })
+ }
+ completeChildren(taskId)
+ }
+
+ CompleteSubTask(taskId, Number(choreId), newCompletedAt).then(res => {
if (res.status !== 200) {
console.log('Error updating task')
return
}
})
+
setTasks(updatedTasks)
}
const handleDelete = taskId => {
- setTasks(tasks.filter(task => task.id !== taskId))
+ // Find all descendant tasks to delete
+ const findDescendants = id => {
+ const descendants = []
+ const children = tasks.filter(t => t.parentId === id)
+
+ children.forEach(child => {
+ descendants.push(child.id)
+ descendants.push(...findDescendants(child.id))
+ })
+
+ return descendants
+ }
+
+ const descendantIds = findDescendants(taskId)
+ const idsToDelete = [taskId, ...descendantIds]
+
+ // Filter out the task and all its descendants
+ const updatedTasks = tasks
+ .filter(task => !idsToDelete.includes(task.id))
+ .map((task, index) => ({
+ ...task,
+ orderId: task.parentId === null ? index : task.orderId,
+ }))
+
+ setTasks(updatedTasks)
}
const handleAdd = () => {
if (!newTask.trim()) return
- setTasks([
- ...tasks,
- {
- name: newTask,
- completedAt: null,
- orderId: tasks.length,
- },
- ])
+
+ const newTaskObj = {
+ name: newTask,
+ completedAt: null,
+ orderId: topLevelTasks.length,
+ parentId: null,
+ id: (tasks.length + 1) * -1, // Temporary negative ID
+ }
+
+ setTasks([...tasks, newTaskObj])
setNewTask('')
}
+ const handleAddSubtask = (parentId, name) => {
+ if (!name.trim()) return
+
+ // Find siblings to determine orderId
+ const siblings = tasks.filter(t => t.parentId === parentId)
+
+ const newSubtask = {
+ name,
+ completedAt: null,
+ orderId: siblings.length,
+ parentId,
+ id: (tasks.length + 1) * -1, // Temporary negative ID
+ }
+
+ setTasks([...tasks, newSubtask])
+ }
+
const onDragEnd = event => {
const { active, over } = event
- if (active.id !== over.id) {
- setTasks(items => {
- const oldIndex = items.findIndex(item => item.id === active.id)
- const newIndex = items.findIndex(item => item.id === over.id)
- const reorderedItems = arrayMove(items, oldIndex, newIndex)
- return reorderedItems.map((item, index) => ({
- ...item,
- orderId: index,
- }))
+ if (!over || active.id === over.id) return
+
+ setTasks(items => {
+ const oldIndex = items.findIndex(item => item.id === active.id)
+ const newIndex = items.findIndex(item => item.id === over.id)
+
+ if (oldIndex === -1 || newIndex === -1) return items
+
+ const activeItem = items[oldIndex]
+ const overItem = items[newIndex]
+
+ const reorderedItems = arrayMove(items, oldIndex, newIndex)
+
+ const parentId = overItem.parentId
+ const siblings = reorderedItems.filter(item => item.parentId === parentId)
+
+ return reorderedItems.map(item => {
+ if (item.id === activeItem.id) {
+ return { ...item, parentId, orderId: siblings.indexOf(item) }
+ }
+ return item.parentId === parentId
+ ? { ...item, orderId: siblings.indexOf(item) }
+ : item
})
- }
+ })
}
const handleKeyPress = event => {
@@ -191,38 +403,37 @@ const SubTasks = ({ editMode = true, choreId = 0, tasks, setTasks }) => {
}
}
- // Sort tasks by orderId before rendering
- const sortedTasks = [...tasks].sort((a, b) => a.orderId - b.orderId)
-
return (
<>
-
+
- {sortedTasks.map((task, index) => (
-
- ))}
+ {topLevelTasks
+ .sort((a, b) => a.orderId - b.orderId)
+ .map((task, index) => (
+
+ ))}
{editMode && (
setNewTask(e.target.value)}
onKeyPress={handleKeyPress}
sx={{ flex: 1 }}
/>
-
+
)}