@open-mercato/core 0.6.7-develop.6785.1.1dd7cfac55 → 0.6.7-develop.6795.1.8a3f27921c

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 (196) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities.ids.generated.js +1 -0
  3. package/dist/generated/entities.ids.generated.js.map +2 -2
  4. package/dist/modules/auth/backend/settings/page.js +14 -9
  5. package/dist/modules/auth/backend/settings/page.js.map +2 -2
  6. package/dist/modules/customers/cli.js +17 -7
  7. package/dist/modules/customers/cli.js.map +2 -2
  8. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +229 -216
  9. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  10. package/dist/modules/customers/search.js +50 -29
  11. package/dist/modules/customers/search.js.map +2 -2
  12. package/dist/modules/data_sync/api/mappings/[id]/route.js +22 -9
  13. package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
  14. package/dist/modules/data_sync/api/mappings/route.js +13 -4
  15. package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
  16. package/dist/modules/data_sync/api/options.js +7 -2
  17. package/dist/modules/data_sync/api/options.js.map +2 -2
  18. package/dist/modules/data_sync/api/run.js +7 -2
  19. package/dist/modules/data_sync/api/run.js.map +2 -2
  20. package/dist/modules/data_sync/api/runs/[id]/cancel.js +8 -3
  21. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  22. package/dist/modules/data_sync/api/runs/[id]/retry.js +7 -2
  23. package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
  24. package/dist/modules/data_sync/api/runs/[id]/route.js +8 -3
  25. package/dist/modules/data_sync/api/runs/[id]/route.js.map +2 -2
  26. package/dist/modules/data_sync/api/runs.js +7 -2
  27. package/dist/modules/data_sync/api/runs.js.map +2 -2
  28. package/dist/modules/data_sync/api/schedules/[id]/route.js +19 -6
  29. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  30. package/dist/modules/data_sync/api/schedules/route.js +13 -4
  31. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  32. package/dist/modules/data_sync/api/validate.js +9 -4
  33. package/dist/modules/data_sync/api/validate.js.map +2 -2
  34. package/dist/modules/design_system/acl.js +9 -0
  35. package/dist/modules/design_system/acl.js.map +7 -0
  36. package/dist/modules/design_system/backend/design-system/page.js +11 -0
  37. package/dist/modules/design_system/backend/design-system/page.js.map +7 -0
  38. package/dist/modules/design_system/backend/design-system/page.meta.js +15 -0
  39. package/dist/modules/design_system/backend/design-system/page.meta.js.map +7 -0
  40. package/dist/modules/design_system/gallery/components/CodeSnippet.js +52 -0
  41. package/dist/modules/design_system/gallery/components/CodeSnippet.js.map +7 -0
  42. package/dist/modules/design_system/gallery/components/EntryCard.js +63 -0
  43. package/dist/modules/design_system/gallery/components/EntryCard.js.map +7 -0
  44. package/dist/modules/design_system/gallery/components/GalleryShell.js +163 -0
  45. package/dist/modules/design_system/gallery/components/GalleryShell.js.map +7 -0
  46. package/dist/modules/design_system/gallery/components/VariantPreview.js +9 -0
  47. package/dist/modules/design_system/gallery/components/VariantPreview.js.map +7 -0
  48. package/dist/modules/design_system/gallery/entries/banners.js +186 -0
  49. package/dist/modules/design_system/gallery/entries/banners.js.map +7 -0
  50. package/dist/modules/design_system/gallery/entries/buttons.js +361 -0
  51. package/dist/modules/design_system/gallery/entries/buttons.js.map +7 -0
  52. package/dist/modules/design_system/gallery/entries/charts.js +457 -0
  53. package/dist/modules/design_system/gallery/entries/charts.js.map +7 -0
  54. package/dist/modules/design_system/gallery/entries/dates.js +306 -0
  55. package/dist/modules/design_system/gallery/entries/dates.js.map +7 -0
  56. package/dist/modules/design_system/gallery/entries/detail.js +358 -0
  57. package/dist/modules/design_system/gallery/entries/detail.js.map +7 -0
  58. package/dist/modules/design_system/gallery/entries/display.js +785 -0
  59. package/dist/modules/design_system/gallery/entries/display.js.map +7 -0
  60. package/dist/modules/design_system/gallery/entries/feedback.js +658 -0
  61. package/dist/modules/design_system/gallery/entries/feedback.js.map +7 -0
  62. package/dist/modules/design_system/gallery/entries/filters.js +505 -0
  63. package/dist/modules/design_system/gallery/entries/filters.js.map +7 -0
  64. package/dist/modules/design_system/gallery/entries/foundations.js +453 -0
  65. package/dist/modules/design_system/gallery/entries/foundations.js.map +7 -0
  66. package/dist/modules/design_system/gallery/entries/icons.js +140 -0
  67. package/dist/modules/design_system/gallery/entries/icons.js.map +7 -0
  68. package/dist/modules/design_system/gallery/entries/inputs.js +1217 -0
  69. package/dist/modules/design_system/gallery/entries/inputs.js.map +7 -0
  70. package/dist/modules/design_system/gallery/entries/messages.js +334 -0
  71. package/dist/modules/design_system/gallery/entries/messages.js.map +7 -0
  72. package/dist/modules/design_system/gallery/entries/navigation.js +475 -0
  73. package/dist/modules/design_system/gallery/entries/navigation.js.map +7 -0
  74. package/dist/modules/design_system/gallery/entries/notifications.js +257 -0
  75. package/dist/modules/design_system/gallery/entries/notifications.js.map +7 -0
  76. package/dist/modules/design_system/gallery/entries/overlays.js +576 -0
  77. package/dist/modules/design_system/gallery/entries/overlays.js.map +7 -0
  78. package/dist/modules/design_system/gallery/entries/scaffolding.js +378 -0
  79. package/dist/modules/design_system/gallery/entries/scaffolding.js.map +7 -0
  80. package/dist/modules/design_system/gallery/entries/schedule.js +297 -0
  81. package/dist/modules/design_system/gallery/entries/schedule.js.map +7 -0
  82. package/dist/modules/design_system/gallery/registry.js +139 -0
  83. package/dist/modules/design_system/gallery/registry.js.map +7 -0
  84. package/dist/modules/design_system/gallery/types.js +1 -0
  85. package/dist/modules/design_system/gallery/types.js.map +7 -0
  86. package/dist/modules/design_system/index.js +9 -0
  87. package/dist/modules/design_system/index.js.map +7 -0
  88. package/dist/modules/design_system/setup.js +14 -0
  89. package/dist/modules/design_system/setup.js.map +7 -0
  90. package/dist/modules/inbox_ops/search.js +15 -4
  91. package/dist/modules/inbox_ops/search.js.map +2 -2
  92. package/dist/modules/integrations/api/[id]/credentials/route.js +11 -5
  93. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  94. package/dist/modules/integrations/api/[id]/health/route.js +6 -3
  95. package/dist/modules/integrations/api/[id]/health/route.js.map +2 -2
  96. package/dist/modules/integrations/api/[id]/route.js +6 -3
  97. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  98. package/dist/modules/integrations/api/[id]/state/route.js +6 -3
  99. package/dist/modules/integrations/api/[id]/state/route.js.map +2 -2
  100. package/dist/modules/integrations/api/[id]/version/route.js +6 -3
  101. package/dist/modules/integrations/api/[id]/version/route.js.map +2 -2
  102. package/dist/modules/integrations/api/logs/route.js +6 -3
  103. package/dist/modules/integrations/api/logs/route.js.map +2 -2
  104. package/dist/modules/integrations/api/route.js +6 -3
  105. package/dist/modules/integrations/api/route.js.map +2 -2
  106. package/dist/modules/integrations/api/umes-read.js +2 -2
  107. package/dist/modules/integrations/api/umes-read.js.map +2 -2
  108. package/dist/modules/integrations/lib/organization-scope.js +2 -9
  109. package/dist/modules/integrations/lib/organization-scope.js.map +2 -2
  110. package/dist/modules/messages/search.js +10 -5
  111. package/dist/modules/messages/search.js.map +2 -2
  112. package/dist/modules/query_index/data/entities.js +2 -1
  113. package/dist/modules/query_index/data/entities.js.map +2 -2
  114. package/dist/modules/query_index/lib/engine.js +32 -50
  115. package/dist/modules/query_index/lib/engine.js.map +2 -2
  116. package/dist/modules/query_index/migrations/Migration20260731105052_query_index.js +17 -0
  117. package/dist/modules/query_index/migrations/Migration20260731105052_query_index.js.map +7 -0
  118. package/generated/entities.ids.generated.ts +1 -0
  119. package/package.json +16 -16
  120. package/src/modules/auth/backend/settings/page.tsx +17 -8
  121. package/src/modules/customers/cli.ts +17 -7
  122. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +24 -13
  123. package/src/modules/customers/i18n/de.json +17 -0
  124. package/src/modules/customers/i18n/en.json +17 -0
  125. package/src/modules/customers/i18n/es.json +17 -0
  126. package/src/modules/customers/i18n/pl.json +17 -0
  127. package/src/modules/customers/search.ts +59 -27
  128. package/src/modules/data_sync/AGENTS.md +1 -0
  129. package/src/modules/data_sync/api/mappings/[id]/route.ts +22 -9
  130. package/src/modules/data_sync/api/mappings/route.ts +13 -4
  131. package/src/modules/data_sync/api/options.ts +7 -2
  132. package/src/modules/data_sync/api/run.ts +7 -2
  133. package/src/modules/data_sync/api/runs/[id]/cancel.ts +8 -3
  134. package/src/modules/data_sync/api/runs/[id]/retry.ts +7 -2
  135. package/src/modules/data_sync/api/runs/[id]/route.ts +8 -3
  136. package/src/modules/data_sync/api/runs.ts +7 -2
  137. package/src/modules/data_sync/api/schedules/[id]/route.ts +19 -6
  138. package/src/modules/data_sync/api/schedules/route.ts +13 -4
  139. package/src/modules/data_sync/api/validate.ts +9 -4
  140. package/src/modules/design_system/AGENTS.md +34 -0
  141. package/src/modules/design_system/acl.ts +5 -0
  142. package/src/modules/design_system/backend/design-system/page.meta.ts +11 -0
  143. package/src/modules/design_system/backend/design-system/page.tsx +14 -0
  144. package/src/modules/design_system/gallery/components/CodeSnippet.tsx +55 -0
  145. package/src/modules/design_system/gallery/components/EntryCard.tsx +95 -0
  146. package/src/modules/design_system/gallery/components/GalleryShell.tsx +206 -0
  147. package/src/modules/design_system/gallery/components/VariantPreview.tsx +15 -0
  148. package/src/modules/design_system/gallery/entries/banners.tsx +226 -0
  149. package/src/modules/design_system/gallery/entries/buttons.tsx +422 -0
  150. package/src/modules/design_system/gallery/entries/charts.tsx +518 -0
  151. package/src/modules/design_system/gallery/entries/dates.tsx +325 -0
  152. package/src/modules/design_system/gallery/entries/detail.tsx +403 -0
  153. package/src/modules/design_system/gallery/entries/display.tsx +893 -0
  154. package/src/modules/design_system/gallery/entries/feedback.tsx +746 -0
  155. package/src/modules/design_system/gallery/entries/filters.tsx +570 -0
  156. package/src/modules/design_system/gallery/entries/foundations.tsx +529 -0
  157. package/src/modules/design_system/gallery/entries/icons.tsx +168 -0
  158. package/src/modules/design_system/gallery/entries/inputs.tsx +1516 -0
  159. package/src/modules/design_system/gallery/entries/messages.tsx +339 -0
  160. package/src/modules/design_system/gallery/entries/navigation.tsx +573 -0
  161. package/src/modules/design_system/gallery/entries/notifications.tsx +285 -0
  162. package/src/modules/design_system/gallery/entries/overlays.tsx +677 -0
  163. package/src/modules/design_system/gallery/entries/scaffolding.tsx +430 -0
  164. package/src/modules/design_system/gallery/entries/schedule.tsx +307 -0
  165. package/src/modules/design_system/gallery/registry.ts +152 -0
  166. package/src/modules/design_system/gallery/types.ts +32 -0
  167. package/src/modules/design_system/i18n/.hardcoded-allowlist.json +71 -0
  168. package/src/modules/design_system/i18n/de.json +46 -0
  169. package/src/modules/design_system/i18n/en.json +46 -0
  170. package/src/modules/design_system/i18n/es.json +46 -0
  171. package/src/modules/design_system/i18n/pl.json +46 -0
  172. package/src/modules/design_system/index.ts +7 -0
  173. package/src/modules/design_system/setup.ts +12 -0
  174. package/src/modules/inbox_ops/i18n/de.json +3 -0
  175. package/src/modules/inbox_ops/i18n/en.json +3 -0
  176. package/src/modules/inbox_ops/i18n/es.json +3 -0
  177. package/src/modules/inbox_ops/i18n/pl.json +3 -0
  178. package/src/modules/inbox_ops/search.ts +20 -4
  179. package/src/modules/integrations/api/[id]/credentials/route.ts +11 -5
  180. package/src/modules/integrations/api/[id]/health/route.ts +6 -3
  181. package/src/modules/integrations/api/[id]/route.ts +6 -3
  182. package/src/modules/integrations/api/[id]/state/route.ts +6 -3
  183. package/src/modules/integrations/api/[id]/version/route.ts +6 -3
  184. package/src/modules/integrations/api/logs/route.ts +6 -3
  185. package/src/modules/integrations/api/route.ts +6 -3
  186. package/src/modules/integrations/api/umes-read.ts +2 -2
  187. package/src/modules/integrations/lib/organization-scope.ts +5 -25
  188. package/src/modules/messages/i18n/de.json +2 -0
  189. package/src/modules/messages/i18n/en.json +2 -0
  190. package/src/modules/messages/i18n/es.json +2 -0
  191. package/src/modules/messages/i18n/pl.json +2 -0
  192. package/src/modules/messages/search.ts +11 -5
  193. package/src/modules/query_index/data/entities.ts +7 -0
  194. package/src/modules/query_index/lib/engine.ts +40 -63
  195. package/src/modules/query_index/migrations/.snapshot-open-mercato.json +12 -0
  196. package/src/modules/query_index/migrations/Migration20260731105052_query_index.ts +35 -0
