sheva77 3 år sedan
förälder
incheckning
cde75424da

+ 6 - 6
README.md

@@ -1,10 +1,10 @@
-FEA-23. Final project "Chat"
+п»їFEA-23. Final project "Chat"
 ---
 
-Что, как мне кажется, можно усовершенсвовать в back-е:
+Что, как мне кажется, можно усовершенсвовать в back-е:
 
-1) В сущность "Chat" внести поле "lastModified", в которое сервер будет вносить
-	- при создании чата: копию поля "createdAt"
-	- при добавлении в чат нового сообщения: дату создания этого сообщения
+1) В сущность "Chat" внести поле "lastModified", в которое сервер будет вносить
+	- при создании чата: копию поля "createdAt"
+	- при добавлении в чат нового сообщения: дату создания этого сообщения
 
-2) При создании нового чата в "members" автоматически добавлять owner-а этого чата
+2) При создании нового чата в "members" автоматически добавлять owner-а этого чата

+ 17 - 7
chat_final_bootstrap/src/Actions/ActionLogin.js

@@ -1,7 +1,7 @@
 import { actionPromise } from "../Reducers";
 import history from "../history";
 import { store } from "../Reducers";
-import { gql } from "../Actions";
+import { gql, actionSearchMessagesByChatId } from "../Actions";
 
 export const actionAuthLogin = (jwt) => ({ type: "LOGIN", jwt });
 
