Implement AvailabilityGrid component
parent
280e02c15a
commit
a11b6f454f
|
@ -40,7 +40,10 @@
|
||||||
--green: #a6e3a1;
|
--green: #a6e3a1;
|
||||||
--lavender: #7287fd;
|
--lavender: #7287fd;
|
||||||
--accent: var(--lavender);
|
--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 {
|
button {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
background-color: var(--surface-0);
|
background-color: var(--crust);
|
||||||
border: none;
|
border: none;
|
||||||
padding: 8px;
|
padding: 8px 16px;
|
||||||
border-radius: 4px;
|
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 {
|
button.accent {
|
||||||
|
@ -37,6 +50,10 @@ button.accent {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.accent {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-size: 200%;
|
font-size: 200%;
|
||||||
|
@ -46,3 +63,7 @@ h1 {
|
||||||
em.aside {
|
em.aside {
|
||||||
color: var(--overlay-0);
|
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">
|
<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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="grid">
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<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>
|
</style>
|
||||||
|
|
|
@ -52,6 +52,11 @@ function onClick() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const playtime = computed(() => {
|
||||||
|
let hours = props.player?.playtime / 3600 ?? 0;
|
||||||
|
return hours.toFixed(1);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -67,19 +72,26 @@ function onClick() {
|
||||||
<div v-if="player" class="role-info">
|
<div v-if="player" class="role-info">
|
||||||
<span>
|
<span>
|
||||||
<h4 class="player-name">{{ player.name }}</h4>
|
<h4 class="player-name">{{ player.name }}</h4>
|
||||||
<span v-if="roleTitle != player.role">
|
<div class="subtitle">
|
||||||
Subbing in as
|
<span>
|
||||||
</span>
|
{{ player.role }}
|
||||||
{{ player.role }}
|
<span v-if="!player.main && isRoster">
|
||||||
<span v-if="!player.main && isRoster">
|
(alternate)
|
||||||
(alternate)
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span v-if="playtime > 0">
|
||||||
|
{{ playtime }} hours
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="isRinger" class="role-info">
|
<div v-else-if="isRinger" class="role-info">
|
||||||
<span>
|
<span>
|
||||||
<h4 class="player-name">Ringer</h4>
|
<h4 class="player-name">Ringer</h4>
|
||||||
{{ roleTitle }}
|
<div class="subtitle">
|
||||||
|
<span>{{ roleTitle }}</span>
|
||||||
|
<span>nobody likes to play {{ roleTitle }}</span>
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="role-info">
|
<div v-else class="role-info">
|
||||||
|
@ -116,6 +128,13 @@ function onClick() {
|
||||||
|
|
||||||
.player-card .role-info {
|
.player-card .role-info {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-info .subtitle {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-card:hover {
|
.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,
|
availability: 2,
|
||||||
playtime: 47324,
|
playtime: 47324,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
steamId: 2083,
|
||||||
|
name: "IF_YOU_READ_THIS_",
|
||||||
|
role: "Demoman",
|
||||||
|
main: true,
|
||||||
|
availability: 1,
|
||||||
|
playtime: 32812,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
steamId: 2842,
|
steamId: 2842,
|
||||||
name: "BossOfThisGym",
|
name: "BossOfThisGym",
|
||||||
|
@ -142,14 +150,24 @@ export const useRosterStore = defineStore("roster", () => {
|
||||||
return availablePlayerRoles.value.filter((player) => player.availability == 1);
|
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(() => {
|
const mainRoles = computed(() => {
|
||||||
return availablePlayerRoles.value.filter((player) => player.main)
|
return availablePlayerRoles.value.filter((player) => player.main)
|
||||||
.sort((a, b) => b.playtime - a.playtime);
|
.sort(comparator);
|
||||||
});
|
});
|
||||||
|
|
||||||
const alternateRoles = computed(() => {
|
const alternateRoles = computed(() => {
|
||||||
return availablePlayerRoles.value.filter((player) => !player.main)
|
return availablePlayerRoles.value.filter((player) => !player.main)
|
||||||
.sort((a, b) => b.playtime - a.playtime);
|
.sort(comparator);
|
||||||
});
|
});
|
||||||
|
|
||||||
const roleIcons = reactive({
|
const roleIcons = reactive({
|
||||||
|
|
|
@ -24,11 +24,8 @@ const hasAlternates = computed(() => {
|
||||||
<em class="aside date">Aug. 13, 2036 @ 11:30 PM EST</em>
|
<em class="aside date">Aug. 13, 2036 @ 11:30 PM EST</em>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button>
|
<button>Cancel</button>
|
||||||
<i class="bi bi-box-arrow-left"></i>
|
<button class="accent">Save Roster</button>
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
<button class="accent">Submit</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
|
|
|
@ -1,8 +1,75 @@
|
||||||
<script setup lang="ts">
|
<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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<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>
|
</main>
|
||||||
</template>
|
</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