moving towards real data

This commit is contained in:
Casey Wittrock 2025-11-06 13:00:19 -06:00
parent ac3c05cb78
commit 40c4a5a37f
8 changed files with 303 additions and 84 deletions

View File

@ -1,5 +1,58 @@
import frappe, json import frappe, json
@frappe.whitelist()
def get_clients(options):
options = json.loads(options)
defaultOptions = {
"fields": ["*"],
"filters": {},
"sorting": {},
"page": 1,
"page_size": 10
}
options = {**defaultOptions, **options}
clients = []
count = frappe.db.count("Address", filters=options["filters"])
print("DEBUG: Total addresses count:", count)
addresses = frappe.db.get_all(
"Address",
fields=options["fields"],
filters=options["filters"],
limit=options["page_size"],
start=(options["page"] - 1) * options["page_size"],
order_by=(options["sorting"])
)
for address in addresses:
client = {}
print("DEBUG: Processing address:", address)
quotations = frappe.db.get_all(
"Quotation",
fields=["*"],
filters={"custom_installation_address": address["name"]}
)
jobs = frappe.db.get_all("Project",
fields=["*"],
filters={"custom_installation_address": address["name"],
"project_template": "SNW Install"})
client["address"] = address
client["on_site_meetings"] = []
client["jobs"] = jobs
client["quotations"] = quotations
clients.append(client)
return {
"count": count,
"page": options["page"],
"page_size": options["page_size"],
"clients": clients
}
@frappe.whitelist() @frappe.whitelist()
def upsert_client(data): def upsert_client(data):
data = json.loads(data) data = json.loads(data)
@ -15,17 +68,14 @@ def upsert_client(data):
"customer_name": data.get("customer_name"), "customer_name": data.get("customer_name"),
"customer_type": data.get("customer_type") "customer_type": data.get("customer_type")
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True)
customer = customer_doc.name
else: else:
customer_doc = frappe.get_doc("Customer", customer) customer_doc = frappe.get_doc("Customer", customer)
print("Customer:", customer_doc.as_dict())
filters = { filters = {
"address_line1": data.get("address_line1"), "address_title": data.get("address_title"),
"city": data.get("city"),
"state": data.get("state"),
"country": "US",
"pincode": data.get("pincode")
} }
existing_address = frappe.db.exists("Address", filters) existing_address = frappe.db.exists("Address", filters)
print("Existing address check:", existing_address)
if existing_address: if existing_address:
frappe.throw(f"Address already exists for customer {data.get('customer_name')}.", frappe.ValidationError) frappe.throw(f"Address already exists for customer {data.get('customer_name')}.", frappe.ValidationError)
address_doc = frappe.get_doc({ address_doc = frappe.get_doc({
@ -33,17 +83,19 @@ def upsert_client(data):
"address_line1": data.get("address_line1"), "address_line1": data.get("address_line1"),
"city": data.get("city"), "city": data.get("city"),
"state": data.get("state"), "state": data.get("state"),
"country": "US", "country": "United States",
"address_title": data.get("address_title"),
"pincode": data.get("pincode"), "pincode": data.get("pincode"),
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True)
link = { link = {
"link_doctype": "Customer", "link_doctype": "Customer",
"link_name": customer "link_name": customer_doc.name
} }
address_doc.append("links", link) address_doc.append("links", link)
address_doc.save(ignore_permissions=True) address_doc.save(ignore_permissions=True)
return { return {
"customer": customer.name, "customer": customer_doc,
"address": address_doc.name "address": address_doc,
"success": True
} }

View File

@ -7,13 +7,15 @@ const FRAPPE_UPSERT_CLIENT_METHOD = "custom_ui.api.db.upsert_client";
class Api { class Api {
static async request(frappeMethod, args = {}) { static async request(frappeMethod, args = {}) {
args = DataUtils.toSnakeCaseObject(args);
try { try {
const response = await frappe.call({ let response = await frappe.call({
method: frappeMethod, method: frappeMethod,
args: { args: {
...args, ...args,
}, },
}); });
response = DataUtils.toCamelCaseObject(response);
console.log("DEBUG: API - Request Response: ", response); console.log("DEBUG: API - Request Response: ", response);
return response.message; return response.message;
} catch (error) { } catch (error) {
@ -24,39 +26,26 @@ class Api {
} }
static async getClientDetails(options = {}) { static async getClientDetails(options = {}) {
const { return await this.request("custom_ui.api.db.get_clients", { options });
forTable = true,
page = 0,
pageSize = 10,
filters = {},
sortField = null,
sortOrder = null,
searchTerm = null,
} = options;
console.log("DEBUG: API - getClientDetails called with options:", options); // Handle fullName filter by searching in multiple fields
// Since fullName is constructed from customer_name + address_line1 + city + state,
// Build filters for the Address query // we need to search across these fields
let addressFilters = {}; if (filters.addressTitle && filters.addressTitle.value) {
const searchTerm = filters.addressTitle.value;
// Add search functionality across multiple fields // Search in address fields - this is a simplified approach
if (searchTerm) { // In a real implementation, you'd want to join with Customer table and search across all fields
// Note: This is a simplified version. You might want to implement
// full-text search on the backend for better performance
addressFilters.address_line1 = ["like", `%${searchTerm}%`]; addressFilters.address_line1 = ["like", `%${searchTerm}%`];
} }
// Add any custom filters // Add any other custom filters
Object.keys(filters).forEach((key) => { Object.keys(filters).forEach((key) => {
if (filters[key] && filters[key].value) { if (filters[key] && filters[key].value && key !== "fullName") {
// Map frontend filter names to backend field names if needed // Map other frontend filter names to backend field names if needed
switch (key) { switch (
case "fullName": key
// This will need special handling since fullName is constructed
// For now, we'll search in address_line1 or city
addressFilters.address_line1 = ["like", `%${filters[key].value}%`];
break;
// Add other filter mappings as needed // Add other filter mappings as needed
) {
} }
} }
}); });
@ -161,20 +150,15 @@ class Api {
}); });
} }
// Apply client-side filtering for constructed fields like fullName // Since we're applying filters at the database level, use the fetched data as-is
let filteredData = data; let filteredData = data;
if (filters.fullName && filters.fullName.value && forTable) {
const searchValue = filters.fullName.value.toLowerCase();
filteredData = data.filter((item) =>
item.fullName.toLowerCase().includes(searchValue),
);
}
console.log("DEBUG: API - Fetched Client Details:", { console.log("DEBUG: API - Fetched Client Details:", {
total: totalCount, total: totalCount,
page: page, page: page,
pageSize: pageSize, pageSize: pageSize,
returned: filteredData.length, returned: filteredData.length,
filtersApplied: Object.keys(addressFilters).length > 0,
}); });
// Return paginated response with metadata // Return paginated response with metadata
@ -198,7 +182,7 @@ class Api {
// ...job, // ...job,
// stepProgress: DataUtils.calculateStepProgress(job.steps), // stepProgress: DataUtils.calculateStepProgress(job.steps),
//})); //}));
const projects = await this.getDocsList("Project") const projects = await this.getDocsList("Project");
const data = []; const data = [];
for (let prj of projects) { for (let prj of projects) {
let project = await this.getDetailedDoc("Project", prj.name); let project = await this.getDetailedDoc("Project", prj.name);
@ -207,7 +191,7 @@ class Api {
customInstallationAddress: project.custom_installation_address, customInstallationAddress: project.custom_installation_address,
customer: project.customer, customer: project.customer,
status: project.status, status: project.status,
percentComplete: project.percent_complete percentComplete: project.percent_complete,
}; };
data.push(tableRow); data.push(tableRow);
} }
@ -249,8 +233,7 @@ class Api {
const { page = 0, pageSize = 10, sortField = null, sortOrder = null } = paginationParams; const { page = 0, pageSize = 10, sortField = null, sortOrder = null } = paginationParams;
const options = { const options = {
forTable: true, page: page + 1,
page,
pageSize, pageSize,
filters, filters,
sortField, sortField,
@ -260,8 +243,7 @@ class Api {
const result = await this.getClientDetails(options); const result = await this.getClientDetails(options);
return { return {
data: result.data, ...result,
totalRecords: result.pagination.total,
}; };
} }
@ -277,10 +259,13 @@ class Api {
const docs = await frappe.db.get_list(doctype, { const docs = await frappe.db.get_list(doctype, {
fields, fields,
filters, filters,
start: page * pageLength, start: start,
limit: pageLength, limit: pageLength,
}); });
console.log(`DEBUG: API - Fetched ${doctype} list: `, docs); console.log(
`DEBUG: API - Fetched ${doctype} list (page ${page + 1}, start ${start}): `,
docs,
);
return docs; return docs;
} }

View File

@ -34,7 +34,7 @@ const createButtons = ref([
{ {
label: "Client", label: "Client",
command: () => { command: () => {
modalStore.openCreateClient(); modalStore.openModal("createClient");
}, },
}, },
{ {

View File

@ -1,6 +1,6 @@
<template lang="html"> <template lang="html">
<!-- Filter Controls Panel --> <!-- Filter Controls Panel -->
<div v-if="lazy && hasFilters" class="filter-controls-panel mb-3 p-3 bg-light rounded"> <div v-if="hasFilters" class="filter-controls-panel mb-3 p-3 bg-light rounded">
<div class="row g-3 align-items-end"> <div class="row g-3 align-items-end">
<div v-for="col in filterableColumns" :key="col.fieldName" class="col-md-4 col-lg-3"> <div v-for="col in filterableColumns" :key="col.fieldName" class="col-md-4 col-lg-3">
<label :for="`filter-${col.fieldName}`" class="form-label small fw-semibold"> <label :for="`filter-${col.fieldName}`" class="form-label small fw-semibold">
@ -41,7 +41,7 @@
</div> </div>
<!-- Page Jump Controls --> <!-- Page Jump Controls -->
<div v-if="lazy && totalPages > 1" class="page-controls-panel mb-3 p-2 bg-light rounded"> <div v-if="totalPages > 1" class="page-controls-panel mb-3 p-2 bg-light rounded">
<div class="row g-3 align-items-center"> <div class="row g-3 align-items-center">
<div class="col-auto"> <div class="col-auto">
<small class="text-muted">Quick navigation:</small> <small class="text-muted">Quick navigation:</small>
@ -69,12 +69,13 @@
</div> </div>
<DataTable <DataTable
ref="dataTableRef"
:value="data" :value="data"
:rowsPerPageOptions="[5, 10, 20, 50]" :rowsPerPageOptions="[5, 10, 20, 50]"
:paginator="true" :paginator="true"
:rows="currentRows" :rows="currentRows"
:lazy="lazy" :lazy="lazy"
:totalRecords="lazy ? totalRecords : data.length" :totalRecords="lazy ? totalRecords : getFilteredDataLength"
@page="handlePage" @page="handlePage"
@sort="handleSort" @sort="handleSort"
@filter="handleFilter" @filter="handleFilter"
@ -201,6 +202,11 @@ const props = defineProps({
type: Number, type: Number,
default: 0, default: 0,
}, },
// Total filtered records for non-lazy tables (when server-side filtering is used)
totalFilteredRecords: {
type: Number,
default: null,
},
// Custom pagination event handler // Custom pagination event handler
onLazyLoad: { onLazyLoad: {
type: Function, type: Function,
@ -208,7 +214,14 @@ const props = defineProps({
}, },
}); });
const emit = defineEmits(["rowClick", "lazy-load", "page-change", "sort-change", "filter-change"]); const emit = defineEmits([
"rowClick",
"lazy-load",
"page-change",
"sort-change",
"filter-change",
"data-refresh",
]);
// Computed loading state that considers both prop and global store // Computed loading state that considers both prop and global store
const loading = computed(() => { const loading = computed(() => {
@ -292,6 +305,8 @@ watch(
const selectedRows = ref(); const selectedRows = ref();
const pendingFilters = ref({}); const pendingFilters = ref({});
const selectedPageJump = ref(""); const selectedPageJump = ref("");
const dataTableRef = ref();
const currentPageState = ref({ page: 0, first: 0 }); // Track current page for non-lazy tables
// Computed properties for filtering // Computed properties for filtering
const filterableColumns = computed(() => { const filterableColumns = computed(() => {
@ -319,8 +334,12 @@ const hasFilterChanges = computed(() => {
}); });
const totalPages = computed(() => { const totalPages = computed(() => {
if (!props.lazy) return 0; if (props.lazy) {
return paginationStore.getTotalPages(props.tableName); return paginationStore.getTotalPages(props.tableName);
}
// For non-lazy tables, calculate based on current filtered data
const filteredDataLength = getFilteredDataLength.value;
return Math.ceil(filteredDataLength / currentRows.value) || 1;
}); });
// Initialize pending filters from store // Initialize pending filters from store
@ -331,6 +350,17 @@ onMounted(() => {
}); });
}); });
// Computed property to get filtered data length for non-lazy tables
const getFilteredDataLength = computed(() => {
if (props.lazy) {
return props.totalRecords;
}
// For non-lazy tables, use totalFilteredRecords if provided (server-side filtering),
// otherwise fall back to data length (client-side filtering)
return props.totalFilteredRecords !== null ? props.totalFilteredRecords : props.data.length;
});
// Filter management methods // Filter management methods
const applyFilters = () => { const applyFilters = () => {
// Update store with pending filter values // Update store with pending filter values
@ -344,10 +374,16 @@ const applyFilters = () => {
); );
}); });
// For lazy tables, reset to first page and trigger reload // Reset to first page when filters change (for both lazy and non-lazy)
currentPageState.value = { page: 0, first: 0 };
// For both lazy and non-lazy tables, trigger reload with new filters
if (props.lazy) { if (props.lazy) {
paginationStore.resetToFirstPage(props.tableName); paginationStore.resetToFirstPage(props.tableName);
triggerLazyLoad(); triggerLazyLoad();
} else {
// For non-lazy tables, also trigger a reload to get fresh data from server
triggerDataRefresh();
} }
}; };
@ -360,10 +396,16 @@ const clearFilters = () => {
// Clear store filters // Clear store filters
filtersStore.clearTableFilters(props.tableName); filtersStore.clearTableFilters(props.tableName);
// For lazy tables, reset to first page and trigger reload // Reset to first page when filters are cleared (for both lazy and non-lazy)
currentPageState.value = { page: 0, first: 0 };
// For both lazy and non-lazy tables, trigger reload with cleared filters
if (props.lazy) { if (props.lazy) {
paginationStore.resetToFirstPage(props.tableName); paginationStore.resetToFirstPage(props.tableName);
triggerLazyLoad(); triggerLazyLoad();
} else {
// For non-lazy tables, also trigger a reload to get fresh data from server
triggerDataRefresh();
} }
}; };
@ -383,21 +425,53 @@ const getActiveFiltersText = () => {
// Page navigation methods // Page navigation methods
const jumpToPage = () => { const jumpToPage = () => {
if (selectedPageJump.value && props.lazy) { if (selectedPageJump.value) {
const pageNumber = parseInt(selectedPageJump.value) - 1; // Convert to 0-based const pageNumber = parseInt(selectedPageJump.value) - 1; // Convert to 0-based
paginationStore.setPage(props.tableName, pageNumber);
triggerLazyLoad(); if (props.lazy) {
paginationStore.setPage(props.tableName, pageNumber);
triggerLazyLoad();
} else {
// For non-lazy tables, update our internal state
// The DataTable will handle the actual pagination
currentPageState.value = {
page: pageNumber,
first: pageNumber * currentRows.value,
};
}
} }
selectedPageJump.value = ""; // Reset selection selectedPageJump.value = ""; // Reset selection
}; };
const getPageInfo = () => { const getPageInfo = () => {
return paginationStore.getPageInfo(props.tableName); if (props.lazy) {
return paginationStore.getPageInfo(props.tableName);
}
// For non-lazy tables, calculate based on current page state and filtered data
const filteredTotal = getFilteredDataLength.value;
const rows = currentRows.value;
const currentFirst = currentPageState.value.first;
const start = filteredTotal > 0 ? currentFirst + 1 : 0;
const end = Math.min(filteredTotal, currentFirst + rows);
return {
start,
end,
total: filteredTotal,
};
}; };
// Handle pagination events // Handle pagination events
const handlePage = (event) => { const handlePage = (event) => {
console.log("Page event:", event); console.log("Page event:", event);
// Update current page state for both lazy and non-lazy tables
currentPageState.value = {
page: event.page,
first: event.first,
};
if (props.lazy) { if (props.lazy) {
paginationStore.updateTablePagination(props.tableName, { paginationStore.updateTablePagination(props.tableName, {
page: event.page, page: event.page,
@ -424,8 +498,11 @@ const handleSort = (event) => {
// Handle filter events // Handle filter events
const handleFilter = (event) => { const handleFilter = (event) => {
console.log("Filter event:", event); console.log("Filter event:", event);
// Reset to first page when filters change (for both lazy and non-lazy)
currentPageState.value = { page: 0, first: 0 };
if (props.lazy) { if (props.lazy) {
// Reset to first page when filters change
paginationStore.resetToFirstPage(props.tableName); paginationStore.resetToFirstPage(props.tableName);
triggerLazyLoad(); triggerLazyLoad();
} }
@ -453,6 +530,37 @@ const triggerLazyLoad = () => {
} }
}; };
// Trigger data refresh for non-lazy tables when filters change
const triggerDataRefresh = () => {
const filters = filtersStore.getTableFilters(props.tableName);
const refreshEvent = {
filters: filters,
page: currentPageState.value.page,
first: currentPageState.value.first,
rows: currentRows.value,
};
console.log("Triggering data refresh with:", refreshEvent);
emit("data-refresh", refreshEvent);
};
const handleFilterInput = (fieldName, value, filterCallback) => {
// Update the filter store
filtersStore.updateTableFilter(props.tableName, fieldName, value, FilterMatchMode.CONTAINS);
// Call the PrimeVue callback to update the filter
if (filterCallback) {
filterCallback();
}
// For non-lazy tables, also trigger a data refresh when individual filters change
if (!props.lazy) {
// Reset to first page when filters change
currentPageState.value = { page: 0, first: 0 };
triggerDataRefresh();
}
};
const getBadgeColor = (status) => { const getBadgeColor = (status) => {
console.log("DEBUG: - getBadgeColor status", status); console.log("DEBUG: - getBadgeColor status", status);
switch (status?.toLowerCase()) { switch (status?.toLowerCase()) {
@ -498,6 +606,8 @@ defineExpose({
refresh: () => { refresh: () => {
if (props.lazy) { if (props.lazy) {
triggerLazyLoad(); triggerLazyLoad();
} else {
triggerDataRefresh();
} }
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<form @submit.prevent="handleSubmit" class="dynamic-form"> <form class="dynamic-form">
<div class="form-container"> <div class="form-container">
<div class="form-row"> <div class="form-row">
<div <div
@ -372,11 +372,12 @@
<div v-if="showSubmitButton || showCancelButton" class="form-buttons"> <div v-if="showSubmitButton || showCancelButton" class="form-buttons">
<Button <Button
v-if="showSubmitButton" v-if="showSubmitButton"
type="submit" type="button"
:label="submitButtonText" :label="submitButtonText"
:loading="isLoading" :loading="isLoading"
:disabled="isFormDisabled" :disabled="isFormDisabled"
severity="primary" severity="primary"
@click="handleSubmit"
/> />
<Button <Button
v-if="showCancelButton" v-if="showCancelButton"
@ -839,9 +840,15 @@ const validateForm = () => {
// Handle form submission // Handle form submission
const handleSubmit = async () => { const handleSubmit = async () => {
// Prevent double submission
if (isSubmitting.value) {
console.warn("Form: submission already in progress, ignoring duplicate submission");
return;
}
// Always validate on submit if enabled // Always validate on submit if enabled
if (props.validateOnSubmit && !validateForm()) { if (props.validateOnSubmit && !validateForm()) {
console.warn("Form validation failed on submit"); console.warn("Form: validation failed on submit");
return; return;
} }
@ -849,17 +856,23 @@ const handleSubmit = async () => {
try { try {
const formData = getCurrentFormData(); const formData = getCurrentFormData();
console.log("Form: emitting submit event with data:", formData);
// Only emit the submit event - let parent handle the actual submission
// This prevents the dual submission pathway issue
emit("submit", formData);
// Call onSubmit prop if provided (for backward compatibility)
if (props.onSubmit && typeof props.onSubmit === "function") { if (props.onSubmit && typeof props.onSubmit === "function") {
await props.onSubmit(formData); await props.onSubmit(formData);
} }
emit("submit", formData);
} catch (error) { } catch (error) {
console.error("Form submission error:", error); console.error("Form: submission error:", error);
} finally { // Reset isSubmitting on error so user can retry
isSubmitting.value = false; isSubmitting.value = false;
} }
// Note: Don't reset isSubmitting.value here in finally - let parent control this
// The parent should call the exposed stopLoading() method when done
}; };
// Handle cancel action // Handle cancel action

View File

@ -14,12 +14,15 @@
</div> </div>
<Form <Form
ref="formRef"
:fields="formFields" :fields="formFields"
:form-data="formData" :form-data="formData"
:show-cancel-button="true" :show-cancel-button="true"
:validate-on-change="false" :validate-on-change="false"
:validate-on-blur="true" :validate-on-blur="true"
:validate-on-submit="true" :validate-on-submit="true"
:loading="isSubmitting"
:disable-on-loading="true"
submit-button-text="Create Client" submit-button-text="Create Client"
cancel-button-text="Cancel" cancel-button-text="Cancel"
@submit="handleSubmit" @submit="handleSubmit"
@ -41,6 +44,8 @@ const modalStore = useModalStore();
// Modal visibility computed property // Modal visibility computed property
const isVisible = computed(() => modalStore.isModalOpen("createClient")); const isVisible = computed(() => modalStore.isModalOpen("createClient"));
const customerNames = ref([]); const customerNames = ref([]);
// Form reference for controlling its state
const formRef = ref(null);
// Form data // Form data
const formData = reactive({ const formData = reactive({
customertype: "", customertype: "",
@ -78,6 +83,16 @@ const modalOptions = {
// Form field definitions // Form field definitions
const formFields = computed(() => [ const formFields = computed(() => [
{
name: "addressTitle",
label: "Address Title",
type: "text",
required: true,
placeholder: "Enter address title",
helpText: "A short title to identify this address (e.g., Johnson Home, Johnson Office)",
cols: 12,
md: 6,
},
{ {
name: "customertype", name: "customertype",
label: "Client Type", label: "Client Type",
@ -318,15 +333,38 @@ function getStatusIcon(type) {
} }
} }
// Submission state to prevent double submission
const isSubmitting = ref(false);
// Handle form submission // Handle form submission
async function handleSubmit() { async function handleSubmit(formDataFromEvent) {
// Prevent double submission with detailed logging
if (isSubmitting.value) {
console.warn(
"CreateClientModal: Form submission already in progress, ignoring duplicate submission",
);
return;
}
console.log(
"CreateClientModal: Form submission started with data:",
formDataFromEvent || formData,
);
isSubmitting.value = true;
try { try {
showStatusMessage("Creating client...", "info"); showStatusMessage("Creating client...", "info");
// Convert form data to the expected format // Use the form data from the event if provided, otherwise use reactive formData
const dataToSubmit = formDataFromEvent || formData;
console.log("CreateClientModal: Calling API with data:", dataToSubmit);
// Call API to create client // Call API to create client
const response = await Api.createClient(formData); const response = await Api.createClient(dataToSubmit);
console.log("CreateClientModal: API response received:", response);
if (response && response.success) { if (response && response.success) {
showStatusMessage("Client created successfully!", "success"); showStatusMessage("Client created successfully!", "success");
@ -339,8 +377,15 @@ async function handleSubmit() {
throw new Error(response?.message || "Failed to create client"); throw new Error(response?.message || "Failed to create client");
} }
} catch (error) { } catch (error) {
console.error("Error creating client:", error); console.error("CreateClientModal: Error creating client:", error);
showStatusMessage(error.message || "Failed to create client. Please try again.", "error"); showStatusMessage(error.message || "Failed to create client. Please try again.", "error");
} finally {
isSubmitting.value = false;
// Also reset the Form component's internal submission state
if (formRef.value && formRef.value.stopLoading) {
formRef.value.stopLoading();
}
console.log("CreateClientModal: Form submission completed, isSubmitting reset to false");
} }
} }
@ -351,7 +396,7 @@ function handleCancel() {
// Handle modal close // Handle modal close
function handleClose() { function handleClose() {
modalStore.closeCreateClient(); modalStore.closeModal("createClient");
resetForm(); resetForm();
} }
@ -383,13 +428,9 @@ watch(isVisible, async () => {
if (isVisible.value) { if (isVisible.value) {
try { try {
const names = await Api.getCustomerNames(); const names = await Api.getCustomerNames();
console.log("Loaded customer names:", names);
customerNames.value = names; customerNames.value = names;
} catch (error) { } catch (error) {
console.error("Error loading customer names:", error); console.error("Error loading customer names:", error);
// Set some test data to debug if autocomplete works
customerNames.value = ["Test Customer 1", "Test Customer 2", "Another Client"];
console.log("Using test customer names:", customerNames.value);
} }
} }
}); });

View File

@ -90,6 +90,12 @@ const handleLazyLoad = async (event) => {
}); });
} }
// Clear cache when filters are active to ensure fresh data
const hasActiveFilters = Object.keys(filters).length > 0;
if (hasActiveFilters) {
paginationStore.clearTableCache("clients");
}
// Check cache first // Check cache first
const cachedData = paginationStore.getCachedPage( const cachedData = paginationStore.getCachedPage(
"clients", "clients",

View File

@ -1697,13 +1697,25 @@ class DataUtils {
static toSnakeCaseObject(obj) { static toSnakeCaseObject(obj) {
const newObj = Object.entries(obj).reduce((acc, [key, value]) => { const newObj = Object.entries(obj).reduce((acc, [key, value]) => {
const snakeKey = key.replace(/[A-Z]/g, "_$1").toLowerCase(); const snakeKey = key.replace(/[A-Z]/g, (match) => "_" + match.toLowerCase());
value = Object.prototype.toString.call(value) === "[object Object]" ? this.toSnakeCaseObject(value) : value;
acc[snakeKey] = value; acc[snakeKey] = value;
return acc; return acc;
}, {}); }, {});
console.log("DEBUG: toSnakeCaseObject -> newObj", newObj); console.log("DEBUG: toSnakeCaseObject -> newObj", newObj);
return newObj; return newObj;
} }
static toCamelCaseObject(obj) {
const newObj = Object.entries(obj).reduce((acc, [key, value]) => {
const camelKey = key.replace(/_([a-z])/g, (match, p1) => p1.toUpperCase());
value = Object.prototype.toString.call(value) === "[object Object]" ? this.toCamelCaseObject(value) : value;
acc[camelKey] = value;
return acc;
}, {});
console.log("DEBUG: toCamelCaseObject -> newObj", newObj);
return newObj;
}
} }
export default DataUtils; export default DataUtils;