@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,535 @@
1
+ /**
2
+ * calendarReducer.js
3
+ * Minimalistický reducer pro klikací rozvrh nad intervaly v celých dnech
4
+ */
5
+
6
+ /**
7
+ * @typedef {Object} Event
8
+ * @property {string} id
9
+ * @property {string} startDate - YYYY-MM-DD
10
+ * @property {string} endDate - YYYY-MM-DD
11
+ * @property {string} typeId
12
+ */
13
+
14
+ /**
15
+ * @typedef {Object} SelectedType
16
+ * @property {string} id
17
+ */
18
+
19
+ /**
20
+ * @typedef {Object} State
21
+ * @property {Event[]} events
22
+ * @property {SelectedType|null} selectedType
23
+ */
24
+
25
+ /**
26
+ * Vrátí datum posunuté o zadaný počet dní.
27
+ * @param {string} dayStr - Datum ve formátu YYYY-MM-DD.
28
+ * @param {number} amount - Počet dní k posunutí.
29
+ * @returns {string}
30
+ */
31
+ const addDays = (dayStr, amount) => {
32
+ const d = new Date(dayStr + "T00:00:00");
33
+ d.setDate(d.getDate() + amount);
34
+ const y = d.getFullYear();
35
+ const m = String(d.getMonth() + 1).padStart(2, "0");
36
+ const day = String(d.getDate()).padStart(2, "0");
37
+ return `${y}-${m}-${day}`;
38
+ };
39
+
40
+ /**
41
+ * Vrátí interval ve správném pořadí od menšího do většího data.
42
+ * @param {string} startDate
43
+ * @param {string} endDate
44
+ * @returns {{startDate: string, endDate: string}}
45
+ */
46
+ export const normalizeRange = (startDate, endDate) => {
47
+ return startDate <= endDate
48
+ ? { startDate, endDate }
49
+ : { startDate: endDate, endDate: startDate };
50
+ };
51
+
52
+ /**
53
+ * Zjistí, zda se event překrývá s intervalem.
54
+ * @param {Event} event
55
+ * @param {string} startDate
56
+ * @param {string} endDate
57
+ * @returns {boolean}
58
+ */
59
+ export const overlapsRange = (event, startDate, endDate) => {
60
+ return event.startDate <= endDate && startDate <= event.endDate;
61
+ };
62
+
63
+ /**
64
+ * Zjistí, zda event plně obsahuje interval.
65
+ * @param {Event} event
66
+ * @param {string} startDate
67
+ * @param {string} endDate
68
+ * @returns {boolean}
69
+ */
70
+ export const containsRange = (event, startDate, endDate) => {
71
+ return event.startDate <= startDate && endDate <= event.endDate;
72
+ };
73
+
74
+ /**
75
+ * Vytvoří nové unikátní ID.
76
+ * @returns {string}
77
+ */
78
+ export const makeId = () => crypto.randomUUID();
79
+
80
+ /**
81
+ * Seřadí eventy podle začátku a konce.
82
+ * @param {Event[]} events
83
+ * @returns {Event[]}
84
+ */
85
+ export const sortEvents = (events) => {
86
+ return [...events].sort((a, b) => {
87
+ if (a.startDate !== b.startDate) {
88
+ return a.startDate.localeCompare(b.startDate);
89
+ }
90
+ return a.endDate.localeCompare(b.endDate);
91
+ });
92
+ };
93
+
94
+ /**
95
+ * Odebere z eventu zadaný interval.
96
+ * Může vrátit:
97
+ * - []
98
+ * - [event]
99
+ * - [leftPart, rightPart]
100
+ *
101
+ * @param {Event} event
102
+ * @param {string} startDate
103
+ * @param {string} endDate
104
+ * @returns {Event[]}
105
+ */
106
+ export const removeRangeFromEvent = (event, startDate, endDate) => {
107
+ if (!overlapsRange(event, startDate, endDate)) {
108
+ return [event];
109
+ }
110
+
111
+ const cutsLeft = startDate <= event.startDate;
112
+ const cutsRight = endDate >= event.endDate;
113
+
114
+ // interval smaže celý event
115
+ if (cutsLeft && cutsRight) {
116
+ return [];
117
+ }
118
+
119
+ // ukrojí levou část eventu
120
+ if (cutsLeft) {
121
+ return [
122
+ {
123
+ ...event,
124
+ startDate: addDays(endDate, 1)
125
+ }
126
+ ];
127
+ }
128
+
129
+ // ukrojí pravou část eventu
130
+ if (cutsRight) {
131
+ return [
132
+ {
133
+ ...event,
134
+ endDate: addDays(startDate, -1)
135
+ }
136
+ ];
137
+ }
138
+
139
+ // interval je uvnitř eventu => rozdělení na dva eventy
140
+ return [
141
+ {
142
+ ...event,
143
+ endDate: addDays(startDate, -1)
144
+ },
145
+ {
146
+ ...event,
147
+ id: makeId(),
148
+ startDate: addDays(endDate, 1)
149
+ }
150
+ ];
151
+ };
152
+
153
+ /**
154
+ * Sloučí sousední nebo překrývající se eventy stejného typu
155
+ * a zachová stabilní identitu původních eventů.
156
+ *
157
+ * Pokud se spojuje původní event a synteticky vytvořený event,
158
+ * přednost dostane ID původního eventu.
159
+ *
160
+ * Neměněné eventy vrací ve stejné referenci.
161
+ *
162
+ * @param {Event[]} events
163
+ * @returns {Event[]}
164
+ */
165
+ export const normalizeEvents = (events) => {
166
+ const sorted = sortEvents(events);
167
+ const result = [];
168
+
169
+ for (const event of sorted) {
170
+ const last = result[result.length - 1];
171
+
172
+ if (
173
+ last &&
174
+ last.typeId === event.typeId &&
175
+ addDays(last.endDate, 1) >= event.startDate
176
+ ) {
177
+ const lastSynthetic = !!last._isSynthetic;
178
+ const eventSynthetic = !!event._isSynthetic;
179
+
180
+ const nextId =
181
+ lastSynthetic && !eventSynthetic
182
+ ? event.id
183
+ : last.id;
184
+
185
+ const nextSynthetic =
186
+ lastSynthetic && !eventSynthetic
187
+ ? event._isSynthetic
188
+ : last._isSynthetic;
189
+
190
+ const nextStartDate =
191
+ event.startDate < last.startDate
192
+ ? event.startDate
193
+ : last.startDate;
194
+
195
+ const nextEndDate =
196
+ event.endDate > last.endDate
197
+ ? event.endDate
198
+ : last.endDate;
199
+
200
+ const changed =
201
+ nextId !== last.id ||
202
+ nextSynthetic !== last._isSynthetic ||
203
+ nextStartDate !== last.startDate ||
204
+ nextEndDate !== last.endDate;
205
+
206
+ if (changed) {
207
+ result[result.length - 1] = {
208
+ ...last,
209
+ id: nextId,
210
+ startDate: nextStartDate,
211
+ endDate: nextEndDate,
212
+ _isSynthetic: nextSynthetic
213
+ };
214
+ }
215
+ } else {
216
+ result.push(event);
217
+ }
218
+ }
219
+
220
+ let hasSynthetic = false;
221
+ for (const event of result) {
222
+ if (event._isSynthetic !== undefined) {
223
+ hasSynthetic = true;
224
+ break;
225
+ }
226
+ }
227
+
228
+ if (!hasSynthetic) {
229
+ return result;
230
+ }
231
+
232
+ return result.map((event) => {
233
+ if (event._isSynthetic === undefined) {
234
+ return event;
235
+ }
236
+
237
+ const { _isSynthetic, ...cleanEvent } = event;
238
+ return cleanEvent;
239
+ });
240
+ };
241
+
242
+ /**
243
+ * Vrátí průnik dvou intervalů.
244
+ *
245
+ * @param {{startDate: string, endDate: string}} a
246
+ * @param {{startDate: string, endDate: string}} b
247
+ * @returns {{startDate: string, endDate: string} | null}
248
+ */
249
+ export const intersectRanges = (a, b) => {
250
+ const startDate = a.startDate > b.startDate ? a.startDate : b.startDate;
251
+ const endDate = a.endDate < b.endDate ? a.endDate : b.endDate;
252
+
253
+ if (startDate > endDate) {
254
+ return null;
255
+ }
256
+
257
+ return { startDate, endDate };
258
+ };
259
+
260
+ /**
261
+ * Odečte jeden interval od jednoho segmentu.
262
+ *
263
+ * @param {{startDate: string, endDate: string}} segment
264
+ * @param {{startDate: string, endDate: string}} cut
265
+ * @returns {{startDate: string, endDate: string}[]}
266
+ */
267
+ export const subtractRange = (segment, cut) => {
268
+ const intersection = intersectRanges(segment, cut);
269
+
270
+ if (!intersection) {
271
+ return [segment];
272
+ }
273
+
274
+ const result = [];
275
+
276
+ if (segment.startDate < intersection.startDate) {
277
+ result.push({
278
+ startDate: segment.startDate,
279
+ endDate: addDays(intersection.startDate, -1)
280
+ });
281
+ }
282
+
283
+ if (intersection.endDate < segment.endDate) {
284
+ result.push({
285
+ startDate: addDays(intersection.endDate, 1),
286
+ endDate: segment.endDate
287
+ });
288
+ }
289
+
290
+ return result;
291
+ };
292
+
293
+ /**
294
+ * Odečte více intervalů od jednoho základního intervalu.
295
+ *
296
+ * @param {{startDate: string, endDate: string}} baseRange
297
+ * @param {{startDate: string, endDate: string}[]} cuts
298
+ * @returns {{startDate: string, endDate: string}[]}
299
+ */
300
+ export const subtractRanges = (baseRange, cuts) => {
301
+ let segments = [baseRange];
302
+
303
+ for (const cut of cuts) {
304
+ segments = segments.flatMap((segment) => subtractRange(segment, cut));
305
+ }
306
+
307
+ return segments;
308
+ };
309
+
310
+ /**
311
+ * Aplikuje vybraný typ na interval.
312
+ *
313
+ * Chování:
314
+ * - v částech intervalu, kde už je selectedType, se tento typ odebere
315
+ * - v ostatních částech intervalu se selectedType přidá
316
+ *
317
+ * Nově vytvořené segmenty označuje jako syntetické, aby při merge
318
+ * šlo zachovat ID původních eventů.
319
+ *
320
+ * Neměněné eventy ponechává ve stejné referenci.
321
+ *
322
+ * @param {Event[]} events
323
+ * @param {string} startDate
324
+ * @param {string} endDate
325
+ * @param {{id: string}|null} selectedType
326
+ * @returns {Event[]}
327
+ */
328
+ export const applyIntervalTransition = (
329
+ events,
330
+ startDate,
331
+ endDate,
332
+ selectedType
333
+ ) => {
334
+ if (!selectedType) return events;
335
+
336
+ const range = normalizeRange(startDate, endDate);
337
+
338
+ const selectedTypeOverlaps = events
339
+ .filter(
340
+ (event) =>
341
+ event.typeId === selectedType.id &&
342
+ overlapsRange(event, range.startDate, range.endDate)
343
+ )
344
+ .map((event) =>
345
+ intersectRanges(
346
+ { startDate: event.startDate, endDate: event.endDate },
347
+ range
348
+ )
349
+ )
350
+ .filter(Boolean);
351
+
352
+ const rangesToAdd = subtractRanges(range, selectedTypeOverlaps);
353
+
354
+ const withoutClickedRange = events.flatMap((event) => {
355
+ const parts = removeRangeFromEvent(
356
+ event,
357
+ range.startDate,
358
+ range.endDate
359
+ );
360
+
361
+ if (parts.length === 1 && parts[0] === event) {
362
+ return parts;
363
+ }
364
+
365
+ if (event._isSynthetic === undefined) {
366
+ return parts;
367
+ }
368
+
369
+ return parts.map((part) => {
370
+ if (part._isSynthetic === event._isSynthetic) {
371
+ return part;
372
+ }
373
+
374
+ return {
375
+ ...part,
376
+ _isSynthetic: event._isSynthetic
377
+ };
378
+ });
379
+ });
380
+
381
+ if (rangesToAdd.length === 0 && withoutClickedRange.length === events.length) {
382
+ let same = true;
383
+ for (let i = 0; i < events.length; i++) {
384
+ if (events[i] !== withoutClickedRange[i]) {
385
+ same = false;
386
+ break;
387
+ }
388
+ }
389
+ if (same) {
390
+ return events;
391
+ }
392
+ }
393
+
394
+ const next = [
395
+ ...withoutClickedRange,
396
+ ...rangesToAdd.map((rangeToAdd) => ({
397
+ id: makeId(),
398
+ startDate: rangeToAdd.startDate,
399
+ endDate: rangeToAdd.endDate,
400
+ typeId: selectedType.id,
401
+ _isSynthetic: true
402
+ }))
403
+ ];
404
+
405
+ return normalizeEvents(next);
406
+ };
407
+
408
+ /**
409
+ * Výchozí stav reduceru.
410
+ * @type {State}
411
+ */
412
+ export const initialState = {
413
+ events: [],
414
+ selectedType: null
415
+ };
416
+
417
+ /**
418
+ * Reducer kompatibilní s Redux i React useReducer.
419
+ *
420
+ * Akce:
421
+ * - SET_SELECTED_TYPE
422
+ * - SET_EVENTS
423
+ * - APPLY_INTERVAL
424
+ *
425
+ * @param {State} state
426
+ * @param {{type: string, payload?: any}} action
427
+ * @returns {State}
428
+ */
429
+ export const calendarReducer = (state = initialState, action) => {
430
+ switch (action.type) {
431
+ case "SET_SELECTED_TYPE":
432
+ return {
433
+ ...state,
434
+ selectedType: action.payload
435
+ };
436
+
437
+ case "SET_EVENTS":
438
+ return {
439
+ ...state,
440
+ events: normalizeEvents(action.payload)
441
+ };
442
+
443
+ case "APPLY_INTERVAL":
444
+ return {
445
+ ...state,
446
+ events: applyIntervalTransition(
447
+ state.events,
448
+ action.payload.startDate,
449
+ action.payload.endDate,
450
+ state.selectedType
451
+ )
452
+ };
453
+
454
+ default:
455
+ return state;
456
+ }
457
+ };
458
+
459
+ /**
460
+ * @typedef {Object} Event
461
+ * @property {string} id
462
+ * @property {string} startDate - Datum začátku ve formátu YYYY-MM-DD
463
+ * @property {string} endDate - Datum konce ve formátu YYYY-MM-DD
464
+ * @property {string} typeId - Identifikátor typu eventu
465
+ */
466
+
467
+ /**
468
+ * @typedef {Object} UpdatedEvent
469
+ * @property {Event} before - Původní verze eventu
470
+ * @property {Event} after - Nová verze eventu
471
+ */
472
+
473
+ /**
474
+ * @typedef {Object} EventsDiff
475
+ * @property {Event[]} added - Eventy, které jsou nové (existují pouze v nextEvents)
476
+ * @property {UpdatedEvent[]} updated - Eventy, které existují v obou stavech,
477
+ * ale změnily některé vlastnosti (startDate, endDate, typeId)
478
+ * @property {Event[]} deleted - Eventy, které byly odstraněny (existují pouze v prevEvents)
479
+ */
480
+
481
+ /**
482
+ * Porovná dva seznamy eventů a vrátí rozdíly ve formě přidaných,
483
+ * upravených a smazaných eventů.
484
+ *
485
+ * Porovnání probíhá podle `id`:
486
+ * - pokud `id` existuje pouze v `nextEvents` → event je přidaný (`added`)
487
+ * - pokud `id` existuje pouze v `prevEvents` → event je smazaný (`deleted`)
488
+ * - pokud `id` existuje v obou, ale změnily se hodnoty
489
+ * (`startDate`, `endDate`, `typeId`) → event je upravený (`updated`)
490
+ *
491
+ * Funkce NEPOROVNÁVÁ jiné vlastnosti než:
492
+ * - startDate
493
+ * - endDate
494
+ * - typeId
495
+ *
496
+ * @param {Event[]} prevEvents - Předchozí stav eventů
497
+ * @param {Event[]} nextEvents - Nový stav eventů
498
+ * @returns {EventsDiff} Objekt obsahující seznam přidaných, upravených a smazaných eventů
499
+ */
500
+ export const diffEvents = (prevEvents, nextEvents) => {
501
+ const prevMap = new Map(prevEvents.map(e => [e.id, e]));
502
+ const nextMap = new Map(nextEvents.map(e => [e.id, e]));
503
+
504
+ const added = [];
505
+ const updated = [];
506
+ const deleted = [];
507
+
508
+ for (const [id, prev] of prevMap) {
509
+ const next = nextMap.get(id);
510
+
511
+ if (!next) {
512
+ deleted.push(prev);
513
+ continue;
514
+ }
515
+
516
+ if (
517
+ prev.startDate !== next.startDate ||
518
+ prev.endDate !== next.endDate ||
519
+ prev.typeId !== next.typeId
520
+ ) {
521
+ updated.push({
522
+ before: prev,
523
+ after: next
524
+ });
525
+ }
526
+ }
527
+
528
+ for (const [id, next] of nextMap) {
529
+ if (!prevMap.has(id)) {
530
+ added.push(next);
531
+ }
532
+ }
533
+
534
+ return { added, updated, deleted };
535
+ };
@@ -0,0 +1,3 @@
1
+ export * from './PlanRow'
2
+ export * from './_utils'
3
+ export * from './calendarReducer'
@@ -0,0 +1,7 @@
1
+ import { Table as BaseTable } from "../../../../_template/src/Base/Components/Table"
2
+
3
+ export const Table = ({ data }) => {
4
+ return (
5
+ <BaseTable data={data} />
6
+ )
7
+ }
@@ -0,0 +1,15 @@
1
+
2
+ export * from './CardCapsule'
3
+ export * from './Children'
4
+ export * from './LargeCard'
5
+ export * from './Link'
6
+ export * from './MediumContent'
7
+ export * from './MediumCard'
8
+
9
+ export * from './MediumEditableContent'
10
+ export * from './LiveEdit'
11
+
12
+
13
+ export * from './ConfirmEdit'
14
+
15
+ export * from './Plan'