Skip to content

Commit

Permalink
feat: notifications tab (#1567)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssiyad authored Sep 20, 2023
1 parent ba1a671 commit 6e0346d
Show file tree
Hide file tree
Showing 18 changed files with 359 additions and 70 deletions.
43 changes: 22 additions & 21 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:json/recommended",
"plugin:vue/vue3-recommended",
"plugin:tailwindcss/recommended",
"plugin:prettier/recommended",
],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
parser: "@typescript-eslint/parser",
},
rules: {
"tailwindcss/no-custom-classname": "off",
},
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:json/recommended",
"plugin:vue/vue3-recommended",
"plugin:tailwindcss/recommended",
"plugin:prettier/recommended",
],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
parser: "@typescript-eslint/parser",
},
rules: {
"tailwindcss/no-custom-classname": "off",
"vue/multi-word-component-names": "off",
},
};
8 changes: 5 additions & 3 deletions desk/src/components/SidebarLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
}"
>
{{ label }}
<Tooltip :text="betaText">
<Badge v-if="isBeta" theme="orange" variant="subtle">beta</Badge>
</Tooltip>
<slot name="right">
<Tooltip :text="betaText">
<Badge v-if="isBeta" theme="orange" variant="subtle">beta</Badge>
</Tooltip>
</slot>
</div>
</div>
</template>
Expand Down
83 changes: 47 additions & 36 deletions desk/src/components/desk/sidebar/SideBar.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
<template>
<div
class="-all flex select-none flex-col border-r border-gray-200 bg-gray-50 p-2 text-base duration-300 ease-in-out"
:style="isExpanded ? widthExpanded : widthMinimised"
class="z-50 flex select-none flex-col border-r border-gray-200 bg-gray-50 p-2 text-base duration-300 ease-in-out"
:style="{
'min-width': width,
'max-width': width,
}"
>
<UserMenu class="mb-2 ml-0.5" :options="profileSettings" />
<div class="flex flex-col gap-1">
<span class="mb-4">
<div
v-if="!isExpanded && notificationStore.unread"
class="absolute z-20 h-1.5 w-1.5 translate-x-6 translate-y-1 rounded-full bg-gray-800"
theme="gray"
variant="solid"
/>
<SidebarLink
class="relative"
label=" Notifications"
:icon="LucideInbox"
:on-click="() => notificationStore.toggle()"
:is-expanded="isExpanded"
>
<template #right>
<Badge
v-if="isExpanded && notificationStore.unread"
:label="notificationStore.unread"
theme="gray"
variant="subtle"
/>
</template>
</SidebarLink>
</span>
<div class="mb-4 flex flex-col gap-1">
<SidebarLink
v-for="option in menuOptions"
v-bind="option"
Expand All @@ -13,21 +40,18 @@
:is-active="option.to?.includes(route.name.toString())"
/>
</div>
<span v-if="showExtra">
<hr class="my-2" />
<div class="flex flex-col gap-1">
<SidebarLink
v-for="option in extraOptions.filter((o) => !o.hide)"
v-bind="option"
:key="option.label"
:is-expanded="isExpanded"
:is-active="option.to?.includes(route.name?.toString())"
/>
</div>
</span>
<div class="flex flex-col gap-1">
<SidebarLink
v-for="option in extraOptions.filter((o) => !o.hide)"
v-bind="option"
:key="option.label"
:is-expanded="isExpanded"
:is-active="option.to?.includes(route.name?.toString())"
/>
</div>
<div class="grow" />
<SidebarLink
:icon="isExpanded ? LucideChevronsLeft : LucideChevronsRight"
:icon="isExpanded ? LucideArrowLeftFromLine : LucideArrowRightFromLine"
:is-active="false"
:is-expanded="isExpanded"
:label="isExpanded ? 'Collapse' : 'Expand'"
Expand All @@ -37,11 +61,12 @@
</template>

