update filter functionality

This commit is contained in:
Casey 2025-11-04 09:09:56 -06:00
parent 464c62d1e5
commit 9431a0502a
10 changed files with 2362 additions and 220 deletions

View file

@ -1,12 +1,86 @@
<template lang="html">
<!-- Filter Controls Panel -->
<div v-if="lazy && 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">
{{ col.label }}
</label>
<InputText
:id="`filter-${col.fieldName}`"
v-model="pendingFilters[col.fieldName]"
:placeholder="`Filter by ${col.label.toLowerCase()}...`"
class="form-control"
@keyup.enter="applyFilters"
/>
</div>
<div class="col-md-4 col-lg-3">
<div class="d-flex gap-2">
<Button
label="Apply Filters"
icon="pi pi-search"
@click="applyFilters"
:disabled="!hasFilterChanges"
size="small"
/>
<Button
label="Clear"
icon="pi pi-times"
@click="clearFilters"
severity="secondary"
outlined
size="small"
:disabled="!hasActiveFilters"
/>
</div>
</div>
</div>
<div v-if="hasActiveFilters" class="mt-2">
<small class="text-muted"> Active filters: {{ getActiveFiltersText() }} </small>
</div>
</div>
<!-- Page Jump Controls -->
<div v-if="lazy && 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>
</div>
<div class="col-auto">
<select
v-model="selectedPageJump"
@change="jumpToPage"
class="form-select form-select-sm"
style="width: auto"
>
<option value="">Jump to page...</option>
<option v-for="page in totalPages" :key="page" :value="page">
Page {{ page }}
</option>
</select>
</div>
<div class="col-auto">
<small class="text-muted">
Showing {{ getPageInfo().start }} - {{ getPageInfo().end }} of
{{ getPageInfo().total }} records
</small>
</div>
</div>
</div>
<DataTable
:value="data"
:rowsPerPageOptions="[5, 10, 20, 50]"
:paginator="true"
:rows="10"
:rows="currentRows"
:lazy="lazy"
:totalRecords="lazy ? totalRecords : data.length"
@page="handlePage"
@sort="handleSort"
@filter="handleFilter"
sortMode="multiple"
removableSort
filterDisplay="row"
filterDisplay="none"
v-model:filters="filterRef"
scrollable
scrollHeight="70vh"
@ -63,19 +137,20 @@
</DataTable>
</template>
<script setup>
import { defineProps, computed, onMounted, watch } from "vue";
import { defineProps, computed, onMounted, watch, ref } from "vue";
import DataTable from "primevue/datatable";
import Column from "primevue/column";
import Tag from "primevue/tag";
import Button from "primevue/button";
import InputText from "primevue/inputtext";
import { ref } from "vue";
import { FilterMatchMode } from "@primevue/core";
import { useFiltersStore } from "../../stores/filters";
import { useLoadingStore } from "../../stores/loading";
import { usePaginationStore } from "../../stores/pagination";
const filtersStore = useFiltersStore();
const loadingStore = useLoadingStore();
const paginationStore = usePaginationStore();
const props = defineProps({
columns: {
@ -117,9 +192,23 @@ const props = defineProps({
type: Boolean,
default: true,
},
// Server-side pagination support
lazy: {
type: Boolean,
default: false,
},
totalRecords: {
type: Number,
default: 0,
},
// Custom pagination event handler
onLazyLoad: {
type: Function,
default: null,
},
});
const emit = defineEmits(["rowClick"]);
const emit = defineEmits(["rowClick", "lazy-load", "page-change", "sort-change", "filter-change"]);
// Computed loading state that considers both prop and global store
const loading = computed(() => {
@ -134,9 +223,23 @@ const loading = computed(() => {
return props.loading;
});
// Initialize filters in store when component mounts
// Get current rows per page from pagination store or default
const currentRows = computed(() => {
if (props.lazy) {
return paginationStore.getTablePagination(props.tableName).rows;
}
return 10; // Default for non-lazy tables
});
// Initialize filters and pagination in store when component mounts
onMounted(() => {
filtersStore.initializeTableFilters(props.tableName, props.columns);
if (props.lazy) {
paginationStore.initializeTablePagination(props.tableName, {
rows: 10,
totalRecords: props.totalRecords,
});
}
});
// Get filters from store, with fallback to props.filters
@ -187,17 +290,167 @@ watch(
);
const selectedRows = ref();
const pendingFilters = ref({});
const selectedPageJump = ref("");
// Handle filter input changes
const handleFilterInput = (fieldName, value, filterCallback) => {
// Get the current filter to preserve the match mode
const currentFilter = filterRef.value[fieldName];
const matchMode = currentFilter?.matchMode || FilterMatchMode.CONTAINS;
// Computed properties for filtering
const filterableColumns = computed(() => {
return props.columns.filter((col) => col.filterable);
});
// Update the store with both value and match mode
filtersStore.updateTableFilter(props.tableName, fieldName, value, matchMode);
// Call the PrimeVue filter callback
filterCallback();
const hasFilters = computed(() => {
return filterableColumns.value.length > 0;
});
const hasActiveFilters = computed(() => {
const currentFilters = filtersStore.getTableFilters(props.tableName);
return Object.values(currentFilters).some(
(filter) => filter.value && filter.value.trim() !== "",
);
});
const hasFilterChanges = computed(() => {
const currentFilters = filtersStore.getTableFilters(props.tableName);
return Object.keys(pendingFilters.value).some((key) => {
const pending = pendingFilters.value[key] || "";
const current = currentFilters[key]?.value || "";
return pending.trim() !== current.trim();
});
});
const totalPages = computed(() => {
if (!props.lazy) return 0;
return paginationStore.getTotalPages(props.tableName);
});
// Initialize pending filters from store
onMounted(() => {
const currentFilters = filtersStore.getTableFilters(props.tableName);
filterableColumns.value.forEach((col) => {
pendingFilters.value[col.fieldName] = currentFilters[col.fieldName]?.value || "";
});
});
// Filter management methods
const applyFilters = () => {
// Update store with pending filter values
Object.keys(pendingFilters.value).forEach((fieldName) => {
const value = pendingFilters.value[fieldName]?.trim();
filtersStore.updateTableFilter(
props.tableName,
fieldName,
value || null,
FilterMatchMode.CONTAINS,
);
});
// For lazy tables, reset to first page and trigger reload
if (props.lazy) {
paginationStore.resetToFirstPage(props.tableName);
triggerLazyLoad();
}
};
const clearFilters = () => {
// Clear pending filters
filterableColumns.value.forEach((col) => {
pendingFilters.value[col.fieldName] = "";
});
// Clear store filters
filtersStore.clearTableFilters(props.tableName);
// For lazy tables, reset to first page and trigger reload
if (props.lazy) {
paginationStore.resetToFirstPage(props.tableName);
triggerLazyLoad();
}
};
const getActiveFiltersText = () => {
const currentFilters = filtersStore.getTableFilters(props.tableName);
const activeFilters = [];
Object.keys(currentFilters).forEach((key) => {
if (currentFilters[key]?.value) {
const column = filterableColumns.value.find((col) => col.fieldName === key);
if (column) {
activeFilters.push(`${column.label}: "${currentFilters[key].value}"`);
}
}
});
return activeFilters.join(", ");
};
// Page navigation methods
const jumpToPage = () => {
if (selectedPageJump.value && props.lazy) {
const pageNumber = parseInt(selectedPageJump.value) - 1; // Convert to 0-based
paginationStore.setPage(props.tableName, pageNumber);
triggerLazyLoad();
}
selectedPageJump.value = ""; // Reset selection
};
const getPageInfo = () => {
return paginationStore.getPageInfo(props.tableName);
};
// Handle pagination events
const handlePage = (event) => {
console.log("Page event:", event);
if (props.lazy) {
paginationStore.updateTablePagination(props.tableName, {
page: event.page,
first: event.first,
rows: event.rows,
});
triggerLazyLoad();
}
emit("page-change", event);
};
// Handle sorting events
const handleSort = (event) => {
console.log("Sort event:", event);
if (props.lazy) {
paginationStore.setSorting(props.tableName, event.sortField, event.sortOrder);
// Reset to first page when sorting changes
paginationStore.resetToFirstPage(props.tableName);
triggerLazyLoad();
}
emit("sort-change", event);
};
// Handle filter events
const handleFilter = (event) => {
console.log("Filter event:", event);
if (props.lazy) {
// Reset to first page when filters change
paginationStore.resetToFirstPage(props.tableName);
triggerLazyLoad();
}
emit("filter-change", event);
};
// Trigger lazy load event
const triggerLazyLoad = () => {
if (props.lazy && props.onLazyLoad) {
const paginationParams = paginationStore.getPaginationParams(props.tableName);
const filters = filtersStore.getTableFilters(props.tableName);
const lazyEvent = {
first: paginationParams.offset,
rows: paginationParams.limit,
page: paginationParams.page,
sortField: paginationParams.sortField,
sortOrder: paginationParams.sortOrder,
filters: filters,
};
console.log("Triggering lazy load with:", lazyEvent);
emit("lazy-load", lazyEvent);
props.onLazyLoad(lazyEvent);
}
};
const getBadgeColor = (status) => {
@ -216,11 +469,43 @@ const getBadgeColor = (status) => {
console.log("DEBUG: - DataTable props.columns", props.columns);
console.log("DEBUG: - DataTable props.data", props.data);
// Expose loading control methods for parent components
// Expose control methods for parent components
defineExpose({
// Loading controls
startLoading: (message) => loadingStore.setComponentLoading(props.tableName, true, message),
stopLoading: () => loadingStore.setComponentLoading(props.tableName, false),
isLoading: () => loading.value,
// Pagination controls (for lazy tables)
goToPage: (page) => {
if (props.lazy) {
paginationStore.setPage(props.tableName, page);
triggerLazyLoad();
}
},
nextPage: () => {
if (props.lazy) {
paginationStore.nextPage(props.tableName);
triggerLazyLoad();
}
},
previousPage: () => {
if (props.lazy) {
paginationStore.previousPage(props.tableName);
triggerLazyLoad();
}
},
refresh: () => {
if (props.lazy) {
triggerLazyLoad();
}
},
// Get current state
getCurrentPage: () => paginationStore.getTablePagination(props.tableName).page,
getTotalPages: () => paginationStore.getTotalPages(props.tableName),
getFilters: () => filtersStore.getTableFilters(props.tableName),
getPaginationInfo: () => paginationStore.getPageInfo(props.tableName),
});
</script>
<style lang=""></style>

View file

@ -6,7 +6,17 @@
Add
</button>
</div>
<DataTable :data="tableData" :columns="columns" :filters="filters" tableName="clients" />
<DataTable
:data="tableData"
:columns="columns"
:filters="filters"
tableName="clients"
:lazy="true"
:totalRecords="totalRecords"
:loading="isLoading"
:onLazyLoad="handleLazyLoad"
@lazy-load="handleLazyLoad"
/>
</div>
</template>
<script setup>
@ -15,13 +25,16 @@ import DataTable from "../common/DataTable.vue";
import Api from "../../api";
import { FilterMatchMode } from "@primevue/core";
import { useLoadingStore } from "../../stores/loading";
import { usePaginationStore } from "../../stores/pagination";
import { useFiltersStore } from "../../stores/filters";
const loadingStore = useLoadingStore();
const paginationStore = usePaginationStore();
const filtersStore = useFiltersStore();
const itemCount = ref(0);
const page = ref(0);
const pageLength = ref(30);
const tableData = ref([]);
const totalRecords = ref(0);
const isLoading = ref(false);
const onClick = () => {
frappe.new_doc("Customer");
@ -49,23 +62,114 @@ const columns = [
{ label: "Payment Received", fieldName: "paymentStatus", type: "status", sortable: true },
{ label: "Job Status", fieldName: "jobStatus", type: "status", sortable: true },
];
onMounted(async () => {
if (tableData.value.length > 0) {
return;
}
// Handle lazy loading events from DataTable
const handleLazyLoad = async (event) => {
console.log("Clients page - handling lazy load:", event);
try {
// Use the loading store to track this API call
const data = await loadingStore.withComponentLoading(
isLoading.value = true;
// Get pagination parameters
const paginationParams = {
page: event.page || 0,
pageSize: event.rows || 10,
sortField: event.sortField,
sortOrder: event.sortOrder,
};
// Get filters (convert PrimeVue format to API format)
const filters = {};
if (event.filters) {
Object.keys(event.filters).forEach((key) => {
if (key !== "global" && event.filters[key] && event.filters[key].value) {
filters[key] = event.filters[key];
}
});
}
// Check cache first
const cachedData = paginationStore.getCachedPage(
"clients",
() => Api.getClientDetails(),
"Loading client data...",
paginationParams.page,
paginationParams.pageSize,
paginationParams.sortField,
paginationParams.sortOrder,
filters,
);
tableData.value = data;
if (cachedData) {
// Use cached data
tableData.value = cachedData.records;
totalRecords.value = cachedData.totalRecords;
paginationStore.setTotalRecords("clients", cachedData.totalRecords);
console.log("Loaded from cache:", {
records: cachedData.records.length,
total: cachedData.totalRecords,
page: paginationParams.page + 1,
});
return;
}
console.log("Making API call with:", { paginationParams, filters });
// Call API with pagination and filters
const result = await Api.getPaginatedClientDetails(paginationParams, filters);
// Update local state
tableData.value = result.data;
totalRecords.value = result.totalRecords;
// Update pagination store with new total
paginationStore.setTotalRecords("clients", result.totalRecords);
// Cache the result
paginationStore.setCachedPage(
"clients",
paginationParams.page,
paginationParams.pageSize,
paginationParams.sortField,
paginationParams.sortOrder,
filters,
{
records: result.data,
totalRecords: result.totalRecords,
},
);
console.log("Loaded from API:", {
records: result.data.length,
total: result.totalRecords,
page: paginationParams.page + 1,
});
} catch (error) {
console.error("Error loading client data:", error);
// You could also show a toast or other error notification here
tableData.value = [];
totalRecords.value = 0;
} finally {
isLoading.value = false;
}
};
// Load initial data
onMounted(async () => {
// Initialize pagination and filters
paginationStore.initializeTablePagination("clients", { rows: 10 });
filtersStore.initializeTableFilters("clients", columns);
// Load first page
const initialPagination = paginationStore.getTablePagination("clients");
const initialFilters = filtersStore.getTableFilters("clients");
await handleLazyLoad({
page: initialPagination.page,
rows: initialPagination.rows,
first: initialPagination.first,
sortField: initialPagination.sortField,
sortOrder: initialPagination.sortOrder,
filters: initialFilters,
});
});
</script>
<style lang="css"></style>

View file

@ -1,15 +1,34 @@
<template>
<div>
<h2>Jobs</h2>
<DataTable :data="tableData" :columns="columns" tableName="jobs" />
<DataTable
:data="tableData"
:columns="columns"
tableName="jobs"
:lazy="true"
:totalRecords="totalRecords"
:loading="isLoading"
:onLazyLoad="handleLazyLoad"
@lazy-load="handleLazyLoad"
/>
</div>
</template>
<script setup>
import DataTable from "../common/DataTable.vue";
import { ref, onMounted } from "vue";
import Api from "../../api";
import { useLoadingStore } from "../../stores/loading";
import { usePaginationStore } from "../../stores/pagination";
import { useFiltersStore } from "../../stores/filters";
const loadingStore = useLoadingStore();
const paginationStore = usePaginationStore();
const filtersStore = useFiltersStore();
const tableData = ref([]);
const totalRecords = ref(0);
const isLoading = ref(false);
const columns = [
{ label: "Job ID", fieldName: "jobId", type: "text", sortable: true, filterable: true },
{ label: "Address", fieldName: "address", type: "text", sortable: true },
@ -18,12 +37,119 @@ const columns = [
{ label: "Progress", fieldName: "stepProgress", type: "text", sortable: true },
];
onMounted(async () => {
if (tableData.value.length > 0) {
return;
// Handle lazy loading events from DataTable
const handleLazyLoad = async (event) => {
console.log("Jobs page - handling lazy load:", event);
try {
isLoading.value = true;
// Get pagination parameters
const paginationParams = {
page: event.page || 0,
pageSize: event.rows || 10,
sortField: event.sortField,
sortOrder: event.sortOrder,
};
// Get filters (convert PrimeVue format to API format)
const filters = {};
if (event.filters) {
Object.keys(event.filters).forEach((key) => {
if (key !== "global" && event.filters[key] && event.filters[key].value) {
filters[key] = event.filters[key];
}
});
}
// Check cache first
const cachedData = paginationStore.getCachedPage(
"jobs",
paginationParams.page,
paginationParams.pageSize,
paginationParams.sortField,
paginationParams.sortOrder,
filters,
);
if (cachedData) {
// Use cached data
tableData.value = cachedData.records;
totalRecords.value = cachedData.totalRecords;
paginationStore.setTotalRecords("jobs", cachedData.totalRecords);
console.log("Loaded from cache:", {
records: cachedData.records.length,
total: cachedData.totalRecords,
page: paginationParams.page + 1,
});
return;
}
console.log("Making API call with:", { paginationParams, filters });
// For now, use existing API but we should create a paginated version
// TODO: Create Api.getPaginatedJobDetails() method
let data = await Api.getJobDetails();
// Simulate pagination on client side for now
const startIndex = paginationParams.page * paginationParams.pageSize;
const endIndex = startIndex + paginationParams.pageSize;
const paginatedData = data.slice(startIndex, endIndex);
// Update local state
tableData.value = paginatedData;
totalRecords.value = data.length;
// Update pagination store with new total
paginationStore.setTotalRecords("jobs", data.length);
// Cache the result
paginationStore.setCachedPage(
"jobs",
paginationParams.page,
paginationParams.pageSize,
paginationParams.sortField,
paginationParams.sortOrder,
filters,
{
records: paginatedData,
totalRecords: data.length,
},
);
console.log("Loaded from API:", {
records: paginatedData.length,
total: data.length,
page: paginationParams.page + 1,
});
} catch (error) {
console.error("Error loading job data:", error);
tableData.value = [];
totalRecords.value = 0;
} finally {
isLoading.value = false;
}
let data = await Api.getJobDetails();
tableData.value = data;
};
// Load initial data
onMounted(async () => {
// Initialize pagination and filters
paginationStore.initializeTablePagination("jobs", { rows: 10 });
filtersStore.initializeTableFilters("jobs", columns);
// Load first page
const initialPagination = paginationStore.getTablePagination("jobs");
const initialFilters = filtersStore.getTableFilters("jobs");
await handleLazyLoad({
page: initialPagination.page,
rows: initialPagination.rows,
first: initialPagination.first,
sortField: initialPagination.sortField,
sortOrder: initialPagination.sortOrder,
filters: initialFilters,
});
});
</script>
<style lang=""></style>

View file

@ -7,7 +7,17 @@
<!-- Routes Data Table -->
<div class="routes-table-container">
<DataTable :data="tableData" :columns="columns" tableName="routes" @row-click="viewRouteDetails" />
<DataTable
:data="tableData"
:columns="columns"
tableName="routes"
:lazy="true"
:totalRecords="totalRecords"
:loading="isLoading"
:onLazyLoad="handleLazyLoad"
@lazy-load="handleLazyLoad"
@row-click="viewRouteDetails"
/>
</div>
<!-- Route Details Modal -->
@ -139,9 +149,7 @@
<v-icon size="x-small" class="mr-1"
>mdi-clock</v-icon
>
{{ stop.estimatedTime }} ({{
stop.duration
}}
{{ stop.estimatedTime }} ({{ stop.duration }}
min)
</div>
</div>
@ -230,17 +238,33 @@
import { ref, onMounted } from "vue";
import DataTable from "../common/DataTable.vue";
import Api from "../../api";
import { useLoadingStore } from "../../stores/loading";
import { usePaginationStore } from "../../stores/pagination";
import { useFiltersStore } from "../../stores/filters";
const loadingStore = useLoadingStore();
const paginationStore = usePaginationStore();
const filtersStore = useFiltersStore();
// Reactive data
const tableData = ref([]);
const totalRecords = ref(0);
const isLoading = ref(false);
const routeDialog = ref(false);
const selectedRoute = ref(null);
const fullRouteData = ref([]); // Store full route data for modal access
// Table columns configuration
const columns = [
{ label: "Route ID", fieldName: "routeId", type: "text", sortable: true },
{ label: "Route ID", fieldName: "routeId", type: "text", sortable: true, filterable: true },
{ label: "Route Name", fieldName: "routeName", type: "text", sortable: true },
{ label: "Technician", fieldName: "technician", type: "text", sortable: true },
{
label: "Technician",
fieldName: "technician",
type: "text",
sortable: true,
filterable: true,
},
{ label: "Date", fieldName: "date", type: "text", sortable: true },
{ label: "Status", fieldName: "status", type: "status", sortable: true },
{ label: "Progress", fieldName: "progress", type: "text", sortable: true },
@ -249,12 +273,127 @@ const columns = [
{ label: "Actions", fieldName: "actions", type: "button", sortable: false },
];
// Handle lazy loading events from DataTable
const handleLazyLoad = async (event) => {
console.log("Routes page - handling lazy load:", event);
try {
isLoading.value = true;
// Get pagination parameters
const paginationParams = {
page: event.page || 0,
pageSize: event.rows || 10,
sortField: event.sortField,
sortOrder: event.sortOrder,
};
// Get filters (convert PrimeVue format to API format)
const filters = {};
if (event.filters) {
Object.keys(event.filters).forEach((key) => {
if (key !== "global" && event.filters[key] && event.filters[key].value) {
filters[key] = event.filters[key];
}
});
}
// Check cache first
const cachedData = paginationStore.getCachedPage(
"routes",
paginationParams.page,
paginationParams.pageSize,
paginationParams.sortField,
paginationParams.sortOrder,
filters,
);
if (cachedData) {
// Use cached data
tableData.value = cachedData.records;
totalRecords.value = cachedData.totalRecords;
fullRouteData.value = cachedData.fullData || [];
paginationStore.setTotalRecords("routes", cachedData.totalRecords);
console.log("Loaded from cache:", {
records: cachedData.records.length,
total: cachedData.totalRecords,
page: paginationParams.page + 1,
});
return;
}
console.log("Making API call with:", { paginationParams, filters });
// For now, use existing API but we should create a paginated version
// TODO: Create Api.getPaginatedRouteData() method
const data = await Api.getRouteData();
// Store full data for modal access
fullRouteData.value = data;
// Simulate pagination on client side for now
const startIndex = paginationParams.page * paginationParams.pageSize;
const endIndex = startIndex + paginationParams.pageSize;
// Transform data for table display
const transformedData = data.map((route) => ({
routeId: route.routeId,
routeName: route.routeName,
technician: route.technician,
date: route.date,
status: route.status,
progress: `${route.completedStops}/${route.totalStops}`,
totalStops: route.totalStops,
estimatedDuration: route.estimatedDuration,
actions: "View Details",
}));
const paginatedData = transformedData.slice(startIndex, endIndex);
// Update local state
tableData.value = paginatedData;
totalRecords.value = transformedData.length;
// Update pagination store with new total
paginationStore.setTotalRecords("routes", transformedData.length);
// Cache the result
paginationStore.setCachedPage(
"routes",
paginationParams.page,
paginationParams.pageSize,
paginationParams.sortField,
paginationParams.sortOrder,
filters,
{
records: paginatedData,
totalRecords: transformedData.length,
fullData: data,
},
);
console.log("Loaded from API:", {
records: paginatedData.length,
total: transformedData.length,
page: paginationParams.page + 1,
});
} catch (error) {
console.error("Error loading route data:", error);
tableData.value = [];
totalRecords.value = 0;
fullRouteData.value = [];
} finally {
isLoading.value = false;
}
};
// Methods
const viewRouteDetails = (event) => {
const routeId = event.data.routeId;
const route = tableData.value.find((r) => r.routeId === routeId);
if (route && route.fullData) {
selectedRoute.value = route.fullData;
const route = fullRouteData.value.find((r) => r.routeId === routeId);
if (route) {
selectedRoute.value = route;
routeDialog.value = true;
}
};
@ -293,29 +432,24 @@ const optimizeRoute = () => {
alert("Route optimization feature coming soon!");
};
// Load data on component mount
// Load initial data
onMounted(async () => {
try {
const data = await Api.getRouteData();
// Initialize pagination and filters
paginationStore.initializeTablePagination("routes", { rows: 10 });
filtersStore.initializeTableFilters("routes", columns);
// Transform data for table display and keep full data reference
tableData.value = data.map((route) => ({
routeId: route.routeId,
routeName: route.routeName,
technician: route.technician,
date: route.date,
status: route.status,
progress: `${route.completedStops}/${route.totalStops}`,
totalStops: route.totalStops,
estimatedDuration: route.estimatedDuration,
actions: "View Details",
fullData: route, // Keep reference to full route data
}));
// Load first page
const initialPagination = paginationStore.getTablePagination("routes");
const initialFilters = filtersStore.getTableFilters("routes");
console.log("Loaded routes:", tableData.value);
} catch (error) {
console.error("Error loading routes:", error);
}
await handleLazyLoad({
page: initialPagination.page,
rows: initialPagination.rows,
first: initialPagination.first,
sortField: initialPagination.sortField,
sortOrder: initialPagination.sortOrder,
filters: initialFilters,
});
});
</script>

View file

@ -124,10 +124,14 @@
<!-- Main Timesheet Table -->
<div class="timesheets-table-container">
<DataTable
:data="filteredTableData"
:data="tableData"
:columns="columns"
:filters="filters"
tableName="timesheets"
:lazy="true"
:totalRecords="totalRecords"
:loading="isLoading"
:onLazyLoad="handleLazyLoad"
@lazy-load="handleLazyLoad"
@row-click="viewTimesheetDetails"
/>
</div>
@ -372,12 +376,21 @@ import { ref, onMounted, computed } from "vue";
import DataTable from "../common/DataTable.vue";
import Api from "../../api";
import { FilterMatchMode } from "@primevue/core";
import { useLoadingStore } from "../../stores/loading";
import { usePaginationStore } from "../../stores/pagination";
import { useFiltersStore } from "../../stores/filters";
const loadingStore = useLoadingStore();
const paginationStore = usePaginationStore();
const filtersStore = useFiltersStore();
// Reactive data
const tableData = ref([]);
const filteredTableData = ref([]);
const totalRecords = ref(0);
const isLoading = ref(false);
const timesheetDialog = ref(false);
const selectedTimesheet = ref(null);
const allTimesheetData = ref([]); // Store all data for filtering and calculations
// Filter controls
const selectedWeek = ref("current");
@ -412,8 +425,156 @@ const columns = [
{ label: "Actions", fieldName: "actions", type: "button", sortable: false },
];
const filters = {
employee: { value: null, matchMode: FilterMatchMode.CONTAINS },
// Handle lazy loading events from DataTable
const handleLazyLoad = async (event) => {
console.log("TimeSheets page - handling lazy load:", event);
try {
isLoading.value = true;
// Get pagination parameters
const paginationParams = {
page: event.page || 0,
pageSize: event.rows || 10,
sortField: event.sortField,
sortOrder: event.sortOrder,
};
// Get filters (convert PrimeVue format to API format)
const filters = {};
if (event.filters) {
Object.keys(event.filters).forEach((key) => {
if (key !== "global" && event.filters[key] && event.filters[key].value) {
filters[key] = event.filters[key];
}
});
}
// Apply additional filters from the controls
const additionalFilters = {
week: selectedWeek.value,
employee: selectedEmployee.value,
status: selectedStatus.value,
};
// Combine filters
const combinedFilters = { ...filters, ...additionalFilters };
// Check cache first
const cacheKey = `${JSON.stringify(combinedFilters)}`;
const cachedData = paginationStore.getCachedPage(
"timesheets",
paginationParams.page,
paginationParams.pageSize,
paginationParams.sortField,
paginationParams.sortOrder,
combinedFilters,
);
if (cachedData) {
// Use cached data
tableData.value = cachedData.records;
totalRecords.value = cachedData.totalRecords;
allTimesheetData.value = cachedData.allData || [];
paginationStore.setTotalRecords("timesheets", cachedData.totalRecords);
console.log("Loaded from cache:", {
records: cachedData.records.length,
total: cachedData.totalRecords,
page: paginationParams.page + 1,
});
return;
}
console.log("Making API call with:", { paginationParams, combinedFilters });
// For now, use existing API but we should create a paginated version
// TODO: Create Api.getPaginatedTimesheetData() method
const data = await Api.getTimesheetData();
// Store all data for calculations
allTimesheetData.value = data;
// Apply local filtering based on controls
let filteredData = data;
// Week filter
if (selectedWeek.value !== "all") {
const currentWeekStart = getCurrentWeekStart();
let weekStart;
switch (selectedWeek.value) {
case "current":
weekStart = currentWeekStart;
break;
case "last":
weekStart = getWeekStart(1);
break;
case "last2":
weekStart = getWeekStart(2);
break;
default:
weekStart = null;
}
if (weekStart) {
filteredData = filteredData.filter((timesheet) => {
const timesheetDate = new Date(timesheet.date);
return timesheetDate >= weekStart;
});
}
}
// Employee filter
if (selectedEmployee.value) {
filteredData = filteredData.filter((ts) => ts.employee === selectedEmployee.value);
}
// Status filter
if (selectedStatus.value) {
filteredData = filteredData.filter((ts) => ts.status === selectedStatus.value);
}
// Simulate pagination on filtered data
const startIndex = paginationParams.page * paginationParams.pageSize;
const endIndex = startIndex + paginationParams.pageSize;
const paginatedData = filteredData.slice(startIndex, endIndex);
// Update local state
tableData.value = paginatedData;
totalRecords.value = filteredData.length;
// Update pagination store with new total
paginationStore.setTotalRecords("timesheets", filteredData.length);
// Cache the result
paginationStore.setCachedPage(
"timesheets",
paginationParams.page,
paginationParams.pageSize,
paginationParams.sortField,
paginationParams.sortOrder,
combinedFilters,
{
records: paginatedData,
totalRecords: filteredData.length,
allData: data,
},
);
console.log("Loaded from API:", {
records: paginatedData.length,
total: filteredData.length,
page: paginationParams.page + 1,
});
} catch (error) {
console.error("Error loading timesheet data:", error);
tableData.value = [];
totalRecords.value = 0;
allTimesheetData.value = [];
} finally {
isLoading.value = false;
}
};
// Computed properties
@ -425,7 +586,7 @@ const totalHoursThisWeek = computed(() => {
});
const pendingApprovals = computed(() => {
return tableData.value.filter((ts) => !ts.approved).length;
return allTimesheetData.value.filter((ts) => !ts.approved).length;
});
const totalLaborCost = computed(() => {
@ -435,7 +596,7 @@ const totalLaborCost = computed(() => {
});
const activeEmployees = computed(() => {
const uniqueEmployees = new Set(tableData.value.map((ts) => ts.employee));
const uniqueEmployees = new Set(allTimesheetData.value.map((ts) => ts.employee));
return uniqueEmployees.size;
});
@ -444,10 +605,11 @@ const hasSelectedForApproval = computed(() => {
return pendingApprovals.value > 0;
});
// Methods
// Methods
const getCurrentWeekTimesheets = () => {
const currentWeekStart = getCurrentWeekStart();
return tableData.value.filter((timesheet) => {
return allTimesheetData.value.filter((timesheet) => {
const timesheetDate = new Date(timesheet.date);
return timesheetDate >= currentWeekStart;
});
@ -468,15 +630,38 @@ const getWeekStart = (weeksAgo) => {
};
const filterByWeek = () => {
applyFilters();
// Reset to first page when filters change
paginationStore.resetToFirstPage("timesheets");
triggerLazyLoad();
};
const filterByEmployee = () => {
applyFilters();
// Reset to first page when filters change
paginationStore.resetToFirstPage("timesheets");
triggerLazyLoad();
};
const filterByStatus = () => {
applyFilters();
// Reset to first page when filters change
paginationStore.resetToFirstPage("timesheets");
triggerLazyLoad();
};
const triggerLazyLoad = () => {
const paginationParams = paginationStore.getPaginationParams("timesheets");
const filters = filtersStore.getTableFilters("timesheets");
const lazyEvent = {
page: paginationParams.page,
rows: paginationParams.pageSize,
first: paginationParams.offset,
sortField: paginationParams.sortField,
sortOrder: paginationParams.sortOrder,
filters: filters,
};
console.log("Triggering lazy load with:", lazyEvent);
handleLazyLoad(lazyEvent);
};
const applyFilters = () => {
@ -513,7 +698,7 @@ const applyFilters = () => {
const viewTimesheetDetails = (event) => {
const timesheetId = event.data.timesheetId;
const timesheet = tableData.value.find((ts) => ts.timesheetId === timesheetId);
const timesheet = allTimesheetData.value.find((ts) => ts.timesheetId === timesheetId);
if (timesheet) {
selectedTimesheet.value = timesheet;
timesheetDialog.value = true;
@ -570,10 +755,13 @@ const formatDate = (dateString) => {
// Load data on component mount
onMounted(async () => {
try {
const data = await Api.getTimesheetData();
// Initialize pagination and filters
paginationStore.initializeTablePagination("timesheets", { rows: 10 });
filtersStore.initializeTableFilters("timesheets", columns);
// Transform data for table display
tableData.value = data.map((timesheet) => ({
// Load data to set up employee options
const data = await Api.getTimesheetData();
allTimesheetData.value = data.map((timesheet) => ({
...timesheet,
totalPayFormatted: `$${timesheet.totalPay.toLocaleString()}`,
actions: "View Details",
@ -583,10 +771,20 @@ onMounted(async () => {
const uniqueEmployees = [...new Set(data.map((ts) => ts.employee))];
employeeOptions.value = uniqueEmployees.map((emp) => ({ label: emp, value: emp }));
// Apply initial filters
applyFilters();
// Load first page
const initialPagination = paginationStore.getTablePagination("timesheets");
const initialFilters = filtersStore.getTableFilters("timesheets");
console.log("Loaded timesheets:", tableData.value);
await handleLazyLoad({
page: initialPagination.page,
rows: initialPagination.rows,
first: initialPagination.first,
sortField: initialPagination.sortField,
sortOrder: initialPagination.sortOrder,
filters: initialFilters,
});
console.log("Loaded timesheets:", allTimesheetData.value.length);
} catch (error) {
console.error("Error loading timesheets:", error);
}

View file

@ -6,7 +6,16 @@
Add New Warranty Claim
</button>
</div>
<DataTable :data="tableData" :columns="columns" :filters="filters" tableName="warranties" />
<DataTable
:data="tableData"
:columns="columns"
tableName="warranties"
:lazy="true"
:totalRecords="totalRecords"
:loading="isLoading"
:onLazyLoad="handleLazyLoad"
@lazy-load="handleLazyLoad"
/>
</div>
</template>
@ -15,8 +24,17 @@ import { onMounted, ref } from "vue";
import DataTable from "../common/DataTable.vue";
import Api from "../../api";
import { FilterMatchMode } from "@primevue/core";
import { useLoadingStore } from "../../stores/loading";
import { usePaginationStore } from "../../stores/pagination";
import { useFiltersStore } from "../../stores/filters";
const loadingStore = useLoadingStore();
const paginationStore = usePaginationStore();
const filtersStore = useFiltersStore();
const tableData = ref([]);
const totalRecords = ref(0);
const isLoading = ref(false);
const addNewWarranty = () => {
// TODO: Open modal or navigate to create warranty form
@ -25,13 +43,6 @@ const addNewWarranty = () => {
// For now, just log the action
};
const filters = {
customer: { value: null, matchMode: FilterMatchMode.CONTAINS },
warrantyId: { value: null, matchMode: FilterMatchMode.CONTAINS },
address: { value: null, matchMode: FilterMatchMode.CONTAINS },
assignedTechnician: { value: null, matchMode: FilterMatchMode.CONTAINS },
};
const columns = [
{
label: "Warranty ID",
@ -93,12 +104,119 @@ const columns = [
},
];
onMounted(async () => {
if (tableData.value.length > 0) {
return;
// Handle lazy loading events from DataTable
const handleLazyLoad = async (event) => {
console.log("Warranties page - handling lazy load:", event);
try {
isLoading.value = true;
// Get pagination parameters
const paginationParams = {
page: event.page || 0,
pageSize: event.rows || 10,
sortField: event.sortField,
sortOrder: event.sortOrder,
};
// Get filters (convert PrimeVue format to API format)
const filters = {};
if (event.filters) {
Object.keys(event.filters).forEach((key) => {
if (key !== "global" && event.filters[key] && event.filters[key].value) {
filters[key] = event.filters[key];
}
});
}
// Check cache first
const cachedData = paginationStore.getCachedPage(
"warranties",
paginationParams.page,
paginationParams.pageSize,
paginationParams.sortField,
paginationParams.sortOrder,
filters,
);
if (cachedData) {
// Use cached data
tableData.value = cachedData.records;
totalRecords.value = cachedData.totalRecords;
paginationStore.setTotalRecords("warranties", cachedData.totalRecords);
console.log("Loaded from cache:", {
records: cachedData.records.length,
total: cachedData.totalRecords,
page: paginationParams.page + 1,
});
return;
}
console.log("Making API call with:", { paginationParams, filters });
// For now, use existing API but we should create a paginated version
// TODO: Create Api.getPaginatedWarrantyData() method
let data = await Api.getWarrantyData();
// Simulate pagination on client side for now
const startIndex = paginationParams.page * paginationParams.pageSize;
const endIndex = startIndex + paginationParams.pageSize;
const paginatedData = data.slice(startIndex, endIndex);
// Update local state
tableData.value = paginatedData;
totalRecords.value = data.length;
// Update pagination store with new total
paginationStore.setTotalRecords("warranties", data.length);
// Cache the result
paginationStore.setCachedPage(
"warranties",
paginationParams.page,
paginationParams.pageSize,
paginationParams.sortField,
paginationParams.sortOrder,
filters,
{
records: paginatedData,
totalRecords: data.length,
},
);
console.log("Loaded from API:", {
records: paginatedData.length,
total: data.length,
page: paginationParams.page + 1,
});
} catch (error) {
console.error("Error loading warranty data:", error);
tableData.value = [];
totalRecords.value = 0;
} finally {
isLoading.value = false;
}
let data = await Api.getWarrantyData();
tableData.value = data;
};
// Load initial data
onMounted(async () => {
// Initialize pagination and filters
paginationStore.initializeTablePagination("warranties", { rows: 10 });
filtersStore.initializeTableFilters("warranties", columns);
// Load first page
const initialPagination = paginationStore.getTablePagination("warranties");
const initialFilters = filtersStore.getTableFilters("warranties");
await handleLazyLoad({
page: initialPagination.page,
rows: initialPagination.rows,
first: initialPagination.first,
sortField: initialPagination.sortField,
sortOrder: initialPagination.sortOrder,
filters: initialFilters,
});
});
</script>