Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
<template>
<div
class="absolute inset-0 flex flex-col items-center justify-center pointer-events-none"
:style="{
backgroundImage:
'radial-gradient(ellipse at center, rgba(255, 255, 255, 1) 30%, rgba(255, 255, 255, 0.8) 30%, rgba(255, 255, 255, 0) 70%)',
}"
>
<div
class="bg-surface-white/80 backdrop-blur-md space-y-1.5 w-64 p-4 rounded border border-white/20 shadow-2xl"
>
<div class="text-ink-gray-9 font-semibold text-center text-base">
<div class="space-y-1.5 w-64 p-2 rounded-2xl border-0">
<div class="text-ink-gray-8 font-semibold text-center text-base">
{{ title }}
</div>
<div class="text-ink-gray-5 text-center text-p-sm">
<div class="text-ink-gray-6 text-center text-p-sm">
{{ description }}
</div>
</div>
Expand Down
36 changes: 26 additions & 10 deletions desk/src/pages/dashboard/Dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,18 @@ interface NumberCardData {
tooltip: string;
}

type Filters = {
period: string;
agent: null | string;
team: null | string;
};

const filters = reactive<Filters>({
period: getLastXDays(),
agent: null,
team: null,
});

interface ChartData {
data: ChartValues[];
title: string;
Expand Down Expand Up @@ -303,12 +315,6 @@ const tabButtons = computed(() => {
];
});

const filters = reactive({
period: getLastXDays(),
agent: null,
team: null,
});

