Add viewing teammate schedules
parent
104282da30
commit
5f45a8ebda
|
@ -11,6 +11,7 @@ export { OpenAPI } from './core/OpenAPI';
|
||||||
export type { OpenAPIConfig } from './core/OpenAPI';
|
export type { OpenAPIConfig } from './core/OpenAPI';
|
||||||
|
|
||||||
export type { AddPlayerJson } from './models/AddPlayerJson';
|
export type { AddPlayerJson } from './models/AddPlayerJson';
|
||||||
|
export type { AvailabilitySchema } from './models/AvailabilitySchema';
|
||||||
export type { CreateTeamJson } from './models/CreateTeamJson';
|
export type { CreateTeamJson } from './models/CreateTeamJson';
|
||||||
export type { EditMemberRolesJson } from './models/EditMemberRolesJson';
|
export type { EditMemberRolesJson } from './models/EditMemberRolesJson';
|
||||||
export type { PlayerSchema } from './models/PlayerSchema';
|
export type { PlayerSchema } from './models/PlayerSchema';
|
||||||
|
@ -30,6 +31,7 @@ export type { ViewScheduleResponse } from './models/ViewScheduleResponse';
|
||||||
export type { ViewTeamMembersResponse } from './models/ViewTeamMembersResponse';
|
export type { ViewTeamMembersResponse } from './models/ViewTeamMembersResponse';
|
||||||
export type { ViewTeamMembersResponseList } from './models/ViewTeamMembersResponseList';
|
export type { ViewTeamMembersResponseList } from './models/ViewTeamMembersResponseList';
|
||||||
export type { ViewTeamResponse } from './models/ViewTeamResponse';
|
export type { ViewTeamResponse } from './models/ViewTeamResponse';
|
||||||
|
export type { ViewTeamScheduleResponse } from './models/ViewTeamScheduleResponse';
|
||||||
export type { ViewTeamsResponse } from './models/ViewTeamsResponse';
|
export type { ViewTeamsResponse } from './models/ViewTeamsResponse';
|
||||||
|
|
||||||
export { DefaultService } from './services/DefaultService';
|
export { DefaultService } from './services/DefaultService';
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type AvailabilitySchema = {
|
||||||
|
availability?: Array<number>;
|
||||||
|
steamId: string;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { AvailabilitySchema } from './AvailabilitySchema';
|
||||||
|
export type ViewTeamScheduleResponse = {
|
||||||
|
playerAvailability: Record<string, AvailabilitySchema>;
|
||||||
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ import type { ViewAvailablePlayersResponse } from '../models/ViewAvailablePlayer
|
||||||
import type { ViewScheduleResponse } from '../models/ViewScheduleResponse';
|
import type { ViewScheduleResponse } from '../models/ViewScheduleResponse';
|
||||||
import type { ViewTeamMembersResponseList } from '../models/ViewTeamMembersResponseList';
|
import type { ViewTeamMembersResponseList } from '../models/ViewTeamMembersResponseList';
|
||||||
import type { ViewTeamResponse } from '../models/ViewTeamResponse';
|
import type { ViewTeamResponse } from '../models/ViewTeamResponse';
|
||||||
|
import type { ViewTeamScheduleResponse } from '../models/ViewTeamScheduleResponse';
|
||||||
import type { ViewTeamsResponse } from '../models/ViewTeamsResponse';
|
import type { ViewTeamsResponse } from '../models/ViewTeamsResponse';
|
||||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||||
import type { BaseHttpRequest } from '../core/BaseHttpRequest';
|
import type { BaseHttpRequest } from '../core/BaseHttpRequest';
|
||||||
|
@ -130,6 +131,32 @@ export class DefaultService {
|
||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* get_team_availability <GET>
|
||||||
|
* @param windowStart
|
||||||
|
* @param teamId
|
||||||
|
* @param windowSizeDays
|
||||||
|
* @returns ViewTeamScheduleResponse OK
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public getApiScheduleTeam(
|
||||||
|
windowStart: string,
|
||||||
|
teamId: number,
|
||||||
|
windowSizeDays: number = 7,
|
||||||
|
): CancelablePromise<ViewTeamScheduleResponse> {
|
||||||
|
return this.httpRequest.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/schedule/team',
|
||||||
|
query: {
|
||||||
|
'windowStart': windowStart,
|
||||||
|
'teamId': teamId,
|
||||||
|
'windowSizeDays': windowSizeDays,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Unprocessable Entity`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* view_available_at_time <GET>
|
* view_available_at_time <GET>
|
||||||
* @param startTime
|
* @param startTime
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, defineModel, defineProps, reactive, ref, onMounted, onUnmounted } from "vue";
|
import { computed, defineModel, defineProps, reactive, ref, onMounted, onUnmounted, type PropType } from "vue";
|
||||||
|
import moment, { type Moment } from "moment";
|
||||||
|
|
||||||
const model = defineModel();
|
const model = defineModel();
|
||||||
|
|
||||||
|
const selectedTime = defineModel("selectedTime");
|
||||||
|
|
||||||
|
const hoveredIndex = defineModel("hoveredIndex");
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
selectionMode: Number,
|
selectionMode: Number,
|
||||||
isDisabled: Boolean,
|
isDisabled: Boolean,
|
||||||
dateStart: Date,
|
overlay: Array,
|
||||||
|
dateStart: Object as PropType<Moment>,
|
||||||
firstHour: {
|
firstHour: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 14
|
default: 14
|
||||||
|
@ -17,6 +23,8 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isEditing = computed(() => !props.isDisabled);
|
||||||
|
|
||||||
const selectionStart = reactive({ x: undefined, y: undefined });
|
const selectionStart = reactive({ x: undefined, y: undefined });
|
||||||
const selectionEnd = reactive({ x: undefined, y: undefined });
|
const selectionEnd = reactive({ x: undefined, y: undefined });
|
||||||
const isCtrlDown = ref(false);
|
const isCtrlDown = ref(false);
|
||||||
|
@ -74,11 +82,40 @@ const daysOfWeek = [
|
||||||
"Sat"
|
"Sat"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function getTimeAtCell(dayIndex: number, hour: number) {
|
||||||
|
return props.dateStart.clone()
|
||||||
|
.add(dayIndex, "days")
|
||||||
|
.add(hour, "hours");
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSlotMouseOver($event, x, y) {
|
||||||
|
hoveredIndex.value = 24 * x + y;
|
||||||
|
|
||||||
|
if (!isEditing.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($event.buttons & 1 == 1) {
|
||||||
|
isShiftDown.value = $event.shiftKey;
|
||||||
|
isCtrlDown.value = $event.ctrlKey;
|
||||||
|
|
||||||
|
selectionEnd.x = x;
|
||||||
|
selectionEnd.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSlotMouseLeave($event, x, y) {
|
||||||
|
let index = 24 * x + y;
|
||||||
|
if (hoveredIndex.value == index) {
|
||||||
|
hoveredIndex.value = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isMouseDown = ref(false);
|
const isMouseDown = ref(false);
|
||||||
const selectionValue = ref(0);
|
const selectionValue = ref(0);
|
||||||
|
|
||||||
function onSlotMouseDown($event, x, y) {
|
function onSlotMouseDown($event, x, y) {
|
||||||
if (props.isDisabled) {
|
if (!isEditing.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,22 +133,8 @@ function onSlotMouseDown($event, x, y) {
|
||||||
console.log("selected " + x + " " + y);
|
console.log("selected " + x + " " + y);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSlotMouseOver($event, x, y) {
|
|
||||||
if (props.isDisabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($event.buttons & 1 == 1) {
|
|
||||||
isShiftDown.value = $event.shiftKey;
|
|
||||||
isCtrlDown.value = $event.ctrlKey;
|
|
||||||
|
|
||||||
selectionEnd.x = x;
|
|
||||||
selectionEnd.y = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSlotMouseUp($event) {
|
function onSlotMouseUp($event) {
|
||||||
if (props.isDisabled || selectionStart.x == undefined) {
|
if (!isEditing.value || selectionStart.x == undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +147,14 @@ function onSlotMouseUp($event) {
|
||||||
selectionStart.x = undefined;
|
selectionStart.x = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSlotClick(dayIndex, hour) {
|
||||||
|
if (isEditing.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedTime.value = getTimeAtCell(dayIndex, hour);
|
||||||
|
}
|
||||||
|
|
||||||
function onKeyUp($event) {
|
function onKeyUp($event) {
|
||||||
switch ($event.key) {
|
switch ($event.key) {
|
||||||
case "Shift":
|
case "Shift":
|
||||||
|
@ -158,6 +189,13 @@ onUnmounted(() => {
|
||||||
window.removeEventListener("keyup", onKeyUp);
|
window.removeEventListener("keyup", onKeyUp);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getAvailabilityCell(day: number, hour: number) {
|
||||||
|
let index = day * 24 + hour;
|
||||||
|
if (props.overlay && props.overlay[index] != undefined) {
|
||||||
|
return props.overlay[index]
|
||||||
|
}
|
||||||
|
return model.value[index];
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -188,11 +226,13 @@ onUnmounted(() => {
|
||||||
}"
|
}"
|
||||||
:selection="
|
:selection="
|
||||||
selectionInside(dayIndex, hour) ? selectionValue
|
selectionInside(dayIndex, hour) ? selectionValue
|
||||||
: model[24 * dayIndex + hour]
|
: getAvailabilityCell(dayIndex, hour)
|
||||||
"
|
"
|
||||||
v-for="hour in hours"
|
v-for="hour in hours"
|
||||||
@mousedown="onSlotMouseDown($event, dayIndex, hour)"
|
@mousedown="onSlotMouseDown($event, dayIndex, hour)"
|
||||||
@mouseover="onSlotMouseOver($event, dayIndex, hour)"
|
@mouseover="onSlotMouseOver($event, dayIndex, hour)"
|
||||||
|
@mouseleave="onSlotMouseLeave($event, dayIndex, hour)"
|
||||||
|
@click="onSlotClick(dayIndex, hour)"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { PlayerTeamRole } from "../player";
|
import type { PlayerTeamRoleFlat } from "../player";
|
||||||
import { computed, type PropType } from "vue";
|
import { computed, type PropType } from "vue";
|
||||||
import { useRosterStore } from "../stores/roster";
|
import { useRosterStore } from "../stores/roster";
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ const rosterStore = useRosterStore();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
roleTitle: String,
|
roleTitle: String,
|
||||||
player: Object as PropType<PlayerTeamRole>,
|
player: Object as PropType<PlayerTeamRoleFlat>,
|
||||||
isRoster: Boolean,
|
isRoster: Boolean,
|
||||||
isRinger: Boolean,
|
isRinger: Boolean,
|
||||||
});
|
});
|
||||||
|
@ -64,7 +64,7 @@ const playtime = computed(() => {
|
||||||
'player-card': true,
|
'player-card': true,
|
||||||
'no-player': !player && !isRinger,
|
'no-player': !player && !isRinger,
|
||||||
'selected': isSelected,
|
'selected': isSelected,
|
||||||
'can-be-available': player?.availability == 2
|
'can-be-available': player?.availability == 1
|
||||||
}" @click="onClick">
|
}" @click="onClick">
|
||||||
<div class="role-icon">
|
<div class="role-icon">
|
||||||
<i :class="rosterStore.roleIcons[roleTitle]" />
|
<i :class="rosterStore.roleIcons[roleTitle]" />
|
||||||
|
@ -74,8 +74,8 @@ const playtime = computed(() => {
|
||||||
<h4 class="player-name">{{ player.name }}</h4>
|
<h4 class="player-name">{{ player.name }}</h4>
|
||||||
<div class="subtitle">
|
<div class="subtitle">
|
||||||
<span>
|
<span>
|
||||||
{{ player.role }}
|
{{ rosterStore.roleNames[player.role] }}
|
||||||
<span v-if="!player.main && isRoster">
|
<span v-if="!player.isMain && isRoster">
|
||||||
(alternate)
|
(alternate)
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -89,14 +89,14 @@ const playtime = computed(() => {
|
||||||
<span>
|
<span>
|
||||||
<h4 class="player-name">Ringer</h4>
|
<h4 class="player-name">Ringer</h4>
|
||||||
<div class="subtitle">
|
<div class="subtitle">
|
||||||
<span>{{ roleTitle }}</span>
|
<span>{{ rosterStore.roleNames[roleTitle] }}</span>
|
||||||
<span>nobody likes to play {{ roleTitle }}</span>
|
<!--span>nobody likes to play {{ roleTitle }}</span-->
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="role-info">
|
<div v-else class="role-info">
|
||||||
<span>
|
<span>
|
||||||
{{ roleTitle }}
|
{{ rosterStore.roleNames[roleTitle] }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useScheduleStore } from "../stores/schedule";
|
||||||
|
import SchedulePlayerListItem from "./SchedulePlayerListItem.vue";
|
||||||
|
|
||||||
|
const scheduleStore = useScheduleStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="schedule-player-list">
|
||||||
|
<h3>{{ scheduleStore.team?.teamName }}</h3>
|
||||||
|
<SchedulePlayerListItem
|
||||||
|
v-for="record in scheduleStore.playerAvailability"
|
||||||
|
:player="record"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
h3 {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player:hover {
|
||||||
|
background-color: var(--mantle);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,71 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useScheduleStore } from "../stores/schedule";
|
||||||
|
import { computed, type PropType } from "vue";
|
||||||
|
import { type AvailabilitySchema } from "@/client";
|
||||||
|
|
||||||
|
const scheduleStore = useScheduleStore();
|
||||||
|
|
||||||
|
const hoveredIndex = computed(() => scheduleStore.hoveredIndex);
|
||||||
|
|
||||||
|
const availabilityAtHoveredIndex = computed(() => {
|
||||||
|
if (hoveredIndex.value) {
|
||||||
|
return props.player?.availability[hoveredIndex.value] ?? 0;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
player: Object as PropType<AvailabilitySchema>,
|
||||||
|
});
|
||||||
|
|
||||||
|
function onMouseOver() {
|
||||||
|
scheduleStore.overlay = props.player;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseLeave() {
|
||||||
|
if (scheduleStore.overlay == props.player) {
|
||||||
|
scheduleStore.overlay = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="player"
|
||||||
|
@mouseover="onMouseOver(player)"
|
||||||
|
@mouseleave="onMouseLeave"
|
||||||
|
>
|
||||||
|
<span v-if="availabilityAtHoveredIndex > 0">
|
||||||
|
<span v-if="availabilityAtHoveredIndex == 1" class="can-be-available">
|
||||||
|
{{ player.username }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="available">
|
||||||
|
{{ player.username }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<s v-else-if="availabilityAtHoveredIndex == 0">
|
||||||
|
{{ player.username }}
|
||||||
|
</s>
|
||||||
|
<span v-else>
|
||||||
|
{{ player.username }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.player:hover {
|
||||||
|
background-color: var(--mantle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.player span.can-be-available {
|
||||||
|
background-color: var(--yellow-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.player span.available {
|
||||||
|
background-color: var(--green-transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.player s {
|
||||||
|
color: var(--overlay-0);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -49,7 +49,11 @@ router
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
console.log("test");
|
console.log("test");
|
||||||
if (!authStore.isLoggedIn && !authStore.hasCheckedAuth) {
|
if (!authStore.isLoggedIn && !authStore.hasCheckedAuth) {
|
||||||
await authStore.getUser();
|
try {
|
||||||
|
await authStore.getUser();
|
||||||
|
} catch (exception) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,38 @@
|
||||||
import { computed } from "@vue/reactivity";
|
import { computed } from "@vue/reactivity";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { reactive, ref, watch } from "vue";
|
import { reactive, ref, type Ref, watch } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { useClientStore } from "./client";
|
import { useClientStore } from "./client";
|
||||||
import type { TeamSchema } from "@/client";
|
import type { AvailabilitySchema, TeamSchema } from "@/client";
|
||||||
import moment, { type Moment } from "moment";
|
import moment, { type Moment } from "moment";
|
||||||
import "moment-timezone";
|
import "moment-timezone";
|
||||||
|
import { useAuthStore } from "./auth";
|
||||||
|
|
||||||
export const useScheduleStore = defineStore("schedule", () => {
|
export const useScheduleStore = defineStore("schedule", () => {
|
||||||
const client = useClientStore().client;
|
const client = useClientStore().client;
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
const dateStart = ref(moment());
|
const dateStart = ref(moment());
|
||||||
|
|
||||||
const windowStart = computed(() => Math.floor(dateStart.value.unix()));
|
const windowStart = computed(() => Math.floor(dateStart.value.unix()));
|
||||||
|
|
||||||
const availability = reactive(new Array(168));
|
const availability = reactive(new Array(168));
|
||||||
|
|
||||||
availability.fill(0);
|
availability.fill(0);
|
||||||
|
|
||||||
const route = useRoute();
|
watch(availability, () => {
|
||||||
const router = useRouter();
|
// TODO: maybe do not sync these values so that we can cancel editing
|
||||||
|
// availability
|
||||||
|
let index = playerAvailability.value
|
||||||
|
.findIndex((v) => v.steamId == authStore.steamId);
|
||||||
|
playerAvailability.value[index].availability = availability;
|
||||||
|
});
|
||||||
|
|
||||||
|
const playerAvailability: Ref<AvailabilitySchema[]> = ref([ ]);
|
||||||
|
|
||||||
|
const overlay: Ref<AvailabilitySchema[] | undefined> = ref();
|
||||||
|
|
||||||
|
const hoveredIndex: Ref<number | undefined> = ref();
|
||||||
|
|
||||||
//const teamId = computed({
|
|
||||||
// get: () => Number(route?.query?.teamId),
|
|
||||||
// set: (value) => router.push({ query: { teamId: value } }),
|
|
||||||
//});
|
|
||||||
const team = ref();
|
const team = ref();
|
||||||
|
|
||||||
function getWindowStart(team: TeamSchema) {
|
function getWindowStart(team: TeamSchema) {
|
||||||
|
@ -46,7 +54,7 @@ export const useScheduleStore = defineStore("schedule", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(dateStart, () => {
|
watch(dateStart, () => {
|
||||||
fetchSchedule();
|
fetchTeamSchedule();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(team, () => {
|
watch(team, () => {
|
||||||
|
@ -66,19 +74,29 @@ export const useScheduleStore = defineStore("schedule", () => {
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
//return fetch(import.meta.env.VITE_API_BASE_URL + "/schedule?" + new URLSearchParams({
|
}
|
||||||
// window_start: windowStart.value.toString(),
|
|
||||||
// team_id: teamId.toString(),
|
async function fetchTeamSchedule(dateStartOverride?: Moment) {
|
||||||
//}).toString(),{
|
dateStartOverride = dateStartOverride ?? dateStart.value;
|
||||||
// credentials: "include",
|
return client.default.getApiScheduleTeam(
|
||||||
// })
|
Math.floor(dateStartOverride.unix()).toString(),
|
||||||
// .then((response) => response.json())
|
team.value.id,
|
||||||
// .then((response) => {
|
)
|
||||||
// response.availability.forEach((value: number, i: number) => {
|
.then((response) => {
|
||||||
// availability[i] = value;
|
const values = Object.values(response.playerAvailability);
|
||||||
// });
|
playerAvailability.value = values;
|
||||||
// return response;
|
|
||||||
// });
|
let record = values.find((value) => value.steamId == authStore.steamId);
|
||||||
|
|
||||||
|
if (record?.availability) {
|
||||||
|
record.availability
|
||||||
|
.forEach((value, i) => {
|
||||||
|
availability[i] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveSchedule() {
|
async function saveSchedule() {
|
||||||
|
@ -87,25 +105,17 @@ export const useScheduleStore = defineStore("schedule", () => {
|
||||||
teamId: team.value.id,
|
teamId: team.value.id,
|
||||||
availability,
|
availability,
|
||||||
});
|
});
|
||||||
//return fetch(import.meta.env.VITE_API_BASE_URL + "/schedule", {
|
|
||||||
// method: "PUT",
|
|
||||||
// credentials: "include",
|
|
||||||
// headers: {
|
|
||||||
// "Content-Type": "application/json",
|
|
||||||
// },
|
|
||||||
// body: JSON.stringify({
|
|
||||||
// window_start: Math.floor(dateStart.value.getTime() / 1000),
|
|
||||||
// team_id: teamId.toString(),
|
|
||||||
// availability: availability,
|
|
||||||
// })
|
|
||||||
//});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dateStart,
|
dateStart,
|
||||||
windowStart,
|
windowStart,
|
||||||
availability,
|
availability,
|
||||||
|
playerAvailability,
|
||||||
|
overlay,
|
||||||
|
hoveredIndex,
|
||||||
fetchSchedule,
|
fetchSchedule,
|
||||||
|
fetchTeamSchedule,
|
||||||
saveSchedule,
|
saveSchedule,
|
||||||
team,
|
team,
|
||||||
getWindowStart,
|
getWindowStart,
|
||||||
|
|
|
@ -31,7 +31,7 @@ onMounted(() => {
|
||||||
Roster for Snus Brotherhood
|
Roster for Snus Brotherhood
|
||||||
<em class="aside date">
|
<em class="aside date">
|
||||||
@
|
@
|
||||||
{{ moment(startTime).format("L LT") }}
|
{{ moment.unix(route.params.startTime).format("L LT") }}
|
||||||
</em>
|
</em>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import AvailabilityGrid from "../components/AvailabilityGrid.vue";
|
import AvailabilityGrid from "../components/AvailabilityGrid.vue";
|
||||||
import AvailabilityComboBox from "../components/AvailabilityComboBox.vue";
|
import AvailabilityComboBox from "../components/AvailabilityComboBox.vue";
|
||||||
import WeekSelectionBox from "../components/WeekSelectionBox.vue";
|
import WeekSelectionBox from "../components/WeekSelectionBox.vue";
|
||||||
|
import SchedulePlayerList from "../components/SchedulePlayerList.vue";
|
||||||
import { computed, onMounted, reactive, ref, watch } from "vue";
|
import { computed, onMounted, reactive, ref, watch } from "vue";
|
||||||
import { useTeamsStore } from "../stores/teams";
|
import { useTeamsStore } from "../stores/teams";
|
||||||
import { useScheduleStore } from "../stores/schedule";
|
import { useScheduleStore } from "../stores/schedule";
|
||||||
|
@ -24,6 +25,10 @@ const availability = schedule.availability;
|
||||||
|
|
||||||
const selectionMode = ref(1);
|
const selectionMode = ref(1);
|
||||||
|
|
||||||
|
const selectedTime = ref(undefined);
|
||||||
|
|
||||||
|
const availabilityOverlay = computed(() => schedule.overlay?.availability);
|
||||||
|
|
||||||
const isEditing = ref(false);
|
const isEditing = ref(false);
|
||||||
|
|
||||||
const selectedTeam = ref();
|
const selectedTeam = ref();
|
||||||
|
@ -49,6 +54,16 @@ function copyPreviousWeek() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scheduleRoster() {
|
||||||
|
router.push({
|
||||||
|
name: "roster-builder",
|
||||||
|
params: {
|
||||||
|
teamId: selectedTeam.value.id,
|
||||||
|
startTime: selectedTime.value.unix(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
teamsStore.fetchTeams()
|
teamsStore.fetchTeams()
|
||||||
.then((teamsList) => {
|
.then((teamsList) => {
|
||||||
|
@ -59,7 +74,7 @@ onMounted(() => {
|
||||||
if (queryTeam) {
|
if (queryTeam) {
|
||||||
selectedTeam.value = queryTeam;
|
selectedTeam.value = queryTeam;
|
||||||
schedule.team = queryTeam;
|
schedule.team = queryTeam;
|
||||||
schedule.fetchSchedule();
|
schedule.fetchTeamSchedule();
|
||||||
} else {
|
} else {
|
||||||
selectedTeam.value = options.value[0];
|
selectedTeam.value = options.value[0];
|
||||||
}
|
}
|
||||||
|
@ -87,6 +102,9 @@ onMounted(() => {
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-container">
|
<div class="grid-container">
|
||||||
<AvailabilityGrid v-model="availability"
|
<AvailabilityGrid v-model="availability"
|
||||||
|
v-model:selectedTime="selectedTime"
|
||||||
|
v-model:hoveredIndex="schedule.hoveredIndex"
|
||||||
|
:overlay="availabilityOverlay"
|
||||||
:selection-mode="selectionMode"
|
:selection-mode="selectionMode"
|
||||||
:is-disabled="!isEditing"
|
:is-disabled="!isEditing"
|
||||||
:date-start="schedule.dateStart"
|
:date-start="schedule.dateStart"
|
||||||
|
@ -123,6 +141,9 @@ onMounted(() => {
|
||||||
<button @click="copyPreviousWeek">
|
<button @click="copyPreviousWeek">
|
||||||
Copy previous week
|
Copy previous week
|
||||||
</button>
|
</button>
|
||||||
|
<button @click="scheduleRoster" v-if="selectedTime">
|
||||||
|
Schedule for {{ selectedTime.format("L LT") }}
|
||||||
|
</button>
|
||||||
<button class="accent" @click="isEditing = true">
|
<button class="accent" @click="isEditing = true">
|
||||||
<i class="bi bi-pencil-fill"></i>
|
<i class="bi bi-pencil-fill"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -133,15 +154,24 @@ onMounted(() => {
|
||||||
<div v-else>
|
<div v-else>
|
||||||
You currently are not in any team to schedule for.
|
You currently are not in any team to schedule for.
|
||||||
</div>
|
</div>
|
||||||
|
<div class="player-list">
|
||||||
|
<SchedulePlayerList />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
main {
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
.schedule-view-container {
|
.schedule-view-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-menu {
|
.top-menu {
|
||||||
|
|
|
@ -30,6 +30,28 @@ class PlayerTeamAvailability(app_db.BaseModel):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class AvailabilitySchema(spec.BaseModel):
|
||||||
|
steam_id: str
|
||||||
|
username: str
|
||||||
|
availability: list[int] = [0] * 168
|
||||||
|
|
||||||
|
def add_availability_region(
|
||||||
|
self,
|
||||||
|
region: PlayerTeamAvailability,
|
||||||
|
window_start: datetime,
|
||||||
|
):
|
||||||
|
relative_start_time = region.start_time - window_start
|
||||||
|
relative_start_hour = int(relative_start_time.total_seconds() // 3600)
|
||||||
|
relative_end_time = region.end_time - window_start
|
||||||
|
relative_end_hour = int(relative_end_time.total_seconds() // 3600)
|
||||||
|
window_size_hours = 168 # TODO: change me if window_size is variable
|
||||||
|
|
||||||
|
i = max(0, relative_start_hour)
|
||||||
|
while i < window_size_hours and i < relative_end_hour:
|
||||||
|
print(i, "=", region.availability)
|
||||||
|
self.availability[i] = region.availability
|
||||||
|
i += 1
|
||||||
|
|
||||||
class PlayerTeamAvailabilityRoleSchema(spec.BaseModel):
|
class PlayerTeamAvailabilityRoleSchema(spec.BaseModel):
|
||||||
from models.player import PlayerSchema
|
from models.player import PlayerSchema
|
||||||
from models.player_team_role import RoleSchema
|
from models.player_team_role import RoleSchema
|
||||||
|
|
|
@ -3,10 +3,11 @@ from typing import cast
|
||||||
from flask import Blueprint, abort, jsonify, make_response, request
|
from flask import Blueprint, abort, jsonify, make_response, request
|
||||||
from spectree import Response
|
from spectree import Response
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
|
from sqlalchemy.sql import and_
|
||||||
from app_db import db
|
from app_db import db
|
||||||
from models.player import Player, PlayerSchema
|
from models.player import Player, PlayerSchema
|
||||||
from models.player_team import PlayerTeam
|
from models.player_team import PlayerTeam
|
||||||
from models.player_team_availability import PlayerTeamAvailability, PlayerTeamAvailabilityRoleSchema
|
from models.player_team_availability import AvailabilitySchema, PlayerTeamAvailability, PlayerTeamAvailabilityRoleSchema
|
||||||
from models.player_team_role import PlayerTeamRole, RoleSchema
|
from models.player_team_role import PlayerTeamRole, RoleSchema
|
||||||
from middleware import requires_authentication
|
from middleware import requires_authentication
|
||||||
from spec import spec, BaseModel
|
from spec import spec, BaseModel
|
||||||
|
@ -70,6 +71,7 @@ def get(query: ViewScheduleForm, player: Player, **kwargs):
|
||||||
print(i, "=", region.availability)
|
print(i, "=", region.availability)
|
||||||
availability[i] = region.availability
|
availability[i] = region.availability
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"availability": availability
|
"availability": availability
|
||||||
}
|
}
|
||||||
|
@ -185,6 +187,56 @@ def put(json: PutScheduleForm, player: Player, **kwargs):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return make_response({ }, 200)
|
return make_response({ }, 200)
|
||||||
|
|
||||||
|
class ViewTeamScheduleResponse(BaseModel):
|
||||||
|
player_availability: dict[str, AvailabilitySchema]
|
||||||
|
|
||||||
|
@api_schedule.get("/team")
|
||||||
|
@spec.validate(
|
||||||
|
resp=Response(
|
||||||
|
HTTP_200=ViewTeamScheduleResponse
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@requires_authentication
|
||||||
|
def get_team_availability(query: ViewScheduleForm, player: Player, **kwargs):
|
||||||
|
window_start = query.window_start
|
||||||
|
window_end = window_start + datetime.timedelta(days=query.window_size_days)
|
||||||
|
|
||||||
|
players_teams = db.session.query(
|
||||||
|
PlayerTeam
|
||||||
|
).outerjoin(
|
||||||
|
PlayerTeamAvailability,
|
||||||
|
and_(
|
||||||
|
PlayerTeamAvailability.start_time.between(window_start, window_end) |
|
||||||
|
PlayerTeamAvailability.end_time.between(window_start, window_end) |
|
||||||
|
|
||||||
|
# handle edge case where someone for some reason might list their
|
||||||
|
# availability spanning more than a week total
|
||||||
|
((PlayerTeamAvailability.start_time < window_start) &
|
||||||
|
(PlayerTeamAvailability.end_time > window_end))
|
||||||
|
)
|
||||||
|
).join(
|
||||||
|
Player
|
||||||
|
).where(
|
||||||
|
PlayerTeam.team_id == query.team_id
|
||||||
|
).all()
|
||||||
|
|
||||||
|
ret: dict[str, AvailabilitySchema] = { }
|
||||||
|
|
||||||
|
for player_team in players_teams:
|
||||||
|
player_id = str(player_team.player_id)
|
||||||
|
|
||||||
|
ret[player_id] = AvailabilitySchema(
|
||||||
|
steam_id=player_id,
|
||||||
|
username=player_team.player.username,
|
||||||
|
)
|
||||||
|
|
||||||
|
for region in player_team.availability:
|
||||||
|
ret[player_id].add_availability_region(region, window_start)
|
||||||
|
|
||||||
|
return ViewTeamScheduleResponse(
|
||||||
|
player_availability=ret,
|
||||||
|
).dict(by_alias=True)
|
||||||
|
|
||||||
class ViewAvailablePlayersQuery(BaseModel):
|
class ViewAvailablePlayersQuery(BaseModel):
|
||||||
start_time: datetime.datetime
|
start_time: datetime.datetime
|
||||||
team_id: int
|
team_id: int
|
||||||
|
|
Loading…
Reference in New Issue