<script setup lang="ts">
import { computed, ref } from "vue";
import { computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { storeToRefs } from "pinia";
import { useAuthStore } from "@/stores/auth";
import { useKeymapStore } from "@/stores/keymap";
import { useNotificationStore } from "@/stores/notification";
import { useSidebarStore } from "@/stores/sidebar";
import {
AGENT_PORTAL_AGENT_LIST,
Expand All @@ -60,15 +85,15 @@ import {
import { SidebarLink } from "@/components";
import UserMenu from "./UserMenu.vue";
import LucideArrowUpFromLine from "~icons/lucide/arrow-up-from-line";
import LucideArrowRightFromLine from "~icons/lucide/arrow-right-from-line";
import LucideArrowLeftFromLine from "~icons/lucide/arrow-left-from-line";
import LucideAtSign from "~icons/lucide/at-sign";
import LucideBookOpen from "~icons/lucide/book-open";
import LucideChevronsLeft from "~icons/lucide/chevrons-left";
import LucideChevronsRight from "~icons/lucide/chevrons-right";
import LucideCloudLightning from "~icons/lucide/cloud-lightning";
import LucideContact2 from "~icons/lucide/contact-2";
import LucideFolderOpen from "~icons/lucide/folder-open";
import LucideInbox from "~icons/lucide/inbox";
import LucideLayoutGrid from "~icons/lucide/layout-grid";
import LucideMoreHorizontal from "~icons/lucide/more-horizontal";
import LucideScrollText from "~icons/lucide/scroll-text";
import LucideTicket from "~icons/lucide/ticket";
import LucideUser from "~icons/lucide/user";
Expand All @@ -79,16 +104,9 @@ const route = useRoute();
const router = useRouter();
const authStore = useAuthStore();
const keymapStore = useKeymapStore();
const { isExpanded } = storeToRefs(useSidebarStore());
const notificationStore = useNotificationStore();
const { isExpanded, width } = storeToRefs(useSidebarStore());
const widthExpanded = {
"min-width": "256px",
"max-width": "256px",
};
const widthMinimised = {
"min-width": "50px",
"max-width": "50px",
};
const menuOptions = computed(() => [
{
label: "Tickets",
Expand All @@ -111,11 +129,6 @@ const menuOptions = computed(() => [
to: "DeskKBHome",
isBeta: true,
},
{
label: showExtra.value ? "Less" : "More",
icon: LucideMoreHorizontal,
onClick: () => (showExtra.value = !showExtra.value),
},
]);
const extraOptions = [
Expand Down Expand Up @@ -186,6 +199,4 @@ const profileSettings = [
onClick: () => authStore.logout(),
},
];
const showExtra = ref(!!extraOptions.find((o) => o.to === route.name));
</script>
1 change: 1 addition & 0 deletions desk/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { default as FilterPopover } from "./FilterPopover.vue";
export { default as HCard } from "./HCard.vue";
export { default as ListView } from "./list-view/LV.vue";
export { default as NestedPopover } from "./NestedPopover.vue";
export { default as Notifications } from "./notifications/Notifications.vue";
export { default as PageTitle } from "./PageTitle.vue";
export { default as SearchComplete } from "./SearchComplete.vue";
export { default as SidebarLink } from "./SidebarLink.vue";
Expand Down
114 changes: 114 additions & 0 deletions desk/src/components/notifications/Notifications.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<template>
<TransitionRoot
ref="target"
as="div"
class="fixed z-40 h-screen overflow-auto bg-white"
:show="notificationStore.visible"
:style="{
'box-shadow': '8px 0px 8px rgba(0, 0, 0, 0.1)',
'max-width': '350px',
'min-width': '350px',
left: sidebarStore.width,
}"
enter="transition duration-200 ease-in-out transform"
enter-from="-translate-x-full"
enter-to="translate-x-0"
leave="transition duration-200 ease-in-out transform"
leave-from="translate-x-0"
leave-to="-translate-x-full"
>
<span>
<div
class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-5 py-2.5"
>
<span class="text-lg font-medium">Notifications</span>
<span>
<Button
theme="blue"
variant="ghost"
@click="() => notificationStore.clear.submit()"
>
<template #icon>
<LucideCheckCheck class="h-4 w-4" />
</template>
</Button>
<Button
theme="gray"
variant="ghost"
@click="() => notificationStore.toggle()"
>
<template #icon>
<LucideX class="h-4 w-4" />
</template>
</Button>
</span>
</div>
<div class="divide-y text-base">
<RouterLink
v-for="n in notificationStore.data"
:key="n.name"
class="flex cursor-pointer items-start gap-3.5 px-5 py-2.5 hover:bg-gray-100"
:to="getRoute(n)"
@click="() => notificationStore.toggle()"
>
<UserAvatar :user="n.user_from" />
<span>
<div class="mb-2 leading-5">
<component :is="getBody(n)" v-bind="n" />
</div>
<div class="flex items-center gap-2">
<div class="text-sm text-gray-600">
{{ dayjs(n.creation).fromNow() }}
</div>
<div
v-if="!n.read"
class="h-1.5 w-1.5 rounded-full bg-gray-900"
/>
</div>
</span>
</RouterLink>
</div>
</span>
</TransitionRoot>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { onClickOutside } from "@vueuse/core";
import { TransitionRoot } from "@headlessui/vue";
import { dayjs } from "@/dayjs";
import { Notification } from "@/types";
import { useSidebarStore } from "@/stores/sidebar";
import { useNotificationStore } from "@/stores/notification";
import { UserAvatar } from "@/components";
import NotificationsMention from "./NotificationsMention.vue";
const notificationStore = useNotificationStore();
const sidebarStore = useSidebarStore();
const target = ref(null);
onClickOutside(target, () => {
if (notificationStore.visible) {
notificationStore.toggle();
}
});
function getBody(n: Notification) {
switch (n.notification_type) {
case "Mention":
return NotificationsMention;
}
}
function getRoute(n: Notification) {
switch (n.notification_type) {
case "Mention":
return {
name: "TicketAgent",
params: {
ticketId: n.reference_ticket,
},
hash: "#" + n.reference_comment,
};
}
}
</script>
20 changes: 20 additions & 0 deletions desk/src/components/notifications/NotificationsMention.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<span class="space-x-1 text-gray-700">
<span class="font-medium text-gray-900">{{ user.full_name || user }}</span>
<span>mentioned you in ticket</span>
<span class="font-medium text-gray-900">{{ props.reference_ticket }}</span>
</span>
</template>

<script setup lang="ts">
import { useUserStore } from "@/stores/user";
interface P {
user_from: string;
reference_ticket: string;
}
const props = defineProps<P>();
const { getUser } = useUserStore();
const user = getUser(props.user_from);
</script>
2 changes: 0 additions & 2 deletions desk/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { createApp } from "vue";
import { createPinia } from "pinia";
import {
frappeRequest,
onOutsideClickDirective,
resourcesPlugin,
setConfig,
Badge,
Expand Down Expand Up @@ -35,7 +34,6 @@ setConfig("resourceFetcher", frappeRequest);
const pinia = createPinia();
const app = createApp(App);

app.directive("on-outside-click", onOutsideClickDirective);
app.use(resourcesPlugin);
app.use(pinia);
app.use(router);
Expand Down
2 changes: 2 additions & 0 deletions desk/src/pages/desk/AgentRoot.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<div class="flex h-screen w-screen">
<SideBar />
<Notifications />
<RouterView :key="$route.fullPath" class="z-0 grow overflow-auto" />
</div>
</template>
Expand All @@ -11,6 +12,7 @@ import { useRouter } from "vue-router";
import { useAuthStore } from "@/stores/auth";
import { useConfigStore } from "@/stores/config";
import { CUSTOMER_PORTAL_LANDING, ONBOARDING_PAGE } from "@/router";
import { Notifications } from "@/components";
import SideBar from "@/components/desk/sidebar/SideBar.vue";
const router = useRouter();
Expand Down
Loading

0 comments on commit 6e0346d

Please sign in to comment.