feat: Add attendance options dropdown

master
John Montagu, the 4th Earl of Sandvich 2024-11-30 22:26:28 -08:00
parent a2712504e2
commit f0e2e28d65
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
8 changed files with 174 additions and 47 deletions

View File

@ -271,7 +271,7 @@ hr {
/*box-shadow: 0 0 4px var(--text);*/ /*box-shadow: 0 0 4px var(--text);*/
} }
div[role="menu"] { [role="menu"] {
background-color: var(--base); background-color: var(--base);
border: 1px solid var(--overlay-0); border: 1px solid var(--overlay-0);
border-radius: 4px; border-radius: 4px;
@ -279,22 +279,22 @@ div[role="menu"] {
min-width: 8rem; min-width: 8rem;
} }
div[role="menu"] button { [role="menu"] button {
background-color: var(--base); background-color: var(--base);
width: 100%; width: 100%;
border-radius: 0; border-radius: 0;
} }
div[role="menu"] button.destructive { [role="menu"] button.destructive {
color: var(--destructive); color: var(--destructive);
} }
div[role="menu"] button.destructive:hover { [role="menu"] button.destructive:hover {
background-color: var(--destructive); background-color: var(--destructive);
color: var(--base); color: var(--base);
} }
div[role="menu"] button > i.bi.margin { [role="menu"] button > i.bi.margin {
margin-right: 0.5em; margin-right: 0.5em;
} }
/* /*
@ -309,7 +309,7 @@ div[role="menu"] div[role="menuitem"]:last-child button {
} }
*/ */
div[role="menu"] button:hover { [role="menu"] button:hover {
background-color: var(--surface-0); background-color: var(--surface-0);
width: 100%; width: 100%;
} }

View File

@ -11,6 +11,7 @@ export { OpenAPI } from './core/OpenAPI';
export type { OpenAPIConfig } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI';
export type { AddPlayerJson } from './models/AddPlayerJson'; export type { AddPlayerJson } from './models/AddPlayerJson';
export type { AttendanceJson } from './models/AttendanceJson';
export type { AvailabilitySchema } from './models/AvailabilitySchema'; export type { AvailabilitySchema } from './models/AvailabilitySchema';
export type { CreateEventJson } from './models/CreateEventJson'; export type { CreateEventJson } from './models/CreateEventJson';
export type { CreateTeamJson } from './models/CreateTeamJson'; export type { CreateTeamJson } from './models/CreateTeamJson';

View File

@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type AttendanceJson = {
confirm: boolean;
};

View File

@ -3,6 +3,7 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
import type { AddPlayerJson } from '../models/AddPlayerJson'; import type { AddPlayerJson } from '../models/AddPlayerJson';
import type { AttendanceJson } from '../models/AttendanceJson';
import type { CreateEventJson } from '../models/CreateEventJson'; import type { CreateEventJson } from '../models/CreateEventJson';
import type { CreateTeamJson } from '../models/CreateTeamJson'; import type { CreateTeamJson } from '../models/CreateTeamJson';
import type { EditMemberRolesJson } from '../models/EditMemberRolesJson'; import type { EditMemberRolesJson } from '../models/EditMemberRolesJson';
@ -197,11 +198,13 @@ export class DefaultService {
/** /**
* attend_event <PUT> * attend_event <PUT>
* @param eventId * @param eventId
* @param requestBody
* @returns EventWithPlayerSchema OK * @returns EventWithPlayerSchema OK
* @throws ApiError * @throws ApiError
*/ */
public attendEvent( public attendEvent(
eventId: number, eventId: number,
requestBody?: AttendanceJson,
): CancelablePromise<EventWithPlayerSchema> { ): CancelablePromise<EventWithPlayerSchema> {
return this.httpRequest.request({ return this.httpRequest.request({
method: 'PUT', method: 'PUT',
@ -209,6 +212,8 @@ export class DefaultService {
path: { path: {
'event_id': eventId, 'event_id': eventId,
}, },
body: requestBody,
mediaType: 'application/json',
errors: { errors: {
422: `Unprocessable Content`, 422: `Unprocessable Content`,
}, },

View File

@ -6,6 +6,7 @@ import { useTeamsEventsStore } from "@/stores/teams/events";
import moment from "moment"; import moment from "moment";
import { computed } from "vue"; import { computed } from "vue";
import EventCardDropdown from "./EventCardDropdown.vue"; import EventCardDropdown from "./EventCardDropdown.vue";
import EventConfirmButton from "./EventConfirmButton.vue";
const teamsStore = useTeamsStore(); const teamsStore = useTeamsStore();
const rosterStore = useRosterStore(); const rosterStore = useRosterStore();
const teamEventsStore = useTeamsEventsStore(); const teamEventsStore = useTeamsEventsStore();
@ -46,16 +47,13 @@ function attend() {
teamEventsStore.attendEvent(props.event.event.id); teamEventsStore.attendEvent(props.event.event.id);
} }
function unattend() { function pending() {
teamEventsStore.unattendEvent(props.event.event.id); console.log("pending");
teamEventsStore.attendEvent(props.event.event.id, false);
} }
function attendOrUnattend() { function unattend() {
if (props.event.playerEvent?.hasConfirmed) { teamEventsStore.unattendEvent(props.event.event.id);
unattend();
} else {
attend();
}
} }
</script> </script>
@ -87,30 +85,12 @@ function attendOrUnattend() {
<em v-else class="subtext">No description provided.</em> <em v-else class="subtext">No description provided.</em>
</div> </div>
<div class="button-group"> <div class="button-group">
<button <EventConfirmButton
@click="attendOrUnattend()" :playerEvent="event.playerEvent"
v-if="event.playerEvent" @attend="attend"
:class="{ @pending="pending"
'class-info': true, @unattend="unattend"
'confirmed': event.playerEvent.hasConfirmed, />
}"
>
<template v-if="!event.playerEvent.hasConfirmed">
<i class="bi bi-check2" />
Confirm
</template>
<template v-else>
<i class="bi bi-check2-all" />
Confirmed
</template>
<span v-if="event.playerEvent.role">
as {{ rosterStore.roleNames[event.playerEvent.role.role] }}
</span>
</button>
<button @click="attend" v-else>
<i class="bi bi-check2" />
Attend
</button>
</div> </div>
</div> </div>
</div> </div>
@ -193,9 +173,4 @@ h3 {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
button.confirmed {
background-color: var(--text);
color: var(--base);
}
</style> </style>

View File

@ -0,0 +1,132 @@
<script setup lang="ts">
import type { PlayerEventRolesSchema } from "@/client";
import { useRosterStore } from "@/stores/roster";
import {
DropdownMenuRoot,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRadioItem,
DropdownMenuRadioGroup,
DropdownMenuItemIndicator,
} from "radix-vue";
import { computed } from "vue";
const rosterStore = useRosterStore();
const props = defineProps<{
playerEvent: PlayerEventRolesSchema | null;
}>();
const emit = defineEmits<{
attend: [],
unattend: [],
pending: [],
}>();
function attendOrUnattend() {
if (props.playerEvent?.hasConfirmed) {
emit("unattend");
} else {
emit("attend");
}
}
const confirmationOptions = ["Attending", "Pending", "Not attending"];
const selectedOption = computed({
get() {
if (props.playerEvent?.hasConfirmed) {
return "Attending";
} else if (props.playerEvent?.hasConfirmed === false) {
return "Pending";
} else {
return "Not attending";
}
},
set(value: string) {
console.log(value);
if (value === "Attending") {
emit("attend");
} else if (value === "Not attending") {
emit("unattend");
} else {
emit("pending");
}
},
});
</script>
<template>
<div
:class="{
'event-confirm-button': true,
'confirmed': playerEvent?.hasConfirmed,
}"
>
<button
@click="attendOrUnattend()"
v-if="playerEvent"
class="class-info left recolor"
>
<template v-if="!playerEvent.hasConfirmed">
<i class="bi bi-check2" />
Confirm
</template>
<template v-else>
<i class="bi bi-check2-all" />
Attending
</template>
<span v-if="playerEvent.role">
as {{ rosterStore.roleNames[playerEvent.role.role] }}
</span>
</button>
<button v-else @click="emit('attend')" class="left">
<i class="bi bi-check2" />
Attend
</button>
<DropdownMenuRoot>
<DropdownMenuTrigger className="right recolor">
<i class="bi bi-caret-down-fill" />
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuRadioGroup v-model="selectedOption">
<DropdownMenuRadioItem
v-for="option in confirmationOptions"
as="button"
:value="option"
>
<DropdownMenuItemIndicator>
<i class="bi bi-check" />
</DropdownMenuItemIndicator>
{{ option }}
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</div>
</template>
<style scoped>
.event-confirm-button {
display: flex;
gap: 2px;
}
.left {
border-radius: 4px 0 0 4px;
}
.right {
border-radius: 0 4px 4px 0;
padding: 6px 12px;
}
.confirmed button.recolor {
background-color: var(--text);
color: var(--base);
}
</style>

View File

@ -21,12 +21,15 @@ export const useTeamsEventsStore = defineStore("teamsEvents", () => {
); );
} }
async function attendEvent(eventId: number) { async function attendEvent(eventId: number, confirm = true) {
return client.default.attendEvent(eventId) return client.default.attendEvent(eventId, {
confirm,
})
.then((response) => { .then((response) => {
let index = teamEvents[response.event.teamId] let index = teamEvents[response.event.teamId]
.findIndex((event) => event.event.id == response.event.id); .findIndex((event) => event.event.id == response.event.id);
teamEvents[response.event.teamId][index] = response; teamEvents[response.event.teamId][index] = response;
return response;
}); });
} }

View File

@ -143,6 +143,9 @@ def create_event(player_team: PlayerTeam, team_id: int, json: CreateEventJson, *
return EventSchema.from_model(event).dict(by_alias=True), 200 return EventSchema.from_model(event).dict(by_alias=True), 200
class AttendanceJson(BaseModel):
confirm: bool
@api_events.put("/<int:event_id>/attendance") @api_events.put("/<int:event_id>/attendance")
@spec.validate( @spec.validate(
resp=Response( resp=Response(
@ -151,7 +154,7 @@ def create_event(player_team: PlayerTeam, team_id: int, json: CreateEventJson, *
operation_id="attend_event", operation_id="attend_event",
) )
@requires_authentication @requires_authentication
def attend_event(player: Player, event_id: int, **_): def attend_event(player: Player, event_id: int, json: AttendanceJson, **_):
event = db.session.query(Event).where(Event.id == event_id).one_or_none() event = db.session.query(Event).where(Event.id == event_id).one_or_none()
if not event: if not event:
@ -175,7 +178,7 @@ def attend_event(player: Player, event_id: int, **_):
player_event.player_id = player.steam_id player_event.player_id = player.steam_id
db.session.add(player_event) db.session.add(player_event)
player_event.has_confirmed = True player_event.has_confirmed = json.confirm
db.session.commit() db.session.commit()