@hrbolek/uoisfrontend-template 0.6.2 → 0.6.3

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 (68) hide show
  1. package/dist/cjs/index.js +17 -17
  2. package/dist/es/index.js +38 -31
  3. package/dist/umd/index.js +31 -31
  4. package/package.json +1 -1
  5. package/src/Base/Components/Link.jsx +13 -8
  6. package/src/Base/Mutations/General.jsx +3 -1
  7. package/src/Base/Mutations/Update.jsx +5 -0
  8. package/src/EventGQLModel/Components/A4Plan/A4Plan.jsx +363 -0
  9. package/src/EventGQLModel/Components/A4Plan/index.js +1 -0
  10. package/src/EventGQLModel/Components/CardCapsule.jsx +43 -0
  11. package/src/EventGQLModel/Components/Children.jsx +31 -0
  12. package/src/EventGQLModel/Components/ConfirmEdit.jsx +61 -0
  13. package/src/EventGQLModel/Components/Filter.jsx +14 -0
  14. package/src/EventGQLModel/Components/LargeCard.jsx +54 -0
  15. package/src/EventGQLModel/Components/Link.jsx +55 -0
  16. package/src/EventGQLModel/Components/LiveEdit.jsx +111 -0
  17. package/src/EventGQLModel/Components/MediumCard.jsx +39 -0
  18. package/src/EventGQLModel/Components/MediumContent.jsx +96 -0
  19. package/src/EventGQLModel/Components/MediumEditableContent.jsx +35 -0
  20. package/src/EventGQLModel/Components/Plan/PlanRow.jsx +470 -0
  21. package/src/EventGQLModel/Components/Plan/_utils.js +971 -0
  22. package/src/EventGQLModel/Components/Plan/calendarReducer.js +535 -0
  23. package/src/EventGQLModel/Components/Plan/index.js +3 -0
  24. package/src/EventGQLModel/Components/Table.jsx +7 -0
  25. package/src/EventGQLModel/Components/index.js +15 -0
  26. package/src/EventGQLModel/Mutations/Create.jsx +202 -0
  27. package/src/EventGQLModel/Mutations/Delete.jsx +173 -0
  28. package/src/EventGQLModel/Mutations/InteractiveMutations.jsx +30 -0
  29. package/src/EventGQLModel/Mutations/Update.jsx +147 -0
  30. package/src/EventGQLModel/Mutations/helpers.jsx +7 -0
  31. package/src/EventGQLModel/Pages/PageBase.jsx +56 -0
  32. package/src/EventGQLModel/Pages/PageCreateItem.jsx +28 -0
  33. package/src/EventGQLModel/Pages/PageDeleteItem.jsx +16 -0
  34. package/src/EventGQLModel/Pages/PageNavbar.jsx +160 -0
  35. package/src/EventGQLModel/Pages/PagePlan.jsx +42 -0
  36. package/src/EventGQLModel/Pages/PageReadItem.jsx +11 -0
  37. package/src/EventGQLModel/Pages/PageReadItemEx.jsx +42 -0
  38. package/src/EventGQLModel/Pages/PageSubevents.jsx +43 -0
  39. package/src/EventGQLModel/Pages/PageUpdateItem.jsx +14 -0
  40. package/src/EventGQLModel/Pages/PageVector.jsx +80 -0
  41. package/src/EventGQLModel/Pages/RouterSegment.jsx +82 -0
  42. package/src/EventGQLModel/Pages/index.js +2 -0
  43. package/src/EventGQLModel/Queries/DeleteAsyncAction.jsx +32 -0
  44. package/src/EventGQLModel/Queries/Fragments.jsx +123 -0
  45. package/src/EventGQLModel/Queries/InsertAsyncAction.jsx +40 -0
  46. package/src/EventGQLModel/Queries/ReadAsyncAction.jsx +44 -0
  47. package/src/EventGQLModel/Queries/ReadPageAsyncAction.jsx +13 -0
  48. package/src/EventGQLModel/Queries/ReadSubEventsAsyncAction.jsx +44 -0
  49. package/src/EventGQLModel/Queries/SearchAsyncAction.jsx +16 -0
  50. package/src/EventGQLModel/Queries/UpdateAsyncAction.jsx +40 -0
  51. package/src/EventGQLModel/Queries/index.js +6 -0
  52. package/src/EventGQLModel/Scalars/ScalarAttribute.jsx +54 -0
  53. package/src/EventGQLModel/Scalars/TemplateScalarAttribute.jsx +88 -0
  54. package/src/EventGQLModel/Scalars/index.js +1 -0
  55. package/src/EventGQLModel/Vectors/TemplateVectorsAttribute.jsx +326 -0
  56. package/src/EventGQLModel/Vectors/VectorAttribute.jsx +56 -0
  57. package/src/EventGQLModel/Vectors/index.js +1 -0
  58. package/src/EventGQLModel/WhatToDo.md +44 -0
  59. package/src/EventGQLModel/index.js +71 -0
  60. package/src/GroupGQLModel/Mutations/Create.jsx +8 -2
  61. package/src/GroupGQLModel/Mutations/Delete.jsx +8 -2
  62. package/src/GroupGQLModel/Mutations/Update.jsx +8 -8
  63. package/src/GroupGQLModel/Queries/Fragments.jsx +17 -1
  64. package/src/GroupGQLModel/Scalars/RBACObject.jsx +17 -5
  65. package/src/GroupGQLModel/Vectors/GroupMemberships.jsx +1 -1
  66. package/src/UserGQLModel/Components/MediumContent.jsx +9 -3
  67. package/src/UserGQLModel/Queries/Fragments.jsx +6 -0
  68. package/src/_Template/WhatToDo.md +1 -1
