289 lines
11 KiB
Svelte
289 lines
11 KiB
Svelte
<!--
|
|
- This file is a part of the SteamWar software.
|
|
-
|
|
- Copyright (C) 2023 SteamWar.de-Serverteam
|
|
-
|
|
- This program is free software: you can redistribute it and/or modify
|
|
- it under the terms of the GNU Affero General Public License as published by
|
|
- the Free Software Foundation, either version 3 of the License, or
|
|
- (at your option) any later version.
|
|
-
|
|
- This program is distributed in the hope that it will be useful,
|
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
- GNU Affero General Public License for more details.
|
|
-
|
|
- You should have received a copy of the GNU Affero General Public License
|
|
- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
-->
|
|
|
|
<script lang="ts">
|
|
import type {EventFight, ExtendedEvent} from "@type/event.ts";
|
|
import {
|
|
Button,
|
|
Checkbox, Input, Label,
|
|
Modal,
|
|
SpeedDial,
|
|
SpeedDialButton,
|
|
Toolbar,
|
|
ToolbarButton,
|
|
ToolbarGroup,
|
|
Tooltip
|
|
} from "flowbite-svelte";
|
|
import {
|
|
ArrowsRepeatSolid, CalendarWeekOutline,
|
|
PlusSolid, ProfileCardOutline, TrashBinOutline, UsersGroupOutline,
|
|
} from "flowbite-svelte-icons";
|
|
import FightCard from "./FightCard.svelte";
|
|
import CreateFightModal from "./modals/CreateFightModal.svelte";
|
|
import {fightRepo} from "@repo/repo.ts";
|
|
import {groups, players} from "@stores/stores.ts";
|
|
import TypeAheadSearch from "../../components/TypeAheadSearch.svelte";
|
|
import type {UpdateFight} from "@repo/fight.ts";
|
|
import dayjs from "dayjs";
|
|
import duration from "dayjs/plugin/duration"
|
|
dayjs.extend(duration)
|
|
|
|
export let data: ExtendedEvent;
|
|
|
|
let createOpen = false;
|
|
let fights = data.fights;
|
|
let selectedFights: Set<EventFight> = new Set();
|
|
|
|
$: groupsMap = new Set(fights.map(fight => fight.group));
|
|
$: groupedFights = Array.from(groupsMap).map(group => {
|
|
return {
|
|
group: group,
|
|
fights: fights.filter(fight => fight.group === group)
|
|
}
|
|
});
|
|
|
|
function cycleSelect() {
|
|
if (selectedFights.size === fights.length) {
|
|
selectedFights = new Set();
|
|
} else if(selectedFights.size === 0){
|
|
selectedFights = new Set(fights.filter(fight => fight.start > Date.now()));
|
|
|
|
if (selectedFights.size === 0) {
|
|
selectedFights = new Set(fights);
|
|
}
|
|
} else {
|
|
selectedFights = new Set(fights);
|
|
}
|
|
}
|
|
|
|
function cycleGroup(groupFights: EventFight[]) {
|
|
if(groupFights.every(gf => selectedFights.has(gf))) {
|
|
groupFights.forEach(fight => selectedFights.delete(fight));
|
|
} else {
|
|
groupFights.forEach(fight => selectedFights.add(fight));
|
|
}
|
|
selectedFights = selectedFights;
|
|
}
|
|
|
|
let deleteOpen = false;
|
|
async function deleteFights() {
|
|
for (const fight of selectedFights) {
|
|
await $fightRepo.deleteFight(fight.id);
|
|
}
|
|
fights = await $fightRepo.listFights(data.event.id);
|
|
selectedFights = new Set();
|
|
deleteOpen = false;
|
|
}
|
|
|
|
let kampfleiterOpen = false;
|
|
$: selectPlayers = $players.map(player => {
|
|
return {
|
|
name: player.name,
|
|
value: player.id.toString()
|
|
}
|
|
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
let kampfleiter = "";
|
|
async function updateKampfleiter() {
|
|
for (const fight of selectedFights) {
|
|
let f: UpdateFight = {
|
|
blueTeam: null,
|
|
group: null,
|
|
kampfleiter: Number.parseInt(kampfleiter),
|
|
map: null,
|
|
redTeam: null,
|
|
spielmodus: null,
|
|
start: null
|
|
};
|
|
await $fightRepo.updateFight(fight.id, f);
|
|
}
|
|
fights = await $fightRepo.listFights(data.event.id);
|
|
selectedFights = new Set();
|
|
kampfleiter = "";
|
|
kampfleiterOpen = false;
|
|
}
|
|
|
|
let groupChangeOpen = false;
|
|
let group = "";
|
|
let groupSearch = "";
|
|
|
|
$: selectableGroups = [{
|
|
name: 'None',
|
|
value: ''
|
|
}, {
|
|
value: groupSearch,
|
|
name: `Create: '${groupSearch}'`
|
|
}, ...$groups.map(group => {
|
|
return {
|
|
name: group,
|
|
value: group
|
|
}
|
|
}).sort((a, b) => a.name.localeCompare(b.name))];
|
|
async function updateGroup() {
|
|
for (const fight of selectedFights) {
|
|
let f: UpdateFight = {
|
|
blueTeam: null,
|
|
group: group,
|
|
kampfleiter: null,
|
|
map: null,
|
|
redTeam: null,
|
|
spielmodus: null,
|
|
start: null
|
|
};
|
|
await $fightRepo.updateFight(fight.id, f);
|
|
}
|
|
fights = await $fightRepo.listFights(data.event.id);
|
|
selectedFights = new Set();
|
|
group = "";
|
|
groupSearch = "";
|
|
groupChangeOpen = false;
|
|
}
|
|
|
|
$: minTime = dayjs(Math.min(...fights.map(fight => fight.start))).utc(true);
|
|
let changeTimeOpen = false;
|
|
let changedTime = dayjs(Math.min(...fights.map(fight => fight.start)))?.utc(true)?.toISOString()?.slice(0, -1);
|
|
|
|
$: deltaTime = dayjs.duration(dayjs(changedTime).utc(true).diff(minTime))
|
|
|
|
async function updateStartTime() {
|
|
for (const fight of selectedFights) {
|
|
let f: UpdateFight = {
|
|
blueTeam: null,
|
|
group: null,
|
|
kampfleiter: null,
|
|
map: null,
|
|
redTeam: null,
|
|
spielmodus: null,
|
|
start: dayjs(fight.start).add(deltaTime.asMilliseconds(), 'millisecond')
|
|
};
|
|
await $fightRepo.updateFight(fight.id, f);
|
|
}
|
|
fights = await $fightRepo.listFights(data.event.id);
|
|
changedTime = minTime.toISOString().slice(0, -1);
|
|
selectedFights = new Set();
|
|
changeTimeOpen = false;
|
|
}
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>{data.event.name} - Fights</title>
|
|
</svelte:head>
|
|
|
|
<div class="pb-28">
|
|
<Toolbar class="mx-4 mt-2 w-fit">
|
|
<ToolbarGroup>
|
|
<Checkbox class="ml-2" checked={selectedFights.size === fights.length} on:click={cycleSelect}/>
|
|
<Tooltip>Select Upcoming</Tooltip>
|
|
</ToolbarGroup>
|
|
<ToolbarGroup>
|
|
<ToolbarButton on:click={() => selectedFights.size > 0 ? changeTimeOpen = true : changeTimeOpen = false}>
|
|
<CalendarWeekOutline/>
|
|
</ToolbarButton>
|
|
<Tooltip>Reschedule Fights</Tooltip>
|
|
<ToolbarButton on:click={() => selectedFights.size > 0 ? kampfleiterOpen = true : kampfleiterOpen = false}>
|
|
<ProfileCardOutline/>
|
|
</ToolbarButton>
|
|
<Tooltip>Change Kampfleiter</Tooltip>
|
|
<ToolbarButton on:click={() => selectedFights.size > 0 ? groupChangeOpen = true : groupChangeOpen = false}>
|
|
<UsersGroupOutline/>
|
|
</ToolbarButton>
|
|
<Tooltip>Change Group</Tooltip>
|
|
</ToolbarGroup>
|
|
<ToolbarGroup>
|
|
<ToolbarButton color="red" on:click={() => selectedFights.size > 0 ? deleteOpen = true : deleteOpen = false}>
|
|
<TrashBinOutline/>
|
|
</ToolbarButton>
|
|
<Tooltip>Delete</Tooltip>
|
|
</ToolbarGroup>
|
|
</Toolbar>
|
|
{#each groupedFights as group}
|
|
<div class="flex mt-4">
|
|
<Checkbox class="ml-2 text-center" checked={group.fights.every(gf => selectedFights.has(gf))} on:click={() => cycleGroup(group.fights)}/>
|
|
<h1 class="ml-4 text-2xl">{group.group ?? "Ungrouped"}</h1>
|
|
</div>
|
|
{#each group.fights.sort((a, b) => a.start - b.start) as fight, i (fight.id)}
|
|
<FightCard {fight} {i} {data} selected={selectedFights.has(fight)}
|
|
on:select={() => {
|
|
if (selectedFights.has(fight)) {
|
|
selectedFights.delete(fight);
|
|
} else {
|
|
selectedFights.add(fight);
|
|
}
|
|
selectedFights = selectedFights;
|
|
}}
|
|
on:update={async () => fights = await $fightRepo.listFights(data.event.id)}
|
|
/>
|
|
{/each}
|
|
{/each}
|
|
</div>
|
|
|
|
<CreateFightModal {data} bind:open={createOpen} on:create={async () => data.fights = await $fightRepo.listFights(data.event.id)}></CreateFightModal>
|
|
|
|
<Modal bind:open={deleteOpen} title="Delete {selectedFights.size} Fights" autoclose size="sm">
|
|
<p>Are you sure you want to delete {selectedFights.size} fights?</p>
|
|
<svelte:fragment slot="footer">
|
|
<Button color="red" class="ml-auto" on:click={deleteFights}>Delete</Button>
|
|
<Button on:click={() => deleteOpen = false} color="alternative">Cancel</Button>
|
|
</svelte:fragment>
|
|
</Modal>
|
|
|
|
<Modal bind:open={kampfleiterOpen} title="Change Kampfleiter" size="sm">
|
|
<div class="m-2">
|
|
<Label for="fight-kampf">Kampfleiter</Label>
|
|
<TypeAheadSearch items={selectPlayers} bind:selected={kampfleiter}></TypeAheadSearch>
|
|
</div>
|
|
<svelte:fragment slot="footer">
|
|
<Button class="ml-auto" on:click={updateKampfleiter}>Change</Button>
|
|
<Button on:click={() => kampfleiterOpen = false} color="alternative">Cancel</Button>
|
|
</svelte:fragment>
|
|
</Modal>
|
|
|
|
<Modal bind:open={groupChangeOpen} title="Change Group" size="sm">
|
|
<div class="m-2">
|
|
<Label for="fight-kampf">Group</Label>
|
|
<TypeAheadSearch items={selectableGroups} bind:selected={group} bind:searchValue={groupSearch} all></TypeAheadSearch>
|
|
</div>
|
|
<svelte:fragment slot="footer">
|
|
<Button class="ml-auto" on:click={updateGroup}>Change</Button>
|
|
<Button on:click={() => groupChangeOpen = false} color="alternative">Cancel</Button>
|
|
</svelte:fragment>
|
|
</Modal>
|
|
|
|
<Modal bind:open={changeTimeOpen} title="Change Start Time" size="sm">
|
|
<div class="m-2">
|
|
<Label for="fight-start">New Start Time:</Label>
|
|
<Input id="fight-start" bind:value={changedTime} let:props>
|
|
<input type="datetime-local" {...props} bind:value={changedTime}/>
|
|
</Input>
|
|
</div>
|
|
<p>{deltaTime.asMilliseconds() < 0 ? '' : '+'}{("0" + deltaTime.hours()).slice(-2)}:{("0" + deltaTime.minutes()).slice(-2)}</p>
|
|
<svelte:fragment slot="footer">
|
|
<Button class="ml-auto" on:click={updateStartTime}>Update</Button>
|
|
<Button on:click={() => changeTimeOpen = false} color="alternative">Cancel</Button>
|
|
</svelte:fragment>
|
|
</Modal>
|
|
|
|
<SpeedDial>
|
|
<SpeedDialButton name="Add" on:click={() => createOpen = true}>
|
|
<PlusSolid/>
|
|
</SpeedDialButton>
|
|
<SpeedDialButton name="Generate" href="#/event/{data.event.id}/generate">
|
|
<ArrowsRepeatSolid/>
|
|
</SpeedDialButton>
|
|
</SpeedDial>
|