Improve usability
parent
afba73e1e8
commit
cb9e29b402
|
@ -11,7 +11,6 @@ const baseUrl = window.location.origin;
|
|||
<h1>availabili.tf</h1>
|
||||
<RouterLink to="/">Home</RouterLink>
|
||||
<RouterLink to="/schedule">Schedule</RouterLink>
|
||||
<RouterLink to="/schedule/roster">Roster Builder</RouterLink>
|
||||
<form action="https://steamcommunity.com/openid/login" method="get">
|
||||
<input type="hidden" name="openid.identity"
|
||||
value="http://specs.openid.net/auth/2.0/identifier_select" />
|
||||
|
|
|
@ -142,6 +142,9 @@ main {
|
|||
}
|
||||
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: var(--text);
|
||||
padding: 6px 9px;
|
||||
border: none;
|
||||
/*outline: 1px solid var(--overlay-0);*/
|
||||
|
@ -164,3 +167,25 @@ input {
|
|||
'Helvetica Neue',
|
||||
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">
|
||||
import { computed, defineModel, defineProps, reactive, ref, onMounted, onUnmounted, type PropType } from "vue";
|
||||
import moment, { type Moment } from "moment";
|
||||
import { useScheduleStore } from "../stores/schedule";
|
||||
|
||||
const scheduleStore = useScheduleStore();
|
||||
|
||||
const model = defineModel();
|
||||
|
||||
|
@ -196,6 +199,17 @@ function getAvailabilityCell(day: number, hour: number) {
|
|||
}
|
||||
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>
|
||||
|
||||
<template>
|
||||
|
@ -204,12 +218,18 @@ function getAvailabilityCell(day: number, hour: number) {
|
|||
<div class="height-48px"></div>
|
||||
<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">
|
||||
{{ 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>
|
||||
</div>
|
||||
<div class="height-24px hour-marker-container">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useTeamsStore } from "../stores/teams";
|
|||
import { computed, onMounted, ref } from "vue";
|
||||
import PlayerTeamCard from "../components/PlayerTeamCard.vue";
|
||||
import InviteEntry from "../components/InviteEntry.vue";
|
||||
import moment from "moment";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
@ -13,6 +14,12 @@ const team = computed(() => {
|
|||
return teamsStore.teams[route.params.id];
|
||||
});
|
||||
|
||||
const creationDate = computed(() => {
|
||||
if (team.value) {
|
||||
return moment(team.value.createdAt).format("L");
|
||||
}
|
||||
});
|
||||
|
||||
const invites = computed(() => {
|
||||
return teamsStore.teamInvites[route.params.id];
|
||||
});
|
||||
|
@ -68,8 +75,16 @@ onMounted(async () => {
|
|||
<template>
|
||||
<main>
|
||||
<template v-if="team">
|
||||
<h1>
|
||||
{{ team.teamName }}
|
||||
<center class="team-info">
|
||||
<h1>
|
||||
{{ 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]">
|
||||
{{ teamsStore.teamMembers[route.params.id]?.length }} member(s),
|
||||
{{ availableMembers?.length }} currently available,
|
||||
|
@ -89,7 +104,7 @@ onMounted(async () => {
|
|||
Leave
|
||||
</button>
|
||||
</div>
|
||||
</h1>
|
||||
</div>
|
||||
<table class="member-table">
|
||||
<!--thead>
|
||||
<tr>
|
||||
|
@ -157,13 +172,17 @@ onMounted(async () => {
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
.team-info {
|
||||
margin: 4em;
|
||||
}
|
||||
|
||||
.member-list-header {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 > em.aside {
|
||||
.member-list-header > .aside {
|
||||
font-size: 12pt;
|
||||
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.attributes import Mapped
|
||||
from sqlalchemy.sql import func
|
||||
|
@ -25,11 +25,19 @@ class Team(app_db.BaseModel):
|
|||
class TeamSchema(spec.BaseModel):
|
||||
id: int
|
||||
team_name: str
|
||||
discord_webhook_url: str | None
|
||||
tz_timezone: str
|
||||
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.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
|
||||
import sys
|
||||
import time
|
||||
|
@ -22,15 +22,6 @@ import pytz
|
|||
|
||||
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):
|
||||
return PlayerSchema(
|
||||
steam_id=str(player.steam_id),
|
||||
|
@ -40,6 +31,7 @@ def map_player_to_schema(player: Player):
|
|||
class CreateTeamJson(BaseModel):
|
||||
team_name: str
|
||||
discord_webhook_url: str | None = None
|
||||
minute_offset: int = 0
|
||||
league_timezone: str
|
||||
|
||||
@validator("league_timezone")
|
||||
|
@ -75,6 +67,7 @@ def create_team(json: CreateTeamJson, player: Player, **kwargs):
|
|||
team = Team(
|
||||
team_name=json.team_name,
|
||||
tz_timezone=json.league_timezone,
|
||||
minute_offset=json.minute_offset,
|
||||
)
|
||||
if 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()
|
||||
|
||||
response = ViewTeamResponse(team=map_team_to_schema(team))
|
||||
return jsonify(response.dict(by_alias=True))
|
||||
response = ViewTeamResponse(team=TeamSchema.from_model(team))
|
||||
return response.dict(by_alias=True), 200
|
||||
|
||||
@api_team.delete("/id/<team_id>/")
|
||||
@spec.validate(
|
||||
|
@ -255,7 +248,7 @@ def view_teams(**kwargs):
|
|||
player: Player = kwargs["player"]
|
||||
response = fetch_teams_for_player(player, None)
|
||||
if isinstance(response, ViewTeamsResponse):
|
||||
return jsonify(response.dict(by_alias=True))
|
||||
return response.dict(by_alias=True)
|
||||
abort(404)
|
||||
|
||||
@api_team.get("/id/<team_id>/")
|
||||
|
@ -272,7 +265,7 @@ def view_team(team_id: int, **kwargs):
|
|||
player: Player = kwargs["player"]
|
||||
response = fetch_teams_for_player(player, team_id)
|
||||
if isinstance(response, ViewTeamResponse):
|
||||
return jsonify(response.dict(by_alias=True))
|
||||
return response.dict(by_alias=True)
|
||||
abort(404)
|
||||
|
||||
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:
|
||||
teams = q.all()
|
||||
return ViewTeamsResponse(
|
||||
teams=list(map(map_team_to_schema, teams))
|
||||
teams=list(map(TeamSchema.from_model, teams))
|
||||
)
|
||||
else:
|
||||
team = q.one_or_none()
|
||||
if team:
|
||||
return ViewTeamResponse(
|
||||
team=map_team_to_schema(team)
|
||||
team=TeamSchema.from_model(team)
|
||||
)
|
||||
|
||||
class ViewTeamMembersResponse(PlayerSchema):
|
||||
|
|
Loading…
Reference in New Issue