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) {
 | 
			
		||||
      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