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