@omniumretail/component-library 1.2.26 → 1.2.28

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}}.oibCayv_cOyhyWCKzHSr{display:flex;justify-content:space-between;align-items:center;padding:10px;background-color:#f5f5f5;border-bottom:1px solid #e8e8e8}.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,22 @@
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
+ }
21
+ export declare const BellNotifications: ({ notifications, onFilterChange, onNotificationClick }: BellNotificationsProps) => import("react/jsx-runtime").JSX.Element;
22
+ 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,7 @@ 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
+ notifications?: BellNotification[];
21
25
  }
@@ -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 }: 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.26",
3
+ "version": "1.2.28",
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,141 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Badge, Dropdown, Menu } from 'antd';
3
+ import { BellOutlined, ExclamationCircleOutlined } 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
+ }
30
+
31
+ export const BellNotifications = ({ notifications, onFilterChange, onNotificationClick }: BellNotificationsProps) => {
32
+ const { t } = useTranslation();
33
+ const [maxChars, setMaxChars] = useState(30);
34
+ const [filter, setFilter] = useState<'all' | 'unread'>('all');
35
+ const [isShaking, setIsShaking] = useState(false);
36
+
37
+ const unreadCount = notifications.filter((notif) => !notif.IsRead).length;
38
+
39
+ const handleNotificationClick = (id: string, url: string) => {
40
+ onNotificationClick(id, url);
41
+ }
42
+
43
+ const handleFilterChange = (checked: boolean) => {
44
+ const newFilter = checked ? 'unread' : 'all';
45
+ setFilter(newFilter);
46
+ onFilterChange(newFilter);
47
+ };
48
+
49
+ useEffect(() => {
50
+ const handleResize = () => {
51
+ if (window.innerWidth < 1024) {
52
+ setMaxChars(30);
53
+ } else {
54
+ setMaxChars(60);
55
+ }
56
+ };
57
+
58
+ handleResize();
59
+ window.addEventListener('resize', handleResize);
60
+
61
+ return () => {
62
+ window.removeEventListener('resize', handleResize);
63
+ };
64
+ }, []);
65
+
66
+ useEffect(() => {
67
+ if (unreadCount > 0) {
68
+ const interval = setInterval(() => {
69
+ setIsShaking(true);
70
+
71
+ // Parar a animação após 2 segundos
72
+ const stopShakeTimeout = setTimeout(() => setIsShaking(false), 2000);
73
+
74
+ return () => clearTimeout(stopShakeTimeout);
75
+ }, 15000); // Repetir a cada 30 segundos
76
+
77
+ return () => clearInterval(interval);
78
+ }
79
+ }, [unreadCount]);
80
+
81
+ const menu = (
82
+ <Menu className={styles.notificationMenu}>
83
+ <div className={styles.switchContainer}>
84
+ <span>{t('components.bellNotifications.onlyShowNoRead')}</span>
85
+
86
+ <Switch
87
+ checked={filter === 'unread'}
88
+ onChange={handleFilterChange}
89
+ size="small"
90
+ />
91
+ </div>
92
+
93
+ {notifications.map((notif) => (
94
+ <Menu.Item
95
+ key={notif.Id}
96
+ className={styles.notificationItem}
97
+ onClick={() => handleNotificationClick(notif.Id, notif.RedirectURL)}
98
+ >
99
+ <div className={styles.notificationContent}>
100
+ {notif.Criticality?.Name === 'High' && (
101
+ <ExclamationCircleOutlined
102
+ className={styles.criticalityIcon}
103
+ style={{ color: notif.Criticality?.ColorHex }}
104
+ />
105
+ )}
106
+
107
+ <img src={notif.Icon} alt="Notification Icon" className={styles.notificationIcon} />
108
+
109
+ <div className={styles.notificationText}>
110
+ <div
111
+ className={styles.notificationTitle}
112
+ >
113
+ {notif.Title}
114
+ </div>
115
+
116
+ <div className={styles.notificationDescription}>
117
+ {notif.Description.length > maxChars
118
+ ? `${notif.Description.slice(0, maxChars)}...`
119
+ : notif.Description}
120
+ </div>
121
+ </div>
122
+
123
+ {!notif.IsRead && <div className={styles.unreadIndicator}></div>}
124
+ </div>
125
+ </Menu.Item>
126
+ ))}
127
+
128
+ {notifications.length === 0 && <Menu.Item disabled>{t('components.bellNotifications.noNotifications')}</Menu.Item>}
129
+ </Menu>
130
+ );
131
+
132
+ return (
133
+ <Dropdown overlay={menu} trigger={['click']}>
134
+ <Badge count={unreadCount} className={styles.badgeStyle} overflowCount={99} offset={[8, 0]}>
135
+ <BellOutlined className={`${styles.bellIcon} ${isShaking ? styles.shake : ''}`} />
136
+ </Badge>
137
+ </Dropdown>
138
+ );
139
+ };
140
+
141
+ export default BellNotifications;
@@ -0,0 +1,163 @@
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
+ .switchContainer {
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
+ span {
45
+ font-size: 14px;
46
+ font-weight: 500;
47
+ color: #595959;
48
+ }
49
+ }
50
+
51
+ .notificationContent {
52
+ display: flex;
53
+ align-items: center;
54
+ position: relative;
55
+ padding-left: 12px;
56
+
57
+ @media (max-width: 768px) {
58
+ padding-left: 8px;
59
+ }
60
+ }
61
+
62
+ .criticalityIcon {
63
+ position: absolute;
64
+ left: 0;
65
+ top: 50%;
66
+ transform: translateY(-50%);
67
+ font-size: 16px;
68
+
69
+ @media (max-width: 768px) {
70
+ font-size: 12px;
71
+ }
72
+ }
73
+
74
+ .notificationIcon {
75
+ width: 50px;
76
+ height: 50px;
77
+ margin-left: 10px;
78
+ margin-right: 10px;
79
+
80
+ @media (max-width: 768px) {
81
+ width: 40px;
82
+ height: 40px;
83
+ margin-left: 8px;
84
+ margin-right: 8px;
85
+ }
86
+ }
87
+
88
+ .notificationText {
89
+ flex: 1;
90
+ overflow: hidden;
91
+ }
92
+
93
+ .notificationTitle {
94
+ font-weight: bold;
95
+ font-size: 14px;
96
+
97
+ @media (max-width: 768px) {
98
+ font-size: 12px;
99
+ }
100
+ }
101
+
102
+ .notificationDescription {
103
+ font-size: 12px;
104
+ color: #666;
105
+ margin-top: 2px;
106
+
107
+ @media (max-width: 768px) {
108
+ font-size: 10px;
109
+ }
110
+ }
111
+
112
+ .unreadIndicator {
113
+ width: 6px;
114
+ height: 6px;
115
+ background-color: #1890ff;
116
+ border-radius: 50%;
117
+ margin-left: 10px;
118
+
119
+ @media (max-width: 768px) {
120
+ width: 4px;
121
+ height: 4px;
122
+ margin-left: 8px;
123
+ }
124
+ }
125
+
126
+ .badgeStyle {
127
+ font-size: 12px;
128
+ }
129
+
130
+ .bellIcon {
131
+ font-size: 20px;
132
+ cursor: pointer;
133
+
134
+ @media (max-width: 768px) {
135
+ font-size: 18px;
136
+ }
137
+ }
138
+
139
+ .shake {
140
+ animation: shake 1s ease-in-out infinite;
141
+ }
142
+
143
+ @keyframes shake {
144
+ 0% {
145
+ transform: rotate(0deg);
146
+ }
147
+
148
+ 25% {
149
+ transform: rotate(10deg);
150
+ }
151
+
152
+ 50% {
153
+ transform: rotate(-10deg);
154
+ }
155
+
156
+ 75% {
157
+ transform: rotate(10deg);
158
+ }
159
+
160
+ 100% {
161
+ transform: rotate(0deg);
162
+ }
163
+ }
@@ -225,13 +225,36 @@ export const CategorySidebar = (props: SidebarProps) => {
225
225
  };
