Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .env.exemple
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
# Copy this file to .env and fill in real values. Never commit .env itself.

# Postgres connection — currently unused by the Next.js app now that the
# TBA client lives in-process (src/lib/api/tba); kept here for the
# in-house scouting CRUD/persistence work that hasn't landed yet.
DB_NAME=your-db
DB_USER=api-user
DB_PASSWORD=your_password_here
DB_HOST=your_database-host
DB_PORT=db_port

# The Blue Alliance API v3 — get a read key at
# https://www.thebluealliance.com/account. Read server-side only via
Expand Down
118 changes: 118 additions & 0 deletions src/app/events/[event_key]/EventMatchesTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* EventMatchesTab
*
* Matches tab for `/events/[event_key]`. Splits `matches` into
* Qualification / Playoff brackets (via `getMatchBracket`, based on TBA's
* `comp_level`) behind a toggle — that toggle used to be two static,
* non-interactive pills; it's now real state. Within the active bracket,
* matches are sorted into the order they're actually run
* (`sortMatchesByExecutionOrder`).
*/
"use client";

import { useMemo, useState } from "react";
import { Swords } from "lucide-react";
import { EventMatch } from "@/types/events";
import { getMatchBracket, sortMatchesByExecutionOrder, MatchBracket } from "@/utils/formatMatch";
import MatchesList from "@/components/events/event/matches/MatchesList";
import EmptyTabState from "./EmptyTabState";

interface EventMatchesTabProps {
matches?: EventMatch[];
loading?: boolean;
}

const BRACKETS: { value: MatchBracket; label: string }[] = [
{ value: "qualification", label: "Qualification" },
{ value: "playoff", label: "Playoffs" },
];

export default function EventMatchesTab({ matches = [], loading = true }: EventMatchesTabProps) {
const [bracket, setBracket] = useState<MatchBracket>("qualification");

const { qualification, playoff } = useMemo(() => {
const grouped: Record<MatchBracket, EventMatch[]> = {
qualification: [],
playoff: [],
};

for (const match of matches) {
grouped[getMatchBracket(match.compLevel)].push(match);
}

return {
qualification: sortMatchesByExecutionOrder(grouped.qualification),
playoff: sortMatchesByExecutionOrder(grouped.playoff),
};
}, [matches]);

const visibleMatches = bracket === "qualification" ? qualification : playoff;
const isEmpty = !loading && matches.length === 0;
const isBracketEmpty = !loading && !isEmpty && visibleMatches.length === 0;

return (
<div className="space-y-6">
{(loading || isEmpty) && (
<EmptyTabState
icon={Swords}
title={loading ? "Match schedule not available yet" : "No matches yet"}
description={
loading
? "Qualification and playoff matches will appear here once the event publishes its schedule."
: "This event doesn't have any scheduled matches yet."
}
/>
)}

<section className="rounded-2xl border border-slate-800 bg-slate-900 p-6">
<div className="flex items-center justify-between gap-3">
<h2 className="text-lg font-semibold text-white">
Matches
</h2>

<div className="flex gap-2">
{BRACKETS.map(({ value, label }) => {
const isActive = bracket === value;
const count = value === "qualification" ? qualification.length : playoff.length;

return (
<button
key={value}
type="button"
onClick={() => setBracket(value)}
aria-pressed={isActive}
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition ${
isActive
? "bg-blue-600/20 text-blue-400"
: "text-slate-500 hover:text-slate-300"
}`}
>
{label}
{!loading && !isEmpty && (
<span className="ml-1.5 text-slate-500">
{count}
</span>
)}
</button>
);
})}
</div>
</div>

{!isEmpty && (
<div className="mt-5">
{isBracketEmpty ? (
<p className="rounded-lg border border-dashed border-slate-800 bg-slate-950 p-4 text-sm text-slate-400">
{bracket === "qualification"
? "No qualification matches yet."
: "The playoff bracket hasn't started yet."}
</p>
) : (
<MatchesList matches={visibleMatches} loading={loading} />
)}
</div>
)}
</section>
</div>
);
}
2 changes: 1 addition & 1 deletion src/components/scout/MatchSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from "lucide-react";

import { MatchOption } from "@/types/scout";
import { formatMatchLabel } from "@/utils/match";
import { formatMatchLabel } from "@/utils/formatMatch";

interface MatchSelectorProps {
matches: MatchOption[] | null;
Expand Down
5 changes: 5 additions & 0 deletions src/lib/api/match.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import * as tba from "@/lib/api/tba";
import { AllianceTeam, MatchAlliances, MatchOption } from "@/types/scout";
<<<<<<< Updated upstream
=======
import { formatMatchLabel } from "@/utils/formatMatch";
import { promises } from "node:timers";
>>>>>>> Stashed changes

/* -------------------------------------------------------------------------- */
/* Constants */
Expand Down
8 changes: 0 additions & 8 deletions src/lib/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,6 @@ function env(name: string): string {
return value;
}

export const dbConfig = {
get NAME(): string { return env("DB_NAME"); },
get USER(): string { return env("DB_USER"); },
get PASSWORD(): string { return env("DB_PASSWORD"); },
get HOST(): string { return env("DB_HOST"); },
get PORT(): string { return env("DB_PORT"); },
};

export const tbaConfig = {
get KEY(): string { return env("TBA_KEY"); },
get BASE_URL(): string { return env("TBA_BASE_URL"); },
Expand Down
5 changes: 5 additions & 0 deletions src/utils/match.ts → src/utils/formatMatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export function formatMatchLabel(
// "qm12", then drop the 2-letter level prefix to get just "12".
let adition = key ? key.slice(key.indexOf("_") + 1) : null;
adition = adition ? adition.slice(2) : null;
<<<<<<< Updated upstream:src/utils/match.ts
=======
adition = adition ? adition.replace("m1", "") : null;
adition = adition ? adition.replace("m", "") : null;
>>>>>>> Stashed changes:src/utils/formatMatch.ts

switch (competitionLevel) {
case "qm":
Expand Down
Loading