hrms-mgt/src/modules/00_support/components/FormChat.vue

447 lines
12 KiB
Vue
Raw Normal View History

2024-02-16 16:20:18 +07:00
<script setup lang="ts">
2024-02-16 16:20:18 +07:00
import "moment/dist/locale/th";
import moment from "moment";
import DialogStatus from "@/modules/00_support/components/DialogStatus.vue";
2024-02-16 16:20:18 +07:00
import { ref, onMounted } from "vue";
import { storeToRefs } from "pinia";
import { useSupportStore } from "@/modules/00_support/store/Main";
import type { QInfiniteScroll, QInfiniteScrollProps } from "quasar";
2024-02-16 16:20:18 +07:00
const store = useSupportStore();
const content = ref<string>("");
2024-02-16 16:20:18 +07:00
const searchInput = ref<string>("");
const currentIssuePage = ref<number>(1);
const totalPageIssue = ref<number>();
const iconAvatar = ref<string>(
"https://bma-ehr.frappet.synology.me/assets/avatar_user-8c8fe276.jpg"
);
2024-02-16 16:20:18 +07:00
const { scrollContainer } = storeToRefs(store);
2024-02-16 16:20:18 +07:00
function dateIssue(timestamp: string): string {
const parsedTimestamp = moment(timestamp);
const diff = moment().diff(parsedTimestamp);
if (diff < 1000) {
return "just now";
} else if (diff < 60000) {
return `${Math.floor(diff / 1000)}s`;
} else if (diff < 3600000) {
return `${Math.floor(diff / 60000)}m`;
} else if (diff < 86400000) {
return `${Math.floor(diff / 3600000)}h`;
} else {
const beYear = parsedTimestamp.year() + 543;
const formattedDate = parsedTimestamp.clone().year(beYear).format("DD MMM");
return formattedDate;
}
}
function getOnlineStatus(option: "icon" | "status", userId: string) {
const isOnline: boolean = store.userStatus.some((u) =>
u.userId.includes(userId)
);
if (option === "icon") return isOnline ? "green" : "grey";
if (option === "status") return isOnline ? "ออนไลน์" : "ออฟไลน์";
}
2024-02-16 16:20:18 +07:00
onMounted(async () => {
if (store.currentIssue) {
await store.fetchIssue();
}
2024-02-16 16:20:18 +07:00
await store.fetchIssue();
totalPageIssue.value = Math.ceil((store.currentTotalIssue || 0) / 30);
2024-02-16 16:20:18 +07:00
});
2024-02-16 16:20:18 +07:00
const onLoad = (async (_: any, done: any) => {
const totalPages = Math.ceil((store.currentTotalMessage || 1) / 30);
2024-02-16 16:20:18 +07:00
if (store.currentPage && totalPages > store.currentPage) {
await store.loadMessage(store.currentPage + 1);
done();
}
}) satisfies QInfiniteScrollProps["onLoad"];
2024-02-16 16:20:18 +07:00
</script>
<template>
<div class="flex">
<p class="text-h6 text-weight-medium align-center">ถาม - ตอบ</p>
<q-space />
<p>
<q-btn
dense
color="secondary"
class="button-link-no-deco q-px-md"
@click="$router.push('/support/category')"
>
ดการประเภทของปญหา
2024-02-16 16:20:18 +07:00
</q-btn>
</p>
</div>
2024-02-16 16:20:18 +07:00
<div class="container">
<div class="i1 bg-white align-center">
<q-toolbar>
<q-item-section>
2024-02-16 16:20:18 +07:00
<q-input
dense
rounded
outlined
label="ค้นหาข้อความ..."
v-model="searchInput"
@keydown.enter.prevent="
() => {
store.searchIssue(searchInput);
}
"
2024-02-16 16:20:18 +07:00
>
2024-02-16 16:20:18 +07:00
<template v-slot:prepend>
<q-icon name="search" />
</template>
</q-input>
</q-item-section>
</q-toolbar>
</div>
2024-02-16 16:20:18 +07:00
2024-02-16 16:20:18 +07:00
<div class="i2 bg-white align-center">
<q-toolbar>
<q-avatar>
<img :src="iconAvatar" />
2024-02-16 16:20:18 +07:00
</q-avatar>
<q-item-section v-if="store.currentIssue" class="q-pl-sm">
<q-item-label
>{{ store.currentTitle }}
<q-badge color="blue">
{{ store.currentCategory }}
</q-badge></q-item-label
>
<span>
<q-icon
name="mdi-circle"
size="10px"
:color="getOnlineStatus('icon', store.createdByUser)"
/>
{{ getOnlineStatus("status", store.createdByUser) }}
</span>
2024-02-16 16:20:18 +07:00
</q-item-section>
2024-02-16 16:20:18 +07:00
<q-space />
<div v-if="store.currentIssue">
<q-badge
color="white"
text-color="black"
:label="
store.correntStatusIssue == 'new'
? 'ปัญหาใหม่'
: store.correntStatusIssue == 'ongoing'
? 'กำลังดำเนินการ'
: 'เสร็จสิ้น'
"
/>
<dialog-status />
</div>
2024-02-16 16:20:18 +07:00
<q-btn flat round dense icon="o_info" text-color="grey" />
</q-toolbar>
</div>
2024-02-16 16:20:18 +07:00
<div class="i3 bg-white scroll">
<div v-for="(item, index) in store.issue?.result" :key="index">
<div
@click="
async () => {
store.currentIssue = item.id;
store.currentTitle = item.title;
store.correntStatusIssue = item.status;
store.currentCategory = item.category.name;
store.createdByUser = item.createdByUserId;
store.issue
? (store.issue.result = store.issue.result.map((v) => {
if (v.id === item.id) {
v.unreadCount = 0;
}
return v;
}))
: '';
await store.fetchMessageStatus(item.id);
await store.fetchMessage(item.id);
}
"
:class="{ active: store.currentIssue === item.id }"
class="noactive row q-py-sm justify-between items-center q-px-md"
>
<div class="col-10 row items-center">
<div class="noactive-avatar">
<q-avatar color="grey-2" text-color="white" size="40px">
<img :src="iconAvatar" />
</q-avatar>
</div>
<div class="col column q-ml-md">
<span>
<q-badge color="blue">
{{ item.category.name }}
</q-badge>
</span>
<span class="text-longcol text-weight-bold line-ellipsis">
{{ item.title }}
</span>
<span
class="col text-caption line-ellipsis"
:class="{
'text-weight-bold': item.unreadCount > 0,
'text-grey-8': item.unreadCount > 0,
'text-grey': item.unreadCount === 0,
}"
>{{ item.lastMessage }}</span
>
</div>
<div></div>
</div>
<div class="col-2 items-center text-right">
<q-icon
v-if="item.lastMessage?.length === 0"
name="mdi-send"
size="xs"
color="primary"
/>
<div v-else class="column">
<span class="col text-caption text-grey">{{
dateIssue(item.updatedAt)
}}</span>
<div class="col">
<q-badge
v-if="item.unreadCount > 0"
rounded
color="negative"
text-color="white"
:label="item.unreadCount"
2024-02-16 16:20:18 +07:00
/>
<q-icon v-else name="mdi-check-all" size="xs" color="grey" />
</div>
</div>
</div>
2024-02-16 16:20:18 +07:00
</div>
<q-separator inset />
</div>
2024-02-16 16:20:18 +07:00
</div>
2024-02-16 16:20:18 +07:00
<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="iconAvatar"
2024-02-16 16:20:18 +07:00
: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>
{{ console.log(item.fromUserId === store.userId) }}
2024-02-16 16:20:18 +07:00
<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
2024-02-16 16:20:18 +07:00
)
"
class="q-mr-xl"
2024-02-16 16:20:18 +07:00
>
<q-icon name="mdi-check-all" size="xs" />
<span class="text-caption q-ml-sm">านแล</span>
2024-02-16 16:20:18 +07:00
</div>
</q-item-label>
</q-item-section>
</div>
<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>
2024-02-16 16:20:18 +07:00
</div>
2024-02-16 16:20:18 +07:00
<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>
2024-02-16 16:20:18 +07:00
<div class="i5 bg-white container-input align-center">
2024-02-16 16:20:18 +07:00
<!-- <div class="input-file">
2024-02-16 16:20:18 +07:00
<q-btn flat>
<q-icon name="attach_file" style="transform: rotate(-125deg)" />
</q-btn>
2024-02-16 16:20:18 +07:00
</div> -->
2024-02-16 16:20:18 +07:00
<div class="input-chat">
<q-input
@keydown.enter.prevent="
() => {
if (store.currentIssue) {
store.sendMessage(content, store.currentIssue);
content = '';
}
}
"
outlined
dense
placeholder="Aa"
v-model="content"
id="message"
:disable="store.currentIssue ? false : true"
>
</q-input>
2024-02-16 16:20:18 +07:00
</div>
<div class="btn-chat">
<q-btn
@click="
() => {
if (store.currentIssue) {
store.sendMessage(content, store.currentIssue);
content = '';
}
2024-02-16 16:20:18 +07:00
}
"
:disable="store.currentIssue ? false : true"
2024-02-16 16:20:18 +07:00
flat
class="col-2"
style="color: #009789"
2024-02-16 16:20:18 +07:00
label="ส่งข้อความ"
/>
</div>
</div>
</div>
</template>
<style scoped>
.text-long {
width: 100px;
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.active {
background: #ebf9f7;
border-left: 5px solid #03a898;
& {
.noactive-avatar {
border-radius: 50%;
border: 1px solid #03a898;
}
}
}
2024-02-16 16:20:18 +07:00
.align-center {
display: flex;
align-items: center;
justify-content: center;
}
.container {
display: grid;
grid-template-areas:
"search toolbar"
"menu chat"
"manage input";
2024-02-16 16:20:18 +07:00
grid-template-rows: 60px 400px 60px;
grid-template-columns: 300px 1fr;
2024-02-16 16:20:18 +07:00
}
.container-input {
display: grid;
2024-02-16 16:20:18 +07:00
grid-template-areas: "input-chat btn";
2024-02-16 16:20:18 +07:00
grid-template-rows: 1fr;
2024-02-16 16:20:18 +07:00
grid-template-columns: 1fr 100px;
2024-02-16 16:20:18 +07:00
}
.input-file {
grid-area: file;
}
.input-chat {
grid-area: input-chat;
}
.btn-chat {
grid-area: btn;
}
.i1 {
grid-area: search;
}
2024-02-16 16:20:18 +07:00
.i2 {
grid-area: toolbar;
}
.i3 {
grid-area: menu;
}
.i4 {
grid-area: chat;
}
.i5 {
grid-area: input;
}
.grid-manage {
grid-area: manage;
}
2024-02-16 16:20:18 +07:00
.container-1 {
display: grid;
/* grid-template-columns: 1fr 2fr; */
grid: 50px 450px / 1fr 2fr;
}
.container-2 {
display: grid;
/* grid-template-columns: 1fr 2fr; */
grid: 50px 450px / 1fr 2fr;
}
.button-link-no-deco >>> a {
text-decoration: none;
color: inherit;
}
2024-02-16 16:20:18 +07:00
</style>