เพิ่ม Socket mark-read , notify-message

This commit is contained in:
net 2024-02-16 16:20:18 +07:00 committed by Net
parent 5b081878c4
commit 081c1e0589
6 changed files with 191 additions and 78 deletions

View file

@ -4,7 +4,11 @@ export const supportIssue = `${env.API_SUPPORT_URI}/issue`;
export const supportMessage = (id: string) =>
`${env.API_SUPPORT_URI}/issue/${id}/message`;
export const supportMessageStatus = (id: string) =>
`${env.API_SUPPORT_URI}/message-status?issueId=${id}`;
export default {
supportIssue,
supportMessage,
supportMessageStatus,
};

View file

@ -4,10 +4,9 @@ import { useSupportStore } from "@/modules/00_support/store/Main.ts";
import MessageChat from "@/modules/00_support/components/MessageChat.vue";
const store = useSupportStore();
const content = ref<string>("");
const scrollContainerRef = ref();
const readStatus = ref<boolean>(false);
const readStatus = ref<boolean>(true);
onUpdated(() => {
nextTick(() => {
@ -43,75 +42,77 @@ onMounted(async () => {
</q-avatar>
<q-item-section class="q-pl-sm">
<q-item-label>{{ currentTitle }}</q-item-label>
<!-- <q-item-label caption>
<q-icon name="noise_control_off" color="positive" />
ออนไลน
</q-item-label> -->
<q-item-label>{{ store.currentTitle }}</q-item-label>
</q-item-section>
<q-space />
<!-- <q-btn
flat
round
dense
icon="o_videocam"
class="q-mr-xs"
text-color="primary"
/> -->
<q-btn flat round dense icon="o_info" text-color="grey" />
</q-toolbar>
</div>
<div class="i3 bg-white">
<q-list class="text-primary">
<div v-for="data in store.issue?.result">
<q-item
clickable
v-ripple
:active="currentIssue === data.id"
@click="
() => {
<q-scroll-area ref="scrollContainerRef" style="height: 400px; width: 1fr">
<q-list class="text-primary">
<div v-for="data in store.issue?.result">
<q-item
clickable
v-ripple
:active="store.currentIssue === data.id"
@click="
store.currentIssue = data.id;
store.currentTitle = data.title;
store.fetchMessage(data.id);
}
"
active-class="my-menu-link"
>
<q-avatar>
<img src="https://cdn.quasar.dev/img/avatar1.jpg" />
</q-avatar>
store.fetchMessageStatus(data.id);
store.issue.result = store.issue.result.map((v) => {
if (v.id === data.id) {
v.unreadCount = 0;
}
return v;
});
"
active-class="my-menu-link"
>
<q-avatar>
<img src="https://cdn.quasar.dev/img/avatar1.jpg" />
</q-avatar>
<q-item-section class="q-pl-sm">
<q-item-label>{{ data.title }}</q-item-label>
<q-item-section class="q-pl-sm">
<q-item-label>{{ data.title }}</q-item-label>
<q-item-label class="flex" caption>
{{ data.lastMessage }}
<q-space />
<q-toggle v-model="readStatus" label="test" />
<q-badge
v-if="readStatus"
class=""
rounded
color="red"
label="1"
/>
<q-icon v-else size="18px" name="done_all" />
</q-item-label>
</q-item-section>
</q-item>
<q-separator inset />
</div>
</q-list>
<q-item-label class="flex" caption>
{{ data.lastMessage }}
<q-space />
<q-icon
v-if="data.lastMessage?.length === 0"
color="green"
size="18px"
name="mdi-send"
/>
<div v-else>
<q-badge
v-if="data.unreadCount > 0"
class=""
rounded
color="red"
:label="data.unreadCount"
/>
<q-icon v-else size="18px" name="done_all" />
</div>
</q-item-label>
</q-item-section>
</q-item>
<q-separator inset />
</div>
</q-list>
</q-scroll-area>
</div>
<div class="i4 bg-grey-3">
<!-- <div class="i4 bg-grey-3 q-pa-sm"> -->
<q-scroll-area ref="scrollContainerRef" style="height: 400px; width: 1fr">
<message-chat class="q-pr-md" />
<message-chat v-if="store.currentIssue" class="q-pr-md" />
</q-scroll-area>
</div>
<div class="grid-manage bg-white"></div>
<div class="i5 bg-white container-input align-center">
<div class="input-file">
<q-btn flat>
@ -166,7 +167,7 @@ onMounted(async () => {
grid-template-areas:
"search toolbar"
"menu chat"
"menu input";
"manage input";
grid-template-rows: 60px 400px 60px;
grid-template-columns: 400px 1fr;
}
@ -206,6 +207,10 @@ onMounted(async () => {
.i5 {
grid-area: input;
}
.grid-manage {
grid-area: manage;
}
.container-1 {
display: grid;
/* grid-template-columns: 1fr 2fr; */

View file

@ -7,21 +7,59 @@ import { useSupportStore } from "@/modules/00_support/store/Main";
const store = useSupportStore();
onMounted(() => {});
function onLoad() {
console.log("ทำงานเเล้ว");
}
</script>
<template>
<div class="bg-grey-3 q-pb-sm">
<q-chat-message label="Sunday, 19th" />
<q-chat-message
v-for="(data, index) in store.message?.result.message"
:key="index"
:id="data.id"
avatar="https://cdn.quasar.dev/img/avatar4.jpg"
:text="[data.content]"
:bg-color="data.fromUserId === store.userId ? 'primary' : 'white'"
:text-color="data.fromUserId === store.userId ? 'white' : 'black'"
:sent="data.fromUserId === store.userId"
:stamp="moment(data.createdAt).fromNow()"
/>
<q-infinite-scroll @load="onLoad" reverse>
<template v-slot:loading>
<div class="row justify-center q-my-md">
<q-spinner color="primary" name="dots" size="40px" />
</div>
</template>
<q-chat-message label="Sunday, 19th" />
<div v-for="(data, index) in store.message?.result.message">
<q-item-section class="q-pl-sm">
<q-item-label>
<q-chat-message
:key="index"
:id="data.id"
avatar="https://cdn.quasar.dev/img/avatar4.jpg"
:text="[data.content]"
:bg-color="data.fromUserId === store.userId ? 'primary' : 'white'"
:text-color="data.fromUserId === store.userId ? 'white' : 'black'"
:sent="data.fromUserId === store.userId"
:stamp="moment(data.createdAt).fromNow()"
/>
</q-item-label>
<q-item-label
v-if="data.fromUserId === store.userId"
class="flex"
caption
>
<!-- <q-space v-if="data.fromUserId === store.userId" /> -->
<q-space />
<div
v-if="
store.messageStatus.result.some(
(v) =>
new Date(v.lastAccessDate).getTime() >=
new Date(data.createdAt).getTime() &&
index === store.message?.result.message.length - 1
)
"
>
านเเล
</div>
<!-- <q-toggle v-model="readStatus" label="test" /> -->
</q-item-label>
</q-item-section>
</div>
</q-infinite-scroll>
</div>
</template>

View file

@ -1,3 +1,14 @@
export interface SupportMessageStatus {
result: SupportResult[];
}
export interface SupportResult {
fromUserId: string;
fromUserName: string;
lastAccessDate: string;
issueId: string;
}
export interface SupportStatusUser {
socketId: string;
userId: string;
@ -59,4 +70,9 @@ export interface SupportIssueMessage {
issueId: string;
}
export type { SupportMessageResponse, SupportIssueResponse, SupportStatusUser };
export type {
SupportMessageStatus,
SupportMessageResponse,
SupportIssueResponse,
SupportStatusUser,
};

View file

@ -1,5 +1,5 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { ref, onMounted } from "vue";
import { io } from "socket.io-client";
import http from "@/plugins/http";
import config from "@/app.config";
@ -17,6 +17,7 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
const userId = ref<string>(keycloak.subject);
const issue = ref<SupportIssueResponse>();
const message = ref<SupportMessageResponse>();
const messageStatus = ref<SupportMessageStatus>();
const statusUser = ref<SupportStatusUser>([]);
const currentIssue = ref<string>("");
const currentTitle = ref<string>("");
@ -24,32 +25,56 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
const scrollContainer = ref();
function scrollToEnd() {
scrollContainer.value.setScrollPosition("vertical", 10000);
scrollContainer.value.setScrollPosition("vertical", 1000000);
}
const socket = io("http://192.168.1.10:3000/", {
auth: { token: keycloak.token },
autoConnect: false,
});
socket.on("users", (users) => {
// console.log(users);
socket.on("users", (data: SupportStatusUser[]) => {
statusUser.value = data;
});
socket.on("online", (r) => {
console.log(r);
statusUser.value.push({
socketId: r.socketId,
userId: r.userId,
name: r.name,
role: r.role,
});
});
console.log(statusUser.value);
socket.on("offline", (socketId: string) => {
if (socketId === socket.id) return;
statusUser.value = statusUser.value.filter((v) => v.socketId !== socketId);
});
// console.log(JSON.stringify(r, null, -2));
socket.on("notify-message", (r) => {
issue.value.result = issue.value.result.map((v) => {
if (v.id === r.issueId) {
v.unreadCount++;
v.lastMessage = r.content;
}
return v;
});
});
socket.on("read", (r) => {
setTimeout(() => {
messageStatus.value.result = messageStatus.value.result.map((v) => {
if (v.issueId === r.issueId) {
v.lastAccessDate = r.lastAccessDate;
}
return v;
});
}, 100);
});
socket.on("message", (r) => {
console.log(r);
message.value.result.message.push({
id: r.id,
fromUserId: r.fromUserId,
@ -60,17 +85,29 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
read: r.read,
issueId: r.issueId,
});
socket.emit("mark-read", { currentIssue });
socket.emit("mark-read", { issueId: currentIssue.value });
scrollToEnd();
});
function sendMessage(content: string, to: string) {
socket.emit("message", { to, content });
setTimeout(() => {
scrollToEnd();
}, 100);
}
async function fetchMessageStatus(issueId: string) {
const res = await http
.get(config.API.supportMessageStatus(issueId))
.catch((err) => {
messageError($q, err);
})
.finally(() => {});
if (res && res.data) {
messageStatus.value = res.data;
}
}
async function fetchMessage(issueId: string) {
showLoader();
const res = await http
@ -106,7 +143,6 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
if (res && res.data) {
issue.value = res.data;
// console.log(JSON.stringify(res.data, null, -2));
}
}
@ -116,11 +152,14 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
message,
fetchIssue,
fetchMessage,
fetchMessageStatus,
sendMessage,
items,
scrollToEnd,
scrollContainer,
currentIssue,
currentTitle,
socket,
messageStatus,
};
});

View file

@ -1,5 +1,16 @@
<script setup lang="ts">
import FormChat from "@/modules/00_support/components/FormChat.vue";
import { useSupportStore } from "@/modules/00_support/store/Main.ts";
import { onMounted, onUnmounted, ref } from "vue";
const store = useSupportStore();
onMounted(async () => {
store.socket.connect();
});
onUnmounted(async () => {
store.socket.disconnect();
});
</script>
<template>