feat: allow adding existing charts
This commit is contained in:
parent
0909423fe9
commit
37c2d3a2b0
@ -27,7 +27,7 @@ def get_dashboard(from_date="", to_date="", user=""):
|
||||
layout = json.loads(dashboard.layout) if dashboard and dashboard.layout else []
|
||||
|
||||
for l in layout:
|
||||
method_name = f"get_{l['id']}"
|
||||
method_name = f"get_{l['name']}"
|
||||
if hasattr(frappe.get_attr("crm.api.dashboard"), method_name):
|
||||
method = getattr(frappe.get_attr("crm.api.dashboard"), method_name)
|
||||
l["data"] = method(from_date, to_date, user)
|
||||
@ -37,6 +37,29 @@ def get_dashboard(from_date="", to_date="", user=""):
|
||||
return layout
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@sales_user_only
|
||||
def get_chart(name, type, from_date="", to_date="", user=""):
|
||||
"""
|
||||
Get number chart data for the dashboard.
|
||||
"""
|
||||
if not from_date or not to_date:
|
||||
from_date = frappe.utils.get_first_day(from_date or frappe.utils.nowdate())
|
||||
to_date = frappe.utils.get_last_day(to_date or frappe.utils.nowdate())
|
||||
|
||||
roles = frappe.get_roles(frappe.session.user)
|
||||
is_sales_user = "Sales User" in roles and "Sales Manager" not in roles and "System Manager" not in roles
|
||||
if is_sales_user and not user:
|
||||
user = frappe.session.user
|
||||
|
||||
method_name = f"get_{name}"
|
||||
if hasattr(frappe.get_attr("crm.api.dashboard"), method_name):
|
||||
method = getattr(frappe.get_attr("crm.api.dashboard"), method_name)
|
||||
return method(from_date, to_date, user)
|
||||
else:
|
||||
return {"error": _("Invalid chart name")}
|
||||
|
||||
|
||||
def get_total_leads(from_date, to_date, user=""):
|
||||
"""
|
||||
Get lead count for the dashboard.
|
||||
|
||||
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@ -12,6 +12,7 @@ declare module 'vue' {
|
||||
Activities: typeof import('./src/components/Activities/Activities.vue')['default']
|
||||
ActivityHeader: typeof import('./src/components/Activities/ActivityHeader.vue')['default']
|
||||
ActivityIcon: typeof import('./src/components/Icons/ActivityIcon.vue')['default']
|
||||
AddChartModal: typeof import('./src/components/Dashboard/AddChartModal.vue')['default']
|
||||
AddExistingUserModal: typeof import('./src/components/Modals/AddExistingUserModal.vue')['default']
|
||||
AddressIcon: typeof import('./src/components/Icons/AddressIcon.vue')['default']
|
||||
AddressModal: typeof import('./src/components/Modals/AddressModal.vue')['default']
|
||||
|
||||
165
frontend/src/components/Dashboard/AddChartModal.vue
Normal file
165
frontend/src/components/Dashboard/AddChartModal.vue
Normal file
@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="show"
|
||||
:options="{ title: __('Add chart') }"
|
||||
@close="show = false"
|
||||
>
|
||||
<template #body-content>
|
||||
<div class="flex flex-col gap-4">
|
||||
<FormControl
|
||||
v-model="chartType"
|
||||
type="select"
|
||||
:label="__('Chart Type')"
|
||||
:options="chartTypes"
|
||||
/>
|
||||
<FormControl
|
||||
v-if="chartType === 'number_chart'"
|
||||
v-model="numberChart"
|
||||
type="select"
|
||||
:label="__('Number chart')"
|
||||
:options="numberCharts"
|
||||
/>
|
||||
<FormControl
|
||||
v-if="chartType === 'axis_chart'"
|
||||
v-model="axisChart"
|
||||
type="select"
|
||||
:label="__('Axis chart')"
|
||||
:options="axisCharts"
|
||||
/>
|
||||
<FormControl
|
||||
v-if="chartType === 'donut_chart'"
|
||||
v-model="donutChart"
|
||||
type="select"
|
||||
:label="__('Donut chart')"
|
||||
:options="donutCharts"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<Button variant="outline" :label="__('Cancel')" @click="show = false" />
|
||||
<Button variant="solid" :label="__('Add')" @click="addChart" />
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getRandom } from '@/utils'
|
||||
import { createResource, Dialog, FormControl } from 'frappe-ui'
|
||||
import { ref, reactive, inject } from 'vue'
|
||||
|
||||
const show = defineModel({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
})
|
||||
|
||||
const items = defineModel('items', {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
})
|
||||
|
||||
const fromDate = inject('fromDate', ref(''))
|
||||
const toDate = inject('toDate', ref(''))
|
||||
const filters = inject('filters', reactive({ period: '', user: '' }))
|
||||
|
||||
const chartType = ref('blank_card')
|
||||
const chartTypes = [
|
||||
{ label: __('Blank card'), value: 'blank_card' },
|
||||
{ label: __('Number chart'), value: 'number_chart' },
|
||||
{ label: __('Axis chart'), value: 'axis_chart' },
|
||||
{ label: __('Donut chart'), value: 'donut_chart' },
|
||||
]
|
||||
|
||||
const numberChart = ref('')
|
||||
const numberCharts = [
|
||||
{ label: __('Total leads'), value: 'total_leads' },
|
||||
{ label: __('Ongoing deals'), value: 'ongoing_deals' },
|
||||
{ label: __('Avg ongoing deal value'), value: 'average_ongoing_deal_value' },
|
||||
{ label: __('Won deals'), value: 'won_deals' },
|
||||
{ label: __('Avg won deal value'), value: 'average_won_deal_value' },
|
||||
{ label: __('Avg deal value'), value: 'average_deal_value' },
|
||||
{
|
||||
label: __('Avg time to close a lead'),
|
||||
value: 'average_time_to_close_a_lead',
|
||||
},
|
||||
{
|
||||
label: __('Avg time to close a deal'),
|
||||
value: 'average_time_to_close_a_deal',
|
||||
},
|
||||
]
|
||||
|
||||
const axisChart = ref('sales_trend')
|
||||
const axisCharts = [
|
||||
{ label: __('Sales trend'), value: 'sales_trend' },
|
||||
{ label: __('Forecasted revenue'), value: 'forecasted_revenue' },
|
||||
{ label: __('Funnel conversion'), value: 'funnel_conversion' },
|
||||
{ label: __('Deals by ongoing & won stage'), value: 'deals_by_stage_axis' },
|
||||
{ label: __('Lost deal reasons'), value: 'lost_deal_reasons' },
|
||||
{ label: __('Deals by territory'), value: 'deals_by_territory' },
|
||||
{ label: __('Deals by salesperson'), value: 'deals_by_salesperson' },
|
||||
]
|
||||
|
||||
const donutChart = ref('deals_by_stage_donut')
|
||||
const donutCharts = [
|
||||
{ label: __('Deals by stage'), value: 'deals_by_stage_donut' },
|
||||
{ label: __('Leads by source'), value: 'leads_by_source' },
|
||||
{ label: __('Deals by source'), value: 'deals_by_source' },
|
||||
]
|
||||
|
||||
async function addChart() {
|
||||
show.value = false
|
||||
if (chartType.value == 'blank_card') {
|
||||
items.value.push({
|
||||
name: 'blank_card',
|
||||
type: 'blank_card',
|
||||
layout: { x: 0, y: 0, w: 4, h: 2, i: 'blank_card_' + getRandom(4) },
|
||||
})
|
||||
} else {
|
||||
await getChart(chartType.value)
|
||||
}
|
||||
}
|
||||
|
||||
async function getChart(type: string) {
|
||||
let name =
|
||||
type == 'number_chart'
|
||||
? numberChart.value
|
||||
: type == 'axis_chart'
|
||||
? axisChart.value
|
||||
: donutChart.value
|
||||
|
||||
await createResource({
|
||||
url: 'crm.api.dashboard.get_chart',
|
||||
params: {
|
||||
name,
|
||||
type,
|
||||
from_date: fromDate.value,
|
||||
to_date: toDate.value,
|
||||
user: filters.user,
|
||||
},
|
||||
auto: true,
|
||||
onSuccess: (data = {}) => {
|
||||
let width = 4
|
||||
let height = 2
|
||||
|
||||
if (['axis_chart', 'donut_chart'].includes(type)) {
|
||||
width = 10
|
||||
height = 7
|
||||
}
|
||||
|
||||
items.value.push({
|
||||
name,
|
||||
type,
|
||||
layout: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: width,
|
||||
h: height,
|
||||
i: name + '_' + getRandom(4),
|
||||
},
|
||||
data: data,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@ -23,6 +23,13 @@
|
||||
<LucidePenLine class="size-4" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
v-if="editing"
|
||||
:label="__('Add chart')"
|
||||
icon-left="plus"
|
||||
@click="showAddChartModal = true"
|
||||
/>
|
||||
<Button v-if="editing" :label="__('Cancel')" @click="cancel" />
|
||||
<Button
|
||||
v-if="editing"
|
||||
variant="solid"
|
||||
@ -117,9 +124,14 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<AddChartModal
|
||||
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 LucidePenLine from '~icons/lucide/pen-line'
|
||||
import DashboardGrid from '@/components/Dashboard/DashboardGrid.vue'
|
||||
@ -136,7 +148,7 @@ import {
|
||||
Dropdown,
|
||||
Tooltip,
|
||||
} from 'frappe-ui'
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ref, reactive, computed, provide } from 'vue'
|
||||
|
||||
const { users, getUser, isManager, isAdmin } = usersStore()
|
||||
|
||||
@ -145,6 +157,7 @@ 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(),
|
||||
@ -230,10 +243,9 @@ const dashboardItems = createResource({
|
||||
auto: true,
|
||||
})
|
||||
|
||||
function save() {
|
||||
// Implement save logic here
|
||||
editing.value = false
|
||||
}
|
||||
provide('fromDate', fromDate)
|
||||
provide('toDate', toDate)
|
||||
provide('filters', filters)
|
||||
|
||||
function cancel() {
|
||||
editing.value = false
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user