diff --git a/custom_ui/api/db.py b/custom_ui/api/db.py index c2f7f6c..d84e81c 100644 --- a/custom_ui/api/db.py +++ b/custom_ui/api/db.py @@ -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): diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0abc594..24122ff 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index 0c67a5e..68ce6bb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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" }, diff --git a/frontend/src/api.js b/frontend/src/api.js index 9c19ffd..2349fe1 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -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 = {}) { diff --git a/frontend/src/components/common/StatusChart.vue b/frontend/src/components/common/StatusChart.vue new file mode 100644 index 0000000..73c75bd --- /dev/null +++ b/frontend/src/components/common/StatusChart.vue @@ -0,0 +1,1076 @@ + + + + + diff --git a/frontend/src/components/pages/Clients.vue b/frontend/src/components/pages/Clients.vue index 77f44fa..b79da30 100644 --- a/frontend/src/components/pages/Clients.vue +++ b/frontend/src/components/pages/Clients.vue @@ -1,6 +1,16 @@