@buerokratt-ria/menu 0.2.3 → 0.2.5

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.
@@ -1,47 +1,47 @@
1
- import { useEffect, useState } from "react";
2
- import { useQuery } from "@tanstack/react-query";
3
- import { MenuItem } from "../types/menuItem";
4
- import useMenuItems from "./useMenuItems";
5
- import { CountConf } from "../types/countConf";
6
-
7
- const useFilteredMenuItems = (countConf?: CountConf) => {
8
- const items = useMenuItems(countConf);
9
- const [menuItems, setMenuItems] = useState<MenuItem[]>([]);
10
-
11
- const { data } = useQuery<{ response: [] }>({
12
- queryKey: ['accounts/user-role', 'prod'],
13
- });
14
-
15
- useEffect(() => {
16
- if (!data) {
17
- return;
18
- }
19
-
20
- const roles: any[] = data.response;
21
- const rolePermissions = {
22
- ROLE_ADMINISTRATOR: ["conversations", "training", "analytics", "services", "settings", "monitoring"],
23
- ROLE_SERVICE_MANAGER: ["training", "services", "monitoring"],
24
- ROLE_CUSTOMER_SUPPORT_AGENT: ["conversations", "monitoring"],
25
- ROLE_CHATBOT_TRAINER: ["training", "monitoring"],
26
- ROLE_ANALYST: ["analytics", "monitoring"],
27
- };
28
-
29
- let permissions = new Set();
30
-
31
- roles.forEach((role) => {
32
- if (rolePermissions[role]) {
33
- rolePermissions[role].forEach((permission: any) => permissions.add(permission));
34
- }
35
- });
36
-
37
- const filteredItems = items.filter((item: any) => {
38
- return permissions.has(item.id);
39
- });
40
-
41
- setMenuItems(filteredItems ?? []);
42
- }, [items, data]);
43
-
44
- return menuItems;
45
- }
46
-
47
- export default useFilteredMenuItems;
1
+ import { useEffect, useState } from "react";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { MenuItem } from "../types/menuItem";
4
+ import useMenuItems from "./useMenuItems";
5
+ import { CountConf } from "../types/countConf";
6
+
7
+ const useFilteredMenuItems = (countConf?: CountConf) => {
8
+ const items = useMenuItems(countConf);
9
+ const [menuItems, setMenuItems] = useState<MenuItem[]>([]);
10
+
11
+ const { data } = useQuery<{ response: [] }>({
12
+ queryKey: ['accounts/user-role', 'prod'],
13
+ });
14
+
15
+ useEffect(() => {
16
+ if (!data) {
17
+ return;
18
+ }
19
+
20
+ const roles: any[] = data.response;
21
+ const rolePermissions = {
22
+ ROLE_ADMINISTRATOR: ["conversations", "training", "analytics", "services", "settings", "monitoring"],
23
+ ROLE_SERVICE_MANAGER: ["training", "services", "monitoring"],
24
+ ROLE_CUSTOMER_SUPPORT_AGENT: ["conversations", "monitoring"],
25
+ ROLE_CHATBOT_TRAINER: ["training", "monitoring"],
26
+ ROLE_ANALYST: ["analytics", "monitoring"],
27
+ };
28
+
29
+ let permissions = new Set();
30
+
31
+ roles.forEach((role) => {
32
+ if (rolePermissions[role]) {
33
+ rolePermissions[role].forEach((permission: any) => permissions.add(permission));
34
+ }
35
+ });
36
+
37
+ const filteredItems = items.filter((item: any) => {
38
+ return permissions.has(item.id);
39
+ });
40
+
41
+ setMenuItems(filteredItems ?? []);
42
+ }, [items, data]);
43
+
44
+ return menuItems;
45
+ }
46
+
47
+ export default useFilteredMenuItems;
@@ -1,77 +1,77 @@
1
- import { useMemo, useState } from "react";
2
- import { useQuery } from "@tanstack/react-query";
3
- import menuStructure from '../data/menu-structure.json';
4
- import { MenuItem } from "../types/menuItem";
5
- import { CountConf } from "../types/countConf";
6
-
7
- const useMenuItems = (count?: CountConf) => {
8
- const externalMenuItems = import.meta.env.REACT_APP_MENU_JSON;
9
- const cachedMenu = getMenuCache();
10
- const [mainMenuItems, setMainMenuItems] = useState<MenuItem[] | null>(cachedMenu ?? []);
11
-
12
- useQuery({
13
- enabled: !externalMenuItems && !cachedMenu,
14
- queryKey: [import.meta.env.REACT_APP_MENU_URL + import.meta.env.REACT_APP_MENU_PATH],
15
- onSuccess: (res: any) => {
16
- try {
17
- setMainMenuItems(res);
18
- setCache(res);
19
- } catch (e) {
20
- console.error(e);
21
- }
22
- },
23
- onError: () => {
24
- setMainMenuItems(cachedMenu);
25
- },
26
- });
27
-
28
- const items = useMemo(() => {
29
- let externals;
30
-
31
- try {
32
- if (externalMenuItems) {
33
- externals = JSON.parse(externalMenuItems);
34
- if (!Array.isArray(externals)) {
35
- console.warn('REACT_APP_MENU_JSON was ignored becuase it wasn\'t an array');
36
- }
37
- }
38
- } catch (e) {
39
- console.warn(e);
40
- }
41
-
42
- const addCountToPathFields = (item: MenuItem) => {
43
- if (count && item.path in count) {
44
- item.count = count[item.path]
45
- }
46
-
47
- if (item.children) {
48
- item.children.forEach(child => addCountToPathFields(child));
49
- }
50
- }
51
-
52
- const finalMenu = externals ?? mainMenuItems ?? menuStructure ?? [];
53
- finalMenu.forEach((item: MenuItem) => addCountToPathFields(item))
54
-
55
- return finalMenu;
56
- }, [externalMenuItems, mainMenuItems, menuStructure, count]);
57
-
58
- return items;
59
- }
60
-
61
- function getMenuCache(): any {
62
- const cached = getCache();
63
- if (Array.isArray(cached) && cached.length > 0)
64
- return cached;
65
- return null;
66
- }
67
-
68
- function getCache(): any {
69
- const cache = localStorage.getItem('mainmenu-cache') ?? '[]';
70
- return JSON.parse(cache);
71
- }
72
-
73
- function setCache(res: any) {
74
- localStorage.setItem('mainmenu-cache', JSON.stringify(res));
75
- }
76
-
77
- export default useMenuItems;
1
+ import { useMemo, useState } from "react";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import menuStructure from '../data/menu-structure.json';
4
+ import { MenuItem } from "../types/menuItem";
5
+ import { CountConf } from "../types/countConf";
6
+
7
+ const useMenuItems = (count?: CountConf) => {
8
+ const externalMenuItems = import.meta.env.REACT_APP_MENU_JSON;
9
+ const cachedMenu = getMenuCache();
10
+ const [mainMenuItems, setMainMenuItems] = useState<MenuItem[] | null>(cachedMenu ?? []);
11
+
12
+ useQuery({
13
+ enabled: !externalMenuItems && !cachedMenu,
14
+ queryKey: [import.meta.env.REACT_APP_MENU_URL + import.meta.env.REACT_APP_MENU_PATH],
15
+ onSuccess: (res: any) => {
16
+ try {
17
+ setMainMenuItems(res);
18
+ setCache(res);
19
+ } catch (e) {
20
+ console.error(e);
21
+ }
22
+ },
23
+ onError: () => {
24
+ setMainMenuItems(cachedMenu);
25
+ },
26
+ });
27
+
28
+ const items = useMemo(() => {
29
+ let externals;
30
+
31
+ try {
32
+ if (externalMenuItems) {
33
+ externals = JSON.parse(externalMenuItems);
34
+ if (!Array.isArray(externals)) {
35
+ console.warn('REACT_APP_MENU_JSON was ignored becuase it wasn\'t an array');
36
+ }
37
+ }
38
+ } catch (e) {
39
+ console.warn(e);
40
+ }
41
+
42
+ const addCountToPathFields = (item: MenuItem) => {
43
+ if (count && item.path in count) {
44
+ item.count = count[item.path]
45
+ }
46
+
47
+ if (item.children) {
48
+ item.children.forEach(child => addCountToPathFields(child));
49
+ }
50
+ }
51
+
52
+ const finalMenu = externals ?? mainMenuItems ?? menuStructure ?? [];
53
+ finalMenu.forEach((item: MenuItem) => addCountToPathFields(item))
54
+
55
+ return finalMenu;
56
+ }, [externalMenuItems, mainMenuItems, menuStructure, count]);
57
+
58
+ return items;
59
+ }
60
+
61
+ function getMenuCache(): any {
62
+ const cached = getCache();
63
+ if (Array.isArray(cached) && cached.length > 0)
64
+ return cached;
65
+ return null;
66
+ }
67
+
68
+ function getCache(): any {
69
+ const cache = localStorage.getItem('mainmenu-cache') ?? '[]';
70
+ return JSON.parse(cache);
71
+ }
72
+
73
+ function setCache(res: any) {
74
+ localStorage.setItem('mainmenu-cache', JSON.stringify(res));
75
+ }
76
+
77
+ export default useMenuItems;
@@ -1,53 +1,53 @@
1
- import React, { FC, MouseEvent, useState, useMemo } from 'react';
2
- import clsx from 'clsx';
3
- import { MdClose } from 'react-icons/md';
4
- import Icon from './components/icons/icon/icon';
5
- import { useTranslation } from "react-i18next";
6
- import MenuTree from './components/menuTree';
7
- import useFilteredMenuItems from './hooks/useFilteredMenuItems';
8
- import './main-navigation.scss';
9
- import { CountConf } from "./types/countConf";
10
-
11
- interface MainNavigationProps {
12
- countConf?: CountConf
13
- }
14
-
15
- const MainNavigation: FC<MainNavigationProps> = ({countConf}) => {
16
- const { t } = useTranslation();
17
- const menuItems = useFilteredMenuItems(countConf);
18
- const serviceId = useMemo(() => import.meta.env.REACT_APP_SERVICE_ID.split(','), []);
19
-
20
- const [navCollapsed, setNavCollapsed] = useState(false);
21
-
22
- const handleNavToggle = (event: MouseEvent) => {
23
- setNavCollapsed(false);
24
- const isExpanded = event.currentTarget.getAttribute('aria-expanded') === 'true';
25
- event.currentTarget.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
26
- };
27
-
28
- const handleCloseButtonClick = () => {
29
- const doesMenuHasExpandedItem = !!document.querySelector('button[aria-expanded="true"]');
30
- if(doesMenuHasExpandedItem)
31
- setNavCollapsed(!navCollapsed);
32
- }
33
-
34
- if (!menuItems) return null;
35
-
36
- return (
37
- <nav className={clsx('nav', { 'collapsed': navCollapsed })}>
38
- <button className='nav__menu-toggle close-button-item' onClick={handleCloseButtonClick}>
39
- <Icon icon={<MdClose />} />
40
- <span className='menu-item-title'>{t(navCollapsed ? 'mainMenu.openMenu' : 'mainMenu.closeMenu' )}</span>
41
- </button>
42
- <ul className='nav__menu'>
43
- <MenuTree
44
- menuItems={menuItems}
45
- serviceId={serviceId}
46
- handleNavToggle={handleNavToggle}
47
- />
48
- </ul>
49
- </nav>
50
- );
51
- };
52
-
53
- export default MainNavigation;
1
+ import React, { FC, MouseEvent, useState, useMemo } from 'react';
2
+ import clsx from 'clsx';
3
+ import { MdClose } from 'react-icons/md';
4
+ import Icon from './components/icons/icon/icon';
5
+ import { useTranslation } from "react-i18next";
6
+ import MenuTree from './components/menuTree';
7
+ import useFilteredMenuItems from './hooks/useFilteredMenuItems';
8
+ import './main-navigation.scss';
9
+ import { CountConf } from "./types/countConf";
10
+
11
+ interface MainNavigationProps {
12
+ countConf?: CountConf
13
+ }
14
+
15
+ const MainNavigation: FC<MainNavigationProps> = ({countConf}) => {
16
+ const { t } = useTranslation();
17
+ const menuItems = useFilteredMenuItems(countConf);
18
+ const serviceId = useMemo(() => import.meta.env.REACT_APP_SERVICE_ID.split(','), []);
19
+
20
+ const [navCollapsed, setNavCollapsed] = useState(false);
21
+
22
+ const handleNavToggle = (event: MouseEvent) => {
23
+ setNavCollapsed(false);
24
+ const isExpanded = event.currentTarget.getAttribute('aria-expanded') === 'true';
25
+ event.currentTarget.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
26
+ };
27
+
28
+ const handleCloseButtonClick = () => {
29
+ const doesMenuHasExpandedItem = !!document.querySelector('button[aria-expanded="true"]');
30
+ if(doesMenuHasExpandedItem)
31
+ setNavCollapsed(!navCollapsed);
32
+ }
33
+
34
+ if (!menuItems) return null;
35
+
36
+ return (
37
+ <nav className={clsx('nav', { 'collapsed': navCollapsed })}>
38
+ <button className='nav__menu-toggle close-button-item' onClick={handleCloseButtonClick}>
39
+ <Icon icon={<MdClose />} />
40
+ <span className='menu-item-title'>{t(navCollapsed ? 'mainMenu.openMenu' : 'mainMenu.closeMenu' )}</span>
41
+ </button>
42
+ <ul className='nav__menu'>
43
+ <MenuTree
44
+ menuItems={menuItems}
45
+ serviceId={serviceId}
46
+ handleNavToggle={handleNavToggle}
47
+ />
48
+ </ul>
49
+ </nav>
50
+ );
51
+ };
52
+
53
+ export default MainNavigation;
@@ -1,129 +1,129 @@
1
- @import '@buerokratt-ria/styles/styles/tools/spacing';
2
- @import '@buerokratt-ria/styles/styles/tools/color';
3
- @import '@buerokratt-ria/styles/styles/settings/variables/typography';
4
-
5
- .nav {
6
- $self: &;
7
- width: 208px;
8
- background-color: get-color(sapphire-blue-10);
9
- overflow: auto;
10
- scrollbar-width: none;
11
- transition: width .1s ease-out;
12
-
13
- &::-webkit-scrollbar {
14
- display: none;
15
- }
16
-
17
- li, a, .nav__toggle, .nav__menu-toggle {
18
- font-size: 14px;
19
- line-height: 1.5;
20
- }
21
-
22
- &__menu-toggle {
23
- display: flex;
24
- align-items: center;
25
-
26
- &:hover {
27
- background-color: get-color(sapphire-blue-8);
28
- }
29
-
30
- &:active {
31
- background-color: get-color(sapphire-blue-7);
32
- }
33
- }
34
-
35
- a, .nav__toggle {
36
- width: 100%;
37
- display: flex;
38
- align-items: center;
39
- gap: get-spacing(paldiski);
40
- color: get-color(black-coral-0);
41
- padding: 14px 8px 14px 32px;
42
- box-shadow: inset 0 -1px 0 get-color(sapphire-blue-14);
43
-
44
- span:not(.icon) {
45
- flex: 1;
46
- display: block;
47
- }
48
-
49
- &:hover {
50
- background-color: get-color(sapphire-blue-8);
51
- }
52
-
53
- &:active {
54
- background-color: get-color(sapphire-blue-7);
55
- }
56
-
57
- &.active {
58
- font-weight: 700;
59
- }
60
- }
61
-
62
- &__toggle {
63
- &[aria-expanded=true] {
64
- font-weight: 700;
65
-
66
- .icon {
67
- transform: rotate(180deg);
68
- }
69
-
70
- + ul {
71
- display: block;
72
- }
73
- }
74
-
75
- &.nav__toggle--icon {
76
- padding-left: 8px;
77
-
78
- .icon:first-child {
79
- transform: none;
80
- }
81
- }
82
- }
83
-
84
- &__toggle-icon {
85
- margin-left: auto;
86
- }
87
-
88
- &__menu-toggle {
89
- display: flex;
90
- align-items: center;
91
- gap: get-spacing(paldiski);
92
- width: 100%;
93
- color: get-color(white);
94
- padding: 14px 8px;
95
- box-shadow: inset 0 -1px 0 get-color(sapphire-blue-14);
96
- }
97
-
98
- &__submenu {
99
- display: none;
100
-
101
- a, .nav__toggle {
102
- background-color: get-color(sapphire-blue-14);
103
- box-shadow: inset 0 -1px 0 get-color(sapphire-blue-17);
104
- }
105
-
106
- #{$self} {
107
- &__submenu {
108
- a {
109
- background-color: get-color(sapphire-blue-17);
110
- box-shadow: inset 0 -1px 0 get-color(black);
111
- padding: 14px 48px 14px 40px;
112
- }
113
- }
114
- }
115
- }
116
- }
117
-
118
- .collapsed {
119
- .nav__submenu {
120
- visibility: hidden;
121
- height: 0;
122
- }
123
-
124
- button[aria-expanded=true] {
125
- .icon {
126
- transform: rotate(0deg);
127
- }
128
- }
129
- }
1
+ @import '@buerokratt-ria/styles/styles/tools/spacing';
2
+ @import '@buerokratt-ria/styles/styles/tools/color';
3
+ @import '@buerokratt-ria/styles/styles/settings/variables/typography';
4
+
5
+ .nav {
6
+ $self: &;
7
+ width: 208px;
8
+ background-color: get-color(sapphire-blue-10);
9
+ overflow: auto;
10
+ scrollbar-width: none;
11
+ transition: width .1s ease-out;
12
+
13
+ &::-webkit-scrollbar {
14
+ display: none;
15
+ }
16
+
17
+ li, a, .nav__toggle, .nav__menu-toggle {
18
+ font-size: 14px;
19
+ line-height: 1.5;
20
+ }
21
+
22
+ &__menu-toggle {
23
+ display: flex;
24
+ align-items: center;
25
+
26
+ &:hover {
27
+ background-color: get-color(sapphire-blue-8);
28
+ }
29
+
30
+ &:active {
31
+ background-color: get-color(sapphire-blue-7);
32
+ }
33
+ }
34
+
35
+ a, .nav__toggle {
36
+ width: 100%;
37
+ display: flex;
38
+ align-items: center;
39
+ gap: get-spacing(paldiski);
40
+ color: get-color(black-coral-0);
41
+ padding: 14px 8px 14px 32px;
42
+ box-shadow: inset 0 -1px 0 get-color(sapphire-blue-14);
43
+
44
+ span:not(.icon) {
45
+ flex: 1;
46
+ display: block;
47
+ }
48
+
49
+ &:hover {
50
+ background-color: get-color(sapphire-blue-8);
51
+ }
52
+
53
+ &:active {
54
+ background-color: get-color(sapphire-blue-7);
55
+ }
56
+
57
+ &.active {
58
+ font-weight: 700;
59
+ }
60
+ }
61
+
62
+ &__toggle {
63
+ &[aria-expanded=true] {
64
+ font-weight: 700;
65
+
66
+ .icon {
67
+ transform: rotate(180deg);
68
+ }
69
+
70
+ + ul {
71
+ display: block;
72
+ }
73
+ }
74
+
75
+ &.nav__toggle--icon {
76
+ padding-left: 8px;
77
+
78
+ .icon:first-child {
79
+ transform: none;
80
+ }
81
+ }
82
+ }
83
+
84
+ &__toggle-icon {
85
+ margin-left: auto;
86
+ }
87
+
88
+ &__menu-toggle {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: get-spacing(paldiski);
92
+ width: 100%;
93
+ color: get-color(white);
94
+ padding: 14px 8px;
95
+ box-shadow: inset 0 -1px 0 get-color(sapphire-blue-14);
96
+ }
97
+
98
+ &__submenu {
99
+ display: none;
100
+
101
+ a, .nav__toggle {
102
+ background-color: get-color(sapphire-blue-14);
103
+ box-shadow: inset 0 -1px 0 get-color(sapphire-blue-17);
104
+ }
105
+
106
+ #{$self} {
107
+ &__submenu {
108
+ a {
109
+ background-color: get-color(sapphire-blue-17);
110
+ box-shadow: inset 0 -1px 0 get-color(black);
111
+ padding: 14px 48px 14px 40px;
112
+ }
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ .collapsed {
119
+ .nav__submenu {
120
+ visibility: hidden;
121
+ height: 0;
122
+ }
123
+
124
+ button[aria-expanded=true] {
125
+ .icon {
126
+ transform: rotate(0deg);
127
+ }
128
+ }
129
+ }
@@ -1,3 +1,3 @@
1
- export interface CountConf {
2
- [key: string]: number
3
- }
1
+ export interface CountConf {
2
+ [key: string]: number
3
+ }
@@ -1,14 +1,14 @@
1
-
2
- export interface TranslatedLabel {
3
- [lang: string] : string;
4
- }
5
-
6
- export interface MenuItem {
7
- id?: string;
8
- label: TranslatedLabel;
9
- path?: string;
10
- target?: '_blank' | '_self';
11
- children?: MenuItem[];
12
- hidden?: boolean;
13
- count?: number
14
- }
1
+
2
+ export interface TranslatedLabel {
3
+ [lang: string] : string;
4
+ }
5
+
6
+ export interface MenuItem {
7
+ id?: string;
8
+ label: TranslatedLabel;
9
+ path?: string;
10
+ target?: '_blank' | '_self';
11
+ children?: MenuItem[];
12
+ hidden?: boolean;
13
+ count?: number
14
+ }