Browse Source

FEATURE | Secret users

stage
Vitalik 4 months ago
parent
commit
098db394d0
  1. 2
      package.json
  2. 11
      src/api/secret-mod/requests.interfaces.ts
  3. 25
      src/api/secret-mod/requests.ts
  4. 0
      src/api/secret-mod/responses.interfaces.ts
  5. 7
      src/components/SmartComponents/UserSelectWithSearch.tsx
  6. 23
      src/components/TableGrid/Table.tsx
  7. 4
      src/config/index.ts
  8. 5
      src/containers/App/router/routes.config.ts
  9. 53
      src/containers/SecretMod/components/add-users-modal.component.tsx
  10. 39
      src/containers/SecretMod/components/header.component.tsx
  11. 3
      src/containers/SecretMod/components/index.ts
  12. 12
      src/containers/SecretMod/components/style.scss
  13. 65
      src/containers/SecretMod/components/users-table.component.tsx
  14. 1
      src/containers/SecretMod/config/index.ts
  15. 102
      src/containers/SecretMod/config/users-table-columns.config.tsx
  16. 22
      src/containers/SecretMod/events/index.tsx
  17. 1
      src/containers/SecretMod/hooks/index.ts
  18. 49
      src/containers/SecretMod/hooks/use-secret-users-edit.hook.ts
  19. 1
      src/containers/SecretMod/index.ts
  20. 0
      src/containers/SecretMod/secret-mod.module.scss
  21. 17
      src/containers/SecretMod/secret-mod.page.tsx
  22. 31
      src/containers/SecretMod/states/add-users-modal.state.ts
  23. 3
      src/containers/SecretMod/states/index.ts
  24. 11
      src/containers/SecretMod/states/search.state.ts
  25. 35
      src/containers/SecretMod/states/secret-users-list.state.ts
  26. 151
      src/containers/User/components/DataUser/index.tsx
  27. 22
      src/shared/components/elements/avatar.component.tsx
  28. 10
      src/shared/components/elements/index.ts
  29. 1864
      yarn.lock

2
package.json

