feat: Add VAC/ban status column to match scoreboard

Add per-player VAC and game ban status display in match details scoreboard.

## Changes

- **Types & Schema**: Add vac and game_ban optional boolean fields to MatchPlayer
- **Transformer**: Extract vac and game_ban from legacy player.vac and player.game_ban
- **UI**: Add "Status" column to details table showing VAC/BAN badges
- **Mocks**: Update mock player data with ban status fields

## Implementation Details

The backend API provides per-player ban status in match details via the player
object (player.vac and player.game_ban). These were previously not being extracted
by the transformer.

The new Status column displays:
- Red "VAC" badge if player has VAC ban
- Red "BAN" badge if player has game ban
- Both badges if player has both bans
- "-" if player has no bans

Users can identify banned players at a glance in the scoreboard, improving
transparency and match context.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-12 19:34:39 +01:00
parent 8f3b652740
commit 7d642b0be3
5 changed files with 44 additions and 5 deletions

View File

@@ -195,7 +195,10 @@ export function transformPlayerStats(legacy: LegacyPlayerStats): MatchPlayer {
flash_duration_enemy: legacy.flash?.duration?.enemy, flash_duration_enemy: legacy.flash?.duration?.enemy,
flash_total_self: legacy.flash?.total?.self, flash_total_self: legacy.flash?.total?.self,
flash_total_team: legacy.flash?.total?.team, flash_total_team: legacy.flash?.total?.team,
flash_total_enemy: legacy.flash?.total?.enemy flash_total_enemy: legacy.flash?.total?.enemy,
// Ban status
vac: legacy.player.vac,
game_ban: legacy.player.game_ban
}; };
} }

View File

@@ -54,7 +54,11 @@ export const matchPlayerSchema = z.object({
// Other // Other
crosshair: z.string().optional(), crosshair: z.string().optional(),
color: z.enum(['green', 'yellow', 'purple', 'blue', 'orange', 'grey']).optional(), color: z.enum(['green', 'yellow', 'purple', 'blue', 'orange', 'grey']).optional(),
avg_ping: z.number().nonnegative().optional() avg_ping: z.number().nonnegative().optional(),
// Ban status
vac: z.boolean().optional(),
game_ban: z.boolean().optional()
}); });
/** Match schema */ /** Match schema */

View File

@@ -134,6 +134,10 @@ export interface MatchPlayer {
crosshair?: string; crosshair?: string;
color?: 'green' | 'yellow' | 'purple' | 'blue' | 'orange' | 'grey'; color?: 'green' | 'yellow' | 'purple' | 'blue' | 'orange' | 'grey';
avg_ping?: number; avg_ping?: number;
// Ban status
vac?: boolean; // Whether player has VAC ban
game_ban?: boolean; // Whether player has game ban
} }
/** /**

View File

@@ -74,7 +74,9 @@ export const mockMatchPlayers: MatchPlayer[] = [
ud_flash: 5, ud_flash: 5,
ud_smoke: 3, ud_smoke: 3,
avg_ping: 25.5, avg_ping: 25.5,
color: 'yellow' color: 'yellow',
vac: false,
game_ban: false
}, },
{ {
id: '765611980876543', id: '765611980876543',
@@ -93,7 +95,9 @@ export const mockMatchPlayers: MatchPlayer[] = [
dmg_enemy: 2180, dmg_enemy: 2180,
dmg_team: 85, dmg_team: 85,
avg_ping: 32.1, avg_ping: 32.1,
color: 'blue' color: 'blue',
vac: false,
game_ban: false
}, },
{ {
id: '765611980111111', id: '765611980111111',
@@ -112,7 +116,9 @@ export const mockMatchPlayers: MatchPlayer[] = [
dmg_enemy: 2680, dmg_enemy: 2680,
dmg_team: 45, dmg_team: 45,
avg_ping: 18.3, avg_ping: 18.3,
color: 'green' color: 'green',
vac: false,
game_ban: false
} }
]; ];

View File

@@ -131,6 +131,28 @@
if (numValue > 0) return `<span class="badge badge-warning badge-sm">${numValue}</span>`; if (numValue > 0) return `<span class="badge badge-warning badge-sm">${numValue}</span>`;
return '<span class="text-base-content/40">-</span>'; return '<span class="text-base-content/40">-</span>';
} }
},
{
key: 'vac' as keyof (typeof playersWithStats)[0],
label: 'Status',
sortable: true,
align: 'center' as const,
render: (
_value: string | number | boolean | undefined,
row: (typeof playersWithStats)[0]
) => {
const badges = [];
if (row.vac) {
badges.push('<span class="badge badge-error badge-sm" title="VAC Banned">VAC</span>');
}
if (row.game_ban) {
badges.push('<span class="badge badge-error badge-sm" title="Game Banned">BAN</span>');
}
if (badges.length > 0) {
return `<div class="flex gap-1 justify-center">${badges.join('')}</div>`;
}
return '<span class="text-base-content/40">-</span>';
}
} }
]; ];