Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ux): response editor #1220

Merged
merged 8 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion desk/src/pages/desk/ticket/CommunicationItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
import { toRefs } from "vue";
import dayjs from "dayjs";
import { Avatar, Dropdown, FeatherIcon } from "frappe-ui";
import { editor } from "./data";
import { useTicketStore } from "./data";
import IconDot from "~icons/ph/dot-bold";
import AttachmentItem from "@/components/AttachmentItem.vue";

Expand Down Expand Up @@ -93,6 +93,7 @@ const props = defineProps({
});

const { content, date, sender, senderImage, cc, bcc } = toRefs(props);
const { editor } = useTicketStore();
const dateDisplay = dayjs(date.value).format("h:mm A");
const options = [
{
Expand Down
6 changes: 4 additions & 2 deletions desk/src/pages/desk/ticket/ContactDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<Button
appearance="minimal"
icon="x"
@click="sidebar.isVisible = false"
@click="sidebar.isExpanded = false"
/>
</div>
<div class="flex items-center gap-3 border-b py-6">
Expand Down Expand Up @@ -51,11 +51,13 @@
import { isEmpty } from "lodash";
import { computed } from "vue";
import { Avatar, Button, createDocumentResource } from "frappe-ui";
import { sidebar, ticket } from "./data";
import { useTicketStore } from "./data";
import CustomFieldList from "./CustomFieldList.vue";
import OpenTicketList from "./OpenTicketList.vue";
import IconEmail from "~icons/espresso/email";

const { sidebar, ticket } = useTicketStore();

const c = createDocumentResource({
doctype: "Contact",
name: ticket.doc.contact,
Expand Down
97 changes: 56 additions & 41 deletions desk/src/pages/desk/ticket/ConversationBox.vue
Original file line number Diff line number Diff line change
@@ -1,68 +1,82 @@
<template>
<div ref="listElement" class="flex flex-col items-center overflow-scroll">
<div class="content flex flex-col gap-4">
<div v-for="(c, i) in conversations" :key="c.name" class="mt-4">
<div v-if="isNewDay(i)">
<div class="flex items-center">
<div class="bg h-0.5 grow rounded-full bg-gray-100"></div>
<div class="my-2 ml-5 grow-0 text-sm text-gray-800">
{{ dayShort(c.creation) }}
<div class="flex flex-col overflow-hidden">
<div
v-if="isLoaded"
ref="listElement"
class="flex w-full flex-col items-center gap-4 overflow-scroll"
>
<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"
/>
<CommentItem
v-else
:name="c.name"
:content="c.content"
:date="c.creation"
:sender="c.sender"
/>
</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"
/>
<CommentItem
v-else
:name="c.name"
:content="c.content"
:date="c.creation"
:sender="c.sender"
/>
</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, LoadingIndicator } from "frappe-ui";
import dayjs from "dayjs";
import { orderBy, unionBy } from "lodash";
import { socket } from "@/socket";
import { ticket } from "./data";
import { useTicketStore } from "./data";
import CommentItem from "./CommentItem.vue";
import CommunicationItem from "./CommunicationItem.vue";

type SocketData = {
ticket_id: string;
};

ticket.getCommunications
.submit()
.then(() => (isCommunicationsLoaded.value = true));
ticket.getComments.submit().then(() => (isCommentsLoaded.value = true));

const listElement = ref<HTMLElement | null>(null);
const { y: scrollY } = useScroll(listElement, { behavior: "smooth" });

const { editor, ticket } = useTicketStore();
const listElement = ref(null);
const isCommunicationsLoaded = ref(false);
const isCommentsLoaded = ref(false);
const isLoaded = computed(
() => isCommunicationsLoaded.value && isCommentsLoaded.value
);

watch(isLoaded, (v) => {
if (v) scrollToBottom();
if (v) scrollBottom();
});
watch(
() => editor.isExpanded,
() => scrollBottom()
);

ticket.getCommunications
.submit()
.then(() => (isCommunicationsLoaded.value = true));
ticket.getComments.submit().then(() => (isCommentsLoaded.value = true));

const ticketId = computed(() => ticket.doc.name);
const communications = computed(
Expand All @@ -75,17 +89,18 @@ const conversations = computed(() =>
)
);

const scrollBottom = debounce(() => {
const { y } = useScroll(listElement, { behavior: "smooth" });
y.value = listElement.value.scrollHeight;
}, 500);

function mapCommunication(c) {
return {
...c,
isCommunication: true,
};
}

function scrollToBottom() {
scrollY.value = listElement.value.scrollHeight;
}

function isNewDay(index: number) {
if (index === 0) return true;

Expand All @@ -108,12 +123,12 @@ function dayShort(date: string) {

socket.on("helpdesk:new-communication", (data: SocketData) => {
if (data.ticket_id !== ticketId.value) return;
ticket.getCommunications.reload().then(() => scrollToBottom());
ticket.getCommunications.reload().then(() => scrollBottom());
});

socket.on("helpdesk:new-ticket-comment", (data: SocketData) => {
if (data.ticket_id !== ticketId.value) return;
ticket.getComments.reload().then(() => scrollToBottom());
ticket.getComments.reload().then(() => scrollBottom());
});

socket.on("helpdesk:delete-ticket-comment", (data: SocketData) => {
Expand Down
3 changes: 2 additions & 1 deletion desk/src/pages/desk/ticket/CustomFieldList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
<script setup lang="ts">
import { isEmpty } from "lodash";
import { computed } from "vue";
import { ticket } from "./data";
import { useTicketStore } from "./data";
import IconTeams from "~icons/espresso/teams";
import IconWebLink from "~icons/espresso/web-link";

const { ticket } = useTicketStore();
const fields = computed(() => ticket.doc?.custom_fields);
</script>
17 changes: 7 additions & 10 deletions desk/src/pages/desk/ticket/OpenTicketList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@
<IconCaretDown v-else class="h-4 w-4 text-gray-600" />
</div>
<div v-if="isExpanded" class="flex flex-col gap-2 pt-4">
<div v-for="ticket in tickets" :key="ticket.name">
<router-link
:to="ticket.to"
target="_blank"
class="flex items-start gap-2"
>
<div v-for="t in tickets" :key="t.name">
<router-link :to="t.to" target="_blank" class="flex items-start gap-2">
<div class="flex h-5 w-5 items-center justify-center">
<IconWebLink class="h-5 w-5 text-gray-600" />
</div>
<div class="text-base text-gray-800">{{ ticket.subject }}</div>
<div class="text-base text-gray-800">{{ t.subject }}</div>
</router-link>
</div>
</div>
Expand All @@ -36,7 +32,7 @@ import { isEmpty } from "lodash";
import { computed, ref } from "vue";
import { createListResource } from "frappe-ui";
import { AGENT_PORTAL_TICKET } from "@/router";
import { ticket } from "./data";
import { useTicketStore } from "./data";
import IconWebLink from "~icons/espresso/web-link";
import IconCaretDown from "~icons/ph/caret-down";
import IconCaretUp from "~icons/ph/caret-up";
Expand All @@ -54,9 +50,10 @@ class Ticket {
}
}

const { ticket } = useTicketStore();
const isExpanded = ref(false);

const t = createListResource({
const ticketRes = createListResource({
doctype: "HD Ticket",
fields: ["name", "subject"],
filters: {
Expand All @@ -68,6 +65,6 @@ const t = createListResource({
});

const tickets = computed(
() => t.data?.map((t: Ticket) => new Ticket(t.name, t.subject)) || []
() => ticketRes.data?.map((t: Ticket) => new Ticket(t.name, t.subject)) || []
);
</script>
11 changes: 6 additions & 5 deletions desk/src/pages/desk/ticket/SideBar.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div class="flex">
<TabGroup vertical>
<TabPanels v-if="sidebar.isVisible" class="main-panel h-full">
<TabPanels v-if="sidebar.isExpanded" class="main-panel h-full">
<TabPanel v-for="item in items" :key="item.name" class="h-full">
<component :is="item.component" class="h-full" />
</TabPanel>
Expand All @@ -11,10 +11,10 @@
<div
class="flex h-7 w-7 items-center justify-center rounded-lg text-gray-600"
:class="{
'bg-gray-200': sidebar.isVisible && selected,
'text-gray-900': sidebar.isVisible && selected,
'bg-gray-200': sidebar.isExpanded && selected,
'text-gray-900': sidebar.isExpanded && selected,
}"
@click="sidebar.isVisible = true"
@click="sidebar.isExpanded = true"
>
<component :is="item.icon" />
</div>
Expand All @@ -26,14 +26,15 @@

<script setup lang="ts">
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from "@headlessui/vue";
import { sidebar } from "./data";
import { useTicketStore } from "./data";
import ContactDetails from "./ContactDetails.vue";
import TicketDetails from "./TicketDetails.vue";
import TicketHistory from "./TicketHistory.vue";
import IconActivity from "~icons/espresso/activity";
import IconAlert from "~icons/espresso/alert-circle";
import IconDetails from "~icons/espresso/details";

const { sidebar } = useTicketStore();
const items = [
{
name: "Ticket Details",
Expand Down
5 changes: 3 additions & 2 deletions desk/src/pages/desk/ticket/TicketDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Button
appearance="minimal"
icon="x"
@click="sidebar.isVisible = false"
@click="sidebar.isExpanded = false"
/>
</div>
<div class="my-6 flex flex-col justify-between gap-3.5">
Expand Down Expand Up @@ -113,14 +113,15 @@ import { useTicketPriorityStore } from "@/stores/ticketPriority";
import { useTicketStatusStore } from "@/stores/ticketStatus";
import { useTicketTypeStore } from "@/stores/ticketType";
import { createToast } from "@/utils/toasts";
import { sidebar, ticket } from "./data";
import { useTicketStore } from "./data";

const agentStore = useAgentStore();
const keymapStore = useKeymapStore();
const teamStore = useTeamStore();
const ticketPriorityStore = useTicketPriorityStore();
const ticketStatusStore = useTicketStatusStore();
const ticketTypeStore = useTicketTypeStore();
const { sidebar, ticket } = useTicketStore();

const isSaveButtonVisible = ref(false);

Expand Down
6 changes: 4 additions & 2 deletions desk/src/pages/desk/ticket/TicketHistory.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<Button
appearance="minimal"
icon="x"
@click="sidebar.isVisible = false"
@click="sidebar.isExpanded = false"
/>
</div>
<div class="overflow-scroll px-4">
Expand Down Expand Up @@ -34,7 +34,7 @@
import { computed, ComputedRef } from "vue";
import { Button, createListResource, Tooltip } from "frappe-ui";
import dayjs from "dayjs";
import { sidebar, ticket } from "./data";
import { useTicketStore } from "./data";
import IconDot from "~icons/ph/dot-bold";

class Activity {
Expand All @@ -59,6 +59,8 @@ class Activity {
}
}

const { sidebar, ticket } = useTicketStore();

const r = createListResource({
doctype: "HD Ticket Activity",
fields: ["name", "creation", "action", "owner"],
Expand Down
6 changes: 3 additions & 3 deletions desk/src/pages/desk/ticket/TicketSingle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
<TopBar />
<div class="flex grow overflow-hidden">
<div class="flex grow flex-col">
<ConversationBox />
<div class="grow"></div>
<ConversationBox class="grow" />
<ResponseEditor />
</div>
<SideBar />
Expand All @@ -15,7 +14,7 @@
<script setup lang="ts">
import { ref, onUnmounted } from "vue";
import { useConfigStore } from "@/stores/config";
import { deinit, init, ticket } from "./data";
import { useTicketStore } from "./data";
import ConversationBox from "./ConversationBox.vue";
import ResponseEditor from "./editor/ResponseEditor.vue";
import SideBar from "./SideBar.vue";
Expand All @@ -27,6 +26,7 @@ const props = defineProps({
required: true,
},
});
const { init, deinit, ticket } = useTicketStore();
const isResLoaded = ref(false);
const configStore = useConfigStore();

Expand Down
3 changes: 2 additions & 1 deletion desk/src/pages/desk/ticket/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { Tooltip } from "frappe-ui";
import { useClipboard } from "@vueuse/core";
import dayjs from "dayjs";
import { AGENT_PORTAL_TICKET_LIST } from "@/router";
import { ticket } from "./data";
import { useTicketStore } from "./data";
import { createToast } from "@/utils/toasts";
import IconAtSign from "~icons/espresso/at-sign";
import IconCaretLeft from "~icons/ph/caret-left";
Expand All @@ -52,6 +52,7 @@ import IconWeb from "~icons/espresso/web";

const { copy } = useClipboard();
const router = useRouter();
const { ticket } = useTicketStore();

const date = computed(() => dayjs(ticket.doc.modified).tz(dayjs.tz.guess()));
const dateShort = computed(() => date.value.fromNow());
Expand Down
Loading
Loading