144 lines
2.8 KiB
Vue
144 lines
2.8 KiB
Vue
<style scoped>
|
|
.message-list {
|
|
max-height: 500px;
|
|
overflow-y: auto;
|
|
padding: 0.5rem;
|
|
}
|
|
|
|
.message-item {
|
|
display: flex;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.message-item-incoming {
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.message-item-outgoing {
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.message-bubble {
|
|
max-width: 80%;
|
|
padding: 0.5rem 0.75rem;
|
|
border-radius: 0.75rem;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
.message-bubble-incoming {
|
|
background-color: #e9ecef;
|
|
border-bottom-left-radius: 0.25rem;
|
|
}
|
|
|
|
.message-bubble-outgoing {
|
|
background-color: #0d6efd;
|
|
color: #fff;
|
|
border-bottom-right-radius: 0.25rem;
|
|
}
|
|
|
|
.message-content {
|
|
font-size: 0.9rem;
|
|
line-height: 1.4;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.message-meta {
|
|
font-size: 0.7rem;
|
|
margin-top: 0.25rem;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.message-bubble-outgoing .message-meta {
|
|
text-align: right;
|
|
}
|
|
|
|
.message-phone {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
color: #6c757d;
|
|
}
|
|
</style>
|
|
|
|
<template>
|
|
<div class="card mb-3">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<span>
|
|
<i class="bi bi-chat-dots"></i>
|
|
Message History
|
|
</span>
|
|
<span class="badge bg-secondary">
|
|
{{ sortedMessages.length }}
|
|
</span>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div v-if="sortedMessages.length === 0" class="empty-state">
|
|
<i class="bi bi-chat-square-text fs-1"></i>
|
|
<p class="mb-0 mt-2">No messages</p>
|
|
</div>
|
|
<div v-else class="message-list">
|
|
<div
|
|
v-for="(msg, idx) in sortedMessages"
|
|
:key="idx"
|
|
class="message-item"
|
|
:class="
|
|
isOwn(msg) ? 'message-item-outgoing' : 'message-item-incoming'
|
|
"
|
|
>
|
|
<div
|
|
class="message-bubble"
|
|
:class="
|
|
isOwn(msg) ? 'message-bubble-outgoing' : 'message-bubble-incoming'
|
|
"
|
|
>
|
|
<div class="message-content">
|
|
{{ msg.content }}
|
|
</div>
|
|
<div class="message-meta">
|
|
<TimeRelative :time="msg.created" />
|
|
<span
|
|
v-if="ownIdentifier && msg.source && msg.destination"
|
|
class="message-phone"
|
|
>
|
|
·
|
|
{{ isOwn(msg) ? "to" : "from" }}
|
|
{{ isOwn(msg) ? msg.destination : msg.source }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from "vue";
|
|
import TimeRelative from "@/components/TimeRelative.vue";
|
|
import { Message } from "@/type/api";
|
|
|
|
interface Props {
|
|
messages: Message[];
|
|
ownIdentifier?: string;
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
messages: () => [],
|
|
});
|
|
|
|
const sortedMessages = computed(() => {
|
|
return [...props.messages].sort(
|
|
(a: Message, b: Message) => a.created.getTime() - b.created.getTime(),
|
|
);
|
|
});
|
|
|
|
function isOwn(msg: Message): boolean {
|
|
if (!props.ownIdentifier) {
|
|
return false;
|
|
}
|
|
return msg.source === props.ownIdentifier;
|
|
}
|
|
</script>
|