เเก้ไข type

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

View file

@ -5,7 +5,7 @@ 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}`;
`${env.API_SUPPORT_URI}/issue/${id}/message-status?issueId`;
export default {
supportIssue,

View file

@ -35,7 +35,7 @@ const config = ref<any>({
LINK_EVALUATE_PUBLISH: "https://bma-ehr-publish.frappet.synology.me",
API_REPORT_TEMPLATE_URI:
"https://report-server.frappet.synology.me/api/v1/report-template",
API_SUPPORT_URI: "http://192.168.1.10:3000/api/support/v1",
API_SUPPORT_URI: "http://192.168.1.10:3000/api/v1/support",
},
test: {
API_URI: "http://localhost:5010/api/v1",

View file

@ -1,23 +1,30 @@
<script setup lang="ts">
import { ref, watch, onUpdated, onMounted, nextTick } from "vue";
import { useSupportStore } from "@/modules/00_support/store/Main.ts";
import MessageChat from "@/modules/00_support/components/MessageChat.vue";
import "moment/dist/locale/th";
import moment from "moment";
import { ref, onMounted } from "vue";
import { storeToRefs } from "pinia";
import { useSupportStore } from "@/modules/00_support/store/Main";
import type { QInfiniteScroll, QInfiniteScrollProps } from "quasar";
const store = useSupportStore();
const content = ref<string>("");
const scrollContainerRef = ref();
const readStatus = ref<boolean>(true);
onUpdated(() => {
nextTick(() => {
store.scrollToEnd();
});
});
const searchInput = ref<string>("");
const currentIssuePage = ref<number>(1);
const totalPageIssue = ref<number>();
const { scrollContainer } = storeToRefs(store);
onMounted(async () => {
store.scrollContainer = scrollContainerRef.value;
store.fetchIssue();
await store.fetchIssue();
totalPageIssue.value = Math.ceil(store.currentTotalIssue || 0 / 6);
});
const onLoad = (async (_: any, done: any) => {
const totalPages = Math.ceil(store.currentTotalMessage || 0 / 30);
if (store.currentPage && totalPages > store.currentPage) {
await store.loadMessage(store.currentPage + 1);
done();
}
}) satisfies QInfiniteScrollProps["onLoad"];
</script>
<template>
@ -27,7 +34,13 @@ onMounted(async () => {
<div class="i1 bg-white align-center">
<q-toolbar>
<q-item-section>
<q-input dense rounded outlined label="ค้นหาข้อความ...">
<q-input
dense
rounded
outlined
label="ค้นหาข้อความ..."
v-model="searchInput"
>
<template v-slot:prepend>
<q-icon name="search" />
</template>
@ -35,12 +48,12 @@ onMounted(async () => {
</q-item-section>
</q-toolbar>
</div>
<div class="i2 bg-white align-center">
<q-toolbar>
<q-avatar>
<img src="https://cdn.quasar.dev/img/avatar3.jpg" />
</q-avatar>
<q-item-section class="q-pl-sm">
<q-item-label>{{ store.currentTitle }}</q-item-label>
</q-item-section>
@ -48,78 +61,141 @@ onMounted(async () => {
<q-btn flat round dense icon="o_info" text-color="grey" />
</q-toolbar>
</div>
<div class="i3 bg-white">
<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);
store.fetchMessageStatus(data.id);
<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);
store.fetchMessageStatus(data.id);
if (store.issue) {
store.issue.result = store.issue.result.map((v) => {
if (v.id === data.id) {
v.unreadCount = 0;
}
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-label class="flex" caption>
{{ data.lastMessage }}
<q-space />
<q-icon
v-if="data.lastMessage?.length === 0"
color="green"
size="18px"
name="mdi-send"
}
"
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-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"
/>
<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 />
<q-icon v-else size="18px" name="done_all" />
</div>
</q-item-label>
</q-item-section>
</q-item>
<q-separator inset />
</div>
</q-list>
</div>
<div class="i4 bg-grey-3" v-if="store.currentIssue">
<q-scroll-area
ref="scrollContainer"
style="width: 100%; height: 100%"
v-if="store.message?.result.message.length || 0 > 0"
>
<q-infinite-scroll @load="onLoad" :offset="250" reverse>
<div
v-for="(item, index) in store.message?.result.message"
:key="index"
class="caption"
>
<q-item-section class="q-pr-md">
<q-item-label>
<q-chat-message
:key="index"
:id="item.id"
avatar="https://cdn.quasar.dev/img/avatar4.jpg"
:text="[item.content]"
:bg-color="
item.fromUserId === store.userId ? 'primary' : 'white'
"
:text-color="
item.fromUserId === store.userId ? 'white' : 'black'
"
:sent="item.fromUserId === store.userId"
:stamp="moment(item.createdAt).fromNow()"
/>
</q-item-label>
<q-item-label
v-if="item.fromUserId === store.userId"
class="flex"
caption
>
<q-space />
<div
v-if="
store.messageStatus?.result.some(
(v) =>
new Date(v.lastAccessDate).getTime() >=
new Date(item.createdAt).getTime() &&
index + 1 === store.message?.result.message.length
)
"
>
านเเล
</div>
</q-item-label>
</q-item-section>
</div>
</q-list>
<template v-slot:loading>
<div class="row justify-center q-my-md">
<q-spinner-dots color="primary" size="40px" />
</div>
</template>
</q-infinite-scroll>
</q-scroll-area>
</div>
<div class="i4 bg-grey-3">
<q-scroll-area ref="scrollContainerRef" style="height: 400px; width: 1fr">
<message-chat v-if="store.currentIssue" class="q-pr-md" />
</q-scroll-area>
<div class="grid-manage bg-white align-center">
<q-pagination
v-model="currentIssuePage"
:max="totalPageIssue || 1"
:max-pages="6"
boundary-numbers
@update:model-value="
(value) => {
store.fetchIssue(value);
}
"
/>
</div>
<div class="grid-manage bg-white"></div>
<div class="i5 bg-white container-input align-center">
<div class="input-file">
<!-- <div class="input-file">
<q-btn flat>
<q-icon name="attach_file" style="transform: rotate(-125deg)" />
</q-btn>
</div>
</div> -->
<div class="input-chat">
<q-input
dense
@ -135,7 +211,6 @@ onMounted(async () => {
"
/>
</div>
<div class="btn-chat">
<q-btn
@click="
@ -174,9 +249,11 @@ onMounted(async () => {
.container-input {
display: grid;
grid-template-areas: "file input-chat btn";
grid-template-areas: "input-chat btn";
/* grid-template-areas: "file input-chat btn"; */
grid-template-rows: 1fr;
grid-template-columns: 60px 1fr 100px;
/* grid-template-columns: 60px 1fr 100px; */
grid-template-columns: 1fr 100px;
}
.input-file {

View file

@ -1,66 +0,0 @@
<script setup lang="ts">
import "moment/dist/locale/th";
import moment from "moment";
import { ref, onMounted } from "vue";
import type { SupportMessageResponse } from "@/modules/00_support/interface/index/Main";
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-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>
<style scoped></style>

View file

@ -1,5 +1,5 @@
import { defineStore } from "pinia";
import { ref, onMounted } from "vue";
import { ref, onMounted, nextTick } from "vue";
import { io } from "socket.io-client";
import http from "@/plugins/http";
import config from "@/app.config";
@ -9,23 +9,30 @@ import type {
SupportMessageResponse,
SupportIssueResponse,
SupportStatusUser,
SupportMessageStatus,
} from "@/modules/00_support/interface/index/Main";
import keycloak from "@/plugins/keycloak";
import { useQuasar } from "quasar";
export const useSupportStore = defineStore("supportServiceStore", () => {
const { showLoader, hideLoader } = useCounterMixin();
const userId = ref<string>(keycloak.subject);
const { showLoader, hideLoader, messageError } = useCounterMixin();
const $q = useQuasar();
const userId = ref<string | undefined>(keycloak.subject);
const issue = ref<SupportIssueResponse>();
const message = ref<SupportMessageResponse>();
const messageStatus = ref<SupportMessageStatus>();
const statusUser = ref<SupportStatusUser>([]);
const statusUser = ref<SupportStatusUser[]>([]);
const currentIssue = ref<string>("");
const currentTitle = ref<string>("");
const items = ref<string>([{}, {}, {}, {}, {}, {}, {}]);
const currentTotalMessage = ref<number>();
const currentPage = ref<number>();
const scrollContainer = ref();
function scrollToEnd() {
scrollContainer.value.setScrollPosition("vertical", 1000000);
const currentPageIssue = ref<number>();
const currentTotalIssue = ref<number>();
function scrollToEnd(position: Number = 1) {
setTimeout(() => {
scrollContainer.value?.setScrollPercentage("vertical", position);
}, 150);
}
const socket = io("http://192.168.1.10:3000/", {
@ -52,48 +59,43 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
});
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;
if (issue.value) {
issue.value.result = issue.value.result.map((v) => {
if (v.id === r.issueId) {
v.unreadCount++;
v.lastMessage = r.content;
}
return v;
});
}, 100);
}
});
socket.on("read", (r) => {
if (messageStatus.value) {
messageStatus.value.result = messageStatus.value.result.map((v) => {
if (v.fromUserId !== r.fromUserId) return r;
return v;
});
}
});
socket.on("message", (r) => {
console.log(r);
message.value?.result.message.push(r);
if (issue.value) {
issue.value.result = issue.value.result.map((v) => {
if (v.id === r.issueId) v.lastMessage = r.content;
return v;
});
}
message.value.result.message.push({
id: r.id,
fromUserId: r.fromUserId,
fromUserName: r.fromUserName,
createdAt: r.createdAt,
updatedAt: r.updatedAt,
content: r.content,
read: r.read,
issueId: r.issueId,
});
socket.emit("mark-read", { issueId: currentIssue.value });
scrollToEnd();
});
function sendMessage(content: string, to: string) {
socket.emit("message", { to, content });
setTimeout(() => {
scrollToEnd();
}, 100);
scrollToEnd();
}
async function fetchMessageStatus(issueId: string) {
@ -103,11 +105,25 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
messageError($q, err);
})
.finally(() => {});
if (res && res.data) {
messageStatus.value = res.data;
}
}
async function loadMessage(page: number) {
const res = await http
.get(`${config.API.supportMessage(currentIssue.value)}?page=${page}`)
.catch((err) => {
messageError($q, err);
});
if (res && res.data) {
message.value?.result.message.unshift(...res.data.result.message);
currentPage.value = res.data.page;
}
}
async function fetchMessage(issueId: string) {
showLoader();
const res = await http
@ -118,22 +134,22 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
.finally(() => {
hideLoader();
});
if (res && res.data) {
message.value = await res.data;
message.value.result.message.reverse();
message.value?.result.message.reverse();
currentPage.value = res.data.page;
currentTotalMessage.value = res.data.total;
socket.emit("join-issue", { issueId });
socket.emit("mark-read", { issueId });
setTimeout(() => {
scrollToEnd();
}, 3);
scrollToEnd();
}
}
async function fetchIssue() {
async function fetchIssue(page: number = 1) {
showLoader();
const res = await http
.get(config.API.supportIssue)
.get(`${config.API.supportIssue}?page=${page}&&pageSize=6`)
.catch((err) => {
messageError($q, err);
})
@ -143,6 +159,8 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
if (res && res.data) {
issue.value = res.data;
currentPageIssue.value = res.data.page;
currentTotalIssue.value = res.data.total;
}
}
@ -154,12 +172,16 @@ export const useSupportStore = defineStore("supportServiceStore", () => {
fetchMessage,
fetchMessageStatus,
sendMessage,
items,
scrollToEnd,
scrollContainer,
currentIssue,
currentTitle,
socket,
messageStatus,
loadMessage,
currentTotalMessage,
currentPage,
currentPageIssue,
currentTotalIssue,
};
});