website refactor
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user