397 lines
11 KiB
JavaScript
397 lines
11 KiB
JavaScript
import { defineStore } from "pinia";
|
|
|
|
export const usePaginationStore = defineStore("pagination", {
|
|
state: () => ({
|
|
// Store pagination state by table/component name
|
|
tablePagination: {
|
|
clients: {
|
|
first: 0, // Starting index for current page
|
|
page: 0, // Current page number (0-based)
|
|
rows: 10, // Items per page
|
|
totalRecords: 0, // Total number of records available
|
|
sortField: null, // Current sort field
|
|
sortOrder: null, // Sort direction (1 for asc, -1 for desc)
|
|
},
|
|
jobs: {
|
|
first: 0,
|
|
page: 0,
|
|
rows: 10,
|
|
totalRecords: 0,
|
|
sortField: null,
|
|
sortOrder: null,
|
|
},
|
|
estimates: {
|
|
first: 0,
|
|
page: 0,
|
|
rows: 10,
|
|
totalRecords: 0,
|
|
sortField: null,
|
|
sortOrder: null,
|
|
},
|
|
timesheets: {
|
|
first: 0,
|
|
page: 0,
|
|
rows: 10,
|
|
totalRecords: 0,
|
|
sortField: null,
|
|
sortOrder: null,
|
|
},
|
|
warranties: {
|
|
first: 0,
|
|
page: 0,
|
|
rows: 10,
|
|
totalRecords: 0,
|
|
sortField: null,
|
|
sortOrder: null,
|
|
},
|
|
routes: {
|
|
first: 0,
|
|
page: 0,
|
|
rows: 10,
|
|
totalRecords: 0,
|
|
sortField: null,
|
|
sortOrder: null,
|
|
},
|
|
},
|
|
|
|
// Page data cache: tableName -> { pageKey -> { data, timestamp, filterHash } }
|
|
pageCache: {},
|
|
|
|
// Cache configuration
|
|
cacheTimeout: 5 * 60 * 1000, // 5 minutes in milliseconds
|
|
maxCacheSize: 50, // Maximum number of cached pages per table
|
|
}),
|
|
|
|
getters: {
|
|
// Get pagination state for a specific table
|
|
getTablePagination: (state) => (tableName) => {
|
|
return (
|
|
state.tablePagination[tableName] || {
|
|
first: 0,
|
|
page: 0,
|
|
rows: 10,
|
|
totalRecords: 0,
|
|
sortField: null,
|
|
sortOrder: null,
|
|
}
|
|
);
|
|
},
|
|
|
|
// Calculate total pages for a table
|
|
getTotalPages: (state) => (tableName) => {
|
|
const pagination = state.tablePagination[tableName];
|
|
if (!pagination || pagination.totalRecords === 0) return 0;
|
|
return Math.ceil(pagination.totalRecords / pagination.rows);
|
|
},
|
|
|
|
// Check if there's a next page
|
|
hasNextPage: (state) => (tableName) => {
|
|
const pagination = state.tablePagination[tableName];
|
|
if (!pagination) return false;
|
|
return pagination.page + 1 < Math.ceil(pagination.totalRecords / pagination.rows);
|
|
},
|
|
|
|
// Check if there's a previous page
|
|
hasPreviousPage: (state) => (tableName) => {
|
|
const pagination = state.tablePagination[tableName];
|
|
if (!pagination) return false;
|
|
return pagination.page > 0;
|
|
},
|
|
|
|
// Get current page info for display
|
|
getPageInfo: (state) => (tableName) => {
|
|
const pagination = state.tablePagination[tableName];
|
|
if (!pagination) return { start: 0, end: 0, total: 0 };
|
|
|
|
const start = pagination.first + 1;
|
|
const end = Math.min(pagination.first + pagination.rows, pagination.totalRecords);
|
|
const total = pagination.totalRecords;
|
|
|
|
return { start, end, total };
|
|
},
|
|
},
|
|
|
|
actions: {
|
|
// Initialize pagination for a table if it doesn't exist
|
|
initializeTablePagination(tableName, options = {}) {
|
|
if (!this.tablePagination[tableName]) {
|
|
this.tablePagination[tableName] = {
|
|
first: 0,
|
|
page: 0,
|
|
rows: options.rows || 10,
|
|
totalRecords: options.totalRecords || 0,
|
|
sortField: options.sortField || null,
|
|
sortOrder: options.sortOrder || null,
|
|
};
|
|
}
|
|
},
|
|
|
|
// Update pagination state for a table
|
|
updateTablePagination(tableName, paginationData) {
|
|
if (!this.tablePagination[tableName]) {
|
|
this.initializeTablePagination(tableName);
|
|
}
|
|
|
|
// Update provided fields
|
|
Object.keys(paginationData).forEach((key) => {
|
|
if (paginationData[key] !== undefined) {
|
|
this.tablePagination[tableName][key] = paginationData[key];
|
|
}
|
|
});
|
|
|
|
// Ensure consistency between page and first
|
|
if (paginationData.page !== undefined) {
|
|
this.tablePagination[tableName].first =
|
|
paginationData.page * this.tablePagination[tableName].rows;
|
|
} else if (paginationData.first !== undefined) {
|
|
this.tablePagination[tableName].page = Math.floor(
|
|
paginationData.first / this.tablePagination[tableName].rows,
|
|
);
|
|
}
|
|
},
|
|
|
|
// Set current page
|
|
setPage(tableName, page) {
|
|
this.updateTablePagination(tableName, {
|
|
page: page,
|
|
first: page * this.getTablePagination(tableName).rows,
|
|
});
|
|
},
|
|
|
|
// Set rows per page
|
|
setRowsPerPage(tableName, rows) {
|
|
const currentPagination = this.getTablePagination(tableName);
|
|
const newPage = Math.floor(currentPagination.first / rows);
|
|
|
|
this.updateTablePagination(tableName, {
|
|
rows: rows,
|
|
page: newPage,
|
|
first: newPage * rows,
|
|
});
|
|
},
|
|
|
|
// Set total records (usually after API response)
|
|
setTotalRecords(tableName, totalRecords) {
|
|
this.updateTablePagination(tableName, {
|
|
totalRecords: totalRecords,
|
|
});
|
|
|
|
// Ensure current page is valid
|
|
const currentPagination = this.getTablePagination(tableName);
|
|
const maxPages = Math.ceil(totalRecords / currentPagination.rows);
|
|
if (currentPagination.page >= maxPages && maxPages > 0) {
|
|
this.setPage(tableName, maxPages - 1);
|
|
}
|
|
},
|
|
|
|
// Set sort information
|
|
setSorting(tableName, sortField, sortOrder) {
|
|
this.updateTablePagination(tableName, {
|
|
sortField: sortField,
|
|
sortOrder: sortOrder,
|
|
});
|
|
},
|
|
|
|
// Go to next page
|
|
nextPage(tableName) {
|
|
const pagination = this.getTablePagination(tableName);
|
|
if (this.hasNextPage(tableName)) {
|
|
this.setPage(tableName, pagination.page + 1);
|
|
}
|
|
},
|
|
|
|
// Go to previous page
|
|
previousPage(tableName) {
|
|
const pagination = this.getTablePagination(tableName);
|
|
if (this.hasPreviousPage(tableName)) {
|
|
this.setPage(tableName, pagination.page - 1);
|
|
}
|
|
},
|
|
|
|
// Go to first page
|
|
firstPage(tableName) {
|
|
this.setPage(tableName, 0);
|
|
},
|
|
|
|
// Go to last page
|
|
lastPage(tableName) {
|
|
const totalPages = this.getTotalPages(tableName);
|
|
if (totalPages > 0) {
|
|
this.setPage(tableName, totalPages - 1);
|
|
}
|
|
},
|
|
|
|
// Reset pagination to first page (useful when filters change)
|
|
resetToFirstPage(tableName) {
|
|
this.updateTablePagination(tableName, {
|
|
page: 0,
|
|
first: 0,
|
|
});
|
|
},
|
|
|
|
// Clear all pagination data for a table
|
|
clearTablePagination(tableName) {
|
|
if (this.tablePagination[tableName]) {
|
|
this.tablePagination[tableName] = {
|
|
first: 0,
|
|
page: 0,
|
|
rows: 10,
|
|
totalRecords: 0,
|
|
sortField: null,
|
|
sortOrder: null,
|
|
};
|
|
}
|
|
},
|
|
|
|
// Get formatted pagination parameters for API calls
|
|
getPaginationParams(tableName) {
|
|
const pagination = this.getTablePagination(tableName);
|
|
return {
|
|
page: pagination.page,
|
|
pageSize: pagination.rows,
|
|
offset: pagination.first,
|
|
limit: pagination.rows,
|
|
sortField: pagination.sortField,
|
|
sortOrder: pagination.sortOrder,
|
|
};
|
|
},
|
|
|
|
// Handle PrimeVue lazy pagination event
|
|
handleLazyLoad(tableName, event) {
|
|
console.log("Pagination lazy load event:", event);
|
|
|
|
const page = Math.floor(event.first / event.rows);
|
|
|
|
this.updateTablePagination(tableName, {
|
|
first: event.first,
|
|
page: page,
|
|
rows: event.rows,
|
|
sortField: event.sortField,
|
|
sortOrder: event.sortOrder,
|
|
});
|
|
|
|
return this.getPaginationParams(tableName);
|
|
},
|
|
|
|
// Cache management methods
|
|
generateCacheKey(page, pageSize, sortField, sortOrder, filters) {
|
|
const filterHash = this.hashFilters(filters);
|
|
return `${page}-${pageSize}-${sortField || "none"}-${sortOrder || 0}-${filterHash}`;
|
|
},
|
|
|
|
hashFilters(filters) {
|
|
if (!filters || Object.keys(filters).length === 0) return "no-filters";
|
|
|
|
const sortedKeys = Object.keys(filters).sort();
|
|
const filterString = sortedKeys
|
|
.map((key) => {
|
|
const filter = filters[key];
|
|
const value = filter?.value || "";
|
|
const matchMode = filter?.matchMode || "";
|
|
return `${key}:${value}:${matchMode}`;
|
|
})
|
|
.join("|");
|
|
|
|
// Simple hash function
|
|
let hash = 0;
|
|
for (let i = 0; i < filterString.length; i++) {
|
|
const char = filterString.charCodeAt(i);
|
|
hash = (hash << 5) - hash + char;
|
|
hash = hash & hash; // Convert to 32bit integer
|
|
}
|
|
return Math.abs(hash).toString(36);
|
|
},
|
|
|
|
getCachedPage(tableName, page, pageSize, sortField, sortOrder, filters) {
|
|
if (!this.pageCache[tableName]) return null;
|
|
|
|
const cacheKey = this.generateCacheKey(page, pageSize, sortField, sortOrder, filters);
|
|
const cachedEntry = this.pageCache[tableName][cacheKey];
|
|
|
|
if (!cachedEntry) return null;
|
|
|
|
// Check if cache entry is still valid (not expired)
|
|
const now = Date.now();
|
|
if (now - cachedEntry.timestamp > this.cacheTimeout) {
|
|
// Cache expired, remove it
|
|
delete this.pageCache[tableName][cacheKey];
|
|
return null;
|
|
}
|
|
|
|
console.log(`Cache HIT for ${tableName} page ${page + 1}`);
|
|
return cachedEntry.data;
|
|
},
|
|
|
|
setCachedPage(tableName, page, pageSize, sortField, sortOrder, filters, data) {
|
|
if (!this.pageCache[tableName]) {
|
|
this.pageCache[tableName] = {};
|
|
}
|
|
|
|
const cacheKey = this.generateCacheKey(page, pageSize, sortField, sortOrder, filters);
|
|
|
|
// Clean up old cache entries if we're at the limit
|
|
const cacheKeys = Object.keys(this.pageCache[tableName]);
|
|
if (cacheKeys.length >= this.maxCacheSize) {
|
|
// Remove oldest entries (simple FIFO approach)
|
|
const sortedEntries = cacheKeys
|
|
.map((key) => ({ key, timestamp: this.pageCache[tableName][key].timestamp }))
|
|
.sort((a, b) => a.timestamp - b.timestamp);
|
|
|
|
// Remove oldest 25% of entries
|
|
const toRemove = Math.floor(this.maxCacheSize * 0.25);
|
|
for (let i = 0; i < toRemove; i++) {
|
|
delete this.pageCache[tableName][sortedEntries[i].key];
|
|
}
|
|
}
|
|
|
|
this.pageCache[tableName][cacheKey] = {
|
|
data: JSON.parse(JSON.stringify(data)), // Deep clone to prevent mutations
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
console.log(
|
|
`Cache SET for ${tableName} page ${page + 1}, total cached pages: ${Object.keys(this.pageCache[tableName]).length}`,
|
|
);
|
|
},
|
|
|
|
clearTableCache(tableName) {
|
|
if (this.pageCache[tableName]) {
|
|
delete this.pageCache[tableName];
|
|
console.log(`Cache cleared for ${tableName}`);
|
|
}
|
|
},
|
|
|
|
clearExpiredCache() {
|
|
const now = Date.now();
|
|
Object.keys(this.pageCache).forEach((tableName) => {
|
|
Object.keys(this.pageCache[tableName]).forEach((cacheKey) => {
|
|
if (now - this.pageCache[tableName][cacheKey].timestamp > this.cacheTimeout) {
|
|
delete this.pageCache[tableName][cacheKey];
|
|
}
|
|
});
|
|
|
|
// If no cache entries left for this table, remove the table key
|
|
if (Object.keys(this.pageCache[tableName]).length === 0) {
|
|
delete this.pageCache[tableName];
|
|
}
|
|
});
|
|
},
|
|
|
|
getCacheStats(tableName) {
|
|
if (!this.pageCache[tableName]) {
|
|
return { totalPages: 0, totalSize: 0 };
|
|
}
|
|
|
|
const pages = Object.keys(this.pageCache[tableName]);
|
|
const totalSize = pages.reduce((size, key) => {
|
|
return size + JSON.stringify(this.pageCache[tableName][key]).length;
|
|
}, 0);
|
|
|
|
return {
|
|
totalPages: pages.length,
|
|
totalSize: Math.round(totalSize / 1024) + " KB",
|
|
};
|
|
},
|
|
},
|
|
});
|