Enhance project management UI and handle bulk changes (#186)

* enable hold to select, remove once x1 chip, unify reschedule modal

* project title still show fromwhen login from different account
it's was cache in local storage so makdikngsure we do clean up

* Fix: show the button for creating task on the modal instead of the panel

* Bulk moving project, Move the button in scanner to the modal

* - Handle bulk change for projects
- allow no assignee being selected
- choreactionmenu become modal on mobile
This commit is contained in:
Mohamad Tarbin
2026-08-02 16:14:52 -04:00
committed by GitHub
parent 9478f7d0f5
commit b2949b2dd9
20 changed files with 1587 additions and 954 deletions

View File

@@ -401,18 +401,18 @@ const TaskPreviewCard = ({
/**
* Inline voice-to-task panel. Mounts inside AddTaskModal — no second modal.
*
* Opens straight into hands-free listening. Pauses and spoken separators
* ("also") split the transcript into task cards; tapping a card opens inline
* pickers whose edits override the parsed values. A single captured task
* lands in the smart input for review; multiple are created directly.
* Mounted only while voice capture is active, and opens straight into
* hands-free listening. Pauses and spoken separators ("also") split the
* transcript into task cards; tapping a card opens inline pickers whose edits
* override the parsed values. The confirm action lives in the modal footer
* alongside Cancel — this panel only reports its state up through
* onStateChange so the modal can label and enable that button.
*/
const VoicePanel = ({
open,
userLabels = [],
members = [],
userProfile,
onUseSingle,
onCreateMany,
onStateChange,
}) => {
const {
phase,
@@ -427,8 +427,6 @@ const VoicePanel = ({
patchSegment,
isNative,
} = useVoiceToTask({ members, userLabels })
const [creating, setCreating] = useState(false)
const autoStartedRef = useRef(false)
const segmentsScrollRef = useRef(null)
const parseCtx = useMemo(
@@ -441,14 +439,12 @@ const VoicePanel = ({
[partialText, parseCtx],
)
// Start capturing the moment the panel opens — the mic tap that opened it
// is the only tap needed
// Start capturing the moment the panel mounts — the mic tap that opened it
// is the only tap needed. startHandsFree no-ops if already listening.
useEffect(() => {
if (open && !autoStartedRef.current) {
autoStartedRef.current = true
startHandsFree()
}
}, [open, startHandsFree])
startHandsFree()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// Keep the newest captured task visible as more are added
useEffect(() => {
@@ -456,24 +452,14 @@ const VoicePanel = ({
if (el) el.scrollTop = el.scrollHeight
}, [segments.length])
if (!open) return null
const isListening = phase === 'listening'
const showActions = segments.length > 0 && !isListening && !creating
const mergedTask = segment => ({
...parseVoiceTask(segment.text, parseCtx),
...(segment.overrides || {}),
})
const handleCreateAll = async () => {
setCreating(true)
try {
await onCreateMany(segments.map(mergedTask))
} finally {
setCreating(false)
}
}
// The confirm action lives in the modal footer, so report the raw segments
// and whether the mic is live — that's all it needs to label and enable the
// button. It parses the segments itself when the user confirms.
useEffect(() => {
onStateChange?.({ segments, isListening })
}, [segments, isListening, onStateChange])
const micCaption = isListening
? isLocked
@@ -642,47 +628,6 @@ const VoicePanel = ({
</Typography>
</Box>
)}
{/* ── Footer — dismissing is the modal's Cancel; this owns confirm only ── */}
{(creating || showActions) && (
<Box
sx={{
px: 1.5,
py: 1,
display: 'flex',
justifyContent: 'flex-end',
gap: 1,
borderTop: '1px solid',
borderColor: 'divider',
}}
>
{creating ? (
<Button size='sm' variant='solid' color='primary' loading>
Creating
</Button>
) : segments.length === 1 ? (
<Button
size='sm'
variant='solid'
color='primary'
onClick={() =>
onUseSingle(segments[0].text, segments[0].overrides || {})
}
>
Use Task
</Button>
) : (
<Button
size='sm'
variant='solid'
color='primary'
onClick={handleCreateAll}
>
Create {segments.length} Tasks
</Button>
)}
</Box>
)}
</Box>
)
}