- Add round grouping time and prefix to group view config - Use the new settings in group display and editor defaults
This commit is contained in:
@@ -15,12 +15,10 @@
|
|||||||
config: GroupViewConfig;
|
config: GroupViewConfig;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
// Groups fights into rounds: a round starts at the first fight's start;
|
function detectRounds(fights: EventFight[], groupingTimeMinutes: number): EventFight[][] {
|
||||||
// all fights starting within 10 minutes (600_000 ms) of that are in the same round.
|
|
||||||
function detectRounds(fights: EventFight[]): EventFight[][] {
|
|
||||||
if (!fights || fights.length === 0) return [];
|
if (!fights || fights.length === 0) return [];
|
||||||
|
|
||||||
const TEN_MIN_MS = 10 * 60 * 1000;
|
const groupingTimeMs = Math.max(1, Math.floor(groupingTimeMinutes || 10)) * 60 * 1000;
|
||||||
const sorted = [...fights].sort((a, b) => a.start - b.start);
|
const sorted = [...fights].sort((a, b) => a.start - b.start);
|
||||||
|
|
||||||
const rounds: EventFight[][] = [];
|
const rounds: EventFight[][] = [];
|
||||||
@@ -28,7 +26,7 @@
|
|||||||
let roundStart = sorted[0].start;
|
let roundStart = sorted[0].start;
|
||||||
|
|
||||||
for (const fight of sorted) {
|
for (const fight of sorted) {
|
||||||
if (fight.start - roundStart <= TEN_MIN_MS) {
|
if (fight.start - roundStart <= groupingTimeMs) {
|
||||||
currentRound.push(fight);
|
currentRound.push(fight);
|
||||||
} else {
|
} else {
|
||||||
if (currentRound.length) rounds.push(currentRound);
|
if (currentRound.length) rounds.push(currentRound);
|
||||||
@@ -61,8 +59,9 @@
|
|||||||
{#each config.groups as groupId}
|
{#each config.groups as groupId}
|
||||||
{@const group = event.groups.find((g) => g.id === groupId)!!}
|
{@const group = event.groups.find((g) => g.id === groupId)!!}
|
||||||
{@const fights = event.fights.filter((f) => f.group?.id === groupId)}
|
{@const fights = event.fights.filter((f) => f.group?.id === groupId)}
|
||||||
{@const rounds = detectRounds(fights)}
|
{@const rounds = detectRounds(fights, config.roundGroupingTimeMinutes ?? 10)}
|
||||||
{@const roundRows = config.roundRows ?? 1}
|
{@const roundRows = config.roundRows ?? 1}
|
||||||
|
{@const roundPrefix = config.roundPrefix ?? "Runde"}
|
||||||
{@const roundRowsChunked = chunkIntoRows(rounds, roundRows)}
|
{@const roundRowsChunked = chunkIntoRows(rounds, roundRows)}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div>
|
<div>
|
||||||
@@ -83,7 +82,7 @@
|
|||||||
{@const roundIndex = rounds.indexOf(round)}
|
{@const roundIndex = rounds.indexOf(round)}
|
||||||
{@const teams = Array.from(new Set(round.flatMap((f) => [f.redTeam, f.blueTeam])))}
|
{@const teams = Array.from(new Set(round.flatMap((f) => [f.redTeam, f.blueTeam])))}
|
||||||
<div class="{hover.currentHover && !teams.some((t) => t?.id === hover.currentHover) ? 'opacity-30' : ''} transition-opacity">
|
<div class="{hover.currentHover && !teams.some((t) => t?.id === hover.currentHover) ? 'opacity-30' : ''} transition-opacity">
|
||||||
<EventCard title="Runde {roundIndex + 1}">
|
<EventCard title={`${roundPrefix} ${roundIndex + 1}`}>
|
||||||
{#each round as fight}
|
{#each round as fight}
|
||||||
<EventFightChip {event} {fight} {group} />
|
<EventFightChip {event} {fight} {group} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ export const GroupViewSchema = z.object({
|
|||||||
type: z.literal("GROUP"),
|
type: z.literal("GROUP"),
|
||||||
groups: z.array(z.number()),
|
groups: z.array(z.number()),
|
||||||
roundRows: z.number().int().positive().optional().default(1),
|
roundRows: z.number().int().positive().optional().default(1),
|
||||||
|
roundGroupingTimeMinutes: z.number().int().positive().optional().default(10),
|
||||||
|
roundPrefix: z.enum(["Runde", "Tag"]).optional().default("Runde"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type GroupViewConfig = z.infer<typeof GroupViewSchema>;
|
export type GroupViewConfig = z.infer<typeof GroupViewSchema>;
|
||||||
|
|||||||
@@ -7,10 +7,18 @@
|
|||||||
import { Plus, Trash2 } from "lucide-svelte";
|
import { Plus, Trash2 } from "lucide-svelte";
|
||||||
|
|
||||||
type StageType = "GROUP" | "ELEMINATION" | "DOUBLE_ELEMINATION";
|
type StageType = "GROUP" | "ELEMINATION" | "DOUBLE_ELEMINATION";
|
||||||
|
type RoundPrefix = "Runde" | "Tag";
|
||||||
|
const roundGroupingTimeOptions = [
|
||||||
|
{ label: "10 Minutes", value: 10 },
|
||||||
|
{ label: "30 Minutes", value: 30 },
|
||||||
|
{ label: "1 Hour", value: 60 },
|
||||||
|
{ label: "2 Hours", value: 120 },
|
||||||
|
{ label: "12 Hours", value: 720 },
|
||||||
|
];
|
||||||
type StageConfig = {
|
type StageConfig = {
|
||||||
name: string;
|
name: string;
|
||||||
view:
|
view:
|
||||||
| { type: "GROUP"; groups: number[]; roundRows?: number }
|
| { type: "GROUP"; groups: number[]; roundRows?: number; roundGroupingTimeMinutes?: number; roundPrefix?: RoundPrefix }
|
||||||
| { type: "ELEMINATION"; finalFight: number }
|
| { type: "ELEMINATION"; finalFight: number }
|
||||||
| { type: "DOUBLE_ELEMINATION"; winnersFinalFight: number; losersFinalFight: number; grandFinalFight: number };
|
| { type: "DOUBLE_ELEMINATION"; winnersFinalFight: number; losersFinalFight: number; grandFinalFight: number };
|
||||||
};
|
};
|
||||||
@@ -68,7 +76,7 @@
|
|||||||
|
|
||||||
draft[key] = {
|
draft[key] = {
|
||||||
name: "New stage",
|
name: "New stage",
|
||||||
view: { type: "GROUP", groups: [], roundRows: 1 },
|
view: { type: "GROUP", groups: [], roundRows: 1, roundGroupingTimeMinutes: 10, roundPrefix: "Runde" },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -105,7 +113,7 @@
|
|||||||
function setStageType(key: string, type: StageType, event?: ExtendedEvent) {
|
function setStageType(key: string, type: StageType, event?: ExtendedEvent) {
|
||||||
nextConfig((draft) => {
|
nextConfig((draft) => {
|
||||||
if (type === "GROUP") {
|
if (type === "GROUP") {
|
||||||
draft[key].view = { type, groups: [], roundRows: 1 };
|
draft[key].view = { type, groups: [], roundRows: 1, roundGroupingTimeMinutes: 10, roundPrefix: "Runde" };
|
||||||
} else if (type === "ELEMINATION") {
|
} else if (type === "ELEMINATION") {
|
||||||
draft[key].view = { type, finalFight: event?.fights[0]?.id ?? 0 };
|
draft[key].view = { type, finalFight: event?.fights[0]?.id ?? 0 };
|
||||||
} else {
|
} else {
|
||||||
@@ -139,6 +147,24 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setRoundGroupingTimeMinutes(key: string, value: string) {
|
||||||
|
nextConfig((draft) => {
|
||||||
|
const view = draft[key].view;
|
||||||
|
if (view.type === "GROUP") {
|
||||||
|
view.roundGroupingTimeMinutes = Math.max(1, Math.floor(Number(value) || 10));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRoundPrefix(key: string, value: RoundPrefix) {
|
||||||
|
nextConfig((draft) => {
|
||||||
|
const view = draft[key].view;
|
||||||
|
if (view.type === "GROUP") {
|
||||||
|
view.roundPrefix = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function setFightParam(key: string, param: "finalFight" | "winnersFinalFight" | "losersFinalFight" | "grandFinalFight", fightId: string) {
|
function setFightParam(key: string, param: "finalFight" | "winnersFinalFight" | "losersFinalFight" | "grandFinalFight", fightId: string) {
|
||||||
nextConfig((draft) => {
|
nextConfig((draft) => {
|
||||||
const view = draft[key].view as Record<string, unknown>;
|
const view = draft[key].view as Record<string, unknown>;
|
||||||
@@ -235,6 +261,29 @@
|
|||||||
<Label>Round rows</Label>
|
<Label>Round rows</Label>
|
||||||
<Input type="number" min="1" value={stage.view.roundRows ?? 1} onchange={(event) => setRoundRows(key, (event.currentTarget as HTMLInputElement).value)} />
|
<Input type="number" min="1" value={stage.view.roundRows ?? 1} onchange={(event) => setRoundRows(key, (event.currentTarget as HTMLInputElement).value)} />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label>Round grouping time (minutes)</Label>
|
||||||
|
<select
|
||||||
|
class="h-9 w-full rounded-md border border-neutral-800 bg-neutral-900 px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
|
value={(stage.view.roundGroupingTimeMinutes ?? 10).toString()}
|
||||||
|
onchange={(event) => setRoundGroupingTimeMinutes(key, (event.currentTarget as HTMLSelectElement).value)}
|
||||||
|
>
|
||||||
|
{#each roundGroupingTimeOptions as option}
|
||||||
|
<option value={option.value.toString()}>{option.label}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label>Round prefix</Label>
|
||||||
|
<select
|
||||||
|
class="h-9 w-full rounded-md border border-neutral-800 bg-neutral-900 px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
|
value={stage.view.roundPrefix ?? "Runde"}
|
||||||
|
onchange={(event) => setRoundPrefix(key, (event.currentTarget as HTMLSelectElement).value as RoundPrefix)}
|
||||||
|
>
|
||||||
|
<option value="Runde">Runde</option>
|
||||||
|
<option value="Tag">Tag</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if stage.view.type === "ELEMINATION"}
|
{:else if stage.view.type === "ELEMINATION"}
|
||||||
|
|||||||
Reference in New Issue
Block a user