website refactor

This commit is contained in:
2026-01-15 17:12:24 +01:00
parent c3b308e960
commit f035cfe7ce
468 changed files with 24378 additions and 17324 deletions

View File

@@ -1,21 +1,23 @@
'use client';
import React, { useMemo, useState } from 'react';
import { Card } from '@/ui/Card';
import { DriverIdentity } from '@/components/drivers/DriverIdentity';
import { useTeamRoster } from "@/lib/hooks/team";
import { useState } from 'react';
import { useTeamRoster } from "@/hooks/team/useTeamRoster";
import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Select } from '@/ui/Select';
import { Surface } from '@/ui/Surface';
import { Badge } from '@/ui/Badge';
import { Button } from '@/ui/Button';
import { routes } from '@/lib/routing/RouteConfig';
import { TeamRosterList } from '@/ui/TeamRosterList';
import { TeamRosterItem } from '@/ui/TeamRosterItem';
import { MinimalEmptyState } from '@/ui/EmptyState';
import { sortMembers } from '@/lib/utilities/roster-utils';
type TeamRole = 'owner' | 'admin' | 'member';
type TeamMemberRole = 'owner' | 'manager' | 'member';
export type TeamRole = 'owner' | 'admin' | 'member';
export type TeamMemberRole = 'owner' | 'manager' | 'member';
interface TeamRosterProps {
teamId: string;
@@ -32,6 +34,18 @@ interface TeamRosterProps {
onChangeRole?: (driverId: string, newRole: TeamRole) => void;
}
interface TeamMember {
driver: {
id: string;
name: string;
country: string;
};
role: TeamMemberRole;
joinedAt: string;
rating: number | null;
overallRank: number | null;
}
export function TeamRoster({
teamId,
memberships,
@@ -44,51 +58,25 @@ export function TeamRoster({
// Use hook for data fetching
const { data: teamMembers = [], isLoading: loading } = useTeamRoster(memberships);
const getRoleLabel = (role: TeamRole | TeamMemberRole) => {
const getRoleLabel = useMemo(() => (role: TeamRole | TeamMemberRole) => {
// Convert manager to admin for display
const displayRole = role === 'manager' ? 'admin' : role;
return displayRole.charAt(0).toUpperCase() + displayRole.slice(1);
};
}, []);
function getRoleOrder(role: TeamMemberRole): number {
switch (role) {
case 'owner':
return 0;
case 'manager':
return 1;
case 'member':
return 2;
default:
return 3;
}
}
const sortedMembers = useMemo(() => {
return sortMembers(teamMembers as unknown as TeamMember[], sortBy);
}, [teamMembers, sortBy]);
const sortedMembers = [...teamMembers].sort((a, b) => {
switch (sortBy) {
case 'rating': {
const ratingA = a.rating ?? 0;
const ratingB = b.rating ?? 0;
return ratingB - ratingA;
}
case 'role': {
return getRoleOrder(a.role) - getRoleOrder(b.role);
}
case 'name': {
return a.driver.name.localeCompare(b.driver.name);
}
default:
return 0;
}
});
const teamAverageRating = teamMembers.length > 0
? teamMembers.reduce((sum: number, m: any) => sum + (m.rating || 0), 0) / teamMembers.length
: 0;
const teamAverageRating = useMemo(() => {
if (teamMembers.length === 0) return 0;
return teamMembers.reduce((sum: number, m: { rating?: number | null }) => sum + (m.rating || 0), 0) / teamMembers.length;
}, [teamMembers]);
if (loading) {
return (
<Card>
<Box textAlign="center" py={8}>
<Box display="flex" justifyContent="center" py={8}>
<Text color="text-gray-400">Loading roster...</Text>
</Box>
</Card>
@@ -97,7 +85,7 @@ export function TeamRoster({
return (
<Card>
<Stack direction="row" align="center" justify="between" mb={6} wrap>
<Stack direction="row" align="center" justify="between" mb={6} wrap gap={4}>
<Box>
<Heading level={3}>Team Roster</Heading>
<Text size="sm" color="text-gray-400" block mt={1}>
@@ -108,7 +96,7 @@ export function TeamRoster({
<Stack direction="row" align="center" gap={2}>
<Text size="sm" color="text-gray-400">Sort by:</Text>
<Box width={32}>
<Box width="32">
<Select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as typeof sortBy)}
@@ -117,61 +105,32 @@ export function TeamRoster({
{ value: 'role', label: 'Role' },
{ value: 'name', label: 'Name' },
]}
className="py-1 text-sm"
/>
</Box>
</Stack>
</Stack>
<Stack gap={3}>
{sortedMembers.map((member) => {
const { driver, role, joinedAt, rating, overallRank } = member;
{sortedMembers.length > 0 ? (
<TeamRosterList>
{sortedMembers.map((member) => {
const { driver, role, joinedAt, rating, overallRank } = member;
// Convert manager to admin for display purposes
const displayRole: TeamRole = role === 'manager' ? 'admin' : (role as TeamRole);
const canManageMembership = isAdmin && role !== 'owner';
// Convert manager to admin for display purposes
const displayRole: TeamRole = role === 'manager' ? 'admin' : (role as TeamRole);
const canManageMembership = isAdmin && role !== 'owner';
return (
<Surface
key={driver.id}
variant="dark"
rounded="lg"
border
padding={4}
>
<Stack direction="row" align="center" justify="between" wrap gap={4}>
<DriverIdentity
driver={driver as DriverViewModel}
href={`/drivers/${driver.id}?from=team&teamId=${teamId}`}
contextLabel={getRoleLabel(role)}
meta={
<Text size="xs" color="text-gray-400">
{driver.country} Joined {new Date(joinedAt).toLocaleDateString()}
</Text>
}
size="md"
/>
{rating !== null && (
<Stack direction="row" align="center" gap={6}>
<Box textAlign="center">
<Text size="lg" weight="bold" color="text-primary-blue" block>
{rating}
</Text>
<Text size="xs" color="text-gray-400">Rating</Text>
</Box>
{overallRank !== null && (
<Box textAlign="center">
<Text size="sm" color="text-gray-300" block>#{overallRank}</Text>
<Text size="xs" color="text-gray-500">Rank</Text>
</Box>
)}
</Stack>
)}
{canManageMembership && (
<Stack direction="row" align="center" gap={2}>
<Box width={32}>
return (
<TeamRosterItem
key={driver.id}
driver={driver as DriverViewModel}
href={`${routes.driver.detail(driver.id)}?from=team&teamId=${teamId}`}
roleLabel={getRoleLabel(role)}
joinedAt={joinedAt}
rating={rating}
overallRank={overallRank}
actions={canManageMembership ? (
<>
<Box width="32">
<Select
value={displayRole}
onChange={(e) =>
@@ -181,7 +140,6 @@ export function TeamRoster({
{ value: 'member', label: 'Member' },
{ value: 'admin', label: 'Admin' },
]}
className="text-sm"
/>
</Box>
@@ -192,18 +150,17 @@ export function TeamRoster({
>
Remove
</Button>
</Stack>
)}
</Stack>
</Surface>
);
})}
</Stack>
{memberships.length === 0 && (
<Box textAlign="center" py={8}>
<Text color="text-gray-400">No team members yet.</Text>
</Box>
</>
) : undefined}
/>
);
})}
</TeamRosterList>
) : (
<MinimalEmptyState
title="No team members yet"
description="When drivers join your team, they will appear here."
/>
)}
</Card>
);