@ramses1er/cbk-toolkit 2.1.1 → 2.3.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.
Files changed (155) hide show
  1. package/dist/packages/lib/src/auth-guard.d.ts +10 -0
  2. package/dist/packages/lib/src/auth-guard.d.ts.map +1 -0
  3. package/dist/packages/lib/src/auth-guard.js +60 -0
  4. package/dist/packages/lib/src/auth-guard.js.map +1 -0
  5. package/dist/packages/lib/src/handle-server-error.d.ts +9 -0
  6. package/dist/packages/lib/src/handle-server-error.d.ts.map +1 -0
  7. package/dist/packages/lib/src/handle-server-error.js +43 -0
  8. package/dist/packages/lib/src/handle-server-error.js.map +1 -0
  9. package/dist/packages/lib/src/http-error.d.ts +6 -0
  10. package/dist/packages/lib/src/http-error.d.ts.map +1 -0
  11. package/dist/packages/lib/src/http-error.js +12 -0
  12. package/dist/packages/lib/src/http-error.js.map +1 -0
  13. package/dist/packages/lib/src/index.d.ts +5 -0
  14. package/dist/packages/lib/src/index.d.ts.map +1 -0
  15. package/dist/packages/lib/src/index.js +9 -0
  16. package/dist/packages/lib/src/index.js.map +1 -0
  17. package/dist/packages/lib/src/with-auth.d.ts +8 -0
  18. package/dist/packages/lib/src/with-auth.d.ts.map +1 -0
  19. package/dist/packages/lib/src/with-auth.js +32 -0
  20. package/dist/packages/lib/src/with-auth.js.map +1 -0
  21. package/dist/packages/ui/src/config/create-side-nav.d.ts +6 -0
  22. package/dist/packages/ui/src/config/create-side-nav.d.ts.map +1 -0
  23. package/dist/packages/ui/src/config/create-side-nav.js +9 -0
  24. package/dist/packages/ui/src/config/create-side-nav.js.map +1 -0
  25. package/dist/packages/ui/src/feedback/confirmation.d.ts +16 -0
  26. package/dist/packages/ui/src/feedback/confirmation.d.ts.map +1 -0
  27. package/dist/packages/ui/src/feedback/confirmation.js +82 -0
  28. package/dist/packages/ui/src/feedback/confirmation.js.map +1 -0
  29. package/dist/packages/ui/src/feedback/error-display.d.ts +7 -0
  30. package/dist/packages/ui/src/feedback/error-display.d.ts.map +1 -0
  31. package/dist/packages/ui/src/feedback/error-display.js +11 -0
  32. package/dist/packages/ui/src/feedback/error-display.js.map +1 -0
  33. package/dist/packages/ui/src/forms/date-picker.d.ts +4 -0
  34. package/dist/packages/ui/src/forms/date-picker.d.ts.map +1 -0
  35. package/dist/packages/ui/src/forms/date-picker.js +9 -0
  36. package/dist/packages/ui/src/forms/date-picker.js.map +1 -0
  37. package/dist/packages/ui/src/forms/field-container.d.ts +8 -0
  38. package/dist/packages/ui/src/forms/field-container.d.ts.map +1 -0
  39. package/dist/packages/ui/src/forms/field-container.js +6 -0
  40. package/dist/packages/ui/src/forms/field-container.js.map +1 -0
  41. package/dist/packages/ui/src/forms/form.d.ts +9 -0
  42. package/dist/packages/ui/src/forms/form.d.ts.map +1 -0
  43. package/dist/packages/ui/src/forms/form.js +63 -0
  44. package/dist/packages/ui/src/forms/form.js.map +1 -0
  45. package/dist/packages/ui/src/forms/input.d.ts +4 -0
  46. package/dist/packages/ui/src/forms/input.d.ts.map +1 -0
  47. package/dist/packages/ui/src/forms/input.js +13 -0
  48. package/dist/packages/ui/src/forms/input.js.map +1 -0
  49. package/dist/packages/ui/src/forms/label.d.ts +7 -0
  50. package/dist/packages/ui/src/forms/label.d.ts.map +1 -0
  51. package/dist/packages/ui/src/forms/label.js +9 -0
  52. package/dist/packages/ui/src/forms/label.js.map +1 -0
  53. package/dist/packages/ui/src/forms/select.d.ts +6 -0
  54. package/dist/packages/ui/src/forms/select.d.ts.map +1 -0
  55. package/dist/packages/ui/src/forms/select.js +19 -0
  56. package/dist/packages/ui/src/forms/select.js.map +1 -0
  57. package/dist/packages/ui/src/forms/textarea.d.ts +4 -0
  58. package/dist/packages/ui/src/forms/textarea.d.ts.map +1 -0
  59. package/dist/packages/ui/src/forms/textarea.js +9 -0
  60. package/dist/packages/ui/src/forms/textarea.js.map +1 -0
  61. package/dist/packages/ui/src/headers/header-content.d.ts +11 -0
  62. package/dist/packages/ui/src/headers/header-content.d.ts.map +1 -0
  63. package/dist/packages/ui/src/headers/header-content.js +6 -0
  64. package/dist/packages/ui/src/headers/header-content.js.map +1 -0
  65. package/dist/packages/ui/src/illustrations/topic-illustration.d.ts +1 -0
  66. package/dist/packages/ui/src/illustrations/topic-illustration.d.ts.map +1 -0
  67. package/dist/packages/ui/src/illustrations/topic-illustration.js +69 -0
  68. package/dist/packages/ui/src/illustrations/topic-illustration.js.map +1 -0
  69. package/dist/packages/ui/src/index.d.ts +28 -0
  70. package/dist/packages/ui/src/index.d.ts.map +1 -0
  71. package/dist/packages/ui/src/index.js +38 -0
  72. package/dist/packages/ui/src/index.js.map +1 -0
  73. package/dist/packages/ui/src/layouts/side-nav.d.ts +45 -0
  74. package/dist/packages/ui/src/layouts/side-nav.d.ts.map +1 -0
  75. package/dist/packages/ui/src/layouts/side-nav.js +127 -0
  76. package/dist/packages/ui/src/layouts/side-nav.js.map +1 -0
  77. package/dist/packages/ui/src/loaders/loader-submit.d.ts +8 -0
  78. package/dist/packages/ui/src/loaders/loader-submit.d.ts.map +1 -0
  79. package/dist/packages/ui/src/loaders/loader-submit.js +7 -0
  80. package/dist/packages/ui/src/loaders/loader-submit.js.map +1 -0
  81. package/dist/packages/ui/src/loaders/loading.d.ts +6 -0
  82. package/dist/packages/ui/src/loaders/loading.d.ts.map +1 -0
  83. package/dist/packages/ui/src/loaders/loading.js +7 -0
  84. package/dist/packages/ui/src/loaders/loading.js.map +1 -0
  85. package/dist/packages/ui/src/modals/modal.d.ts +10 -0
  86. package/dist/packages/ui/src/modals/modal.d.ts.map +1 -0
  87. package/dist/packages/ui/src/modals/modal.js +36 -0
  88. package/dist/packages/ui/src/modals/modal.js.map +1 -0
  89. package/dist/packages/ui/src/pages/access-denied.d.ts +8 -0
  90. package/dist/packages/ui/src/pages/access-denied.d.ts.map +1 -0
  91. package/dist/packages/ui/src/pages/access-denied.js +8 -0
  92. package/dist/packages/ui/src/pages/access-denied.js.map +1 -0
  93. package/dist/packages/ui/src/pages/session-expired.d.ts +6 -0
  94. package/dist/packages/ui/src/pages/session-expired.d.ts.map +1 -0
  95. package/dist/packages/ui/src/pages/session-expired.js +21 -0
  96. package/dist/packages/ui/src/pages/session-expired.js.map +1 -0
  97. package/dist/packages/ui/src/pwa/InstallPrompt.d.ts +7 -0
  98. package/dist/packages/ui/src/pwa/InstallPrompt.d.ts.map +1 -0
  99. package/dist/packages/ui/src/pwa/InstallPrompt.js +71 -0
  100. package/dist/packages/ui/src/pwa/InstallPrompt.js.map +1 -0
  101. package/dist/packages/ui/src/pwa/SwRegistration.d.ts +5 -0
  102. package/dist/packages/ui/src/pwa/SwRegistration.d.ts.map +1 -0
  103. package/dist/packages/ui/src/pwa/SwRegistration.js +13 -0
  104. package/dist/packages/ui/src/pwa/SwRegistration.js.map +1 -0
  105. package/dist/packages/ui/src/route-guard/route-guard.d.ts +31 -0
  106. package/dist/packages/ui/src/route-guard/route-guard.d.ts.map +1 -0
  107. package/dist/packages/ui/src/route-guard/route-guard.js +75 -0
  108. package/dist/packages/ui/src/route-guard/route-guard.js.map +1 -0
  109. package/dist/packages/ui/src/search/input-search.d.ts +1 -0
  110. package/dist/packages/ui/src/search/input-search.d.ts.map +1 -0
  111. package/dist/packages/ui/src/search/input-search.js +47 -0
  112. package/dist/packages/ui/src/search/input-search.js.map +1 -0
  113. package/dist/packages/ui/src/search/select-search.d.ts +1 -0
  114. package/dist/packages/ui/src/search/select-search.d.ts.map +1 -0
  115. package/dist/packages/ui/src/search/select-search.js +100 -0
  116. package/dist/packages/ui/src/search/select-search.js.map +1 -0
  117. package/dist/packages/ui/src/table/actions-menu.d.ts +14 -0
  118. package/dist/packages/ui/src/table/actions-menu.d.ts.map +1 -0
  119. package/dist/packages/ui/src/table/actions-menu.js +53 -0
  120. package/dist/packages/ui/src/table/actions-menu.js.map +1 -0
  121. package/dist/packages/ui/src/table/table.d.ts +24 -0
  122. package/dist/packages/ui/src/table/table.d.ts.map +1 -0
  123. package/dist/packages/ui/src/table/table.js +99 -0
  124. package/dist/packages/ui/src/table/table.js.map +1 -0
  125. package/dist/packages/utils/src/fetcher.d.ts +16 -0
  126. package/dist/packages/utils/src/fetcher.d.ts.map +1 -0
  127. package/dist/packages/utils/src/fetcher.js +38 -0
  128. package/dist/packages/utils/src/fetcher.js.map +1 -0
  129. package/dist/packages/utils/src/formatCurrency.d.ts +1 -0
  130. package/dist/packages/utils/src/formatCurrency.d.ts.map +1 -0
  131. package/dist/packages/utils/src/formatCurrency.js +2 -0
  132. package/dist/packages/utils/src/formatCurrency.js.map +1 -0
  133. package/dist/packages/utils/src/formatDate.d.ts +1 -0
  134. package/dist/packages/utils/src/formatDate.d.ts.map +1 -0
  135. package/dist/packages/utils/src/formatDate.js +2 -0
  136. package/dist/packages/utils/src/formatDate.js.map +1 -0
  137. package/dist/packages/utils/src/index.d.ts +3 -0
  138. package/dist/packages/utils/src/index.d.ts.map +1 -0
  139. package/dist/packages/utils/src/index.js +2 -0
  140. package/dist/packages/utils/src/index.js.map +1 -0
  141. package/dist/src/client.d.ts +3 -0
  142. package/dist/src/client.d.ts.map +1 -0
  143. package/dist/src/client.js +5 -0
  144. package/dist/src/client.js.map +1 -0
  145. package/dist/src/server.d.ts +2 -0
  146. package/dist/src/server.d.ts.map +1 -0
  147. package/dist/src/server.js +3 -0
  148. package/dist/src/server.js.map +1 -0
  149. package/package.json +8 -8
  150. package/dist/client.d.ts +0 -210
  151. package/dist/client.js +0 -1407
  152. package/dist/client.js.map +0 -1
  153. package/dist/server.d.ts +0 -33
  154. package/dist/server.js +0 -161
  155. package/dist/server.js.map +0 -1
