diff --git a/exm-web/app/(main)/chats/detail/page.tsx b/exm-web/app/(main)/chats/detail/page.tsx index da4ac9fdd8..5a895d1dab 100644 --- a/exm-web/app/(main)/chats/detail/page.tsx +++ b/exm-web/app/(main)/chats/detail/page.tsx @@ -1,9 +1,15 @@ +import RightSideBar from "@/modules/chat/component/RightSideBar" import Messages from "@/modules/chat/component/messages/Messages" export default function Detail() { return ( -
- -
+ <> +
+ +
+
+ +
+ ) } diff --git a/exm-web/app/(main)/page.tsx b/exm-web/app/(main)/page.tsx index 53c943c8ef..4fe8d8dde1 100644 --- a/exm-web/app/(main)/page.tsx +++ b/exm-web/app/(main)/page.tsx @@ -1,7 +1,6 @@ import dynamic from "next/dynamic" const Feed = dynamic(() => import("@/modules/feed/component/Feed")) - const RightSideBar = dynamic( () => import("@/modules/feed/component/RightSideBar") ) diff --git a/exm-web/modules/ApolloProvider.tsx b/exm-web/modules/ApolloProvider.tsx index cd8c4501c6..6e1eed377a 100644 --- a/exm-web/modules/ApolloProvider.tsx +++ b/exm-web/modules/ApolloProvider.tsx @@ -39,7 +39,9 @@ const Provider = ({ children }: { children: React.ReactNode }) => { setLoading(false) }, []) - if (loading) return null + if (loading) { + return null + } return {children} } diff --git a/exm-web/modules/auth/login.tsx b/exm-web/modules/auth/login.tsx index 45e4be5b0a..4bb3f6a24e 100644 --- a/exm-web/modules/auth/login.tsx +++ b/exm-web/modules/auth/login.tsx @@ -9,7 +9,6 @@ import { mutations } from "./graphql" const LoginContainer = () => { const { toast } = useToast() - const [login, { loading }] = useMutation(mutations.login, { onCompleted() { return (window.location.href = "/") diff --git a/exm-web/modules/auth/types.ts b/exm-web/modules/auth/types.ts index db38481995..62c2885543 100644 --- a/exm-web/modules/auth/types.ts +++ b/exm-web/modules/auth/types.ts @@ -116,6 +116,7 @@ export interface IDepartment extends IStructureCommon { export type IUser = IUserC & { isSubscribed?: boolean + isAdmin?: boolean department?: IDepartment } & { isShowNotification?: boolean diff --git a/exm-web/modules/chat/component/ChatItem.tsx b/exm-web/modules/chat/component/ChatItem.tsx index 67a4039610..077a2f019d 100644 --- a/exm-web/modules/chat/component/ChatItem.tsx +++ b/exm-web/modules/chat/component/ChatItem.tsx @@ -9,8 +9,9 @@ import relativeTime from "dayjs/plugin/relativeTime" import { useAtomValue } from "jotai" import { MoreHorizontalIcon } from "lucide-react" +import { readFile } from "@/lib/utils" +import Avatar from "@/components/ui/avatar" import { Card } from "@/components/ui/card" -import Image from "@/components/ui/image" import { Popover, PopoverContent, @@ -91,15 +92,17 @@ export const ChatItem = ({ onMouseEnter={() => setShowAction(true)} onMouseLeave={() => setShowAction(false)} > -
- User Profile -
+

diff --git a/exm-web/modules/chat/component/ParticipantItem.tsx b/exm-web/modules/chat/component/ParticipantItem.tsx new file mode 100644 index 0000000000..34bf79f779 --- /dev/null +++ b/exm-web/modules/chat/component/ParticipantItem.tsx @@ -0,0 +1,134 @@ +"use client" + +import { useState } from "react" +import { IUser } from "@/modules/auth/types" +import { + AlertTriangleIcon, + CheckCircleIcon, + ShieldOffIcon, + UserXIcon, + XCircleIcon, +} from "lucide-react" + +import { readFile } from "@/lib/utils" +import Avatar from "@/components/ui/avatar" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogFooter, + DialogTrigger, +} from "@/components/ui/dialog" + +import useChatsMutation from "../hooks/useChatsMutation" + +const ParticipantItem = ({ + participant, + chatId, + isAdmin, +}: { + participant: IUser + chatId: string + isAdmin: boolean +}) => { + const [open, setOpen] = useState(false) + const [showAction, setShowAction] = useState(false) + + const { makeOrRemoveAdmin, addOrRemoveMember } = useChatsMutation() + + const adminMutation = () => { + makeOrRemoveAdmin(chatId, participant._id) + } + + const userMutation = () => { + addOrRemoveMember(chatId, "remove", [participant._id]) + } + + const renderActionButtons = () => { + if (!isAdmin) { + return null + } + + const renderForm = () => { + return ( + +

+ Are you sure? +
+ + + + + + + ) + } + + return ( +
+
+ +
+ setOpen(!open)}> + +
+ +
+
+ + {renderForm()} +
+
+ ) + } + + return ( +
setShowAction(true)} + onMouseLeave={() => setShowAction(false)} + > +
+
+ +
+

+ {participant?.details?.fullName || participant?.email} +

+

+ {participant.isAdmin ? "Admin " : ""} + {participant?.details?.position || ""} +

+
+
+ {showAction ? renderActionButtons() : null} +
+
+ ) +} + +export default ParticipantItem diff --git a/exm-web/modules/chat/component/ParticipantList.tsx b/exm-web/modules/chat/component/ParticipantList.tsx new file mode 100644 index 0000000000..ec2b25c23e --- /dev/null +++ b/exm-web/modules/chat/component/ParticipantList.tsx @@ -0,0 +1,112 @@ +"use client" + +import { useState } from "react" +import { queries } from "@/common/team/graphql" +import { currentUserAtom } from "@/modules/JotaiProiveder" +import { IUser } from "@/modules/auth/types" +import { useQuery } from "@apollo/client" +import { useAtomValue } from "jotai" +import { PlusIcon } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { FacetedFilter } from "@/components/ui/faceted-filter" +import { Input } from "@/components/ui/input" + +import useChatsMutation from "../hooks/useChatsMutation" +import { IChat } from "../types" +import ParticipantItem from "./ParticipantItem" + +const ParticipantList = ({ chat }: { chat: IChat }) => { + const currentUser = useAtomValue(currentUserAtom) || ({} as IUser) + const [userIds, setUserIds] = useState([]) + const [open, setOpen] = useState(false) + + const { data: usersData, loading } = useQuery(queries.users) + + const { users } = usersData || {} + + const { addOrRemoveMember } = useChatsMutation() + + const addMember = () => { + addOrRemoveMember(chat._id, "add", userIds) + setOpen(false) + } + + const isAdmin = + (chat?.participantUsers || []).find( + (pUser) => pUser._id === currentUser._id + )?.isAdmin || false + + const renderAdd = () => { + const renderForm = () => { + return ( + + + Create bravo + + + {loading ? ( + + ) : ( + ({ + label: user?.details?.fullName || user.email, + value: user._id, + }))} + title="Users" + values={userIds} + onSelect={setUserIds} + /> + )} + + + ) + } + + return ( + setOpen(!open)}> + +
+
+ +
+
+

Add member

+
+
+
+ + {renderForm()} +
+ ) + } + + return ( +
+ {chat.participantUsers.map((user: any, index: number) => ( + + ))} + + {isAdmin && renderAdd()} +
+ ) +} + +export default ParticipantList diff --git a/exm-web/modules/chat/component/RightSideBar.tsx b/exm-web/modules/chat/component/RightSideBar.tsx new file mode 100644 index 0000000000..4d35cc3441 --- /dev/null +++ b/exm-web/modules/chat/component/RightSideBar.tsx @@ -0,0 +1,88 @@ +"use client" + +import { useState } from "react" +import { currentUserAtom } from "@/modules/JotaiProiveder" +import { useAtomValue } from "jotai" +import { PenSquareIcon } from "lucide-react" + +import { readFile } from "@/lib/utils" +import Avatar from "@/components/ui/avatar" +import { Dialog, DialogTrigger } from "@/components/ui/dialog" + +import { useChatDetail } from "../hooks/useChatDetail" +import ParticipantList from "./ParticipantList" +import UserDetail from "./UserDetail" +import { GroupChatAction } from "./form/GroupChatAction" + +const RightSideBar = () => { + const currentUser = useAtomValue(currentUserAtom) + const { chatDetail, loading } = useChatDetail() + + const [open, setOpen] = useState(false) + + if (loading) { + return null + } + + const users: any[] = chatDetail?.participantUsers || [] + const user: any = + users?.length > 1 + ? users?.filter((u) => u._id !== currentUser?._id)[0] + : users?.[0] + + const renderAction = () => { + return ( + setOpen(!open)}> + +
+ +
+
+ + +
+ ) + } + + const renderGroup = () => { + return ( + <> +
+
+ +

+ {chatDetail.name} +

+
+ {renderAction()} +
+ + + + ) + } + + const renderDirect = () => { + return + } + + return ( +
+

Details

+ {chatDetail?.type === "group" ? renderGroup() : renderDirect()} +
+ ) +} + +export default RightSideBar diff --git a/exm-web/modules/chat/component/UserDetail.tsx b/exm-web/modules/chat/component/UserDetail.tsx new file mode 100644 index 0000000000..a01d882e3e --- /dev/null +++ b/exm-web/modules/chat/component/UserDetail.tsx @@ -0,0 +1,55 @@ +"use client" + +import { IUser } from "@/modules/auth/types" + +import { readFile } from "@/lib/utils" +import Avatar from "@/components/ui/avatar" + +const UserDetail = ({ user }: { user: IUser }) => { + return ( + <> +
+ + +
+

+ {user?.details?.fullName || user?.email} +

+ {user?.details?.position ? ( + + {" "} + {user?.details?.position} + + ) : null} +
+
+ +
+
+

Email

+

{user?.email || "-"}

+
+
+

Phone

+

+ {user?.details?.operatorPhone || "-"} +

+
+
+

Employee ID

+

{user?.employeeId || "-"}

+
+
+ + ) +} + +export default UserDetail diff --git a/exm-web/modules/chat/component/form/GroupChatAction.tsx b/exm-web/modules/chat/component/form/GroupChatAction.tsx new file mode 100644 index 0000000000..14a8bbd84c --- /dev/null +++ b/exm-web/modules/chat/component/form/GroupChatAction.tsx @@ -0,0 +1,81 @@ +"use client" + +import { useState } from "react" +import Uploader from "@/modules/feed/component/form/uploader/Uploader" + +import { Button } from "@/components/ui/button" +import { + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { AttachmentWithPreview } from "@/components/AttachmentWithPreview" + +import useChatsMutation from "../../hooks/useChatsMutation" +import { IChat } from "../../types" + +export const GroupChatAction = ({ + chat, + setOpen, +}: { + chat: IChat + setOpen: (open: boolean) => void +}) => { + const { chatEdit, loadingEdit } = useChatsMutation() + + const [name, SetName] = useState(chat?.name || "") + const [featuredImage, setFeaturedImage] = useState(chat?.featuredImage || []) + + const deleteImage = (index: number) => { + const updated = [...featuredImage] + + updated.splice(index, 1) + + setFeaturedImage(updated) + } + + const editAction = () => { + chatEdit(chat._id, name, featuredImage) + + if (!loadingEdit) { + setOpen(false) + } + } + + return ( + <> + + + Chat Item edit + + + + SetName(e.target.value)} + /> + + + + + {featuredImage && featuredImage.length > 0 && ( + + )} + + + + + ) +} diff --git a/exm-web/modules/chat/hooks/useChatDetail.tsx b/exm-web/modules/chat/hooks/useChatDetail.tsx new file mode 100644 index 0000000000..7d244c745b --- /dev/null +++ b/exm-web/modules/chat/hooks/useChatDetail.tsx @@ -0,0 +1,29 @@ +import { useSearchParams } from "next/navigation" +import { useQuery } from "@apollo/client" + +import { queries } from "../graphql" +import { IChat } from "../types" + +export interface IUseChats { + loading: boolean + chatDetail: IChat +} + +export const useChatDetail = (): IUseChats => { + const searchParams = useSearchParams() + + const id = searchParams.get("id") as string + + const { data, loading } = useQuery(queries.chatDetail, { + variables: { id }, + }) + + const chatDetail = data ? (data || {}).chatDetail : {} + + return { + loading, + chatDetail, + } +} + +export const FETCH_MORE_PER_PAGE: number = 20 diff --git a/exm-web/modules/chat/hooks/useChatMessages.tsx b/exm-web/modules/chat/hooks/useChatMessages.tsx index 1e09927078..4fcf295cfa 100644 --- a/exm-web/modules/chat/hooks/useChatMessages.tsx +++ b/exm-web/modules/chat/hooks/useChatMessages.tsx @@ -1,9 +1,6 @@ import { useEffect } from "react" import { useSearchParams } from "next/navigation" -import { currentUserAtom } from "@/modules/JotaiProiveder" -import { IUser } from "@/modules/types" import { useMutation, useQuery, useSubscription } from "@apollo/client" -import { useAtomValue } from "jotai" import { mutations, queries, subscriptions } from "../graphql" import { IChatMessage } from "../types" diff --git a/exm-web/modules/chat/hooks/useChatsMutation.tsx b/exm-web/modules/chat/hooks/useChatsMutation.tsx index 1ad14d18bc..8ff1015420 100644 --- a/exm-web/modules/chat/hooks/useChatsMutation.tsx +++ b/exm-web/modules/chat/hooks/useChatsMutation.tsx @@ -19,15 +19,51 @@ const useChatsMutation = () => { } ) + const [editChatMutation, { loading: loadingEdit }] = useMutation( + mutations.chatEdit + ) + + const [adminMutation] = useMutation(mutations.chatMakeOrRemoveAdmin) + const [memberMutation] = useMutation(mutations.chatAddOrRemoveMember) + const togglePinned = (chatId: string) => { togglePinnedChat({ variables: { id: chatId }, }) } + const makeOrRemoveAdmin = (chatId: string, userId: string) => { + adminMutation({ + variables: { id: chatId, userId }, + refetchQueries: ["chats", "chatDetail"], + }) + } + + const chatEdit = (chatId: string, name?: string, featuredImage?: any[]) => { + editChatMutation({ + variables: { _id: chatId, name, featuredImage }, + refetchQueries: ["chats", "chatDetail"], + }) + } + + const addOrRemoveMember = ( + chatId: string, + type: string, + userIds: string[] + ) => { + memberMutation({ + variables: { id: chatId, type, userIds }, + refetchQueries: ["chats", "chatDetail"], + }) + } + return { togglePinned, + makeOrRemoveAdmin, + addOrRemoveMember, + chatEdit, loading, + loadingEdit, } } diff --git a/exm-web/modules/chat/types.ts b/exm-web/modules/chat/types.ts index 4f066e531f..59ded90c3a 100644 --- a/exm-web/modules/chat/types.ts +++ b/exm-web/modules/chat/types.ts @@ -16,7 +16,7 @@ export interface IChat { name: string type: string isSeen: string - featuredImage: string + featuredImage: any[] createdAt: string createdUser: IUser participantUsers: IUser[]