162 lines
4.7 KiB
Svelte
162 lines
4.7 KiB
Svelte
<script lang="ts">
|
|
import type { PageProps, Snapshot } from './$types';
|
|
|
|
import BarChart from 'lucide-svelte/icons/bar-chart-2';
|
|
import Users from 'lucide-svelte/icons/users';
|
|
|
|
import Puzzle from '$lib/components/puzzle.svelte';
|
|
import Button from '$lib/components/ui/button/button.svelte';
|
|
|
|
let { data }: PageProps = $props();
|
|
|
|
// TODO: Refactor, this is for event purpose only, this is a mess
|
|
|
|
type Filters = {
|
|
name: string;
|
|
themeList?: string;
|
|
difficultyList?: string;
|
|
};
|
|
|
|
let name: string = '';
|
|
let themeList: string[] = [];
|
|
let difficultyList: string[] = [];
|
|
|
|
const difficulties = data.chapter?.puzzles?.reduce((acc: Record<string, string>, puzzle) => {
|
|
const tag = puzzle.tags?.find((tag) =>
|
|
['easy', 'medium', 'hard'].includes(tag.name.toLowerCase())
|
|
);
|
|
|
|
if (tag) {
|
|
acc[tag.name] = tag.name.toLowerCase();
|
|
}
|
|
|
|
return acc;
|
|
}, {}) as Record<string, string>;
|
|
|
|
const themes = data.chapter?.puzzles?.reduce((acc: Record<string, string>, puzzle) => {
|
|
puzzle.tags?.forEach((tag) => {
|
|
if (!['easy', 'medium', 'hard'].includes(tag.name.toLowerCase())) {
|
|
acc[tag.name] = tag.name.toLowerCase();
|
|
}
|
|
});
|
|
|
|
return acc;
|
|
}, {}) as Record<string, string>;
|
|
|
|
const filteredPuzzles = $derived(
|
|
data.chapter?.puzzles?.filter((puzzle) => {
|
|
const regex = new RegExp(name, 'i');
|
|
|
|
const nameMatch = regex.test(puzzle.name);
|
|
|
|
const themeMatch = themeList.length
|
|
? puzzle.tags?.some((tag) => themeList.includes(tag.name.toLowerCase()))
|
|
: true;
|
|
|
|
const difficultyMatch = difficultyList.length
|
|
? puzzle.tags?.some((tag) => difficultyList.includes(tag.name.toLowerCase()))
|
|
: true;
|
|
|
|
return nameMatch && themeMatch && difficultyMatch;
|
|
}) ?? []
|
|
);
|
|
|
|
export const snapshot: Snapshot<Filters> = {
|
|
capture: () => ({
|
|
name: name,
|
|
themeList: themeList.length ? themeList.join(',') : '',
|
|
difficultyList: difficultyList.length ? difficultyList.join(',') : ''
|
|
}),
|
|
restore: (value) => {
|
|
name = value.name;
|
|
themeList = value.themeList ? value.themeList.split(',') : [];
|
|
difficultyList = value.difficultyList ? value.difficultyList.split(',') : [];
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<section class="flex w-full flex-col gap-4">
|
|
<header class="flex flex-col justify-between gap-2 lg:flex-row lg:items-center">
|
|
<div class="flex flex-col">
|
|
<h2 class="text-xl font-semibold">{data.chapter.name}</h2>
|
|
{#if !data.chapter?.puzzles?.length}
|
|
<p class="text-muted-foreground">Le chapitre ne contient pour l'instant aucun puzzle</p>
|
|
{:else}
|
|
<p class="text-muted-foreground">
|
|
Il vous reste {data.chapter.puzzles.filter((p) => !p.score).length} puzzles à résoudre sur
|
|
{data.chapter.puzzles.length} puzzles disponibles
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
<div class="flex gap-2">
|
|
{#if data.chapter.start && data.chapter.end}
|
|
<Button href="/chapters/{data.chapter.id}/groups">
|
|
<Users class="mr-2 size-6" />
|
|
Voir les groupes
|
|
</Button>
|
|
<Button href="/chapters/{data.chapter.id}/leaderboard">
|
|
<BarChart class="mr-2 size-6" />
|
|
Voir le classement
|
|
</Button>
|
|
{/if}
|
|
</div>
|
|
</header>
|
|
<!-- <div class="flex flex-col gap-2 md:flex-row">
|
|
<Input bind:value={name} placeholder="Rechercher un puzzle" />
|
|
{#if Object.keys(themes).length}
|
|
<Select.Root
|
|
multiple
|
|
selected={themeList.map((theme) => ({ label: theme, value: theme }))}
|
|
onSelectedChange={(v) => {
|
|
if (v) {
|
|
themeList = v.map((item) => item.value);
|
|
} else {
|
|
themeList = [];
|
|
}
|
|
}}
|
|
>
|
|
<Select.Trigger class="md:w-[180px]">
|
|
<Select.Value placeholder="Thème" />
|
|
</Select.Trigger>
|
|
<Select.Content>
|
|
{#each Object.entries(themes) as [key, value]}
|
|
<Select.Item {value}>{key}</Select.Item>
|
|
{/each}
|
|
</Select.Content>
|
|
</Select.Root>
|
|
{/if}
|
|
{#if Object.keys(difficulties).length}
|
|
<Select.Root
|
|
multiple
|
|
selected={difficultyList.map((difficulty) => ({ label: difficulty, value: difficulty }))}
|
|
onSelectedChange={(v) => {
|
|
if (v) {
|
|
difficultyList = v.map((item) => item.value);
|
|
} else {
|
|
difficultyList = [];
|
|
}
|
|
}}
|
|
>
|
|
<Select.Trigger class="md:w-[180px]">
|
|
<Select.Value placeholder="Difficulté" />
|
|
</Select.Trigger>
|
|
<Select.Content>
|
|
{#each Object.entries(difficulties) as [key, value]}
|
|
<Select.Item {value}>{key}</Select.Item>
|
|
{/each}
|
|
</Select.Content>
|
|
</Select.Root>
|
|
{/if}
|
|
</div> -->
|
|
<div class="flex flex-col gap-2">
|
|
{#if !filteredPuzzles.length}
|
|
<li class="flex h-16 w-full items-center justify-center rounded border border-border bg-card">
|
|
<p class="text-muted-foreground">Aucun puzzle trouvé</p>
|
|
</li>
|
|
{:else}
|
|
{#each filteredPuzzles as puzzle (puzzle.id)}
|
|
<Puzzle {puzzle} chapterId={data.chapter.id} />
|
|
{/each}
|
|
{/if}
|
|
</div>
|
|
</section>
|