@@ -0,0 +1,570 @@
1
+ import * as React from 'react'
2
+ import { Download } from 'lucide-react'
3
+ import { Button } from '@open-mercato/ui/primitives/button'
4
+ import {
5
+ FilterBar,
6
+ type FilterDef,
7
+ type FilterValues,
8
+ } from '@open-mercato/ui/backend/FilterBar'
9
+ import { QuickFilters, type FilterPreset } from '@open-mercato/ui/backend/filters/QuickFilters'
10
+ import { ActiveFilterChips } from '@open-mercato/ui/backend/filters/ActiveFilterChips'
11
+ import { AdvancedFilterBuilder } from '@open-mercato/ui/backend/filters/AdvancedFilterBuilder'
12
+ import { FilterEmptyState } from '@open-mercato/ui/backend/filters/FilterEmptyState'
13
+ import { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'
14
+ import { FilteredEmptyResults } from '@open-mercato/ui/backend/filters/FilteredEmptyResults'
15
+ import { SearchEmptyResults } from '@open-mercato/ui/backend/filters/SearchEmptyResults'
16
+ import type { FilterFieldDef } from '@open-mercato/shared/lib/query/advanced-filter'
17
+ import type {
18
+ AdvancedFilterTree,
19
+ FilterGroup,
20
+ FilterRule,
21
+ } from '@open-mercato/shared/lib/query/advanced-filter-tree'
22
+ import type { GalleryEntry } from '../types'
23
+
24
+ // Component titles and variant names are proper nouns from the codebase and
25
+ // are deliberately not translated. `code` MUST contain the entry's importPath
26
+ // (enforced by the registry-integrity test) and is always reviewed alongside
27
+ // its sibling `render`.
28
+ //
29
+ // AdvancedFilterPanel itself is not showcased: it is a popover host that
30
+ // orchestrates open/anchor state and saved-filter persistence. Its
31
+ // presentational subcomponents (AdvancedFilterBuilder, QuickFilters,
32
+ // FilterEmptyState) are shown standalone below with variant titles noting it.
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Inline demo data — no API calls. Field defs and trees mirror what CRM list
36
+ // pages pass to the advanced filter stack.
37
+ // ---------------------------------------------------------------------------
38
+
39
+ const demoFilterDefs: FilterDef[] = [
40
+ { id: 'city', label: 'City', type: 'text', placeholder: 'e.g. Berlin' },
41
+ {
42
+ id: 'status',
43
+ label: 'Status',
44
+ type: 'select',
45
+ options: [
46
+ { value: 'active', label: 'Active' },
47
+ { value: 'prospect', label: 'Prospect' },
48
+ { value: 'churned', label: 'Churned' },
49
+ ],
50
+ },
51
+ { id: 'hasOpenDeals', label: 'Has open deals', type: 'checkbox' },
52
+ ]
53
+
54
+ const demoFields: FilterFieldDef[] = [
55
+ { key: 'name', label: 'Name', type: 'text' },
56
+ { key: 'email', label: 'Email', type: 'text' },
57
+ {
58
+ key: 'status',
59
+ label: 'Status',
60
+ type: 'select',
61
+ options: [
62
+ { value: 'active', label: 'Active', tone: 'success' },
63
+ { value: 'prospect', label: 'Prospect', tone: 'info' },
64
+ { value: 'churned', label: 'Churned', tone: 'error' },
65
+ ],
66
+ },
67
+ { key: 'employees', label: 'Employees', type: 'number' },
68
+ ]
69
+
70
+ function rule(id: string, field: string, operator: FilterRule['operator'], value: unknown): FilterRule {
71
+ return { id, type: 'rule', field, operator, value }
72
+ }
73
+
74
+ function tree(children: FilterGroup['children']): AdvancedFilterTree {
75
+ return { root: { id: 'root', type: 'group', combinator: 'and', children } }
76
+ }
77
+
78
+ const ruleChipsTree = tree([
79
+ rule('r1', 'name', 'contains', 'north'),
80
+ rule('r2', 'status', 'is', 'active'),
81
+ rule('r3', 'employees', 'between', [10, 50]),
82
+ ])
83
+
84
+ const groupChipTree = tree([
85
+ {
86
+ id: 'g1',
87
+ type: 'group',
88
+ combinator: 'or',
89
+ children: [
90
+ rule('g1r1', 'status', 'is', 'prospect'),
91
+ rule('g1r2', 'status', 'is', 'churned'),
92
+ ],
93
+ },
94
+ ])
95
+
96
+ const valuelessChipTree = tree([rule('r4', 'email', 'is_empty', null)])
97
+
98
+ const builderSingleRuleTree = tree([rule('b1', 'status', 'is', 'active')])
99
+
100
+ const builderNestedTree = tree([
101
+ rule('b2', 'name', 'contains', 'north'),
102
+ {
103
+ id: 'bg1',
104
+ type: 'group',
105
+ combinator: 'or',
106
+ children: [
107
+ rule('bg1r1', 'status', 'is', 'prospect'),
108
+ rule('bg1r2', 'employees', 'greater_than', 100),
109
+ ],
110
+ },
111
+ ])
112
+
113
+ const demoPresets: FilterPreset[] = [
114
+ {
115
+ id: 'active-accounts',
116
+ labelKey: 'Active accounts',
117
+ iconName: 'filter',
118
+ build: () => tree([rule('p1', 'status', 'is', 'active')]),
119
+ },
120
+ {
121
+ id: 'added-this-week',
122
+ labelKey: 'Added this week',
123
+ iconName: 'clock',
124
+ build: () => tree([rule('p2', 'name', 'is_not_empty', null)]),
125
+ },
126
+ ]
127
+
128
+ const userScopedPresets: FilterPreset[] = [
129
+ ...demoPresets,
130
+ {
131
+ id: 'my-accounts',
132
+ labelKey: 'My accounts',
133
+ requiresUser: true,
134
+ build: ({ userId }) => tree([rule('p3', 'name', 'contains', userId)]),
135
+ },
136
+ ]
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Demo wrappers — controlled components need local state to be interactive in
140
+ // the gallery. The `code` snippets show the essential consumer usage, not
141
+ // these wrappers.
142
+ // ---------------------------------------------------------------------------
143
+
144
+ function DemoFilterBar({
145
+ layout,
146
+ trailingItems,
147
+ }: {
148
+ layout?: 'stacked' | 'inline'
149
+ trailingItems?: React.ReactNode
150
+ }) {
151
+ const [search, setSearch] = React.useState('')
152
+ const [values, setValues] = React.useState<FilterValues>({ status: 'active' })
153
+ return (
154
+ <div className="w-full max-w-2xl">
155
+ <FilterBar
156
+ searchValue={search}
157
+ onSearchChange={setSearch}
158
+ filters={demoFilterDefs}
159
+ values={values}
160
+ onApply={setValues}
161
+ onClear={() => setValues({})}
162
+ layout={layout}
163
+ trailingItems={trailingItems}
164
+ />
165
+ </div>
166
+ )
167
+ }
168
+
169
+ function DemoActiveFilterChips({ initialTree }: { initialTree: AdvancedFilterTree }) {
170
+ const [value, setValue] = React.useState(initialTree)
171
+ const removeNode = (id: string) =>
172
+ setValue((prev) => ({
173
+ root: { ...prev.root, children: prev.root.children.filter((child) => child.id !== id) },
174
+ }))
175
+ return (
176
+ <div className="w-full max-w-2xl">
177
+ <ActiveFilterChips
178
+ tree={value}
179
+ fields={demoFields}
180
+ popoverOpen={false}
181
+ onRemoveNode={removeNode}
182
+ onOpen={() => {}}
183
+ />
184
+ </div>
185
+ )
186
+ }
187
+
188
+ function DemoAdvancedFilterBuilder({ initialTree }: { initialTree: AdvancedFilterTree }) {
189
+ const [value, setValue] = React.useState(initialTree)
190
+ return (
191
+ <div className="w-full max-w-2xl rounded-lg border bg-popover p-3">
192
+ <AdvancedFilterBuilder
193
+ fields={demoFields}
194
+ value={value}
195
+ onChange={setValue}
196
+ onApply={() => {}}
197
+ onClear={() => setValue(tree([]))}
198
+ />
199
+ </div>
200
+ )
201
+ }
202
+
203
+ function DemoFilterEmptyState({ withQuickFilters }: { withQuickFilters?: boolean }) {
204
+ const addConditionRef = React.useRef<HTMLButtonElement | null>(null)
205
+ return (
206
+ <div className="w-full max-w-xl rounded-lg border bg-popover">
207
+ <FilterEmptyState
208
+ onAddCondition={() => {}}
209
+ addConditionRef={addConditionRef}
210
+ quickFilters={
211
+ withQuickFilters ? (
212
+ <QuickFilters presets={demoPresets} userId="user-1" onApply={() => {}} />
213
+ ) : undefined
214
+ }
215
+ />
216
+ </div>
217
+ )
218
+ }
219
+
220
+ const filterBarEntry: GalleryEntry = {
221
+ id: 'filter-bar',
222
+ title: 'FilterBar',
223
+ importPath: '@open-mercato/ui/backend/FilterBar',
224
+ variants: [
225
+ {
226
+ id: 'stacked',
227
+ title: 'Stacked (default)',
228
+ render: () => <DemoFilterBar />,
229
+ code: `import { FilterBar, type FilterDef, type FilterValues } from '@open-mercato/ui/backend/FilterBar'
230
+
231
+ const filters: FilterDef[] = [
232
+ { id: 'city', label: 'City', type: 'text' },
233
+ { id: 'status', label: 'Status', type: 'select', options: statusOptions },
234
+ { id: 'hasOpenDeals', label: 'Has open deals', type: 'checkbox' },
235
+ ]
236
+
237
+ <FilterBar
238
+ searchValue={search}
239
+ onSearchChange={setSearch}
240
+ filters={filters}
241
+ values={values}
242
+ onApply={setValues}
243
+ onClear={() => setValues({})}
244
+ />`,
245
+ },
246
+ {
247
+ id: 'inline',
248
+ title: 'Inline layout',
249
+ render: () => <DemoFilterBar layout="inline" />,
250
+ code: `import { FilterBar } from '@open-mercato/ui/backend/FilterBar'
251
+
252
+ <FilterBar
253
+ searchValue={search}
254
+ onSearchChange={setSearch}
255
+ filters={filters}
256
+ values={values}
257
+ onApply={setValues}
258
+ layout="inline"
259
+ />`,
260
+ },
261
+ {
262
+ id: 'trailing-items',
263
+ title: 'With trailing items',
264
+ render: () => (
265
+ <DemoFilterBar
266
+ trailingItems={
267
+ <Button variant="outline" size="sm">
268
+ <Download />
269
+ Export
270
+ </Button>
271
+ }
272
+ />
273
+ ),
274
+ code: `import { Download } from 'lucide-react'
275
+ import { FilterBar } from '@open-mercato/ui/backend/FilterBar'
276
+ import { Button } from '@open-mercato/ui/primitives/button'
277
+
278
+ <FilterBar
279
+ searchValue={search}
280
+ onSearchChange={setSearch}
281
+ filters={filters}
282
+ values={values}
283
+ onApply={setValues}
284
+ trailingItems={<Button variant="outline" size="sm"><Download />Export</Button>}
285
+ />`,
286
+ },
287
+ ],
288
+ }
289
+
290
+ const quickFiltersEntry: GalleryEntry = {
291
+ id: 'quick-filters',
292
+ title: 'QuickFilters',
293
+ importPath: '@open-mercato/ui/backend/filters/QuickFilters',
294
+ variants: [
295
+ {
296
+ id: 'presets',
297
+ title: 'Presets (used inside AdvancedFilterPanel)',
298
+ render: () => (
299
+ <div className="w-full max-w-xl">
300
+ <QuickFilters presets={demoPresets} userId="user-1" onApply={() => {}} />
301
+ </div>
302
+ ),
303
+ code: `import { QuickFilters, type FilterPreset } from '@open-mercato/ui/backend/filters/QuickFilters'
304
+
305
+ const presets: FilterPreset[] = [
306
+ { id: 'active-accounts', labelKey: 'customers.filters.activeAccounts', iconName: 'filter', build: () => activeTree },
307
+ { id: 'added-this-week', labelKey: 'customers.filters.addedThisWeek', iconName: 'clock', build: () => recentTree },
308
+ ]
309
+
310
+ <QuickFilters presets={presets} userId={userId} onApply={(tree, preset) => applyTree(tree)} />`,
311
+ },
312
+ {
313
+ id: 'user-scoped',
314
+ title: 'User-scoped preset (hidden without userId)',
315
+ render: () => (
316
+ <div className="flex w-full max-w-xl flex-col gap-6">
317
+ <QuickFilters presets={userScopedPresets} userId="user-1" onApply={() => {}} />
318
+ <QuickFilters presets={userScopedPresets} userId="" onApply={() => {}} />
319
+ </div>
320
+ ),
321
+ code: `import { QuickFilters } from '@open-mercato/ui/backend/filters/QuickFilters'
322
+
323
+ // Presets with requiresUser: true are hidden when userId is empty.
324
+ <QuickFilters presets={presets} userId={userId} onApply={applyTree} />`,
325
+ },
326
+ ],
327
+ }
328
+
329
+ const activeFilterChipsEntry: GalleryEntry = {
330
+ id: 'active-filter-chips',
331
+ title: 'ActiveFilterChips',
332
+ importPath: '@open-mercato/ui/backend/filters/ActiveFilterChips',
333
+ variants: [
334
+ {
335
+ id: 'rule-chips',
336
+ title: 'Rule chips (text, toned select, range)',
337
+ render: () => <DemoActiveFilterChips initialTree={ruleChipsTree} />,
338
+ code: `import { ActiveFilterChips } from '@open-mercato/ui/backend/filters/ActiveFilterChips'
339
+
340
+ <ActiveFilterChips
341
+ tree={tree}
342
+ fields={fields}
343
+ popoverOpen={panelOpen}
344
+ onRemoveNode={(id) => dispatch({ type: 'removeNode', id })}
345
+ onOpen={(focusNodeId) => setPanelOpen(true)}
346
+ />`,
347
+ },
348
+ {
349
+ id: 'group-chip',
350
+ title: 'Group chip (first rule +N)',
351
+ render: () => <DemoActiveFilterChips initialTree={groupChipTree} />,
352
+ code: `import { ActiveFilterChips } from '@open-mercato/ui/backend/filters/ActiveFilterChips'
353
+
354
+ // An OR group renders as a single chip: "Status: Prospect +1".
355
+ <ActiveFilterChips tree={tree} fields={fields} popoverOpen={false} onRemoveNode={removeNode} onOpen={openPanel} />`,
356
+ },
357
+ {
358
+ id: 'valueless-operator',
359
+ title: 'Valueless operator chip',
360
+ render: () => <DemoActiveFilterChips initialTree={valuelessChipTree} />,
361
+ code: `import { ActiveFilterChips } from '@open-mercato/ui/backend/filters/ActiveFilterChips'
362
+
363
+ // Valueless operators read as a phrase: "Email is empty".
364
+ <ActiveFilterChips tree={tree} fields={fields} popoverOpen={false} onRemoveNode={removeNode} onOpen={openPanel} />`,
365
+ },
366
+ ],
367
+ }
368
+
369
+ const advancedFilterBuilderEntry: GalleryEntry = {
370
+ id: 'advanced-filter-builder',
371
+ title: 'AdvancedFilterBuilder',
372
+ importPath: '@open-mercato/ui/backend/filters/AdvancedFilterBuilder',
373
+ variants: [
374
+ {
375
+ id: 'single-rule',
376
+ title: 'Single rule (presentational core of AdvancedFilterPanel)',
377
+ render: () => <DemoAdvancedFilterBuilder initialTree={builderSingleRuleTree} />,
378
+ code: `import { AdvancedFilterBuilder } from '@open-mercato/ui/backend/filters/AdvancedFilterBuilder'
379
+
380
+ <AdvancedFilterBuilder
381
+ fields={fields}
382
+ value={tree}
383
+ onChange={setTree}
384
+ onApply={applyFilters}
385
+ onClear={clearFilters}
386
+ />`,
387
+ },
388
+ {
389
+ id: 'nested-group',
390
+ title: 'Rule with nested OR group',
391
+ render: () => <DemoAdvancedFilterBuilder initialTree={builderNestedTree} />,
392
+ code: `import { AdvancedFilterBuilder } from '@open-mercato/ui/backend/filters/AdvancedFilterBuilder'
393
+
394
+ // Groups nest up to 3 levels; the value is a plain AdvancedFilterTree.
395
+ <AdvancedFilterBuilder fields={fields} value={tree} onChange={setTree} onApply={applyFilters} onClear={clearFilters} />`,
396
+ },
397
+ ],
398
+ }
399
+
400
+ const filterEmptyStateEntry: GalleryEntry = {
401
+ id: 'filter-empty-state',
402
+ title: 'FilterEmptyState',
403
+ importPath: '@open-mercato/ui/backend/filters/FilterEmptyState',
404
+ variants: [
405
+ {
406
+ id: 'default',
407
+ title: 'Default (panel empty state of AdvancedFilterPanel)',
408
+ render: () => <DemoFilterEmptyState />,
409
+ code: `import { FilterEmptyState } from '@open-mercato/ui/backend/filters/FilterEmptyState'
410
+
411
+ <FilterEmptyState onAddCondition={openFieldPicker} addConditionRef={addConditionRef} />`,
412
+ },
413
+ {
414
+ id: 'with-quick-filters',
415
+ title: 'With quick filters slot',
416
+ render: () => <DemoFilterEmptyState withQuickFilters />,
417
+ code: `import { FilterEmptyState } from '@open-mercato/ui/backend/filters/FilterEmptyState'
418
+ import { QuickFilters } from '@open-mercato/ui/backend/filters/QuickFilters'
419
+
420
+ <FilterEmptyState
421
+ onAddCondition={openFieldPicker}
422
+ addConditionRef={addConditionRef}
423
+ quickFilters={<QuickFilters presets={presets} userId={userId} onApply={applyTree} />}
424
+ />`,
425
+ },
426
+ ],
427
+ }
428
+
429
+ const listEmptyStateEntry: GalleryEntry = {
430
+ id: 'list-empty-state',
431
+ title: 'ListEmptyState',
432
+ importPath: '@open-mercato/ui/backend/filters/ListEmptyState',
433
+ variants: [
434
+ {
435
+ id: 'with-create-action',
436
+ title: 'With create action',
437
+ render: () => (
438
+ <div className="w-full max-w-xl">
439
+ <ListEmptyState entityName="companies" onCreate={() => {}} createLabel="Add company" />
440
+ </div>
441
+ ),
442
+ code: `import { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'
443
+
444
+ // Pass to DataTable's emptyState for the zero-records case.
445
+ <ListEmptyState entityName="companies" createHref="/backend/customers/companies/create" createLabel="Add company" />`,
446
+ },
447
+ {
448
+ id: 'custom-copy',
449
+ title: 'Custom title and description',
450
+ render: () => (
451
+ <div className="w-full max-w-xl">
452
+ <ListEmptyState
453
+ title="No imports yet"
454
+ description="Connect a source or upload a CSV file to bring records in."
455
+ />
456
+ </div>
457
+ ),
458
+ code: `import { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'
459
+
460
+ <ListEmptyState
461
+ title="No imports yet"
462
+ description="Connect a source or upload a CSV file to bring records in."
463
+ />`,
464
+ },
465
+ ],
466
+ }
467
+
468
+ const filteredEmptyResultsEntry: GalleryEntry = {
469
+ id: 'filtered-empty-results',
470
+ title: 'FilteredEmptyResults',
471
+ importPath: '@open-mercato/ui/backend/filters/FilteredEmptyResults',
472
+ variants: [
473
+ {
474
+ id: 'filters-only',
475
+ title: 'Filters active',
476
+ render: () => (
477
+ <div className="w-full max-w-xl">
478
+ <FilteredEmptyResults
479
+ entityNamePlural="companies"
480
+ canRemoveLast
481
+ onClearAll={() => {}}
482
+ onRemoveLast={() => {}}
483
+ />
484
+ </div>
485
+ ),
486
+ code: `import { FilteredEmptyResults } from '@open-mercato/ui/backend/filters/FilteredEmptyResults'
487
+
488
+ // DataTable renders this automatically when filters match nothing.
489
+ <FilteredEmptyResults
490
+ entityNamePlural="companies"
491
+ canRemoveLast={tree.root.children.length > 0}
492
+ onClearAll={clearFilters}
493
+ onRemoveLast={removeLastFilter}
494
+ />`,
495
+ },
496
+ {
497
+ id: 'with-search',
498
+ title: 'Search and filters active',
499
+ render: () => (
500
+ <div className="w-full max-w-xl">
501
+ <FilteredEmptyResults
502
+ entityNamePlural="companies"
503
+ canRemoveLast
504
+ onClearAll={() => {}}
505
+ onRemoveLast={() => {}}
506
+ onClearSearch={() => {}}
507
+ />
508
+ </div>
509
+ ),
510
+ code: `import { FilteredEmptyResults } from '@open-mercato/ui/backend/filters/FilteredEmptyResults'
511
+
512
+ // Passing onClearSearch switches to combined copy and clears both at once.
513
+ <FilteredEmptyResults
514
+ entityNamePlural="companies"
515
+ canRemoveLast
516
+ onClearAll={clearFilters}
517
+ onRemoveLast={removeLastFilter}
518
+ onClearSearch={clearSearch}
519
+ />`,
520
+ },
521
+ ],
522
+ }
523
+
524
+ const searchEmptyResultsEntry: GalleryEntry = {
525
+ id: 'search-empty-results',
526
+ title: 'SearchEmptyResults',
527
+ importPath: '@open-mercato/ui/backend/filters/SearchEmptyResults',
528
+ variants: [
529
+ {
530
+ id: 'default',
531
+ title: 'Default',
532
+ render: () => (
533
+ <div className="flex w-full max-w-xl justify-center">
534
+ <SearchEmptyResults query="northwind" onClearSearch={() => {}} />
535
+ </div>
536
+ ),
537
+ code: `import { SearchEmptyResults } from '@open-mercato/ui/backend/filters/SearchEmptyResults'
538
+
539
+ // DataTable renders this automatically when a search matches nothing.
540
+ <SearchEmptyResults query={search} onClearSearch={() => setSearch('')} />`,
541
+ },
542
+ {
543
+ id: 'with-entity',
544
+ title: 'With entity label',
545
+ render: () => (
546
+ <div className="flex w-full max-w-xl justify-center">
547
+ <SearchEmptyResults
548
+ query="northwind"
549
+ entityNamePlural="companies"
550
+ onClearSearch={() => {}}
551
+ />
552
+ </div>
553
+ ),
554
+ code: `import { SearchEmptyResults } from '@open-mercato/ui/backend/filters/SearchEmptyResults'
555
+
556
+ <SearchEmptyResults query={search} entityNamePlural="companies" onClearSearch={() => setSearch('')} />`,
557
+ },
558
+ ],
559
+ }
560
+
561
+ export const entries: GalleryEntry[] = [
562
+ filterBarEntry,
563
+ quickFiltersEntry,
564
+ activeFilterChipsEntry,
565
+ advancedFilterBuilderEntry,
566
+ filterEmptyStateEntry,
567
+ listEmptyStateEntry,
568
+ filteredEmptyResultsEntry,
569
+ searchEmptyResultsEntry,
570
+ ]