Remove gray panel in add task modal and update label interaction (#187)
* Remove the gray panel in add task modal * migration clicking label to the new advance filtered. * Fix: Clicking x on filter chip remove the filter condition
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
import { Close } from '@mui/icons-material'
|
import { Add, Close } from '@mui/icons-material'
|
||||||
import { Box, Button, Chip, Typography } from '@mui/joy'
|
import { Box, Button, Chip, ChipDelete, Typography } from '@mui/joy'
|
||||||
|
|
||||||
const ActiveFilterChips = ({
|
const ActiveFilterChips = ({
|
||||||
chips = [],
|
chips = [],
|
||||||
onOpen,
|
onOpen,
|
||||||
onClearAll,
|
onClearAll,
|
||||||
|
onAdd,
|
||||||
|
showAddChip = false,
|
||||||
resultCount,
|
resultCount,
|
||||||
totalCount,
|
totalCount,
|
||||||
maxVisible = 2,
|
maxVisible = 2,
|
||||||
@@ -44,13 +46,21 @@ const ActiveFilterChips = ({
|
|||||||
variant='soft'
|
variant='soft'
|
||||||
color={color}
|
color={color}
|
||||||
endDecorator={
|
endDecorator={
|
||||||
<Close
|
// ChipDelete rather than a bare icon: Joy's chip end decorator is
|
||||||
sx={{ cursor: 'pointer', fontSize: chipSize === 'sm' ? 12 : 16 }}
|
// `pointer-events: none`, so anything else here is swallowed by the
|
||||||
onClick={e => {
|
// chip's own click surface and can never clear the condition.
|
||||||
e.stopPropagation()
|
<ChipDelete
|
||||||
onClear?.()
|
variant='plain'
|
||||||
|
color={color}
|
||||||
|
onDelete={() => onClear?.()}
|
||||||
|
aria-label={`Remove ${label} filter`}
|
||||||
|
sx={{
|
||||||
|
'--Chip-deleteSize': chipSize === 'sm' ? '1.1rem' : '1.4rem',
|
||||||
|
'--Icon-fontSize': chipSize === 'sm' ? '12px' : '16px',
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<Close />
|
||||||
|
</ChipDelete>
|
||||||
}
|
}
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
sx={{
|
sx={{
|
||||||
@@ -74,7 +84,10 @@ const ActiveFilterChips = ({
|
|||||||
</Chip>
|
</Chip>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{overflow > 0 && (
|
{/* Everything already visible → spend that slot on a "+" for appending
|
||||||
|
another condition instead. With overflow, the count chip opens the
|
||||||
|
same sheet anyway. */}
|
||||||
|
{overflow > 0 ? (
|
||||||
<Chip
|
<Chip
|
||||||
size={chipSize}
|
size={chipSize}
|
||||||
variant='soft'
|
variant='soft'
|
||||||
@@ -90,6 +103,30 @@ const ActiveFilterChips = ({
|
|||||||
>
|
>
|
||||||
+{overflow} more
|
+{overflow} more
|
||||||
</Chip>
|
</Chip>
|
||||||
|
) : (
|
||||||
|
showAddChip &&
|
||||||
|
(onAdd || onOpen) && (
|
||||||
|
<Chip
|
||||||
|
size={chipSize}
|
||||||
|
variant='outlined'
|
||||||
|
color='neutral'
|
||||||
|
onClick={onAdd || onOpen}
|
||||||
|
aria-label='Add filter condition'
|
||||||
|
title='Add filter condition'
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
flexShrink: 0,
|
||||||
|
px: 0.75,
|
||||||
|
transition: 'all 0.15s ease',
|
||||||
|
'&:hover': { opacity: 0.85 },
|
||||||
|
...overflowChipSx,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Add
|
||||||
|
sx={{ fontSize: chipSize === 'sm' ? 12 : 16, display: 'block' }}
|
||||||
|
/>
|
||||||
|
</Chip>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{resultCount != null && totalCount != null && (
|
{resultCount != null && totalCount != null && (
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ import AdvancedFilterBuilder from '../Modals/Inputs/AdvancedFilterBuilder'
|
|||||||
import { useProjects } from '../Projects/ProjectQueries.js'
|
import { useProjects } from '../Projects/ProjectQueries.js'
|
||||||
import ChoreListView from './ChoreListView.jsx'
|
import ChoreListView from './ChoreListView.jsx'
|
||||||
import ChoreToolbar from './components/ChoreToolbarPrototype'
|
import ChoreToolbar from './components/ChoreToolbarPrototype'
|
||||||
|
import {
|
||||||
|
conditionsToSelections,
|
||||||
|
selectionsToConditions,
|
||||||
|
} from './components/FilterBuilderContent'
|
||||||
import ChoreModals from './components/ChoreModals'
|
import ChoreModals from './components/ChoreModals'
|
||||||
import MultiSelectToolbar from './components/MultiSelectToolbar'
|
import MultiSelectToolbar from './components/MultiSelectToolbar'
|
||||||
import MyChoreHeader from './components/MyChoreHeader'
|
import MyChoreHeader from './components/MyChoreHeader'
|
||||||
@@ -683,14 +687,46 @@ const MyChores = () => {
|
|||||||
setAnchorEl(null)
|
setAnchorEl(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clicking a label / priority chip on a task card feeds the advanced filter
|
||||||
|
// (as a temp filter) rather than the legacy quick filters. Clicking the same
|
||||||
|
// chip again removes that value, so chips toggle.
|
||||||
const handleLabelFiltering = chipClicked => {
|
const handleLabelFiltering = chipClicked => {
|
||||||
clearActiveFilter()
|
const type = chipClicked.label ? 'label' : 'priority'
|
||||||
if (chipClicked.label) {
|
const value = chipClicked.label
|
||||||
setQuickFilter('label', [chipClicked.label.id])
|
? chipClicked.label.id
|
||||||
} else if (chipClicked.priority) {
|
: chipClicked.priority
|
||||||
setQuickFilter('priority', [chipClicked.priority])
|
if (value === undefined || value === null) return
|
||||||
|
|
||||||
|
const selections = conditionsToSelections(tempFilter?.conditions)
|
||||||
|
const currentValues = selections[type].values || []
|
||||||
|
const isActive = currentValues.some(v => String(v) === String(value))
|
||||||
|
selections[type] = {
|
||||||
|
operator: selections[type].operator || 'is',
|
||||||
|
values: isActive
|
||||||
|
? currentValues.filter(v => String(v) !== String(value))
|
||||||
|
: [...currentValues, value],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const conditions = selectionsToConditions(selections)
|
||||||
|
|
||||||
|
clearQuickFilters()
|
||||||
setSelectedCalendarDate(null)
|
setSelectedCalendarDate(null)
|
||||||
|
|
||||||
|
if (conditions.length === 0) {
|
||||||
|
clearTempFilter()
|
||||||
|
clearActiveFilter()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const chipName = chipClicked.label
|
||||||
|
? chipClicked.label.name
|
||||||
|
: `P${chipClicked.priority}`
|
||||||
|
applyTempFilter(
|
||||||
|
{ conditions, operator: 'AND' },
|
||||||
|
// Keep whatever the temp filter was already labelled as (e.g. an
|
||||||
|
// in-progress saved-filter edit); only name it when starting fresh.
|
||||||
|
tempFilterMeta ?? { name: chipName },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to update URL with filter parameters
|
// Helper to update URL with filter parameters
|
||||||
|
|||||||
@@ -317,27 +317,6 @@ const ChoreToolbar = ({
|
|||||||
return `${prefix}${typeLabel} (${rawValues.length})`
|
return `${prefix}${typeLabel} (${rawValues.length})`
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearConditionAtIndex = index => {
|
|
||||||
const nextConditions = (tempFilter?.conditions || []).filter(
|
|
||||||
(_condition, conditionIndex) => conditionIndex !== index,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (nextConditions.length === 0) {
|
|
||||||
setLocalSelections(defaultSelections())
|
|
||||||
clearTempFilter?.()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextFilter = {
|
|
||||||
...tempFilter,
|
|
||||||
operator: tempFilter?.operator || 'AND',
|
|
||||||
conditions: nextConditions,
|
|
||||||
}
|
|
||||||
|
|
||||||
setLocalSelections(conditionsToSelections(nextConditions))
|
|
||||||
applyTempFilter?.(nextFilter)
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeSavedFilter = savedFilterActive
|
const activeSavedFilter = savedFilterActive
|
||||||
? savedFilters.find(f => f.id === activeFilterId)
|
? savedFilters.find(f => f.id === activeFilterId)
|
||||||
: null
|
: null
|
||||||
@@ -346,17 +325,61 @@ const ChoreToolbar = ({
|
|||||||
? activeSavedFilter?.conditions || []
|
? activeSavedFilter?.conditions || []
|
||||||
: tempFilter?.conditions || []
|
: tempFilter?.conditions || []
|
||||||
|
|
||||||
|
const savedFilterEditMeta = activeSavedFilter
|
||||||
|
? {
|
||||||
|
name: activeSavedFilter.name,
|
||||||
|
description: activeSavedFilter.description,
|
||||||
|
sourceFilterId: activeSavedFilter.id,
|
||||||
|
sourceFilterName: activeSavedFilter.name,
|
||||||
|
sourceFilterDescription: activeSavedFilter.description,
|
||||||
|
sourceFilterColor: activeSavedFilter.color,
|
||||||
|
isEditingSavedFilter: true,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
|
||||||
|
const clearConditionAtIndex = index => {
|
||||||
|
const nextConditions = activeChipConditions.filter(
|
||||||
|
(_condition, conditionIndex) => conditionIndex !== index,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (nextConditions.length === 0) {
|
||||||
|
setLocalSelections(defaultSelections())
|
||||||
|
clearTempFilter?.()
|
||||||
|
// A saved filter still owns the view until it's toggled back off.
|
||||||
|
if (savedFilterActive) onSavedFilterClick?.(activeFilterId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocalSelections(conditionsToSelections(nextConditions))
|
||||||
|
|
||||||
|
// Dropping a condition from a saved filter detaches it into a temp filter
|
||||||
|
// that remembers its source, rather than editing the saved filter itself.
|
||||||
|
if (savedFilterActive) {
|
||||||
|
applyTempFilter?.(
|
||||||
|
{
|
||||||
|
conditions: nextConditions,
|
||||||
|
operator: activeSavedFilter?.operator || 'AND',
|
||||||
|
},
|
||||||
|
savedFilterEditMeta,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTempFilter?.(
|
||||||
|
{
|
||||||
|
...tempFilter,
|
||||||
|
operator: tempFilter?.operator || 'AND',
|
||||||
|
conditions: nextConditions,
|
||||||
|
},
|
||||||
|
tempFilterMeta,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
activeChipConditions.forEach((condition, index) => {
|
activeChipConditions.forEach((condition, index) => {
|
||||||
inlineChips.push({
|
inlineChips.push({
|
||||||
key: `${savedFilterActive ? '__saved' : '__temp'}_${index}`,
|
key: `${savedFilterActive ? '__saved' : '__temp'}_${index}`,
|
||||||
label: getConditionChipLabel(condition),
|
label: getConditionChipLabel(condition),
|
||||||
onClear: () => {
|
onClear: () => clearConditionAtIndex(index),
|
||||||
if (savedFilterActive) {
|
|
||||||
onSavedFilterClick?.(activeFilterId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
clearConditionAtIndex(index)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -638,6 +661,8 @@ const ChoreToolbar = ({
|
|||||||
<ActiveFilterChips
|
<ActiveFilterChips
|
||||||
chips={inlineChips}
|
chips={inlineChips}
|
||||||
onOpen={openFilterSheet}
|
onOpen={openFilterSheet}
|
||||||
|
showAddChip
|
||||||
|
onAdd={openFilterSheet}
|
||||||
onClearAll={() => {
|
onClearAll={() => {
|
||||||
setLocalSelections(defaultSelections())
|
setLocalSelections(defaultSelections())
|
||||||
onClearAllFilters?.()
|
onClearAllFilters?.()
|
||||||
|
|||||||
@@ -138,15 +138,7 @@ const ScanPanel = ({
|
|||||||
const isProcessing = phase === 'processing'
|
const isProcessing = phase === 'processing'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box>
|
||||||
sx={{
|
|
||||||
borderRadius: 'md',
|
|
||||||
border: '1px solid',
|
|
||||||
borderColor: 'primary.outlinedBorder',
|
|
||||||
overflow: 'hidden',
|
|
||||||
bgcolor: 'background.level1',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* ── Capture phase ── */}
|
{/* ── Capture phase ── */}
|
||||||
{phase === 'capture' && (
|
{phase === 'capture' && (
|
||||||
<>
|
<>
|
||||||
@@ -160,6 +152,8 @@ const ScanPanel = ({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
bgcolor: 'neutral.900',
|
bgcolor: 'neutral.900',
|
||||||
|
// The wrapper used to clip this; it owns its own corners now
|
||||||
|
borderRadius: 'md',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -211,7 +205,6 @@ const ScanPanel = ({
|
|||||||
{(isNativeScanner || cameraAvailable) && (
|
{(isNativeScanner || cameraAvailable) && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
px: 1.5,
|
|
||||||
py: 1,
|
py: 1,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -240,7 +233,7 @@ const ScanPanel = ({
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 1.5,
|
gap: 1.5,
|
||||||
p: 2.5,
|
py: 2.5,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{capturedImage && (
|
{capturedImage && (
|
||||||
@@ -297,7 +290,7 @@ const ScanPanel = ({
|
|||||||
|
|
||||||
{/* ── Error phase ── */}
|
{/* ── Error phase ── */}
|
||||||
{phase === 'error' && (
|
{phase === 'error' && (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ py: 2 }}>
|
||||||
{capturedImage && (
|
{capturedImage && (
|
||||||
<img
|
<img
|
||||||
src={capturedImage}
|
src={capturedImage}
|
||||||
|
|||||||
@@ -470,20 +470,11 @@ const VoicePanel = ({
|
|||||||
: 'Hold to speak · quick tap for hands-free'
|
: 'Hold to speak · quick tap for hands-free'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box>
|
||||||
sx={{
|
|
||||||
borderRadius: 'md',
|
|
||||||
border: '1px solid',
|
|
||||||
borderColor: 'primary.outlinedBorder',
|
|
||||||
overflow: 'hidden',
|
|
||||||
bgcolor: 'background.level1',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* ── Header ── */}
|
{/* ── Header ── */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
px: 1.5,
|
pt: 0.5,
|
||||||
pt: 1.25,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 1,
|
gap: 1,
|
||||||
@@ -506,7 +497,7 @@ const VoicePanel = ({
|
|||||||
|
|
||||||
{/* ── Permission denied ── */}
|
{/* ── Permission denied ── */}
|
||||||
{phase === 'denied' && (
|
{phase === 'denied' && (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ py: 2 }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, mb: 1.5 }}
|
sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, mb: 1.5 }}
|
||||||
>
|
>
|
||||||
@@ -532,7 +523,6 @@ const VoicePanel = ({
|
|||||||
<Box
|
<Box
|
||||||
ref={segmentsScrollRef}
|
ref={segmentsScrollRef}
|
||||||
sx={{
|
sx={{
|
||||||
px: 1.5,
|
|
||||||
pt: 1.25,
|
pt: 1.25,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@@ -556,14 +546,13 @@ const VoicePanel = ({
|
|||||||
|
|
||||||
{/* ── Live transcript ── */}
|
{/* ── Live transcript ── */}
|
||||||
{isListening && (
|
{isListening && (
|
||||||
<Box sx={{ px: 1.5, pt: 1.25 }}>
|
<Box sx={{ pt: 1.25 }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
minHeight: 44,
|
minHeight: 44,
|
||||||
borderRadius: 'md',
|
borderRadius: 'md',
|
||||||
border: '1px dashed',
|
border: '1px dashed',
|
||||||
borderColor: 'neutral.outlinedBorder',
|
borderColor: 'neutral.outlinedBorder',
|
||||||
bgcolor: 'background.surface',
|
|
||||||
px: 1.25,
|
px: 1.25,
|
||||||
py: 1,
|
py: 1,
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user