Implement Pagination for Form and Process Management
- Added pagination functionality to the form management and process management pages, allowing users to navigate through forms and processes more efficiently. - Introduced controls for items per page selection and pagination navigation, enhancing user experience and accessibility of data. - Updated computed properties to handle paginated data and maintain accurate pagination state across filters and searches. - Ensured that pagination resets when filters change, providing a consistent and intuitive user interface.
This commit is contained in:
parent
0c93e93460
commit
77e3b8601f
@ -398,65 +398,6 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</rs-card>
|
</rs-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Development Roadmap -->
|
|
||||||
<rs-card>
|
|
||||||
<template #header>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<Icon name="material-symbols:road" class="w-5 h-5 mr-2 text-purple-500" />
|
|
||||||
Development Roadmap & System Guide
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #body>
|
|
||||||
<div class="space-y-6">
|
|
||||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
|
||||||
<h3 class="font-semibold text-blue-900 mb-2">🚀 Getting Started with Corrad BPM</h3>
|
|
||||||
<p class="text-blue-800 text-sm mb-3">
|
|
||||||
Follow these steps to start developing business processes in the system:
|
|
||||||
</p>
|
|
||||||
<ol class="list-decimal list-inside space-y-1 text-sm text-blue-800">
|
|
||||||
<li><strong>Design Forms:</strong> Use Form Builder to create data collection forms</li>
|
|
||||||
<li><strong>Create Processes:</strong> Use Process Builder to design workflow logic</li>
|
|
||||||
<li><strong>Configure Integrations:</strong> Set up API calls and business rules</li>
|
|
||||||
<li><strong>Test & Deploy:</strong> Test process execution and publish to production</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
||||||
<div
|
|
||||||
v-for="phase in developmentRoadmap"
|
|
||||||
:key="phase.phase"
|
|
||||||
class="border border-gray-200 rounded-lg p-4"
|
|
||||||
>
|
|
||||||
<div class="flex items-center justify-between mb-3">
|
|
||||||
<h4 class="font-semibold text-gray-900">{{ phase.phase }}</h4>
|
|
||||||
<RsBadge :class="getStatusColor(phase.status)" size="sm">
|
|
||||||
{{ phase.status.replace('-', ' ') }}
|
|
||||||
</RsBadge>
|
|
||||||
</div>
|
|
||||||
<ul class="space-y-2">
|
|
||||||
<li
|
|
||||||
v-for="item in phase.items"
|
|
||||||
:key="item"
|
|
||||||
class="flex items-start text-sm text-gray-600"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
:name="phase.status === 'completed' ? 'material-symbols:check-circle' :
|
|
||||||
phase.status === 'in-progress' ? 'material-symbols:radio-button-partial' :
|
|
||||||
'material-symbols:radio-button-unchecked'"
|
|
||||||
:class="phase.status === 'completed' ? 'text-green-500' :
|
|
||||||
phase.status === 'in-progress' ? 'text-blue-500' :
|
|
||||||
'text-gray-400'"
|
|
||||||
class="w-4 h-4 mr-2 mt-0.5 flex-shrink-0"
|
|
||||||
/>
|
|
||||||
{{ item }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</rs-card>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@
|
|||||||
<!-- Forms Grid -->
|
<!-- Forms Grid -->
|
||||||
<div v-else-if="filteredForms.length > 0" class="grid gap-4">
|
<div v-else-if="filteredForms.length > 0" class="grid gap-4">
|
||||||
<div
|
<div
|
||||||
v-for="form in filteredForms"
|
v-for="form in paginatedForms"
|
||||||
:key="form.id"
|
:key="form.id"
|
||||||
class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow"
|
class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow"
|
||||||
>
|
>
|
||||||
@ -204,8 +204,126 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div v-if="filteredForms.length > 0 && totalPages > 1" class="mt-6 bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
||||||
|
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
|
<!-- Pagination Info -->
|
||||||
|
<div class="text-sm text-gray-700">
|
||||||
|
Showing {{ paginationInfo.start }}-{{ paginationInfo.end }} of {{ paginationInfo.total }} forms
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Items per page selector -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-sm text-gray-700">Items per page:</span>
|
||||||
|
<FormKit
|
||||||
|
:model-value="itemsPerPage"
|
||||||
|
@update:model-value="changeItemsPerPage"
|
||||||
|
type="select"
|
||||||
|
:options="[
|
||||||
|
{ label: '5', value: 5 },
|
||||||
|
{ label: '10', value: 10 },
|
||||||
|
{ label: '20', value: 20 },
|
||||||
|
{ label: '50', value: 50 }
|
||||||
|
]"
|
||||||
|
:classes="{
|
||||||
|
outer: 'mb-0',
|
||||||
|
input: 'px-2 py-1 border border-gray-300 rounded text-sm bg-white min-w-16'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination Controls -->
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<!-- Previous Button -->
|
||||||
|
<button
|
||||||
|
@click="prevPage"
|
||||||
|
:disabled="!hasPrevPage"
|
||||||
|
class="px-3 py-2 text-sm border border-gray-300 rounded-lg bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
<Icon name="material-symbols:chevron-left" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Page Numbers -->
|
||||||
|
<template v-if="totalPages <= 7">
|
||||||
|
<!-- Show all pages if 7 or fewer -->
|
||||||
|
<button
|
||||||
|
v-for="page in totalPages"
|
||||||
|
:key="page"
|
||||||
|
@click="goToPage(page)"
|
||||||
|
:class="[
|
||||||
|
'px-3 py-2 text-sm border rounded-lg transition-colors',
|
||||||
|
page === currentPage
|
||||||
|
? 'bg-blue-600 text-white border-blue-600'
|
||||||
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ page }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<!-- Show first page -->
|
||||||
|
<button
|
||||||
|
@click="goToPage(1)"
|
||||||
|
:class="[
|
||||||
|
'px-3 py-2 text-sm border rounded-lg transition-colors',
|
||||||
|
1 === currentPage
|
||||||
|
? 'bg-blue-600 text-white border-blue-600'
|
||||||
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Show ellipsis if needed -->
|
||||||
|
<span v-if="currentPage > 3" class="px-2 text-gray-500">...</span>
|
||||||
|
|
||||||
|
<!-- Show pages around current page -->
|
||||||
|
<button
|
||||||
|
v-for="page in [currentPage - 1, currentPage, currentPage + 1].filter(p => p > 1 && p < totalPages)"
|
||||||
|
:key="page"
|
||||||
|
@click="goToPage(page)"
|
||||||
|
:class="[
|
||||||
|
'px-3 py-2 text-sm border rounded-lg transition-colors',
|
||||||
|
page === currentPage
|
||||||
|
? 'bg-blue-600 text-white border-blue-600'
|
||||||
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ page }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Show ellipsis if needed -->
|
||||||
|
<span v-if="currentPage < totalPages - 2" class="px-2 text-gray-500">...</span>
|
||||||
|
|
||||||
|
<!-- Show last page -->
|
||||||
|
<button
|
||||||
|
v-if="totalPages > 1"
|
||||||
|
@click="goToPage(totalPages)"
|
||||||
|
:class="[
|
||||||
|
'px-3 py-2 text-sm border rounded-lg transition-colors',
|
||||||
|
totalPages === currentPage
|
||||||
|
? 'bg-blue-600 text-white border-blue-600'
|
||||||
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ totalPages }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Next Button -->
|
||||||
|
<button
|
||||||
|
@click="nextPage"
|
||||||
|
:disabled="!hasNextPage"
|
||||||
|
class="px-3 py-2 text-sm border border-gray-300 rounded-lg bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
<Icon name="material-symbols:chevron-right" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Empty State -->
|
<!-- Empty State -->
|
||||||
<div v-else class="bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center">
|
<div v-else-if="filteredForms.length === 0" class="bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center">
|
||||||
<Icon name="material-symbols:description-outline" class="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<Icon name="material-symbols:description-outline" class="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||||
{{ hasActiveFilters ? 'No forms match your filters' : 'No forms found' }}
|
{{ hasActiveFilters ? 'No forms match your filters' : 'No forms found' }}
|
||||||
@ -437,6 +555,11 @@ const jsonContent = ref('');
|
|||||||
const jsonValidationMessage = ref('');
|
const jsonValidationMessage = ref('');
|
||||||
const jsonIsValid = ref(false);
|
const jsonIsValid = ref(false);
|
||||||
|
|
||||||
|
// Pagination state
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const itemsPerPage = ref(5); // Set to 5 to test pagination
|
||||||
|
const totalItems = ref(0);
|
||||||
|
|
||||||
// Computed properties for grouping
|
// Computed properties for grouping
|
||||||
const categoryOptions = computed(() => {
|
const categoryOptions = computed(() => {
|
||||||
const categories = [...new Set(formStore.savedForms.map(form => form.category).filter(Boolean))];
|
const categories = [...new Set(formStore.savedForms.map(form => form.category).filter(Boolean))];
|
||||||
@ -458,7 +581,7 @@ const hasActiveFilters = computed(() => {
|
|||||||
return searchQuery.value || selectedCategory.value || selectedGroup.value;
|
return searchQuery.value || selectedCategory.value || selectedGroup.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filtered forms
|
// Filtered forms (all forms after applying filters)
|
||||||
const filteredForms = computed(() => {
|
const filteredForms = computed(() => {
|
||||||
let filtered = formStore.savedForms;
|
let filtered = formStore.savedForms;
|
||||||
|
|
||||||
@ -482,9 +605,42 @@ const filteredForms = computed(() => {
|
|||||||
filtered = filtered.filter(form => form.group === selectedGroup.value);
|
filtered = filtered.filter(form => form.group === selectedGroup.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update total items for pagination
|
||||||
|
totalItems.value = filtered.length;
|
||||||
|
|
||||||
return filtered;
|
return filtered;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Paginated forms (forms for current page)
|
||||||
|
const paginatedForms = computed(() => {
|
||||||
|
const start = (currentPage.value - 1) * itemsPerPage.value;
|
||||||
|
const end = start + itemsPerPage.value;
|
||||||
|
return filteredForms.value.slice(start, end);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pagination computed properties
|
||||||
|
const totalPages = computed(() => {
|
||||||
|
return Math.ceil(totalItems.value / itemsPerPage.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasNextPage = computed(() => {
|
||||||
|
return currentPage.value < totalPages.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasPrevPage = computed(() => {
|
||||||
|
return currentPage.value > 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const paginationInfo = computed(() => {
|
||||||
|
const start = totalItems.value === 0 ? 0 : (currentPage.value - 1) * itemsPerPage.value + 1;
|
||||||
|
const end = Math.min(currentPage.value * itemsPerPage.value, totalItems.value);
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
total: totalItems.value
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Format date for display
|
// Format date for display
|
||||||
const formatDate = (isoString) => {
|
const formatDate = (isoString) => {
|
||||||
if (!isoString) return '';
|
if (!isoString) return '';
|
||||||
@ -623,6 +779,8 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// Watch for changes in search and filters
|
// Watch for changes in search and filters
|
||||||
watch([searchQuery, selectedCategory, selectedGroup], () => {
|
watch([searchQuery, selectedCategory, selectedGroup], () => {
|
||||||
|
// Reset to first page when filters change
|
||||||
|
currentPage.value = 1;
|
||||||
// Debounce the search to avoid too many API calls
|
// Debounce the search to avoid too many API calls
|
||||||
clearTimeout(searchTimeout);
|
clearTimeout(searchTimeout);
|
||||||
searchTimeout = setTimeout(() => {
|
searchTimeout = setTimeout(() => {
|
||||||
@ -637,9 +795,34 @@ const clearFilters = () => {
|
|||||||
searchQuery.value = '';
|
searchQuery.value = '';
|
||||||
selectedCategory.value = '';
|
selectedCategory.value = '';
|
||||||
selectedGroup.value = '';
|
selectedGroup.value = '';
|
||||||
|
currentPage.value = 1; // Reset to first page
|
||||||
// loadForms will be called automatically by the watcher
|
// loadForms will be called automatically by the watcher
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Pagination methods
|
||||||
|
const goToPage = (page) => {
|
||||||
|
if (page >= 1 && page <= totalPages.value) {
|
||||||
|
currentPage.value = page;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextPage = () => {
|
||||||
|
if (hasNextPage.value) {
|
||||||
|
currentPage.value++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevPage = () => {
|
||||||
|
if (hasPrevPage.value) {
|
||||||
|
currentPage.value--;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeItemsPerPage = (newItemsPerPage) => {
|
||||||
|
itemsPerPage.value = newItemsPerPage;
|
||||||
|
currentPage.value = 1; // Reset to first page when changing items per page
|
||||||
|
};
|
||||||
|
|
||||||
// Helper function to get category color
|
// Helper function to get category color
|
||||||
const getCategoryColor = (category) => {
|
const getCategoryColor = (category) => {
|
||||||
const colors = {
|
const colors = {
|
||||||
|
@ -29,6 +29,11 @@ const sortBy = ref('processCreatedDate');
|
|||||||
const sortOrder = ref('desc');
|
const sortOrder = ref('desc');
|
||||||
const currentView = ref('dashboard'); // 'dashboard', 'list', 'analytics'
|
const currentView = ref('dashboard'); // 'dashboard', 'list', 'analytics'
|
||||||
|
|
||||||
|
// Pagination state
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const itemsPerPage = ref(5); // Set to 5 to test pagination
|
||||||
|
const totalItems = ref(0);
|
||||||
|
|
||||||
// Dashboard metrics and data
|
// Dashboard metrics and data
|
||||||
const dashboardMetrics = ref({
|
const dashboardMetrics = ref({
|
||||||
totalProcesses: 0,
|
totalProcesses: 0,
|
||||||
@ -66,9 +71,44 @@ const categoryOptions = [
|
|||||||
{ value: 'Procurement', label: 'Procurement' }
|
{ value: 'Procurement', label: 'Procurement' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// Filtered processes
|
// Filtered processes (all processes after applying filters)
|
||||||
const filteredProcesses = computed(() => {
|
const filteredProcesses = computed(() => {
|
||||||
return processStore.processes;
|
let filtered = processStore.processes;
|
||||||
|
|
||||||
|
// Update total items for pagination
|
||||||
|
totalItems.value = filtered.length;
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Paginated processes (processes for current page)
|
||||||
|
const paginatedProcesses = computed(() => {
|
||||||
|
const start = (currentPage.value - 1) * itemsPerPage.value;
|
||||||
|
const end = start + itemsPerPage.value;
|
||||||
|
return filteredProcesses.value.slice(start, end);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pagination computed properties
|
||||||
|
const totalPages = computed(() => {
|
||||||
|
return Math.ceil(totalItems.value / itemsPerPage.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasNextPage = computed(() => {
|
||||||
|
return currentPage.value < totalPages.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasPrevPage = computed(() => {
|
||||||
|
return currentPage.value > 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const paginationInfo = computed(() => {
|
||||||
|
const start = totalItems.value === 0 ? 0 : (currentPage.value - 1) * itemsPerPage.value + 1;
|
||||||
|
const end = Math.min(currentPage.value * itemsPerPage.value, totalItems.value);
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
total: totalItems.value
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load dashboard summary data from API
|
// Load dashboard summary data from API
|
||||||
@ -220,6 +260,8 @@ const loadProcesses = async () => {
|
|||||||
|
|
||||||
// Watch for changes in filters and reload processes
|
// Watch for changes in filters and reload processes
|
||||||
watch([searchQuery, statusFilter, categoryFilter], () => {
|
watch([searchQuery, statusFilter, categoryFilter], () => {
|
||||||
|
// Reset to first page when filters change
|
||||||
|
currentPage.value = 1;
|
||||||
clearTimeout(searchTimeout);
|
clearTimeout(searchTimeout);
|
||||||
searchTimeout = setTimeout(() => {
|
searchTimeout = setTimeout(() => {
|
||||||
loadProcesses();
|
loadProcesses();
|
||||||
@ -349,6 +391,31 @@ const clearFilters = () => {
|
|||||||
searchQuery.value = '';
|
searchQuery.value = '';
|
||||||
statusFilter.value = '';
|
statusFilter.value = '';
|
||||||
categoryFilter.value = '';
|
categoryFilter.value = '';
|
||||||
|
currentPage.value = 1; // Reset to first page
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pagination methods
|
||||||
|
const goToPage = (page) => {
|
||||||
|
if (page >= 1 && page <= totalPages.value) {
|
||||||
|
currentPage.value = page;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextPage = () => {
|
||||||
|
if (hasNextPage.value) {
|
||||||
|
currentPage.value++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevPage = () => {
|
||||||
|
if (hasPrevPage.value) {
|
||||||
|
currentPage.value--;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeItemsPerPage = (newItemsPerPage) => {
|
||||||
|
itemsPerPage.value = newItemsPerPage;
|
||||||
|
currentPage.value = 1; // Reset to first page when changing items per page
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load processes on component mount
|
// Load processes on component mount
|
||||||
@ -653,8 +720,9 @@ const copyWorkflowLink = async (processId) => {
|
|||||||
|
|
||||||
<!-- Processes Grid -->
|
<!-- Processes Grid -->
|
||||||
<div v-else-if="filteredProcesses.length > 0" class="grid gap-4">
|
<div v-else-if="filteredProcesses.length > 0" class="grid gap-4">
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-for="process in filteredProcesses"
|
v-for="process in paginatedProcesses"
|
||||||
:key="process.id"
|
:key="process.id"
|
||||||
class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow"
|
class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow"
|
||||||
>
|
>
|
||||||
@ -774,8 +842,126 @@ const copyWorkflowLink = async (processId) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div v-if="filteredProcesses.length > 0 && totalPages > 1" class="mt-6 bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
||||||
|
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
|
<!-- Pagination Info -->
|
||||||
|
<div class="text-sm text-gray-700">
|
||||||
|
Showing {{ paginationInfo.start }}-{{ paginationInfo.end }} of {{ paginationInfo.total }} processes
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Items per page selector -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-sm text-gray-700">Items per page:</span>
|
||||||
|
<FormKit
|
||||||
|
:model-value="itemsPerPage"
|
||||||
|
@update:model-value="changeItemsPerPage"
|
||||||
|
type="select"
|
||||||
|
:options="[
|
||||||
|
{ label: '5', value: 5 },
|
||||||
|
{ label: '10', value: 10 },
|
||||||
|
{ label: '20', value: 20 },
|
||||||
|
{ label: '50', value: 50 }
|
||||||
|
]"
|
||||||
|
:classes="{
|
||||||
|
outer: 'mb-0',
|
||||||
|
input: 'px-2 py-1 border border-gray-300 rounded text-sm bg-white min-w-16'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination Controls -->
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<!-- Previous Button -->
|
||||||
|
<button
|
||||||
|
@click="prevPage"
|
||||||
|
:disabled="!hasPrevPage"
|
||||||
|
class="px-3 py-2 text-sm border border-gray-300 rounded-lg bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
<Icon name="material-symbols:chevron-left" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Page Numbers -->
|
||||||
|
<template v-if="totalPages <= 7">
|
||||||
|
<!-- Show all pages if 7 or fewer -->
|
||||||
|
<button
|
||||||
|
v-for="page in totalPages"
|
||||||
|
:key="page"
|
||||||
|
@click="goToPage(page)"
|
||||||
|
:class="[
|
||||||
|
'px-3 py-2 text-sm border rounded-lg transition-colors',
|
||||||
|
page === currentPage
|
||||||
|
? 'bg-blue-600 text-white border-blue-600'
|
||||||
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ page }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<!-- Show first page -->
|
||||||
|
<button
|
||||||
|
@click="goToPage(1)"
|
||||||
|
:class="[
|
||||||
|
'px-3 py-2 text-sm border rounded-lg transition-colors',
|
||||||
|
1 === currentPage
|
||||||
|
? 'bg-blue-600 text-white border-blue-600'
|
||||||
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Show ellipsis if needed -->
|
||||||
|
<span v-if="currentPage > 3" class="px-2 text-gray-500">...</span>
|
||||||
|
|
||||||
|
<!-- Show pages around current page -->
|
||||||
|
<button
|
||||||
|
v-for="page in [currentPage - 1, currentPage, currentPage + 1].filter(p => p > 1 && p < totalPages)"
|
||||||
|
:key="page"
|
||||||
|
@click="goToPage(page)"
|
||||||
|
:class="[
|
||||||
|
'px-3 py-2 text-sm border rounded-lg transition-colors',
|
||||||
|
page === currentPage
|
||||||
|
? 'bg-blue-600 text-white border-blue-600'
|
||||||
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ page }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Show ellipsis if needed -->
|
||||||
|
<span v-if="currentPage < totalPages - 2" class="px-2 text-gray-500">...</span>
|
||||||
|
|
||||||
|
<!-- Show last page -->
|
||||||
|
<button
|
||||||
|
v-if="totalPages > 1"
|
||||||
|
@click="goToPage(totalPages)"
|
||||||
|
:class="[
|
||||||
|
'px-3 py-2 text-sm border rounded-lg transition-colors',
|
||||||
|
totalPages === currentPage
|
||||||
|
? 'bg-blue-600 text-white border-blue-600'
|
||||||
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ totalPages }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Next Button -->
|
||||||
|
<button
|
||||||
|
@click="nextPage"
|
||||||
|
:disabled="!hasNextPage"
|
||||||
|
class="px-3 py-2 text-sm border border-gray-300 rounded-lg bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
<Icon name="material-symbols:chevron-right" class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Empty State -->
|
<!-- Empty State -->
|
||||||
<div v-else class="bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center">
|
<div v-else-if="filteredProcesses.length === 0" class="bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center">
|
||||||
<Icon name="material-symbols:folder-open-outline" class="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<Icon name="material-symbols:folder-open-outline" class="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||||
{{ (searchQuery || statusFilter || categoryFilter) ? 'No processes match your filters' : 'No processes found' }}
|
{{ (searchQuery || statusFilter || categoryFilter) ? 'No processes match your filters' : 'No processes found' }}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user