@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hrbolek/uoisfrontend-template",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,23 +1,28 @@
1
1
  // import { URIRoot } from "../../uriroot";
2
2
  import { ProxyLink } from "./ProxyLink";
3
3
 
4
- const RegisterofLinks = {};
5
- export const registerLink = (__typename, Link) => {
6
- const registeredLink = RegisterofLinks[__typename];
7
- if (!registeredLink) {
8
- RegisterofLinks[__typename] = Link;
4
+ const RegisterOfLinks = {};
5
+ export const registerLink = (__typename, Link, overrideLinkURI) => {
6
+
7
+ const Link_ = overrideLinkURI
8
+ ? ({ ...props }) => <Link {...props} LinkURI={overrideLinkURI} />
9
+ : Link;
10
+
11
+ const registeredLink = RegisterOfLinks[__typename];
12
+
13
+ if (!registeredLink || overrideLinkURI) {
14
+ RegisterOfLinks[__typename] = Link_;
9
15
  } else {
10
- // throw new Error(`Link for typename ${__typename} is already registered.`);
11
16
  console.warn(`Link for typename ${__typename} is already registered.`);
12
17
  }
13
- }
18
+ };
14
19
 
15
20
  export const GenericURIRoot = "/generic";
16
21
  export const LinkURI = GenericURIRoot + "/view/";
17
22
  export const VectorItemsURI = GenericURIRoot + "/list/";
18
23
 
19
24
  export const Link = ({ item, action="view", children, ...others }) => {
20
- const SpecificLink = item?.__typename ? RegisterofLinks[item.__typename] : null;
25
+ const SpecificLink = item?.__typename ? RegisterOfLinks[item.__typename] : null;
21
26
  if (SpecificLink && SpecificLink !== Link) {
22
27
  // console.log('Using specific link for typename:', item.__typename);
23
28
  return <SpecificLink item={item} action={action} {...others}>{children}</SpecificLink>;
@@ -90,6 +90,7 @@ export const GeneralButton = ({
90
90
  rbacitem={rbacitem}
91
91
  onOk={handleOk}
92
92
  onCancel={handleCancel}
93
+ title={JSON.stringify(oneOfRoles)}
93
94
  {...props}
94
95
  />
95
96
  </PermissionGate>
@@ -233,8 +234,9 @@ export const GeneralButtonBody = ({
233
234
  </>
234
235
  )
235
236
  } else {
237
+ const {title, onClick, ...others} = props
236
238
  return (
237
- <button {...props} disabled><Lock /> {children || "Vytvořit nový"}</button>
239
+ <button {...others} title={title} style={{ opacity: 0.5, pointerEvents: "auto" }}><Lock /> {children || "Vytvořit nový"}</button>
238
240
  )
239
241
 
240
242
  }
@@ -11,6 +11,11 @@ import { GeneralButton, GeneralDialog, GeneralLink } from "./General"
11
11
 
12
12
  export const UpdateURI = makeMutationURI(LinkURI, "edit", { withId: true });
13
13
 
14
+ // naviguje na stranku, kde se da udelat update
15
+ // testuje opravneni
16
+ // - oneOfRoles = ["superadmin"],
17
+ // - mode = "absolute",
18
+ // item musi obsahovat rbacobject s rolemi aktualniho uzivatele
14
19
  export const UpdateLink = ({
15
20
  item,
16
21
  oneOfRoles = ["superadmin"],
@@ -0,0 +1,363 @@
1
+ import { useMemo } from "react";
2
+ import {
3
+ FlexContainer,
4
+ FlexRow,
5
+ WeekHeaderContent,
6
+ WeekGridItem
7
+ } from "../Plan";
8
+ import {
9
+ getContrastTextColor,
10
+ toLocalDate,
11
+ addDays
12
+ } from "../Plan";
13
+
14
+ /* =========================
15
+ KONSTANTY
16
+ ========================= */
17
+
18
+ const DAY_NAMES = ["pondělí", "úterý", "středa", "čtvrtek", "pátek"];
19
+ const ISO_DAY_INDEXES = [1, 2, 3, 4, 5]; // Po..Pá
20
+
21
+ const DAY_START_MINUTES = 8 * 60; // 8:00
22
+ const DAY_END_MINUTES = 18 * 60; // 18:00
23
+ const DAY_TOTAL_MINUTES = DAY_END_MINUTES - DAY_START_MINUTES;
24
+
25
+ const TIME_MARKS = [
26
+ { label: "8:00", minutes: 8 * 60 },
27
+ { label: "9:30", minutes: 9 * 60 + 30 },
28
+ { label: "9:50", minutes: 9 * 60 + 50 },
29
+ { label: "11:20", minutes: 11 * 60 + 20 },
30
+ { label: "11:40", minutes: 11 * 60 + 40 },
31
+ { label: "13:10", minutes: 13 * 60 + 10 },
32
+ { label: "14:30", minutes: 14 * 60 + 30 },
33
+ { label: "16:00", minutes: 16 * 60 + 0 },
34
+ { label: "16:20", minutes: 16 * 60 + 20 },
35
+ { label: "17:50", minutes: 17 * 60 + 50 }
36
+ ];
37
+
38
+ /* =========================
39
+ DATE HELPERS
40
+ ========================= */
41
+
42
+ function toDateTime(value) {
43
+ if (value instanceof Date) return new Date(value.getTime());
44
+ return new Date(value);
45
+ }
46
+
47
+ function startOfDay(dateLike) {
48
+ const d = toDateTime(dateLike);
49
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
50
+ }
51
+
52
+ function endOfDay(dateLike) {
53
+ const d = toDateTime(dateLike);
54
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);
55
+ }
56
+
57
+ function minutesOfDay(date) {
58
+ return date.getHours() * 60 + date.getMinutes();
59
+ }
60
+
61
+ function rangesIntersect(aStart, aEnd, bStart, bEnd) {
62
+ return aStart <= bEnd && bStart <= aEnd;
63
+ }
64
+
65
+ function getISOWeekday(date) {
66
+ const jsDay = date.getDay();
67
+ return jsDay === 0 ? 7 : jsDay;
68
+ }
69
+
70
+ function startOfISOWeek(dateLike) {
71
+ const d = startOfDay(dateLike);
72
+ return addDays(d, 1 - getISOWeekday(d));
73
+ }
74
+
75
+ function pad2(n) {
76
+ return String(n).padStart(2, "0");
77
+ }
78
+
79
+ function getISOWeek(date) {
80
+ const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
81
+ const dayNum = d.getUTCDay() || 7;
82
+ d.setUTCDate(d.getUTCDate() + 4 - dayNum);
83
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
84
+ return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
85
+ }
86
+
87
+ function buildWeeksInRange(startdate, enddate) {
88
+ const start = startOfISOWeek(startdate);
89
+ const end = startOfISOWeek(enddate);
90
+
91
+ const result = [];
92
+ let cursor = new Date(start);
93
+
94
+ while (cursor <= end) {
95
+ const weekStart = new Date(cursor);
96
+ const weekEnd = addDays(weekStart, 6);
97
+
98
+ const isoYear = (() => {
99
+ const d = new Date(Date.UTC(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate()));
100
+ const dayNum = d.getUTCDay() || 7;
101
+ d.setUTCDate(d.getUTCDate() + 4 - dayNum);
102
+ return d.getUTCFullYear();
103
+ })();
104
+
105
+ const isoWeek = getISOWeek(weekStart);
106
+
107
+ result.push({
108
+ id: `${isoYear}-W${String(isoWeek).padStart(2, "0")}`,
109
+ isoYear,
110
+ isoWeek,
111
+ label: `${isoWeek}`,
112
+ labelFull: `${isoYear}/W${isoWeek}`,
113
+ weekStart,
114
+ weekEnd,
115
+ firstHalfStart: weekStart,
116
+ firstHalfEnd: addDays(weekStart, 2),
117
+ secondHalfStart: addDays(weekStart, 3),
118
+ secondHalfEnd: weekEnd
119
+ });
120
+
121
+ cursor = addDays(cursor, 7);
122
+ }
123
+
124
+ return result;
125
+ }
126
+
127
+ function normalizeEvent(event) {
128
+ const startRaw = event?.start ?? event?.startDate ?? event?.startdate;
129
+ const endRaw = event?.end ?? event?.endDate ?? event?.enddate ?? startRaw;
130
+
131
+ const start = toDateTime(startRaw);
132
+ const end = toDateTime(endRaw);
133
+
134
+ if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
135
+ return null;
136
+ }
137
+
138
+ return start <= end
139
+ ? { ...event, startDateTimeObj: start, endDateTimeObj: end }
140
+ : { ...event, startDateTimeObj: end, endDateTimeObj: start };
141
+ }
142
+
143
+ function getWeekDayDate(week, isoDay) {
144
+ return addDays(week.weekStart, isoDay - 1);
145
+ }
146
+
147
+ function getEventColor(event) {
148
+ return event?.color ?? event?.type?.color ?? "#777777";
149
+ }
150
+
151
+ function getEventLabel(event) {
152
+ return event?.abbreviation ?? event?.type?.abbreviation ?? event?.title ?? event?.name ?? "";
153
+ }
154
+
155
+ function clipEventToDay(event, dayDate) {
156
+ const dayStart = startOfDay(dayDate);
157
+ const dayEnd = endOfDay(dayDate);
158
+
159
+ if (!rangesIntersect(event.startDateTimeObj, event.endDateTimeObj, dayStart, dayEnd)) {
160
+ return null;
161
+ }
162
+
163
+ const clippedStart = event.startDateTimeObj < dayStart ? dayStart : event.startDateTimeObj;
164
+ const clippedEnd = event.endDateTimeObj > dayEnd ? dayEnd : event.endDateTimeObj;
165
+
166
+ const startMinutes = Math.max(minutesOfDay(clippedStart), DAY_START_MINUTES);
167
+ const endMinutes = Math.min(minutesOfDay(clippedEnd), DAY_END_MINUTES);
168
+
169
+ if (endMinutes <= DAY_START_MINUTES || startMinutes >= DAY_END_MINUTES || endMinutes <= startMinutes) {
170
+ return null;
171
+ }
172
+
173
+ return {
174
+ ...event,
175
+ clippedStart,
176
+ clippedEnd,
177
+ topPct: ((startMinutes - DAY_START_MINUTES) / DAY_TOTAL_MINUTES) * 100,
178
+ heightPct: ((endMinutes - startMinutes) / DAY_TOTAL_MINUTES) * 100
179
+ };
180
+ }
181
+
182
+ /* =========================
183
+ UI
184
+ ========================= */
185
+
186
+ const DayLabelCell = ({ label, height = 180 }) => {
187
+ return (
188
+ <div
189
+ className="border rounded overflow-hidden d-flex align-items-center justify-content-center user-select-none"
190
+ style={{
191
+ width: "40px",
192
+ minWidth: "40px",
193
+ height: `${height}px`,
194
+ writingMode: "vertical-rl",
195
+ transform: "rotate(180deg)",
196
+ textAlign: "center",
197
+ fontSize: "14px",
198
+ flex: "0 0 auto"
199
+ }}
200
+ >
201
+ {label}
202
+ {/* <TimeMarksLayer /> */}
203
+ </div>
204
+ );
205
+ };
206
+
207
+ const TimeMarksLayer = () => {
208
+ return (
209
+ <>
210
+ {TIME_MARKS.map((mark) => {
211
+ const topPct = ((mark.minutes - DAY_START_MINUTES) / DAY_TOTAL_MINUTES) * 100;
212
+
213
+ return (
214
+ <div
215
+ key={mark.label}
216
+ className="position-absolute"
217
+ style={{
218
+ left: 0,
219
+ right: 0,
220
+ top: `${topPct}%`,
221
+ borderTop: "1px solid #a72727"
222
+ }}
223
+ />
224
+ );
225
+ })}
226
+ </>
227
+ );
228
+ };
229
+
230
+ const DayEventsLayer = ({ events }) => {
231
+ return (
232
+ <>
233
+ {events.map((event, index) => {
234
+ const backgroundColor = getEventColor(event);
235
+ const color = getContrastTextColor(backgroundColor);
236
+
237
+ return (
238
+ <div
239
+ key={event.id ?? `${index}-${event.topPct}`}
240
+ className="position-absolute border rounded overflow-hidden"
241
+ title={getEventLabel(event)}
242
+ style={{
243
+ left: "2px",
244
+ right: "2px",
245
+ top: `${event.topPct}%`,
246
+ height: `${event.heightPct}%`,
247
+ minHeight: "8px",
248
+ backgroundColor,
249
+ color,
250
+ fontSize: "9px",
251
+ lineHeight: 1.1,
252
+ padding: "1px 2px",
253
+ boxSizing: "border-box"
254
+ }}
255
+ >
256
+ {getEventLabel(event)}
257
+ </div>
258
+ );
259
+ })}
260
+ </>
261
+ );
262
+ };
263
+
264
+ /**
265
+ * Náhrada za ClickableBox:
266
+ * jedna buňka = jeden týden + jeden konkrétní den v týdnu
267
+ */
268
+ const DayWeekCell = ({ week, dayIndex, events, height = 240 }) => {
269
+ const dayDate = useMemo(() => getWeekDayDate(week, dayIndex), [week, dayIndex]);
270
+
271
+ const dayEvents = useMemo(() => {
272
+ return events
273
+ .map((event) => clipEventToDay(event, dayDate))
274
+ .filter(Boolean);
275
+ }, [events, dayDate]);
276
+
277
+ return (
278
+ <WeekGridItem
279
+ height={height}
280
+ title={`${DAY_NAMES[dayIndex - 1]} ${dayDate.getDate()}.${dayDate.getMonth() + 1}.${dayDate.getFullYear()}`}
281
+ >
282
+ <div
283
+ className="position-relative"
284
+ style={{
285
+ width: "100%",
286
+ height: "100%",
287
+ backgroundColor: "#fff"
288
+ }}
289
+ >
290
+ <TimeMarksLayer />
291
+ <DayEventsLayer events={dayEvents} />
292
+
293
+ </div>
294
+ </WeekGridItem>
295
+ );
296
+ };
297
+
298
+ const A4PlanDayRow = ({ dayName, dayIndex, weeks, events, rowHeight = 240 }) => {
299
+ return (
300
+ <FlexRow>
301
+ <DayLabelCell label={dayName} height={rowHeight} />
302
+ {weeks.slice(1).map((week) => (
303
+ <DayWeekCell
304
+ key={`${week.id}-${dayIndex}`}
305
+ week={week}
306
+ dayIndex={dayIndex}
307
+ events={events}
308
+ height={rowHeight}
309
+ />
310
+ ))}
311
+ </FlexRow>
312
+ );
313
+ };
314
+
315
+ export const A4Plan = ({ item, startdate, enddate }) => {
316
+ const { subevents:events=[] } = item || {}
317
+ const weeks = useMemo(
318
+ () => buildWeeksInRange(startdate, enddate),
319
+ [startdate, enddate]
320
+ );
321
+
322
+ const normalizedEvents = useMemo(
323
+ () => events.map(normalizeEvent).filter(Boolean),
324
+ [events]
325
+ );
326
+
327
+ const academicYear = useMemo(() => {
328
+ const firstWeek = weeks[0];
329
+ const lastWeek = weeks[weeks.length - 1];
330
+
331
+ if (!firstWeek || !lastWeek) {
332
+ return {
333
+ year: new Date().getFullYear(),
334
+ startWeek: 1,
335
+ endWeek: 1
336
+ };
337
+ }
338
+
339
+ return {
340
+ year: firstWeek.isoYear,
341
+ startWeek: firstWeek.isoWeek,
342
+ endWeek: lastWeek.isoWeek
343
+ };
344
+ }, [weeks]);
345
+
346
+ return (
347
+ <FlexContainer>
348
+ <FlexRow>
349
+ <WeekHeaderContent academicYear={academicYear} />
350
+ </FlexRow>
351
+
352
+ {DAY_NAMES.map((dayName, index) => (
353
+ <A4PlanDayRow
354
+ key={dayName}
355
+ dayName={dayName}
356
+ dayIndex={ISO_DAY_INDEXES[index]}
357
+ weeks={weeks}
358
+ events={normalizedEvents}
359
+ />
360
+ ))}
361
+ </FlexContainer>
362
+ );
363
+ };
@@ -0,0 +1 @@
1
+ export * from './A4Plan'
@@ -0,0 +1,43 @@
1
+ import { PersonFill } from "react-bootstrap-icons"
2
+ import { Link } from "./Link"
3
+ import { CardCapsule as CardCapsule_ } from "../../../../_template/src/Base/Components"
4
+
5
+ /**
6
+ * A specialized card component that displays an `TemplateLink` as its title and encapsulates additional content.
7
+ *
8
+ * This component extends the `CardCapsule` component by using a combination of a `PersonFill` icon and
9
+ * an `TemplateLink` component in the card's header. The `children` prop is used to render any content
10
+ * inside the card body. It is designed for use with entities represented by the `template` object.
11
+ *
12
+ * @component
13
+ * @param {Object} props - The props for the TemplateCardCapsule component.
14
+ * @param {Object} props.template - The object representing the template entity.
15
+ * @param {string|number} props.template.id - The unique identifier for the template entity.
16
+ * @param {string} props.template.name - The display name for the template entity.
17
+ * @param {React.ReactNode} [props.children=null] - The content to render inside the card's body.
18
+ *
19
+ * @returns {JSX.Element} The rendered card component with a dynamic title and body content.
20
+ *
21
+ * @example
22
+ * // Example usage:
23
+ * import { TemplateCardCapsule } from './TemplateCardCapsule';
24
+ * import { Button } from 'react-bootstrap';
25
+ *
26
+ * const templateEntity = { id: 123, name: "Example Entity" };
27
+ *
28
+ * <TemplateCardCapsule template={templateEntity}>
29
+ * <Button variant="primary">Click Me</Button>
30
+ * </TemplateCardCapsule>
31
+ */
32
+ export const CardCapsule = ({ item, children, title=null}) => {
33
+
34
+ if (!title) {
35
+ title = <><PersonFill /> <Link item={item} /></>
36
+ }
37
+ return (
38
+
39
+ <CardCapsule_ title={title}>
40
+ {children}
41
+ </CardCapsule_>
42
+ )
43
+ }
@@ -0,0 +1,31 @@
1
+ import { ChildWrapper } from "@hrbolek/uoisfrontend-shared";
2
+
3
+ /**
4
+ * TemplateChildren Component
5
+ *
6
+ * A utility React component that wraps its children with the `ChildWrapper` component,
7
+ * passing down an `template` entity along with other props to all child elements.
8
+ * This component is useful for injecting a common `template` entity into multiple children
9
+ * while preserving their existing functionality.
10
+ *
11
+ * @component
12
+ * @param {Object} props - The props for the TemplateChildren component.
13
+ * @param {any} props.template - An entity (e.g., object, string, or other data) to be passed to the children.
14
+ * @param {React.ReactNode} props.children - The children elements to be wrapped and enhanced.
15
+ * @param {...any} props - Additional props to be passed to each child element.
16
+ *
17
+ * @returns {JSX.Element} A `ChildWrapper` component containing the children with the injected `template` entity.
18
+ *
19
+ * @example
20
+ * // Example usage:
21
+ * const templateEntity = { id: 1, message: "No data available" };
22
+ *
23
+ * <TemplateChildren template={templateEntity}>
24
+ * <CustomMessage />
25
+ * <CustomIcon />
26
+ * </TemplateChildren>
27
+ *
28
+ * // Result: Both <CustomMessage /> and <CustomIcon /> receive the 'template' prop with the specified entity.
29
+ */
30
+ export const Children = ({item, children, ...props}) =>
31
+ <ChildWrapper item={item} children={children} {...props} />
@@ -0,0 +1,61 @@
1
+ import { UpdateAsyncAction } from "../Queries";
2
+ import { MediumEditableContent } from "./MediumEditableContent";
3
+ import { useEditAction } from "../../../../dynamic/src/Hooks/useEditAction";
4
+ import { useCallback } from "react";
5
+ import { useGQLEntityContext } from "../../../../_template/src/Base/Helpers/GQLEntityProvider";
6
+
7
+
8
+ export const ConfirmEdit = ({ item, children }) => {
9
+ const { run , error, loading, entity, data, onChange: contextOnChange, onBlur: contextOnBlur } = useGQLEntityContext()
10
+
11
+ const localOnMutationEvent = useCallback((mutationHandler, notifyHandler) => async (e) => {
12
+ const newItem = { ...item, [e.target.id]: e.target.value }
13
+ const newEvent = { target: { value: newItem } }
14
+
15
+ await notifyHandler(newEvent)
16
+ return await mutationHandler(e)
17
+ })
18
+
19
+ const {
20
+ draft,
21
+ dirty,
22
+ onChange,
23
+ onBlur,
24
+ onCancel,
25
+ onConfirm,
26
+ } = useEditAction(UpdateAsyncAction, item, {mode: "confirm"})
27
+
28
+ const handleConfirm = useCallback(async () => {
29
+ const result = await onConfirm();
30
+ console.log("ConfirmEdit handleConfirm result", result, "draft", draft)
31
+ if (result) {
32
+ const event = { target: { value: result } };
33
+ // důležité: použij params z kontextu (provider si je drží jako "poslední vars")
34
+ await contextOnChange(event);
35
+ }
36
+ return result;
37
+ }, [onConfirm, contextOnChange]);
38
+
39
+
40
+ return (
41
+ <MediumEditableContent item={item} onChange={onChange} onBlur={onBlur} >
42
+ {children}
43
+ <hr />
44
+ {/* <pre>{JSON.stringify(item, null, 2)}</pre> */}
45
+ <button
46
+ className="btn btn-warning form-control"
47
+ onClick={onCancel}
48
+ disabled={!dirty || loading}
49
+ >
50
+ Zrušit změny
51
+ </button>
52
+ <button
53
+ className="btn btn-primary form-control"
54
+ onClick={handleConfirm}
55
+ disabled={!dirty || loading}
56
+ >
57
+ Uložit změny
58
+ </button>
59
+ </MediumEditableContent>
60
+ )
61
+ }
@@ -0,0 +1,14 @@
1
+ import { DateTimeFilter, Filter as BaseFilter, StringFilter, UUIDFilter } from "../../../../_template/src/Base/FormControls/Filter"
2
+
3
+ export const Filter = ({ id, onChange: handleChange, children }) => {
4
+ return (
5
+ <BaseFilter id={id} onChange={handleChange}>
6
+ <UUIDFilter id="id" />
7
+ <StringFilter id="name" />
8
+ <DateTimeFilter id="created" emitUtcIso={false} />
9
+ {/* <FloatFilter id="count" /> */}
10
+ {children}
11
+ </BaseFilter>
12
+ )
13
+ }
14
+
@@ -0,0 +1,54 @@
1
+ // import Row from "react-bootstrap/Row"
2
+ import { MediumCard } from "./MediumCard"
3
+ import { CardCapsule as CardCapsule_} from "./CardCapsule"
4
+ import { Row } from "../../../../_template/src/Base/Components/Row"
5
+ // import { LeftColumn, MiddleColumn } from "@hrbolek/uoisfrontend-shared"
6
+ import { MediumContent as MediumContent_ } from "./MediumContent"
7
+ import { InteractiveMutations } from '../Mutations/InteractiveMutations'
8
+ import { LeftColumn, MiddleColumn } from "../../../../_template/src/Base/Components/Col"
9
+ import { PlanRow } from "./Plan/PlanRow"
10
+ import { A4Plan } from "./A4Plan"
11
+ /**
12
+ * A large card component for displaying detailed content and layout for an template entity.
13
+ *
14
+ * This component wraps an `TemplateCardCapsule` with a flexible layout that includes multiple
15
+ * columns. It uses a `Row` layout with a `LeftColumn` for displaying an `TemplateMediumCard`
16
+ * and a `MiddleColumn` for rendering additional children.
17
+ *
18
+ * @component
19
+ * @param {Object} props - The properties for the TemplateLargeCard component.
20
+ * @param {Object} props.template - The object representing the template entity.
21
+ * @param {string|number} props.template.id - The unique identifier for the template entity.
22
+ * @param {string} props.template.name - The name or label of the template entity.
23
+ * @param {React.ReactNode} [props.children=null] - Additional content to render in the middle column.
24
+ *
25
+ * @returns {JSX.Element} A JSX element combining a large card layout with dynamic content.
26
+ *
27
+ * @example
28
+ * // Example usage:
29
+ * const templateEntity = { id: 123, name: "Sample Entity" };
30
+ *
31
+ * <TemplateLargeCard template={templateEntity}>
32
+ * <p>Additional content for the middle column.</p>
33
+ * </TemplateLargeCard>
34
+ */
35
+ export const LargeCard = ({ item, children, CardCapsule=CardCapsule_, MediumContent=MediumContent_ }) => {
36
+ // console.log("LargeCard.item", item)
37
+ return (
38
+ <CardCapsule item={item} >
39
+ <Row>
40
+ <LeftColumn>
41
+ <CardCapsule item={item} title="Detail">
42
+ <MediumContent item={item} />
43
+ </CardCapsule>
44
+ <InteractiveMutations item={item} />
45
+ </LeftColumn>
46
+ <MiddleColumn>
47
+ <PlanRow item={item}/>
48
+ <A4Plan item={item} startdate={"2023-09-01"} enddate={"2024-03-31"}/>
49
+ {children}
50
+ </MiddleColumn>
51
+ </Row>
52
+ </CardCapsule>
53
+ )
54
+ }