288 lines
8.6 KiB
Vue
288 lines
8.6 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, onUnmounted, ref, watch } from "vue";
|
|
import { useSupportStore } from "@/modules/00_support/store/Main";
|
|
import FormChat from "@/modules/00_support/components/FormChat.vue";
|
|
import NewIssue from "@/modules/00_support/components/NewIssue.vue";
|
|
import moment from "moment";
|
|
|
|
const store = useSupportStore();
|
|
const searchData = ref<string>("");
|
|
const newIssueModal = ref<boolean>(false);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
onUnmounted(async () => {
|
|
store.socket.disconnect();
|
|
});
|
|
|
|
onMounted(async () => {
|
|
store.socket.connect();
|
|
store.fetchIssue();
|
|
});
|
|
|
|
watch(searchData, () => {
|
|
store.searchIssue(searchData.value);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="col-12 row justify-center">
|
|
<div class="col-xs-12 col-sm-12 col-md-11">
|
|
<div class="toptitle text-white col-12 row items-center">
|
|
แจ้งปัญหาการใช้งาน
|
|
</div>
|
|
<div class="col-12">
|
|
<q-card bordered>
|
|
<div class="row" style="flex-wrap: nowrap">
|
|
<!-- Left -->
|
|
<div
|
|
v-if="!store.openChat || $q.screen.gt.xs"
|
|
class="col-xs-12 col-sm-4 col-md-4"
|
|
>
|
|
<!-- New -->
|
|
<div class="q-pt-md">
|
|
<div class="q-px-md q-pb-md">
|
|
<q-input
|
|
rounded
|
|
outlined
|
|
dense
|
|
placeholder="ค้นหา"
|
|
debounce="300"
|
|
v-model="searchData"
|
|
id="inputSearch"
|
|
@keydown.enter.prevent="store.searchIssue(searchData)"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="mdi-magnify" class="pointer" />
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
<q-separator inset />
|
|
<div>
|
|
<div
|
|
@click="newIssueModal = true"
|
|
class="col-10 row items-center q-py-sm q-px-md new"
|
|
>
|
|
<div class="no-active-avatar">
|
|
<div class="new-avatar">
|
|
<q-icon name="mdi-plus" size="24px" color="primary" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col column q-ml-md">
|
|
<span class="col text-grey"> แจ้งปัญหา </span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<q-separator inset />
|
|
</div>
|
|
|
|
<!-- List Issue -->
|
|
<div
|
|
class="scrollable-content"
|
|
:style="{
|
|
'max-height': $q.screen.gt.md ? '600px' : '450px',
|
|
}"
|
|
>
|
|
<div v-for="(item, index) in store.issue?.result" :key="index">
|
|
<div
|
|
@click="
|
|
async () => {
|
|
$q.screen.gt.xs ? '' : (store.openChat = true);
|
|
store.currentIssue = item.id;
|
|
store.currentTitle = item.title;
|
|
store.currentIssueDate = item.createdAt;
|
|
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="no-active row q-py-md justify-between items-center q-px-md"
|
|
>
|
|
<div class="col-9 row items-center">
|
|
<div class="no-active-avatar">
|
|
<q-avatar color="grey-2" text-color="white" size="40px">
|
|
<q-icon
|
|
:name="store.icon"
|
|
size="24px"
|
|
color="primary"
|
|
/>
|
|
</q-avatar>
|
|
</div>
|
|
<div class="col column q-ml-md">
|
|
<span class="col 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 class="col-3 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"
|
|
/>
|
|
<q-icon
|
|
v-else
|
|
name="mdi-check-all"
|
|
size="xs"
|
|
color="grey"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<q-separator inset />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<q-separator vertical />
|
|
|
|
<!-- Right -->
|
|
<div v-if="$q.screen.gt.xs || store.openChat" style="width: 100%">
|
|
<form-chat v-if="store.currentIssue.length !== 0" />
|
|
</div>
|
|
</div>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
<new-issue
|
|
:modal="newIssueModal"
|
|
:click-close="() => (newIssueModal = false)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.no-active {
|
|
transition: 0.1s ease all;
|
|
opacity: 1;
|
|
cursor: pointer;
|
|
border-left: 5px solid transparent;
|
|
|
|
&:hover {
|
|
background: #ebf9f7;
|
|
|
|
.no-active-avatar {
|
|
transition: 0.1s ease all;
|
|
border-radius: 50%;
|
|
border: 1px solid $primary;
|
|
}
|
|
}
|
|
}
|
|
|
|
.no-active-avatar {
|
|
border-radius: 50%;
|
|
border: 1px solid #f5f4f4;
|
|
}
|
|
|
|
.active {
|
|
background: #ebf9f7;
|
|
border-left: 5px solid $primary;
|
|
& {
|
|
.no-active-avatar {
|
|
border-radius: 50%;
|
|
border: 1px solid $primary;
|
|
}
|
|
}
|
|
}
|
|
|
|
.new {
|
|
transition: 0.1s ease all;
|
|
opacity: 1;
|
|
cursor: pointer;
|
|
border-left: 5px solid transparent;
|
|
|
|
&:hover {
|
|
background: #ebf9f7;
|
|
}
|
|
}
|
|
|
|
.new-avatar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
border: 2px dashed #9e9e9e;
|
|
}
|
|
|
|
.line-ellipsis {
|
|
display: block;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
overflow: hidden;
|
|
max-width: 90%;
|
|
}
|
|
|
|
.scrollable-content {
|
|
overflow: hidden;
|
|
overflow-y: scroll;
|
|
}
|
|
|
|
.scrollable-content::-webkit-scrollbar {
|
|
width: 0.3em;
|
|
}
|
|
|
|
.scrollable-content::-webkit-scrollbar-track {
|
|
background-color: #f5f4f4;
|
|
}
|
|
|
|
.scrollable-content::-webkit-scrollbar-thumb {
|
|
background-color: #c5c3c2;
|
|
}
|
|
|
|
.scrollable-content::-webkit-scrollbar-thumb:hover {
|
|
background-color: #7a7b7b;
|
|
}
|
|
</style>
|