Initial Commit

This commit is contained in:
2026-10-03 20:07:49 +02:00
commit a1346c98b1
427 changed files with 59320 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
<script setup lang="ts" generic="TData">
import type { ColumnDef, Row } from '@tanstack/vue-table'
import { FlexRender, useTable } from '@tanstack/vue-table'
import { dataTableFeatures, type DataTableFeatures } from '@/lib/table'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import {
Table,
TableBody,
TableCell,
TableEmpty,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
const props = withDefaults(defineProps<{
columns: ColumnDef<DataTableFeatures, TData, unknown>[]
data: TData[]
// Suchfeld über den Global-Filter — searchFn entscheidet, was gematcht wird
searchPlaceholder?: string
searchFn?: (row: TData, query: string) => boolean
pageSize?: number
}>(), {
pageSize: 10,
})
const searchFn = props.searchFn
const table = useTable<DataTableFeatures, TData>({
features: dataTableFeatures,
get columns() { return props.columns },
get data() { return props.data },
globalFilterFn: searchFn
? (row: Row<DataTableFeatures, TData>, _columnId: string, filterValue: unknown) =>
searchFn(row.original, String(filterValue))
: 'auto',
initialState: {
pagination: { pageSize: props.pageSize },
},
})
const pageIndex = computed(() => table.atoms.pagination.get().pageIndex)
const pageCount = computed(() => table.getPageCount())
const globalFilter = computed(() => String(table.atoms.globalFilter.get() ?? ''))
</script>
<template>
<div class="flex flex-col gap-3">
<div v-if="searchPlaceholder" class="relative max-w-xs">
<Icon name="ph:magnifying-glass-fill" class="absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground" />
<Input
:model-value="globalFilter"
:placeholder="searchPlaceholder"
class="pl-8"
@update:model-value="table.setGlobalFilter(String($event))"
/>
</div>
<div class="rounded-md border">
<Table>
<TableHeader>
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
<TableHead v-for="header in headerGroup.headers" :key="header.id">
<slot :name="`header-${header.column.id}`" :header="header" :column="header.column">
<button
v-if="header.column.getCanSort()"
type="button"
class="flex items-center gap-1.5 font-medium transition-colors hover:text-foreground"
@click="header.column.toggleSorting()"
>
<FlexRender :header="header" />
<Icon name="ph:arrow-up-fill" v-if="header.column.getIsSorted() === 'asc'" class="size-3.5" />
<Icon name="ph:arrow-down-fill" v-else-if="header.column.getIsSorted() === 'desc'" class="size-3.5" />
<Icon name="ph:arrows-down-up-fill" v-else class="size-3.5 opacity-50" />
</button>
<FlexRender v-else :header="header" />
</slot>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<template v-if="table.getRowModel().rows.length">
<TableRow v-for="row in table.getRowModel().rows" :key="row.id">
<TableCell v-for="cell in row.getAllCells()" :key="cell.id">
<slot :name="`cell-${cell.column.id}`" :row="row" :cell="cell">
<FlexRender :cell="cell" />
</slot>
</TableCell>
</TableRow>
</template>
<TableEmpty v-else :colspan="columns.length">
Keine Einträge.
</TableEmpty>
</TableBody>
</Table>
</div>
<div v-if="pageCount > 1" class="flex items-center justify-between">
<span class="text-sm text-muted-foreground">
Seite {{ pageIndex + 1 }} von {{ pageCount }}
</span>
<div class="flex gap-2">
<Button
variant="outline"
size="sm"
:disabled="!table.getCanPreviousPage()"
@click="table.previousPage()"
>
Zurück
</Button>
<Button
variant="outline"
size="sm"
:disabled="!table.getCanNextPage()"
@click="table.nextPage()"
>
Weiter
</Button>
</div>
</div>
</div>
</template>