attempt chart component

This commit is contained in:
Casey 2025-11-07 19:05:11 -06:00
parent 6025a9890a
commit 80aae6f09b
7 changed files with 1253 additions and 31 deletions

View file

@ -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",

View file

@ -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"
},

View file

@ -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 = {}) {

File diff suppressed because it is too large Load diff

View file

@ -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>

View file

@ -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;
}
}