feat: Add attendance options dropdown
parent
a2712504e2
commit
f0e2e28d65
|
@ -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%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type AttendanceJson = {
|
||||||
|
confirm: boolean;
|
||||||
|
};
|
||||||
|
|
|
@ -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`,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue