@liorian/sdk 0.28.0-alpha.56010ca

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 (362) hide show
  1. package/README.md +483 -0
  2. package/package.json +94 -0
  3. package/schemas/module.schema.json +818 -0
  4. package/src/application/index.ts +3 -0
  5. package/src/application/module-instance.ts +36 -0
  6. package/src/application/service/auth-api-service.ts +291 -0
  7. package/src/application/service/auth-user.service.ts +108 -0
  8. package/src/application/service/index.ts +12 -0
  9. package/src/application/service/module-activation-api.service.ts +105 -0
  10. package/src/application/service/notifications-api-service.ts +38 -0
  11. package/src/application/service/notifications-push.service.ts +299 -0
  12. package/src/application/service/notifications-socket-service.ts +76 -0
  13. package/src/application/service/organizations-api-service.ts +104 -0
  14. package/src/application/service/otp-api-service.ts +11 -0
  15. package/src/application/service/storage-api-service.ts +77 -0
  16. package/src/application/service/storefront-api.service.ts +72 -0
  17. package/src/application/service/user-preferences-api.service.ts +57 -0
  18. package/src/domain/config/app.config.ts +35 -0
  19. package/src/domain/config/auth.config.ts +30 -0
  20. package/src/domain/config/index.ts +4 -0
  21. package/src/domain/config/logo.config.ts +17 -0
  22. package/src/domain/entities/activities.interface.ts +18 -0
  23. package/src/domain/entities/catalog.interface.ts +96 -0
  24. package/src/domain/entities/index.ts +13 -0
  25. package/src/domain/entities/media.ts +22 -0
  26. package/src/domain/entities/module-activation.interface.ts +173 -0
  27. package/src/domain/entities/module.interface.ts +101 -0
  28. package/src/domain/entities/notification.interface.ts +23 -0
  29. package/src/domain/entities/objectable.type.ts +3 -0
  30. package/src/domain/entities/organization.interface.ts +81 -0
  31. package/src/domain/entities/user-auth.interface.ts +17 -0
  32. package/src/domain/entities/user-phone.interface.ts +11 -0
  33. package/src/domain/entities/user-profile.interface.ts +23 -0
  34. package/src/domain/entities/user.interface.ts +79 -0
  35. package/src/domain/enums/coloring.enum.ts +6 -0
  36. package/src/domain/enums/domains.enum.ts +282 -0
  37. package/src/domain/enums/index.ts +8 -0
  38. package/src/domain/enums/module-category.enum.ts +57 -0
  39. package/src/domain/enums/module.enum.ts +97 -0
  40. package/src/domain/enums/routine.enum.ts +12 -0
  41. package/src/domain/enums/user-profile.enum.ts +25 -0
  42. package/src/domain/enums/user-status.enum.ts +7 -0
  43. package/src/domain/index.ts +6 -0
  44. package/src/domain/typing/auth-caches.ts +12 -0
  45. package/src/domain/typing/auth-oauth.types.ts +172 -0
  46. package/src/domain/typing/devise.ts +2 -0
  47. package/src/domain/typing/index.ts +10 -0
  48. package/src/domain/typing/response.ts +30 -0
  49. package/src/domain/typing/routine.types.ts +113 -0
  50. package/src/domain/typing/session.interface.ts +28 -0
  51. package/src/domain/typing/statisticals.ts +11 -0
  52. package/src/domain/typing/user-media-upload.ts +9 -0
  53. package/src/domain/typing/values.ts +6 -0
  54. package/src/index.tsx +168 -0
  55. package/src/infrastructure/capabilities/cache-storage/capability.ts +129 -0
  56. package/src/infrastructure/capabilities/cache-storage/enum.ts +5 -0
  57. package/src/infrastructure/capabilities/cache-storage/index.ts +4 -0
  58. package/src/infrastructure/capabilities/cache-storage/types.ts +13 -0
  59. package/src/infrastructure/capabilities/index.ts +3 -0
  60. package/src/infrastructure/capabilities/stackable/capability.ts +55 -0
  61. package/src/infrastructure/capabilities/stackable/index.ts +3 -0
  62. package/src/infrastructure/capabilities/stackable/types.ts +24 -0
  63. package/src/infrastructure/common-classname.constant.ts +4 -0
  64. package/src/infrastructure/data/countries.ts +208 -0
  65. package/src/infrastructure/data/gender.ts +5 -0
  66. package/src/infrastructure/data/index.ts +3 -0
  67. package/src/infrastructure/dataset/index.ts +3 -0
  68. package/src/infrastructure/dataset/sign-in.dataset.ts +21 -0
  69. package/src/infrastructure/dataset/sign-up.dataset.ts +64 -0
  70. package/src/infrastructure/hooks/index.ts +10 -0
  71. package/src/infrastructure/hooks/use-activity-lock-draft.ts +69 -0
  72. package/src/infrastructure/hooks/use-auth.ts +24 -0
  73. package/src/infrastructure/hooks/use-elastic-interaction.ts +180 -0
  74. package/src/infrastructure/hooks/use-is-desktop.ts +25 -0
  75. package/src/infrastructure/hooks/use-is-tauri.ts +17 -0
  76. package/src/infrastructure/hooks/use-mobile.ts +19 -0
  77. package/src/infrastructure/hooks/use-notifications.ts +128 -0
  78. package/src/infrastructure/hooks/use-recent-colors.ts +113 -0
  79. package/src/infrastructure/hooks/use-recent-modules.ts +76 -0
  80. package/src/infrastructure/index.tsx +11 -0
  81. package/src/infrastructure/library/design-system.ts +95 -0
  82. package/src/infrastructure/library/index.tsx +5 -0
  83. package/src/infrastructure/library/motion-utils.ts +77 -0
  84. package/src/infrastructure/library/motion.tsx +31 -0
  85. package/src/infrastructure/library/scroll-progress.tsx +93 -0
  86. package/src/infrastructure/providers/auth-guard.provider.tsx +47 -0
  87. package/src/infrastructure/providers/auth.provider.tsx +168 -0
  88. package/src/infrastructure/providers/index.tsx +8 -0
  89. package/src/infrastructure/providers/module-usage.provider.tsx +95 -0
  90. package/src/infrastructure/providers/modules-guard.provider.tsx +64 -0
  91. package/src/infrastructure/providers/modules-routines.provider.tsx +33 -0
  92. package/src/infrastructure/providers/modules-switcher.provider.tsx +25 -0
  93. package/src/infrastructure/providers/query.provider.tsx +27 -0
  94. package/src/infrastructure/routines/__tests__/routine.test.ts +368 -0
  95. package/src/infrastructure/routines/index.tsx +3 -0
  96. package/src/infrastructure/routines/routine.hook.ts +121 -0
  97. package/src/infrastructure/routines/routine.ts +283 -0
  98. package/src/infrastructure/routines/routine.tsx +283 -0
  99. package/src/infrastructure/stores/activity-lock.store.ts +22 -0
  100. package/src/infrastructure/stores/auth-user-connected.store.ts +24 -0
  101. package/src/infrastructure/stores/dataset.store.ts +106 -0
  102. package/src/infrastructure/stores/index.ts +6 -0
  103. package/src/infrastructure/stores/module.store.ts +266 -0
  104. package/src/infrastructure/stores/upload.store.ts +198 -0
  105. package/src/infrastructure/utilities/access-label.util.ts +158 -0
  106. package/src/infrastructure/utilities/activities.util.tsx +54 -0
  107. package/src/infrastructure/utilities/activity-lock.service.ts +263 -0
  108. package/src/infrastructure/utilities/activity-tracker.service.ts +205 -0
  109. package/src/infrastructure/utilities/api-service.ts +237 -0
  110. package/src/infrastructure/utilities/auth-logout.util.ts +23 -0
  111. package/src/infrastructure/utilities/classname.util.ts +7 -0
  112. package/src/infrastructure/utilities/encryption.service.ts +125 -0
  113. package/src/infrastructure/utilities/form-snapshot.service.ts +304 -0
  114. package/src/infrastructure/utilities/format.util.ts +40 -0
  115. package/src/infrastructure/utilities/index.tsx +23 -0
  116. package/src/infrastructure/utilities/media-label.service.ts +47 -0
  117. package/src/infrastructure/utilities/modules.ts +11 -0
  118. package/src/infrastructure/utilities/permission.util.ts +22 -0
  119. package/src/infrastructure/utilities/push-permission.util.ts +43 -0
  120. package/src/infrastructure/utilities/route-access.ts +38 -0
  121. package/src/infrastructure/utilities/socket-service.ts +123 -0
  122. package/src/infrastructure/utilities/sso-auth.util.ts +188 -0
  123. package/src/infrastructure/utilities/stringable.util.ts +16 -0
  124. package/src/infrastructure/utilities/strings.util.ts +18 -0
  125. package/src/infrastructure/utilities/tauri.util.ts +15 -0
  126. package/src/infrastructure/utilities/utils.ts +6 -0
  127. package/src/infrastructure/utilities/values.ts +5 -0
  128. package/src/presentation/8starlabs-ui/button.tsx +94 -0
  129. package/src/presentation/8starlabs-ui/flip-clock.tsx +368 -0
  130. package/src/presentation/8starlabs-ui/heatmap.tsx +430 -0
  131. package/src/presentation/8starlabs-ui/hooks/index.tsx +2 -0
  132. package/src/presentation/8starlabs-ui/hooks/use-mobile.ts +21 -0
  133. package/src/presentation/8starlabs-ui/index.tsx +20 -0
  134. package/src/presentation/8starlabs-ui/json-viewer.tsx +972 -0
  135. package/src/presentation/8starlabs-ui/marquee.tsx +134 -0
  136. package/src/presentation/8starlabs-ui/partition-bar.tsx +198 -0
  137. package/src/presentation/8starlabs-ui/scroll-fade.tsx +158 -0
  138. package/src/presentation/8starlabs-ui/status-indicator.tsx +86 -0
  139. package/src/presentation/8starlabs-ui/system-banner.tsx +50 -0
  140. package/src/presentation/8starlabs-ui/timeline.tsx +576 -0
  141. package/src/presentation/8starlabs-ui/transport-badge.tsx +249 -0
  142. package/src/presentation/8starlabs-ui/ui/copy-button.tsx +60 -0
  143. package/src/presentation/8starlabs-ui/ui/index.tsx +2 -0
  144. package/src/presentation/analytics-section.tsx +85 -0
  145. package/src/presentation/app-sidebar.tsx +234 -0
  146. package/src/presentation/charts/area-widget.chart-skeleton.tsx +61 -0
  147. package/src/presentation/charts/area-widget.chart.tsx +128 -0
  148. package/src/presentation/charts/chart-widgets.types.ts +81 -0
  149. package/src/presentation/charts/index.tsx +16 -0
  150. package/src/presentation/charts/line-widget.chart-skeleton.tsx +64 -0
  151. package/src/presentation/charts/line-widget.chart.tsx +105 -0
  152. package/src/presentation/charts/pie-widget.chart-skeleton.tsx +56 -0
  153. package/src/presentation/charts/pie-widget.chart.tsx +168 -0
  154. package/src/presentation/charts/radar-widget.chart-skeleton.tsx +67 -0
  155. package/src/presentation/charts/radar-widget.chart.tsx +80 -0
  156. package/src/presentation/charts/radial-stacked-widget.chart-skeleton.tsx +66 -0
  157. package/src/presentation/charts/radial-stacked-widget.chart.tsx +120 -0
  158. package/src/presentation/charts/radial-widget.chart-skeleton.tsx +61 -0
  159. package/src/presentation/charts/radial-widget.chart.tsx +87 -0
  160. package/src/presentation/charts/tooltip-widget.chart-skeleton.tsx +64 -0
  161. package/src/presentation/charts/tooltip-widget.chart.tsx +89 -0
  162. package/src/presentation/components/activity-actions.tsx +20 -0
  163. package/src/presentation/components/activity-container.tsx +55 -0
  164. package/src/presentation/components/activity-content.tsx +20 -0
  165. package/src/presentation/components/activity-descriptor-parser.tsx +37 -0
  166. package/src/presentation/components/activity-header.tsx +20 -0
  167. package/src/presentation/components/activity-loader.tsx +19 -0
  168. package/src/presentation/components/activity-title.tsx +32 -0
  169. package/src/presentation/components/activity.tsx +22 -0
  170. package/src/presentation/components/auth/index.tsx +4 -0
  171. package/src/presentation/components/auth/sso-callback.view.tsx +44 -0
  172. package/src/presentation/components/auth/sso-disconnect.view.tsx +41 -0
  173. package/src/presentation/components/auth/sso-sign-in.view.tsx +29 -0
  174. package/src/presentation/components/auth-lock-screen.tsx +309 -0
  175. package/src/presentation/components/auth-session.view.tsx +17 -0
  176. package/src/presentation/components/data-table.tsx +814 -0
  177. package/src/presentation/components/dynamic-icon.tsx +16 -0
  178. package/src/presentation/components/floating-upload.tsx +291 -0
  179. package/src/presentation/components/index.tsx +40 -0
  180. package/src/presentation/components/local-time.tsx +170 -0
  181. package/src/presentation/components/markdown-parser.tsx +201 -0
  182. package/src/presentation/components/message-scroller.tsx +131 -0
  183. package/src/presentation/components/module-first-access-config.view.tsx +181 -0
  184. package/src/presentation/components/module-first-access-pending.view.tsx +38 -0
  185. package/src/presentation/components/module-guard.tsx +34 -0
  186. package/src/presentation/components/modules-first-access.guard.tsx +88 -0
  187. package/src/presentation/components/modules-guard-forbidden.tsx +46 -0
  188. package/src/presentation/components/notification-details.modal.tsx +145 -0
  189. package/src/presentation/components/notification-item.tsx +127 -0
  190. package/src/presentation/components/notifications-popover.tsx +128 -0
  191. package/src/presentation/components/push-notifications.provider.tsx +65 -0
  192. package/src/presentation/components/upload-progress-dialog.tsx +140 -0
  193. package/src/presentation/components/waiting-bar.tsx +22 -0
  194. package/src/presentation/components/waiting-section.tsx +15 -0
  195. package/src/presentation/components/waiting.tsx +14 -0
  196. package/src/presentation/data-grid/data-grid-empty.tsx +24 -0
  197. package/src/presentation/data-grid/data-grid-search-engine.tsx +32 -0
  198. package/src/presentation/data-grid/data-grid.tsx +892 -0
  199. package/src/presentation/data-grid/index.tsx +4 -0
  200. package/src/presentation/form-screen.tsx +95 -0
  201. package/src/presentation/icons/custom.tsx +20 -0
  202. package/src/presentation/icons/glyphIcons.tsx +53 -0
  203. package/src/presentation/icons/icons.enum.ts +4 -0
  204. package/src/presentation/icons/icons.util.ts +6 -0
  205. package/src/presentation/icons/index.tsx +8 -0
  206. package/src/presentation/icons/lucide.tsx +17 -0
  207. package/src/presentation/icons/remix.tsx +33 -0
  208. package/src/presentation/icons/types.ts +27 -0
  209. package/src/presentation/index.tsx +45 -0
  210. package/src/presentation/modals/components/ModalBox.tsx +74 -0
  211. package/src/presentation/modals/components/ModalPortal.tsx +31 -0
  212. package/src/presentation/modals/components/ModalStepper.tsx +319 -0
  213. package/src/presentation/modals/components/ModalWrapper.tsx +65 -0
  214. package/src/presentation/modals/components/index.tsx +5 -0
  215. package/src/presentation/modals/hooks/index.ts +2 -0
  216. package/src/presentation/modals/hooks/useModal.ts +38 -0
  217. package/src/presentation/modals/index.tsx +5 -0
  218. package/src/presentation/modals/stores/index.ts +2 -0
  219. package/src/presentation/modals/stores/useModalStore.ts +90 -0
  220. package/src/presentation/modals/types/index.ts +2 -0
  221. package/src/presentation/modals/types/modal.type.ts +33 -0
  222. package/src/presentation/module-menubar.tsx +300 -0
  223. package/src/presentation/module-widget.tsx +233 -0
  224. package/src/presentation/nav-documents.tsx +89 -0
  225. package/src/presentation/nav-main.tsx +62 -0
  226. package/src/presentation/nav-secondary.tsx +41 -0
  227. package/src/presentation/nav-user.tsx +109 -0
  228. package/src/presentation/permission-action-badge.tsx +35 -0
  229. package/src/presentation/reui/index.tsx +3 -0
  230. package/src/presentation/reui/stepper.tsx +508 -0
  231. package/src/presentation/reui/timeline.tsx +226 -0
  232. package/src/presentation/sheets/index.tsx +2 -0
  233. package/src/presentation/sheets/legacy-sheet-header.tsx +0 -0
  234. package/src/presentation/sheets/legacy-sheet.tsx +37 -0
  235. package/src/presentation/size.enum.ts +19 -0
  236. package/src/presentation/system/index.tsx +3 -0
  237. package/src/presentation/system/logo.theme.tsx +21 -0
  238. package/src/presentation/system/types.ts +10 -0
  239. package/src/presentation/theme/index.tsx +6 -0
  240. package/src/presentation/theme/prefer-color-scheme.tsx +50 -0
  241. package/src/presentation/theme/switch-color-scheme.tsx +16 -0
  242. package/src/presentation/theme/theme-switcher-button.tsx +20 -0
  243. package/src/presentation/theme/theme.enum.ts +4 -0
  244. package/src/presentation/theme/theme.store.ts +87 -0
  245. package/src/presentation/themes/index.tsx +2 -0
  246. package/src/presentation/themes/katon/dashboard-layout.tsx +1080 -0
  247. package/src/presentation/themes/katon/edge-section.tsx +19 -0
  248. package/src/presentation/themes/katon/header-menubar.tsx +270 -0
  249. package/src/presentation/themes/katon/header-modules-available.tsx +44 -0
  250. package/src/presentation/themes/katon/header-modules-recents.tsx +52 -0
  251. package/src/presentation/themes/katon/header-modules-store.tsx +25 -0
  252. package/src/presentation/themes/katon/header-options.tsx +13 -0
  253. package/src/presentation/themes/katon/header-organization-select.tsx +146 -0
  254. package/src/presentation/themes/katon/header-routines.tsx +74 -0
  255. package/src/presentation/themes/katon/header-tasks-connected-user.tsx +150 -0
  256. package/src/presentation/themes/katon/index.tsx +16 -0
  257. package/src/presentation/themes/katon/main-wrapper.tsx +34 -0
  258. package/src/presentation/themes/katon/module-item-link.tsx +32 -0
  259. package/src/presentation/themes/katon/view-frame.tsx +25 -0
  260. package/src/presentation/themes/katon/view-helmet.tsx +53 -0
  261. package/src/presentation/themes/katon/view-status.tsx +32 -0
  262. package/src/presentation/themes/katon/view-wrapper.tsx +15 -0
  263. package/src/presentation/themes/katon/view.tsx +42 -0
  264. package/src/presentation/ui/accordion-multiselect.tsx +206 -0
  265. package/src/presentation/ui/accordion.tsx +81 -0
  266. package/src/presentation/ui/alert-dialog.tsx +199 -0
  267. package/src/presentation/ui/alert.tsx +76 -0
  268. package/src/presentation/ui/aspect-ratio.tsx +11 -0
  269. package/src/presentation/ui/attachment.tsx +204 -0
  270. package/src/presentation/ui/avatar.tsx +112 -0
  271. package/src/presentation/ui/badge.tsx +49 -0
  272. package/src/presentation/ui/breadcrumb.tsx +122 -0
  273. package/src/presentation/ui/bubble.tsx +125 -0
  274. package/src/presentation/ui/button-group.tsx +83 -0
  275. package/src/presentation/ui/button.tsx +87 -0
  276. package/src/presentation/ui/calendar.tsx +222 -0
  277. package/src/presentation/ui/card.tsx +106 -0
  278. package/src/presentation/ui/carousel.tsx +242 -0
  279. package/src/presentation/ui/chart.tsx +373 -0
  280. package/src/presentation/ui/checkbox.tsx +33 -0
  281. package/src/presentation/ui/clickable.tsx +58 -0
  282. package/src/presentation/ui/collapsible.tsx +33 -0
  283. package/src/presentation/ui/color-swatch-selector.tsx +81 -0
  284. package/src/presentation/ui/combobox.tsx +299 -0
  285. package/src/presentation/ui/command.tsx +195 -0
  286. package/src/presentation/ui/context-menu.tsx +263 -0
  287. package/src/presentation/ui/dialog.tsx +168 -0
  288. package/src/presentation/ui/direction.tsx +22 -0
  289. package/src/presentation/ui/drawer.tsx +134 -0
  290. package/src/presentation/ui/dropdown-menu.tsx +269 -0
  291. package/src/presentation/ui/empty.tsx +104 -0
  292. package/src/presentation/ui/field.tsx +238 -0
  293. package/src/presentation/ui/granularity-selector.tsx +58 -0
  294. package/src/presentation/ui/hover-card.tsx +44 -0
  295. package/src/presentation/ui/index.tsx +74 -0
  296. package/src/presentation/ui/input-group.tsx +156 -0
  297. package/src/presentation/ui/input-otp.tsx +87 -0
  298. package/src/presentation/ui/input.tsx +19 -0
  299. package/src/presentation/ui/item.tsx +196 -0
  300. package/src/presentation/ui/kpi-card.tsx +203 -0
  301. package/src/presentation/ui/label-selector.tsx +100 -0
  302. package/src/presentation/ui/label.tsx +24 -0
  303. package/src/presentation/ui/legacy-birth-date-input.tsx +43 -0
  304. package/src/presentation/ui/legacy-color-picker/index.tsx +42 -0
  305. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.content.tsx +37 -0
  306. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.context.tsx +42 -0
  307. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.hex-input.tsx +97 -0
  308. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.picker.tsx +266 -0
  309. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.presets.ts +22 -0
  310. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.quick-list.tsx +49 -0
  311. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.recent.tsx +43 -0
  312. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.swatches.tsx +111 -0
  313. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.trigger.tsx +128 -0
  314. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.tsx +110 -0
  315. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.types.ts +1 -0
  316. package/src/presentation/ui/legacy-color-picker/legacy-color-picker.utils.ts +114 -0
  317. package/src/presentation/ui/legacy-country-input.tsx +171 -0
  318. package/src/presentation/ui/legacy-gender-input.tsx +22 -0
  319. package/src/presentation/ui/legacy-input.tsx +106 -0
  320. package/src/presentation/ui/legacy-phone-input.tsx +213 -0
  321. package/src/presentation/ui/legacy-select-input.tsx +114 -0
  322. package/src/presentation/ui/legacy-text-area.tsx +111 -0
  323. package/src/presentation/ui/list-items.tsx +59 -0
  324. package/src/presentation/ui/marker.tsx +69 -0
  325. package/src/presentation/ui/media-player/index.tsx +12 -0
  326. package/src/presentation/ui/media-player/media-player.context.tsx +36 -0
  327. package/src/presentation/ui/media-player/media-player.tsx +167 -0
  328. package/src/presentation/ui/media-player/player-controls.tsx +207 -0
  329. package/src/presentation/ui/media-player/players/audio-player.tsx +78 -0
  330. package/src/presentation/ui/media-player/players/index.tsx +4 -0
  331. package/src/presentation/ui/media-player/players/video-player.tsx +193 -0
  332. package/src/presentation/ui/media-player/players/voice-note-player.tsx +108 -0
  333. package/src/presentation/ui/media-player/use-media-player.hook.ts +263 -0
  334. package/src/presentation/ui/menubar.tsx +281 -0
  335. package/src/presentation/ui/message.tsx +92 -0
  336. package/src/presentation/ui/navigation-menu.tsx +172 -0
  337. package/src/presentation/ui/pagination.tsx +129 -0
  338. package/src/presentation/ui/popover.tsx +89 -0
  339. package/src/presentation/ui/progress.tsx +31 -0
  340. package/src/presentation/ui/resizable.tsx +50 -0
  341. package/src/presentation/ui/select.tsx +192 -0
  342. package/src/presentation/ui/separator.tsx +28 -0
  343. package/src/presentation/ui/sheet.tsx +147 -0
  344. package/src/presentation/ui/sidebar.tsx +702 -0
  345. package/src/presentation/ui/skeleton.tsx +13 -0
  346. package/src/presentation/ui/slide-to-confirm.tsx +129 -0
  347. package/src/presentation/ui/slider.tsx +59 -0
  348. package/src/presentation/ui/sonner.tsx +58 -0
  349. package/src/presentation/ui/switch.tsx +33 -0
  350. package/src/presentation/ui/table.tsx +116 -0
  351. package/src/presentation/ui/tabs.tsx +90 -0
  352. package/src/presentation/ui/text-gradient.tsx +84 -0
  353. package/src/presentation/ui/textarea.tsx +18 -0
  354. package/src/presentation/ui/timeline-steps.tsx +235 -0
  355. package/src/presentation/ui/toggle-group.tsx +89 -0
  356. package/src/presentation/ui/toggle.tsx +47 -0
  357. package/src/presentation/ui/tooltip.tsx +57 -0
  358. package/src/presentation/uploading/index.tsx +3 -0
  359. package/src/presentation/uploading/media-upload-field.tsx +152 -0
  360. package/src/presentation/uploading/storage-media.tsx +210 -0
  361. package/src/styles.css +403 -0
  362. package/src/themes.css +1038 -0
