ajout de component tableau
This commit is contained in:
164
front-end/src/components/Tableau.vue
Normal file
164
front-end/src/components/Tableau.vue
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<div class="data-table">
|
||||||
|
<div class="dt-toolbar">
|
||||||
|
<input v-model="search" placeholder="Rechercher..." class="dt-search" />
|
||||||
|
<div class="dt-controls">
|
||||||
|
<label>Afficher
|
||||||
|
<select v-model.number="perPage">
|
||||||
|
<option v-for="n in [5,10,20,50]" :key="n" :value="n">{{n}}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="dt-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th v-for="col in columns" :key="col.key" @click="toggleSort(col.key)" class="dt-th">
|
||||||
|
<span>{{ col.label }}</span>
|
||||||
|
<span class="dt-sort" v-if="sortKey === col.key">{{ sortDir === 'asc' ? '▲' : '▼' }}</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in pagedData" :key="row.id" class="dt-row">
|
||||||
|
<td v-for="col in columns" :key="col.key">{{ get(row, col.key) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="pagedData.length === 0">
|
||||||
|
<td :colspan="columns.length" class="dt-empty">Aucun résultat</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="dt-footer">
|
||||||
|
<div class="dt-info">Affichage {{ startItem }} - {{ endItem }} sur {{ filteredData.length }}</div>
|
||||||
|
<div class="dt-pager">
|
||||||
|
<button @click="prevPage" :disabled="page === 1">Préc</button>
|
||||||
|
<button v-for="p in pages" :key="p" @click="page = p" :class="{active: page === p}">{{ p }}</button>
|
||||||
|
<button @click="nextPage" :disabled="page === totalPages">Suiv</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
|
||||||
|
// Accept columns and rows as props so the table is reusable.
|
||||||
|
import type { PropType } from 'vue'
|
||||||
|
|
||||||
|
const defaultColumns = [
|
||||||
|
{ key: 'id', label: 'ID' },
|
||||||
|
{ key: 'name', label: 'Nom' },
|
||||||
|
{ key: 'email', label: 'Email' },
|
||||||
|
{ key: 'phone', label: 'Téléphone' },
|
||||||
|
{ key: 'role', label: 'Role' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const defaultRows = [
|
||||||
|
{ id: 1, name: 'Alice Dupont', email: 'alice@example.com', phone: '0612345678', role: 'Client' },
|
||||||
|
{ id: 2, name: 'Bob Martin', email: 'bob@example.com', phone: '0623456789', role: 'Support' },
|
||||||
|
{ id: 3, name: 'Claire Durand', email: 'claire@example.com', phone: '0634567890', role: 'Client' },
|
||||||
|
{ id: 4, name: 'David Roche', email: 'david@example.com', phone: '0645678901', role: 'Technicien' },
|
||||||
|
{ id: 5, name: 'Emma Petit', email: 'emma@example.com', phone: '0656789012', role: 'Client' },
|
||||||
|
{ id: 6, name: 'Franck Noel', email: 'franck@example.com', phone: '0667890123', role: 'Support' },
|
||||||
|
{ id: 7, name: 'Gisele Moreau', email: 'gisele@example.com', phone: '0678901234', role: 'Client' },
|
||||||
|
{ id: 8, name: 'Hugo Leroy', email: 'hugo@example.com', phone: '0689012345', role: 'Technicien' },
|
||||||
|
{ id: 9, name: 'Isabelle Faure', email: 'isa@example.com', phone: '0690123456', role: 'Client' },
|
||||||
|
{ id: 10, name: 'Julien Blanc', email: 'julien@example.com', phone: '0701234567', role: 'Support' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// define props without default functions (defineProps is hoisted and must not reference local vars)
|
||||||
|
const props = defineProps<{ columns?: { key: string; label: string }[]; rows?: Record<string, any>[] }>()
|
||||||
|
|
||||||
|
const columns = computed(() => props.columns ?? defaultColumns)
|
||||||
|
const rows = ref(props.rows ?? defaultRows)
|
||||||
|
|
||||||
|
// keep rows reactive to prop changes
|
||||||
|
watch(() => props.rows, (v) => { rows.value = v ?? defaultRows })
|
||||||
|
|
||||||
|
// reactive UI state
|
||||||
|
const search = ref('')
|
||||||
|
const sortKey = ref<string | null>(null)
|
||||||
|
const sortDir = ref<'asc' | 'desc'>('asc')
|
||||||
|
const page = ref(1)
|
||||||
|
const perPage = ref(5)
|
||||||
|
|
||||||
|
// computed filtered rows
|
||||||
|
const filteredData = computed(() => {
|
||||||
|
const q = search.value.trim().toLowerCase()
|
||||||
|
if (!q) return rows.value
|
||||||
|
return rows.value.filter(r =>
|
||||||
|
columns.value.some((c: { key: string; label: string }) => String((r as any)[c.key]).toLowerCase().includes(q))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// sorted
|
||||||
|
const sortedData = computed(() => {
|
||||||
|
if (!sortKey.value) return filteredData.value
|
||||||
|
const key = sortKey.value
|
||||||
|
const dir = sortDir.value === 'asc' ? 1 : -1
|
||||||
|
return [...filteredData.value].sort((a, b) => {
|
||||||
|
const A = (a as any)[key]
|
||||||
|
const B = (b as any)[key]
|
||||||
|
if (A == null) return -1 * dir
|
||||||
|
if (B == null) return 1 * dir
|
||||||
|
if (typeof A === 'number' && typeof B === 'number') return (A - B) * dir
|
||||||
|
return String(A).localeCompare(String(B)) * dir
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// pagination
|
||||||
|
const totalPages = computed(() => Math.max(1, Math.ceil(sortedData.value.length / perPage.value)))
|
||||||
|
const pages = computed(() => Array.from({ length: totalPages.value }, (_, i) => i + 1))
|
||||||
|
const pagedData = computed(() => {
|
||||||
|
const start = (page.value - 1) * perPage.value
|
||||||
|
return sortedData.value.slice(start, start + perPage.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const startItem = computed(() => (sortedData.value.length === 0 ? 0 : (page.value - 1) * perPage.value + 1))
|
||||||
|
const endItem = computed(() => Math.min(sortedData.value.length, page.value * perPage.value))
|
||||||
|
|
||||||
|
function toggleSort(key: string) {
|
||||||
|
if (sortKey.value === key) {
|
||||||
|
sortDir.value = sortDir.value === 'asc' ? 'desc' : 'asc'
|
||||||
|
} else {
|
||||||
|
sortKey.value = key
|
||||||
|
sortDir.value = 'asc'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevPage() {
|
||||||
|
if (page.value > 1) page.value--
|
||||||
|
}
|
||||||
|
function nextPage() {
|
||||||
|
if (page.value < totalPages.value) page.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset page when filters change
|
||||||
|
watch([search, perPage, () => sortedData.value.length], () => {
|
||||||
|
page.value = 1
|
||||||
|
})
|
||||||
|
|
||||||
|
function get(row: any, key: string) {
|
||||||
|
return (row as any)[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.data-table{font-family:Inter, sans-serif;color:#16324F}
|
||||||
|
.dt-toolbar{display:flex;justify-content:space-between;gap:1rem;margin-bottom:0.5rem}
|
||||||
|
.dt-search{padding:6px 8px;border-radius:6px;border:1px solid #cbd5e1}
|
||||||
|
.dt-table{width:100%;border-collapse:collapse;background:#fff}
|
||||||
|
.dt-table th,.dt-table td{padding:10px 12px;border-bottom:1px solid #e6eef6;text-align:left}
|
||||||
|
.dt-table thead th{background:#f3f7fb;cursor:pointer}
|
||||||
|
.dt-row:hover{background:#f9fbfd}
|
||||||
|
.dt-empty{padding:20px;text-align:center;color:#6b7a8a}
|
||||||
|
.dt-footer{display:flex;justify-content:space-between;align-items:center;margin-top:8px}
|
||||||
|
.dt-pager button{margin:0 3px;padding:6px 8px;border-radius:4px;border:1px solid #cbd5e1;background:#fff;cursor:pointer}
|
||||||
|
.dt-pager button.active{background:#2D4570;color:#fff}
|
||||||
|
.dt-info{font-size:13px;color:#556b7a}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-ticket">
|
<div class="container-ticket">
|
||||||
Ticket
|
Ticket
|
||||||
|
<div class="tableau-ticket">
|
||||||
|
<Tableau></Tableau>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import Tableau from '@/components/Tableau.vue';
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
Reference in New Issue
Block a user