feat: Add attendance options dropdown
							parent
							
								
									a2712504e2
								
							
						
					
					
						commit
						f0e2e28d65
					
				| 
						 | 
				
			
			@ -271,7 +271,7 @@ hr {
 | 
			
		|||
  /*box-shadow: 0 0 4px var(--text);*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div[role="menu"] {
 | 
			
		||||
[role="menu"] {
 | 
			
		||||
  background-color: var(--base);
 | 
			
		||||
  border: 1px solid var(--overlay-0);
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
| 
						 | 
				
			
			@ -279,22 +279,22 @@ div[role="menu"] {
 | 
			
		|||
  min-width: 8rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div[role="menu"] button {
 | 
			
		||||
[role="menu"] button {
 | 
			
		||||
  background-color: var(--base);
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div[role="menu"] button.destructive {
 | 
			
		||||
[role="menu"] button.destructive {
 | 
			
		||||
  color: var(--destructive);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div[role="menu"] button.destructive:hover {
 | 
			
		||||
[role="menu"] button.destructive:hover {
 | 
			
		||||
  background-color: var(--destructive);
 | 
			
		||||
  color: var(--base);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div[role="menu"] button > i.bi.margin {
 | 
			
		||||
[role="menu"] button > i.bi.margin {
 | 
			
		||||
  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);
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ export { OpenAPI } from './core/OpenAPI';
 | 
			
		|||
export type { OpenAPIConfig } from './core/OpenAPI';
 | 
			
		||||
 | 
			
		||||
export type { AddPlayerJson } from './models/AddPlayerJson';
 | 
			
		||||
export type { AttendanceJson } from './models/AttendanceJson';
 | 
			
		||||
export type { AvailabilitySchema } from './models/AvailabilitySchema';
 | 
			
		||||
export type { CreateEventJson } from './models/CreateEventJson';
 | 
			
		||||
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 */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { AddPlayerJson } from '../models/AddPlayerJson';
 | 
			
		||||
import type { AttendanceJson } from '../models/AttendanceJson';
 | 
			
		||||
import type { CreateEventJson } from '../models/CreateEventJson';
 | 
			
		||||
import type { CreateTeamJson } from '../models/CreateTeamJson';
 | 
			
		||||
import type { EditMemberRolesJson } from '../models/EditMemberRolesJson';
 | 
			
		||||
| 
						 | 
				
			
			@ -197,11 +198,13 @@ export class DefaultService {
 | 
			
		|||
    /**
 | 
			
		||||
     * attend_event <PUT>
 | 
			
		||||
     * @param eventId
 | 
			
		||||
     * @param requestBody
 | 
			
		||||
     * @returns EventWithPlayerSchema OK
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public attendEvent(
 | 
			
		||||
        eventId: number,
 | 
			
		||||
        requestBody?: AttendanceJson,
 | 
			
		||||
    ): CancelablePromise<EventWithPlayerSchema> {
 | 
			
		||||
        return this.httpRequest.request({
 | 
			
		||||
            method: 'PUT',
 | 
			
		||||
| 
						 | 
				
			
			@ -209,6 +212,8 @@ export class DefaultService {
 | 
			
		|||
            path: {
 | 
			
		||||
                'event_id': eventId,
 | 
			
		||||
            },
 | 
			
		||||
            body: requestBody,
 | 
			
		||||
            mediaType: 'application/json',
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Unprocessable Content`,
 | 
			
		||||
            },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@ import { useTeamsEventsStore } from "@/stores/teams/events";
 | 
			
		|||
import moment from "moment";
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
import EventCardDropdown from "./EventCardDropdown.vue";
 | 
			
		||||
import EventConfirmButton from "./EventConfirmButton.vue";
 | 
			
		||||
const teamsStore = useTeamsStore();
 | 
			
		||||
const rosterStore = useRosterStore();
 | 
			
		||||
const teamEventsStore = useTeamsEventsStore();
 | 
			
		||||
| 
						 | 
				
			
			@ -46,16 +47,13 @@ function attend() {
 | 
			
		|||
  teamEventsStore.attendEvent(props.event.event.id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function unattend() {
 | 
			
		||||
  teamEventsStore.unattendEvent(props.event.event.id);
 | 
			
		||||
function pending() {
 | 
			
		||||
  console.log("pending");
 | 
			
		||||
  teamEventsStore.attendEvent(props.event.event.id, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function attendOrUnattend() {
 | 
			
		||||
  if (props.event.playerEvent?.hasConfirmed) {
 | 
			
		||||
    unattend();
 | 
			
		||||
  } else {
 | 
			
		||||
    attend();
 | 
			
		||||
  }
 | 
			
		||||
function unattend() {
 | 
			
		||||
  teamEventsStore.unattendEvent(props.event.event.id);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -87,30 +85,12 @@ function attendOrUnattend() {
 | 
			
		|||
        <em v-else class="subtext">No description provided.</em>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="button-group">
 | 
			
		||||
        <button
 | 
			
		||||
          @click="attendOrUnattend()"
 | 
			
		||||
          v-if="event.playerEvent"
 | 
			
		||||
          :class="{
 | 
			
		||||
            'class-info': true,
 | 
			
		||||
            '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>
 | 
			
		||||
        <EventConfirmButton
 | 
			
		||||
          :playerEvent="event.playerEvent"
 | 
			
		||||
          @attend="attend"
 | 
			
		||||
          @pending="pending"
 | 
			
		||||
          @unattend="unattend"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -193,9 +173,4 @@ h3 {
 | 
			
		|||
  justify-content: space-between;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button.confirmed {
 | 
			
		||||
  background-color: var(--text);
 | 
			
		||||
  color: var(--base);
 | 
			
		||||
}
 | 
			
		||||
</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) {
 | 
			
		||||
    return client.default.attendEvent(eventId)
 | 
			
		||||
  async function attendEvent(eventId: number, confirm = true) {
 | 
			
		||||
    return client.default.attendEvent(eventId, {
 | 
			
		||||
      confirm,
 | 
			
		||||
    })
 | 
			
		||||
      .then((response) => {
 | 
			
		||||
        let index = teamEvents[response.event.teamId]
 | 
			
		||||
          .findIndex((event) => event.event.id == response.event.id);
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
class AttendanceJson(BaseModel):
 | 
			
		||||
    confirm: bool
 | 
			
		||||
 | 
			
		||||
@api_events.put("/<int:event_id>/attendance")
 | 
			
		||||
@spec.validate(
 | 
			
		||||
    resp=Response(
 | 
			
		||||
| 
						 | 
				
			
			@ -151,7 +154,7 @@ def create_event(player_team: PlayerTeam, team_id: int, json: CreateEventJson, *
 | 
			
		|||
    operation_id="attend_event",
 | 
			
		||||
)
 | 
			
		||||
@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()
 | 
			
		||||
 | 
			
		||||
    if not event:
 | 
			
		||||
| 
						 | 
				
			
			@ -175,7 +178,7 @@ def attend_event(player: Player, event_id: int, **_):
 | 
			
		|||
        player_event.player_id = player.steam_id
 | 
			
		||||
        db.session.add(player_event)
 | 
			
		||||
 | 
			
		||||
    player_event.has_confirmed = True
 | 
			
		||||
    player_event.has_confirmed = json.confirm
 | 
			
		||||
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue