Improve usability
							parent
							
								
									afba73e1e8
								
							
						
					
					
						commit
						cb9e29b402
					
				| 
						 | 
					@ -11,7 +11,6 @@ const baseUrl = window.location.origin;
 | 
				
			||||||
        <h1>availabili.tf</h1>
 | 
					        <h1>availabili.tf</h1>
 | 
				
			||||||
        <RouterLink to="/">Home</RouterLink>
 | 
					        <RouterLink to="/">Home</RouterLink>
 | 
				
			||||||
        <RouterLink to="/schedule">Schedule</RouterLink>
 | 
					        <RouterLink to="/schedule">Schedule</RouterLink>
 | 
				
			||||||
        <RouterLink to="/schedule/roster">Roster Builder</RouterLink>
 | 
					 | 
				
			||||||
        <form action="https://steamcommunity.com/openid/login" method="get">
 | 
					        <form action="https://steamcommunity.com/openid/login" method="get">
 | 
				
			||||||
          <input type="hidden" name="openid.identity"
 | 
					          <input type="hidden" name="openid.identity"
 | 
				
			||||||
                 value="http://specs.openid.net/auth/2.0/identifier_select" />
 | 
					                 value="http://specs.openid.net/auth/2.0/identifier_select" />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -142,6 +142,9 @@ main {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
input {
 | 
					input {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  color: var(--text);
 | 
				
			||||||
  padding: 6px 9px;
 | 
					  padding: 6px 9px;
 | 
				
			||||||
  border: none;
 | 
					  border: none;
 | 
				
			||||||
  /*outline: 1px solid var(--overlay-0);*/
 | 
					  /*outline: 1px solid var(--overlay-0);*/
 | 
				
			||||||
| 
						 | 
					@ -164,3 +167,25 @@ input {
 | 
				
			||||||
    'Helvetica Neue',
 | 
					    'Helvetica Neue',
 | 
				
			||||||
    sans-serif;
 | 
					    sans-serif;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  gap: 4px;
 | 
				
			||||||
 | 
					  flex-grow: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group.margin {
 | 
				
			||||||
 | 
					  margin-top: 16px;
 | 
				
			||||||
 | 
					  margin-bottom: 16px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group.row {
 | 
				
			||||||
 | 
					  flex-direction: row;
 | 
				
			||||||
 | 
					  margin: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group .action-buttons {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: end;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,9 @@
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { computed, defineModel, defineProps, reactive, ref, onMounted, onUnmounted, type PropType } from "vue";
 | 
					import { computed, defineModel, defineProps, reactive, ref, onMounted, onUnmounted, type PropType } from "vue";
 | 
				
			||||||
import moment, { type Moment } from "moment";
 | 
					import moment, { type Moment } from "moment";
 | 
				
			||||||
 | 
					import { useScheduleStore } from "../stores/schedule";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const scheduleStore = useScheduleStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const model = defineModel();
 | 
					const model = defineModel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -196,6 +199,17 @@ function getAvailabilityCell(day: number, hour: number) {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return model.value[index];
 | 
					  return model.value[index];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const currentTimezone = computed(() =>
 | 
				
			||||||
 | 
					  Intl.DateTimeFormat().resolvedOptions().timeZone);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getHour(offset, tz) {
 | 
				
			||||||
 | 
					  let time = props.dateStart.clone()
 | 
				
			||||||
 | 
					  if (tz) {
 | 
				
			||||||
 | 
					    time = time.tz(tz);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return time.add(offset, "hours");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
| 
						 | 
					@ -204,12 +218,18 @@ function getAvailabilityCell(day: number, hour: number) {
 | 
				
			||||||
      <div class="height-48px"></div>
 | 
					      <div class="height-48px"></div>
 | 
				
			||||||
      <div class="height-24px hour-marker-container" v-for="hour, i in hours" :key="i">
 | 
					      <div class="height-24px hour-marker-container" v-for="hour, i in hours" :key="i">
 | 
				
			||||||
        <span class="hour-marker" v-if="i % 2 == 0 || i == hours.length">
 | 
					        <span class="hour-marker" v-if="i % 2 == 0 || i == hours.length">
 | 
				
			||||||
          {{ hour % 24 }}:30 / {{ (hour + 3) % 24 }}:30 EST
 | 
					          {{ getHour(hour).format("HH:mm z") }}
 | 
				
			||||||
 | 
					          <span v-if="scheduleStore.team.tzTimezone != currentTimezone">
 | 
				
			||||||
 | 
					            / {{ getHour(hour, scheduleStore.team.tzTimezone).format("HH:mm z") }}
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="height-24px hour-marker-container">
 | 
					      <div class="height-24px hour-marker-container">
 | 
				
			||||||
        <span class="hour-marker">
 | 
					        <span class="hour-marker">
 | 
				
			||||||
          {{ (lastHour + 1) % 24 }}:30 / {{ (lastHour + 4) % 24 }}:30 EST
 | 
					          {{ getHour(hour + 1).format("HH:mm z") }}
 | 
				
			||||||
 | 
					          <span v-if="scheduleStore.team.tzTimezone != currentTimezone">
 | 
				
			||||||
 | 
					            / {{ getHour(hour + 1, scheduleStore.team.tzTimezone).format("HH:mm z") }}
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import { useTeamsStore } from "../stores/teams";
 | 
				
			||||||
import { computed, onMounted, ref } from "vue";
 | 
					import { computed, onMounted, ref } from "vue";
 | 
				
			||||||
import PlayerTeamCard from "../components/PlayerTeamCard.vue";
 | 
					import PlayerTeamCard from "../components/PlayerTeamCard.vue";
 | 
				
			||||||
import InviteEntry from "../components/InviteEntry.vue";
 | 
					import InviteEntry from "../components/InviteEntry.vue";
 | 
				
			||||||
 | 
					import moment from "moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const route = useRoute();
 | 
					const route = useRoute();
 | 
				
			||||||
const router = useRouter();
 | 
					const router = useRouter();
 | 
				
			||||||
| 
						 | 
					@ -13,6 +14,12 @@ const team = computed(() => {
 | 
				
			||||||
  return teamsStore.teams[route.params.id];
 | 
					  return teamsStore.teams[route.params.id];
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const creationDate = computed(() => {
 | 
				
			||||||
 | 
					  if (team.value) {
 | 
				
			||||||
 | 
					    return moment(team.value.createdAt).format("L");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const invites = computed(() => {
 | 
					const invites = computed(() => {
 | 
				
			||||||
  return teamsStore.teamInvites[route.params.id];
 | 
					  return teamsStore.teamInvites[route.params.id];
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -68,8 +75,16 @@ onMounted(async () => {
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <main>
 | 
					  <main>
 | 
				
			||||||
    <template v-if="team">
 | 
					    <template v-if="team">
 | 
				
			||||||
 | 
					      <center class="team-info">
 | 
				
			||||||
        <h1>
 | 
					        <h1>
 | 
				
			||||||
          {{ team.teamName }}
 | 
					          {{ team.teamName }}
 | 
				
			||||||
 | 
					        </h1>
 | 
				
			||||||
 | 
					        <span class="aside">
 | 
				
			||||||
 | 
					          Formed on {{ creationDate }}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					      </center>
 | 
				
			||||||
 | 
					      <div class="member-list-header">
 | 
				
			||||||
 | 
					        <h2>Members</h2>
 | 
				
			||||||
        <em class="aside" v-if="teamsStore.teamMembers[route.params.id]">
 | 
					        <em class="aside" v-if="teamsStore.teamMembers[route.params.id]">
 | 
				
			||||||
          {{ teamsStore.teamMembers[route.params.id]?.length }} member(s),
 | 
					          {{ teamsStore.teamMembers[route.params.id]?.length }} member(s),
 | 
				
			||||||
          {{ availableMembers?.length }} currently available,
 | 
					          {{ availableMembers?.length }} currently available,
 | 
				
			||||||
| 
						 | 
					@ -89,7 +104,7 @@ onMounted(async () => {
 | 
				
			||||||
            Leave
 | 
					            Leave
 | 
				
			||||||
          </button>
 | 
					          </button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </h1>
 | 
					      </div>
 | 
				
			||||||
      <table class="member-table">
 | 
					      <table class="member-table">
 | 
				
			||||||
        <!--thead>
 | 
					        <!--thead>
 | 
				
			||||||
          <tr>
 | 
					          <tr>
 | 
				
			||||||
| 
						 | 
					@ -157,13 +172,17 @@ onMounted(async () => {
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped>
 | 
					<style scoped>
 | 
				
			||||||
h1 {
 | 
					.team-info {
 | 
				
			||||||
 | 
					  margin: 4em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.member-list-header {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  gap: 0.5em;
 | 
					  gap: 0.5em;
 | 
				
			||||||
  align-items: center;
 | 
					  align-items: center;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
h1 > em.aside {
 | 
					.member-list-header > .aside {
 | 
				
			||||||
  font-size: 12pt;
 | 
					  font-size: 12pt;
 | 
				
			||||||
  font-style: normal;
 | 
					  font-style: normal;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import UTC, datetime
 | 
				
			||||||
from sqlalchemy.orm import mapped_column, relationship
 | 
					from sqlalchemy.orm import mapped_column, relationship
 | 
				
			||||||
from sqlalchemy.orm.attributes import Mapped
 | 
					from sqlalchemy.orm.attributes import Mapped
 | 
				
			||||||
from sqlalchemy.sql import func
 | 
					from sqlalchemy.sql import func
 | 
				
			||||||
| 
						 | 
					@ -25,11 +25,19 @@ class Team(app_db.BaseModel):
 | 
				
			||||||
class TeamSchema(spec.BaseModel):
 | 
					class TeamSchema(spec.BaseModel):
 | 
				
			||||||
    id: int
 | 
					    id: int
 | 
				
			||||||
    team_name: str
 | 
					    team_name: str
 | 
				
			||||||
    discord_webhook_url: str | None
 | 
					 | 
				
			||||||
    tz_timezone: str
 | 
					    tz_timezone: str
 | 
				
			||||||
    minute_offset: int
 | 
					    minute_offset: int
 | 
				
			||||||
    #players: list[PlayerTeamSpec] | None
 | 
					    created_at: datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def from_model(cls, team: Team):
 | 
				
			||||||
 | 
					        return cls(
 | 
				
			||||||
 | 
					            id=team.id,
 | 
				
			||||||
 | 
					            team_name=team.team_name,
 | 
				
			||||||
 | 
					            tz_timezone=team.tz_timezone,
 | 
				
			||||||
 | 
					            minute_offset=team.minute_offset,
 | 
				
			||||||
 | 
					            created_at=team.created_at,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from models.player_team import PlayerTeam
 | 
					from models.player_team import PlayerTeam
 | 
				
			||||||
from models.team_integration import TeamIntegration
 | 
					from models.team_integration import TeamIntegration
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
from datetime import datetime, timedelta, timezone
 | 
					from datetime import UTC, datetime, timedelta, timezone
 | 
				
			||||||
from random import randint, random
 | 
					from random import randint, random
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
| 
						 | 
					@ -22,15 +22,6 @@ import pytz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
api_team = Blueprint("team", __name__, url_prefix="/team")
 | 
					api_team = Blueprint("team", __name__, url_prefix="/team")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def map_team_to_schema(team: Team):
 | 
					 | 
				
			||||||
    return TeamSchema(
 | 
					 | 
				
			||||||
        id=team.id,
 | 
					 | 
				
			||||||
        team_name=team.team_name,
 | 
					 | 
				
			||||||
        discord_webhook_url=None,
 | 
					 | 
				
			||||||
        tz_timezone=team.tz_timezone,
 | 
					 | 
				
			||||||
        minute_offset=team.minute_offset
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def map_player_to_schema(player: Player):
 | 
					def map_player_to_schema(player: Player):
 | 
				
			||||||
    return PlayerSchema(
 | 
					    return PlayerSchema(
 | 
				
			||||||
        steam_id=str(player.steam_id),
 | 
					        steam_id=str(player.steam_id),
 | 
				
			||||||
| 
						 | 
					@ -40,6 +31,7 @@ def map_player_to_schema(player: Player):
 | 
				
			||||||
class CreateTeamJson(BaseModel):
 | 
					class CreateTeamJson(BaseModel):
 | 
				
			||||||
    team_name: str
 | 
					    team_name: str
 | 
				
			||||||
    discord_webhook_url: str | None = None
 | 
					    discord_webhook_url: str | None = None
 | 
				
			||||||
 | 
					    minute_offset: int = 0
 | 
				
			||||||
    league_timezone: str
 | 
					    league_timezone: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @validator("league_timezone")
 | 
					    @validator("league_timezone")
 | 
				
			||||||
| 
						 | 
					@ -75,6 +67,7 @@ def create_team(json: CreateTeamJson, player: Player, **kwargs):
 | 
				
			||||||
    team = Team(
 | 
					    team = Team(
 | 
				
			||||||
        team_name=json.team_name,
 | 
					        team_name=json.team_name,
 | 
				
			||||||
        tz_timezone=json.league_timezone,
 | 
					        tz_timezone=json.league_timezone,
 | 
				
			||||||
 | 
					        minute_offset=json.minute_offset,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    if json.discord_webhook_url:
 | 
					    if json.discord_webhook_url:
 | 
				
			||||||
        team.discord_webhook_url = json.discord_webhook_url
 | 
					        team.discord_webhook_url = json.discord_webhook_url
 | 
				
			||||||
| 
						 | 
					@ -91,8 +84,8 @@ def create_team(json: CreateTeamJson, player: Player, **kwargs):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    db.session.commit()
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    response = ViewTeamResponse(team=map_team_to_schema(team))
 | 
					    response = ViewTeamResponse(team=TeamSchema.from_model(team))
 | 
				
			||||||
    return jsonify(response.dict(by_alias=True))
 | 
					    return response.dict(by_alias=True), 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_team.delete("/id/<team_id>/")
 | 
					@api_team.delete("/id/<team_id>/")
 | 
				
			||||||
@spec.validate(
 | 
					@spec.validate(
 | 
				
			||||||
| 
						 | 
					@ -255,7 +248,7 @@ def view_teams(**kwargs):
 | 
				
			||||||
    player: Player = kwargs["player"]
 | 
					    player: Player = kwargs["player"]
 | 
				
			||||||
    response = fetch_teams_for_player(player, None)
 | 
					    response = fetch_teams_for_player(player, None)
 | 
				
			||||||
    if isinstance(response, ViewTeamsResponse):
 | 
					    if isinstance(response, ViewTeamsResponse):
 | 
				
			||||||
        return jsonify(response.dict(by_alias=True))
 | 
					        return response.dict(by_alias=True)
 | 
				
			||||||
    abort(404)
 | 
					    abort(404)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_team.get("/id/<team_id>/")
 | 
					@api_team.get("/id/<team_id>/")
 | 
				
			||||||
| 
						 | 
					@ -272,7 +265,7 @@ def view_team(team_id: int, **kwargs):
 | 
				
			||||||
    player: Player = kwargs["player"]
 | 
					    player: Player = kwargs["player"]
 | 
				
			||||||
    response = fetch_teams_for_player(player, team_id)
 | 
					    response = fetch_teams_for_player(player, team_id)
 | 
				
			||||||
    if isinstance(response, ViewTeamResponse):
 | 
					    if isinstance(response, ViewTeamResponse):
 | 
				
			||||||
        return jsonify(response.dict(by_alias=True))
 | 
					        return response.dict(by_alias=True)
 | 
				
			||||||
    abort(404)
 | 
					    abort(404)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def fetch_teams_for_player(player: Player, team_id: int | None):
 | 
					def fetch_teams_for_player(player: Player, team_id: int | None):
 | 
				
			||||||
| 
						 | 
					@ -292,13 +285,13 @@ def fetch_teams_for_player(player: Player, team_id: int | None):
 | 
				
			||||||
    if team_id is None:
 | 
					    if team_id is None:
 | 
				
			||||||
        teams = q.all()
 | 
					        teams = q.all()
 | 
				
			||||||
        return ViewTeamsResponse(
 | 
					        return ViewTeamsResponse(
 | 
				
			||||||
            teams=list(map(map_team_to_schema, teams))
 | 
					            teams=list(map(TeamSchema.from_model, teams))
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        team = q.one_or_none()
 | 
					        team = q.one_or_none()
 | 
				
			||||||
        if team:
 | 
					        if team:
 | 
				
			||||||
            return ViewTeamResponse(
 | 
					            return ViewTeamResponse(
 | 
				
			||||||
                team=map_team_to_schema(team)
 | 
					                team=TeamSchema.from_model(team)
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ViewTeamMembersResponse(PlayerSchema):
 | 
					class ViewTeamMembersResponse(PlayerSchema):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue