@omniumretail/component-library 1.2.27 → 1.2.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.css CHANGED
@@ -31,3 +31,4 @@
31
31
  .aZEBTQus3Y3MyodeDwkf{display:flex;flex-direction:column;align-items:center;justify-content:center}.cc1pbQlAgw2CmXVml3bs{height:300px;width:400px}.aTf_CXAsyp0tySsoZdD_{display:flex;justify-content:space-between;margin:10px 10px 0px 10px;font-size:20px}.kuu1hf_JTvYD55IW6Nxl{color:#ff4d4f}.N8ODEO3zDbY7gmecjTrp{color:#52c41a}.WIsL9Shu6c0mGJq9qSSz{margin-top:20px;border-radius:4px;overflow:hidden}.WIsL9Shu6c0mGJq9qSSz img{width:100%;height:auto}
32
32
  .OJKeuXyOSPIeCCssVum2{display:flex;gap:32px}.jzxszvL7wzCV5nztJL0S{padding:12px}.OGA8oJw8RDyohcRftjmo{width:100%}.y2_7QVPOhjRZlAHCRWKf{display:grid;grid-template-columns:minmax(200px, 1fr) auto auto auto auto;gap:46px}.Smkcs3LGp1uPuQge2GxE{margin-top:16px}Label{margin-bottom:8px}.djarlYFkE9Kb7wUjUnrY{height:36px}
33
33
  :root{--color-grey: #e4e4e4;--color-grey-100: #E5E5E5}.SF5f_Q_Ggz_4JHUHS2Xa{display:flex;align-items:center;padding:13px 20px;box-sizing:border-box;height:72px}.SF5f_Q_Ggz_4JHUHS2Xa *{box-sizing:border-box}.gSVYvBn1sLbcMsGI32b9{display:flex;flex-grow:1;align-items:flex-end;justify-content:center;max-width:calc(100% - 60px)}.gSVYvBn1sLbcMsGI32b9 img{max-width:196px}.gSVYvBn1sLbcMsGI32b9 .fVSt3bSZBcJGsOdA5nIN{font-weight:var(--font-weight-semibold);text-transform:uppercase;color:var(--color-black)}.BIBQmS_NwoMw2Ncq5yuN{cursor:pointer;padding:4px 4px 4px 0;font-size:20px;width:30px;display:flex;align-items:center;justify-content:center}.ZinSgUtysxITkbGRKdux{position:fixed;top:0;left:-100%;bottom:0;width:100%;max-width:430px;background-color:var(--color-white);padding-inline:15px;z-index:999;box-shadow:0 3px 12px rgba(0,0,0,.45);transition:.6s ease-out}.ZinSgUtysxITkbGRKdux.FW1pOdad0uN5LO9h5PgQ{left:0;transition:.3s ease-in}.ZfA8OEmbRzRsNqLAmdtn{background-color:rgba(0,0,0,.3);backdrop-filter:blur(1px);position:absolute;left:0;top:0;right:0;bottom:0;z-index:998;pointer-events:none;opacity:0;transition:.3s}.ZfA8OEmbRzRsNqLAmdtn.FW1pOdad0uN5LO9h5PgQ{opacity:1;pointer-events:all}.YKSQJmdRP_7tKwvQpeuV{width:calc(100% + 30px);margin-left:-15px;background-color:var(--color-orange);display:flex;align-items:center;justify-content:center}.ISssYC5Uz452JVWP0xDc img{max-width:160px}.E6YZPpa60LxA7qqIW7jQ{position:absolute;top:23px;right:15px;font-size:25px;cursor:pointer;color:var(--color-white)}.YYp3pzktjEDEyW8knAZQ{overflow-y:auto;height:calc(100% - 51px);margin:0;padding:0}.u7k2VQ30AQDk6mKA22GX{height:60px;padding:10px;display:flex;align-items:center;border-bottom:1px solid var(--color-black);cursor:pointer;transition:.3s;text-transform:uppercase;width:100%}.u7k2VQ30AQDk6mKA22GX:hover{background-color:var(--color-grey);color:var(--color-black)}.QQoDB9XPcAK18l2DIym4{list-style:none}.QQoDB9XPcAK18l2DIym4.wrXCksst57UpQuCNgGkN{height:auto;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;padding-bottom:0}.QQoDB9XPcAK18l2DIym4.wrXCksst57UpQuCNgGkN>.u7k2VQ30AQDk6mKA22GX{position:relative}.QQoDB9XPcAK18l2DIym4.wrXCksst57UpQuCNgGkN>.u7k2VQ30AQDk6mKA22GX .NytmT59TmaWHc4FLM2wa{position:absolute;width:60px;height:100%;background:var(--color-black);right:0;display:flex;align-items:center;justify-content:center}.QQoDB9XPcAK18l2DIym4.wrXCksst57UpQuCNgGkN>.u7k2VQ30AQDk6mKA22GX .NytmT59TmaWHc4FLM2wa:hover{background-color:var(--color-orange)}.QQoDB9XPcAK18l2DIym4.wrXCksst57UpQuCNgGkN>.u7k2VQ30AQDk6mKA22GX .zo7CFksIKx7Fahu4e4S_{color:var(--color-white)}.QQoDB9XPcAK18l2DIym4.wrXCksst57UpQuCNgGkN.yk4DnBgiUlzk23Yg_q6E{background:var(--color-grey)}.QQoDB9XPcAK18l2DIym4.wrXCksst57UpQuCNgGkN.yk4DnBgiUlzk23Yg_q6E .NytmT59TmaWHc4FLM2wa{background-color:var(--color-orange)}.QQoDB9XPcAK18l2DIym4.wrXCksst57UpQuCNgGkN.yk4DnBgiUlzk23Yg_q6E .uLgjMU9sbvfaa0qKEguP{height:auto;opacity:1}.uLgjMU9sbvfaa0qKEguP{padding:0;width:100%;margin:0;background-color:var(--color-orange);color:var(--color-white);height:0;opacity:0;overflow:hidden;transition:height .3s ease,opacity .3s ease}.uLgjMU9sbvfaa0qKEguP .u7k2VQ30AQDk6mKA22GX{min-height:60px;height:auto;padding-inline:40px}.efnjUnudkE0UTVQ6wrgH{cursor:pointer}.u7k2VQ30AQDk6mKA22GX .fjNRYZMP1O0pTvBDBB3L span{margin-left:8px;transform:translateY(-2px);display:inline-block;font-size:var(--font-size-body-3);text-transform:capitalize}
34
+ .msELhnfhfo6un3c4Spoc{max-width:300px;max-height:400px;overflow-y:auto;padding:0}@media(max-width: 768px){.msELhnfhfo6un3c4Spoc{max-width:100%;max-height:300px}}@media(min-width: 1024px){.msELhnfhfo6un3c4Spoc{max-width:600px;max-height:700px}}.GeNg2HVTmhQDmsLLTpWj{display:flex;flex-direction:column;align-items:unset !important;padding:10px;border-bottom:1px solid #f0f0f0;background-color:#fff;cursor:pointer}.GeNg2HVTmhQDmsLLTpWj:hover{background-color:#f9f9f9}@media(max-width: 768px){.GeNg2HVTmhQDmsLLTpWj{padding:8px}}.GBb48KXbGnQtAY8xqmUp{display:flex;justify-content:space-between;align-items:center;padding:10px;background-color:#f5f5f5;border-bottom:1px solid #e8e8e8}@media(max-width: 768px){.GBb48KXbGnQtAY8xqmUp{flex-direction:column;align-items:flex-start;gap:8px}}.LoCH7sYpmK3VgRASqTgz{font-size:20px;cursor:pointer;color:#595959}.Xjs10kZPSsD_BVFomnIe{color:#ff674c;margin-right:8px}.oibCayv_cOyhyWCKzHSr{display:flex;align-items:center;gap:10px}.oibCayv_cOyhyWCKzHSr span{font-size:14px;font-weight:500;color:#595959}.hhwYXSd0CR75D8ETnUML{display:flex;align-items:center;position:relative;padding-left:12px}@media(max-width: 768px){.hhwYXSd0CR75D8ETnUML{padding-left:8px}}.LkP1cqeAyAYI9rCurnE8{position:absolute;left:0;top:50%;transform:translateY(-50%);font-size:16px}@media(max-width: 768px){.LkP1cqeAyAYI9rCurnE8{font-size:12px}}.u_jT9kmq2kyThRlj8HT1{width:50px;height:50px;margin-left:10px;margin-right:10px}@media(max-width: 768px){.u_jT9kmq2kyThRlj8HT1{width:40px;height:40px;margin-left:8px;margin-right:8px}}.ieGdf5FTgLUPcfuZi7Xz{flex:1;overflow:hidden}.QgZ3oHW9IRTexL8xWVqP{font-weight:bold;font-size:14px}@media(max-width: 768px){.QgZ3oHW9IRTexL8xWVqP{font-size:12px}}.JwfbSOSIRAz5OO2hiSFc{font-size:12px;color:#666;margin-top:2px}@media(max-width: 768px){.JwfbSOSIRAz5OO2hiSFc{font-size:10px}}.XfXQBYqMfZc4p8s2Mhla{width:6px;height:6px;background-color:#1890ff;border-radius:50%;margin-left:10px}@media(max-width: 768px){.XfXQBYqMfZc4p8s2Mhla{width:4px;height:4px;margin-left:8px}}.RGXTlVxCxbZpD6__J5_g{font-size:12px}.rWa5cnbZQLDtxFxJU6qA{font-size:20px;cursor:pointer}@media(max-width: 768px){.rWa5cnbZQLDtxFxJU6qA{font-size:18px}}.Pus_rAoqX05D4Qz2pYxo{animation:Pus_rAoqX05D4Qz2pYxo 1s ease-in-out infinite}@keyframes Pus_rAoqX05D4Qz2pYxo{0%{transform:rotate(0deg)}25%{transform:rotate(10deg)}50%{transform:rotate(-10deg)}75%{transform:rotate(10deg)}100%{transform:rotate(0deg)}}
@@ -0,0 +1,5 @@
1
+ import { Meta, Story } from "@storybook/react";
2
+ import { BellNotificationsProps } from '.';
3
+ declare const _default: Meta<import("@storybook/react").Args>;
4
+ export default _default;
5
+ export declare const Primary: Story<BellNotificationsProps>;
@@ -0,0 +1,23 @@
1
+ export interface Criticality {
2
+ Id: string;
3
+ Name: string;
4
+ ColorHex: string;
5
+ }
6
+ export interface BellNotification {
7
+ Id: string;
8
+ Title: string;
9
+ Description: string;
10
+ RedirectURL: string;
11
+ Icon: string;
12
+ ExpirationDate: number;
13
+ Criticality: Criticality;
14
+ IsRead: boolean;
15
+ }
16
+ export interface BellNotificationsProps {
17
+ notifications: BellNotification[];
18
+ onFilterChange: (filter: 'all' | 'unread') => void;
19
+ onNotificationClick: (id: string, url: string) => void;
20
+ handleMarkAllAsRead: () => void;
21
+ }
22
+ export declare const BellNotifications: ({ notifications, onFilterChange, onNotificationClick, handleMarkAllAsRead }: BellNotificationsProps) => import("react/jsx-runtime").JSX.Element;
23
+ export default BellNotifications;
@@ -1,3 +1,4 @@
1
+ import { BellNotification } from "components/BellNotifications";
1
2
  export interface MenuItem {
2
3
  name: string;
3
4
  action: () => void;
@@ -18,4 +19,8 @@ export interface HeaderProps {
18
19
  onLeavingPage?: (targetRoute: string) => void;
19
20
  userName?: string;
20
21
  pageTitle?: string;
22
+ onFilterChange?: (filter: 'all' | 'unread') => void;
23
+ onNotificationClick?: (id: string, url: string) => void;
24
+ handleMarkAllAsRead?: () => void;
25
+ notifications?: BellNotification[];
21
26
  }
@@ -3,5 +3,5 @@ import { HeaderProps } from './Header.types';
3
3
  * Header component to display navigation bar with dropdown menus and action button.
4
4
  * @param {HeaderProps} props - Properties passed to the component.
5
5
  */
6
- export declare const Header: ({ menuList, actionButton, logout, homeUrl, profileUrl, onLeavingPage, userName, pageTitle }: HeaderProps) => import("react/jsx-runtime").JSX.Element;
6
+ export declare const Header: ({ menuList, actionButton, logout, homeUrl, profileUrl, onLeavingPage, userName, pageTitle, onFilterChange, notifications, onNotificationClick, handleMarkAllAsRead }: HeaderProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export default Header;
@@ -29,3 +29,4 @@ export * from './ResponsiveTable';
29
29
  export * from './WebCam';
30
30
  export * from './ResponseType';
31
31
  export * from './Header';
32
+ export * from './BellNotifications';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omniumretail/component-library",
3
- "version": "1.2.27",
3
+ "version": "1.2.29",
4
4
  "private": false,
5
5
  "main": "dist/bundle.js",
6
6
  "typings": "./dist/types/index",
@@ -0,0 +1,116 @@
1
+ import { Meta, Story } from "@storybook/react";
2
+ import { BellNotifications, BellNotificationsProps } from '.';
3
+
4
+ export default {
5
+ title: 'BellNotifications',
6
+ component: BellNotifications,
7
+ } as Meta;
8
+
9
+ const Template: Story<BellNotificationsProps> = (args) => <BellNotifications {...args}></BellNotifications>;
10
+
11
+ const notifications = [
12
+ {
13
+ "Id": "A31EB2C3-4937-44F6-B533-18A039A4AA96",
14
+ "Title": "Creation of an Evaluation Cycle",
15
+ "Description": "A new evaluation cycle was created. You must answer it.",
16
+ "RedirectURL": "https://www.google.pt",
17
+ "Icon": "https://omniumfont.s3.eu-west-1.amazonaws.com/Assets/images/aacp-image.png",
18
+ "ExpirationDate": 1736899200,
19
+ "Criticality": {
20
+ "Id": "F133ED4C-6C72-4CDC-86A1-27A76E6BDE7F",
21
+ "Name": "High",
22
+ "ColorHex": "#E05151"
23
+ },
24
+ "IsRead": false
25
+ },
26
+ {
27
+ "Id": "137C1FE7-296C-4523-AC59-F136D2FA91AB",
28
+ "Title": "Creation of an Evaluation Cycle",
29
+ "Description": "A new evaluation cycle was created. You must answer it.",
30
+ "RedirectURL": "https://www.google.pt",
31
+ "Icon": "https://omniumfont.s3.eu-west-1.amazonaws.com/Assets/images/aacp-image.png",
32
+ "ExpirationDate": 1736899200,
33
+ "Criticality": {
34
+ "Id": "F133ED4C-6C72-4CDC-86A1-27A76E6BDE7F",
35
+ "Name": "High",
36
+ "ColorHex": "#E05151"
37
+ },
38
+ "IsRead": false
39
+ },
40
+ {
41
+ "Id": "662C390B-A38F-45AB-B97C-2D00F0A8ACAD",
42
+ "Title": "Creation of an Evaluation Cycle",
43
+ "Description": "A new evaluation cycle was created. You must answer it.",
44
+ "RedirectURL": "https://www.google.pt",
45
+ "Icon": "https://omniumfont.s3.eu-west-1.amazonaws.com/Assets/images/aacp-image.png",
46
+ "ExpirationDate": 1736899200,
47
+ "Criticality": {
48
+ "Id": "F133ED4C-6C72-4CDC-86A1-27A76E6BDE7F",
49
+ "Name": "High",
50
+ "ColorHex": "#E05151"
51
+ },
52
+ "IsRead": false
53
+ },
54
+ {
55
+ "Id": "0419654F-66FE-41EA-BBA4-D8BB6E3D6B33",
56
+ "Title": "Creation of an Evaluation Cycle",
57
+ "Description": "A new evaluation cycle was created. You must answer it.",
58
+ "RedirectURL": "https://www.google.pt",
59
+ "Icon": "https://omniumfont.s3.eu-west-1.amazonaws.com/Assets/images/aacp-image.png",
60
+ "ExpirationDate": 1736899200,
61
+ "Criticality": {
62
+ "Id": "F133ED4C-6C72-4CDC-86A1-27A76E6BDE7F",
63
+ "Name": "High",
64
+ "ColorHex": "#E05151"
65
+ },
66
+ "IsRead": false
67
+ },
68
+ {
69
+ "Id": "CC98EDA1-0461-4D08-982E-8424EF4AFB3B",
70
+ "Title": "Creation of an Evaluation Cycle",
71
+ "Description": "A new evaluation cycle was created. You must answer it.",
72
+ "RedirectURL": "https://www.google.pt",
73
+ "Icon": "https://omniumfont.s3.eu-west-1.amazonaws.com/Assets/images/aacp-image.png",
74
+ "ExpirationDate": 1736899200,
75
+ "Criticality": {
76
+ "Id": "F133ED4C-6C72-4CDC-86A1-27A76E6BDE7F",
77
+ "Name": "High",
78
+ "ColorHex": "#E05151"
79
+ },
80
+ "IsRead": false
81
+ },
82
+ {
83
+ "Id": "3DB9310A-4BF8-4B90-8D5A-7583473664BB",
84
+ "Title": "Creation of an Evaluation Cycle",
85
+ "Description": "A new evaluation cycle was created. You must answer it.",
86
+ "RedirectURL": "https://www.google.pt",
87
+ "Icon": "https://omniumfont.s3.eu-west-1.amazonaws.com/Assets/images/aacp-image.png",
88
+ "ExpirationDate": 1736899200,
89
+ "Criticality": {
90
+ "Id": "F133ED4C-6C72-4CDC-86A1-27A76E6BDE7F",
91
+ "Name": "High",
92
+ "ColorHex": "#E05151"
93
+ },
94
+ "IsRead": true
95
+ },
96
+ {
97
+ "Id": "D6FB93EE-3255-42B2-8EC1-27E7AC3F5249",
98
+ "Title": "Creation of a new user",
99
+ "Description": "A new user was created in the platform with the mecanographic number 8111.",
100
+ "RedirectURL": "https://www.google.pt",
101
+ "Icon": "https://omniumfont.s3.eu-west-1.amazonaws.com/Assets/images/aacp-image.png",
102
+ "ExpirationDate": 1736985600,
103
+ "Criticality": {
104
+ "Id": "F133ED4C-6C72-4CDC-86A1-27A76E6BDE7F",
105
+ "Name": "High",
106
+ "ColorHex": "#E05151"
107
+ },
108
+ "IsRead": true
109
+ }
110
+ ];
111
+
112
+ export const Primary = Template.bind({});
113
+ Primary.args = {
114
+ notifications: notifications
115
+ };
116
+
@@ -0,0 +1,161 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Badge, Dropdown, Menu } from 'antd';
3
+ import { BellOutlined, CheckCircleOutlined, ExclamationCircleOutlined, MoreOutlined } from '@ant-design/icons';
4
+ import styles from './styles.module.scss';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Switch } from '../Switch';
7
+
8
+ export interface Criticality {
9
+ Id: string;
10
+ Name: string;
11
+ ColorHex: string;
12
+ }
13
+
14
+ export interface BellNotification {
15
+ Id: string;
16
+ Title: string;
17
+ Description: string;
18
+ RedirectURL: string;
19
+ Icon: string;
20
+ ExpirationDate: number;
21
+ Criticality: Criticality;
22
+ IsRead: boolean;
23
+ }
24
+
25
+ export interface BellNotificationsProps {
26
+ notifications: BellNotification[];
27
+ onFilterChange: (filter: 'all' | 'unread') => void;
28
+ onNotificationClick: (id: string, url: string) => void;
29
+ handleMarkAllAsRead: () => void;
30
+ }
31
+
32
+ export const BellNotifications = ({ notifications, onFilterChange, onNotificationClick, handleMarkAllAsRead }: BellNotificationsProps) => {
33
+ const { t } = useTranslation();
34
+ const [maxChars, setMaxChars] = useState(30);
35
+ const [filter, setFilter] = useState<'all' | 'unread'>('all');
36
+ const [isShaking, setIsShaking] = useState(false);
37
+
38
+ const unreadCount = notifications.filter((notif) => !notif.IsRead).length;
39
+
40
+ const handleNotificationClick = (id: string, url: string) => {
41
+ onNotificationClick(id, url);
42
+ }
43
+
44
+ const handleFilterChange = (checked: boolean) => {
45
+ const newFilter = checked ? 'unread' : 'all';
46
+ setFilter(newFilter);
47
+ onFilterChange(newFilter);
48
+ };
49
+
50
+ useEffect(() => {
51
+ const handleResize = () => {
52
+ if (window.innerWidth < 1024) {
53
+ setMaxChars(30);
54
+ } else {
55
+ setMaxChars(60);
56
+ }
57
+ };
58
+
59
+ handleResize();
60
+ window.addEventListener('resize', handleResize);
61
+
62
+ return () => {
63
+ window.removeEventListener('resize', handleResize);
64
+ };
65
+ }, []);
66
+
67
+ useEffect(() => {
68
+ if (unreadCount > 0) {
69
+ const interval = setInterval(() => {
70
+ setIsShaking(true);
71
+
72
+ // Parar a animação após 2 segundos
73
+ const stopShakeTimeout = setTimeout(() => setIsShaking(false), 2000);
74
+
75
+ return () => clearTimeout(stopShakeTimeout);
76
+ }, 15000); // Repetir a cada 30 segundos
77
+
78
+ return () => clearInterval(interval);
79
+ }
80
+ }, [unreadCount]);
81
+
82
+ const optionsMenu = (
83
+ <Menu>
84
+ <Menu.Item key="markAllAsRead" onClick={handleMarkAllAsRead}>
85
+ <CheckCircleOutlined className={styles.checkIconStyle} />
86
+
87
+ {t('components.bellNotifications.markAllAsRead')}
88
+ </Menu.Item>
89
+ </Menu>
90
+ );
91
+
92
+ const menu = (
93
+ <Menu className={styles.notificationMenu}>
94
+
95
+ {unreadCount > 0 && (
96
+ <div className={styles.headerContainer}>
97
+ <div className={styles.switchContainer}>
98
+ <span>{t('components.bellNotifications.onlyShowNoRead')}</span>
99
+
100
+ <Switch
101
+ checked={filter === 'unread'}
102
+ onChange={handleFilterChange}
103
+ size="small"
104
+ />
105
+ </div>
106
+
107
+ <Dropdown overlay={optionsMenu} trigger={['click']}>
108
+ <MoreOutlined className={styles.moreOutlinedIconStyle} />
109
+ </Dropdown>
110
+ </div>
111
+ )}
112
+
113
+ {notifications.map((notif) => (
114
+ <Menu.Item
115
+ key={notif.Id}
116
+ className={styles.notificationItem}
117
+ onClick={() => handleNotificationClick(notif.Id, notif.RedirectURL)}
118
+ >
119
+ <div className={styles.notificationContent}>
120
+ {notif.Criticality?.Name === 'High' && (
121
+ <ExclamationCircleOutlined
122
+ className={styles.criticalityIcon}
123
+ style={{ color: notif.Criticality?.ColorHex }}
124
+ />
125
+ )}
126
+
127
+ <img src={notif.Icon} alt="Notification Icon" className={styles.notificationIcon} />
128
+
129
+ <div className={styles.notificationText}>
130
+ <div
131
+ className={styles.notificationTitle}
132
+ >
133
+ {notif.Title}
134
+ </div>
135
+
136
+ <div className={styles.notificationDescription}>
137
+ {notif.Description.length > maxChars
138
+ ? `${notif.Description.slice(0, maxChars)}...`
139
+ : notif.Description}
140
+ </div>
141
+ </div>
142
+
143
+ {!notif.IsRead && <div className={styles.unreadIndicator}></div>}
144
+ </div>
145
+ </Menu.Item>
146
+ ))}
147
+
148
+ {notifications.length === 0 && <Menu.Item disabled>{t('components.bellNotifications.noNotifications')}</Menu.Item>}
149
+ </Menu>
150
+ );
151
+
152
+ return (
153
+ <Dropdown overlay={menu} trigger={['click']}>
154
+ <Badge count={unreadCount} className={styles.badgeStyle} overflowCount={99} offset={[8, 0]}>
155
+ <BellOutlined className={`${styles.bellIcon} ${isShaking ? styles.shake : ''}`} />
156
+ </Badge>
157
+ </Dropdown>
158
+ );
159
+ };
160
+
161
+ export default BellNotifications;
@@ -0,0 +1,186 @@
1
+ .notificationMenu {
2
+ max-width: 300px;
3
+ max-height: 400px;
4
+ overflow-y: auto;
5
+ padding: 0;
6
+
7
+ @media (max-width: 768px) {
8
+ max-width: 100%;
9
+ max-height: 300px;
10
+ }
11
+
12
+ @media (min-width: 1024px) {
13
+ max-width: 600px;
14
+ max-height: 700px;
15
+ }
16
+ }
17
+
18
+ .notificationItem {
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: unset !important;
22
+ padding: 10px;
23
+ border-bottom: 1px solid #f0f0f0;
24
+ background-color: #fff;
25
+ cursor: pointer;
26
+
27
+ &:hover {
28
+ background-color: #f9f9f9;
29
+ }
30
+
31
+ @media (max-width: 768px) {
32
+ padding: 8px;
33
+ }
34
+ }
35
+
36
+ .headerContainer {
37
+ display: flex;
38
+ justify-content: space-between;
39
+ align-items: center;
40
+ padding: 10px;
41
+ background-color: #f5f5f5;
42
+ border-bottom: 1px solid #e8e8e8;
43
+
44
+ @media (max-width: 768px) {
45
+ flex-direction: column;
46
+ align-items: flex-start;
47
+ gap: 8px;
48
+ }
49
+ }
50
+
51
+ .moreOutlinedIconStyle {
52
+ font-size: 20px;
53
+ cursor: pointer;
54
+ color: #595959;
55
+ }
56
+
57
+ .checkIconStyle {
58
+ color: #FF674C;
59
+ margin-right: 8px;
60
+ }
61
+
62
+ .switchContainer {
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 10px;
66
+
67
+ span {
68
+ font-size: 14px;
69
+ font-weight: 500;
70
+ color: #595959;
71
+ }
72
+ }
73
+
74
+ .notificationContent {
75
+ display: flex;
76
+ align-items: center;
77
+ position: relative;
78
+ padding-left: 12px;
79
+
80
+ @media (max-width: 768px) {
81
+ padding-left: 8px;
82
+ }
83
+ }
84
+
85
+ .criticalityIcon {
86
+ position: absolute;
87
+ left: 0;
88
+ top: 50%;
89
+ transform: translateY(-50%);
90
+ font-size: 16px;
91
+
92
+ @media (max-width: 768px) {
93
+ font-size: 12px;
94
+ }
95
+ }
96
+
97
+ .notificationIcon {
98
+ width: 50px;
99
+ height: 50px;
100
+ margin-left: 10px;
101
+ margin-right: 10px;
102
+
103
+ @media (max-width: 768px) {
104
+ width: 40px;
105
+ height: 40px;
106
+ margin-left: 8px;
107
+ margin-right: 8px;
108
+ }
109
+ }
110
+
111
+ .notificationText {
112
+ flex: 1;
113
+ overflow: hidden;
114
+ }
115
+
116
+ .notificationTitle {
117
+ font-weight: bold;
118
+ font-size: 14px;
119
+
120
+ @media (max-width: 768px) {
121
+ font-size: 12px;
122
+ }
123
+ }
124
+
125
+ .notificationDescription {
126
+ font-size: 12px;
127
+ color: #666;
128
+ margin-top: 2px;
129
+
130
+ @media (max-width: 768px) {
131
+ font-size: 10px;
132
+ }
133
+ }
134
+
135
+ .unreadIndicator {
136
+ width: 6px;
137
+ height: 6px;
138
+ background-color: #1890ff;
139
+ border-radius: 50%;
140
+ margin-left: 10px;
141
+
142
+ @media (max-width: 768px) {
143
+ width: 4px;
144
+ height: 4px;
145
+ margin-left: 8px;
146
+ }
147
+ }
148
+
149
+ .badgeStyle {
150
+ font-size: 12px;
151
+ }
152
+
153
+ .bellIcon {
154
+ font-size: 20px;
155
+ cursor: pointer;
156
+
157
+ @media (max-width: 768px) {
158
+ font-size: 18px;
159
+ }
160
+ }
161
+
162
+ .shake {
163
+ animation: shake 1s ease-in-out infinite;
164
+ }
165
+
166
+ @keyframes shake {
167
+ 0% {
168
+ transform: rotate(0deg);
169
+ }
170
+
171
+ 25% {
172
+ transform: rotate(10deg);
173
+ }
174
+
175
+ 50% {
176
+ transform: rotate(-10deg);
177
+ }
178
+
179
+ 75% {
180
+ transform: rotate(10deg);
181
+ }
182
+
183
+ 100% {
184
+ transform: rotate(0deg);
185
+ }
186
+ }
@@ -1,3 +1,5 @@
1
+ import { BellNotification } from "components/BellNotifications";
2
+
1
3
  export interface MenuItem {
2
4
  name: string;
3
5
  action: () => void;
@@ -20,4 +22,8 @@ export interface HeaderProps {
20
22
  onLeavingPage?: (targetRoute: string) => void;
21
23
  userName?: string;
22
24
  pageTitle?: string;
25
+ onFilterChange?: (filter: 'all' | 'unread') => void;
26
+ onNotificationClick?: (id: string, url: string) => void;
27
+ handleMarkAllAsRead?: () => void;
28
+ notifications?: BellNotification[];
23
29
  }
@@ -1,17 +1,32 @@
1
1
  import { useRef, useState } from 'react';
2
- import { DownOutlined, UpOutlined, BarsOutlined, CloseOutlined } from '@ant-design/icons';
2
+ import { DownOutlined, UpOutlined, BarsOutlined, CloseOutlined } from '@ant-design/icons';
3
3
  import omniumIcon from '../../assets/images/omnium-retail-logo.png';
4
4
  import omniumWhiteIcon from '../../assets/images/omnium-retail-logo-white.png';
5
5
  import styles from './styles.module.scss';
6
6
  import classNames from 'classnames';
7
7
  import { HeaderProps, MenuItem } from './Header.types';
8
8
  import { getMenuTopList, getMenuBottomList } from './Header.data';
9
+ import { BellNotifications } from '../BellNotifications';
9
10
 
10
11
  /**
11
12
  * Header component to display navigation bar with dropdown menus and action button.
12
13
  * @param {HeaderProps} props - Properties passed to the component.
13
14
  */
14
- export const Header = ({ menuList, actionButton, logout, homeUrl, profileUrl, onLeavingPage, userName, pageTitle }: HeaderProps) => {
15
+ export const Header = ({
16
+ menuList,
17
+ actionButton,
18
+ logout,
19
+ homeUrl,
20
+ profileUrl,
21
+ onLeavingPage,
22
+ userName,
23
+ pageTitle,
24
+ onFilterChange,
25
+ notifications,
26
+ onNotificationClick,
27
+ handleMarkAllAsRead
28
+ }: HeaderProps) => {
29
+
15
30
  const [isMenuOpen, setIsMenuOpen] = useState(false);
16
31
  const [activeDropdown, setActiveDropdown] = useState<number | null>(null);
17
32
  const headerRef = useRef<HTMLDivElement>(null);
@@ -21,7 +36,7 @@ export const Header = ({ menuList, actionButton, logout, homeUrl, profileUrl, on
21
36
  * Handle the logout action. If no logout function is provided, redirect to home as discussed in a meet
22
37
  */
23
38
  const onLogout = () => {
24
- if( logout ) {
39
+ if (logout) {
25
40
  logout();
26
41
  } else {
27
42
  onHome();
@@ -96,9 +111,9 @@ export const Header = ({ menuList, actionButton, logout, homeUrl, profileUrl, on
96
111
  }
97
112
  }}
98
113
  >
99
- <p
100
- className={ styles.name }
101
- dangerouslySetInnerHTML={{ __html: link.name }}></p>
114
+ <p
115
+ className={styles.name}
116
+ dangerouslySetInnerHTML={{ __html: link.name }}></p>
102
117
 
103
118
  {
104
119
  link.dropdownMenu && link.dropdownMenu.length > 0 && <div className={styles.arrow} onClick={(event) => handleDropdownClick(event, index)}>
@@ -133,16 +148,20 @@ export const Header = ({ menuList, actionButton, logout, homeUrl, profileUrl, on
133
148
 
134
149
  return (
135
150
  <div className={styles.header} ref={headerRef}>
136
- <BarsOutlined onClick={onToggleMenu} className={ styles.toggleMenuOpen } />
151
+ <BarsOutlined onClick={onToggleMenu} className={styles.toggleMenuOpen} />
137
152
 
138
153
  <div className={styles.logoContainer}>
139
154
  {
140
- pageTitle
141
- ? <p className={ styles.title}>{pageTitle}</p>
155
+ pageTitle
156
+ ? <p className={styles.title}>{pageTitle}</p>
142
157
  : <img src={omniumIcon} alt="Omnium Retail Blue Logo" />
143
158
  }
144
159
  </div>
145
160
 
161
+ {notifications &&
162
+ <BellNotifications onFilterChange={onFilterChange!} notifications={notifications} onNotificationClick={onNotificationClick!} handleMarkAllAsRead={handleMarkAllAsRead!} />
163
+ }
164
+
146
165
  <div className={classNames(styles.menu, { [styles.active]: isMenuOpen })}>
147
166
  <div className={styles.head}>
148
167
  <p className={styles.logoWrapper}>
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect } from 'react';
2
- import { Space, TableProps, Select, Dropdown, MenuProps } from 'antd';
2
+ import { Space, TableProps, Select, Dropdown } from 'antd';
3
3
  import { Table as AntdTable } from 'antd';
4
4
  import type { ColumnsType } from 'antd/es/table/interface';
5
5
  import styles from './styles.module.scss';
@@ -7,7 +7,6 @@ import { useTranslation } from 'react-i18next';
7
7
  import { MoreOutlined } from '@ant-design/icons';
8
8
  import classnames from 'classnames';
9
9
  import { Button } from '../Button';
10
- import classNames from 'classnames';
11
10
 
12
11
  export interface FilterTableOptions {
13
12
  value: string;
@@ -29,3 +29,4 @@ export * from './ResponsiveTable';
29
29
  export * from './WebCam';
30
30
  export * from './ResponseType';
31
31
  export * from './Header';
32
+ export * from './BellNotifications';
@@ -125,6 +125,11 @@
125
125
  },
126
126
  "upload": {
127
127
  "loadFile": "Upload file"
128
+ },
129
+ "bellNotifications": {
130
+ "noNotifications": "No notifications",
131
+ "onlyShowNoRead": "Only show unread",
132
+ "markAllAsRead": "Mark all as read"
128
133
  }
129
134
  }
130
135
  }