@hrbolek/uoisfrontend-template 0.6.1 → 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.
- package/dist/cjs/index.js +447 -0
- package/dist/es/index.js +11062 -0
- package/dist/umd/index.js +447 -0
- package/package.json +5 -1
- package/src/Base/Components/Link.jsx +13 -8
- package/src/Base/Mutations/General.jsx +3 -1
- package/src/Base/Mutations/Update.jsx +5 -0
- package/src/EventGQLModel/Components/A4Plan/A4Plan.jsx +363 -0
- package/src/EventGQLModel/Components/A4Plan/index.js +1 -0
- package/src/EventGQLModel/Components/CardCapsule.jsx +43 -0
- package/src/EventGQLModel/Components/Children.jsx +31 -0
- package/src/EventGQLModel/Components/ConfirmEdit.jsx +61 -0
- package/src/EventGQLModel/Components/Filter.jsx +14 -0
- package/src/EventGQLModel/Components/LargeCard.jsx +54 -0
- package/src/EventGQLModel/Components/Link.jsx +55 -0
- package/src/EventGQLModel/Components/LiveEdit.jsx +111 -0
- package/src/EventGQLModel/Components/MediumCard.jsx +39 -0
- package/src/EventGQLModel/Components/MediumContent.jsx +96 -0
- package/src/EventGQLModel/Components/MediumEditableContent.jsx +35 -0
- package/src/EventGQLModel/Components/Plan/PlanRow.jsx +470 -0
- package/src/EventGQLModel/Components/Plan/_utils.js +971 -0
- package/src/EventGQLModel/Components/Plan/calendarReducer.js +535 -0
- package/src/EventGQLModel/Components/Plan/index.js +3 -0
- package/src/EventGQLModel/Components/Table.jsx +7 -0
- package/src/EventGQLModel/Components/index.js +15 -0
- package/src/EventGQLModel/Mutations/Create.jsx +202 -0
- package/src/EventGQLModel/Mutations/Delete.jsx +173 -0
- package/src/EventGQLModel/Mutations/InteractiveMutations.jsx +30 -0
- package/src/EventGQLModel/Mutations/Update.jsx +147 -0
- package/src/EventGQLModel/Mutations/helpers.jsx +7 -0
- package/src/EventGQLModel/Pages/PageBase.jsx +56 -0
- package/src/EventGQLModel/Pages/PageCreateItem.jsx +28 -0
- package/src/EventGQLModel/Pages/PageDeleteItem.jsx +16 -0
- package/src/EventGQLModel/Pages/PageNavbar.jsx +160 -0
- package/src/EventGQLModel/Pages/PagePlan.jsx +42 -0
- package/src/EventGQLModel/Pages/PageReadItem.jsx +11 -0
- package/src/EventGQLModel/Pages/PageReadItemEx.jsx +42 -0
- package/src/EventGQLModel/Pages/PageSubevents.jsx +43 -0
- package/src/EventGQLModel/Pages/PageUpdateItem.jsx +14 -0
- package/src/EventGQLModel/Pages/PageVector.jsx +80 -0
- package/src/EventGQLModel/Pages/RouterSegment.jsx +82 -0
- package/src/EventGQLModel/Pages/index.js +2 -0
- package/src/EventGQLModel/Queries/DeleteAsyncAction.jsx +32 -0
- package/src/EventGQLModel/Queries/Fragments.jsx +123 -0
- package/src/EventGQLModel/Queries/InsertAsyncAction.jsx +40 -0
- package/src/EventGQLModel/Queries/ReadAsyncAction.jsx +44 -0
- package/src/EventGQLModel/Queries/ReadPageAsyncAction.jsx +13 -0
- package/src/EventGQLModel/Queries/ReadSubEventsAsyncAction.jsx +44 -0
- package/src/EventGQLModel/Queries/SearchAsyncAction.jsx +16 -0
- package/src/EventGQLModel/Queries/UpdateAsyncAction.jsx +40 -0
- package/src/EventGQLModel/Queries/index.js +6 -0
- package/src/EventGQLModel/Scalars/ScalarAttribute.jsx +54 -0
- package/src/EventGQLModel/Scalars/TemplateScalarAttribute.jsx +88 -0
- package/src/EventGQLModel/Scalars/index.js +1 -0
- package/src/EventGQLModel/Vectors/TemplateVectorsAttribute.jsx +326 -0
- package/src/EventGQLModel/Vectors/VectorAttribute.jsx +56 -0
- package/src/EventGQLModel/Vectors/index.js +1 -0
- package/src/EventGQLModel/WhatToDo.md +44 -0
- package/src/EventGQLModel/index.js +71 -0
- package/src/GroupGQLModel/Mutations/Create.jsx +8 -2
- package/src/GroupGQLModel/Mutations/Delete.jsx +8 -2
- package/src/GroupGQLModel/Mutations/Update.jsx +8 -8
- package/src/GroupGQLModel/Queries/Fragments.jsx +17 -1
- package/src/GroupGQLModel/Scalars/RBACObject.jsx +17 -5
- package/src/GroupGQLModel/Vectors/GroupMemberships.jsx +1 -1
- package/src/UserGQLModel/Components/MediumContent.jsx +9 -3
- package/src/UserGQLModel/Queries/Fragments.jsx +6 -0
- package/src/_Template/WhatToDo.md +1 -1
- package/index.html +0 -104
- package/vite.config.js +0 -47
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hrbolek/uoisfrontend-template",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"_main": "src/index.js",
|
|
9
9
|
"_module": "src/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
10
14
|
"scripts": {
|
|
11
15
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
16
|
"dev": "vite",
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
// import { URIRoot } from "../../uriroot";
|
|
2
2
|
import { ProxyLink } from "./ProxyLink";
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
export const registerLink = (__typename, Link) => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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 ?
|
|
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 {...
|
|
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
|
+
}
|