Skip to content

Commit

Permalink
feat(Events): styling
Browse files Browse the repository at this point in the history
  • Loading branch information
simonknittel committed Feb 4, 2025
1 parent 1a608b0 commit 83e0fec
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 40 deletions.
8 changes: 5 additions & 3 deletions app/src/common/components/SingleRole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ export const SingleRole = ({
return (
<span
className={clsx(
"px-2 py-1 rounded bg-neutral-700/50 flex gap-2 items-center overflow-hidden",
className,
"px-2 py-1 rounded bg-neutral-700/50 flex gap-2 items-center whitespace-nowrap",
)}
>
{role.iconId && (
<span className="aspect-square w-6 h-6 flex items-center justify-center rounded overflow-hidden">
<span className="aspect-square size-6 flex items-center justify-center">
<Image
src={`https://${env.NEXT_PUBLIC_R2_PUBLIC_URL}/${role.iconId}`}
alt=""
Expand All @@ -35,7 +35,9 @@ export const SingleRole = ({

{!role.iconId && showPlaceholder && <span className="size-6" />}

{role.name}
<span className="overflow-hidden whitespace-nowrap text-ellipsis">
{role.name}
</span>
</span>
);
};
56 changes: 19 additions & 37 deletions app/src/events/components/OverviewTab.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { requireAuthentication } from "@/auth/server";
import { SingleRole } from "@/common/components/SingleRole";
import type { getEvent } from "@/discord/utils/getEvent";
import { VariantTagBadge } from "@/fleet/components/VariantTagBadge";
import { getAssignedRoles } from "@/roles/utils/getRoles";
import type { Role, VariantTag } from "@prisma/client";
import clsx from "clsx";
import { getEventFleet } from "../utils/getEventFleet";
import { getParticipants } from "../utils/getParticipants";
import { OverviewTile } from "./OverviewTile";
import { RolesTable } from "./RolesTable";
import { VariantTagsTable } from "./VariantTagsTable";

type Props = Readonly<{
className?: string;
Expand All @@ -28,13 +28,21 @@ export const OverviewTab = async ({ className, event }: Props) => {
<OverviewTile
event={event.data}
date={event.date}
className="w-[480px] flex-none"
className="w-full max-w-[480px] flex-none"
/>

<div className="flex-1 w-full flex flex-col gap-4">
{showFleetSummary && <FleetSummary event={event.data} />}

<ParticipantsSummary event={event.data} />
<div className="flex-1 w-full flex-col md:flex-row lg:flex-col xl:flex-row 2xl:flex-col 3xl:flex-row flex gap-2">
{showFleetSummary && (
<FleetSummary
event={event.data}
className="flex-initial w-full md:w-1/2 lg:w-full xl:w-1/2 2xl:w-full 3xl:w-1/2"
/>
)}

<ParticipantsSummary
event={event.data}
className="flex-initial w-full md:w-1/2 lg:w-full xl:w-1/2 2xl:w-full 3xl:w-1/2"
/>
</div>
</div>
);
Expand Down Expand Up @@ -75,21 +83,8 @@ const FleetSummary = async ({ className, event }: FleetSummaryProps) => {
Summe aller Tags. Nur flight ready.
</p>

<div className="flex gap-2 flex-wrap">
{Array.from(countedTags.values())
.toSorted((a, b) => b.count - a.count)
.map((countedTag) => (
<div
key={countedTag.tag.id}
className="flex items-center rounded-l bg-neutral-700/50"
>
<span className="inline-block px-2 text-xl font-bold">
{countedTag.count}
</span>

<VariantTagBadge tag={countedTag.tag} />
</div>
))}
<div className="flex gap-2 flex-wrap overflow-x-auto">
<VariantTagsTable rows={Array.from(countedTags.values())} />
</div>
</section>
);
Expand Down Expand Up @@ -135,21 +130,8 @@ const ParticipantsSummary = async ({
Summe aller Rollen/Zertifikate
</p>

<div className="flex gap-2 flex-wrap">
{Array.from(countedRoles.values())
.toSorted((a, b) => b.count - a.count)
.map((countedRole) => (
<div
key={countedRole.role.id}
className="flex items-center rounded-l bg-neutral-700/50"
>
<span className="inline-block px-2 text-xl font-bold">
{countedRole.count}
</span>

<SingleRole role={countedRole.role} />
</div>
))}
<div className="flex gap-2 flex-wrap overflow-x-auto">
<RolesTable rows={Array.from(countedRoles.values())} />
</div>
</section>
);
Expand Down
115 changes: 115 additions & 0 deletions app/src/events/components/RolesTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"use client";

import { SingleRole } from "@/common/components/SingleRole";
import type { Role } from "@prisma/client";
import {
createColumnHelper,
flexRender,
getCoreRowModel,
getSortedRowModel,
useReactTable,
type SortingState,
} from "@tanstack/react-table";
import clsx from "clsx";
import { useMemo, useState } from "react";
import { FaSortAlphaDown, FaSortAlphaUpAlt } from "react-icons/fa";

interface Row {
role: Role;
count: number;
}

const columnHelper = createColumnHelper<Row>();

const TABLE_MIN_WIDTH = "min-w-[320px]";
const GRID_COLS = "grid-cols-[256px_56px]";

type Props = Readonly<{
className?: string;
rows: Row[];
}>;

export const RolesTable = ({ className, rows }: Props) => {
const [sorting, setSorting] = useState<SortingState>([
{ id: "name", desc: false },
]);

const columns = useMemo(() => {
return [
columnHelper.accessor("role.name", {
header: "Rolle",
id: "name",
cell: (row) => {
const { role } = row.row.original;
return <SingleRole role={role} className="inline-flex" />;
},
}),
columnHelper.accessor("count", {
header: "Anzahl",
}),
];
}, []);

const table = useReactTable({
data: rows,
columns,
state: {
sorting,
},
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
});

return (
<table className={clsx("w-full", TABLE_MIN_WIDTH, className)}>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr
key={headerGroup.id}
className={clsx("grid items-center gap-4 pb-2", GRID_COLS)}
>
{headerGroup.headers.map((header) => (
<th key={header.id} className="text-left text-neutral-500 p-0">
{header.isPlaceholder ? null : (
<div
{...{
className: header.column.getCanSort()
? "cursor-pointer select-none flex items-center gap-2 hover:text-neutral-300"
: "",
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{{
asc: <FaSortAlphaDown />,
desc: <FaSortAlphaUpAlt />,
}[header.column.getIsSorted() as string] ?? null}
</div>
)}
</th>
))}
</tr>
))}
</thead>

<tbody>
{table.getRowModel().rows.map((row) => (
<tr
key={row.id}
className={clsx("grid items-center gap-4", GRID_COLS)}
>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="overflow-hidden">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
);
};
115 changes: 115 additions & 0 deletions app/src/events/components/VariantTagsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"use client";

import { VariantTagBadge } from "@/fleet/components/VariantTagBadge";
import type { VariantTag } from "@prisma/client";
import {
createColumnHelper,
flexRender,
getCoreRowModel,
getSortedRowModel,
useReactTable,
type SortingState,
} from "@tanstack/react-table";
import clsx from "clsx";
import { useMemo, useState } from "react";
import { FaSortAlphaDown, FaSortAlphaUpAlt } from "react-icons/fa";

interface Row {
tag: VariantTag;
count: number;
}

const columnHelper = createColumnHelper<Row>();

const TABLE_MIN_WIDTH = "min-w-[320px]";
const GRID_COLS = "grid-cols-[256px_56px]";

type Props = Readonly<{
className?: string;
rows: Row[];
}>;

export const VariantTagsTable = ({ className, rows }: Props) => {
const [sorting, setSorting] = useState<SortingState>([
{ id: "name", desc: false },
]);

const columns = useMemo(() => {
return [
columnHelper.accessor("tag.value", {
header: "Tag",
id: "name",
cell: (row) => {
const { tag } = row.row.original;
return <VariantTagBadge tag={tag} className="inline-flex" />;
},
}),
columnHelper.accessor("count", {
header: "Anzahl",
}),
];
}, []);

const table = useReactTable({
data: rows,
columns,
state: {
sorting,
},
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
});

return (
<table className={clsx("w-full", TABLE_MIN_WIDTH, className)}>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr
key={headerGroup.id}
className={clsx("grid items-center gap-4 pb-2", GRID_COLS)}
>
{headerGroup.headers.map((header) => (
<th key={header.id} className="text-left text-neutral-500 p-0">
{header.isPlaceholder ? null : (
<div
{...{
className: header.column.getCanSort()
? "cursor-pointer select-none flex items-center gap-2 hover:text-neutral-300"
: "",
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{{
asc: <FaSortAlphaDown />,
desc: <FaSortAlphaUpAlt />,
}[header.column.getIsSorted() as string] ?? null}
</div>
)}
</th>
))}
</tr>
))}
</thead>

<tbody>
{table.getRowModel().rows.map((row) => (
<tr
key={row.id}
className={clsx("grid items-center gap-4", GRID_COLS)}
>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="overflow-hidden">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
);
};

0 comments on commit 83e0fec

Please sign in to comment.