@goralys/ui 0.0.0-stage → 0.1.0
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/index.d.ts +495 -0
- package/dist/index.js +3514 -0
- package/package.json +59 -5
- package/README.md +0 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,3514 @@
|
|
|
1
|
+
// src/hooks/goralys-ui-provider.tsx
|
|
2
|
+
import { createContext, useContext, useMemo } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var GoralysUIContext = createContext(null);
|
|
5
|
+
function GoralysUIProvider({ config, children }) {
|
|
6
|
+
const value = useMemo(() => config, [config]);
|
|
7
|
+
return /* @__PURE__ */ jsx(GoralysUIContext.Provider, { value, children });
|
|
8
|
+
}
|
|
9
|
+
function useGoralysUI() {
|
|
10
|
+
const context = useContext(GoralysUIContext);
|
|
11
|
+
if (!context) throw new Error("useGoralysUI must be used inside a GoralysUIProvider");
|
|
12
|
+
return context;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/hooks/use-media-query.ts
|
|
16
|
+
import { useEffect, useState } from "react";
|
|
17
|
+
function useMediaQuery(query) {
|
|
18
|
+
const [matches, setMatches] = useState(false);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const run = () => {
|
|
21
|
+
const media = window.matchMedia(query);
|
|
22
|
+
setMatches(media.matches);
|
|
23
|
+
const listener = (e) => setMatches(e.matches);
|
|
24
|
+
media.addEventListener("change", listener);
|
|
25
|
+
return () => media.removeEventListener("change", listener);
|
|
26
|
+
};
|
|
27
|
+
return run();
|
|
28
|
+
}, [query]);
|
|
29
|
+
return matches;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/lib/inputs.ts
|
|
33
|
+
import { useCallback } from "react";
|
|
34
|
+
function useAutoResize(ref) {
|
|
35
|
+
return useCallback(
|
|
36
|
+
(el) => {
|
|
37
|
+
if (typeof ref === "function") {
|
|
38
|
+
ref(el);
|
|
39
|
+
} else if (ref && "current" in ref) {
|
|
40
|
+
Object.assign(ref, { current: el });
|
|
41
|
+
}
|
|
42
|
+
if (!el) return;
|
|
43
|
+
const resize = () => {
|
|
44
|
+
el.style.height = "auto";
|
|
45
|
+
el.style.height = `${el.scrollHeight}px`;
|
|
46
|
+
};
|
|
47
|
+
resize();
|
|
48
|
+
el.addEventListener("input", resize);
|
|
49
|
+
return () => el.removeEventListener("input", resize);
|
|
50
|
+
},
|
|
51
|
+
[ref]
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/lib/modals.ts
|
|
56
|
+
import { useEffect as useEffect2 } from "react";
|
|
57
|
+
function useModalClose(modalRef, visible, onClose) {
|
|
58
|
+
useEffect2(() => {
|
|
59
|
+
if (!visible) return;
|
|
60
|
+
const handleClickOutside = (e) => {
|
|
61
|
+
if (modalRef.current && !modalRef.current.contains(e.target)) {
|
|
62
|
+
onClose();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const handleKeyDown = (e) => {
|
|
66
|
+
if (e.key === "Escape") {
|
|
67
|
+
onClose();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
71
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
72
|
+
return () => {
|
|
73
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
74
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
75
|
+
};
|
|
76
|
+
}, [visible, onClose, modalRef]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/ui/button.tsx
|
|
80
|
+
import clsx from "clsx";
|
|
81
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
82
|
+
var colors = {
|
|
83
|
+
sky: "bg-sky-100 border-sky-300 before:bg-sky-300",
|
|
84
|
+
red: "bg-red-50 border-red-300 before:bg-red-300",
|
|
85
|
+
green: "bg-green-50 border-green-300 before:bg-green-300",
|
|
86
|
+
amber: "bg-amber-50 border-amber-300 before:bg-amber-300"
|
|
87
|
+
};
|
|
88
|
+
function Button({ text, type, className, onClick, color = "sky", disabled = false }) {
|
|
89
|
+
return /* @__PURE__ */ jsx2(
|
|
90
|
+
"button",
|
|
91
|
+
{
|
|
92
|
+
type,
|
|
93
|
+
className: clsx(
|
|
94
|
+
`
|
|
95
|
+
relative block w-full h-10 ${colors[color]} border mt-2 mb-2 rounded-xs shadow-lg z-0
|
|
96
|
+
transition-colors duration-500 overflow-hidden
|
|
97
|
+
|
|
98
|
+
before:w-0 before:h-full before:content-[''] before:absolute before:left-0 before:top-0 before:z-0
|
|
99
|
+
before:transition-all before:duration-500
|
|
100
|
+
hover:text-gray-900 hover:before:w-full
|
|
101
|
+
${className}
|
|
102
|
+
`,
|
|
103
|
+
{
|
|
104
|
+
"bg-gray-200! border-gray-500! before:bg-transparent cursor-not-allowed": disabled
|
|
105
|
+
}
|
|
106
|
+
),
|
|
107
|
+
onClick,
|
|
108
|
+
disabled,
|
|
109
|
+
children: /* @__PURE__ */ jsx2("p", { className: "relative text-[17px] z-10", children: text })
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/ui/card-title.tsx
|
|
115
|
+
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
116
|
+
function CardTitle({ title }) {
|
|
117
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
118
|
+
/* @__PURE__ */ jsx3("p", { className: "text-lg", children: title }),
|
|
119
|
+
/* @__PURE__ */ jsx3("span", { className: "w-full bg-sky-300 h-px -mt-3 mb-1.5" })
|
|
120
|
+
] });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/ui/card.tsx
|
|
124
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
125
|
+
function Card({ children, className }) {
|
|
126
|
+
return /* @__PURE__ */ jsx4("div", { className: `flex w-full rounded-xs p-3 gap-3 ${className}`, children });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/ui/admin-panel/admin-card.tsx
|
|
130
|
+
import { cookiesGet, PUB_ID_KEY } from "@goralys/core";
|
|
131
|
+
import { ShieldExclamationIcon } from "@heroicons/react/24/outline";
|
|
132
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
133
|
+
function AdminCard({ admin }) {
|
|
134
|
+
const router = useGoralysUI().useRouter();
|
|
135
|
+
return /* @__PURE__ */ jsx5(Card, { className: "flex-col w-175! bg-sky-200 gap-1 p-1 mb-1 mt-1", children: /* @__PURE__ */ jsxs2("div", { className: "flex flex-row justify-between items-center", children: [
|
|
136
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-row", children: [
|
|
137
|
+
/* @__PURE__ */ jsx5(ShieldExclamationIcon, { width: 27.5, className: "mr-1.5" }),
|
|
138
|
+
/* @__PURE__ */ jsxs2("strong", { children: [
|
|
139
|
+
admin.fullName,
|
|
140
|
+
" (",
|
|
141
|
+
admin.username,
|
|
142
|
+
")"
|
|
143
|
+
] })
|
|
144
|
+
] }),
|
|
145
|
+
/* @__PURE__ */ jsx5("div", { className: "flex flex-row w-100 gap-1 items-center justify-end", children: cookiesGet(PUB_ID_KEY) === admin.publicId ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
146
|
+
/* @__PURE__ */ jsx5("p", { className: "ml-auto", children: "(vous)" }),
|
|
147
|
+
/* @__PURE__ */ jsx5(
|
|
148
|
+
Button,
|
|
149
|
+
{
|
|
150
|
+
className: "w-50!",
|
|
151
|
+
type: "button",
|
|
152
|
+
text: "Consulter votre profil",
|
|
153
|
+
onClick: () => router.push("/admin/user?u=" + admin.publicId)
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
] }) : /* @__PURE__ */ jsx5(
|
|
157
|
+
Button,
|
|
158
|
+
{
|
|
159
|
+
className: "w-50!",
|
|
160
|
+
type: "button",
|
|
161
|
+
text: "G\xE9rer l'administrateur",
|
|
162
|
+
onClick: () => router.push("/admin/user?u=" + admin.publicId)
|
|
163
|
+
}
|
|
164
|
+
) })
|
|
165
|
+
] }) });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/ui/inputs/floating-input.tsx
|
|
169
|
+
import { clsx as clsx2 } from "clsx";
|
|
170
|
+
import { useState as useState2 } from "react";
|
|
171
|
+
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/outline";
|
|
172
|
+
import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
173
|
+
function FloatingInput({
|
|
174
|
+
id,
|
|
175
|
+
label,
|
|
176
|
+
helper,
|
|
177
|
+
autocomplete,
|
|
178
|
+
password = false,
|
|
179
|
+
email = false,
|
|
180
|
+
disabled = false,
|
|
181
|
+
required = false,
|
|
182
|
+
defaultValue,
|
|
183
|
+
value,
|
|
184
|
+
onInput
|
|
185
|
+
}) {
|
|
186
|
+
const [show, setShow] = useState2(!password);
|
|
187
|
+
const [isAutofilled, setIsAutofilled] = useState2(false);
|
|
188
|
+
function onEyeClicked() {
|
|
189
|
+
setShow(!show);
|
|
190
|
+
}
|
|
191
|
+
function handleAnimationStart(e) {
|
|
192
|
+
if (e.animationName === "onAutoFillStart") setIsAutofilled(true);
|
|
193
|
+
if (e.animationName === "onAutoFillCancel") setIsAutofilled(false);
|
|
194
|
+
}
|
|
195
|
+
return /* @__PURE__ */ jsxs3(
|
|
196
|
+
"div",
|
|
197
|
+
{
|
|
198
|
+
className: clsx2("relative mt-3 group min-w-50 w-full", {
|
|
199
|
+
"mb-5": helper !== void 0,
|
|
200
|
+
"mb-1": helper === void 0
|
|
201
|
+
}),
|
|
202
|
+
children: [
|
|
203
|
+
/* @__PURE__ */ jsx6(
|
|
204
|
+
"input",
|
|
205
|
+
{
|
|
206
|
+
type: email ? "email" : show ? "text" : "password",
|
|
207
|
+
id,
|
|
208
|
+
name: id,
|
|
209
|
+
placeholder: " ",
|
|
210
|
+
autoComplete: autocomplete,
|
|
211
|
+
disabled,
|
|
212
|
+
required,
|
|
213
|
+
defaultValue,
|
|
214
|
+
value,
|
|
215
|
+
onInput,
|
|
216
|
+
onAnimationStart: handleAnimationStart,
|
|
217
|
+
className: clsx2(
|
|
218
|
+
"peer block w-full leading-none py-0 px-0 cursor-text text-base text-heading bg-transparent border-0 border-b-2 border-sky-300 appearance-none focus:outline-none focus:ring-0 ",
|
|
219
|
+
{
|
|
220
|
+
"cursor-not-allowed!": disabled
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
),
|
|
225
|
+
/* @__PURE__ */ jsx6(
|
|
226
|
+
"span",
|
|
227
|
+
{
|
|
228
|
+
className: "\n pointer-events-none\n absolute left-0 bottom-0 h-0.5 w-full\n origin-left\n bg-sky-500\n transition-transform duration-250\n group-focus-within:scale-x-100\n "
|
|
229
|
+
}
|
|
230
|
+
),
|
|
231
|
+
/* @__PURE__ */ jsx6(
|
|
232
|
+
"label",
|
|
233
|
+
{
|
|
234
|
+
htmlFor: id,
|
|
235
|
+
className: clsx2(
|
|
236
|
+
"absolute text-base text-body cursor-text duration-300 transform -translate-y-4.5 scale-75 top-0 origin-left peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4.5 ",
|
|
237
|
+
{
|
|
238
|
+
"cursor-not-allowed!": disabled,
|
|
239
|
+
"scale-75! -translate-y-4.5!": isAutofilled
|
|
240
|
+
// force active state
|
|
241
|
+
}
|
|
242
|
+
),
|
|
243
|
+
children: label
|
|
244
|
+
}
|
|
245
|
+
),
|
|
246
|
+
password && /* @__PURE__ */ jsx6("button", { type: "button", onClick: onEyeClicked, className: "absolute top-px right-0 text-gray-900", children: show ? /* @__PURE__ */ jsx6(EyeSlashIcon, { className: "size-5" }) : /* @__PURE__ */ jsx6(EyeIcon, { className: "size-5" }) }),
|
|
247
|
+
/* @__PURE__ */ jsxs3(
|
|
248
|
+
"p",
|
|
249
|
+
{
|
|
250
|
+
className: clsx2("mt-0 absolute text-[13px] italic text-gray-600", {
|
|
251
|
+
hidden: helper === void 0
|
|
252
|
+
}),
|
|
253
|
+
children: [
|
|
254
|
+
"*",
|
|
255
|
+
helper
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/ui/admin-panel/admins-search-bar.tsx
|
|
265
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
266
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
267
|
+
function AdminsSearchBar({ type, admins, setCurrentAdminsAction }) {
|
|
268
|
+
const [searchText, setSearchText] = useState3("");
|
|
269
|
+
const handleSearch = (e) => {
|
|
270
|
+
setSearchText(e.currentTarget.value);
|
|
271
|
+
};
|
|
272
|
+
useEffect3(() => {
|
|
273
|
+
if (!admins) return;
|
|
274
|
+
const search = searchText.trim().toLowerCase();
|
|
275
|
+
const sorted = [...admins].filter((u) => !search || u.fullName.trim().toLowerCase().includes(search)).sort((a, b) => a.fullName.trim().toLowerCase().localeCompare(b.fullName.trim().toLowerCase(), "fr"));
|
|
276
|
+
setCurrentAdminsAction((prev) => {
|
|
277
|
+
if (JSON.stringify(prev) === JSON.stringify(sorted)) return prev;
|
|
278
|
+
return sorted;
|
|
279
|
+
});
|
|
280
|
+
}, [searchText, admins, setCurrentAdminsAction]);
|
|
281
|
+
return /* @__PURE__ */ jsx7("div", { className: "flex flex-row gap-2 items-end mb-4 w-175!", children: /* @__PURE__ */ jsx7(FloatingInput, { id: "admins-search-" + type, label: "Rechercher par nom", onInput: handleSearch }) });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/ui/admin-panel/audit-action-card.tsx
|
|
285
|
+
import { parsePhpDateTime } from "@goralys/core";
|
|
286
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
287
|
+
function AuditActionCard({ actions }) {
|
|
288
|
+
if (!actions) return /* @__PURE__ */ jsx8(Fragment3, {});
|
|
289
|
+
if (actions.length === 0) {
|
|
290
|
+
return /* @__PURE__ */ jsxs4(Card, { className: "flex flex-col grow bg-sky-200 m-2", children: [
|
|
291
|
+
/* @__PURE__ */ jsx8(CardTitle, { title: "Actions" }),
|
|
292
|
+
/* @__PURE__ */ jsx8("p", { children: "Aucune action enregistr\xE9e pour cet administrateur" })
|
|
293
|
+
] });
|
|
294
|
+
}
|
|
295
|
+
return /* @__PURE__ */ jsxs4(Card, { className: "flex flex-col grow bg-sky-200 m-2", children: [
|
|
296
|
+
/* @__PURE__ */ jsx8(CardTitle, { title: "Actions" }),
|
|
297
|
+
actions.map((a) => /* @__PURE__ */ jsxs4("div", { className: "w-full h-5 flex flex-row items-center", children: [
|
|
298
|
+
/* @__PURE__ */ jsx8("p", { className: "mr-2 text-xs font-mono", children: parsePhpDateTime(a.at) }),
|
|
299
|
+
/* @__PURE__ */ jsx8("p", { children: a.description })
|
|
300
|
+
] }, a.initiator + parsePhpDateTime(a.at)))
|
|
301
|
+
] });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/ui/admin-panel/auth-token-card.tsx
|
|
305
|
+
import { buildApiUrl, fetchCsrfClient, goralysFetchClient, handleToastRequest, parsePhpDateTime as parsePhpDateTime2 } from "@goralys/core";
|
|
306
|
+
|
|
307
|
+
// src/ui/toast/toast-provider.tsx
|
|
308
|
+
import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect4, useMemo as useMemo2, useState as useState4 } from "react";
|
|
309
|
+
|
|
310
|
+
// src/ui/toast/toast-element.tsx
|
|
311
|
+
import { clsx as clsx3 } from "clsx";
|
|
312
|
+
import { CheckCircleIcon, ExclamationCircleIcon, InformationCircleIcon, XCircleIcon } from "@heroicons/react/24/outline";
|
|
313
|
+
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
314
|
+
var icons = {
|
|
315
|
+
info: /* @__PURE__ */ jsx9(InformationCircleIcon, { className: "size-14 p-0 -ml-1.5 -mr-1.5 text-blue-600" }),
|
|
316
|
+
success: /* @__PURE__ */ jsx9(CheckCircleIcon, { className: "size-14 p-0 -ml-1.5 -mr-1.5 text-green-600" }),
|
|
317
|
+
warning: /* @__PURE__ */ jsx9(ExclamationCircleIcon, { className: "size-14 p-0 -ml-1.5 -mr-1.5 text-amber-600" }),
|
|
318
|
+
error: /* @__PURE__ */ jsx9(XCircleIcon, { className: "size-14 p-0 -ml-1.5 -mr-1.5 text-red-600" })
|
|
319
|
+
};
|
|
320
|
+
function ToastElement({ type, title, message, visible }) {
|
|
321
|
+
return /* @__PURE__ */ jsx9(
|
|
322
|
+
"div",
|
|
323
|
+
{
|
|
324
|
+
className: clsx3(
|
|
325
|
+
"fixed flex gap-2 p-3 h-22 sm:w-115 w-95 bg-sky-300 rounded shadow overflow-hidden left-1/2 -translate-x-1/2 top-1 ",
|
|
326
|
+
"after:absolute after:left-0 after:top-0 after:h-full after:w-1.25 after:content-['']",
|
|
327
|
+
"transition-all duration-500 z-10",
|
|
328
|
+
{
|
|
329
|
+
"after:bg-blue-600": type === "info",
|
|
330
|
+
"after:bg-green-600": type === "success",
|
|
331
|
+
"after:bg-amber-600": type === "warning",
|
|
332
|
+
"after:bg-red-600": type === "error"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"translate-y-0 opacity-100": visible,
|
|
336
|
+
"-translate-y-5 opacity-0": !visible
|
|
337
|
+
}
|
|
338
|
+
),
|
|
339
|
+
role: "alert",
|
|
340
|
+
children: /* @__PURE__ */ jsxs5("div", { className: "flex gap-3", children: [
|
|
341
|
+
/* @__PURE__ */ jsx9("div", { className: "flex self-center", children: icons[type] }),
|
|
342
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-col justify-center", children: [
|
|
343
|
+
/* @__PURE__ */ jsx9("strong", { className: "text-md", children: title }),
|
|
344
|
+
/* @__PURE__ */ jsx9("span", { className: "text-sm", children: message })
|
|
345
|
+
] })
|
|
346
|
+
] })
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/ui/toast/toast-provider.tsx
|
|
352
|
+
import { FLASH_TOAST_KEY, storageSet } from "@goralys/core";
|
|
353
|
+
import { createPortal } from "react-dom";
|
|
354
|
+
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
355
|
+
var ToastContext = createContext2(null);
|
|
356
|
+
function ToastProvider({ children }) {
|
|
357
|
+
const [toast, setToast] = useState4(null);
|
|
358
|
+
const [visible, setVisible] = useState4(false);
|
|
359
|
+
const cacheToast = useCallback2((toastInput, duration = 5500) => {
|
|
360
|
+
if (!toastInput.expires) toastInput.expires = Date.now() + duration;
|
|
361
|
+
storageSet(FLASH_TOAST_KEY, JSON.stringify(toastInput));
|
|
362
|
+
}, []);
|
|
363
|
+
const showToast = useCallback2(
|
|
364
|
+
(toastInput, animate = true, duration = 5500) => {
|
|
365
|
+
cacheToast(toastInput, duration);
|
|
366
|
+
if (!toastInput.expires) toastInput.expires = Date.now() + duration;
|
|
367
|
+
if (toastInput.expires === toast?.expires) return;
|
|
368
|
+
setToast(toastInput);
|
|
369
|
+
setVisible(!animate);
|
|
370
|
+
if (animate) requestAnimationFrame(() => setVisible(true));
|
|
371
|
+
},
|
|
372
|
+
[cacheToast, toast]
|
|
373
|
+
);
|
|
374
|
+
useEffect4(() => {
|
|
375
|
+
if (!toast || !toast.expires) return;
|
|
376
|
+
const left = toast.expires - Date.now();
|
|
377
|
+
const hide = setTimeout(() => setVisible(false), left - 500);
|
|
378
|
+
const clear = setTimeout(() => setToast(null), left);
|
|
379
|
+
return () => {
|
|
380
|
+
clearTimeout(hide);
|
|
381
|
+
clearTimeout(clear);
|
|
382
|
+
};
|
|
383
|
+
}, [toast]);
|
|
384
|
+
const value = useMemo2(() => ({ showToast, cacheToast }), [showToast, cacheToast]);
|
|
385
|
+
return /* @__PURE__ */ jsxs6(ToastContext.Provider, { value, children: [
|
|
386
|
+
children,
|
|
387
|
+
toast && typeof document !== "undefined" && createPortal(/* @__PURE__ */ jsx10(ToastElement, { ...toast, visible }), document.getElementById("toast-root"))
|
|
388
|
+
] });
|
|
389
|
+
}
|
|
390
|
+
function useToast() {
|
|
391
|
+
const context = useContext2(ToastContext);
|
|
392
|
+
if (!context) {
|
|
393
|
+
throw new Error("useToast must be used in a valid context.");
|
|
394
|
+
}
|
|
395
|
+
return context;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/ui/modals/confirm/confirm-provider.tsx
|
|
399
|
+
import { createContext as createContext3, useCallback as useCallback3, useContext as useContext3, useMemo as useMemo3, useState as useState5 } from "react";
|
|
400
|
+
|
|
401
|
+
// src/ui/modals/confirm/confirm-element.tsx
|
|
402
|
+
import { clsx as clsx4 } from "clsx";
|
|
403
|
+
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline";
|
|
404
|
+
import { useRef } from "react";
|
|
405
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
406
|
+
function ConfirmElement({ title, message, visible, onConfirmAction, onCancelAction }) {
|
|
407
|
+
const modalRef = useRef(null);
|
|
408
|
+
useModalClose(modalRef, visible, onCancelAction);
|
|
409
|
+
return /* @__PURE__ */ jsxs7(
|
|
410
|
+
"div",
|
|
411
|
+
{
|
|
412
|
+
ref: modalRef,
|
|
413
|
+
className: clsx4(
|
|
414
|
+
"fixed flex flex-col gap-2 p-3 sm:w-115 w-96 bg-sky-200 rounded shadow overflow-hidden left-1/2 -translate-x-1/2 top-1 ",
|
|
415
|
+
"after:absolute after:left-0 after:top-0 after:h-full after:w-1.25 after:content-[''] after:bg-blue-500",
|
|
416
|
+
"transition-all duration-500 z-50 ",
|
|
417
|
+
{
|
|
418
|
+
"translate-y-0 opacity-100": visible,
|
|
419
|
+
"-translate-y-5 opacity-0": !visible
|
|
420
|
+
}
|
|
421
|
+
),
|
|
422
|
+
role: "dialog",
|
|
423
|
+
"aria-modal": "true",
|
|
424
|
+
children: [
|
|
425
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex gap-3", children: [
|
|
426
|
+
/* @__PURE__ */ jsx11("div", { className: "w-11 h-11 flex self-center items-center justify-center", children: /* @__PURE__ */ jsx11(QuestionMarkCircleIcon, { className: "size-15 text-blue-500" }) }),
|
|
427
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col justify-center flex-1", children: [
|
|
428
|
+
/* @__PURE__ */ jsx11("strong", { className: "text-md", children: title }),
|
|
429
|
+
/* @__PURE__ */ jsx11("span", { className: "text-sm", children: message })
|
|
430
|
+
] })
|
|
431
|
+
] }),
|
|
432
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex justify-between gap-2 mt-2", children: [
|
|
433
|
+
/* @__PURE__ */ jsx11(
|
|
434
|
+
Button,
|
|
435
|
+
{
|
|
436
|
+
className: "bg-gray-400! before:bg-gray-500! text-white! border-none! shadow-none! mt-0! mb-0! w-25!",
|
|
437
|
+
text: "Annuler",
|
|
438
|
+
type: "button",
|
|
439
|
+
onClick: onCancelAction
|
|
440
|
+
}
|
|
441
|
+
),
|
|
442
|
+
/* @__PURE__ */ jsx11(
|
|
443
|
+
Button,
|
|
444
|
+
{
|
|
445
|
+
className: "bg-blue-500! before:bg-blue-600! text-white! border-none! shadow-none! mt-0! mb-0! w-25!",
|
|
446
|
+
text: "Confirmer",
|
|
447
|
+
type: "button",
|
|
448
|
+
onClick: onConfirmAction
|
|
449
|
+
}
|
|
450
|
+
)
|
|
451
|
+
] })
|
|
452
|
+
]
|
|
453
|
+
}
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/ui/modals/confirm/confirm-provider.tsx
|
|
458
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
459
|
+
import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
460
|
+
var ConfirmContext = createContext3(null);
|
|
461
|
+
function ConfirmProvider({ children }) {
|
|
462
|
+
const [confirmState, setConfirmState] = useState5(null);
|
|
463
|
+
const [visible, setVisible] = useState5(false);
|
|
464
|
+
const showConfirm = useCallback3((options) => {
|
|
465
|
+
return new Promise((resolve) => {
|
|
466
|
+
setConfirmState({ options, resolve });
|
|
467
|
+
setVisible(false);
|
|
468
|
+
requestAnimationFrame(() => setVisible(true));
|
|
469
|
+
});
|
|
470
|
+
}, []);
|
|
471
|
+
function handleConfirm() {
|
|
472
|
+
setVisible(false);
|
|
473
|
+
setTimeout(() => {
|
|
474
|
+
confirmState?.resolve(true);
|
|
475
|
+
setConfirmState(null);
|
|
476
|
+
}, 500);
|
|
477
|
+
}
|
|
478
|
+
function handleCancel() {
|
|
479
|
+
setVisible(false);
|
|
480
|
+
setTimeout(() => {
|
|
481
|
+
confirmState?.resolve(false);
|
|
482
|
+
setConfirmState(null);
|
|
483
|
+
}, 500);
|
|
484
|
+
}
|
|
485
|
+
const value = useMemo3(() => ({ showConfirm }), [showConfirm]);
|
|
486
|
+
return /* @__PURE__ */ jsxs8(ConfirmContext.Provider, { value, children: [
|
|
487
|
+
children,
|
|
488
|
+
confirmState && typeof document !== "undefined" && createPortal2(
|
|
489
|
+
/* @__PURE__ */ jsx12("div", { className: "fixed inset-0 z-40 bg-black/20 backdrop-blur-sm", children: /* @__PURE__ */ jsx12(
|
|
490
|
+
ConfirmElement,
|
|
491
|
+
{
|
|
492
|
+
...confirmState.options,
|
|
493
|
+
visible,
|
|
494
|
+
onConfirmAction: handleConfirm,
|
|
495
|
+
onCancelAction: handleCancel
|
|
496
|
+
}
|
|
497
|
+
) }),
|
|
498
|
+
document.getElementById("confirm-root")
|
|
499
|
+
)
|
|
500
|
+
] });
|
|
501
|
+
}
|
|
502
|
+
function useConfirm() {
|
|
503
|
+
const context = useContext3(ConfirmContext);
|
|
504
|
+
if (!context) {
|
|
505
|
+
throw new Error("useConfirm must be used within a ConfirmProvider");
|
|
506
|
+
}
|
|
507
|
+
return context;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/ui/admin-panel/auth-token-card.tsx
|
|
511
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
512
|
+
function AuthTokenCard({ publicId, token, onUpdate }) {
|
|
513
|
+
const { showToast } = useToast();
|
|
514
|
+
const { showConfirm } = useConfirm();
|
|
515
|
+
const revoke = async () => {
|
|
516
|
+
if (!await showConfirm({ title: "R\xE9vocation du jeton", message: "Voulez-vous vraiment r\xE9voquer ce jeton d'authentification ?" }))
|
|
517
|
+
return;
|
|
518
|
+
const res = await goralysFetchClient(
|
|
519
|
+
"DELETE",
|
|
520
|
+
buildApiUrl("user/token/any", {
|
|
521
|
+
"csrf-token": await fetchCsrfClient("revoke-auth-token"),
|
|
522
|
+
name: token.name,
|
|
523
|
+
target: publicId
|
|
524
|
+
}),
|
|
525
|
+
void 0,
|
|
526
|
+
{ suppressRedirect: true }
|
|
527
|
+
);
|
|
528
|
+
await handleToastRequest(res, showToast, false);
|
|
529
|
+
await onUpdate();
|
|
530
|
+
};
|
|
531
|
+
return /* @__PURE__ */ jsxs9(Card, { className: "grid grid-cols-[1fr_auto] grid-rows-[auto_1fr] gap-x-4", children: [
|
|
532
|
+
/* @__PURE__ */ jsx13("p", { className: "py-2", children: token.username }),
|
|
533
|
+
/* @__PURE__ */ jsx13("p", { className: "font-bold py-2 text-right", children: token.name }),
|
|
534
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex flex-col mt-2", children: [
|
|
535
|
+
/* @__PURE__ */ jsxs9("p", { className: "italic text-sm", children: [
|
|
536
|
+
"Date de cr\xE9ation: ",
|
|
537
|
+
parsePhpDateTime2(token.created)
|
|
538
|
+
] }),
|
|
539
|
+
/* @__PURE__ */ jsxs9("p", { className: "italic text-sm", children: [
|
|
540
|
+
"Date d'expiration: ",
|
|
541
|
+
parsePhpDateTime2(token.expires)
|
|
542
|
+
] })
|
|
543
|
+
] }),
|
|
544
|
+
/* @__PURE__ */ jsx13("div", { className: "flex items-center", children: /* @__PURE__ */ jsx13(Button, { text: "R\xE9voquer", type: "button", color: "red", className: "w-[105%]! min-w-25 mt-0! mb-0!", onClick: revoke }) })
|
|
545
|
+
] });
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/ui/admin-panel/create-admin-element.tsx
|
|
549
|
+
import { useState as useState8 } from "react";
|
|
550
|
+
|
|
551
|
+
// src/ui/modals/password/password-modal-provider.tsx
|
|
552
|
+
import { createContext as createContext4, useCallback as useCallback4, useContext as useContext4, useMemo as useMemo4, useState as useState7 } from "react";
|
|
553
|
+
|
|
554
|
+
// src/ui/modals/password/password-modal-element.tsx
|
|
555
|
+
import { LockClosedIcon } from "@heroicons/react/16/solid";
|
|
556
|
+
import { clsx as clsx5 } from "clsx";
|
|
557
|
+
import { useRef as useRef2, useState as useState6 } from "react";
|
|
558
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
559
|
+
function PasswordModalElement({ visible, onConfirmAction, onCancelAction, onCloseModalAction, message }) {
|
|
560
|
+
const [password, setPassword] = useState6("");
|
|
561
|
+
const modalRef = useRef2(null);
|
|
562
|
+
useModalClose(modalRef, visible, onCloseModalAction);
|
|
563
|
+
const onConfirm = () => {
|
|
564
|
+
onConfirmAction(password);
|
|
565
|
+
setPassword("");
|
|
566
|
+
};
|
|
567
|
+
const onCancel = () => {
|
|
568
|
+
setPassword("");
|
|
569
|
+
onCancelAction();
|
|
570
|
+
};
|
|
571
|
+
return /* @__PURE__ */ jsxs10(
|
|
572
|
+
"div",
|
|
573
|
+
{
|
|
574
|
+
ref: modalRef,
|
|
575
|
+
className: clsx5(
|
|
576
|
+
"fixed flex flex-col gap-2 p-3 w-125 bg-sky-200 rounded shadow overflow-hidden left-1/2 -translate-x-1/2 top-1",
|
|
577
|
+
"after:absolute after:left-0 after:top-0 after:h-full after:w-1.25 after:content-[''] after:bg-blue-500",
|
|
578
|
+
"transition-all duration-500 z-50",
|
|
579
|
+
{
|
|
580
|
+
"translate-y-0 opacity-100": visible,
|
|
581
|
+
"-translate-y-5 opacity-0": !visible
|
|
582
|
+
}
|
|
583
|
+
),
|
|
584
|
+
role: "dialog",
|
|
585
|
+
"aria-modal": "true",
|
|
586
|
+
children: [
|
|
587
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex gap-3", children: [
|
|
588
|
+
/* @__PURE__ */ jsx14("div", { className: "w-11 h-11 flex self-center items-center justify-center", children: /* @__PURE__ */ jsx14(LockClosedIcon, { className: "size-15 text-blue-500" }) }),
|
|
589
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-col justify-center flex-1", children: [
|
|
590
|
+
/* @__PURE__ */ jsx14("strong", { className: "text-md", children: "Confirmation requise" }),
|
|
591
|
+
/* @__PURE__ */ jsx14("span", { className: "text-sm", children: message })
|
|
592
|
+
] })
|
|
593
|
+
] }),
|
|
594
|
+
/* @__PURE__ */ jsx14(
|
|
595
|
+
FloatingInput,
|
|
596
|
+
{
|
|
597
|
+
id: "confirm-password",
|
|
598
|
+
label: "Mot de passe",
|
|
599
|
+
password: true,
|
|
600
|
+
autocomplete: "current-password",
|
|
601
|
+
onInput: (e) => setPassword(e.currentTarget.value)
|
|
602
|
+
}
|
|
603
|
+
),
|
|
604
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex justify-between gap-2 mt-2", children: [
|
|
605
|
+
/* @__PURE__ */ jsx14(
|
|
606
|
+
Button,
|
|
607
|
+
{
|
|
608
|
+
className: "bg-gray-400! before:bg-gray-500! text-white! border-none! shadow-none! mt-0! mb-0! h-11! w-50!",
|
|
609
|
+
text: "Annuler",
|
|
610
|
+
type: "button",
|
|
611
|
+
onClick: onCancel
|
|
612
|
+
}
|
|
613
|
+
),
|
|
614
|
+
/* @__PURE__ */ jsx14(
|
|
615
|
+
Button,
|
|
616
|
+
{
|
|
617
|
+
className: "bg-blue-500! before:bg-blue-600! text-white! border-none! shadow-none! mt-0! mb-0! h-11! w-50!",
|
|
618
|
+
text: "Confirmer",
|
|
619
|
+
type: "button",
|
|
620
|
+
onClick: onConfirm
|
|
621
|
+
}
|
|
622
|
+
)
|
|
623
|
+
] })
|
|
624
|
+
]
|
|
625
|
+
}
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/ui/modals/password/password-modal-provider.tsx
|
|
630
|
+
import { createPortal as createPortal3 } from "react-dom";
|
|
631
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
632
|
+
var PasswordModalContext = createContext4(null);
|
|
633
|
+
function PasswordModalProvider({ children }) {
|
|
634
|
+
const [state, setState] = useState7(null);
|
|
635
|
+
const [visible, setVisible] = useState7(false);
|
|
636
|
+
const [message, setMessage] = useState7("");
|
|
637
|
+
const DEFAULT_PASSWORD_CONFIRM = "cette action";
|
|
638
|
+
const showPasswordModal = useCallback4((confirm = DEFAULT_PASSWORD_CONFIRM) => {
|
|
639
|
+
setMessage("Veuillez entrer votre mot de passe pour confirmer " + confirm.replace(".", " ").trim() + ".");
|
|
640
|
+
return new Promise((resolve) => {
|
|
641
|
+
setState({ resolve });
|
|
642
|
+
setVisible(false);
|
|
643
|
+
requestAnimationFrame(() => setVisible(true));
|
|
644
|
+
});
|
|
645
|
+
}, []);
|
|
646
|
+
function handleConfirm(password) {
|
|
647
|
+
setVisible(false);
|
|
648
|
+
setTimeout(() => {
|
|
649
|
+
state?.resolve(password);
|
|
650
|
+
setState(null);
|
|
651
|
+
}, 500);
|
|
652
|
+
}
|
|
653
|
+
function handleCancel() {
|
|
654
|
+
setVisible(false);
|
|
655
|
+
setTimeout(() => {
|
|
656
|
+
state?.resolve(null);
|
|
657
|
+
setState(null);
|
|
658
|
+
}, 500);
|
|
659
|
+
}
|
|
660
|
+
function handleClose() {
|
|
661
|
+
setVisible(false);
|
|
662
|
+
setTimeout(() => {
|
|
663
|
+
state?.resolve(null);
|
|
664
|
+
setState(null);
|
|
665
|
+
}, 500);
|
|
666
|
+
}
|
|
667
|
+
const value = useMemo4(() => ({ showPasswordModal }), [showPasswordModal]);
|
|
668
|
+
return /* @__PURE__ */ jsxs11(PasswordModalContext.Provider, { value, children: [
|
|
669
|
+
children,
|
|
670
|
+
state && typeof document !== "undefined" && createPortal3(
|
|
671
|
+
/* @__PURE__ */ jsx15("div", { className: "fixed inset-0 z-40 bg-black/20 backdrop-blur-sm", children: /* @__PURE__ */ jsx15(
|
|
672
|
+
PasswordModalElement,
|
|
673
|
+
{
|
|
674
|
+
message,
|
|
675
|
+
visible,
|
|
676
|
+
onConfirmAction: handleConfirm,
|
|
677
|
+
onCancelAction: handleCancel,
|
|
678
|
+
onCloseModalAction: handleClose
|
|
679
|
+
}
|
|
680
|
+
) }),
|
|
681
|
+
document.getElementById("password-modal-root")
|
|
682
|
+
)
|
|
683
|
+
] });
|
|
684
|
+
}
|
|
685
|
+
function usePasswordModal() {
|
|
686
|
+
const context = useContext4(PasswordModalContext);
|
|
687
|
+
if (!context) {
|
|
688
|
+
throw new Error("usePasswordModal must be used within a EmailModalProvider");
|
|
689
|
+
}
|
|
690
|
+
return context;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/ui/admin-panel/create-admin-element.tsx
|
|
694
|
+
import { cookiesSet, fetchCsrfClient as fetchCsrfClient2, goralysFetchClient as goralysFetchClient2, handleToastRequest as handleToastRequest2 } from "@goralys/core";
|
|
695
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
696
|
+
function CreateAdminElement({ onUpdateAction, syncKey, virtualSyncKey }) {
|
|
697
|
+
const [firstName, setFirstName] = useState8("");
|
|
698
|
+
const [lastName, setLastName] = useState8("");
|
|
699
|
+
const password = usePasswordModal();
|
|
700
|
+
const toast = useToast();
|
|
701
|
+
const createAdmin = async () => {
|
|
702
|
+
const pwd = await password.showPasswordModal("la cr\xE9ation du nouvel administrateur");
|
|
703
|
+
if (!pwd) return;
|
|
704
|
+
if (pwd.trim() === "") {
|
|
705
|
+
toast.showToast({
|
|
706
|
+
type: "warning",
|
|
707
|
+
title: "Mot de passe",
|
|
708
|
+
message: "Veuillez saisir un mot de passe."
|
|
709
|
+
});
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const csrfToken = await fetchCsrfClient2("create-admin");
|
|
713
|
+
const payload = {
|
|
714
|
+
"first-name": firstName,
|
|
715
|
+
"last-name": lastName,
|
|
716
|
+
"admin-password": pwd,
|
|
717
|
+
"csrf-token": csrfToken
|
|
718
|
+
};
|
|
719
|
+
const res = await goralysFetchClient2("POST", "admin/create", payload);
|
|
720
|
+
await handleToastRequest2(res, toast.showToast, false);
|
|
721
|
+
const data = await res?.json();
|
|
722
|
+
if (data.toastType === "info" && res.ok) {
|
|
723
|
+
cookiesSet(syncKey, "0");
|
|
724
|
+
cookiesSet(virtualSyncKey, "0");
|
|
725
|
+
onUpdateAction();
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
return /* @__PURE__ */ jsxs12("details", { className: "group/details", children: [
|
|
729
|
+
/* @__PURE__ */ jsxs12("summary", { className: "flex flex-row cursor-pointer", children: [
|
|
730
|
+
/* @__PURE__ */ jsx16(
|
|
731
|
+
"svg",
|
|
732
|
+
{
|
|
733
|
+
className: "w-5 h-5 text-gray-900 transition group-open/details:rotate-90",
|
|
734
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
735
|
+
width: "16",
|
|
736
|
+
height: "16",
|
|
737
|
+
fill: "currentColor",
|
|
738
|
+
viewBox: "0 0 16 16",
|
|
739
|
+
children: /* @__PURE__ */ jsx16(
|
|
740
|
+
"path",
|
|
741
|
+
{
|
|
742
|
+
fillRule: "evenodd",
|
|
743
|
+
d: "M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"
|
|
744
|
+
}
|
|
745
|
+
)
|
|
746
|
+
}
|
|
747
|
+
),
|
|
748
|
+
/* @__PURE__ */ jsx16("span", { children: "Cr\xE9er un administrateur" })
|
|
749
|
+
] }),
|
|
750
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-2 mt-2", children: [
|
|
751
|
+
/* @__PURE__ */ jsx16("p", { className: "-mb-2", children: "Entrer le nom du nouvel administrateur" }),
|
|
752
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex flex-row min-w-full gap-x-2.5", children: [
|
|
753
|
+
/* @__PURE__ */ jsx16(FloatingInput, { id: "create-admin-firstname", label: "Pr\xE9nom", onInput: (e) => setFirstName(e.currentTarget.value) }),
|
|
754
|
+
/* @__PURE__ */ jsx16(FloatingInput, { id: "create-admin-lastname", label: "Nom", onInput: (e) => setLastName(e.currentTarget.value) })
|
|
755
|
+
] })
|
|
756
|
+
] }),
|
|
757
|
+
/* @__PURE__ */ jsx16(Button, { type: "button", text: "Cr\xE9er", onClick: createAdmin })
|
|
758
|
+
] });
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/ui/admin-panel/replace-teacher-element.tsx
|
|
762
|
+
import { useState as useState9 } from "react";
|
|
763
|
+
|
|
764
|
+
// src/ui/basic/dropdown.tsx
|
|
765
|
+
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
766
|
+
function Dropdown({ label, children, openDefault = false }) {
|
|
767
|
+
return /* @__PURE__ */ jsxs13("details", { className: "group/details", open: openDefault, children: [
|
|
768
|
+
/* @__PURE__ */ jsxs13("summary", { className: "flex flex-row cursor-pointer", children: [
|
|
769
|
+
/* @__PURE__ */ jsx17(
|
|
770
|
+
"svg",
|
|
771
|
+
{
|
|
772
|
+
className: "w-5 h-5 text-gray-900 transition group-open/details:rotate-90",
|
|
773
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
774
|
+
width: "16",
|
|
775
|
+
height: "16",
|
|
776
|
+
fill: "currentColor",
|
|
777
|
+
viewBox: "0 0 16 16",
|
|
778
|
+
children: /* @__PURE__ */ jsx17(
|
|
779
|
+
"path",
|
|
780
|
+
{
|
|
781
|
+
fillRule: "evenodd",
|
|
782
|
+
d: "M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"
|
|
783
|
+
}
|
|
784
|
+
)
|
|
785
|
+
}
|
|
786
|
+
),
|
|
787
|
+
typeof label === "object" ? label : /* @__PURE__ */ jsx17("span", { children: label })
|
|
788
|
+
] }),
|
|
789
|
+
children
|
|
790
|
+
] });
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/ui/admin-panel/replace-teacher-element.tsx
|
|
794
|
+
import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
795
|
+
function ReplaceTeacherElement({ onReplaceAction, dropDown = true }) {
|
|
796
|
+
const [firstName, setFirstName] = useState9("");
|
|
797
|
+
const [lastName, setLastName] = useState9("");
|
|
798
|
+
return dropDown ? /* @__PURE__ */ jsxs14(Dropdown, { label: "Remplacer le professeur", children: [
|
|
799
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex flex-col gap-2 mt-2", children: [
|
|
800
|
+
/* @__PURE__ */ jsx18("p", { className: "-mb-2", children: "Entrer le nom du nouveau professeur" }),
|
|
801
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex flex-row min-w-full gap-x-2.5", children: [
|
|
802
|
+
/* @__PURE__ */ jsx18(FloatingInput, { id: "replace-teacher-firstname", label: "Pr\xE9nom", onInput: (e) => setFirstName(e.currentTarget.value) }),
|
|
803
|
+
/* @__PURE__ */ jsx18(FloatingInput, { id: "replace-teacher-lastname", label: "Nom", onInput: (e) => setLastName(e.currentTarget.value) })
|
|
804
|
+
] })
|
|
805
|
+
] }),
|
|
806
|
+
/* @__PURE__ */ jsx18(Button, { type: "button", text: "Remplacer", onClick: () => onReplaceAction(firstName, lastName) })
|
|
807
|
+
] }) : /* @__PURE__ */ jsxs14(Fragment4, { children: [
|
|
808
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex flex-col gap-2 mt-2", children: [
|
|
809
|
+
/* @__PURE__ */ jsx18("p", { className: "-mb-2", children: "Entrer le nom du nouveau professeur" }),
|
|
810
|
+
/* @__PURE__ */ jsx18(FloatingInput, { id: "replace-teacher-firstname", label: "Pr\xE9nom", onInput: (e) => setFirstName(e.currentTarget.value) }),
|
|
811
|
+
/* @__PURE__ */ jsx18(FloatingInput, { id: "replace-teacher-lastname", label: "Nom", onInput: (e) => setLastName(e.currentTarget.value) })
|
|
812
|
+
] }),
|
|
813
|
+
/* @__PURE__ */ jsx18(Button, { type: "button", text: "Remplacer", onClick: () => onReplaceAction(firstName, lastName) })
|
|
814
|
+
] });
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/ui/admin-panel/user-card.tsx
|
|
818
|
+
import { AcademicCapIcon, BookOpenIcon } from "@heroicons/react/24/outline";
|
|
819
|
+
import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
820
|
+
function UserCard({ user }) {
|
|
821
|
+
const router = useGoralysUI().useRouter();
|
|
822
|
+
return /* @__PURE__ */ jsx19(Card, { className: "flex-col w-200! bg-sky-200 gap-1 p-1 mb-1 mt-1", children: /* @__PURE__ */ jsxs15("div", { className: "flex flex-row justify-between items-center", children: [
|
|
823
|
+
/* @__PURE__ */ jsxs15("div", {
|
|
824
|
+
className: "flex flex-row",
|
|
825
|
+
// No admins here.
|
|
826
|
+
children: [
|
|
827
|
+
user.role == "teacher" ? /* @__PURE__ */ jsx19(BookOpenIcon, { width: 27.5, className: "mr-1.5" }) : /* @__PURE__ */ jsx19(AcademicCapIcon, { width: 27.5, className: "mr-1.5" }),
|
|
828
|
+
/* @__PURE__ */ jsxs15("strong", { children: [
|
|
829
|
+
user.fullName.length > 25 ? user.fullName.substring(0, 24) + "..." : user.fullName,
|
|
830
|
+
" (",
|
|
831
|
+
user.username,
|
|
832
|
+
")"
|
|
833
|
+
] })
|
|
834
|
+
]
|
|
835
|
+
}),
|
|
836
|
+
/* @__PURE__ */ jsx19("div", { className: "flex flex-row w-100 gap-1 place-content-end", children: /* @__PURE__ */ jsx19(
|
|
837
|
+
Button,
|
|
838
|
+
{
|
|
839
|
+
type: "button",
|
|
840
|
+
className: "w-50!",
|
|
841
|
+
text: "G\xE9rer le compte",
|
|
842
|
+
onClick: () => router.push("/admin/user?u=" + user.publicId)
|
|
843
|
+
}
|
|
844
|
+
) })
|
|
845
|
+
] }) });
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/ui/admin-panel/users-search-bar.tsx
|
|
849
|
+
import { useEffect as useEffect5, useState as useState10 } from "react";
|
|
850
|
+
import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
851
|
+
var roleFields = {
|
|
852
|
+
all: "Tous",
|
|
853
|
+
student: "\xC9l\xE8ves",
|
|
854
|
+
teacher: "Professeurs"
|
|
855
|
+
};
|
|
856
|
+
function UsersSearchBar({ type, users, setCurrentUsers }) {
|
|
857
|
+
const [currentRole, setCurrentRole] = useState10("all");
|
|
858
|
+
const [searchText, setSearchText] = useState10("");
|
|
859
|
+
const handleSearch = (e) => {
|
|
860
|
+
setSearchText(e.currentTarget.value);
|
|
861
|
+
};
|
|
862
|
+
const sortUsers = (list) => {
|
|
863
|
+
return [...list].sort((a, b) => {
|
|
864
|
+
if (a.role !== b.role) return a.role === "teacher" ? -1 : 1;
|
|
865
|
+
return a.fullName.trim().toLowerCase().localeCompare(b.fullName.trim().toLowerCase(), "fr");
|
|
866
|
+
});
|
|
867
|
+
};
|
|
868
|
+
useEffect5(() => {
|
|
869
|
+
if (!users) return;
|
|
870
|
+
const search = searchText.trim().toLowerCase();
|
|
871
|
+
const sorted = sortUsers(
|
|
872
|
+
users.filter((u) => {
|
|
873
|
+
const matchesRole = currentRole === "all" || u.role === currentRole;
|
|
874
|
+
const matchesName = !search || u.fullName.trim().toLowerCase().includes(search);
|
|
875
|
+
return matchesRole && matchesName;
|
|
876
|
+
})
|
|
877
|
+
);
|
|
878
|
+
setCurrentUsers((prev) => {
|
|
879
|
+
if (JSON.stringify(prev) === JSON.stringify(sorted)) return prev;
|
|
880
|
+
return sorted;
|
|
881
|
+
});
|
|
882
|
+
}, [searchText, currentRole, users, setCurrentUsers]);
|
|
883
|
+
return /* @__PURE__ */ jsxs16("div", { className: "flex flex-row gap-2 items-end mb-4 w-175!", children: [
|
|
884
|
+
/* @__PURE__ */ jsx20(FloatingInput, { id: "users-search-" + type, label: "Rechercher par nom", onInput: handleSearch }),
|
|
885
|
+
/* @__PURE__ */ jsxs16("div", { className: "relative pb-0 mb-1", children: [
|
|
886
|
+
/* @__PURE__ */ jsx20(
|
|
887
|
+
"select",
|
|
888
|
+
{
|
|
889
|
+
className: "border-0 border-b-2 border-sky-300 appearance-none\n cursor-pointer outline-none focus:ring-0 text-base leading-5\n text-heading pb-0 pr-5 subjects-search-select",
|
|
890
|
+
value: currentRole,
|
|
891
|
+
onChange: (e) => setCurrentRole(e.target.value),
|
|
892
|
+
children: Object.entries(roleFields).map(([key, label]) => /* @__PURE__ */ jsx20("option", { value: key, children: label }, key))
|
|
893
|
+
}
|
|
894
|
+
),
|
|
895
|
+
/* @__PURE__ */ jsx20(
|
|
896
|
+
"span",
|
|
897
|
+
{
|
|
898
|
+
className: "absolute bottom-0 left-0 h-0.5 bg-sky-500\n transition-all duration-300 ease-in-out\n peer-focus:w-full subjects-search-underline"
|
|
899
|
+
}
|
|
900
|
+
)
|
|
901
|
+
] })
|
|
902
|
+
] });
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// src/ui/admin-panel/subjects/status-legend.tsx
|
|
906
|
+
import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
907
|
+
var STATUS_LABELS = {
|
|
908
|
+
not_submitted: { label: "Brouillon", color: "#2563EB" },
|
|
909
|
+
submitted: { label: "Envoy\xE9e", color: "#D97706" },
|
|
910
|
+
approved: { label: "Valid\xE9e", color: "#059669" },
|
|
911
|
+
rejected: { label: "Rejet\xE9e", color: "#DC2626" }
|
|
912
|
+
};
|
|
913
|
+
function StatusLegend({ total, submitted, approved, rejected, notSubmitted }) {
|
|
914
|
+
const counts = {
|
|
915
|
+
not_submitted: notSubmitted,
|
|
916
|
+
submitted,
|
|
917
|
+
approved,
|
|
918
|
+
rejected
|
|
919
|
+
};
|
|
920
|
+
return /* @__PURE__ */ jsx21("ul", { className: "flex flex-col gap-1 self-center mr-10", children: Object.keys(STATUS_LABELS).map((key) => /* @__PURE__ */ jsxs17("li", { className: "flex items-center gap-2 text-sm", children: [
|
|
921
|
+
/* @__PURE__ */ jsx21(
|
|
922
|
+
"span",
|
|
923
|
+
{
|
|
924
|
+
className: "inline-block w-2.5 h-2.5 rounded-full shrink-0",
|
|
925
|
+
style: { backgroundColor: STATUS_LABELS[key].color }
|
|
926
|
+
}
|
|
927
|
+
),
|
|
928
|
+
/* @__PURE__ */ jsx21("span", { children: STATUS_LABELS[key].label }),
|
|
929
|
+
/* @__PURE__ */ jsxs17("span", { className: "text-gray-500 ml-auto", children: [
|
|
930
|
+
counts[key],
|
|
931
|
+
" / ",
|
|
932
|
+
total
|
|
933
|
+
] })
|
|
934
|
+
] }, key)) });
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// src/ui/admin-panel/subjects/subject-students-donut.tsx
|
|
938
|
+
import { useEffect as useEffect6, useState as useState11 } from "react";
|
|
939
|
+
import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
940
|
+
var STATUS_CONFIG = {
|
|
941
|
+
empty: { percent: 0, color: "#D4D4D8" },
|
|
942
|
+
// zinc-300
|
|
943
|
+
not_submitted: { percent: 33, color: "#2563EB" },
|
|
944
|
+
// blue-600
|
|
945
|
+
submitted: { percent: 66, color: "#D97706" },
|
|
946
|
+
// amber-600
|
|
947
|
+
approved: { percent: 100, color: "#059669" },
|
|
948
|
+
// green-600
|
|
949
|
+
rejected: { percent: 100, color: "#DC2626" }
|
|
950
|
+
// red-600
|
|
951
|
+
};
|
|
952
|
+
var TRACK_COLOR = "#D4D4D8";
|
|
953
|
+
function SubjectStudentsDonut({ status, size = 48 }) {
|
|
954
|
+
const { percent, color } = STATUS_CONFIG[status];
|
|
955
|
+
const strokeWidth = size * 0.15;
|
|
956
|
+
const radius = (size - strokeWidth) / 2;
|
|
957
|
+
const circumference = 2 * Math.PI * radius;
|
|
958
|
+
const [offset, setOffset] = useState11(circumference);
|
|
959
|
+
useEffect6(() => {
|
|
960
|
+
const targetOffset = circumference - percent / 100 * circumference;
|
|
961
|
+
const raf = requestAnimationFrame(() => setOffset(targetOffset));
|
|
962
|
+
return () => cancelAnimationFrame(raf);
|
|
963
|
+
}, [percent, circumference]);
|
|
964
|
+
return /* @__PURE__ */ jsxs18("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, children: [
|
|
965
|
+
/* @__PURE__ */ jsx22("circle", { cx: size / 2, cy: size / 2, r: radius, fill: "none", stroke: TRACK_COLOR, strokeWidth }),
|
|
966
|
+
/* @__PURE__ */ jsx22(
|
|
967
|
+
"circle",
|
|
968
|
+
{
|
|
969
|
+
cx: size / 2,
|
|
970
|
+
cy: size / 2,
|
|
971
|
+
r: radius,
|
|
972
|
+
fill: "none",
|
|
973
|
+
stroke: color,
|
|
974
|
+
strokeWidth,
|
|
975
|
+
strokeDasharray: circumference,
|
|
976
|
+
strokeDashoffset: offset,
|
|
977
|
+
strokeLinecap: "round",
|
|
978
|
+
transform: `rotate(-90 ${size / 2} ${size / 2})`,
|
|
979
|
+
style: { transition: "stroke-dashoffset 0.6s ease-out" }
|
|
980
|
+
}
|
|
981
|
+
)
|
|
982
|
+
] });
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// src/ui/admin-panel/subjects/subject-teachers-donut.tsx
|
|
986
|
+
import { useEffect as useEffect7, useState as useState12 } from "react";
|
|
987
|
+
import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
988
|
+
var STATUS_CONFIG2 = {
|
|
989
|
+
empty: { color: "#D4D4D8" },
|
|
990
|
+
// zinc-300
|
|
991
|
+
not_submitted: { color: "#2563EB" },
|
|
992
|
+
// blue-600
|
|
993
|
+
submitted: { color: "#D97706" },
|
|
994
|
+
// amber-600
|
|
995
|
+
approved: { color: "#059669" },
|
|
996
|
+
// green-600
|
|
997
|
+
rejected: { color: "#DC2626" }
|
|
998
|
+
// red-600
|
|
999
|
+
};
|
|
1000
|
+
var TRACK_COLOR2 = "#D4D4D8";
|
|
1001
|
+
function SubjectTeachersDonut({ size = 192, total, notSubmitted, submitted, rejected, approved }) {
|
|
1002
|
+
const strokeWidth = size * 0.15;
|
|
1003
|
+
const radius = (size - strokeWidth) / 2;
|
|
1004
|
+
const circumference = 2 * Math.PI * radius;
|
|
1005
|
+
const approvedPercent = approved / total;
|
|
1006
|
+
const rejectedPercent = rejected / total;
|
|
1007
|
+
const submittedPercent = submitted / total;
|
|
1008
|
+
const notSubmittedPercent = notSubmitted / total;
|
|
1009
|
+
const [notSubmittedOffset, setNotSubmittedOffset] = useState12(circumference);
|
|
1010
|
+
const [submittedOffset, setSubmittedOffset] = useState12(circumference);
|
|
1011
|
+
const [rejectedOffset, setRejectedOffset] = useState12(circumference);
|
|
1012
|
+
const [approvedOffset, setApprovedOffset] = useState12(circumference);
|
|
1013
|
+
useEffect7(() => {
|
|
1014
|
+
const raf = requestAnimationFrame(() => {
|
|
1015
|
+
let sum = approvedPercent;
|
|
1016
|
+
setApprovedOffset(circumference - sum * circumference);
|
|
1017
|
+
sum += rejectedPercent;
|
|
1018
|
+
setRejectedOffset(circumference - sum * circumference);
|
|
1019
|
+
sum += submittedPercent;
|
|
1020
|
+
setSubmittedOffset(circumference - sum * circumference);
|
|
1021
|
+
setNotSubmittedOffset(0);
|
|
1022
|
+
});
|
|
1023
|
+
return () => cancelAnimationFrame(raf);
|
|
1024
|
+
}, [circumference, approvedPercent, rejectedPercent, submittedPercent]);
|
|
1025
|
+
const circles = [
|
|
1026
|
+
{ status: "not_submitted", color: STATUS_CONFIG2["not_submitted"].color, percent: notSubmittedPercent, offset: notSubmittedOffset },
|
|
1027
|
+
{ status: "submitted", color: STATUS_CONFIG2["submitted"].color, percent: submittedPercent, offset: submittedOffset },
|
|
1028
|
+
{ status: "rejected", color: STATUS_CONFIG2["rejected"].color, percent: rejectedPercent, offset: rejectedOffset },
|
|
1029
|
+
{ status: "approved", color: STATUS_CONFIG2["approved"].color, percent: approvedPercent, offset: approvedOffset }
|
|
1030
|
+
];
|
|
1031
|
+
let delay = circles.filter((c) => c.percent > 0).length;
|
|
1032
|
+
const ANIM_DURATION = 0.6;
|
|
1033
|
+
return /* @__PURE__ */ jsxs19("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "mr-2", children: [
|
|
1034
|
+
/* @__PURE__ */ jsx23("circle", { cx: size / 2, cy: size / 2, r: radius, fill: "none", stroke: TRACK_COLOR2, strokeWidth }),
|
|
1035
|
+
circles.map(
|
|
1036
|
+
(c) => c.percent > 0 && /* @__PURE__ */ jsx23(
|
|
1037
|
+
"circle",
|
|
1038
|
+
{
|
|
1039
|
+
cx: size / 2,
|
|
1040
|
+
cy: size / 2,
|
|
1041
|
+
r: radius,
|
|
1042
|
+
fill: "none",
|
|
1043
|
+
stroke: c.color,
|
|
1044
|
+
strokeWidth,
|
|
1045
|
+
strokeDasharray: circumference,
|
|
1046
|
+
strokeDashoffset: c.offset,
|
|
1047
|
+
strokeLinecap: "butt",
|
|
1048
|
+
transform: `rotate(-90 ${size / 2} ${size / 2})`,
|
|
1049
|
+
style: { transition: `stroke-dashoffset ${ANIM_DURATION}s ease-out ${ANIM_DURATION * delay--}s` }
|
|
1050
|
+
},
|
|
1051
|
+
c.status
|
|
1052
|
+
)
|
|
1053
|
+
)
|
|
1054
|
+
] });
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// src/ui/admin-panel/subjects/user-subjects-student-card.tsx
|
|
1058
|
+
import { getShortFromLong, getStatusLabel } from "@goralys/core";
|
|
1059
|
+
import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1060
|
+
function UserSubjectsStudentCard({ subjects }) {
|
|
1061
|
+
return /* @__PURE__ */ jsx24(Card, { className: "flex flex-col grow bg-sky-200 m-2", children: subjects?.map((s, i) => {
|
|
1062
|
+
const realStatus = s.subject.trim() === "" && s.status === "not_submitted" ? "empty" : s.status;
|
|
1063
|
+
return /* @__PURE__ */ jsxs20(Card, { className: "flex flex-row w-full justify-between", children: [
|
|
1064
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex flex-col", children: [
|
|
1065
|
+
/* @__PURE__ */ jsx24("p", { className: "text-lg font-bold", children: getShortFromLong(s.topic) }),
|
|
1066
|
+
/* @__PURE__ */ jsxs20("p", { className: "italic", children: [
|
|
1067
|
+
"Statut: ",
|
|
1068
|
+
getStatusLabel(s.status)
|
|
1069
|
+
] })
|
|
1070
|
+
] }),
|
|
1071
|
+
/* @__PURE__ */ jsx24(SubjectStudentsDonut, { status: realStatus })
|
|
1072
|
+
] }, s.teacherToken + "#" + i);
|
|
1073
|
+
}) });
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// src/ui/admin-panel/subjects/user-subjects-teacher-card.tsx
|
|
1077
|
+
import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1078
|
+
function UserSubjectsTeacherCard({ subjects }) {
|
|
1079
|
+
if (!subjects) return /* @__PURE__ */ jsx25(Fragment5, {});
|
|
1080
|
+
const total = subjects.length;
|
|
1081
|
+
const notSubmitted = subjects.filter((s) => s.status === "not_submitted").length;
|
|
1082
|
+
const submitted = subjects.filter((s) => s.status === "submitted").length;
|
|
1083
|
+
const rejected = subjects.filter((s) => s.status === "rejected").length;
|
|
1084
|
+
const approved = subjects.filter((s) => s.status === "approved").length;
|
|
1085
|
+
return /* @__PURE__ */ jsxs21(Card, { className: "flex flex-col grow bg-sky-200 m-2", children: [
|
|
1086
|
+
/* @__PURE__ */ jsx25(CardTitle, { title: "Sujets" }),
|
|
1087
|
+
/* @__PURE__ */ jsxs21(Card, { className: "flex flex-row w-full", children: [
|
|
1088
|
+
/* @__PURE__ */ jsx25(
|
|
1089
|
+
SubjectTeachersDonut,
|
|
1090
|
+
{
|
|
1091
|
+
total,
|
|
1092
|
+
approved,
|
|
1093
|
+
rejected,
|
|
1094
|
+
submitted,
|
|
1095
|
+
notSubmitted
|
|
1096
|
+
}
|
|
1097
|
+
),
|
|
1098
|
+
/* @__PURE__ */ jsx25("span", { className: "basis-1/6 grow-0 shrink min-w-0" }),
|
|
1099
|
+
/* @__PURE__ */ jsx25(StatusLegend, { total, approved, rejected, submitted, notSubmitted })
|
|
1100
|
+
] }, subjects.length)
|
|
1101
|
+
] });
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// src/ui/basic/cookies-banner.tsx
|
|
1105
|
+
import { useEffect as useEffect8, useState as useState13 } from "react";
|
|
1106
|
+
|
|
1107
|
+
// ../node_modules/@sidekickicons/react/24/outline/esm/CookieIcon.js
|
|
1108
|
+
import * as React5 from "react";
|
|
1109
|
+
function CookieIcon({
|
|
1110
|
+
title,
|
|
1111
|
+
titleId,
|
|
1112
|
+
...props
|
|
1113
|
+
}, svgRef) {
|
|
1114
|
+
return /* @__PURE__ */ React5.createElement("svg", Object.assign({
|
|
1115
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1116
|
+
viewBox: "0 0 24 24",
|
|
1117
|
+
strokeWidth: 1.5,
|
|
1118
|
+
stroke: "currentColor",
|
|
1119
|
+
"aria-hidden": "true",
|
|
1120
|
+
"data-slot": "icon",
|
|
1121
|
+
fill: "none",
|
|
1122
|
+
ref: svgRef,
|
|
1123
|
+
"aria-labelledby": titleId
|
|
1124
|
+
}, props), title ? /* @__PURE__ */ React5.createElement("title", {
|
|
1125
|
+
id: titleId
|
|
1126
|
+
}, title) : null, /* @__PURE__ */ React5.createElement("path", {
|
|
1127
|
+
strokeLinecap: "round",
|
|
1128
|
+
strokeLinejoin: "round",
|
|
1129
|
+
d: "M11.602 3.009a9 9 0 1 0 9.389 9.389 2.75 2.75 0 0 1-4.6-1.03c-.409.24-.883.381-1.391.381a2.75 2.75 0 0 1-2.371-4.142 2.75 2.75 0 0 1-1.027-4.598zM19 9.499v0M18 6v0m-2.5 2.5v0m-1-3.5v0m-6 2.5v0m-2 7v0m3 1.51h1.25m-4.063-5.796 1.125 1m7.215 4.546.946-1M13 18.009v0m-2-5.01v0"
|
|
1130
|
+
}));
|
|
1131
|
+
}
|
|
1132
|
+
var ForwardRef = /* @__PURE__ */ React5.forwardRef(CookieIcon);
|
|
1133
|
+
var CookieIcon_default = ForwardRef;
|
|
1134
|
+
|
|
1135
|
+
// src/ui/basic/cookies-banner.tsx
|
|
1136
|
+
import { XMarkIcon } from "@heroicons/react/24/outline";
|
|
1137
|
+
import { COOKIES_DISMISSED_KEY, cookiesGet as cookiesGet2, cookiesSet as cookiesSet2 } from "@goralys/core";
|
|
1138
|
+
import { jsx as jsx26, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1139
|
+
function CookieBanner() {
|
|
1140
|
+
const Link = useGoralysUI().Link;
|
|
1141
|
+
const [visible, setVisible] = useState13(false);
|
|
1142
|
+
useEffect8(() => {
|
|
1143
|
+
if (!cookiesGet2(COOKIES_DISMISSED_KEY)) {
|
|
1144
|
+
setVisible(true);
|
|
1145
|
+
}
|
|
1146
|
+
}, []);
|
|
1147
|
+
if (!visible) return null;
|
|
1148
|
+
const discard = () => {
|
|
1149
|
+
cookiesSet2(COOKIES_DISMISSED_KEY, "1", "/", 60 * 60 * 24 * 365);
|
|
1150
|
+
setVisible(false);
|
|
1151
|
+
};
|
|
1152
|
+
return /* @__PURE__ */ jsxs22("div", { className: "fixed bottom-4 left-1/2 -translate-x-1/2 w-fit max-w-md z-10 bg-sky-200 rounded-md shadow-lg p-4 flex flex-col gap-2", children: [
|
|
1153
|
+
/* @__PURE__ */ jsxs22("div", { className: "flex items-center justify-between", children: [
|
|
1154
|
+
/* @__PURE__ */ jsxs22("p", { className: "font-semibold flex items-center gap-1", children: [
|
|
1155
|
+
/* @__PURE__ */ jsx26(CookieIcon_default, { className: "size-5" }),
|
|
1156
|
+
"Cookies"
|
|
1157
|
+
] }),
|
|
1158
|
+
/* @__PURE__ */ jsx26(
|
|
1159
|
+
"button",
|
|
1160
|
+
{
|
|
1161
|
+
onClick: discard,
|
|
1162
|
+
className: "cursor-pointer hover:bg-sky-300 hover:shadow-md hover:-translate-y-1 rounded-md p-0.5\n transition-all ease-out duration-500",
|
|
1163
|
+
title: "Fermer",
|
|
1164
|
+
children: /* @__PURE__ */ jsx26(XMarkIcon, { className: "size-4" })
|
|
1165
|
+
}
|
|
1166
|
+
)
|
|
1167
|
+
] }),
|
|
1168
|
+
/* @__PURE__ */ jsxs22("p", { className: "text-sm", children: [
|
|
1169
|
+
"Goralys utilise uniquement des cookies fonctionnels. Pour plus d'informations, consultez",
|
|
1170
|
+
" ",
|
|
1171
|
+
/* @__PURE__ */ jsx26(Link, { href: "/cookies", className: "text-sky-600 underline hover:text-sky-800 transition-colors duration-200", children: "cette page" })
|
|
1172
|
+
] })
|
|
1173
|
+
] });
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// src/ui/basic/goralys-logo-loader.tsx
|
|
1177
|
+
import { useEffect as useEffect9, useRef as useRef3 } from "react";
|
|
1178
|
+
import { LOGO_PATH } from "@goralys/core";
|
|
1179
|
+
import { jsx as jsx27, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1180
|
+
function GoralysLogoLoader() {
|
|
1181
|
+
const mainPathRef = useRef3(null);
|
|
1182
|
+
const eyePathRef = useRef3(null);
|
|
1183
|
+
const mainColorRef = useRef3(null);
|
|
1184
|
+
const eyeColorRef = useRef3(null);
|
|
1185
|
+
useEffect9(() => {
|
|
1186
|
+
[mainPathRef, eyePathRef, mainColorRef, eyeColorRef].forEach((ref) => {
|
|
1187
|
+
const path = ref.current;
|
|
1188
|
+
if (!path) return;
|
|
1189
|
+
const len = path.getTotalLength();
|
|
1190
|
+
path.style.strokeDasharray = String(len);
|
|
1191
|
+
path.style.setProperty("--path-length", String(len));
|
|
1192
|
+
path.style.strokeDashoffset = String(len);
|
|
1193
|
+
});
|
|
1194
|
+
}, []);
|
|
1195
|
+
return /* @__PURE__ */ jsxs23("div", { className: "flex items-center justify-center rounded-xl bg-sky-500 p-4 w-fit mx-auto", children: [
|
|
1196
|
+
/* @__PURE__ */ jsx27("style", { children: `
|
|
1197
|
+
.chameleon-path {
|
|
1198
|
+
fill: none;
|
|
1199
|
+
stroke-linecap: round;
|
|
1200
|
+
stroke-linejoin: round;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.chameleon-main {
|
|
1204
|
+
stroke: #ffffff;
|
|
1205
|
+
stroke-width: 4;
|
|
1206
|
+
animation: chameleonDraw 9s cubic-bezier(0.37, 0, 0.63, 1) infinite;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.chameleon-eye {
|
|
1210
|
+
stroke: #ffffff;
|
|
1211
|
+
stroke-width: 4;
|
|
1212
|
+
animation: chameleonDraw 9s cubic-bezier(0.37, 0, 0.63, 1) infinite;
|
|
1213
|
+
animation-delay: 0.3s;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
@keyframes chameleonDraw {
|
|
1217
|
+
0% { stroke-dashoffset: var(--path-length); opacity: 0; }
|
|
1218
|
+
3% { opacity: 1; }
|
|
1219
|
+
50% { stroke-dashoffset: 0; opacity: 1; }
|
|
1220
|
+
62% { stroke-dashoffset: 0; opacity: 1; }
|
|
1221
|
+
80% { opacity: 0.6; }
|
|
1222
|
+
97% { opacity: 0; }
|
|
1223
|
+
100% { stroke-dashoffset: var(--path-length); opacity: 0; }
|
|
1224
|
+
}
|
|
1225
|
+
` }),
|
|
1226
|
+
/* @__PURE__ */ jsxs23("svg", { viewBox: "345 350 305 315", fill: "none", "aria-label": "Chargement\u2026", role: "img", className: "block w-56 h-56", children: [
|
|
1227
|
+
/* @__PURE__ */ jsx27("path", { ref: mainPathRef, className: "chameleon-path chameleon-main", d: LOGO_PATH.main }),
|
|
1228
|
+
/* @__PURE__ */ jsx27("path", { ref: eyePathRef, className: "chameleon-path chameleon-eye", d: LOGO_PATH.eye })
|
|
1229
|
+
] })
|
|
1230
|
+
] });
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// src/ui/inputs/checkbox.tsx
|
|
1234
|
+
import { useId } from "react";
|
|
1235
|
+
import { jsx as jsx28, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
1236
|
+
function Checkbox({ id, label, setValueAction, defaultValue, className, disabled = false }) {
|
|
1237
|
+
const generatedId = useId();
|
|
1238
|
+
const checkBoxId = id || generatedId;
|
|
1239
|
+
return /* @__PURE__ */ jsxs24("div", { className: `flex items-center gap-0.5 ${className ?? ""}`, children: [
|
|
1240
|
+
/* @__PURE__ */ jsxs24(
|
|
1241
|
+
"label",
|
|
1242
|
+
{
|
|
1243
|
+
className: `relative flex items-center justify-center rounded-full p-1 ${disabled ? "cursor-not-allowed" : "cursor-pointer"}`,
|
|
1244
|
+
htmlFor: checkBoxId,
|
|
1245
|
+
"data-ripple-dark": "true",
|
|
1246
|
+
children: [
|
|
1247
|
+
/* @__PURE__ */ jsx28(
|
|
1248
|
+
"input",
|
|
1249
|
+
{
|
|
1250
|
+
disabled,
|
|
1251
|
+
defaultChecked: defaultValue,
|
|
1252
|
+
onChange: (e) => setValueAction(e.target.checked),
|
|
1253
|
+
id: checkBoxId,
|
|
1254
|
+
type: "checkbox",
|
|
1255
|
+
className: "peer h-4 w-4 appearance-none rounded border border-sky-400\n bg-white shadow hover:shadow-md transition-all\n before:absolute before:top-2/4 before:left-2/4 before:block\n before:h-5 before:w-5 before:-translate-y-2/4 before:-translate-x-2/4 before:rounded-full\n before:bg-sky-500 before:opacity-0 before:transition-opacity\n checked:border-sky-600 checked:bg-sky-600\n checked:before:bg-sky-500 hover:before:opacity-10\n cursor-pointer disabled:cursor-not-allowed"
|
|
1256
|
+
}
|
|
1257
|
+
),
|
|
1258
|
+
/* @__PURE__ */ jsx28(
|
|
1259
|
+
"span",
|
|
1260
|
+
{
|
|
1261
|
+
className: "pointer-events-none absolute inset-0 flex items-center justify-center\n text-white opacity-0 transition-opacity peer-checked:opacity-100",
|
|
1262
|
+
children: /* @__PURE__ */ jsx28(
|
|
1263
|
+
"svg",
|
|
1264
|
+
{
|
|
1265
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1266
|
+
className: "h-3.5 w-3.5",
|
|
1267
|
+
viewBox: "0 0 20 20",
|
|
1268
|
+
fill: "currentColor",
|
|
1269
|
+
stroke: "currentColor",
|
|
1270
|
+
strokeWidth: "1",
|
|
1271
|
+
children: /* @__PURE__ */ jsx28(
|
|
1272
|
+
"path",
|
|
1273
|
+
{
|
|
1274
|
+
fillRule: "evenodd",
|
|
1275
|
+
d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8\n 12.586l7.293-7.293a1 1 0 011.414 0z",
|
|
1276
|
+
clipRule: "evenodd"
|
|
1277
|
+
}
|
|
1278
|
+
)
|
|
1279
|
+
}
|
|
1280
|
+
)
|
|
1281
|
+
}
|
|
1282
|
+
)
|
|
1283
|
+
]
|
|
1284
|
+
}
|
|
1285
|
+
),
|
|
1286
|
+
/* @__PURE__ */ jsx28("label", { className: "text-black text-sm cursor-pointer", htmlFor: checkBoxId, children: label })
|
|
1287
|
+
] });
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// src/ui/inputs/input-text-file.tsx
|
|
1291
|
+
import { useState as useState14 } from "react";
|
|
1292
|
+
import { ArrowUpTrayIcon } from "@heroicons/react/24/outline";
|
|
1293
|
+
import clsx6 from "clsx";
|
|
1294
|
+
import { jsx as jsx29, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
1295
|
+
function InputTextFile({ text, maxSizeKB, onFileSelected }) {
|
|
1296
|
+
const [fileName, setFileName] = useState14(null);
|
|
1297
|
+
const [dragging, setDragging] = useState14(false);
|
|
1298
|
+
const toast = useToast();
|
|
1299
|
+
const handleFile = (file) => {
|
|
1300
|
+
if (!file) {
|
|
1301
|
+
toast.showToast({
|
|
1302
|
+
type: "warning",
|
|
1303
|
+
title: "Fichier",
|
|
1304
|
+
message: "Merci de fournir un fichier"
|
|
1305
|
+
});
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
if (!(file.name.endsWith(".txt") || file.name.endsWith(".odt") || file.name.endsWith(".docx"))) {
|
|
1309
|
+
toast.showToast({
|
|
1310
|
+
type: "warning",
|
|
1311
|
+
title: "Fichier",
|
|
1312
|
+
message: "Merci de fournir un fichier zip (.zip)"
|
|
1313
|
+
});
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
setFileName(file.name || null);
|
|
1317
|
+
onFileSelected(file);
|
|
1318
|
+
return;
|
|
1319
|
+
};
|
|
1320
|
+
return /* @__PURE__ */ jsxs25(
|
|
1321
|
+
"label",
|
|
1322
|
+
{
|
|
1323
|
+
htmlFor: "doc",
|
|
1324
|
+
className: clsx6("flex items-center gap-0 rounded-xs border border-sky-400 border-dashed bg-sky-200 cursor-pointer", {
|
|
1325
|
+
"bg-sky-300": dragging
|
|
1326
|
+
}),
|
|
1327
|
+
onDragOver: (e) => {
|
|
1328
|
+
e.preventDefault();
|
|
1329
|
+
setDragging(true);
|
|
1330
|
+
},
|
|
1331
|
+
onDragLeave: () => setDragging(false),
|
|
1332
|
+
onDrop: (e) => {
|
|
1333
|
+
e.preventDefault();
|
|
1334
|
+
setDragging(false);
|
|
1335
|
+
handleFile(e.dataTransfer?.files[0]);
|
|
1336
|
+
},
|
|
1337
|
+
children: [
|
|
1338
|
+
/* @__PURE__ */ jsx29(ArrowUpTrayIcon, { className: "size-7 ml-1 mr-2" }),
|
|
1339
|
+
/* @__PURE__ */ jsxs25("div", { className: "", children: [
|
|
1340
|
+
/* @__PURE__ */ jsx29("h4", { className: "text-base font-semibold text-gray-700", children: fileName || text }),
|
|
1341
|
+
/* @__PURE__ */ jsxs25("span", { className: "text-sm text-gray-500", children: [
|
|
1342
|
+
"Max ",
|
|
1343
|
+
maxSizeKB,
|
|
1344
|
+
" KO (.txt, .odt, .docx)"
|
|
1345
|
+
] })
|
|
1346
|
+
] }),
|
|
1347
|
+
/* @__PURE__ */ jsx29(
|
|
1348
|
+
"input",
|
|
1349
|
+
{
|
|
1350
|
+
type: "file",
|
|
1351
|
+
id: "doc",
|
|
1352
|
+
name: "doc",
|
|
1353
|
+
accept: ".txt,.odt,.docx",
|
|
1354
|
+
hidden: true,
|
|
1355
|
+
onChange: (e) => handleFile(e.target.files?.[0])
|
|
1356
|
+
},
|
|
1357
|
+
`input-file-input-${text}-max-size-${maxSizeKB}-kb`
|
|
1358
|
+
)
|
|
1359
|
+
]
|
|
1360
|
+
},
|
|
1361
|
+
`input-file-label-${text}-max-size-${maxSizeKB}-kb`
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// src/ui/inputs/input-zip-file.tsx
|
|
1366
|
+
import { useState as useState15 } from "react";
|
|
1367
|
+
import { ArrowUpTrayIcon as ArrowUpTrayIcon2 } from "@heroicons/react/24/outline";
|
|
1368
|
+
import clsx7 from "clsx";
|
|
1369
|
+
import { jsx as jsx30, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
1370
|
+
function InputZipFile({ text, onFileSelected }) {
|
|
1371
|
+
const [fileName, setFileName] = useState15(null);
|
|
1372
|
+
const [dragging, setDragging] = useState15(false);
|
|
1373
|
+
const toast = useToast();
|
|
1374
|
+
const handleFile = (file) => {
|
|
1375
|
+
if (!file) {
|
|
1376
|
+
toast.showToast({
|
|
1377
|
+
type: "warning",
|
|
1378
|
+
title: "Fichier",
|
|
1379
|
+
message: "Merci de fournir un fichier"
|
|
1380
|
+
});
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
if (!file.name.endsWith(".zip")) {
|
|
1384
|
+
toast.showToast({
|
|
1385
|
+
type: "warning",
|
|
1386
|
+
title: "Fichier",
|
|
1387
|
+
message: "Merci de fournir un fichier zip (.zip)"
|
|
1388
|
+
});
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
setFileName(file.name || null);
|
|
1392
|
+
onFileSelected(file);
|
|
1393
|
+
return;
|
|
1394
|
+
};
|
|
1395
|
+
return /* @__PURE__ */ jsxs26(
|
|
1396
|
+
"label",
|
|
1397
|
+
{
|
|
1398
|
+
htmlFor: "doc",
|
|
1399
|
+
className: clsx7("flex items-center gap-0 rounded-xs border border-sky-400 border-dashed bg-sky-200 cursor-pointer", {
|
|
1400
|
+
"bg-sky-300": dragging
|
|
1401
|
+
}),
|
|
1402
|
+
onDragOver: (e) => {
|
|
1403
|
+
e.preventDefault();
|
|
1404
|
+
setDragging(true);
|
|
1405
|
+
},
|
|
1406
|
+
onDragLeave: () => setDragging(false),
|
|
1407
|
+
onDrop: (e) => {
|
|
1408
|
+
e.preventDefault();
|
|
1409
|
+
setDragging(false);
|
|
1410
|
+
handleFile(e.dataTransfer?.files[0]);
|
|
1411
|
+
},
|
|
1412
|
+
children: [
|
|
1413
|
+
/* @__PURE__ */ jsx30(ArrowUpTrayIcon2, { className: "size-7 ml-1 mr-2" }),
|
|
1414
|
+
/* @__PURE__ */ jsxs26("div", { className: "", children: [
|
|
1415
|
+
/* @__PURE__ */ jsx30("h4", { className: "text-base font-semibold text-gray-700", children: fileName || text }),
|
|
1416
|
+
/* @__PURE__ */ jsx30("span", { className: "text-sm text-gray-500", children: "(.zip)" })
|
|
1417
|
+
] }),
|
|
1418
|
+
/* @__PURE__ */ jsx30(
|
|
1419
|
+
"input",
|
|
1420
|
+
{
|
|
1421
|
+
type: "file",
|
|
1422
|
+
id: "doc",
|
|
1423
|
+
name: "doc",
|
|
1424
|
+
accept: ".zip",
|
|
1425
|
+
hidden: true,
|
|
1426
|
+
onChange: (e) => handleFile(e.target.files?.[0])
|
|
1427
|
+
},
|
|
1428
|
+
`input-file-input-${text}`
|
|
1429
|
+
)
|
|
1430
|
+
]
|
|
1431
|
+
},
|
|
1432
|
+
`input-file-label-${text}`
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// src/ui/inputs/subject-input-admin.tsx
|
|
1437
|
+
import { getStatusHelper } from "@goralys/core";
|
|
1438
|
+
|
|
1439
|
+
// src/ui/inputs/subject-text-area.tsx
|
|
1440
|
+
import { clsx as clsx8 } from "clsx";
|
|
1441
|
+
import { useState as useState16 } from "react";
|
|
1442
|
+
import { jsx as jsx31, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
1443
|
+
function SubjectTextArea({
|
|
1444
|
+
id,
|
|
1445
|
+
label,
|
|
1446
|
+
helper,
|
|
1447
|
+
disabled = false,
|
|
1448
|
+
ref,
|
|
1449
|
+
defaultValue,
|
|
1450
|
+
maxLength,
|
|
1451
|
+
onChangeAction,
|
|
1452
|
+
subjectData,
|
|
1453
|
+
animate
|
|
1454
|
+
}) {
|
|
1455
|
+
const setRef = useAutoResize(ref);
|
|
1456
|
+
const [currentValue, setCurrentValue] = useState16(defaultValue ?? "");
|
|
1457
|
+
const update = (e) => {
|
|
1458
|
+
if (maxLength && e.target.value.length > maxLength) {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
setCurrentValue(e.currentTarget.value);
|
|
1462
|
+
if (onChangeAction) onChangeAction(e);
|
|
1463
|
+
};
|
|
1464
|
+
return /* @__PURE__ */ jsxs27("div", { className: "relative mt-0 mb-0 group min-w-50 w-full", children: [
|
|
1465
|
+
/* @__PURE__ */ jsxs27("div", { className: "relative", children: [
|
|
1466
|
+
/* @__PURE__ */ jsx31(
|
|
1467
|
+
"textarea",
|
|
1468
|
+
{
|
|
1469
|
+
ref: setRef,
|
|
1470
|
+
id,
|
|
1471
|
+
name: id,
|
|
1472
|
+
rows: 1,
|
|
1473
|
+
placeholder: " ",
|
|
1474
|
+
spellCheck: "true",
|
|
1475
|
+
defaultValue,
|
|
1476
|
+
readOnly: disabled,
|
|
1477
|
+
maxLength,
|
|
1478
|
+
onChange: update,
|
|
1479
|
+
className: clsx8(
|
|
1480
|
+
"peer block w-full py-0 px-0 cursor-text text-base leading-5 bg-transparent border-0 border-b-2 border-sky-300 appearance-none focus:outline-none focus:ring-0 resize-none overflow-hidden ",
|
|
1481
|
+
{
|
|
1482
|
+
"border-green-600!": subjectData.status === "approved",
|
|
1483
|
+
"border-amber-600!": subjectData.status === "submitted",
|
|
1484
|
+
"border-red-600!": subjectData.status === "rejected"
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
"text-gray-700 cursor-not-allowed!": disabled
|
|
1488
|
+
}
|
|
1489
|
+
)
|
|
1490
|
+
}
|
|
1491
|
+
),
|
|
1492
|
+
animate && /* @__PURE__ */ jsx31(
|
|
1493
|
+
"span",
|
|
1494
|
+
{
|
|
1495
|
+
className: "pointer-events-none\n absolute bg-sky-500 left-0 bottom-0 h-0.5 w-full\n origin-left scale-x-0\n transition-transform duration-250\n group-focus-within:scale-x-100 "
|
|
1496
|
+
}
|
|
1497
|
+
),
|
|
1498
|
+
/* @__PURE__ */ jsx31(
|
|
1499
|
+
"label",
|
|
1500
|
+
{
|
|
1501
|
+
htmlFor: id,
|
|
1502
|
+
className: clsx8(
|
|
1503
|
+
"absolute text-base text-body cursor-text duration-300 transform -translate-y-4.5 scale-75 top-0 origin-left peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4.5",
|
|
1504
|
+
{ "cursor-not-allowed": disabled }
|
|
1505
|
+
),
|
|
1506
|
+
children: label
|
|
1507
|
+
}
|
|
1508
|
+
)
|
|
1509
|
+
] }),
|
|
1510
|
+
/* @__PURE__ */ jsx31("div", { className: "flex flex-row content-between w-full", children: /* @__PURE__ */ jsxs27("div", { className: "flex flex-col", children: [
|
|
1511
|
+
maxLength && /* @__PURE__ */ jsxs27(
|
|
1512
|
+
"p",
|
|
1513
|
+
{
|
|
1514
|
+
className: clsx8("mt-1 mb-0 p-0 relative text-[11px] italic", {
|
|
1515
|
+
"text-gray-600": currentValue.length < maxLength * 0.9,
|
|
1516
|
+
"text-amber-600": currentValue.length >= maxLength * 0.9 && maxLength > currentValue.length,
|
|
1517
|
+
"text-red-600": currentValue.length >= maxLength
|
|
1518
|
+
}),
|
|
1519
|
+
children: [
|
|
1520
|
+
currentValue.length,
|
|
1521
|
+
"/",
|
|
1522
|
+
maxLength,
|
|
1523
|
+
" caract\xE8res"
|
|
1524
|
+
]
|
|
1525
|
+
}
|
|
1526
|
+
),
|
|
1527
|
+
helper && helper.length !== 0 && /* @__PURE__ */ jsxs27(
|
|
1528
|
+
"p",
|
|
1529
|
+
{
|
|
1530
|
+
className: clsx8("self-center relative text-[13px] italic text-gray-600", {
|
|
1531
|
+
"mt-1": maxLength === void 0
|
|
1532
|
+
}),
|
|
1533
|
+
children: [
|
|
1534
|
+
"*",
|
|
1535
|
+
helper
|
|
1536
|
+
]
|
|
1537
|
+
}
|
|
1538
|
+
)
|
|
1539
|
+
] }) })
|
|
1540
|
+
] });
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// src/ui/inputs/subject-input-admin.tsx
|
|
1544
|
+
import { jsx as jsx32 } from "react/jsx-runtime";
|
|
1545
|
+
function SubjectInputAdmin({ id, label, helper, subjectData, onChangeAction }) {
|
|
1546
|
+
helper = getStatusHelper(subjectData.status, "admin");
|
|
1547
|
+
return /* @__PURE__ */ jsx32("div", { className: "relative mt-2 group min-w-50", children: /* @__PURE__ */ jsx32(
|
|
1548
|
+
SubjectTextArea,
|
|
1549
|
+
{
|
|
1550
|
+
id,
|
|
1551
|
+
disabled: true,
|
|
1552
|
+
onChangeAction,
|
|
1553
|
+
label,
|
|
1554
|
+
animate: false,
|
|
1555
|
+
subjectData,
|
|
1556
|
+
defaultValue: subjectData.subject,
|
|
1557
|
+
helper
|
|
1558
|
+
}
|
|
1559
|
+
) });
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// src/ui/inputs/subject-input-student.tsx
|
|
1563
|
+
import { getStatusHelper as getStatusHelper2 } from "@goralys/core";
|
|
1564
|
+
import { jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
1565
|
+
function SubjectInputStudent({
|
|
1566
|
+
id,
|
|
1567
|
+
label,
|
|
1568
|
+
helper,
|
|
1569
|
+
subjectData,
|
|
1570
|
+
setIsInterdisciplinaryAction = () => {
|
|
1571
|
+
},
|
|
1572
|
+
onChangeAction
|
|
1573
|
+
}) {
|
|
1574
|
+
const initialValue = subjectData.status == "rejected" ? subjectData.lastRejected ?? "" : subjectData.subject ?? "";
|
|
1575
|
+
const MAX_CHARS = 250;
|
|
1576
|
+
helper = getStatusHelper2(subjectData.status, "student");
|
|
1577
|
+
const editable = subjectData.status != "approved" && subjectData.status != "submitted";
|
|
1578
|
+
const handleOnChange = (e) => {
|
|
1579
|
+
if (onChangeAction) {
|
|
1580
|
+
onChangeAction(e);
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
return /* @__PURE__ */ jsxs28("div", { className: "relative mt-2 group min-w-50 mb-0 flex sm:block flex-col", children: [
|
|
1584
|
+
/* @__PURE__ */ jsx33(
|
|
1585
|
+
SubjectTextArea,
|
|
1586
|
+
{
|
|
1587
|
+
id,
|
|
1588
|
+
disabled: !editable,
|
|
1589
|
+
defaultValue: initialValue,
|
|
1590
|
+
maxLength: MAX_CHARS,
|
|
1591
|
+
onChangeAction: handleOnChange,
|
|
1592
|
+
label,
|
|
1593
|
+
subjectData,
|
|
1594
|
+
animate: editable,
|
|
1595
|
+
helper
|
|
1596
|
+
}
|
|
1597
|
+
),
|
|
1598
|
+
/* @__PURE__ */ jsx33("div", { className: "flex flex-row content-between w-full", children: /* @__PURE__ */ jsx33(
|
|
1599
|
+
Checkbox,
|
|
1600
|
+
{
|
|
1601
|
+
className: "m-0 sm:-mt-7.5 sm:ml-auto self-center",
|
|
1602
|
+
label: "Question transversale",
|
|
1603
|
+
setValueAction: setIsInterdisciplinaryAction,
|
|
1604
|
+
defaultValue: subjectData.interdisciplinary,
|
|
1605
|
+
disabled: !editable
|
|
1606
|
+
}
|
|
1607
|
+
) })
|
|
1608
|
+
] });
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// src/ui/inputs/subject-input-teacher.tsx
|
|
1612
|
+
import { buildApiUrl as buildApiUrl2, getGoralysClientConfig, getStatusHelper as getStatusHelper3 } from "@goralys/core";
|
|
1613
|
+
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
|
1614
|
+
import { jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
1615
|
+
function SubjectInputTeacher({ id, label, helper, subjectData, onChangeAction }) {
|
|
1616
|
+
helper = getStatusHelper3(subjectData.status, "teacher");
|
|
1617
|
+
const initialValue = subjectData.status == "rejected" ? subjectData.lastRejected ?? "" : subjectData.subject ?? "";
|
|
1618
|
+
const MAX_CHARS = 250;
|
|
1619
|
+
const handleOnChange = (e) => {
|
|
1620
|
+
if (onChangeAction) {
|
|
1621
|
+
onChangeAction(e);
|
|
1622
|
+
}
|
|
1623
|
+
};
|
|
1624
|
+
const getDraft = () => {
|
|
1625
|
+
const cfg = getGoralysClientConfig();
|
|
1626
|
+
window.location.href = buildApiUrl2(
|
|
1627
|
+
"subjects/draft",
|
|
1628
|
+
{
|
|
1629
|
+
"high-school-token": cfg.getSchoolToken() ?? null,
|
|
1630
|
+
teacher: subjectData.teacherToken,
|
|
1631
|
+
student: subjectData.studentToken,
|
|
1632
|
+
topic: subjectData.topicCode,
|
|
1633
|
+
"file-name": `Brouillon - ${subjectData.student} ${subjectData.topicCode}`
|
|
1634
|
+
},
|
|
1635
|
+
true
|
|
1636
|
+
);
|
|
1637
|
+
};
|
|
1638
|
+
return /* @__PURE__ */ jsxs29("div", { className: "relative mt-2 group min-w-50 mb-0 flex sm:block flex-col", children: [
|
|
1639
|
+
/* @__PURE__ */ jsxs29("div", { className: "flex flex-row", children: [
|
|
1640
|
+
/* @__PURE__ */ jsx34(
|
|
1641
|
+
SubjectTextArea,
|
|
1642
|
+
{
|
|
1643
|
+
id,
|
|
1644
|
+
disabled: true,
|
|
1645
|
+
defaultValue: initialValue,
|
|
1646
|
+
maxLength: MAX_CHARS,
|
|
1647
|
+
onChangeAction: handleOnChange,
|
|
1648
|
+
label,
|
|
1649
|
+
subjectData,
|
|
1650
|
+
animate: false,
|
|
1651
|
+
helper
|
|
1652
|
+
}
|
|
1653
|
+
),
|
|
1654
|
+
subjectData.hasDraft && /* @__PURE__ */ jsx34(
|
|
1655
|
+
"button",
|
|
1656
|
+
{
|
|
1657
|
+
className: "h-6 w-6 cursor-pointer bg-sky-200 rounded-md items-center justify-center\n hover:bg-sky-300 hover:shadow-md hover:-translate-y-1 transition-all ease-out duration-500",
|
|
1658
|
+
type: "submit",
|
|
1659
|
+
title: "T\xE9l\xE9charger le brouillon de l'\xE9l\xE8ve",
|
|
1660
|
+
onClick: getDraft,
|
|
1661
|
+
children: /* @__PURE__ */ jsx34(ArrowDownTrayIcon, { className: "size-5 m-auto" })
|
|
1662
|
+
}
|
|
1663
|
+
)
|
|
1664
|
+
] }),
|
|
1665
|
+
/* @__PURE__ */ jsx34("div", { className: "flex flex-row content-between w-full", children: /* @__PURE__ */ jsx34(
|
|
1666
|
+
Checkbox,
|
|
1667
|
+
{
|
|
1668
|
+
id: `interdisciplinary-teacher-${subjectData.studentToken}-${subjectData.teacherToken}`,
|
|
1669
|
+
className: "m-0 sm:ml-auto sm:-mt-7.5 mb-2.5 self-center",
|
|
1670
|
+
label: "Question transversale",
|
|
1671
|
+
setValueAction: () => {
|
|
1672
|
+
},
|
|
1673
|
+
defaultValue: subjectData.interdisciplinary,
|
|
1674
|
+
disabled: true
|
|
1675
|
+
}
|
|
1676
|
+
) })
|
|
1677
|
+
] });
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// src/ui/inputs/text-area.tsx
|
|
1681
|
+
import { clsx as clsx9 } from "clsx";
|
|
1682
|
+
import { useState as useState17 } from "react";
|
|
1683
|
+
import { jsx as jsx35, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
1684
|
+
function TextArea({
|
|
1685
|
+
id,
|
|
1686
|
+
label,
|
|
1687
|
+
helper,
|
|
1688
|
+
required = false,
|
|
1689
|
+
disabled = false,
|
|
1690
|
+
ref,
|
|
1691
|
+
defaultValue,
|
|
1692
|
+
onChangeAction,
|
|
1693
|
+
maxLength
|
|
1694
|
+
}) {
|
|
1695
|
+
const setRef = useAutoResize(ref);
|
|
1696
|
+
const [currentValue, setCurrentValue] = useState17(defaultValue ?? "");
|
|
1697
|
+
const update = (e) => {
|
|
1698
|
+
if (maxLength && e.target.value.length > maxLength) {
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
setCurrentValue(e.currentTarget.value);
|
|
1702
|
+
if (onChangeAction) onChangeAction(e);
|
|
1703
|
+
};
|
|
1704
|
+
return /* @__PURE__ */ jsxs30("div", { className: "relative mt-3 mb-1 group min-w-50", children: [
|
|
1705
|
+
/* @__PURE__ */ jsxs30("div", { className: "relative", children: [
|
|
1706
|
+
/* @__PURE__ */ jsx35(
|
|
1707
|
+
"textarea",
|
|
1708
|
+
{
|
|
1709
|
+
ref: setRef,
|
|
1710
|
+
id,
|
|
1711
|
+
name: id,
|
|
1712
|
+
rows: 1,
|
|
1713
|
+
placeholder: " ",
|
|
1714
|
+
spellCheck: "true",
|
|
1715
|
+
defaultValue,
|
|
1716
|
+
maxLength,
|
|
1717
|
+
readOnly: disabled,
|
|
1718
|
+
onChange: update,
|
|
1719
|
+
required,
|
|
1720
|
+
className: "\n peer block w-full py-0 px-0 text-base text-heading\n resize-none overflow-hidden bg-transparent\n border-0 border-b-2 border-sky-300\n appearance-none focus:outline-none focus:ring-0\n "
|
|
1721
|
+
}
|
|
1722
|
+
),
|
|
1723
|
+
/* @__PURE__ */ jsx35(
|
|
1724
|
+
"span",
|
|
1725
|
+
{
|
|
1726
|
+
className: "pointer-events-none\n absolute bg-sky-500 left-0 bottom-0 h-0.5 w-full\n origin-left scale-x-0\n transition-transform duration-250\n group-focus-within:scale-x-100 "
|
|
1727
|
+
}
|
|
1728
|
+
),
|
|
1729
|
+
/* @__PURE__ */ jsx35(
|
|
1730
|
+
"label",
|
|
1731
|
+
{
|
|
1732
|
+
htmlFor: id,
|
|
1733
|
+
className: clsx9(
|
|
1734
|
+
"absolute text-base text-body cursor-text duration-300 transform -translate-y-4.5 scale-75 top-0 origin-left peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4.5",
|
|
1735
|
+
{ "cursor-not-allowed": disabled }
|
|
1736
|
+
),
|
|
1737
|
+
children: label
|
|
1738
|
+
}
|
|
1739
|
+
)
|
|
1740
|
+
] }),
|
|
1741
|
+
/* @__PURE__ */ jsx35("div", { className: "flex flex-row content-between w-full", children: /* @__PURE__ */ jsxs30("div", { className: "flex flex-col", children: [
|
|
1742
|
+
maxLength && /* @__PURE__ */ jsxs30(
|
|
1743
|
+
"p",
|
|
1744
|
+
{
|
|
1745
|
+
className: clsx9("mt-1 mb-0 p-0 relative text-[11px] italic", {
|
|
1746
|
+
"text-gray-600": currentValue.length < maxLength * 0.9,
|
|
1747
|
+
"text-amber-600": currentValue.length >= maxLength * 0.9 && maxLength > currentValue.length,
|
|
1748
|
+
"text-red-600": currentValue.length >= maxLength
|
|
1749
|
+
}),
|
|
1750
|
+
children: [
|
|
1751
|
+
currentValue.length,
|
|
1752
|
+
"/",
|
|
1753
|
+
maxLength,
|
|
1754
|
+
" caract\xE8res"
|
|
1755
|
+
]
|
|
1756
|
+
}
|
|
1757
|
+
),
|
|
1758
|
+
helper && helper.length !== 0 && /* @__PURE__ */ jsxs30(
|
|
1759
|
+
"p",
|
|
1760
|
+
{
|
|
1761
|
+
className: clsx9("self-center relative text-[13px] italic text-gray-600", {
|
|
1762
|
+
"mt-1": maxLength === void 0
|
|
1763
|
+
}),
|
|
1764
|
+
children: [
|
|
1765
|
+
"*",
|
|
1766
|
+
helper
|
|
1767
|
+
]
|
|
1768
|
+
}
|
|
1769
|
+
)
|
|
1770
|
+
] }) })
|
|
1771
|
+
] });
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
// src/ui/modals/drafts/draft-modal-provider.tsx
|
|
1775
|
+
import { createContext as createContext5, useCallback as useCallback5, useContext as useContext5, useMemo as useMemo5, useState as useState19 } from "react";
|
|
1776
|
+
|
|
1777
|
+
// src/ui/modals/drafts/draft-modal-element.tsx
|
|
1778
|
+
import { clsx as clsx10 } from "clsx";
|
|
1779
|
+
import { useRef as useRef4, useState as useState18 } from "react";
|
|
1780
|
+
import { QuestionMarkCircleIcon as QuestionMarkCircleIcon2 } from "@heroicons/react/24/outline";
|
|
1781
|
+
import { jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
1782
|
+
function DraftModalElement({ visible, onChooseDraftAction, onCancelAction, onCloseModalAction }) {
|
|
1783
|
+
const [draftFile, setDraftFile] = useState18(null);
|
|
1784
|
+
const modalRef = useRef4(null);
|
|
1785
|
+
useModalClose(modalRef, visible, onCloseModalAction);
|
|
1786
|
+
const onConfirm = () => {
|
|
1787
|
+
onChooseDraftAction(draftFile);
|
|
1788
|
+
};
|
|
1789
|
+
return /* @__PURE__ */ jsxs31(
|
|
1790
|
+
"div",
|
|
1791
|
+
{
|
|
1792
|
+
ref: modalRef,
|
|
1793
|
+
className: clsx10(
|
|
1794
|
+
"fixed flex flex-col gap-2 p-3 w-125 bg-sky-200 rounded shadow overflow-hidden left-1/2 -translate-x-1/2 top-1 ",
|
|
1795
|
+
"after:absolute after:left-0 after:top-0 after:h-full after:w-1.25 after:content-[''] after:bg-blue-500",
|
|
1796
|
+
"transition-all duration-500 z-50 ",
|
|
1797
|
+
{
|
|
1798
|
+
"translate-y-0 opacity-100": visible,
|
|
1799
|
+
"-translate-y-5 opacity-0": !visible
|
|
1800
|
+
}
|
|
1801
|
+
),
|
|
1802
|
+
role: "dialog",
|
|
1803
|
+
"aria-modal": "true",
|
|
1804
|
+
children: [
|
|
1805
|
+
/* @__PURE__ */ jsxs31("div", { className: "flex gap-3", children: [
|
|
1806
|
+
/* @__PURE__ */ jsx36("div", { className: "w-11 h-11 flex self-center items-center justify-center", children: /* @__PURE__ */ jsx36(QuestionMarkCircleIcon2, { className: "size-15 text-blue-500" }) }),
|
|
1807
|
+
/* @__PURE__ */ jsxs31("div", { className: "flex flex-col justify-center flex-1", children: [
|
|
1808
|
+
/* @__PURE__ */ jsx36("strong", { className: "text-md", children: "Choix du brouillon" }),
|
|
1809
|
+
/* @__PURE__ */ jsx36("span", { className: "text-sm", children: "Vous pouvez joindre un brouillon \xE0 votre question avant de l'envoyer. Le professeur pourra ensuite le t\xE9l\xE9charger et le consulter." })
|
|
1810
|
+
] })
|
|
1811
|
+
] }),
|
|
1812
|
+
/* @__PURE__ */ jsx36(InputTextFile, { text: "T\xE9verser un brouillon", maxSizeKB: 50, onFileSelected: setDraftFile }),
|
|
1813
|
+
/* @__PURE__ */ jsxs31("div", { className: "flex justify-between gap-2 mt-2", children: [
|
|
1814
|
+
/* @__PURE__ */ jsx36(
|
|
1815
|
+
Button,
|
|
1816
|
+
{
|
|
1817
|
+
className: "bg-gray-400! before:bg-gray-500! text-white! border-none! shadow-none! mt-0! mb-0! h-11! w-50!",
|
|
1818
|
+
text: "Envoyer la question seule",
|
|
1819
|
+
type: "button",
|
|
1820
|
+
onClick: onCancelAction
|
|
1821
|
+
}
|
|
1822
|
+
),
|
|
1823
|
+
/* @__PURE__ */ jsx36(
|
|
1824
|
+
Button,
|
|
1825
|
+
{
|
|
1826
|
+
className: "bg-blue-500! before:bg-blue-600! text-white! border-none! shadow-none! mt-0! mb-0! h-11! w-50!",
|
|
1827
|
+
text: "Envoyer le brouillon",
|
|
1828
|
+
type: "button",
|
|
1829
|
+
onClick: onConfirm
|
|
1830
|
+
}
|
|
1831
|
+
)
|
|
1832
|
+
] })
|
|
1833
|
+
]
|
|
1834
|
+
}
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// src/ui/modals/drafts/draft-modal-provider.tsx
|
|
1839
|
+
import { createPortal as createPortal4 } from "react-dom";
|
|
1840
|
+
import { jsx as jsx37, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
1841
|
+
var DraftModalContext = createContext5(null);
|
|
1842
|
+
function DraftModalProvider({ children }) {
|
|
1843
|
+
const [fileChosen, setChosenFile] = useState19(null);
|
|
1844
|
+
const [visible, setVisible] = useState19(false);
|
|
1845
|
+
const showDraftModal = useCallback5(() => {
|
|
1846
|
+
return new Promise((resolve) => {
|
|
1847
|
+
setChosenFile({ resolve });
|
|
1848
|
+
setVisible(false);
|
|
1849
|
+
requestAnimationFrame(() => setVisible(true));
|
|
1850
|
+
});
|
|
1851
|
+
}, []);
|
|
1852
|
+
function handleChooseDraft(file) {
|
|
1853
|
+
setVisible(false);
|
|
1854
|
+
setTimeout(() => {
|
|
1855
|
+
fileChosen?.resolve({ type: "withDraft", file });
|
|
1856
|
+
setChosenFile(null);
|
|
1857
|
+
}, 500);
|
|
1858
|
+
}
|
|
1859
|
+
function handleCancel() {
|
|
1860
|
+
setVisible(false);
|
|
1861
|
+
setTimeout(() => {
|
|
1862
|
+
fileChosen?.resolve({ type: "withoutDraft" });
|
|
1863
|
+
setChosenFile(null);
|
|
1864
|
+
}, 500);
|
|
1865
|
+
}
|
|
1866
|
+
function handleClose() {
|
|
1867
|
+
setVisible(false);
|
|
1868
|
+
setTimeout(() => {
|
|
1869
|
+
fileChosen?.resolve({ type: "closed" });
|
|
1870
|
+
setChosenFile(null);
|
|
1871
|
+
}, 500);
|
|
1872
|
+
}
|
|
1873
|
+
const value = useMemo5(() => ({ showDraftModal }), [showDraftModal]);
|
|
1874
|
+
return /* @__PURE__ */ jsxs32(DraftModalContext.Provider, { value, children: [
|
|
1875
|
+
children,
|
|
1876
|
+
fileChosen && typeof document !== "undefined" && createPortal4(
|
|
1877
|
+
/* @__PURE__ */ jsx37("div", { className: "fixed inset-0 z-40 bg-black/20 backdrop-blur-sm", children: /* @__PURE__ */ jsx37(
|
|
1878
|
+
DraftModalElement,
|
|
1879
|
+
{
|
|
1880
|
+
visible,
|
|
1881
|
+
onChooseDraftAction: handleChooseDraft,
|
|
1882
|
+
onCancelAction: handleCancel,
|
|
1883
|
+
onCloseModalAction: handleClose
|
|
1884
|
+
}
|
|
1885
|
+
) }),
|
|
1886
|
+
document.getElementById("draft-modal-root")
|
|
1887
|
+
)
|
|
1888
|
+
] });
|
|
1889
|
+
}
|
|
1890
|
+
function useDraftModal() {
|
|
1891
|
+
const context = useContext5(DraftModalContext);
|
|
1892
|
+
if (!context) {
|
|
1893
|
+
throw new Error("useDraftModal must be used within a ConfirmProvider");
|
|
1894
|
+
}
|
|
1895
|
+
return context;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
// src/ui/modals/import-topics/import-topics-modal-provider.tsx
|
|
1899
|
+
import { createContext as createContext6, useCallback as useCallback6, useContext as useContext6, useMemo as useMemo6, useState as useState21 } from "react";
|
|
1900
|
+
|
|
1901
|
+
// src/ui/modals/import-topics/import-topics-modal-element.tsx
|
|
1902
|
+
import { clsx as clsx11 } from "clsx";
|
|
1903
|
+
import { useRef as useRef5, useState as useState20 } from "react";
|
|
1904
|
+
import { QuestionMarkCircleIcon as QuestionMarkCircleIcon3 } from "@heroicons/react/24/outline";
|
|
1905
|
+
import { jsx as jsx38, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
1906
|
+
function ImportTopicsModalElement({ visible, onImportTopicsAction, onCancelAction, onCloseModalAction }) {
|
|
1907
|
+
const [topicsFile, setTopicsFile] = useState20(null);
|
|
1908
|
+
const modalRef = useRef5(null);
|
|
1909
|
+
useModalClose(modalRef, visible, onCloseModalAction);
|
|
1910
|
+
const onConfirm = () => {
|
|
1911
|
+
onImportTopicsAction(topicsFile);
|
|
1912
|
+
};
|
|
1913
|
+
return /* @__PURE__ */ jsxs33(
|
|
1914
|
+
"div",
|
|
1915
|
+
{
|
|
1916
|
+
ref: modalRef,
|
|
1917
|
+
className: clsx11(
|
|
1918
|
+
"fixed flex flex-col gap-2 p-3 w-125 bg-sky-200 rounded shadow overflow-hidden left-1/2 -translate-x-1/2 top-1 ",
|
|
1919
|
+
"after:absolute after:left-0 after:top-0 after:h-full after:w-1.25 after:content-[''] after:bg-blue-500",
|
|
1920
|
+
"transition-all duration-500 z-50 ",
|
|
1921
|
+
{
|
|
1922
|
+
"translate-y-0 opacity-100": visible,
|
|
1923
|
+
"-translate-y-5 opacity-0": !visible
|
|
1924
|
+
}
|
|
1925
|
+
),
|
|
1926
|
+
role: "dialog",
|
|
1927
|
+
"aria-modal": "true",
|
|
1928
|
+
children: [
|
|
1929
|
+
/* @__PURE__ */ jsxs33("div", { className: "flex gap-3", children: [
|
|
1930
|
+
/* @__PURE__ */ jsx38("div", { className: "w-11 h-11 flex self-center items-center justify-center", children: /* @__PURE__ */ jsx38(QuestionMarkCircleIcon3, { className: "size-15 text-blue-500" }) }),
|
|
1931
|
+
/* @__PURE__ */ jsxs33("div", { className: "flex flex-col justify-center flex-1", children: [
|
|
1932
|
+
/* @__PURE__ */ jsx38("strong", { className: "text-md", children: "Import des donn\xE9es" }),
|
|
1933
|
+
/* @__PURE__ */ jsx38("span", { className: "text-sm", children: "Vous pouvez importer les donn\xE9es sous forme d'un fichiez zip" })
|
|
1934
|
+
] })
|
|
1935
|
+
] }),
|
|
1936
|
+
/* @__PURE__ */ jsx38(InputZipFile, { text: "T\xE9verser une archive", onFileSelected: setTopicsFile }),
|
|
1937
|
+
/* @__PURE__ */ jsxs33("div", { className: "flex justify-between gap-2 mt-2", children: [
|
|
1938
|
+
/* @__PURE__ */ jsx38(
|
|
1939
|
+
Button,
|
|
1940
|
+
{
|
|
1941
|
+
className: "bg-gray-400! before:bg-gray-500! text-white! border-none! shadow-none! mt-0! mb-0! h-11! w-50!",
|
|
1942
|
+
text: "Annuler",
|
|
1943
|
+
type: "button",
|
|
1944
|
+
onClick: onCancelAction
|
|
1945
|
+
}
|
|
1946
|
+
),
|
|
1947
|
+
/* @__PURE__ */ jsx38(
|
|
1948
|
+
Button,
|
|
1949
|
+
{
|
|
1950
|
+
className: "bg-blue-500! before:bg-blue-600! text-white! border-none! shadow-none! mt-0! mb-0! h-11! w-50!",
|
|
1951
|
+
text: "Envoyer les donn\xE9es",
|
|
1952
|
+
type: "button",
|
|
1953
|
+
onClick: onConfirm
|
|
1954
|
+
}
|
|
1955
|
+
)
|
|
1956
|
+
] })
|
|
1957
|
+
]
|
|
1958
|
+
}
|
|
1959
|
+
);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// src/ui/modals/import-topics/import-topics-modal-provider.tsx
|
|
1963
|
+
import { createPortal as createPortal5 } from "react-dom";
|
|
1964
|
+
import { jsx as jsx39, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
1965
|
+
var ImportTopicsModalContext = createContext6(null);
|
|
1966
|
+
function ImportTopicsModalProvider({ children }) {
|
|
1967
|
+
const [fileChosen, setChosenFile] = useState21(null);
|
|
1968
|
+
const [visible, setVisible] = useState21(false);
|
|
1969
|
+
const showImportTopicsModal = useCallback6(() => {
|
|
1970
|
+
return new Promise((resolve) => {
|
|
1971
|
+
setChosenFile({ resolve });
|
|
1972
|
+
setVisible(false);
|
|
1973
|
+
requestAnimationFrame(() => setVisible(true));
|
|
1974
|
+
});
|
|
1975
|
+
}, []);
|
|
1976
|
+
function handleImportTopics(file) {
|
|
1977
|
+
setVisible(false);
|
|
1978
|
+
setTimeout(() => {
|
|
1979
|
+
fileChosen?.resolve(file);
|
|
1980
|
+
setChosenFile(null);
|
|
1981
|
+
}, 500);
|
|
1982
|
+
}
|
|
1983
|
+
function handleCancel() {
|
|
1984
|
+
setVisible(false);
|
|
1985
|
+
setTimeout(() => {
|
|
1986
|
+
fileChosen?.resolve(null);
|
|
1987
|
+
setChosenFile(null);
|
|
1988
|
+
}, 500);
|
|
1989
|
+
}
|
|
1990
|
+
function handleClose() {
|
|
1991
|
+
setVisible(false);
|
|
1992
|
+
setTimeout(() => {
|
|
1993
|
+
fileChosen?.resolve("modalClosed");
|
|
1994
|
+
setChosenFile(null);
|
|
1995
|
+
}, 500);
|
|
1996
|
+
}
|
|
1997
|
+
const value = useMemo6(() => ({ showImportTopicsModal }), [showImportTopicsModal]);
|
|
1998
|
+
return /* @__PURE__ */ jsxs34(ImportTopicsModalContext.Provider, { value, children: [
|
|
1999
|
+
children,
|
|
2000
|
+
fileChosen && typeof document !== "undefined" && createPortal5(
|
|
2001
|
+
/* @__PURE__ */ jsx39("div", { className: "fixed inset-0 z-40 bg-black/20 backdrop-blur-sm", children: /* @__PURE__ */ jsx39(
|
|
2002
|
+
ImportTopicsModalElement,
|
|
2003
|
+
{
|
|
2004
|
+
visible,
|
|
2005
|
+
onImportTopicsAction: handleImportTopics,
|
|
2006
|
+
onCancelAction: handleCancel,
|
|
2007
|
+
onCloseModalAction: handleClose
|
|
2008
|
+
}
|
|
2009
|
+
) }),
|
|
2010
|
+
document.getElementById("import-topics-modal-root")
|
|
2011
|
+
)
|
|
2012
|
+
] });
|
|
2013
|
+
}
|
|
2014
|
+
function useImportTopicsModal() {
|
|
2015
|
+
const context = useContext6(ImportTopicsModalContext);
|
|
2016
|
+
if (!context) {
|
|
2017
|
+
throw new Error("useImportTopicsModal must be used within a ConfirmProvider");
|
|
2018
|
+
}
|
|
2019
|
+
return context;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// src/ui/modals/email/email-modal-provider.tsx
|
|
2023
|
+
import { createContext as createContext7, useCallback as useCallback7, useContext as useContext7, useMemo as useMemo7, useState as useState23 } from "react";
|
|
2024
|
+
|
|
2025
|
+
// src/ui/modals/email/email-modal-element.tsx
|
|
2026
|
+
import { clsx as clsx12 } from "clsx";
|
|
2027
|
+
import { useRef as useRef6, useState as useState22 } from "react";
|
|
2028
|
+
import { AtSymbolIcon } from "@heroicons/react/24/outline";
|
|
2029
|
+
import { jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
2030
|
+
function EmailModalElement({ visible, onConfirmAction, onCancelAction, onCloseModalAction, message }) {
|
|
2031
|
+
const [email, setEmail] = useState22("");
|
|
2032
|
+
const modalRef = useRef6(null);
|
|
2033
|
+
useModalClose(modalRef, visible, onCloseModalAction);
|
|
2034
|
+
const onConfirm = () => {
|
|
2035
|
+
onConfirmAction(email);
|
|
2036
|
+
setEmail("");
|
|
2037
|
+
};
|
|
2038
|
+
const onCancel = () => {
|
|
2039
|
+
setEmail("");
|
|
2040
|
+
onCancelAction();
|
|
2041
|
+
};
|
|
2042
|
+
return /* @__PURE__ */ jsxs35(
|
|
2043
|
+
"div",
|
|
2044
|
+
{
|
|
2045
|
+
ref: modalRef,
|
|
2046
|
+
className: clsx12(
|
|
2047
|
+
"fixed flex flex-col gap-2 p-3 sm:w-125 w-96 bg-sky-200 rounded shadow overflow-hidden left-1/2 -translate-x-1/2 top-1",
|
|
2048
|
+
"after:absolute after:left-0 after:top-0 after:h-full after:w-1.25 after:content-[''] after:bg-blue-500",
|
|
2049
|
+
"transition-all duration-500 z-50",
|
|
2050
|
+
{
|
|
2051
|
+
"translate-y-0 opacity-100": visible,
|
|
2052
|
+
"-translate-y-5 opacity-0": !visible
|
|
2053
|
+
}
|
|
2054
|
+
),
|
|
2055
|
+
role: "dialog",
|
|
2056
|
+
"aria-modal": "true",
|
|
2057
|
+
children: [
|
|
2058
|
+
/* @__PURE__ */ jsxs35("div", { className: "flex gap-3", children: [
|
|
2059
|
+
/* @__PURE__ */ jsx40("div", { className: "w-11 h-11 flex self-center items-center justify-center", children: /* @__PURE__ */ jsx40(AtSymbolIcon, { className: "size-15 text-blue-500" }) }),
|
|
2060
|
+
/* @__PURE__ */ jsxs35("div", { className: "flex flex-col justify-center flex-1", children: [
|
|
2061
|
+
/* @__PURE__ */ jsx40("strong", { className: "text-md", children: "Confirmation requise" }),
|
|
2062
|
+
/* @__PURE__ */ jsx40("span", { className: "text-sm", children: message })
|
|
2063
|
+
] })
|
|
2064
|
+
] }),
|
|
2065
|
+
/* @__PURE__ */ jsx40(
|
|
2066
|
+
FloatingInput,
|
|
2067
|
+
{
|
|
2068
|
+
id: "confirm-email",
|
|
2069
|
+
label: "Adresse mail",
|
|
2070
|
+
email: true,
|
|
2071
|
+
autocomplete: "email",
|
|
2072
|
+
onInput: (e) => setEmail(e.currentTarget.value)
|
|
2073
|
+
}
|
|
2074
|
+
),
|
|
2075
|
+
/* @__PURE__ */ jsxs35("div", { className: "flex justify-between gap-2 mt-2", children: [
|
|
2076
|
+
/* @__PURE__ */ jsx40(
|
|
2077
|
+
Button,
|
|
2078
|
+
{
|
|
2079
|
+
className: "bg-gray-400! before:bg-gray-500! text-white! border-none! shadow-none! mt-0! mb-0! h-11! w-50!",
|
|
2080
|
+
text: "Annuler",
|
|
2081
|
+
type: "button",
|
|
2082
|
+
onClick: onCancel
|
|
2083
|
+
}
|
|
2084
|
+
),
|
|
2085
|
+
/* @__PURE__ */ jsx40(
|
|
2086
|
+
Button,
|
|
2087
|
+
{
|
|
2088
|
+
className: "bg-blue-500! before:bg-blue-600! text-white! border-none! shadow-none! mt-0! mb-0! h-11! w-50!",
|
|
2089
|
+
text: "Confirmer",
|
|
2090
|
+
type: "button",
|
|
2091
|
+
onClick: onConfirm
|
|
2092
|
+
}
|
|
2093
|
+
)
|
|
2094
|
+
] })
|
|
2095
|
+
]
|
|
2096
|
+
}
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
// src/ui/modals/email/email-modal-provider.tsx
|
|
2101
|
+
import { createPortal as createPortal6 } from "react-dom";
|
|
2102
|
+
import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
2103
|
+
var EmailModalContext = createContext7(null);
|
|
2104
|
+
function EmailModalProvider({ children }) {
|
|
2105
|
+
const [state, setState] = useState23(null);
|
|
2106
|
+
const [visible, setVisible] = useState23(false);
|
|
2107
|
+
const [message, setMessage] = useState23("");
|
|
2108
|
+
const DEFAULT_EMAIL_CONFIRM = "Veuillez entrer nouvel email pour confirmer.";
|
|
2109
|
+
const showEmailModal = useCallback7((message2 = DEFAULT_EMAIL_CONFIRM) => {
|
|
2110
|
+
setMessage(message2);
|
|
2111
|
+
return new Promise((resolve) => {
|
|
2112
|
+
setState({ resolve });
|
|
2113
|
+
setVisible(false);
|
|
2114
|
+
requestAnimationFrame(() => setVisible(true));
|
|
2115
|
+
});
|
|
2116
|
+
}, []);
|
|
2117
|
+
function handleConfirm(email) {
|
|
2118
|
+
setVisible(false);
|
|
2119
|
+
setTimeout(() => {
|
|
2120
|
+
state?.resolve(email);
|
|
2121
|
+
setState(null);
|
|
2122
|
+
}, 500);
|
|
2123
|
+
}
|
|
2124
|
+
function handleCancel() {
|
|
2125
|
+
setVisible(false);
|
|
2126
|
+
setTimeout(() => {
|
|
2127
|
+
state?.resolve(null);
|
|
2128
|
+
setState(null);
|
|
2129
|
+
}, 500);
|
|
2130
|
+
}
|
|
2131
|
+
function handleClose() {
|
|
2132
|
+
setVisible(false);
|
|
2133
|
+
setTimeout(() => {
|
|
2134
|
+
state?.resolve(null);
|
|
2135
|
+
setState(null);
|
|
2136
|
+
}, 500);
|
|
2137
|
+
}
|
|
2138
|
+
const value = useMemo7(() => ({ showEmailModal }), [showEmailModal]);
|
|
2139
|
+
return /* @__PURE__ */ jsxs36(EmailModalContext.Provider, { value, children: [
|
|
2140
|
+
children,
|
|
2141
|
+
state && typeof document !== "undefined" && createPortal6(
|
|
2142
|
+
/* @__PURE__ */ jsx41("div", { className: "fixed inset-0 z-40 bg-black/20 backdrop-blur-sm", children: /* @__PURE__ */ jsx41(
|
|
2143
|
+
EmailModalElement,
|
|
2144
|
+
{
|
|
2145
|
+
message,
|
|
2146
|
+
visible,
|
|
2147
|
+
onConfirmAction: handleConfirm,
|
|
2148
|
+
onCancelAction: handleCancel,
|
|
2149
|
+
onCloseModalAction: handleClose
|
|
2150
|
+
}
|
|
2151
|
+
) }),
|
|
2152
|
+
document.getElementById("email-modal-root")
|
|
2153
|
+
)
|
|
2154
|
+
] });
|
|
2155
|
+
}
|
|
2156
|
+
function useEmailModal() {
|
|
2157
|
+
const context = useContext7(EmailModalContext);
|
|
2158
|
+
if (!context) {
|
|
2159
|
+
throw new Error("useEmailModal must be used within a EmailModalProvider");
|
|
2160
|
+
}
|
|
2161
|
+
return context;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
// src/ui/modals/providers.tsx
|
|
2165
|
+
import { Fragment as Fragment6, jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
2166
|
+
function Providers({ children }) {
|
|
2167
|
+
return /* @__PURE__ */ jsxs37(Fragment6, { children: [
|
|
2168
|
+
/* @__PURE__ */ jsx42("div", { id: "toast-root" }),
|
|
2169
|
+
/* @__PURE__ */ jsx42("div", { id: "confirm-root" }),
|
|
2170
|
+
/* @__PURE__ */ jsx42("div", { id: "draft-modal-root" }),
|
|
2171
|
+
/* @__PURE__ */ jsx42("div", { id: "import-topics-modal-root" }),
|
|
2172
|
+
/* @__PURE__ */ jsx42("div", { id: "password-modal-root" }),
|
|
2173
|
+
/* @__PURE__ */ jsx42("div", { id: "email-modal-root" }),
|
|
2174
|
+
/* @__PURE__ */ jsx42(ToastProvider, { children: /* @__PURE__ */ jsx42(ConfirmProvider, { children: /* @__PURE__ */ jsx42(DraftModalProvider, { children: /* @__PURE__ */ jsx42(ImportTopicsModalProvider, { children: /* @__PURE__ */ jsx42(EmailModalProvider, { children: /* @__PURE__ */ jsx42(PasswordModalProvider, { children }) }) }) }) }) })
|
|
2175
|
+
] });
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// src/ui/nav/desktop/nav-link.tsx
|
|
2179
|
+
import { clsx as clsx13 } from "clsx";
|
|
2180
|
+
import { jsx as jsx43 } from "react/jsx-runtime";
|
|
2181
|
+
function NavLink({ name, url, exact = false }) {
|
|
2182
|
+
const Link = useGoralysUI().Link;
|
|
2183
|
+
const current = useGoralysUI().usePathname();
|
|
2184
|
+
const isActive = exact || url === "/" ? current === url : current === url || current.startsWith(`${url}/`);
|
|
2185
|
+
return /* @__PURE__ */ jsx43(
|
|
2186
|
+
Link,
|
|
2187
|
+
{
|
|
2188
|
+
className: clsx13(
|
|
2189
|
+
"hover:bg-sky-200 h-12.5 w-full items-center flex flex-row hover:text-sky-600 left-1 rounded-md transition-colors p-2",
|
|
2190
|
+
{
|
|
2191
|
+
"bg-sky-200 text-sky-600": isActive,
|
|
2192
|
+
"bg-gray-100 text-gray-900": !isActive
|
|
2193
|
+
}
|
|
2194
|
+
),
|
|
2195
|
+
href: url,
|
|
2196
|
+
children: name
|
|
2197
|
+
}
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
// src/ui/nav/desktop/user-nav.tsx
|
|
2202
|
+
import { useEffect as useEffect10, useState as useState24 } from "react";
|
|
2203
|
+
import { cookiesGet as cookiesGet3, emptyUserCacheClient, FULL_NAME_KEY, onUserEvent, USERNAME_KEY } from "@goralys/core";
|
|
2204
|
+
import { UserCircleIcon } from "@heroicons/react/24/outline";
|
|
2205
|
+
import clsx14 from "clsx";
|
|
2206
|
+
import { jsx as jsx44, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
2207
|
+
function UserNav() {
|
|
2208
|
+
const Link = useGoralysUI().Link;
|
|
2209
|
+
const [text, setText] = useState24(null);
|
|
2210
|
+
const [loggedIn, setLoggedIn] = useState24(false);
|
|
2211
|
+
const current = useGoralysUI().usePathname();
|
|
2212
|
+
const targetUrl = loggedIn ? "/user/me" : "/user/login";
|
|
2213
|
+
const isActive = current === targetUrl || current.startsWith(`${targetUrl}/`);
|
|
2214
|
+
useEffect10(() => {
|
|
2215
|
+
const run = () => {
|
|
2216
|
+
const isLoggedIn = !!cookiesGet3(USERNAME_KEY);
|
|
2217
|
+
setLoggedIn(isLoggedIn);
|
|
2218
|
+
let name = isLoggedIn ? cookiesGet3(FULL_NAME_KEY) ?? "" : "Se connecter";
|
|
2219
|
+
if (name.length > 20) name = name.substring(0, 19) + "...";
|
|
2220
|
+
setText(name);
|
|
2221
|
+
};
|
|
2222
|
+
run();
|
|
2223
|
+
}, []);
|
|
2224
|
+
useEffect10(() => {
|
|
2225
|
+
const unsubscribe = onUserEvent((event) => {
|
|
2226
|
+
const isLoggedIn = event === "login";
|
|
2227
|
+
if (!isLoggedIn) {
|
|
2228
|
+
emptyUserCacheClient();
|
|
2229
|
+
}
|
|
2230
|
+
setLoggedIn(isLoggedIn);
|
|
2231
|
+
let name = isLoggedIn ? cookiesGet3(FULL_NAME_KEY) ?? "" : "Se connecter";
|
|
2232
|
+
if (name.length > 17) name = name.substring(0, 15) + "...";
|
|
2233
|
+
setText(name);
|
|
2234
|
+
});
|
|
2235
|
+
return () => {
|
|
2236
|
+
unsubscribe?.();
|
|
2237
|
+
};
|
|
2238
|
+
}, []);
|
|
2239
|
+
return /* @__PURE__ */ jsxs38(
|
|
2240
|
+
Link,
|
|
2241
|
+
{
|
|
2242
|
+
className: clsx14(
|
|
2243
|
+
"h-12.5 w-full flex items-center gap-2 rounded-md transition-colors p-1.5",
|
|
2244
|
+
"hover:bg-sky-200 hover:text-sky-600",
|
|
2245
|
+
{
|
|
2246
|
+
"bg-sky-200 text-sky-600": isActive,
|
|
2247
|
+
"bg-gray-100 text-gray-900": !isActive
|
|
2248
|
+
}
|
|
2249
|
+
),
|
|
2250
|
+
href: targetUrl,
|
|
2251
|
+
children: [
|
|
2252
|
+
loggedIn && /* @__PURE__ */ jsx44(UserCircleIcon, { width: 27.5, className: "-mr-1.25" }),
|
|
2253
|
+
text
|
|
2254
|
+
]
|
|
2255
|
+
}
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
// src/ui/nav/desktop/side-nav.tsx
|
|
2260
|
+
import { useEffect as useEffect11, useState as useState25 } from "react";
|
|
2261
|
+
import { buildArray, cookiesGet as cookiesGet4, cookiesOnChange, ROLE_KEY, USER_ROLES } from "@goralys/core";
|
|
2262
|
+
import { jsx as jsx45, jsxs as jsxs39 } from "react/jsx-runtime";
|
|
2263
|
+
function SideNav() {
|
|
2264
|
+
const Image = useGoralysUI().Image;
|
|
2265
|
+
const [role, setRole] = useState25("none");
|
|
2266
|
+
useEffect11(() => {
|
|
2267
|
+
const run = () => {
|
|
2268
|
+
const current = cookiesGet4(ROLE_KEY) ?? "none";
|
|
2269
|
+
setRole(USER_ROLES.includes(current) ? current : "none");
|
|
2270
|
+
};
|
|
2271
|
+
const onChange = () => {
|
|
2272
|
+
const role2 = cookiesGet4(ROLE_KEY) ?? "none";
|
|
2273
|
+
setRole(USER_ROLES.includes(role2) ? role2 : "none");
|
|
2274
|
+
};
|
|
2275
|
+
run();
|
|
2276
|
+
return cookiesOnChange(onChange);
|
|
2277
|
+
}, []);
|
|
2278
|
+
function getSubjectLinkText() {
|
|
2279
|
+
switch (role) {
|
|
2280
|
+
case "student":
|
|
2281
|
+
return "Mes Questions";
|
|
2282
|
+
case "teacher":
|
|
2283
|
+
return "Mes \xC9l\xE8ves";
|
|
2284
|
+
case "admin":
|
|
2285
|
+
return "Questions";
|
|
2286
|
+
case "none":
|
|
2287
|
+
return "Mon Espace";
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
const links = buildArray(
|
|
2291
|
+
{ name: "Accueil", url: "/" },
|
|
2292
|
+
{ name: getSubjectLinkText(), url: "/subject" },
|
|
2293
|
+
role == "admin" && { name: "Utilisateurs", url: "/admin/users" },
|
|
2294
|
+
role == "admin" && { name: "Acc\xE8s", url: "/admin/admins" },
|
|
2295
|
+
role == "admin" && { name: "Support", url: "/support" }
|
|
2296
|
+
);
|
|
2297
|
+
return /* @__PURE__ */ jsxs39("div", { className: "min-w-50 w-55 h-auto min-h-screen fixed top-0 flex flex-col m-0 p-2 rounded-xl", children: [
|
|
2298
|
+
/* @__PURE__ */ jsx45("div", { className: "flex rounded-md min-w-full h-25 bg-sky-500 mb-2 p-4", children: /* @__PURE__ */ jsx45(
|
|
2299
|
+
Image,
|
|
2300
|
+
{
|
|
2301
|
+
src: "/logo/goralys-logo.svg",
|
|
2302
|
+
width: 125,
|
|
2303
|
+
height: 10,
|
|
2304
|
+
alt: "Goralys logo",
|
|
2305
|
+
loading: "eager",
|
|
2306
|
+
priority: true,
|
|
2307
|
+
className: "-ml-3 self-center w-auto h-auto"
|
|
2308
|
+
}
|
|
2309
|
+
) }),
|
|
2310
|
+
/* @__PURE__ */ jsx45("div", { className: "flex flex-col gap-2", children: links.map((link) => /* @__PURE__ */ jsx45(NavLink, { name: link.name, url: link.url }, link.url)) }),
|
|
2311
|
+
/* @__PURE__ */ jsx45("div", { className: "grow bg-gray-100 rounded-xl my-3" }),
|
|
2312
|
+
/* @__PURE__ */ jsx45(NavLink, { name: "Centre d'Aide", url: "/help" }, "/help"),
|
|
2313
|
+
/* @__PURE__ */ jsx45(UserNav, {})
|
|
2314
|
+
] });
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
// src/ui/nav/mobile/tap-bar.tsx
|
|
2318
|
+
import { buildArray as buildArray2 } from "@goralys/core";
|
|
2319
|
+
import { AcademicCapIcon as AcademicCapIcon2, HomeIcon } from "@heroicons/react/24/outline";
|
|
2320
|
+
|
|
2321
|
+
// src/ui/nav/mobile/tap-bar-link.tsx
|
|
2322
|
+
import { clsx as clsx15 } from "clsx";
|
|
2323
|
+
import { jsx as jsx46, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
2324
|
+
function TapBarLink({ name, url, icon: Icon, exact = false }) {
|
|
2325
|
+
const Link = useGoralysUI().Link;
|
|
2326
|
+
const current = useGoralysUI().usePathname();
|
|
2327
|
+
const isActive = exact || url === "/" ? current === url : current === url || current.startsWith(`${url}/`);
|
|
2328
|
+
return /* @__PURE__ */ jsxs40(Link, { className: "flex flex-col w-15 items-center", href: url, children: [
|
|
2329
|
+
/* @__PURE__ */ jsx46(Icon, { className: "w-6 h-6 relative top-1" }),
|
|
2330
|
+
/* @__PURE__ */ jsx46("p", { className: "text-sm", children: name }),
|
|
2331
|
+
/* @__PURE__ */ jsx46(
|
|
2332
|
+
"span",
|
|
2333
|
+
{
|
|
2334
|
+
className: clsx15("relative bottom-0 w-full h-0.5", {
|
|
2335
|
+
"bg-sky-400": isActive,
|
|
2336
|
+
"bg-sky-200": !isActive
|
|
2337
|
+
})
|
|
2338
|
+
}
|
|
2339
|
+
)
|
|
2340
|
+
] });
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// src/ui/nav/mobile/tap-bar-user.tsx
|
|
2344
|
+
import { useEffect as useEffect12, useState as useState26 } from "react";
|
|
2345
|
+
import { cookiesGet as cookiesGet5, emptyUserCacheClient as emptyUserCacheClient2, onUserEvent as onUserEvent2, USERNAME_KEY as USERNAME_KEY2 } from "@goralys/core";
|
|
2346
|
+
import { UserCircleIcon as UserCircleIcon2 } from "@heroicons/react/24/outline";
|
|
2347
|
+
import clsx16 from "clsx";
|
|
2348
|
+
import { jsx as jsx47, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
2349
|
+
function TapBarUser() {
|
|
2350
|
+
const Link = useGoralysUI().Link;
|
|
2351
|
+
const [text, setText] = useState26(null);
|
|
2352
|
+
const [loggedIn, setLoggedIn] = useState26(false);
|
|
2353
|
+
const current = useGoralysUI().usePathname();
|
|
2354
|
+
const targetUrl = loggedIn ? "/user/me" : "/user/login";
|
|
2355
|
+
const isActive = current === targetUrl || current.startsWith(`${targetUrl}/`);
|
|
2356
|
+
useEffect12(() => {
|
|
2357
|
+
const run = () => {
|
|
2358
|
+
const isLoggedIn = !!cookiesGet5(USERNAME_KEY2);
|
|
2359
|
+
setLoggedIn(isLoggedIn);
|
|
2360
|
+
setText(isLoggedIn ? "Mon compte" : "Se connecter");
|
|
2361
|
+
};
|
|
2362
|
+
run();
|
|
2363
|
+
}, []);
|
|
2364
|
+
useEffect12(() => {
|
|
2365
|
+
const unsubscribe = onUserEvent2((event) => {
|
|
2366
|
+
const isLoggedIn = event === "login";
|
|
2367
|
+
if (!isLoggedIn) {
|
|
2368
|
+
emptyUserCacheClient2();
|
|
2369
|
+
}
|
|
2370
|
+
setLoggedIn(isLoggedIn);
|
|
2371
|
+
setText(isLoggedIn ? "Mon compte" : "Se connecter");
|
|
2372
|
+
});
|
|
2373
|
+
return () => {
|
|
2374
|
+
unsubscribe?.();
|
|
2375
|
+
};
|
|
2376
|
+
}, []);
|
|
2377
|
+
return /* @__PURE__ */ jsxs41(Link, { className: "flex flex-col w-20 items-center", href: targetUrl, children: [
|
|
2378
|
+
/* @__PURE__ */ jsx47(UserCircleIcon2, { className: "w-6 h-6 relative top-1" }),
|
|
2379
|
+
/* @__PURE__ */ jsx47("p", { className: "text-sm", children: text }),
|
|
2380
|
+
/* @__PURE__ */ jsx47(
|
|
2381
|
+
"span",
|
|
2382
|
+
{
|
|
2383
|
+
className: clsx16("relative bottom-0 w-full h-0.5", {
|
|
2384
|
+
"bg-sky-400": isActive,
|
|
2385
|
+
"bg-sky-200": !isActive
|
|
2386
|
+
})
|
|
2387
|
+
}
|
|
2388
|
+
)
|
|
2389
|
+
] });
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
// src/ui/nav/mobile/tap-bar.tsx
|
|
2393
|
+
import { jsx as jsx48, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
2394
|
+
function TapBar() {
|
|
2395
|
+
const links = buildArray2(
|
|
2396
|
+
{ name: "Accueil", url: "/", icon: HomeIcon },
|
|
2397
|
+
{ name: "Sujets", url: "/subject", icon: AcademicCapIcon2 }
|
|
2398
|
+
);
|
|
2399
|
+
return /* @__PURE__ */ jsxs42("div", { className: "z-50 fixed bottom-0 w-full h-12 bg-white flex flex-row content-center justify-evenly", children: [
|
|
2400
|
+
links.map((l) => /* @__PURE__ */ jsx48(TapBarLink, { name: l.name, url: l.url, icon: l.icon }, l.url)),
|
|
2401
|
+
/* @__PURE__ */ jsx48(TapBarUser, {})
|
|
2402
|
+
] });
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
// src/ui/nav/dynamic-nav.tsx
|
|
2406
|
+
import { jsx as jsx49 } from "react/jsx-runtime";
|
|
2407
|
+
function DynamicNav() {
|
|
2408
|
+
const isDesktop = useMediaQuery("(min-width: 640px)");
|
|
2409
|
+
return isDesktop ? /* @__PURE__ */ jsx49(SideNav, {}) : /* @__PURE__ */ jsx49(TapBar, {});
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
// src/ui/skeletons/button.tsx
|
|
2413
|
+
import { jsx as jsx50 } from "react/jsx-runtime";
|
|
2414
|
+
function ButtonSkeleton({ className }) {
|
|
2415
|
+
return /* @__PURE__ */ jsx50("div", { className: `skeleton w-full h-10 mt-2 mb-2 rounded-xs ${className ?? ""}` });
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
// src/ui/skeletons/admin-panel/admin-card.tsx
|
|
2419
|
+
import { jsx as jsx51, jsxs as jsxs43 } from "react/jsx-runtime";
|
|
2420
|
+
function AdminPanelCardSkeleton() {
|
|
2421
|
+
return /* @__PURE__ */ jsx51(Card, { className: "flex-col w-175! bg-sky-200 gap-1 p-1 mb-1 mt-1", children: /* @__PURE__ */ jsxs43("div", { className: "flex flex-row justify-between items-center", children: [
|
|
2422
|
+
/* @__PURE__ */ jsxs43("div", { className: "flex flex-row", children: [
|
|
2423
|
+
/* @__PURE__ */ jsx51("div", { className: "skeleton h-7 w-7 rounded-xs mr-1.5" }),
|
|
2424
|
+
/* @__PURE__ */ jsx51("div", { className: "skeleton h-5 w-48 rounded-xs self-center" })
|
|
2425
|
+
] }),
|
|
2426
|
+
/* @__PURE__ */ jsx51("div", { className: "flex flex-row w-50 gap-1", children: /* @__PURE__ */ jsx51(ButtonSkeleton, { className: "w-50!" }) })
|
|
2427
|
+
] }) });
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
// src/ui/skeletons/admin-panel/audit-action-card.tsx
|
|
2431
|
+
import clsx17 from "clsx";
|
|
2432
|
+
import { jsx as jsx52, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
2433
|
+
var ROWS = ["w-40", "w-56", "w-32", "w-48", "w-36"];
|
|
2434
|
+
function AuditActionCardSkeleton() {
|
|
2435
|
+
return /* @__PURE__ */ jsxs44(Card, { className: "flex flex-col grow bg-sky-200 m-2", children: [
|
|
2436
|
+
/* @__PURE__ */ jsx52(CardTitle, { title: "Actions" }),
|
|
2437
|
+
ROWS.map((width, i) => /* @__PURE__ */ jsxs44("div", { className: "w-full h-5 flex flex-row items-center", children: [
|
|
2438
|
+
/* @__PURE__ */ jsx52("div", { className: "skeleton h-3 w-28 rounded-xs shrink-0 mr-2" }),
|
|
2439
|
+
/* @__PURE__ */ jsx52("div", { className: clsx17("skeleton h-4 rounded-xs", width) })
|
|
2440
|
+
] }, i))
|
|
2441
|
+
] });
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
// src/ui/skeletons/admin-panel/user-card.tsx
|
|
2445
|
+
import { jsx as jsx53, jsxs as jsxs45 } from "react/jsx-runtime";
|
|
2446
|
+
function UserCardSkeleton() {
|
|
2447
|
+
return /* @__PURE__ */ jsx53(Card, { className: "flex-col w-200! bg-sky-200 gap-1 p-1 mb-1 mt-1", children: /* @__PURE__ */ jsxs45("div", { className: "flex flex-row justify-between items-center", children: [
|
|
2448
|
+
/* @__PURE__ */ jsxs45("div", { className: "flex flex-row", children: [
|
|
2449
|
+
/* @__PURE__ */ jsx53("div", { className: "skeleton h-7 w-7 rounded-xs mr-1.5" }),
|
|
2450
|
+
/* @__PURE__ */ jsx53("div", { className: "skeleton h-5 w-48 rounded-xs self-center" })
|
|
2451
|
+
] }),
|
|
2452
|
+
/* @__PURE__ */ jsx53("div", { className: "flex flex-row w-50 gap-1", children: /* @__PURE__ */ jsx53(ButtonSkeleton, { className: "w-50!" }) })
|
|
2453
|
+
] }) });
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
// src/ui/skeletons/admin-panel/subjects/user-subjects-teacher-card.tsx
|
|
2457
|
+
import { jsx as jsx54, jsxs as jsxs46 } from "react/jsx-runtime";
|
|
2458
|
+
function UserSubjectsTeacherCardSkeleton() {
|
|
2459
|
+
return /* @__PURE__ */ jsxs46(Card, { className: "flex flex-col grow bg-sky-200 m-2", children: [
|
|
2460
|
+
/* @__PURE__ */ jsx54(CardTitle, { title: "Sujets" }),
|
|
2461
|
+
/* @__PURE__ */ jsxs46(Card, { className: "flex flex-row w-full", children: [
|
|
2462
|
+
/* @__PURE__ */ jsx54("div", { className: "skeleton h-48 w-48 rounded-full shrink-0 mr-2 relative", children: /* @__PURE__ */ jsx54("div", { className: "absolute inset-[15%] rounded-full bg-sky-200" }) }),
|
|
2463
|
+
/* @__PURE__ */ jsx54("span", { className: "basis-1/6 grow-0 shrink min-w-0" }),
|
|
2464
|
+
/* @__PURE__ */ jsx54("ul", { className: "flex flex-col gap-1 self-center mr-10", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsxs46("li", { className: "flex items-center gap-2", children: [
|
|
2465
|
+
/* @__PURE__ */ jsx54("div", { className: "skeleton h-2.5 w-2.5 rounded-full shrink-0" }),
|
|
2466
|
+
/* @__PURE__ */ jsx54("div", { className: "skeleton h-4 w-16 rounded-xs" }),
|
|
2467
|
+
/* @__PURE__ */ jsx54("div", { className: "skeleton h-4 w-10 rounded-xs ml-auto" })
|
|
2468
|
+
] }, i)) })
|
|
2469
|
+
] })
|
|
2470
|
+
] });
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
// src/ui/skeletons/admin-panel/subjects/user-subjetcs-student-card.tsx
|
|
2474
|
+
import { jsx as jsx55, jsxs as jsxs47 } from "react/jsx-runtime";
|
|
2475
|
+
function UserSubjectsStudentCardSkeleton() {
|
|
2476
|
+
return /* @__PURE__ */ jsx55(Card, { className: "flex flex-col grow bg-sky-200 m-2", children: Array.from({ length: 2 }).map((_, i) => /* @__PURE__ */ jsxs47(Card, { className: "flex flex-row w-full justify-between", children: [
|
|
2477
|
+
/* @__PURE__ */ jsxs47("div", { className: "flex flex-col gap-1.5", children: [
|
|
2478
|
+
/* @__PURE__ */ jsx55("div", { className: "skeleton h-5 w-16 rounded-xs" }),
|
|
2479
|
+
/* @__PURE__ */ jsx55("div", { className: "skeleton h-4 w-24 rounded-xs" })
|
|
2480
|
+
] }),
|
|
2481
|
+
/* @__PURE__ */ jsx55("div", { className: "skeleton h-12 w-12 rounded-full shrink-0 relative", children: /* @__PURE__ */ jsx55("div", { className: "absolute inset-[15%] rounded-full bg-sky-200" }) })
|
|
2482
|
+
] }, i)) });
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
// src/ui/skeletons/inputs/subject-input.tsx
|
|
2486
|
+
import { jsx as jsx56, jsxs as jsxs48 } from "react/jsx-runtime";
|
|
2487
|
+
function SubjectInputSkeleton({ showMeta = true }) {
|
|
2488
|
+
return /* @__PURE__ */ jsxs48("div", { className: "relative mt-3 min-w-50 mb-5", children: [
|
|
2489
|
+
/* @__PURE__ */ jsx56("div", { className: "skeleton w-full h-10 rounded-xs" }),
|
|
2490
|
+
showMeta && /* @__PURE__ */ jsxs48("div", { className: "flex flex-row justify-between mt-1.5", children: [
|
|
2491
|
+
/* @__PURE__ */ jsx56("div", { className: "skeleton h-3 w-24 rounded-xs" }),
|
|
2492
|
+
/* @__PURE__ */ jsx56("div", { className: "skeleton h-4 w-32 rounded-xs" })
|
|
2493
|
+
] })
|
|
2494
|
+
] });
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
// src/ui/skeletons/subjects/admin-card.tsx
|
|
2498
|
+
import { jsx as jsx57, jsxs as jsxs49 } from "react/jsx-runtime";
|
|
2499
|
+
function AdminSubjectCardSkeleton() {
|
|
2500
|
+
return /* @__PURE__ */ jsxs49("div", { className: "h-fit w-200 flex flex-col bg-sky-200 gap-1 p-1 mb-1 mt-1", children: [
|
|
2501
|
+
/* @__PURE__ */ jsxs49("div", { className: "flex flex-row w-full justify-between", children: [
|
|
2502
|
+
/* @__PURE__ */ jsx57("div", { className: "skeleton h-5 w-24 rounded-xs" }),
|
|
2503
|
+
/* @__PURE__ */ jsx57("div", { className: "skeleton h-5 w-40 rounded-xs" })
|
|
2504
|
+
] }),
|
|
2505
|
+
/* @__PURE__ */ jsx57(SubjectInputSkeleton, { showMeta: false })
|
|
2506
|
+
] });
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// src/ui/skeletons/subjects/comment.tsx
|
|
2510
|
+
import { jsx as jsx58, jsxs as jsxs50 } from "react/jsx-runtime";
|
|
2511
|
+
function CommentSkeleton() {
|
|
2512
|
+
return /* @__PURE__ */ jsxs50("div", { className: "flex flex-row items-center gap-1.5", children: [
|
|
2513
|
+
/* @__PURE__ */ jsx58("div", { className: "skeleton h-4 w-4 rounded-xs" }),
|
|
2514
|
+
/* @__PURE__ */ jsx58("div", { className: "skeleton h-4 w-44 rounded-xs" })
|
|
2515
|
+
] });
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
// src/ui/skeletons/subjects/student-card.tsx
|
|
2519
|
+
import { Fragment as Fragment7, jsx as jsx59, jsxs as jsxs51 } from "react/jsx-runtime";
|
|
2520
|
+
function StudentCardSkeleton({ showButtons = true }) {
|
|
2521
|
+
return /* @__PURE__ */ jsxs51("div", { className: "h-fit sm:w-200 w-96 flex flex-col bg-sky-200 gap-1 p-1 mb-1 mt-1", children: [
|
|
2522
|
+
/* @__PURE__ */ jsxs51("div", { className: "flex flex-row w-full justify-between", children: [
|
|
2523
|
+
/* @__PURE__ */ jsx59("div", { className: "skeleton h-5 w-24 rounded-xs" }),
|
|
2524
|
+
/* @__PURE__ */ jsx59("div", { className: "skeleton h-5 w-24 rounded-xs" })
|
|
2525
|
+
] }),
|
|
2526
|
+
/* @__PURE__ */ jsx59(SubjectInputSkeleton, {}),
|
|
2527
|
+
/* @__PURE__ */ jsx59(CommentSkeleton, {}),
|
|
2528
|
+
showButtons && /* @__PURE__ */ jsxs51(Fragment7, { children: [
|
|
2529
|
+
/* @__PURE__ */ jsx59(ButtonSkeleton, { className: "mb-1! mt-1!" }),
|
|
2530
|
+
/* @__PURE__ */ jsx59(ButtonSkeleton, { className: "mb-1! mt-1!" })
|
|
2531
|
+
] })
|
|
2532
|
+
] });
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
// src/ui/skeletons/subjects/teacher-card.tsx
|
|
2536
|
+
import { Fragment as Fragment8, jsx as jsx60, jsxs as jsxs52 } from "react/jsx-runtime";
|
|
2537
|
+
function TeacherCardSkeleton({ showButtons = false }) {
|
|
2538
|
+
return /* @__PURE__ */ jsxs52("div", { className: "h-fit sm:w-200 w-96 flex flex-col bg-sky-200 gap-1 p-1 mb-1 mt-1", children: [
|
|
2539
|
+
/* @__PURE__ */ jsxs52("div", { className: "flex flex-row w-full justify-between", children: [
|
|
2540
|
+
/* @__PURE__ */ jsx60("div", { className: "skeleton h-5 w-24 rounded-xs" }),
|
|
2541
|
+
/* @__PURE__ */ jsx60("div", { className: "skeleton h-5 w-24 rounded-xs" })
|
|
2542
|
+
] }),
|
|
2543
|
+
/* @__PURE__ */ jsx60(SubjectInputSkeleton, {}),
|
|
2544
|
+
/* @__PURE__ */ jsx60(CommentSkeleton, {}),
|
|
2545
|
+
showButtons && /* @__PURE__ */ jsxs52(Fragment8, { children: [
|
|
2546
|
+
/* @__PURE__ */ jsx60(ButtonSkeleton, { className: "-mb-1! mt-1!" }),
|
|
2547
|
+
/* @__PURE__ */ jsx60(ButtonSkeleton, { className: "mb-1! mt-1!" })
|
|
2548
|
+
] })
|
|
2549
|
+
] });
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
// src/ui/skeletons/support/support-ticket-card.tsx
|
|
2553
|
+
import { jsx as jsx61, jsxs as jsxs53 } from "react/jsx-runtime";
|
|
2554
|
+
function SupportTicketCardSkeleton() {
|
|
2555
|
+
return /* @__PURE__ */ jsxs53("div", { className: "h-fit w-200 flex flex-col bg-sky-200 gap-1 gap-y-3 p-1 mt-1", children: [
|
|
2556
|
+
/* @__PURE__ */ jsxs53("div", { className: "flex flex-row w-full justify-between", children: [
|
|
2557
|
+
/* @__PURE__ */ jsx61("div", { className: "skeleton h-5 w-32 rounded-xs" }),
|
|
2558
|
+
/* @__PURE__ */ jsx61("div", { className: "skeleton h-5 w-24 rounded-xs" })
|
|
2559
|
+
] }),
|
|
2560
|
+
/* @__PURE__ */ jsx61("div", { className: "skeleton h-24 w-full rounded-xs" }),
|
|
2561
|
+
/* @__PURE__ */ jsx61("div", { className: "skeleton h-10 w-full rounded-xs" })
|
|
2562
|
+
] });
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
// src/ui/subjects/admin-card.tsx
|
|
2566
|
+
import {
|
|
2567
|
+
cookiesSet as cookiesSet3,
|
|
2568
|
+
fetchCsrfClient as fetchCsrfClient3,
|
|
2569
|
+
getStatusLabel as getStatusLabel2,
|
|
2570
|
+
goralysFetchClient as goralysFetchClient3,
|
|
2571
|
+
handleToastRequest as handleToastRequest3,
|
|
2572
|
+
SubjectStatusConfig
|
|
2573
|
+
} from "@goralys/core";
|
|
2574
|
+
import { jsx as jsx62, jsxs as jsxs54 } from "react/jsx-runtime";
|
|
2575
|
+
function AdminSubjectsCard({ subjectData, onUpdateAction, syncKey }) {
|
|
2576
|
+
const password = usePasswordModal();
|
|
2577
|
+
const toast = useToast();
|
|
2578
|
+
const updateStatus = async (status) => {
|
|
2579
|
+
if (status === subjectData.status) return;
|
|
2580
|
+
const pwd = await password.showPasswordModal("le changement de statut");
|
|
2581
|
+
if (!pwd) return;
|
|
2582
|
+
if (pwd.trim() === "") {
|
|
2583
|
+
toast.showToast({
|
|
2584
|
+
type: "warning",
|
|
2585
|
+
title: "Mot de passe",
|
|
2586
|
+
message: "Veuillez saisir un mot de passe."
|
|
2587
|
+
});
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
const csrfToken = await fetchCsrfClient3("update-subject-status");
|
|
2591
|
+
const payload = {
|
|
2592
|
+
student: subjectData.studentToken,
|
|
2593
|
+
teacher: subjectData.teacherToken,
|
|
2594
|
+
status,
|
|
2595
|
+
topic: subjectData.topicCode,
|
|
2596
|
+
"admin-password": pwd,
|
|
2597
|
+
"csrf-token": csrfToken
|
|
2598
|
+
};
|
|
2599
|
+
const res = await goralysFetchClient3("PATCH", "subjects/status", payload);
|
|
2600
|
+
await handleToastRequest3(res, toast.showToast, false);
|
|
2601
|
+
const data = await res?.json();
|
|
2602
|
+
if (data.toastType === "info" && res.ok) {
|
|
2603
|
+
cookiesSet3(syncKey, "0");
|
|
2604
|
+
onUpdateAction();
|
|
2605
|
+
}
|
|
2606
|
+
};
|
|
2607
|
+
return /* @__PURE__ */ jsxs54("div", { className: "h-fit w-200 flex flex-col bg-sky-200 gap-1 p-1 mb-1 mt-1", children: [
|
|
2608
|
+
/* @__PURE__ */ jsxs54("div", { className: "flex flex-row w-full justify-between", children: [
|
|
2609
|
+
/* @__PURE__ */ jsx62("strong", { children: subjectData.student }),
|
|
2610
|
+
/* @__PURE__ */ jsxs54("strong", { children: [
|
|
2611
|
+
subjectData.topic,
|
|
2612
|
+
" (",
|
|
2613
|
+
subjectData.teacher,
|
|
2614
|
+
")"
|
|
2615
|
+
] })
|
|
2616
|
+
] }),
|
|
2617
|
+
/* @__PURE__ */ jsx62(
|
|
2618
|
+
SubjectInputAdmin,
|
|
2619
|
+
{
|
|
2620
|
+
id: subjectData.studentToken + subjectData.teacherToken + "-input",
|
|
2621
|
+
subjectData,
|
|
2622
|
+
label: "Question de l'El\xE8ve"
|
|
2623
|
+
}
|
|
2624
|
+
),
|
|
2625
|
+
/* @__PURE__ */ jsxs54("div", { className: "flex flex-row justify-between", children: [
|
|
2626
|
+
/* @__PURE__ */ jsxs54("p", { children: [
|
|
2627
|
+
"Statut de la question: ",
|
|
2628
|
+
getStatusLabel2(subjectData.status)
|
|
2629
|
+
] }),
|
|
2630
|
+
/* @__PURE__ */ jsxs54("div", { className: "relative pb-0 mb-1", children: [
|
|
2631
|
+
/* @__PURE__ */ jsx62(
|
|
2632
|
+
"select",
|
|
2633
|
+
{
|
|
2634
|
+
className: "border-0 border-b-2 border-sky-300 appearance-none\n cursor-pointer outline-none focus:ring-0 text-base leading-5\n text-heading pb-0 pr-5 subjects-search-select",
|
|
2635
|
+
value: subjectData.status,
|
|
2636
|
+
onChange: (e) => updateStatus(e.target.value),
|
|
2637
|
+
children: Object.entries(SubjectStatusConfig).map(([key, info]) => /* @__PURE__ */ jsx62("option", { value: key, children: info.label }, key))
|
|
2638
|
+
}
|
|
2639
|
+
),
|
|
2640
|
+
/* @__PURE__ */ jsx62(
|
|
2641
|
+
"span",
|
|
2642
|
+
{
|
|
2643
|
+
className: "absolute bottom-0 left-0 w-0 h-0.5 bg-sky-500\n transition-all duration-300 ease-in-out\n peer-focus:w-full subjects-search-underline"
|
|
2644
|
+
}
|
|
2645
|
+
)
|
|
2646
|
+
] })
|
|
2647
|
+
] })
|
|
2648
|
+
] });
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
// src/ui/subjects/comment-student.tsx
|
|
2652
|
+
import { ChevronRightIcon } from "@heroicons/react/24/outline";
|
|
2653
|
+
import { Fragment as Fragment9, jsx as jsx63, jsxs as jsxs55 } from "react/jsx-runtime";
|
|
2654
|
+
function CommentStudent({ subjectData, disabled, onChange }) {
|
|
2655
|
+
const visible = !!subjectData?.comment && !(subjectData.status === "submitted" || subjectData.status === "approved");
|
|
2656
|
+
const showLastRejected = subjectData.status === "not_submitted" && subjectData.lastRejected;
|
|
2657
|
+
if (!visible) {
|
|
2658
|
+
return /* @__PURE__ */ jsx63(Fragment9, {});
|
|
2659
|
+
}
|
|
2660
|
+
return /* @__PURE__ */ jsx63(Fragment9, { children: /* @__PURE__ */ jsxs55(
|
|
2661
|
+
"details",
|
|
2662
|
+
{
|
|
2663
|
+
className: "group",
|
|
2664
|
+
open: subjectData.status === "rejected",
|
|
2665
|
+
children: [
|
|
2666
|
+
/* @__PURE__ */ jsxs55("summary", { className: "flex flex-row cursor-pointer", children: [
|
|
2667
|
+
/* @__PURE__ */ jsx63(ChevronRightIcon, { className: "w-5 h-5 transition-transform duration-200 group-open:rotate-90" }),
|
|
2668
|
+
/* @__PURE__ */ jsx63("span", { children: "Commentaire du professeur" })
|
|
2669
|
+
] }),
|
|
2670
|
+
showLastRejected && /* @__PURE__ */ jsxs55(Fragment9, { children: [
|
|
2671
|
+
/* @__PURE__ */ jsx63("span", { className: "h-2 w-full block" }),
|
|
2672
|
+
/* @__PURE__ */ jsx63(
|
|
2673
|
+
SubjectTextArea,
|
|
2674
|
+
{
|
|
2675
|
+
id: subjectData.studentToken + subjectData.teacherToken + "-last-rejected",
|
|
2676
|
+
label: "Votre question non valid\xE9e",
|
|
2677
|
+
defaultValue: subjectData.lastRejected,
|
|
2678
|
+
disabled: true,
|
|
2679
|
+
animate: false,
|
|
2680
|
+
subjectData
|
|
2681
|
+
}
|
|
2682
|
+
)
|
|
2683
|
+
] }),
|
|
2684
|
+
/* @__PURE__ */ jsx63("span", { className: "h-1.5 w-full block" }),
|
|
2685
|
+
/* @__PURE__ */ jsx63(
|
|
2686
|
+
TextArea,
|
|
2687
|
+
{
|
|
2688
|
+
id: subjectData.studentToken + subjectData.teacherToken + "-subject-comment",
|
|
2689
|
+
label: "Commentaire",
|
|
2690
|
+
defaultValue: subjectData.comment,
|
|
2691
|
+
onChangeAction: onChange,
|
|
2692
|
+
disabled
|
|
2693
|
+
}
|
|
2694
|
+
)
|
|
2695
|
+
]
|
|
2696
|
+
},
|
|
2697
|
+
`comment-student-details-for-${subjectData.teacher}-${subjectData.topic}`
|
|
2698
|
+
) });
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
// src/ui/subjects/comment-teacher.tsx
|
|
2702
|
+
import { ChevronRightIcon as ChevronRightIcon2 } from "@heroicons/react/24/outline";
|
|
2703
|
+
import { Fragment as Fragment10, jsx as jsx64, jsxs as jsxs56 } from "react/jsx-runtime";
|
|
2704
|
+
function CommentTeacher({ subjectData, disabled, ref, onChange }) {
|
|
2705
|
+
const visible = subjectData.status === "submitted";
|
|
2706
|
+
if (!visible) {
|
|
2707
|
+
return /* @__PURE__ */ jsx64(Fragment10, {});
|
|
2708
|
+
}
|
|
2709
|
+
return /* @__PURE__ */ jsx64(Fragment10, { children: /* @__PURE__ */ jsxs56(
|
|
2710
|
+
"details",
|
|
2711
|
+
{
|
|
2712
|
+
className: "group",
|
|
2713
|
+
open: subjectData.status === "rejected",
|
|
2714
|
+
children: [
|
|
2715
|
+
/* @__PURE__ */ jsxs56("summary", { className: "flex flex-row cursor-pointer", children: [
|
|
2716
|
+
/* @__PURE__ */ jsx64(ChevronRightIcon2, { className: "w-5 h-5 transition-transform duration-200 group-open:rotate-90" }),
|
|
2717
|
+
/* @__PURE__ */ jsx64("span", { children: "Votre commentaire" })
|
|
2718
|
+
] }),
|
|
2719
|
+
/* @__PURE__ */ jsx64(
|
|
2720
|
+
TextArea,
|
|
2721
|
+
{
|
|
2722
|
+
id: subjectData.studentToken + subjectData.teacherToken + "-subject-comment",
|
|
2723
|
+
label: "Commentaire",
|
|
2724
|
+
defaultValue: subjectData.comment,
|
|
2725
|
+
onChangeAction: onChange,
|
|
2726
|
+
disabled,
|
|
2727
|
+
ref
|
|
2728
|
+
},
|
|
2729
|
+
`comment-teacher-details-for-${subjectData.student}-${subjectData.topic}`
|
|
2730
|
+
)
|
|
2731
|
+
]
|
|
2732
|
+
},
|
|
2733
|
+
`comment-teacher-details-for-${subjectData.student}-${subjectData.topic}`
|
|
2734
|
+
) });
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
// src/ui/subjects/readonly-card.tsx
|
|
2738
|
+
import { getStatusLabel as getStatusLabel3 } from "@goralys/core";
|
|
2739
|
+
import { jsx as jsx65, jsxs as jsxs57 } from "react/jsx-runtime";
|
|
2740
|
+
function ReadonlyCard({ subjectData }) {
|
|
2741
|
+
return /* @__PURE__ */ jsxs57("div", { className: "h-fit w-200 flex flex-col bg-sky-200 gap-1 p-1 mb-1 mt-1", children: [
|
|
2742
|
+
/* @__PURE__ */ jsxs57("div", { className: "flex flex-row w-full justify-between", children: [
|
|
2743
|
+
/* @__PURE__ */ jsx65("strong", { children: subjectData.student }),
|
|
2744
|
+
/* @__PURE__ */ jsxs57("strong", { children: [
|
|
2745
|
+
subjectData.topic,
|
|
2746
|
+
" (",
|
|
2747
|
+
subjectData.teacher,
|
|
2748
|
+
")"
|
|
2749
|
+
] })
|
|
2750
|
+
] }),
|
|
2751
|
+
/* @__PURE__ */ jsx65(
|
|
2752
|
+
SubjectInputAdmin,
|
|
2753
|
+
{
|
|
2754
|
+
id: subjectData.studentToken + subjectData.teacherToken + "-input",
|
|
2755
|
+
subjectData,
|
|
2756
|
+
label: "Question de l'El\xE8ve"
|
|
2757
|
+
}
|
|
2758
|
+
),
|
|
2759
|
+
/* @__PURE__ */ jsx65("div", { className: "flex flex-row justify-between", children: /* @__PURE__ */ jsxs57("p", { children: [
|
|
2760
|
+
"Statut de la question: ",
|
|
2761
|
+
getStatusLabel3(subjectData.status)
|
|
2762
|
+
] }) })
|
|
2763
|
+
] });
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
// src/ui/subjects/student-card.tsx
|
|
2767
|
+
import {
|
|
2768
|
+
cookiesSet as cookiesSet4,
|
|
2769
|
+
fetchCsrfClient as fetchCsrfClient4,
|
|
2770
|
+
getShortFromLong as getShortFromLong2,
|
|
2771
|
+
goralysFetchClient as goralysFetchClient4,
|
|
2772
|
+
handleToastRequest as handleToastRequest4,
|
|
2773
|
+
SUBJECT_SYNCS
|
|
2774
|
+
} from "@goralys/core";
|
|
2775
|
+
import { useState as useState27 } from "react";
|
|
2776
|
+
import { Fragment as Fragment11, jsx as jsx66, jsxs as jsxs58 } from "react/jsx-runtime";
|
|
2777
|
+
function StudentCard({ subjectData, onUpdateAction }) {
|
|
2778
|
+
const toast = useToast();
|
|
2779
|
+
const confirm = useConfirm();
|
|
2780
|
+
const isDesktop = useMediaQuery("(min-width:640px)");
|
|
2781
|
+
const [subject, setSubject] = useState27(subjectData.subject);
|
|
2782
|
+
const [isInterdisciplinary, setIsInterdisciplinary] = useState27(subjectData.interdisciplinary);
|
|
2783
|
+
const modal = useDraftModal();
|
|
2784
|
+
const saveDraft = async () => {
|
|
2785
|
+
if (!subject || subject.trim() == "") {
|
|
2786
|
+
toast.showToast({
|
|
2787
|
+
type: "warning",
|
|
2788
|
+
title: "Brouillon",
|
|
2789
|
+
message: "Veuillez saisir une question."
|
|
2790
|
+
});
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
const csrfToken = await fetchCsrfClient4("save-draft");
|
|
2794
|
+
const payload = {
|
|
2795
|
+
teacher: subjectData.teacherToken,
|
|
2796
|
+
student: subjectData.studentToken,
|
|
2797
|
+
topic: subjectData.topicCode,
|
|
2798
|
+
draft: subject,
|
|
2799
|
+
interdisciplinary: isInterdisciplinary,
|
|
2800
|
+
"csrf-token": csrfToken
|
|
2801
|
+
};
|
|
2802
|
+
const res = await goralysFetchClient4("PUT", "subjects/draft", payload);
|
|
2803
|
+
await handleToastRequest4(res, toast.showToast, false);
|
|
2804
|
+
const data = await res.json();
|
|
2805
|
+
if (data.toastType === "info" && res.ok) {
|
|
2806
|
+
cookiesSet4(SUBJECT_SYNCS["student"], "0");
|
|
2807
|
+
onUpdateAction();
|
|
2808
|
+
}
|
|
2809
|
+
};
|
|
2810
|
+
const sendSubject = async () => {
|
|
2811
|
+
if (!subject || subject.trim() == "") {
|
|
2812
|
+
toast.showToast({
|
|
2813
|
+
type: "warning",
|
|
2814
|
+
title: "Envoi",
|
|
2815
|
+
message: "Veuillez saisir une question."
|
|
2816
|
+
});
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
if (!await confirm.showConfirm({
|
|
2820
|
+
title: "Envoi",
|
|
2821
|
+
message: "\xCAtes-vous s\xFBr de vouloir envoyer cette question au professeur ?"
|
|
2822
|
+
}))
|
|
2823
|
+
return;
|
|
2824
|
+
if (subject?.trim() === subjectData.lastRejected?.trim()) {
|
|
2825
|
+
toast.showToast({
|
|
2826
|
+
type: "warning",
|
|
2827
|
+
title: "Envoi",
|
|
2828
|
+
message: "Cette question n\u2019a pas \xE9t\xE9 modifi\xE9e depuis son invalidation. Merci de la corriger avant de la renvoyer."
|
|
2829
|
+
});
|
|
2830
|
+
return;
|
|
2831
|
+
}
|
|
2832
|
+
const formData = new FormData();
|
|
2833
|
+
if (isDesktop) {
|
|
2834
|
+
const result = await modal.showDraftModal();
|
|
2835
|
+
if (result.type === "closed") return;
|
|
2836
|
+
if (result.type == "withDraft" && !result.file) {
|
|
2837
|
+
toast.showToast({
|
|
2838
|
+
type: "warning",
|
|
2839
|
+
title: "Envoi",
|
|
2840
|
+
message: "Veuillez choisir un brouillon ou envoyer la question seule."
|
|
2841
|
+
});
|
|
2842
|
+
return;
|
|
2843
|
+
}
|
|
2844
|
+
if (result.type == "withDraft") {
|
|
2845
|
+
formData.append("draft-file", result.file ?? "");
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
const csrfToken = await fetchCsrfClient4("submit-subject");
|
|
2849
|
+
formData.append("teacher", subjectData.teacherToken);
|
|
2850
|
+
formData.append("student", subjectData.studentToken);
|
|
2851
|
+
formData.append("topic", subjectData.topic);
|
|
2852
|
+
formData.append("subject", subject ?? "");
|
|
2853
|
+
formData.append("csrf-token", csrfToken ?? "");
|
|
2854
|
+
formData.append("interdisciplinary", isInterdisciplinary ? "1" : "0");
|
|
2855
|
+
const res = await goralysFetchClient4("POST", "subjects/submit", formData);
|
|
2856
|
+
if (await handleToastRequest4(res, toast.showToast, false)) {
|
|
2857
|
+
const data = await res.json();
|
|
2858
|
+
if (data.toastType === "info" && res.ok) {
|
|
2859
|
+
cookiesSet4(SUBJECT_SYNCS["student"], "0");
|
|
2860
|
+
onUpdateAction();
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
};
|
|
2864
|
+
const key = subjectData.teacher + subjectData.topic;
|
|
2865
|
+
return /* @__PURE__ */ jsxs58("div", { className: "h-fit sm:w-200 w-96 flex flex-col bg-sky-200 gap-1 p-1 mb-1 mt-1", children: [
|
|
2866
|
+
/* @__PURE__ */ jsxs58("div", { className: "flex flex-row w-full justify-between", children: [
|
|
2867
|
+
/* @__PURE__ */ jsx66("strong", { children: isDesktop ? subjectData.topic : getShortFromLong2(subjectData.topic) }),
|
|
2868
|
+
isDesktop && /* @__PURE__ */ jsx66("strong", { children: subjectData.teacher })
|
|
2869
|
+
] }),
|
|
2870
|
+
/* @__PURE__ */ jsx66(
|
|
2871
|
+
SubjectInputStudent,
|
|
2872
|
+
{
|
|
2873
|
+
id: `subject-input-student-for-${key}`,
|
|
2874
|
+
label: "Votre Question",
|
|
2875
|
+
subjectData,
|
|
2876
|
+
onChangeAction: (e) => {
|
|
2877
|
+
setSubject(e.target.value);
|
|
2878
|
+
},
|
|
2879
|
+
setIsInterdisciplinaryAction: setIsInterdisciplinary
|
|
2880
|
+
}
|
|
2881
|
+
),
|
|
2882
|
+
/* @__PURE__ */ jsx66(CommentStudent, { subjectData, disabled: true }, `comment-student-for-${key}`),
|
|
2883
|
+
!(subjectData.status === "submitted" || subjectData.status === "approved") && /* @__PURE__ */ jsxs58(Fragment11, { children: [
|
|
2884
|
+
/* @__PURE__ */ jsx66(Button, { className: "mb-1! mt-1!", text: "Enregistrer comme brouillon", type: "button", onClick: saveDraft }),
|
|
2885
|
+
/* @__PURE__ */ jsx66(Button, { className: "mb-1! mt-1!", text: "Envoyer la question", type: "button", onClick: sendSubject })
|
|
2886
|
+
] })
|
|
2887
|
+
] });
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// src/ui/subjects/subjects-filter.tsx
|
|
2891
|
+
import { useCallback as useCallback8, useEffect as useEffect13, useState as useState28 } from "react";
|
|
2892
|
+
import { getStatusLabel as getStatusLabel4 } from "@goralys/core";
|
|
2893
|
+
import { jsx as jsx67 } from "react/jsx-runtime";
|
|
2894
|
+
function SubjectsFilter({ subjects, setCurrentSubjects, defaults }) {
|
|
2895
|
+
const DEFAULT_STATUS = {
|
|
2896
|
+
not_submitted: { default: defaults?.not_submitted ?? false, label: getStatusLabel4("not_submitted") },
|
|
2897
|
+
submitted: { default: defaults?.submitted ?? true, label: getStatusLabel4("submitted") },
|
|
2898
|
+
rejected: { default: defaults?.rejected ?? false, label: getStatusLabel4("rejected") },
|
|
2899
|
+
approved: { default: defaults?.approved ?? false, label: getStatusLabel4("approved") }
|
|
2900
|
+
};
|
|
2901
|
+
const [shownStatus, setShownStatus] = useState28(
|
|
2902
|
+
Object.keys(DEFAULT_STATUS).filter((k) => DEFAULT_STATUS[k].default)
|
|
2903
|
+
);
|
|
2904
|
+
const toggleStatus = (s) => {
|
|
2905
|
+
console.log("toggling status: ", s);
|
|
2906
|
+
const status = shownStatus;
|
|
2907
|
+
if (shownStatus.includes(s)) {
|
|
2908
|
+
const i = shownStatus.indexOf(s);
|
|
2909
|
+
status.splice(i, 1);
|
|
2910
|
+
} else {
|
|
2911
|
+
status.push(s);
|
|
2912
|
+
}
|
|
2913
|
+
setShownStatus(status);
|
|
2914
|
+
console.log("toggle done, new shown status: ", shownStatus);
|
|
2915
|
+
console.log("filtering");
|
|
2916
|
+
filter();
|
|
2917
|
+
console.log("filtering done");
|
|
2918
|
+
};
|
|
2919
|
+
const filter = useCallback8(() => {
|
|
2920
|
+
const filtered = [];
|
|
2921
|
+
subjects.forEach((s) => {
|
|
2922
|
+
if (shownStatus.includes(s.status)) filtered.push(s);
|
|
2923
|
+
});
|
|
2924
|
+
console.log(filtered);
|
|
2925
|
+
setCurrentSubjects(filtered);
|
|
2926
|
+
}, [shownStatus, subjects, setCurrentSubjects]);
|
|
2927
|
+
useEffect13(() => {
|
|
2928
|
+
filter();
|
|
2929
|
+
}, [filter]);
|
|
2930
|
+
return /* @__PURE__ */ jsx67("div", { className: "flex flex-row gap-2 w-full", children: Object.entries(DEFAULT_STATUS).map(([k, v]) => /* @__PURE__ */ jsx67(
|
|
2931
|
+
Checkbox,
|
|
2932
|
+
{
|
|
2933
|
+
label: v.label,
|
|
2934
|
+
setValueAction: () => toggleStatus(k),
|
|
2935
|
+
defaultValue: DEFAULT_STATUS[k].default
|
|
2936
|
+
},
|
|
2937
|
+
"subjects-filter-checkbox-for-" + k
|
|
2938
|
+
)) });
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// src/ui/subjects/subjects-search-bar.tsx
|
|
2942
|
+
import { useEffect as useEffect14, useState as useState29 } from "react";
|
|
2943
|
+
import { getLongFromShort, searchFields } from "@goralys/core";
|
|
2944
|
+
import { jsx as jsx68, jsxs as jsxs59 } from "react/jsx-runtime";
|
|
2945
|
+
function SubjectsSearchBar({ subjects, setCurrentSubjects }) {
|
|
2946
|
+
const [currentField, setCurrentField] = useState29("all");
|
|
2947
|
+
const [searchText, setSearchText] = useState29("");
|
|
2948
|
+
const handleSearch = (e) => {
|
|
2949
|
+
const value = e.currentTarget.value;
|
|
2950
|
+
setSearchText(value);
|
|
2951
|
+
};
|
|
2952
|
+
const sortSubjects = (list) => {
|
|
2953
|
+
return [...list].sort((a, b) => {
|
|
2954
|
+
const topicA = a.topic.trim().toLowerCase();
|
|
2955
|
+
const topicB = b.topic.trim().toLowerCase();
|
|
2956
|
+
const nameA = a.student.trim().toLowerCase();
|
|
2957
|
+
const nameB = b.student.trim().toLowerCase();
|
|
2958
|
+
const topicDiff = topicA.localeCompare(topicB, "fr");
|
|
2959
|
+
if (topicDiff !== 0) return topicDiff;
|
|
2960
|
+
return nameA.localeCompare(nameB, "fr");
|
|
2961
|
+
});
|
|
2962
|
+
};
|
|
2963
|
+
useEffect14(() => {
|
|
2964
|
+
if (!subjects) return;
|
|
2965
|
+
const search = searchText.trim().toLowerCase();
|
|
2966
|
+
const sorted = sortSubjects(
|
|
2967
|
+
subjects.filter((s) => {
|
|
2968
|
+
if (!search) return true;
|
|
2969
|
+
const searchTeachers = s.teacher.split(",").map((t) => t.trim().toLowerCase());
|
|
2970
|
+
switch (currentField) {
|
|
2971
|
+
case "student":
|
|
2972
|
+
return s.student.trim().toLowerCase().startsWith(search);
|
|
2973
|
+
case "teacher":
|
|
2974
|
+
return searchTeachers.some((t) => t.startsWith(search));
|
|
2975
|
+
case "topic":
|
|
2976
|
+
return s.topic.trim().toLowerCase().includes(getLongFromShort(search));
|
|
2977
|
+
case "all":
|
|
2978
|
+
return s.student.trim().toLowerCase().startsWith(search) || searchTeachers.some((t) => t.startsWith(search)) || s.topic.trim().toLowerCase().includes(getLongFromShort(search));
|
|
2979
|
+
default:
|
|
2980
|
+
return true;
|
|
2981
|
+
}
|
|
2982
|
+
})
|
|
2983
|
+
);
|
|
2984
|
+
setCurrentSubjects((prev) => {
|
|
2985
|
+
if (JSON.stringify(prev) === JSON.stringify(sorted)) return prev;
|
|
2986
|
+
return sorted;
|
|
2987
|
+
});
|
|
2988
|
+
}, [searchText, currentField, subjects, setCurrentSubjects]);
|
|
2989
|
+
return /* @__PURE__ */ jsxs59("div", { className: "flex flex-row gap-2 items-end mb-4", children: [
|
|
2990
|
+
/* @__PURE__ */ jsx68(FloatingInput, { id: "admins-subjects-search", label: "Rechercher", onInput: handleSearch }),
|
|
2991
|
+
/* @__PURE__ */ jsxs59("div", { className: "relative pb-0 mb-1", children: [
|
|
2992
|
+
/* @__PURE__ */ jsx68(
|
|
2993
|
+
"select",
|
|
2994
|
+
{
|
|
2995
|
+
className: "border-0 border-b-2 border-sky-300 appearance-none\n cursor-pointer outline-none focus:ring-0 text-base leading-5\n text-heading pb-0 pr-5 subjects-search-select",
|
|
2996
|
+
value: currentField,
|
|
2997
|
+
onChange: (e) => setCurrentField(e.target.value),
|
|
2998
|
+
children: Object.entries(searchFields).map(([key, label]) => /* @__PURE__ */ jsx68("option", { value: key, children: label }, key))
|
|
2999
|
+
}
|
|
3000
|
+
),
|
|
3001
|
+
/* @__PURE__ */ jsx68(
|
|
3002
|
+
"span",
|
|
3003
|
+
{
|
|
3004
|
+
className: "absolute bottom-0 left-0 w-0 h-0.5 bg-sky-500\n transition-all duration-300 ease-in-out\n peer-focus:w-full subjects-search-underline"
|
|
3005
|
+
}
|
|
3006
|
+
)
|
|
3007
|
+
] })
|
|
3008
|
+
] });
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
// src/ui/subjects/teacher-card.tsx
|
|
3012
|
+
import {
|
|
3013
|
+
cookiesSet as cookiesSet5,
|
|
3014
|
+
fetchCsrfClient as fetchCsrfClient5,
|
|
3015
|
+
getShortFromLong as getShortFromLong3,
|
|
3016
|
+
goralysFetchClient as goralysFetchClient5,
|
|
3017
|
+
handleToastRequest as handleToastRequest5,
|
|
3018
|
+
SUBJECT_SYNCS as SUBJECT_SYNCS2
|
|
3019
|
+
} from "@goralys/core";
|
|
3020
|
+
import { useRef as useRef7, useState as useState30 } from "react";
|
|
3021
|
+
import { Fragment as Fragment12, jsx as jsx69, jsxs as jsxs60 } from "react/jsx-runtime";
|
|
3022
|
+
function TeacherCard({ subjectData, onUpdateAction }) {
|
|
3023
|
+
const toast = useToast();
|
|
3024
|
+
const confirm = useConfirm();
|
|
3025
|
+
const [comment, setComment] = useState30(subjectData.comment);
|
|
3026
|
+
const commentRef = useRef7(null);
|
|
3027
|
+
const isDesktop = useMediaQuery("(min-width: 640px)");
|
|
3028
|
+
const rejectSubject = async () => {
|
|
3029
|
+
if (comment?.trim() === "" || !comment) {
|
|
3030
|
+
toast.showToast({
|
|
3031
|
+
type: "warning",
|
|
3032
|
+
title: "Commentaire requis",
|
|
3033
|
+
message: "Vous devez fournir un commentaire avant de rejeter cette question."
|
|
3034
|
+
});
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
if (!await confirm.showConfirm({
|
|
3038
|
+
title: "Confirmer le rejet",
|
|
3039
|
+
message: "Une fois la question rejet\xE9e, l'\xE9l\xE8ve devra en soumettre une nouvelle. Voulez-vous quand m\xEAme rejeter cette question ?"
|
|
3040
|
+
}))
|
|
3041
|
+
return;
|
|
3042
|
+
if (comment?.trim() === subjectData.comment.trim()) {
|
|
3043
|
+
const confirmed = await confirm.showConfirm({
|
|
3044
|
+
title: "Confirmer le rejet",
|
|
3045
|
+
message: "Le commentaire n\u2019a pas \xE9t\xE9 modifi\xE9. Voulez-vous quand m\xEAme rejeter cette question ?"
|
|
3046
|
+
});
|
|
3047
|
+
if (!confirmed) {
|
|
3048
|
+
requestAnimationFrame(() => {
|
|
3049
|
+
commentRef?.current?.blur();
|
|
3050
|
+
commentRef?.current?.focus();
|
|
3051
|
+
});
|
|
3052
|
+
return;
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
const csrfToken = await fetchCsrfClient5("reject-subject");
|
|
3056
|
+
const payload = {
|
|
3057
|
+
teacher: subjectData.teacherToken,
|
|
3058
|
+
student: subjectData.studentToken,
|
|
3059
|
+
topic: subjectData.topicCode,
|
|
3060
|
+
comment,
|
|
3061
|
+
"csrf-token": csrfToken
|
|
3062
|
+
};
|
|
3063
|
+
const res = await goralysFetchClient5("POST", "subjects/reject", payload);
|
|
3064
|
+
if (await handleToastRequest5(res, toast.showToast, false)) {
|
|
3065
|
+
const data = await res.json();
|
|
3066
|
+
if (data.toastType === "info" && res.ok) {
|
|
3067
|
+
cookiesSet5(SUBJECT_SYNCS2["teacher"], "0");
|
|
3068
|
+
onUpdateAction();
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
};
|
|
3072
|
+
const approveSubject = async () => {
|
|
3073
|
+
if (!await confirm.showConfirm({
|
|
3074
|
+
title: "Validation de la question",
|
|
3075
|
+
message: "\xCAtes-vous s\xFBr de vouloir valider cette question de mani\xE8re d\xE9finitive. Une fois la question valid\xE9e, toute modification devient impossible."
|
|
3076
|
+
}))
|
|
3077
|
+
return;
|
|
3078
|
+
const csrfToken = await fetchCsrfClient5("approve-subject");
|
|
3079
|
+
const payload = {
|
|
3080
|
+
teacher: subjectData.teacherToken,
|
|
3081
|
+
student: subjectData.studentToken,
|
|
3082
|
+
topic: subjectData.topicCode,
|
|
3083
|
+
"new-status": "approved",
|
|
3084
|
+
"csrf-token": csrfToken
|
|
3085
|
+
};
|
|
3086
|
+
const res = await goralysFetchClient5("POST", "subjects/approve", payload);
|
|
3087
|
+
if (await handleToastRequest5(res, toast.showToast, false)) {
|
|
3088
|
+
const data = await res.json();
|
|
3089
|
+
if (data.toastType === "info" && res.ok) {
|
|
3090
|
+
cookiesSet5(SUBJECT_SYNCS2["teacher"], "0");
|
|
3091
|
+
onUpdateAction();
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
};
|
|
3095
|
+
return /* @__PURE__ */ jsxs60("div", { className: "h-fit sm:w-200 w-96 flex flex-col bg-sky-200 gap-1 p-1 mb-1 mt-1", children: [
|
|
3096
|
+
/* @__PURE__ */ jsxs60("div", { className: "flex flex-row w-full justify-between", children: [
|
|
3097
|
+
/* @__PURE__ */ jsx69("strong", { children: subjectData.student }),
|
|
3098
|
+
/* @__PURE__ */ jsx69("strong", { children: isDesktop ? subjectData.topic : getShortFromLong3(subjectData.topic) })
|
|
3099
|
+
] }),
|
|
3100
|
+
/* @__PURE__ */ jsx69(
|
|
3101
|
+
SubjectInputTeacher,
|
|
3102
|
+
{
|
|
3103
|
+
id: subjectData.studentToken + subjectData.teacherToken + "-comment",
|
|
3104
|
+
subjectData,
|
|
3105
|
+
label: "Question de l'El\xE8ve"
|
|
3106
|
+
}
|
|
3107
|
+
),
|
|
3108
|
+
/* @__PURE__ */ jsx69(
|
|
3109
|
+
CommentTeacher,
|
|
3110
|
+
{
|
|
3111
|
+
subjectData,
|
|
3112
|
+
disabled: subjectData.status !== "submitted",
|
|
3113
|
+
ref: commentRef,
|
|
3114
|
+
onChange: (e) => {
|
|
3115
|
+
setComment(e.target.value);
|
|
3116
|
+
}
|
|
3117
|
+
},
|
|
3118
|
+
`comment-teacher-for-${subjectData.student}-${subjectData.topic}`
|
|
3119
|
+
),
|
|
3120
|
+
subjectData.status === "submitted" && /* @__PURE__ */ jsxs60(Fragment12, { children: [
|
|
3121
|
+
/* @__PURE__ */ jsx69(
|
|
3122
|
+
Button,
|
|
3123
|
+
{
|
|
3124
|
+
className: "-mb-1! mt-1! shadow-none!",
|
|
3125
|
+
text: "Ne pas valider la question",
|
|
3126
|
+
type: "button",
|
|
3127
|
+
onClick: rejectSubject,
|
|
3128
|
+
color: "sky"
|
|
3129
|
+
}
|
|
3130
|
+
),
|
|
3131
|
+
/* @__PURE__ */ jsx69(
|
|
3132
|
+
Button,
|
|
3133
|
+
{
|
|
3134
|
+
className: "mb-1! mt-1! shadow-none!",
|
|
3135
|
+
text: "Valider la question",
|
|
3136
|
+
type: "button",
|
|
3137
|
+
onClick: approveSubject,
|
|
3138
|
+
color: "sky"
|
|
3139
|
+
}
|
|
3140
|
+
)
|
|
3141
|
+
] })
|
|
3142
|
+
] });
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
// src/ui/support/support-form.tsx
|
|
3146
|
+
import { useEffect as useEffect15, useState as useState31 } from "react";
|
|
3147
|
+
import { cookiesGet as cookiesGet6, EMAIL_KEY, fetchCsrfClient as fetchCsrfClient6, getGoralysClientConfig as getGoralysClientConfig2, reasonsConfig, USERNAME_KEY as USERNAME_KEY3 } from "@goralys/core";
|
|
3148
|
+
import { jsx as jsx70, jsxs as jsxs61 } from "react/jsx-runtime";
|
|
3149
|
+
function SupportForm() {
|
|
3150
|
+
const { apiDomain, getSchoolToken } = getGoralysClientConfig2();
|
|
3151
|
+
const [loggedIn, setLoggedIn] = useState31(true);
|
|
3152
|
+
const [csrfToken, setCsrfToken] = useState31(null);
|
|
3153
|
+
const requestUrl = `${apiDomain}/support/contact?high-school-token=${getSchoolToken()}`;
|
|
3154
|
+
const [reason, setReason] = useState31("password-forgot");
|
|
3155
|
+
useEffect15(() => {
|
|
3156
|
+
const run = async () => {
|
|
3157
|
+
setCsrfToken(await fetchCsrfClient6("support-ticket"));
|
|
3158
|
+
setLoggedIn(!!cookiesGet6(USERNAME_KEY3));
|
|
3159
|
+
};
|
|
3160
|
+
run().then();
|
|
3161
|
+
}, []);
|
|
3162
|
+
return /* @__PURE__ */ jsxs61(Card, { className: "flex-col min-h-73 bg-sky-200 order-1 sm:order-2 h-fit", children: [
|
|
3163
|
+
/* @__PURE__ */ jsx70("h1", { className: "text-xl", children: "Contactez le support Goralys" }),
|
|
3164
|
+
/* @__PURE__ */ jsxs61("form", { className: "relative flex flex-col h-full", method: "POST", action: requestUrl, autoComplete: "on", children: [
|
|
3165
|
+
!loggedIn && /* @__PURE__ */ jsx70(
|
|
3166
|
+
FloatingInput,
|
|
3167
|
+
{
|
|
3168
|
+
id: "full-name",
|
|
3169
|
+
label: "Nom Complet",
|
|
3170
|
+
helper: "Ce nom sera transmit \xE0 notre \xE9quipe afin de vous identifi\xE9",
|
|
3171
|
+
autocomplete: "name",
|
|
3172
|
+
required: !loggedIn
|
|
3173
|
+
}
|
|
3174
|
+
),
|
|
3175
|
+
/* @__PURE__ */ jsx70(
|
|
3176
|
+
FloatingInput,
|
|
3177
|
+
{
|
|
3178
|
+
id: "user-email",
|
|
3179
|
+
label: "Votre email",
|
|
3180
|
+
helper: "Votre email sera simplement utilis\xE9 pour vous r\xE9pondre.",
|
|
3181
|
+
defaultValue: cookiesGet6(EMAIL_KEY) ?? "",
|
|
3182
|
+
autocomplete: "email",
|
|
3183
|
+
required: true,
|
|
3184
|
+
email: true
|
|
3185
|
+
}
|
|
3186
|
+
),
|
|
3187
|
+
/* @__PURE__ */ jsxs61("div", { className: "relative pb-0 mb-3", children: [
|
|
3188
|
+
/* @__PURE__ */ jsx70(
|
|
3189
|
+
"select",
|
|
3190
|
+
{
|
|
3191
|
+
className: "w-full border-0 border-b-2 border-sky-300 appearance-none\n cursor-pointer outline-none focus:ring-0 text-base leading-5\n text-heading pb-0 pr-5 subjects-search-select",
|
|
3192
|
+
value: reason,
|
|
3193
|
+
name: "raw-reason",
|
|
3194
|
+
onChange: (e) => setReason(e.target.value),
|
|
3195
|
+
children: Object.entries(reasonsConfig).map(([key, { label }]) => /* @__PURE__ */ jsx70("option", { value: key, children: label }, key))
|
|
3196
|
+
}
|
|
3197
|
+
),
|
|
3198
|
+
/* @__PURE__ */ jsx70(
|
|
3199
|
+
"span",
|
|
3200
|
+
{
|
|
3201
|
+
className: "absolute bottom-0 left-0 w-0 h-0.5 bg-sky-500\n transition-all duration-300 ease-in-out\n peer-focus:w-full subjects-search-underline"
|
|
3202
|
+
}
|
|
3203
|
+
)
|
|
3204
|
+
] }),
|
|
3205
|
+
/* @__PURE__ */ jsx70(TextArea, { maxLength: 250, id: "message", label: "Message", helper: "Veuillez d\xE9crire pr\xE9cis\xE9ment votre probl\xE8me.", required: true }),
|
|
3206
|
+
/* @__PURE__ */ jsx70("input", { type: "hidden", name: "csrf-token", value: (csrfToken ? csrfToken : "no-token").trim() }),
|
|
3207
|
+
/* @__PURE__ */ jsx70("input", { type: "hidden", name: "reason", value: reason }),
|
|
3208
|
+
/* @__PURE__ */ jsx70(Button, { type: "submit", text: "Envoyer mon message", className: "bottom-0 mt-2.5", disabled: !csrfToken })
|
|
3209
|
+
] })
|
|
3210
|
+
] });
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
// src/ui/support/support-ticket-card.tsx
|
|
3214
|
+
import { buildApiUrl as buildApiUrl3, reasonsConfig as reasonsConfig2 } from "@goralys/core";
|
|
3215
|
+
import { jsx as jsx71, jsxs as jsxs62 } from "react/jsx-runtime";
|
|
3216
|
+
function SupportTicketCard({ ticket }) {
|
|
3217
|
+
return /* @__PURE__ */ jsxs62("div", { className: "h-fit w-200 flex flex-col bg-sky-200 gap-1 gap-y-3 p-1 mt-1", children: [
|
|
3218
|
+
/* @__PURE__ */ jsxs62("div", { className: "flex flex-row w-full justify-between", children: [
|
|
3219
|
+
/* @__PURE__ */ jsxs62("span", { children: [
|
|
3220
|
+
/* @__PURE__ */ jsxs62("strong", { children: [
|
|
3221
|
+
"#",
|
|
3222
|
+
ticket.id
|
|
3223
|
+
] }),
|
|
3224
|
+
" [",
|
|
3225
|
+
reasonsConfig2[ticket.reason].label,
|
|
3226
|
+
"]"
|
|
3227
|
+
] }),
|
|
3228
|
+
/* @__PURE__ */ jsx71(
|
|
3229
|
+
"a",
|
|
3230
|
+
{
|
|
3231
|
+
href: buildApiUrl3("ticket/contact", { t: ticket.id.toString() }, true),
|
|
3232
|
+
target: "_blank",
|
|
3233
|
+
title: "Contactez l'utilisateur",
|
|
3234
|
+
className: "cursor-pointer",
|
|
3235
|
+
children: /* @__PURE__ */ jsx71("strong", { children: ticket.opener })
|
|
3236
|
+
}
|
|
3237
|
+
)
|
|
3238
|
+
] }),
|
|
3239
|
+
/* @__PURE__ */ jsx71(
|
|
3240
|
+
TextArea,
|
|
3241
|
+
{
|
|
3242
|
+
id: "message",
|
|
3243
|
+
label: "Message",
|
|
3244
|
+
defaultValue: ticket.message,
|
|
3245
|
+
disabled: true
|
|
3246
|
+
},
|
|
3247
|
+
"support-message-" + ticket.id + ticket.opener
|
|
3248
|
+
),
|
|
3249
|
+
/* @__PURE__ */ jsx71(
|
|
3250
|
+
Button,
|
|
3251
|
+
{
|
|
3252
|
+
text: "Ce probl\xE8me a \xE9t\xE9 r\xE9solu",
|
|
3253
|
+
type: "button",
|
|
3254
|
+
onClick: () => {
|
|
3255
|
+
window.location.href = "/support/ticket?t=" + ticket.id;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
)
|
|
3259
|
+
] });
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
// src/ui/toast/flash-toast-listener.tsx
|
|
3263
|
+
import { useEffect as useEffect16, useLayoutEffect, useRef as useRef8 } from "react";
|
|
3264
|
+
import { buildApiUrl as buildApiUrl4, FLASH_TOAST_KEY as FLASH_TOAST_KEY2, goralysFetchClient as goralysFetchClient6, storageGet, storageRemove } from "@goralys/core";
|
|
3265
|
+
function FlashToastListener() {
|
|
3266
|
+
const { showToast } = useToast();
|
|
3267
|
+
const showToastRef = useRef8(showToast);
|
|
3268
|
+
useEffect16(() => {
|
|
3269
|
+
showToastRef.current = showToast;
|
|
3270
|
+
}, [showToast]);
|
|
3271
|
+
const pathname = useGoralysUI().usePathname();
|
|
3272
|
+
const showCachedToast = (destroyCache = true) => {
|
|
3273
|
+
const raw = storageGet(FLASH_TOAST_KEY2);
|
|
3274
|
+
if (!raw) return;
|
|
3275
|
+
if (destroyCache) storageRemove(FLASH_TOAST_KEY2);
|
|
3276
|
+
try {
|
|
3277
|
+
const parsed = JSON.parse(raw);
|
|
3278
|
+
if (!parsed.expires) return;
|
|
3279
|
+
const remaining = parsed.expires - Date.now();
|
|
3280
|
+
if (remaining <= 0) return;
|
|
3281
|
+
showToastRef.current(parsed, false, remaining);
|
|
3282
|
+
} catch {
|
|
3283
|
+
}
|
|
3284
|
+
};
|
|
3285
|
+
useLayoutEffect(() => showCachedToast(false), [pathname]);
|
|
3286
|
+
useEffect16(() => {
|
|
3287
|
+
let cancelled = false;
|
|
3288
|
+
const run = async () => {
|
|
3289
|
+
try {
|
|
3290
|
+
const res = await goralysFetchClient6("GET", buildApiUrl4("toast/flash", {}), void 0, {
|
|
3291
|
+
cache: "no-store"
|
|
3292
|
+
});
|
|
3293
|
+
const data = await res.json();
|
|
3294
|
+
if (data?.toast) {
|
|
3295
|
+
storageRemove(FLASH_TOAST_KEY2);
|
|
3296
|
+
showToastRef.current({
|
|
3297
|
+
type: data.toast.toastType,
|
|
3298
|
+
title: data.toast.toastTitle,
|
|
3299
|
+
message: data.toast.toastMessage
|
|
3300
|
+
});
|
|
3301
|
+
}
|
|
3302
|
+
if (cancelled) return;
|
|
3303
|
+
showCachedToast();
|
|
3304
|
+
} catch {
|
|
3305
|
+
if (!cancelled) showCachedToast();
|
|
3306
|
+
}
|
|
3307
|
+
};
|
|
3308
|
+
void run();
|
|
3309
|
+
return () => {
|
|
3310
|
+
cancelled = true;
|
|
3311
|
+
};
|
|
3312
|
+
}, [pathname]);
|
|
3313
|
+
return null;
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
// src/ui/user/forms/login-form.tsx
|
|
3317
|
+
import { useEffect as useEffect17, useRef as useRef9, useState as useState32 } from "react";
|
|
3318
|
+
import { fetchCsrfClient as fetchCsrfClient7, getGoralysClientConfig as getGoralysClientConfig3 } from "@goralys/core";
|
|
3319
|
+
import { jsx as jsx72, jsxs as jsxs63 } from "react/jsx-runtime";
|
|
3320
|
+
function LoginForm() {
|
|
3321
|
+
const { apiDomain, getSchoolToken } = getGoralysClientConfig3();
|
|
3322
|
+
const isDesktop = useMediaQuery("(min-width: 640px)");
|
|
3323
|
+
const [csrfToken, setCsrfToken] = useState32(null);
|
|
3324
|
+
const [submitting, setSubmitting] = useState32(false);
|
|
3325
|
+
const submittingRef = useRef9(false);
|
|
3326
|
+
const requestUrl = `${apiDomain}/user/login?high-school-token=${getSchoolToken}`;
|
|
3327
|
+
useEffect17(() => {
|
|
3328
|
+
const canceled = false;
|
|
3329
|
+
fetchCsrfClient7("login").then((token) => {
|
|
3330
|
+
if (!canceled) setCsrfToken(token);
|
|
3331
|
+
}).catch(() => {
|
|
3332
|
+
if (!canceled) setCsrfToken(null);
|
|
3333
|
+
});
|
|
3334
|
+
}, []);
|
|
3335
|
+
function handleSubmit(e) {
|
|
3336
|
+
if (submittingRef.current) {
|
|
3337
|
+
e.preventDefault();
|
|
3338
|
+
return;
|
|
3339
|
+
}
|
|
3340
|
+
submittingRef.current = true;
|
|
3341
|
+
setSubmitting(true);
|
|
3342
|
+
}
|
|
3343
|
+
return /* @__PURE__ */ jsxs63(Card, { className: "relative flex-col h-65 bg-sky-200 order-1 sm:order-2", children: [
|
|
3344
|
+
/* @__PURE__ */ jsx72("h1", { className: "text-xl", children: "Connectez vous \xE0 votre compte Goralys" }),
|
|
3345
|
+
/* @__PURE__ */ jsxs63("form", { className: "relative flex flex-col h-full", action: requestUrl, method: "POST", autoComplete: "on", onSubmit: handleSubmit, children: [
|
|
3346
|
+
/* @__PURE__ */ jsx72(FloatingInput, { id: "username", label: "Identifiant", helper: "Identifiant au format p.nomX", autocomplete: "username", required: true }),
|
|
3347
|
+
/* @__PURE__ */ jsx72(FloatingInput, { id: "password", label: "Mot de passe", autocomplete: "current-password", password: true, required: true }),
|
|
3348
|
+
/* @__PURE__ */ jsx72("input", { type: "hidden", name: "csrf-token", value: (csrfToken ? csrfToken : "no-token").trim() }),
|
|
3349
|
+
/* @__PURE__ */ jsx72("input", { type: "hidden", name: "client_context", value: isDesktop ? "desktop" : "mobile" }),
|
|
3350
|
+
/* @__PURE__ */ jsx72(
|
|
3351
|
+
Button,
|
|
3352
|
+
{
|
|
3353
|
+
type: "submit",
|
|
3354
|
+
text: csrfToken === null ? "Chargement..." : submitting ? "Connexion..." : "Se connecter",
|
|
3355
|
+
className: "absolute! bottom-0",
|
|
3356
|
+
disabled: csrfToken === null || submitting
|
|
3357
|
+
}
|
|
3358
|
+
)
|
|
3359
|
+
] })
|
|
3360
|
+
] });
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
// src/ui/user/forms/register-form.tsx
|
|
3364
|
+
import { useEffect as useEffect18, useRef as useRef10, useState as useState33 } from "react";
|
|
3365
|
+
import { fetchCsrfClient as fetchCsrfClient8, getGoralysClientConfig as getGoralysClientConfig4 } from "@goralys/core";
|
|
3366
|
+
import { jsx as jsx73, jsxs as jsxs64 } from "react/jsx-runtime";
|
|
3367
|
+
function RegisterForm() {
|
|
3368
|
+
const { apiDomain, getSchoolToken } = getGoralysClientConfig4();
|
|
3369
|
+
const searchParams = useGoralysUI().useSearchParams();
|
|
3370
|
+
const router = useGoralysUI().useRouter();
|
|
3371
|
+
const [id, setId] = useState33(void 0);
|
|
3372
|
+
const hasRun = useRef10(false);
|
|
3373
|
+
useEffect18(() => {
|
|
3374
|
+
if (hasRun.current) return;
|
|
3375
|
+
hasRun.current = true;
|
|
3376
|
+
const update = () => setId(searchParams.get("id") ?? void 0);
|
|
3377
|
+
if (!searchParams.get("id")) {
|
|
3378
|
+
router.replace("/user/register");
|
|
3379
|
+
return;
|
|
3380
|
+
}
|
|
3381
|
+
update();
|
|
3382
|
+
router.replace("/user/register");
|
|
3383
|
+
}, [searchParams, router]);
|
|
3384
|
+
const [csrfToken, setCsrfToken] = useState33(null);
|
|
3385
|
+
const requestUrl = `${apiDomain}/user/register?high-school-token=${getSchoolToken()}`;
|
|
3386
|
+
useEffect18(() => {
|
|
3387
|
+
const run = async () => setCsrfToken(await fetchCsrfClient8("register"));
|
|
3388
|
+
run().then();
|
|
3389
|
+
}, []);
|
|
3390
|
+
return /* @__PURE__ */ jsxs64(Card, { className: "flex-col h-79 bg-sky-200 order-1 sm:order-2", children: [
|
|
3391
|
+
/* @__PURE__ */ jsx73("h1", { className: "text-xl", children: "Cr\xE9ez votre compte chez Goralys" }),
|
|
3392
|
+
/* @__PURE__ */ jsxs64("form", { className: "relative flex flex-col h-full", method: "POST", action: requestUrl, autoComplete: "on", children: [
|
|
3393
|
+
/* @__PURE__ */ jsx73(
|
|
3394
|
+
FloatingInput,
|
|
3395
|
+
{
|
|
3396
|
+
id: "user-name",
|
|
3397
|
+
label: "Identifiant",
|
|
3398
|
+
helper: "Identifiant au format p.nomX.",
|
|
3399
|
+
defaultValue: id,
|
|
3400
|
+
autocomplete: "username",
|
|
3401
|
+
required: true
|
|
3402
|
+
}
|
|
3403
|
+
),
|
|
3404
|
+
/* @__PURE__ */ jsx73(
|
|
3405
|
+
FloatingInput,
|
|
3406
|
+
{
|
|
3407
|
+
id: "password",
|
|
3408
|
+
label: "Mot de passe",
|
|
3409
|
+
helper: "Choisissez un mot de passe s\xE9curis\xE9.",
|
|
3410
|
+
autocomplete: "new-password",
|
|
3411
|
+
password: true,
|
|
3412
|
+
required: true
|
|
3413
|
+
}
|
|
3414
|
+
),
|
|
3415
|
+
/* @__PURE__ */ jsx73(FloatingInput, { id: "email", label: "Addresse Mail", autocomplete: "email", helper: "Ce champ est optionnel.", email: true }),
|
|
3416
|
+
/* @__PURE__ */ jsx73("input", { type: "hidden", name: "csrf-token", value: (csrfToken ? csrfToken : "no-token").trim() }),
|
|
3417
|
+
/* @__PURE__ */ jsx73(
|
|
3418
|
+
Button,
|
|
3419
|
+
{
|
|
3420
|
+
type: "submit",
|
|
3421
|
+
text: csrfToken === null ? "Chargement..." : "Cr\xE9er mon compte",
|
|
3422
|
+
className: "absolute! bottom-0",
|
|
3423
|
+
disabled: csrfToken === null
|
|
3424
|
+
}
|
|
3425
|
+
)
|
|
3426
|
+
] })
|
|
3427
|
+
] });
|
|
3428
|
+
}
|
|
3429
|
+
export {
|
|
3430
|
+
AdminCard,
|
|
3431
|
+
AdminPanelCardSkeleton,
|
|
3432
|
+
AdminSubjectCardSkeleton,
|
|
3433
|
+
AdminSubjectsCard,
|
|
3434
|
+
AdminsSearchBar,
|
|
3435
|
+
AuditActionCard,
|
|
3436
|
+
AuditActionCardSkeleton,
|
|
3437
|
+
AuthTokenCard,
|
|
3438
|
+
Button,
|
|
3439
|
+
ButtonSkeleton,
|
|
3440
|
+
Card,
|
|
3441
|
+
CardTitle,
|
|
3442
|
+
Checkbox,
|
|
3443
|
+
CommentSkeleton,
|
|
3444
|
+
CommentStudent,
|
|
3445
|
+
CommentTeacher,
|
|
3446
|
+
ConfirmElement,
|
|
3447
|
+
ConfirmProvider,
|
|
3448
|
+
CookieBanner,
|
|
3449
|
+
CreateAdminElement,
|
|
3450
|
+
DraftModalElement,
|
|
3451
|
+
DraftModalProvider,
|
|
3452
|
+
Dropdown,
|
|
3453
|
+
DynamicNav,
|
|
3454
|
+
EmailModalElement,
|
|
3455
|
+
EmailModalProvider,
|
|
3456
|
+
FlashToastListener,
|
|
3457
|
+
FloatingInput,
|
|
3458
|
+
GoralysLogoLoader,
|
|
3459
|
+
GoralysUIProvider,
|
|
3460
|
+
ImportTopicsModalElement,
|
|
3461
|
+
ImportTopicsModalProvider,
|
|
3462
|
+
InputTextFile,
|
|
3463
|
+
InputZipFile,
|
|
3464
|
+
LoginForm,
|
|
3465
|
+
NavLink,
|
|
3466
|
+
PasswordModalElement,
|
|
3467
|
+
PasswordModalProvider,
|
|
3468
|
+
Providers,
|
|
3469
|
+
ReadonlyCard,
|
|
3470
|
+
RegisterForm,
|
|
3471
|
+
ReplaceTeacherElement,
|
|
3472
|
+
SideNav,
|
|
3473
|
+
StatusLegend,
|
|
3474
|
+
StudentCard,
|
|
3475
|
+
StudentCardSkeleton,
|
|
3476
|
+
SubjectInputAdmin,
|
|
3477
|
+
SubjectInputSkeleton,
|
|
3478
|
+
SubjectInputStudent,
|
|
3479
|
+
SubjectInputTeacher,
|
|
3480
|
+
SubjectStudentsDonut,
|
|
3481
|
+
SubjectTeachersDonut,
|
|
3482
|
+
SubjectTextArea,
|
|
3483
|
+
SubjectsFilter,
|
|
3484
|
+
SubjectsSearchBar,
|
|
3485
|
+
SupportForm,
|
|
3486
|
+
SupportTicketCard,
|
|
3487
|
+
SupportTicketCardSkeleton,
|
|
3488
|
+
TapBar,
|
|
3489
|
+
TapBarLink,
|
|
3490
|
+
TapBarUser,
|
|
3491
|
+
TeacherCard,
|
|
3492
|
+
TeacherCardSkeleton,
|
|
3493
|
+
TextArea,
|
|
3494
|
+
ToastElement,
|
|
3495
|
+
ToastProvider,
|
|
3496
|
+
UserCard,
|
|
3497
|
+
UserCardSkeleton,
|
|
3498
|
+
UserNav,
|
|
3499
|
+
UserSubjectsStudentCard,
|
|
3500
|
+
UserSubjectsStudentCardSkeleton,
|
|
3501
|
+
UserSubjectsTeacherCard,
|
|
3502
|
+
UserSubjectsTeacherCardSkeleton,
|
|
3503
|
+
UsersSearchBar,
|
|
3504
|
+
useAutoResize,
|
|
3505
|
+
useConfirm,
|
|
3506
|
+
useDraftModal,
|
|
3507
|
+
useEmailModal,
|
|
3508
|
+
useGoralysUI,
|
|
3509
|
+
useImportTopicsModal,
|
|
3510
|
+
useMediaQuery,
|
|
3511
|
+
useModalClose,
|
|
3512
|
+
usePasswordModal,
|
|
3513
|
+
useToast
|
|
3514
|
+
};
|