@@ -0,0 +1,46 @@
1
+ "use client"
2
+
3
+ import {useRouter} from "next/navigation"
4
+ import {Button} from "../ui/button"
5
+ import {Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle} from "../ui/empty"
6
+ import {DynamicIcon} from "./dynamic-icon"
7
+ import {ModuleDeclarationInterface} from "../../domain/entities/module.interface"
8
+ import {TriangleAlert} from "lucide-react";
9
+
10
+ interface ModulesGuardForbiddenProps {
11
+ module?: ModuleDeclarationInterface | null
12
+ }
13
+
14
+ export function ModulesGuardForbiddenView({module}: ModulesGuardForbiddenProps) {
15
+ const router = useRouter()
16
+
17
+ return (
18
+ <div className="flex min-h-svh items-center justify-center p-4">
19
+ <Empty className="max-w-md">
20
+ <EmptyMedia variant="icon" className="size-24 rounded-full aspect-square">
21
+ <DynamicIcon strokeWidth={1} name="ShieldAlertIcon" className="size-18 text-primary"/>
22
+ </EmptyMedia>
23
+ <EmptyHeader>
24
+ <EmptyTitle>Accès refusé</EmptyTitle>
25
+ </EmptyHeader>
26
+ <EmptyDescription>
27
+ {module
28
+ ? <>Vous n&apos;avez pas la permission d&apos;accéder au module «&nbsp;{module.name}&nbsp;».</>
29
+ : <>Vous n&apos;avez pas la permission d&apos;accéder à ce module.</>}
30
+ </EmptyDescription>
31
+ <EmptyDescription className={"p-3"}>
32
+ Si vous avez le droit d'accès, cela peut être du problème de votre connexion internet ou
33
+ renseignez-vous auprès de votre administrateur.
34
+ </EmptyDescription>
35
+ <div className="flex gap-4">
36
+ <Button variant="outline" size="lg" onClick={() => history.go(0)}>
37
+ Actualiser
38
+ </Button>
39
+ <Button size="lg" onClick={() => router.push('/dashboard')}>
40
+ Retour au tableau de bord
41
+ </Button>
42
+ </div>
43
+ </Empty>
44
+ </div>
45
+ )
46
+ }
@@ -0,0 +1,145 @@
1
+ "use client"
2
+
3
+ import {useState} from "react";
4
+ import {useQueryClient} from "@tanstack/react-query";
5
+ import {format} from "date-fns";
6
+ import {fr} from "date-fns/locale";
7
+ import {AlertTriangleIcon, BellRingIcon, CheckIcon, CircleCheckIcon, InfoIcon, MailOpenIcon, ShieldAlertIcon, TimerIcon} from "lucide-react";
8
+ import {toast} from "sonner";
9
+ import {Badge} from "../ui/badge";
10
+ import {Button} from "../ui/button";
11
+ import {Separator} from "../ui/separator";
12
+ import {NotificationsApiService} from "../../application/service/notifications-api-service";
13
+ import {NotificationInterface, NotificationTypeEnum} from "../../domain/entities/notification.interface";
14
+
15
+ export interface NotificationDetailsModalProps {
16
+ notification: NotificationInterface;
17
+ close?: () => void;
18
+ }
19
+
20
+ const notificationTypeIcon = {
21
+ [NotificationTypeEnum.ALERT]: AlertTriangleIcon,
22
+ [NotificationTypeEnum.INFORMATION]: InfoIcon,
23
+ [NotificationTypeEnum.CONFIRMATION]: CircleCheckIcon,
24
+ [NotificationTypeEnum.REMINDER]: TimerIcon,
25
+ [NotificationTypeEnum.SECURITY]: ShieldAlertIcon,
26
+ };
27
+
28
+ const notificationTypeLabel = {
29
+ [NotificationTypeEnum.ALERT]: "Alerte",
30
+ [NotificationTypeEnum.INFORMATION]: "Information",
31
+ [NotificationTypeEnum.CONFIRMATION]: "Confirmation",
32
+ [NotificationTypeEnum.REMINDER]: "Rappel",
33
+ [NotificationTypeEnum.SECURITY]: "Sécurité",
34
+ };
35
+
36
+ const notificationTypeVariant = {
37
+ [NotificationTypeEnum.ALERT]: "destructive" as const,
38
+ [NotificationTypeEnum.INFORMATION]: "secondary" as const,
39
+ [NotificationTypeEnum.CONFIRMATION]: "default" as const,
40
+ [NotificationTypeEnum.REMINDER]: "outline" as const,
41
+ [NotificationTypeEnum.SECURITY]: "destructive" as const,
42
+ };
43
+
44
+ function formatDateTime(date?: string | null): string {
45
+ if (!date) return "Non renseigné";
46
+ try {
47
+ return format(new Date(date), "d MMMM yyyy 'à' HH:mm", {locale: fr});
48
+ } catch {
49
+ return "Date invalide";
50
+ }
51
+ }
52
+
53
+ export function NotificationDetailsModal({notification, close}: NotificationDetailsModalProps) {
54
+ const queryClient = useQueryClient();
55
+ const [isRead, setIsRead] = useState(!!notification.readAt);
56
+
57
+ const title = notification.title ?? notification.subject ?? "Notification";
58
+ const body = notification.body ?? notification.message ?? notification.content ?? "Aucun contenu";
59
+ const TypeIcon = notificationTypeIcon[(notification.type as NotificationTypeEnum) ?? NotificationTypeEnum.INFORMATION] ?? BellRingIcon;
60
+
61
+ const markAsRead = async () => {
62
+ try {
63
+ await NotificationsApiService.markAsRead(notification.id);
64
+ setIsRead(true);
65
+ await queryClient.invalidateQueries({queryKey: ['notifications']});
66
+ } catch (error: any) {
67
+ toast.error("Impossible de marquer la notification comme lue");
68
+ }
69
+ };
70
+
71
+ return (
72
+ <div className="flex flex-col">
73
+ <div className="flex items-start gap-3 p-4 pb-3">
74
+ <div className="flex size-11 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
75
+ <TypeIcon className="size-5.5"/>
76
+ </div>
77
+
78
+ <div className="min-w-0 flex-1">
79
+ <h3 className="text-lg font-semibold leading-snug break-words">
80
+ {title}
81
+ </h3>
82
+ <div className="mt-1.5 flex flex-wrap items-center gap-2">
83
+ {notification.type && (
84
+ <Badge variant={notificationTypeVariant[(notification.type as NotificationTypeEnum) ?? NotificationTypeEnum.INFORMATION]}>
85
+ {notificationTypeLabel[(notification.type as NotificationTypeEnum) ?? NotificationTypeEnum.INFORMATION]}
86
+ </Badge>
87
+ )}
88
+ {isRead ? (
89
+ <Badge variant="secondary" className="gap-1">
90
+ <MailOpenIcon/>
91
+ Lue
92
+ </Badge>
93
+ ) : (
94
+ <Badge variant="outline" className="gap-1">
95
+ Nouvelle
96
+ </Badge>
97
+ )}
98
+ </div>
99
+ </div>
100
+ </div>
101
+
102
+ <Separator/>
103
+
104
+ <div className="p-4">
105
+ <p className="text-sm leading-relaxed whitespace-pre-wrap break-words text-foreground/90">
106
+ {body}
107
+ </p>
108
+
109
+ <div className="mt-5 grid grid-cols-1 gap-3 sm:grid-cols-2">
110
+ <DetailField label="Reçue le" value={formatDateTime(notification.createdAt)}/>
111
+ <DetailField label="Lue le" value={formatDateTime(notification.readAt ?? (isRead ? notification.updatedAt : null))}/>
112
+ <DetailField label="Organisation" value={notification.organizationId ?? "—"}/>
113
+ <DetailField label="ID" value={notification.id}/>
114
+ </div>
115
+ </div>
116
+
117
+ <Separator/>
118
+
119
+ <div className="flex items-center justify-end gap-2 p-3">
120
+ {!isRead && (
121
+ <Button variant="outline" className="gap-1.5" onClick={markAsRead}>
122
+ <CheckIcon/>
123
+ Marquer comme lue
124
+ </Button>
125
+ )}
126
+ <Button variant="default" onClick={close}>
127
+ Fermer
128
+ </Button>
129
+ </div>
130
+ </div>
131
+ );
132
+ }
133
+
134
+ function DetailField({label, value}: { label: string; value: string }) {
135
+ return (
136
+ <div className="flex flex-col gap-0.5 rounded-lg bg-muted/40 px-3 py-2">
137
+ <span className="text-[10px] font-medium text-muted-foreground uppercase tracking-tight">
138
+ {label}
139
+ </span>
140
+ <span className="text-sm font-medium break-words">
141
+ {value}
142
+ </span>
143
+ </div>
144
+ );
145
+ }
@@ -0,0 +1,127 @@
1
+ "use client"
2
+
3
+ import {formatDistanceToNow} from "date-fns";
4
+ import {fr} from "date-fns/locale";
5
+ import {AlertTriangleIcon, BellRingIcon, CheckIcon, CircleCheckIcon, InfoIcon, MailOpenIcon, ShieldAlertIcon, TimerIcon} from "lucide-react";
6
+ import {Badge} from "../ui/badge";
7
+ import {Button} from "../ui/button";
8
+ import {useModal} from "../modals/hooks/useModal";
9
+ import {cn} from "../../infrastructure/utilities/utils";
10
+ import {NotificationDetailsModal, NotificationDetailsModalProps} from "./notification-details.modal";
11
+ import {NotificationInterface, NotificationTypeEnum} from "../../domain/entities/notification.interface";
12
+
13
+ export interface NotificationItemProps {
14
+ notification: NotificationInterface;
15
+ onRead?: (notification: NotificationInterface) => void;
16
+ }
17
+
18
+ const notificationTypeIcon = {
19
+ [NotificationTypeEnum.ALERT]: AlertTriangleIcon,
20
+ [NotificationTypeEnum.INFORMATION]: InfoIcon,
21
+ [NotificationTypeEnum.CONFIRMATION]: CircleCheckIcon,
22
+ [NotificationTypeEnum.REMINDER]: TimerIcon,
23
+ [NotificationTypeEnum.SECURITY]: ShieldAlertIcon,
24
+ };
25
+
26
+ const notificationTypeLabel = {
27
+ [NotificationTypeEnum.ALERT]: "Alerte",
28
+ [NotificationTypeEnum.INFORMATION]: "Information",
29
+ [NotificationTypeEnum.CONFIRMATION]: "Confirmation",
30
+ [NotificationTypeEnum.REMINDER]: "Rappel",
31
+ [NotificationTypeEnum.SECURITY]: "Sécurité",
32
+ };
33
+
34
+ export function NotificationItem({notification, onRead}: NotificationItemProps) {
35
+ const {open, close} = useModal();
36
+ const title = notification.title ?? notification.subject ?? "Notification";
37
+ const body = notification.body ?? notification.message ?? notification.content;
38
+ const isRead = notification.readAt ?? false;
39
+ const TypeIcon = notificationTypeIcon[(notification.type as NotificationTypeEnum) ?? NotificationTypeEnum.INFORMATION] ?? BellRingIcon;
40
+
41
+ const createdAt = notification.createdAt ? formatDistanceToNow(new Date(notification.createdAt), {addSuffix: true, locale: fr}) : undefined;
42
+
43
+ const openDetails = () => {
44
+ const modalId = open(
45
+ (props: NotificationDetailsModalProps) => (
46
+ <NotificationDetailsModal
47
+ notification={props.notification}
48
+ close={() => close(modalId)}
49
+ />
50
+ ),
51
+ {notification, close: () => close(modalId)},
52
+ {
53
+ size: "MD",
54
+ scrollable: true,
55
+ className: "rounded-lg",
56
+ }
57
+ );
58
+ };
59
+
60
+ return (
61
+ <div
62
+ onClick={openDetails}
63
+ className={cn(
64
+ "group/notification flex flex-row gap-3 rounded-lg border border-transparent p-2.5 transition-colors cursor-pointer",
65
+ "hover:bg-muted/60 hover:border-foreground/10",
66
+ !isRead && "bg-primary/5 border-primary/10"
67
+ )}
68
+ >
69
+ <div className="flex size-9 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground">
70
+ <TypeIcon className="size-4.5"/>
71
+ </div>
72
+
73
+ <div className="min-w-0 flex-1">
74
+ <div className="flex items-start justify-between gap-2">
75
+ <p className={cn(
76
+ "truncate text-sm font-medium",
77
+ !isRead && "text-foreground"
78
+ )}>
79
+ {title}
80
+ </p>
81
+ {!isRead && (
82
+ <Badge className="shrink-0" variant="default">
83
+ Nouveau
84
+ </Badge>
85
+ )}
86
+ </div>
87
+
88
+ {body && (
89
+ <p className="mt-0.5 line-clamp-2 text-xs text-muted-foreground">
90
+ {body}
91
+ </p>
92
+ )}
93
+
94
+ <div className="mt-1.5 flex flex-row items-center gap-2">
95
+ {notification.type && (
96
+ <Badge variant="outline" className="h-4 text-[10px]">
97
+ {notificationTypeLabel[notification.type as NotificationTypeEnum] ?? notification.type}
98
+ </Badge>
99
+ )}
100
+ {createdAt && (
101
+ <span className="text-[11px] text-muted-foreground">
102
+ {createdAt}
103
+ </span>
104
+ )}
105
+ {!isRead && onRead && (
106
+ <Button
107
+ size="sm"
108
+ variant="ghost"
109
+ className="ml-auto h-6 gap-1 px-1.5 text-[11px] text-muted-foreground"
110
+ onClick={(event) => {
111
+ event.stopPropagation();
112
+ onRead(notification);
113
+ }}
114
+ >
115
+ <CheckIcon/>
116
+ Marquer lu
117
+ </Button>
118
+ )}
119
+ </div>
120
+ </div>
121
+
122
+ {isRead && (
123
+ <MailOpenIcon className="size-4 shrink-0 self-center text-muted-foreground/40"/>
124
+ )}
125
+ </div>
126
+ );
127
+ }
@@ -0,0 +1,128 @@
1
+ "use client"
2
+
3
+ import {useQueryClient} from "@tanstack/react-query";
4
+ import {BellIcon, InboxIcon} from "lucide-react";
5
+ import Link from "next/link";
6
+ import {toast} from "sonner";
7
+ import {Button} from "../ui/button";
8
+ import {Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger} from "../ui/popover";
9
+ import {Tabs, TabsContent, TabsList, TabsTrigger} from "../ui/tabs";
10
+ import {Skeleton} from "../ui/skeleton";
11
+ import {Badge} from "../ui/badge";
12
+ import {NotificationItem} from "./notification-item";
13
+ import {useNotifications} from "../../infrastructure/hooks/use-notifications";
14
+ import {NotificationsApiService} from "../../application/service/notifications-api-service";
15
+ import {NotificationInterface} from "../../domain/entities/notification.interface";
16
+
17
+ function NotificationsList({category}: { category: "user" | "organization" }) {
18
+ const {data, isLoading, isError, refetch} = useNotifications(category, {
19
+ limit: 5,
20
+ manualPagination: false,
21
+ read: false
22
+ });
23
+ const queryClient = useQueryClient();
24
+
25
+ const markAsRead = async (notification: NotificationInterface) => {
26
+ try {
27
+ await NotificationsApiService.markAsRead(notification.id);
28
+ await queryClient.invalidateQueries({queryKey: ['notifications']});
29
+ } catch (error: any) {
30
+ toast.error("Impossible de marquer la notification comme lue");
31
+ }
32
+ };
33
+
34
+ if (isLoading) {
35
+ return (
36
+ <div className="flex flex-col gap-2">
37
+ <Skeleton className="h-12 w-full"/>
38
+ <Skeleton className="h-12 w-full"/>
39
+ <Skeleton className="h-12 w-full"/>
40
+ </div>
41
+ );
42
+ }
43
+
44
+ if (isError || data.length === 0) {
45
+ return (
46
+ <div className="flex flex-col items-center gap-2 py-6 text-center text-muted-foreground">
47
+ <InboxIcon className="size-8 opacity-40"/>
48
+ <p className="text-xs">Aucune notification</p>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ return (
54
+ <div className="flex flex-col gap-1">
55
+ {data.slice(0, 5).map((notification) => (
56
+ <NotificationItem
57
+ key={notification.id}
58
+ notification={notification}
59
+ onRead={markAsRead}
60
+ />
61
+ ))}
62
+ </div>
63
+ );
64
+ }
65
+
66
+ export function NotificationsPopover() {
67
+ const {data: userNotifications} = useNotifications("user", {
68
+ limit: 10,
69
+ manualPagination: false,
70
+ read: false
71
+ });
72
+
73
+ const unreadCount = userNotifications.filter((notification) => !notification.readAt).length;
74
+
75
+ return (
76
+ <Popover>
77
+ <PopoverTrigger asChild>
78
+ <Button variant="ghost" className="relative w-8 h-8" aria-label="Notifications">
79
+ <BellIcon/>
80
+ {unreadCount > 0 && (
81
+ <Badge className="absolute -top-0.5 -right-0.5 h-4 min-w-4 px-1 text-[10px]">
82
+ {unreadCount > 9 ? "9+" : unreadCount}
83
+ </Badge>
84
+ )}
85
+ </Button>
86
+ </PopoverTrigger>
87
+ <PopoverContent
88
+ className="w-96 p-0"
89
+ side={"bottom"} align={"end"}
90
+ sideOffset={8}
91
+ >
92
+ <PopoverHeader className="px-4 pt-3.5 pb-1">
93
+ <PopoverTitle>Notifications</PopoverTitle>
94
+ </PopoverHeader>
95
+
96
+ <Tabs defaultValue="user">
97
+ <div className="px-3">
98
+ <TabsList className="w-full">
99
+ <TabsTrigger value="user" className="flex-1">
100
+ Utilisateur
101
+ </TabsTrigger>
102
+ <TabsTrigger value="organization" className="flex-1">
103
+ Organisation
104
+ </TabsTrigger>
105
+ </TabsList>
106
+ </div>
107
+
108
+ <div className="max-h-80 overflow-y-auto p-2">
109
+ <TabsContent value="user">
110
+ <NotificationsList category="user"/>
111
+ </TabsContent>
112
+ <TabsContent value="organization">
113
+ <NotificationsList category="organization"/>
114
+ </TabsContent>
115
+ </div>
116
+ </Tabs>
117
+
118
+ <div className="border-t p-2">
119
+ <Button asChild variant="ghost" className="w-full justify-center gap-1 text-xs">
120
+ <Link href="/notifications">
121
+ Voir toutes les notifications
122
+ </Link>
123
+ </Button>
124
+ </div>
125
+ </PopoverContent>
126
+ </Popover>
127
+ );
128
+ }
@@ -0,0 +1,65 @@
1
+ "use client"
2
+
3
+ import {useEffect} from "react";
4
+ import {toast} from "sonner";
5
+ import {useAuth} from "../../infrastructure/hooks/use-auth";
6
+ import {NotificationsPushService} from "../../application/service/notifications-push.service";
7
+ import {UserPreferencesApiService} from "../../application/service/user-preferences-api.service";
8
+
9
+ export function PushNotificationsProvider() {
10
+ const {user, currentOrganization} = useAuth();
11
+
12
+ useEffect(() => {
13
+ if (!user) {
14
+ NotificationsPushService.stopActivityTracking();
15
+ NotificationsPushService.stopPolling();
16
+ NotificationsPushService.unsubscribe().catch(() => undefined);
17
+ return;
18
+ }
19
+
20
+ // Les notifications sont rattachées à une organisation : on attend la
21
+ // sélection de l'organisation avant d'initialiser le canal push, sinon
22
+ // les appels (préférences, liste des notifications) partent sans
23
+ // contexte d'organisation (x-organization-id) et sont rejetés en 400.
24
+ if (!currentOrganization) {
25
+ return;
26
+ }
27
+
28
+ const offInApp = NotificationsPushService.onInAppNotification((payload) => {
29
+ toast(payload.title || "Nouvelle notification", {
30
+ description: payload.body || "",
31
+ richColors: true,
32
+ });
33
+ });
34
+
35
+ NotificationsPushService.register()
36
+ .then(async () => {
37
+ NotificationsPushService.startActivityTracking();
38
+
39
+ const pushEnabled = (await UserPreferencesApiService
40
+ .getValue(user.id!, NotificationsPushService.PUSH_PREFERENCE_LABEL)
41
+ .catch(() => undefined)) !== "false";
42
+
43
+ if (!pushEnabled) return;
44
+
45
+ if (NotificationsPushService.isSupported) {
46
+ if (Notification.permission !== "granted") return;
47
+ const subscription = await NotificationsPushService.getSubscription();
48
+ if (!subscription) await NotificationsPushService.subscribe();
49
+ return;
50
+ }
51
+
52
+ // Web Push indisponible (ex: Tauri) : canal REST de secours
53
+ await NotificationsPushService.startPolling();
54
+ })
55
+ .catch((error) => console.error("Push notifications initialization failed", error));
56
+
57
+ return () => {
58
+ offInApp();
59
+ NotificationsPushService.stopActivityTracking();
60
+ NotificationsPushService.stopPolling();
61
+ };
62
+ }, [user, currentOrganization]);
63
+
64
+ return null;
65
+ }
@@ -0,0 +1,140 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { CheckCircle2, CloudUpload, XCircle } from "lucide-react"
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogFooter,
10
+ DialogHeader,
11
+ DialogTitle,
12
+ } from "../ui/dialog"
13
+ import { Button } from "../ui/button"
14
+ import { Activity } from "./activity"
15
+ import { Progress } from "../ui/progress"
16
+ import { useUploadStore, type UploadItem } from "../../infrastructure/stores/upload.store"
17
+ import { formatEta, formatFileSize } from "../../infrastructure/utilities/format.util"
18
+
19
+ interface UploadProgressDialogProps {
20
+ open: boolean
21
+ onOpenChange: (open: boolean) => void
22
+ onAllComplete?: () => void
23
+ }
24
+
25
+ function UploadProgressRow({ upload }: { upload: UploadItem }) {
26
+ const isUploading = upload.status === "uploading"
27
+ const isDone = upload.status === "done"
28
+
29
+ return (
30
+ <div className="flex flex-col gap-1.5 rounded-lg border border-border/60 bg-muted/30 p-2.5">
31
+ <div className="flex items-center gap-2 min-w-0">
32
+ {isDone ? (
33
+ <CheckCircle2 className="size-4 shrink-0 text-emerald-500"/>
34
+ ) : upload.status === "error" ? (
35
+ <XCircle className="size-4 shrink-0 text-destructive"/>
36
+ ) : (
37
+ <Activity.Loader size={16}/>
38
+ )}
39
+ <span className="min-w-0 flex-1 truncate text-sm font-medium">{upload.name}</span>
40
+ <span className="shrink-0 text-xs tabular-nums text-muted-foreground">
41
+ {formatFileSize(upload.size)}
42
+ </span>
43
+ </div>
44
+ {isUploading && (
45
+ <div className="flex items-center gap-2">
46
+ <Progress value={upload.progress} className="h-1.5 flex-1"/>
47
+ <span className="w-11 text-right text-xs tabular-nums text-muted-foreground">
48
+ {Math.round(upload.progress)}%
49
+ </span>
50
+ </div>
51
+ )}
52
+ {isUploading && (
53
+ <span className="text-xs text-muted-foreground">
54
+ {upload.eta ? formatEta(upload.eta) : "Estimation du temps restant…"}
55
+ </span>
56
+ )}
57
+ {upload.status === "error" && (
58
+ <span className="text-xs text-destructive line-clamp-1">{upload.error}</span>
59
+ )}
60
+ </div>
61
+ )
62
+ }
63
+
64
+ export function UploadProgressDialog({ open, onOpenChange, onAllComplete }: UploadProgressDialogProps) {
65
+ const uploads = useUploadStore((state) => state.uploads)
66
+ const hasActiveUploads = useUploadStore((state) => state.hasActiveUploads)
67
+ const didAutoComplete = React.useRef(false)
68
+
69
+ const activeUploads = uploads.filter((u) => u.status === "uploading" || u.status === "idle")
70
+ const failedCount = uploads.filter((u) => u.status === "error").length
71
+
72
+ React.useEffect(() => {
73
+ if (!open) {
74
+ didAutoComplete.current = false
75
+ return
76
+ }
77
+ if (open && !hasActiveUploads && failedCount === 0 && !didAutoComplete.current) {
78
+ didAutoComplete.current = true
79
+ onAllComplete?.()
80
+ }
81
+ }, [open, hasActiveUploads, failedCount, onAllComplete])
82
+
83
+ const handleComplete = () => {
84
+ didAutoComplete.current = true
85
+ onOpenChange(false)
86
+ onAllComplete?.()
87
+ }
88
+
89
+ return (
90
+ <Dialog open={open} onOpenChange={onOpenChange}>
91
+ <DialogContent className="sm:max-w-md" showCloseButton={false}>
92
+ <DialogHeader>
93
+ <DialogTitle className="flex items-center gap-2">
94
+ <CloudUpload className="size-5 text-primary"/>
95
+ Téléversement en cours
96
+ </DialogTitle>
97
+ <DialogDescription>
98
+ {hasActiveUploads
99
+ ? "Certains fichiers sont encore en cours de téléversement. Veuillez patienter."
100
+ : failedCount > 0
101
+ ? "Certains fichiers n'ont pas pu être téléversés."
102
+ : "Tous les fichiers ont été téléversés avec succès."}
103
+ </DialogDescription>
104
+ </DialogHeader>
105
+
106
+ <div className="flex max-h-64 flex-col gap-2 overflow-y-auto">
107
+ {activeUploads.length === 0 && failedCount === 0 && (
108
+ <p className="text-sm text-muted-foreground">
109
+ Aucun téléversement en attente.
110
+ </p>
111
+ )}
112
+ {activeUploads.map((upload) => (
113
+ <UploadProgressRow key={upload.id} upload={upload}/>
114
+ ))}
115
+ {uploads
116
+ .filter((u) => u.status === "error")
117
+ .map((upload) => (
118
+ <UploadProgressRow key={upload.id} upload={upload}/>
119
+ ))}
120
+ </div>
121
+
122
+ <DialogFooter>
123
+ <Button
124
+ variant="ghost"
125
+ onClick={() => {
126
+ didAutoComplete.current = true
127
+ onOpenChange(false)
128
+ }}
129
+ disabled={hasActiveUploads}
130
+ >
131
+ Fermer
132
+ </Button>
133
+ <Button onClick={handleComplete} disabled={hasActiveUploads || failedCount > 0}>
134
+ Terminer
135
+ </Button>
136
+ </DialogFooter>
137
+ </DialogContent>
138
+ </Dialog>
139
+ )
140
+ }
@@ -0,0 +1,22 @@
1
+ export function WaitingBar() {
2
+ return (
3
+ <div className="w-full h-[3px] relative rounded-xl overflow-hidden bg-black/20">
4
+ <div
5
+ className="absolute top-0 -left-[100%] w-full h-full bg-primary rounded-xl animate-progress-infinite"
6
+ style={{ animationDelay: "0ms" }}
7
+ />
8
+ <div
9
+ className="absolute top-0 -left-[100%] w-full h-full bg-(--chart-2) rounded-xl animate-progress-infinite"
10
+ style={{ animationDelay: "750ms" }}
11
+ />
12
+ <div
13
+ className="absolute top-0 -left-[100%] w-full h-full bg-(--chart-3) rounded-xl animate-progress-infinite"
14
+ style={{ animationDelay: "100ms" }}
15
+ />
16
+ <div
17
+ className="absolute top-0 -left-[100%] w-full h-full bg-(--chart-4) rounded-xl animate-progress-infinite"
18
+ style={{ animationDelay: "300ms" }}
19
+ />
20
+ </div>
21
+ )
22
+ }