@@ -12,7 +12,6 @@ export const actionAuthLogout = () => {
 
 export const actionAuthInfo = ({ login, nick, _id, avatar, chats = [] }) => {
     let url = avatar && avatar.url;
-    // console.log("actionAuthInfo - ", login, nick, _id, url);
     return { type: "INFO", userInfo: { login, nick, _id, url, chats } };
 };
 
@@ -32,6 +31,7 @@ export const actionUserInfo = (userId) => async (dispatch) => {
                             chats {
                                 _id
                                 title
+                                createdAt
                                 owner{_id}
                                 avatar {url}
                                 members{_id login nick avatar{url}}
@@ -46,7 +46,14 @@ export const actionUserInfo = (userId) => async (dispatch) => {
     // console.log("UserFindOne - ##########", userData.data.UserFindOne);
 
     if (userData && userData.data.UserFindOne) {
-        dispatch(actionAuthInfo(userData.data.UserFindOne));
+        dispatch(actionAuthInfo(userData.data.UserFindOne)); // получить доп инфу о юзере
+        if (userData.data.UserFindOne.chats) {
+            userData.data.UserFindOne.chats.forEach(async (chat) => {
+                // получить по 10 первых сообщений и каждого из моих чатов
+                await dispatch(actionSearchMessagesByChatId(chat._id));
+            });
+        }
+        //
     } else {
         console.log("UserFindOne - ошибка");
     }
@@ -102,10 +109,13 @@ export const actionRegistration = (login, password, nick) => async (dispatch) =>
         )
     );
 
-    // console.log("regdata - ", regData);
-
-    if (regData && regData.data && regData.data.UserUpsert && regData.data.UserUpsert.login) {
-        await actionLogin(login, password)(dispatch);
+    console.log("regdata - ", regData);
+    if (!regData.error) {
+        if (regData && regData.data && regData.data.UserUpsert && regData.data.UserUpsert.login) {
+            await actionLogin(login, password)(dispatch);
+        } else {
+            alert(`Ошибка: ${regData.errors[0].message}`);
+        }
     } else {
         alert(
             `Ошибка регистрации: ${

+ 65 - 8
chat_final_bootstrap/src/Actions/ActionsGql.js

@@ -2,7 +2,7 @@
 
 import { urlConst } from "../const";
 import { actionPromise } from "../Reducers";
-import { actionMsgNewChat, actionMsgInsertInHead } from "../Actions";
+import { actionMsgNewChat, actionMsgInsertInHead, actionUpdateChatCreatedAt } from "../Actions";
 import { store } from "../Reducers";
 
 const getGQL = (url) => (query, variables = {}) => {
@@ -19,13 +19,14 @@ const getGQL = (url) => (query, variables = {}) => {
 
 export const gql = getGQL(urlConst);
 
-const toQuery = (str, fields = ["title", "text", "login", "nick", "_id"]) => {
+const toQuery = (str, fields = ["title", "text", "login", "nick", '"_id"']) => {
     str = str.replace(/ +/g, " ").trim(); // "/ +/g" - оставляет только по одному пробелу в последовательностях пробелов
     str = "/" + str.split(" ").join("|") + "/";
 
     let arr = fields.map((s) => {
         return { [s]: str };
     });
+    // console.log({ $or: arr });
     return { $or: arr };
 };
 
@@ -88,15 +89,68 @@ export const actionSearchMessagesByChatId = (_chatId, skip = 0, searchStr = "",
     );
     // console.log("actionFindMessagesByChatId result: ", messages);
 
-    if (messages && messages.data && messages.data.MessageFind && messages.data.MessageFind.length) {
-        if (!skip) {
-            dispatch(actionMsgNewChat(messages.data.MessageFind.reverse()));
+    // если данные приехали - добавить в store либо как новый чат, либо как добавка к существующему
+    // в зависимости от skip
+
+    if (messages && messages.data && messages.data.MessageFind) {
+        if (messages.data.MessageFind.length) {
+            if (!skip) {
+                dispatch(actionMsgNewChat(messages.data.MessageFind.reverse()));
+            } else {
+                dispatch(actionMsgInsertInHead(messages.data.MessageFind.reverse()));
+            }
+
+            // и поменять в store.auth.chats в соответствующем чате поле createdAt на данные
+            // из поля createdAt последнего прибывшего сообщения
+            // теперь store.auth.chats...createdAt будет говорить о дате последнего изменения в этом чате
+
+            // console.log("MessageFind", _chatId, messages.data.MessageFind[messages.data.MessageFind.length - 1].createdAt);
+
+            dispatch(
+                actionUpdateChatCreatedAt(
+                    _chatId,
+                    messages.data.MessageFind[messages.data.MessageFind.length - 1].createdAt
+                )
+            );
+
+            // подсчет количества сообщений в чате с таким-то id
+            let count = await gql(
+                `query MessageCountByChatId ($chatId:String){
+                    MessageCount(query: $chatId)
+                }`,
+                { chatId: JSON.stringify([{ "chat._id": _chatId }]) }
+            );
+
+            if (!count.data.errors) {
+                dispatch({
+                    type: "NEW_COUNT",
+                    count: {
+                        [_chatId]: count.data.MessageCount,
+                    },
+                });
+            }
         } else {
-            dispatch(actionMsgInsertInHead(messages.data.MessageFind.reverse()));
+            dispatch({
+                type: "NEW_COUNT",
+                count: {
+                    [_chatId]: 0,
+                },
+            });
         }
     }
 };
 
+const messageCountByChatId = async (id) => {
+    let count = await gql(
+        `query MessageCountByChatId ($chatId:String){
+            MessageCount(query: $chatId)
+        }`,
+        { chatId: JSON.stringify([{ "chat._id": id }]) }
+    );
+
+    // if (!count.data.errors) setRes(count.data.MessageCount);
+};
+
 // получить все сообщения из чата с такм-то _id
 export const actionGetMessagesByChatId = (_chatId) => async (dispatch) => {
     let ChatFindOne = await dispatch(
@@ -125,7 +179,7 @@ export const actionGetMessagesByChatId = (_chatId) => async (dispatch) => {
             )
         )
     );
-    console.log("actionGetMessagesByChatId");
+    // console.log("actionGetMessagesByChatId");
 };
 
 export const actionSearchChat = (_userId = "", str = "") => async (dispatch) => {
@@ -161,6 +215,9 @@ export const actionSearchChat = (_userId = "", str = "") => async (dispatch) =>
 export const actionAllUsersFind = (skip = 0, str = "") => async (dispatch) => {
     str = toQuery(str);
 
+    //FIXME:
+    // console.log(str);
+
     let users = await dispatch(
         actionPromise(
             "UserFind",
@@ -173,7 +230,7 @@ export const actionAllUsersFind = (skip = 0, str = "") => async (dispatch) => {
                         avatar{url}
                     }
                 }`,
-                { query: JSON.stringify([str, { sort: [{ _id: -1 }], skip: [skip], limit: [100] }]) }
+                { query: JSON.stringify([str, { sort: [{ login: -1 }], skip: [skip], limit: [100] }]) }
             )
         )
     );

+ 6 - 0
chat_final_bootstrap/src/Actions/ActionsMsg.js

@@ -11,3 +11,9 @@ export const actionMsgInsertInHead = (msgArr) => {
 };
 
 export const actionCurChatId = (curChatId) => ({ type: "CURRENTID", curChatId });
+
+export const actionUpdateChatCreatedAt = (_chatId, lastMsgCreatedAt) => ({
+    type: "UPDATE_CHAT_CREATEDAT",
+    _chatId,
+    lastMsgCreatedAt,
+});

+ 2 - 1
chat_final_bootstrap/src/Actions/index.js

@@ -6,7 +6,7 @@ import {
     actionSearchChat,
     actionAllUsersFind,
 } from "./ActionsGql";
-import { actionMsgNewChat, actionCurChatId, actionMsgInsertInHead } from "./ActionsMsg";
+import { actionMsgNewChat, actionCurChatId, actionMsgInsertInHead, actionUpdateChatCreatedAt } from "./ActionsMsg";
 import { actionAddUserToChatList, actionDelUserFromChatList, actionNewChatList } from "./ActionsChatUsers";
 import { actionCreateNewChat, actionMessageUpsert, actionUserUpdate } from "./ActionsGqlUpsert";
 
@@ -30,4 +30,5 @@ export {
     actionCreateNewChat,
     actionMessageUpsert,
     actionUserUpdate,
+    actionUpdateChatCreatedAt,
 };

+ 4 - 1
chat_final_bootstrap/src/Components/ChatMessages.js

@@ -61,8 +61,11 @@ const MessageItem = ({
             >
                 {/* <span>{`myid: ${myId}`}</span> */}
                 {/* <p>{`Login: ${login}, Nick: ${nick}, ownerId: ${ownerId}`}</p> */}
+
                 <div className="lh-sm mb-2 text-success fw-bolder">{`${nick}`}</div>
+                {/* <div className="text-dark fs-6 lh-sm mb-3">{text.replace(/\r?\n|\r/g, "\n")}</div> */}
                 <div className="text-dark fs-6 lh-sm mb-3">{text}</div>
+
                 {/* <span className="text-success">{`message id: ${_id}`}</span> */}
                 <span className="position-absolute bottom-0 end-0  badge rounded-pill bg-secondary">
                     {dateStr} <span className="visually-hidden">дата сообщения</span>
@@ -157,7 +160,7 @@ const Messages = ({ _id = "", chatInfo, messages, getMsg }) => {
     // );
 
     let avatar = chatInfo && chatInfo[0] && chatInfo[0].avatar && chatInfo[0].avatar.url;
-    let title = chatInfo && chatInfo[0] && chatInfo[0].title;
+    let title = chatInfo && chatInfo[0] && chatInfo[0].title.trim();
 
     useEffect(() => {
         if (

+ 8 - 20
chat_final_bootstrap/src/Components/ChatsList.js

@@ -2,15 +2,13 @@ import logo from "../images//logo23.jpg";
 import { connect } from "react-redux";
 import { urlUploadConst } from "../const";
 import { Link } from "react-router-dom";
-import { Counter } from "../Components";
+import { CCounter } from "../Components";
 import personFillIcon from "../icons/person-fill.svg";
 import history from "../history";
 import { useEffect } from "react";
 import { useState } from "react";
 
-// const CCounter = connect;
-
-const ChatItem = ({ _id = "", avatar, title, messages, userId, currentChatId }) => {
+const ChatItem = ({ _id = "", avatar, title, currentChatId }) => {
     // здесь _id, avatar, title - чата,
     // currentChatId - текущий выбраный чат
     // console.log("ChatItem - ", _id, currentChatId);
@@ -50,7 +48,7 @@ const ChatItem = ({ _id = "", avatar, title, messages, userId, currentChatId })
                         <div className="text-dark fs-6 fw-bolder ms-2 lh-1">{`${title}`}</div>
                     </div>
                     <span className="position-absolute bottom-0 end-0  badge rounded-pill bg-secondary">
-                        {Counter(_id)}
+                        <CCounter chatId={_id} />
                         <span className="visually-hidden">всего сообщений</span>
                     </span>
                     {/* FIXME: */}
@@ -62,15 +60,13 @@ const ChatItem = ({ _id = "", avatar, title, messages, userId, currentChatId })
 };
 
 const List = ({ arrayOfChats, userId, currentChatId }) => {
-    // console.log(arrayOfChats);
+    // console.log(arrayOfChats, msgsObj);
     // arrayOfChats - массив всех чатов пользователя
-    if (!arrayOfChats) return <></>;
-    // console.log("+++++++++++ ", arrayOfChats);
 
-    // сортируем чаты так, чтобы сверху показывались чаты, в которых последнее сообщение "свежее" всех остальных
-    // чаты без сообщений отправляются в конец списка
-    // на сервере сделать такую сортировку не получается
+    if (!arrayOfChats) return <></>;
 
+    // сортировка массива чатов по полю createdAt
+    // у кого createdAt больше (свежее) - тот в начало массива
     // надо "попросить" backend-ера внести в сущность Chat поле "lastModified"
     // либо "lastMessageCreatedAt"
 
@@ -80,16 +76,8 @@ const List = ({ arrayOfChats, userId, currentChatId }) => {
     //     return +b.messages[b.messages.length - 1].createdAt - +a.messages[a.messages.length - 1].createdAt;
     // });
 
-    // сортировка вариант2:
-    // за последнюю дату чата принимается дата последнего сообщения либо если нет сообщений,
-    // то дата создания чата
-
     //FIXME:
-    arrayOfChats.sort((a, b) => {
-        a = a.messages && a.messages.length ? a.messages[a.messages.length - 1].createdAt : a.createdAt;
-        b = b.messages && b.messages.length ? b.messages[b.messages.length - 1].createdAt : b.createdAt;
-        return +b - +a;
-    });
+    arrayOfChats.sort((a, b) => b.createdAt - a.createdAt);
 
     // console.log("chatsList - arrayOfChats.sort: ", arrayOfChats);
     // console.log("chatsList - : ", currentChatId);

+ 9 - 19
chat_final_bootstrap/src/Components/Counter.js

@@ -1,26 +1,16 @@
 import { useEffect } from "react";
 import { useRef, useState } from "react";
 import { gql } from "../Actions";
+import { connect } from "react-redux";
 
-export const Counter = (chatId) => {
-    const [res, setRes] = useState("error");
-    const [id, setId] = useState(chatId);
+const Counter = ({ chatId, countMsg }) => {
+    // console.log(chatId, countMsg);
 
-    useEffect(() => messageCountByChatId(id), [id]);
+    if (!chatId || !countMsg) {
+        return <span>0</span>;
+    }
 
-    const messageCountByChatId = async (id) => {
-        let count = await gql(
-            `query MessageCountByChatId ($chatId:String){
-            MessageCount(query: $chatId)
-        }`,
-            { chatId: JSON.stringify([{ "chat._id": id }]) }
-        );
-
-        if (!count.data.errors) setRes(count.data.MessageCount);
-    };
-
-    if (!id) return <span>0</span>;
-    // console.log(typeof res, res);
-
-    return <span>{res}</span>;
+    return <span>{countMsg[chatId] || 0}</span>;
 };
+
+export const CCounter = connect((s) => ({ countMsg: s.countMsg }))(Counter);

+ 0 - 0
chat_final_bootstrap/src/Components/UserDashBoard.js


+ 2 - 2
chat_final_bootstrap/src/Components/index.js

@@ -6,7 +6,7 @@ import { CButtonUpload } from "./ButtonUpload";
 import { ButtonToMain } from "./ButtonToMain";
 import { ButtonCancel } from "./ButtonCansel";
 import { CLoginInfo } from "./LoginInfo";
-import { Counter } from "./Counter";
+import { CCounter } from "./Counter";
 import { CNewChatDashBoard } from "./NewChatDashBoard";
 
 export {
@@ -18,6 +18,6 @@ export {
     CButtonUpload,
     ButtonToMain,
     ButtonCancel,
-    Counter,
+    CCounter,
     CNewChatDashBoard,
 };

+ 1 - 1
chat_final_bootstrap/src/Layout/AllUsersList.js

@@ -34,7 +34,7 @@ const UserItem = ({ _id, login, nick, avatar, myId, addUserToList = null, delUse
     return (
         <>
             <li
-                className={`m-2 list-group-item-${
+                className={`position-relative m-2 list-group-item-${
                     _id in newChatUsers || _id === myId ? "success rounded-pill shadow" : "light rounded-3 shadow-sm"
                 } m-1 gradient border-2 `}
                 onClick={doSelectUser}

+ 16 - 3
chat_final_bootstrap/src/Pages/PageMain.js

@@ -8,7 +8,7 @@ import { useState, useEffect, useRef } from "react";
 import userEvent from "@testing-library/user-event";
 
 const MessageInput = ({ curChatId: { curChatId } = {}, messageUpsert }) => {
-    const textRef = useRef("");
+    const textRef = useRef({});
     const [text, setText] = useState("");
 
     const textTyping = (e) => {
@@ -18,7 +18,8 @@ const MessageInput = ({ curChatId: { curChatId } = {}, messageUpsert }) => {
 
     // отправка по Enter
     const sendMsgByEnterKey = (e) => {
-        if (["NumpadEnter", "Enter"].includes(e.code) && text.trim()) {
+        console.log(e);
+        if (["NumpadEnter", "Enter"].includes(e.code) && !e.shiftKey && text.trim()) {
             sendMsg();
         }
     };
@@ -27,17 +28,29 @@ const MessageInput = ({ curChatId: { curChatId } = {}, messageUpsert }) => {
         if (text.trim()) {
             // console.log(text.trim());
             setText("");
-            textRef.current.value = "";
+            if (textRef.current) {
+                textRef.current.value = "";
+            }
             messageUpsert({ text: text.trim(), chatId: curChatId });
         }
     };
 
+    useEffect(() => {
+        setText("");
+        if (textRef.current) {
+            textRef.current.value = "";
+        }
+    }, [curChatId]);
+
     return (
         <>
             {curChatId && (
                 <div className="bg-light shadow-sm border border-2 rounded-3 MessageInput">
                     {/* <div> InputArea</div>
                     <div> {`${curChatId}`}</div> */}
+
+                    {/* -------------- Поле отправки сообщения -----------------*/}
+
                     <div className="position-relative">
                         <textarea
                             className="form-control h-75"

+ 1 - 1
chat_final_bootstrap/src/Pages/PageNewChat.js

@@ -48,7 +48,7 @@ const PageNewChat = ({ doSearchUsers = null, match: { params: { _chatId = "" } =
                                 <div className="p-2">
                                     <input
                                         className="form-control mb-2 border border-success border-2"
-                                        placeholder="Search users by nick/login/_id"
+                                        placeholder="Search users by nick/login"
                                         onInput={(e) => {
                                             setSearchUserStr(e.target.value);
                                         }}

+ 33 - 0
chat_final_bootstrap/src/Reducers/index.js

@@ -52,6 +52,37 @@ function authReducer(state, action) {
         };
     }
 
+    // для корректной сортировки чатов по дате последнего сообщения
+    if (action.type === "UPDATE_CHAT_CREATEDAT") {
+        if (Array.isArray(state.chats)) {
+            for (let i in state.chats) {
+                if (state.chats[i]._id === action._chatId) {
+                    // надо пересобрать объект, чтобы React "почуствовал" изменения
+                    state.chats[i] = { ...state.chats[i], createdAt: action.lastMsgCreatedAt };
+                }
+            }
+        }
+
+        // теперь пересобрать массив, чтобы React "почуствовал" изменения
+        if (Array.isArray(state.chats)) {
+            state.chats = [...state.chats];
+        }
+
+        // ну и пересобрать state, чтобы React "почуствовал" изменения
+        return { ...state };
+    }
+
+    return state;
+}
+
+// счетчики общего числа сообщений в чатах
+function countReduser(state = {}, action) {
+    if (["LOGOUT", "LOGIN"].includes(action.type)) return {};
+
+    if (action.type === "NEW_COUNT") {
+        return { ...state, ...action.count };
+    }
+
     return state;
 }
 
@@ -69,6 +100,7 @@ function msgReduser(state = {}, action) {
 
     return state;
 }
+
 function newChatUsersReduser(state = {}, action) {
     // console.log(action);
     if (["LOGOUT", "LOGIN"].includes(action.type)) return {};
@@ -142,6 +174,7 @@ export const store = createStore(
         msg: msgReduser,
         curChatId: currentChatIdReduser,
         newChatUsers: newChatUsersReduser,
+        countMsg: countReduser,
     }),
     applyMiddleware(thunk)
 );