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 { Box, Button, Chip, Typography } from '@mui/joy'
|
||||
import { Add, Close } from '@mui/icons-material'
|
||||
import { Box, Button, Chip, ChipDelete, Typography } from '@mui/joy'
|
||||
|
||||
const ActiveFilterChips = ({
|
||||
chips = [],
|
||||
onOpen,
|
||||
onClearAll,
|
||||
onAdd,
|
||||
showAddChip = false,
|
||||
resultCount,
|
||||
totalCount,
|
||||
maxVisible = 2,
|
||||
@@ -44,13 +46,21 @@ const ActiveFilterChips = ({
|
||||
variant='soft'
|
||||
color={color}
|
||||
endDecorator={
|
||||
<Close
|
||||
sx={{ cursor: 'pointer', fontSize: chipSize === 'sm' ? 12 : 16 }}
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
onClear?.()
|
||||
// ChipDelete rather than a bare icon: Joy's chip end decorator is
|
||||
// `pointer-events: none`, so anything else here is swallowed by the
|
||||
// chip's own click surface and can never clear the condition.
|
||||
<ChipDelete
|
||||
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}
|
||||
sx={{
|
||||
@@ -74,7 +84,10 @@ const ActiveFilterChips = ({
|
||||
</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
|
||||
size={chipSize}
|
||||
variant='soft'
|
||||
@@ -90,6 +103,30 @@ const ActiveFilterChips = ({
|
||||
>
|
||||
+{overflow} more
|
||||
</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 && (
|
||||
|
||||
@@ -52,6 +52,10 @@ import AdvancedFilterBuilder from '../Modals/Inputs/AdvancedFilterBuilder'
|
||||
import { useProjects } from '../Projects/ProjectQueries.js'
|
||||
import ChoreListView from './ChoreListView.jsx'
|
||||
import ChoreToolbar from './components/ChoreToolbarPrototype'
|
||||
import {
|
||||
conditionsToSelections,
|
||||
selectionsToConditions,
|
||||
} from './components/FilterBuilderContent'
|
||||
import ChoreModals from './components/ChoreModals'
|
||||
import MultiSelectToolbar from './components/MultiSelectToolbar'
|
||||
import MyChoreHeader from './components/MyChoreHeader'
|
||||
@@ -683,14 +687,46 @@ const MyChores = () => {
|
||||
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 => {
|
||||
clearActiveFilter()
|
||||
if (chipClicked.label) {
|
||||
setQuickFilter('label', [chipClicked.label.id])
|
||||
} else if (chipClicked.priority) {
|
||||
setQuickFilter('priority', [chipClicked.priority])
|
||||
const type = chipClicked.label ? 'label' : 'priority'
|
||||
const value = chipClicked.label
|
||||
? chipClicked.label.id
|
||||
: 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)
|
||||
|
||||
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
|
||||
|
||||
@@ -317,27 +317,6 @@ const ChoreToolbar = ({
|
||||
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
|
||||
? savedFilters.find(f => f.id === activeFilterId)
|
||||
: null
|
||||
@@ -346,17 +325,61 @@ const ChoreToolbar = ({
|
||||
? activeSavedFilter?.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) => {
|
||||
inlineChips.push({
|
||||
key: `${savedFilterActive ? '__saved' : '__temp'}_${index}`,
|
||||
label: getConditionChipLabel(condition),
|
||||
onClear: () => {
|
||||
if (savedFilterActive) {
|
||||
onSavedFilterClick?.(activeFilterId)
|
||||
return
|
||||
}
|
||||
clearConditionAtIndex(index)
|
||||
},
|
||||
onClear: () => clearConditionAtIndex(index),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -638,6 +661,8 @@ const ChoreToolbar = ({
|
||||
<ActiveFilterChips
|
||||
chips={inlineChips}
|
||||
onOpen={openFilterSheet}
|
||||
showAddChip
|
||||
onAdd={openFilterSheet}
|
||||
onClearAll={() => {
|
||||
setLocalSelections(defaultSelections())
|
||||
onClearAllFilters?.()
|
||||
|
||||
@@ -138,15 +138,7 @@ const ScanPanel = ({
|
||||
const isProcessing = phase === 'processing'
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
borderRadius: 'md',
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.outlinedBorder',
|
||||
overflow: 'hidden',
|
||||
bgcolor: 'background.level1',
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
{/* ── Capture phase ── */}
|
||||
{phase === 'capture' && (
|
||||
<>
|
||||
@@ -160,6 +152,8 @@ const ScanPanel = ({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
bgcolor: 'neutral.900',
|
||||
// The wrapper used to clip this; it owns its own corners now
|
||||
borderRadius: 'md',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
@@ -211,7 +205,6 @@ const ScanPanel = ({
|
||||
{(isNativeScanner || cameraAvailable) && (
|
||||
<Box
|
||||
sx={{
|
||||
px: 1.5,
|
||||
py: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@@ -240,7 +233,7 @@ const ScanPanel = ({
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: 1.5,
|
||||
p: 2.5,
|
||||
py: 2.5,
|
||||
}}
|
||||
>
|
||||
{capturedImage && (
|
||||
@@ -297,7 +290,7 @@ const ScanPanel = ({
|
||||
|
||||
{/* ── Error phase ── */}
|
||||
{phase === 'error' && (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Box sx={{ py: 2 }}>
|
||||
{capturedImage && (
|
||||
<img
|
||||
src={capturedImage}
|
||||
|
||||
@@ -470,20 +470,11 @@ const VoicePanel = ({
|
||||
: 'Hold to speak · quick tap for hands-free'
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
borderRadius: 'md',
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.outlinedBorder',
|
||||
overflow: 'hidden',
|
||||
bgcolor: 'background.level1',
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
{/* ── Header ── */}
|
||||
<Box
|
||||
sx={{
|
||||
px: 1.5,
|
||||
pt: 1.25,
|
||||
pt: 0.5,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
@@ -506,7 +497,7 @@ const VoicePanel = ({
|
||||
|
||||
{/* ── Permission denied ── */}
|
||||
{phase === 'denied' && (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Box sx={{ py: 2 }}>
|
||||
<Box
|
||||
sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, mb: 1.5 }}
|
||||
>
|
||||
@@ -532,7 +523,6 @@ const VoicePanel = ({
|
||||
<Box
|
||||
ref={segmentsScrollRef}
|
||||
sx={{
|
||||
px: 1.5,
|
||||
pt: 1.25,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -556,14 +546,13 @@ const VoicePanel = ({
|
||||
|
||||
{/* ── Live transcript ── */}
|
||||
{isListening && (
|
||||
<Box sx={{ px: 1.5, pt: 1.25 }}>
|
||||
<Box sx={{ pt: 1.25 }}>
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: 44,
|
||||
borderRadius: 'md',
|
||||
border: '1px dashed',
|
||||
borderColor: 'neutral.outlinedBorder',
|
||||
bgcolor: 'background.surface',
|
||||
px: 1.25,
|
||||
py: 1,
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user