Browse Source

Merge branch 'stage' of gitlab.work-jetup.site:task-me/rws-web-app into stage

pull/25/head
Vitalik 8 months ago
parent
commit
bd09c17bbc
  1. 23459
      package-lock.json
  2. 3
      package.json
  3. 1
      src/api/notifications/request.interfaces.ts
  4. 3
      src/assets/img/3-dots.svg
  5. 2
      src/components/Fields/DateField.tsx
  6. 10
      src/containers/Chats/atoms/audio-player.atom.tsx
  7. 1
      src/containers/Chats/atoms/index.ts
  8. 83
      src/containers/Chats/atoms/selected-messages-info.atom.tsx
  9. 39
      src/containers/Chats/atoms/style.scss
  10. 21
      src/containers/Chats/chats.screen.tsx
  11. 4
      src/containers/Chats/components/forward-message-modal.component.tsx
  12. 6
      src/containers/Chats/configs/chat-message-menu-options.config.ts
  13. 1
      src/containers/Chats/configs/index.ts
  14. 45
      src/containers/Chats/configs/selected-messages-menu-options.config.ts
  15. 6
      src/containers/Chats/consts/index.ts
  16. 1
      src/containers/Chats/enums/chat-message-action.enum.ts
  17. 4
      src/containers/Chats/enums/chat-view-mode.enum.ts
  18. 1
      src/containers/Chats/enums/index.ts
  19. 38
      src/containers/Chats/helpers/get-copied-messages-content.helper.ts
  20. 1
      src/containers/Chats/helpers/index.ts
  21. 2
      src/containers/Chats/hooks/index.ts
  22. 70
      src/containers/Chats/hooks/use-chat-messages.hook.ts
  23. 14
      src/containers/Chats/hooks/use-chat-view-mode-state.hook.ts
  24. 165
      src/containers/Chats/hooks/use-selected-messages.hook.tsx
  25. 10
      src/containers/Chats/modals/chat-confirm-delete-modal.tsx
  26. 4
      src/containers/Chats/modals/user-info.modal.tsx
  27. 22
      src/containers/Chats/modals/user-info.styles.scss
  28. 14
      src/containers/Chats/plugins/chat-header.component.tsx
  29. 8
      src/containers/Chats/plugins/chat-item-file.component.tsx
  30. 7
      src/containers/Chats/plugins/chat-item-image.component.tsx
  31. 60
      src/containers/Chats/plugins/chat-item-video.component.tsx
  32. 57
      src/containers/Chats/plugins/chat-item.component.tsx
  33. 1
      src/containers/Chats/plugins/interfaces.ts
  34. 62
      src/containers/Chats/plugins/style.scss
  35. 17
      src/containers/Chats/smart-components/chats-select-list.smart-component.tsx
  36. 3
      src/containers/Chats/transforms/chat-messages.transforms.ts
  37. 21
      src/containers/Profile/components/form-user/form-user.component.tsx
  38. 2
      src/containers/Tasks/tasks.screen.tsx
  39. 6
      src/containers/User/components/FormUser/style.scss
  40. 20
      src/scss/containers/profile.scss
  41. 22
      src/services/domain/chat-messages.service.ts
  42. 12
      src/shared/components/fields/contact-field.component.tsx
  43. 23
      src/shared/components/fields/functional-email-field.component.tsx
  44. 1
      src/shared/components/fields/input-mask-field.component.tsx
  45. 40
      src/shared/components/fields/styles.scss
  46. 9
      src/shared/components/form/CheckBox.tsx
  47. 15
      src/shared/events/index.ts
  48. 1
      src/shared/helpers/index.ts
  49. 17
      src/shared/helpers/title-by-count.helper.ts
  50. 1
      src/shared/interfaces/messages.interfaces.ts
  51. 9
      src/widgets/send-notifications/hooks/use-send-notifications.hook.ts
  52. 28
      src/widgets/send-notifications/send-notifications.widget.tsx

23459
package-lock.json generated

File diff suppressed because it is too large Load Diff

3
package.json

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

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

