Files
BarangaySystem/resources/js/Components/Core/SearchableTable.vue
2026-06-06 18:43:00 +08:00

117 lines
3.2 KiB
Vue

<template>
<div id="tableContainer">
<table :id="tableId" class="display">
<thead>
<tr>
<th v-for="(header, i) in computedHeaders" :key="i">{{ header }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="(row, rowIdx) in filteredData"
:key="rowIdx"
@click="$emit('row-click', row, rowIdx)"
class="searchable-row"
>
<td v-for="(header, colIdx) in computedHeaders" :key="colIdx">
{{ isObjectData ? (row[header] ?? '') : (row[colIdx] ?? '') }}
</td>
</tr>
</tbody>
</table>
<!-- Simple pagination -->
<div v-if="totalPages > 1" class="table-pagination mt-3 d-flex justify-content-between align-items-center">
<button class="btn btn-sm btn-default" :disabled="currentPage <= 1" @click="currentPage--">
Previous
</button>
<span>Page {{ currentPage }} of {{ totalPages }}</span>
<button class="btn btn-sm btn-default" :disabled="currentPage >= totalPages" @click="currentPage++">
Next
</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
/**
* Array of objects or array of arrays
*/
data: { type: Array, required: true },
headers: { type: Array, default: () => [] },
tableId: { type: String, default: 'dynamicTable' },
pageLength: { type: Number, default: 10 },
defaultSort: { type: Object, default: () => ({ column: 0, direction: 'asc' }) },
defaultSearch: { type: String, default: '' },
})
defineEmits(['row-click'])
const currentPage = ref(1)
const searchTerm = ref(props.defaultSearch)
const isObjectData = computed(() =>
props.data.length > 0 &&
props.data[0] !== null &&
typeof props.data[0] === 'object' &&
!Array.isArray(props.data[0])
)
const computedHeaders = computed(() => {
if (props.headers.length > 0) return props.headers
if (isObjectData.value) return Object.keys(props.data[0])
return []
})
const sortedData = computed(() => {
const dataCopy = [...props.data]
const { column, direction } = props.defaultSort
dataCopy.sort((a, b) => {
const valA = isObjectData.value ? a[computedHeaders.value[column]] : a[column]
const valB = isObjectData.value ? b[computedHeaders.value[column]] : b[column]
if (valA < valB) return direction === 'asc' ? -1 : 1
if (valA > valB) return direction === 'asc' ? 1 : -1
return 0
})
return dataCopy
})
const filteredData = computed(() => {
let result = sortedData.value
if (searchTerm.value) {
const q = searchTerm.value.toLowerCase()
result = result.filter(row => {
const text = isObjectData.value
? Object.values(row).join(' ')
: row.join(' ')
return text.toLowerCase().includes(q)
})
}
// Pagination
const start = (currentPage.value - 1) * props.pageLength
return result.slice(start, start + props.pageLength)
})
const totalPages = computed(() => {
const total = sortedData.value.length
return Math.max(1, Math.ceil(total / props.pageLength))
})
</script>
<style scoped>
.searchable-row {
cursor: pointer;
}
.searchable-row:hover {
background-color: rgba(0, 0, 0, 0.05);
}
</style>