Added editing and viewing roles
							parent
							
								
									708aafed9e
								
							
						
					
					
						commit
						5d620c07ed
					
				| 
						 | 
				
			
			@ -10,6 +10,8 @@
 | 
			
		|||
      "dependencies": {
 | 
			
		||||
        "axios": "^1.7.7",
 | 
			
		||||
        "bootstrap-icons": "^1.11.3",
 | 
			
		||||
        "moment": "^2.30.1",
 | 
			
		||||
        "moment-timezone": "^0.5.46",
 | 
			
		||||
        "pinia": "^2.2.4",
 | 
			
		||||
        "v-tooltip": "^2.1.3",
 | 
			
		||||
        "vue": "^3.5.12",
 | 
			
		||||
| 
						 | 
				
			
			@ -5138,6 +5140,27 @@
 | 
			
		|||
        "node": ">=16 || 14 >=14.17"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/moment": {
 | 
			
		||||
      "version": "2.30.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
 | 
			
		||||
      "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/moment-timezone": {
 | 
			
		||||
      "version": "0.5.46",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz",
 | 
			
		||||
      "integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "moment": "^2.29.4"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/ms": {
 | 
			
		||||
      "version": "2.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,11 +12,13 @@
 | 
			
		|||
    "type-check": "vue-tsc --build --force",
 | 
			
		||||
    "lint": "eslint . --fix",
 | 
			
		||||
    "format": "prettier --write src/",
 | 
			
		||||
    "openapi-generate": "openapi --input 'http://localhost:5000/apidoc/openapi.json' --output src/client --name AvailabilitfClient"
 | 
			
		||||
    "openapi-generate": "openapi --input 'http://localhost:8000/apidoc/openapi.json' --output src/client --name AvailabilitfClient"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "axios": "^1.7.7",
 | 
			
		||||
    "bootstrap-icons": "^1.11.3",
 | 
			
		||||
    "moment": "^2.30.1",
 | 
			
		||||
    "moment-timezone": "^0.5.46",
 | 
			
		||||
    "pinia": "^2.2.4",
 | 
			
		||||
    "v-tooltip": "^2.1.3",
 | 
			
		||||
    "vue": "^3.5.12",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@ export type { OpenAPIConfig } from './core/OpenAPI';
 | 
			
		|||
 | 
			
		||||
export type { AddPlayerJson } from './models/AddPlayerJson';
 | 
			
		||||
export type { CreateTeamJson } from './models/CreateTeamJson';
 | 
			
		||||
export type { EditMemberRolesJson } from './models/EditMemberRolesJson';
 | 
			
		||||
export type { PutScheduleForm } from './models/PutScheduleForm';
 | 
			
		||||
export type { RoleSchema } from './models/RoleSchema';
 | 
			
		||||
export { TeamRole } from './models/TeamRole';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { RoleSchema } from './RoleSchema';
 | 
			
		||||
export type EditMemberRolesJson = {
 | 
			
		||||
    roles: Array<RoleSchema>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4,6 +4,9 @@
 | 
			
		|||
/* eslint-disable */
 | 
			
		||||
import type { RoleSchema } from './RoleSchema';
 | 
			
		||||
export type ViewTeamMembersResponse = {
 | 
			
		||||
    availability: number;
 | 
			
		||||
    createdAt: string;
 | 
			
		||||
    playtime: number;
 | 
			
		||||
    roles: Array<RoleSchema>;
 | 
			
		||||
    steamId: string;
 | 
			
		||||
    username: string;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
/* eslint-disable */
 | 
			
		||||
import type { AddPlayerJson } from '../models/AddPlayerJson';
 | 
			
		||||
import type { CreateTeamJson } from '../models/CreateTeamJson';
 | 
			
		||||
import type { EditMemberRolesJson } from '../models/EditMemberRolesJson';
 | 
			
		||||
import type { PutScheduleForm } from '../models/PutScheduleForm';
 | 
			
		||||
import type { ViewScheduleResponse } from '../models/ViewScheduleResponse';
 | 
			
		||||
import type { ViewTeamMembersResponseList } from '../models/ViewTeamMembersResponseList';
 | 
			
		||||
| 
						 | 
				
			
			@ -210,6 +211,35 @@ export class DefaultService {
 | 
			
		|||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * edit_member_roles <PATCH>
 | 
			
		||||
     * @param teamId
 | 
			
		||||
     * @param targetPlayerId
 | 
			
		||||
     * @param requestBody
 | 
			
		||||
     * @returns void
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public editMemberRoles(
 | 
			
		||||
        teamId: string,
 | 
			
		||||
        targetPlayerId: string,
 | 
			
		||||
        requestBody?: EditMemberRolesJson,
 | 
			
		||||
    ): CancelablePromise<void> {
 | 
			
		||||
        return this.httpRequest.request({
 | 
			
		||||
            method: 'PATCH',
 | 
			
		||||
            url: '/api/team/id/{team_id}/edit-player/{target_player_id}',
 | 
			
		||||
            path: {
 | 
			
		||||
                'team_id': teamId,
 | 
			
		||||
                'target_player_id': targetPlayerId,
 | 
			
		||||
            },
 | 
			
		||||
            body: requestBody,
 | 
			
		||||
            mediaType: 'application/json',
 | 
			
		||||
            errors: {
 | 
			
		||||
                403: `Forbidden`,
 | 
			
		||||
                404: `Not Found`,
 | 
			
		||||
                422: `Unprocessable Entity`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * add_player <PUT>
 | 
			
		||||
     * @param teamId
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,74 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import type { PlayerTeamRole } from "../player";
 | 
			
		||||
import { computed, type PropType } from "vue";
 | 
			
		||||
import { computed, type PropType, ref, watch } from "vue";
 | 
			
		||||
import { useTeamsStore } from "../stores/teams";
 | 
			
		||||
import { useRosterStore } from "../stores/roster";
 | 
			
		||||
import { type ViewTeamMembersResponse } from "@/client";
 | 
			
		||||
import { type ViewTeamMembersResponse, type TeamSchema } from "@/client";
 | 
			
		||||
import RoleTag from "../components/RoleTag.vue";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  player: Object as PropType<ViewTeamMembersResponse>,
 | 
			
		||||
  team: Object as PropType<TeamSchema>,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const teamsStore = useTeamsStore();
 | 
			
		||||
 | 
			
		||||
const rosterStore = useRosterStore();
 | 
			
		||||
 | 
			
		||||
//const roles = computed({
 | 
			
		||||
//  get: () => ({
 | 
			
		||||
//    "PocketScout": "",
 | 
			
		||||
//    "FlankScout": "",
 | 
			
		||||
//    "PocketSoldier": "",
 | 
			
		||||
//    "Roamer": "",
 | 
			
		||||
//    "Demoman": "",
 | 
			
		||||
//    "Medic": "",
 | 
			
		||||
//  }),
 | 
			
		||||
//});
 | 
			
		||||
 | 
			
		||||
const isEditing = ref(false);
 | 
			
		||||
 | 
			
		||||
// this is the roles of the player we are editing
 | 
			
		||||
const roles = ref([]);
 | 
			
		||||
const updatedRoles = ref([]);
 | 
			
		||||
 | 
			
		||||
//const rolesMap = reactive({
 | 
			
		||||
//  "Role.PocketScout": undefined,
 | 
			
		||||
//  "Role.FlankScout": undefined,
 | 
			
		||||
//  "Role.PocketSoldier": undefined,
 | 
			
		||||
//  "Role.Roamer": undefined,
 | 
			
		||||
//  "Role.Demoman": undefined,
 | 
			
		||||
//  "Role.Medic": undefined,
 | 
			
		||||
//});
 | 
			
		||||
 | 
			
		||||
const possibleRoles = [
 | 
			
		||||
  "PocketScout",
 | 
			
		||||
  "FlankScout",
 | 
			
		||||
  "PocketSoldier",
 | 
			
		||||
  "Roamer",
 | 
			
		||||
  "Demoman",
 | 
			
		||||
  "Medic",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
watch(isEditing, (newValue) => {
 | 
			
		||||
  if (newValue) {
 | 
			
		||||
    // editing
 | 
			
		||||
    roles.value = possibleRoles.map((roleName) => {
 | 
			
		||||
      console.log(roleName);
 | 
			
		||||
      return props.player.roles
 | 
			
		||||
        .find((playerRole) => playerRole.role == roleName) ?? undefined;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function updateRoles() {
 | 
			
		||||
  isEditing.value = false;
 | 
			
		||||
  updatedRoles.value = roles.value.filter(x => x);
 | 
			
		||||
  props.player.roles = updatedRoles.value;
 | 
			
		||||
  console.log(roles.value);
 | 
			
		||||
  console.log(updatedRoles.value);
 | 
			
		||||
  teamsStore.updateRoles(props.team.id, props.player.steamId, updatedRoles.value);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
| 
						 | 
				
			
			@ -23,24 +83,39 @@ const rosterStore = useRosterStore();
 | 
			
		|||
    </td>
 | 
			
		||||
    <td>
 | 
			
		||||
      <div class="role-icons flex-middle">
 | 
			
		||||
        <i
 | 
			
		||||
          v-for="role in player.roles"
 | 
			
		||||
          :class="{
 | 
			
		||||
            [rosterStore.roleIcons[role.role]]: true,
 | 
			
		||||
            main: role.is_main,
 | 
			
		||||
          }"
 | 
			
		||||
        />
 | 
			
		||||
        <div class="role-buttons" v-if="isEditing">
 | 
			
		||||
          <RoleTag
 | 
			
		||||
            v-for="role, i in possibleRoles"
 | 
			
		||||
            :role="role"
 | 
			
		||||
            :player="player"
 | 
			
		||||
            v-model="roles[i]"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <template v-else>
 | 
			
		||||
          <i
 | 
			
		||||
            v-for="role in player.roles"
 | 
			
		||||
            :class="{
 | 
			
		||||
              [rosterStore.roleIcons[role.role]]: true,
 | 
			
		||||
              main: role.isMain,
 | 
			
		||||
            }"
 | 
			
		||||
          />
 | 
			
		||||
        </template>
 | 
			
		||||
      </div>
 | 
			
		||||
    </td>
 | 
			
		||||
    <td>
 | 
			
		||||
      {{ player.playtime.toFixed(1) }} hours
 | 
			
		||||
    </td>
 | 
			
		||||
    <td>
 | 
			
		||||
      {{ new Date(player.created_at).toLocaleString() }}
 | 
			
		||||
      {{ new Date(player.createdAt).toLocaleString() }}
 | 
			
		||||
    </td>
 | 
			
		||||
    <td>
 | 
			
		||||
      <div class="edit-group">
 | 
			
		||||
        <button>
 | 
			
		||||
        <template v-if="isEditing">
 | 
			
		||||
          <button class="editing" @click="updateRoles()">
 | 
			
		||||
            <i class="bi bi-check-lg" />
 | 
			
		||||
          </button>
 | 
			
		||||
        </template>
 | 
			
		||||
        <button v-else @click="isEditing = true">
 | 
			
		||||
          <i class="bi bi-pencil-fill edit-icon" />
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +169,7 @@ const rosterStore = useRosterStore();
 | 
			
		|||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.role-icons {
 | 
			
		||||
.role-icons i {
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
  line-height: 0;
 | 
			
		||||
  color: var(--overlay-0);
 | 
			
		||||
| 
						 | 
				
			
			@ -104,6 +179,12 @@ const rosterStore = useRosterStore();
 | 
			
		|||
  color: var(--text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.role-buttons {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  gap: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.edit-group {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: end;
 | 
			
		||||
| 
						 | 
				
			
			@ -122,4 +203,8 @@ const rosterStore = useRosterStore();
 | 
			
		|||
.player-card:hover .edit-group > button {
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.edit-group > button.editing {
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,112 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import { type ViewTeamMembersResponse } from "@/client";
 | 
			
		||||
import { useRosterStore } from "../stores/roster";
 | 
			
		||||
 | 
			
		||||
const rosterStore = useRosterStore();
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  role: String,
 | 
			
		||||
  player: Object as PropType<ViewTeamMembersResponse>,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const roleObject = defineModel();
 | 
			
		||||
 | 
			
		||||
function toggle(isMain) {
 | 
			
		||||
  if (isMain == roleObject.value?.isMain) {
 | 
			
		||||
    roleObject.value = undefined;
 | 
			
		||||
  } else {
 | 
			
		||||
    if (!roleObject.value) {
 | 
			
		||||
      // create a new role object
 | 
			
		||||
      roleObject.value = {
 | 
			
		||||
        role: props.role,
 | 
			
		||||
        isMain: isMain
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      roleObject.value.isMain = isMain;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="role">
 | 
			
		||||
    <div
 | 
			
		||||
      :class="{
 | 
			
		||||
        'role-info': true,
 | 
			
		||||
        'unselected': !roleObject,
 | 
			
		||||
      }"
 | 
			
		||||
    >
 | 
			
		||||
      <i
 | 
			
		||||
        :class="{
 | 
			
		||||
          [rosterStore.roleIcons[role]]: true,
 | 
			
		||||
        }"
 | 
			
		||||
      />
 | 
			
		||||
      <span>
 | 
			
		||||
        {{ rosterStore.roleNames[role] }}
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <button
 | 
			
		||||
      :class="{
 | 
			
		||||
        'center': true,
 | 
			
		||||
        'selected': roleObject?.isMain
 | 
			
		||||
      }"
 | 
			
		||||
      @click="toggle(true)"
 | 
			
		||||
    >
 | 
			
		||||
      Main
 | 
			
		||||
    </button>
 | 
			
		||||
    <button
 | 
			
		||||
      :class="{
 | 
			
		||||
        'right': true,
 | 
			
		||||
        'selected': !(roleObject?.isMain ?? true)
 | 
			
		||||
      }"
 | 
			
		||||
      @click="toggle(false)"
 | 
			
		||||
    >
 | 
			
		||||
      Alternate
 | 
			
		||||
    </button>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.role {
 | 
			
		||||
  display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.role-info {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  background-color: var(--mantle);
 | 
			
		||||
  gap: 8px;
 | 
			
		||||
  padding: 8px 16px;
 | 
			
		||||
  border-radius: 8px 0 0 8px;
 | 
			
		||||
  font-size: 10pt;
 | 
			
		||||
  line-height: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.role-info.unselected {
 | 
			
		||||
  color: var(--overlay-0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.role button {
 | 
			
		||||
  font-size: 10pt;
 | 
			
		||||
  background-color: var(--mantle);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.role button.center {
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.role button.right {
 | 
			
		||||
  border-radius: 0 8px 8px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.role button.selected {
 | 
			
		||||
  background-color: var(--accent-transparent);
 | 
			
		||||
  color: var(--accent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.role i {
 | 
			
		||||
  line-height: unset;
 | 
			
		||||
  font-size: 12pt;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +27,7 @@ onMounted(() => {
 | 
			
		|||
        v-for="team in teams.teams"
 | 
			
		||||
      >
 | 
			
		||||
        <RouterLink :to="'/team/id/' + team.id">
 | 
			
		||||
          {{ team.team_name }}
 | 
			
		||||
          {{ team.teamName }}
 | 
			
		||||
        </RouterLink>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,9 +4,9 @@ import { computed, reactive, ref, type Reactive, type Ref } from "vue";
 | 
			
		|||
 | 
			
		||||
export const useRosterStore = defineStore("roster", () => {
 | 
			
		||||
  const neededRoles: Reactive<Array<String>> = reactive([
 | 
			
		||||
    "Pocket Scout",
 | 
			
		||||
    "Flank Scout",
 | 
			
		||||
    "Pocket Soldier",
 | 
			
		||||
    "PocketScout",
 | 
			
		||||
    "FlankScout",
 | 
			
		||||
    "PocketSoldier",
 | 
			
		||||
    "Roamer",
 | 
			
		||||
    "Demoman",
 | 
			
		||||
    "Medic",
 | 
			
		||||
| 
						 | 
				
			
			@ -171,19 +171,21 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
  });
 | 
			
		||||
 | 
			
		||||
  const roleIcons = reactive({
 | 
			
		||||
    "Pocket Scout": "tf2-PocketScout",
 | 
			
		||||
    "Flank Scout": "tf2-FlankScout",
 | 
			
		||||
    "Pocket Soldier": "tf2-PocketSoldier",
 | 
			
		||||
    "PocketScout": "tf2-PocketScout",
 | 
			
		||||
    "FlankScout": "tf2-FlankScout",
 | 
			
		||||
    "PocketSoldier": "tf2-PocketSoldier",
 | 
			
		||||
    "Roamer": "tf2-FlankSoldier",
 | 
			
		||||
    "Demoman": "tf2-Demo",
 | 
			
		||||
    "Medic": "tf2-Medic",
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
    "Role.PocketScout": "tf2-PocketScout",
 | 
			
		||||
    "Role.FlankScout": "tf2-FlankScout",
 | 
			
		||||
    "Role.PocketSoldier": "tf2-PocketSoldier",
 | 
			
		||||
    "Role.Roamer": "tf2-FlankSoldier",
 | 
			
		||||
    "Role.Demoman": "tf2-Demo",
 | 
			
		||||
    "Role.Medic": "tf2-Medic",
 | 
			
		||||
  const roleNames = reactive({
 | 
			
		||||
    "PocketScout": "Pocket Scout",
 | 
			
		||||
    "FlankScout": "Flank Scout",
 | 
			
		||||
    "PocketSoldier": "Pocket Soldier",
 | 
			
		||||
    "Roamer": "Roamer",
 | 
			
		||||
    "Demoman": "Demoman",
 | 
			
		||||
    "Medic": "Medic",
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  function selectPlayerForRole(player: PlayerTeamRole, role: string) {
 | 
			
		||||
| 
						 | 
				
			
			@ -211,6 +213,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
    definitelyAvailable,
 | 
			
		||||
    canBeAvailable,
 | 
			
		||||
    roleIcons,
 | 
			
		||||
    roleNames,
 | 
			
		||||
    mainRoles,
 | 
			
		||||
    alternateRoles,
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,15 +30,15 @@ export const useScheduleStore = defineStore("schedule", () => {
 | 
			
		|||
  function getWindowStart(team: TeamSchema) {
 | 
			
		||||
    // convert local 00:00 to league timezone
 | 
			
		||||
    let localMidnight = moment().startOf("isoWeek");
 | 
			
		||||
    let leagueTime = localMidnight.clone().tz(team.tz_timezone);
 | 
			
		||||
    let leagueTime = localMidnight.clone().tz(team.tzTimezone);
 | 
			
		||||
 | 
			
		||||
    let nextMinuteOffsetTime = leagueTime.clone();
 | 
			
		||||
 | 
			
		||||
    if (nextMinuteOffsetTime.minute() > team.minute_offset) {
 | 
			
		||||
    if (nextMinuteOffsetTime.minute() > team.minuteOffset) {
 | 
			
		||||
      nextMinuteOffsetTime.add(1, "hour");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    nextMinuteOffsetTime.minute(team.minute_offset);
 | 
			
		||||
    nextMinuteOffsetTime.minute(team.minuteOffset);
 | 
			
		||||
 | 
			
		||||
    const deltaMinutes = nextMinuteOffsetTime.diff(leagueTime, "minutes");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
import Cacheable from "@/cacheable";
 | 
			
		||||
import { AvailabilitfClient, type TeamSpec, type ViewTeamMembersResponse, type ViewTeamResponse, type ViewTeamsResponse } from "@/client";
 | 
			
		||||
import { AvailabilitfClient, type RoleSchema, type TeamSpec, type ViewTeamMembersResponse, type ViewTeamResponse, type ViewTeamsResponse } from "@/client";
 | 
			
		||||
import { defineStore } from "pinia";
 | 
			
		||||
import { computed, reactive, ref, type Reactive, type Ref } from "vue";
 | 
			
		||||
import { useClientStore } from "./client";
 | 
			
		||||
| 
						 | 
				
			
			@ -47,11 +47,12 @@ export const useTeamsStore = defineStore("teams", () => {
 | 
			
		|||
        response = response
 | 
			
		||||
          .map((member): ViewTeamMembersResponse => {
 | 
			
		||||
            // TODO: snake_case to camelCase
 | 
			
		||||
            member.roles = member.roles.sort((a, b) => {
 | 
			
		||||
                if (a.is_main == b.is_main) {
 | 
			
		||||
            member.roles = member.roles
 | 
			
		||||
              .sort((a, b) => {
 | 
			
		||||
                if (a.isMain == b.isMain) {
 | 
			
		||||
                  return 0;
 | 
			
		||||
                }
 | 
			
		||||
                return a.is_main ? -1 : 1;
 | 
			
		||||
                return a.isMain ? -1 : 1;
 | 
			
		||||
              });
 | 
			
		||||
            return member;
 | 
			
		||||
          });
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +71,13 @@ export const useTeamsStore = defineStore("teams", () => {
 | 
			
		|||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function updateRoles(teamId: number, playerId: number, roles: RoleSchema[]) {
 | 
			
		||||
    return await client.default
 | 
			
		||||
      .editMemberRoles(teamId.toString(), playerId.toString(), {
 | 
			
		||||
        roles,
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    teams,
 | 
			
		||||
    teamMembers,
 | 
			
		||||
| 
						 | 
				
			
			@ -77,5 +85,6 @@ export const useTeamsStore = defineStore("teams", () => {
 | 
			
		|||
    fetchTeam,
 | 
			
		||||
    fetchTeamMembers,
 | 
			
		||||
    createTeam,
 | 
			
		||||
    updateRoles
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,12 +45,15 @@ onMounted(() => {
 | 
			
		|||
  teamsStore.fetchTeams()
 | 
			
		||||
    .then((teamsList) => {
 | 
			
		||||
      options.value = Object.values(teamsList.teams);
 | 
			
		||||
 | 
			
		||||
      // select team with id in query parameter if exists
 | 
			
		||||
      const queryTeam = teamsList.teams.find(x => x.id == route.query.teamId);
 | 
			
		||||
      if (queryTeam) {
 | 
			
		||||
        selectedTeam.value = queryTeam;
 | 
			
		||||
        schedule.team = queryTeam;
 | 
			
		||||
        schedule.fetchSchedule();
 | 
			
		||||
      } else {
 | 
			
		||||
        selectedTeam.value = options.value[0];
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +67,7 @@ onMounted(() => {
 | 
			
		|||
          Availability for
 | 
			
		||||
          <v-select
 | 
			
		||||
            :options="options"
 | 
			
		||||
            label="team_name"
 | 
			
		||||
            label="teamName"
 | 
			
		||||
            v-model="selectedTeam"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import { useRoute, useRouter } from "vue-router";
 | 
			
		||||
import { useRoute, useRouter, RouterLink } from "vue-router";
 | 
			
		||||
import { useTeamsStore } from "../stores/teams";
 | 
			
		||||
import { computed, onMounted } from "vue";
 | 
			
		||||
import PlayerTeamCard from "../components/PlayerTeamCard.vue";
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +12,11 @@ const team = computed(() => {
 | 
			
		|||
  return teamsStore.teams[route.params.id];
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const availableMembers = computed(() => {
 | 
			
		||||
  return teamsStore.teamMembers[route.params.id]
 | 
			
		||||
    .filter((member) => member.availability > 0);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  teamsStore.fetchTeam(route.params.id)
 | 
			
		||||
    .then(() => teamsStore.fetchTeamMembers(route.params.id));
 | 
			
		||||
| 
						 | 
				
			
			@ -22,10 +27,20 @@ onMounted(() => {
 | 
			
		|||
  <main>
 | 
			
		||||
    <template v-if="team">
 | 
			
		||||
      <h1>
 | 
			
		||||
        {{ team.team_name }}
 | 
			
		||||
        {{ team.teamName }}
 | 
			
		||||
        <RouterLink :to="'/schedule?teamId=' + team.id">
 | 
			
		||||
          <button class="accent">
 | 
			
		||||
            <i class="bi bi-calendar-fill margin"></i>
 | 
			
		||||
            View schedule
 | 
			
		||||
          </button>
 | 
			
		||||
        </RouterLink>
 | 
			
		||||
        <em class="aside" v-if="teamsStore.teamMembers[route.params.id]">
 | 
			
		||||
          {{ teamsStore.teamMembers[route.params.id]?.length }} member(s),
 | 
			
		||||
          {{ availableMembers?.length }} currently available
 | 
			
		||||
        </em>
 | 
			
		||||
      </h1>
 | 
			
		||||
      <table class="member-table">
 | 
			
		||||
        <thead>
 | 
			
		||||
        <!--thead>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <th>
 | 
			
		||||
              Name
 | 
			
		||||
| 
						 | 
				
			
			@ -40,11 +55,13 @@ onMounted(() => {
 | 
			
		|||
              Joined
 | 
			
		||||
            </th>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        </thead-->
 | 
			
		||||
        <tbody>
 | 
			
		||||
          <PlayerTeamCard
 | 
			
		||||
            v-for="member in teamsStore.teamMembers[route.params.id]"
 | 
			
		||||
            :player="member"
 | 
			
		||||
            :team="team"
 | 
			
		||||
            :key="member.username"
 | 
			
		||||
          />
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +72,13 @@ onMounted(() => {
 | 
			
		|||
<style scoped>
 | 
			
		||||
h1 {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  gap: 0.5em;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 > em.aside {
 | 
			
		||||
  font-size: 12pt;
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.member-table {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -274,7 +274,7 @@ def view_team_members(player: Player, team_id: int, **kwargs):
 | 
			
		|||
 | 
			
		||||
    def map_role_to_schema(player_team_role: PlayerTeamRole):
 | 
			
		||||
        return ViewTeamMembersResponse.RoleSchema(
 | 
			
		||||
            role=str(player_team_role.role),
 | 
			
		||||
            role=player_team_role.role.name,
 | 
			
		||||
            is_main=player_team_role.is_main,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -297,3 +297,59 @@ def view_team_members(player: Player, team_id: int, **kwargs):
 | 
			
		|||
        ).dict(by_alias=True)
 | 
			
		||||
 | 
			
		||||
    return list(map(map_to_response, player_teams))
 | 
			
		||||
 | 
			
		||||
class EditMemberRolesJson(BaseModel):
 | 
			
		||||
    roles: list[ViewTeamMembersResponse.RoleSchema]
 | 
			
		||||
 | 
			
		||||
@api_team.patch("/id/<team_id>/edit-player/<target_player_id>")
 | 
			
		||||
@spec.validate(
 | 
			
		||||
    resp=Response(
 | 
			
		||||
        HTTP_204=None,
 | 
			
		||||
        HTTP_403=None,
 | 
			
		||||
        HTTP_404=None,
 | 
			
		||||
    ),
 | 
			
		||||
    operation_id="edit_member_roles"
 | 
			
		||||
)
 | 
			
		||||
@requires_authentication
 | 
			
		||||
def edit_member_roles(
 | 
			
		||||
    json: EditMemberRolesJson,
 | 
			
		||||
    player: Player,
 | 
			
		||||
    team_id: int,
 | 
			
		||||
    target_player_id: int,
 | 
			
		||||
    **kwargs,
 | 
			
		||||
):
 | 
			
		||||
    print("hiiii lol")
 | 
			
		||||
    target_player = db.session.query(
 | 
			
		||||
        PlayerTeam
 | 
			
		||||
    ).where(
 | 
			
		||||
        PlayerTeam.player_id == target_player_id
 | 
			
		||||
    ).where(
 | 
			
		||||
        PlayerTeam.team_id == team_id
 | 
			
		||||
    ).options(
 | 
			
		||||
        joinedload(PlayerTeam.player),
 | 
			
		||||
        joinedload(PlayerTeam.player_roles),
 | 
			
		||||
    ).one_or_none()
 | 
			
		||||
 | 
			
		||||
    if not target_player:
 | 
			
		||||
        abort(401)
 | 
			
		||||
 | 
			
		||||
    # TODO: change this to a MERGE statement
 | 
			
		||||
 | 
			
		||||
    for role in target_player.player_roles:
 | 
			
		||||
        # delete role if not found in json
 | 
			
		||||
        f = filter(lambda x: x.role == role.role.name, json.roles)
 | 
			
		||||
        matched_role = next(f, None)
 | 
			
		||||
 | 
			
		||||
        if not matched_role:
 | 
			
		||||
            db.session.delete(role)
 | 
			
		||||
 | 
			
		||||
    for schema in json.roles:
 | 
			
		||||
        role = PlayerTeamRole()
 | 
			
		||||
        role.player_team = target_player
 | 
			
		||||
        role.role = PlayerTeamRole.Role[schema.role]
 | 
			
		||||
        role.is_main = schema.is_main
 | 
			
		||||
        db.session.merge(role)
 | 
			
		||||
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
 | 
			
		||||
    return make_response({ }, 204)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue