Browse Source

Check permissions

merge-requests/166/head
Oksana Stepanenko 2 years ago committed by Coder
parent
commit
68a484e329
  1. 1
      package.json
  2. 1
      public/OneSignalSDKWorker.js
  3. 3
      src/App.tsx
  4. 7
      src/api/factories/requests.ts
  5. 11
      src/api/notifications/request.interfaces.ts
  6. 33
      src/api/notifications/request.ts
  7. 13
      src/api/permissions/requests.ts
  8. 11
      src/api/tasks/requests.ts
  9. 13
      src/components/Fields/ImageField.tsx
  10. 11
      src/components/SmartComponents/UserSelectWithSearch.tsx
  11. 1
      src/components/TableGrid/components/custom-table-row.component.tsx
  12. 8
      src/components/TableGrid/style.scss
  13. 15
      src/containers/App/router/Router.tsx
  14. 4
      src/containers/App/router/routes.enum.ts
  15. 15
      src/containers/Contact/components/Actions/actions.component.tsx
  16. 11
      src/containers/ContactsUsers/index.tsx
  17. 22
      src/containers/Factory/components/TreeFactory/index.tsx
  18. 31
      src/containers/Factory/hooks/use-factory.hook.ts
  19. 19
      src/containers/Factory/index.tsx
  20. 3
      src/containers/GroupPermissions/components/Data/index.tsx
  21. 28
      src/containers/GroupPermissions/configs/columns.config.tsx
  22. 47
      src/containers/GroupPermissions/index.tsx
  23. 8
      src/containers/Layout/index.tsx
  24. 66
      src/containers/Layout/sidebar/SidebarContent.tsx
  25. 2
      src/containers/Layout/sidebar/filter_task/FilterTask.tsx
  26. 32
      src/containers/Layout/sidebar/filter_user/FilterUser.tsx
  27. 111
      src/containers/Layout/topbar/Topbar.tsx
  28. 91
      src/containers/Layout/topbar/TopbarNotificationBlock.tsx
  29. 59
      src/containers/Layout/topbar/config/config.ts
  30. 17
      src/containers/Permissions/components/FormPermissions/index.tsx
  31. 33
      src/containers/Permissions/components/PermissionsInfo/index.tsx
  32. 23
      src/containers/Profile/components/Actions/actions.component.tsx
  33. 2
      src/containers/Profile/components/profile-main.component.tsx
  34. 40
      src/containers/Profile/components/profile-tabs.component.tsx
  35. 12
      src/containers/Profile/hooks/use-edit-user-permissions.hook.ts
  36. 7
      src/containers/Profile/profile.page.tsx
  37. 20
      src/containers/Tasks/components/create-update-task-modal.component.tsx
  38. 22
      src/containers/Tasks/components/file-item.component.tsx
  39. 20
      src/containers/Tasks/components/modal-tab-document.component.tsx
  40. 11
      src/containers/Tasks/components/modal-tab-main-info.component.tsx
  41. 54
      src/containers/Tasks/configs/selected-tasks-menu.config.ts
  42. 22
      src/containers/Tasks/tasks.screen.tsx
  43. 33
      src/containers/Taxonomy/components/Form/index.tsx
  44. 21
      src/containers/Taxonomy/components/TreeTaxonomy/index.tsx
  45. 63
      src/containers/Taxonomy/index.tsx
  46. 13
      src/containers/User/components/FormUser/index.tsx
  47. 82
      src/containers/User/configs/selected-users-menu.config.tsx
  48. 17
      src/containers/User/hooks/use-create-edit-user.hook.ts
  49. 75
      src/containers/User/index.tsx
  50. 13
      src/containers/UserDetail/user-detail.page.tsx
  51. 11
      src/index.tsx
  52. 61
      src/scss/component/topbar.scss
  53. 8
      src/services/domain/auth.service.ts
  54. 4
      src/services/domain/factories.service.ts
  55. 22
      src/services/domain/permissions.service.ts
  56. 1
      src/services/system/index.ts
  57. 140
      src/services/system/notifications.service.ts
  58. 9
      src/services/system/real-time.service.ts
  59. 1
      src/shared/enums/index.ts
  60. 19
      src/shared/enums/push-notification.enum.ts
  61. 3
      src/shared/enums/user.enum.ts
  62. 10
      src/shared/events/index.ts
  63. 45
      src/shared/helpers/permissions.helpers.ts
  64. 1
      src/shared/interfaces/index.ts
  65. 6
      src/shared/interfaces/notification.interfaces.ts
  66. 8
      src/shared/interfaces/permissions.interface.ts
  67. 15
      src/shared/interfaces/push-notification.interfaces.ts
  68. 12
      src/store/permissions/reducer.ts
  69. 3
      src/store/permissions/selectors.ts
  70. 16
      src/store/permissions/types.ts

1
package.json

@ -118,6 +118,7 @@ @@ -118,6 +118,7 @@
"react-infinite-scroll-component": "^6.1.0",
"react-input-mask": "^2.0.4",
"react-mentions": "^4.4.7",
"react-onesignal": "^2.0.4",
"react-phone-input-2": "^2.9.5",
"react-player": "^2.10.1",
"react-redux": "^7.2.5",

1
public/OneSignalSDKWorker.js

@ -0,0 +1 @@ @@ -0,0 +1 @@
importScripts('https://cdn.onesignal.com/sdks/OneSignalSDKWorker.js');

3
src/App.tsx