const hasAppliedFilter = computed(() => {
return (
filters.agent ||
Expand All @@ -326,12 +332,21 @@ const isEmpty = computed(() => {
);
});

const parseFilters = (filters: Filters) => {
return {
from_date: filters.period.split(",")[0],
to_date: filters.period.split(",")[1],
team: filters.team,
agent: filters.agent,
};
};

const numberCards = createResource({
url: "helpdesk.api.dashboard.get_dashboard_data",
cache: ["Analytics", "NumberCards"],
params: {
dashboard_type: "number_card",
filters,
filters: parseFilters(filters),
},
});

Expand All @@ -340,7 +355,7 @@ const masterData = createResource({
cache: ["Analytics", "MasterCharts"],
params: {
dashboard_type: "master",
filters,
filters: parseFilters(filters),
},
});

Expand All @@ -349,7 +364,7 @@ const trendData = createResource({
cache: ["Analytics", "TrendCharts"],
params: {
dashboard_type: "trend",
filters,
filters: parseFilters(filters),
},
});

Expand Down Expand Up @@ -432,7 +447,8 @@ function getChartType(chart: any) {
function getLastXDays(range: number = 30): string {
const today = new Date();
const lastXDate = new Date(today);
lastXDate.setDate(today.getDate() - range);

lastXDate.setDate(today.getDate() - (range > 0 ? range - 1 : 0));

return `${dayjs(lastXDate).format("YYYY-MM-DD")},${dayjs(today).format(
"YYYY-MM-DD"
Expand Down
16 changes: 8 additions & 8 deletions desk/src/pages/home/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
</LayoutHeader>
<div class="flex-1 overflow-auto">
<div
class="flex flex-col p-1 pt-4 md:p-5 mx-auto max-w-6xl w-full grow relative h-full"
class="flex flex-col p-1 pt-4 md:p-4 md:pl-3 mx-auto max-w-[1500px] w-full grow relative h-full"
>
<div class="grow pb-12">
<div
Expand All @@ -83,16 +83,16 @@
class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
>
<div class="flex flex-col items-center justify-center gap-1">
<FeatherIcon name="layout" class="size-12 text-ink-gray-8" />
<div class="text-xl font-semibold text-ink-gray-8">
<FeatherIcon name="layout" class="size-12 text-ink-gray-4" />
<div class="text-lg font-medium text-ink-gray-8">
{{ __("No charts added") }}
</div>
<div class="text-sm text-ink-gray-5">
<div class="text-p-base text-ink-gray-6">
{{ __("Add charts to get started") }}
</div>
</div>
</div>
<div class="mt-5">
<div class="mt-3">
<GridLayout
v-if="!agentDashboard.loading && layout.length > 0"
class="h-fit w-full"
Expand Down Expand Up @@ -294,7 +294,7 @@ const chartsDropdown = computed(() => {
chart: "agent_tickets",
onClick: () =>
addChart("agent_tickets", {
w: 17,
w: 16,
h: 10,
minW: 16,
minH: 10,
Expand All @@ -318,7 +318,7 @@ const chartsDropdown = computed(() => {
chart: "avg_first_response_time",
onClick: () =>
addChart("avg_first_response_time", {
w: 17,
w: 16,
h: 10,
minW: 16,
minH: 10,
Expand All @@ -330,7 +330,7 @@ const chartsDropdown = computed(() => {
chart: "avg_resolution_time",
onClick: () =>
addChart("avg_resolution_time", {
w: 17,
w: 16,
h: 10,
minW: 16,
minH: 10,
Expand Down
188 changes: 161 additions & 27 deletions desk/src/pages/home/components/AvgTimeMetrics.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,107 @@
<div class="text-lg font-semibold text-ink-gray-8">
{{ __("Average Time Metrics") }}
</div>
<TabButtons
:buttons="[
{
label: '3M',
value: '3m',
},
{
label: '6M',
value: '6m',
},
{
label: '1Y',
value: '1y',
},
]"
:model-value="currentDuration"
@update:model-value="onDurationChange"
/>
<div class="flex items-center gap-2">
<Dropdown
v-if="currentDuration !== 'custom_range'"
:options="durationOptions"
placement="right"
>
<template #default>
<Button :label="currentDurationLabel" icon-right="chevron-down" />
</template>
<template #item="{ item }">
<div
class="data-[disabled]:cursor-not-allowed group flex h-7 w-full items-center rounded px-2 text-base focus:outline-none focus:bg-surface-gray-3 data-[highlighted]:bg-surface-gray-3 data-[state=open]:bg-surface-gray-3 whitespace-nowrap text-ink-gray-7 cursor-pointer justify-between"
>
<span>
{{ item.label }}
</span>
<FeatherIcon
v-if="item.label == durationLabels[currentDuration]"
name="check"
class="size-4"
/>
</div>
</template>
</Dropdown>
<DateRangePicker
v-else
ref="datePickerRef"
v-model="customDateRange"
:placeholder="__('Select range')"
@update:model-value="onCustomRangeSelected"
:format="dateFormat"
@click="datePickerRef?.open()"
placement="bottom-end"
class="w-[228px]"
/>
</div>
</div>
<div
v-if="
timeAverages.first_response == '0m' && timeAverages.resolution == '0m'
"
class="flex flex-col justify-center items-center text-center gap-2 h-full w-full"
class="relative flex flex-col mt-5 grow w-full select-none"
>
<div class="flex flex-col gap-2 max-w-60">
<div class="text-base font-medium text-ink-gray-7">
{{ __("No average metrics") }}
<div class="flex items-center gap-12">
<div>
<div
class="text-lg font-medium text-ink-gray-8 w-20 rounded-sm h-4 bg-surface-gray-1"
/>
<div
class="w-40 rounded-sm h-4 bg-surface-gray-1 text-base flex items-center gap-2 mt-1"
/>
</div>
<div class="text-base text-ink-gray-6">
{{ __("Average response and resolution metrics not yet generated") }}
<div>
<div
class="text-lg font-medium text-ink-gray-8 w-20 rounded-sm h-4 bg-surface-gray-1"
/>
<div
class="w-40 rounded-sm h-4 bg-surface-gray-1 text-base flex items-center gap-2 mt-1"
/>
</div>
</div>
<div class="w-full grow pointer-events-none mt-6 relative flex flex-col">
<div class="flex-1 relative flex items-end justify-around px-4">
<div class="absolute inset-0 flex flex-col justify-between z-0">
<div
v-for="idx in 5"
:key="idx"
class="border-t border-dashed border-surface-gray-2 w-full"
/>
</div>
<div
v-for="idx in 6"
:key="idx"
class="relative z-10 flex gap-2 h-full items-end pb-0"
>
<div
class="w-[12px] bg-surface-gray-2 rounded-t-sm"
:style="{ height: [20, 20, 30, 15, 10, 10][idx - 1] + '%' }"
/>
<div
class="w-[12px] bg-surface-gray-2 rounded-t-sm"
:style="{ height: [60, 55, 85, 45, 30, 20][idx - 1] + '%' }"
/>
</div>
</div>
<div class="flex justify-around mt-3 mb-2 px-6">
<div
class="w-6 h-2 bg-surface-gray-2 rounded"
v-for="i in 6"
:key="i"
/>
</div>
</div>
<div class="z-10">
<EmptyState2
:title="__('No average metrics')"
:description="
__('Average response and resolution metrics not available')
"
/>
</div>
</div>
<div v-else class="flex flex-col mt-5 grow w-full">
<div class="flex items-center gap-12">
Expand Down Expand Up @@ -67,11 +135,19 @@
</template>

<script setup lang="ts">
import { computed, onMounted, ref, type PropType } from "vue";
import { computed, onMounted, ref, type PropType, nextTick } from "vue";
import { EChartsOption } from "echarts";
import { createResource, TabButtons, ECharts } from "frappe-ui";
import {
createResource,
Dropdown,
DateRangePicker,
Button,
FeatherIcon,
ECharts,
} from "frappe-ui";
import { formatTime } from "@/utils";
import { __ } from "@/translation";
import EmptyState2 from "@/components/EmptyState2.vue";

type MetricsData = {
averages: {
Expand All @@ -89,14 +165,70 @@ const props = defineProps({
});

const currentDuration = ref("6m");
const datePickerRef = ref<{ open: () => void } | null>(null);
const customDateRange = ref<string | undefined>(undefined);
const dateFormat = window.date_format.toUpperCase();

const durationLabels: Record<string, string> = {
"3m": __("3 Months"),
"6m": __("6 Months"),
"1y": __("1 Year"),
custom_range: __("Custom Range"),
};

const currentDurationLabel = computed(() => {
return durationLabels[currentDuration.value] || __("6 Months");
});

const durationOptions = computed(() => [
{
label: __("3 Months"),
onClick: () => onDurationChange("3m"),
},
{
label: __("6 Months"),
onClick: () => onDurationChange("6m"),
},
{
label: __("1 Year"),
onClick: () => onDurationChange("1y"),
},
{
label: __("Custom Range"),
onClick: () => {
currentDuration.value = "custom_range";
nextTick(() => {
datePickerRef.value?.open();
});
},
},
]);

const onCustomRangeSelected = (range: string) => {
if (!range) {
currentDuration.value = "6m";
customDateRange.value = undefined;
getAvgTimeMetricsResource.submit();
return;
}
currentDuration.value = "custom_range";
customDateRange.value = range;
getAvgTimeMetricsResource.submit();
};

const getAvgTimeMetricsResource = createResource({
url: "helpdesk.api.agent_home.agent_home.get_avg_time_metrics",
type: "GET",
makeParams: () => {
return {
const params: Record<string, string> = {
period: currentDuration.value.toLowerCase(),
};
if (currentDuration.value === "custom_range" && customDateRange.value) {
const [from, to] = customDateRange.value.split(",");
params.from_date = from;
params.to_date = to;
}
return params;
},
});

Expand Down Expand Up @@ -217,6 +349,8 @@ const chartConfig = computed<EChartsOption>(() => {
});

const onDurationChange = (duration: string) => {
if (currentDuration.value === duration) return;
customDateRange.value = undefined;
currentDuration.value = duration;
getAvgTimeMetricsResource.submit();
};
Expand Down
Loading
Loading