Compare commits

..

9 Commits

7 changed files with 545 additions and 136 deletions

View File

@ -9,7 +9,7 @@
type ToastStore, type ToastStore,
} from "@skeletonlabs/skeleton"; } from "@skeletonlabs/skeleton";
import { Button, Card, Dropdown } from "$lib/components"; import { Button, Card, Dropdown } from "$lib/components";
import type { Driver, Race, RaceResult } from "$lib/schema"; import type { Driver, Race, RaceResult, Substitution } from "$lib/schema";
import { get_by_value } from "$lib/database"; import { get_by_value } from "$lib/database";
import { race_dropdown_options } from "$lib/dropdown"; import { race_dropdown_options } from "$lib/dropdown";
import { pb } from "$lib/pocketbase"; import { pb } from "$lib/pocketbase";
@ -36,12 +36,16 @@
const toastStore: ToastStore = getToastStore(); const toastStore: ToastStore = getToastStore();
// Await promises
let races: Race[] | undefined = $state(undefined); let races: Race[] | undefined = $state(undefined);
data.races.then((r: Race[]) => (races = r)); data.races.then((r: Race[]) => (races = r));
let drivers: Driver[] | undefined = $state(undefined); let drivers: Driver[] | undefined = $state(undefined);
data.drivers.then((d: Driver[]) => (drivers = d)); data.drivers.then((d: Driver[]) => (drivers = d));
let substitutions: Substitution[] | undefined = $state(undefined);
data.substitutions.then((s: Substitution[]) => (substitutions = s));
// Constants // Constants
const labelwidth: string = "70px"; const labelwidth: string = "70px";
@ -69,10 +73,7 @@
// Set the pxxs/dnfs states once the drivers are loaded // Set the pxxs/dnfs states once the drivers are loaded
data.drivers.then(async (drivers: Driver[]) => { data.drivers.then(async (drivers: Driver[]) => {
pxxs_chips = pxxs_chips =
result?.pxxs.map( result?.pxxs.map((id: string) => get_by_value(drivers, "id", id)?.code ?? "Invalid") ?? [];
(id: string, index: number) =>
`P${(currentrace?.pxx ?? -10) + index - 3}: ${get_by_value(drivers, "id", id)?.code ?? "Invalid"}`,
) ?? [];
dnfs_chips = dnfs_chips =
result?.dnfs.map((id: string) => get_by_value(drivers, "id", id)?.code ?? "Invalid") ?? []; result?.dnfs.map((id: string) => get_by_value(drivers, "id", id)?.code ?? "Invalid") ?? [];
@ -82,32 +83,37 @@
let pxxs_ids: string[] = $state(result?.pxxs ?? []); let pxxs_ids: string[] = $state(result?.pxxs ?? []);
let dnfs_ids: string[] = $state(result?.dnfs ?? []); let dnfs_ids: string[] = $state(result?.dnfs ?? []);
let pxxs_options: AutocompleteOption<string>[] = $derived.by(() => let pxxs_options: AutocompleteOption<string>[] = $derived.by(() => {
(drivers ?? []) if (!race_select_value) return [];
.filter((driver: Driver) => {
// Filter out all drivers that are already selected let active_and_substitutes: Driver[] = (drivers ?? []).filter(
return !pxxs_ids.includes(driver.id); (driver: Driver) => driver.active,
}) );
(substitutions ?? [])
.filter((substitution: Substitution) => substitution.race === race_select_value)
.forEach((substitution: Substitution) => {
const for_index = active_and_substitutes.findIndex(
(driver: Driver) => driver.id === substitution.for,
);
const sub_index = (drivers ?? []).findIndex(
(driver: Driver) => driver.id === substitution.substitute,
);
active_and_substitutes[for_index] = (drivers ?? [])[sub_index];
});
return active_and_substitutes
.sort((a: Driver, b: Driver) => a.firstname.localeCompare(b.firstname))
.map((driver: Driver) => { .map((driver: Driver) => {
return { return {
// NOTE: Because Skeleton displays the values inside the autocomplete input, // NOTE: Because Skeleton displays the values inside the autocomplete input,
// we have to supply the driver code twice and manage a list of ids manually (ugh) // we have to supply the driver code twice and manage a list of ids manually (ugh)
label: driver.code, label: `${driver.firstname} ${driver.lastname}`,
value: driver.code, value: driver.code,
}; };
}), });
); });
let dnfs_options: AutocompleteOption<string>[] = $derived.by(() =>
(drivers ?? []).map((driver: Driver) => {
return {
// NOTE: Because Skeleton displays the values inside the autocomplete input,
// we have to supply the driver code twice and manage a list of ids manually (ugh)
label: driver.code,
value: driver.code,
};
}),
);
let pxxs_whitelist: string[] = $derived.by(() => let pxxs_whitelist: string[] = $derived.by(() =>
(drivers ?? []).map((driver: Driver) => { (drivers ?? []).map((driver: Driver) => {
@ -116,24 +122,17 @@
); );
// Event handlers // Event handlers
const on_race_select = (event: Event): void => {
pxxs_chips = pxxs_chips.map(
(label: string, index: number) =>
`P${(currentrace?.pxx ?? -10) + index - 3}: ${label.split(" ").pop()}`,
);
};
const on_pxxs_chip_select = (event: CustomEvent<AutocompleteOption<string>>): void => { const on_pxxs_chip_select = (event: CustomEvent<AutocompleteOption<string>>): void => {
if (disabled || !currentrace || !drivers) return; if (disabled || !drivers) return;
// Can only select 7 drivers // Can only select 7 drivers
if (pxxs_chips.length >= 7) return; if (pxxs_chips.length >= 7) return;
// Can only select a driver once (because we display the PXX, check for string suffixes) // Can only select a driver once
if (pxxs_chips.some((label: string) => label.endsWith(event.detail.value))) return; if (pxxs_chips.includes(event.detail.value)) return;
// Manage labels that are displayed // Manage labels that are displayed
pxxs_chips.push(`P${currentrace.pxx + pxxs_chips.length - 3}: ${event.detail.value}`); pxxs_chips.push(event.detail.value);
pxxs_input = ""; pxxs_input = "";
// Manage ids that are submitted via form // Manage ids that are submitted via form
@ -145,11 +144,6 @@
const on_pxxs_chip_remove = (event: CustomEvent): void => { const on_pxxs_chip_remove = (event: CustomEvent): void => {
pxxs_ids.splice(event.detail.chipIndex, 1); pxxs_ids.splice(event.detail.chipIndex, 1);
pxxs_chips = pxxs_chips.map(
(label: string, index: number) =>
`P${(currentrace?.pxx ?? -10) + index - 3}: ${label.split(" ").pop()}`,
);
}; };
const on_dnfs_chip_select = (event: CustomEvent<AutocompleteOption<string>>): void => { const on_dnfs_chip_select = (event: CustomEvent<AutocompleteOption<string>>): void => {
@ -227,75 +221,78 @@
</script> </script>
<Card width="w-full sm:w-[512px]"> <Card width="w-full sm:w-[512px]">
<!-- Race select input --> <div class="flex flex-col gap-2">
{#await data.races then races} <!-- Race select input -->
<Dropdown {#await data.races then races}
name="race" <Dropdown
bind:value={race_select_value} name="race"
options={race_dropdown_options(races)} bind:value={race_select_value}
onchange={on_race_select} options={race_dropdown_options(races)}
{labelwidth} {labelwidth}
{disabled} {disabled}
{required} {required}
> >
Race Race
</Dropdown> </Dropdown>
{/await} {/await}
<div class="mt-2 flex flex-col gap-2"> {#if race_select_value}
<!-- PXXs autocomplete chips --> <!-- PXXs autocomplete chips -->
<InputChip <InputChip
bind:input={pxxs_input}
bind:value={pxxs_chips}
whitelist={pxxs_whitelist}
allowUpperCase
placeholder={pxxs_placeholder}
name="pxxs_codes"
{disabled}
{required}
on:remove={on_pxxs_chip_remove}
/>
<div class="card max-h-48 w-full overflow-y-auto p-2" tabindex="-1">
<Autocomplete
bind:input={pxxs_input} bind:input={pxxs_input}
options={pxxs_options} bind:value={pxxs_chips}
denylist={pxxs_chips} whitelist={pxxs_whitelist}
on:selection={on_pxxs_chip_select} allowUpperCase
placeholder={pxxs_placeholder}
name="pxxs_codes"
{disabled}
{required}
on:remove={on_pxxs_chip_remove}
/> />
</div> <div class="card max-h-48 w-full overflow-y-auto p-2" tabindex="-1">
<Autocomplete
bind:input={pxxs_input}
options={pxxs_options}
denylist={pxxs_chips}
on:selection={on_pxxs_chip_select}
/>
</div>
<!-- DNFs autocomplete chips --> <!-- DNFs autocomplete chips -->
<InputChip <InputChip
bind:input={dnfs_input}
bind:value={dnfs_chips}
whitelist={pxxs_whitelist}
allowUpperCase
placeholder="Select DNFs..."
name="dnfs_codes"
{disabled}
on:remove={on_dnfs_chip_remove}
/>
<div class="card max-h-48 w-full overflow-y-auto p-2" tabindex="-1">
<Autocomplete
bind:input={dnfs_input} bind:input={dnfs_input}
options={dnfs_options} bind:value={dnfs_chips}
denylist={dnfs_chips} whitelist={pxxs_whitelist}
on:selection={on_dnfs_chip_select} allowUpperCase
placeholder="Select DNFs..."
name="dnfs_codes"
{disabled}
on:remove={on_dnfs_chip_remove}
/> />
</div> <div class="card max-h-48 w-full overflow-y-auto p-2" tabindex="-1">
<Autocomplete
bind:input={dnfs_input}
options={pxxs_options}
denylist={dnfs_chips}
on:selection={on_dnfs_chip_select}
/>
</div>
<!-- Save/Delete buttons --> <!-- Save/Delete buttons -->
<div class="flex items-center justify-end gap-2"> <div class="flex items-center justify-end gap-2">
{#if result} {#if result}
<Button onclick={update_raceresult()} color="secondary" {disabled} width="w-1/2" <Button onclick={update_raceresult()} color="secondary" {disabled} width="w-1/2"
>Save</Button >Save</Button
> >
<Button onclick={delete_raceresult} color="primary" {disabled} width="w-1/2">Delete</Button> <Button onclick={delete_raceresult} color="primary" {disabled} width="w-1/2"
{:else} >Delete</Button
<Button onclick={update_raceresult(true)} color="tertiary" {disabled} width="w-full"> >
Create Result {:else}
</Button> <Button onclick={update_raceresult(true)} color="tertiary" {disabled} width="w-full">
{/if} Create Result
</div> </Button>
{/if}
</div>
{/if}
</div> </div>
</Card> </Card>

View File

@ -0,0 +1,399 @@
<script lang="ts">
import { Card, Button, Dropdown, Input } from "$lib/components";
import type { Driver, SeasonPick, Team } from "$lib/schema";
import { get_by_value, get_driver_headshot_template } from "$lib/database";
import {
Autocomplete,
getModalStore,
getToastStore,
InputChip,
type AutocompleteOption,
type ModalStore,
type ToastStore,
} from "@skeletonlabs/skeleton";
import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config";
import { driver_dropdown_options, team_dropdown_options } from "$lib/dropdown";
import { get_error_toast } from "$lib/toast";
import { pb } from "$lib/pocketbase";
import type { PageData } from "../../../routes/seasonpicks/$types";
interface SeasonPickCardProps {
/** Data passed from the page context */
data: PageData;
/** The [SeasonPick] object used to prefill values. */
seasonpick?: SeasonPick;
}
let { data, seasonpick = undefined }: SeasonPickCardProps = $props();
const modalStore: ModalStore = getModalStore();
if ($modalStore[0].meta) {
const meta = $modalStore[0].meta;
data = meta.data;
seasonpick = meta.seasonpick;
}
const toastStore: ToastStore = getToastStore();
// Await promises
let drivers: Driver[] | undefined = $state(undefined);
data.drivers.then((d: Driver[]) => (drivers = d));
let teams: Team[] | undefined = $state(undefined);
data.teams.then((t: Team[]) => (teams = t));
// Constants
const labelwidth: string = "150px";
// Reactive state
let required: boolean = $derived(!seasonpick);
let disabled: boolean = false; // TODO: Datelock
let hottake_value: string = $state(seasonpick?.hottake ?? "");
let wdc_value: string = $state(seasonpick?.wdcwinner ?? "");
let wcc_value: string = $state(seasonpick?.wccwinner ?? "");
let overtakes_value: string = $state(seasonpick?.mostovertakes ?? "");
let dnfs_value: string = $state(seasonpick?.mostdnfs ?? "");
let doohan_value: string = $state(seasonpick?.doohanstarts.toString() ?? "");
let teamwinners_input: string = $state("");
let teamwinners_chips: string[] = $state([]);
let podiums_input: string = $state("");
let podiums_chips: string[] = $state([]);
// Set the teamwinners/podiums states once the drivers are loaded
data.drivers.then(async (drivers: Driver[]) => {
teamwinners_chips =
seasonpick?.teamwinners.map(
(id: string) => get_by_value(drivers, "id", id)?.code ?? "Invalid",
) ?? [];
podiums_chips =
seasonpick?.podiums.map((id: string) => get_by_value(drivers, "id", id)?.code ?? "Invalid") ??
[];
});
// This is the actual data that gets sent through the form
let teamwinners_ids: string[] = $state(seasonpick?.teamwinners ?? []);
let podiums_ids: string[] = $state(seasonpick?.podiums ?? []);
let teamwinners_options: AutocompleteOption<string>[] = $derived.by(() =>
(drivers ?? [])
.filter((driver: Driver) => driver.active)
.map((driver: Driver) => {
const teamname: string = get_by_value(teams ?? [], "id", driver.team)?.name ?? "Invalid";
return {
firstname: driver.firstname,
lastname: driver.lastname,
code: driver.code,
teamname: teamname,
};
})
.sort((a, b) => a.teamname.localeCompare(b.teamname))
.map((driver) => {
return {
label: `${driver.teamname}: ${driver.firstname} ${driver.lastname}`,
value: driver.code,
};
}),
);
let teamwinners_whitelist: string[] = $derived.by(() =>
(drivers ?? []).map((driver: Driver) => driver.code),
);
let teamwinners_denylist: string[] = $derived.by(() => {
let denylist: string[] = [];
teamwinners_chips
.map((driver: string) => get_by_value(drivers ?? [], "code", driver))
.forEach((driver: Driver | undefined) => {
if (driver) {
(drivers ?? [])
.filter((d: Driver) => d.team === driver.team)
.forEach((d: Driver) => {
denylist.push(d.code);
});
}
});
return denylist;
});
let podiums_options: AutocompleteOption<string>[] = $derived.by(() =>
(drivers ?? [])
.filter((driver: Driver) => driver.active)
.sort((a: Driver, b: Driver) => a.firstname.localeCompare(b.firstname))
.map((driver: Driver) => {
return {
label: `${driver.firstname} ${driver.lastname}`,
value: driver.code,
};
}),
);
// Event handlers
const on_teamwinners_chip_select = (event: CustomEvent<AutocompleteOption<string>>): void => {
if (disabled || !drivers) return;
// Can only select 10 drivers
if (teamwinners_chips.length >= 10) return;
// Can only select a driver once
if (teamwinners_chips.includes(event.detail.value)) return;
// Manage labels that are displayed
teamwinners_chips.push(event.detail.value);
teamwinners_input = "";
// Manage ids that are submitted via form
const id: string = get_by_value(drivers, "code", event.detail.value)?.id ?? "Invalid";
if (!teamwinners_ids.includes(id)) {
teamwinners_ids.push(id);
}
};
const on_teamwinners_chip_remove = (event: CustomEvent): void => {
teamwinners_ids.splice(event.detail.chipIndex, 1);
};
const on_podiums_chip_select = (event: CustomEvent<AutocompleteOption<string>>): void => {
if (disabled || !drivers) return;
// Can only select a driver once
if (podiums_chips.includes(event.detail.value)) return;
// Manage labels that are displayed
podiums_chips.push(event.detail.value);
podiums_input = "";
// Manage ids that are submitted via form
const id: string = get_by_value(drivers, "code", event.detail.value)?.id ?? "Invalid";
if (!podiums_ids.includes(id)) {
podiums_ids.push(id);
}
};
const on_podiums_chip_remove = (event: CustomEvent): void => {
podiums_ids.splice(event.detail.chipIndex, 1);
};
// Database actions
const update_seasonpick = (create?: boolean): (() => Promise<void>) => {
const handler = async (): Promise<void> => {
if (!data.user?.id || data.user.id === "") {
toastStore.trigger(get_error_toast("Invalid user id!"));
return;
}
if (!hottake_value || hottake_value === "") {
toastStore.trigger(get_error_toast("Please enter a hottake!"));
return;
}
if (!wdc_value || wdc_value === "") {
toastStore.trigger(get_error_toast("Please select a driver for WDC!"));
return;
}
if (!wcc_value || wcc_value === "") {
toastStore.trigger(get_error_toast("Please select a team for WCC!"));
return;
}
if (!overtakes_value || overtakes_value === "") {
toastStore.trigger(get_error_toast("Please select a driver for most overtakes!"));
return;
}
if (!dnfs_value || dnfs_value === "") {
toastStore.trigger(get_error_toast("Please select a driver for most DNFs!"));
return;
}
if (
!doohan_value ||
doohan_value === "" ||
parseInt(doohan_value) <= 0 ||
parseInt(doohan_value) > 24
) {
toastStore.trigger(
get_error_toast("Please enter between 0 and 24 starts for Jack Doohan!"),
);
return;
}
if (!teamwinners_ids || teamwinners_ids.length !== 10) {
toastStore.trigger(get_error_toast("Please select a winner for each team!"));
return;
}
if (!podiums_ids || podiums_ids.length < 3) {
toastStore.trigger(get_error_toast("Please select at least 3 drivers with podiums!"));
return;
}
const seasonpick_data = {
user: data.user.id,
hottake: hottake_value,
wdcwinner: wdc_value,
wccwinner: wcc_value,
mostovertakes: overtakes_value,
mostdnfs: dnfs_value,
doohanstarts: doohan_value,
teamwinners: teamwinners_ids,
podiums: podiums_ids,
};
try {
if (create) {
await pb.collection("seasonpicks").create(seasonpick_data);
} else {
if (!seasonpick?.id) {
toastStore.trigger(get_error_toast("Invalid seasonpick id!"));
return;
}
await pb.collection("seasonpicks").update(seasonpick.id, seasonpick_data);
}
modalStore.close();
} catch (error) {
toastStore.trigger(get_error_toast("" + error));
}
};
return handler;
};
const delete_seasonpick = async (): Promise<void> => {
if (!seasonpick?.id) {
toastStore.trigger(get_error_toast("Invalid seasonpick id!"));
return;
}
try {
await pb.collection("seasonpicks").delete(seasonpick.id);
modalStore.close();
} catch (error) {
toastStore.trigger(get_error_toast("" + error));
}
};
</script>
{#await Promise.all([data.graphics, data.drivers, data.teams]) then [graphics, drivers, teams]}
<Card width="w-full sm:w-[512px]">
<div class="flex flex-col gap-2">
<!-- Hottake -->
<Input bind:value={hottake_value} {labelwidth} {disabled} {required}>Hottake</Input>
<!-- WDC select -->
<Dropdown
bind:value={wdc_value}
options={driver_dropdown_options(drivers.filter((driver: Driver) => driver.active))}
{labelwidth}
{disabled}
{required}
>
WDC
</Dropdown>
<!-- WCC select -->
<Dropdown
bind:value={wcc_value}
options={team_dropdown_options(teams)}
{labelwidth}
{disabled}
{required}
>
WCC
</Dropdown>
<!-- Overtakes select -->
<Dropdown
bind:value={overtakes_value}
options={driver_dropdown_options(drivers.filter((driver: Driver) => driver.active))}
{labelwidth}
{disabled}
{required}
>
Most Overtakes
</Dropdown>
<!-- DNFs select -->
<Dropdown
bind:value={dnfs_value}
options={driver_dropdown_options(drivers.filter((driver: Driver) => driver.active))}
{labelwidth}
{disabled}
{required}
>
Most DNFs
</Dropdown>
<!-- Doohan Starts -->
<Input
type="number"
min={0}
max={24}
bind:value={doohan_value}
{labelwidth}
{disabled}
{required}
>
Doohan Starts
</Input>
<!-- Teamwinners autocomplete chips -->
<InputChip
bind:input={teamwinners_input}
bind:value={teamwinners_chips}
whitelist={teamwinners_whitelist}
allowUpperCase
placeholder="Select Teamwinners..."
name="teamwinners_codes"
{disabled}
{required}
on:remove={on_teamwinners_chip_remove}
/>
<div class="card max-h-48 w-full overflow-y-auto p-2" tabindex="-1">
<Autocomplete
bind:input={teamwinners_input}
options={teamwinners_options}
denylist={teamwinners_denylist}
on:selection={on_teamwinners_chip_select}
/>
</div>
<!-- Podiums autocomplete chips -->
<InputChip
bind:input={podiums_input}
bind:value={podiums_chips}
whitelist={teamwinners_whitelist}
allowUpperCase
placeholder="Select Drivers with Podiums..."
name="podiums_codes"
{disabled}
{required}
on:remove={on_podiums_chip_remove}
/>
<div class="card max-h-48 w-full overflow-y-auto p-2" tabindex="-1">
<Autocomplete
bind:input={podiums_input}
options={podiums_options}
denylist={podiums_chips}
on:selection={on_podiums_chip_select}
/>
</div>
<!-- Save/Delete buttons -->
<div class="flex justify-end gap-2">
{#if seasonpick}
<Button onclick={update_seasonpick()} color="secondary" {disabled} width="w-1/2">
Save Changes
</Button>
<Button onclick={delete_seasonpick} color="primary" {disabled} width="w-1/2">
Delete
</Button>
{:else}
<Button onclick={update_seasonpick(true)} color="tertiary" {disabled} width="w-full">
Make Pick
</Button>
{/if}
</div>
</div>
</Card>
{/await}

View File

@ -12,6 +12,7 @@ import DriverCard from "./cards/DriverCard.svelte";
import RaceCard from "./cards/RaceCard.svelte"; import RaceCard from "./cards/RaceCard.svelte";
import RacePickCard from "./cards/RacePickCard.svelte"; import RacePickCard from "./cards/RacePickCard.svelte";
import RaceResultCard from "./cards/RaceResultCard.svelte"; import RaceResultCard from "./cards/RaceResultCard.svelte";
import SeasonPickCard from "./cards/SeasonPickCard.svelte";
import SubstitutionCard from "./cards/SubstitutionCard.svelte"; import SubstitutionCard from "./cards/SubstitutionCard.svelte";
import TeamCard from "./cards/TeamCard.svelte"; import TeamCard from "./cards/TeamCard.svelte";
@ -43,6 +44,7 @@ export {
RaceCard, RaceCard,
RacePickCard, RacePickCard,
RaceResultCard, RaceResultCard,
SeasonPickCard,
SubstitutionCard, SubstitutionCard,
TeamCard, TeamCard,

View File

@ -13,40 +13,46 @@ import {
* Generates a list of [DropdownOptions] for a <Dropdown> component. * Generates a list of [DropdownOptions] for a <Dropdown> component.
*/ */
export const team_dropdown_options = (teams: Team[]): DropdownOption[] => export const team_dropdown_options = (teams: Team[]): DropdownOption[] =>
teams.map((team: Team) => { teams
return { .sort((a: Team, b: Team) => a.name.localeCompare(b.name))
label: team.name, .map((team: Team) => {
value: team.id, return {
icon_url: team.banner_url, label: team.name,
icon_width: TEAM_BANNER_WIDTH, value: team.id,
icon_height: TEAM_BANNER_HEIGHT, icon_url: team.banner_url,
}; icon_width: TEAM_BANNER_WIDTH,
}); icon_height: TEAM_BANNER_HEIGHT,
};
});
/** /**
* Generates a list of [DropdownOptions] for a <Dropdown> component. * Generates a list of [DropdownOptions] for a <Dropdown> component.
*/ */
export const driver_dropdown_options = (drivers: Driver[]): DropdownOption[] => export const driver_dropdown_options = (drivers: Driver[]): DropdownOption[] =>
drivers.map((driver: Driver) => { drivers
return { .sort((a: Driver, b: Driver) => a.firstname.localeCompare(b.firstname))
label: driver.code, .map((driver: Driver) => {
value: driver.id, return {
icon_url: driver.headshot_url, label: `${driver.firstname} ${driver.lastname}`,
icon_width: DRIVER_HEADSHOT_WIDTH, value: driver.id,
icon_height: DRIVER_HEADSHOT_HEIGHT, icon_url: driver.headshot_url,
}; icon_width: DRIVER_HEADSHOT_WIDTH,
}); icon_height: DRIVER_HEADSHOT_HEIGHT,
};
});
/** /**
* Generates a list of [DropdownOptions] for a <Dropdown> component. * Generates a list of [DropdownOptions] for a <Dropdown> component.
*/ */
export const race_dropdown_options = (races: Race[]): DropdownOption[] => export const race_dropdown_options = (races: Race[]): DropdownOption[] =>
races.map((race: Race) => { races
return { .sort((a: Race, b: Race) => a.step - b.step)
label: race.name, .map((race: Race) => {
value: race.id, return {
icon_url: race.pictogram_url, label: race.name,
icon_width: RACE_PICTOGRAM_WIDTH, value: race.id,
icon_height: RACE_PICTOGRAM_HEIGHT, icon_url: race.pictogram_url,
}; icon_width: RACE_PICTOGRAM_WIDTH,
}); icon_height: RACE_PICTOGRAM_HEIGHT,
};
});

View File

@ -17,6 +17,7 @@
NameIcon, NameIcon,
RacePickCard, RacePickCard,
RaceResultCard, RaceResultCard,
SeasonPickCard,
} from "$lib/components"; } from "$lib/components";
import { get_avatar_preview_event_handler } from "$lib/image"; import { get_avatar_preview_event_handler } from "$lib/image";
import { import {
@ -58,6 +59,7 @@
raceCard: { ref: RaceCard }, raceCard: { ref: RaceCard },
racePickCard: { ref: RacePickCard }, racePickCard: { ref: RacePickCard },
raceResultCard: { ref: RaceResultCard }, raceResultCard: { ref: RaceResultCard },
seasonPickCard: { ref: SeasonPickCard },
substitutionCard: { ref: SubstitutionCard }, substitutionCard: { ref: SubstitutionCard },
}; };

View File

@ -1,4 +1,4 @@
import { fetch_drivers, fetch_raceresults, fetch_races } from "$lib/fetch"; import { fetch_drivers, fetch_raceresults, fetch_races, fetch_substitutions } from "$lib/fetch";
import type { PageLoad } from "../../$types"; import type { PageLoad } from "../../$types";
export const load: PageLoad = async ({ fetch, depends }) => { export const load: PageLoad = async ({ fetch, depends }) => {
@ -8,5 +8,6 @@ export const load: PageLoad = async ({ fetch, depends }) => {
drivers: fetch_drivers(fetch), drivers: fetch_drivers(fetch),
races: fetch_races(fetch), races: fetch_races(fetch),
raceresults: fetch_raceresults(fetch), raceresults: fetch_raceresults(fetch),
substitutions: fetch_substitutions(fetch),
}; };
}; };

View File

@ -29,7 +29,7 @@
const seasonpick_handler = async (event: Event) => { const seasonpick_handler = async (event: Event) => {
const modalSettings: ModalSettings = { const modalSettings: ModalSettings = {
type: "component", type: "component",
component: "seasonPickcard", component: "seasonPickCard",
meta: { meta: {
data, data,
seasonpick: data.seasonpick, seasonpick: data.seasonpick,
@ -85,9 +85,11 @@
</div> </div>
<div class="card w-full min-w-40 p-2 shadow"> <div class="card w-full min-w-40 p-2 shadow">
<h1 class="mb-2 text-nowrap font-bold">Doohan Starts:</h1> <h1 class="mb-2 text-nowrap font-bold">Doohan Starts:</h1>
<span class="text-sm"> {#if data.seasonpick}
Jack Doohan startet {data.seasonpick?.doohanstarts} mal. <span class="text-sm">
</span> Jack Doohan startet {data.seasonpick?.doohanstarts} mal.
</span>
{/if}
</div> </div>
</div> </div>