This commit is contained in:
2025-12-04 23:31:55 +01:00
parent 9fa21a488a
commit fb509607c1
96 changed files with 5839 additions and 1609 deletions

View File

@@ -2,10 +2,11 @@
import { useState, useEffect } from 'react';
import Card from '@/components/ui/Card';
import DriverIdentity from '@/components/drivers/DriverIdentity';
import { getDriverRepository, getDriverStats } from '@/lib/di-container';
import { EntityMappers } from '@gridpilot/racing/application/mappers/EntityMappers';
import type { DriverDTO } from '@gridpilot/racing/application/dto/DriverDTO';
import { TeamMembership, TeamRole } from '@/lib/racingLegacyFacade';
import type { TeamMembership, TeamRole } from '@gridpilot/racing';
interface TeamRosterProps {
teamId: string;
@@ -33,7 +34,7 @@ export default function TeamRoster({
const driverMap: Record<string, DriverDTO> = {};
for (const membership of memberships) {
const driver = allDrivers.find(d => d.id === membership.driverId);
const driver = allDrivers.find((d) => d.id === membership.driverId);
if (driver) {
const dto = EntityMappers.toDriverDTO(driver);
if (dto) {
@@ -41,12 +42,12 @@ export default function TeamRoster({
}
}
}
setDrivers(driverMap);
setLoading(false);
};
loadDrivers();
void loadDrivers();
}, [memberships]);
const getRoleBadgeColor = (role: TeamRole) => {
@@ -85,14 +86,15 @@ export default function TeamRoster({
}
});
const teamAverageRating = memberships.length > 0
? Math.round(
memberships.reduce((sum, m) => {
const stats = getDriverStats(m.driverId);
return sum + (stats?.rating || 0);
}, 0) / memberships.length
)
: 0;
const teamAverageRating =
memberships.length > 0
? Math.round(
memberships.reduce((sum, m) => {
const stats = getDriverStats(m.driverId);
return sum + (stats?.rating || 0);
}, 0) / memberships.length,
)
: 0;
if (loading) {
return (
@@ -108,10 +110,11 @@ export default function TeamRoster({
<div>
<h3 className="text-xl font-semibold text-white">Team Roster</h3>
<p className="text-sm text-gray-400 mt-1">
{memberships.length} {memberships.length === 1 ? 'member' : 'members'} Avg Rating: <span className="text-primary-blue font-medium">{teamAverageRating}</span>
{memberships.length} {memberships.length === 1 ? 'member' : 'members'} Avg Rating:{' '}
<span className="text-primary-blue font-medium">{teamAverageRating}</span>
</p>
</div>
<div className="flex items-center gap-2">
<label className="text-sm text-gray-400">Sort by:</label>
<select
@@ -125,70 +128,67 @@ export default function TeamRoster({
</select>
</div>
</div>
<div className="space-y-3">
{sortedMemberships.map((membership) => {
const driver = drivers[membership.driverId];
const driverStats = getDriverStats(membership.driverId);
if (!driver) return null;
const canManageMembership = isAdmin && membership.role !== 'owner';
return (
<div
key={membership.driverId}
className="flex items-center justify-between p-4 rounded-lg bg-deep-graphite border border-charcoal-outline hover:border-charcoal-outline/60 transition-colors"
>
<div className="flex items-center gap-4 flex-1">
<div className="w-12 h-12 rounded-full bg-primary-blue/20 flex items-center justify-center text-lg font-bold text-white">
{driver.name.charAt(0)}
</div>
<div className="flex-1">
<div className="flex items-center gap-2">
<h4 className="text-white font-medium">{driver.name}</h4>
<span className={`px-2 py-1 rounded text-xs font-medium ${getRoleBadgeColor(membership.role)}`}>
{getRoleLabel(membership.role)}
</span>
</div>
<p className="text-sm text-gray-400">
{driver.country} Joined {new Date(membership.joinedAt).toLocaleDateString()}
</p>
</div>
<DriverIdentity
driver={driver}
href={`/drivers/${driver.id}?from=team&teamId=${teamId}`}
contextLabel={getRoleLabel(membership.role)}
meta={
<span>
{driver.country} Joined{' '}
{new Date(membership.joinedAt).toLocaleDateString()}
</span>
}
size="md"
/>
{driverStats && (
<div className="flex items-center gap-6 text-center">
<div>
<div className="text-lg font-bold text-primary-blue">{driverStats.rating}</div>
<div className="text-xs text-gray-400">Rating</div>
</div>
<div>
<div className="text-sm text-gray-300">#{driverStats.overallRank}</div>
<div className="text-xs text-gray-500">Rank</div>
{driverStats && (
<div className="flex items-center gap-6 text-center">
<div>
<div className="text-lg font-bold text-primary-blue">
{driverStats.rating}
</div>
<div className="text-xs text-gray-400">Rating</div>
</div>
)}
</div>
<div>
<div className="text-sm text-gray-300">#{driverStats.overallRank}</div>
<div className="text-xs text-gray-500">Rank</div>
</div>
</div>
)}
{isAdmin && membership.role !== 'owner' && (
{canManageMembership && (
<div className="flex items-center gap-2">
{onChangeRole && (
<select
className="px-3 py-2 bg-iron-gray border-0 rounded text-white ring-1 ring-inset ring-charcoal-outline focus:ring-2 focus:ring-primary-blue transition-all duration-150 text-sm"
value={membership.role}
onChange={(e) => onChangeRole(membership.driverId, e.target.value as TeamRole)}
>
<option value="driver">Driver</option>
<option value="manager">Manager</option>
</select>
)}
{onRemoveMember && (
<button
onClick={() => onRemoveMember(membership.driverId)}
className="px-3 py-2 bg-danger-red/20 hover:bg-danger-red/30 text-danger-red rounded text-sm font-medium transition-colors"
>
Remove
</button>
)}
<select
className="px-3 py-2 bg-iron-gray border-0 rounded text-white ring-1 ring-inset ring-charcoal-outline focus:ring-2 focus:ring-primary-blue transition-all duration-150 text-sm"
value={membership.role}
onChange={(e) =>
onChangeRole?.(membership.driverId, e.target.value as TeamRole)
}
>
<option value="driver">Driver</option>
<option value="manager">Manager</option>
</select>
<button
onClick={() => onRemoveMember?.(membership.driverId)}
className="px-3 py-2 bg-danger-red/20 hover:bg-danger-red/30 text-danger-red rounded text-sm font-medium transition-colors"
>
Remove
</button>
</div>
)}
</div>
@@ -197,9 +197,7 @@ export default function TeamRoster({
</div>
{memberships.length === 0 && (
<div className="text-center py-8 text-gray-400">
No team members yet.
</div>
<div className="text-center py-8 text-gray-400">No team members yet.</div>
)}
</Card>
);