226
226
 
227
227
  const addCategory = () => {
228
+ // Função para obter a maior chave em gData
229
+ const getNextKey = (categories: DataNode[]): string => {
230
+ let maxKey = 0;
231
+
232
+ const traverse = (nodes: DataNode[]) => {
233
+ nodes.forEach((node) => {
234
+ const key = parseFloat(node.key as string); // Converte a chave para número
235
+ if (key > maxKey) {
236
+ maxKey = key;
237
+ }
238
+ if (node.children) {
239
+ traverse(node.children); // Recorre para os filhos
240
+ }
241
+ });
242
+ };
243
+
244
+ traverse(categories);
245
+ return `${Math.floor(maxKey) + 1}`; // Retorna a próxima chave como um número inteiro
246
+ };
247
+
248
+ const newKey = getNextKey(gData); // Calcula a próxima chave disponível
249
+
228
250
  setGData((prevCategories: DataNode[]) => [
229
251
  ...prevCategories,
230
- { title: t('components.category.newCategory'), key: `${gData.length}` },
231
- ])
232
-
233
- setSidebarInfo([`${gData.length}`]);
234
- }
252
+ { title: t('components.category.newCategory'), key: newKey },
253
+ ]);
254
+
255
+ setSidebarInfo([newKey]); // Atualiza o estado do sidebar
256
+ };
257
+
235
258
 
