@open-mercato/ui 0.6.1-develop.3276.1.1fcd145078 → 0.6.1-develop.3287.1.450f4ffb56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +23 -14
  3. package/dist/backend/AppShell.js +8 -32
  4. package/dist/backend/AppShell.js.map +2 -2
  5. package/dist/backend/CrudForm.js +18 -0
  6. package/dist/backend/CrudForm.js.map +2 -2
  7. package/dist/backend/DataTable.js +149 -53
  8. package/dist/backend/DataTable.js.map +2 -2
  9. package/dist/backend/FilterBar.js +11 -14
  10. package/dist/backend/FilterBar.js.map +2 -2
  11. package/dist/backend/columns/ColumnChooserPanel.js +4 -6
  12. package/dist/backend/columns/ColumnChooserPanel.js.map +2 -2
  13. package/dist/backend/filters/ActiveFilterChips.js +142 -0
  14. package/dist/backend/filters/ActiveFilterChips.js.map +7 -0
  15. package/dist/backend/filters/AdvancedFilterBuilder.js +689 -163
  16. package/dist/backend/filters/AdvancedFilterBuilder.js.map +2 -2
  17. package/dist/backend/filters/AdvancedFilterPanel.js +354 -0
  18. package/dist/backend/filters/AdvancedFilterPanel.js.map +7 -0
  19. package/dist/backend/filters/FilterEmptyState.js +41 -0
  20. package/dist/backend/filters/FilterEmptyState.js.map +7 -0
  21. package/dist/backend/filters/FilterFieldPicker.js +148 -0
  22. package/dist/backend/filters/FilterFieldPicker.js.map +7 -0
  23. package/dist/backend/filters/FilteredEmptyResults.js +26 -0
  24. package/dist/backend/filters/FilteredEmptyResults.js.map +7 -0
  25. package/dist/backend/filters/QuickFilters.js +41 -0
  26. package/dist/backend/filters/QuickFilters.js.map +7 -0
  27. package/dist/backend/filters/filterValidation.js +47 -0
  28. package/dist/backend/filters/filterValidation.js.map +7 -0
  29. package/dist/backend/filters/treeReducer.js +159 -0
  30. package/dist/backend/filters/treeReducer.js.map +7 -0
  31. package/dist/backend/hooks/useAdvancedFilter.js +82 -1
  32. package/dist/backend/hooks/useAdvancedFilter.js.map +2 -2
  33. package/dist/backend/inputs/ComboboxInput.js +11 -7
  34. package/dist/backend/inputs/ComboboxInput.js.map +2 -2
  35. package/dist/backend/inputs/EventPatternInput.js +5 -2
  36. package/dist/backend/inputs/EventPatternInput.js.map +2 -2
  37. package/dist/backend/inputs/EventSelect.js +10 -4
  38. package/dist/backend/inputs/EventSelect.js.map +2 -2
  39. package/dist/backend/inputs/LookupSelect.js +51 -47
  40. package/dist/backend/inputs/LookupSelect.js.map +2 -2
  41. package/dist/backend/inputs/PhoneNumberField.js +196 -45
  42. package/dist/backend/inputs/PhoneNumberField.js.map +2 -2
  43. package/dist/backend/inputs/SwitchableMarkdownInput.js.map +2 -2
  44. package/dist/backend/messages/message-compose-form-groups.js +2 -2
  45. package/dist/backend/messages/message-compose-form-groups.js.map +2 -2
  46. package/dist/backend/utils/api.js +18 -1
  47. package/dist/backend/utils/api.js.map +2 -2
  48. package/dist/backend/utils/serverErrors.js +6 -2
  49. package/dist/backend/utils/serverErrors.js.map +2 -2
  50. package/dist/backend/utils/useAutoDiscoveredFields.js +1 -0
  51. package/dist/backend/utils/useAutoDiscoveredFields.js.map +2 -2
  52. package/dist/backend/utils/useCurrentUserId.js +31 -0
  53. package/dist/backend/utils/useCurrentUserId.js.map +7 -0
  54. package/dist/portal/utils/nav.js +17 -0
  55. package/dist/portal/utils/nav.js.map +2 -2
  56. package/dist/primitives/amount-input.js +128 -0
  57. package/dist/primitives/amount-input.js.map +7 -0
  58. package/dist/primitives/button-input.js +47 -0
  59. package/dist/primitives/button-input.js.map +7 -0
  60. package/dist/primitives/card-input.js +133 -0
  61. package/dist/primitives/card-input.js.map +7 -0
  62. package/dist/primitives/dialog.js +7 -5
  63. package/dist/primitives/dialog.js.map +2 -2
  64. package/dist/primitives/email-input.js +29 -0
  65. package/dist/primitives/email-input.js.map +7 -0
  66. package/dist/primitives/password-input.js +64 -0
  67. package/dist/primitives/password-input.js.map +7 -0
  68. package/dist/primitives/search-input.js +57 -0
  69. package/dist/primitives/search-input.js.map +7 -0
  70. package/dist/primitives/select.js +43 -5
  71. package/dist/primitives/select.js.map +2 -2
  72. package/dist/primitives/tag.js +4 -2
  73. package/dist/primitives/tag.js.map +2 -2
  74. package/dist/primitives/website-input.js +56 -0
  75. package/dist/primitives/website-input.js.map +7 -0
  76. package/package.json +3 -3
  77. package/src/backend/AppShell.tsx +7 -30
  78. package/src/backend/CrudForm.tsx +17 -0
  79. package/src/backend/DataTable.tsx +307 -63
  80. package/src/backend/FilterBar.tsx +5 -6
  81. package/src/backend/__tests__/DataTable.extensions.test.tsx +170 -0
  82. package/src/backend/__tests__/serverErrors.test.ts +79 -1
  83. package/src/backend/columns/ColumnChooserPanel.tsx +4 -5
  84. package/src/backend/filters/ActiveFilterChips.tsx +179 -0
  85. package/src/backend/filters/AdvancedFilterBuilder.tsx +840 -237
  86. package/src/backend/filters/AdvancedFilterPanel.tsx +456 -0
  87. package/src/backend/filters/FilterEmptyState.tsx +51 -0
  88. package/src/backend/filters/FilterFieldPicker.tsx +139 -0
  89. package/src/backend/filters/FilteredEmptyResults.tsx +40 -0
  90. package/src/backend/filters/QuickFilters.tsx +62 -0
  91. package/src/backend/filters/__tests__/ActiveFilterChips.test.tsx +75 -0
  92. package/src/backend/filters/__tests__/AdvancedFilterBuilder.test.tsx +210 -0
  93. package/src/backend/filters/__tests__/AdvancedFilterPanel.test.tsx +212 -0
  94. package/src/backend/filters/__tests__/FilterEmptyState.test.tsx +31 -0
  95. package/src/backend/filters/__tests__/FilterFieldPicker.test.tsx +61 -0
  96. package/src/backend/filters/__tests__/FilteredEmptyResults.test.tsx +35 -0
  97. package/src/backend/filters/__tests__/QuickFilters.test.tsx +34 -0
  98. package/src/backend/filters/__tests__/filterValidation.test.ts +69 -0
  99. package/src/backend/filters/__tests__/treeReducer.test.ts +189 -0
  100. package/src/backend/filters/filterValidation.ts +61 -0
  101. package/src/backend/filters/treeReducer.ts +201 -0
  102. package/src/backend/hooks/__tests__/useAdvancedFilter.test.tsx +213 -0
  103. package/src/backend/hooks/useAdvancedFilter.ts +130 -0
  104. package/src/backend/inputs/ComboboxInput.tsx +32 -26
  105. package/src/backend/inputs/EventPatternInput.tsx +11 -2
  106. package/src/backend/inputs/EventSelect.tsx +13 -4
  107. package/src/backend/inputs/LookupSelect.tsx +75 -53
  108. package/src/backend/inputs/PhoneNumberField.tsx +237 -47
  109. package/src/backend/inputs/SwitchableMarkdownInput.tsx +30 -0
  110. package/src/backend/inputs/__tests__/ComboboxInput.test.tsx +4 -0
  111. package/src/backend/inputs/__tests__/PhoneNumberField.test.tsx +4 -0
  112. package/src/backend/messages/message-compose-form-groups.tsx +2 -2
  113. package/src/backend/utils/api.ts +27 -2
  114. package/src/backend/utils/serverErrors.ts +15 -8
  115. package/src/backend/utils/useAutoDiscoveredFields.ts +2 -0
  116. package/src/backend/utils/useCurrentUserId.ts +40 -0
  117. package/src/portal/utils/__tests__/nav.test.ts +80 -0
  118. package/src/portal/utils/nav.ts +23 -1
  119. package/src/primitives/__tests__/password-input.test.tsx +93 -0
  120. package/src/primitives/__tests__/search-input.test.tsx +95 -0
  121. package/src/primitives/__tests__/zindex-overlay.test.tsx +26 -0
  122. package/src/primitives/amount-input.tsx +184 -0
  123. package/src/primitives/button-input.tsx +68 -0
  124. package/src/primitives/card-input.tsx +193 -0
  125. package/src/primitives/dialog.tsx +18 -7
  126. package/src/primitives/email-input.tsx +36 -0
  127. package/src/primitives/password-input.tsx +100 -0
  128. package/src/primitives/search-input.tsx +87 -0
  129. package/src/primitives/select.tsx +98 -11
  130. package/src/primitives/tag.tsx +3 -1
  131. package/src/primitives/website-input.tsx +72 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/filters/AdvancedFilterBuilder.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ChevronDown, Plus, Trash2, X } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n AdvancedFilterState,\n FilterCondition,\n FilterFieldDef,\n FilterFieldType,\n FilterJoinOperator,\n FilterOperator,\n} from '@open-mercato/shared/lib/query/advanced-filter'\nimport {\n OPERATORS_BY_FIELD_TYPE,\n getDefaultOperator,\n isValuelessOperator,\n createEmptyCondition,\n normalizeAdvancedFilterState,\n} from '@open-mercato/shared/lib/query/advanced-filter'\n\nexport type AdvancedFilterBuilderProps = {\n fields: FilterFieldDef[]\n value: AdvancedFilterState\n onChange: (state: AdvancedFilterState) => void\n onApply: () => void\n onClear: () => void\n}\n\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n is: 'is',\n is_not: 'is not',\n contains: 'contains',\n does_not_contain: 'does not contain',\n starts_with: 'starts with',\n ends_with: 'ends with',\n is_empty: 'is empty',\n is_not_empty: 'is not empty',\n equals: 'equals',\n not_equals: 'not equals',\n greater_than: 'greater than',\n less_than: 'less than',\n greater_or_equal: 'greater or equal',\n less_or_equal: 'less or equal',\n between: 'between',\n is_before: 'is before',\n is_after: 'is after',\n is_any_of: 'is any of',\n is_none_of: 'is none of',\n is_true: 'is true',\n is_false: 'is false',\n has_any_of: 'has any of',\n has_all_of: 'has all of',\n has_none_of: 'has none of',\n}\n\nfunction getFieldType(fields: FilterFieldDef[], fieldKey: string): FilterFieldType {\n const field = fields.find((f) => f.key === fieldKey)\n return field?.type ?? 'text'\n}\n\nfunction ConditionRow({\n condition,\n index,\n fields,\n join,\n onUpdate,\n onRemove,\n onToggleJoin,\n t,\n}: {\n condition: FilterCondition\n index: number\n fields: FilterFieldDef[]\n join: FilterJoinOperator\n onUpdate: (id: string, updates: Partial<FilterCondition>) => void\n onRemove: (id: string) => void\n onToggleJoin: (id: string) => void\n t: ReturnType<typeof useT>\n}) {\n const fieldType = getFieldType(fields, condition.field)\n const operators = OPERATORS_BY_FIELD_TYPE[fieldType] ?? OPERATORS_BY_FIELD_TYPE.text\n const valueless = isValuelessOperator(condition.operator)\n\n const handleFieldChange = (newField: string) => {\n const newType = getFieldType(fields, newField)\n const newOp = getDefaultOperator(newType)\n onUpdate(condition.id, { field: newField, operator: newOp, value: '' })\n }\n\n const joinLabel = join === 'and'\n ? t('ui.advancedFilter.and', 'And')\n : t('ui.advancedFilter.or', 'Or')\n\n return (\n <div className=\"flex flex-wrap items-start gap-2\">\n <div className=\"w-20 shrink-0 pt-0.5 text-sm text-muted-foreground\">\n {index === 0 ? (\n <span>{t('ui.advancedFilter.where', 'Where')}</span>\n ) : (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-9 min-w-[5rem] justify-between px-3 text-sm font-medium\"\n onClick={() => onToggleJoin(condition.id)}\n aria-label={t('ui.advancedFilter.toggleJoin', 'Toggle filter join operator')}\n title={t('ui.advancedFilter.toggleJoinHint', 'Click to switch between AND and OR')}\n >\n <span>{joinLabel}</span>\n <ChevronDown className=\"size-3.5 text-muted-foreground\" />\n </Button>\n )}\n </div>\n\n <Select\n value={condition.field || undefined}\n onValueChange={(next) => handleFieldChange(next ?? '')}\n >\n <SelectTrigger\n className=\"min-w-[140px]\"\n aria-label={t('ui.advancedFilter.selectField', 'Select field')}\n >\n <SelectValue placeholder={t('ui.advancedFilter.selectFieldPlaceholder', 'Select field...')} />\n </SelectTrigger>\n <SelectContent>\n {fields.map((f) => (\n <SelectItem key={f.key} value={f.key}>{f.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <Select\n value={condition.operator}\n onValueChange={(next) => onUpdate(condition.id, { operator: next as FilterOperator, value: '' })}\n >\n <SelectTrigger\n className=\"min-w-[120px]\"\n aria-label={t('ui.advancedFilter.selectOperator', 'Select operator')}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {operators.map((op) => (\n <SelectItem key={op} value={op}>\n {t(`ui.advancedFilter.operator.${op}`, OPERATOR_LABELS[op])}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {!valueless ? (\n <ValueInput\n condition={condition}\n fields={fields}\n fieldType={fieldType}\n onUpdate={onUpdate}\n t={t}\n />\n ) : null}\n\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n type=\"button\"\n onClick={() => onRemove(condition.id)}\n aria-label={t('ui.advancedFilter.removeCondition', 'Remove condition')}\n >\n <Trash2 className=\"size-4 text-muted-foreground\" />\n </IconButton>\n </div>\n )\n}\n\nfunction ValueInput({\n condition,\n fields,\n fieldType,\n onUpdate,\n t,\n}: {\n condition: FilterCondition\n fields: FilterFieldDef[]\n fieldType: FilterFieldType\n onUpdate: (id: string, updates: Partial<FilterCondition>) => void\n t: ReturnType<typeof useT>\n}) {\n const fieldDef = fields.find((f) => f.key === condition.field)\n const value = condition.value\n\n if (fieldType === 'select' && fieldDef?.options) {\n return (\n <Select\n value={typeof value === 'string' && value ? value : undefined}\n onValueChange={(next) => onUpdate(condition.id, { value: next ?? '' })}\n >\n <SelectTrigger\n className=\"min-w-[140px]\"\n aria-label={t('ui.advancedFilter.selectValue', 'Select value')}\n >\n <SelectValue placeholder={t('ui.advancedFilter.selectValuePlaceholder', 'Select...')} />\n </SelectTrigger>\n <SelectContent>\n {fieldDef.options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n }\n\n if (fieldType === 'date') {\n return (\n <input\n type=\"date\"\n className=\"rounded border bg-background px-2 py-1.5 text-sm\"\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onUpdate(condition.id, { value: e.target.value })}\n aria-label={t('ui.advancedFilter.dateValue', 'Date value')}\n />\n )\n }\n\n if (fieldType === 'number') {\n return (\n <Input\n type=\"number\"\n size=\"sm\"\n className=\"w-[120px]\"\n value={typeof value === 'number' ? value : typeof value === 'string' ? value : ''}\n onChange={(e) => onUpdate(condition.id, { value: e.target.value })}\n placeholder={t('ui.advancedFilter.numberPlaceholder', 'Value')}\n aria-label={t('ui.advancedFilter.numberValue', 'Number value')}\n />\n )\n }\n\n return (\n <Input\n type=\"text\"\n size=\"sm\"\n className=\"min-w-[140px]\"\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onUpdate(condition.id, { value: e.target.value })}\n placeholder={t('ui.advancedFilter.textPlaceholder', 'Value...')}\n aria-label={t('ui.advancedFilter.textValue', 'Text value')}\n />\n )\n}\n\nexport function AdvancedFilterBuilder({\n fields,\n value,\n onChange,\n onApply,\n onClear,\n}: AdvancedFilterBuilderProps) {\n const t = useT()\n const normalizedValue = React.useMemo(() => normalizeAdvancedFilterState(value), [value])\n const emitChange = React.useCallback((next: AdvancedFilterState) => {\n onChange(normalizeAdvancedFilterState(next))\n }, [onChange])\n\n const updateCondition = React.useCallback((id: string, updates: Partial<FilterCondition>) => {\n emitChange({\n ...normalizedValue,\n conditions: normalizedValue.conditions.map((c) => (c.id === id ? { ...c, ...updates } : c)),\n })\n }, [emitChange, normalizedValue])\n\n const removeCondition = React.useCallback((id: string) => {\n emitChange({\n ...normalizedValue,\n conditions: normalizedValue.conditions.filter((c) => c.id !== id),\n })\n }, [emitChange, normalizedValue])\n\n const addCondition = React.useCallback(() => {\n const newCondition = createEmptyCondition()\n if (fields.length > 0) {\n newCondition.field = fields[0].key\n newCondition.operator = getDefaultOperator(fields[0].type)\n }\n emitChange({\n ...normalizedValue,\n conditions: [...normalizedValue.conditions, newCondition],\n })\n }, [emitChange, fields, normalizedValue])\n\n const toggleConditionJoin = React.useCallback((id: string) => {\n emitChange({\n ...normalizedValue,\n conditions: normalizedValue.conditions.map((condition) => (\n condition.id === id\n ? { ...condition, join: condition.join === 'or' ? 'and' : 'or' }\n : condition\n )),\n })\n }, [emitChange, normalizedValue])\n\n return (\n <div className=\"inline-flex flex-col gap-3 p-3\">\n {normalizedValue.conditions.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('ui.advancedFilter.noConditions', 'No filter conditions. Click \"Add filter\" to start.')}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {normalizedValue.conditions.map((condition, index) => (\n <ConditionRow\n key={condition.id}\n condition={condition}\n index={index}\n fields={fields}\n join={condition.join ?? normalizedValue.logic}\n onUpdate={updateCondition}\n onRemove={removeCondition}\n onToggleJoin={toggleConditionJoin}\n t={t}\n />\n ))}\n </div>\n )}\n\n <div className=\"flex flex-wrap items-center gap-3 pt-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 py-0 text-base font-medium text-primary hover:bg-transparent hover:text-primary/90\"\n onClick={addCondition}\n >\n <Plus className=\"size-4\" />\n {t('ui.advancedFilter.addFilter', 'Add filter')}\n </Button>\n {normalizedValue.conditions.length > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 py-0 text-base text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={onClear}\n >\n <X className=\"size-4\" />\n {t('ui.advancedFilter.clear', 'Clear')}\n </Button>\n ) : null}\n {normalizedValue.conditions.length > 0 ? (\n <Button type=\"button\" className=\"ml-auto min-w-[8rem]\" onClick={onApply}>\n {t('ui.advancedFilter.apply', 'Apply')}\n </Button>\n ) : null}\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { Plus, Trash2, X, GripVertical, ChevronDown } from 'lucide-react'\nimport {\n DndContext,\n DragOverlay,\n PointerSensor,\n KeyboardSensor,\n useSensor,\n useSensors,\n closestCenter,\n type DragEndEvent,\n type DragStartEvent,\n} from '@dnd-kit/core'\nimport {\n SortableContext,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from '@dnd-kit/sortable'\nimport { CSS } from '@dnd-kit/utilities'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport { Popover, PopoverContent, PopoverTrigger } from '../../primitives/popover'\nimport { Checkbox } from '../../primitives/checkbox'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n FilterFieldDef,\n FilterFieldType,\n FilterOperator,\n} from '@open-mercato/shared/lib/query/advanced-filter'\nimport {\n OPERATORS_BY_FIELD_TYPE,\n getDefaultOperator,\n isValuelessOperator,\n} from '@open-mercato/shared/lib/query/advanced-filter'\nimport {\n type AdvancedFilterTree,\n type FilterRule,\n type FilterGroup,\n type FilterCombinator,\n TREE_LIMITS,\n} from '@open-mercato/shared/lib/query/advanced-filter-tree'\nimport { treeReducer, canAddRule, canAddGroup, type TreeAction } from './treeReducer'\nimport { FilterFieldPicker } from './FilterFieldPicker'\n\nexport type AdvancedFilterBuilderProps = {\n fields: FilterFieldDef[]\n value: AdvancedFilterTree\n onChange: (state: AdvancedFilterTree) => void\n onApply: () => void\n onClear: () => void\n pendingErrors?: Array<{ ruleId: string; messageKey: string; message: string }>\n}\n\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n is: 'is', is_not: 'is not', contains: 'contains', does_not_contain: 'does not contain',\n starts_with: 'starts with', ends_with: 'ends with', is_empty: 'is empty', is_not_empty: 'is not empty',\n equals: 'equals', not_equals: 'not equals', greater_than: 'greater than', less_than: 'less than',\n greater_or_equal: 'greater or equal', less_or_equal: 'less or equal', between: 'between',\n is_before: 'is before', is_after: 'is after', is_any_of: 'is any of', is_none_of: 'is none of',\n is_true: 'is true', is_false: 'is false', has_any_of: 'has any of', has_all_of: 'has all of', has_none_of: 'has none of',\n}\n\nfunction getFieldType(fields: FilterFieldDef[], fieldKey: string): FilterFieldType {\n return fields.find((f) => f.key === fieldKey)?.type ?? 'text'\n}\n\n// Operator value shapes. Used to decide whether changing operator preserves the\n// current value: same shape preserves (`is` \u2192 `is not` keeps \"Active\"); different\n// shape resets (`is` \u2192 `between` clears, because value goes from scalar to pair).\ntype OperatorShape = 'valueless' | 'multi' | 'range' | 'single'\n\nconst MULTI_VALUE_OPS = new Set<FilterOperator>([\n 'is_any_of', 'is_none_of', 'has_any_of', 'has_all_of', 'has_none_of',\n])\n\nfunction getOperatorShape(op: FilterOperator): OperatorShape {\n if (isValuelessOperator(op)) return 'valueless'\n if (MULTI_VALUE_OPS.has(op)) return 'multi'\n if (op === 'between') return 'range'\n return 'single'\n}\n\ntype DragHandleProps = React.HTMLAttributes<HTMLButtonElement> & {\n ref?: React.Ref<HTMLButtonElement>\n}\n\ntype Translator = ReturnType<typeof useT>\n\nexport function AdvancedFilterBuilder({\n fields, value, onChange, onApply, onClear, pendingErrors,\n}: AdvancedFilterBuilderProps) {\n const t = useT()\n\n const dispatch = React.useCallback((action: TreeAction) => {\n onChange(treeReducer(value, action))\n }, [onChange, value])\n\n React.useEffect(() => {\n function onKey(e: KeyboardEvent) {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n onApply()\n }\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [onApply])\n\n const errorByRuleId = React.useMemo(() => {\n const m = new Map<string, string>()\n for (const e of pendingErrors ?? []) m.set(e.ruleId, t(e.messageKey, e.message))\n return m\n }, [pendingErrors, t])\n\n const empty = value.root.children.length === 0\n const defaultField = fields.length > 0 ? fields[0].key : undefined\n\n // Per-group SortableContexts scope drag-and-drop to within-group ordering.\n // The DragOverlay below renders a stable preview that\n // matches the original row's footprint \u2014 without it, dnd-kit applies\n // `transform` to the source row in place, which the wrapping flex layout\n // distorted into a \"tall\" / morphed ghost.\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),\n )\n const [activeDrag, setActiveDrag] = React.useState<{ node: FilterRule | FilterGroup; parent: FilterGroup } | null>(null)\n const findNode = React.useCallback((id: string) => locateInTree(value.root, id), [value])\n\n const handleDragStart = React.useCallback((e: DragStartEvent) => {\n const found = findNode(String(e.active.id))\n if (found) setActiveDrag(found)\n }, [findNode])\n\n const handleDragEnd = React.useCallback((e: DragEndEvent) => {\n setActiveDrag(null)\n const { active, over } = e\n if (!over || active.id === over.id) return\n const from = findNode(String(active.id))\n const to = findNode(String(over.id))\n if (!from || !to) return\n if (from.parent.id !== to.parent.id) return\n const fromIdx = from.parent.children.findIndex((c) => c.id === active.id)\n const toIdx = to.parent.children.findIndex((c) => c.id === over.id)\n if (fromIdx < 0 || toIdx < 0) return\n dispatch({ type: 'reorderChildren', groupId: from.parent.id, fromIdx, toIdx })\n }, [dispatch, findNode])\n\n const handleDragCancel = React.useCallback(() => setActiveDrag(null), [])\n\n // Wrapper has no `min-w-[640px]`: it forced horizontal scroll on a 375px viewport.\n // Rules wrap onto multiple lines on mobile; on desktop (`sm:max-w-[720px]`) they fit\n // on one line.\n return (\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n onDragCancel={handleDragCancel}\n >\n <div className=\"flex flex-col gap-3 p-3\">\n {!empty ? (\n <GroupView\n group={value.root}\n level={1}\n fields={fields}\n tree={value}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n ) : null}\n {!empty ? (\n <div className=\"flex items-center gap-3 pt-2 border-t border-border\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 py-0 text-base text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={onClear}\n >\n <X className=\"size-4\" />\n {t('ui.advancedFilter.clearAll', 'Clear all')}\n </Button>\n </div>\n ) : null}\n </div>\n {/* Portal DragOverlay to document.body. Radix DialogContent uses CSS `transform`\n for centering, which becomes the containing block for `position: fixed`\n descendants (dnd-kit's overlay) and shifts the ghost off-screen. Portaling\n restores viewport-relative coordinates. */}\n {typeof document !== 'undefined'\n ? createPortal(\n // dnd-kit's DragOverlay sets its own inline z-index, so a Tailwind\n // utility class would be overridden \u2014 read the DS token via CSS var.\n <DragOverlay dropAnimation={null} style={{ zIndex: 'var(--z-index-modal-elevated, 55)' }}>\n {activeDrag ? (\n <DragGhost node={activeDrag.node} parent={activeDrag.parent} fields={fields} t={t} />\n ) : null}\n </DragOverlay>,\n document.body,\n )\n : null}\n </DndContext>\n )\n}\n\n/** Find a node in the tree along with its parent group. Used by drag handlers. */\nfunction locateInTree(root: FilterGroup, id: string): { node: FilterRule | FilterGroup; parent: FilterGroup } | null {\n for (const child of root.children) {\n if (child.id === id) return { node: child, parent: root }\n if (child.type === 'group') {\n const found = locateInTree(child, id)\n if (found) return found\n }\n }\n return null\n}\n\n// Compact drag preview rendered inside DragOverlay. Cannot reuse RuleRow/GroupView \u2014\n// their popovers and form inputs conflict with the overlay.\nfunction DragGhost({\n node, parent, fields, t,\n}: {\n node: FilterRule | FilterGroup\n parent: FilterGroup\n fields: FilterFieldDef[]\n t: Translator\n}) {\n const isGroup = node.type === 'group'\n const labelParts: string[] = []\n if (isGroup) {\n const ruleCount = countRulesIn(node)\n labelParts.push(t('ui.advancedFilter.dragGhost.group', 'Group'))\n labelParts.push(`(${ruleCount})`)\n } else {\n const def = fields.find((f) => f.key === node.field)\n labelParts.push(def?.label ?? (node.field || t('ui.advancedFilter.dragGhost.unsetField', 'Unset field')))\n labelParts.push(OPERATOR_LABELS[node.operator] ?? node.operator)\n if (typeof node.value === 'string' && node.value) labelParts.push(`\"${node.value}\"`)\n }\n return (\n <div className=\"inline-flex items-center gap-2 rounded-md border border-border bg-popover px-3 py-1.5 text-sm shadow-md cursor-grabbing\">\n <GripVertical className=\"size-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">{parent.combinator === 'and' ? t('ui.advancedFilter.connector.and', 'and') : t('ui.advancedFilter.connector.or', 'or')}</span>\n <span className=\"font-medium\">{labelParts.join(' ')}</span>\n </div>\n )\n}\n\nfunction countRulesIn(group: FilterGroup): number {\n let n = 0\n for (const c of group.children) {\n if (c.type === 'rule') n += 1\n else n += countRulesIn(c)\n }\n return n\n}\n\nfunction GroupView({\n group, level, fields, tree, dispatch, defaultField, errorByRuleId, t,\n}: {\n group: FilterGroup\n level: number\n fields: FilterFieldDef[]\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n errorByRuleId: Map<string, string>\n t: Translator\n}) {\n const isRoot = level === 1\n // Sub-groups render as a card so the nesting boundary is visible; root group stays\n // flush with the popover so it doesn't double-up the dialog's own card chrome.\n const containerClass = isRoot\n ? 'space-y-3'\n : 'space-y-3 rounded-lg border border-border bg-muted/30 p-3'\n\n // Toolbar-first layout (Kendo React Filter pattern): one [And]/[Or] toggle per group\n // is the only combinator UI. Mixing AND with OR requires `+ Add subgroup`. Matches\n // `CompositeFilterDescriptor` semantics (one logic per group).\n return (\n <div className={containerClass}>\n <GroupToolbar\n group={group}\n level={level}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n fields={fields}\n isRoot={isRoot}\n t={t}\n />\n\n <SortableChildrenList\n group={group}\n level={level}\n fields={fields}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n </div>\n )\n}\n\n// Kendo-style segmented `[And] [Or]` toggle. Clicking the unselected side flips\n// the group's combinator. Selected side is brand-violet filled so the affordance\n// is unambiguous.\nfunction AndOrToggle({\n combinator, onChange, t,\n}: {\n combinator: FilterCombinator\n onChange: (c: FilterCombinator) => void\n t: Translator\n}) {\n const andLabel = t('ui.advancedFilter.combinator.and', 'And')\n const orLabel = t('ui.advancedFilter.combinator.or', 'Or')\n const baseBtn = 'h-8 px-3 text-sm font-medium outline-none focus-visible:shadow-focus disabled:cursor-not-allowed'\n const selBtn = 'bg-brand-violet text-white hover:bg-brand-violet/90'\n const unselBtn = 'bg-background text-foreground hover:bg-accent'\n return (\n <div\n role=\"group\"\n aria-label={t('ui.advancedFilter.combinator.label', 'Group combinator')}\n className=\"inline-flex rounded-md border border-input overflow-hidden shrink-0\"\n data-testid=\"advanced-filter-combinator-toggle\"\n data-combinator={combinator}\n >\n <button\n type=\"button\"\n aria-pressed={combinator === 'and'}\n onClick={() => combinator !== 'and' && onChange('and')}\n className={`${baseBtn} ${combinator === 'and' ? selBtn : unselBtn}`}\n >\n {andLabel}\n </button>\n <button\n type=\"button\"\n aria-pressed={combinator === 'or'}\n onClick={() => combinator !== 'or' && onChange('or')}\n className={`${baseBtn} border-l border-input ${combinator === 'or' ? selBtn : unselBtn}`}\n >\n {orLabel}\n </button>\n </div>\n )\n}\n\nfunction SortableChildrenList({\n group, level, fields, tree, dispatch, defaultField, errorByRuleId, t,\n}: {\n group: FilterGroup\n level: number\n fields: FilterFieldDef[]\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n errorByRuleId: Map<string, string>\n t: Translator\n}) {\n // DndContext + handlers live one level up so a drag can move children across\n // groups. Each group owns its own SortableContext to scope sort ordering.\n return (\n <SortableContext items={group.children.map((c) => c.id)} strategy={verticalListSortingStrategy}>\n {group.children.map((child, idx) => (\n <SortableRow\n key={child.id}\n child={child}\n idx={idx}\n parent={group}\n level={level}\n fields={fields}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n ))}\n </SortableContext>\n )\n}\n\nfunction SortableRow({\n child, idx, parent, level, fields, tree, dispatch, defaultField, errorByRuleId, t,\n}: {\n child: FilterRule | FilterGroup\n idx: number\n parent: FilterGroup\n level: number\n fields: FilterFieldDef[]\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n errorByRuleId: Map<string, string>\n t: Translator\n}) {\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id: child.id,\n })\n // Hide the source while dragging (DragOverlay renders the snapshot); keep it in\n // layout via `visibility: hidden` so siblings don't jump. Without this, the flex\n // wrapper applies both `transform` and its own resizing to the source row.\n const style: React.CSSProperties = isDragging\n ? { visibility: 'hidden' as const }\n : { transform: CSS.Transform.toString(transform), transition }\n const dragHandleProps: DragHandleProps = { ...attributes, ...listeners }\n\n return (\n <div ref={setNodeRef} style={style} data-testid=\"filter-rule-row\" data-filter-node-id={child.id}>\n {child.type === 'rule' ? (\n <RuleRow\n rule={child}\n fields={fields}\n dispatch={dispatch}\n errorByRuleId={errorByRuleId}\n dragHandleProps={dragHandleProps}\n t={t}\n />\n ) : (\n <div className=\"flex items-start gap-2\">\n {/* Raw <button> required: dnd-kit spreads `attributes`+`listeners` onto the\n activator; <Button>'s own props/ref handling breaks the keyboard sensor. */}\n <button\n type=\"button\"\n {...dragHandleProps}\n data-testid=\"filter-drag-handle\"\n className=\"mt-1 text-muted-foreground/60 hover:text-muted-foreground cursor-grab outline-none focus-visible:shadow-focus rounded-sm\"\n aria-label={t('ui.advancedFilter.dragHandle', 'Drag to reorder')}\n >\n <GripVertical className=\"size-4\" />\n </button>\n <div className=\"flex-1\">\n <GroupView\n group={child}\n level={level + 1}\n fields={fields}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n </div>\n </div>\n )}\n </div>\n )\n}\n\nfunction RuleRow({\n rule, fields, dispatch, errorByRuleId, dragHandleProps, t,\n}: {\n rule: FilterRule\n fields: FilterFieldDef[]\n dispatch: (a: TreeAction) => void\n errorByRuleId: Map<string, string>\n dragHandleProps: DragHandleProps\n t: Translator\n}) {\n const fieldType = getFieldType(fields, rule.field)\n const operators = OPERATORS_BY_FIELD_TYPE[fieldType] ?? OPERATORS_BY_FIELD_TYPE.text\n const valueless = isValuelessOperator(rule.operator)\n const errorMsg = errorByRuleId.get(rule.id)\n const fieldDef = fields.find((f) => f.key === rule.field)\n const [pickerOpen, setPickerOpen] = React.useState(false)\n const fieldBtnRef = React.useRef<HTMLButtonElement>(null)\n\n return (\n <div className=\"flex flex-col gap-1\" data-testid=\"filter-rule\" data-filter-rule-id={rule.id}>\n <div className=\"flex flex-wrap items-start gap-2\">\n {/* Raw <button> required for dnd-kit activator: see SortableRow drag-handle comment. */}\n <button\n type=\"button\"\n {...dragHandleProps}\n data-testid=\"filter-drag-handle\"\n className=\"mt-1 text-muted-foreground/60 hover:text-muted-foreground cursor-grab outline-none focus-visible:shadow-focus rounded-sm\"\n aria-label={t('ui.advancedFilter.dragHandle', 'Drag to reorder')}\n >\n <GripVertical className=\"size-4\" />\n </button>\n <Button\n ref={fieldBtnRef}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setPickerOpen(true)}\n className=\"min-w-[140px] justify-start\"\n >\n {fieldDef?.label ?? t('ui.advancedFilter.selectField', 'Select field')}\n </Button>\n <FilterFieldPicker\n fields={fields}\n open={pickerOpen}\n onOpenChange={setPickerOpen}\n onSelect={(f) => {\n const nextType = getFieldType(fields, f.key)\n dispatch({\n type: 'updateRule',\n ruleId: rule.id,\n updates: { field: f.key, operator: getDefaultOperator(nextType), value: '' },\n })\n }}\n triggerRef={fieldBtnRef as React.RefObject<HTMLElement | null>}\n />\n <Select\n value={rule.operator}\n onValueChange={(next) => {\n // Preserve the value when the operator shape doesn't change; reset\n // only when shape changes (e.g. `is` \u2192 `between` clears scalar to pair).\n const nextOp = next as FilterOperator\n const sameShape = getOperatorShape(rule.operator) === getOperatorShape(nextOp)\n const updates: Partial<Pick<FilterRule, 'operator' | 'value'>> = sameShape\n ? { operator: nextOp }\n : { operator: nextOp, value: getOperatorShape(nextOp) === 'multi' || getOperatorShape(nextOp) === 'range' ? [] : '' }\n dispatch({ type: 'updateRule', ruleId: rule.id, updates })\n }}\n >\n {/* `w-auto` overrides SelectTrigger's default `w-full` so it sits\n inline with the field/value inputs instead of breaking onto a new row. */}\n <SelectTrigger\n size=\"sm\"\n className=\"w-auto min-w-[140px]\"\n aria-label={t('ui.advancedFilter.selectOperator', 'Select operator')}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent data-advanced-filter-portal=\"\">\n {operators.map((op) => (\n <SelectItem key={op} value={op}>\n {t(`ui.advancedFilter.operator.${op}`, OPERATOR_LABELS[op])}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {!valueless ? (\n <ValueInput\n rule={rule}\n fields={fields}\n fieldType={fieldType}\n dispatch={dispatch}\n hasError={!!errorMsg}\n t={t}\n />\n ) : null}\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n type=\"button\"\n onClick={() => dispatch({ type: 'removeNode', nodeId: rule.id })}\n aria-label={t('ui.advancedFilter.removeCondition', 'Remove condition')}\n title={t('ui.advancedFilter.removeCondition', 'Remove condition')}\n >\n <Trash2 className=\"size-4 text-muted-foreground\" />\n </IconButton>\n </div>\n {errorMsg ? (\n <div className=\"ml-6 pl-12 text-xs text-status-error-text\">{errorMsg}</div>\n ) : null}\n </div>\n )\n}\n\nfunction ValueInput({\n rule, fields, fieldType, dispatch, hasError, t,\n}: {\n rule: FilterRule\n fields: FilterFieldDef[]\n fieldType: FilterFieldType\n dispatch: (a: TreeAction) => void\n hasError: boolean\n t: Translator\n}) {\n const fieldDef = fields.find((f) => f.key === rule.field)\n const value = rule.value\n const errorClass = hasError\n ? 'border-status-error-border ring-1 ring-status-error-border focus-visible:border-status-error-border focus-within:border-status-error-border'\n : ''\n\n if (fieldType === 'select' && fieldDef?.options) {\n // Multi-value operators (`is_any_of`, `is_none_of`, `has_any_of`, etc.)\n // need an array value with multiple selectable options. Radix `Select` is\n // single-select only, so the previous code silently overwrote each\n // selection (and validation kept failing because the value never became an\n // array). Render a checkbox-list popover when the operator is multi-shape.\n if (getOperatorShape(rule.operator) === 'multi') {\n return (\n <MultiValuePicker\n rule={rule}\n options={fieldDef.options}\n dispatch={dispatch}\n errorClass={errorClass}\n t={t}\n />\n )\n }\n return (\n <Select\n value={typeof value === 'string' && value.length ? value : undefined}\n onValueChange={(next) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: next ?? '' } })\n }\n >\n <SelectTrigger\n size=\"sm\"\n className={`w-auto min-w-[160px] ${errorClass}`}\n aria-label={t('ui.advancedFilter.selectValue', 'Select value')}\n >\n <SelectValue placeholder={t('ui.advancedFilter.selectValuePlaceholder', 'Select...')} />\n </SelectTrigger>\n <SelectContent data-advanced-filter-portal=\"\">\n {fieldDef.options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.tone ? (\n <span className=\"inline-flex items-center gap-2\">\n <span className={toneDotClass(opt.tone)} aria-hidden=\"true\" />\n {opt.label}\n </span>\n ) : (\n opt.label\n )}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n }\n\n // `Input` ships `w-full` by default \u2014 use `w-auto` so inputs don't stretch\n // inside the wrapping flex row. `between` needs a `[start, end]` pair, so it\n // gets its own component below.\n if (rule.operator === 'between' && (fieldType === 'number' || fieldType === 'date')) {\n return (\n <BetweenInput\n rule={rule}\n fieldType={fieldType}\n dispatch={dispatch}\n errorClass={errorClass}\n t={t}\n />\n )\n }\n\n if (fieldType === 'date') {\n return (\n <Input\n type=\"date\"\n size=\"sm\"\n className={`w-auto min-w-[160px] ${errorClass}`}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: e.target.value } })\n }\n aria-label={t('ui.advancedFilter.dateValue', 'Date value')}\n />\n )\n }\n\n if (fieldType === 'number') {\n return (\n <Input\n type=\"number\"\n size=\"sm\"\n className={`w-[120px] ${errorClass}`}\n value={typeof value === 'number' ? value : typeof value === 'string' ? value : ''}\n onChange={(e) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: e.target.value } })\n }\n placeholder={t('ui.advancedFilter.numberPlaceholder', 'Value')}\n aria-label={t('ui.advancedFilter.numberValue', 'Number value')}\n />\n )\n }\n\n return (\n <Input\n type=\"text\"\n size=\"sm\"\n className={`w-auto min-w-[160px] ${errorClass}`}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: e.target.value } })\n }\n placeholder={t('ui.advancedFilter.textPlaceholder', 'Value...')}\n aria-label={t('ui.advancedFilter.textValue', 'Text value')}\n />\n )\n}\n\n/**\n * Two inputs (start / end) for the `between` operator on date and number\n * fields. Stores the value as `[start, end]`; either side can be empty while\n * the user types, but validation requires both to be set before apply.\n */\nfunction BetweenInput({\n rule, fieldType, dispatch, errorClass, t,\n}: {\n rule: FilterRule\n fieldType: FilterFieldType\n dispatch: (a: TreeAction) => void\n errorClass: string\n t: Translator\n}) {\n const pair = Array.isArray(rule.value) ? rule.value : []\n const start = typeof pair[0] === 'string' || typeof pair[0] === 'number' ? String(pair[0] ?? '') : ''\n const end = typeof pair[1] === 'string' || typeof pair[1] === 'number' ? String(pair[1] ?? '') : ''\n const update = (which: 'start' | 'end', next: string) => {\n const value = which === 'start' ? [next, end] : [start, next]\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value } })\n }\n const inputType = fieldType === 'date' ? 'date' : 'number'\n const inputWidth = fieldType === 'date' ? 'w-auto min-w-[140px]' : 'w-[110px]'\n return (\n <div className=\"inline-flex items-center gap-1\">\n <Input\n type={inputType}\n size=\"sm\"\n className={`${inputWidth} ${errorClass}`}\n value={start}\n onChange={(e) => update('start', e.target.value)}\n placeholder={fieldType === 'number' ? t('ui.advancedFilter.numberPlaceholder', 'Value') : undefined}\n aria-label={t('ui.advancedFilter.betweenStart', 'Start')}\n />\n <span className=\"text-xs text-muted-foreground\">{t('ui.advancedFilter.and', 'and')}</span>\n <Input\n type={inputType}\n size=\"sm\"\n className={`${inputWidth} ${errorClass}`}\n value={end}\n onChange={(e) => update('end', e.target.value)}\n placeholder={fieldType === 'number' ? t('ui.advancedFilter.numberPlaceholder', 'Value') : undefined}\n aria-label={t('ui.advancedFilter.betweenEnd', 'End')}\n />\n </div>\n )\n}\n\n/**\n * Trigger + popover with a checkbox list. Used when a select-typed rule has a\n * multi-shape operator (`is_any_of`, `has_all_of`, etc.). The trigger displays\n * a \"{first} +N\" summary; the popover lets the user toggle each option.\n */\nfunction MultiValuePicker({\n rule, options, dispatch, errorClass, t,\n}: {\n rule: FilterRule\n options: NonNullable<FilterFieldDef['options']>\n dispatch: (a: TreeAction) => void\n errorClass: string\n t: Translator\n}) {\n const selected: string[] = Array.isArray(rule.value)\n ? rule.value.map((v) => String(v)).filter((v) => v.length > 0)\n : []\n const selectedLabel = (() => {\n if (selected.length === 0) return t('ui.advancedFilter.selectValuePlaceholder', 'Select...')\n const firstOpt = options.find((o) => o.value === selected[0])\n const head = firstOpt?.label ?? selected[0]\n const more = selected.length - 1\n return more > 0 ? `${head} +${more}` : head\n })()\n const toggle = (val: string) => {\n const next = selected.includes(val) ? selected.filter((v) => v !== val) : [...selected, val]\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: next } })\n }\n return (\n <Popover>\n <PopoverTrigger asChild>\n {/* Visually mirrors `SelectTrigger size=\"sm\"` (h-8, rounded-md, border)\n so the operator + value pair stays visually consistent. */}\n <button\n type=\"button\"\n className={`inline-flex h-8 items-center justify-between gap-2 rounded-md border border-input bg-background px-2.5 text-xs shadow-xs transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:shadow-focus focus-visible:border-foreground w-auto min-w-[160px] ${errorClass}`}\n aria-label={t('ui.advancedFilter.selectValue', 'Select value')}\n data-testid=\"multi-value-trigger\"\n >\n <span className={selected.length === 0 ? 'text-muted-foreground' : ''}>{selectedLabel}</span>\n <ChevronDown className=\"size-3 text-muted-foreground\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-64 p-1\" align=\"start\" data-advanced-filter-portal=\"\">\n <div className=\"max-h-64 overflow-y-auto\" role=\"listbox\" aria-multiselectable=\"true\">\n {options.map((opt) => {\n const isOn = selected.includes(opt.value)\n return (\n // Raw <button>: needs `role=\"option\"` + `aria-selected`; <Button> would override role.\n <button\n key={opt.value}\n type=\"button\"\n role=\"option\"\n aria-selected={isOn}\n onClick={() => toggle(opt.value)}\n className=\"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm hover:bg-accent focus-visible:bg-accent focus-visible:outline-none\"\n >\n <Checkbox checked={isOn} aria-hidden=\"true\" tabIndex={-1} className=\"pointer-events-none\" />\n {opt.tone ? (\n <span className=\"inline-flex items-center gap-2\">\n <span className={toneDotClass(opt.tone)} aria-hidden=\"true\" />\n {opt.label}\n </span>\n ) : (\n <span>{opt.label}</span>\n )}\n </button>\n )\n })}\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nfunction toneDotClass(tone: NonNullable<FilterFieldDef['options']>[number]['tone']): string {\n // Solid filled dot using the saturated `*-icon` token (matches Tag's `dotColorMap`).\n const base = 'inline-block size-2 rounded-full shrink-0'\n switch (tone) {\n case 'success': return `${base} bg-status-success-icon`\n case 'error': return `${base} bg-status-error-icon`\n case 'warning': return `${base} bg-status-warning-icon`\n case 'info': return `${base} bg-status-info-icon`\n case 'neutral': return `${base} bg-status-neutral-icon`\n case 'pink': return `${base} bg-status-pink-icon`\n case 'brand': return `${base} bg-brand-violet`\n default: return `${base} bg-muted-foreground/40`\n }\n}\n\n// Single Kendo-style toolbar per group: combinator toggle, `+ Add condition`,\n// `+ Add subgroup` / `+ Add group`, plus a trailing delete for non-root groups.\nfunction GroupToolbar({\n group, level, tree, dispatch, defaultField, fields, isRoot, t,\n}: {\n group: FilterGroup\n level: number\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n fields: FilterFieldDef[]\n isRoot: boolean\n t: Translator\n}) {\n const [pickerOpen, setPickerOpen] = React.useState(false)\n const addBtnRef = React.useRef<HTMLButtonElement>(null)\n const addRuleEnabled = canAddRule(tree, group.id)\n const addGroupEnabled = canAddGroup(tree, group.id)\n\n const widthCapHit = group.children.length >= TREE_LIMITS.maxChildrenPerGroup\n const depthCapHit = level >= TREE_LIMITS.maxGroupLevel\n\n const addRuleTitle = !addRuleEnabled\n ? widthCapHit\n ? t('ui.advancedFilter.limitWidthReached', 'Maximum {max} conditions per group', {\n max: TREE_LIMITS.maxChildrenPerGroup,\n })\n : t('ui.advancedFilter.limitTotalReached', 'Maximum {max} conditions reached', {\n max: TREE_LIMITS.maxTotalRules,\n })\n : undefined\n\n const addGroupTitle = !addGroupEnabled\n ? depthCapHit\n ? t('ui.advancedFilter.limitDepthReached', 'Maximum nesting depth reached')\n : widthCapHit\n ? t('ui.advancedFilter.limitWidthReached', 'Maximum {max} conditions per group', {\n max: TREE_LIMITS.maxChildrenPerGroup,\n })\n : t('ui.advancedFilter.limitTotalReached', 'Maximum {max} conditions reached', {\n max: TREE_LIMITS.maxTotalRules,\n })\n : undefined\n\n const addGroupLabel = level === 1\n ? t('ui.advancedFilter.addGroup', '+ Add group')\n : t('ui.advancedFilter.addSubgroup', '+ Add subgroup')\n\n // `text-brand-violet` overrides Button's default disabled token, so we have to\n // re-state muted disabled colors here \u2014 otherwise disabled CTAs still look clickable.\n const ctaClass = 'text-brand-violet hover:text-brand-violet/80 hover:bg-transparent px-0 disabled:text-muted-foreground/50 disabled:hover:text-muted-foreground/50'\n\n return (\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <AndOrToggle\n combinator={group.combinator}\n onChange={(c) => dispatch({ type: 'updateGroupCombinator', groupId: group.id, combinator: c })}\n t={t}\n />\n <Button\n ref={addBtnRef}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={!addRuleEnabled}\n title={addRuleTitle}\n onClick={() => setPickerOpen(true)}\n className={ctaClass}\n >\n <Plus className=\"size-4\" />\n {t('ui.advancedFilter.addCondition', '+ Add condition')}\n </Button>\n <FilterFieldPicker\n fields={fields}\n open={pickerOpen}\n onOpenChange={setPickerOpen}\n onSelect={(f) =>\n dispatch({\n type: 'addRule',\n groupId: group.id,\n defaultField: f.key,\n defaultOperator: getDefaultOperator(f.type),\n })\n }\n triggerRef={addBtnRef as React.RefObject<HTMLElement | null>}\n />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={!addGroupEnabled}\n title={addGroupTitle}\n onClick={() =>\n dispatch({ type: 'addGroup', groupId: group.id, defaultField })\n }\n className={ctaClass}\n >\n <Plus className=\"size-4\" />\n {addGroupLabel}\n </Button>\n {!isRoot ? (\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n type=\"button\"\n onClick={() => dispatch({ type: 'removeNode', nodeId: group.id })}\n aria-label={t('ui.advancedFilter.deleteGroup', 'Delete group')}\n title={t('ui.advancedFilter.deleteGroup', 'Delete group')}\n className=\"ml-auto\"\n >\n <X className=\"size-4 text-muted-foreground\" />\n </IconButton>\n ) : null}\n </div>\n {!addGroupEnabled && depthCapHit ? (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"filter-depth-limit-hint\">\n {t('ui.advancedFilter.limitDepthHint', 'You\\'ve reached the maximum nesting depth ({max} levels). Switch this group\\'s combinator with the toggle above instead of nesting deeper.', {\n max: TREE_LIMITS.maxGroupLevel,\n })}\n </p>\n ) : null}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,354 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Popover, PopoverAnchor, PopoverContent } from "../../primitives/popover.js";
5
+ import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
6
+ import { Alert, AlertDescription } from "../../primitives/alert.js";
7
+ import { Button } from "../../primitives/button.js";
8
+ import { Input } from "../../primitives/input.js";
9
+ import {
10
+ Dialog,
11
+ DialogContent,
12
+ DialogDescription,
13
+ DialogFooter,
14
+ DialogHeader,
15
+ DialogTitle
16
+ } from "../../primitives/dialog.js";
17
+ import { Filter, Save, Trash2 } from "lucide-react";
18
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
19
+ import {
20
+ deserializeTreeFromPersist,
21
+ isPersistedFilterTree,
22
+ serializeTreeForPersist
23
+ } from "@open-mercato/shared/lib/query/advanced-filter-tree";
24
+ import { getDefaultOperator } from "@open-mercato/shared/lib/query/advanced-filter";
25
+ import { AdvancedFilterBuilder } from "./AdvancedFilterBuilder.js";
26
+ import { FilterEmptyState } from "./FilterEmptyState.js";
27
+ import { QuickFilters } from "./QuickFilters.js";
28
+ import { FilterFieldPicker } from "./FilterFieldPicker.js";
29
+ import { treeReducer } from "./treeReducer.js";
30
+ import { flash } from "../FlashMessages.js";
31
+ const SAVED_FILTER_STORAGE_PREFIX = "open-mercato:advanced-filters:";
32
+ const MAX_SAVED_FILTERS = 20;
33
+ const CURRENT_SAVED_FILTERS_VERSION = 1;
34
+ function makeSavedFilterStorageKey(key) {
35
+ const trimmed = key?.trim();
36
+ return trimmed ? `${SAVED_FILTER_STORAGE_PREFIX}${trimmed}` : null;
37
+ }
38
+ function isSavedAdvancedFilter(value) {
39
+ if (!value || typeof value !== "object") return false;
40
+ const record = value;
41
+ return typeof record.id === "string" && typeof record.name === "string" && typeof record.createdAt === "string" && typeof record.updatedAt === "string" && isPersistedFilterTree(record.tree);
42
+ }
43
+ function readSavedFilters(storageKey) {
44
+ if (typeof window === "undefined") return [];
45
+ try {
46
+ const raw = window.localStorage.getItem(storageKey);
47
+ if (!raw) return [];
48
+ const parsed = JSON.parse(raw);
49
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
50
+ const envelope = parsed;
51
+ if (envelope.v === 1 && Array.isArray(envelope.filters)) {
52
+ return envelope.filters.filter(isSavedAdvancedFilter);
53
+ }
54
+ }
55
+ if (Array.isArray(parsed)) {
56
+ return parsed.filter(isSavedAdvancedFilter);
57
+ }
58
+ return [];
59
+ } catch {
60
+ return [];
61
+ }
62
+ }
63
+ function writeSavedFilters(storageKey, filters) {
64
+ if (typeof window === "undefined") return;
65
+ const envelope = { v: CURRENT_SAVED_FILTERS_VERSION, filters };
66
+ window.localStorage.setItem(storageKey, JSON.stringify(envelope));
67
+ }
68
+ function SaveFilterDialog({
69
+ open,
70
+ onOpenChange,
71
+ onSave,
72
+ t
73
+ }) {
74
+ const [name, setName] = React.useState("");
75
+ const [saving, setSaving] = React.useState(false);
76
+ const [error, setError] = React.useState(null);
77
+ const trimmed = name.trim();
78
+ React.useEffect(() => {
79
+ if (!open) {
80
+ setName("");
81
+ setSaving(false);
82
+ setError(null);
83
+ }
84
+ }, [open]);
85
+ const submit = React.useCallback(async () => {
86
+ if (!trimmed || saving) return;
87
+ setSaving(true);
88
+ setError(null);
89
+ try {
90
+ await onSave(trimmed);
91
+ onOpenChange(false);
92
+ } catch (err) {
93
+ setError(err instanceof Error ? err.message : t("ui.advancedFilter.saveFilter.error", "Failed to save filter"));
94
+ } finally {
95
+ setSaving(false);
96
+ }
97
+ }, [onSave, onOpenChange, saving, t, trimmed]);
98
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsx(DialogContent, { elevated: true, className: "sm:max-w-md", "data-advanced-filter-portal": true, children: /* @__PURE__ */ jsxs(
99
+ "form",
100
+ {
101
+ className: "contents",
102
+ onSubmit: (event) => {
103
+ event.preventDefault();
104
+ void submit();
105
+ },
106
+ children: [
107
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
108
+ /* @__PURE__ */ jsx(DialogTitle, { children: t("ui.advancedFilter.saveFilter.title", "Save filter") }),
109
+ /* @__PURE__ */ jsx(DialogDescription, { children: t("ui.advancedFilter.saveFilter.description", "Name this filter so you can reuse the current conditions later.") })
110
+ ] }),
111
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
112
+ /* @__PURE__ */ jsx(
113
+ Input,
114
+ {
115
+ value: name,
116
+ onChange: (event) => setName(event.target.value),
117
+ placeholder: t("ui.advancedFilter.saveFilter.placeholder", "Filter name"),
118
+ "aria-label": t("ui.advancedFilter.saveFilter.nameAriaLabel", "Filter name"),
119
+ autoFocus: true
120
+ }
121
+ ),
122
+ error ? /* @__PURE__ */ jsx("p", { className: "text-sm text-status-error-text", children: error }) : null
123
+ ] }),
124
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
125
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: t("common.cancel", "Cancel") }),
126
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: !trimmed || saving, children: saving ? t("ui.advancedFilter.saveFilter.saving", "Saving...") : t("ui.advancedFilter.saveFilter.submit", "Save filter") })
127
+ ] })
128
+ ]
129
+ }
130
+ ) }) });
131
+ }
132
+ function SavedFiltersSection({
133
+ filters,
134
+ canSaveCurrent,
135
+ onSaveClick,
136
+ onApply,
137
+ onDelete,
138
+ t
139
+ }) {
140
+ if (!canSaveCurrent && filters.length === 0) return null;
141
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-testid": "advanced-filter-saved-filters", children: [
142
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
143
+ /* @__PURE__ */ jsx("div", { className: "text-overline font-medium uppercase tracking-widest text-muted-foreground", children: t("ui.advancedFilter.savedFilters.heading", "Saved filters") }),
144
+ canSaveCurrent ? /* @__PURE__ */ jsxs(
145
+ Button,
146
+ {
147
+ "data-testid": "advanced-filter-save-trigger",
148
+ type: "button",
149
+ variant: "outline",
150
+ size: "sm",
151
+ className: "h-8 rounded-full gap-1.5",
152
+ onClick: onSaveClick,
153
+ children: [
154
+ /* @__PURE__ */ jsx(Save, { className: "size-3.5 text-muted-foreground" }),
155
+ t("ui.advancedFilter.saveFilter.button", "Save filter")
156
+ ]
157
+ }
158
+ ) : null
159
+ ] }),
160
+ filters.length ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: filters.map((filter) => /* @__PURE__ */ jsxs("div", { className: "inline-flex max-w-full items-center overflow-hidden rounded-full border border-border bg-background", children: [
161
+ /* @__PURE__ */ jsxs(
162
+ Button,
163
+ {
164
+ type: "button",
165
+ variant: "ghost",
166
+ size: "sm",
167
+ className: "h-8 min-w-0 rounded-r-none px-3 gap-1.5",
168
+ title: filter.name,
169
+ onClick: () => onApply(filter),
170
+ children: [
171
+ /* @__PURE__ */ jsx(Filter, { className: "size-3.5 shrink-0 text-muted-foreground" }),
172
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: filter.name })
173
+ ]
174
+ }
175
+ ),
176
+ /* @__PURE__ */ jsx(
177
+ Button,
178
+ {
179
+ type: "button",
180
+ variant: "ghost",
181
+ size: "sm",
182
+ className: "h-8 w-8 rounded-l-none px-0 text-muted-foreground hover:text-status-error-text",
183
+ "aria-label": `${t("ui.advancedFilter.savedFilters.delete", "Delete saved filter")}: ${filter.name}`,
184
+ onClick: () => onDelete(filter.id),
185
+ children: /* @__PURE__ */ jsx(Trash2, { className: "size-3.5" })
186
+ }
187
+ )
188
+ ] }, filter.id)) }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("ui.advancedFilter.savedFilters.empty", "No saved filters yet") })
189
+ ] });
190
+ }
191
+ function AdvancedFilterPanel(props) {
192
+ const t = useT();
193
+ const empty = props.value.root.children.length === 0;
194
+ const [pickerOpen, setPickerOpen] = React.useState(false);
195
+ const [saveDialogOpen, setSaveDialogOpen] = React.useState(false);
196
+ const [savedFilters, setSavedFilters] = React.useState([]);
197
+ const addConditionRef = React.useRef(null);
198
+ const savedFilterStorageKey = React.useMemo(
199
+ () => makeSavedFilterStorageKey(props.savedFilterStorageKey),
200
+ [props.savedFilterStorageKey]
201
+ );
202
+ const wasOpenRef = React.useRef(props.open);
203
+ React.useEffect(() => {
204
+ if (wasOpenRef.current && !props.open && props.onFlush) props.onFlush();
205
+ wasOpenRef.current = props.open;
206
+ }, [props.open, props.onFlush]);
207
+ const handlePresetApply = (tree) => {
208
+ props.onChange(tree);
209
+ props.onOpenChange(false);
210
+ };
211
+ React.useEffect(() => {
212
+ if (!savedFilterStorageKey) {
213
+ setSavedFilters([]);
214
+ return;
215
+ }
216
+ if (props.open) {
217
+ setSavedFilters(readSavedFilters(savedFilterStorageKey));
218
+ }
219
+ }, [props.open, savedFilterStorageKey]);
220
+ const persistSavedFilters = React.useCallback((next) => {
221
+ if (!savedFilterStorageKey) return;
222
+ setSavedFilters(next);
223
+ writeSavedFilters(savedFilterStorageKey, next);
224
+ }, [savedFilterStorageKey]);
225
+ const handleSaveFilter = React.useCallback(async (name) => {
226
+ if (!savedFilterStorageKey) return;
227
+ const now = (/* @__PURE__ */ new Date()).toISOString();
228
+ const normalizedName = name.trim();
229
+ const existing = savedFilters.find((item) => item.name.trim().toLowerCase() === normalizedName.toLowerCase());
230
+ const record = {
231
+ id: existing?.id ?? crypto.randomUUID(),
232
+ name: normalizedName,
233
+ tree: serializeTreeForPersist(props.value),
234
+ createdAt: existing?.createdAt ?? now,
235
+ updatedAt: now
236
+ };
237
+ const next = [
238
+ record,
239
+ ...savedFilters.filter((item) => item.id !== record.id)
240
+ ].slice(0, MAX_SAVED_FILTERS);
241
+ persistSavedFilters(next);
242
+ flash(t("ui.advancedFilter.saveFilter.success", "Filter saved"), "success");
243
+ }, [persistSavedFilters, props.value, savedFilterStorageKey, savedFilters, t]);
244
+ const handleApplySavedFilter = React.useCallback((filter) => {
245
+ const tree = deserializeTreeFromPersist(filter.tree);
246
+ if (!tree) return;
247
+ props.onChange(tree);
248
+ props.onOpenChange(false);
249
+ }, [props.onChange, props.onOpenChange]);
250
+ const handleDeleteSavedFilter = React.useCallback((filterId) => {
251
+ persistSavedFilters(savedFilters.filter((item) => item.id !== filterId));
252
+ }, [persistSavedFilters, savedFilters]);
253
+ const shouldShowSavedFilters = Boolean(savedFilterStorageKey && (!empty || savedFilters.length > 0));
254
+ const savedFiltersSection = shouldShowSavedFilters ? /* @__PURE__ */ jsx(
255
+ SavedFiltersSection,
256
+ {
257
+ filters: savedFilters,
258
+ canSaveCurrent: !empty,
259
+ onSaveClick: () => setSaveDialogOpen(true),
260
+ onApply: handleApplySavedFilter,
261
+ onDelete: handleDeleteSavedFilter,
262
+ t
263
+ }
264
+ ) : null;
265
+ const emptyStateShortcuts = props.presets.length || savedFiltersSection ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
266
+ props.presets.length ? /* @__PURE__ */ jsx(QuickFilters, { presets: props.presets, userId: props.userId, onApply: handlePresetApply }) : null,
267
+ savedFiltersSection
268
+ ] }) : null;
269
+ const builderErrors = React.useMemo(
270
+ () => props.pendingErrors.map((e) => ({ ruleId: e.ruleId, messageKey: e.messageKey, message: e.message })),
271
+ [props.pendingErrors]
272
+ );
273
+ const ignoreAdvancedFilterPortalInteractions = React.useCallback((event) => {
274
+ const target = event.target;
275
+ if (!(target instanceof Element)) return;
276
+ if (target.closest("[data-advanced-filter-portal]")) {
277
+ event.preventDefault();
278
+ }
279
+ }, []);
280
+ return /* @__PURE__ */ jsxs(Popover, { open: props.open, onOpenChange: props.onOpenChange, children: [
281
+ props.triggerRef ? /* @__PURE__ */ jsx(PopoverAnchor, { virtualRef: props.triggerRef }) : null,
282
+ /* @__PURE__ */ jsxs(
283
+ PopoverContent,
284
+ {
285
+ className: "w-[min(calc(100vw-2rem),780px)] max-h-[min(calc(100vh-6rem),720px)] overflow-y-auto p-0 gap-0",
286
+ align: "end",
287
+ sideOffset: 8,
288
+ "data-testid": "advanced-filter-panel",
289
+ onPointerDownOutside: ignoreAdvancedFilterPortalInteractions,
290
+ onFocusOutside: ignoreAdvancedFilterPortalInteractions,
291
+ onInteractOutside: ignoreAdvancedFilterPortalInteractions,
292
+ children: [
293
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: /* @__PURE__ */ jsx("h2", { children: t("ui.advancedFilter.dialog.title", "Advanced filter") }) }),
294
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
295
+ empty ? /* @__PURE__ */ jsx(
296
+ FilterEmptyState,
297
+ {
298
+ onAddCondition: () => setPickerOpen(true),
299
+ addConditionRef,
300
+ aiSlot: props.aiSlot,
301
+ quickFilters: emptyStateShortcuts
302
+ }
303
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
304
+ /* @__PURE__ */ jsx(
305
+ AdvancedFilterBuilder,
306
+ {
307
+ fields: props.fields,
308
+ value: props.value,
309
+ onChange: props.onChange,
310
+ onApply: props.onApply,
311
+ onClear: props.onClear,
312
+ pendingErrors: builderErrors
313
+ }
314
+ ),
315
+ savedFiltersSection ? /* @__PURE__ */ jsx("div", { className: "border-t border-border p-3", children: savedFiltersSection }) : null
316
+ ] }),
317
+ props.pendingErrors.length > 0 ? /* @__PURE__ */ jsx("div", { className: "p-3 border-t border-border", "data-testid": "filter-validation-banner", children: /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: props.pendingErrors.length === 1 ? t("ui.advancedFilter.banner.incomplete", "{count} filter is incomplete \u2014 filters won't apply until all values are picked.", { count: 1 }) : t("ui.advancedFilter.banner.incompletePlural", "{count} filters are incomplete \u2014 filters won't apply until all values are picked.", { count: props.pendingErrors.length }) }) }) }) : null
318
+ ] })
319
+ ]
320
+ }
321
+ ),
322
+ /* @__PURE__ */ jsx(
323
+ FilterFieldPicker,
324
+ {
325
+ fields: props.fields,
326
+ open: pickerOpen,
327
+ onOpenChange: setPickerOpen,
328
+ onSelect: (field) => {
329
+ const next = treeReducer(props.value, {
330
+ type: "addRule",
331
+ groupId: props.value.root.id,
332
+ defaultField: field.key,
333
+ defaultOperator: getDefaultOperator(field.type)
334
+ });
335
+ props.onChange(next);
336
+ },
337
+ triggerRef: addConditionRef
338
+ }
339
+ ),
340
+ /* @__PURE__ */ jsx(
341
+ SaveFilterDialog,
342
+ {
343
+ open: saveDialogOpen,
344
+ onOpenChange: setSaveDialogOpen,
345
+ onSave: handleSaveFilter,
346
+ t
347
+ }
348
+ )
349
+ ] });
350
+ }
351
+ export {
352
+ AdvancedFilterPanel
353
+ };
354
+ //# sourceMappingURL=AdvancedFilterPanel.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/backend/filters/AdvancedFilterPanel.tsx"],
4
+ "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { Popover, PopoverAnchor, PopoverContent } from '../../primitives/popover'\nimport { VisuallyHidden } from '@radix-ui/react-visually-hidden'\nimport { Alert, AlertDescription } from '../../primitives/alert'\nimport { Button } from '../../primitives/button'\nimport { Input } from '../../primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../../primitives/dialog'\nimport { Filter, Save, Trash2 } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n deserializeTreeFromPersist,\n isPersistedFilterTree,\n serializeTreeForPersist,\n type AdvancedFilterTree,\n type PersistedFilterTree,\n} from '@open-mercato/shared/lib/query/advanced-filter-tree'\nimport type { FilterFieldDef } from '@open-mercato/shared/lib/query/advanced-filter'\nimport { getDefaultOperator } from '@open-mercato/shared/lib/query/advanced-filter'\nimport type { ValidationError } from './filterValidation'\nimport { AdvancedFilterBuilder } from './AdvancedFilterBuilder'\nimport { FilterEmptyState } from './FilterEmptyState'\nimport { QuickFilters, type FilterPreset } from './QuickFilters'\nimport { FilterFieldPicker } from './FilterFieldPicker'\nimport { treeReducer } from './treeReducer'\nimport { flash } from '../FlashMessages'\n\nexport type AdvancedFilterPanelProps = {\n fields: FilterFieldDef[]\n value: AdvancedFilterTree\n onChange: (t: AdvancedFilterTree) => void\n onApply: () => void\n onClear: () => void\n onFlush?: () => void\n pendingErrors: ValidationError[]\n userId: string\n presets: FilterPreset[]\n open: boolean\n onOpenChange: (open: boolean) => void\n triggerRef?: React.RefObject<HTMLElement | null>\n aiSlot?: React.ReactNode\n savedFilterStorageKey?: string\n}\n\ntype SavedAdvancedFilter = {\n id: string\n name: string\n tree: PersistedFilterTree\n createdAt: string\n updatedAt: string\n}\n\n/**\n * Versioned envelope for saved-filter records in localStorage. The `v` discriminator\n * lets future schema changes (rename fields, retention policy, sharing flags) migrate\n * existing records instead of silently dropping them. Bump `v` when adding a breaking\n * change and add a read-old migration branch in `readSavedFilters`.\n */\ntype SavedFiltersEnvelopeV1 = {\n v: 1\n filters: SavedAdvancedFilter[]\n}\n\nconst SAVED_FILTER_STORAGE_PREFIX = 'open-mercato:advanced-filters:'\nconst MAX_SAVED_FILTERS = 20\nconst CURRENT_SAVED_FILTERS_VERSION = 1 as const\n\nfunction makeSavedFilterStorageKey(key?: string): string | null {\n const trimmed = key?.trim()\n return trimmed ? `${SAVED_FILTER_STORAGE_PREFIX}${trimmed}` : null\n}\n\nfunction isSavedAdvancedFilter(value: unknown): value is SavedAdvancedFilter {\n if (!value || typeof value !== 'object') return false\n const record = value as Record<string, unknown>\n return (\n typeof record.id === 'string'\n && typeof record.name === 'string'\n && typeof record.createdAt === 'string'\n && typeof record.updatedAt === 'string'\n && isPersistedFilterTree(record.tree)\n )\n}\n\nfunction readSavedFilters(storageKey: string): SavedAdvancedFilter[] {\n if (typeof window === 'undefined') return []\n try {\n const raw = window.localStorage.getItem(storageKey)\n if (!raw) return []\n const parsed = JSON.parse(raw)\n // v1 envelope (current shape).\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n const envelope = parsed as { v?: unknown; filters?: unknown }\n if (envelope.v === 1 && Array.isArray(envelope.filters)) {\n return envelope.filters.filter(isSavedAdvancedFilter)\n }\n }\n // Pre-envelope shape (bare array) \u2014 migrate forward on next write.\n if (Array.isArray(parsed)) {\n return parsed.filter(isSavedAdvancedFilter)\n }\n return []\n } catch {\n return []\n }\n}\n\nfunction writeSavedFilters(storageKey: string, filters: SavedAdvancedFilter[]) {\n if (typeof window === 'undefined') return\n const envelope: SavedFiltersEnvelopeV1 = { v: CURRENT_SAVED_FILTERS_VERSION, filters }\n window.localStorage.setItem(storageKey, JSON.stringify(envelope))\n}\n\nfunction SaveFilterDialog({\n open,\n onOpenChange,\n onSave,\n t,\n}: {\n open: boolean\n onOpenChange: (open: boolean) => void\n onSave: (name: string) => Promise<void>\n t: (key: string, fallback: string) => string\n}) {\n const [name, setName] = React.useState('')\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const trimmed = name.trim()\n\n React.useEffect(() => {\n if (!open) {\n setName('')\n setSaving(false)\n setError(null)\n }\n }, [open])\n\n const submit = React.useCallback(async () => {\n if (!trimmed || saving) return\n setSaving(true)\n setError(null)\n try {\n await onSave(trimmed)\n onOpenChange(false)\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : t('ui.advancedFilter.saveFilter.error', 'Failed to save filter'))\n } finally {\n setSaving(false)\n }\n }, [onSave, onOpenChange, saving, t, trimmed])\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n {/* `elevated` lifts this dialog above the popover (z-popover=45) \u2014 at default\n z-modal=40 the dialog is occluded by the popover that opens it. */}\n <DialogContent elevated className=\"sm:max-w-md\" data-advanced-filter-portal>\n <form\n className=\"contents\"\n onSubmit={(event) => {\n event.preventDefault()\n void submit()\n }}\n >\n <DialogHeader>\n <DialogTitle>{t('ui.advancedFilter.saveFilter.title', 'Save filter')}</DialogTitle>\n <DialogDescription>\n {t('ui.advancedFilter.saveFilter.description', 'Name this filter so you can reuse the current conditions later.')}\n </DialogDescription>\n </DialogHeader>\n <div className=\"space-y-2\">\n <Input\n value={name}\n onChange={(event) => setName(event.target.value)}\n placeholder={t('ui.advancedFilter.saveFilter.placeholder', 'Filter name')}\n aria-label={t('ui.advancedFilter.saveFilter.nameAriaLabel', 'Filter name')}\n autoFocus\n />\n {error ? <p className=\"text-sm text-status-error-text\">{error}</p> : null}\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n {t('common.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={!trimmed || saving}>\n {saving ? t('ui.advancedFilter.saveFilter.saving', 'Saving...') : t('ui.advancedFilter.saveFilter.submit', 'Save filter')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n\nfunction SavedFiltersSection({\n filters,\n canSaveCurrent,\n onSaveClick,\n onApply,\n onDelete,\n t,\n}: {\n filters: SavedAdvancedFilter[]\n canSaveCurrent: boolean\n onSaveClick: () => void\n onApply: (filter: SavedAdvancedFilter) => void\n onDelete: (filterId: string) => void\n t: (key: string, fallback: string) => string\n}) {\n if (!canSaveCurrent && filters.length === 0) return null\n return (\n <div className=\"flex flex-col gap-2\" data-testid=\"advanced-filter-saved-filters\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"text-overline font-medium uppercase tracking-widest text-muted-foreground\">\n {t('ui.advancedFilter.savedFilters.heading', 'Saved filters')}\n </div>\n {canSaveCurrent ? (\n <Button\n data-testid=\"advanced-filter-save-trigger\"\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 rounded-full gap-1.5\"\n onClick={onSaveClick}\n >\n <Save className=\"size-3.5 text-muted-foreground\" />\n {t('ui.advancedFilter.saveFilter.button', 'Save filter')}\n </Button>\n ) : null}\n </div>\n {filters.length ? (\n <div className=\"flex flex-wrap gap-2\">\n {filters.map((filter) => (\n <div key={filter.id} className=\"inline-flex max-w-full items-center overflow-hidden rounded-full border border-border bg-background\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 min-w-0 rounded-r-none px-3 gap-1.5\"\n title={filter.name}\n onClick={() => onApply(filter)}\n >\n <Filter className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{filter.name}</span>\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 rounded-l-none px-0 text-muted-foreground hover:text-status-error-text\"\n aria-label={`${t('ui.advancedFilter.savedFilters.delete', 'Delete saved filter')}: ${filter.name}`}\n onClick={() => onDelete(filter.id)}\n >\n <Trash2 className=\"size-3.5\" />\n </Button>\n </div>\n ))}\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t('ui.advancedFilter.savedFilters.empty', 'No saved filters yet')}\n </p>\n )}\n </div>\n )\n}\n\nexport function AdvancedFilterPanel(props: AdvancedFilterPanelProps) {\n const t = useT()\n const empty = props.value.root.children.length === 0\n const [pickerOpen, setPickerOpen] = React.useState(false)\n const [saveDialogOpen, setSaveDialogOpen] = React.useState(false)\n const [savedFilters, setSavedFilters] = React.useState<SavedAdvancedFilter[]>([])\n const addConditionRef = React.useRef<HTMLButtonElement>(null)\n const savedFilterStorageKey = React.useMemo(\n () => makeSavedFilterStorageKey(props.savedFilterStorageKey),\n [props.savedFilterStorageKey],\n )\n\n // Flush on close \u2014 the hook's useEffect on close also flushes; this is belt-and-suspenders.\n const wasOpenRef = React.useRef(props.open)\n React.useEffect(() => {\n if (wasOpenRef.current && !props.open && props.onFlush) props.onFlush()\n wasOpenRef.current = props.open\n }, [props.open, props.onFlush])\n\n const handlePresetApply = (tree: AdvancedFilterTree) => {\n props.onChange(tree)\n props.onOpenChange(false)\n }\n\n React.useEffect(() => {\n if (!savedFilterStorageKey) {\n setSavedFilters([])\n return\n }\n if (props.open) {\n setSavedFilters(readSavedFilters(savedFilterStorageKey))\n }\n }, [props.open, savedFilterStorageKey])\n\n const persistSavedFilters = React.useCallback((next: SavedAdvancedFilter[]) => {\n if (!savedFilterStorageKey) return\n setSavedFilters(next)\n writeSavedFilters(savedFilterStorageKey, next)\n }, [savedFilterStorageKey])\n\n const handleSaveFilter = React.useCallback(async (name: string) => {\n if (!savedFilterStorageKey) return\n const now = new Date().toISOString()\n const normalizedName = name.trim()\n const existing = savedFilters.find((item) => item.name.trim().toLowerCase() === normalizedName.toLowerCase())\n const record: SavedAdvancedFilter = {\n id: existing?.id ?? crypto.randomUUID(),\n name: normalizedName,\n tree: serializeTreeForPersist(props.value),\n createdAt: existing?.createdAt ?? now,\n updatedAt: now,\n }\n const next = [\n record,\n ...savedFilters.filter((item) => item.id !== record.id),\n ].slice(0, MAX_SAVED_FILTERS)\n persistSavedFilters(next)\n flash(t('ui.advancedFilter.saveFilter.success', 'Filter saved'), 'success')\n }, [persistSavedFilters, props.value, savedFilterStorageKey, savedFilters, t])\n\n const handleApplySavedFilter = React.useCallback((filter: SavedAdvancedFilter) => {\n const tree = deserializeTreeFromPersist(filter.tree)\n if (!tree) return\n props.onChange(tree)\n props.onOpenChange(false)\n }, [props.onChange, props.onOpenChange])\n\n const handleDeleteSavedFilter = React.useCallback((filterId: string) => {\n persistSavedFilters(savedFilters.filter((item) => item.id !== filterId))\n }, [persistSavedFilters, savedFilters])\n\n const shouldShowSavedFilters = Boolean(savedFilterStorageKey && (!empty || savedFilters.length > 0))\n const savedFiltersSection = shouldShowSavedFilters ? (\n <SavedFiltersSection\n filters={savedFilters}\n canSaveCurrent={!empty}\n onSaveClick={() => setSaveDialogOpen(true)}\n onApply={handleApplySavedFilter}\n onDelete={handleDeleteSavedFilter}\n t={t}\n />\n ) : null\n\n const emptyStateShortcuts = props.presets.length || savedFiltersSection ? (\n <div className=\"flex flex-col gap-4\">\n {props.presets.length ? <QuickFilters presets={props.presets} userId={props.userId} onApply={handlePresetApply} /> : null}\n {savedFiltersSection}\n </div>\n ) : null\n\n const builderErrors = React.useMemo(\n () => props.pendingErrors.map(e => ({ ruleId: e.ruleId, messageKey: e.messageKey, message: e.message })),\n [props.pendingErrors],\n )\n const ignoreAdvancedFilterPortalInteractions = React.useCallback((event: { target: EventTarget | null; preventDefault: () => void }) => {\n const target = event.target\n if (!(target instanceof Element)) return\n if (target.closest('[data-advanced-filter-portal]')) {\n event.preventDefault()\n }\n }, [])\n\n return (\n <Popover open={props.open} onOpenChange={props.onOpenChange}>\n {props.triggerRef ? (\n <PopoverAnchor virtualRef={props.triggerRef as React.RefObject<{ getBoundingClientRect(): DOMRect }>} />\n ) : null}\n <PopoverContent\n className=\"w-[min(calc(100vw-2rem),780px)] max-h-[min(calc(100vh-6rem),720px)] overflow-y-auto p-0 gap-0\"\n align=\"end\"\n sideOffset={8}\n data-testid=\"advanced-filter-panel\"\n onPointerDownOutside={ignoreAdvancedFilterPortalInteractions}\n onFocusOutside={ignoreAdvancedFilterPortalInteractions}\n onInteractOutside={ignoreAdvancedFilterPortalInteractions}\n >\n <VisuallyHidden>\n <h2>{t('ui.advancedFilter.dialog.title', 'Advanced filter')}</h2>\n </VisuallyHidden>\n <div className=\"flex flex-col\">\n {empty ? (\n <FilterEmptyState\n onAddCondition={() => setPickerOpen(true)}\n addConditionRef={addConditionRef}\n aiSlot={props.aiSlot}\n quickFilters={emptyStateShortcuts}\n />\n ) : (\n <>\n <AdvancedFilterBuilder\n fields={props.fields}\n value={props.value}\n onChange={props.onChange}\n onApply={props.onApply}\n onClear={props.onClear}\n pendingErrors={builderErrors}\n />\n {savedFiltersSection ? (\n <div className=\"border-t border-border p-3\">\n {savedFiltersSection}\n </div>\n ) : null}\n </>\n )}\n {props.pendingErrors.length > 0 ? (\n <div className=\"p-3 border-t border-border\" data-testid=\"filter-validation-banner\">\n <Alert variant=\"destructive\">\n <AlertDescription>\n {props.pendingErrors.length === 1\n ? t('ui.advancedFilter.banner.incomplete', '{count} filter is incomplete \u2014 filters won\\'t apply until all values are picked.', { count: 1 })\n : t('ui.advancedFilter.banner.incompletePlural', '{count} filters are incomplete \u2014 filters won\\'t apply until all values are picked.', { count: props.pendingErrors.length })}\n </AlertDescription>\n </Alert>\n </div>\n ) : null}\n </div>\n </PopoverContent>\n <FilterFieldPicker\n fields={props.fields}\n open={pickerOpen}\n onOpenChange={setPickerOpen}\n onSelect={(field) => {\n // Pass defaultOperator so the rule starts with a valid operator for the field's\n // type \u2014 otherwise emptyRule defaults to 'contains', which only matches text.\n const next = treeReducer(props.value, {\n type: 'addRule',\n groupId: props.value.root.id,\n defaultField: field.key,\n defaultOperator: getDefaultOperator(field.type),\n })\n props.onChange(next)\n }}\n triggerRef={addConditionRef}\n />\n <SaveFilterDialog\n open={saveDialogOpen}\n onOpenChange={setSaveDialogOpen}\n onSave={handleSaveFilter}\n t={t}\n />\n </Popover>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }