Implement AvailabilityGrid component
							parent
							
								
									280e02c15a
								
							
						
					
					
						commit
						a11b6f454f
					
				| 
						 | 
				
			
			@ -40,7 +40,10 @@
 | 
			
		|||
  --green: #a6e3a1;
 | 
			
		||||
  --lavender: #7287fd;
 | 
			
		||||
  --accent: var(--lavender);
 | 
			
		||||
  --accent-transparent: color-mix(in srgb, var(--accent), transparent 80%);
 | 
			
		||||
  --accent-transparent-80: color-mix(in srgb, var(--accent), transparent 80%);
 | 
			
		||||
  --accent-transparent-50: color-mix(in srgb, var(--accent), transparent 50%);
 | 
			
		||||
  --accent-transparent: var(--accent-transparent-80);
 | 
			
		||||
  --shadow: color-mix(in srgb, var(--text), transparent 50%);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,10 +25,23 @@ a,
 | 
			
		|||
button {
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  color: var(--text);
 | 
			
		||||
  background-color: var(--surface-0);
 | 
			
		||||
  background-color: var(--crust);
 | 
			
		||||
  border: none;
 | 
			
		||||
  padding: 8px;
 | 
			
		||||
  padding: 8px 16px;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
    font-family:
 | 
			
		||||
    Inter,
 | 
			
		||||
    -apple-system,
 | 
			
		||||
    BlinkMacSystemFont,
 | 
			
		||||
    'Segoe UI',
 | 
			
		||||
    Roboto,
 | 
			
		||||
    Oxygen,
 | 
			
		||||
    Ubuntu,
 | 
			
		||||
    Cantarell,
 | 
			
		||||
    'Fira Sans',
 | 
			
		||||
    'Droid Sans',
 | 
			
		||||
    'Helvetica Neue',
 | 
			
		||||
    sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button.accent {
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +50,10 @@ button.accent {
 | 
			
		|||
  text-transform: uppercase;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button.accent {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 {
 | 
			
		||||
  font-weight: 800;
 | 
			
		||||
  font-size: 200%;
 | 
			
		||||
| 
						 | 
				
			
			@ -46,3 +63,7 @@ h1 {
 | 
			
		|||
em.aside {
 | 
			
		||||
  color: var(--overlay-0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
select {
 | 
			
		||||
  appearance: none;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import { computed, defineModel, defineProps, ref } from "vue";
 | 
			
		||||
 | 
			
		||||
const model = defineModel();
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  options: Array<String>,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const isOpen = ref(false);
 | 
			
		||||
const selectedOption = computed(() => props.options[model.value]);
 | 
			
		||||
 | 
			
		||||
function selectOption(index) {
 | 
			
		||||
  model.value = index;
 | 
			
		||||
  isOpen.value = false;
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div :class="{ 'dropdown-container': true, 'is-open': isOpen }">
 | 
			
		||||
    <button @click="isOpen = !isOpen">
 | 
			
		||||
      {{ selectedOption }}
 | 
			
		||||
    </button>
 | 
			
		||||
    <ul class="dropdown" v-if="isOpen" @blur="isOpen = false">
 | 
			
		||||
      <li v-for="(option, i) in options" :key="i" @click="selectOption(i)">
 | 
			
		||||
        <button :class="{ 'is-selected': i == model }">
 | 
			
		||||
          {{ option }}
 | 
			
		||||
        </button>
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.dropdown-container {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown-container button {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  padding: 4px;
 | 
			
		||||
  transition-duration: 200ms;
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown-container button:hover {
 | 
			
		||||
  background-color: var(--crust);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown-container.is-open ul.dropdown {
 | 
			
		||||
  box-shadow: 1px 1px 8px var(--shadow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul.dropdown {
 | 
			
		||||
  display: block;
 | 
			
		||||
  background-color: var(--base);
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  margin-top: 8px;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  z-index: 2;
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  overflow: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul.dropdown > li {
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown li > button {
 | 
			
		||||
  padding: 8px 16px;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown li > button.is-selected {
 | 
			
		||||
  background-color: var(--accent-transparent);
 | 
			
		||||
  color: var(--accent);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +1,268 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import { computed, defineModel, defineProps, reactive, ref, onMounted, onUnmounted } from "vue";
 | 
			
		||||
 | 
			
		||||
const model = defineModel();
 | 
			
		||||
const firstHour = 15;
 | 
			
		||||
const lastHour = 23;
 | 
			
		||||
 | 
			
		||||
const windowStart = new Date(2024, 9, 21);
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  selectionMode: Number,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const selectionStart = reactive({ x: undefined, y: undefined });
 | 
			
		||||
const selectionEnd = reactive({ x: undefined, y: undefined });
 | 
			
		||||
const isCtrlDown = ref(false);
 | 
			
		||||
const isShiftDown = ref(false);
 | 
			
		||||
 | 
			
		||||
const lowerBoundX = computed(() => {
 | 
			
		||||
  return isShiftDown.value ? 0 :
 | 
			
		||||
    Math.min(selectionStart.x, selectionEnd.x)
 | 
			
		||||
});
 | 
			
		||||
const upperBoundX = computed(() => {
 | 
			
		||||
  return isShiftDown.value ? 7 :
 | 
			
		||||
    Math.max(selectionStart.x, selectionEnd.x)
 | 
			
		||||
});
 | 
			
		||||
const lowerBoundY = computed(() => {
 | 
			
		||||
  return isCtrlDown.value ? firstHour :
 | 
			
		||||
    Math.min(selectionStart.y, selectionEnd.y)
 | 
			
		||||
});
 | 
			
		||||
const upperBoundY = computed(() => {
 | 
			
		||||
  return isCtrlDown.value ? lastHour :
 | 
			
		||||
    Math.max(selectionStart.y, selectionEnd.y)
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function selectionInside(dayIndex, hour) {
 | 
			
		||||
  if (selectionStart.x != undefined) {
 | 
			
		||||
    return (dayIndex >= lowerBoundX.value && dayIndex <= upperBoundX.value) &&
 | 
			
		||||
      (hour >= lowerBoundY.value && hour <= upperBoundY.value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const days = computed(() => {
 | 
			
		||||
  let ret = [];
 | 
			
		||||
  for (let i = 0; i < 7; i++) {
 | 
			
		||||
    const date = new Date(windowStart);
 | 
			
		||||
    date.setDate(windowStart.getDate() + i);
 | 
			
		||||
    ret.push(date);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const hours = computed(() => {
 | 
			
		||||
  return Array.from(Array(lastHour - firstHour + 1).keys())
 | 
			
		||||
    .map(x => x + firstHour);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const daysOfWeek = [
 | 
			
		||||
  "Sun",
 | 
			
		||||
  "Mon",
 | 
			
		||||
  "Tue",
 | 
			
		||||
  "Wed",
 | 
			
		||||
  "Thu",
 | 
			
		||||
  "Fri",
 | 
			
		||||
  "Sat"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const isMouseDown = ref(false);
 | 
			
		||||
const selectionValue = ref(0);
 | 
			
		||||
 | 
			
		||||
function onSlotMouseDown($event, x, y) {
 | 
			
		||||
  selectionValue.value = model.value[24 * x + y] == props.selectionMode ?
 | 
			
		||||
    0 : props.selectionMode;
 | 
			
		||||
 | 
			
		||||
  selectionStart.x = x;
 | 
			
		||||
  selectionStart.y = y;
 | 
			
		||||
  selectionEnd.x = x;
 | 
			
		||||
  selectionEnd.y = y;
 | 
			
		||||
  console.log("selected " + x + " " + y);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onSlotMouseOver($event, x, y) {
 | 
			
		||||
  if ($event.buttons & 1 == 1) {
 | 
			
		||||
    //if (isAdding.value) {
 | 
			
		||||
    //  model.value[24 * x + y] = props.selectionMode;
 | 
			
		||||
    //} else {
 | 
			
		||||
    //  model.value[24 * x + y] = 0;
 | 
			
		||||
    //}
 | 
			
		||||
    console.log("selected " + (24 * x + y));
 | 
			
		||||
    selectionEnd.x = x;
 | 
			
		||||
    selectionEnd.y = y;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onSlotMouseUp($event) {
 | 
			
		||||
  console.log("mouseup");
 | 
			
		||||
  console.log(selectionStart);
 | 
			
		||||
  if (selectionStart.x == undefined) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (let x = lowerBoundX.value; x <= upperBoundX.value; x++) {
 | 
			
		||||
    for (let y = lowerBoundY.value; y <= upperBoundY.value; y++) {
 | 
			
		||||
      model.value[24 * x + y] = selectionValue.value;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  selectionStart.x = undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onKeyUp($event) {
 | 
			
		||||
  switch ($event.key) {
 | 
			
		||||
    case "Shift":
 | 
			
		||||
      isShiftDown.value = false;
 | 
			
		||||
      break;
 | 
			
		||||
    case "Control":
 | 
			
		||||
      isCtrlDown.value = false;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onKeyDown($event) {
 | 
			
		||||
  switch ($event.key) {
 | 
			
		||||
    case "Shift":
 | 
			
		||||
      isShiftDown.value = true;
 | 
			
		||||
      break;
 | 
			
		||||
    case "Control":
 | 
			
		||||
      isCtrlDown.value = true;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  window.addEventListener("mouseup", onSlotMouseUp);
 | 
			
		||||
  window.addEventListener("keydown", onKeyDown);
 | 
			
		||||
  window.addEventListener("keyup", onKeyUp);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  console.log("removing");
 | 
			
		||||
  window.removeEventListener("mouseup", onSlotMouseUp);
 | 
			
		||||
  window.removeEventListener("keydown", onKeyDown);
 | 
			
		||||
  window.removeEventListener("keyup", onKeyUp);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="grid">
 | 
			
		||||
 | 
			
		||||
    <div>
 | 
			
		||||
      <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)
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="height-24px hour-marker-container">
 | 
			
		||||
        <span class="hour-marker">
 | 
			
		||||
          {{ (lastHour + 1) % 24 }}:30 ({{ (lastHour + 4) % 24 }}:30 EST)
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-for="(day, dayIndex) in days" :key="dayIndex" class="column">
 | 
			
		||||
      <div class="date">
 | 
			
		||||
        <div class="day-of-week">{{ daysOfWeek[day.getDay()] }}</div>
 | 
			
		||||
        <div class="day">{{ day.getDate() }}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="column-time-slots">
 | 
			
		||||
        <div
 | 
			
		||||
          :class="{
 | 
			
		||||
            'time-slot': true,
 | 
			
		||||
            'height-24px': true,
 | 
			
		||||
          }"
 | 
			
		||||
          :selection="
 | 
			
		||||
            selectionInside(dayIndex, hour) ? selectionValue
 | 
			
		||||
              : model[24 * dayIndex + hour]
 | 
			
		||||
          "
 | 
			
		||||
          v-for="hour in hours"
 | 
			
		||||
          @mousedown="onSlotMouseDown($event, dayIndex, hour)"
 | 
			
		||||
          @mouseover="onSlotMouseOver($event, dayIndex, hour)"
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.height-24px {
 | 
			
		||||
  height: 24px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.height-32px {
 | 
			
		||||
  height: 32px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.height-48px {
 | 
			
		||||
  height: 48px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hour-marker-container {
 | 
			
		||||
  text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hour-marker {
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  line-height: 0;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  top: -0.75rem;
 | 
			
		||||
  margin-right: 8px;
 | 
			
		||||
  color: var(--subtext-0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid > .column > .column-time-slots {
 | 
			
		||||
  width: 72px;
 | 
			
		||||
  border: 4px;
 | 
			
		||||
  border: 1px solid var(--text);
 | 
			
		||||
  border-left: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid > .column:nth-child(2) > .column-time-slots {
 | 
			
		||||
  border-left: 1px solid var(--text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.date {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  justify-content: end;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  height: 48px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.date .day-of-week {
 | 
			
		||||
  color: var(--subtext-0);
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
  line-height: 0;
 | 
			
		||||
  margin-bottom: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.date .day {
 | 
			
		||||
  font-size: 20px;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.time-slot:hover {
 | 
			
		||||
  background-color: var(--crust);
 | 
			
		||||
  outline: 2px inset var(--subtext-0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.time-slot:nth-child(2n):not(:last-child) {
 | 
			
		||||
  border-bottom: 1px dashed var(--text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.time-slot[selection="1"] {
 | 
			
		||||
  background-color: var(--accent-transparent-80);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.time-slot[selection="2"] {
 | 
			
		||||
  background-color: var(--accent);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,6 +52,11 @@ function onClick() {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const playtime = computed(() => {
 | 
			
		||||
  let hours = props.player?.playtime / 3600 ?? 0;
 | 
			
		||||
  return hours.toFixed(1);
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
| 
						 | 
				
			
			@ -67,19 +72,26 @@ function onClick() {
 | 
			
		|||
    <div v-if="player" class="role-info">
 | 
			
		||||
      <span>
 | 
			
		||||
        <h4 class="player-name">{{ player.name }}</h4>
 | 
			
		||||
        <span v-if="roleTitle != player.role">
 | 
			
		||||
          Subbing in as
 | 
			
		||||
        </span>
 | 
			
		||||
        {{ player.role }}
 | 
			
		||||
        <span v-if="!player.main && isRoster">
 | 
			
		||||
          (alternate)
 | 
			
		||||
        </span>
 | 
			
		||||
        <div class="subtitle">
 | 
			
		||||
          <span>
 | 
			
		||||
            {{ player.role }}
 | 
			
		||||
            <span v-if="!player.main && isRoster">
 | 
			
		||||
              (alternate)
 | 
			
		||||
            </span>
 | 
			
		||||
          </span>
 | 
			
		||||
          <span v-if="playtime > 0">
 | 
			
		||||
            {{ playtime }} hours
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-else-if="isRinger" class="role-info">
 | 
			
		||||
      <span>
 | 
			
		||||
        <h4 class="player-name">Ringer</h4>
 | 
			
		||||
        {{ roleTitle }}
 | 
			
		||||
        <div class="subtitle">
 | 
			
		||||
          <span>{{ roleTitle }}</span>
 | 
			
		||||
          <span>nobody likes to play {{ roleTitle }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-else class="role-info">
 | 
			
		||||
| 
						 | 
				
			
			@ -116,6 +128,13 @@ function onClick() {
 | 
			
		|||
 | 
			
		||||
.player-card .role-info {
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  flex-grow: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.role-info .subtitle {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.player-card:hover {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import { defineModel } from "vue";
 | 
			
		||||
 | 
			
		||||
const model = defineModel();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="scroll-box">
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.scroll-box {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +65,14 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
      availability: 2,
 | 
			
		||||
      playtime: 47324,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2083,
 | 
			
		||||
      name: "IF_YOU_READ_THIS_",
 | 
			
		||||
      role: "Demoman",
 | 
			
		||||
      main: true,
 | 
			
		||||
      availability: 1,
 | 
			
		||||
      playtime: 32812,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      steamId: 2842,
 | 
			
		||||
      name: "BossOfThisGym",
 | 
			
		||||
| 
						 | 
				
			
			@ -142,14 +150,24 @@ export const useRosterStore = defineStore("roster", () => {
 | 
			
		|||
    return availablePlayerRoles.value.filter((player) => player.availability == 1);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  function comparator(p1: PlayerTeamRole, p2: PlayerTeamRole) {
 | 
			
		||||
    // definitely available > can be available
 | 
			
		||||
    let availabilityDiff = p1.availability - p2.availability;
 | 
			
		||||
 | 
			
		||||
    // less playtime is preferred
 | 
			
		||||
    let playtimeDiff = p1.playtime - p2.playtime;
 | 
			
		||||
 | 
			
		||||
    return availabilityDiff || playtimeDiff;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const mainRoles = computed(() => {
 | 
			
		||||
    return availablePlayerRoles.value.filter((player) => player.main)
 | 
			
		||||
      .sort((a, b) => b.playtime - a.playtime);
 | 
			
		||||
      .sort(comparator);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const alternateRoles = computed(() => {
 | 
			
		||||
    return availablePlayerRoles.value.filter((player) => !player.main)
 | 
			
		||||
      .sort((a, b) => b.playtime - a.playtime);
 | 
			
		||||
      .sort(comparator);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const roleIcons = reactive({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,11 +24,8 @@ const hasAlternates = computed(() => {
 | 
			
		|||
        <em class="aside date">Aug. 13, 2036 @ 11:30 PM EST</em>
 | 
			
		||||
      </h1>
 | 
			
		||||
      <div class="button-group">
 | 
			
		||||
        <button>
 | 
			
		||||
          <i class="bi bi-box-arrow-left"></i>
 | 
			
		||||
          Back
 | 
			
		||||
        </button>
 | 
			
		||||
        <button class="accent">Submit</button>
 | 
			
		||||
        <button>Cancel</button>
 | 
			
		||||
        <button class="accent">Save Roster</button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="columns">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,75 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import AvailabilityGrid from "../components/AvailabilityGrid.vue";
 | 
			
		||||
import AvailabilityComboBox from "../components/AvailabilityComboBox.vue";
 | 
			
		||||
import WeekSelectionBox from "../components/WeekSelectionBox.vue";
 | 
			
		||||
import { reactive, ref } from "vue";
 | 
			
		||||
 | 
			
		||||
const options = reactive([
 | 
			
		||||
  "Team pepeja",
 | 
			
		||||
  "Snus Brotherhood",
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
const comboBoxIndex = ref(0);
 | 
			
		||||
 | 
			
		||||
const availability = reactive(new Array(168));
 | 
			
		||||
 | 
			
		||||
const selectionMode = ref(1);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <main>
 | 
			
		||||
    <h1>Master Schedule</h1>
 | 
			
		||||
    <div v-if="options.length > 0">
 | 
			
		||||
      <div>
 | 
			
		||||
        Availability for
 | 
			
		||||
        <AvailabilityComboBox :options="options" v-model="comboBoxIndex" />
 | 
			
		||||
        <WeekSelectionBox />
 | 
			
		||||
      </div>
 | 
			
		||||
      <AvailabilityGrid v-model="availability" :selection-mode="selectionMode" />
 | 
			
		||||
      <div class="radio-group">
 | 
			
		||||
        <button
 | 
			
		||||
          :class="{ 'radio': true, 'selected': selectionMode == 1, 'left': true }"
 | 
			
		||||
          @click="selectionMode = 1"
 | 
			
		||||
        >
 | 
			
		||||
          Available if needed
 | 
			
		||||
        </button>
 | 
			
		||||
        <button
 | 
			
		||||
          :class="{ 'radio': true, 'selected': selectionMode == 2, 'right': true }"
 | 
			
		||||
          @click="selectionMode = 2"
 | 
			
		||||
        >
 | 
			
		||||
          Definitely available
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-else>
 | 
			
		||||
      You currently are not in any team to schedule for.
 | 
			
		||||
    </div>
 | 
			
		||||
  </main>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.radio-group {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  gap: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button.radio {
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button.radio:hover {
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button.radio.selected {
 | 
			
		||||
  color: var(--accent);
 | 
			
		||||
  background-color: var(--accent-transparent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button.left {
 | 
			
		||||
  border-radius: 8px 0 0 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button.right {
 | 
			
		||||
  border-radius: 0 8px 8px 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue