feat: enhance EventFightList with grouping and selection features
All checks were successful
SteamWarCI Build successful

- Added grouping functionality to the EventFightList component, allowing fights to be grouped by their associated group.
- Implemented row selection with checkboxes for both individual fights and groups, enabling bulk selection.
- Updated columns definition to include a checkbox for selecting all rows and individual row selection checkboxes.
- Modified the checkbox component to support indeterminate state and improved styling.
- Enhanced date formatting for fight start times in the table.
This commit is contained in:
2025-04-15 16:28:19 +02:00
parent 063638d016
commit 9eea0b2b3f
7 changed files with 158 additions and 9343 deletions

1
.gitignore vendored
View File

@ -26,3 +26,4 @@ pnpm-debug.log*
/src/env.d.ts /src/env.d.ts
/src/pages/en/ /src/pages/en/
/.idea /.idea
pnpm-lock.yaml

View File

@ -21,6 +21,7 @@
"@astrojs/tailwind": "^5.1.5", "@astrojs/tailwind": "^5.1.5",
"@astropub/icons": "^0.2.0", "@astropub/icons": "^0.2.0",
"@internationalized/date": "^3.7.0", "@internationalized/date": "^3.7.0",
"@lucide/svelte": "^0.488.0",
"@types/color": "^4.2.0", "@types/color": "^4.2.0",
"@types/node": "^22.9.3", "@types/node": "^22.9.3",
"@types/three": "^0.170.0", "@types/three": "^0.170.0",

9276
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -18,26 +18,36 @@
--> -->
<script lang="ts"> <script lang="ts">
import type {ExtendedEvent} from "@type/event"; import type { ExtendedEvent } from "@type/event";
import {createSvelteTable, FlexRender} from "@components/ui/data-table"; import { createSvelteTable, FlexRender } from "@components/ui/data-table";
import { import {
type ColumnFiltersState, type ColumnFiltersState,
getCoreRowModel, getFilteredRowModel, getCoreRowModel,
getPaginationRowModel, getSortedRowModel, getFilteredRowModel,
getGroupedRowModel,
getPaginationRowModel,
getSortedRowModel,
type GroupingState,
type RowSelectionState,
type SortingState, type SortingState,
} from "@tanstack/table-core"; } from "@tanstack/table-core";
import { columns } from "./columns" import { columns } from "./columns";
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@components/ui/table";
import { Checkbox } from "@components/ui/checkbox";
let { data }: { data: ExtendedEvent } = $props(); let { data }: { data: ExtendedEvent } = $props();
let sorting = $state<SortingState>([]); let sorting = $state<SortingState>([]);
let columnFilters = $state<ColumnFiltersState>([]); let columnFilters = $state<ColumnFiltersState>([]);
let selection = $state<RowSelectionState>({});
const table = createSvelteTable({ const table = createSvelteTable({
get data() { get data() {
return data.fights; return data.fights;
}, },
initialState: {
columnOrder: ["auswahl", "begegnung", "group"],
},
state: { state: {
get sorting() { get sorting() {
return sorting; return sorting;
@ -45,6 +55,12 @@
get columnFilters() { get columnFilters() {
return columnFilters; return columnFilters;
}, },
get grouping() {
return ["group"];
},
get rowSelection() {
return selection;
},
}, },
onSortingChange: (updater) => { onSortingChange: (updater) => {
if (typeof updater === "function") { if (typeof updater === "function") {
@ -60,10 +76,20 @@
columnFilters = updater; columnFilters = updater;
} }
}, },
onRowSelectionChange: (updater) => {
if (typeof updater === "function") {
selection = updater(selection);
} else {
selection = updater;
}
},
columns, columns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(), getFilteredRowModel: getFilteredRowModel(),
getGroupedRowModel: getGroupedRowModel(),
groupedColumnMode: "remove",
getRowId: (row) => row.id.toString(),
}); });
</script> </script>
@ -74,10 +100,7 @@
{#each headerGroup.headers as header (header.id)} {#each headerGroup.headers as header (header.id)}
<TableHead> <TableHead>
{#if !header.isPlaceholder} {#if !header.isPlaceholder}
<FlexRender <FlexRender content={header.column.columnDef.header} context={header.getContext()} />
content={header.column.columnDef.header}
context={header.getContext()}
/>
{/if} {/if}
</TableHead> </TableHead>
{/each} {/each}
@ -85,22 +108,40 @@
{/each} {/each}
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{#each table.getRowModel().rows as row (row.id)} {#each table.getRowModel().rows as groupRow (groupRow.id)}
{#if groupRow.getIsGrouped()}
<TableRow class="bg-muted font-bold">
<TableCell colspan={columns.length}>
<Checkbox
checked={groupRow.getIsSelected()}
indeterminate={groupRow.getIsSomeSelected() && !groupRow.getIsSelected()}
onCheckedChange={() => groupRow.toggleSelected()}
class="mr-4"
/>
Gruppe: {groupRow.getValue("group") ?? "Keine"}
</TableCell>
</TableRow>
{#each groupRow.subRows as row (row.id)}
<TableRow data-state={row.getIsSelected() && "selected"}> <TableRow data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)} {#each row.getVisibleCells() as cell (cell.id)}
<TableCell> <TableCell>
<FlexRender <FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
content={cell.column.columnDef.cell}
context={cell.getContext()}
/>
</TableCell> </TableCell>
{/each} {/each}
</TableRow> </TableRow>
{/each}
{:else}
<TableRow data-state={groupRow.getIsSelected() && "selected"}>
{#each groupRow.getVisibleCells() as cell (cell.id)}
<TableCell>
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
</TableCell>
{/each}
</TableRow>
{/if}
{:else} {:else}
<TableRow> <TableRow>
<TableCell colspan={columns.length} class="h-24 text-center"> <TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell>
No results.
</TableCell>
</TableRow> </TableRow>
{/each} {/each}
</TableBody> </TableBody>

View File

@ -17,16 +17,64 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import type {ColumnDef} from "@tanstack/table-core"; import { Checkbox } from "@components/ui/checkbox";
import type {EventFight} from "@type/event.ts"; import { renderComponent } from "@components/ui/data-table";
import type { ColumnDef } from "@tanstack/table-core";
import type { EventFight } from "@type/event.ts";
export const columns: ColumnDef<EventFight> = [ export const columns: ColumnDef<EventFight> = [
{ {
accessorFn: (r) => r.blueTeam.name, id: "auswahl",
header: "Team Blue", header: ({ table }) => {
return renderComponent(Checkbox, {
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onCheckedChange: () => {
if (!table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected()) {
const now = new Date();
const rows = table.getRowModel().rows.filter((row) => new Date(row.original.date) > now);
if (rows.length > 0) {
rows.forEach((row) => {
row.toggleSelected();
});
} else {
table.toggleAllRowsSelected(true);
}
} else if (table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected()) {
table.toggleAllRowsSelected(true);
} else {
table.toggleAllRowsSelected(false);
}
},
});
},
cell: ({ row }) => {
return renderComponent(Checkbox, {
checked: row.getIsSelected(),
onCheckedChange: row.getToggleSelectedHandler(),
});
},
}, },
{ {
accessorFn: (r) => r.redTeam.name, accessorFn: (r) => r.blueTeam.name + " vs " + r.redTeam.name,
header: "Team Red", id: "begegnung",
header: "Begegnung",
},
{
header: "Gruppe",
accessorKey: "group",
id: "group",
},
{
header: "Datum",
accessorKey: "start",
id: "start",
cell: ({ row }) => {
return new Date(row.getValue("start")).toLocaleString("de-DE", {
dateStyle: "short",
timeStyle: "medium",
});
},
}, },
]; ];

View File

@ -1,35 +1,35 @@
<script lang="ts"> <script lang="ts">
import { Checkbox as CheckboxPrimitive } from "bits-ui"; import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from "bits-ui";
import Check from "lucide-svelte/icons/check"; import Check from "@lucide/svelte/icons/check";
import Minus from "lucide-svelte/icons/minus"; import Minus from "@lucide/svelte/icons/minus";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = CheckboxPrimitive.Props; let {
type $$Events = CheckboxPrimitive.Events; ref = $bindable(null),
checked = $bindable(false),
let className: $$Props["class"] = undefined; indeterminate = $bindable(false),
export let checked: $$Props["checked"] = false; class: className,
export { className as class }; ...restProps
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
</script> </script>
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
bind:ref
class={cn( class={cn(
"border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer box-content h-4 w-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50", "border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer box-content size-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
className className
)} )}
bind:checked bind:checked
{...$$restProps} bind:indeterminate
on:click {...restProps}
> >
<CheckboxPrimitive.Indicator {#snippet children({ checked, indeterminate })}
class={cn("flex h-4 w-4 items-center justify-center text-current")} <div class="flex size-4 items-center justify-center text-current">
let:isChecked {#if indeterminate}
let:isIndeterminate <Minus class="size-3.5" />
> {:else}
{#if isChecked} <Check class={cn("size-3.5", !checked && "text-transparent")} />
<Check class="h-3.5 w-3.5" />
{:else if isIndeterminate}
<Minus class="h-3.5 w-3.5" />
{/if} {/if}
</CheckboxPrimitive.Indicator> </div>
{/snippet}
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>