package/dist/client.js DELETED
@@ -1,1407 +0,0 @@
1
- // packages/ui/src/layouts/side-nav.tsx
2
- import {
3
- useState as useState2,
4
- useEffect as useEffect3,
5
- createElement,
6
- isValidElement
7
- } from "react";
8
-
9
- // packages/ui/src/modals/modal.tsx
10
- import { X } from "lucide-react";
11
- import { useEffect } from "react";
12
- import { createPortal } from "react-dom";
13
- import { jsx, jsxs } from "react/jsx-runtime";
14
- function Modal({ title, isOpen, onClose, children }) {
15
- const requestClose = () => {
16
- onClose();
17
- };
18
- useEffect(() => {
19
- if (isOpen) {
20
- document.body.style.overflow = "hidden";
21
- }
22
- return () => {
23
- document.body.style.overflow = "";
24
- };
25
- }, [isOpen]);
26
- useEffect(() => {
27
- function handleEsc(e) {
28
- if (e.key === "Escape") {
29
- onClose();
30
- }
31
- }
32
- if (isOpen) {
33
- document.addEventListener("keydown", handleEsc);
34
- }
35
- return () => {
36
- document.removeEventListener("keydown", handleEsc);
37
- };
38
- }, [isOpen, onClose]);
39
- if (!isOpen) return null;
40
- const content = typeof children === "function" ? children(requestClose) : children;
41
- return createPortal(
42
- /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
43
- /* @__PURE__ */ jsx(
44
- "div",
45
- {
46
- className: "absolute inset-0 bg-black/20 backdrop-blur-sm",
47
- onClick: onClose
48
- }
49
- ),
50
- /* @__PURE__ */ jsxs("div", { className: "relative bg-white rounded-lg shadow-lg w-full max-w-2xl mx-4 p-6 flex flex-col max-h-[85vh]", children: [
51
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4 border-b-2 border-gray-400 pb-3 sticky top-0 bg-white z-10", children: [
52
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-gray-900", children: title }),
53
- /* @__PURE__ */ jsx(
54
- "button",
55
- {
56
- onClick: onClose,
57
- className: "text-gray-500 hover:text-gray-700 text-xl cursor-pointer",
58
- children: /* @__PURE__ */ jsx(X, {})
59
- }
60
- )
61
- ] }),
62
- /* @__PURE__ */ jsx("div", { className: "overflow-y-auto flex-1 min-h-0", children: content })
63
- ] })
64
- ] }),
65
- document.body
66
- );
67
- }
68
-
69
- // packages/ui/src/feedback/confirmation.tsx
70
- import { useCallback, useEffect as useEffect2, useRef, useState } from "react";
71
- import { CircleQuestionMark } from "lucide-react";
72
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
73
- var ERROR_COUNTDOWN = 10;
74
- function Confirmation({
75
- question,
76
- messageYes,
77
- messageNo,
78
- requestClose,
79
- onAction,
80
- isProcessing,
81
- error,
82
- resetRequest
83
- }) {
84
- const [countdown, setCountdown] = useState(0);
85
- const countdownRef = useRef(null);
86
- const resetRequestRef = useRef(resetRequest);
87
- useEffect2(() => {
88
- resetRequestRef.current = resetRequest;
89
- }, [resetRequest]);
90
- const clearCountdown = useCallback(() => {
91
- if (countdownRef.current !== null) {
92
- window.clearInterval(countdownRef.current);
93
- countdownRef.current = null;
94
- }
95
- }, []);
96
- const resetConfirmationState = useCallback(() => {
97
- clearCountdown();
98
- setCountdown(0);
99
- }, [clearCountdown]);
100
- const handleResetRequest = useCallback(() => {
101
- resetConfirmationState();
102
- resetRequest();
103
- }, [resetConfirmationState, resetRequest]);
104
- const handleRequestClose = useCallback(() => {
105
- resetConfirmationState();
106
- resetRequest();
107
- requestClose();
108
- }, [requestClose, resetConfirmationState, resetRequest]);
109
- const handleAction = useCallback(async () => {
110
- try {
111
- await onAction();
112
- handleRequestClose();
113
- } catch {
114
- }
115
- }, [handleRequestClose, onAction]);
116
- useEffect2(() => {
117
- clearCountdown();
118
- if (!error) {
119
- const resetTimeoutId = window.setTimeout(() => {
120
- setCountdown(0);
121
- }, 0);
122
- return () => {
123
- window.clearTimeout(resetTimeoutId);
124
- };
125
- }
126
- const startCountdownTimeoutId = window.setTimeout(() => {
127
- setCountdown(ERROR_COUNTDOWN);
128
- countdownRef.current = window.setInterval(() => {
129
- setCountdown((previousCountdown) => {
130
- if (previousCountdown <= 1) {
131
- clearCountdown();
132
- window.setTimeout(() => {
133
- resetRequestRef.current();
134
- }, 0);
135
- return 0;
136
- }
137
- return previousCountdown - 1;
138
- });
139
- }, 1e3);
140
- }, 0);
141
- return () => {
142
- window.clearTimeout(startCountdownTimeoutId);
143
- clearCountdown();
144
- };
145
- }, [clearCountdown, error]);
146
- useEffect2(() => {
147
- return () => {
148
- clearCountdown();
149
- resetRequestRef.current();
150
- };
151
- }, [clearCountdown]);
152
- const isErrorVisible = Boolean(error) && countdown > 0;
153
- if (!question) return null;
154
- return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-3 z-40", children: [
155
- /* @__PURE__ */ jsx2(CircleQuestionMark, { className: "text-red-500 text-5xl" }),
156
- /* @__PURE__ */ jsx2("h2", { className: "text-xl font-bold text-red-500", children: "Confirmation requise" }),
157
- /* @__PURE__ */ jsx2("p", { className: "mt-4 text-md leading-relaxed text-gray-700", children: question }),
158
- /* @__PURE__ */ jsxs2("div", { className: "mt-6 flex w-full gap-3", children: [
159
- /* @__PURE__ */ jsx2(
160
- "button",
161
- {
162
- type: "button",
163
- onClick: handleRequestClose,
164
- disabled: isProcessing,
165
- className: "w-full rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 transition hover:cursor-pointer hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-white",
166
- children: messageNo
167
- }
168
- ),
169
- /* @__PURE__ */ jsx2(
170
- "button",
171
- {
172
- type: "button",
173
- onClick: handleAction,
174
- disabled: isProcessing,
175
- className: "w-full rounded-lg bg-red-400 px-4 py-2 text-sm font-semibold text-white transition hover:cursor-pointer hover:bg-red-500 active:scale-95 disabled:opacity-50 disabled:hover:bg-red-500",
176
- children: isProcessing ? "operation en cours" : messageYes
177
- }
178
- )
179
- ] }),
180
- error && isErrorVisible && /* @__PURE__ */ jsxs2("div", { className: "mt-4 w-full rounded-lg border border-red-200 bg-red-50 p-4 text-left text-sm text-red-700", children: [
181
- /* @__PURE__ */ jsxs2("div", { className: "flex items-start justify-between gap-3", children: [
182
- /* @__PURE__ */ jsxs2("div", { children: [
183
- /* @__PURE__ */ jsx2("p", { className: "font-semibold", children: "Erreur" }),
184
- /* @__PURE__ */ jsx2("p", { className: "mt-1 text-sm", children: error.message })
185
- ] }),
186
- /* @__PURE__ */ jsx2(
187
- "button",
188
- {
189
- type: "button",
190
- onClick: handleResetRequest,
191
- className: "rounded-full bg-red-100 px-2 py-1 text-xs font-semibold text-red-700 hover:bg-red-200",
192
- children: "Fermer"
193
- }
194
- )
195
- ] }),
196
- /* @__PURE__ */ jsxs2("p", { className: "mt-3 text-xs text-red-600", children: [
197
- "Ce message disparait dans ",
198
- countdown,
199
- "s..."
200
- ] })
201
- ] })
202
- ] });
203
- }
204
-
205
- // packages/utils/src/fetcher.ts
206
- var ApiError = class extends Error {
207
- status;
208
- code;
209
- data;
210
- constructor(params) {
211
- super(params.message);
212
- this.status = params.status;
213
- this.code = params.code;
214
- this.data = params.data;
215
- }
216
- };
217
- async function fetcher(url, options = {}) {
218
- const { headers, skipJsonParsing = false, ...rest } = options;
219
- const response = await fetch(url, {
220
- credentials: "include",
221
- headers: {
222
- "Content-Type": "application/json",
223
- ...headers
224
- },
225
- ...rest
226
- });
227
- let responseData = null;
228
- const contentType = response.headers.get("content-type");
229
- if (!skipJsonParsing && contentType?.includes("application/json")) {
230
- responseData = await response.json();
231
- }
232
- if (!response.ok) {
233
- const backendError = responseData;
234
- throw new ApiError({
235
- message: backendError?.message || "Une erreur est survenue",
236
- status: backendError?.status || response.status,
237
- code: backendError?.code,
238
- data: responseData
239
- });
240
- }
241
- return responseData;
242
- }
243
-
244
- // packages/ui/src/layouts/side-nav.tsx
245
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
246
- function isActive(href, currentPath) {
247
- return currentPath === href || currentPath.startsWith(href + "/");
248
- }
249
- function isExactActive(href, currentPath) {
250
- return currentPath === href;
251
- }
252
- function findActiveTitleSource(mainItems, footer, currentPath) {
253
- let best;
254
- const consider = (item) => {
255
- if (!isActive(item.href, currentPath)) return;
256
- if (!best || item.href.length > best.href.length) {
257
- best = item;
258
- }
259
- };
260
- const visit = (items) => {
261
- for (const item of items) {
262
- consider(item);
263
- if (item.subItems && item.subItems.length > 0) visit(item.subItems);
264
- }
265
- };
266
- visit(mainItems);
267
- footer?.forEach((item) => {
268
- if (!("confirm" in item)) consider(item);
269
- });
270
- return best;
271
- }
272
- function renderItemIcon(icon) {
273
- if (!icon) return null;
274
- if (typeof icon === "string") return /* @__PURE__ */ jsx3("span", { className: "text-lg", children: icon });
275
- if (isValidElement(icon)) return icon;
276
- return createElement(icon, { size: 20 });
277
- }
278
- function SideNav({
279
- children,
280
- title,
281
- mainItems,
282
- currentPath,
283
- brand,
284
- footer,
285
- toggleIcon
286
- }) {
287
- const activeMain = mainItems.find((item) => isActive(item.href, currentPath));
288
- const activeTitleSource = findActiveTitleSource(mainItems, footer, currentPath);
289
- const headerTitle = title ?? activeTitleSource?.title ?? activeTitleSource?.label ?? activeMain?.label;
290
- const [expandedSection, setExpandedSection] = useState2(
291
- activeMain?.id ?? null
292
- );
293
- const toggleSection = (id) => {
294
- setExpandedSection((prev) => prev === id ? null : id);
295
- };
296
- const [isMobileOpen, setIsMobileOpen] = useState2(false);
297
- useEffect3(() => {
298
- const handleEscape = (e) => {
299
- if (e.key === "Escape") setIsMobileOpen(false);
300
- };
301
- document.addEventListener("keydown", handleEscape);
302
- return () => document.removeEventListener("keydown", handleEscape);
303
- }, []);
304
- useEffect3(() => {
305
- if (isMobileOpen) {
306
- document.body.style.overflow = "hidden";
307
- } else {
308
- document.body.style.overflow = "";
309
- }
310
- return () => {
311
- document.body.style.overflow = "";
312
- };
313
- }, [isMobileOpen]);
314
- const closeMobileNav = () => setIsMobileOpen(false);
315
- const [confirmingItem, setConfirmingItem] = useState2(null);
316
- const [pending, setPending] = useState2(false);
317
- const [error, setError] = useState2(null);
318
- const openConfirm = (item) => {
319
- setError(null);
320
- setConfirmingItem(item);
321
- closeMobileNav();
322
- };
323
- const closeConfirm = () => {
324
- if (pending) return;
325
- setConfirmingItem(null);
326
- setError(null);
327
- };
328
- const handleConfirmAction = async () => {
329
- if (!confirmingItem || !("confirm" in confirmingItem)) return;
330
- setPending(true);
331
- setError(null);
332
- try {
333
- await confirmingItem.confirm.onAction();
334
- } catch (e) {
335
- setError(
336
- e instanceof ApiError ? e : new ApiError({
337
- message: e instanceof Error ? e.message : "Une erreur est survenue",
338
- status: 500
339
- })
340
- );
341
- throw e;
342
- } finally {
343
- setPending(false);
344
- }
345
- };
346
- return /* @__PURE__ */ jsxs3("div", { className: "flex min-h-screen", children: [
347
- /* @__PURE__ */ jsxs3(
348
- "aside",
349
- {
350
- className: `w-64 bg-white border-r border-gray-200 flex flex-col fixed h-full z-50 transition-transform duration-300 ease-in-out md:translate-x-0 ${isMobileOpen ? "translate-x-0" : "-translate-x-full"}`,
351
- children: [
352
- brand && /* @__PURE__ */ jsx3("div", { className: "p-[6.5px] border-b border-gray-100", children: /* @__PURE__ */ jsxs3("div", { className: "flex flex-row gap-2 items-center", children: [
353
- brand.logo && (typeof brand.logo === "string" ? /* @__PURE__ */ jsx3(
354
- "img",
355
- {
356
- src: brand.logo,
357
- alt: brand.name ?? "",
358
- className: "w-12.5 h-12.5"
359
- }
360
- ) : brand.logo),
361
- brand.name && /* @__PURE__ */ jsx3("p", { className: "text-gray-500 text-base font-semibold", children: brand.name })
362
- ] }) }),
363
- /* @__PURE__ */ jsx3("nav", { className: "flex-1 p-4 overflow-y-auto", children: /* @__PURE__ */ jsx3("ul", { className: "space-y-1", children: mainItems.map((item) => {
364
- const active = isActive(item.href, currentPath);
365
- const isExpanded = expandedSection === item.id;
366
- const hasSubItems = item.subItems && item.subItems.length > 0;
367
- return /* @__PURE__ */ jsxs3("li", { children: [
368
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", children: [
369
- /* @__PURE__ */ jsxs3(
370
- "a",
371
- {
372
- href: item.href,
373
- onClick: closeMobileNav,
374
- className: `flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors flex-1 ${active ? "bg-gray-200 text-black" : "text-gray-500 hover:bg-gray-100"}`,
375
- children: [
376
- item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsx3("span", { className: "text-lg", children: item.icon }) : isValidElement(item.icon) ? item.icon : createElement(
377
- item.icon,
378
- { size: 20 }
379
- )),
380
- item.label
381
- ]
382
- }
383
- ),
384
- active && hasSubItems && /* @__PURE__ */ jsx3(
385
- "button",
386
- {
387
- onClick: () => toggleSection(item.id),
388
- className: `p-1.5 rounded-lg transition-colors ${active ? "text-black hover:bg-gray-100" : "text-gray-500 hover:bg-gray-100"}`,
389
- title: isExpanded ? "R\xE9duire" : "D\xE9velopper",
390
- children: toggleIcon ? /* @__PURE__ */ jsx3(
391
- "span",
392
- {
393
- className: `inline-block transition-transform duration-200 ${isExpanded ? "rotate-0" : "-rotate-90"}`,
394
- children: toggleIcon
395
- }
396
- ) : /* @__PURE__ */ jsx3("span", { className: "text-xs", children: isExpanded ? "\u25BC" : "\u25B6" })
397
- }
398
- )
399
- ] }),
400
- active && hasSubItems && /* @__PURE__ */ jsx3(
401
- "div",
402
- {
403
- className: `grid transition-all duration-300 ease-in-out ${isExpanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"}`,
404
- children: /* @__PURE__ */ jsx3("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx3("ul", { className: "ml-4 mt-1 space-y-1 pl-2", children: item.subItems.map((sub) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3(
405
- "a",
406
- {
407
- href: sub.href,
408
- onClick: closeMobileNav,
409
- className: `flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${isExactActive(sub.href, currentPath) ? "bg-gray-200 text-black" : "text-gray-500 hover:bg-gray-100"}`,
410
- children: sub.label
411
- }
412
- ) }, sub.id)) }) })
413
- }
414
- )
415
- ] }, item.id);
416
- }) }) }),
417
- footer && footer.length > 0 && /* @__PURE__ */ jsx3("div", { className: "p-4 border-t border-gray-100 space-y-1", children: footer.map(
418
- (item) => "confirm" in item ? /* @__PURE__ */ jsxs3(
419
- "button",
420
- {
421
- type: "button",
422
- onClick: () => openConfirm(item),
423
- className: "flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-100 transition-colors bg-transparent cursor-pointer",
424
- children: [
425
- renderItemIcon(item.icon),
426
- item.label
427
- ]
428
- },
429
- item.label
430
- ) : /* @__PURE__ */ jsxs3(
431
- "a",
432
- {
433
- href: item.href,
434
- onClick: closeMobileNav,
435
- className: "flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-100 transition-colors",
436
- children: [
437
- renderItemIcon(item.icon),
438
- item.label
439
- ]
440
- },
441
- item.label
442
- )
443
- ) })
444
- ]
445
- }
446
- ),
447
- confirmingItem && "confirm" in confirmingItem && /* @__PURE__ */ jsx3(
448
- Modal,
449
- {
450
- title: confirmingItem.confirm.title ?? confirmingItem.label,
451
- isOpen: true,
452
- onClose: closeConfirm,
453
- children: (requestClose) => /* @__PURE__ */ jsx3(
454
- Confirmation,
455
- {
456
- question: confirmingItem.confirm.question,
457
- messageYes: confirmingItem.confirm.messageYes,
458
- messageNo: confirmingItem.confirm.messageNo,
459
- requestClose: () => {
460
- setConfirmingItem(null);
461
- setError(null);
462
- },
463
- onAction: handleConfirmAction,
464
- isProcessing: pending,
465
- error,
466
- resetRequest: () => setError(null)
467
- }
468
- )
469
- }
470
- ),
471
- isMobileOpen && /* @__PURE__ */ jsx3(
472
- "div",
473
- {
474
- className: "fixed inset-0 z-40 bg-black/50 md:hidden",
475
- onClick: closeMobileNav,
476
- "aria-hidden": "true"
477
- }
478
- ),
479
- /* @__PURE__ */ jsxs3("div", { className: "flex-1 ml-64 max-md:ml-0 min-w-0", children: [
480
- headerTitle && /* @__PURE__ */ jsxs3("header", { className: "h-16 bg-white border-b border-gray-200 flex items-center justify-between px-6 fixed top-0 right-0 left-64 max-md:left-0 z-20", children: [
481
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
482
- /* @__PURE__ */ jsx3(
483
- "button",
484
- {
485
- className: "md:hidden p-2 -ml-2 rounded-lg hover:bg-gray-100 transition-colors",
486
- onClick: () => setIsMobileOpen(!isMobileOpen),
487
- "aria-label": isMobileOpen ? "Fermer le menu" : "Menu",
488
- "aria-expanded": isMobileOpen,
489
- children: isMobileOpen ? /* @__PURE__ */ jsx3(
490
- "svg",
491
- {
492
- className: "w-5 h-5",
493
- fill: "none",
494
- viewBox: "0 0 24 24",
495
- stroke: "currentColor",
496
- strokeWidth: 2,
497
- children: /* @__PURE__ */ jsx3(
498
- "path",
499
- {
500
- strokeLinecap: "round",
501
- strokeLinejoin: "round",
502
- d: "M6 18L18 6M6 6l12 12"
503
- }
504
- )
505
- }
506
- ) : /* @__PURE__ */ jsx3(
507
- "svg",
508
- {
509
- className: "w-5 h-5",
510
- fill: "none",
511
- viewBox: "0 0 24 24",
512
- stroke: "currentColor",
513
- strokeWidth: 2,
514
- children: /* @__PURE__ */ jsx3(
515
- "path",
516
- {
517
- strokeLinecap: "round",
518
- strokeLinejoin: "round",
519
- d: "M4 6h16M4 12h16M4 18h16"
520
- }
521
- )
522
- }
523
- )
524
- }
525
- ),
526
- /* @__PURE__ */ jsx3("h1", { className: "text-base font-semibold text-gray-500", children: headerTitle })
527
- ] }),
528
- brand?.tagline && /* @__PURE__ */ jsx3("span", { className: "text-sm text-gray-500 max-md:hidden", children: brand.tagline })
529
- ] }),
530
- !headerTitle && /* @__PURE__ */ jsx3(
531
- "button",
532
- {
533
- className: "md:hidden fixed top-4 left-4 z-10 p-2 rounded-lg bg-white border border-gray-200 shadow-sm hover:bg-gray-100 transition-colors",
534
- onClick: () => setIsMobileOpen(!isMobileOpen),
535
- "aria-label": isMobileOpen ? "Fermer le menu" : "Menu",
536
- "aria-expanded": isMobileOpen,
537
- children: isMobileOpen ? /* @__PURE__ */ jsx3(
538
- "svg",
539
- {
540
- className: "w-5 h-5",
541
- fill: "none",
542
- viewBox: "0 0 24 24",
543
- stroke: "currentColor",
544
- strokeWidth: 2,
545
- children: /* @__PURE__ */ jsx3(
546
- "path",
547
- {
548
- strokeLinecap: "round",
549
- strokeLinejoin: "round",
550
- d: "M6 18L18 6M6 6l12 12"
551
- }
552
- )
553
- }
554
- ) : /* @__PURE__ */ jsx3(
555
- "svg",
556
- {
557
- className: "w-5 h-5",
558
- fill: "none",
559
- viewBox: "0 0 24 24",
560
- stroke: "currentColor",
561
- strokeWidth: 2,
562
- children: /* @__PURE__ */ jsx3(
563
- "path",
564
- {
565
- strokeLinecap: "round",
566
- strokeLinejoin: "round",
567
- d: "M4 6h16M4 12h16M4 18h16"
568
- }
569
- )
570
- }
571
- )
572
- }
573
- ),
574
- /* @__PURE__ */ jsx3(
575
- "main",
576
- {
577
- className: (headerTitle ? "p-6 pt-20" : "p-6") + " overflow-x-auto",
578
- children
579
- }
580
- )
581
- ] })
582
- ] });
583
- }
584
-
585
- // packages/ui/src/config/create-side-nav.tsx
586
- import { jsx as jsx4 } from "react/jsx-runtime";
587
- function createSideNav(config) {
588
- return function ConfiguredSideNav({
589
- children,
590
- currentPath
591
- }) {
592
- return /* @__PURE__ */ jsx4(SideNav, { ...config, currentPath, children });
593
- };
594
- }
595
-
596
- // packages/ui/src/table/table.tsx
597
- import { Printer, ChevronLeft, ChevronRight } from "lucide-react";
598
- import clsx from "clsx";
599
- import { useState as useState4, useMemo, useCallback as useCallback2 } from "react";
600
-
601
- // packages/ui/src/feedback/error-display.tsx
602
- import { X as X2 } from "lucide-react";
603
- import { useState as useState3 } from "react";
604
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
605
- function ErrorDisplay({ error }) {
606
- const [dismissed, setDismissed] = useState3(false);
607
- if (!error || dismissed) return null;
608
- return /* @__PURE__ */ jsx5("div", { className: "w-full overflow-x-auto", children: /* @__PURE__ */ jsx5("div", { className: "rounded-lg shadow-md border border-red-200 bg-red-50", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-start justify-between gap-4 px-4 py-4", children: [
609
- /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-1", children: [
610
- /* @__PURE__ */ jsx5("p", { className: "text-red-700 font-medium", children: "Erreur" }),
611
- /* @__PURE__ */ jsx5("p", { className: "text-red-600 text-sm", children: error.message })
612
- ] }),
613
- /* @__PURE__ */ jsx5(
614
- "button",
615
- {
616
- onClick: () => setDismissed(true),
617
- className: "text-red-400 hover:text-red-600 shrink-0 cursor-pointer",
618
- children: /* @__PURE__ */ jsx5(X2, { className: "w-5 h-5" })
619
- }
620
- )
621
- ] }) }) });
622
- }
623
-
624
- // packages/ui/src/table/table.tsx
625
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
626
- var VISIBLE_PAGES = 5;
627
- function Table({
628
- data,
629
- columns,
630
- loading,
631
- error,
632
- label,
633
- printable,
634
- pageSize = 5,
635
- pageSizeOptions = [5, 10, 20],
636
- showPagination = true
637
- }) {
638
- const [tooltip, setTooltip] = useState4(null);
639
- const [page, setPage] = useState4(1);
640
- const [perPage, setPerPage] = useState4(pageSize);
641
- const totalPages = useMemo(
642
- () => Math.max(1, Math.ceil(data.length / perPage)),
643
- [data.length, perPage]
644
- );
645
- const safePage = Math.min(page, totalPages);
646
- const displayData = useMemo(
647
- () => data.slice((safePage - 1) * perPage, safePage * perPage),
648
- [data, safePage, perPage]
649
- );
650
- const paginationRange = useMemo(() => {
651
- const half = Math.floor(VISIBLE_PAGES / 2);
652
- let start = Math.max(1, safePage - half);
653
- let end = Math.min(totalPages, start + VISIBLE_PAGES - 1);
654
- if (end - start + 1 < VISIBLE_PAGES) {
655
- start = Math.max(1, end - VISIBLE_PAGES + 1);
656
- }
657
- const range = [];
658
- if (start > 1) {
659
- range.push(1);
660
- if (start > 2) range.push("...");
661
- }
662
- for (let i = start; i <= end; i++) range.push(i);
663
- if (end < totalPages) {
664
- if (end < totalPages - 1) range.push("...");
665
- range.push(totalPages);
666
- }
667
- return range;
668
- }, [safePage, totalPages]);
669
- const handlePageSizeChange = useCallback2((value) => {
670
- setPerPage(value);
671
- setPage(1);
672
- }, []);
673
- const colCount = columns.length;
674
- const colWidth = colCount > 0 ? `${Math.round(100 / colCount)}%` : "auto";
675
- const handleMouseEnter = (e, content) => {
676
- const rect = e.currentTarget.getBoundingClientRect();
677
- setTooltip({
678
- content,
679
- x: rect.left + rect.width / 2,
680
- y: rect.top
681
- });
682
- };
683
- const handleMouseLeave = () => {
684
- setTooltip(null);
685
- };
686
- const banner = error ? /* @__PURE__ */ jsx6("div", { className: "mb-4", children: /* @__PURE__ */ jsx6(ErrorDisplay, { error }, error.message) }) : null;
687
- if (loading) {
688
- return /* @__PURE__ */ jsxs5("div", { className: "w-full overflow-x-auto", children: [
689
- banner,
690
- /* @__PURE__ */ jsx6("div", { className: "rounded-lg shadow-md border border-gray-200", children: /* @__PURE__ */ jsx6("div", { className: "flex items-center justify-center py-20 text-gray-500", children: "Chargement..." }) })
691
- ] });
692
- }
693
- if (!data || data.length === 0) {
694
- return /* @__PURE__ */ jsxs5("div", { className: "w-full overflow-x-auto", children: [
695
- banner,
696
- /* @__PURE__ */ jsx6("div", { className: "rounded-lg shadow-md border border-gray-200", children: /* @__PURE__ */ jsx6("div", { className: "flex items-center justify-center py-20 text-gray-500", children: "Aucun \xE9l\xE9ment" }) })
697
- ] });
698
- }
699
- return /* @__PURE__ */ jsxs5("div", { className: "w-full overflow-x-auto", children: [
700
- banner,
701
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between mb-2", children: [
702
- label && /* @__PURE__ */ jsxs5("div", { className: "text-sm font-semibold text-gray-700", children: [
703
- label,
704
- ": ",
705
- data.length
706
- ] }),
707
- printable && /* @__PURE__ */ jsxs5(
708
- "button",
709
- {
710
- onClick: () => window.print(),
711
- className: "flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-blue-500 rounded-lg hover:bg-blue-600 transition-colors print:hidden",
712
- children: [
713
- /* @__PURE__ */ jsx6(Printer, { className: "w-4 h-4" }),
714
- "Imprimer"
715
- ]
716
- }
717
- )
718
- ] }),
719
- /* @__PURE__ */ jsx6("div", { className: "rounded-lg shadow-md border border-gray-200", children: /* @__PURE__ */ jsxs5("table", { className: "w-full table-fixed divide-y divide-gray-200 print:table-fixed", children: [
720
- /* @__PURE__ */ jsx6("thead", { className: "bg-blue-500 rounded-t-lg", children: /* @__PURE__ */ jsx6("tr", { children: columns.map((col, index) => /* @__PURE__ */ jsx6(
721
- "th",
722
- {
723
- style: { width: colWidth },
724
- className: clsx(
725
- "px-6 py-3 text-left text-xs font-bold text-white uppercase tracking-wider truncate",
726
- col.excludeFromPrint && "print:hidden"
727
- ),
728
- children: col.header
729
- },
730
- index
731
- )) }) }),
732
- /* @__PURE__ */ jsx6(
733
- "tbody",
734
- {
735
- className: clsx(
736
- "bg-white divide-y divide-gray-200",
737
- printable && "print:hidden"
738
- ),
739
- children: displayData.map((row, rowIndex) => /* @__PURE__ */ jsx6(
740
- "tr",
741
- {
742
- className: "hover:bg-gray-100 transition-colors duration-200",
743
- children: columns.map((col, colIndex) => {
744
- const cellContent = col.render ? col.render(row) : String(row[col.accessor]);
745
- const textContent = typeof cellContent === "string" ? cellContent : "";
746
- return /* @__PURE__ */ jsx6(
747
- "td",
748
- {
749
- style: { width: colWidth },
750
- className: clsx(
751
- "px-6 py-4 text-sm text-gray-700",
752
- col.excludeFromPrint && "print:hidden"
753
- ),
754
- children: typeof cellContent === "string" ? /* @__PURE__ */ jsx6(
755
- "div",
756
- {
757
- className: "truncate",
758
- onMouseEnter: (e) => handleMouseEnter(e, textContent),
759
- onMouseLeave: handleMouseLeave,
760
- children: cellContent
761
- }
762
- ) : cellContent
763
- },
764
- colIndex
765
- );
766
- })
767
- },
768
- rowIndex
769
- ))
770
- }
771
- ),
772
- printable && /* @__PURE__ */ jsx6("tbody", { className: "hidden print:table-row-group", children: data.map((row, rowIndex) => /* @__PURE__ */ jsx6("tr", { children: columns.map((col, colIndex) => {
773
- const cellContent = col.render ? col.render(row) : String(row[col.accessor]);
774
- return /* @__PURE__ */ jsx6(
775
- "td",
776
- {
777
- style: { width: colWidth },
778
- className: clsx(
779
- "px-6 py-4 text-sm text-gray-700",
780
- col.excludeFromPrint && "print:hidden"
781
- ),
782
- children: cellContent
783
- },
784
- colIndex
785
- );
786
- }) }, rowIndex)) })
787
- ] }) }),
788
- showPagination && totalPages > 1 && /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between mt-4 text-sm print:hidden", children: [
789
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 text-gray-600", children: [
790
- /* @__PURE__ */ jsx6("span", { children: "Lignes par page :" }),
791
- /* @__PURE__ */ jsx6(
792
- "select",
793
- {
794
- value: perPage,
795
- onChange: (e) => handlePageSizeChange(Number(e.target.value)),
796
- className: "border border-gray-300 rounded px-2 py-1 text-sm bg-white focus:outline-none focus:ring-2 focus:ring-blue-400",
797
- children: pageSizeOptions.map((opt) => /* @__PURE__ */ jsx6("option", { value: opt, children: opt }, opt))
798
- }
799
- )
800
- ] }),
801
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1", children: [
802
- /* @__PURE__ */ jsx6(
803
- "button",
804
- {
805
- onClick: () => setPage((p) => Math.max(1, p - 1)),
806
- disabled: safePage <= 1,
807
- className: clsx(
808
- "p-1.5 rounded transition-colors",
809
- safePage <= 1 ? "text-gray-300 cursor-not-allowed" : "text-gray-600 hover:bg-gray-100"
810
- ),
811
- children: /* @__PURE__ */ jsx6(ChevronLeft, { className: "w-4 h-4" })
812
- }
813
- ),
814
- paginationRange.map(
815
- (item, i) => item === "..." ? /* @__PURE__ */ jsx6("span", { className: "px-2 text-gray-400", children: "..." }, `ellipsis-${i}`) : /* @__PURE__ */ jsx6(
816
- "button",
817
- {
818
- onClick: () => setPage(item),
819
- className: clsx(
820
- "min-w-8 px-2 py-1 rounded text-sm font-medium transition-colors",
821
- item === safePage ? "bg-blue-500 text-white" : "text-gray-600 hover:bg-gray-100"
822
- ),
823
- children: item
824
- },
825
- item
826
- )
827
- ),
828
- /* @__PURE__ */ jsx6(
829
- "button",
830
- {
831
- onClick: () => setPage((p) => Math.min(totalPages, p + 1)),
832
- disabled: safePage >= totalPages,
833
- className: clsx(
834
- "p-1.5 rounded transition-colors",
835
- safePage >= totalPages ? "text-gray-300 cursor-not-allowed" : "text-gray-600 hover:bg-gray-100"
836
- ),
837
- children: /* @__PURE__ */ jsx6(ChevronRight, { className: "w-4 h-4" })
838
- }
839
- )
840
- ] }),
841
- /* @__PURE__ */ jsxs5("div", { className: "text-gray-500", children: [
842
- "Page ",
843
- safePage,
844
- " sur ",
845
- totalPages
846
- ] })
847
- ] }),
848
- tooltip && /* @__PURE__ */ jsx6(
849
- "div",
850
- {
851
- className: "fixed z-20 px-3 py-2 text-sm text-white bg-gray-800 rounded-lg shadow-lg pointer-events-none whitespace-nowrap",
852
- style: {
853
- left: tooltip.x,
854
- top: tooltip.y - 8,
855
- transform: "translate(-50%, -100%)"
856
- },
857
- children: tooltip.content
858
- }
859
- ),
860
- /* @__PURE__ */ jsx6("style", { children: `
861
- @media print {
862
- body * { visibility: hidden; }
863
- .w-full.overflow-x-auto { visibility: visible; }
864
- .w-full.overflow-x-auto * { visibility: visible; }
865
- .w-full.overflow-x-auto { position: absolute; left: 0; top: 0; width: 100%; }
866
- }
867
- ` })
868
- ] });
869
- }
870
-
871
- // packages/ui/src/table/actions-menu.tsx
872
- import { useState as useState5, useRef as useRef2, useEffect as useEffect4 } from "react";
873
- import { Ellipsis } from "lucide-react";
874
- import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
875
- function ActionsMenu({ row, actions }) {
876
- const [isOpen, setIsOpen] = useState5(false);
877
- const [coords, setCoords] = useState5({ top: 0, left: 0 });
878
- const buttonRef = useRef2(null);
879
- const menuRef = useRef2(null);
880
- useEffect4(() => {
881
- function handleClickOutside(event) {
882
- if (menuRef.current && buttonRef.current && !menuRef.current.contains(event.target) && !buttonRef.current.contains(event.target)) {
883
- setIsOpen(false);
884
- }
885
- }
886
- document.addEventListener("mousedown", handleClickOutside);
887
- return () => document.removeEventListener("mousedown", handleClickOutside);
888
- }, []);
889
- const toggleMenu = () => {
890
- if (buttonRef.current) {
891
- const rect = buttonRef.current.getBoundingClientRect();
892
- const menuWidth = 180;
893
- const menuHeight = actions.length * 40;
894
- const spaceBelow = window.innerHeight - rect.bottom;
895
- const spaceRight = window.innerWidth - rect.left;
896
- let top = rect.bottom;
897
- let left = rect.left;
898
- if (spaceBelow < menuHeight + 20) {
899
- top = rect.top - menuHeight;
900
- }
901
- if (spaceRight < menuWidth + 20) {
902
- left = rect.left - menuWidth + 30;
903
- }
904
- setCoords({ top, left });
905
- }
906
- setIsOpen(!isOpen);
907
- };
908
- return /* @__PURE__ */ jsxs6(Fragment, { children: [
909
- /* @__PURE__ */ jsx7(
910
- "button",
911
- {
912
- ref: buttonRef,
913
- onClick: toggleMenu,
914
- className: "p-1 hover:bg-gray-100 rounded-full",
915
- children: /* @__PURE__ */ jsx7(Ellipsis, { className: "w-5 h-5" })
916
- }
917
- ),
918
- isOpen && /* @__PURE__ */ jsx7(
919
- "div",
920
- {
921
- ref: menuRef,
922
- className: "fixed bg-white border border-gray-200 rounded shadow-lg z-20",
923
- style: {
924
- top: coords.top,
925
- left: coords.left,
926
- minWidth: 180
927
- },
928
- children: actions.map((action, idx) => /* @__PURE__ */ jsxs6(
929
- "button",
930
- {
931
- className: `flex items-center gap-2 px-4 py-2 w-full text-sm hover:bg-gray-100 ${action.color || "text-gray-700"}`,
932
- onClick: () => {
933
- action.callback(row);
934
- setIsOpen(false);
935
- },
936
- children: [
937
- action.icon,
938
- action.label
939
- ]
940
- },
941
- idx
942
- ))
943
- }
944
- )
945
- ] });
946
- }
947
-
948
- // packages/ui/src/headers/header-content.tsx
949
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
950
- function HeaderContent({
951
- title,
952
- description,
953
- actionLabel,
954
- onAction,
955
- icon
956
- }) {
957
- return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-6", children: [
958
- /* @__PURE__ */ jsxs7("div", { children: [
959
- /* @__PURE__ */ jsx8("h1", { className: "text-xl font-semibold text-gray-900 flex items-center gap-2", children: title }),
960
- description && /* @__PURE__ */ jsx8("p", { className: "text-sm text-gray-500 mt-1", children: description })
961
- ] }),
962
- actionLabel && onAction && /* @__PURE__ */ jsxs7(
963
- "button",
964
- {
965
- onClick: onAction,
966
- className: "font-semibold md:inline-flex items-center gap-2 bg-gray-900 text-white px-4 py-2 rounded-md text-sm hover:bg-gray-700 transition hover:cursor-pointer",
967
- children: [
968
- icon && /* @__PURE__ */ jsx8("span", { className: "w-4 h-4", children: icon }),
969
- actionLabel
970
- ]
971
- }
972
- )
973
- ] });
974
- }
975
-
976
- // packages/ui/src/forms/form.tsx
977
- import { useState as useState6, useEffect as useEffect5, useRef as useRef3, useCallback as useCallback3 } from "react";
978
- import { X as X3 } from "lucide-react";
979
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
980
- var Form = ({
981
- erreurs,
982
- onErrorClose,
983
- children,
984
- className,
985
- ...props
986
- }) => {
987
- const formClassName = ["flex flex-col gap-3", className].filter(Boolean).join(" ");
988
- const errorMessage = erreurs?.message || null;
989
- const [showError, setShowError] = useState6(!!erreurs);
990
- const [countdown, setCountdown] = useState6(erreurs ? 10 : 0);
991
- const [animating, setAnimating] = useState6(!!erreurs);
992
- const intervalRef = useRef3(null);
993
- const hasClosedRef = useRef3(false);
994
- const closeError = useCallback3(
995
- (triggerCallback = true) => {
996
- if (intervalRef.current !== null) {
997
- window.clearInterval(intervalRef.current);
998
- intervalRef.current = null;
999
- }
1000
- setShowError(false);
1001
- setAnimating(false);
1002
- setCountdown(0);
1003
- if (triggerCallback && onErrorClose && !hasClosedRef.current) {
1004
- hasClosedRef.current = true;
1005
- onErrorClose();
1006
- }
1007
- },
1008
- [onErrorClose]
1009
- );
1010
- useEffect5(() => {
1011
- hasClosedRef.current = false;
1012
- if (!erreurs) {
1013
- const closeTimeout = window.setTimeout(() => closeError(false), 0);
1014
- return () => {
1015
- window.clearTimeout(closeTimeout);
1016
- };
1017
- }
1018
- const initTimeout = window.setTimeout(() => {
1019
- setShowError(true);
1020
- setAnimating(true);
1021
- setCountdown(10);
1022
- }, 0);
1023
- intervalRef.current = window.setInterval(() => {
1024
- setCountdown((prev) => {
1025
- if (prev <= 1) {
1026
- closeError(true);
1027
- return 0;
1028
- }
1029
- return prev - 1;
1030
- });
1031
- }, 1e3);
1032
- return () => {
1033
- window.clearTimeout(initTimeout);
1034
- if (intervalRef.current !== null) {
1035
- window.clearInterval(intervalRef.current);
1036
- intervalRef.current = null;
1037
- }
1038
- };
1039
- }, [erreurs, closeError]);
1040
- return /* @__PURE__ */ jsxs8(Fragment2, { children: [
1041
- /* @__PURE__ */ jsx9("form", { className: formClassName, ...props, children }),
1042
- showError && errorMessage && /* @__PURE__ */ jsxs8(
1043
- "div",
1044
- {
1045
- className: `fixed top-0 left-0 right-0 z-[9999] bg-red-500 text-white p-4 rounded-b-md shadow-lg flex justify-between items-center transition-transform duration-300 ${animating ? "transform translate-y-0" : "transform -translate-y-full"}`,
1046
- children: [
1047
- /* @__PURE__ */ jsx9("span", { children: errorMessage }),
1048
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center", children: [
1049
- /* @__PURE__ */ jsxs8("span", { className: "mr-2", children: [
1050
- countdown,
1051
- "s"
1052
- ] }),
1053
- /* @__PURE__ */ jsx9("button", { onClick: () => closeError(true), children: /* @__PURE__ */ jsx9(X3, { size: 20 }) })
1054
- ] })
1055
- ]
1056
- }
1057
- )
1058
- ] });
1059
- };
1060
-
1061
- // packages/ui/src/forms/field-container.tsx
1062
- import { jsx as jsx10 } from "react/jsx-runtime";
1063
- function FieldContainer({ children, className }) {
1064
- return /* @__PURE__ */ jsx10("div", { className: `flex flex-col gap-2 mb-3 relative ${className ?? ""}`, children: /* @__PURE__ */ jsx10("div", { className: "flex flex-col gap-1", children }) });
1065
- }
1066
-
1067
- // packages/ui/src/forms/label.tsx
1068
- import React4 from "react";
1069
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1070
- var Label = React4.forwardRef(
1071
- ({ required, children, className, ...props }, ref) => {
1072
- return /* @__PURE__ */ jsxs9(
1073
- "label",
1074
- {
1075
- ref,
1076
- className: `font-semibold text-gray-600 block ${className ?? ""}`.trim(),
1077
- ...props,
1078
- children: [
1079
- children,
1080
- required ? /* @__PURE__ */ jsx11("span", { className: "text-red-500 ml-1", children: "*" }) : null
1081
- ]
1082
- }
1083
- );
1084
- }
1085
- );
1086
- Label.displayName = "Label";
1087
-
1088
- // packages/ui/src/forms/input.tsx
1089
- import React5, { useState as useState7 } from "react";
1090
- import { Eye, EyeOff } from "lucide-react";
1091
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1092
- var Input = React5.forwardRef(({ type, className, ...props }, ref) => {
1093
- const [showPassword, setShowPassword] = useState7(false);
1094
- const isPassword = type === "password";
1095
- const inputType = isPassword ? showPassword ? "text" : "password" : type;
1096
- return /* @__PURE__ */ jsxs10("div", { className: "relative", children: [
1097
- /* @__PURE__ */ jsx12(
1098
- "input",
1099
- {
1100
- ref,
1101
- type: inputType,
1102
- className: `w-full px-3 py-2 pr-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-transparent transition duration-200 ${className || ""}`,
1103
- ...props
1104
- }
1105
- ),
1106
- isPassword && /* @__PURE__ */ jsx12(
1107
- "button",
1108
- {
1109
- type: "button",
1110
- onClick: () => setShowPassword(!showPassword),
1111
- className: "absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-gray-700",
1112
- children: showPassword ? /* @__PURE__ */ jsx12(EyeOff, { size: 20 }) : /* @__PURE__ */ jsx12(Eye, { size: 20 })
1113
- }
1114
- )
1115
- ] });
1116
- });
1117
- Input.displayName = "Input";
1118
-
1119
- // packages/ui/src/forms/select.tsx
1120
- import React6 from "react";
1121
- import { jsx as jsx13 } from "react/jsx-runtime";
1122
- var Select = React6.forwardRef(({ className, ...props }, ref) => {
1123
- return /* @__PURE__ */ jsx13(
1124
- "select",
1125
- {
1126
- ref,
1127
- className: `w-full px-3 py-2 pr-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-transparent transition duration-200 bg-white cursor-pointer appearance-none bg-no-repeat bg-right bg-contain ${className || ""}`,
1128
- style: {
1129
- backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e")`,
1130
- backgroundPosition: "right 0.75rem center",
1131
- backgroundSize: "1.25em 1.25em"
1132
- },
1133
- ...props
1134
- }
1135
- );
1136
- });
1137
- Select.displayName = "Select";
1138
- var Option = React6.forwardRef(({ ...props }, ref) => {
1139
- return /* @__PURE__ */ jsx13("option", { ref, ...props });
1140
- });
1141
- Option.displayName = "Option";
1142
-
1143
- // packages/ui/src/forms/textarea.tsx
1144
- import React7 from "react";
1145
- import { jsx as jsx14 } from "react/jsx-runtime";
1146
- var TextArea = React7.forwardRef(({ ...props }, ref) => {
1147
- return /* @__PURE__ */ jsx14(
1148
- "textarea",
1149
- {
1150
- ref,
1151
- className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-transparent transition duration-200 resize-none",
1152
- ...props
1153
- }
1154
- );
1155
- });
1156
- TextArea.displayName = "TextArea";
1157
-
1158
- // packages/ui/src/forms/date-picker.tsx
1159
- import React8 from "react";
1160
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1161
- var DatePicker = React8.forwardRef(({ className, placeholder, required, ...props }, ref) => {
1162
- return /* @__PURE__ */ jsxs11("div", { className: "relative", children: [
1163
- /* @__PURE__ */ jsx15(
1164
- "input",
1165
- {
1166
- ref,
1167
- type: "date",
1168
- required: required ?? true,
1169
- className: `peer w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-transparent transition duration-200 ${className ?? ""}`,
1170
- ...props
1171
- }
1172
- ),
1173
- /* @__PURE__ */ jsx15("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none peer-valid:hidden md:hidden", children: placeholder ?? "jj/mm/aaaa" })
1174
- ] });
1175
- });
1176
- DatePicker.displayName = "DatePicker";
1177
-
1178
- // packages/ui/src/loaders/loading.tsx
1179
- import { Loader } from "lucide-react";
1180
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1181
- function Loading(props) {
1182
- return /* @__PURE__ */ jsx16("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsxs12("div", { className: "flex flex-col items-center gap-4", children: [
1183
- /* @__PURE__ */ jsx16(Loader, { className: "animate-spin", size: 40 }),
1184
- /* @__PURE__ */ jsx16("p", { className: "text-gray-600 text-md font-medium", children: props.text })
1185
- ] }) });
1186
- }
1187
-
1188
- // packages/ui/src/loaders/loader-submit.tsx
1189
- import { LoaderCircle } from "lucide-react";
1190
- import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1191
- function LoaderSubmit({ label, loadingLabel, isLoading }) {
1192
- return /* @__PURE__ */ jsx17(Fragment3, { children: isLoading ? /* @__PURE__ */ jsxs13("div", { className: "flex flex-row gap-2 items-center", children: [
1193
- /* @__PURE__ */ jsx17(LoaderCircle, { className: "animate-spin text-gray-500" }),
1194
- /* @__PURE__ */ jsx17("p", { className: "text-gray-500 font-semibold", children: loadingLabel })
1195
- ] }) : /* @__PURE__ */ jsx17(
1196
- "button",
1197
- {
1198
- type: "submit",
1199
- className: "bg-gray-900 text-white px-6 py-2 rounded-md hover:bg-gray-700 hover:cursor-pointer transition-colors duration-300 font-semibold shadow-md",
1200
- disabled: isLoading,
1201
- children: label
1202
- }
1203
- ) });
1204
- }
1205
-
1206
- // packages/ui/src/pages/session-expired.tsx
1207
- import { useRouter } from "next/navigation";
1208
- import { useEffect as useEffect6, useState as useState8 } from "react";
1209
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1210
- function SessionExpired({ redirectUrl = "/" }) {
1211
- const router = useRouter();
1212
- const [countdown, setCountdown] = useState8(10);
1213
- useEffect6(() => {
1214
- if (countdown <= 0) {
1215
- router.push(redirectUrl);
1216
- return;
1217
- }
1218
- const timer = setTimeout(() => setCountdown((c) => c - 1), 1e3);
1219
- return () => clearTimeout(timer);
1220
- }, [countdown, router, redirectUrl]);
1221
- const handleConnect = () => {
1222
- router.push(redirectUrl);
1223
- };
1224
- return /* @__PURE__ */ jsx18("div", { className: "min-h-screen flex items-center justify-center bg-gray-50", children: /* @__PURE__ */ jsxs14("div", { className: "max-w-md w-full text-center space-y-8 p-8", children: [
1225
- /* @__PURE__ */ jsxs14("div", { className: "space-y-3", children: [
1226
- /* @__PURE__ */ jsx18("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx18(
1227
- "svg",
1228
- {
1229
- className: "w-16 h-16 text-gray-300",
1230
- fill: "none",
1231
- viewBox: "0 0 24 24",
1232
- stroke: "currentColor",
1233
- strokeWidth: 1.5,
1234
- children: /* @__PURE__ */ jsx18(
1235
- "path",
1236
- {
1237
- strokeLinecap: "round",
1238
- strokeLinejoin: "round",
1239
- d: "M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"
1240
- }
1241
- )
1242
- }
1243
- ) }),
1244
- /* @__PURE__ */ jsx18("h1", { className: "text-2xl font-semibold text-gray-900", children: "Session expir\xE9e" }),
1245
- /* @__PURE__ */ jsx18("p", { className: "text-gray-500", children: "Votre session a expir\xE9 ou vous n'\xEAtes pas connect\xE9. Veuillez vous reconnecter pour acc\xE9der \xE0 cette page." })
1246
- ] }),
1247
- /* @__PURE__ */ jsx18(
1248
- "button",
1249
- {
1250
- onClick: handleConnect,
1251
- className: "bg-gray-900 text-white px-6 py-2.5 rounded-md text-sm font-semibold hover:bg-gray-700 transition-colors",
1252
- children: "Me connecter"
1253
- }
1254
- ),
1255
- /* @__PURE__ */ jsxs14("p", { className: "text-sm text-gray-400", children: [
1256
- "Redirection automatique dans ",
1257
- countdown,
1258
- "s..."
1259
- ] })
1260
- ] }) });
1261
- }
1262
-
1263
- // packages/ui/src/pages/access-denied.tsx
1264
- import Image from "next/image";
1265
- import Link from "next/link";
1266
- import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1267
- function AccessDenied({ logo, redirectTo, textRedirection }) {
1268
- return /* @__PURE__ */ jsx19("main", { className: "min-h-screen py-16 px-4 bg-gradient-to-b from-gray-100 to-white text-gray-900 flex items-center justify-center", children: /* @__PURE__ */ jsxs15("div", { className: "max-w-md mx-auto text-center", children: [
1269
- /* @__PURE__ */ jsx19("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ jsx19(
1270
- Image,
1271
- {
1272
- alt: "Logo",
1273
- height: 100,
1274
- width: 100,
1275
- src: logo
1276
- }
1277
- ) }),
1278
- /* @__PURE__ */ jsx19("h1", { className: "text-2xl font-bold text-red-600 mb-4", children: "Acc\xE8s refus\xE9" }),
1279
- /* @__PURE__ */ jsx19("p", { className: "text-gray-500 mb-8", children: "Vous ne disposez pas des droits n\xE9cessaires pour acc\xE9der \xE0 cette page." }),
1280
- /* @__PURE__ */ jsx19(
1281
- Link,
1282
- {
1283
- href: redirectTo,
1284
- className: "inline-block px-6 py-3 bg-gray-900 text-white rounded-lg font-semibold hover:bg-gray-700 transition-colors",
1285
- children: textRedirection
1286
- }
1287
- )
1288
- ] }) });
1289
- }
1290
-
1291
- // packages/ui/src/route-guard/route-guard.tsx
1292
- import {
1293
- createContext,
1294
- useContext,
1295
- useMemo as useMemo2
1296
- } from "react";
1297
- import { redirect, usePathname } from "next/navigation";
1298
- import { Fragment as Fragment4, jsx as jsx20 } from "react/jsx-runtime";
1299
- function createRouteGuardConfig(input) {
1300
- return {
1301
- useCurrentUser: input.useCurrentUser,
1302
- pagePermissions: input.pagePermissions,
1303
- deniedPath: input.deniedPath ?? "/access-denied"
1304
- };
1305
- }
1306
- var RouteGuardContext = createContext(null);
1307
- function RouteGuardProvider({
1308
- children,
1309
- config,
1310
- useCurrentUser,
1311
- pagePermissions,
1312
- deniedPath
1313
- }) {
1314
- const resolved = useMemo2(() => {
1315
- if (config) return config;
1316
- return {
1317
- useCurrentUser,
1318
- pagePermissions,
1319
- deniedPath: deniedPath ?? "/access-denied"
1320
- };
1321
- }, [config, useCurrentUser, pagePermissions, deniedPath]);
1322
- if (!resolved.useCurrentUser || !resolved.pagePermissions) {
1323
- throw new Error(
1324
- "RouteGuardProvider requires either a `config` prop (from createRouteGuardConfig) or `useCurrentUser` + `pagePermissions` directly."
1325
- );
1326
- }
1327
- return /* @__PURE__ */ jsx20(RouteGuardContext.Provider, { value: resolved, children: /* @__PURE__ */ jsx20(AutoRouteGuard, { config: resolved, children }) });
1328
- }
1329
- function AutoRouteGuard({
1330
- config,
1331
- children
1332
- }) {
1333
- const pathname = usePathname();
1334
- const { data: user, isLoading } = config.useCurrentUser();
1335
- const allowedRoles = useMemo2(
1336
- () => findAllowedRoles(pathname, config.pagePermissions),
1337
- [pathname, config.pagePermissions]
1338
- );
1339
- if (!allowedRoles) {
1340
- return /* @__PURE__ */ jsx20(Fragment4, { children });
1341
- }
1342
- if (isLoading) {
1343
- return /* @__PURE__ */ jsx20("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsx20("p", { className: "text-gray-500", children: "Chargement..." }) });
1344
- }
1345
- if (!user || !allowedRoles.includes(user.role.name)) {
1346
- redirect(config.deniedPath);
1347
- }
1348
- return /* @__PURE__ */ jsx20(Fragment4, { children });
1349
- }
1350
- function routeGuard(page, Component) {
1351
- return function GuardedPage(props) {
1352
- const config = useContext(RouteGuardContext);
1353
- if (!config) {
1354
- throw new Error(
1355
- "routeGuard requires a RouteGuardProvider ancestor. Wrap your layout with <RouteGuardProvider>."
1356
- );
1357
- }
1358
- const { data: user, isLoading } = config.useCurrentUser();
1359
- const allowedRoles = findAllowedRoles(page, config.pagePermissions);
1360
- if (!allowedRoles) {
1361
- return /* @__PURE__ */ jsx20(Component, { ...props });
1362
- }
1363
- if (isLoading) {
1364
- return /* @__PURE__ */ jsx20("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsx20("p", { className: "text-gray-500", children: "Chargement..." }) });
1365
- }
1366
- if (!user || !allowedRoles.includes(user.role.name)) {
1367
- redirect(config.deniedPath);
1368
- }
1369
- return /* @__PURE__ */ jsx20(Component, { ...props });
1370
- };
1371
- }
1372
- function findAllowedRoles(page, pagePermissions) {
1373
- const segments = page.split("/").filter(Boolean);
1374
- for (let i = 0; i <= segments.length; i++) {
1375
- const candidate = "/" + segments.slice(0, i).join("/");
1376
- const roles = pagePermissions[candidate];
1377
- if (roles) return roles;
1378
- }
1379
- return void 0;
1380
- }
1381
- export {
1382
- AccessDenied,
1383
- ActionsMenu,
1384
- ApiError,
1385
- Confirmation,
1386
- DatePicker,
1387
- FieldContainer,
1388
- Form,
1389
- HeaderContent,
1390
- Input,
1391
- Label,
1392
- LoaderSubmit,
1393
- Loading,
1394
- Modal,
1395
- Option,
1396
- RouteGuardProvider,
1397
- Select,
1398
- SessionExpired,
1399
- SideNav,
1400
- Table,
1401
- TextArea,
1402
- createRouteGuardConfig,
1403
- createSideNav,
1404
- fetcher,
1405
- routeGuard
1406
- };
1407
- //# sourceMappingURL=client.js.map