@edifice.io/react 2.6.2 → 2.6.3-develop-enabling.20260825194305

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 (77) hide show
  1. package/dist/components/Alert/Alert.js +1 -1
  2. package/dist/components/DatePicker/DatePicker.d.ts +5 -0
  3. package/dist/components/DatePicker/DatePicker.js +2 -0
  4. package/dist/components/Layout/Layout.js +5 -3
  5. package/dist/components/Layout/components/NavLink.d.ts +1 -1
  6. package/dist/components/Layout/components/NavLink.js +2 -0
  7. package/dist/hooks/useClickOutside/useClickOutside.d.ts +1 -1
  8. package/dist/hooks/useClickOutside/useClickOutside.js +1 -1
  9. package/dist/icons-apps.js +112 -100
  10. package/dist/icons.js +340 -338
  11. package/dist/modules/editor/test-utils/createTestEditor.d.ts +10 -0
  12. package/dist/modules/homepage/components/Communities/Communities.d.ts +10 -0
  13. package/dist/modules/homepage/components/Communities/CommunitiesContainer.d.ts +9 -0
  14. package/dist/modules/homepage/components/Communities/CommunitiesSkeleton.d.ts +5 -0
  15. package/dist/modules/homepage/components/Communities/CommunityItem.d.ts +11 -0
  16. package/dist/modules/homepage/components/Communities/index.d.ts +8 -0
  17. package/dist/modules/homepage/components/Communities/useCommunities.d.ts +13 -0
  18. package/dist/modules/homepage/components/Favorites/Favorites.js +7 -4
  19. package/dist/modules/homepage/components/Header/Header.js +20 -2
  20. package/dist/modules/homepage/components/HomeCard/HomeCard.d.ts +5 -1
  21. package/dist/modules/homepage/components/HomeCard/HomeCard.js +10 -2
  22. package/dist/modules/homepage/components/LastInfos/LastInfosList.js +2 -2
  23. package/dist/modules/homepage/components/Notifications/NotificationItem.js +5 -4
  24. package/dist/modules/homepage/components/Notifications/NotificationList.d.ts +7 -1
  25. package/dist/modules/homepage/components/Notifications/NotificationList.js +10 -3
  26. package/dist/modules/homepage/components/Notifications/NotificationListContainer.js +4 -1
  27. package/dist/modules/homepage/components/Notifications/components/CommonNotificationItem.d.ts +2 -1
  28. package/dist/modules/homepage/components/Notifications/components/CommonNotificationItem.js +14 -10
  29. package/dist/modules/homepage/components/Notifications/components/NotificationFilterMenu.d.ts +16 -0
  30. package/dist/modules/homepage/components/Notifications/components/NotificationFilterMenu.js +29 -0
  31. package/dist/modules/homepage/components/Notifications/components/NotificationFilterModal.d.ts +18 -0
  32. package/dist/modules/homepage/components/Notifications/components/NotificationFilterModal.js +67 -0
  33. package/dist/modules/homepage/components/Notifications/components/NotificationItemResource.d.ts +2 -1
  34. package/dist/modules/homepage/components/Notifications/components/NotificationItemResource.js +3 -2
  35. package/dist/modules/homepage/components/Notifications/components/NotificationOptionsMenu.d.ts +12 -0
  36. package/dist/modules/homepage/components/Notifications/components/NotificationOptionsMenu.js +42 -0
  37. package/dist/modules/homepage/components/Notifications/components/ReportNotificationModal.d.ts +13 -0
  38. package/dist/modules/homepage/components/Notifications/components/ReportNotificationModal.js +31 -0
  39. package/dist/modules/homepage/components/Notifications/components/SystemNotificationItem.d.ts +3 -1
  40. package/dist/modules/homepage/components/Notifications/components/SystemNotificationItem.js +3 -2
  41. package/dist/modules/homepage/components/Notifications/components/UserNotificationItem.d.ts +3 -1
  42. package/dist/modules/homepage/components/Notifications/components/UserNotificationItem.js +3 -2
  43. package/dist/modules/homepage/components/Notifications/components/notificationAdapter.d.ts +22 -7
  44. package/dist/modules/homepage/components/Notifications/components/notificationAdapter.js +31 -13
  45. package/dist/modules/homepage/components/Notifications/hooks/useNotificationList.d.ts +11 -1
  46. package/dist/modules/homepage/components/Notifications/hooks/useNotificationList.js +34 -2
  47. package/dist/modules/homepage/components/Notifications/services/api/index.d.ts +2 -0
  48. package/dist/modules/homepage/components/Notifications/services/api/notificationService.d.ts +8 -0
  49. package/dist/modules/homepage/components/Notifications/services/api/notificationService.js +12 -0
  50. package/dist/modules/homepage/components/Notifications/services/queries/notification.d.ts +13 -1
  51. package/dist/modules/homepage/components/Notifications/services/queries/notification.js +41 -4
  52. package/dist/modules/homepage/components/SchoolSpace/SchoolSpace.js +20 -11
  53. package/dist/modules/homepage/components/UserSpace/UserSpace.js +5 -2
  54. package/dist/modules/homepage/components/UserSpace/hooks/useChildren.d.ts +14 -0
  55. package/dist/modules/homepage/components/UserSpace/hooks/useChildren.js +23 -0
  56. package/dist/modules/homepage/components/UserSpace/hooks/useProfileLinks.d.ts +8 -0
  57. package/dist/modules/homepage/components/UserSpace/hooks/useProfileLinks.js +65 -0
  58. package/dist/modules/icons/components/IconClass.d.ts +7 -0
  59. package/dist/modules/icons/components/IconClass.js +13 -0
  60. package/dist/modules/icons/components/apps/IconNextcloudDeck.d.ts +7 -0
  61. package/dist/modules/icons/components/apps/IconNextcloudDeck.js +12 -0
  62. package/dist/modules/icons/components/apps/IconNextcloudFiles.d.ts +7 -0
  63. package/dist/modules/icons/components/apps/IconNextcloudFiles.js +12 -0
  64. package/dist/modules/icons/components/apps/IconNextcloudForms.d.ts +7 -0
  65. package/dist/modules/icons/components/apps/IconNextcloudForms.js +12 -0
  66. package/dist/modules/icons/components/apps/IconNextcloudNotes.d.ts +7 -0
  67. package/dist/modules/icons/components/apps/IconNextcloudNotes.js +12 -0
  68. package/dist/modules/icons/components/apps/IconNextcloudTalk.d.ts +7 -0
  69. package/dist/modules/icons/components/apps/IconNextcloudTalk.js +12 -0
  70. package/dist/modules/icons/components/apps/IconPeertube.d.ts +7 -0
  71. package/dist/modules/icons/components/apps/IconPeertube.js +21 -0
  72. package/dist/modules/icons/components/apps/index.d.ts +6 -0
  73. package/dist/modules/icons/components/index.d.ts +1 -0
  74. package/dist/modules/multimedia/testing/mediaHarness.d.ts +154 -0
  75. package/package.json +6 -6
  76. package/dist/modules/multimedia/LinkerCard/LinkerCard._.d.ts +0 -8
  77. package/dist/modules/multimedia/UploadCard/UploadCard._.d.ts +0 -9
