Improve usability

master
John Montagu, the 4th Earl of Sandvich 2024-11-15 19:40:28 -08:00
parent afba73e1e8
commit cb9e29b402
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
6 changed files with 91 additions and 27 deletions

View File

@ -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" />

View File

@ -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;
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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

View File

@ -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):