crm/frontend/src/pages/Dashboard.vue

307 lines
7.7 KiB
Vue

<template>
<div class="flex flex-col h-full overflow-hidden">
<LayoutHeader>
<template #left-header>
<ViewBreadcrumbs routeName="Dashboard" />
</template>
<template #right-header>
<Button
v-if="!editing"
:label="__('Refresh')"
:iconLeft="LucideRefreshCcw"
@click="dashboardItems.reload"
/>
<Button
v-if="!editing && isAdmin()"
:label="__('Edit')"
:iconLeft="LucidePenLine"
@click="enableEditing"
/>
<Button
v-if="editing"
:label="__('Chart')"
iconLeft="plus"
@click="showAddChartModal = true"
/>
<Button
v-if="editing && isAdmin()"
:label="__('Reset to default')"
:iconLeft="LucideUndo2"
@click="resetToDefault"
/>
<Button v-if="editing" :label="__('Cancel')" @click="cancel" />
<Button
v-if="editing"
variant="solid"
:label="__('Save')"
:disabled="!dirty"
:loading="saveDashboard.loading"
@click="save"
/>
</template>
</LayoutHeader>
<div class="p-5 pb-2 flex items-center gap-4">
<Dropdown
v-if="!showDatePicker"
:options="options"
class="form-control"
v-model="preset"
:placeholder="__('Select Range')"
:button="{
label: __(preset),
class:
'!w-full justify-start [&>span]:mr-auto [&>svg]:text-ink-gray-5 ',
variant: 'outline',
iconRight: 'chevron-down',
iconLeft: 'calendar',
}"
/>
<DateRangePicker
v-else
class="!w-48"
ref="datePickerRef"
:value="filters.period"
variant="outline"
:placeholder="__('Period')"
@change="
(v) =>
updateFilter('period', v, () => {
showDatePicker = false
if (!v) {
filters.period = getLastXDays()
preset = 'Last 30 Days'
} else {
preset = formatter(v)
}
})
"
:formatter="formatRange"
>
<template #prefix>
<LucideCalendar class="size-4 text-ink-gray-5 mr-2" />
</template>
</DateRangePicker>
<Link
v-if="isAdmin() || isManager()"
class="form-control w-48"
variant="outline"
:value="filters.user && getUser(filters.user).full_name"
doctype="User"
:filters="{ name: ['in', users.data.crmUsers?.map((u) => u.name)] }"
@change="(v) => updateFilter('user', v)"
:placeholder="__('Sales user')"
:hideMe="true"
>
<template #prefix>
<UserAvatar
v-if="filters.user"
class="mr-2"
:user="filters.user"
size="sm"
/>
</template>
<template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.value" size="sm" />
</template>
<template #item-label="{ option }">
<Tooltip :text="option.value">
<div class="cursor-pointer">
{{ getUser(option.value).full_name }}
</div>
</Tooltip>
</template>
</Link>
</div>
<div class="w-full overflow-y-scroll">
<DashboardGrid
class="pt-1"
v-if="!dashboardItems.loading && dashboardItems.data"
v-model="dashboardItems.data"
:editing="editing"
/>
</div>
</div>
<AddChartModal
v-if="showAddChartModal"
v-model="showAddChartModal"
v-model:items="dashboardItems.data"
/>
</template>
<script setup lang="ts">
import AddChartModal from '@/components/Dashboard/AddChartModal.vue'
import LucideRefreshCcw from '~icons/lucide/refresh-ccw'
import LucideUndo2 from '~icons/lucide/undo-2'
import LucidePenLine from '~icons/lucide/pen-line'
import DashboardGrid from '@/components/Dashboard/DashboardGrid.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
import Link from '@/components/Controls/Link.vue'
import { usersStore } from '@/stores/users'
import { copy } from '@/utils'
import { getLastXDays, formatter, formatRange } from '@/utils/dashboard'
import {
usePageMeta,
createResource,
DateRangePicker,
Dropdown,
Tooltip,
} from 'frappe-ui'
import { ref, reactive, computed, provide } from 'vue'
const { users, getUser, isManager, isAdmin } = usersStore()
const editing = ref(false)
const showDatePicker = ref(false)
const datePickerRef = ref(null)
const preset = ref('Last 30 Days')
const showAddChartModal = ref(false)
const filters = reactive({
period: getLastXDays(),
user: null,
})
const fromDate = computed(() => {
if (!filters.period) return null
return filters.period.split(',')[0]
})
const toDate = computed(() => {
if (!filters.period) return null
return filters.period.split(',')[1]
})
function updateFilter(key: string, value: any, callback?: () => void) {
filters[key] = value
callback?.()
dashboardItems.reload()
}
const options = computed(() => [
{
group: 'Presets',
hideLabel: true,
items: [
{
label: 'Last 7 Days',
onClick: () => {
preset.value = 'Last 7 Days'
filters.period = getLastXDays(7)
dashboardItems.reload()
},
},
{
label: 'Last 30 Days',
onClick: () => {
preset.value = 'Last 30 Days'
filters.period = getLastXDays(30)
dashboardItems.reload()
},
},
{
label: 'Last 60 Days',
onClick: () => {
preset.value = 'Last 60 Days'
filters.period = getLastXDays(60)
dashboardItems.reload()
},
},
{
label: 'Last 90 Days',
onClick: () => {
preset.value = 'Last 90 Days'
filters.period = getLastXDays(90)
dashboardItems.reload()
},
},
],
},
{
label: 'Custom Range',
onClick: () => {
showDatePicker.value = true
setTimeout(() => datePickerRef.value?.open(), 0)
preset.value = 'Custom Range'
filters.period = null // Reset period to allow custom date selection
},
},
])
const dashboardItems = createResource({
url: 'crm.api.dashboard.get_dashboard',
makeParams() {
return {
from_date: fromDate.value,
to_date: toDate.value,
user: filters.user,
}
},
auto: true,
})
const dirty = computed(() => {
if (!editing.value) return false
return JSON.stringify(dashboardItems.data) !== JSON.stringify(oldItems.value)
})
const oldItems = ref([])
provide('fromDate', fromDate)
provide('toDate', toDate)
provide('filters', filters)
function enableEditing() {
editing.value = true
oldItems.value = copy(dashboardItems.data)
}
function cancel() {
editing.value = false
dashboardItems.data = copy(oldItems.value)
}
const saveDashboard = createResource({
url: 'frappe.client.set_value',
method: 'POST',
onSuccess: () => {
dashboardItems.reload()
editing.value = false
},
})
function save() {
const dashboardItemsCopy = copy(dashboardItems.data)
dashboardItemsCopy.forEach((item: any) => {
delete item.data
})
saveDashboard.submit({
doctype: 'CRM Dashboard',
name: 'Manager Dashboard',
fieldname: 'layout',
value: JSON.stringify(dashboardItemsCopy),
})
}
function resetToDefault() {
createResource({
url: 'crm.api.dashboard.reset_to_default',
auto: true,
onSuccess: () => {
dashboardItems.reload()
editing.value = false
},
})
}
usePageMeta(() => {
return { title: __('CRM Dashboard') }
})
</script>