ajout de component tableau

This commit is contained in:
Antoine BRYJA
2025-11-12 21:55:05 +01:00
parent f759e92698
commit 9ef94762dd
2 changed files with 169 additions and 1 deletions

View 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>

View File

@@ -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>