@@ -0,0 +1,470 @@
1
+ import { useCallback, useMemo, useState } from "react"
2
+ import {
3
+ assignEventsToWeeks,
4
+ buildAcademicWeeks,
5
+ formatDate,
6
+ formatWeek,
7
+ getContrastTextColor,
8
+ normalizeEvent,
9
+ rangesIntersect,
10
+ toIsoDate
11
+ } from "./_utils"
12
+ import { applyIntervalTransition, diffEvents } from "./calendarReducer"
13
+
14
+
15
+ /**
16
+ * Kontejner pro horizontální layout týdnů (scrollovatelný grid).
17
+ *
18
+ * Používá flexbox:
19
+ * - děti jsou řazeny v řádku
20
+ * - umožňuje horizontální scroll (`overflow-auto`)
21
+ *
22
+ * Vhodné pro:
23
+ * - hlavičku týdnů (WeekHeader)
24
+ * - řádky planneru (PlanRow)
25
+ *
26
+ * @param {{
27
+ * children: React.ReactNode
28
+ * }} props
29
+ *
30
+ * @returns {JSX.Element}
31
+ *
32
+ * @example
33
+ * <WeekGrid>
34
+ * <WeekGridItem />
35
+ * <WeekGridItem />
36
+ * </WeekGrid>
37
+ */
38
+ export const WeekGrid = ({ children }) => {
39
+ return (
40
+ <div
41
+ className="d-flex gap-1 overflow-auto"
42
+ style={{
43
+ width: "100%",
44
+ paddingBottom: "4px"
45
+ }}
46
+ >
47
+ {children}
48
+ </div>
49
+ )
50
+ }
51
+
52
+ /**
53
+ * Základní buňka gridu pro jeden týden.
54
+ *
55
+ * Poskytuje:
56
+ * - pevnou minimální šířku a výšku
57
+ * - relativní pozici (pro absolutně pozicované prvky uvnitř)
58
+ * - tooltip s informacemi o týdnu
59
+ *
60
+ * Slouží jako wrapper pro:
61
+ * - ClickableBox (interaktivní obsah)
62
+ * - WeekHeader (popisky týdne)
63
+ *
64
+ * @param {{
65
+ * children: React.ReactNode,
66
+ * week: {
67
+ * labelFull: string,
68
+ * firstHalfStart: Date,
69
+ * firstHalfEnd: Date,
70
+ * secondHalfStart: Date,
71
+ * secondHalfEnd: Date
72
+ * }
73
+ * }} props
74
+ *
75
+ * @returns {JSX.Element}
76
+ *
77
+ * @example
78
+ * <WeekGridItem week={week}>
79
+ * <ClickableBox ... />
80
+ * </WeekGridItem>
81
+ */
82
+ export const WeekGridItem = ({ children, title, minWidth = 40, height = 69 }) => {
83
+ return (
84
+ <div
85
+ className="position-relative border rounded overflow-hidden user-select-none"
86
+ style={{
87
+ minWidth: `${minWidth}px`,
88
+ height: `${height}px`,
89
+ textAlign: "center",
90
+ flex: "1 0 auto"
91
+ }}
92
+ title={title}
93
+ >
94
+ {children}
95
+ </div>
96
+ )
97
+ }
98
+
99
+ const academicYear_ = {
100
+ year: 2023,
101
+ startWeek: 35,
102
+ endWeek: 42
103
+ }
104
+ const GREY = "#777777";
105
+ export const WeekHeaderContent = ({ academicYear = academicYear_ }) => {
106
+ const { year: academicStartYear, startWeek, endWeek } = academicYear
107
+ const weeks = useMemo(
108
+ () => buildAcademicWeeks(academicStartYear, startWeek, endWeek),
109
+ [academicYear]
110
+ );
111
+ // return <div>hi</div>
112
+ const link = () => "#"
113
+ return (
114
+ <>
115
+ {weeks.map((week) => (
116
+ <WeekGridItem
117
+ key={week.id}
118
+ title={`${week.labelFull}\n1. polovina: ${formatDate(week.firstHalfStart)} až ${formatDate(week.firstHalfEnd)}\n2. polovina: ${formatDate(week.secondHalfStart)} až ${formatDate(week.secondHalfEnd)}`}
119
+ >
120
+ <div
121
+ className="position-absolute fw-bold"
122
+ style={{
123
+ left: "3px",
124
+ top: "2px",
125
+ zIndex: 2,
126
+ fontSize: "10px",
127
+ textAlign: "center",
128
+ // lineHeight: 1,
129
+ // color: leftTextColor,
130
+ // pointerEvents: "none"
131
+ }}
132
+ >
133
+ <a href={link(week)}>{week.label}</a><br />
134
+ {formatWeek(week)}
135
+ </div>
136
+ </WeekGridItem>
137
+ ))}
138
+ </>
139
+ )
140
+ }
141
+ export const WeekHeader = ({ academicYear = academicYear_ }) => {
142
+ return (
143
+ <WeekGrid>
144
+ <WeekHeaderContent academicYear={academicYear} />
145
+ </WeekGrid>
146
+ )
147
+ }
148
+
149
+
150
+ const WeekTop = ({ textColor, backgroundColor, text, onClick }) => {
151
+ return (<>
152
+ <div
153
+ className="position-absolute"
154
+ onClick={onClick}
155
+ style={{
156
+ inset: 0,
157
+ backgroundColor: backgroundColor,
158
+ clipPath: "polygon(0 0, 100% 0, 0 100%)",
159
+ cursor: "pointer"
160
+ }}
161
+ />
162
+ <div
163
+ className="position-absolute fw-bold"
164
+ style={{
165
+ left: "3px",
166
+ top: "2px",
167
+ zIndex: 2,
168
+ fontSize: "10px",
169
+ lineHeight: 1,
170
+ color: textColor,
171
+ pointerEvents: "none"
172
+ }}
173
+ >
174
+ {text}
175
+ </div>
176
+ </>)
177
+ }
178
+
179
+ const WeekBottom = ({ textColor, backgroundColor, text, onClick }) => {
180
+ return (<>
181
+ <div
182
+ className="position-absolute"
183
+ onClick={onClick}
184
+ style={{
185
+ inset: 0,
186
+ backgroundColor: backgroundColor,
187
+ clipPath: "polygon(100% 0, 100% 100%, 0 100%)",
188
+ cursor: "pointer"
189
+ }}
190
+ />
191
+
192
+ <div
193
+ className="position-absolute fw-bold text-end"
194
+ style={{
195
+ right: "3px",
196
+ bottom: "2px",
197
+ zIndex: 2,
198
+ fontSize: "10px",
199
+ lineHeight: 1,
200
+ color: textColor,
201
+ pointerEvents: "none"
202
+ }}
203
+ >
204
+ {text}
205
+ </div>
206
+ </>)
207
+ }
208
+
209
+ const ClickableBox = ({ week, height = 46, onClick = () => null }) => {
210
+ const weekEvents = [...week.firstHalfEvents, ...week.secondHalfEvents];
211
+
212
+ const topEvent = weekEvents.find(
213
+ event => rangesIntersect(
214
+ week.firstHalfStart,
215
+ week.firstHalfEnd,
216
+ event.startDateObj,
217
+ event.endDateObj
218
+ )
219
+ );
220
+
221
+ const bottomEvent = weekEvents.find(
222
+ event => rangesIntersect(
223
+ week.secondHalfStart,
224
+ week.secondHalfEnd,
225
+ event.startDateObj,
226
+ event.endDateObj
227
+ )
228
+ );
229
+
230
+ const topColor = topEvent?.type?.color || GREY;
231
+ const bottomColor = bottomEvent?.type?.color || GREY;
232
+ const topText = topEvent?.type?.abbreviation || "";
233
+ const bottomText = bottomEvent?.type?.abbreviation || "";
234
+
235
+ const topTextColor = getContrastTextColor(topColor);
236
+ const bottomTextColor = getContrastTextColor(bottomColor);
237
+
238
+ const handleHalfClick = (half) => {
239
+ const isTop = half === "top";
240
+
241
+ onClick({
242
+ half,
243
+ weekId: week.id,
244
+ startDate: toIsoDate(isTop ? week.firstHalfStart : week.secondHalfStart),
245
+ endDate: toIsoDate(isTop ? week.firstHalfEnd : week.secondHalfEnd),
246
+ event: isTop ? topEvent : bottomEvent
247
+ });
248
+ };
249
+
250
+ return (
251
+ <WeekGridItem
252
+ height={height}
253
+ key={week.id}
254
+ title={`${week.labelFull}\n1. polovina: ${formatDate(week.firstHalfStart)} až ${formatDate(week.firstHalfEnd)}\n2. polovina: ${formatDate(week.secondHalfStart)} až ${formatDate(week.secondHalfEnd)}`}
255
+ >
256
+ <FlexContainer>
257
+ <FlexRow>
258
+ <WeekTop
259
+ textColor={topTextColor}
260
+ backgroundColor={topColor}
261
+ text={topText}
262
+ onClick={() => handleHalfClick("top")}
263
+ />
264
+ <WeekBottom
265
+ textColor={bottomTextColor}
266
+ backgroundColor={bottomColor}
267
+ text={bottomText}
268
+ onClick={() => handleHalfClick("bottom")}
269
+ />
270
+ </FlexRow>
271
+ </FlexContainer>
272
+ </WeekGridItem>
273
+ );
274
+ };
275
+
276
+ const eventTypes = [
277
+ { id: "69ec2b0b-a39d-40df-9cea-e295b36749c9", name: "Semestr", abbreviation: "S", color: "#7f7f7f" },
278
+
279
+ { id: "ac3238a2-a3ca-4f4b-a56b-8ac7c3953aff", name: "výuka - zimní semestr", abbreviation: "V-ZS", color: "#ffffff" },
280
+ { id: "78a6f015-b8f4-49c8-b218-7861454cb8e9", name: "výuka - letní semestr", abbreviation: "V-LS", color: "#ffffff" },
281
+
282
+ { id: "6fef77f1-a580-4e13-a088-30368a95af2f", name: "řádná dovolená", abbreviation: "ŘD", color: "#d9a441" },
283
+ { id: "7e4187e5-b219-4332-b50e-54411541bba6", name: "zkouškové období", abbreviation: "Z", color: "#f28c28" },
284
+ { id: "79c71457-b926-449a-b227-a3461bf1df4c", name: "příprava v poli - teorie", abbreviation: "PT", color: "#8bc34a" },
285
+ { id: "539c45b2-629c-43ca-87cb-db753e7bbab9", name: "příprava v poli - praxe", abbreviation: "PP", color: "#7cb342" },
286
+ { id: "83d0a942-6456-4f35-bce8-37c9a08cb966", name: "rezerva", abbreviation: "R", color: "#ff1f1f" },
287
+
288
+ { id: "5f147c3c-b307-4334-a339-6f4c83f1dad7", name: "intenzivní kurz AJ", abbreviation: "AJ", color: "#ffff33" },
289
+ { id: "48ff1e53-83c8-48d6-84e6-b63e4f5fa60d", name: "kurz TV", abbreviation: "TV", color: "#7a6000" },
290
+ { id: "1af8ea7e-b280-4202-8efe-1df22763081e", name: "odborná praxe", abbreviation: "OP", color: "#bdbdbd" },
291
+ { id: "0b624b6c-ed72-4316-b070-8e09f73e531c", name: "stáž na systematizovaném místě", abbreviation: "SSM", color: "#d9d9d9" },
292
+ { id: "cd309340-cae0-4ec2-8adc-f4c61dc5f023", name: "letecká AJ", abbreviation: "LA", color: "#ffff66" },
293
+ { id: "0f5713ca-7134-4c6a-99a0-7166c689dbfb", name: "vyřazení", abbreviation: "V", color: "#ff1f1f" },
294
+ { id: "7edccbad-3c70-49ec-b5ca-b744bec8d234", name: "aplikované vojenské technologie", abbreviation: "AVT", color: "#00b0f0" },
295
+
296
+ { id: "d9494b47-b46c-4ba4-a900-1443751cabce", name: "příprava na SZZ", abbreviation: "PSZZ", color: "#a64ac9" },
297
+ { id: "eb86a21b-04e2-419c-a3a3-5f21603d349b", name: "SZZ", abbreviation: "SZZ", color: "#8e24aa" },
298
+ { id: "4d0d3e3f-4d45-4fc4-95f5-612905cf5823", name: "kurz SERE, případně LV, CANI, PARA", abbreviation: "SERE", color: "#9fd3f2" },
299
+ { id: "4560fca2-533b-45c1-945c-16dcf0935126", name: "zkouškové období / letecký výcvik", abbreviation: "ŘD/LV", color: "#d4af37" },
300
+ { id: "3d5c8255-9abc-4d3e-aa25-b3984e5ce1a1", name: "diplomový projekt", abbreviation: "DP", color: "#d9d9d9" }
301
+ ];
302
+
303
+ const events_ = [
304
+ { id: "8969d826-bb56-4a26-b3cd-82554cee8897", startDate: "2025-08-25", endDate: "2025-12-21", typeId: "ac3238a2-a3ca-4f4b-a56b-8ac7c3953aff" },
305
+ { id: "d6ad5dad-43d8-4ccf-b4bc-35fe30357f1e", startDate: "2025-12-22", endDate: "2026-01-04", typeId: "6fef77f1-a580-4e13-a088-30368a95af2f" },
306
+ { id: "e3e44b78-aa94-49a8-a0e9-a0cb73a3f871", startDate: "2026-01-05", endDate: "2026-01-25", typeId: "7e4187e5-b219-4332-b50e-54411541bba6" },
307
+ { id: "c689f2bd-0703-42f7-a456-1f5eaa6dc968", startDate: "2026-02-02", endDate: "2026-03-01", typeId: "78a6f015-b8f4-49c8-b218-7861454cb8e9" },
308
+ { id: "f95385c6-c89e-4a1b-8c45-c039f6ae7253", startDate: "2026-03-02", endDate: "2026-03-22", typeId: "79c71457-b926-449a-b227-a3461bf1df4c" },
309
+ { id: "ede4e6a5-742c-4848-8238-e0de940c6b52", startDate: "2026-03-23", endDate: "2026-04-12", typeId: "539c45b2-629c-43ca-87cb-db753e7bbab9" },
310
+ { id: "ca98c5bd-657c-474a-a1d8-334189fce221", startDate: "2026-04-13", endDate: "2026-05-17", typeId: "d9494b47-b46c-4ba4-a900-1443751cabce" },
311
+ { id: "1d96403b-c421-4d3b-9123-54368789273a", startDate: "2026-05-18", endDate: "2026-05-31", typeId: "eb86a21b-04e2-419c-a3a3-5f21603d349b" },
312
+ { id: "ac437892-86d5-495d-bd0d-7e511c777c5d", startDate: "2026-06-01", endDate: "2026-06-21", typeId: "4d0d3e3f-4d45-4fc4-95f5-612905cf5823" },
313
+ { id: "b6b0288d-ddbf-48a7-8aa7-060215cf9dd2", startDate: "2026-06-22", endDate: "2026-09-20", typeId: "3d5c8255-9abc-4d3e-aa25-b3984e5ce1a1" }
314
+ ];
315
+
316
+ const enrichEvent = (event) => {
317
+ return {
318
+ ...event,
319
+ "type": {
320
+ ...eventTypes.find(t => t.id === event.typeId)
321
+ }
322
+ }
323
+ }
324
+
325
+ export const FlexRow = ({ children }) => {
326
+ return (
327
+ <div
328
+ className="d-flex gap-1 align-items-center"
329
+ style={{ flexWrap: "nowrap" }}
330
+ >
331
+ {children}
332
+ </div>
333
+ )
334
+ }
335
+
336
+ export const FlexContainer = ({ children }) => {
337
+ return (
338
+ <div
339
+ style={{
340
+ overflowX: "auto",
341
+ overflowY: "hidden",
342
+ width: "100%",
343
+ }}
344
+ >
345
+ <div
346
+ style={{
347
+ display: "inline-flex",
348
+ flexDirection: "column",
349
+ gap: "4px",
350
+ minWidth: "max-content",
351
+ }}
352
+ >
353
+ {children}
354
+ </div>
355
+ </div>
356
+ )
357
+ }
358
+
359
+ export const Tools = ({ selected, onSelect }) => {
360
+ return (<>
361
+ {eventTypes.map(current_tool => (
362
+ <button
363
+ key={current_tool.id}
364
+ style={{
365
+ backgroundColor: current_tool?.color,
366
+ border: current_tool == selected ? "3px solid black" : null
367
+ }}
368
+ className="btn btn-sm"
369
+ onClick={() => onSelect(current_tool)}
370
+ >
371
+ {current_tool.name}
372
+ </button>
373
+ ))}
374
+ </>)
375
+ }
376
+
377
+ export const PlanRow = ({ item, academicYear = academicYear_ }) => {
378
+ //predpoklada se, ze item ma subevents, pokud ne, tak se pouziji demodata, nevhodne pro produkci
379
+ const { subevents = events_ } = item
380
+ // const subevents=events_
381
+ //dekompozice akademickeho roku,
382
+ // const { year: academicStartYear, startWeek, endWeek } = academicYear;
383
+ const { year: academicStartYear, startWeek, endWeek } = {
384
+ year: 2025,
385
+ startWeek: 35,
386
+ endWeek: 42
387
+ }
388
+
389
+ //tydny dopocitane z akademickeho roku, automaticky se porepocitaji pri zmene pouzitych parametru
390
+ const weeks = useMemo(
391
+ () => buildAcademicWeeks(academicStartYear, startWeek, endWeek),
392
+ [academicStartYear, startWeek, endWeek]
393
+ );
394
+
395
+ //aktualni nastroj pro klikani
396
+ const [tool, setTool] = useState(eventTypes.find(t => t?.id === "7e4187e5-b219-4332-b50e-54411541bba6"))
397
+ //poznamenani si udalosti, jejich datove osetreni, toto je lokalni cache
398
+ const [events, setEvents] = useState(subevents.map(normalizeEvent));
399
+
400
+ //prirazeni udalosti tydnum, pozor na multiplicity
401
+ const weeksWithEvents = useMemo(
402
+ () => assignEventsToWeeks(weeks, events.map(normalizeEvent).map(enrichEvent)),
403
+ [weeks, events]
404
+ );
405
+
406
+ const handleIntervalClick = ({ startDate, endDate }) =>
407
+ setEvents(prev => applyIntervalTransition(prev, startDate, endDate, tool));
408
+
409
+ // poznamenani si udalosti, jejich datove osetreni
410
+ // diff(old, events) je definice neulozenych zmen
411
+ // lze proves debounced synchronizaci s backendem, tj. bezpecne ulozeni dat na server
412
+ // po ulozeni se nactena data pomoci setOld tady aktualizuji,
413
+ // zmeny udelane behem teto aktualizace je mozne opet ulozit
414
+ const [old, setOld] = useState([...events])
415
+ const { added, updated, deleted } = diffEvents(old, events)
416
+
417
+ return (<>
418
+ <FlexContainer>
419
+ <FlexRow>
420
+ <Tools selected={tool} onSelect={setTool} />
421
+ </FlexRow>
422
+ <FlexRow>
423
+ <WeekHeaderContent academicYear={academicYear} />
424
+ </FlexRow>
425
+
426
+ <FlexRow>
427
+ {weeksWithEvents.map((week) => (
428
+ <ClickableBox
429
+ key={week.id}
430
+ week={week}
431
+ onClick={handleIntervalClick}
432
+ />
433
+ ))}
434
+ </FlexRow>
435
+
436
+ </FlexContainer>
437
+
438
+ <pre>
439
+ <b>inserted</b><br />
440
+ {JSON.stringify(added, null, 2)}
441
+ </pre>
442
+
443
+ <pre>
444
+ <b>updated</b><br />
445
+ {JSON.stringify(updated, null, 2)}
446
+ </pre>
447
+
448
+ <pre>
449
+ <b>removed</b><br />
450
+ {JSON.stringify(deleted, null, 2)}
451
+ </pre>
452
+
453
+ {/* <Row>
454
+ <Col>
455
+ <pre>
456
+ <b>Old list</b><br />
457
+ {JSON.stringify(old.map(enrichEvent), null, 2)}
458
+ </pre>
459
+ </Col>
460
+ <Col>
461
+ <pre>
462
+ <b>New list</b><br />
463
+ {JSON.stringify(events.map(enrichEvent), null, 2)}
464
+ </pre>
465
+ </Col>
466
+ </Row> */}
467
+
468
+ </>
469
+ );
470
+ };