attempt chart component
This commit is contained in:
parent
6025a9890a
commit
80aae6f09b
@ -25,27 +25,31 @@ def get_client_status_counts(weekly=False, week_start_date=None, week_end_date=N
|
||||
|
||||
|
||||
onsite_meeting_scheduled_status_counts = {
|
||||
"Not Started": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled_status", "Not Started")),
|
||||
"In Progress": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled_status", "In Progress")),
|
||||
"Completed": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled_status", "Completed"))
|
||||
"label": "On-Site Meeting Scheduled",
|
||||
"not_started": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "Not Started")),
|
||||
"in_progress": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "In Progress")),
|
||||
"completed": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "Completed"))
|
||||
}
|
||||
|
||||
estimate_sent_status_counts = {
|
||||
"Not Started": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "Not Started")),
|
||||
"In Progress": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "In Progress")),
|
||||
"Completed": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "Completed"))
|
||||
"label": "Estimate Sent",
|
||||
"not_started": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "Not Started")),
|
||||
"in_progress": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "In Progress")),
|
||||
"completed": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "Completed"))
|
||||
}
|
||||
|
||||
job_status_counts = {
|
||||
"Not Started": frappe.db.count("Address", filters=get_filters("custom_job_status", "Not Started")),
|
||||
"In Progress": frappe.db.count("Address", filters=get_filters("custom_job_status", "In Progress")),
|
||||
"Completed": frappe.db.count("Address", filters=get_filters("custom_job_status", "Completed"))
|
||||
"label": "Job Status",
|
||||
"not_started": frappe.db.count("Address", filters=get_filters("custom_job_status", "Not Started")),
|
||||
"in_progress": frappe.db.count("Address", filters=get_filters("custom_job_status", "In Progress")),
|
||||
"completed": frappe.db.count("Address", filters=get_filters("custom_job_status", "Completed"))
|
||||
}
|
||||
|
||||
payment_received_status_counts = {
|
||||
"Not Started": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "Not Started")),
|
||||
"In Progress": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "In Progress")),
|
||||
"Completed": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "Completed"))
|
||||
"label": "Payment Received",
|
||||
"not_started": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "Not Started")),
|
||||
"in_progress": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "In Progress")),
|
||||
"completed": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "Completed"))
|
||||
}
|
||||
|
||||
status_dicts = [
|
||||
@ -54,18 +58,31 @@ def get_client_status_counts(weekly=False, week_start_date=None, week_end_date=N
|
||||
job_status_counts,
|
||||
payment_received_status_counts
|
||||
]
|
||||
categories = []
|
||||
for status_dict in status_dicts:
|
||||
category = {
|
||||
"label": status_dict["label"],
|
||||
"statuses": [
|
||||
{
|
||||
"color": "red",
|
||||
"label": "Not Started",
|
||||
"count": status_dict["not_started"]
|
||||
},
|
||||
{
|
||||
"color": "yellow",
|
||||
"label": "In Progress",
|
||||
"count": status_dict["in_progress"]
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
"label": "Completed",
|
||||
"count": status_dict["completed"]
|
||||
}
|
||||
]
|
||||
}
|
||||
categories.append(category)
|
||||
|
||||
return {
|
||||
"totals": {
|
||||
"not_started": get_status_total(status_dicts, "Not Started"),
|
||||
"in_progress": get_status_total(status_dicts, "In Progress"),
|
||||
"completed": get_status_total(status_dicts, "Completed")
|
||||
},
|
||||
"onsite_meeting_scheduled_status": onsite_meeting_scheduled_status_counts,
|
||||
"estimate_sent_status": estimate_sent_status_counts,
|
||||
"job_status": job_status_counts,
|
||||
"payment_received_status": payment_received_status_counts
|
||||
}
|
||||
return categories
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_clients(options):
|
||||
|
||||
30
frontend/package-lock.json
generated
30
frontend/package-lock.json
generated
@ -12,10 +12,12 @@
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@primeuix/themes": "^1.2.5",
|
||||
"axios": "^1.12.2",
|
||||
"chart.js": "^4.5.1",
|
||||
"frappe-ui": "^0.1.205",
|
||||
"pinia": "^3.0.3",
|
||||
"primevue": "^4.4.1",
|
||||
"vue": "^3.5.22",
|
||||
"vue-chartjs": "^5.3.3",
|
||||
"vue-router": "^4.6.3",
|
||||
"vuetify": "^3.10.7"
|
||||
},
|
||||
@ -738,6 +740,12 @@
|
||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@kurkle/color": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mdi/font": {
|
||||
"version": "7.4.47",
|
||||
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz",
|
||||
@ -2351,6 +2359,18 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
|
||||
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@ -4596,6 +4616,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-chartjs": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.3.tgz",
|
||||
"integrity": "sha512-jqxtL8KZ6YJ5NTv6XzrzLS7osyegOi28UGNZW0h9OkDL7Sh1396ht4Dorh04aKrl2LiSalQ84WtqiG0RIJb0tA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"chart.js": "^4.1.1",
|
||||
"vue": "^3.0.0-0 || ^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.3.tgz",
|
||||
|
||||
@ -13,10 +13,12 @@
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@primeuix/themes": "^1.2.5",
|
||||
"axios": "^1.12.2",
|
||||
"chart.js": "^4.5.1",
|
||||
"frappe-ui": "^0.1.205",
|
||||
"pinia": "^3.0.3",
|
||||
"primevue": "^4.4.1",
|
||||
"vue": "^3.5.22",
|
||||
"vue-chartjs": "^5.3.3",
|
||||
"vue-router": "^4.6.3",
|
||||
"vuetify": "^3.10.7"
|
||||
},
|
||||
|
||||
@ -6,7 +6,7 @@ const FRAPPE_UPSERT_CLIENT_METHOD = "custom_ui.api.db.upsert_client";
|
||||
const FRAPPE_UPSERT_ESTIMATE_METHOD = "custom_ui.api.db.upsert_estimate";
|
||||
const FRAPPE_UPSERT_JOB_METHOD = "custom_ui.api.db.upsert_job";
|
||||
const FRAPPE_UPSERT_INVOICE_METHOD = "custom_ui.api.db.upsert_invoice";
|
||||
const FRAPPE_GET_STATUS_COUNTS_METHOD = "custom_ui.api.db.get_client_status_counts";
|
||||
const FRAPPE_GET_CLIENT_STATUS_COUNTS_METHOD = "custom_ui.api.db.get_client_status_counts";
|
||||
|
||||
class Api {
|
||||
static async request(frappeMethod, args = {}) {
|
||||
@ -29,8 +29,8 @@ class Api {
|
||||
}
|
||||
}
|
||||
|
||||
static async getStatusCounts() {
|
||||
return;
|
||||
static async getClientStatusCounts(params = {}) {
|
||||
return await this.request(FRAPPE_GET_CLIENT_STATUS_COUNTS_METHOD, params);
|
||||
}
|
||||
|
||||
static async getClientDetails(options = {}) {
|
||||
|
||||
1076
frontend/src/components/common/StatusChart.vue
Normal file
1076
frontend/src/components/common/StatusChart.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,16 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<H2>Client Contact List</H2>
|
||||
|
||||
<!-- Status Chart Section -->
|
||||
<div class="chart-section">
|
||||
<StatusChart
|
||||
:statusData="statusCounts"
|
||||
:onWeekChange="handleWeekChange"
|
||||
:loading="chartLoading"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="filter-container" class="filter-container">
|
||||
<button @click="onClick" id="add-customer-button" class="interaction-button">
|
||||
Add
|
||||
@ -19,8 +29,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref, watch, computed } from "vue";
|
||||
import DataTable from "../common/DataTable.vue";
|
||||
import StatusChart from "../common/StatusChart.vue";
|
||||
import Api from "../../api";
|
||||
import { FilterMatchMode } from "@primevue/core";
|
||||
import { useLoadingStore } from "../../stores/loading";
|
||||
@ -36,12 +47,67 @@ const modalStore = useModalStore();
|
||||
const tableData = ref([]);
|
||||
const totalRecords = ref(0);
|
||||
const isLoading = ref(false);
|
||||
const statusCounts = ref({}); // Start with empty object
|
||||
const currentWeekParams = ref({});
|
||||
const chartLoading = ref(true); // Start with loading state
|
||||
|
||||
// Computed property to get current filters for the chart
|
||||
const currentFilters = computed(() => {
|
||||
return filtersStore.getTableFilters("clients");
|
||||
});
|
||||
|
||||
const onClick = () => {
|
||||
//frappe.new_doc("Customer");
|
||||
modalStore.openCreateClient();
|
||||
};
|
||||
|
||||
// Handle week change from chart
|
||||
const handleWeekChange = async (weekParams) => {
|
||||
console.log("handleWeekChange called with:", weekParams);
|
||||
currentWeekParams.value = weekParams;
|
||||
await refreshStatusCounts();
|
||||
};
|
||||
|
||||
// Refresh status counts with current week and filters
|
||||
const refreshStatusCounts = async () => {
|
||||
chartLoading.value = true;
|
||||
try {
|
||||
let params = {};
|
||||
|
||||
// Only apply weekly filtering if weekParams is provided (not null)
|
||||
if (currentWeekParams.value) {
|
||||
params = {
|
||||
weekly: true,
|
||||
weekStartDate: currentWeekParams.value.weekStartDate,
|
||||
weekEndDate: currentWeekParams.value.weekEndDate,
|
||||
};
|
||||
console.log("Using weekly filter:", params);
|
||||
} else {
|
||||
// No weekly filtering - get all time data
|
||||
params = {
|
||||
weekly: false,
|
||||
};
|
||||
console.log("Using all-time data (no weekly filter)");
|
||||
}
|
||||
|
||||
// Add current filters to the params
|
||||
const currentFilters = filtersStore.getTableFilters("clients");
|
||||
if (currentFilters && Object.keys(currentFilters).length > 0) {
|
||||
params.filters = currentFilters;
|
||||
}
|
||||
|
||||
const response = await Api.getClientStatusCounts(params);
|
||||
statusCounts.value = response || {};
|
||||
|
||||
console.log("Status counts updated:", statusCounts.value);
|
||||
} catch (error) {
|
||||
console.error("Error refreshing status counts:", error);
|
||||
statusCounts.value = {};
|
||||
} finally {
|
||||
chartLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const filters = {
|
||||
addressTitle: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||
};
|
||||
@ -78,7 +144,6 @@ const handleLazyLoad = async (event) => {
|
||||
|
||||
// Get sorting information from filters store first (needed for cache key)
|
||||
const sorting = filtersStore.getTableSorting("clients");
|
||||
console.log("Current sorting state:", sorting);
|
||||
|
||||
// Get pagination parameters
|
||||
const paginationParams = {
|
||||
@ -176,8 +241,15 @@ const handleLazyLoad = async (event) => {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
// Watch for filters change to update status counts
|
||||
watch(
|
||||
() => filtersStore.getTableFilters("clients"),
|
||||
async () => {
|
||||
await refreshStatusCounts();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// Load initial data
|
||||
onMounted(async () => {
|
||||
// Initialize pagination and filters
|
||||
paginationStore.initializeTablePagination("clients", { rows: 10 });
|
||||
@ -189,6 +261,9 @@ onMounted(async () => {
|
||||
const initialFilters = filtersStore.getTableFilters("clients");
|
||||
const initialSorting = filtersStore.getTableSorting("clients");
|
||||
|
||||
// Don't load initial status counts here - let the chart component handle it
|
||||
// The chart will emit the initial week parameters and trigger refreshStatusCounts
|
||||
|
||||
await handleLazyLoad({
|
||||
page: initialPagination.page,
|
||||
rows: initialPagination.rows,
|
||||
@ -203,4 +278,28 @@ onMounted(async () => {
|
||||
.page-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.interaction-button {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.interaction-button:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1710,7 +1710,6 @@ class DataUtils {
|
||||
acc[snakeKey] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
console.log("DEBUG: toSnakeCaseObject -> newObj", newObj);
|
||||
return newObj;
|
||||
}
|
||||
|
||||
@ -1730,7 +1729,6 @@ class DataUtils {
|
||||
acc[camelKey] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
console.log("DEBUG: toCamelCaseObject -> newObj", newObj);
|
||||
return newObj;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user