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 { AddPlayerJson } from './models/AddPlayerJson';
|
||||
export type { AvailabilitySchema } from './models/AvailabilitySchema';
|
||||
export type { CreateTeamJson } from './models/CreateTeamJson';
|
||||
export type { EditMemberRolesJson } from './models/EditMemberRolesJson';
|
||||
export type { PlayerSchema } from './models/PlayerSchema';
|
||||
|
@ -30,6 +31,7 @@ export type { ViewScheduleResponse } from './models/ViewScheduleResponse';
|
|||
export type { ViewTeamMembersResponse } from './models/ViewTeamMembersResponse';
|
||||
export type { ViewTeamMembersResponseList } from './models/ViewTeamMembersResponseList';
|
||||
export type { ViewTeamResponse } from './models/ViewTeamResponse';
|
||||
export type { ViewTeamScheduleResponse } from './models/ViewTeamScheduleResponse';
|
||||
export type { ViewTeamsResponse } from './models/ViewTeamsResponse';
|
||||
|
||||
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 { ViewTeamMembersResponseList } from '../models/ViewTeamMembersResponseList';
|
||||
import type { ViewTeamResponse } from '../models/ViewTeamResponse';
|
||||
import type { ViewTeamScheduleResponse } from '../models/ViewTeamScheduleResponse';
|
||||
import type { ViewTeamsResponse } from '../models/ViewTeamsResponse';
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import type { BaseHttpRequest } from '../core/BaseHttpRequest';
|
||||
|
@ -130,6 +131,32 @@ export class DefaultService {
|
|||
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>
|
||||
* @param startTime
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
<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 selectedTime = defineModel("selectedTime");
|
||||
|
||||
const hoveredIndex = defineModel("hoveredIndex");
|
||||
|
||||
const props = defineProps({
|
||||
selectionMode: Number,
|
||||
isDisabled: Boolean,
|
||||
dateStart: Date,
|
||||
overlay: Array,
|
||||
dateStart: Object as PropType<Moment>,
|
||||
firstHour: {
|
||||
type: Number,
|
||||
default: 14
|
||||
|
@ -17,6 +23,8 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const isEditing = computed(() => !props.isDisabled);
|
||||
|
||||
const selectionStart = reactive({ x: undefined, y: undefined });
|
||||
const selectionEnd = reactive({ x: undefined, y: undefined });
|
||||
const isCtrlDown = ref(false);
|
||||
|
@ -74,11 +82,40 @@ const daysOfWeek = [
|
|||
"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 selectionValue = ref(0);
|
||||
|
||||
function onSlotMouseDown($event, x, y) {
|
||||
if (props.isDisabled) {
|
||||
if (!isEditing.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -96,22 +133,8 @@ function onSlotMouseDown($event, 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) {
|
||||
if (props.isDisabled || selectionStart.x == undefined) {
|
||||
if (!isEditing.value || selectionStart.x == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -124,6 +147,14 @@ function onSlotMouseUp($event) {
|
|||
selectionStart.x = undefined;
|
||||
}
|
||||
|
||||
function onSlotClick(dayIndex, hour) {
|
||||
if (isEditing.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedTime.value = getTimeAtCell(dayIndex, hour);
|
||||
}
|
||||
|
||||
function onKeyUp($event) {
|
||||
switch ($event.key) {
|
||||
case "Shift":
|
||||
|
@ -158,6 +189,13 @@ onUnmounted(() => {
|
|||
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>
|
||||
|
||||
<template>
|
||||
|
@ -188,11 +226,13 @@ onUnmounted(() => {
|
|||
}"
|
||||
:selection="
|
||||
selectionInside(dayIndex, hour) ? selectionValue
|
||||
: model[24 * dayIndex + hour]
|
||||
: getAvailabilityCell(dayIndex, hour)
|
||||
"
|
||||
v-for="hour in hours"
|
||||
@mousedown="onSlotMouseDown($event, dayIndex, hour)"
|
||||
@mouseover="onSlotMouseOver($event, dayIndex, hour)"
|
||||
@mouseleave="onSlotMouseLeave($event, dayIndex, hour)"
|
||||
@click="onSlotClick(dayIndex, hour)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { PlayerTeamRole } from "../player";
|
||||
import type { PlayerTeamRoleFlat } from "../player";
|
||||
import { computed, type PropType } from "vue";
|
||||
import { useRosterStore } from "../stores/roster";
|
||||
|
||||
|
@ -7,7 +7,7 @@ const rosterStore = useRosterStore();
|
|||
|
||||
const props = defineProps({
|
||||
roleTitle: String,
|
||||
player: Object as PropType<PlayerTeamRole>,
|
||||
player: Object as PropType<PlayerTeamRoleFlat>,
|
||||
isRoster: Boolean,
|
||||
isRinger: Boolean,
|
||||
});
|
||||
|
@ -64,7 +64,7 @@ const playtime = computed(() => {
|
|||
'player-card': true,
|
||||
'no-player': !player && !isRinger,
|
||||
'selected': isSelected,
|
||||
'can-be-available': player?.availability == 2
|
||||
'can-be-available': player?.availability == 1
|
||||
}" @click="onClick">
|
||||
<div class="role-icon">
|
||||
<i :class="rosterStore.roleIcons[roleTitle]" />
|
||||
|
@ -74,8 +74,8 @@ const playtime = computed(() => {
|
|||
<h4 class="player-name">{{ player.name }}</h4>
|
||||
<div class="subtitle">
|
||||
<span>
|
||||
{{ player.role }}
|
||||
<span v-if="!player.main && isRoster">
|
||||
{{ rosterStore.roleNames[player.role] }}
|
||||
<span v-if="!player.isMain && isRoster">
|
||||
(alternate)
|
||||
</span>
|
||||
</span>
|
||||
|
@ -89,14 +89,14 @@ const playtime = computed(() => {
|
|||
<span>
|
||||
<h4 class="player-name">Ringer</h4>
|
||||
<div class="subtitle">
|
||||
<span>{{ roleTitle }}</span>
|
||||
<span>nobody likes to play {{ roleTitle }}</span>
|
||||
<span>{{ rosterStore.roleNames[roleTitle] }}</span>
|
||||
<!--span>nobody likes to play {{ roleTitle }}</span-->
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="role-info">
|
||||
<span>
|
||||
{{ roleTitle }}
|
||||
{{ rosterStore.roleNames[roleTitle] }}
|
||||
</span>
|
||||
</div>
|
||||
</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();
|
||||
console.log("test");
|
||||
if (!authStore.isLoggedIn && !authStore.hasCheckedAuth) {
|
||||
await authStore.getUser();
|
||||
try {
|
||||
await authStore.getUser();
|
||||
} catch (exception) {
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,30 +1,38 @@
|
|||
import { computed } from "@vue/reactivity";
|
||||
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 { useClientStore } from "./client";
|
||||
import type { TeamSchema } from "@/client";
|
||||
import type { AvailabilitySchema, TeamSchema } from "@/client";
|
||||
import moment, { type Moment } from "moment";
|
||||
import "moment-timezone";
|
||||
import { useAuthStore } from "./auth";
|
||||
|
||||
export const useScheduleStore = defineStore("schedule", () => {
|
||||
const client = useClientStore().client;
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const dateStart = ref(moment());
|
||||
|
||||
const windowStart = computed(() => Math.floor(dateStart.value.unix()));
|
||||
|
||||
const availability = reactive(new Array(168));
|
||||
|
||||
availability.fill(0);
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
watch(availability, () => {
|
||||
// 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();
|
||||
|
||||
function getWindowStart(team: TeamSchema) {
|
||||
|
@ -46,7 +54,7 @@ export const useScheduleStore = defineStore("schedule", () => {
|
|||
}
|
||||
|
||||
watch(dateStart, () => {
|
||||
fetchSchedule();
|
||||
fetchTeamSchedule();
|
||||
});
|
||||
|
||||
watch(team, () => {
|
||||
|
@ -66,19 +74,29 @@ export const useScheduleStore = defineStore("schedule", () => {
|
|||
});
|
||||
return response;
|
||||
});
|
||||
//return fetch(import.meta.env.VITE_API_BASE_URL + "/schedule?" + new URLSearchParams({
|
||||
// window_start: windowStart.value.toString(),
|
||||
// team_id: teamId.toString(),
|
||||
//}).toString(),{
|
||||
// credentials: "include",
|
||||
// })
|
||||
// .then((response) => response.json())
|
||||
// .then((response) => {
|
||||
// response.availability.forEach((value: number, i: number) => {
|
||||
// availability[i] = value;
|
||||
// });
|
||||
// return response;
|
||||
// });
|
||||
}
|
||||
|
||||
async function fetchTeamSchedule(dateStartOverride?: Moment) {
|
||||
dateStartOverride = dateStartOverride ?? dateStart.value;
|
||||
return client.default.getApiScheduleTeam(
|
||||
Math.floor(dateStartOverride.unix()).toString(),
|
||||
team.value.id,
|
||||
)
|
||||
.then((response) => {
|
||||
const values = Object.values(response.playerAvailability);
|
||||
playerAvailability.value = values;
|
||||
|
||||
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() {
|
||||
|
@ -87,25 +105,17 @@ export const useScheduleStore = defineStore("schedule", () => {
|
|||
teamId: team.value.id,
|
||||
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 {
|
||||
dateStart,
|
||||
windowStart,
|
||||
availability,
|
||||
playerAvailability,
|
||||
overlay,
|
||||
hoveredIndex,
|
||||
fetchSchedule,
|
||||
fetchTeamSchedule,
|
||||
saveSchedule,
|
||||
team,
|
||||
getWindowStart,
|
||||
|
|
|
@ -31,7 +31,7 @@ onMounted(() => {
|
|||
Roster for Snus Brotherhood
|
||||
<em class="aside date">
|
||||
@
|
||||
{{ moment(startTime).format("L LT") }}
|
||||
{{ moment.unix(route.params.startTime).format("L LT") }}
|
||||
</em>
|
||||
</h1>
|
||||
<div class="button-group">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import AvailabilityGrid from "../components/AvailabilityGrid.vue";
|
||||
import AvailabilityComboBox from "../components/AvailabilityComboBox.vue";
|
||||
import WeekSelectionBox from "../components/WeekSelectionBox.vue";
|
||||
import SchedulePlayerList from "../components/SchedulePlayerList.vue";
|
||||
import { computed, onMounted, reactive, ref, watch } from "vue";
|
||||
import { useTeamsStore } from "../stores/teams";
|
||||
import { useScheduleStore } from "../stores/schedule";
|
||||
|
@ -24,6 +25,10 @@ const availability = schedule.availability;
|
|||
|
||||
const selectionMode = ref(1);
|
||||
|
||||
const selectedTime = ref(undefined);
|
||||
|
||||
const availabilityOverlay = computed(() => schedule.overlay?.availability);
|
||||
|
||||
const isEditing = ref(false);
|
||||
|
||||
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(() => {
|
||||
teamsStore.fetchTeams()
|
||||
.then((teamsList) => {
|
||||
|
@ -59,7 +74,7 @@ onMounted(() => {
|
|||
if (queryTeam) {
|
||||
selectedTeam.value = queryTeam;
|
||||
schedule.team = queryTeam;
|
||||
schedule.fetchSchedule();
|
||||
schedule.fetchTeamSchedule();
|
||||
} else {
|
||||
selectedTeam.value = options.value[0];
|
||||
}
|
||||
|
@ -87,6 +102,9 @@ onMounted(() => {
|
|||
</div>
|
||||
<div class="grid-container">
|
||||
<AvailabilityGrid v-model="availability"
|
||||
v-model:selectedTime="selectedTime"
|
||||
v-model:hoveredIndex="schedule.hoveredIndex"
|
||||
:overlay="availabilityOverlay"
|
||||
:selection-mode="selectionMode"
|
||||
:is-disabled="!isEditing"
|
||||
:date-start="schedule.dateStart"
|
||||
|
@ -123,6 +141,9 @@ onMounted(() => {
|
|||
<button @click="copyPreviousWeek">
|
||||
Copy previous week
|
||||
</button>
|
||||
<button @click="scheduleRoster" v-if="selectedTime">
|
||||
Schedule for {{ selectedTime.format("L LT") }}
|
||||
</button>
|
||||
<button class="accent" @click="isEditing = true">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</button>
|
||||
|
@ -133,15 +154,24 @@ onMounted(() => {
|
|||
<div v-else>
|
||||
You currently are not in any team to schedule for.
|
||||
</div>
|
||||
<div class="player-list">
|
||||
<SchedulePlayerList />
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
main {
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.schedule-view-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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):
|
||||
from models.player import PlayerSchema
|
||||
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 spectree import Response
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.sql import and_
|
||||
from app_db import db
|
||||
from models.player import Player, PlayerSchema
|
||||
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 middleware import requires_authentication
|
||||
from spec import spec, BaseModel
|
||||
|
@ -70,6 +71,7 @@ def get(query: ViewScheduleForm, player: Player, **kwargs):
|
|||
print(i, "=", region.availability)
|
||||
availability[i] = region.availability
|
||||
i += 1
|
||||
|
||||
return {
|
||||
"availability": availability
|
||||
}
|
||||
|
@ -185,6 +187,56 @@ def put(json: PutScheduleForm, player: Player, **kwargs):
|
|||
db.session.commit()
|
||||
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):
|
||||
start_time: datetime.datetime
|
||||
team_id: int
|
||||
|
|
Loading…
Reference in New Issue