@@ -0,0 +1,10 @@
1
+ import { Editor, EditorOptions, Extensions } from '@tiptap/core';
2
+ /**
3
+ * Builds a real, headless (uncoupled from React) tiptap `Editor` for
4
+ * component specs that need genuine ProseMirror state - selection, commands,
5
+ * `isActive` - rather than a hand-rolled mock. Callers should call
6
+ * `.destroy()` in `afterEach` to avoid leaking editor instances across tests.
7
+ */
8
+ export declare const createTestEditor: (overrides?: {
9
+ extensions?: Extensions;
10
+ } & Partial<EditorOptions>) => Editor;
@@ -0,0 +1,10 @@
1
+ import { CommunityItemProps } from './CommunityItem';
2
+ export interface CommunitiesProps {
3
+ communitiesList?: CommunityItemProps[];
4
+ handleActionClick: () => void;
5
+ }
6
+ declare const Communities: {
7
+ ({ communitiesList, handleActionClick, }: CommunitiesProps): import("react/jsx-runtime").JSX.Element;
8
+ displayName: string;
9
+ };
10
+ export default Communities;
@@ -0,0 +1,9 @@
1
+ import { CommunitiesModel } from './useCommunities';
2
+ export type CommunitiesContainerProps = {
3
+ onCommunityClick?: (community: CommunitiesModel) => void;
4
+ onHeaderActionClick: () => void;
5
+ };
6
+ export declare function CommunitiesContainer({ onCommunityClick, onHeaderActionClick, }: CommunitiesContainerProps): import("react/jsx-runtime").JSX.Element | "An error occurred while loading communities.";
7
+ export declare namespace CommunitiesContainer {
8
+ var displayName: string;
9
+ }
@@ -0,0 +1,5 @@
1
+ declare const CommunitiesSkeleton: {
2
+ (): import("react/jsx-runtime").JSX.Element;
3
+ displayName: string;
4
+ };
5
+ export default CommunitiesSkeleton;
@@ -0,0 +1,11 @@
1
+ export interface CommunityItemProps {
2
+ title: string;
3
+ communityImage: string;
4
+ onActionClick: () => void;
5
+ nbNotifications?: number;
6
+ }
7
+ declare const CommunityItem: {
8
+ ({ title, communityImage, nbNotifications, onActionClick, }: CommunityItemProps): import("react/jsx-runtime").JSX.Element;
9
+ displayName: string;
10
+ };
11
+ export default CommunityItem;
@@ -0,0 +1,8 @@
1
+ export * from './Communities';
2
+ export { default as Communities } from './Communities';
3
+ export * from './CommunitiesContainer';
4
+ export * from './CommunitiesSkeleton';
5
+ export { default as CommunitiesSkeleton } from './CommunitiesSkeleton';
6
+ export * from './CommunityItem';
7
+ export { default as CommunitiesItem } from './CommunityItem';
8
+ export * from './useCommunities';
@@ -0,0 +1,13 @@
1
+ export interface CommunitiesModel {
2
+ id: number | string;
3
+ title: string;
4
+ communityImage?: string;
5
+ icon?: string;
6
+ nbNotifications?: number;
7
+ notifications?: number;
8
+ }
9
+ export declare function useCommunities(): {
10
+ communities: CommunitiesModel[];
11
+ isLoading: boolean;
12
+ error: Error | null;
13
+ };
@@ -1,4 +1,5 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
2
3
  import { useTranslation } from "react-i18next";
3
4
  import illuEmptyFavorite from "@edifice.io/bootstrap/dist/images/homepage/illu-empty-favorite.svg";
4
5
  import SvgIconArrowRight from "../../../icons/components/IconArrowRight.js";
