moving towards real data

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

View file

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

View file

@ -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();
}
},

View file

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

View file

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

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
const cachedData = paginationStore.getCachedPage(
"clients",