Skip to content

Commit

Permalink
feat(ui): icons for Reply and Reply All (#1311)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssiyad committed Jul 13, 2023
1 parent b67d9d6 commit 3569025
Showing 1 changed file with 121 additions and 113 deletions.
234 changes: 121 additions & 113 deletions desk/src/pages/desk/ticket/ConversationBox.vue
Original file line number Diff line number Diff line change
@@ -1,188 +1,196 @@
<template>
<div class="flex flex-col overflow-hidden">
<div
v-if="isLoaded"
ref="listElement"
class="flex w-full flex-col items-center gap-4 overflow-auto"
>
<div class="content">
<div v-for="(c, i) in conversations" :key="c.name" class="mt-4">
<div v-if="isNewDay(i)">
<div class="my-4 border-t text-center">
<div class="-translate-y-1/2">
<span class="bg-white px-2 text-xs text-gray-700">
{{ dayShort(c.creation) }}
</span>
</div>
</div>
</div>
<CommunicationItem
v-if="c.isCommunication"
:content="c.content"
:date="c.creation"
:sender="c.sender.full_name"
:sender-image="c.sender.image"
:cc="c.cc"
:bcc="c.bcc"
:attachments="c.attachments"
>
<template #extra="{ content, cc, bcc }">
<Dropdown :options="dropdownOptions(content, cc, bcc)">
<template #default>
<FeatherIcon
name="more-horizontal"
class="h-5 w-5 cursor-pointer opacity-0 group-hover:opacity-100"
/>
</template>
</Dropdown>
</template>
</CommunicationItem>
<CommentItem
v-else
:name="c.name"
:content="c.content"
:date="c.creation"
:sender="c.sender"
/>
</div>
</div>
</div>
<div v-else class="flex grow items-center justify-center">
<LoadingIndicator class="w-5 text-gray-900" />
</div>
</div>
<div class="flex flex-col overflow-hidden">
<div
v-if="isLoaded"
ref="listElement"
class="flex w-full flex-col items-center gap-4 overflow-auto"
>
<div class="content">
<div v-for="(c, i) in conversations" :key="c.name" class="mt-4">
<div v-if="isNewDay(i)">
<div class="my-4 border-t text-center">
<div class="-translate-y-1/2">
<span class="bg-white px-2 text-xs text-gray-700">
{{ dayShort(c.creation) }}
</span>
</div>
</div>
</div>
<CommunicationItem
v-if="c.isCommunication"
:content="c.content"
:date="c.creation"
:sender="c.sender.full_name"
:sender-image="c.sender.image"
:cc="c.cc"
:bcc="c.bcc"
:attachments="c.attachments"
>
<template #extra="{ content, cc, bcc }">
<Dropdown :options="dropdownOptions(content, cc, bcc)">
<Button
theme="gray"
variant="ouline"
class="opacity-0 group-hover:opacity-100"
>
<template #icon>
<IconMoreHorizontal class="h-4 w-4" />
</template>
</Button>
</Dropdown>
</template>
</CommunicationItem>
<CommentItem
v-else
:name="c.name"
:content="c.content"
:date="c.creation"
:sender="c.sender"
/>
</div>
</div>
</div>
<div v-else class="flex grow items-center justify-center">
<LoadingIndicator class="w-5 text-gray-900" />
</div>
</div>
</template>

<script setup lang="ts">
import { computed, onUnmounted, ref, watch } from "vue";
import { useScroll } from "@vueuse/core";
import { debounce, Dropdown, FeatherIcon, LoadingIndicator } from "frappe-ui";
import { debounce, Button, Dropdown, LoadingIndicator } from "frappe-ui";
import dayjs from "dayjs";
import { orderBy, unionBy } from "lodash";
import { socket } from "@/socket";
import { useTicketStore } from "./data";
import CommunicationItem from "@/components/CommunicationItem.vue";
import CommentItem from "./CommentItem.vue";
import IconMoreHorizontal from "~icons/lucide/more-horizontal";
import IconReply from "~icons/lucide/reply";
import IconReplyAll from "~icons/lucide/reply-all";
type SocketData = {
ticket_id: string;
ticket_id: string;
};
const { editor, ticket } = useTicketStore();
const listElement = ref(null);
const isCommunicationsLoaded = ref(false);
const isCommentsLoaded = ref(false);
const isLoaded = computed(
() => isCommunicationsLoaded.value && isCommentsLoaded.value
() => isCommunicationsLoaded.value && isCommentsLoaded.value
);
watch(isLoaded, (v) => {
if (v) scrollBottom();
if (v) scrollBottom();
});
watch(
() => editor.isExpanded,
() => scrollBottom()
() => editor.isExpanded,
() => scrollBottom()
);
ticket.getCommunications
.submit()
.then(() => (isCommunicationsLoaded.value = true));
.submit()
.then(() => (isCommunicationsLoaded.value = true));
ticket.getComments.submit().then(() => (isCommentsLoaded.value = true));
const ticketId = computed(() => ticket.doc.name);
const communications = computed(
() => ticket.getCommunications.data?.message?.map(mapCommunication) || []
() => ticket.getCommunications.data?.message?.map(mapCommunication) || []
);
const comments = computed(() => ticket.getComments.data?.message || []);
const conversations = computed(() =>
orderBy(unionBy(communications.value, comments.value), (c) =>
dayjs(c.creation)
)
orderBy(unionBy(communications.value, comments.value), (c) =>
dayjs(c.creation)
)
);
function dropdownOptions(content: string, cc: string, bcc: string) {
return [
{
label: "Reply",
onClick: () => {
editor.cc = [];
editor.bcc = [];
editor.content = quote(content);
editor.isExpanded = true;
},
},
{
label: "Reply All",
onClick: () => {
editor.cc = cc.split(",");
editor.bcc = bcc.split(",");
editor.content = quote(content);
editor.isExpanded = true;
},
},
];
return [
{
label: "Reply",
icon: IconReply,
onClick: () => {
editor.cc = [];
editor.bcc = [];
editor.content = quote(content);
editor.isExpanded = true;
},
},
{
label: "Reply All",
icon: IconReplyAll,
onClick: () => {
editor.cc = cc.split(",");
editor.bcc = bcc.split(",");
editor.content = quote(content);
editor.isExpanded = true;
},
},
];
}
const scrollBottom = debounce(() => {
const { y } = useScroll(listElement, { behavior: "smooth" });
y.value = listElement.value.scrollHeight;
const { y } = useScroll(listElement, { behavior: "smooth" });
y.value = listElement.value.scrollHeight;
}, 500);
function mapCommunication(c) {
return {
...c,
isCommunication: true,
};
return {
...c,
isCommunication: true,
};
}
function isNewDay(index: number) {
if (index === 0) return true;
if (index === 0) return true;
const currEntry = conversations.value[index];
const prevEntry = conversations.value[index - 1];
const currEntry = conversations.value[index];
const prevEntry = conversations.value[index - 1];
return dayjs(currEntry.creation).diff(prevEntry.creation, "day") > 0;
return dayjs(currEntry.creation).diff(prevEntry.creation, "day") > 0;
}
function dayShort(date: string) {
switch (dayjs(date).diff(dayjs(), "day")) {
case 0:
return "Today";
case 1:
return "Yesterday";
default:
return dayjs(date).format("DD/MM/YYYY");
}
switch (dayjs(date).diff(dayjs(), "day")) {
case 0:
return "Today";
case 1:
return "Yesterday";
default:
return dayjs(date).format("DD/MM/YYYY");
}
}
function quote(s: string) {
return `<blockquote>${s}</blockquote><br/>`;
return `<blockquote>${s}</blockquote><br/>`;
}
socket.on("helpdesk:new-communication", (data: SocketData) => {
if (data.ticket_id !== ticketId.value) return;
ticket.getCommunications.reload().then(() => scrollBottom());
if (data.ticket_id !== ticketId.value) return;
ticket.getCommunications.reload().then(() => scrollBottom());
});
socket.on("helpdesk:new-ticket-comment", (data: SocketData) => {
if (data.ticket_id !== ticketId.value) return;
ticket.getComments.reload().then(() => scrollBottom());
if (data.ticket_id !== ticketId.value) return;
ticket.getComments.reload().then(() => scrollBottom());
});
socket.on("helpdesk:delete-ticket-comment", (data: SocketData) => {
if (data.ticket_id !== ticketId.value) return;
ticket.getComments.reload();
if (data.ticket_id !== ticketId.value) return;
ticket.getComments.reload();
});
onUnmounted(() => {
socket.off("helpdesk:new-communication");
socket.off("helpdesk:new-ticket-comment");
socket.off("helpdesk:delete-ticket-comment");
socket.off("helpdesk:new-communication");
socket.off("helpdesk:new-ticket-comment");
socket.off("helpdesk:delete-ticket-comment");
});
</script>

<style scoped>
.content {
width: 742px;
width: 742px;
}
</style>

0 comments on commit 3569025

Please sign in to comment.