diff --git a/src/views/components/AddTaskModal.jsx b/src/views/components/AddTaskModal.jsx
index 92878d1..b6f2a7e 100644
--- a/src/views/components/AddTaskModal.jsx
+++ b/src/views/components/AddTaskModal.jsx
@@ -25,6 +25,9 @@ import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint'
import { useDocumentScanner } from '../../hooks/useDocumentScanner'
import { localAIService } from '../../service/LocalAIService'
import { TASK_COLOR } from '../../utils/Colors'
+import AdvancedOptionsSection, {
+ AdvancedOptionsTrigger,
+} from './AdvancedOptionsSection'
import AssigneePickerField from './AssigneePickerField'
import AttachmentPickerField from './AttachmentPickerField'
import DueDatePickerField from './DueDatePickerField'
@@ -101,6 +104,11 @@ const TaskInput = ({ onChoreUpdate, isModalOpen, onClose }) => {
const [hasDescription, setHasDescription] = useState(false)
const [hasSubTasks, setHasSubTasks] = useState(false)
const [deadlineOffset, setDeadlineOffset] = useState(-1)
+ const [requireApproval, setRequireApproval] = useState(false)
+ const [completionWindow, setCompletionWindow] = useState(-1)
+ const [assignStrategy, setAssignStrategy] = useState('keep_last_assigned')
+ const [isPrivate, setIsPrivate] = useState(false)
+ const [showAdvanced, setShowAdvanced] = useState(false)
const [dueDateOnly, setDueDateOnly] = useState(null)
const [dueTime, setDueTime] = useState(null)
const [useCustomTime, setUseCustomTime] = useState(false)
@@ -599,6 +607,11 @@ const TaskInput = ({ onChoreUpdate, isModalOpen, onClose }) => {
setAssignees([])
setProjectId(getInitialProject())
setDeadlineOffset(-1)
+ setRequireApproval(false)
+ setCompletionWindow(-1)
+ setAssignStrategy('keep_last_assigned')
+ setIsPrivate(false)
+ setShowAdvanced(false)
setDueDateOnly(null)
setDueTime(null)
setUseCustomTime(false)
@@ -610,26 +623,22 @@ const TaskInput = ({ onChoreUpdate, isModalOpen, onClose }) => {
// Handle different assignee scenarios
let finalAssignees = assignees
let finalAssignedTo = null
- let finalAssignStrategy = 'random'
+ let finalAssignStrategy = assignStrategy
if (isAnyoneTask) {
- // @Anyone was explicitly used - anyone can do the task
finalAssignees = []
finalAssignedTo = null
finalAssignStrategy = 'no_assignee'
} else if (assignees.length === 0) {
- // No assignees and no @Anyone - fallback to current user
finalAssignees = [{ userId: userProfile?.id }]
finalAssignedTo = userProfile?.id
- finalAssignStrategy = 'keep_last_assigned'
+ finalAssignStrategy = assignStrategy
} else if (assignees.length === 1) {
- // Single assignee
finalAssignedTo = assignees[0].userId
- finalAssignStrategy = 'keep_last_assigned'
+ finalAssignStrategy = assignStrategy
} else {
- // Multiple assignees
finalAssignedTo = null
- finalAssignStrategy = 'random'
+ finalAssignStrategy = assignStrategy
}
const chore = {
@@ -644,6 +653,10 @@ const TaskInput = ({ onChoreUpdate, isModalOpen, onClose }) => {
priority: priority ? Number(priority) : 0,
points: points > -1 ? points : null,
deadlineOffset: deadlineOffset < 0 ? null : deadlineOffset,
+ completionWindow:
+ completionWindow < 0 || !dueDate ? null : completionWindow,
+ requireApproval: requireApproval,
+ isPrivate: isPrivate,
status: 0,
frequencyType: 'once',
frequencyMetadata: {},
@@ -715,7 +728,6 @@ const TaskInput = ({ onChoreUpdate, isModalOpen, onClose }) => {
footer={
{
/>
-
+
{!hasDescription && (
}
+ size='sm'
variant='outlined'
color='neutral'
- size='md'
+ startDecorator={}
onClick={() => setHasDescription(true)}
endDecorator={
showKeyboardShortcuts && (
)
}
+ sx={{ borderRadius: '128px', minHeight: 40, px: 1.25 }}
>
Description
)}
{!hasSubTasks && (
}
+ size='sm'
variant='outlined'
color='neutral'
- size='md'
+ startDecorator={}
onClick={() => setHasSubTasks(true)}
endDecorator={
showKeyboardShortcuts && (
)
}
+ sx={{ borderRadius: '128px', minHeight: 40, px: 1.25 }}
>
Subtasks
)}
+ setShowAdvanced(v => !v)}
+ activeCount={
+ [
+ points > -1,
+ requireApproval,
+ completionWindow > -1,
+ deadlineOffset > -1,
+ ].filter(Boolean).length
+ }
+ emptyDisplay={pickerEmptyDisplay}
+ />
+ 1}
+ hasAssignees={assignees.length > 0}
+ isPrivate={isPrivate}
+ onIsPrivateChange={setIsPrivate}
+ />
+
{hasDescription && (
Description:
diff --git a/src/views/components/AdvancedOptionsSection.jsx b/src/views/components/AdvancedOptionsSection.jsx
new file mode 100644
index 0000000..81b0324
--- /dev/null
+++ b/src/views/components/AdvancedOptionsSection.jsx
@@ -0,0 +1,361 @@
+import {
+ Add,
+ Approval,
+ HourglassTop,
+ Lock,
+ MoreHoriz,
+ People,
+ Remove,
+ Timer,
+} from '@mui/icons-material'
+import {
+ Box,
+ IconButton,
+ Input,
+ Option,
+ Select,
+ Switch,
+ Typography,
+} from '@mui/joy'
+
+const STRATEGY_OPTIONS = [
+ { value: 'keep_last_assigned', label: 'Keep same assignee' },
+ { value: 'random', label: 'Random' },
+ { value: 'least_completed', label: 'Least completed' },
+ { value: 'round_robin', label: 'Round robin' },
+]
+
+const FieldRow = ({ label, description, children }) => (
+
+
+
+ {label}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ {children}
+
+
+)
+
+// Trigger button — place this inside the chip/action row
+export const AdvancedOptionsTrigger = ({
+ open,
+ onToggle,
+ activeCount = 0,
+ emptyDisplay = 'icon-text',
+}) => {
+ const showLabel = emptyDisplay === 'icon-text' || open || activeCount > 0
+
+ return (
+
+ 0
+ ? 'primary.outlinedBorder'
+ : 'neutral.outlinedBorder',
+ bgcolor:
+ open || activeCount > 0 ? 'primary.softBg' : 'transparent',
+ color:
+ open || activeCount > 0 ? 'primary.softColor' : 'text.secondary',
+ borderRadius: '128px',
+ minHeight: 40,
+ px: showLabel ? 1.5 : 0.75,
+ cursor: 'pointer',
+ transition: 'all 0.2s ease-in-out',
+ '&:hover': {
+ bgcolor:
+ open || activeCount > 0
+ ? 'primary.softHoverBg'
+ : 'neutral.softHoverBg',
+ borderColor:
+ open || activeCount > 0
+ ? 'primary.outlinedHoverBorder'
+ : 'neutral.outlinedHoverBorder',
+ },
+ }}
+ >
+
+
+ More
+
+
+
+ {activeCount > 0 && (
+
+ {activeCount}
+
+ )}
+
+ )
+}
+
+// Panel — place this as a sibling below the description/subtask sections
+const AdvancedOptionsSection = ({
+ open,
+ points,
+ onPointsChange,
+ requireApproval,
+ onRequireApprovalChange,
+ completionWindow,
+ onCompletionWindowChange,
+ deadlineOffset,
+ onDeadlineOffsetChange,
+ assignStrategy,
+ onAssignStrategyChange,
+ isPrivate,
+ onIsPrivateChange,
+ hasDueDate,
+ hasMultipleAssignees,
+ hasAssignees,
+}) => {
+ const displayPoints = points <= 0 ? 0 : points
+
+ const handleDecrement = () => {
+ const next = Math.max(0, displayPoints - 1)
+ onPointsChange(next === 0 ? -1 : next)
+ }
+
+ const handleIncrement = () => {
+ onPointsChange(displayPoints + 1)
+ }
+
+ const handlePointsInput = e => {
+ const v = parseInt(e.target.value)
+ if (isNaN(v) || v <= 0) {
+ onPointsChange(-1)
+ } else {
+ onPointsChange(Math.min(v, 9999))
+ }
+ }
+
+ return (
+
+
+
+ {/* Points */}
+
+
+
+
+
+
+
+
+
+
+ {/* Require approval */}
+
+ onRequireApprovalChange(e.target.checked)}
+ />
+
+
+ {/* Privacy */}
+
+ onIsPrivateChange(e.target.checked)}
+ />
+
+
+ {/* Assignment strategy — only shown when there are multiple assignees */}
+ {hasMultipleAssignees && (
+
+
+
+ )}
+
+ {/* Completion window and deadline — only when due date set */}
+ {hasDueDate ? (
+ <>
+
+ -1 ? completionWindow : ''}
+ onChange={e => {
+ const v = parseInt(e.target.value)
+ onCompletionWindowChange(isNaN(v) ? -1 : Math.max(0, v))
+ }}
+ endDecorator={
+
+ hrs
+
+ }
+ sx={{ width: 96 }}
+ slotProps={{ input: { min: 0 } }}
+ />
+
+
+
+ -1 ? deadlineOffset : ''}
+ onChange={e => {
+ const v = parseInt(e.target.value)
+ onDeadlineOffsetChange(isNaN(v) ? -1 : Math.max(0, v))
+ }}
+ endDecorator={
+
+ hrs
+
+ }
+ sx={{ width: 96 }}
+ slotProps={{ input: { min: 0 } }}
+ />
+
+ >
+ ) : (
+
+ Set a due date to configure completion window and deadline.
+
+ )}
+
+
+
+ )
+}
+
+export default AdvancedOptionsSection