blixify-ui-web-mcp 0.1.60 → 0.1.62
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.
|
@@ -89,6 +89,46 @@ Radar chart comparing multiple attributes across query groups:
|
|
|
89
89
|
}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
Grouped by any column, top 5 with the rest as "Others", highest first:
|
|
93
|
+
|
|
94
|
+
```jsx
|
|
95
|
+
{
|
|
96
|
+
gridSize: 2,
|
|
97
|
+
type: "table",
|
|
98
|
+
title: "Leads by Agent",
|
|
99
|
+
spaceId: "leads",
|
|
100
|
+
query: [],
|
|
101
|
+
queryBaseType: "count",
|
|
102
|
+
groupBy: "agent",
|
|
103
|
+
sortBy: "value",
|
|
104
|
+
sortDirection: "desc",
|
|
105
|
+
topN: 5,
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Revenue per month as a running total, split by status:
|
|
110
|
+
|
|
111
|
+
```jsx
|
|
112
|
+
{
|
|
113
|
+
gridSize: 2,
|
|
114
|
+
type: "bar",
|
|
115
|
+
title: "Revenue per Month",
|
|
116
|
+
spaceId: "orders",
|
|
117
|
+
query: [],
|
|
118
|
+
queryBaseType: "sum",
|
|
119
|
+
queryAttributeId: ["amount"],
|
|
120
|
+
queryBaseDateId: "paidAt",
|
|
121
|
+
queryDateInterval: "month",
|
|
122
|
+
cumulative: true,
|
|
123
|
+
segmentBy: "status",
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A chart that can't load or draw shows an error card in its place — a plain
|
|
128
|
+
message, Retry, and the technical cause under Details — and the other charts
|
|
129
|
+
keep drawing. Pass `structure` so a sum or average of a text column is caught
|
|
130
|
+
before any request is sent.
|
|
131
|
+
|
|
92
132
|
Custom layout via renderSettings:
|
|
93
133
|
|
|
94
134
|
```jsx
|
|
@@ -114,6 +154,9 @@ Custom layout via renderSettings:
|
|
|
114
154
|
| ---------------- | ------------------------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
115
155
|
| `gridSize` | `2 \| 3` | — | Override the grid column layout. Default is a responsive `1 → 2 → 4` column grid. `2` forces 2 cols, `3` forces 3 cols. |
|
|
116
156
|
| `renderSettings` | `(charts: ReactElement, data: any) => any` | — | Custom render function. Receives the rendered charts element and the raw analytics data object. Use to wrap charts in a custom layout. |
|
|
157
|
+
| `structure` | `DataStructure[]` | — | The datasheet's columns. Groups read by their labels, and a chart measuring a column that is not a number shows why before any request. |
|
|
158
|
+
| `personOptions` | `Option[]` | — | Names for the user ids a `person` column groups by. |
|
|
159
|
+
| `onDrillDown` | `(drillDown: ChartDrillDown) => void` | — | Called when a bar, slice or table row is clicked, with its label and the query that finds its records. |
|
|
117
160
|
|
|
118
161
|
##### Charts
|
|
119
162
|
|
|
@@ -139,6 +182,13 @@ Custom layout via renderSettings:
|
|
|
139
182
|
| `colors` | `string[]` | Custom color array for chart segments/bars/lines |
|
|
140
183
|
| `orientation` | `"col" \| "row"` | Bar chart orientation. Defaults to `"row"`. |
|
|
141
184
|
| `stackedVertical` | `boolean` | Enables stacked bars on bar charts |
|
|
185
|
+
| `groupBy` | `string` | Column of any type to group by in one request. A `reference` or `person` column reads by name. |
|
|
186
|
+
| `queryDateInterval`| `"day" \| "week" \| "month"` | With `queryBaseDateId`, buckets records by day, week or month. Defaults to the last 30 days, 12 weeks or 12 months. |
|
|
187
|
+
| `segmentBy` | `string` | Second column split into stacked series (bar, line, table), at most 10 values plus "Others". |
|
|
188
|
+
| `sortBy` | `"value" \| "label"` | Sort the groups. `sortDirection` is `"asc"` or `"desc"` (default). |
|
|
189
|
+
| `topN` | `number` | Keep the first N groups; the rest are added up as "Others" unless `showOthers` is `false` (averages never get "Others"). |
|
|
190
|
+
| `showPercent` | `boolean` | Show each group as a percentage of the total (count and sum, unsplit charts). |
|
|
191
|
+
| `cumulative` | `boolean` | Running total over the date buckets. |
|
|
142
192
|
|
|
143
193
|
##### WorkspaceDevSettings
|
|
144
194
|
|
|
@@ -513,14 +513,24 @@ const CHART_SCHEMA: DataStructure[] = [
|
|
|
513
513
|
listSelection: ["web", "store", "phone"],
|
|
514
514
|
listSelectionLabel: ["Web", "Store", "Phone"],
|
|
515
515
|
},
|
|
516
|
+
{ id: "agent", name: "Agent", type: "person" },
|
|
516
517
|
{ id: "amount", name: "Amount", type: "number" },
|
|
517
518
|
{ id: "items", name: "Items", type: "number" },
|
|
518
519
|
{ id: "createdAt", name: "Created At", type: "date" },
|
|
519
520
|
];
|
|
520
521
|
|
|
522
|
+
const CHART_MEMBERS = [
|
|
523
|
+
{ key: "u1", label: "Aisha" },
|
|
524
|
+
{ key: "u2", label: "Ben" },
|
|
525
|
+
{ key: "u3", label: "Chong" },
|
|
526
|
+
{ key: "u4", label: "Devi" },
|
|
527
|
+
{ key: "u5", label: "Eli" },
|
|
528
|
+
];
|
|
529
|
+
|
|
521
530
|
const renderChartRecords = () => {
|
|
522
531
|
const statuses = ["pending", "paid", "shipped", "cancelled"];
|
|
523
532
|
const channels = ["web", "store", "phone"];
|
|
533
|
+
const agents = ["u1", "u2", "u3", "u1", "u4", "u1", "u2", "u5"];
|
|
524
534
|
return Array.from({ length: 40 }, (_, index) => ({
|
|
525
535
|
_id: `o${index + 1}`,
|
|
526
536
|
orderNo: `ORD-${1000 + index}`,
|
|
@@ -528,7 +538,16 @@ const renderChartRecords = () => {
|
|
|
528
538
|
channel: channels[(index * 5) % channels.length],
|
|
529
539
|
amount: 20 + ((index * 37) % 180),
|
|
530
540
|
items: 1 + ((index * 3) % 6),
|
|
531
|
-
|
|
541
|
+
agent: index % 9 === 0 ? undefined : agents[index % agents.length],
|
|
542
|
+
// INFO: half in the last week for the daily line, the rest over months
|
|
543
|
+
createdAt: new Date(
|
|
544
|
+
Date.now() -
|
|
545
|
+
(index % 2 === 0 ? index % 7 : (index * 11) % 300) *
|
|
546
|
+
24 *
|
|
547
|
+
60 *
|
|
548
|
+
60 *
|
|
549
|
+
1000,
|
|
550
|
+
),
|
|
532
551
|
}));
|
|
533
552
|
};
|
|
534
553
|
|
|
@@ -552,12 +571,26 @@ const handleInstallChartMock = (store: any[]) => {
|
|
|
552
571
|
return {};
|
|
553
572
|
}
|
|
554
573
|
};
|
|
555
|
-
// INFO:
|
|
574
|
+
// INFO: the conditions charts and their drill-downs send — enough to show
|
|
556
575
|
// the table's filters reaching the charts
|
|
557
576
|
const handleMatch = (record: any, query: any[] = []) =>
|
|
558
577
|
query.every((eachQuery) => {
|
|
559
|
-
|
|
560
|
-
|
|
578
|
+
const value = record[eachQuery.queryId];
|
|
579
|
+
// INFO: null matches a missing value too, as Mongo's { column: null }
|
|
580
|
+
if (eachQuery.type === "=") return (value ?? null) === eachQuery.value;
|
|
581
|
+
if (eachQuery.type === "in")
|
|
582
|
+
return eachQuery.value.includes(value ?? null);
|
|
583
|
+
if (eachQuery.type === "!in")
|
|
584
|
+
return !eachQuery.value.includes(value ?? null);
|
|
585
|
+
if (eachQuery.type === "exists")
|
|
586
|
+
return (value !== undefined && value !== null) === eachQuery.value;
|
|
587
|
+
if (eachQuery.type === "><")
|
|
588
|
+
return (
|
|
589
|
+
new Date(value) >= new Date(eachQuery.value[0]) &&
|
|
590
|
+
new Date(value) <= new Date(eachQuery.value[1])
|
|
591
|
+
);
|
|
592
|
+
if (eachQuery.type === "<")
|
|
593
|
+
return new Date(value) < new Date(eachQuery.value);
|
|
561
594
|
if (eachQuery.type === "search")
|
|
562
595
|
return Object.values(record).some((eachValue) =>
|
|
563
596
|
String(eachValue)
|
|
@@ -602,6 +635,20 @@ const handleInstallChartMock = (store: any[]) => {
|
|
|
602
635
|
);
|
|
603
636
|
})
|
|
604
637
|
: matched;
|
|
638
|
+
if (body.aggregate.groupId) {
|
|
639
|
+
const groups = new Map<any, any[]>();
|
|
640
|
+
inRange.forEach((eachRecord) => {
|
|
641
|
+
const key = eachRecord[body.aggregate.groupId] ?? null;
|
|
642
|
+
groups.set(key, [...(groups.get(key) ?? []), eachRecord]);
|
|
643
|
+
});
|
|
644
|
+
return {
|
|
645
|
+
_id: eachRange,
|
|
646
|
+
value: Array.from(groups.entries()).map(([key, records]) => ({
|
|
647
|
+
_id: key,
|
|
648
|
+
value: handleAggregate(records, body.aggregate),
|
|
649
|
+
})),
|
|
650
|
+
};
|
|
651
|
+
}
|
|
605
652
|
return {
|
|
606
653
|
_id: eachRange,
|
|
607
654
|
value: handleAggregate(inRange, body.aggregate),
|
|
@@ -705,6 +752,55 @@ const ChartViewDemo = () => {
|
|
|
705
752
|
seriesLabels: ["Pending", "Paid", "Shipped", "Cancelled"],
|
|
706
753
|
queryBaseType: "count",
|
|
707
754
|
},
|
|
755
|
+
{
|
|
756
|
+
title: "Top Agents",
|
|
757
|
+
type: "table",
|
|
758
|
+
gridSize: 2,
|
|
759
|
+
spaceId: CHART_COLLECTION,
|
|
760
|
+
query: [],
|
|
761
|
+
queryBaseType: "count",
|
|
762
|
+
groupBy: "agent",
|
|
763
|
+
sortBy: "value",
|
|
764
|
+
sortDirection: "desc",
|
|
765
|
+
topN: 3,
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
title: "Revenue Per Month",
|
|
769
|
+
type: "bar",
|
|
770
|
+
orientation: "col",
|
|
771
|
+
gridSize: 2,
|
|
772
|
+
spaceId: CHART_COLLECTION,
|
|
773
|
+
query: [],
|
|
774
|
+
queryBaseType: "sum",
|
|
775
|
+
queryAttributeId: ["amount"],
|
|
776
|
+
queryBaseDateId: "createdAt",
|
|
777
|
+
queryDateInterval: "month",
|
|
778
|
+
cumulative: true,
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
title: "Channel By Status",
|
|
782
|
+
type: "bar",
|
|
783
|
+
orientation: "col",
|
|
784
|
+
gridSize: 2,
|
|
785
|
+
spaceId: CHART_COLLECTION,
|
|
786
|
+
query: ["web", "store", "phone"].map((eachValue) => ({
|
|
787
|
+
type: "=" as const,
|
|
788
|
+
queryId: "channel",
|
|
789
|
+
value: eachValue,
|
|
790
|
+
})),
|
|
791
|
+
seriesLabels: ["Web", "Store", "Phone"],
|
|
792
|
+
queryBaseType: "count",
|
|
793
|
+
segmentBy: "status",
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
title: "Sum Of Order No",
|
|
797
|
+
type: "card",
|
|
798
|
+
gridSize: 2,
|
|
799
|
+
spaceId: CHART_COLLECTION,
|
|
800
|
+
query: [],
|
|
801
|
+
queryBaseType: "sum",
|
|
802
|
+
queryAttributeId: ["orderNo"],
|
|
803
|
+
},
|
|
708
804
|
],
|
|
709
805
|
},
|
|
710
806
|
]);
|
|
@@ -719,7 +815,7 @@ const ChartViewDemo = () => {
|
|
|
719
815
|
collectionId={CHART_COLLECTION}
|
|
720
816
|
model={CHART_SCHEMA}
|
|
721
817
|
localURLEndpoint=""
|
|
722
|
-
devSettings={demoDevSettings}
|
|
818
|
+
devSettings={{ ...demoDevSettings, organisationMembers: CHART_MEMBERS }}
|
|
723
819
|
lib={{ axios, Menu, Transition, papaParser: Papa }}
|
|
724
820
|
limit={10}
|
|
725
821
|
edit={{ allEditable: true }}
|
|
@@ -259,6 +259,38 @@ const isStatusClosed = liveFilters.some(
|
|
|
259
259
|
);
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
+
**Filtering on a referenced record's fields**
|
|
263
|
+
|
|
264
|
+
`bareFlexFilterReferences` maps a `reference` or `listReference` column to the collection it points to and that collection's columns. The list and export filters then offer each of those columns as `<Column> › <Field>`, one level deep. A condition on one is sent with a `foreign` lookup (alias `ref_<column>`), which MongoWrapper joins before its `$match`. Hidden columns, `_id`, and reference, file and object columns of the referenced record are not offered.
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
bareSettings={{
|
|
268
|
+
bareFlexFilters: true,
|
|
269
|
+
bareFlexFilterReferences: {
|
|
270
|
+
lead: { collection: "master-leads", structure: LeadSchema },
|
|
271
|
+
},
|
|
272
|
+
}}
|
|
273
|
+
|
|
274
|
+
// "Lead › Status is Won" is sent as
|
|
275
|
+
// { type: "=", queryId: "ref_lead.status", value: "Won",
|
|
276
|
+
// foreign: [{ collection: "master-leads", localField: "lead", alias: "ref_lead", searchFields: [] }] }
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Filtering on a listObject**
|
|
280
|
+
|
|
281
|
+
`bareFlexFilterListObjects: true` offers each `listObject` column's fields as `<List> › <Field>`, sent on their path (`crew.role`), so a record matches when any row does. It also offers `<List> › Count` with Is, Greater Than, Less Than and Between. A count is sent as whether the list's n-th row exists, so it holds on records where the list was never set:
|
|
282
|
+
|
|
283
|
+
| Count filter | Sent as |
|
|
284
|
+
| --- | --- |
|
|
285
|
+
| Is 3 | `crew.2` exists, `crew.3` does not |
|
|
286
|
+
| Greater Than 2 | `crew.2` exists |
|
|
287
|
+
| Less Than 2 | `crew.1` does not exist |
|
|
288
|
+
| Between 2 and 5 | `crew.1` exists, `crew.5` does not |
|
|
289
|
+
|
|
290
|
+
Two conditions on one list may each be met by a different row. Referenced records and listObjects are not offered on Firebase or `unionCollections` lists.
|
|
291
|
+
|
|
292
|
+
The standalone `FlexFilter` takes both as `extras={{ references, listObjects: true }}`; pass the same object to `renderFlexFilterConditions` when building the query.
|
|
293
|
+
|
|
262
294
|
**8. newTabUrl (Optional)**
|
|
263
295
|
|
|
264
296
|
Enables right-click → "Open in new tab" on list rows. The component builds the link as `default + recordId + addons`.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { Transition } from "@headlessui/react";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { StorySection } from "../../storyUtils";
|
|
5
|
+
import { Loading } from "../../display/loading";
|
|
6
|
+
import { DataStructure, DevSettings } from "../dataTemplate/model";
|
|
7
|
+
import { DataTemplateWrapper } from "../dataWrapper";
|
|
8
|
+
import {
|
|
9
|
+
STORY_API_ENDPOINT,
|
|
10
|
+
STORY_NAMES,
|
|
11
|
+
storyWorkspace,
|
|
12
|
+
useStoryBackend,
|
|
13
|
+
} from "../dataWrapper/storyBackend.fixtures";
|
|
14
|
+
import {
|
|
15
|
+
FlexFilter,
|
|
16
|
+
FlexFilterReferences,
|
|
17
|
+
renderFlexFilterConditions,
|
|
18
|
+
} from ".";
|
|
19
|
+
import { FlexFilterData } from "./model";
|
|
20
|
+
|
|
21
|
+
const meta = { title: "Data/Flex Filter/Referenced Record" };
|
|
22
|
+
export default meta;
|
|
23
|
+
|
|
24
|
+
const LEAD_COLLECTION = "demo-ref-leads";
|
|
25
|
+
const REFERRAL_COLLECTION = "demo-ref-referrals";
|
|
26
|
+
const LEAD_STATUSES = ["New", "Quoted", "Won", "Lost"];
|
|
27
|
+
|
|
28
|
+
const leadSchema: DataStructure[] = [
|
|
29
|
+
{ id: "name", name: "Name", type: "string" },
|
|
30
|
+
{
|
|
31
|
+
id: "status",
|
|
32
|
+
name: "Status",
|
|
33
|
+
type: "list",
|
|
34
|
+
listSelection: LEAD_STATUSES,
|
|
35
|
+
},
|
|
36
|
+
{ id: "systemSize", name: "System Size (kWp)", type: "number" },
|
|
37
|
+
{ id: "signedAt", name: "Signed Date", type: "date" },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const referralSchema: DataStructure[] = [
|
|
41
|
+
{ id: "referrer", name: "Referrer", type: "string" },
|
|
42
|
+
{
|
|
43
|
+
id: "leadId",
|
|
44
|
+
name: "Lead",
|
|
45
|
+
type: "reference",
|
|
46
|
+
referenceSpaceId: LEAD_COLLECTION,
|
|
47
|
+
referenceLabel: "name",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "payout",
|
|
51
|
+
name: "Payout",
|
|
52
|
+
type: "list",
|
|
53
|
+
listSelection: ["Pending", "Paid"],
|
|
54
|
+
},
|
|
55
|
+
{ id: "createdAt", name: "Created At", type: "date" },
|
|
56
|
+
{
|
|
57
|
+
id: "followUps",
|
|
58
|
+
name: "Follow-ups",
|
|
59
|
+
type: "listObject",
|
|
60
|
+
objectData: [
|
|
61
|
+
{
|
|
62
|
+
id: "outcome",
|
|
63
|
+
name: "Outcome",
|
|
64
|
+
type: "list",
|
|
65
|
+
listSelection: ["No Answer", "Callback", "Site Visit"],
|
|
66
|
+
},
|
|
67
|
+
{ id: "note", name: "Note", type: "string" },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const OUTCOMES = ["No Answer", "Callback", "Site Visit"];
|
|
73
|
+
|
|
74
|
+
const references: FlexFilterReferences = {
|
|
75
|
+
leadId: { collection: LEAD_COLLECTION, structure: leadSchema },
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// INFO: dates spread around today, so "This month" and "Last 30 days" match some
|
|
79
|
+
const handleDaysAgo = (days: number) =>
|
|
80
|
+
new Date(Date.now() - days * 86400000).toISOString();
|
|
81
|
+
|
|
82
|
+
const leads = STORY_NAMES.slice(0, 12).map((name, index) => ({
|
|
83
|
+
_id: `lead-${index + 1}`,
|
|
84
|
+
name,
|
|
85
|
+
status: LEAD_STATUSES[index % LEAD_STATUSES.length],
|
|
86
|
+
systemSize: 4 + (index % 5) * 2,
|
|
87
|
+
signedAt: handleDaysAgo(index * 9),
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
const referrals = leads.map((eachLead, index) => ({
|
|
91
|
+
_id: `referral-${index + 1}`,
|
|
92
|
+
referrer: STORY_NAMES[(index + 5) % STORY_NAMES.length],
|
|
93
|
+
leadId: eachLead._id,
|
|
94
|
+
payout: index % 3 === 0 ? "Paid" : "Pending",
|
|
95
|
+
createdAt: handleDaysAgo(index * 4),
|
|
96
|
+
// INFO: 0 to 3 follow-ups, so a count filter has something to split
|
|
97
|
+
followUps: Array.from({ length: index % 4 }, (_, row) => ({
|
|
98
|
+
outcome: OUTCOMES[(index + row) % OUTCOMES.length],
|
|
99
|
+
note: `Call ${row + 1}`,
|
|
100
|
+
})),
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
const extras = { references, listObjects: true };
|
|
104
|
+
|
|
105
|
+
const devSettings: DevSettings = {
|
|
106
|
+
server: "mongoServer",
|
|
107
|
+
dbEndpoint: STORY_API_ENDPOINT,
|
|
108
|
+
imageEndpoint: "",
|
|
109
|
+
uploadEndpoint: "",
|
|
110
|
+
dbAPIToken: "",
|
|
111
|
+
dbUserToken: "",
|
|
112
|
+
schemaEndpoint: "",
|
|
113
|
+
bucketName: "",
|
|
114
|
+
organisationMembers: [],
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const ReferencedRecord = () => {
|
|
118
|
+
const mount = useStoryBackend({
|
|
119
|
+
collections: {
|
|
120
|
+
[LEAD_COLLECTION]: leads,
|
|
121
|
+
[REFERRAL_COLLECTION]: referrals,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
const [filters, setFilters] = useState<FlexFilterData[]>([
|
|
125
|
+
{ id: "init", columnId: "", operator: "=", value: undefined },
|
|
126
|
+
]);
|
|
127
|
+
const query = renderFlexFilterConditions(filters, referralSchema, extras);
|
|
128
|
+
|
|
129
|
+
if (!mount) return <Loading />;
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div className="flex flex-col space-y-12">
|
|
133
|
+
<StorySection
|
|
134
|
+
title="Referrals list filtered on the lead and the follow-ups"
|
|
135
|
+
description="Open the filter and pick a Lead › field, e.g. Lead › Status is Won, or Follow-ups › Count / Outcome."
|
|
136
|
+
>
|
|
137
|
+
<DataTemplateWrapper
|
|
138
|
+
title="Referrals"
|
|
139
|
+
type="list"
|
|
140
|
+
collectionId={REFERRAL_COLLECTION}
|
|
141
|
+
schema={referralSchema}
|
|
142
|
+
workspace={storyWorkspace()}
|
|
143
|
+
limit={20}
|
|
144
|
+
organise={{ breadcrumbType: "hidden" }}
|
|
145
|
+
bareSettings={{
|
|
146
|
+
bareNewDTW: true,
|
|
147
|
+
bareFlexFilters: true,
|
|
148
|
+
bareFlexFilterReferences: references,
|
|
149
|
+
bareFlexFilterListObjects: true,
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
</StorySection>
|
|
153
|
+
|
|
154
|
+
<StorySection
|
|
155
|
+
title="Standalone filter (notification audience)"
|
|
156
|
+
description="The same extras on the bare FlexFilter, with the query it builds."
|
|
157
|
+
>
|
|
158
|
+
<div className="flex flex-col">
|
|
159
|
+
<div className="max-w-[700px]">
|
|
160
|
+
<FlexFilter
|
|
161
|
+
structure={referralSchema}
|
|
162
|
+
extras={extras}
|
|
163
|
+
filters={filters}
|
|
164
|
+
onChange={setFilters}
|
|
165
|
+
devSettings={devSettings}
|
|
166
|
+
dbModule={axios}
|
|
167
|
+
variant="bare"
|
|
168
|
+
lib={{ Transition }}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
<div className="mt-4 p-4 bg-gray-50 rounded-lg border">
|
|
172
|
+
<p className="text-sm font-semibold mb-2">
|
|
173
|
+
Generated Query Conditions ({query.length})
|
|
174
|
+
</p>
|
|
175
|
+
<pre className="text-xs whitespace-pre-wrap">
|
|
176
|
+
{JSON.stringify(query, null, 2)}
|
|
177
|
+
</pre>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</StorySection>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
};
|