@ -16,4 +16,5 @@ export interface ISendNotificationsPayload { @@ -16,4 +16,5 @@ export interface ISendNotificationsPayload {
title: string;
content: string;
haveToSendEmail?: boolean;
sendToAll?: boolean;
}

3
src/assets/img/3-dots.svg

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
<svg width="4" height="14" viewBox="0 0 4 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 5C1.60444 5 1.21776 5.1173 0.888861 5.33706C0.559963 5.55683 0.303617 5.86918 0.152242 6.23463C0.000866562 6.60009 -0.0387401 7.00222 0.0384303 7.39018C0.115601 7.77814 0.306082 8.13451 0.585787 8.41422C0.865493 8.69392 1.22186 8.8844 1.60982 8.96157C1.99778 9.03874 2.39992 8.99914 2.76537 8.84776C3.13082 8.69639 3.44318 8.44004 3.66294 8.11114C3.8827 7.78224 4 7.39556 4 7C4 6.46957 3.78929 5.96086 3.41421 5.58579C3.03914 5.21072 2.53043 5 2 5ZM2 8C1.80222 8 1.60888 7.94135 1.44443 7.83147C1.27998 7.72159 1.15181 7.56541 1.07612 7.38269C1.00043 7.19996 0.980631 6.99889 1.01922 6.80491C1.0578 6.61093 1.15304 6.43275 1.29289 6.2929C1.43275 6.15304 1.61093 6.0578 1.80491 6.01922C1.99889 5.98063 2.19996 6.00043 2.38269 6.07612C2.56541 6.15181 2.72159 6.27998 2.83147 6.44443C2.94135 6.60888 3 6.80222 3 7C3 7.26522 2.89464 7.51957 2.70711 7.70711C2.51957 7.89464 2.26522 8 2 8ZM2 4C2.39556 4 2.78224 3.8827 3.11114 3.66294C3.44004 3.44318 3.69639 3.13082 3.84776 2.76537C3.99914 2.39992 4.03874 1.99778 3.96157 1.60982C3.8844 1.22186 3.69392 0.865492 3.41421 0.585787C3.13451 0.306082 2.77814 0.115601 2.39018 0.0384303C2.00222 -0.0387401 1.60009 0.000866562 1.23463 0.152242C0.869182 0.303617 0.556825 0.559962 0.337062 0.88886C0.117299 1.21776 1.07779e-06 1.60444 1.07779e-06 2C1.07779e-06 2.53043 0.210715 3.03914 0.585787 3.41422C0.96086 3.78929 1.46957 4 2 4ZM2 1C2.19778 1 2.39112 1.05865 2.55557 1.16853C2.72002 1.27841 2.84819 1.43459 2.92388 1.61732C2.99957 1.80004 3.01937 2.00111 2.98079 2.19509C2.9422 2.38907 2.84696 2.56726 2.70711 2.70711C2.56726 2.84696 2.38907 2.9422 2.19509 2.98079C2.00111 3.01937 1.80004 2.99957 1.61732 2.92388C1.43459 2.84819 1.27841 2.72002 1.16853 2.55557C1.05865 2.39112 1 2.19778 1 2C1 1.73478 1.10536 1.48043 1.29289 1.29289C1.48043 1.10536 1.73478 1 2 1ZM2 10C1.60444 10 1.21776 10.1173 0.888861 10.3371C0.559963 10.5568 0.303617 10.8692 0.152242 11.2346C0.000866562 11.6001 -0.0387401 12.0022 0.0384303 12.3902C0.115601 12.7781 0.306082 13.1345 0.585787 13.4142C0.865493 13.6939 1.22186 13.8844 1.60982 13.9616C1.99778 14.0387 2.39992 13.9991 2.76537 13.8478C3.13082 13.6964 3.44318 13.44 3.66294 13.1111C3.8827 12.7822 4 12.3956 4 12C4 11.4696 3.78929 10.9609 3.41421 10.5858C3.03914 10.2107 2.53043 10 2 10ZM2 13C1.80222 13 1.60888 12.9414 1.44443 12.8315C1.27998 12.7216 1.15181 12.5654 1.07612 12.3827C1.00043 12.2 0.980631 11.9989 1.01922 11.8049C1.0578 11.6109 1.15304 11.4327 1.29289 11.2929C1.43275 11.153 1.61093 11.0578 1.80491 11.0192C1.99889 10.9806 2.19996 11.0004 2.38269 11.0761C2.56541 11.1518 2.72159 11.28 2.83147 11.4444C2.94135 11.6089 3 11.8022 3 12C3 12.2652 2.89464 12.5196 2.70711 12.7071C2.51957 12.8946 2.26522 13 2 13Z" fill="#9E2743"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

2
src/components/Fields/DateField.tsx

@ -56,8 +56,8 @@ export const DateField: FC<IProps> = ({ @@ -56,8 +56,8 @@ export const DateField: FC<IProps> = ({
return (
<>
<p className="profile__data-tooltip">{label}</p>
<div className="profile__data_card" style={style}>
<p className="profile__data-tooltip">{label}</p>
<DatePicker
allowClear={input ? input?.allowClear : allowClear}
value={

10
src/containers/Chats/atoms/audio-player.atom.tsx

@ -7,6 +7,8 @@ import pauseIcon from "@/assets/img/pause-icon.svg"; @@ -7,6 +7,8 @@ import pauseIcon from "@/assets/img/pause-icon.svg";
import { AudioBar } from "./audio-bar.atom";
import "./style.scss";
import _ from "lodash";
import { useChatViewModeState } from "../hooks";
import { ChatViewModeEnum } from "../enums";
export const PlayBar = () => {
const { position, duration } = useAudioPosition({ highRefreshRate: true });
@ -36,10 +38,16 @@ export const AudioPlayer: FC<IAudioPlayerProps> = ({ @@ -36,10 +38,16 @@ export const AudioPlayer: FC<IAudioPlayerProps> = ({
activeAudioId,
onPressPlay,
}) => {
const viewMode = useChatViewModeState((s) => s.mode);
useEffect(() => {
if (activeAudioId !== fileId && playing) player.pause();
}, [activeAudioId]);
useEffect(() => {
if (viewMode === ChatViewModeEnum.SELECT && playing) player.pause();
}, [viewMode]);
const { togglePlayPause, ready, loading, playing, player } = useAudioPlayer({
src: fileUrl,
format: "mp4",
@ -52,6 +60,8 @@ export const AudioPlayer: FC<IAudioPlayerProps> = ({ @@ -52,6 +60,8 @@ export const AudioPlayer: FC<IAudioPlayerProps> = ({
if (loading) return <div>Loading audio</div>;
const actionHandler = async () => {
if (viewMode === ChatViewModeEnum.SELECT) return;
if (onPressPlay) onPressPlay();
await togglePlayPause();
};

1
src/containers/Chats/atoms/index.ts

@ -22,3 +22,4 @@ export * from "./set-user-admin-button.atom"; @@ -22,3 +22,4 @@ export * from "./set-user-admin-button.atom";
export * from "./forward-message-button.atom";
export * from "./pinned-limit-chat-modal.atom";
export * from "./pinned-message-button.atom";
export * from "./selected-messages-info.atom";

83
src/containers/Chats/atoms/selected-messages-info.atom.tsx

@ -0,0 +1,83 @@ @@ -0,0 +1,83 @@
import React, { useMemo, useState } from "react";
import { ChatViewModeEnum } from "../enums";
import { useChatViewModeState } from "../hooks";
import DotsIcon from "@/assets/img/3-dots.svg";
import { Dropdown, Menu } from "antd";
import { IconComponent, getTitleByCount, useEventsListener } from "@/shared";
import { useChatSelectedMessagesState } from "../hooks";
import _ from "lodash";
import { ItemType } from "antd/lib/menu/hooks/useItems";
interface IProps {
onPressActionsBtn: () => void;
}
export const SelectedMessagesInfo = ({ onPressActionsBtn }: IProps) => {
const mode = useChatViewModeState((s) => s.mode);
const { setMode } = useChatViewModeState();
const selectedMessages = useChatSelectedMessagesState((s) => s.messages);
const { unselectAll: unselectAllMessages } = useChatSelectedMessagesState();
const [menuItems, setMenuItems] = useState([]);
const menu = useMemo(() => {
const preparedItems = menuItems.map((item) => ({
..._.pick(item, ["key", "label", "onClick"]),
icon: item.iconNode ? item.iconNode : null,
}));
return (
<Menu
className="context-menu-chat-item"
items={preparedItems as ItemType[]}
/>
);
}, [menuItems]);
useEventsListener(
"openSelectedMessagesMenuOptions",
(payload) => {
setMenuItems(payload.items);
},
[setMenuItems]
);
const infoText = useMemo(() => {
if (selectedMessages.length >= 1)
return `Вибрано ${getTitleByCount(selectedMessages.length, [
"повідомлення",
"повідомлення",
"повідомлень",
])}`;
return "Виберіть повідомлення";
}, [selectedMessages]);
if (mode === ChatViewModeEnum.DEFAULT) return <></>;
const cancelSelectMode = () => {
unselectAllMessages();
setMode(ChatViewModeEnum.DEFAULT);
};
return (
<div className="chat-selected-messages-info">
<div className="selected-messages-cancel-btn" onClick={cancelSelectMode}>
Скасувати
</div>
<div className="selected-messages-count-text">{infoText}</div>
{selectedMessages?.length > 0 && (
<Dropdown
onVisibleChange={onPressActionsBtn}
overlay={menu}
trigger={["click"]}
>
<div className="selected-messages-action-btn">
Дії
<IconComponent name={DotsIcon} />
</div>
</Dropdown>
)}
</div>
);
};

39
src/containers/Chats/atoms/style.scss

@ -297,3 +297,42 @@ $bg-color-lighter: rgba(158, 39, 67, 0.1); @@ -297,3 +297,42 @@ $bg-color-lighter: rgba(158, 39, 67, 0.1);
.error-tooltip {
color: red;
}
.chat-selected-messages-info {
display: flex;
flex-direction: row;
align-items: center;
gap: 34px;
font-size: 14px;
line-height: 16px;
}
.selected-messages-cancel-btn {
color: #9e2743;
&:hover {
cursor: pointer;
}
}
.selected-messages-count-text {
font-weight: 500;
}
.selected-messages-action-btn {
display: flex;
justify-content: center;
align-items: center;
width: 36px;
border-radius: 50%;
color: #9e2743;
img {
height: 15px;
margin-bottom: 2px;
}
&:hover {
cursor: pointer;
}
}

21
src/containers/Chats/chats.screen.tsx

@ -26,6 +26,9 @@ import { @@ -26,6 +26,9 @@ import {
useChatDetails,
useChatList,
useChatMessages,
useChatSelectedMessages,
useChatSelectedMessagesState,
useChatViewModeState,
useCreateTextMessage,
usePinedMessages,
useSendFiles,
@ -39,18 +42,34 @@ import _ from "lodash"; @@ -39,18 +42,34 @@ import _ from "lodash";
import { ChatConfirmDeleteModal } from "./modals/chat-confirm-delete-modal";
import { SelectStickersModalSmart } from "@/smart-components";
import { ChatSendImgModal } from "./components/chat-send-img-modal.component";
import { ChatViewModeEnum } from "./enums";
import { message } from "antd";
const ChatsPage: FC = () => {
const accountId = useSelector(getProfile);
const currentBgImgId = useSelector(selectCurrentChatBgId);
const selectedChatId = useSelector(selectSelectedChatId);
const [messageApi, contextHolder] = message.useMessage();
const [isCreateChatModal, setCreateChatModal] = useState<boolean>(false);
const [isMenuOpen, setMenuOpen] = useState<boolean>(false);
const [isOpenUserInfoModal, setUserInfoModal] = useState<boolean>(false);
const [selectedUserId, setSelectedUserId] = useState<number>();
const [chatBg, setBg] = useState<any>();
const { setMode } = useChatViewModeState();
const { unselectAll: unselectAllMessages } = useChatSelectedMessagesState();
const { openSelectedMessagesMenu } = useChatSelectedMessages({
infoMessageApi: messageApi,
});
useEffect(() => {
setMode(ChatViewModeEnum.DEFAULT);
unselectAllMessages();
}, [selectedChatId]);
const preparedBg = async () => {
if (currentBgImgId === ChatBgKeys.DEFAULT) return setBg(null);
@ -206,6 +225,7 @@ const ChatsPage: FC = () => { @@ -206,6 +225,7 @@ const ChatsPage: FC = () => {
return (
<Container>
{contextHolder}
<div className="chats">
<Row>
<CreateChatModal
@ -255,6 +275,7 @@ const ChatsPage: FC = () => { @@ -255,6 +275,7 @@ const ChatsPage: FC = () => {
setUnread={() =>
onSetUnread(selectedChatId, headerChatInfo)
}
onPressActionsBtn={() => openSelectedMessagesMenu(role)}
/>
<div

4
src/containers/Chats/components/forward-message-modal.component.tsx

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
import React, { useState } from "react";
import { IMessage, useEventsListener } from "@/shared";
import ModalComponent from "@/components/Modal";
import { ChatsSelectListSmart } from "../smart-components";
import { IChatMessage } from "@/containers/Chats/plugins/interfaces";
export const ForwardMessageModal = () => {
const [isOpen, setIsOpen] = useState(false);
const [item, setItem] = useState<IMessage>(null);
const [item, setItem] = useState<IMessage | IChatMessage[]>(null);
useEventsListener(
"openForwardMessageModal",

6
src/containers/Chats/configs/chat-message-menu-options.config.ts

@ -65,6 +65,11 @@ export const getChatMessageMenuOptions = ({ @@ -65,6 +65,11 @@ export const getChatMessageMenuOptions = ({
label: "Видалити для всіх",
onClick: () => onClick(ChatMessageActionEnum.DELETE_FOR_ALL),
},
select: {
key: "select",
label: "Вибрати",
onClick: () => onClick(ChatMessageActionEnum.SELECT),
},
};
const optionsKeys = ["forward", "reply"];
@ -76,6 +81,7 @@ export const getChatMessageMenuOptions = ({ @@ -76,6 +81,7 @@ export const getChatMessageMenuOptions = ({
optionsKeys.push("delete");
if (canDeleteForAll) optionsKeys.push("deleteForAll");
if (canEdit) optionsKeys.push("edit");
optionsKeys.push("select");
const options = optionsKeys.map((key) => menuOptions[key]);

1
src/containers/Chats/configs/index.ts

@ -3,3 +3,4 @@ export * from "./chat-info-mock.config"; @@ -3,3 +3,4 @@ export * from "./chat-info-mock.config";
export * from "./chat-users-mock.config";
export * from "./attachments-menu.config";
export * from "./chat-message-menu-options.config";
export * from "./selected-messages-menu-options.config";

45
src/containers/Chats/configs/selected-messages-menu-options.config.ts

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
import { ChatMessageActionEnum } from "../enums";
interface IProps {
onClick: (actionType: ChatMessageActionEnum) => void;
canCopy?: boolean;
canDeleteForAll?: boolean;
}
export const getSelectedMessagesMenuOptions = ({
onClick,
canCopy,
canDeleteForAll,
}: IProps) => {
const menuOptions = {
forward: {
key: "forward",
label: "Переслати",
onClick: () => onClick(ChatMessageActionEnum.FORWARD),
},
copy: {
key: "copy",
label: "Копіювати",
onClick: () => onClick(ChatMessageActionEnum.COPY),
},
delete: {
key: "delete",
label: "Видалити",
onClick: () => onClick(ChatMessageActionEnum.DELETE),
},
deleteForAll: {
key: "deleteForAll",
label: "Видалити для всіх",
onClick: () => onClick(ChatMessageActionEnum.DELETE_FOR_ALL),
},
};
const optionsKeys = ["forward"];
if (canCopy) optionsKeys.push("copy");
optionsKeys.push("delete");
if (canDeleteForAll) optionsKeys.push("deleteForAll");
const options = optionsKeys.map((key) => menuOptions[key]);
return options;
};

6
src/containers/Chats/consts/index.ts

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
import { MessageType } from "@/shared";
export const COPY_ENABLED_MESSAGES_TYPES = [
MessageType.Text,
MessageType.Image,
];

1
src/containers/Chats/enums/chat-message-action.enum.ts

@ -8,4 +8,5 @@ export enum ChatMessageActionEnum { @@ -8,4 +8,5 @@ export enum ChatMessageActionEnum {
DELETE_FOR_ALL,
DOWNLOAD,
EDIT,
SELECT,
}

4
src/containers/Chats/enums/chat-view-mode.enum.ts

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
export enum ChatViewModeEnum {
DEFAULT,
SELECT,
}

1
src/containers/Chats/enums/index.ts

@ -1 +1,2 @@ @@ -1 +1,2 @@
export * from "./chat-message-action.enum";
export * from "./chat-view-mode.enum";

38
src/containers/Chats/helpers/get-copied-messages-content.helper.ts

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
import { MessageType } from "@/shared";
import _ from "lodash";
import moment from "moment";
import { IChatMessage } from "../plugins/interfaces";
export const getCopiedContent = (items: Partial<IChatMessage>[]) => {
if (items.length === 1 && items[0].type === MessageType.Image) {
return items[0].content.fileUrl;
} else if (_.every(items, (it) => it.type === MessageType.Text)) {
return createCopiedMessagesContent(items);
}
};
function createCopiedMessagesContent(items: Partial<IChatMessage>[]) {
const text = [];
const sortedItems = _.orderBy(items, ["createdAt"], ["asc"]);
for (const item of sortedItems) {
const formattedContent = getFormattedMessageContent(item);
text.push(formattedContent);
}
return text.join("\n");
}
function getFormattedMessageContent(item: Partial<IChatMessage>) {
const dateAndTime = getDateAndTime(item);
const authorName = _.defaultTo(item.author?.name, "");
const text = _.defaultTo(item.content?.message, "");
return `[${dateAndTime}] ${authorName}: ${text}`;
}
function getDateAndTime(item: Partial<IChatMessage>) {
if (!item.createdAt) return "";
return moment(item.createdAt).format("DD.MM.YY, HH:mm");
}

1
src/containers/Chats/helpers/index.ts

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
export * from "./get-chat-info.helper";
export * from "./get-header-chat-info.helper";
export * from "./chat-messages.helper";
export * from "./get-copied-messages-content.helper";

2
src/containers/Chats/hooks/index.ts

@ -11,3 +11,5 @@ export * from "./use-selected-chats.hook"; @@ -11,3 +11,5 @@ export * from "./use-selected-chats.hook";
export * from "./use-send-files.hook";
export * from "./use-pined-messages.hook";
export * from "./use-chats-settings.hook";
export * from "./use-chat-view-mode-state.hook";
export * from "./use-selected-messages.hook";

70
src/containers/Chats/hooks/use-chat-messages.hook.ts

@ -2,13 +2,13 @@ import { @@ -2,13 +2,13 @@ import {
ChatMemberRole,
ChatMessageEventType,
IChatMessage,
MessageType
MessageType,
} from "@/shared";
import {
useEventsListener,
useIdsList,
useSocket,
useSocketListener
useSocketListener,
} from "@/shared/hooks/";
import _ from "lodash";
import { useCallback, useEffect, useState } from "react";
@ -17,9 +17,10 @@ import { IFetchChatMessages } from "@/api/chats-messages/request.interfaces"; @@ -17,9 +17,10 @@ import { IFetchChatMessages } from "@/api/chats-messages/request.interfaces";
import { useSelector } from "react-redux";
import { getProfile } from "@/store/account";
import { appEvents } from "@/shared/events";
import { ChatMessageActionEnum } from "../enums";
import { ChatMessageActionEnum, ChatViewModeEnum } from "../enums";
import { getChatMessageMenuOptions } from "../configs";
import { chatMessagesApi } from "@/api";
import { useChatViewModeState } from "./use-chat-view-mode-state.hook";
export const useChatMessages = (
chatId: number,
@ -32,6 +33,8 @@ export const useChatMessages = ( @@ -32,6 +33,8 @@ export const useChatMessages = (
const [replyTo, setReplyTo] = useState<IChatMessage>(null);
const [scrollToId, setScrollToId] = useState(null);
const { setMode: setChatViewMode } = useChatViewModeState();
const {
items: messages,
loadNew,
@ -44,14 +47,14 @@ export const useChatMessages = ( @@ -44,14 +47,14 @@ export const useChatMessages = (
resetList,
_setItems,
loadParams,
setLoadParams
setLoadParams,
} = useIdsList<IChatMessage, IFetchChatMessages>({
limit: 20,
req: async params => await chatMessagesService.fetchMessages(params),
req: async (params) => await chatMessagesService.fetchMessages(params),
needInit: false,
clearWhenReload: false,
lastMessageId,
firstMessageId
firstMessageId,
});
// const afterAction = () => {
@ -76,7 +79,7 @@ export const useChatMessages = ( @@ -76,7 +79,7 @@ export const useChatMessages = (
appEvents.emit("openConfirmDeleteMessageModal", {
message,
deleteForAll,
isShow: true
isShow: true,
}),
300
);
@ -93,11 +96,11 @@ export const useChatMessages = ( @@ -93,11 +96,11 @@ export const useChatMessages = (
const isAuthor = message.userId === account.id;
if (_.isEmpty(message.events) && isAuthor) return true;
const viewed = _.some(message.events, event => {
const viewed = _.some(message.events, (event) => {
const keys = Object.keys(event);
return _.some(
keys,
key => Number(key) !== account.id && event[key] === "view"
(key) => Number(key) !== account.id && event[key] === "view"
);
});
@ -126,6 +129,8 @@ export const useChatMessages = ( @@ -126,6 +129,8 @@ export const useChatMessages = (
appEvents.emit("onPressEditmessage", { message });
};
const onSelect = () => setChatViewMode(ChatViewModeEnum.SELECT);
const actions = {
[ChatMessageActionEnum.FORWARD]: onForward,
[ChatMessageActionEnum.DELETE]: onDelete,
@ -134,7 +139,8 @@ export const useChatMessages = ( @@ -134,7 +139,8 @@ export const useChatMessages = (
[ChatMessageActionEnum.COPY]: onCopy,
[ChatMessageActionEnum.PIN]: onPin,
[ChatMessageActionEnum.UNPIN]: onUnpin,
[ChatMessageActionEnum.EDIT]: onEdit
[ChatMessageActionEnum.EDIT]: onEdit,
[ChatMessageActionEnum.SELECT]: onSelect,
};
const onMessageActions = (message: IChatMessage, role: ChatMemberRole) => {
@ -153,7 +159,7 @@ export const useChatMessages = ( @@ -153,7 +159,7 @@ export const useChatMessages = (
canCopy,
onClick: (actionType: ChatMessageActionEnum) =>
actions[actionType](message),
canEdit
canEdit,
});
appEvents.emit("openMessageMenuOptions", { items: options });
@ -179,24 +185,24 @@ export const useChatMessages = ( @@ -179,24 +185,24 @@ export const useChatMessages = (
}, [chatId]);
const messageContainsItem = (itemId: number) =>
_.find(messages, message => message.id === itemId);
_.find(messages, (message) => message.id === itemId);
const updateEvents = (
messages: IChatMessage[],
userId: number,
event: ChatMessageEventType
) => {
const updatedItems = messages.map(item => {
const updatedItems = messages.map((item) => {
const userEvent = _.find(
item.events,
it =>
(it) =>
_.includes(Object.keys(it), userId.toString()) && it[userId] === event
);
if (userEvent) return item;
return {
...item,
events: [...item.events, { userId: event }]
events: [...item.events, { userId: event }],
};
});
@ -213,7 +219,7 @@ export const useChatMessages = ( @@ -213,7 +219,7 @@ export const useChatMessages = (
socket.emit("chat/read-message", {
userId: account.id,
messagesIds: [message.id],
chatId
chatId,
});
if (message.userId === account.id) setScrollToId(message.id);
@ -226,18 +232,18 @@ export const useChatMessages = ( @@ -226,18 +232,18 @@ export const useChatMessages = (
{
const messagesToAdd = _.filter(
message,
it => !messageContainsItem(it.id)
(it) => !messageContainsItem(it.id)
);
if (!_.isEmpty(messagesToAdd)) {
_setItems([...messagesToAdd, ...messages]);
const messagesIds = messagesToAdd.map(it => it.id);
const messagesIds = messagesToAdd.map((it) => it.id);
socket.emit("chat/read-message", {
userId: account.id,
messagesIds,
chatId
chatId,
});
}
}
@ -263,15 +269,15 @@ export const useChatMessages = ( @@ -263,15 +269,15 @@ export const useChatMessages = (
const onPinedMessage = (data: { chatId: number; messageId: number }) => {
if (
data?.chatId !== chatId ||
!_.find(messages, message => message.id === data.messageId)
!_.find(messages, (message) => message.id === data.messageId)
)
return;
const newItems = messages.map(item => {
const newItems = messages.map((item) => {
if (item.id === data.messageId)
return {
...item,
isPined: !item.isPined
isPined: !item.isPined,
};
return item;
});
@ -282,7 +288,7 @@ export const useChatMessages = ( @@ -282,7 +288,7 @@ export const useChatMessages = (
const onMessageDeleted = (data: { messageId: number; chatId: number }) => {
if (data?.chatId !== chatId) return;
const updatedMessages = messages.map(item => {
const updatedMessages = messages.map((item) => {
if (
item.content?.replyToMessage &&
item.content?.replyToMessage?.id === data.messageId
@ -293,16 +299,16 @@ export const useChatMessages = ( @@ -293,16 +299,16 @@ export const useChatMessages = (
...item.content,
replyToMessage: {
...item.content.replyToMessage,
isDeleted: true
}
}
isDeleted: true,
},
},
};
return item;
});
const filteredItems = _.filter(
updatedMessages,
item => item.id !== data?.messageId
(item) => item.id !== data?.messageId
);
_setItems(filteredItems);
@ -315,7 +321,7 @@ export const useChatMessages = ( @@ -315,7 +321,7 @@ export const useChatMessages = (
const onUpdateMessage = ({ message }) => {
console.log("on update message");
const newItems = messages.map(it => {
const newItems = messages.map((it) => {
if (it.id === message.id) {
return message;
}
@ -357,7 +363,7 @@ export const useChatMessages = ( @@ -357,7 +363,7 @@ export const useChatMessages = (
useSocketListener(
"chat/new-message",
data => {
(data) => {
onNewMessage(data);
},
[chatId, messages]
@ -365,7 +371,7 @@ export const useChatMessages = ( @@ -365,7 +371,7 @@ export const useChatMessages = (
useSocketListener("chat/delete-message", onMessageDeleted, [
chatId,
messages
messages,
]);
useSocketListener("chat/pined-message", onPinedMessage, [chatId, messages]);
@ -384,7 +390,7 @@ export const useChatMessages = ( @@ -384,7 +390,7 @@ export const useChatMessages = (
isLoadingNew,
isLoadingOld,
lastMessageLoaded,
firstMessageLoaded
firstMessageLoaded,
},
onMessageActions,
@ -394,6 +400,6 @@ export const useChatMessages = ( @@ -394,6 +400,6 @@ export const useChatMessages = (
onForward,
onPressMessagePreview,
replyTo,
setReplyToMessage: setReplyTo
setReplyToMessage: setReplyTo,
};
};

14
src/containers/Chats/hooks/use-chat-view-mode-state.hook.ts

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
import { create } from 'zustand'
import { ChatViewModeEnum } from '../enums'
interface IChatViewModeState {
mode: ChatViewModeEnum
setMode: (mode: ChatViewModeEnum) => void
}
export const useChatViewModeState = create<IChatViewModeState>()(set => ({
mode: ChatViewModeEnum.DEFAULT,
setMode: (mode: ChatViewModeEnum) => {
set({ mode })
},
}))

165
src/containers/Chats/hooks/use-selected-messages.hook.tsx

@ -0,0 +1,165 @@ @@ -0,0 +1,165 @@
import _ from "lodash";
import { IChatMessage } from "../plugins/interfaces";
import { create } from "zustand";
import { ChatMemberRole, MessageType } from "@/shared";
import { ChatMessageActionEnum, ChatViewModeEnum } from "../enums";
import { useChatViewModeState } from "./use-chat-view-mode-state.hook";
import { appEvents } from "@/shared/events";
import { getCopiedContent } from "../helpers";
import { COPY_ENABLED_MESSAGES_TYPES } from "../consts";
import { getSelectedMessagesMenuOptions } from "../configs";
import React from "react";
import { MessageInstance } from "antd/lib/message";
interface IChatSelectedMessagesState {
messages: Partial<IChatMessage>[];
selectMessage: (message: Partial<IChatMessage>) => void;
unselectAll: () => void;
}
export const useChatSelectedMessagesState = create<
IChatSelectedMessagesState
>()((set) => ({
messages: [],
selectMessage(message: Partial<IChatMessage>) {
set((state) => {
if (_.find(state.messages, (it) => it.id === message.id))
return {
messages: state.messages.filter((it) => it.id !== message.id),
};
else
return {
messages: [
...state.messages,
_.pick(message, [
"id",
"type",
"content",
"authorId",
"chatId",
"read",
"isMy",
"author",
"isPined",
"createdAt",
]),
],
};
});
},
unselectAll() {
set({ messages: [] });
},
}));
interface IProps {
infoMessageApi: MessageInstance;
}
export const useChatSelectedMessages = ({ infoMessageApi }: IProps) => {
const messages = useChatSelectedMessagesState((s) => s.messages);
const { unselectAll } = useChatSelectedMessagesState();
const { setMode } = useChatViewModeState();
const actions = {
[ChatMessageActionEnum.FORWARD]: forwardMany,
[ChatMessageActionEnum.COPY]: copyMany,
[ChatMessageActionEnum.DELETE]: deleteMany,
[ChatMessageActionEnum.DELETE_FOR_ALL]: deleteForAllMany,
};
const afterAction = () => {
unselectAll();
setMode(ChatViewModeEnum.DEFAULT);
};
function forwardMany() {
appEvents.emit("openForwardMessageModal", {
message: messages as IChatMessage[],
isShow: true,
});
afterAction();
}
function copyMany() {
const copiedContent = getCopiedContent(messages);
navigator.clipboard.writeText(copiedContent);
infoMessageApi.open({
type: "success",
icon: <></>,
className: "custom-info-message",
content: "Повідомлення скопійовано",
style: {
marginTop: 60,
},
});
afterAction();
}
function deleteMany() {
deleteMessages();
}
function deleteForAllMany() {
deleteMessages(true);
}
function deleteMessages(deleteForAll?: boolean) {
setTimeout(
() =>
appEvents.emit("openConfirmDeleteMessageModal", {
message: messages,
deleteForAll,
isShow: true,
onSuccess: afterAction,
}),
300
);
}
const openSelectedMessagesMenu = (userRoleInChat: ChatMemberRole) => {
const canCopy = checkCanCopy();
const canDeleteForAll = checkCanDeleteForAll();
function checkCanCopy() {
if (
messages.length === 1 &&
COPY_ENABLED_MESSAGES_TYPES.includes(messages[0].type)
)
return true;
if (
messages.length > 1 &&
_.every(messages, (it) => it.type === MessageType.Text)
)
return true;
return false;
}
function checkCanDeleteForAll() {
if (userRoleInChat === ChatMemberRole.Admin) return true;
return _.every(messages, (it) => {
if (!it.read && it.isMy) return true;
return false;
});
}
const options = getSelectedMessagesMenuOptions({
canDeleteForAll,
canCopy,
onClick: (actionType: ChatMessageActionEnum) =>
actions[actionType](messages),
});
appEvents.emit("openSelectedMessagesMenuOptions", { items: options });
};
return {
openSelectedMessagesMenu,
};
};

10
src/containers/Chats/modals/chat-confirm-delete-modal.tsx

@ -2,12 +2,17 @@ import { chatMessagesService } from "@/services/domain"; @@ -2,12 +2,17 @@ import { chatMessagesService } from "@/services/domain";
import { IChatMessage, useEventsListener } from "@/shared";
import React, { useState } from "react";
import Modal from "../../../components/Modal";
import { IChatMessage as IChatMessageItem } from "../plugins/interfaces";
// import "./styles.scss";
export const ChatConfirmDeleteModal = () => {
const [isOpen, setIsOpen] = useState(false);
const [item, setItem] = useState<IChatMessage>(null);
const [item, setItem] = useState<IChatMessage | Partial<IChatMessageItem>[]>(
null
);
const [isDeleteForAll, setDeleteForAll] = useState<boolean>(false);
const [actions, setActions] = useState(null);
const toogle = () => setIsOpen((prev) => !prev);
@ -17,6 +22,8 @@ export const ChatConfirmDeleteModal = () => { @@ -17,6 +22,8 @@ export const ChatConfirmDeleteModal = () => {
setItem(payload.message);
setIsOpen(payload.isShow);
setDeleteForAll(payload.deleteForAll);
if (payload.onSuccess) setActions({ onSuccess: payload.onSuccess });
},
[setItem, setIsOpen]
);
@ -31,6 +38,7 @@ export const ChatConfirmDeleteModal = () => { @@ -31,6 +38,7 @@ export const ChatConfirmDeleteModal = () => {
const onConfirm = async () => {
await chatMessagesService.deleteChatMessage(item, isDeleteForAll);
if (actions?.onSuccess) actions?.onSuccess();
toogle();
};

4
src/containers/Chats/modals/user-info.modal.tsx

@ -70,7 +70,7 @@ export const UserInfoModal: FC<UserInfoModalProps> = (props) => { @@ -70,7 +70,7 @@ export const UserInfoModal: FC<UserInfoModalProps> = (props) => {
toggle={props.toggle}
modalHeaderClass="user-info"
>
<form className="form container">
<form className="form container user-modal-container">
<div className="top-block">
<div className="avatar-wrap">
<p className="avatar-label">Аватар</p>
@ -92,6 +92,7 @@ export const UserInfoModal: FC<UserInfoModalProps> = (props) => { @@ -92,6 +92,7 @@ export const UserInfoModal: FC<UserInfoModalProps> = (props) => {
user?.info?.lastName
)}
onChange={_.noop}
enableCopy={true}
/>
<DateField
@ -159,6 +160,7 @@ export const UserInfoModal: FC<UserInfoModalProps> = (props) => { @@ -159,6 +160,7 @@ export const UserInfoModal: FC<UserInfoModalProps> = (props) => {
if (user?.status !== EUserStatus.Deleted)
window.open(`mailto:${user?.email}`);
}}
enableCopy
/>
<Button

22
src/containers/Chats/modals/user-info.styles.scss

@ -11,6 +11,28 @@ @@ -11,6 +11,28 @@
}
}
.user-modal-container {
.input-mask-field {
.input-container {
padding: 0px 0px;
}
}
.profile__data_card {
padding-right: 0px;
.profile__data-tooltip {
margin-bottom: 4px !important;
}
.contact-copy {
position: absolute;
cursor: pointer;
right: 10px;
}
}
}
.modal-header.user-info {
margin-bottom: 7px;
}

14
src/containers/Chats/plugins/chat-header.component.tsx

@ -1,7 +1,11 @@ @@ -1,7 +1,11 @@
import React, { FC, useState } from "react";
import { Tooltip } from "antd";
import { hasImageUrl, IChatDetails, IconComponent } from "@/shared";
import { ChatAvatarWithOnlineIndicator, ChatHeaderInfo } from "../atoms";
import {
ChatAvatarWithOnlineIndicator,
ChatHeaderInfo,
SelectedMessagesInfo,
} from "../atoms";
import "./style.scss";
import gearIcon from "@/assets/img/gear-icon.svg";
import chatUnreadIcon from "@/assets/img/chat-unread-icon.svg";
@ -15,9 +19,7 @@ interface ChatHeaderProps { @@ -15,9 +19,7 @@ interface ChatHeaderProps {
isUnread: boolean;
setPinned: () => void | Promise<void>;
setUnread: () => void | Promise<void>;
// unreadMessagesCount: number;
// sendDateTime: string;
// onPress: () => void;
onPressActionsBtn: () => void;
}
export const ChatHeader: FC<ChatHeaderProps> = ({
@ -29,6 +31,7 @@ export const ChatHeader: FC<ChatHeaderProps> = ({ @@ -29,6 +31,7 @@ export const ChatHeader: FC<ChatHeaderProps> = ({
isUnread,
setPinned,
setUnread,
onPressActionsBtn,
}) => {
const [isSettingsOpen, setSettingsOpen] = useState<boolean>(false);
@ -51,6 +54,9 @@ export const ChatHeader: FC<ChatHeaderProps> = ({ @@ -51,6 +54,9 @@ export const ChatHeader: FC<ChatHeaderProps> = ({
/>
<ChatHeaderInfo label={label} userCount={userCount} />
</div>
<SelectedMessagesInfo onPressActionsBtn={onPressActionsBtn} />
<div className="chat-header-settings">
{!isUnread && (
<Tooltip

8
src/containers/Chats/plugins/chat-item-file.component.tsx

@ -11,6 +11,8 @@ import { ChatItem } from "./chat-item.component"; @@ -11,6 +11,8 @@ import { ChatItem } from "./chat-item.component";
import { IChatMessage } from "./interfaces";
import { ShowDocModal } from "../components";
import { useChatViewModeState } from "../hooks";
import { ChatViewModeEnum } from "../enums";
interface ChatItemFileProps extends IChatMessage {
onMenuPress?: () => void;
@ -22,6 +24,8 @@ interface ChatItemFileProps extends IChatMessage { @@ -22,6 +24,8 @@ interface ChatItemFileProps extends IChatMessage {
}
export const ChatItemFile: FC<ChatItemFileProps> = (props) => {
const viewMode = useChatViewModeState((s) => s.mode);
const [isOpenDocModal, setOpenDocModal] = useState<boolean>(false);
const iconName = getIconNameByExtension(props?.content?.fileUrl);
@ -48,7 +52,9 @@ export const ChatItemFile: FC<ChatItemFileProps> = (props) => { @@ -48,7 +52,9 @@ export const ChatItemFile: FC<ChatItemFileProps> = (props) => {
<ChatItem {...props}>
<div
className="chat-item-file-container"
onClick={() => setOpenDocModal(true)}
onClick={() => {
if (viewMode === ChatViewModeEnum.DEFAULT) setOpenDocModal(true);
}}
>
<div className="file-icon-container">
<IconComponent className="file-item-icon" name={iconName} />

7
src/containers/Chats/plugins/chat-item-image.component.tsx

@ -3,6 +3,8 @@ import React, { FC, useEffect, useState } from "react"; @@ -3,6 +3,8 @@ import React, { FC, useEffect, useState } from "react";
import { IMessage } from "@/shared";
import { ChatItem } from "./chat-item.component";
import { ShowImageModal } from "../components";
import { useChatViewModeState } from "../hooks";
import { ChatViewModeEnum } from "../enums";
interface ChatItemImageProps extends IMessage {
onMenuPress?: () => void;
@ -14,6 +16,8 @@ interface ChatItemImageProps extends IMessage { @@ -14,6 +16,8 @@ interface ChatItemImageProps extends IMessage {
}
export const ChatItemImage: FC<ChatItemImageProps> = (props) => {
const viewMode = useChatViewModeState((s) => s.mode);
const [isOpenImageModal, setOpenImageModal] = useState<boolean>(false);
const [imgHeight, setHeight] = useState(null);
@ -65,7 +69,8 @@ export const ChatItemImage: FC<ChatItemImageProps> = (props) => { @@ -65,7 +69,8 @@ export const ChatItemImage: FC<ChatItemImageProps> = (props) => {
<div
className="chat-item-image-container"
onClick={() => {
setOpenImageModal(true);
if (viewMode === ChatViewModeEnum.DEFAULT)
setOpenImageModal(true);
}}
>
<img

60
src/containers/Chats/plugins/chat-item-video.component.tsx

@ -1,8 +1,13 @@ @@ -1,8 +1,13 @@
import React, { FC, useMemo, useRef, useState } from "react";
import React, { FC, useCallback, useMemo, useRef, useState } from "react";
import { Dropdown, Menu } from "antd";
import { ItemType } from "antd/lib/menu/hooks/useItems";
import _ from "lodash";
import { IconComponent, MessageType, useEventsListener } from "@/shared";
import {
CheckBoxForm,
IconComponent,
MessageType,
useEventsListener,
} from "@/shared";
import ReactPlayer from "react-player/lazy";
import { IChatMessage } from "./interfaces";
import check_1 from "@/assets/img/check_1.svg";
@ -11,6 +16,8 @@ import moment from "moment"; @@ -11,6 +16,8 @@ import moment from "moment";
import { Avatar, ForwardMessageButton } from "../atoms";
import { ForwardedMessageHeader } from "./forwarded-message-header.component";
import { RepliedMessageInfo } from "./replied-message-info.component";
import { useChatSelectedMessagesState, useChatViewModeState } from "../hooks";
import { ChatViewModeEnum } from "../enums";
interface ChatItemVideoProps extends IChatMessage {
onMenuPress?: () => void;
@ -23,9 +30,14 @@ interface ChatItemVideoProps extends IChatMessage { @@ -23,9 +30,14 @@ interface ChatItemVideoProps extends IChatMessage {
export const ChatItemVideo: FC<ChatItemVideoProps> = (props) => {
// const player = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const [items, setItems] = useState([]);
const viewMode = useChatViewModeState((s) => s.mode);
const selectedMessages = useChatSelectedMessagesState((s) => s.messages);
const { selectMessage } = useChatSelectedMessagesState();
useEventsListener(
"openMessageMenuOptions",
(payload) => {
@ -34,6 +46,10 @@ export const ChatItemVideo: FC<ChatItemVideoProps> = (props) => { @@ -34,6 +46,10 @@ export const ChatItemVideo: FC<ChatItemVideoProps> = (props) => {
[setItems]
);
useMemo(() => {
if (viewMode === ChatViewModeEnum.SELECT) setIsPlaying(false);
}, [viewMode]);
const repliedMessage =
props.content?.replyToMessage?.type === MessageType.Forwarded
? props.content?.replyToMessage?.content?.originalMessage
@ -52,13 +68,26 @@ export const ChatItemVideo: FC<ChatItemVideoProps> = (props) => { @@ -52,13 +68,26 @@ export const ChatItemVideo: FC<ChatItemVideoProps> = (props) => {
);
}, [items]);
const select = () => selectMessage(props);
const handleClickMessageItem = useCallback(() => {
if (viewMode === ChatViewModeEnum.SELECT) select();
}, [viewMode]);
return (
<div
className={
"chat-item-video-container " + (props.isMy ? "my-video-container" : "")
"chat-item-video-container " +
(props.isMy && viewMode === ChatViewModeEnum.DEFAULT
? "my-video-container"
: "") +
(props.isMy && viewMode === ChatViewModeEnum.SELECT
? "mySelectableContainer"
: "")
}
onClick={handleClickMessageItem}
>
{!props.isMy ? (
{!props.isMy && viewMode === ChatViewModeEnum.DEFAULT ? (
<div className="chat-avatar-container">
<Avatar
imageUrl={props.author.avatar}
@ -68,8 +97,24 @@ export const ChatItemVideo: FC<ChatItemVideoProps> = (props) => { @@ -68,8 +97,24 @@ export const ChatItemVideo: FC<ChatItemVideoProps> = (props) => {
/>
</div>
) : null}
{viewMode === ChatViewModeEnum.SELECT ? (
<CheckBoxForm
className="message-checkbox"
label=""
onChange={select}
checkBoxProps={{
checked: Boolean(
_.find(selectedMessages, (it) => it.id === props.id)
),
onClick: select,
}}
/>
) : null}
<Dropdown
onVisibleChange={props.onMenuPress}
disabled={viewMode === ChatViewModeEnum.SELECT}
overlay={menu}
trigger={[`contextMenu`]}
>
@ -107,8 +152,9 @@ export const ChatItemVideo: FC<ChatItemVideoProps> = (props) => { @@ -107,8 +152,9 @@ export const ChatItemVideo: FC<ChatItemVideoProps> = (props) => {
<ReactPlayer
url={props.content?.fileUrl}
className="react-player"
controls={true}
playing={false}
controls={viewMode === ChatViewModeEnum.DEFAULT}
playing={isPlaying}
onPlay={() => setIsPlaying(true)}
width="100%"
height="100%"
config={{ file: { attributes: { controlsList: "nodownload" } } }}
@ -127,7 +173,7 @@ export const ChatItemVideo: FC<ChatItemVideoProps> = (props) => { @@ -127,7 +173,7 @@ export const ChatItemVideo: FC<ChatItemVideoProps> = (props) => {
</div>
</div>
</Dropdown>
{!props.isMy && (
{!props.isMy && viewMode === ChatViewModeEnum.DEFAULT && (
<>
<ForwardMessageButton onPress={props.onForwardPressMessage} />
</>

57
src/containers/Chats/plugins/chat-item.component.tsx

@ -1,8 +1,9 @@ @@ -1,8 +1,9 @@
import React, { FC, useMemo, useState } from "react";
import React, { FC, useCallback, useMemo, useState } from "react";
import { Dropdown, Menu } from "antd";
import { ItemType } from "antd/lib/menu/hooks/useItems";
import _ from "lodash";
import {
CheckBoxForm,
IconComponent,
IMessage,
MessageType,
@ -15,6 +16,9 @@ import check_1 from "@/assets/img/check_1.svg"; @@ -15,6 +16,9 @@ import check_1 from "@/assets/img/check_1.svg";
import checks_1 from "@/assets/img/checks_1.svg";
import { ForwardedMessageHeader } from "./forwarded-message-header.component";
import { RepliedMessageInfo } from "./replied-message-info.component";
import { useChatViewModeState } from "../hooks";
import { ChatViewModeEnum } from "../enums";
import { useChatSelectedMessagesState } from "../hooks/use-selected-messages.hook";
interface ChatItemProps extends IMessage {
containerStyle?: string;
@ -37,6 +41,22 @@ export const ChatItem: FC<ChatItemProps> = (props) => { @@ -37,6 +41,22 @@ export const ChatItem: FC<ChatItemProps> = (props) => {
const [innerVisible, setInnerVisible] = useState(false);
const [linkValue, setLinkValue] = useState<string>("");
const viewMode = useChatViewModeState((s) => s.mode);
const selectedMessages = useChatSelectedMessagesState((s) => s.messages);
const { selectMessage } = useChatSelectedMessagesState();
const isSelectable = useMemo(() => {
if (viewMode === ChatViewModeEnum.DEFAULT) return false;
return (
props.type === MessageType.Text ||
props.type === MessageType.Image ||
props.type === MessageType.Video ||
props.type === MessageType.Audio ||
props.type === MessageType.File ||
props.type === MessageType.Sticker
);
}, [viewMode, props.type]);
const repliedMessage =
props.content?.replyToMessage?.type === MessageType.Forwarded
? props.content?.replyToMessage?.content?.originalMessage
@ -113,6 +133,7 @@ export const ChatItem: FC<ChatItemProps> = (props) => { @@ -113,6 +133,7 @@ export const ChatItem: FC<ChatItemProps> = (props) => {
};
const onContextMenu = (event) => {
if (isSelectable) return;
closeOtherMenu();
if (event.target.id === "link") {
const linkValue = (event.target as Element).getAttribute("href");
@ -131,14 +152,25 @@ export const ChatItem: FC<ChatItemProps> = (props) => { @@ -131,14 +152,25 @@ export const ChatItem: FC<ChatItemProps> = (props) => {
setInnerVisible(false);
});
const select = () => selectMessage(props);
const handleClickMessageItem = useCallback(() => {
setVisible(false);
if (isSelectable) select();
}, [isSelectable]);
return (
<div
className={"chat-item-container " + (props.isMy ? "myContainer" : "")}
className={
"chat-item-container " +
(props.isMy && !isSelectable ? "myContainer" : "") +
(props.isMy && isSelectable ? "mySelectableContainer" : "")
}
id="message"
onClick={() => setVisible(false)}
onClick={handleClickMessageItem}
onContextMenu={onContextMenu}
>
{!props.isMy ? (
{!props.isMy && !isSelectable ? (
<div className="chat-avatar-container">
<Avatar
onClick={props.onProfilePress}
@ -148,6 +180,21 @@ export const ChatItem: FC<ChatItemProps> = (props) => { @@ -148,6 +180,21 @@ export const ChatItem: FC<ChatItemProps> = (props) => {
/>
</div>
) : null}
{isSelectable ? (
<CheckBoxForm
className="message-checkbox"
label=""
onChange={select}
checkBoxProps={{
checked: Boolean(
_.find(selectedMessages, (it) => it.id === props.id)
),
onClick: select,
}}
/>
) : null}
<Dropdown
onVisibleChange={props.onMenuPress}
visible={_.isEmpty(items) ? false : visible}
@ -252,7 +299,7 @@ export const ChatItem: FC<ChatItemProps> = (props) => { @@ -252,7 +299,7 @@ export const ChatItem: FC<ChatItemProps> = (props) => {
</div>
</div>
</Dropdown>
{!props.isMy && (
{!props.isMy && !isSelectable && (
<>
<ForwardMessageButton onPress={props.onForwardPressMessage} />
</>

1
src/containers/Chats/plugins/interfaces.ts

@ -3,6 +3,7 @@ import { MessageType } from "@/shared/enums"; @@ -3,6 +3,7 @@ import { MessageType } from "@/shared/enums";
export interface IChatMessage {
id: number;
content: any;
chatId: number;
authorId?: number;
readByUsersId?: number[];
author?: {

62
src/containers/Chats/plugins/style.scss

@ -33,6 +33,14 @@ $bg-color-lighter: rgba(158, 39, 67, 0.1); @@ -33,6 +33,14 @@ $bg-color-lighter: rgba(158, 39, 67, 0.1);
justify-content: flex-end;
}
.chat-item-container.mySelectableContainer {
justify-content: space-between;
}
.chat-item-video-container.mySelectableContainer {
justify-content: space-between;
}
.chat-item-container:hover > div {
display: block;
}
@ -955,3 +963,57 @@ $bg-color-lighter: rgba(158, 39, 67, 0.1); @@ -955,3 +963,57 @@ $bg-color-lighter: rgba(158, 39, 67, 0.1);
}
}
}
.message-checkbox {
align-self: center;
margin-right: 19px;
&:hover {
.ant-checkbox-inner {
border: 1px solid #e2e8f0;
background-color: #f8f8f8;
}
}
& .ant-checkbox-input:focus + .ant-checkbox-inner {
border-color: #e2e8f0 !important;
}
.ant-checkbox-inner {
width: 20px;
height: 20px;
border: 1px solid #e2e8f0;
background-color: #f8f8f8;
:hover {
border: 1px solid #e2e8f0;
background-color: #f8f8f8;
}
::after {
border-radius: 50% !important;
}
}
.ant-checkbox-checked {
& .ant-checkbox-input:focus + .ant-checkbox-inner {
border-color: #9e2743 !important;
}
&::after {
border: none !important;
}
::after {
left: 4.7px;
top: 7.8px;
}
}
}
.custom-info-message {
.ant-message-notice-content {
border-radius: 4px;
filter: drop-shadow(rgb(224, 224, 224) 2px 2px 4px);
}
}

17
src/containers/Chats/smart-components/chats-select-list.smart-component.tsx

@ -8,9 +8,10 @@ import React, { FC } from "react"; @@ -8,9 +8,10 @@ import React, { FC } from "react";
import { useHistory } from "react-router-dom";
import { ChatsSelectListWithSearch } from "../components";
import { useSelectedChats } from "../hooks";
import { IChatMessage } from "../plugins/interfaces";
interface IProps {
item: IMessage;
item: IMessage | IChatMessage[];
}
export const ChatsSelectListSmart: FC<IProps> = ({ item }) => {
@ -30,11 +31,17 @@ export const ChatsSelectListSmart: FC<IProps> = ({ item }) => { @@ -30,11 +31,17 @@ export const ChatsSelectListSmart: FC<IProps> = ({ item }) => {
const chatsIds = selectedChats.map((item) => item.id);
if (_.isEmpty(chatsIds)) return;
const messages = _.isArray(item)
? (item as IChatMessage[])
: [item as IMessage];
try {
await chatMessagesApi.forwardMessageReq({
messageId: item.id,
chatsIds: chatsIds,
});
for await (const message of messages) {
await chatMessagesApi.forwardMessageReq({
messageId: message.id,
chatsIds: chatsIds,
});
}
appEvents.emit("closeForwardMessageModal", { isShow: false });

3
src/containers/Chats/transforms/chat-messages.transforms.ts

@ -176,11 +176,12 @@ export const transformMessage = ( @@ -176,11 +176,12 @@ export const transformMessage = (
return {
id: item.id,
text: item.content?.message,
chatId: item.chatId,
createdAt: item.createdAt,
authorId: item.userId,
author: {
id: author?.userId,
name: item.userId !== accountId && fullName,
name: fullName,
avatar:
item.userId !== accountId &&
hasImageUrl(author?.user?.avatarUrl, fullName),

21
src/containers/Profile/components/form-user/form-user.component.tsx

@ -103,7 +103,6 @@ export const FormUser: FC<IProps> = ({ @@ -103,7 +103,6 @@ export const FormUser: FC<IProps> = ({
validateFullName(text);
}}
value={value}
enableCopy={true}
/>
);
}}
@ -133,7 +132,6 @@ export const FormUser: FC<IProps> = ({ @@ -133,7 +132,6 @@ export const FormUser: FC<IProps> = ({
validateEmail(text);
}}
value={value}
enableCopy={true}
/>
);
}}
@ -186,7 +184,6 @@ export const FormUser: FC<IProps> = ({ @@ -186,7 +184,6 @@ export const FormUser: FC<IProps> = ({
value={value}
placeholder={"+38 (0xx) xxx xx xx"}
mask="+380 (999) 999 99 99"
enableCopy={true}
onChange={(text) => {
setError({
...errors,
@ -244,7 +241,6 @@ export const FormUser: FC<IProps> = ({ @@ -244,7 +241,6 @@ export const FormUser: FC<IProps> = ({
value={value}
placeholder={"+38 (0xx) xxx xx xx"}
mask="+380 (999) 999 99 99"
enableCopy={true}
onChange={(text) => {
setError({ ...errors, personalPhoneNumber: "" });
onChange(text.replace(/[^+\d]/g, ""));
@ -256,21 +252,6 @@ export const FormUser: FC<IProps> = ({ @@ -256,21 +252,6 @@ export const FormUser: FC<IProps> = ({
</div>
</div>
{/* {profile.id != authProfileId && (
<Controller
name="factories"
control={control}
render={({ field }) => (
<TreeSelectField
label={"Підприємство"}
placeholder={"Підприємство"}
multiple
tree={genTree(_.cloneDeep(factory))}
register={field}
/>
)}
/>
)} */}
<div
className="row row-profile-user"
// style={{ width: "100%", margin: 0, marginTop: 20 }}
@ -288,7 +269,6 @@ export const FormUser: FC<IProps> = ({ @@ -288,7 +269,6 @@ export const FormUser: FC<IProps> = ({
onChange={onChange}
value={value}
error={fieldState?.error?.message}
enableCopy={true}
/>
)}
/>
@ -316,7 +296,6 @@ export const FormUser: FC<IProps> = ({ @@ -316,7 +296,6 @@ export const FormUser: FC<IProps> = ({
validateLogin(text);
}}
value={value}
enableCopy={true}
/>
)}
/>

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

@ -43,7 +43,7 @@ const startTextByDeleteAction = { @@ -43,7 +43,7 @@ const startTextByDeleteAction = {
const endTextByDeleteAction = {
delete: "?",
hardDelete: " без можливості повернутися?",
hardDelete: " без можливості відновлення?",
};
export const TasksScreen: FC = () => {

6
src/containers/User/components/FormUser/style.scss

@ -87,6 +87,12 @@ @@ -87,6 +87,12 @@
width: 50%;
}
}
.input-mask-field {
.input-container {
padding: 0px 0px;
}
}
}
.contact-row-field {

20
src/scss/containers/profile.scss

@ -134,24 +134,32 @@ @@ -134,24 +134,32 @@
.profile__data_card {
width: 100%;
display: flex;
align-items: center;
background: #f5f5f5;
border-color: #d9d9d9;
border-radius: 10px;
padding-right: 10px;
// display: flex;
// align-items: center;
// background: #f5f5f5;
// border-color: #d9d9d9;
// border-radius: 10px;
// padding-right: 10px;
.contact-copy {
position: absolute;
cursor: pointer;
width: 20px;
height: 20px;
right: 10px;
bottom: 33px;
}
.ant-picker {
margin-top: 0px !important;
}
// .profile__data-tooltip {
// margin-bottom: 10px !important;
// }
}
.profile__data-tooltip {
font-size: 12px;
// line-height: 1.7;
color: $color-additional-text !important;
}

22
src/services/domain/chat-messages.service.ts

@ -15,6 +15,8 @@ import { IChatMessage, runActionByType } from "@/shared"; @@ -15,6 +15,8 @@ import { IChatMessage, runActionByType } from "@/shared";
import { appEvents } from "@/shared/events";
import { SetUnreadMessagesCount } from "@/store/chats";
import { simpleDispatch } from "@/store/store-helpers";
import { IChatMessage as IChatMessageItem } from "@/containers/Chats/plugins/interfaces";
import _ from "lodash";
const fetchMessages = async (params: {
params: IFetchChatMessages;
@ -66,6 +68,7 @@ const sendMediaMessage = async (data: ISendFileMessage[]) => { @@ -66,6 +68,7 @@ const sendMediaMessage = async (data: ISendFileMessage[]) => {
});
}
};
const sendFileMessage = async (data: ISendFilesMessages) => {
try {
await chatMessagesApi.sendFileMessageReq(data);
@ -74,20 +77,27 @@ const sendFileMessage = async (data: ISendFilesMessages) => { @@ -74,20 +77,27 @@ const sendFileMessage = async (data: ISendFilesMessages) => {
console.log("SEND FILE MESSAGE ERROR", err.response.data);
}
};
const deleteChatMessage = async (
message: IChatMessage,
message: IChatMessage | Partial<IChatMessageItem>[],
deleteForAll?: boolean
) => {
const params: IDeleteMessageParams = {};
if (deleteForAll) params.deleteForAll = deleteForAll;
const messages = _.isArray(message)
? (message as IChatMessageItem[])
: [message as IChatMessage];
try {
await chatMessagesApi.deleteMessageByIdReq(message.id, params);
for await (const item of messages) {
await chatMessagesApi.deleteMessageByIdReq(item.id, params);
appEvents.emit("onDeleteMessage", {
chatId: message.chatId,
messageId: message.id,
});
appEvents.emit("onDeleteMessage", {
chatId: item.chatId,
messageId: item.id,
});
}
} catch (err) {
console.log("ERROR ON DELETE MESSAGE", err);
}

12
src/shared/components/fields/contact-field.component.tsx

@ -2,7 +2,7 @@ import React, { FC, CSSProperties } from "react"; @@ -2,7 +2,7 @@ import React, { FC, CSSProperties } from "react";
import "./styles.scss";
import { AsYouType } from "libphonenumber-js";
import copySvg from "@/assets/img/copy-icon-1.svg";
import { message } from "antd";
import { notification } from "@/shared/tools";
interface IProps {
label: string;
value: string | number;
@ -31,7 +31,7 @@ export const ContactField: FC<IProps> = ({ @@ -31,7 +31,7 @@ export const ContactField: FC<IProps> = ({
const onCopy = (event) => {
event.stopPropagation();
navigator.clipboard.writeText(value.toString());
message.info("Данні скопійовані!");
notification.showSuccess("Успішно", "Данні скопійовані!");
};
return (
@ -39,11 +39,13 @@ export const ContactField: FC<IProps> = ({ @@ -39,11 +39,13 @@ export const ContactField: FC<IProps> = ({
<p className="contact-label">{label}</p>
<div className="value-container">
{!phone ? (
<span>{value || placeholder}</span>
<span title={String(value)}>{value || placeholder}</span>
) : (
<span>{formattedPhone || placeholder}</span>
<span title={String(formattedPhone)}>
{formattedPhone || placeholder}
</span>
)}
<div>
<div className="images">
<img src={svgIcon} />
<img
className="contact-copy"

23
src/shared/components/fields/functional-email-field.component.tsx

@ -1,4 +1,6 @@ @@ -1,4 +1,6 @@
import React, { FC, CSSProperties } from "react";
import copySvg from "@/assets/img/copy-icon-1.svg";
import { message } from "antd";
import "./styles.scss";
interface IProps {
@ -6,6 +8,7 @@ interface IProps { @@ -6,6 +8,7 @@ interface IProps {
value: string | number;
style?: CSSProperties;
onClick?: () => void;
enableCopy?: boolean;
}
export const FunctionalEmailField: FC<IProps> = ({
@ -13,12 +16,32 @@ export const FunctionalEmailField: FC<IProps> = ({ @@ -13,12 +16,32 @@ export const FunctionalEmailField: FC<IProps> = ({
value,
style,
onClick,
enableCopy,
}) => {
const onCopy = (value) => {
navigator.clipboard.writeText(value.toString());
message.info("Данні скопійовані!");
};
const handleIconClick = (event) => {
event.stopPropagation();
onCopy(value);
};
return (
<div className="functional-email-field" style={style}>
<p className="functional-email-label">{label}</p>
<div className="value-container" onClick={onClick}>
<span>{value}</span>
{enableCopy && (
<img
className="contact-copy"
src={copySvg}
alt="copy"
onClick={handleIconClick}
/>
)}
</div>
</div>
);

1
src/shared/components/fields/input-mask-field.component.tsx

@ -64,7 +64,6 @@ export const InputMaskField: FC<IProps> = ({ @@ -64,7 +64,6 @@ export const InputMaskField: FC<IProps> = ({
inputStyle={{
background: "transparent",
paddingLeft: 0,
width: "100%",
}}
onChange={(value: string) => {
onChange(value);

40
src/shared/components/fields/styles.scss

@ -75,12 +75,13 @@ $border-radius: 10px; @@ -75,12 +75,13 @@ $border-radius: 10px;
width: 100%;
margin-bottom: 20px;
position: relative;
.contact-copy {
width: 20px;
height: 20px;
position: absolute;
top: 33px;
right: 5px;
top: 34px;
right: 10px;
cursor: pointer;
}
@ -369,7 +370,6 @@ $border-radius: 10px; @@ -369,7 +370,6 @@ $border-radius: 10px;
.contact-field {
width: 100%;
// margin-bottom: 20px;
.contact-label {
font-size: 12px;
@ -384,31 +384,33 @@ $border-radius: 10px; @@ -384,31 +384,33 @@ $border-radius: 10px;
border-radius: 10px;
font-size: 16px;
line-height: 16px;
// margin-top: 5px;
justify-content: space-between;
align-items: center;
padding: 0px 20px 0px 20px;
img {
// margin-top: 10px;
// fill: $color-primary;
width: 24px;
height: 24px;
}
span {
max-width: 250px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.images {
display: flex;
flex-direction: row;
}
}
.contact-copy {
margin-left: 15px;
cursor: pointer;
}
// input {
// border: none !important;
// border-radius: $border-radius !important;
// &::placeholder {
// color: $placeholder-color;
// }
// }
}
.functional-email-field {
@ -428,10 +430,16 @@ $border-radius: 10px; @@ -428,10 +430,16 @@ $border-radius: 10px;
line-height: 16px;
justify-content: space-between;
align-items: center;
padding: 10px 15px 10px 15px;
padding: 10px 10px 10px 15px;
&:hover {
cursor: pointer;
}
.contact-copy {
cursor: pointer;
width: 20px;
height: 20px;
}
}
}

9
src/shared/components/form/CheckBox.tsx

@ -1,18 +1,21 @@ @@ -1,18 +1,21 @@
import React, { PureComponent } from "react";
import React from "react";
import { Checkbox } from "antd";
import { UseFormRegisterReturn } from "react-hook-form";
import { CheckboxProps } from "antd/lib/checkbox/Checkbox";
interface IProps {
label: string;
onChange?: (v: any) => any;
field?: any;
className?: string;
checkBoxProps?: CheckboxProps;
}
export const CheckBoxForm = (props: IProps) => {
return (
<Checkbox
// style={{ borderColor "rgba(158, 39, 67, 1)" }}
className={props.className}
onChange={(v) => props.onChange(v.target.checked)}
{...props.checkBoxProps}
{...props?.field}
>
{props.label}

15
src/shared/events/index.ts

@ -6,6 +6,7 @@ import { @@ -6,6 +6,7 @@ import {
} from "../interfaces";
import { Events } from "jet-tools";
import { ChatMemberRole, EntityType } from "../enums";
import { IChatMessage as IMessageInChat } from "@/containers/Chats/plugins/interfaces";
export type AppEvents = {
onDeleteChatForMe: { chatId: number };
@ -27,7 +28,7 @@ export type AppEvents = { @@ -27,7 +28,7 @@ export type AppEvents = {
onReadDocuments: { taskId: number };
openForwardMessageModal: {
message: IMessage;
message: IMessage | IMessageInChat[];
isShow: boolean;
onCancel?: () => void;
};
@ -41,9 +42,10 @@ export type AppEvents = { @@ -41,9 +42,10 @@ export type AppEvents = {
};
openConfirmDeleteMessageModal: {
message: IChatMessage;
message: IChatMessage | Partial<IMessageInChat>[];
deleteForAll: boolean;
isShow: boolean;
onSuccess?: () => void;
};
closeConfirmDeleteMessageModal: {
@ -59,6 +61,15 @@ export type AppEvents = { @@ -59,6 +61,15 @@ export type AppEvents = {
}>;
onCancel?: () => void;
};
openSelectedMessagesMenuOptions: {
items: Array<{
name: string;
onClick: () => void | Promise<void>;
}>;
onCancel?: () => void;
};
selectSticker: {
onSelect: (sticker: string) => void;
};

1
src/shared/helpers/index.ts

@ -17,3 +17,4 @@ export * from "./tranposition-cipher.helper"; @@ -17,3 +17,4 @@ export * from "./tranposition-cipher.helper";
export * from "./transforms.helpers";
export * from "./url.helpers";
export * from "./user.helpers";
export * from "./title-by-count.helper";

17
src/shared/helpers/title-by-count.helper.ts

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
export const getTitleByCount = (
count: number,
[form1, form2, form3]: string[],
) => {
const tens = Math.abs(count) % 100
const units = tens % 10
if (tens > 10 && tens < 20) {
return `${count} ${form3}`
}
if (units > 1 && units < 5) {
return `${count} ${form2}`
}
if (units == 1) {
return `${count} ${form1}`
}
return `${count} ${form3}`
}

1
src/shared/interfaces/messages.interfaces.ts

@ -5,6 +5,7 @@ export interface IMessage { @@ -5,6 +5,7 @@ export interface IMessage {
content: any;
authorId?: number;
readByUsersId?: number[];
chatId: number;
author?: {
name: string;
id: number;

9
src/widgets/send-notifications/hooks/use-send-notifications.hook.ts

@ -2,7 +2,7 @@ import { notificApi } from "@/api/notifications/request"; @@ -2,7 +2,7 @@ import { notificApi } from "@/api/notifications/request";
import { IShortUser, notification } from "@/shared";
import { cloneDeep } from "lodash";
import { useState } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { useForm } from "react-hook-form";
export interface SendNotificationsForm {
title: string;
@ -13,6 +13,8 @@ export interface SendNotificationsForm { @@ -13,6 +13,8 @@ export interface SendNotificationsForm {
export const useSendNotifications = (onSuccess?: () => void) => {
const [isLoading, setLoading] = useState(false);
const [allSelected, setAllSelected] = useState<boolean>(false);
const form = useForm<SendNotificationsForm>({
defaultValues: {
users: [],
@ -48,10 +50,11 @@ export const useSendNotifications = (onSuccess?: () => void) => { @@ -48,10 +50,11 @@ export const useSendNotifications = (onSuccess?: () => void) => {
title: String(values.title).trim(),
content: String(values.content).trim(),
haveToSendEmail: Boolean(values.haveToSendEmail),
sendToAll: allSelected,
});
onSuccess();
form.reset();
notification.showSuccess("Успішно", "Сповіщення були створенні");
notification.showSuccess("Успішно", "Пуш-сповіщення надіслано");
} catch (e) {
notification.showError("Помилка", "Не вдалось відправити сповіщення");
} finally {
@ -67,5 +70,7 @@ export const useSendNotifications = (onSuccess?: () => void) => { @@ -67,5 +70,7 @@ export const useSendNotifications = (onSuccess?: () => void) => {
unselectUser,
selectedUsers,
isLoading,
allSelected,
setAllSelected,
};
};

28
src/widgets/send-notifications/send-notifications.widget.tsx

@ -25,6 +25,8 @@ export const SendNotificationsWidget: FC<Props> = ({ isOpen, onClose }) => { @@ -25,6 +25,8 @@ export const SendNotificationsWidget: FC<Props> = ({ isOpen, onClose }) => {
unselectUser,
selectUsers,
submit,
allSelected,
setAllSelected,
} = useSendNotifications(onClose);
const {
@ -35,14 +37,16 @@ export const SendNotificationsWidget: FC<Props> = ({ isOpen, onClose }) => { @@ -35,14 +37,16 @@ export const SendNotificationsWidget: FC<Props> = ({ isOpen, onClose }) => {
loadParams,
} = useFetchChatUsers();
const isSelectedAll = Boolean(selectedUsers?.length === items?.length);
const toggleSelectAll = () => {
if (isSelectedAll) selectUsers([]);
else selectUsers(items);
if (allSelected) {
selectUsers([]);
setAllSelected(false);
} else setAllSelected(true);
};
const reset = () => {
setSearchVal("");
setAllSelected(false);
form.reset({ title: "", haveToSendEmail: false, content: "", users: [] });
};
@ -50,8 +54,16 @@ export const SendNotificationsWidget: FC<Props> = ({ isOpen, onClose }) => { @@ -50,8 +54,16 @@ export const SendNotificationsWidget: FC<Props> = ({ isOpen, onClose }) => {
if (!isOpen) reset();
}, [isOpen]);
useEffect(() => {
if (allSelected) selectUsers(items);
}, [allSelected, items]);
useEffect(() => {
if (selectedUsers.length === loadParams?.count) setAllSelected(true);
}, [loadParams?.count, selectedUsers]);
return (
<Modal show={isOpen} toggle={() => onClose()} title="Нове сповіщення">
<Modal show={isOpen} toggle={() => onClose()} title="Нове пуш-сповіщення">
<div className="form send-notifi-form">
<Controller
name="title"
@ -89,7 +101,7 @@ export const SendNotificationsWidget: FC<Props> = ({ isOpen, onClose }) => { @@ -89,7 +101,7 @@ export const SendNotificationsWidget: FC<Props> = ({ isOpen, onClose }) => {
searchStr={searchString}
onChangeContact={setSearchVal}
selectAll={toggleSelectAll}
allBtnLabel={isSelectedAll ? "Очистити" : "Додати всіх"}
allBtnLabel={allSelected ? "Очистити" : "Додати всіх"}
/>
</div>
<Controller
@ -123,9 +135,9 @@ export const SendNotificationsWidget: FC<Props> = ({ isOpen, onClose }) => { @@ -123,9 +135,9 @@ export const SendNotificationsWidget: FC<Props> = ({ isOpen, onClose }) => {
name={"allSelect"}
color="primary"
onClick={submit}
title={"Відправити"}
title={"Надіслати пуш-сповіщення"}
>
Створити
Надіслати пуш-сповіщення
</Button>
<Controller

Loading…
Cancel
Save