blixify-ui-web-mcp 0.1.61 → 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.
|
@@ -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
|
+
};
|