@@ -11,13 +12,15 @@ function Favorites({
11
12
  }) {
12
13
  const {
13
14
  t
14
- } = useTranslation(), getAppName = (app) => app.prefix && app.prefix.length > 1 ? t(app.prefix.substring(1)) : t(app.displayName) || "";
15
+ } = useTranslation(), getAppName = (app) => app.prefix && app.prefix.length > 1 ? t(app.prefix.substring(1)) : t(app.displayName) || "", visibleApps = useMemo(() => apps.filter((app) => app.display !== !1).sort((a, b) => getAppName(a).localeCompare(getAppName(b), void 0, {
16
+ sensitivity: "base"
17
+ })), [apps, t]);
15
18
  return /* @__PURE__ */ jsxs(HomeCard, { variant: "secondary", children: [
16
19
  /* @__PURE__ */ jsx(HomeCard.Header, { title: t("homepage.favorites.title", "Favoris"), actionLabel: t("homepage.favorites.all", "Mes applis"), onActionClick: onSeeAllClick, actionRightIcon: /* @__PURE__ */ jsx(SvgIconArrowRight, {}) }),
17
- /* @__PURE__ */ jsx(HomeCard.Content, { children: /* @__PURE__ */ jsx("div", { className: "favorites-content", children: apps.length === 0 ? /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "12", children: [
18
- /* @__PURE__ */ jsx("img", { src: illuEmptyFavorite, alt: "", width: 80, height: 80 }),
20
+ /* @__PURE__ */ jsx(HomeCard.Content, { children: /* @__PURE__ */ jsx("div", { className: "favorites-content", children: visibleApps.length === 0 ? /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "12", children: [
21
+ /* @__PURE__ */ jsx("img", { src: illuEmptyFavorite, alt: "", width: 50, height: 50 }),
19
22
  /* @__PURE__ */ jsx("span", { className: "favorites-empty-text", children: t("homepage.favorites.empty", "Ajouter des applications à vos favoris pour les retrouver ici et y accéder rapidement !") })
20
- ] }) : /* @__PURE__ */ jsx(Flex, { wrap: "wrap", gap: "8 16", children: apps.map((app) => {
23
+ ] }) : /* @__PURE__ */ jsx(Flex, { wrap: "wrap", gap: "8 16", children: visibleApps.map((app) => {
21
24
  const appName = getAppName(app), opensInNewTab = app.isExternal || app.target === "_blank";
22
25
  return /* @__PURE__ */ jsx("a", { href: app.address, "aria-label": appName, title: appName, target: opensInNewTab ? "_blank" : void 0, rel: opensInNewTab ? "noopener noreferrer" : void 0, children: /* @__PURE__ */ jsx(AppIcon, { app, size: "40", variant: "square" }) }, app.name);
23
26
  }) }) }) })
@@ -12,6 +12,7 @@ import SvgIconHomeBeta from "../../../icons/components/nav/IconHomeBeta.js";
12
12
  import SvgIconMessagesBeta from "../../../icons/components/nav/IconMessagesBeta.js";
13
13
  import SvgIconMyAppsBeta from "../../../icons/components/nav/IconMyAppsBeta.js";
14
14
  import SvgIconNotificationBeta from "../../../icons/components/nav/IconNotificationBeta.js";
15
+ import { useHasNotificationToday } from "../Notifications/hooks/useNotificationList.js";
15
16
  import useConversation from "../../../../hooks/useConversation/useConversation.js";
16
17
  import useUser from "../../../../hooks/useUser/useUser.js";
17
18
  import useHasWorkflow from "../../../../hooks/useHasWorkflow/useHasWorkflow.js";
@@ -45,7 +46,7 @@ const Header = ({
45
46
  avatar
46
47
  }), {
47
48
  theme
48
- } = useEdificeTheme(), hasMessages = messages > 0, [userRef, isUserHovered] = useHover(), popoverUserId = useId(), handleNotificationsClick = () => {
49
+ } = useEdificeTheme(), hasMessages = messages > 0, hasNotificationToday = useHasNotificationToday(), [userRef, isUserHovered] = useHover(), popoverUserId = useId(), handleNotificationsClick = () => {
49
50
  onNotificationsClick == null || onNotificationsClick();
50
51
  };
51
52
  return /* @__PURE__ */ jsx("header", { className: classes, "data-product": dataProduct, children: /* @__PURE__ */ jsxs(Navbar, { className: "px-24", children: [
@@ -65,7 +66,24 @@ const Header = ({
65
66
  /* @__PURE__ */ jsx(VisuallyHidden, { children: t("conversation") })
66
67
  ] }) }),
67
68
  /* @__PURE__ */ jsx(NavItem, { children: /* @__PURE__ */ jsx(NavLink, { link: "/welcome", translate: t("navbar.applications"), "data-testid": "header-my-apps-button", children: /* @__PURE__ */ jsx(SvgIconMyAppsBeta, {}) }) }),
68
- /* @__PURE__ */ jsx(NavItem, { children: /* @__PURE__ */ jsx(ButtonBeta, { leftIcon: /* @__PURE__ */ jsx(SvgIconNotificationBeta, {}), variant: "ghost", onClick: handleNotificationsClick }) }),
69
+ /* @__PURE__ */ jsxs(NavItem, { className: "position-relative", children: [
70
+ /* @__PURE__ */ jsx(ButtonBeta, { leftIcon: /* @__PURE__ */ jsx(SvgIconNotificationBeta, {}), variant: "ghost", onClick: handleNotificationsClick }),
71
+ hasNotificationToday && /* @__PURE__ */ jsx("span", { style: {
72
+ position: "absolute",
73
+ top: 0,
74
+ right: 0,
75
+ width: 15,
76
+ height: 15,
77
+ background: "red",
78
+ borderRadius: "50%",
79
+ border: "2px solid white",
80
+ pointerEvents: "none",
81
+ zIndex: 1,
82
+ display: "block"
83
+ }, "aria-label": t("homepage.notifications.new-badge", {
84
+ defaultValue: "Nouvelle notification"
85
+ }) })
86
+ ] }),
69
87
  /* @__PURE__ */ jsxs(NavItem, { className: "position-relative", ref: userRef, id: popoverUserId, "aria-haspopup": "true", "aria-expanded": isUserHovered, "data-testid": "header-user-menu-button", children: [
70
88
  /* @__PURE__ */ jsx(NavLink, { link: "/userbook/mon-compte", translate: t("navbar.myaccount"), "data-testid": "header-user-profile-button", children: /* @__PURE__ */ jsx(Avatar, { alt: userName, size: "sm", src: userAvatar, variant: "circle", className: "bg-white", width: "32", height: "32" }) }),
71
89
  /* @__PURE__ */ jsx(Popover, { align: "end", className: "widget", id: popoverUserId, isVisible: isUserHovered, children: /* @__PURE__ */ jsx(PopoverBody, { children: /* @__PURE__ */ jsxs("a", { href: "/auth/logout?callback=" + ((theme == null ? void 0 : theme.logoutCallback) ?? ""), className: "nav-link logout d-flex align-items-center gap-8", "data-testid": "header-logout-button", children: [
@@ -20,9 +20,13 @@ export interface HomeCardProps extends ComponentPropsWithoutRef<'div'> {
20
20
  * `HomeCard.Header` is rendered with these props.
21
21
  */
22
22
  headerProps?: HomeCardHeaderProps;
23
+ /**
24
+ * Optional footer
25
+ */
26
+ footer?: ReactNode;
23
27
  }
24
28
  declare const HomeCard: {
25
- ({ variant, children, headerProps, className, ...rest }: HomeCardProps): import("react/jsx-runtime").JSX.Element;
29
+ ({ variant, children, headerProps, footer, className, ...rest }: HomeCardProps): import("react/jsx-runtime").JSX.Element;
26
30
  displayName: string;
27
31
  } & {
28
32
  Header: {
@@ -1,14 +1,22 @@
1
- import { jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import clsx from "clsx";
3
3
  import HomeCardContent from "./HomeCardContent.js";
4
4
  import HomeCardHeader from "./HomeCardHeader.js";
5
+ import Flex from "../../../../components/Flex/Flex.js";
5
6
  const Root = ({
6
7
  variant = "user",
7
8
  children,
8
9
  headerProps,
10
+ footer,
9
11
  className,
10
12
  ...rest
11
- }) => /* @__PURE__ */ jsx("div", { "data-testid": "home-card", "data-variant": variant, className: clsx("home-card", `home-card--${variant}`, className), ...rest, children: headerProps && !children ? /* @__PURE__ */ jsx(HomeCardHeader, { ...headerProps }) : children }), HomeCard = Object.assign(Root, {
13
+ }) => {
14
+ const classNames = clsx("home-card", `home-card--${variant}`, className), content = /* @__PURE__ */ jsx("div", { "data-testid": "home-card", "data-variant": variant, className: classNames, ...rest, children: headerProps && !children ? /* @__PURE__ */ jsx(HomeCardHeader, { ...headerProps }) : children });
15
+ return footer ? /* @__PURE__ */ jsxs("div", { className: "home-card--with-footer", children: [
16
+ content,
17
+ /* @__PURE__ */ jsx(Flex, { direction: "row", gap: "8", className: "px-16 py-4 home-card-footer", children: footer })
18
+ ] }) : content;
19
+ }, HomeCard = Object.assign(Root, {
12
20
  Header: HomeCardHeader,
13
21
  Content: HomeCardContent
14
22
  });
@@ -2,7 +2,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useTranslation } from "react-i18next";
3
3
  import SvgIconArrowRight from "../../../icons/components/IconArrowRight.js";
4
4
  import LastInfos from "./LastInfos.js";
5
- import illuLastInfosEmptyScreen from "@edifice.io/bootstrap/dist/images/homepage/illu-last-infos-beta.svg";
5
+ import illuLastInfosEmptyScreen from "@edifice.io/bootstrap/dist/images/homepage/illu-empty-last-infos.png";
6
6
  import Flex from "../../../../components/Flex/Flex.js";
7
7
  import ButtonBeta from "../../../../components/ButtonBeta/ButtonBeta.js";
8
8
  import EmptyScreen from "../../../../components/EmptyScreen/EmptyScreen.js";
@@ -23,7 +23,7 @@ function LastInfosList({
23
23
  /* @__PURE__ */ jsx("h2", { className: "last-infos-list-title", children: t("homepage.last-infos-list.title") }),
24
24
  /* @__PURE__ */ jsx(ButtonBeta, { color: "tertiary", variant: "ghost", className: "last-infos-list-seemore", rightIcon: /* @__PURE__ */ jsx(SvgIconArrowRight, {}), onClick: handleSeeMoreClick, children: t("homepage.last-infos-list.see.more") })
25
25
  ] }),
26
- /* @__PURE__ */ jsx(Flex, { gap: "16", direction: "column", className: "last-infos-list-body", children: infos.length === 0 ? /* @__PURE__ */ jsx(EmptyScreen, { imageSrc: illuLastInfosEmptyScreen, size: 64, text: t("homepage.last-infos-list.empty") }) : infos.map((infosProps) => /* @__PURE__ */ jsx(LastInfos, { onClick: handleInfoClick, ...infosProps }, infosProps.id)) })
26
+ /* @__PURE__ */ jsx("div", { className: "last-infos-list-body", children: infos.length === 0 ? /* @__PURE__ */ jsx(EmptyScreen, { imageSrc: illuLastInfosEmptyScreen, size: 135, text: t("homepage.last-infos-list.empty") }) : infos.map((infosProps) => /* @__PURE__ */ jsx(LastInfos, { onClick: handleInfoClick, ...infosProps }, infosProps.id)) })
27
27
  ] });
28
28
  }
29
29
  export {
@@ -1,14 +1,15 @@
1
- import { jsxs, Fragment, jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import NotificationOptionsMenu from "./components/NotificationOptionsMenu.js";
2
3
  import SystemNotificationItem from "./components/SystemNotificationItem.js";
3
4
  import UserNotificationItem from "./components/UserNotificationItem.js";
4
5
  import { notificationAdapter } from "./components/notificationAdapter.js";
5
6
  const NotificationItem = ({
6
7
  notification
7
8
  }) => {
8
- const notif = notificationAdapter(notification);
9
+ const notif = notificationAdapter(notification), menu = /* @__PURE__ */ jsx(NotificationOptionsMenu, { notificationId: notification._id });
9
10
  return /* @__PURE__ */ jsxs(Fragment, { children: [
10
- notif.type === "user" && /* @__PURE__ */ jsx(UserNotificationItem, { notification: notif }),
11
- notif.type === "system" && /* @__PURE__ */ jsx(SystemNotificationItem, { notification: notif })
11
+ notif.type === "user" && /* @__PURE__ */ jsx(UserNotificationItem, { notification: notif, menu }),
12
+ notif.type === "system" && /* @__PURE__ */ jsx(SystemNotificationItem, { notification: notif, menu })
12
13
  ] });
13
14
  };
14
15
  export {
@@ -2,6 +2,12 @@ import { NotificationModel } from '@edifice.io/client';
2
2
  export type NotificationListProps = {
3
3
  /** List of notifications to display */
4
4
  notifications?: NotificationModel[];
5
+ /** All notification types available to filter on */
6
+ notificationTypes?: string[];
7
+ /** Notification types currently applied as filter */
8
+ selectedTypes?: string[];
9
+ /** Callback when the user validates a new filter selection */
10
+ onFilterChange?: (types: string[]) => void;
5
11
  /** Callback when the notifications list is closed */
6
12
  onCloseNotifications?: () => void;
7
13
  /** Callback to load the next page of notifications, used for infinite scrolling */
@@ -12,7 +18,7 @@ export type NotificationListProps = {
12
18
  isLoading?: boolean;
13
19
  };
14
20
  declare const NotificationList: {
15
- ({ notifications, onCloseNotifications, onLoadNextPage, hasNextPage, isLoading, }: NotificationListProps): import("react/jsx-runtime").JSX.Element;
21
+ ({ notifications, notificationTypes, selectedTypes, onFilterChange, onCloseNotifications, onLoadNextPage, hasNextPage, isLoading, }: NotificationListProps): import("react/jsx-runtime").JSX.Element;
16
22
  displayName: string;
17
23
  };
18
24
  export default NotificationList;
@@ -1,7 +1,8 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import illuEmptyNotification from "@edifice.io/bootstrap/dist/images/emptyscreen/illu-notifications.png";
2
+ import illuEmptyNotification from "@edifice.io/bootstrap/dist/images/homepage/illu-empty-notifications.png";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import SvgIconClose from "../../../icons/components/IconClose.js";
5
+ import NotificationFilterMenu from "./components/NotificationFilterMenu.js";
5
6
  import NotificationItem from "./NotificationItem.js";
6
7
  import NotificationListSkeleton from "./NotificationListSkeleton.js";
7
8
  import NotificationSkeleton from "./NotificationSkeleton.js";
@@ -12,6 +13,9 @@ import EmptyScreen from "../../../../components/EmptyScreen/EmptyScreen.js";
12
13
  import Divider from "../../../../components/Divider/Divider.js";
13
14
  const NotificationList = ({
14
15
  notifications,
16
+ notificationTypes,
17
+ selectedTypes,
18
+ onFilterChange,
15
19
  onCloseNotifications,
16
20
  onLoadNextPage,
17
21
  hasNextPage,
@@ -26,10 +30,13 @@ const NotificationList = ({
26
30
  };
27
31
  return /* @__PURE__ */ jsx("section", { role: "region", className: "notification-list", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", children: [
28
32
  /* @__PURE__ */ jsxs(Flex, { justify: "between", align: "center", wrap: "nowrap", className: "py-16 ps-24 pe-8", children: [
29
- /* @__PURE__ */ jsx("h4", { className: "text-truncate", children: t("homepage.notifications-list.title") }),
33
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "8", className: "notification-list-title", children: [
34
+ /* @__PURE__ */ jsx("h4", { className: "text-truncate", children: t("homepage.notifications-list.title") }),
35
+ notificationTypes && notificationTypes.length > 0 && /* @__PURE__ */ jsx(NotificationFilterMenu, { notificationTypes, selectedTypes: selectedTypes ?? notificationTypes, onFilterChange: (types) => onFilterChange == null ? void 0 : onFilterChange(types) })
36
+ ] }),
30
37
  onCloseNotifications && /* @__PURE__ */ jsx(ButtonBeta, { color: "tertiary", variant: "ghost", rightIcon: /* @__PURE__ */ jsx(SvgIconClose, {}), onClick: handleCloseClick, "aria-label": t("homepage.notifications-list.close"), title: t("homepage.notifications-list.close"), "data-testid": "notification-list-close-button" })
31
38
  ] }),
32
- (notifications == null ? void 0 : notifications.length) === 0 ? /* @__PURE__ */ jsx("div", { className: "mx-24", children: /* @__PURE__ */ jsx(EmptyScreen, { size: 120, imageSrc: illuEmptyNotification, imageAlt: t("homepage.notifications-list.empty.description"), text: t("homepage.notifications-list.empty.description") }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
39
+ (notifications == null ? void 0 : notifications.length) === 0 ? /* @__PURE__ */ jsx("div", { className: "mx-24", children: /* @__PURE__ */ jsx(EmptyScreen, { size: 135, imageSrc: illuEmptyNotification, imageAlt: t("homepage.notifications-list.empty.description"), text: t("homepage.notifications-list.empty.description") }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
33
40
  /* @__PURE__ */ jsx("ul", { children: notifications == null ? void 0 : notifications.map((notification, index) => /* @__PURE__ */ jsxs("li", { children: [
34
41
  /* @__PURE__ */ jsx(NotificationItem, { notification }),
35
42
  /* @__PURE__ */ jsx(Divider, { className: "border-grey-300 my-0" })
@@ -6,11 +6,14 @@ const NotificationListContainer = ({
6
6
  }) => {
7
7
  const {
8
8
  notifications,
9
+ notificationTypes,
10
+ selectedTypes,
11
+ setSelectedTypes,
9
12
  hasNextPage,
10
13
  loadNextPage,
11
14
  isLoading
12
15
  } = useNotificationListContainer();
13
- return /* @__PURE__ */ jsx(NotificationList, { notifications, onCloseNotifications, onLoadNextPage: () => {
16
+ return /* @__PURE__ */ jsx(NotificationList, { notifications, notificationTypes, selectedTypes, onFilterChange: setSelectedTypes, onCloseNotifications, onLoadNextPage: () => {
14
17
  hasNextPage && !isLoading && loadNextPage();
15
18
  }, hasNextPage, isLoading });
16
19
  };
@@ -5,6 +5,7 @@ export type NotificationItemProps = {
5
5
  date: Date;
6
6
  uri?: string;
7
7
  children?: ReactNode;
8
+ menu?: ReactNode;
8
9
  };
9
10
  /**
10
11
  * Base layout for a single notification row.
@@ -18,7 +19,7 @@ export type NotificationItemProps = {
18
19
  * `SystemNotification` instead of consuming this directly.
19
20
  */
20
21
  declare const CommonNotificationItem: {
21
- ({ picture, message, date, uri, children, }: NotificationItemProps): import("react/jsx-runtime").JSX.Element;
22
+ ({ picture, message, date, uri, children, menu, }: NotificationItemProps): import("react/jsx-runtime").JSX.Element;
22
23
  displayName: string;
23
24
  };
24
25
  export default CommonNotificationItem;
@@ -6,21 +6,25 @@ const CommonNotificationItem = ({
6
6
  message,
7
7
  date,
8
8
  uri,
9
- children
9
+ children,
10
+ menu
10
11
  }) => {
11
12
  const {
12
13
  formatTimeAgo,
13
14
  formatDate
14
15
  } = useDate();
15
- return /* @__PURE__ */ jsx(Flex, { direction: "column", className: "notification-item", gap: "8", children: /* @__PURE__ */ jsxs(Flex, { direction: "row", gap: "8", children: [
16
- /* @__PURE__ */ jsx("div", { className: "notification-item-picture", children: picture }),
17
- /* @__PURE__ */ jsx("a", { href: uri, "data-testid": "notification-item-content", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "8", children: [
18
- /* @__PURE__ */ jsx("div", { className: "notification-item-message", dangerouslySetInnerHTML: {
19
- __html: message
20
- } }),
21
- children,
22
- /* @__PURE__ */ jsx("p", { className: "notification-item-date", title: formatDate(date, "LLL"), children: formatTimeAgo(date) })
23
- ] }) })
16
+ return /* @__PURE__ */ jsx(Flex, { direction: "column", className: "notification-item", gap: "8", children: /* @__PURE__ */ jsxs(Flex, { direction: "row", justify: "between", wrap: "nowrap", gap: "8", children: [
17
+ /* @__PURE__ */ jsxs(Flex, { direction: "row", align: "start", gap: "8", className: "notification-item-content-wrapper", children: [
18
+ /* @__PURE__ */ jsx("div", { className: "notification-item-picture", children: picture }),
19
+ /* @__PURE__ */ jsx("a", { href: uri, className: "notification-item-link", "data-testid": "notification-item-content", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "8", children: [
20
+ /* @__PURE__ */ jsx("div", { className: "notification-item-message", dangerouslySetInnerHTML: {
21
+ __html: message
22
+ } }),
23
+ children,
24
+ /* @__PURE__ */ jsx("p", { className: "notification-item-date", title: formatDate(date, "LLL"), children: formatTimeAgo(date) })
25
+ ] }) })
26
+ ] }),
27
+ menu && /* @__PURE__ */ jsx("div", { className: "notification-item-menu", children: menu })
24
28
  ] }) });
25
29
  };
26
30
  export {
@@ -0,0 +1,16 @@
1
+ export type NotificationFilterMenuProps = {
2
+ /** All notification types available */
3
+ notificationTypes: string[];
4
+ /** Notification types currently applied as filter */
5
+ selectedTypes: string[];
6
+ /** Callback when the user validates a new filter selection */
7
+ onFilterChange: (types: string[]) => void;
8
+ };
9
+ /**
10
+ * Trigger button opening the notification types filter modal.
11
+ */
12
+ declare const NotificationFilterMenu: {
13
+ ({ notificationTypes, selectedTypes, onFilterChange, }: NotificationFilterMenuProps): import("react/jsx-runtime").JSX.Element;
14
+ displayName: string;
15
+ };
16
+ export default NotificationFilterMenu;
@@ -0,0 +1,29 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useTranslation } from "react-i18next";
4
+ import SvgIconFilter from "../../../../icons/components/IconFilter.js";
5
+ import SvgIconRafterDown from "../../../../icons/components/IconRafterDown.js";
6
+ import NotificationFilterModal from "./NotificationFilterModal.js";
7
+ import ButtonBeta from "../../../../../components/ButtonBeta/ButtonBeta.js";
8
+ const NotificationFilterMenu = ({
9
+ notificationTypes,
10
+ selectedTypes,
11
+ onFilterChange
12
+ }) => {
13
+ const {
14
+ t
15
+ } = useTranslation(), [isOpen, setIsOpen] = useState(!1), handleApply = (types) => {
16
+ onFilterChange(types), setIsOpen(!1);
17
+ };
18
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
19
+ /* @__PURE__ */ jsx(ButtonBeta, { type: "button", color: "secondary", variant: "outline", size: "md", leftIcon: /* @__PURE__ */ jsx(SvgIconFilter, {}), rightIcon: /* @__PURE__ */ jsx(SvgIconRafterDown, {}), "aria-label": t("homepage.notifications.filter-modal.trigger", {
20
+ defaultValue: "Filtrer les notifications"
21
+ }), title: t("homepage.notifications.filter-modal.trigger", {
22
+ defaultValue: "Filtrer les notifications"
23
+ }), onClick: () => setIsOpen(!0), "data-testid": "notification-filter-button" }),
24
+ isOpen && /* @__PURE__ */ jsx(NotificationFilterModal, { isOpen, allTypes: notificationTypes, appliedTypes: selectedTypes, onCancel: () => setIsOpen(!1), onApply: handleApply })
25
+ ] });
26
+ };
27
+ export {
28
+ NotificationFilterMenu as default
29
+ };
@@ -0,0 +1,18 @@
1
+ export type NotificationFilterModalProps = {
2
+ isOpen: boolean;
3
+ /** All notification types available */
4
+ allTypes: string[];
5
+ /** Notification types currently applied as filter */
6
+ appliedTypes: string[];
7
+ onCancel: () => void;
8
+ onApply: (types: string[]) => void;
9
+ };
10
+ /**
11
+ * Modal letting the user choose which notification types to display in the
12
+ * homepage notifications list.
13
+ */
14
+ declare const NotificationFilterModal: {
15
+ ({ isOpen, allTypes, appliedTypes, onCancel, onApply, }: NotificationFilterModalProps): import("react/jsx-runtime").JSX.Element;
16
+ displayName: string;
17
+ };
18
+ export default NotificationFilterModal;
@@ -0,0 +1,67 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import clsx from "clsx";
3
+ import { useState } from "react";
4
+ import { useTranslation } from "react-i18next";
5
+ import SvgIconCheck from "../../../../icons/components/IconCheck.js";
6
+ import { getAppCodeAndI18nKey } from "./notificationAdapter.js";
7
+ import Modal from "../../../../../components/Modal/Modal.js";
8
+ import Flex from "../../../../../components/Flex/Flex.js";
9
+ import Checkbox from "../../../../../components/Checkbox/Checkbox.js";
10
+ import AppIcon from "../../../../../components/AppIcon/AppIcon.js";
11
+ import ButtonBeta from "../../../../../components/ButtonBeta/ButtonBeta.js";
12
+ const NotificationFilterModal = ({
13
+ isOpen,
14
+ allTypes,
15
+ appliedTypes,
16
+ onCancel,
17
+ onApply
18
+ }) => {
19
+ const {
20
+ t
21
+ } = useTranslation(), [selected, setSelected] = useState(appliedTypes), toggleType = (type) => {
22
+ setSelected((current) => current.includes(type) ? current.filter((value) => value !== type) : [...current, type]);
23
+ }, toggleAll = () => {
24
+ setSelected(selected.length === allTypes.length ? [] : allTypes);
25
+ };
26
+ return /* @__PURE__ */ jsxs(Modal, { id: "notification-filter-modal", size: "lg", scrollable: !0, isOpen, onModalClose: onCancel, children: [
27
+ /* @__PURE__ */ jsx(Modal.Header, { onModalClose: onCancel, children: t("homepage.notifications.filter-modal.title", {
28
+ defaultValue: "Filtrer les notifications"
29
+ }) }),
30
+ /* @__PURE__ */ jsx(Modal.Subtitle, { children: t("homepage.notifications.filter-modal.subtitle", {
31
+ defaultValue: "Sélectionnez seulement les notifications importantes pour vous"
32
+ }) }),
33
+ /* @__PURE__ */ jsxs(Modal.Body, { children: [
34
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "8", className: "mb-16", children: [
35
+ /* @__PURE__ */ jsx("div", { className: "notification-filter-select-all", children: /* @__PURE__ */ jsx(Checkbox, { label: t("homepage.notifications.filter-modal.select-all", {
36
+ defaultValue: "Tout sélectionner"
37
+ }), checked: selected.length === allTypes.length, indeterminate: selected.length > 0 && selected.length < allTypes.length, onChange: toggleAll }) }),
38
+ /* @__PURE__ */ jsx("span", { className: "notification-filter-count", children: t("homepage.notifications.filter-modal.count", {
39
+ selected: selected.length,
40
+ total: allTypes.length,
41
+ defaultValue: "[[selected]] / [[total]] sélectionnées"
42
+ }) })
43
+ ] }),
44
+ /* @__PURE__ */ jsx(Flex, { wrap: "wrap", gap: "8", className: "notification-filter-options", children: allTypes.map((type) => {
45
+ const [appCode, appI18nKey] = getAppCodeAndI18nKey(type), checked = selected.includes(type);
46
+ return /* @__PURE__ */ jsxs("label", { className: clsx("notification-filter-chip", {
47
+ "is-checked": checked
48
+ }), children: [
49
+ /* @__PURE__ */ jsx(AppIcon, { app: appCode, size: "24", iconFit: "contain" }),
50
+ /* @__PURE__ */ jsx("span", { children: t(appI18nKey, {
51
+ defaultValue: appCode
52
+ }) }),
53
+ /* @__PURE__ */ jsx("input", { type: "checkbox", className: "form-check-input", checked, onChange: () => toggleType(type) })
54
+ ] }, type);
55
+ }) })
56
+ ] }),
57
+ /* @__PURE__ */ jsxs(Modal.Footer, { children: [
58
+ /* @__PURE__ */ jsx(ButtonBeta, { color: "tertiary", onClick: onCancel, type: "button", variant: "ghost", children: t("cancel") }),
59
+ /* @__PURE__ */ jsx(ButtonBeta, { color: "default", onClick: () => onApply(selected), type: "button", variant: "filled", rightIcon: /* @__PURE__ */ jsx(SvgIconCheck, {}), children: t("homepage.notifications.filter-modal.confirm", {
60
+ defaultValue: "Enregistrer"
61
+ }) })
62
+ ] })
63
+ ] });
64
+ };
65
+ export {
66
+ NotificationFilterModal as default
67
+ };
@@ -1,5 +1,6 @@
1
1
  export type NotificationItemResourceProps = {
2
2
  appCode: string;
3
+ appI18nKey: string;
3
4
  };
4
5
  /**
5
6
  * Displays the originating application of a notification as a small icon + label.
@@ -9,7 +10,7 @@ export type NotificationItemResourceProps = {
9
10
  *
10
11
  */
11
12
  declare const NotificationItemResource: {
12
- ({ appCode, }: NotificationItemResourceProps): import("react/jsx-runtime").JSX.Element;
13
+ ({ appCode, appI18nKey, }: NotificationItemResourceProps): import("react/jsx-runtime").JSX.Element;
13
14
  displayName: string;
14
15
  };
15
16
  export default NotificationItemResource;
@@ -2,11 +2,12 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useTranslation } from "react-i18next";
3
3
  import AppIcon from "../../../../../components/AppIcon/AppIcon.js";
4
4
  const NotificationItemResource = ({
5
- appCode
5
+ appCode,
6
+ appI18nKey
6
7
  }) => {
7
8
  const {
8
9
  t
9
- } = useTranslation(), appLabel = t(`${appCode}`, {
10
+ } = useTranslation(), appLabel = t(`${appI18nKey}`, {
10
11
  defaultValue: appCode
11
12
  }), appCssClass = `app-${appCode}`;
12
13
  return /* @__PURE__ */ jsxs("div", { className: `notification-item-resource ${appCssClass} bg-app-light`, children: [
@@ -0,0 +1,12 @@
1
+ export type NotificationOptionsMenuProps = {
2
+ notificationId: string;
3
+ };
4
+ /**
5
+ * Options menu for a single notification row, allowing the user to report
6
+ * or delete it.
7
+ */
8
+ declare const NotificationOptionsMenu: {
9
+ ({ notificationId, }: NotificationOptionsMenuProps): import("react/jsx-runtime").JSX.Element;
10
+ displayName: string;
11
+ };
12
+ export default NotificationOptionsMenu;
@@ -0,0 +1,42 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useTranslation } from "react-i18next";
4
+ import SvgIconAlertTriangle from "../../../../icons/components/IconAlertTriangle.js";
5
+ import SvgIconDelete from "../../../../icons/components/IconDelete.js";
6
+ import SvgIconOptions from "../../../../icons/components/IconOptions.js";
7
+ import { useReportNotification, useDeleteNotification } from "../services/queries/notification.js";
8
+ import ReportNotificationModal from "./ReportNotificationModal.js";
9
+ import Dropdown from "../../../../../components/Dropdown/Dropdown.js";
10
+ import IconButton from "../../../../../components/Button/IconButton.js";
11
+ const NotificationOptionsMenu = ({
12
+ notificationId
13
+ }) => {
14
+ const {
15
+ t
16
+ } = useTranslation(), {
17
+ mutate: reportNotification
18
+ } = useReportNotification(), {
19
+ mutate: deleteNotification
20
+ } = useDeleteNotification(), [isReportModalOpen, setIsReportModalOpen] = useState(!1);
21
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
22
+ /* @__PURE__ */ jsx(Dropdown, { overflow: !1, children: (triggerProps) => /* @__PURE__ */ jsxs(Fragment, { children: [
23
+ /* @__PURE__ */ jsx(IconButton, { ...triggerProps, type: "button", "aria-label": t("homepage.notifications.options.label", {
24
+ defaultValue: "Options de la notification"
25
+ }), color: "tertiary", variant: "ghost", icon: /* @__PURE__ */ jsx(SvgIconOptions, {}), "data-testid": "notification-item-options-button" }),
26
+ /* @__PURE__ */ jsxs(Dropdown.Menu, { children: [
27
+ /* @__PURE__ */ jsx(Dropdown.Item, { icon: /* @__PURE__ */ jsx(SvgIconAlertTriangle, {}), onClick: () => setIsReportModalOpen(!0), children: t("homepage.notifications.options.report", {
28
+ defaultValue: "Signaler"
29
+ }) }),
30
+ /* @__PURE__ */ jsx(Dropdown.Item, { icon: /* @__PURE__ */ jsx(SvgIconDelete, {}), onClick: () => deleteNotification(notificationId), children: t("homepage.notifications.options.delete", {
31
+ defaultValue: "Supprimer"
32
+ }) })
33
+ ] })
34
+ ] }) }),
35
+ isReportModalOpen && /* @__PURE__ */ jsx(ReportNotificationModal, { isOpen: isReportModalOpen, onCancel: () => setIsReportModalOpen(!1), onConfirm: () => {
36
+ reportNotification(notificationId), setIsReportModalOpen(!1);
37
+ } })
38
+ ] });
39
+ };
40
+ export {
41
+ NotificationOptionsMenu as default
42
+ };
@@ -0,0 +1,13 @@
1
+ export type ReportNotificationModalProps = {
2
+ isOpen: boolean;
3
+ onCancel: () => void;
4
+ onConfirm: () => void;
5
+ };
6
+ /**
7
+ * Confirmation modal shown before reporting a notification as inappropriate.
8
+ */
9
+ declare const ReportNotificationModal: {
10
+ ({ isOpen, onCancel, onConfirm, }: ReportNotificationModalProps): import("react/jsx-runtime").JSX.Element;
11
+ displayName: string;
12
+ };
13
+ export default ReportNotificationModal;