@vonaffenfels/contentful-teasermanager 1.2.9 → 1.2.10
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.
- package/dist/_base_slate-editor_src_dev_testComponents_TestStory2_js.js +94 -0
- package/dist/_base_slate-editor_src_dev_testComponents_TestStory3_js.js +94 -0
- package/dist/_base_slate-editor_src_dev_testComponents_TestStory_js.js +94 -0
- package/dist/index.html +12 -0
- package/dist/index.js +64938 -0
- package/package.json +3 -3
- package/src/components/Contentful/Dialog/LogicEditor.js +503 -503
- package/src/queryFromLogic.js +28 -28
|
@@ -1,504 +1,504 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TextInput,
|
|
3
|
-
Pill,
|
|
4
|
-
SectionHeading,
|
|
5
|
-
Checkbox,
|
|
6
|
-
Button,
|
|
7
|
-
DragHandle,
|
|
8
|
-
} from "@contentful/f36-components";
|
|
9
|
-
import {
|
|
10
|
-
DndContext, useDraggable, useDroppable,
|
|
11
|
-
PointerSensor, useSensor, useSensors,
|
|
12
|
-
} from '@dnd-kit/core';
|
|
13
|
-
import contentfulResolveResponse from "contentful-resolve-response";
|
|
14
|
-
import {
|
|
15
|
-
useEffect, useState,
|
|
16
|
-
} from "react";
|
|
17
|
-
import useDebounce from "../../../hooks/useDebounce";
|
|
18
|
-
import {getContentfulClient} from "../../../lib/contentfulClient";
|
|
19
|
-
import {PlusIcon} from "@contentful/f36-icons";
|
|
20
|
-
import {CSS} from "@dnd-kit/utilities";
|
|
21
|
-
import {queryFromLogic} from "../../../queryFromLogic";
|
|
22
|
-
import {useQuery} from "@tanstack/react-query";
|
|
23
|
-
export const LogicEditor = (props) => {
|
|
24
|
-
const [selectedTags, setSelectedTags] = useState([]);
|
|
25
|
-
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
setSelectedTags(props?.logicData?.tags || []);
|
|
28
|
-
}, [props?.logicData?.tags]);
|
|
29
|
-
|
|
30
|
-
const handleDragEnd = (event) => {
|
|
31
|
-
const {
|
|
32
|
-
over,
|
|
33
|
-
active,
|
|
34
|
-
} = event;
|
|
35
|
-
|
|
36
|
-
if (over) {
|
|
37
|
-
const draggedTagIndex = selectedTags.findIndex(tag => tag.id === active.id);
|
|
38
|
-
if (draggedTagIndex !== -1) {
|
|
39
|
-
const updatedTags = [...selectedTags];
|
|
40
|
-
updatedTags[draggedTagIndex] = {
|
|
41
|
-
...updatedTags[draggedTagIndex],
|
|
42
|
-
type: over.id, // "and" or "or"
|
|
43
|
-
};
|
|
44
|
-
setSelectedTags(updatedTags);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return <DndContext onDragEnd={handleDragEnd}>
|
|
50
|
-
<LogicEditorInner {...props} selectedTags={selectedTags} setSelectedTags={setSelectedTags} />
|
|
51
|
-
</DndContext>;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const LogicEditorInner = ({
|
|
55
|
-
onSave = () => alert("onSave missing"),
|
|
56
|
-
sdk,
|
|
57
|
-
logicData,
|
|
58
|
-
selectedTags,
|
|
59
|
-
setSelectedTags,
|
|
60
|
-
}) => {
|
|
61
|
-
const contentfulClient = getContentfulClient();
|
|
62
|
-
const [loading, setLoading] = useState(false);
|
|
63
|
-
const [tags, setTags] = useState([]);
|
|
64
|
-
const [categories, setCategories] = useState([]);
|
|
65
|
-
const [searchQuery, setSearchQuery] = useState("");
|
|
66
|
-
const [addTimepoint, setAddTimepoint] = useState(7);
|
|
67
|
-
const debouncedSearch = useDebounce(searchQuery, 500);
|
|
68
|
-
const [selectedTimepoints, setSelectedTimepoints] = useState([]);
|
|
69
|
-
const {
|
|
70
|
-
portal,
|
|
71
|
-
locale = "de",
|
|
72
|
-
} = sdk.parameters.invocation;
|
|
73
|
-
|
|
74
|
-
const {data: articles} = useQuery({
|
|
75
|
-
enabled: !!selectedTags.length && !!portal && !!contentfulClient,
|
|
76
|
-
queryKey: ["articles", selectedTags, portal],
|
|
77
|
-
queryFn: async () => {
|
|
78
|
-
const query = queryFromLogic({
|
|
79
|
-
tags: selectedTags,
|
|
80
|
-
project: portal,
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
const result = await contentfulClient.getEntries({
|
|
85
|
-
content_type: "article",
|
|
86
|
-
locale: "de",
|
|
87
|
-
order: "-fields.date",
|
|
88
|
-
limit: 10,
|
|
89
|
-
...query,
|
|
90
|
-
select: [
|
|
91
|
-
"fields.title",
|
|
92
|
-
"fields.slug",
|
|
93
|
-
"fields.date",
|
|
94
|
-
"sys.id",
|
|
95
|
-
].join(","),
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
return result;
|
|
99
|
-
} catch (e) {
|
|
100
|
-
console.error(e);
|
|
101
|
-
return {items: []};
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
if (logicData?.timepoints) {
|
|
108
|
-
setSelectedTimepoints(logicData?.timepoints);
|
|
109
|
-
} else {
|
|
110
|
-
setSelectedTimepoints([7, 12, 18]);
|
|
111
|
-
}
|
|
112
|
-
}, [logicData]);
|
|
113
|
-
const {
|
|
114
|
-
setNodeRef: setOrNodeRef,
|
|
115
|
-
isOver: isOrOver,
|
|
116
|
-
node: orNode,
|
|
117
|
-
} = useDroppable({id: 'or'});
|
|
118
|
-
const {
|
|
119
|
-
setNodeRef: setAndNodeRef,
|
|
120
|
-
isOver: isAndOver,
|
|
121
|
-
node: andNode,
|
|
122
|
-
} = useDroppable({id: "and"});
|
|
123
|
-
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
setLoading(true);
|
|
126
|
-
const params = {
|
|
127
|
-
limit: 100,
|
|
128
|
-
skip: 0,
|
|
129
|
-
content_type: "tags",
|
|
130
|
-
locale,
|
|
131
|
-
order: "-sys.updatedAt",
|
|
132
|
-
"fields.portal": portal,
|
|
133
|
-
select: [
|
|
134
|
-
"fields.title",
|
|
135
|
-
"fields.isCategory",
|
|
136
|
-
"fields.hidden",
|
|
137
|
-
"sys.publishedAt",
|
|
138
|
-
"sys.id",
|
|
139
|
-
].join(","),
|
|
140
|
-
};
|
|
141
|
-
const paramsNavigation = {
|
|
142
|
-
limit: 1,
|
|
143
|
-
content_type: "navigation",
|
|
144
|
-
include: 2,
|
|
145
|
-
locale,
|
|
146
|
-
"fields.portal": portal,
|
|
147
|
-
"fields.type": "main",
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
if (debouncedSearch) {
|
|
151
|
-
params["fields.title[match]"] = debouncedSearch;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
Promise.all([
|
|
155
|
-
contentfulClient.getEntries(params),
|
|
156
|
-
contentfulClient.getEntries(paramsNavigation).then(async (response) => {
|
|
157
|
-
if (!response?.items?.[0]) {
|
|
158
|
-
return [];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const references = await sdk.cma.entry.references({entryId: response.items[0].sys.id}, {include: 10});
|
|
162
|
-
const resolved = contentfulResolveResponse(references);
|
|
163
|
-
const allCategories = {};
|
|
164
|
-
|
|
165
|
-
for (const category of resolved) {
|
|
166
|
-
if (!category?.fields?.children?.de) {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const children = category.fields.children.de || [];
|
|
171
|
-
|
|
172
|
-
for (const child of children) {
|
|
173
|
-
getAllTagsFromCategory(child, allCategories);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return Object.values(allCategories).filter((c) => c.tags.length > 0);
|
|
178
|
-
}),
|
|
179
|
-
]).then(([tags, categories]) => {
|
|
180
|
-
setLoading(false);
|
|
181
|
-
setTags(tags.items);
|
|
182
|
-
setCategories(categories);
|
|
183
|
-
});
|
|
184
|
-
}, [debouncedSearch, portal, locale]);
|
|
185
|
-
|
|
186
|
-
const andTags = selectedTags.filter((t) => t.type === "and");
|
|
187
|
-
const orTags = selectedTags.filter((t) => t.type === "or");
|
|
188
|
-
|
|
189
|
-
return <div className="flex flex-col gap-2 p-4">
|
|
190
|
-
<div className="z-10 flex w-full flex-col gap-4 border-b border-gray-200 bg-white py-4">
|
|
191
|
-
<div className="flex w-full flex-row items-center gap-2" style={{minHeight: 36}}>
|
|
192
|
-
<SectionHeading style={{marginBottom: 0}}>Gewählte Zeitpunkte:</SectionHeading>
|
|
193
|
-
<div className="flex flex-row items-center gap-2">
|
|
194
|
-
{selectedTimepoints.map((timepoint) => {
|
|
195
|
-
return <Pill
|
|
196
|
-
key={timepoint}
|
|
197
|
-
draggable={false}
|
|
198
|
-
testId="pill-item"
|
|
199
|
-
variant="active"
|
|
200
|
-
label={`${timepoint} Uhr`}
|
|
201
|
-
onClose={() => {
|
|
202
|
-
setSelectedTimepoints(selectedTimepoints.filter((t) => t !== timepoint));
|
|
203
|
-
}}
|
|
204
|
-
/>;
|
|
205
|
-
})}
|
|
206
|
-
<TextInput
|
|
207
|
-
type="number"
|
|
208
|
-
min={1}
|
|
209
|
-
max={24}
|
|
210
|
-
value={addTimepoint}
|
|
211
|
-
onChange={(e) => {
|
|
212
|
-
setAddTimepoint(e.target.value);
|
|
213
|
-
}}
|
|
214
|
-
/>
|
|
215
|
-
<SectionHeading style={{marginBottom: 0}}>Uhr</SectionHeading>
|
|
216
|
-
<Button
|
|
217
|
-
variant="secondary"
|
|
218
|
-
onClick={() => {
|
|
219
|
-
if (selectedTimepoints.includes(addTimepoint)) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
setSelectedTimepoints([...selectedTimepoints, addTimepoint]);
|
|
224
|
-
}}
|
|
225
|
-
>
|
|
226
|
-
<PlusIcon />
|
|
227
|
-
</Button>
|
|
228
|
-
</div>
|
|
229
|
-
</div>
|
|
230
|
-
<div className="flex w-full flex-row items-center gap-2" style={{minHeight: 36}}>
|
|
231
|
-
<SectionHeading style={{marginBottom: 0}}>Gewählte Kategorien & Tags:</SectionHeading>
|
|
232
|
-
<div className="grid w-full grid-cols-4 gap-4">
|
|
233
|
-
<div className="flex w-full flex-col gap-2">
|
|
234
|
-
<div>
|
|
235
|
-
<SectionHeading style={{marginBottom: 0}}>Oder</SectionHeading>
|
|
236
|
-
</div>
|
|
237
|
-
<div
|
|
238
|
-
ref={setOrNodeRef}
|
|
239
|
-
style={{
|
|
240
|
-
minHeight: 36,
|
|
241
|
-
backgroundColor: isOrOver ? "rgba(0, 255, 0, 0.3)" : "transparent",
|
|
242
|
-
}}
|
|
243
|
-
className="flex h-full w-full flex-row flex-wrap gap-2"
|
|
244
|
-
>
|
|
245
|
-
{orTags.map((tag) => {
|
|
246
|
-
return <DraggablePill
|
|
247
|
-
key={tag.id}
|
|
248
|
-
id={tag.id}
|
|
249
|
-
variant="active"
|
|
250
|
-
label={(`${tag.label}`)}
|
|
251
|
-
onClose={() => {
|
|
252
|
-
setSelectedTags(selectedTags.filter((t) => t.id !== tag.id));
|
|
253
|
-
}}
|
|
254
|
-
/>;
|
|
255
|
-
})}
|
|
256
|
-
</div>
|
|
257
|
-
</div>
|
|
258
|
-
<div className="flex w-full flex-col gap-2">
|
|
259
|
-
<SectionHeading style={{marginBottom: 0}}>Und</SectionHeading>
|
|
260
|
-
<div
|
|
261
|
-
ref={setAndNodeRef}
|
|
262
|
-
style={{
|
|
263
|
-
minHeight: 36,
|
|
264
|
-
backgroundColor: isAndOver ? "rgba(0, 255, 0, 0.3)" : "transparent",
|
|
265
|
-
}}
|
|
266
|
-
className="flex h-full w-full flex-row flex-wrap gap-2"
|
|
267
|
-
>
|
|
268
|
-
{andTags.map((tag) => {
|
|
269
|
-
return <DraggablePill
|
|
270
|
-
key={tag.id}
|
|
271
|
-
id={tag.id}
|
|
272
|
-
testId={tag.id}
|
|
273
|
-
variant="active"
|
|
274
|
-
label={(`${tag.label}`)}
|
|
275
|
-
onClose={() => {
|
|
276
|
-
setSelectedTags(selectedTags.filter((t) => t.id !== tag.id));
|
|
277
|
-
}}
|
|
278
|
-
/>;
|
|
279
|
-
})}
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
282
|
-
<div className="col-span-2 flex w-full flex-col gap-4">
|
|
283
|
-
<div>
|
|
284
|
-
<SectionHeading style={{marginBottom: 0}}>Artikel</SectionHeading>
|
|
285
|
-
</div>
|
|
286
|
-
<div className="flex flex-col gap-4">
|
|
287
|
-
<table className="w-full max-w-full table-auto gap-4 text-left">
|
|
288
|
-
<thead>
|
|
289
|
-
<tr>
|
|
290
|
-
<th>Datum</th>
|
|
291
|
-
<th className="pl-4">Titel</th>
|
|
292
|
-
</tr>
|
|
293
|
-
</thead>
|
|
294
|
-
<tbody>
|
|
295
|
-
{articles?.items?.map((article) => {
|
|
296
|
-
return <tr key={article.sys.id}>
|
|
297
|
-
<td className="whitespace-nowrap">
|
|
298
|
-
{new Date(article.fields.date.de).toLocaleString("de-DE", {
|
|
299
|
-
hour: "2-digit",
|
|
300
|
-
minute: "2-digit",
|
|
301
|
-
day: "2-digit",
|
|
302
|
-
month: "2-digit",
|
|
303
|
-
year: "numeric",
|
|
304
|
-
})}
|
|
305
|
-
</td>
|
|
306
|
-
<td className="truncate whitespace-nowrap pl-4">
|
|
307
|
-
{article.fields.title.de}
|
|
308
|
-
</td>
|
|
309
|
-
</tr>;
|
|
310
|
-
})}
|
|
311
|
-
</tbody>
|
|
312
|
-
</table>
|
|
313
|
-
</div>
|
|
314
|
-
</div>
|
|
315
|
-
</div>
|
|
316
|
-
</div>
|
|
317
|
-
<div className="flex w-full flex-row gap-2">
|
|
318
|
-
<TextInput
|
|
319
|
-
placeholder="Suche nach Kategorie oder Tag"
|
|
320
|
-
value={searchQuery}
|
|
321
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
322
|
-
/>
|
|
323
|
-
</div>
|
|
324
|
-
</div>
|
|
325
|
-
<div className="grid grid-cols-2 gap-4 overflow-y-auto">
|
|
326
|
-
<div className="flex flex-col gap-4">
|
|
327
|
-
<div>
|
|
328
|
-
<SectionHeading style={{marginBottom: 0}}>Kategorien</SectionHeading>
|
|
329
|
-
</div>
|
|
330
|
-
<div className="grid grid-cols-3 gap-4">
|
|
331
|
-
{categories?.map((category) => {
|
|
332
|
-
return (
|
|
333
|
-
<Checkbox
|
|
334
|
-
key={category.id}
|
|
335
|
-
isChecked={selectedTags.some((t) => category.tags.some((c) => c.id === t.id))}
|
|
336
|
-
onChange={(e) => {
|
|
337
|
-
let newTags = [...selectedTags];
|
|
338
|
-
|
|
339
|
-
if (e.target.checked) {
|
|
340
|
-
for (const tag of category.tags) {
|
|
341
|
-
if (!selectedTags.find((t) => t.id === tag.id)) {
|
|
342
|
-
newTags.push({
|
|
343
|
-
label: tag.label,
|
|
344
|
-
id: tag.id,
|
|
345
|
-
type: "or",
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
} else {
|
|
350
|
-
for (const tag of category.tags) {
|
|
351
|
-
newTags = newTags.filter((t) => t.id !== tag.id);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
setSelectedTags(newTags);
|
|
356
|
-
}}
|
|
357
|
-
>
|
|
358
|
-
{category.title} ({selectedTags.filter((t) =>
|
|
359
|
-
category.tags.some((c) => c.id === t.id)).length}/{category.tags.length})
|
|
360
|
-
</Checkbox>
|
|
361
|
-
);
|
|
362
|
-
})}
|
|
363
|
-
</div>
|
|
364
|
-
</div>
|
|
365
|
-
<div className="flex flex-col gap-4">
|
|
366
|
-
<div>
|
|
367
|
-
<SectionHeading style={{marginBottom: 0}}>Tags</SectionHeading>
|
|
368
|
-
</div>
|
|
369
|
-
<div className="grid grid-cols-3 gap-4">
|
|
370
|
-
{tags?.map((tag) => {
|
|
371
|
-
return (
|
|
372
|
-
<Checkbox
|
|
373
|
-
key={tag.sys.id}
|
|
374
|
-
isChecked={!!selectedTags.find((t) => t.id === tag.sys.id)}
|
|
375
|
-
onChange={(e) => {
|
|
376
|
-
if (e.target.checked) {
|
|
377
|
-
setSelectedTags([...selectedTags, {
|
|
378
|
-
label: tag.fields.title[locale],
|
|
379
|
-
id: tag.sys.id,
|
|
380
|
-
type: "or",
|
|
381
|
-
}]);
|
|
382
|
-
} else {
|
|
383
|
-
setSelectedTags(selectedTags.filter((t) => t.id !== tag.sys.id));
|
|
384
|
-
}
|
|
385
|
-
}}
|
|
386
|
-
>
|
|
387
|
-
{tag.fields.title[locale]}
|
|
388
|
-
</Checkbox>
|
|
389
|
-
);
|
|
390
|
-
})}
|
|
391
|
-
</div>
|
|
392
|
-
</div>
|
|
393
|
-
</div>
|
|
394
|
-
<div className="sticky bottom-0 flex w-full flex-row justify-between gap-2 border-t border-gray-200 bg-white py-4">
|
|
395
|
-
<Button
|
|
396
|
-
variant="positive"
|
|
397
|
-
type="button"
|
|
398
|
-
onClick={() => onSave({
|
|
399
|
-
tags: selectedTags,
|
|
400
|
-
timepoints: selectedTimepoints,
|
|
401
|
-
})}>Speichern</Button>
|
|
402
|
-
<Button
|
|
403
|
-
variant="negative"
|
|
404
|
-
type="button"
|
|
405
|
-
onClick={() => onSave({
|
|
406
|
-
tags: [],
|
|
407
|
-
timepoints: [],
|
|
408
|
-
})}>Logik löschen</Button>
|
|
409
|
-
</div>
|
|
410
|
-
</div>;
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
function getAllTagsFromCategory(categoryNode, allCategories = {}, parentCategoryTitle = null) {
|
|
414
|
-
if (!categoryNode || !categoryNode.fields?.children) {
|
|
415
|
-
return allCategories;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const children = categoryNode.fields.children.de || [];
|
|
419
|
-
const title = parentCategoryTitle ? `${parentCategoryTitle} > ${categoryNode.fields.title.de}` : categoryNode.fields.title.de;
|
|
420
|
-
|
|
421
|
-
allCategories[categoryNode.sys.id] = {
|
|
422
|
-
title,
|
|
423
|
-
tags: [],
|
|
424
|
-
id: categoryNode.sys.id,
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
if (categoryNode?.fields?.tags?.de) {
|
|
428
|
-
for (const tag of categoryNode.fields.tags.de) {
|
|
429
|
-
if (!tag?.fields?.title) {
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
if (!allCategories[categoryNode.sys.id].tags.find((c) => c.id === tag.sys.id)) {
|
|
434
|
-
allCategories[categoryNode.sys.id].tags.push({
|
|
435
|
-
label: tag.fields.title.de,
|
|
436
|
-
id: tag.sys.id,
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
for (const child of children) {
|
|
443
|
-
if (!child || !child.fields) {
|
|
444
|
-
continue;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
if (child?.fields?.tags?.de) {
|
|
448
|
-
for (const tag of child.fields.tags.de) {
|
|
449
|
-
if (!allCategories[categoryNode.sys.id].tags.find((c) => c.id === tag.sys.id)) {
|
|
450
|
-
if (!tag?.fields?.title) {
|
|
451
|
-
continue;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
allCategories[categoryNode.sys.id].tags.push({
|
|
455
|
-
label: tag.fields.title.de,
|
|
456
|
-
id: tag.sys.id,
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
getAllTagsFromCategory(child, allCategories, title);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
allCategories[categoryNode.sys.id].tags = [...new Set(allCategories[categoryNode.sys.id].tags)];
|
|
466
|
-
|
|
467
|
-
return allCategories;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
function DraggablePill({
|
|
472
|
-
id, ...props
|
|
473
|
-
}) {
|
|
474
|
-
const {
|
|
475
|
-
attributes,
|
|
476
|
-
listeners,
|
|
477
|
-
setNodeRef,
|
|
478
|
-
transform,
|
|
479
|
-
transition,
|
|
480
|
-
} = useDraggable({id});
|
|
481
|
-
const style = {
|
|
482
|
-
transform: CSS.Translate.toString(transform),
|
|
483
|
-
transition,
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
return (
|
|
487
|
-
<div>
|
|
488
|
-
<Pill
|
|
489
|
-
dragHandleComponent={
|
|
490
|
-
<DragHandle
|
|
491
|
-
label="Reorder item"
|
|
492
|
-
variant="transparent"
|
|
493
|
-
{...attributes}
|
|
494
|
-
{...listeners}
|
|
495
|
-
/>
|
|
496
|
-
}
|
|
497
|
-
isDraggable
|
|
498
|
-
ref={setNodeRef}
|
|
499
|
-
style={style}
|
|
500
|
-
{...props}
|
|
501
|
-
/>
|
|
502
|
-
</div>
|
|
503
|
-
);
|
|
1
|
+
import {
|
|
2
|
+
TextInput,
|
|
3
|
+
Pill,
|
|
4
|
+
SectionHeading,
|
|
5
|
+
Checkbox,
|
|
6
|
+
Button,
|
|
7
|
+
DragHandle,
|
|
8
|
+
} from "@contentful/f36-components";
|
|
9
|
+
import {
|
|
10
|
+
DndContext, useDraggable, useDroppable,
|
|
11
|
+
PointerSensor, useSensor, useSensors,
|
|
12
|
+
} from '@dnd-kit/core';
|
|
13
|
+
import contentfulResolveResponse from "contentful-resolve-response";
|
|
14
|
+
import {
|
|
15
|
+
useEffect, useState,
|
|
16
|
+
} from "react";
|
|
17
|
+
import useDebounce from "../../../hooks/useDebounce";
|
|
18
|
+
import {getContentfulClient} from "../../../lib/contentfulClient";
|
|
19
|
+
import {PlusIcon} from "@contentful/f36-icons";
|
|
20
|
+
import {CSS} from "@dnd-kit/utilities";
|
|
21
|
+
import {queryFromLogic} from "../../../queryFromLogic";
|
|
22
|
+
import {useQuery} from "@tanstack/react-query";
|
|
23
|
+
export const LogicEditor = (props) => {
|
|
24
|
+
const [selectedTags, setSelectedTags] = useState([]);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
setSelectedTags(props?.logicData?.tags || []);
|
|
28
|
+
}, [props?.logicData?.tags]);
|
|
29
|
+
|
|
30
|
+
const handleDragEnd = (event) => {
|
|
31
|
+
const {
|
|
32
|
+
over,
|
|
33
|
+
active,
|
|
34
|
+
} = event;
|
|
35
|
+
|
|
36
|
+
if (over) {
|
|
37
|
+
const draggedTagIndex = selectedTags.findIndex(tag => tag.id === active.id);
|
|
38
|
+
if (draggedTagIndex !== -1) {
|
|
39
|
+
const updatedTags = [...selectedTags];
|
|
40
|
+
updatedTags[draggedTagIndex] = {
|
|
41
|
+
...updatedTags[draggedTagIndex],
|
|
42
|
+
type: over.id, // "and" or "or"
|
|
43
|
+
};
|
|
44
|
+
setSelectedTags(updatedTags);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return <DndContext onDragEnd={handleDragEnd}>
|
|
50
|
+
<LogicEditorInner {...props} selectedTags={selectedTags} setSelectedTags={setSelectedTags} />
|
|
51
|
+
</DndContext>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const LogicEditorInner = ({
|
|
55
|
+
onSave = () => alert("onSave missing"),
|
|
56
|
+
sdk,
|
|
57
|
+
logicData,
|
|
58
|
+
selectedTags,
|
|
59
|
+
setSelectedTags,
|
|
60
|
+
}) => {
|
|
61
|
+
const contentfulClient = getContentfulClient();
|
|
62
|
+
const [loading, setLoading] = useState(false);
|
|
63
|
+
const [tags, setTags] = useState([]);
|
|
64
|
+
const [categories, setCategories] = useState([]);
|
|
65
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
66
|
+
const [addTimepoint, setAddTimepoint] = useState(7);
|
|
67
|
+
const debouncedSearch = useDebounce(searchQuery, 500);
|
|
68
|
+
const [selectedTimepoints, setSelectedTimepoints] = useState([]);
|
|
69
|
+
const {
|
|
70
|
+
portal,
|
|
71
|
+
locale = "de",
|
|
72
|
+
} = sdk.parameters.invocation;
|
|
73
|
+
|
|
74
|
+
const {data: articles} = useQuery({
|
|
75
|
+
enabled: !!selectedTags.length && !!portal && !!contentfulClient,
|
|
76
|
+
queryKey: ["articles", selectedTags, portal],
|
|
77
|
+
queryFn: async () => {
|
|
78
|
+
const query = queryFromLogic({
|
|
79
|
+
tags: selectedTags,
|
|
80
|
+
project: portal,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const result = await contentfulClient.getEntries({
|
|
85
|
+
content_type: "article",
|
|
86
|
+
locale: "de",
|
|
87
|
+
order: "-fields.date",
|
|
88
|
+
limit: 10,
|
|
89
|
+
...query,
|
|
90
|
+
select: [
|
|
91
|
+
"fields.title",
|
|
92
|
+
"fields.slug",
|
|
93
|
+
"fields.date",
|
|
94
|
+
"sys.id",
|
|
95
|
+
].join(","),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
} catch (e) {
|
|
100
|
+
console.error(e);
|
|
101
|
+
return {items: []};
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (logicData?.timepoints) {
|
|
108
|
+
setSelectedTimepoints(logicData?.timepoints);
|
|
109
|
+
} else {
|
|
110
|
+
setSelectedTimepoints([7, 12, 18]);
|
|
111
|
+
}
|
|
112
|
+
}, [logicData]);
|
|
113
|
+
const {
|
|
114
|
+
setNodeRef: setOrNodeRef,
|
|
115
|
+
isOver: isOrOver,
|
|
116
|
+
node: orNode,
|
|
117
|
+
} = useDroppable({id: 'or'});
|
|
118
|
+
const {
|
|
119
|
+
setNodeRef: setAndNodeRef,
|
|
120
|
+
isOver: isAndOver,
|
|
121
|
+
node: andNode,
|
|
122
|
+
} = useDroppable({id: "and"});
|
|
123
|
+
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
setLoading(true);
|
|
126
|
+
const params = {
|
|
127
|
+
limit: 100,
|
|
128
|
+
skip: 0,
|
|
129
|
+
content_type: "tags",
|
|
130
|
+
locale,
|
|
131
|
+
order: "-sys.updatedAt",
|
|
132
|
+
"fields.portal": portal,
|
|
133
|
+
select: [
|
|
134
|
+
"fields.title",
|
|
135
|
+
"fields.isCategory",
|
|
136
|
+
"fields.hidden",
|
|
137
|
+
"sys.publishedAt",
|
|
138
|
+
"sys.id",
|
|
139
|
+
].join(","),
|
|
140
|
+
};
|
|
141
|
+
const paramsNavigation = {
|
|
142
|
+
limit: 1,
|
|
143
|
+
content_type: "navigation",
|
|
144
|
+
include: 2,
|
|
145
|
+
locale,
|
|
146
|
+
"fields.portal": portal,
|
|
147
|
+
"fields.type": "main",
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
if (debouncedSearch) {
|
|
151
|
+
params["fields.title[match]"] = debouncedSearch;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Promise.all([
|
|
155
|
+
contentfulClient.getEntries(params),
|
|
156
|
+
contentfulClient.getEntries(paramsNavigation).then(async (response) => {
|
|
157
|
+
if (!response?.items?.[0]) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const references = await sdk.cma.entry.references({entryId: response.items[0].sys.id}, {include: 10});
|
|
162
|
+
const resolved = contentfulResolveResponse(references);
|
|
163
|
+
const allCategories = {};
|
|
164
|
+
|
|
165
|
+
for (const category of resolved) {
|
|
166
|
+
if (!category?.fields?.children?.de) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const children = category.fields.children.de || [];
|
|
171
|
+
|
|
172
|
+
for (const child of children) {
|
|
173
|
+
getAllTagsFromCategory(child, allCategories);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return Object.values(allCategories).filter((c) => c.tags.length > 0);
|
|
178
|
+
}),
|
|
179
|
+
]).then(([tags, categories]) => {
|
|
180
|
+
setLoading(false);
|
|
181
|
+
setTags(tags.items);
|
|
182
|
+
setCategories(categories);
|
|
183
|
+
});
|
|
184
|
+
}, [debouncedSearch, portal, locale]);
|
|
185
|
+
|
|
186
|
+
const andTags = selectedTags.filter((t) => t.type === "and");
|
|
187
|
+
const orTags = selectedTags.filter((t) => t.type === "or");
|
|
188
|
+
|
|
189
|
+
return <div className="flex flex-col gap-2 p-4">
|
|
190
|
+
<div className="z-10 flex w-full flex-col gap-4 border-b border-gray-200 bg-white py-4">
|
|
191
|
+
<div className="flex w-full flex-row items-center gap-2" style={{minHeight: 36}}>
|
|
192
|
+
<SectionHeading style={{marginBottom: 0}}>Gewählte Zeitpunkte:</SectionHeading>
|
|
193
|
+
<div className="flex flex-row items-center gap-2">
|
|
194
|
+
{selectedTimepoints.map((timepoint) => {
|
|
195
|
+
return <Pill
|
|
196
|
+
key={timepoint}
|
|
197
|
+
draggable={false}
|
|
198
|
+
testId="pill-item"
|
|
199
|
+
variant="active"
|
|
200
|
+
label={`${timepoint} Uhr`}
|
|
201
|
+
onClose={() => {
|
|
202
|
+
setSelectedTimepoints(selectedTimepoints.filter((t) => t !== timepoint));
|
|
203
|
+
}}
|
|
204
|
+
/>;
|
|
205
|
+
})}
|
|
206
|
+
<TextInput
|
|
207
|
+
type="number"
|
|
208
|
+
min={1}
|
|
209
|
+
max={24}
|
|
210
|
+
value={addTimepoint}
|
|
211
|
+
onChange={(e) => {
|
|
212
|
+
setAddTimepoint(e.target.value);
|
|
213
|
+
}}
|
|
214
|
+
/>
|
|
215
|
+
<SectionHeading style={{marginBottom: 0}}>Uhr</SectionHeading>
|
|
216
|
+
<Button
|
|
217
|
+
variant="secondary"
|
|
218
|
+
onClick={() => {
|
|
219
|
+
if (selectedTimepoints.includes(addTimepoint)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
setSelectedTimepoints([...selectedTimepoints, addTimepoint]);
|
|
224
|
+
}}
|
|
225
|
+
>
|
|
226
|
+
<PlusIcon />
|
|
227
|
+
</Button>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
<div className="flex w-full flex-row items-center gap-2" style={{minHeight: 36}}>
|
|
231
|
+
<SectionHeading style={{marginBottom: 0}}>Gewählte Kategorien & Tags:</SectionHeading>
|
|
232
|
+
<div className="grid w-full grid-cols-4 gap-4">
|
|
233
|
+
<div className="flex w-full flex-col gap-2">
|
|
234
|
+
<div>
|
|
235
|
+
<SectionHeading style={{marginBottom: 0}}>Oder</SectionHeading>
|
|
236
|
+
</div>
|
|
237
|
+
<div
|
|
238
|
+
ref={setOrNodeRef}
|
|
239
|
+
style={{
|
|
240
|
+
minHeight: 36,
|
|
241
|
+
backgroundColor: isOrOver ? "rgba(0, 255, 0, 0.3)" : "transparent",
|
|
242
|
+
}}
|
|
243
|
+
className="flex h-full w-full flex-row flex-wrap gap-2"
|
|
244
|
+
>
|
|
245
|
+
{orTags.map((tag) => {
|
|
246
|
+
return <DraggablePill
|
|
247
|
+
key={tag.id}
|
|
248
|
+
id={tag.id}
|
|
249
|
+
variant="active"
|
|
250
|
+
label={(`${tag.label}`)}
|
|
251
|
+
onClose={() => {
|
|
252
|
+
setSelectedTags(selectedTags.filter((t) => t.id !== tag.id));
|
|
253
|
+
}}
|
|
254
|
+
/>;
|
|
255
|
+
})}
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
<div className="flex w-full flex-col gap-2">
|
|
259
|
+
<SectionHeading style={{marginBottom: 0}}>Und</SectionHeading>
|
|
260
|
+
<div
|
|
261
|
+
ref={setAndNodeRef}
|
|
262
|
+
style={{
|
|
263
|
+
minHeight: 36,
|
|
264
|
+
backgroundColor: isAndOver ? "rgba(0, 255, 0, 0.3)" : "transparent",
|
|
265
|
+
}}
|
|
266
|
+
className="flex h-full w-full flex-row flex-wrap gap-2"
|
|
267
|
+
>
|
|
268
|
+
{andTags.map((tag) => {
|
|
269
|
+
return <DraggablePill
|
|
270
|
+
key={tag.id}
|
|
271
|
+
id={tag.id}
|
|
272
|
+
testId={tag.id}
|
|
273
|
+
variant="active"
|
|
274
|
+
label={(`${tag.label}`)}
|
|
275
|
+
onClose={() => {
|
|
276
|
+
setSelectedTags(selectedTags.filter((t) => t.id !== tag.id));
|
|
277
|
+
}}
|
|
278
|
+
/>;
|
|
279
|
+
})}
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
<div className="col-span-2 flex w-full flex-col gap-4">
|
|
283
|
+
<div>
|
|
284
|
+
<SectionHeading style={{marginBottom: 0}}>Artikel</SectionHeading>
|
|
285
|
+
</div>
|
|
286
|
+
<div className="flex flex-col gap-4">
|
|
287
|
+
<table className="w-full max-w-full table-auto gap-4 text-left">
|
|
288
|
+
<thead>
|
|
289
|
+
<tr>
|
|
290
|
+
<th>Datum</th>
|
|
291
|
+
<th className="pl-4">Titel</th>
|
|
292
|
+
</tr>
|
|
293
|
+
</thead>
|
|
294
|
+
<tbody>
|
|
295
|
+
{articles?.items?.map((article) => {
|
|
296
|
+
return <tr key={article.sys.id}>
|
|
297
|
+
<td className="whitespace-nowrap">
|
|
298
|
+
{new Date(article.fields.date.de).toLocaleString("de-DE", {
|
|
299
|
+
hour: "2-digit",
|
|
300
|
+
minute: "2-digit",
|
|
301
|
+
day: "2-digit",
|
|
302
|
+
month: "2-digit",
|
|
303
|
+
year: "numeric",
|
|
304
|
+
})}
|
|
305
|
+
</td>
|
|
306
|
+
<td className="truncate whitespace-nowrap pl-4">
|
|
307
|
+
{article.fields.title.de}
|
|
308
|
+
</td>
|
|
309
|
+
</tr>;
|
|
310
|
+
})}
|
|
311
|
+
</tbody>
|
|
312
|
+
</table>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
<div className="flex w-full flex-row gap-2">
|
|
318
|
+
<TextInput
|
|
319
|
+
placeholder="Suche nach Kategorie oder Tag"
|
|
320
|
+
value={searchQuery}
|
|
321
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
322
|
+
/>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
<div className="grid grid-cols-2 gap-4 overflow-y-auto">
|
|
326
|
+
<div className="flex flex-col gap-4">
|
|
327
|
+
<div>
|
|
328
|
+
<SectionHeading style={{marginBottom: 0}}>Kategorien</SectionHeading>
|
|
329
|
+
</div>
|
|
330
|
+
<div className="grid grid-cols-3 gap-4">
|
|
331
|
+
{categories?.map((category) => {
|
|
332
|
+
return (
|
|
333
|
+
<Checkbox
|
|
334
|
+
key={category.id}
|
|
335
|
+
isChecked={selectedTags.some((t) => category.tags.some((c) => c.id === t.id))}
|
|
336
|
+
onChange={(e) => {
|
|
337
|
+
let newTags = [...selectedTags];
|
|
338
|
+
|
|
339
|
+
if (e.target.checked) {
|
|
340
|
+
for (const tag of category.tags) {
|
|
341
|
+
if (!selectedTags.find((t) => t.id === tag.id)) {
|
|
342
|
+
newTags.push({
|
|
343
|
+
label: tag.label,
|
|
344
|
+
id: tag.id,
|
|
345
|
+
type: "or",
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
for (const tag of category.tags) {
|
|
351
|
+
newTags = newTags.filter((t) => t.id !== tag.id);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
setSelectedTags(newTags);
|
|
356
|
+
}}
|
|
357
|
+
>
|
|
358
|
+
{category.title} ({selectedTags.filter((t) =>
|
|
359
|
+
category.tags.some((c) => c.id === t.id)).length}/{category.tags.length})
|
|
360
|
+
</Checkbox>
|
|
361
|
+
);
|
|
362
|
+
})}
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
<div className="flex flex-col gap-4">
|
|
366
|
+
<div>
|
|
367
|
+
<SectionHeading style={{marginBottom: 0}}>Tags</SectionHeading>
|
|
368
|
+
</div>
|
|
369
|
+
<div className="grid grid-cols-3 gap-4">
|
|
370
|
+
{tags?.map((tag) => {
|
|
371
|
+
return (
|
|
372
|
+
<Checkbox
|
|
373
|
+
key={tag.sys.id}
|
|
374
|
+
isChecked={!!selectedTags.find((t) => t.id === tag.sys.id)}
|
|
375
|
+
onChange={(e) => {
|
|
376
|
+
if (e.target.checked) {
|
|
377
|
+
setSelectedTags([...selectedTags, {
|
|
378
|
+
label: tag.fields.title[locale],
|
|
379
|
+
id: tag.sys.id,
|
|
380
|
+
type: "or",
|
|
381
|
+
}]);
|
|
382
|
+
} else {
|
|
383
|
+
setSelectedTags(selectedTags.filter((t) => t.id !== tag.sys.id));
|
|
384
|
+
}
|
|
385
|
+
}}
|
|
386
|
+
>
|
|
387
|
+
{tag.fields.title[locale]}
|
|
388
|
+
</Checkbox>
|
|
389
|
+
);
|
|
390
|
+
})}
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
<div className="sticky bottom-0 flex w-full flex-row justify-between gap-2 border-t border-gray-200 bg-white py-4">
|
|
395
|
+
<Button
|
|
396
|
+
variant="positive"
|
|
397
|
+
type="button"
|
|
398
|
+
onClick={() => onSave({
|
|
399
|
+
tags: selectedTags,
|
|
400
|
+
timepoints: selectedTimepoints,
|
|
401
|
+
})}>Speichern</Button>
|
|
402
|
+
<Button
|
|
403
|
+
variant="negative"
|
|
404
|
+
type="button"
|
|
405
|
+
onClick={() => onSave({
|
|
406
|
+
tags: [],
|
|
407
|
+
timepoints: [],
|
|
408
|
+
})}>Logik löschen</Button>
|
|
409
|
+
</div>
|
|
410
|
+
</div>;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
function getAllTagsFromCategory(categoryNode, allCategories = {}, parentCategoryTitle = null) {
|
|
414
|
+
if (!categoryNode || !categoryNode.fields?.children) {
|
|
415
|
+
return allCategories;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const children = categoryNode.fields.children.de || [];
|
|
419
|
+
const title = parentCategoryTitle ? `${parentCategoryTitle} > ${categoryNode.fields.title.de}` : categoryNode.fields.title.de;
|
|
420
|
+
|
|
421
|
+
allCategories[categoryNode.sys.id] = {
|
|
422
|
+
title,
|
|
423
|
+
tags: [],
|
|
424
|
+
id: categoryNode.sys.id,
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
if (categoryNode?.fields?.tags?.de) {
|
|
428
|
+
for (const tag of categoryNode.fields.tags.de) {
|
|
429
|
+
if (!tag?.fields?.title) {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!allCategories[categoryNode.sys.id].tags.find((c) => c.id === tag.sys.id)) {
|
|
434
|
+
allCategories[categoryNode.sys.id].tags.push({
|
|
435
|
+
label: tag.fields.title.de,
|
|
436
|
+
id: tag.sys.id,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
for (const child of children) {
|
|
443
|
+
if (!child || !child.fields) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (child?.fields?.tags?.de) {
|
|
448
|
+
for (const tag of child.fields.tags.de) {
|
|
449
|
+
if (!allCategories[categoryNode.sys.id].tags.find((c) => c.id === tag.sys.id)) {
|
|
450
|
+
if (!tag?.fields?.title) {
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
allCategories[categoryNode.sys.id].tags.push({
|
|
455
|
+
label: tag.fields.title.de,
|
|
456
|
+
id: tag.sys.id,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
getAllTagsFromCategory(child, allCategories, title);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
allCategories[categoryNode.sys.id].tags = [...new Set(allCategories[categoryNode.sys.id].tags)];
|
|
466
|
+
|
|
467
|
+
return allCategories;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
function DraggablePill({
|
|
472
|
+
id, ...props
|
|
473
|
+
}) {
|
|
474
|
+
const {
|
|
475
|
+
attributes,
|
|
476
|
+
listeners,
|
|
477
|
+
setNodeRef,
|
|
478
|
+
transform,
|
|
479
|
+
transition,
|
|
480
|
+
} = useDraggable({id});
|
|
481
|
+
const style = {
|
|
482
|
+
transform: CSS.Translate.toString(transform),
|
|
483
|
+
transition,
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
return (
|
|
487
|
+
<div>
|
|
488
|
+
<Pill
|
|
489
|
+
dragHandleComponent={
|
|
490
|
+
<DragHandle
|
|
491
|
+
label="Reorder item"
|
|
492
|
+
variant="transparent"
|
|
493
|
+
{...attributes}
|
|
494
|
+
{...listeners}
|
|
495
|
+
/>
|
|
496
|
+
}
|
|
497
|
+
isDraggable
|
|
498
|
+
ref={setNodeRef}
|
|
499
|
+
style={style}
|
|
500
|
+
{...props}
|
|
501
|
+
/>
|
|
502
|
+
</div>
|
|
503
|
+
);
|
|
504
504
|
}
|