@ -13,6 +13,7 @@ import { ConfigProvider } from "antd"; @@ -13,6 +13,7 @@ import { ConfigProvider } from "antd";
import "moment/locale/uk";
import locale from "antd/lib/locale/uk_UA";
import { appService } from "./services/system/app.service";
import { notificationsService } from "./services/system";
const App = () => {
// const registerPushListener = (pushNotification?: any) => {
@ -56,7 +57,7 @@ const App = () => { @@ -56,7 +57,7 @@ const App = () => {
appService.initApp();
handleAutoAuth();
mobileCheck();
notificationsService.runOneSignal();
// if (messaging) {
// messaging.requestPermission().then(async function () {
// const token = await messaging.getToken();

7
src/api/factories/requests.ts

@ -7,6 +7,10 @@ const fetchFactories = (): ApiResponse<IFactory[]> => { @@ -7,6 +7,10 @@ const fetchFactories = (): ApiResponse<IFactory[]> => {
return http.get<IFactory[]>("admin/factories", null);
};
const fetchFactoriesFullList = (): ApiResponse<IFactory[]> => {
return http.get<IFactory[]>("admin/factories/all", null);
};
const createFactory = (payload: ICreateFactory): ApiResponse<IFactory> => {
return http.post<IFactory>("admin/factories", payload);
};
@ -22,5 +26,6 @@ export const factoriesApi = { @@ -22,5 +26,6 @@ export const factoriesApi = {
fetchFactories,
createFactory,
updateFactory,
deleteFactory
deleteFactory,
fetchFactoriesFullList
};

11
src/api/notifications/request.interfaces.ts

@ -1,5 +1,12 @@ @@ -1,5 +1,12 @@
import { IPagination } from "@/shared";
export interface IFetchNotification extends IPagination {
export interface IFetchNotification extends IPagination {}
}
export interface ISaveDevicePayload {
deviceUuid: string;
notificationUserId: string;
}
export interface IReadNotificationsPayload {
ids: number[]
}

33
src/api/notifications/request.ts

@ -1,14 +1,29 @@ @@ -1,14 +1,29 @@
import http from '../http.service'
import { ApiResponse } from '../http.types'
import * as Req from './request.interfaces'
import * as Res from './response.interfaces'
import http from "../http.service";
import { ApiResponse } from "../http.types";
import * as Req from "./request.interfaces";
import * as Res from "./response.interfaces";
const fetchNotificationsList = (
params: Req.IFetchNotification,
params: Req.IFetchNotification
): ApiResponse<Res.INotificationsList> => {
return http.get<Res.INotificationsList>(`admin/notifications`, { params })
}
return http.get<Res.INotificationsList>(`admin/notifications`, { params });
};
export const saveUserDevice = (payload: Req.ISaveDevicePayload) => {
return http.post("admin/notifications/device", payload);
};
const readAll = () => {
return http.put<void>("admin/notifications/read", {});
};
const readByIds = (payload: Req.IReadNotificationsPayload) => {
return http.put<void>("admin/notifications/read-by-ids", payload);
};
export const notificApi = {
fetchNotificationsList
}
fetchNotificationsList,
saveUserDevice,
readAll,
readByIds
};

13
src/api/permissions/requests.ts

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import * as Res from "./responses.interfaces";
import * as Req from "./requests.interfaces";
import http from "../http.service";
import { IMyPermissions } from "@/shared";
const fetchAvailablePermissions = () => {
return http.get<Res.IAvailablePermissions>("admin/permissions");
@ -33,10 +34,20 @@ const fetchUserPermissionsWithInfo = ( @@ -33,10 +34,20 @@ const fetchUserPermissionsWithInfo = (
);
};
const fetchMyPermissionsTransformed = () => {
return http.get<IMyPermissions>("admin/permissions/my-transformed");
};
const fetchMyPermissionsOriginal = () => {
return http.get<IMyPermissions>("admin/permissions/my-original");
};
export const permissionsApi = {
fetchAvailablePermissions,
saveUserPermissions,
deleteUserPermissions,
fetchUserPermissions,
fetchUserPermissionsWithInfo
fetchUserPermissionsWithInfo,
fetchMyPermissionsTransformed,
fetchMyPermissionsOriginal
};

11
src/api/tasks/requests.ts

@ -32,7 +32,15 @@ const getTaskExecutors = ( @@ -32,7 +32,15 @@ const getTaskExecutors = (
params: IPagination & { includeIds?: number[]; name?: string }
): ApiResponse<res.IFetchExecutorsList> => {
return http.get<res.IFetchExecutorsList>("admin/tasks/executors", {
params,
params
});
};
const getAllExecutors = (
params: IPagination & { includeIds?: number[]; name?: string }
): ApiResponse<res.IFetchExecutorsList> => {
return http.get<res.IFetchExecutorsList>("admin/tasks/executors-all", {
params
});
};
@ -77,4 +85,5 @@ export const tasksApi = { @@ -77,4 +85,5 @@ export const tasksApi = {
finishTasks,
reassignTask,
removeTasks,
getAllExecutors
};

13
src/components/Fields/ImageField.tsx

@ -13,6 +13,7 @@ interface IProps { @@ -13,6 +13,7 @@ interface IProps {
alt?: string;
alt2?: string;
file?: any;
disabled?: boolean;
}
interface IState {
@ -200,11 +201,13 @@ class ImageField extends Component<IProps, IState> { @@ -200,11 +201,13 @@ class ImageField extends Component<IProps, IState> {
src={this.state.imagePreviewUrl}
alt=""
/>
<input
style={{ display: "none" }}
type="file"
onChange={this.onChange}
/>
{!this.props.disabled && (
<input
style={{ display: "none" }}
type="file"
onChange={this.onChange}
/>
)}
</label>
{alt ? (
<p style={{ padding: 0, margin: 0, fontSize: "10px" }}>{alt}</p>

11
src/components/SmartComponents/UserSelectWithSearch.tsx

@ -47,6 +47,7 @@ export const UserSelectWithSearch: FC<IUserSelectWithSearchProps> = ({ @@ -47,6 +47,7 @@ export const UserSelectWithSearch: FC<IUserSelectWithSearchProps> = ({
const fetchItemsByType = {
[EUsersListType.All]: usersApi.fetchShortInfoUsersList,
[EUsersListType.Executors]: tasksApi.getTaskExecutors,
[EUsersListType.ExecutorsAll]: tasksApi.getAllExecutors,
};
const userPaginationList = usePaginationList<Option>({
@ -55,9 +56,7 @@ export const UserSelectWithSearch: FC<IUserSelectWithSearchProps> = ({ @@ -55,9 +56,7 @@ export const UserSelectWithSearch: FC<IUserSelectWithSearchProps> = ({
items.map((it) => ({
title: `${it.firstName} ${it.lastName}`,
value:
type === EUsersListType.Executors
? it.userId.toString()
: it.id.toString(),
type === EUsersListType.All ? it.id.toString() : it.userId.toString(),
})),
loadParams: {
limit: !_.isEmpty(defaultOptions) ? defaultOptions.length : limit,
@ -81,9 +80,9 @@ export const UserSelectWithSearch: FC<IUserSelectWithSearchProps> = ({ @@ -81,9 +80,9 @@ export const UserSelectWithSearch: FC<IUserSelectWithSearchProps> = ({
const options = data.items.map((option) => ({
title: `${option.firstName} ${option.lastName}`,
value:
type === EUsersListType.Executors
? (option as IExecutorsInListRes).userId.toString()
: (option as IShortInfoUserRes).id.toString(),
type === EUsersListType.All
? (option as IShortInfoUserRes).id.toString()
: (option as IExecutorsInListRes).userId.toString(),
}));
setAdditionalOptions(options);

1
src/components/TableGrid/components/custom-table-row.component.tsx

@ -45,6 +45,7 @@ export const CustomTableRow = <T extends DataType>(props: IProps<T>) => { @@ -45,6 +45,7 @@ export const CustomTableRow = <T extends DataType>(props: IProps<T>) => {
onVisibleChange={(visible: boolean) => {
props.onOpenMenu(visible, props.rowData.row.id);
}}
visible={_.isEmpty(props.menuConfig) ? false : undefined}
overlay={menu}
trigger={[`contextMenu`]}
>

8
src/components/TableGrid/style.scss

@ -478,12 +478,12 @@ $danger: #9e2743; @@ -478,12 +478,12 @@ $danger: #9e2743;
&.icon-add-user {
width: 22px;
height: 22px;
margin-left: 8px;
margin-right: 20px;
color: #898989eb;
svg {
width: 100%;
height: 100%;
width: 21px;
height: 21px;
color: #8f8c8c;
}
}

15
src/containers/App/router/Router.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import React, { Fragment } from "react";
import React, { Fragment, useEffect } from "react";
import {
Route,
Switch,
@ -15,14 +15,27 @@ import { routesConfig } from "./routes.config"; @@ -15,14 +15,27 @@ import { routesConfig } from "./routes.config";
import { Layout } from "@/containers/Layout";
import { isLoadingAccount } from "@/store/account";
import { useAppSocketListener } from "../hooks";
import { needRedirect } from "@/services/system";
export const AppRouter = () => {
const themeColor = useSelector(getThemeColor);
const accessToken = useSelector(getAccessToken);
const isLoading = useSelector(isLoadingAccount);
const history = useHistory();
useAppSocketListener();
useEffect(() => {
const timer = setTimeout(() => {
if (needRedirect.to) {
history.push(needRedirect.to, needRedirect.payload);
needRedirect.to = null;
needRedirect.payload = null;
}
}, 1000);
return () => clearTimeout(timer);
});
if (isLoading) return <Loader />;
const modules = () => {

4
src/containers/App/router/routes.enum.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
export enum RouteEnum {
Login = "/login",
Login = "",
Forgot = "/forgot",
Reset = "/reset/:key",
@ -17,5 +17,5 @@ export enum RouteEnum { @@ -17,5 +17,5 @@ export enum RouteEnum {
Ips = "/ips",
Chats = "/chats",
Contacts = "/contacts",
Contact = "/contact",
Contact = "/contact"
}

15
src/containers/Contact/components/Actions/actions.component.tsx

@ -1,11 +1,8 @@ @@ -1,11 +1,8 @@
import React, { FC } from "react";
import { Card, CardBody, Col, Button } from "reactstrap";
// import { updateUser, logoutUser } from "../../actions";
import { getPermissionCheck } from "../../../../lib/helper";
import { CardBody, Col, Button } from "reactstrap";
interface IProps {
contact: any;
// authcontact: any;
}
export const Actions: FC<IProps> = ({ contact }) => {
@ -13,12 +10,8 @@ export const Actions: FC<IProps> = ({ contact }) => { @@ -13,12 +10,8 @@ export const Actions: FC<IProps> = ({ contact }) => {
<Col md={2} lg={12} xl={12}>
<CardBody className="btn_container">
<div className="contact__action-card">
<div className="contact__action-header">
Спілкування в «Task Me»{" "}
{/* <strong>{contact.isBan ? "Заблокований" : "Активний"}</strong> */}
</div>
<div className="contact__action-header">Спілкування в «Task Me» </div>
<div className="contact__action-btns">
{/* {getPermissionCheck("user", "ban", contact) && ( */}
<Button
color={"danger"}
onClick={() => {}}
@ -27,8 +20,7 @@ export const Actions: FC<IProps> = ({ contact }) => { @@ -27,8 +20,7 @@ export const Actions: FC<IProps> = ({ contact }) => {
>
{"Виклик"}
</Button>
{/* )} */}
{/* {getPermissionCheck("user", "logout", authContact) && ( */}
<Button
color={"danger"}
onClick={() => {}}
@ -37,7 +29,6 @@ export const Actions: FC<IProps> = ({ contact }) => { @@ -37,7 +29,6 @@ export const Actions: FC<IProps> = ({ contact }) => {
>
{"Повідомлення"}
</Button>
{/* )} */}
</div>
</div>
</CardBody>

11
src/containers/ContactsUsers/index.tsx

@ -8,12 +8,23 @@ import { columnsConfig, defaultActiveColumnsConfig } from "./configs"; @@ -8,12 +8,23 @@ import { columnsConfig, defaultActiveColumnsConfig } from "./configs";
import { useSelector } from "react-redux";
import { ClearContactsFilter, getContactsFilter } from "@/store/users";
import { simpleDispatch } from "@/store/store-helpers";
import { useHistory } from "react-router-dom";
export const ContactsUsers = () => {
const [searchString, setSearchString] = useState<string>("");
const [isSoonBirthday, setOnlyWithSoonBirthday] = useState<boolean>(false);
const [focusFilterKey, setFocusFilterKey] = useState<string>("");
const filter = useSelector(getContactsFilter);
const history = useHistory();
useEffect(() => {
const timer = setTimeout(() => {
if ((history.location.state as any)?.soonBirthday) {
setOnlyWithSoonBirthday(true);
}
}, 300);
return () => clearTimeout(timer);
}, [history.location]);
useEffect(() => {
simpleDispatch(new ClearContactsFilter());

22
src/containers/Factory/components/TreeFactory/index.tsx

@ -1,12 +1,21 @@ @@ -1,12 +1,21 @@
import { Input, Popconfirm, Tree } from "antd";
import React, { FC, useState, useCallback, useEffect } from "react";
import { genTree, getPermissionCheck } from "@/lib/helper";
import { genTree } from "@/lib/helper";
import _ from "lodash";
import { ConfirmModal, IFactory, IUser, usePaginationList } from "@/shared";
import {
checkPermission,
ConfirmModal,
IFactory,
IUser,
Permissions,
usePaginationList,
} from "@/shared";
import "./style.scss";
import { ActionBottomBar } from "@/components/TableGrid/components";
import { permissionsGroupApi } from "@/api/permissions-group/requests";
import TrashSvgIcon from "../../../../assets/img/trash-icon.svg";
import { useSelector } from "react-redux";
import { getMyPermissions } from "@/store/permissions";
const ROW_HEIGHT = 55;
@ -31,6 +40,7 @@ export const TreeFactory: FC<IProps> = ({ @@ -31,6 +40,7 @@ export const TreeFactory: FC<IProps> = ({
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(false);
const [isOpenConfirmModal, setConfirmModal] = useState<boolean>(false);
const [factoryIdToDelete, setFactoryIdToDelete] = useState<number>();
const permissions = useSelector(getMyPermissions);
const preparedTree = genTree(_.cloneDeep(factories));
@ -150,7 +160,13 @@ export const TreeFactory: FC<IProps> = ({ @@ -150,7 +160,13 @@ export const TreeFactory: FC<IProps> = ({
</div>
</div>
{getPermissionCheck("factory", "destroy", profile) && (
{checkPermission(
"tabs",
"factory",
profile,
permissions,
Permissions.DESTROY
) && (
// eslint-disable-next-line jsx-a11y/alt-text
<img
src={TrashSvgIcon}

31
src/containers/Factory/hooks/use-factory.hook.ts

@ -5,6 +5,7 @@ import { factoriesService } from "@/services/domain"; @@ -5,6 +5,7 @@ import { factoriesService } from "@/services/domain";
import { IFactory } from "@/shared";
import { ICreateFactory } from "@/api/factories/requests.interface";
import { isLoadingFactories } from "@/store/factories";
import { factoriesApi } from "@/api";
export const useFactory = () => {
const [factories, setFactories] = useState<IFactory[]>([]);
@ -13,24 +14,24 @@ export const useFactory = () => { @@ -13,24 +14,24 @@ export const useFactory = () => {
const isLoading = useSelector(isLoadingFactories);
const fetchFactories = async () => {
const factories = await factoriesService.fetchFactories();
setFactories(factories);
try {
const { data: factories } = await factoriesApi.fetchFactoriesFullList();
setFactories(factories);
} catch (e) {
console.log("FETCH FACTORIES ERROR", e);
}
};
useEffect(() => {
fetchFactories();
}, []);
useEffect(
() => {
if (needRefetch) {
fetchFactories();
setRefetch(false);
}
},
[needRefetch]
);
useEffect(() => {
if (needRefetch) {
fetchFactories();
setRefetch(false);
}
}, [needRefetch]);
const createFactory = async (factory: ICreateFactory) => {
const createdFactory = await factoriesService.createFactory(factory);
@ -41,7 +42,7 @@ export const useFactory = () => { @@ -41,7 +42,7 @@ export const useFactory = () => {
const updateFactory = async (factory: IUpdateFactory) => {
const updatedFactory = await factoriesService.updateFactory(factory);
factories.forEach((it) => {
factories.forEach(it => {
if (it.id !== factory.id) return it;
else return updatedFactory;
});
@ -53,7 +54,7 @@ export const useFactory = () => { @@ -53,7 +54,7 @@ export const useFactory = () => {
const deleteFactory = async (id: number) => {
await factoriesService.deleteFactory(id);
factories.filter((it) => it.id !== id);
factories.filter(it => it.id !== id);
setFactories(factories);
setRefetch(true);
@ -64,6 +65,6 @@ export const useFactory = () => { @@ -64,6 +65,6 @@ export const useFactory = () => {
isLoading,
createFactory,
updateFactory,
deleteFactory,
deleteFactory
};
};

19
src/containers/Factory/index.tsx

@ -1,5 +1,10 @@ @@ -1,5 +1,10 @@
import { getPermissionCheck } from "@/lib/helper";
import { IFactory, Loader, SearchInput } from "@/shared";
import {
checkPermission,
IFactory,
Loader,
Permissions,
SearchInput,
} from "@/shared";
import { getProfile } from "@/store/account";
import React, { FC, useState } from "react";
import { useSelector } from "react-redux";
@ -9,6 +14,7 @@ import { TreeFactory } from "./components/TreeFactory"; @@ -9,6 +14,7 @@ import { TreeFactory } from "./components/TreeFactory";
import { useFactory } from "./hooks";
import Modal from "../../components/Modal";
import "./style.scss";
import { getMyPermissions } from "@/store/permissions";
export const Factory: FC = () => {
const [isOpenModal, setOpenModal] = useState<boolean>(false);
@ -16,6 +22,7 @@ export const Factory: FC = () => { @@ -16,6 +22,7 @@ export const Factory: FC = () => {
const [searchStr, setSearchStr] = useState<string>("");
const profile = useSelector(getProfile);
const permissions = useSelector(getMyPermissions);
const {
isLoading,
@ -58,7 +65,13 @@ export const Factory: FC = () => { @@ -58,7 +65,13 @@ export const Factory: FC = () => {
onChange={setSearchStr}
/>
{getPermissionCheck("factory", "create", profile) && (
{checkPermission(
"tabs",
"factory",
profile,
permissions,
Permissions.CREATE
) && (
<Button
color="primary"
size="sm"

3
src/containers/GroupPermissions/components/Data/index.tsx

@ -6,6 +6,7 @@ import { permissionsGroupApi } from "@/api/permissions-group/requests"; @@ -6,6 +6,7 @@ import { permissionsGroupApi } from "@/api/permissions-group/requests";
import { ConfirmModal } from "@/shared";
import { defaultColumnsActive, getColumnsConfig } from "../../configs";
import _ from "lodash";
import { getMyPermissions } from "@/store/permissions";
interface IProps {
paginationList: any;
@ -19,6 +20,7 @@ export const Data = ({ paginationList, ...props }: IProps) => { @@ -19,6 +20,7 @@ export const Data = ({ paginationList, ...props }: IProps) => {
const [isShowDeleteModal, setConfirmModal] = useState<boolean>(false);
const profile = useSelector(getProfile);
const permissions = useSelector(getMyPermissions);
const onDeleteGroup = async (id: number) => {
await permissionsGroupApi.deletePermissionsGroup(id);
@ -35,6 +37,7 @@ export const Data = ({ paginationList, ...props }: IProps) => { @@ -35,6 +37,7 @@ export const Data = ({ paginationList, ...props }: IProps) => {
const columnsConfig = getColumnsConfig({
profile,
permissions,
onEdit: props.onPressEdit,
onDelete: openDeleteModal,
});

28
src/containers/GroupPermissions/configs/columns.config.tsx

@ -1,10 +1,16 @@ @@ -1,10 +1,16 @@
import { voidColumn } from "@/components/TableGrid/configs";
import { getPermissionCheck } from "@/lib/helper";
import { IColumn, IUser } from "@/shared";
import {
checkPermission,
IColumn,
IMyPermissions,
IUser,
Permissions,
} from "@/shared";
import React from "react";
interface IProps {
profile: IUser;
permissions: IMyPermissions;
onEdit: (id: number) => void;
onDelete: (id: number) => void;
}
@ -32,7 +38,13 @@ export const getColumnsConfig = (props: IProps): IColumn[] => [ @@ -32,7 +38,13 @@ export const getColumnsConfig = (props: IProps): IColumn[] => [
formatter: ({ row }) => {
return (
<span>
{getPermissionCheck("group_permission", "update", props.profile) && (
{checkPermission(
"tabs",
"group_permission",
props.profile,
props.permissions,
Permissions.UPDATE
) && (
<a
className="lnr lnr-pencil"
href="javascript:;"
@ -42,11 +54,13 @@ export const getColumnsConfig = (props: IProps): IColumn[] => [ @@ -42,11 +54,13 @@ export const getColumnsConfig = (props: IProps): IColumn[] => [
)}
<span style={{ padding: "10px" }}> </span>
{!getPermissionCheck(
{checkPermission(
"tabs",
"group_permission",
"destroy",
props.profile
) ? null : (
props.profile,
props.permissions,
Permissions.DESTROY
) && (
<a
onClick={() => props.onDelete(row.id)}
className="lnr lnr-trash"

47
src/containers/GroupPermissions/index.tsx

@ -2,10 +2,15 @@ import React, { FC, useEffect, useState } from "react"; @@ -2,10 +2,15 @@ import React, { FC, useEffect, useState } from "react";
import { Col, Container, Row, Card, CardBody, Button } from "reactstrap";
import { useSelector } from "react-redux";
import { CreateEditGroupPermissionsForm } from "./components";
import { getPermissionCheck } from "../../lib/helper";
import Modal from "../../components/Modal";
import { IUser, Loader, SearchInput, usePaginationList } from "@/shared";
import { useHistory } from "react-router-dom";
import {
checkPermission,
IUser,
Loader,
Permissions,
SearchInput,
usePaginationList,
} from "@/shared";
import { getProfile } from "@/store/account";
import { Data } from "./components/Data";
import { useForm } from "react-hook-form";
@ -16,9 +21,9 @@ import { @@ -16,9 +21,9 @@ import {
IFetchPermissionsGroupList,
IUpdatePermissionsGroup,
} from "@/api/permissions-group/responses.interfaces";
import { getMyPermissions } from "@/store/permissions";
export const GroupPermission: FC = () => {
const history = useHistory();
const [searchString, setSearch] = useState<string>();
const [showModal, setShowModal] = useState<boolean>(false);
const [groupPermissionsToEdit, setGroupPermissionsToEdit] = useState<
@ -31,10 +36,7 @@ export const GroupPermission: FC = () => { @@ -31,10 +36,7 @@ export const GroupPermission: FC = () => {
});
const profile: IUser = useSelector(getProfile);
if (!getPermissionCheck("group_permission", "find", profile)) {
history.push("/");
}
const permissions = useSelector(getMyPermissions);
const onPressEdit = (id: number) => {
getGroupPermissionsToEdit(id);
@ -117,19 +119,22 @@ export const GroupPermission: FC = () => { @@ -117,19 +119,22 @@ export const GroupPermission: FC = () => {
onFocus={() => setFocusFilterKey("globalSearch")}
/>
{getPermissionCheck(
"group_permission",
"create",
profile
) && (
<Button
color="primary"
size="sm"
onClick={() => setShowModal(true)}
>
Створити
</Button>
)}
<Button
color="primary"
size="sm"
disabled={
!checkPermission(
"tabs",
"group_permission",
profile,
permissions,
Permissions.CREATE
)
}
onClick={() => setShowModal(true)}
>
Створити
</Button>
</div>
</Row>

8
src/containers/Layout/index.tsx

@ -12,6 +12,8 @@ import { @@ -12,6 +12,8 @@ import {
} from "@/store/shared";
import { simpleDispatch } from "@/store/store-helpers";
import { SocketIo } from "@/services/system";
import { useSocketListener } from "@/shared";
import { permissionsService } from "@/services/domain";
interface IProps {}
@ -22,6 +24,12 @@ export const Layout = (props: IProps) => { @@ -22,6 +24,12 @@ export const Layout = (props: IProps) => {
socketIo.init();
}, []);
const onPermissionsChanged = async () => {
await permissionsService.loadMyPermissions();
};
useSocketListener("user/change-permissions", onPermissionsChanged, []);
const sidebarState = useSelector(getSidebar);
const changeSidebarVisibility = () => {

66
src/containers/Layout/sidebar/SidebarContent.tsx

@ -6,8 +6,7 @@ import FilterTask from "./filter_task/FilterTask"; @@ -6,8 +6,7 @@ import FilterTask from "./filter_task/FilterTask";
import { getProfile } from "@/store/account";
import { RouteComponentProps, withRouter } from "react-router-dom";
import { RouteEnum } from "@/containers/App/router";
import { EUserRole, getParentsTree, IFactory } from "@/shared";
import { getPermissionCheck } from "../../../lib/helper";
import { checkPermission, EUserRole, getParentsTree, IFactory } from "@/shared";
import {
FILE_ChatIcon,
@ -21,7 +20,6 @@ import { @@ -21,7 +20,6 @@ import {
FILE_NotebookIcon,
} from "../../../assets/img/index";
import FilterUser from "./filter_user/FilterUser";
import { factoriesService } from "@/services/domain";
import FilterContact from "./filter_user_contacts/FilterContact";
import { simpleDispatch } from "@/store/store-helpers";
import {
@ -34,6 +32,8 @@ import { @@ -34,6 +32,8 @@ import {
} from "@/store/users";
import { ClearFilter, filterInitialState, getTaskFilter } from "@/store/task";
import _ from "lodash";
import { getMyPermissions } from "@/store/permissions";
import { factoriesApi } from "@/api";
interface IProps extends RouteComponentProps {
hideSidebar: () => void;
@ -44,14 +44,18 @@ interface IProps extends RouteComponentProps { @@ -44,14 +44,18 @@ interface IProps extends RouteComponentProps {
const SidebarContent = (props: IProps) => {
const [factories, setFactories] = useState<IFactory[]>([]);
const profile = useSelector(getProfile);
const permissions = useSelector(getMyPermissions);
const tasksFilter = useSelector(getTaskFilter);
const usersFilter = useSelector(getUsersFilter);
const contactsFilter = useSelector(getContactsFilter);
const fetchFactories = async () => {
const factories = await factoriesService.fetchFactories();
setFactories(factories);
try {
const { data: factories } = await factoriesApi.fetchFactoriesFullList();
setFactories(factories);
} catch (e) {
console.log("FETCH FACTORIES LIST ERROR", e);
}
};
useEffect(() => {
@ -103,7 +107,7 @@ const SidebarContent = (props: IProps) => { @@ -103,7 +107,7 @@ const SidebarContent = (props: IProps) => {
route={RouteEnum.Tasks}
onClick={props.hideSidebar}
/>
{getPermissionCheck("user", "find", profile) && (
{checkPermission("tabs", "user", profile, permissions) && (
<SidebarLink
title="Користувачі"
iconCustom={FILE_UserIcon}
@ -119,7 +123,7 @@ const SidebarContent = (props: IProps) => { @@ -119,7 +123,7 @@ const SidebarContent = (props: IProps) => {
// iconFontawesome="fal fa-address-book"
onClick={props.hideSidebar}
/>
{getPermissionCheck("factory", "find", profile) && (
{checkPermission("tabs", "factory", profile, permissions) && (
<SidebarLink
title="Підприємства"
// icon="apartment"
@ -128,7 +132,7 @@ const SidebarContent = (props: IProps) => { @@ -128,7 +132,7 @@ const SidebarContent = (props: IProps) => {
onClick={props.hideSidebar}
/>
)}
{getPermissionCheck("group_permission", "find", profile) && (
{checkPermission("tabs", "group_permission", profile, permissions) && (
<SidebarLink
title="Групи прав"
// icon="layers"
@ -156,22 +160,22 @@ const SidebarContent = (props: IProps) => { @@ -156,22 +160,22 @@ const SidebarContent = (props: IProps) => {
/>
)}
{getPermissionCheck("taxonomy", "find", profile) && (
{checkPermission("tabs", "taxonomy", profile, permissions) && (
<SidebarCategory title="Довідники" iconCustom={FILE_NotebookIcon}>
{getPermissionCheck("taxonomy", "find", profile) && (
<SidebarLink
title="Групи задач"
route={`${RouteEnum.Taxonomy}/task`}
onClick={props.hideSidebar}
/>
)}
{getPermissionCheck("taxonomy", "find", profile) && (
<SidebarLink
title="Підстави задач"
route={`${RouteEnum.Taxonomy}/reasons_task`}
onClick={props.hideSidebar}
/>
)}
{/* {checkPermission("taxonomy", "find", profile, permissions) && ( */}
<SidebarLink
title="Групи задач"
route={`${RouteEnum.Taxonomy}/task`}
onClick={props.hideSidebar}
/>
{/* )} */}
{/* {checkPermission("taxonomy", "find", profile, permissions) && ( */}
<SidebarLink
title="Підстави задач"
route={`${RouteEnum.Taxonomy}/reasons_task`}
onClick={props.hideSidebar}
/>
{/* )} */}
</SidebarCategory>
)}
{props.history.location.pathname === RouteEnum.Tasks && (
@ -186,12 +190,22 @@ const SidebarContent = (props: IProps) => { @@ -186,12 +190,22 @@ const SidebarContent = (props: IProps) => {
)}
{props.location.pathname === RouteEnum.User && (
<SidebarCategory isOpen={true} title="Фільтр" icon="sliders">
<FilterUser factoriesTree={factoriesTree} />
<FilterUser
factoriesTree={factoriesTree}
filterFactory={checkPermission(
"filter_factory",
null,
profile,
permissions
)}
/>
</SidebarCategory>
)}
{props.location.pathname === RouteEnum.Contacts && (
<SidebarCategory isOpen={true} title="Фільтр" icon="sliders">
<FilterContact factoriesTree={factoriesTree} />
{checkPermission("filter_factory", null, profile, permissions) && (
<FilterContact factoriesTree={factoriesTree} />
)}
</SidebarCategory>
)}
</ul>

2
src/containers/Layout/sidebar/filter_task/FilterTask.tsx

@ -83,7 +83,7 @@ const FilterTask: FC = () => { @@ -83,7 +83,7 @@ const FilterTask: FC = () => {
name="executor"
label="Виконавець"
component={UserSelectWithSearch}
type={EUsersListType.Executors}
type={EUsersListType.ExecutorsAll}
input={{
value: filter.executor,
onChange: (val: Option) => onChange("executor", val),

32
src/containers/Layout/sidebar/filter_user/FilterUser.tsx

@ -9,9 +9,10 @@ import { Field, reduxForm } from "redux-form"; @@ -9,9 +9,10 @@ import { Field, reduxForm } from "redux-form";
interface IProps {
factoriesTree: any;
filterFactory?: boolean;
}
const FilterUsers: FC<IProps> = ({ factoriesTree }) => {
const FilterUsers: FC<IProps> = ({ factoriesTree, filterFactory = true }) => {
const filter = useSelector(getUsersFilter);
const onChange = (key: keyof IUsersFilter, value: any) =>
@ -31,20 +32,21 @@ const FilterUsers: FC<IProps> = ({ factoriesTree }) => { @@ -31,20 +32,21 @@ const FilterUsers: FC<IProps> = ({ factoriesTree }) => {
onChange: (val: boolean) => onChange("isBlocked", val),
}}
/>
<Field
name="factoryId"
component={TreeSelectField}
placeholder="Підприємство"
label="Підприємство"
hideIcon
value_field="id"
tree={factoriesTree}
register={{
value: filter.factoryId,
onChange: (val: string) => onChange("factoryId", val),
}}
/>
{filterFactory && (
<Field
name="factoryId"
component={TreeSelectField}
placeholder="Підприємство"
label="Підприємство"
hideIcon
value_field="id"
tree={factoriesTree}
register={{
value: filter.factoryId,
onChange: (val: string) => onChange("factoryId", val),
}}
/>
)}
</div>
</div>
);

111
src/containers/Layout/topbar/Topbar.tsx

@ -1,66 +1,81 @@ @@ -1,66 +1,81 @@
import React, { useEffect, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import { TopBarProfile } from './TopbarProfile';
import { TopBarSidebarButton } from './TopbarSidebarButton';
import TopbarNotification from './TopbarNotification';
import Dimensions from 'react-dimensions'
import { RouteEnum } from '@/containers/App/router';
import React, { useEffect, useRef } from "react";
import { Link } from "react-router-dom";
import { TopBarProfile } from "./TopbarProfile";
import { TopBarSidebarButton } from "./TopbarSidebarButton";
import Dimensions from "react-dimensions";
import { RouteEnum } from "@/containers/App/router";
import { TopbarNotificationBlock } from "./TopbarNotificationBlock";
import { dataNotifications } from "./config/config";
import { notificApi } from '@/api/notifications/request';
import { usePaginationList } from '@/shared';
import { notificApi } from "@/api/notifications/request";
import {
IPushNotification,
usePaginationList,
useSocketListener,
} from "@/shared";
import _ from "lodash";
import { SocketEvents } from "@/shared/events";
interface IProps {
containerHeight: number
containerWidth: number
updateDimensions: any
changeMobileSidebarVisibility: () => void
changeSidebarVisibility: () => void
containerHeight: number;
containerWidth: number;
updateDimensions: any;
changeMobileSidebarVisibility: () => void;
changeSidebarVisibility: () => void;
sidebar: {
show: boolean
collapse: boolean
}
show: boolean;
collapse: boolean;
};
}
const Topbar = (props: IProps) => {
const changeWidth = useRef<boolean>(false)
const [isNotic, setIsNotic] = useState({ notic: [], count: null })
const changeWidth = useRef<boolean>(false);
useEffect(() => {
changeWidth.current = true
changeWidth.current = true;
if (changeWidth.current && props.containerWidth < 768 && !props.sidebar.collapse) {
if (
changeWidth.current &&
props.containerWidth < 768 &&
!props.sidebar.collapse
) {
props.changeSidebarVisibility();
} else if (changeWidth.current && props.containerWidth > 768 && props.sidebar.collapse) {
} else if (
changeWidth.current &&
props.containerWidth > 768 &&
props.sidebar.collapse
) {
props.changeSidebarVisibility();
}
}, [props?.containerWidth])
}, [props?.containerWidth]);
const paginationList = usePaginationList({
const paginationList = usePaginationList<IPushNotification>({
fetchItems: notificApi.fetchNotificationsList,
loadParams: {
limit: 20,
limit: 9999999,
page: 1,
}
})
useEffect(() => {
if (paginationList.items.some((el: any) => el.isRead === false)) {
const isNotRead = paginationList.items.filter((el: any) => el.isRead === false)
if (isNotRead.length <= 2) {
const sortItem = paginationList.items.sort((a:any, b): any => (a.isRead === false ? 1 : -1))
setIsNotic({ notic: sortItem.slice(sortItem.length - 3, sortItem.length), count: isNotRead.length })
} else {
setIsNotic({ notic: isNotRead, count: isNotRead.length })
}
sort: "DESC",
sortField: "id",
},
});
const onReadNotif = (ids?: number[]) => {
if (ids) {
const newList = paginationList.items.filter(
(it) => !_.includes(ids, it.id)
);
paginationList._setItems(newList);
} else {
setIsNotic({ notic: paginationList.items.slice(paginationList.items.length - 3, paginationList.items.length), count: null })
paginationList._setItems([]);
}
}, [paginationList.isLoading, paginationList.isLoading])
};
const onNewNotification = ({
notification,
}: SocketEvents["notification"]) => {
const newList = [notification, ...paginationList.items];
paginationList._setItems(newList);
};
useSocketListener("notification", onNewNotification, [paginationList.items]);
return (
<div className="topbar">
@ -73,13 +88,15 @@ const Topbar = (props: IProps) => { @@ -73,13 +88,15 @@ const Topbar = (props: IProps) => {
<Link className="topbar__logo" to={RouteEnum.Tasks} />
</div>
<div className="topbar__right">
<TopbarNotificationBlock notics={isNotic} />
<TopbarNotificationBlock
notics={paginationList.items}
onReadNotif={onReadNotif}
/>
<TopBarProfile />
</div>
</div>
</div>
)
}
);
};
export default Dimensions()(Topbar);

91
src/containers/Layout/topbar/TopbarNotificationBlock.tsx

@ -1,25 +1,48 @@ @@ -1,25 +1,48 @@
/* eslint-disable react/no-array-index-key */
import { RouteEnum } from "@/containers/App/router";
import { Collapse } from "reactstrap";
import React, { Component, useRef, useState } from "react";
import { Link } from "react-router-dom";
// import MailIcon from 'mdi-react/MailIcon';
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import bell_icon from "../../../assets/img/Bell.svg";
import { TopBarMenuLink } from "./TopbarMenuLink";
import moment from "moment";
import { IPushNotification, notification } from "@/shared";
import { notificationActions } from "./config/config";
import { Button } from "antd";
import { notificApi } from "@/api/notifications/request";
interface IProps {
notics: any;
notics: IPushNotification[];
onReadNotif: (ids?: number[]) => void;
}
export const TopbarNotificationBlock = (props: IProps) => {
const [collapse, setCollapse] = useState<boolean>(false);
const history = useHistory();
const unreadMessages = props.notics.count ? props.notics.count : null;
const unreadMessages = props.notics.length ? props.notics.length : null;
const toggle = () => {
setCollapse((prevState) => !prevState);
};
const myRef = useRef(null);
const handlePressDetails = async (notif: IPushNotification) => {
setCollapse(false);
const action = notificationActions(notif, history)[notif?.data?.type];
action?.redirect();
props.onReadNotif([notif.id]);
try {
await notificApi.readByIds({ ids: [notif.id] });
} catch (e) {
console.log("ERROR ON READ NOTIFICATIONS BY IDS", e);
}
};
const handleReadAll = async () => {
try {
setCollapse(false);
await notificApi.readAll();
props.onReadNotif();
} catch (e) {
notification.showError("Помилка", "Не вдалось зберегти зміни");
}
};
return (
<div
@ -27,25 +50,23 @@ export const TopbarNotificationBlock = (props: IProps) => { @@ -27,25 +50,23 @@ export const TopbarNotificationBlock = (props: IProps) => {
className="topbar__collapse"
style={{ marginLeft: "10px", position: "relative" }}
>
{/* <Link
id="topbar__chat_bar"
className="topbar__btn topbar__btn--mail"
type="button"
to="/chats"
> */}
<div className="topbar__btn topbar__btn--mail">
<img src={bell_icon} alt="" className="topbar__notification-icon" />
{unreadMessages && unreadMessages > 0 ? (
<div className="topbar_notification-count">
<p style={{ fontSize: 7, color: "#FFFF", textAlign: "center" }}>
{unreadMessages}
<p
style={{
fontSize: unreadMessages > 99 ? 7 : 8,
color: "#FFFF",
textAlign: "center",
}}
>
{unreadMessages > 99 ? "99+" : unreadMessages}
</p>
</div>
) : null}
</div>
{/* </Link> */}
{collapse && (
<button type="button" className="topbar__back" onClick={() => toggle} />
)}
@ -54,24 +75,34 @@ export const TopbarNotificationBlock = (props: IProps) => { @@ -54,24 +75,34 @@ export const TopbarNotificationBlock = (props: IProps) => {
className="notification_content"
onClick={(e) => e.stopPropagation()}
>
<p className="title">Сповіщення</p>
<div>
{props.notics.notic.map((el) => (
<div key={el.id}>
<div className="notification_content_item">
<p style={{ maxWidth: 235 }}>{el.content}</p>
<div className="notification_title_wrapper">
<p className="title">Сповіщення</p>
<Button
className="read-all-button"
type="text"
onClick={() => handleReadAll()}
>
Познати всі як прочитані
</Button>
</div>
<p>{moment(el.createDate).format("HH : ss")}</p>
<div className="notification_content_items_list">
{props.notics.map((el) => (
<div key={el.id} className="notification_content_item_wrapper">
<div className="notification_content_item">
<p>{el.content}</p>
</div>
<div style={{ display: "flex", justifyContent: "right" }}>
<button className="details_btn" onClick={() => {}}>
<div className="notification_right-block">
<p>{moment(el.createDate).format("DD-MM-YYYY")}</p>
<button
className="details_btn"
onClick={() => handlePressDetails(el)}
>
<p style={{ color: "#9E2743" }}>Детальніше</p>
</button>
</div>
<div />
<div className="topbar__menu-divider" />
</div>
))}
</div>

59
src/containers/Layout/topbar/config/config.ts

@ -1,4 +1,57 @@ @@ -1,4 +1,57 @@
import { RouteEnum } from "@/containers/App/router";
import { IPushNotification, NotificationKeys } from "@/shared";
import { SelectChatId } from "@/store/chats";
import { simpleDispatch } from "@/store/store-helpers";
export const dataNotifications = {
nameNotification: 'Зробити дизайн мобільн...',
time: '17:45'
}
nameNotification: "Зробити дизайн мобільн...",
time: "17:45"
};
export const notificationActions = (
notification: IPushNotification,
history: any
) => ({
[NotificationKeys.NEW_MESSAGE]: {
redirect: () => {
simpleDispatch(
new SelectChatId({ id: Number(notification.data.chatId) })
);
history.push(`/chats?id=${notification.data.chatId}`);
}
},
[NotificationKeys.NEW_CHAT_MEMBER]: {
redirect: () => {
simpleDispatch(
new SelectChatId({ id: Number(notification.data.chatId) })
);
history.push(`/chats?id=${notification.data.chatId}`);
}
},
[NotificationKeys.NEW_TASK]: {
redirect: () => {
history.push(RouteEnum.Tasks);
}
},
[NotificationKeys.NEW_TASK_COMMENT]: {
redirect: () => {
history.push(RouteEnum.Tasks, {
type: NotificationKeys.NEW_TASK_COMMENT,
taskId: notification.data.taskId
});
}
},
[NotificationKeys.NEW_TASK_FILE]: {
redirect: () => {
history.push(RouteEnum.Tasks, {
type: NotificationKeys.NEW_TASK_COMMENT,
taskId: notification.data.taskId
});
}
},
[NotificationKeys.TODAY_BIRTHDAY]: {
redirect: () => {
history.push(RouteEnum.Contacts, { soonBirthday: true });
}
}
});

17
src/containers/Permissions/components/FormPermissions/index.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import React, { FC, useEffect, useMemo, useState } from "react";
import React, { FC, useEffect, useMemo, useRef, useState } from "react";
import { objectToOption } from "@/shared";
import _ from "lodash";
import { Button } from "reactstrap";
@ -30,6 +30,8 @@ export const PermissionsForm: FC<PermissionsFormProps> = ({ @@ -30,6 +30,8 @@ export const PermissionsForm: FC<PermissionsFormProps> = ({
const [dataBySection, setDataBySection] = useState<any>({});
const availablePermissions = useSelector(getAvailablePermissions);
const bottomRef = useRef(null);
const watchValues = form.watch();
const values = form.getValues();
@ -75,6 +77,10 @@ export const PermissionsForm: FC<PermissionsFormProps> = ({ @@ -75,6 +77,10 @@ export const PermissionsForm: FC<PermissionsFormProps> = ({
]);
};
useEffect(() => {
bottomRef.current?.scrollIntoView(false);
}, [values]);
const onPermissionChange = (key: string, val: any, index: number) => {
const values = form.getValues();
@ -148,7 +154,14 @@ export const PermissionsForm: FC<PermissionsFormProps> = ({ @@ -148,7 +154,14 @@ export const PermissionsForm: FC<PermissionsFormProps> = ({
</Button>
</div>
<div className="permissions_container">{renderPermissions}</div>
<div className="permissions_container">
{renderPermissions}
<div
ref={(el) => {
bottomRef.current = el;
}}
></div>
</div>
</div>
);
};

33
src/containers/Permissions/components/PermissionsInfo/index.tsx

@ -1,8 +1,10 @@ @@ -1,8 +1,10 @@
import { permissionsApi } from "@/api";
import { IFactoryShortInfo, IUserPermissions, IUserShortInfo } from "@/shared";
import { getProfile } from "@/store/account";
import _ from "lodash";
import React, { useEffect, useMemo, useState } from "react";
import { FC } from "react";
import { useSelector } from "react-redux";
import { transformPermissionsToInfo } from "../../transforms";
interface IPermissionsInfoProps {
@ -15,15 +17,32 @@ export const PermissionsInfo: FC<IPermissionsInfoProps> = ({ userId }) => { @@ -15,15 +17,32 @@ export const PermissionsInfo: FC<IPermissionsInfoProps> = ({ userId }) => {
);
const [users, setUsers] = useState<IUserShortInfo[]>(null);
const [factories, setFactories] = useState<IFactoryShortInfo[]>(null);
const profile = useSelector(getProfile);
const fetchUserPermissions = async () => {
const { data } = await permissionsApi.fetchUserPermissionsWithInfo(userId, {
withExtendedInfo: true,
});
if (data) {
setUserPermissions(JSON.parse(data.permissions));
if (data.users) setUsers(data.users);
if (data.factories) setFactories(data.factories);
let permissions;
try {
if (userId === profile.id) {
const {
data: myPermissions,
} = await permissionsApi.fetchMyPermissionsOriginal();
permissions = myPermissions;
} else {
const {
data: userPermissions,
} = await permissionsApi.fetchUserPermissionsWithInfo(userId, {
withExtendedInfo: true,
});
permissions = userPermissions;
}
if (permissions) {
setUserPermissions(JSON.parse(permissions.permissions));
if (permissions.users) setUsers(permissions.users);
if (permissions.factories) setFactories(permissions.factories);
}
} catch (e) {
console.log("GET MY PERMISSIONS ERROR", e);
}
};

23
src/containers/Profile/components/Actions/actions.component.tsx

@ -1,7 +1,8 @@ @@ -1,7 +1,8 @@
import React, { FC } from "react";
import { Card, CardBody, Col, Button } from "reactstrap";
import { getPermissionCheck } from "../../../../lib/helper";
import { EUserStatus, IUser } from "@/shared";
import { checkPermission, EUserStatus, IUser, Permissions } from "@/shared";
import { useSelector } from "react-redux";
import { getMyPermissions } from "@/store/permissions";
interface IProps {
profile: IUser;
@ -16,6 +17,8 @@ export const Actions: FC<IProps> = ({ @@ -16,6 +17,8 @@ export const Actions: FC<IProps> = ({
onPressBlock,
onPressLogout,
}) => {
const permissions = useSelector(getMyPermissions);
return (
<Col md={12} lg={12} xl={12}>
<Card>
@ -30,7 +33,13 @@ export const Actions: FC<IProps> = ({ @@ -30,7 +33,13 @@ export const Actions: FC<IProps> = ({
</strong>
</div>
<div className="profile__action-btns">
{getPermissionCheck("user", "ban", authProfile) && (
{checkPermission(
"tabs",
"user",
authProfile,
permissions,
Permissions.BAN
) && (
<Button
color="primary"
onClick={onPressBlock}
@ -46,7 +55,13 @@ export const Actions: FC<IProps> = ({ @@ -46,7 +55,13 @@ export const Actions: FC<IProps> = ({
: "Розблокувати"}
</Button>
)}
{getPermissionCheck("user", "logout", authProfile) && (
{checkPermission(
"tabs",
"user",
authProfile,
permissions,
Permissions.LOGOUT
) && (
<Button
color="primary"
onClick={onPressLogout}

2
src/containers/Profile/components/profile-main.component.tsx

@ -43,7 +43,7 @@ export const ProfileMain: FC<IProps> = ({ @@ -43,7 +43,7 @@ export const ProfileMain: FC<IProps> = ({
<div>
<Avatar
profile={profile}
isUpdate={true}
isUpdate={isUpdate || profile.id === authProfile.id}
onChangeAvatar={onChangeAvatar}
onDeleteAvatar={onDeleteAvatar}
/>

40
src/containers/Profile/components/profile-tabs.component.tsx

@ -9,12 +9,13 @@ import { @@ -9,12 +9,13 @@ import {
TabPane,
} from "reactstrap";
import classnames from "classnames";
import { getPermissionCheck } from "../../../lib/helper";
import { FormUser } from "./form-user";
import { UserInfo } from "./user-info";
import { IUser, notification } from "@/shared";
import { checkPermission, IUser, notification, Permissions } from "@/shared";
import { ProfilePermissionsForm } from "./FormPermissions/profile-permissions-form";
import { PermissionsInfo } from "@/containers/Permissions";
import { useSelector } from "react-redux";
import { getMyPermissions } from "@/store/permissions";
interface IProps {
isUpdate: boolean;
@ -29,6 +30,7 @@ export const ProfileTabs: FC<IProps> = ({ @@ -29,6 +30,7 @@ export const ProfileTabs: FC<IProps> = ({
profile,
onUpdate,
}) => {
const permissions = useSelector(getMyPermissions);
const [activeTab, setTab] = useState<1 | 2>(1);
const openSuccessNotification = () =>
@ -42,6 +44,14 @@ export const ProfileTabs: FC<IProps> = ({ @@ -42,6 +44,14 @@ export const ProfileTabs: FC<IProps> = ({
if (onUpdate) onUpdate();
};
const canUpdatePermissions = checkPermission(
"tabs",
"user",
authProfile,
permissions,
Permissions.PERMISSIONS
);
return (
<Col md={12} lg={12} xl={8}>
<Card>
@ -53,22 +63,20 @@ export const ProfileTabs: FC<IProps> = ({ @@ -53,22 +63,20 @@ export const ProfileTabs: FC<IProps> = ({
className={classnames({ active: activeTab === 1 })}
onClick={() => setTab(1)}
>
{getPermissionCheck("user", "update", authProfile) ||
authProfile.id === profile.id
{authProfile.id === profile.id
? "Налаштування профайлу"
: "Інформація"}
</NavLink>
</NavItem>
{getPermissionCheck("user", "permissions", authProfile) && (
<NavItem>
<NavLink
className={classnames({ active: activeTab === 2 })}
onClick={() => setTab(2)}
>
Права доступу
</NavLink>
</NavItem>
)}
<NavItem>
<NavLink
className={classnames({ active: activeTab === 2 })}
onClick={() => setTab(2)}
>
Права доступу
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={activeTab} style={{ paddingBottom: 20 }}>
@ -86,7 +94,7 @@ export const ProfileTabs: FC<IProps> = ({ @@ -86,7 +94,7 @@ export const ProfileTabs: FC<IProps> = ({
)}
</TabPane>
<TabPane tabId={2}>
{activeTab === 2 && isUpdate && (
{activeTab === 2 && canUpdatePermissions && (
<ProfilePermissionsForm
userId={profile.id}
onSuccessUpdate={openSuccessNotification}
@ -94,7 +102,7 @@ export const ProfileTabs: FC<IProps> = ({ @@ -94,7 +102,7 @@ export const ProfileTabs: FC<IProps> = ({
/>
)}
{activeTab === 2 && !isUpdate && (
{activeTab === 2 && !canUpdatePermissions && (
<PermissionsInfo userId={profile.id} />
)}
</TabPane>

12
src/containers/Profile/hooks/use-edit-user-permissions.hook.ts

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import { permissionsApi } from "@/api";
import { factoriesApi, permissionsApi } from "@/api";
import { PermissionForm } from "@/containers/Permissions/interfaces";
import { factoriesService } from "@/services/domain";
import {
IFactory,
IUserPermissions,
@ -57,9 +56,12 @@ export const useEditUserPermissions = ({ @@ -57,9 +56,12 @@ export const useEditUserPermissions = ({
}, [userPermissions]);
const fetchFactories = async () => {
const factories = await factoriesService.fetchFactories();
setFactories(factories);
try {
const { data: factories } = await factoriesApi.fetchFactoriesFullList();
setFactories(factories);
} catch (e) {
console.log("FETCH FACTORIES LIST ERROR", e);
}
};
useEffect(() => {

7
src/containers/Profile/profile.page.tsx

@ -4,7 +4,6 @@ import "./style.scss"; @@ -4,7 +4,6 @@ import "./style.scss";
import { ProfileMain, ProfileTabs } from "./components";
import { useSelector } from "react-redux";
import { getProfile } from "@/store/account/selectors";
import { getPermissionCheck } from "@/lib/helper";
import { useContact } from "../Contact/hooks/use-contact.hook";
import { ConfirmModal } from "@/shared";
import { useUserActions } from "./hooks";
@ -15,8 +14,6 @@ export const ProfileDetail: FC = () => { @@ -15,8 +14,6 @@ export const ProfileDetail: FC = () => {
const profile = useSelector(getProfile);
const refreshToken = useSelector(getRefreshToken);
const isUpdated = getPermissionCheck("user", "update", profile);
const { contactStats, contactTasksCount } = useContact(profile.info.userId);
const { confirmModal, onPressAction } = useUserActions({
@ -44,7 +41,7 @@ export const ProfileDetail: FC = () => { @@ -44,7 +41,7 @@ export const ProfileDetail: FC = () => {
<Col md={12} lg={12} xl={4}>
<Row>
<ProfileMain
isUpdate={isUpdated}
isUpdate={true}
profile={profile}
authProfile={profile}
countStats={contactStats}
@ -57,7 +54,7 @@ export const ProfileDetail: FC = () => { @@ -57,7 +54,7 @@ export const ProfileDetail: FC = () => {
</Col>
<ProfileTabs
isUpdate={isUpdated}
isUpdate={true}
profile={profile}
authProfile={profile}
/>

20
src/containers/Tasks/components/create-update-task-modal.component.tsx

@ -7,9 +7,12 @@ import { ModalTabMainInfo } from "./modal-tab-main-info.component"; @@ -7,9 +7,12 @@ import { ModalTabMainInfo } from "./modal-tab-main-info.component";
import { ModalTabDocument } from "./modal-tab-document.component";
import { useCreateUpdateTask } from "../hooks";
import { ITaskModalState } from "../interfaces";
import { ETaskStatus, ITask } from "@/shared";
import { checkPermission, ETaskStatus, ITask, Permissions } from "@/shared";
import { ModalTabDetails } from "./modal-tab-details.component";
import { ModalTabComments } from "./modal-tab-comments.component";
import { useSelector } from "react-redux";
import { getProfile } from "@/store/account";
import { getMyPermissions } from "@/store/permissions";
interface IProps {
state: ITaskModalState;
@ -26,6 +29,9 @@ export const CreateUpdateTaskModal: FC<IProps> = ({ @@ -26,6 +29,9 @@ export const CreateUpdateTaskModal: FC<IProps> = ({
selectedTask,
onClose,
}) => {
const profile = useSelector(getProfile);
const permissions = useSelector(getMyPermissions);
const [activeTab, setTab] = useState<ETasksModalTabs>(ETasksModalTabs.Tasks);
const {
@ -65,6 +71,7 @@ export const CreateUpdateTaskModal: FC<IProps> = ({ @@ -65,6 +71,7 @@ export const CreateUpdateTaskModal: FC<IProps> = ({
toggle={() => {
setOpen(false);
onClose();
if (state.mode === ETaskModalMode.Update) form.reset();
}}
>
<ModalTabs type={state.mode} activeTab={activeTab} onToggleTab={setTab} />
@ -89,7 +96,16 @@ export const CreateUpdateTaskModal: FC<IProps> = ({ @@ -89,7 +96,16 @@ export const CreateUpdateTaskModal: FC<IProps> = ({
onSelect={addAttachment}
onDelete={deleteAttachment}
taskId={selectedTask?.id}
disabled={selectedTask?.status !== ETaskStatus.Active}
disabled={
selectedTask?.status !== ETaskStatus.Active ||
!checkPermission(
"user",
selectedTask?.executorId,
profile,
permissions,
Permissions.UPDATE
)
}
mode={state.mode}
onTaskFilesChange={setCopyDocumentsIds}
/>

22
src/containers/Tasks/components/file-item.component.tsx

@ -10,7 +10,7 @@ import { Tooltip } from "antd"; @@ -10,7 +10,7 @@ import { Tooltip } from "antd";
interface IProps {
title: string;
date?: string;
onDelete: () => void;
onDelete?: () => void;
onDownload?: () => void;
}
@ -36,15 +36,17 @@ export const FileItem: FC<IProps> = ({ title, date, onDelete, onDownload }) => { @@ -36,15 +36,17 @@ export const FileItem: FC<IProps> = ({ title, date, onDelete, onDownload }) => {
</div>
</Tooltip>
)}
<Tooltip title="Видалити файл">
<div>
<IconComponent
className="file-item_small-icon"
name={TrashSvgIcon}
onClick={onDelete}
/>
</div>
</Tooltip>
{onDelete && (
<Tooltip title="Видалити файл">
<div>
<IconComponent
className="file-item_small-icon"
name={TrashSvgIcon}
onClick={onDelete}
/>
</div>
</Tooltip>
)}
</div>
</div>
);

20
src/containers/Tasks/components/modal-tab-document.component.tsx

@ -112,14 +112,18 @@ export const ModalTabDocument: FC<IProps> = ({ @@ -112,14 +112,18 @@ export const ModalTabDocument: FC<IProps> = ({
? null
: () => window.open(it.url, "_blank", "noopener,noreferrer")
}
onDelete={() => {
if (mode === ETaskModalMode.Copy) {
softDelete(it.id);
} else {
setDeleteDocId(it.id);
setConfirmModal(true);
}
}}
onDelete={
disabled
? undefined
: () => {
if (mode === ETaskModalMode.Copy) {
softDelete(it.id);
} else {
setDeleteDocId(it.id);
setConfirmModal(true);
}
}
}
/>
))}
</div>

11
src/containers/Tasks/components/modal-tab-main-info.component.tsx

@ -58,6 +58,7 @@ export const ModalTabMainInfo: FC<IProps> = ({ @@ -58,6 +58,7 @@ export const ModalTabMainInfo: FC<IProps> = ({
}>({ isShowPop: false });
const taxonomiesList = useSelector(getTaxonomiesList);
const watchValues = form.watch();
const categoriesOptions = useMemo(
() =>
@ -85,12 +86,14 @@ export const ModalTabMainInfo: FC<IProps> = ({ @@ -85,12 +86,14 @@ export const ModalTabMainInfo: FC<IProps> = ({
notification.showError("Помилка", description);
useEffect(() => {
if (mode === ETaskModalMode.Create && !form.getValues()?.groupId) {
const defaultGroup = taxonomiesList?.list?.find((it) => it.isDefault);
if (mode === ETaskModalMode.Create && !watchValues?.groupId) {
const defaultGroup = taxonomiesList?.list?.find(
(it) => it.isDefault && it.type === ETaxonomyType.taskCategory
);
if (defaultGroup) form.setValue("groupId", String(defaultGroup?.id));
if (defaultGroup) form.setValue("groupId", defaultGroup?.id?.toString());
}
}, [form, taxonomiesList]);
}, [form, watchValues, taxonomiesList, mode]);
const addGroup = async () => {
if (_.isEmpty(newGroup)) return;

54
src/containers/Tasks/configs/selected-tasks-menu.config.ts

@ -1,4 +1,12 @@ @@ -1,4 +1,12 @@
import { ETaskStatus, ISelectedRowsMenuItemConfig, ITask } from "@/shared";
import {
checkPermission,
ETaskStatus,
IMyPermissions,
ISelectedRowsMenuItemConfig,
ITask,
IUser,
Permissions
} from "@/shared";
import iconPencil from "@/assets/img/pencil.svg";
import iconDoubleCheck from "@/assets/img/doubleCheck.svg";
import iconIncomeFile from "@/assets/img/iconPaper.svg";
@ -12,6 +20,8 @@ interface IProps { @@ -12,6 +20,8 @@ interface IProps {
key: "edit" | "finish" | "reassign" | "copy" | "delete" | "print"
) => void;
selectedItems: ITask[];
profile: IUser;
permissions: IMyPermissions;
}
export const getSelectedTasksMenuConfig = (
@ -85,18 +95,54 @@ export const getSelectedTasksMenuConfig = ( @@ -85,18 +95,54 @@ export const getSelectedTasksMenuConfig = (
if (
props.selectedItems.length === 1 &&
props.selectedItems[0].status === ETaskStatus.Active
props.selectedItems[0].status === ETaskStatus.Active &&
checkPermission(
"user",
props.selectedItems[0]?.executorId,
props.profile,
props.permissions,
Permissions.UPDATE
)
)
items.push(menuItems.edit);
if (_.every(props.selectedItems, it => it.status === ETaskStatus.Active)) {
if (
_.every(
props.selectedItems,
it =>
it.status === ETaskStatus.Active &&
checkPermission(
"user",
it?.executorId,
props.profile,
props.permissions,
Permissions.UPDATE
)
)
) {
items.push(menuItems.finish);
}
if (_.every(props.selectedItems, it => it.status === ETaskStatus.Active)) {
items.push(menuItems.reassign);
}
if (props.selectedItems.length === 1) items.push(menuItems.copy);
if (_.every(props.selectedItems, it => it.status !== ETaskStatus.Deleted))
if (
_.every(
props.selectedItems,
it =>
it.status !== ETaskStatus.Deleted &&
checkPermission(
"user",
it?.executorId,
props.profile,
props.permissions,
Permissions.DESTROY
)
)
)
items.push(menuItems.delete);
items.push(menuItems.print);

22
src/containers/Tasks/tasks.screen.tsx

@ -22,8 +22,16 @@ import { useTasksList } from "./hooks"; @@ -22,8 +22,16 @@ import { useTasksList } from "./hooks";
import { IConfirmModalState, ITaskModalState } from "./interfaces";
import iconNotePencil from "@/assets/img/notepencil-light-icon.svg";
import { CustomTableRow } from "@/components/TableGrid/components";
import { getProfile } from "@/store/account";
import { useSelector } from "react-redux";
import { getMyPermissions } from "@/store/permissions";
import { useHistory } from "react-router-dom";
import { tasksApi } from "@/api";
export const TasksScreen: FC = () => {
const profile = useSelector(getProfile);
const permissions = useSelector(getMyPermissions);
const [taskModalState, setTaskModal] = useState<ITaskModalState>(
defaultModalState
);
@ -37,6 +45,18 @@ export const TasksScreen: FC = () => { @@ -37,6 +45,18 @@ export const TasksScreen: FC = () => {
const [focusFilterKey, setFocusFilterKey] = useState<string>("");
const [openTask, setOpenTask] = useState<ITask>(null);
const history = useHistory();
const loadAndOpenTask = async (id) => {
const { data: task } = await tasksApi.getTaskDetails(id);
if (task) onPressName(task);
};
useEffect(() => {
if ((history.location.state as any)?.taskId)
loadAndOpenTask((history.location.state as any)?.taskId);
}, [history.location]);
const {
paginationList,
selectedTasksIds,
@ -109,6 +129,8 @@ export const TasksScreen: FC = () => { @@ -109,6 +129,8 @@ export const TasksScreen: FC = () => {
selectedItems: _.filter(paginationList.items, (it) =>
_.includes(selectedTasksIds, it.id)
),
profile,
permissions,
}),
[selectedTasksIds, paginationList.items]
);

33
src/containers/Taxonomy/components/Form/index.tsx

@ -20,6 +20,7 @@ interface IProps { @@ -20,6 +20,7 @@ interface IProps {
taxonomiesList: ITaxonomy[];
setShowModal: (val: boolean) => void;
paramsType: any;
editable?: boolean;
}
interface IFormData {
@ -88,11 +89,12 @@ export const Form = (props: IProps) => { @@ -88,11 +89,12 @@ export const Form = (props: IProps) => {
validModal
label="Аватар"
onChange={(file) => setFile(file)}
disabled={!props.editable}
image={av}
alt="Розмір зображення не більший 200px./200px., 500kb."
alt2=" Дозволені типи: JPG, SVG, PNG, WebP"
/>
{props.targetTaxonomy.iconUrl && (
{props.targetTaxonomy.iconUrl && props.editable && (
<div
style={{
position: "absolute",
@ -131,7 +133,7 @@ export const Form = (props: IProps) => { @@ -131,7 +133,7 @@ export const Form = (props: IProps) => {
formState,
}) => (
<InputField
register={{ onChange, onBlur, value }}
register={{ onChange, onBlur, value, disabled: !props.editable }}
error={error?.message}
label={"Назва"}
/>
@ -156,6 +158,7 @@ export const Form = (props: IProps) => { @@ -156,6 +158,7 @@ export const Form = (props: IProps) => {
if (!val) onChange(null);
else onChange(val);
},
disabled: !props.editable,
}}
error={error?.message}
touched={isTouched}
@ -187,21 +190,29 @@ export const Form = (props: IProps) => { @@ -187,21 +190,29 @@ export const Form = (props: IProps) => {
render={({ field }) => (
<CheckBoxForm
label={"По-замовчуванню"}
field={{ ...field, checked: field.value }}
field={{
...field,
onChange: (val: any) => {
if (props.editable) field.onChange(val);
},
checked: field.value,
}}
/>
)}
/>
</div>
)}
<ButtonToolbar
className="form__button-toolbar"
style={{ marginTop: -5 }}
>
<Button color="primary" type="submit" style={{ width: 150 }}>
Зберегти
</Button>
</ButtonToolbar>
{props.editable && (
<ButtonToolbar
className="form__button-toolbar"
style={{ marginTop: -5 }}
>
<Button color="primary" type="submit" style={{ width: 150 }}>
Зберегти
</Button>
</ButtonToolbar>
)}
</div>
</form>
);

21
src/containers/Taxonomy/components/TreeTaxonomy/index.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import { Tree, Popconfirm } from "antd";
import { genTree, getPermissionCheck } from "../../../../lib/helper";
import { genTree } from "../../../../lib/helper";
import _ from "lodash";
import "./style.scss";
import {
@ -10,10 +10,14 @@ import { @@ -10,10 +10,14 @@ import {
ETaxonomyType,
ConfirmModal,
notification,
checkPermission,
Permissions,
} from "@/shared";
import { taxonomiesService } from "@/services/domain/taxonomies.service";
import TrashSvgIcon from "../../../../assets/img/trash-icon.svg";
import GroupTaskIcon from "../../../../assets/img/tasks-group-icon.svg";
import { useSelector } from "react-redux";
import { getMyPermissions } from "@/store/permissions";
interface IProps {
taxonomiesList: ITaxonomy[];
@ -30,6 +34,7 @@ export const TreeTaxonomy = (props: IProps) => { @@ -30,6 +34,7 @@ export const TreeTaxonomy = (props: IProps) => {
const [isOpenConfirmModal, setConfirmModal] = useState<boolean>(false);
const [taxonomyIdToDelete, setTaxonomyIdToDelete] = useState<number>();
const [showPopconfirm, setShowPopconfirm] = useState<number>(null);
const permissions = useSelector(getMyPermissions);
const preparedTree = genTree(
_.cloneDeep(props.taxonomiesList).filter((item) => {
@ -127,10 +132,8 @@ export const TreeTaxonomy = (props: IProps) => { @@ -127,10 +132,8 @@ export const TreeTaxonomy = (props: IProps) => {
<div className="tree-title">
<div
onClick={() => {
if (getPermissionCheck("taxonomy", "update", props.profile)) {
taxonomiesService.setTargetTaxonomy(item);
props.onShowModal(true);
}
taxonomiesService.setTargetTaxonomy(item);
props.onShowModal(true);
}}
>
{item.iconUrl ? (
@ -153,7 +156,13 @@ export const TreeTaxonomy = (props: IProps) => { @@ -153,7 +156,13 @@ export const TreeTaxonomy = (props: IProps) => {
</div>
</div>
{getPermissionCheck("taxonomy", "destroy", props.profile) && (
{checkPermission(
"tabs",
"taxonomy",
props.profile,
permissions,
Permissions.DESTROY
) && (
<Popconfirm
visible={showPopconfirm === item.id}
title="Для видалення даної групи встановіть спочатку іншу групу групою по-замовчуванню "

63
src/containers/Taxonomy/index.tsx

@ -2,14 +2,19 @@ import React, { useEffect, useState } from "react"; @@ -2,14 +2,19 @@ import React, { useEffect, useState } from "react";
import { Col, Container, Row, Card, CardBody, Button } from "reactstrap";
import { useSelector } from "react-redux";
import { Form } from "./components/Form";
import { getPermissionCheck } from "../../lib/helper";
import Modal from "../../components/Modal";
import { TreeTaxonomy } from "./components/TreeTaxonomy";
import { getProfile } from "@/store/account";
import * as taxonomySelectors from "@/store/taxonomies/selectors";
import { match, useHistory } from "react-router-dom";
import { match } from "react-router-dom";
import { taxonomiesService } from "@/services/domain/taxonomies.service";
import { ETaxonomyType, SearchInput } from "@/shared";
import {
checkPermission,
ETaxonomyType,
Permissions,
SearchInput,
} from "@/shared";
import { getMyPermissions } from "@/store/permissions";
interface IProps {
match: match<{ type: string }>;
@ -27,11 +32,7 @@ export const Taxonomy = (props: IProps) => { @@ -27,11 +32,7 @@ export const Taxonomy = (props: IProps) => {
const [showModal, setShowModal] = useState<boolean>(false);
const [searchStr, setSearchStr] = useState<string>("");
const profile = useSelector(getProfile);
const history = useHistory();
// if (!getPermissionCheck('taxonomy', 'find', profile)) {
// // history.push('/');
// }
const permissions = useSelector(getMyPermissions);
useEffect(() => {
taxonomiesService.fetchTaxonomies();
@ -58,6 +59,17 @@ export const Taxonomy = (props: IProps) => { @@ -58,6 +59,17 @@ export const Taxonomy = (props: IProps) => {
setShowModal={setShowModal}
targetTaxonomy={targetTaxonomy}
taxonomiesList={taxonomiesList.list}
editable={
targetTaxonomy
? checkPermission(
"tabs",
"taxonomy",
profile,
permissions,
Permissions.UPDATE
)
: true
}
/>
</Modal>
@ -73,20 +85,27 @@ export const Taxonomy = (props: IProps) => { @@ -73,20 +85,27 @@ export const Taxonomy = (props: IProps) => {
onChange={setSearchStr}
/>
{getPermissionCheck("taxonomy", "create", profile) && (
<Button
color="primary"
size="sm"
onClick={() => {
taxonomiesService.setTargetTaxonomy({
type: ETaxonomyRouteType[props.match.params.type],
});
setShowModal(true);
}}
>
Створити
</Button>
)}
<Button
color="primary"
size="sm"
disabled={
!checkPermission(
"tabs",
"taxonomy",
profile,
permissions,
Permissions.CREATE
)
}
onClick={() => {
taxonomiesService.setTargetTaxonomy({
type: ETaxonomyRouteType[props.match.params.type],
});
setShowModal(true);
}}
>
Створити
</Button>
</div>
</Row>

13
src/containers/User/components/FormUser/index.tsx

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import React from "react";
import { ButtonToolbar, Button } from "reactstrap";
import { getPermissionCheck } from "../../../../lib/helper";
import _ from "lodash";
import "./style.scss";
import { Nav, NavItem, NavLink, TabContent, TabPane } from "reactstrap";
@ -8,11 +7,14 @@ import classnames from "classnames"; @@ -8,11 +7,14 @@ import classnames from "classnames";
import { Tooltip } from "antd";
import { useState } from "react";
import {
checkPermission,
emailPattern,
getParentsTree,
IMyPermissions,
InputField,
InputMaskField,
IUser,
Permissions,
SelectField,
} from "@/shared";
import { EUserRole } from "@/shared";
@ -26,6 +28,7 @@ import { PermissionsPage } from "@/containers/Permissions"; @@ -26,6 +28,7 @@ import { PermissionsPage } from "@/containers/Permissions";
interface IProps {
profile: IUser;
permissions: IMyPermissions;
onSubmit: () => void;
mode: UserModalMode;
selectedUser?: IUser;
@ -80,7 +83,13 @@ export const FormUser = (props: IProps) => { @@ -80,7 +83,13 @@ export const FormUser = (props: IProps) => {
Основне
</NavLink>
</NavItem>
{getPermissionCheck("user", "permissions", props.profile) && (
{checkPermission(
"tabs",
"user",
props.profile,
props.permissions,
Permissions.PERMISSIONS
) && (
<NavItem>
<NavLink
className={classnames({ active: activeTab === "2" })}

82
src/containers/User/configs/selected-users-menu.config.tsx

@ -1,4 +1,10 @@ @@ -1,4 +1,10 @@
import { ISelectedRowsMenuItemConfig, IUser } from "@/shared";
import {
checkPermission,
IMyPermissions,
ISelectedRowsMenuItemConfig,
IUser,
Permissions,
} from "@/shared";
import iconUserPen from "@/assets/img/userPencil.svg";
import iconUserLock from "@/assets/img/userLock.svg";
import iconUseExit from "@/assets/img/userExit.svg";
@ -8,12 +14,25 @@ import _ from "lodash"; @@ -8,12 +14,25 @@ import _ from "lodash";
interface IProps {
onClick: (key: "edit" | "block" | "logout" | "delete") => void;
selectedItems: IUser[];
profile: IUser;
permissions: IMyPermissions;
}
export const getSelectedUsersMenuConfig = (
props: IProps
): ISelectedRowsMenuItemConfig[] => {
if (_.isEmpty(props.selectedItems)) return [];
if (
_.isEmpty(props.selectedItems) ||
(props.selectedItems.length > 1 &&
!checkPermission(
"tabs",
"user",
props.profile,
props.permissions,
Permissions.DESTROY
))
)
return [];
if (props.selectedItems.length > 1)
return [
@ -29,8 +48,18 @@ export const getSelectedUsersMenuConfig = ( @@ -29,8 +48,18 @@ export const getSelectedUsersMenuConfig = (
},
];
const menuItems = [
{
const menuItems = [];
if (
checkPermission(
"tabs",
"user",
props.profile,
props.permissions,
Permissions.UPDATE
)
)
menuItems.push({
onClick: () => props.onClick("edit"),
key: "edit",
label: "Редагувати",
@ -39,8 +68,18 @@ export const getSelectedUsersMenuConfig = ( @@ -39,8 +68,18 @@ export const getSelectedUsersMenuConfig = (
name: "user pen",
className: "icon-user-pen",
},
},
{
});
if (
checkPermission(
"tabs",
"user",
props.profile,
props.permissions,
Permissions.BAN
)
)
menuItems.push({
onClick: () => props.onClick("block"),
key: "lock",
label: "Заблокувати",
@ -48,8 +87,18 @@ export const getSelectedUsersMenuConfig = ( @@ -48,8 +87,18 @@ export const getSelectedUsersMenuConfig = (
scr: iconUserLock,
name: "user lock",
},
},
{
});
if (
checkPermission(
"tabs",
"user",
props.profile,
props.permissions,
Permissions.LOGOUT
)
)
menuItems.push({
onClick: () => props.onClick("logout"),
key: "logout",
label: "Розлогінити",
@ -57,8 +106,18 @@ export const getSelectedUsersMenuConfig = ( @@ -57,8 +106,18 @@ export const getSelectedUsersMenuConfig = (
scr: iconUseExit,
name: "user logout",
},
},
{
});
if (
checkPermission(
"tabs",
"user",
props.profile,
props.permissions,
Permissions.DESTROY
)
)
menuItems.push({
onClick: () => props.onClick("delete"),
key: "delete",
label: "Видалити",
@ -67,8 +126,7 @@ export const getSelectedUsersMenuConfig = ( @@ -67,8 +126,7 @@ export const getSelectedUsersMenuConfig = (
name: "user delete",
className: "basket-icon",
},
},
];
});
return menuItems;
};

17
src/containers/User/hooks/use-create-edit-user.hook.ts

@ -1,11 +1,7 @@ @@ -1,11 +1,7 @@
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import _ from "lodash";
import {
accountService,
factoriesService,
usersService
} from "@/services/domain";
import { accountService, usersService } from "@/services/domain";
import {
IFactory,
IUser,
@ -17,7 +13,7 @@ import { @@ -17,7 +13,7 @@ import {
import moment from "moment";
import { convertUserDataToFormData, IUserForm } from "../helpers";
import { UserModalMode } from "../enums";
import { accountApi, permissionsApi, usersApi } from "@/api";
import { accountApi, factoriesApi, permissionsApi, usersApi } from "@/api";
import { appEvents } from "@/shared/events";
interface ICreateEditUserProps {
@ -85,9 +81,12 @@ export const useCreateEditUser = ({ @@ -85,9 +81,12 @@ export const useCreateEditUser = ({
}, [watchNewPassword, watchConfirmPassword]);
const fetchFactories = async () => {
const factories = await factoriesService.fetchFactories();
setFactories(factories);
try {
const { data: factories } = await factoriesApi.fetchFactoriesFullList();
setFactories(factories);
} catch (e) {
console.log("FETCH FACTORIES LIST ERROR", e);
}
};
useEffect(() => {

75
src/containers/User/index.tsx

@ -3,9 +3,14 @@ import { Col, Container, Row, Card, CardBody, Button } from "reactstrap"; @@ -3,9 +3,14 @@ import { Col, Container, Row, Card, CardBody, Button } from "reactstrap";
import { useSelector } from "react-redux";
import { UsersTableList } from "./components/DataUser";
import Modal from "../../components/Modal";
import { getPermissionCheck } from "../../lib/helper";
import { useState } from "react";
import { ConfirmModal, SearchInput, SelectedRowsMenu } from "@/shared";
import {
checkPermission,
ConfirmModal,
Permissions,
SearchInput,
SelectedRowsMenu,
} from "@/shared";
import "./style.scss";
import { getProfile } from "@/store/account";
import { FormUser } from "./components/FormUser";
@ -15,6 +20,7 @@ import _ from "lodash"; @@ -15,6 +20,7 @@ import _ from "lodash";
import { useUsersList } from "./hooks";
import { getSelectedUsersMenuConfig } from "./configs";
import { UserAddOutlined } from "@ant-design/icons";
import { getMyPermissions } from "@/store/permissions";
interface IModalState {
isOpen: boolean;
@ -27,6 +33,9 @@ const defaultModalState: IModalState = { @@ -27,6 +33,9 @@ const defaultModalState: IModalState = {
};
export const Users: FC = () => {
const profile = useSelector(getProfile);
const permissions = useSelector(getMyPermissions);
const [userModal, setUserModal] = useState<IModalState>(defaultModalState);
// const [showModalPermissions, setShowModalPermissions] = useState<boolean>(
// false
@ -42,8 +51,6 @@ export const Users: FC = () => { @@ -42,8 +51,6 @@ export const Users: FC = () => {
userActions,
} = useUsersList();
const profile = useSelector(getProfile);
const onOpenContextMenu = (isOpen: boolean, userId: number) => {
if (isOpen && !_.includes(selectedUsersId, userId))
setSelectedUsersId([userId]);
@ -95,24 +102,30 @@ export const Users: FC = () => { @@ -95,24 +102,30 @@ export const Users: FC = () => {
selectedItems: _.filter(paginationList.items, (it) =>
_.includes(selectedUsersId, it.id)
),
profile,
permissions,
}),
[selectedUsersId, paginationList.items]
[selectedUsersId, paginationList.items, profile, permissions]
);
const contextMenuConfig = useMemo(
() => [
{
const contextMenuConfig = useMemo(() => {
const config = [];
if (
checkPermission("tabs", "user", profile, permissions, Permissions.CREATE)
)
config.push({
onClick: () => onUserAction("create"),
key: "create",
label: "Створити",
iconNode: (
<UserAddOutlined className="selected-rows-menu-btn context icon-add-user" />
),
},
...selectedItemsMenuConfig,
],
[selectedItemsMenuConfig]
);
});
config.push(...selectedItemsMenuConfig);
return config;
}, [selectedItemsMenuConfig, profile, permissions]);
return (
<Container className="factory">
@ -143,6 +156,7 @@ export const Users: FC = () => { @@ -143,6 +156,7 @@ export const Users: FC = () => {
(it) => it.id === selectedUsersId[0]
)}
mode={userModal.mode}
permissions={permissions}
/>
</Modal>
@ -177,20 +191,27 @@ export const Users: FC = () => { @@ -177,20 +191,27 @@ export const Users: FC = () => {
isFocused={focusFilterKey === "globalSearch"}
onFocus={() => setFocusFilterKey("globalSearch")}
/>
{getPermissionCheck("user", "create", profile) ? (
<Button
color="primary"
size="sm"
onClick={() => {
setUserModal({
isOpen: true,
mode: UserModalMode.Create,
});
}}
>
Створити
</Button>
) : null}
<Button
color="primary"
size="sm"
disabled={
!checkPermission(
"tabs",
"user",
profile,
permissions,
Permissions.CREATE
)
}
onClick={() => {
setUserModal({
isOpen: true,
mode: UserModalMode.Create,
});
}}
>
Створити
</Button>
</div>
</div>
)}

13
src/containers/UserDetail/user-detail.page.tsx

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
import { getPermissionCheck } from "@/lib/helper";
import { getProfile } from "@/store/account";
import { Col, Container, Row } from "reactstrap";
import React, { FC } from "react";
@ -8,7 +7,8 @@ import { useContact } from "../Contact/hooks/use-contact.hook"; @@ -8,7 +7,8 @@ import { useContact } from "../Contact/hooks/use-contact.hook";
import { ProfileMain, ProfileTabs } from "../Profile/components";
import "./style.scss";
import { useUserActions } from "../Profile/hooks";
import { ConfirmModal } from "@/shared";
import { checkPermission, ConfirmModal, Permissions } from "@/shared";
import { getMyPermissions } from "@/store/permissions";
interface IProps {
match: match<{ id: string }>;
@ -16,6 +16,7 @@ interface IProps { @@ -16,6 +16,7 @@ interface IProps {
export const UserDetail: FC<IProps> = ({ match: { params: id } }) => {
const profile = useSelector(getProfile);
const permissions = useSelector(getMyPermissions);
const contactId = parseInt(id.id);
const { contactInfo, fetchContact } = useContact(contactId);
@ -25,7 +26,13 @@ export const UserDetail: FC<IProps> = ({ match: { params: id } }) => { @@ -25,7 +26,13 @@ export const UserDetail: FC<IProps> = ({ match: { params: id } }) => {
onAction: fetchContact,
});
const isUpdated = getPermissionCheck("user", "update", profile);
const isUpdated = checkPermission(
"tabs",
"user",
profile,
permissions,
Permissions.UPDATE
);
return contactInfo ? (
<Container>

11
src/index.tsx

@ -1,14 +1,13 @@ @@ -1,14 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom';
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { registerServiceWorker } from "./register-sw";
registerServiceWorker();
// registerServiceWorker();
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
document.getElementById("root")
);

61
src/scss/component/topbar.scss

@ -117,10 +117,14 @@ @@ -117,10 +117,14 @@
height: 21px;
}
.topbar_notification-count {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
background-color: #9E2743;
border-radius: 100px;
width: 12px;
width: 14px;
height: 14px;
right: 3px;
top: 17px;
padding: 1px 2px 0px 2px
@ -139,24 +143,55 @@ @@ -139,24 +143,55 @@
.notification_content {
background-color: #FFFF;
width: 343px;
padding: 16px 24px;
// padding: 16px 24px 30px 24px;
padding-bottom: 10px;
border-radius: 20px;
box-shadow: 0px -1px 4px rgba(0, 0, 0, 0.05), 0px 15px 20px rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
max-height: 50vh;
overflow-y: auto;
// display: flex;
// flex-direction: column;
// max-height: 50vh;
// overflow-y: auto;
.notification_title_wrapper {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px 10px 24px;
border-bottom: 1px solid #eff1f5;
}
.title {
font-size: 12px;
font-weight: 600;
margin-bottom: 12px;
font-size: 14px;
font-weight: 500;
// margin-bottom: 12px;
}
.read-all-button {
padding: 0;
font-size: 13px;
color: #c5d2d6
}
}
.notification_content_item {
.notification_content_items_list{
overflow: hidden;
overflow-y: auto;
max-height: 400px;
padding: 16px 24px 0 24px;
& :last-child {
border-bottom: none;
}
}
.notification_content_item_wrapper {
display: flex;
align-items: flex-start;
justify-content: space-between;
border-bottom: 1px solid #eff1f5;
padding: 16px 0;
}
.notification_content_item {
width: 73%;
margin-right: 5px;
p {
margin: 0px;
font-size: 12px;

8
src/services/domain/auth.service.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { authApi } from "@/api";
import { ITokenPair, StorageKey } from "@/shared";
import { EUserRole, ITokenPair, StorageKey } from "@/shared";
import { isLoading, ResetAccount } from "@/store/account";
import { ResetTokens, SaveTokens } from "@/store/auth";
import { Reset } from "@/store/shared";
@ -27,6 +27,9 @@ const signIn = async (payload: ISignIn) => { @@ -27,6 +27,9 @@ const signIn = async (payload: ISignIn) => {
await accountService.getAccount();
await permissionsService.loadAvailablePermissions();
if (store().getState()?.account?.account?.role !== EUserRole.Admin)
await permissionsService.loadMyPermissions();
if (payload.rememberMe) {
localStorage.setItem(StorageKey.RememberMe, "true");
} else {
@ -109,6 +112,9 @@ const refreshSession = async (refreshToken?: string) => { @@ -109,6 +112,9 @@ const refreshSession = async (refreshToken?: string) => {
if (_.isEmpty(store().getState()?.permissions?.availablePermissions))
await permissionsService.loadAvailablePermissions();
if (store().getState()?.account?.account?.role !== EUserRole.Admin)
await permissionsService.loadMyPermissions();
};
const _resetTokens = async () => {

4
src/services/domain/factories.service.ts

@ -3,7 +3,7 @@ import { IFactory } from "@/shared"; @@ -3,7 +3,7 @@ import { IFactory } from "@/shared";
import { factoriesApi } from "@/api";
import {
ICreateFactory,
IUpdateFactory,
IUpdateFactory
} from "@/api/factories/requests.interface";
import { FactoriesIsLoading } from "@/store/factories";
@ -61,5 +61,5 @@ export const factoriesService = { @@ -61,5 +61,5 @@ export const factoriesService = {
fetchFactories,
createFactory,
updateFactory,
deleteFactory,
deleteFactory
};

22
src/services/domain/permissions.service.ts

@ -1,5 +1,9 @@ @@ -1,5 +1,9 @@
import { simpleDispatch } from "@/store/store-helpers";
import { isLoading, SetAvailablePermissions } from "@/store/permissions";
import {
isLoading,
SetAvailablePermissions,
SetMyPermissions
} from "@/store/permissions";
import { permissionsApi } from "@/api";
export const loadAvailablePermissions = async () => {
@ -15,6 +19,20 @@ export const loadAvailablePermissions = async () => { @@ -15,6 +19,20 @@ export const loadAvailablePermissions = async () => {
}
};
export const loadMyPermissions = async () => {
try {
simpleDispatch(new isLoading({ isLoading: true }));
const { data } = await permissionsApi.fetchMyPermissionsTransformed();
simpleDispatch(new SetMyPermissions({ data }));
} catch (error) {
console.log("GET MY PERMISSIONS ERROR", error);
} finally {
simpleDispatch(new isLoading({ isLoading: false }));
}
};
export const permissionsService = {
loadAvailablePermissions
loadAvailablePermissions,
loadMyPermissions
};

1
src/services/system/index.ts

@ -1,2 +1,3 @@ @@ -1,2 +1,3 @@
export * from "./global-container.service";
export * from "./real-time.service";
export * from "./notifications.service";

140
src/services/system/notifications.service.ts

@ -0,0 +1,140 @@ @@ -0,0 +1,140 @@
import { notificApi } from "@/api/notifications/request";
import { RouteEnum } from "@/containers/App/router";
import { IPushNotification, NotificationKeys } from "@/shared";
import { SelectChatId } from "@/store/chats";
import { simpleDispatch } from "@/store/store-helpers";
import OneSignal from "react-onesignal";
export const needRedirect = {
to: null,
payload: null
};
const saveNeedRedirect = ({ to, payload }) => {
needRedirect.to = to;
needRedirect.payload = payload;
};
export const notificationActions = (
notification: IPushNotification,
tabIndex?: number
) => ({
[NotificationKeys.NEW_MESSAGE]: {
redirect: () => {
simpleDispatch(
new SelectChatId({ id: Number(notification.data.chatId) })
);
saveNeedRedirect({
to: `/chats?id=${notification.data.chatId}`,
payload: null
});
}
},
[NotificationKeys.NEW_CHAT_MEMBER]: {
redirect: () => {
simpleDispatch(
new SelectChatId({ id: Number(notification.data.chatId) })
);
saveNeedRedirect({
to: `/chats?id=${notification.data.chatId}`,
payload: null
});
}
},
[NotificationKeys.NEW_TASK]: {
redirect: () => {
saveNeedRedirect({
to: RouteEnum.Tasks,
payload: null
});
}
},
[NotificationKeys.NEW_TASK_COMMENT]: {
redirect: () => {
saveNeedRedirect({
to: RouteEnum.Tasks,
payload: {
type: NotificationKeys.NEW_TASK_COMMENT,
taskId: notification.data.taskId
}
});
}
},
[NotificationKeys.NEW_TASK_FILE]: {
redirect: () => {
saveNeedRedirect({
to: RouteEnum.Tasks,
payload: {
type: NotificationKeys.NEW_TASK_FILE,
taskId: notification.data.taskId
}
});
}
},
[NotificationKeys.TODAY_BIRTHDAY]: {
redirect: () => {
saveNeedRedirect({
to: RouteEnum.Contacts,
payload: { soonBirthday: true }
});
}
}
});
const onOpened = openResult => {
try {
const notif: IPushNotification = {
id: 0,
title: null,
content: null,
createDate: null,
isRead: false,
userId: null,
data: openResult.data as any
};
const action = notificationActions(notif)[
notif?.data?.type as NotificationKeys
];
action?.redirect();
} catch (e) {
console.log(e);
}
};
const runOneSignal = async () => {
try {
await OneSignal.init({
appId: "8b9066f5-8c3f-49f7-bef4-c5ab621f9d27",
allowLocalhostAsSecureOrigin: true
});
const isPermitted = await OneSignal.getNotificationPermission();
if (isPermitted !== "granted") await OneSignal.showNativePrompt();
if (isPermitted) await OneSignal.setSubscription(true);
const userId = await OneSignal.getUserId();
if (userId)
await notificApi.saveUserDevice({
notificationUserId: userId,
deviceUuid: userId
});
} catch (e) {
console.log("ONE SIGNAL RUNNING ERROR", e);
}
};
OneSignal.on("subscriptionChange", async isSubscribed => {
console.log("SUBSCRIPTION IS CHANGED", isSubscribed);
await OneSignal.setSubscription(isSubscribed);
});
OneSignal.addListenerForNotificationOpened(onOpened);
export const notificationsService = {
runOneSignal
};

9
src/services/system/real-time.service.ts

@ -2,6 +2,7 @@ import { config } from "@/config"; @@ -2,6 +2,7 @@ import { config } from "@/config";
import { socketEvents } from "@/shared/events";
import io from "socket.io-client";
import { authService } from "../domain";
import { GlobalContainerService } from "./global-container.service";
const store = () => GlobalContainerService.get("store");
@ -56,7 +57,7 @@ export class SocketIo { @@ -56,7 +57,7 @@ export class SocketIo {
}
initSockets() {
this._onSocketSendEvent("onNeedNotificationsUpdate");
this._onSocketSendEvent("notification");
this._onSocketSendEvent("chat/new-chat");
this._onSocketSendEvent("chat/new-message");
this._onSocketSendEvent("chat/delete-message");
@ -80,9 +81,15 @@ export class SocketIo { @@ -80,9 +81,15 @@ export class SocketIo {
this._onSocketSendEvent("user/connected");
this._onSocketSendEvent("user/disconnected");
this._onSocketSendEvent("user/deleted");
this._onSocketSendEvent("user/change-permissions");
this._onSocketSendEvent("stopSessions");
this._on("error/join-user", async () => {
await authService.refreshSession();
this.emit("join-user");
});
// this._on('reconnect_attempt', error => {
// console.log('SOCKET reconnect_attempt', error)
// })

1
src/shared/enums/index.ts

@ -8,3 +8,4 @@ export * from "./permissions.enums"; @@ -8,3 +8,4 @@ export * from "./permissions.enums";
export * from "./chat.enums";
export * from "./chat-bg.enum";
export * from "./factory.enums";
export * from "./push-notification.enum";

19
src/shared/enums/push-notification.enum.ts

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
export enum NotificationsGroup {
/** Сповіщення, що відносяться до задач */
Tasks = "t",
/** Сповіщення, що відносяться до чатів */
Chats = "c",
/** Сповіщення, що не відносяться до жодної з груп */
Other = "oth"
}
export enum NotificationKeys {
NEW_MESSAGE = "newMessage",
NEW_TASK = "newTask",
NEW_CHAT_MEMBER = "newChatMembers",
TODAY_BIRTHDAY = "todayBirthday",
NEW_TASK_COMMENT = "newTaskComment",
NEW_TASK_FILE = "newTaskFile"
}

3
src/shared/enums/user.enum.ts

@ -12,4 +12,5 @@ export enum EUserStatus { @@ -12,4 +12,5 @@ export enum EUserStatus {
export enum EUsersListType {
All = "a",
Executors = "e",
}
ExecutorsAll = "ea"
}

10
src/shared/events/index.ts

@ -1,4 +1,9 @@ @@ -1,4 +1,9 @@
import { IChatMessage, IFile, IMessage } from "../interfaces";
import {
IChatMessage,
IFile,
IMessage,
IPushNotification
} from "../interfaces";
import { Events } from "jet-tools";
import { ChatMemberRole } from "../enums";
@ -76,8 +81,11 @@ export type SocketEvents = { @@ -76,8 +81,11 @@ export type SocketEvents = {
"user/connected": { userId: number; sessionType: string };
"user/disconnected": { userId: number; sessionType: string };
"user/deleted": { userId: number };
"user/change-permissions": {};
stopSessions: { sessionsIds: number[] };
notification: { notification: IPushNotification };
};
export const appEvents = new Events<AppEvents>();

45
src/shared/helpers/permissions.helpers.ts

@ -1,7 +1,13 @@ @@ -1,7 +1,13 @@
import { PermissionsSection } from "@/containers/Permissions/enums";
import { PermissionForm } from "@/containers/Permissions/interfaces";
import _ from "lodash";
import { IPermissionsAbstract, IUserPermissions } from "../interfaces";
import { EUserRole, EUserStatus, Permissions } from "../enums";
import {
IMyPermissions,
IPermissionsAbstract,
IUser,
IUserPermissions
} from "../interfaces";
export const transformPermissionsToFormValues = (
permissionsObj: IUserPermissions | string
@ -69,3 +75,40 @@ export const transformFormValuesToPermissions = ( @@ -69,3 +75,40 @@ export const transformFormValuesToPermissions = (
return permissions;
};
export const checkPermission = (
controller: "tabs" | "user" | "filter_factory",
target:
| "factory"
| "user"
| "group_permission"
| "taxonomy"
| string
| number
| null,
user: IUser,
permissions: IMyPermissions,
action?: Permissions
): boolean => {
if (user.status !== EUserStatus.Active) {
return false;
}
if (user.role === EUserRole.Admin) {
return true;
}
if (_.isEmpty(permissions)) return false;
const targetPermissions = permissions[controller];
if (controller === "filter_factory") return !!targetPermissions;
if (_.isEmpty(targetPermissions)) return false;
const actions = targetPermissions[target];
if (!_.isEmpty(actions) && action) return _.includes(actions, action);
return !_.isEmpty(actions);
};

1
src/shared/interfaces/index.ts

@ -14,3 +14,4 @@ export * from "./option.interfaces"; @@ -14,3 +14,4 @@ export * from "./option.interfaces";
export * from "./chats.interfaces";
export * from "./messages.interfaces";
export * from "./media.interfaces";
export * from "./push-notification.interfaces";

6
src/shared/interfaces/notification.interfaces.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
export interface INotification {
title: string
message: string
}
title: string;
message: string;
}

8
src/shared/interfaces/permissions.interface.ts

@ -37,3 +37,11 @@ export interface IPermissionsAbstract { @@ -37,3 +37,11 @@ export interface IPermissionsAbstract {
export interface IPermissionsInGroup extends IPermissionsAbstract {}
export interface IUserPermissions extends IPermissionsAbstract {}
export enum PermissionsActions {
Create = "create",
Find = "find",
Update = "update",
Destroy = "destroy"
}
export interface IMyPermissions extends Omit<IPermissions, "factory"> {}

15
src/shared/interfaces/push-notification.interfaces.ts

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
import { NotificationsGroup } from "../enums";
import { IUser } from "./user.interfaces";
export interface IPushNotification {
id: number;
title: string;
content: string;
group?: NotificationsGroup;
isRead: boolean;
userId: number;
user?: IUser;
createDate: string;
imageUrl?: string;
data?: Record<string, string>;
}

12
src/store/permissions/reducer.ts

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { IMyPermissions } from "@/shared";
import { createReducer } from "@bitalikrty/redux-create-reducer";
import { TPermissionsActions } from "./types";
@ -6,11 +7,13 @@ export interface IPermissionsState { @@ -6,11 +7,13 @@ export interface IPermissionsState {
availablePermissions: {
[x: string]: string[] | { [x: string]: string[] | boolean };
};
myPermissions: IMyPermissions;
}
const initialState: IPermissionsState = {
isLoading: false,
availablePermissions: {}
availablePermissions: {},
myPermissions: {}
};
export const PermissionsReducer = createReducer<
@ -24,6 +27,13 @@ export const PermissionsReducer = createReducer< @@ -24,6 +27,13 @@ export const PermissionsReducer = createReducer<
};
},
SET_MY_PERMISSIONS: (state, action) => {
return {
...state,
myPermissions: action.payload.data
};
},
IS_LOADING_PERMISSIONS: (state, action) => {
return {
...state,

3
src/store/permissions/selectors.ts

@ -5,3 +5,6 @@ export const isLoadingPermissions = (state: RootState) => @@ -5,3 +5,6 @@ export const isLoadingPermissions = (state: RootState) =>
export const getAvailablePermissions = (state: RootState) =>
state.permissions.availablePermissions;
export const getMyPermissions = (state: RootState) =>
state.permissions.myPermissions;

16
src/store/permissions/types.ts

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { IMyPermissions } from "@/shared";
import { Action } from "redux";
export class SetAvailablePermissions implements Action {
@ -12,6 +13,16 @@ export class SetAvailablePermissions implements Action { @@ -12,6 +13,16 @@ export class SetAvailablePermissions implements Action {
) {}
}
export class SetMyPermissions implements Action {
readonly type = "SET_MY_PERMISSIONS";
constructor(
public readonly payload: {
data: IMyPermissions;
}
) {}
}
export class isLoading {
readonly type = "IS_LOADING_PERMISSIONS";
constructor(
@ -21,4 +32,7 @@ export class isLoading { @@ -21,4 +32,7 @@ export class isLoading {
) {}
}
export type TPermissionsActions = SetAvailablePermissions | isLoading;
export type TPermissionsActions =
| SetAvailablePermissions
| SetMyPermissions
| isLoading;

Loading…
Cancel
Save