moving towards real data
This commit is contained in:
parent
ac3c05cb78
commit
40c4a5a37f
@ -1,5 +1,58 @@
|
||||
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()
|
||||
def upsert_client(data):
|
||||
data = json.loads(data)
|
||||
@ -15,17 +68,14 @@ def upsert_client(data):
|
||||
"customer_name": data.get("customer_name"),
|
||||
"customer_type": data.get("customer_type")
|
||||
}).insert(ignore_permissions=True)
|
||||
customer = customer_doc.name
|
||||
else:
|
||||
customer_doc = frappe.get_doc("Customer", customer)
|
||||
print("Customer:", customer_doc.as_dict())
|
||||
filters = {
|
||||
"address_line1": data.get("address_line1"),
|
||||
"city": data.get("city"),
|
||||
"state": data.get("state"),
|
||||
"country": "US",
|
||||
"pincode": data.get("pincode")
|
||||
"address_title": data.get("address_title"),
|
||||
}
|
||||
existing_address = frappe.db.exists("Address", filters)
|
||||
print("Existing address check:", existing_address)
|
||||
if existing_address:
|
||||
frappe.throw(f"Address already exists for customer {data.get('customer_name')}.", frappe.ValidationError)
|
||||
address_doc = frappe.get_doc({
|
||||
@ -33,17 +83,19 @@ def upsert_client(data):
|
||||
"address_line1": data.get("address_line1"),
|
||||
"city": data.get("city"),
|
||||
"state": data.get("state"),
|
||||
"country": "US",
|
||||
"country": "United States",
|
||||
"address_title": data.get("address_title"),
|
||||
"pincode": data.get("pincode"),
|
||||
}).insert(ignore_permissions=True)
|
||||
link = {
|
||||
"link_doctype": "Customer",
|
||||
"link_name": customer
|
||||
"link_name": customer_doc.name
|
||||
}
|
||||
address_doc.append("links", link)
|
||||
address_doc.save(ignore_permissions=True)
|
||||
|
||||
return {
|
||||
"customer": customer.name,
|
||||
"address": address_doc.name
|
||||
"customer": customer_doc,
|
||||
"address": address_doc,
|
||||
"success": True
|
||||
}
|
||||
@ -7,13 +7,15 @@ const FRAPPE_UPSERT_CLIENT_METHOD = "custom_ui.api.db.upsert_client";
|
||||
|
||||
class Api {
|
||||
static async request(frappeMethod, args = {}) {
|
||||
args = DataUtils.toSnakeCaseObject(args);
|
||||
try {
|
||||
const response = await frappe.call({
|
||||
let response = await frappe.call({
|
||||
method: frappeMethod,
|
||||
args: {
|
||||
...args,
|
||||
},
|
||||
});
|
||||
response = DataUtils.toCamelCaseObject(response);
|
||||
console.log("DEBUG: API - Request Response: ", response);
|
||||
return response.message;
|
||||
} catch (error) {
|
||||
@ -24,39 +26,26 @@ class Api {
|
||||
}
|
||||
|
||||
static async getClientDetails(options = {}) {
|
||||
const {
|
||||
forTable = true,
|
||||
page = 0,
|
||||
pageSize = 10,
|
||||
filters = {},
|
||||
sortField = null,
|
||||
sortOrder = null,
|
||||
searchTerm = null,
|
||||
} = options;
|
||||
return await this.request("custom_ui.api.db.get_clients", { options });
|
||||
|
||||
console.log("DEBUG: API - getClientDetails called with options:", options);
|
||||
|
||||
// Build filters for the Address query
|
||||
let addressFilters = {};
|
||||
|
||||
// Add search functionality across multiple fields
|
||||
if (searchTerm) {
|
||||
// Note: This is a simplified version. You might want to implement
|
||||
// full-text search on the backend for better performance
|
||||
// Handle fullName filter by searching in multiple fields
|
||||
// Since fullName is constructed from customer_name + address_line1 + city + state,
|
||||
// we need to search across these fields
|
||||
if (filters.addressTitle && filters.addressTitle.value) {
|
||||
const searchTerm = filters.addressTitle.value;
|
||||
// Search in address fields - this is a simplified approach
|
||||
// In a real implementation, you'd want to join with Customer table and search across all fields
|
||||
addressFilters.address_line1 = ["like", `%${searchTerm}%`];
|
||||
}
|
||||
|
||||
// Add any custom filters
|
||||
// Add any other custom filters
|
||||
Object.keys(filters).forEach((key) => {
|
||||
if (filters[key] && filters[key].value) {
|
||||
// Map frontend filter names to backend field names if needed
|
||||
switch (key) {
|
||||
case "fullName":
|
||||
// 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;
|
||||
if (filters[key] && filters[key].value && key !== "fullName") {
|
||||
// Map other frontend filter names to backend field names if needed
|
||||
switch (
|
||||
key
|
||||
// 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;
|
||||
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:", {
|
||||
total: totalCount,
|
||||
page: page,
|
||||
pageSize: pageSize,
|
||||
returned: filteredData.length,
|
||||
filtersApplied: Object.keys(addressFilters).length > 0,
|
||||
});
|
||||
|
||||
// Return paginated response with metadata
|
||||
@ -198,7 +182,7 @@ class Api {
|
||||
// ...job,
|
||||
// stepProgress: DataUtils.calculateStepProgress(job.steps),
|
||||
//}));
|
||||
const projects = await this.getDocsList("Project")
|
||||
const projects = await this.getDocsList("Project");
|
||||
const data = [];
|
||||
for (let prj of projects) {
|
||||
let project = await this.getDetailedDoc("Project", prj.name);
|
||||
@ -207,7 +191,7 @@ class Api {
|
||||
customInstallationAddress: project.custom_installation_address,
|
||||
customer: project.customer,
|
||||
status: project.status,
|
||||
percentComplete: project.percent_complete
|
||||
percentComplete: project.percent_complete,
|
||||
};
|
||||
data.push(tableRow);
|
||||
}
|
||||
@ -249,8 +233,7 @@ class Api {
|
||||
const { page = 0, pageSize = 10, sortField = null, sortOrder = null } = paginationParams;
|
||||
|
||||
const options = {
|
||||
forTable: true,
|
||||
page,
|
||||
page: page + 1,
|
||||
pageSize,
|
||||
filters,
|
||||
sortField,
|
||||
@ -260,8 +243,7 @@ class Api {
|
||||
const result = await this.getClientDetails(options);
|
||||
|
||||
return {
|
||||
data: result.data,
|
||||
totalRecords: result.pagination.total,
|
||||
...result,
|
||||
};
|
||||
}
|
||||
|
||||
@ -277,10 +259,13 @@ class Api {
|
||||
const docs = await frappe.db.get_list(doctype, {
|
||||
fields,
|
||||
filters,
|
||||
start: page * pageLength,
|
||||
start: start,
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ const createButtons = ref([
|
||||
{
|
||||
label: "Client",
|
||||
command: () => {
|
||||
modalStore.openCreateClient();
|
||||
modalStore.openModal("createClient");
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template lang="html">
|
||||
<!-- 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 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">
|
||||
@ -41,7 +41,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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="col-auto">
|
||||
<small class="text-muted">Quick navigation:</small>
|
||||
@ -69,12 +69,13 @@
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
ref="dataTableRef"
|
||||
:value="data"
|
||||
:rowsPerPageOptions="[5, 10, 20, 50]"
|
||||
:paginator="true"
|
||||
:rows="currentRows"
|
||||
:lazy="lazy"
|
||||
:totalRecords="lazy ? totalRecords : data.length"
|
||||
:totalRecords="lazy ? totalRecords : getFilteredDataLength"
|
||||
@page="handlePage"
|
||||
@sort="handleSort"
|
||||
@filter="handleFilter"
|
||||
@ -201,6 +202,11 @@ const props = defineProps({
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
// Total filtered records for non-lazy tables (when server-side filtering is used)
|
||||
totalFilteredRecords: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
// Custom pagination event handler
|
||||
onLazyLoad: {
|
||||
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
|
||||
const loading = computed(() => {
|
||||
@ -292,6 +305,8 @@ watch(
|
||||
const selectedRows = ref();
|
||||
const pendingFilters = 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
|
||||
const filterableColumns = computed(() => {
|
||||
@ -319,8 +334,12 @@ const hasFilterChanges = computed(() => {
|
||||
});
|
||||
|
||||
const totalPages = computed(() => {
|
||||
if (!props.lazy) return 0;
|
||||
return paginationStore.getTotalPages(props.tableName);
|
||||
if (props.lazy) {
|
||||
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
|
||||
@ -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
|
||||
const applyFilters = () => {
|
||||
// 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) {
|
||||
paginationStore.resetToFirstPage(props.tableName);
|
||||
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
|
||||
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) {
|
||||
paginationStore.resetToFirstPage(props.tableName);
|
||||
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
|
||||
const jumpToPage = () => {
|
||||
if (selectedPageJump.value && props.lazy) {
|
||||
if (selectedPageJump.value) {
|
||||
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
|
||||
};
|
||||
|
||||
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
|
||||
const handlePage = (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) {
|
||||
paginationStore.updateTablePagination(props.tableName, {
|
||||
page: event.page,
|
||||
@ -424,8 +498,11 @@ const handleSort = (event) => {
|
||||
// Handle filter events
|
||||
const handleFilter = (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) {
|
||||
// Reset to first page when filters change
|
||||
paginationStore.resetToFirstPage(props.tableName);
|
||||
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) => {
|
||||
console.log("DEBUG: - getBadgeColor status", status);
|
||||
switch (status?.toLowerCase()) {
|
||||
@ -498,6 +606,8 @@ defineExpose({
|
||||
refresh: () => {
|
||||
if (props.lazy) {
|
||||
triggerLazyLoad();
|
||||
} else {
|
||||
triggerDataRefresh();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<form @submit.prevent="handleSubmit" class="dynamic-form">
|
||||
<form class="dynamic-form">
|
||||
<div class="form-container">
|
||||
<div class="form-row">
|
||||
<div
|
||||
@ -372,11 +372,12 @@
|
||||
<div v-if="showSubmitButton || showCancelButton" class="form-buttons">
|
||||
<Button
|
||||
v-if="showSubmitButton"
|
||||
type="submit"
|
||||
type="button"
|
||||
:label="submitButtonText"
|
||||
:loading="isLoading"
|
||||
:disabled="isFormDisabled"
|
||||
severity="primary"
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
<Button
|
||||
v-if="showCancelButton"
|
||||
@ -839,9 +840,15 @@ const validateForm = () => {
|
||||
|
||||
// Handle form submission
|
||||
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
|
||||
if (props.validateOnSubmit && !validateForm()) {
|
||||
console.warn("Form validation failed on submit");
|
||||
console.warn("Form: validation failed on submit");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -849,17 +856,23 @@ const handleSubmit = async () => {
|
||||
|
||||
try {
|
||||
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") {
|
||||
await props.onSubmit(formData);
|
||||
}
|
||||
|
||||
emit("submit", formData);
|
||||
} catch (error) {
|
||||
console.error("Form submission error:", error);
|
||||
} finally {
|
||||
console.error("Form: submission error:", error);
|
||||
// Reset isSubmitting on error so user can retry
|
||||
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
|
||||
|
||||
@ -14,12 +14,15 @@
|
||||
</div>
|
||||
|
||||
<Form
|
||||
ref="formRef"
|
||||
:fields="formFields"
|
||||
:form-data="formData"
|
||||
:show-cancel-button="true"
|
||||
:validate-on-change="false"
|
||||
:validate-on-blur="true"
|
||||
:validate-on-submit="true"
|
||||
:loading="isSubmitting"
|
||||
:disable-on-loading="true"
|
||||
submit-button-text="Create Client"
|
||||
cancel-button-text="Cancel"
|
||||
@submit="handleSubmit"
|
||||
@ -41,6 +44,8 @@ const modalStore = useModalStore();
|
||||
// Modal visibility computed property
|
||||
const isVisible = computed(() => modalStore.isModalOpen("createClient"));
|
||||
const customerNames = ref([]);
|
||||
// Form reference for controlling its state
|
||||
const formRef = ref(null);
|
||||
// Form data
|
||||
const formData = reactive({
|
||||
customertype: "",
|
||||
@ -78,6 +83,16 @@ const modalOptions = {
|
||||
|
||||
// Form field definitions
|
||||
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",
|
||||
label: "Client Type",
|
||||
@ -318,15 +333,38 @@ function getStatusIcon(type) {
|
||||
}
|
||||
}
|
||||
|
||||
// Submission state to prevent double submission
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
// 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 {
|
||||
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
|
||||
const response = await Api.createClient(formData);
|
||||
const response = await Api.createClient(dataToSubmit);
|
||||
|
||||
console.log("CreateClientModal: API response received:", response);
|
||||
|
||||
if (response && response.success) {
|
||||
showStatusMessage("Client created successfully!", "success");
|
||||
@ -339,8 +377,15 @@ async function handleSubmit() {
|
||||
throw new Error(response?.message || "Failed to create client");
|
||||
}
|
||||
} 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");
|
||||
} 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
|
||||
function handleClose() {
|
||||
modalStore.closeCreateClient();
|
||||
modalStore.closeModal("createClient");
|
||||
resetForm();
|
||||
}
|
||||
|
||||
@ -383,13 +428,9 @@ watch(isVisible, async () => {
|
||||
if (isVisible.value) {
|
||||
try {
|
||||
const names = await Api.getCustomerNames();
|
||||
console.log("Loaded customer names:", names);
|
||||
customerNames.value = names;
|
||||
} catch (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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -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
|
||||
const cachedData = paginationStore.getCachedPage(
|
||||
"clients",
|
||||
|
||||
@ -1697,13 +1697,25 @@ class DataUtils {
|
||||
|
||||
static toSnakeCaseObject(obj) {
|
||||
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;
|
||||
return acc;
|
||||
}, {});
|
||||
console.log("DEBUG: toSnakeCaseObject -> newObj", 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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user