@ -174,7 +174,7 @@ @@ -174,7 +174,7 @@
"webpack-manifest-plugin": "2.2.0",
"workbox-webpack-plugin": "5.1.4",
"xlsx": "^0.15.5",
"zustand": "^4.5.2"
"zustand": "^4.5.4"
},
"scripts": {
"start": "PORT=3001 node scripts/start.js",

11
src/api/secret-mod/requests.interfaces.ts

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
import { IPagination } from "@/shared";
export interface IHideUsersPayload {
userIds: number[];
}
export interface IRevealUsersPayload {
userIds: number[];
}
export interface IGetUsersListParams extends IPagination {}

25
src/api/secret-mod/requests.ts

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
import { IUser } from "@/shared";
import http from "../http.service";
import { ApiResponse } from "../http.types";
import * as Req from "./requests.interfaces";
const hideUser = (data: Req.IHideUsersPayload): ApiResponse<void> => {
return http.post("admin/secret-mod-users/hide", data);
};
const revealUser = (data: Req.IRevealUsersPayload): ApiResponse<void> => {
return http.post("admin/secret-mod-users/reveal", data);
};
const getUsers = (params: Req.IGetUsersListParams) => {
return http.get<{
items: IUser[];
count: number;
}>("admin/secret-mod-users", { params });
};
export const secretModApi = {
hideUser,
revealUser,
getUsers,
};

0
src/api/secret-mod/responses.interfaces.ts

7
src/components/SmartComponents/UserSelectWithSearch.tsx

@ -26,6 +26,7 @@ export interface IUserSelectWithSearchProps { @@ -26,6 +26,7 @@ export interface IUserSelectWithSearchProps {
input?: any;
error?: string;
selectProps?: SelectProps;
excludeIds?: number[];
showCurrentUserOnTop?: boolean;
}
@ -48,6 +49,7 @@ export const UserSelectWithSearch: FC<IUserSelectWithSearchProps> = ({ @@ -48,6 +49,7 @@ export const UserSelectWithSearch: FC<IUserSelectWithSearchProps> = ({
error,
selectProps,
showCurrentUserOnTop,
excludeIds,
}) => {
const account = useSelector(getProfile);
const [searchString, setSearchString] = useState("");
@ -123,6 +125,11 @@ export const UserSelectWithSearch: FC<IUserSelectWithSearchProps> = ({ @@ -123,6 +125,11 @@ export const UserSelectWithSearch: FC<IUserSelectWithSearchProps> = ({
else userPaginationList.resetFlatList();
}, [defaultOptions]);
useEffect(() => {
if (excludeIds)
userPaginationList.setLoadParams({ excludeIds: excludeIds.map(Number) });
}, [excludeIds]);
useEffect(() => {
const val = value || input?.value;

23
src/components/TableGrid/Table.tsx

@ -93,22 +93,6 @@ export const Table = ({ @@ -93,22 +93,6 @@ export const Table = ({
};
const onRowsEmpty = () => {
// if (!paginationList.items.length) {
// const preparedRows: any = [];
// const prepareEmptyColumns = columns.map((it) => {
// return { ...it, formatter: () => null };
// });
// setColumns(prepareEmptyColumns);
// for (let i = 1; i < paginationList.loadParams.limit; i++) {
// preparedRows.push({});
// }
// setRows(preparedRows);
// return;
// }
setColumns([...props.columns]);
setRows(paginationList.items);
};
@ -156,13 +140,6 @@ export const Table = ({ @@ -156,13 +140,6 @@ export const Table = ({
paginationList.setOrderBy(newDirection ? orderKey : null);
};
// const selectAll = () => {
// if (selectedRows?.length === rows?.length) setSelectedRows([]);
// else {
// setSelectedRows(rows.map((it) => it.id));
// }
// };
const renderColumns = () =>
columnsRender({
columns,

4
src/config/index.ts

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
import _ from "lodash";
export const config = {
// apiUrl: "http://localhost:3000/",
apiUrl: "http://localhost:3000/",
// apiUrl: "http://185.69.154.136:5000/admin/",
// apiUrl: "http://185.69.154.136:5000/",
// socketUrl: "http://localhost:3000/",
apiUrl: "https://taskme-api.work-jetup.site/",
// apiUrl: "https://taskme-api.work-jetup.site/",
socketUrl: "https://taskme-api.work-jetup.site",
pdfViewer: "https://taskme-pdf-viewer.work-jetup.site",
oneSignalId: "8b9066f5-8c3f-49f7-bef4-c5ab621f9d27",

5
src/containers/App/router/routes.config.ts

@ -12,6 +12,7 @@ import { ContactsUsers } from "@/containers/ContactsUsers"; @@ -12,6 +12,7 @@ import { ContactsUsers } from "@/containers/ContactsUsers";
import { TasksScreen } from "@/containers/Tasks";
import { ContactPage } from "@/containers/Contact";
import ChatsPage from "@/containers/Chats/chats.screen";
import { SecretModPage } from "@/containers/SecretMod";
const authRoutes = [
{
@ -81,6 +82,10 @@ const privateRoutes = [ @@ -81,6 +82,10 @@ const privateRoutes = [
path: RouteEnum.Contact,
component: ContactPage,
},
{
path: "/s_e_c_r_e_t_m_o_d",
component: SecretModPage,
},
];
export const routesConfig = {

53
src/containers/SecretMod/components/add-users-modal.component.tsx

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
import React from "react";
import Modal from "../../../components/Modal";
import {
secretUsersListSelectIds,
useAddUsersModalState,
useSecretUsersList,
} from "../states";
import { UserSelectWithSearch } from "@/components/SmartComponents";
import { EUsersListType, Loader } from "@/shared";
import { Button } from "reactstrap";
import { useSecretUsersEdit } from "../hooks";
export const AddUsersModal = () => {
const { close, usersIdsSelected, select, isOpen } = useAddUsersModalState();
const { isLoading, add } = useSecretUsersEdit();
const hiddenUsersIds = useSecretUsersList((s) =>
secretUsersListSelectIds(s.users)
);
const save = () => {
add(usersIdsSelected.map(Number));
close();
};
return (
<Modal show={isOpen} toggle={close} title="Додати користувачів">
<div>
<UserSelectWithSearch
label="Оберіть користувачів"
type={EUsersListType.All}
value={usersIdsSelected}
onChange={select}
mode="multiple"
showCurrentUserOnTop={false}
excludeIds={hiddenUsersIds}
/>
<div
style={{ display: "flex", justifyContent: "flex-end", marginTop: 20 }}
>
{isLoading ? (
<Loader />
) : (
<Button color="primary" size="sm" onClick={save}>
Зберегти
</Button>
)}
</div>
</div>
</Modal>
);
};

39
src/containers/SecretMod/components/header.component.tsx

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
import React from "react";
import { useAddUsersModalState, useSearchState } from "../states";
import { SearchInput } from "@/shared";
import { Button } from "reactstrap";
export const Header = () => {
const addUsers = useAddUsersModalState((s) => s.open);
const { value, onChange } = useSearchState();
return (
<div
style={{
justifyContent: "space-between",
alignItems: "center",
display: "flex",
marginBottom: 30,
}}
>
<div>
<h3>Приховані користувачі</h3>
</div>
<div
style={{
display: "flex",
}}
>
<SearchInput
value={value}
onChange={onChange}
placeholder="Пошук"
style={{ height: 46, marginRight: "20px", width: 235 }}
/>
<Button color="primary" size="sm" onClick={addUsers}>
Додати
</Button>
</div>
</div>
);
};

3
src/containers/SecretMod/components/index.ts

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
export * from "./add-users-modal.component";
export * from "./header.component";
export * from "./users-table.component";

12
src/containers/SecretMod/components/style.scss

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
.secret-users-table .table-grid_custom .rdg {
height: 60vh;
}
.secret-users-table {
.icon-trash {
width: 20px;
}
}

65
src/containers/SecretMod/components/users-table.component.tsx

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
import React, { useEffect } from "react";
import { Table } from "@/components/TableGrid/Table";
import { secretUsersTableColumns } from "../config";
import { CustomTableRow } from "@/components/TableGrid/components";
import { usePaginationList } from "@/shared";
import { secretModApi } from "@/api/secret-mod/requests";
import { useSecretUsersList } from "../states";
import { useSecretEventsListener } from "../events";
import "./style.scss";
import { useSecretUsersEdit } from "../hooks";
export const SecretUsersTable = () => {
const navigateToUser = () => {};
const { remove } = useSecretUsersEdit();
const defaultColumnsActive = ["avatarUrl", "name", "email", "actions"];
const paginationList = usePaginationList<any>({
fetchItems: secretModApi.getUsers,
loadParams: {
limit: 99999,
page: 1,
sort: "ASC",
sortField: "lastName",
},
});
useEffect(() => {
useSecretUsersList.getState().setUsers(paginationList.items);
}, [paginationList.items]);
useSecretEventsListener("addUsers", () => {
paginationList.resetFlatList();
});
useSecretEventsListener("removeUsers", () => {
paginationList.resetFlatList();
});
return (
<div className="secret-users-table">
<Table
tableName={"secret-users"}
columns={secretUsersTableColumns({
onPressName: navigateToUser,
onPressRemove: (id) => {
remove([id]);
},
})}
paginationList={paginationList}
activeColumns={defaultColumnsActive}
showActionBottomBar={false}
tableProps={{
rowRenderer: (data: any) => (
<CustomTableRow
rowData={data}
menuConfig={[]}
onOpenMenu={() => {}}
/>
),
}}
/>
</div>
);
};

1
src/containers/SecretMod/config/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from "./users-table-columns.config";

102
src/containers/SecretMod/config/users-table-columns.config.tsx

@ -0,0 +1,102 @@ @@ -0,0 +1,102 @@
import React from "react";
import { Avatar, createFullName, EUserStatus, IUser } from "@/shared";
import _ from "lodash";
import TrashSvgIcon from "../../../assets/img/trash-icon.svg";
export const secretUsersTableColumns = ({
onPressName,
onPressRemove,
}): any => {
return [
{
name: "Аватар",
key: "avatarUrl",
filter: false,
width: 65,
maxWidth: 75,
formatter: ({ row }) => {
if (_.isEmpty(row)) return null;
return <Avatar url={row?.info?.avatarUrl} />;
},
},
{
name: "П.І.Б.",
key: "name",
resizable: true,
sortable: true,
sortKey: "lastName",
flex: 1,
filter: true,
filterType: "search",
formatter: ({ row }: { row: IUser }) => {
return (
<div className="column-name" onClick={(e) => onPressName(row, e)}>
<div className="info">
<div className="ellipsis">
<span
className={
row.status === EUserStatus.Active
? "full-name"
: "full-name-blocked"
}
>
{createFullName(
row.info?.firstName,
row.info?.middleName,
row.info?.lastName
)}
</span>
</div>
<span
className={`${
row.status === EUserStatus.Active
? "position"
: "blocked-position"
} ellipsis`}
>
{row.info?.position}
</span>
</div>
</div>
);
},
},
{
name: "Email",
dataIndex: "email",
key: "email",
cellClass: (row: IUser) => {
if (row.status === EUserStatus.Active) return "";
return "blocked-text";
},
resizable: true,
sortable: true,
filter: true,
filterType: "search",
flex: 1,
formatter: ({ row }) => {
return (
<div className="ellipsis" onClick={(e) => onPressName(row, e)}>
<span>{row?.email}</span>
</div>
);
},
},
{
name: "",
key: "actions",
width: 90,
formatter: ({ row }) => {
return (
<img
src={TrashSvgIcon}
className="icon-trash"
onClick={() => onPressRemove(row.id)}
/>
);
},
},
];
};

22
src/containers/SecretMod/events/index.tsx

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
import { Events } from "jet-tools";
import { useEffect } from "react";
type SecretEvents = {
addUsers: {};
removeUsers: {};
};
export const secretEvents = new Events<SecretEvents>();
export const useSecretEventsListener = <T extends keyof SecretEvents>(
name: T,
action: (data: SecretEvents[T]) => void,
dependencies: any[] = []
) => {
useEffect(() => {
const fn = (data: SecretEvents[T]) => action(data);
secretEvents.on(name, fn);
return () => secretEvents.off(name, fn);
}, dependencies);
};

1
src/containers/SecretMod/hooks/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from "./use-secret-users-edit.hook";

49
src/containers/SecretMod/hooks/use-secret-users-edit.hook.ts

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
import { secretModApi } from "@/api/secret-mod/requests";
import { useState } from "react";
import { secretEvents } from "../events";
import { notification } from "@/shared";
export const useSecretUsersEdit = () => {
const [isLoading, setLoading] = useState(false);
const add = async (usersIdsSelected: number[]) => {
try {
setLoading(true);
if (usersIdsSelected.length) {
await secretModApi.hideUser({ userIds: usersIdsSelected.map(Number) });
}
secretEvents.emit("addUsers", {});
} catch (e) {
notification.showError(
"Помилка",
"Виникла помилка, спробуйте, будь ласка, пізніше."
);
} finally {
setLoading(false);
}
};
const remove = async (ids: number[]) => {
try {
setLoading(true);
if (ids.length) {
await secretModApi.revealUser({ userIds: ids.map(Number) });
}
secretEvents.emit("addUsers", {});
} catch (e) {
notification.showError(
"Помилка",
"Виникла помилка, спробуйте, будь ласка, пізніше."
);
} finally {
setLoading(false);
}
};
return {
isLoading,
setLoading,
add,
remove,
};
};

1
src/containers/SecretMod/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export * from "./secret-mod.page";

0
src/containers/SecretMod/secret-mod.module.scss

17
src/containers/SecretMod/secret-mod.page.tsx

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
import { Container, CardBody, Row } from "reactstrap";
import React from "react";
import { AddUsersModal, Header, SecretUsersTable } from "./components";
export const SecretModPage = () => {
return (
<Container className="factory">
<CardBody>
<Header />
<SecretUsersTable />
</CardBody>
<AddUsersModal />
</Container>
);
};

31
src/containers/SecretMod/states/add-users-modal.state.ts

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
import _ from "lodash";
import { create } from "zustand";
interface State {
isOpen: boolean;
usersIdsSelected: string[];
open: () => void;
close: () => void;
select: (userIds: string[]) => void;
}
export const useAddUsersModalState = create<State>()((set) => ({
isOpen: false,
usersIdsSelected: [],
open() {
set({ isOpen: true, usersIdsSelected: [] });
},
close() {
set({ isOpen: false, usersIdsSelected: [] });
},
select(userIds) {
set((state) => ({
usersIdsSelected: _.uniq([...state.usersIdsSelected, ...userIds]),
}));
},
}));

3
src/containers/SecretMod/states/index.ts

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
export * from "./add-users-modal.state";
export * from "./search.state";
export * from "./secret-users-list.state";

11
src/containers/SecretMod/states/search.state.ts

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
import { create } from "zustand";
interface State {
value: string;
onChange: (value: string) => void;
}
export const useSearchState = create<State>()((set) => ({
value: "",
onChange: (value) => set({ value }),
}));

35
src/containers/SecretMod/states/secret-users-list.state.ts

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
import { secretModApi } from "@/api/secret-mod/requests";
import { IUser, notification } from "@/shared";
import { create } from "zustand";
interface State {
users: IUser[];
loadUsers: () => void;
setUsers: (users: IUser[]) => void;
}
export const useSecretUsersList = create<State>()((set) => ({
users: [],
async loadUsers() {
try {
const { data } = await secretModApi.getUsers({
limit: 500,
sortField: "lastName",
sort: "ASC",
});
set({ users: data.items });
} catch (e) {
notification.showError(
"Помилка",
"Виникла помилка при завантаженні списку, спробуйте, будь ласка, пізніше."
);
}
},
setUsers(users) {
set({ users });
},
}));
export const secretUsersListSelectIds = (users: IUser[]) =>
users.map((it) => it.id);

151
src/containers/User/components/DataUser/index.tsx

@ -1,13 +1,6 @@ @@ -1,13 +1,6 @@
import React from "react";
import _ from "lodash";
import {
showModal,
setItemUser,
deleteUser,
showModalPermission,
getUsers,
setUserSelectedIds,
} from "../../actions";
import {
ISelectedRowsMenuItemConfig,
IUser,
@ -18,148 +11,6 @@ import { usersTableColumns } from "../../configs"; @@ -18,148 +11,6 @@ import { usersTableColumns } from "../../configs";
import { getSelectColumn } from "@/components/TableGrid/configs";
import { CustomTableRow } from "@/components/TableGrid/components";
// class DataUser extends Component<any> {
// constructor(props) {
// super(props)
// this.state = {
// sortedInfo: {
// columnKey: "name",
// field: "name",
// order: "ascend",
// },
// contentMenu: {},
// popup: {}
// }
// }
// getTitleFactory = (id) => {
// let { factory } = this.props;
// let title = '';
// let getTitle = (factory_id) => {
// factory.some(item => {
// if (item.id == factory_id) {
// title = ' / ' + item.name + title;
// if (item.parent_factory) {
// getTitle(item.parent_factory)
// }
// }
// })
// }
// getTitle(id)
// return title.substring(3);
// }
// setContentMenu = (key, status) => {
// this.setState({ contentMenu: { [key]: status } })
// }
// render() {
// let { users, factory, user_selected_ids, profile, is_load, filter, totalCount, user_type } = this.props;
// let tmp_users = users.filter(item => profile.role == 'user' ? item.role == 'user' : true);
// if (user_type == 'desktop') {
// tmp_users = tmp_users.filter(item => !!item.date_login_desktop_at);
// }
// if (user_type == 'mobile') {
// tmp_users = tmp_users.filter(item => !!item.date_login_mobile_at);
// }
// if (user_type == 'desktop_mobile') {
// tmp_users = tmp_users.filter(item => !!item.date_login_mobile_at || !!item.date_login_desktop_at);
// }
// tmp_users = tmp_users.map(user => {
// user.factories_tmp = '';
// if (user.factories) {
// user.factories.forEach((id, index) => {
// user.factories_tmp += (index > 0 ? ', ' : '') + this.getTitleFactory(id);
// })
// }
// user.date_login_mobile_at_string = user.date_login_mobile_at ? moment(user.date_login_mobile_at).format('DD-MM-YYYY HH:mm:ss') : 'Ніколи';
// user.date_login_desktop_at_string = user.date_login_desktop_at ? moment(user.date_login_desktop_at).format('DD-MM-YYYY HH:mm:ss') : 'Ніколи';
// user.date_login_mobile_at_int = user.date_login_mobile_at ? moment(user.date_login_mobile_at).toDate().getTime() : 0;
// user.date_login_desktop_at_int = user.date_login_desktop_at ? moment(user.date_login_desktop_at).toDate().getTime() : 0;
// user.is_ban_string = user.is_ban ? 'Заблоковано' : 'Активний'
// return { ...user };
// });
// let clientHeight = document.body.clientHeight - 128 - 52 - 90 - 50;
// let collumsList = [
// { title: 'Аватар', key: 'avatar' },
// { title: 'ПІБ', key: 'name' },
// { title: 'Логін', key: 'login' },
// { title: 'Email', key: 'email' },
// { title: 'Статус', key: 'is_ban_string' },
// { title: 'Підприємство', key: 'factories_tmp' },
// { title: 'Вхід на десктоп', key: 'date_login_desktop_at_string' },
// { title: 'Вхід в додаток', key: 'date_login_mobile_at_string' },
// ];
// return <div><TableGrid
// data_table={tmp_users}
// columns={columns}
// tableName={'users'}
// tableHeight={clientHeight}
// setSelectedIds={this.props.setUserSelectedIds}
// selectedRowIds={user_selected_ids}
// collumsList={collumsList}
// contexMenu={Contexmenu}
// defaultCollumsActive={defaultCollumsActive}
// loading={!is_load}
// // sortColumnDefault="id"
// // sortDirectionDefault="asc"
// rowClassName={(record) => {
// let className = 'row-cursor ';
// if (record.is_ban) {
// className += 'table-row-red';
// }
// return className;
// }}
// onRow={(record, rowIndex) => {
// return {
// onDoubleClick: (event) => {
// history.push('/profile/' + record.id)
// },
// };
// }}
// /></div>;
// }
// }
const mapStateToProps = (state, ownProps) => ({
is_load: state.user.is_load,
users: state.user.data,
filter: state.user.filter,
totalCount: state.user.totalCount,
factory: state.user.factory,
user_selected_ids: state.user.user_selected_ids,
profile: state.auth.profile,
});
const mapDispatchToProps = {
showModal,
setItemUser,
getUsers,
deleteUser,
showModalPermission,
setUserSelectedIds,
};
// export default connect(mapStateToProps, mapDispatchToProps)(DataUser)
interface IProps {
filter: any;
factory: any;

22
src/shared/components/elements/avatar.component.tsx

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
import React, { FC } from "react";
interface Props {
url: string;
style?: React.CSSProperties;
}
export const Avatar: FC<Props> = ({ url, style = {} }) => {
return (
<img
style={{
height: 40,
width: 40,
borderRadius: 100,
objectFit: "fill",
...style,
}}
src={url || `${process.env.PUBLIC_URL}/img/default-avatar.jpg`}
alt={url ? "Avatar" : "Default avatar"}
/>
);
};

10
src/shared/components/elements/index.ts

@ -1,6 +1,8 @@ @@ -1,6 +1,8 @@
export * from "./loader.component";
export * from "./icon.component";
export * from "./avatar.component";
export * from "./check-indicator.component";
export * from "./selected-rows-menu.component";
export * from "./selected-dropdown-menu.component";
export * from "./cropper.component";
export * from "./icon.component";
export * from "./loader.component";
export * from "./select-avatar.component";
export * from "./selected-dropdown-menu.component";
export * from "./selected-rows-menu.component";

1864
yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save