236
259
  const removeCategory = () => {
237
260
  if (sidebarInfo) {
@@ -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,7 @@ 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
+ notifications?: BellNotification[];
23
28
  }
@@ -1,17 +1,18 @@
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 = ({ menuList, actionButton, logout, homeUrl, profileUrl, onLeavingPage, userName, pageTitle, onFilterChange, notifications, onNotificationClick }: HeaderProps) => {
15
16
  const [isMenuOpen, setIsMenuOpen] = useState(false);
16
17
  const [activeDropdown, setActiveDropdown] = useState<number | null>(null);
17
18
  const headerRef = useRef<HTMLDivElement>(null);
@@ -21,7 +22,7 @@ export const Header = ({ menuList, actionButton, logout, homeUrl, profileUrl, on
21
22
  * Handle the logout action. If no logout function is provided, redirect to home as discussed in a meet
22
23
  */
23
24
  const onLogout = () => {
24
- if( logout ) {
25
+ if (logout) {
25
26
  logout();
26
27
  } else {
27
28
  onHome();
@@ -96,9 +97,9 @@ export const Header = ({ menuList, actionButton, logout, homeUrl, profileUrl, on
96
97
  }
97
98
  }}
98
99
  >
99
- <p
100
- className={ styles.name }
101
- dangerouslySetInnerHTML={{ __html: link.name }}></p>
100
+ <p
101
+ className={styles.name}
102
+ dangerouslySetInnerHTML={{ __html: link.name }}></p>
102
103
 
103
104
  {
104
105
  link.dropdownMenu && link.dropdownMenu.length > 0 && <div className={styles.arrow} onClick={(event) => handleDropdownClick(event, index)}>
@@ -133,16 +134,20 @@ export const Header = ({ menuList, actionButton, logout, homeUrl, profileUrl, on
133
134
 
134
135
  return (
135
136
  <div className={styles.header} ref={headerRef}>
136
- <BarsOutlined onClick={onToggleMenu} className={ styles.toggleMenuOpen } />
137
+ <BarsOutlined onClick={onToggleMenu} className={styles.toggleMenuOpen} />
137
138
 
138
139
  <div className={styles.logoContainer}>
139
140
  {
140
- pageTitle
141
- ? <p className={ styles.title}>{pageTitle}</p>
141
+ pageTitle
142
+ ? <p className={styles.title}>{pageTitle}</p>
142
143
  : <img src={omniumIcon} alt="Omnium Retail Blue Logo" />
143
144
  }
144
145
  </div>
145
146
 
147
+ {notifications &&
148
+ <BellNotifications onFilterChange={onFilterChange!} notifications={notifications} onNotificationClick={onNotificationClick!} />
149
+ }
150
+
146
151
  <div className={classNames(styles.menu, { [styles.active]: isMenuOpen })}>
147
152
  <div className={styles.head}>
148
153
  <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,10 @@
125
125
  },
126
126
  "upload": {
127
127
  "loadFile": "Upload file"
128
+ },
129
+ "bellNotifications": {
130
+ "noNotifications": "No notifications",
131
+ "onlyShowNoRead": "Only show unread"
128
132
  }
129
133
  }
130
134
  }
@@ -125,6 +125,10 @@
125
125
  },
126
126
  "upload": {
127
127
  "loadFile": "Cargar archivo"
128
+ },
129
+ "bellNotifications": {
130
+ "noNotifications": "Sin notificaciones",
131
+ "onlyShowNoRead": "Mostrar sólo no leídas"
128
132
  }
129
133
  }
130
134
  }