Compare commits

..

No commits in common. "9013e85ff0e2f318c2bfef98d204aafa315e39c1" and "345caec57f93c86d82228858d6b3fa484c683e48" have entirely different histories.

14 changed files with 14 additions and 233 deletions

View file

@ -29,6 +29,7 @@ defineProps<{
edit?: boolean
}>();
const usersStore = useUsersStore();
await usersStore.fetch();
const assignedIds = defineModel<Set<number>>({ required: true });
const assigned = computed(
() => [...assignedIds.value].map(

View file

@ -5,9 +5,6 @@
<template>
<section class="event">
<h3>{{ event.name }}</h3>
<p v-if=event.host>
Host: {{ event.host }}
</p>
<p>{{ event.description ?? "No description provided" }}</p>
<p v-if="event.interested">
{{ event.interested }} interested
@ -38,7 +35,7 @@
<template v-if="slot.interested">
({{ slot.interested }} interested)
</template>
<p v-if="slot.assigned.size">
<p v-if="slot.assigned">
Crew:
{{ [...slot.assigned].map(id => usersStore.users.get(id)?.name).join(", ") }}
</p>
@ -56,6 +53,7 @@ defineProps<{
const accountStore = useAccountStore();
const usersStore = useUsersStore();
await usersStore.fetch();
function formatTime(time: DateTime) {
return time.toFormat("yyyy-LL-dd HH:mm");

View file

@ -1,68 +0,0 @@
<!--
SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<section class="eventSlot">
<hgroup>
<h3>{{ event?.name }}</h3>
<p>
{{ formatTime(slot.start) }} - {{ formatTime(slot.end) }}
</p>
</hgroup>
<p>{{ event?.description ?? "No description provided" }}</p>
<p v-if="locations.length">
At {{ locations.map(location => location?.name ?? "unknown").join(" + ") }}
</p>
<p v-if="event?.interested">
{{ event?.interested }}
<template v-if="slot.interested">
+ {{ slot.interested }}
</template>
interested
</p>
<p v-if="slot.assigned.size">
Crew:
{{ [...slot.assigned].map(id => usersStore.users.get(id)?.name).join(", ") }}
</p>
</section>
</template>
<script lang="ts" setup>
import { DateTime } from '~/shared/utils/luxon';
const props = defineProps<{
event?: ClientScheduleEvent,
slot: ClientScheduleEventSlot,
}>()
const scheduleStore = useSchedulesStore();
const schedule = scheduleStore.activeSchedule;
const usersStore = useUsersStore();
const locations = computed(() => [...props.slot.locationIds].map(id => schedule.value.locations.get(id)));
function formatTime(time: DateTime) {
return time.toFormat("yyyy-LL-dd HH:mm");
}
</script>
<style scoped>
.eventSlot {
background: color-mix(in oklab, var(--background), grey 20%);
padding: 0.5rem;
border-radius: 0.5rem;
}
.eventSlot h3 {
margin: 0;
}
.eventSlot + .eventSlot {
margin-block-start: 0.5rem;
}
button {
padding-inline: 0.2em;
}
button.active {
color: color-mix(in oklab, var(--foreground), green 50%);
}
</style>

View file

@ -27,7 +27,9 @@ defineProps<{
shift: ClientScheduleShift
}>()
const accountStore = useAccountStore();
const usersStore = useUsersStore();
await usersStore.fetch();
function formatTime(time: DateTime) {
return time.toFormat("yyyy-LL-dd HH:mm");

View file

@ -11,12 +11,6 @@
:after="event.name"
:state
/>
<DiffFieldString
title="Host"
:before="event.serverHost"
:after="event.host"
:state
/>
<DiffFieldString
title="Public"
:before='event.serverCrew ? "No" : "Yes"'

View file

@ -9,9 +9,6 @@
<li>
<NuxtLink to="/">Home</NuxtLink>
</li>
<li>
<NuxtLink to="/events">Events</NuxtLink>
</li>
<li>
<NuxtLink to="/schedule">Schedule</NuxtLink>
</li>

View file

@ -215,6 +215,7 @@ interface Gap {
const accountStore = useAccountStore();
const usersStore = useUsersStore();
await usersStore.fetch();
const schedule = await useSchedule();
const oneDayMs = 24 * 60 * 60 * 1000;

View file

@ -9,7 +9,6 @@
<tr>
<th>id</th>
<th>name</th>
<th>host</th>
<th>description</th>
<th>p</th>
<th>s</th>
@ -31,13 +30,6 @@
v-model="event.name"
>
</td>
<td>
<input
type="text"
:disabled="!canEdit(event)"
v-model="event.host"
>
</td>
<td>
<input
type="text"
@ -109,7 +101,6 @@
>
<td>{{ event.id }}</td>
<td>{{ event.name }}</td>
<td>{{ event.host }}</td>
<td>{{ event.description }}</td>
<td>{{ event.crew ? "" : "Yes"}}</td>
<td>{{ event.slots.size ? event.slots.size : "" }}</td>

View file

@ -209,6 +209,7 @@ interface Gap {
const accountStore = useAccountStore();
const usersStore = useUsersStore();
await usersStore.fetch();
const schedule = await useSchedule();
const oneDayMs = 24 * 60 * 60 * 1000;

View file

@ -55,6 +55,7 @@
<script lang="ts" setup>
useEventSource();
const usersStore = useUsersStore();
await usersStore.fetch();
async function saveUser(user: ClientUser) {
try {

View file

@ -77,7 +77,6 @@
<td :colSpan="totalColumns">
<div
v-if="nowOffset !== undefined"
ref="nowLine"
class="now"
:style="` --now-offset: ${nowOffset}`"
>
@ -678,16 +677,6 @@ const nowOffset = computed(() => {
offset += group.width;
}
});
const nowLine = useTemplateRef("nowLine"); // If I name the ref now, the element disappears?!
function scrollToNow() {
if (nowLine.value) {
nowLine.value.scrollIntoView({ behavior: "smooth", inline: "start", block: "nearest" });
}
}
defineExpose({
scrollToNow,
});
</script>
<style scoped>

View file

@ -1,89 +0,0 @@
<!--
SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<main>
<h1>Events</h1>
<label v-if="accountStore.valid">
Filter:
<select
v-model="filter"
>
<option
:value="undefined"
:selected="filter === undefined"
>&lt;All events&gt;</option>
<option
value="my-schedule"
:selected='filter === "my-schedule"'
>My Schedule</option>
<option
v-if="accountStore.isCrew"
value="assigned"
:selected='filter === "assigned"'
>Assigned to Me</option>
</select>
</label>
<CardEvent
v-for="event in events"
:key="`my-${event.id}`"
:id="String(event.name)"
:event
></CardEvent>
</main>
</template>
<script setup lang="ts">
useHead({
title: "Schedule",
});
const stringSort = useStringSort();
const events = computed(() => {
return [...schedule.value.events.values()].filter(e => !e.deleted && [...e.slots.values()].some(eventSlotFilter.value)).sort((a, b) => stringSort(a.name, b.name));
});
const accountStore = useAccountStore();
const usersStore = useUsersStore();
await usersStore.fetch();
const schedule = await useSchedule();
const route = useRoute();
const filter = computed({
get: () => queryToString(route.query.filter),
set: (value: string | undefined) => navigateTo({
path: route.path,
query: {
...route.query,
filter: value,
},
}),
});
const eventSlotFilter = computed(() => {
if (filter.value === undefined || !accountStore.valid || schedule.value.deleted) {
return () => true;
}
const aid = accountStore.id;
if (filter.value === "my-schedule") {
const slotIds = new Set(accountStore.interestedEventSlotIds);
for (const event of schedule.value.events.values()) {
if (!event.deleted && accountStore.interestedEventIds.has(event.id)) {
for (const slot of event.slots.values()) {
slotIds.add(slot.id);
}
}
}
return (slot: ClientScheduleEventSlot) => slotIds.has(slot.id) || slot.assigned.has(aid!) || false;
}
if (filter.value === "assigned") {
return (slot: ClientScheduleEventSlot) => slot.assigned.has(aid!) || false;
}
if (filter.value.startsWith("crew-")) {
const cid = parseInt(filter.value.slice(5));
return (slot: ClientScheduleEventSlot) => slot.assigned.has(cid) || false;
}
return () => false;
});
</script>

View file

@ -4,7 +4,7 @@
-->
<template>
<main>
<h1>Schedule</h1>
<h1>Schedule & Events</h1>
<p>
Study carefully, we only hold these events once a year.
</p>
@ -44,17 +44,12 @@
</optgroup>
</select>
</label>
<Timetable ref="timetable" :schedule :eventSlotFilter :shiftSlotFilter />
<Timetable :schedule :eventSlotFilter :shiftSlotFilter />
<h2>Events</h2>
<label>
Hide past events
<input type="checkbox" v-model="hidePastEvents">
</label>
<CardEventSlot
v-for="eventSlot in eventSlots"
:key="eventSlot.slot.id"
:event="eventSlot.event"
:slot="eventSlot.slot"
<CardEvent
v-for="event in [...schedule.events.values()].filter(e => !e.deleted && [...e.slots.values()].some(eventSlotFilter))"
:key="event.id"
:event
/>
<template v-if="accountStore.isCrew">
<h2>Shifts</h2>
@ -96,17 +91,6 @@ const filter = computed({
}),
});
const hidePastEvents = computed({
get: () => !queryToBoolean(route.query.showPast),
set: (value: boolean) => navigateTo({
path: route.path,
query: {
...route.query,
showPast: value ? undefined : null,
},
}),
});
const eventSlotFilter = computed(() => {
if (filter.value === undefined || !accountStore.valid || schedule.value.deleted) {
return () => true;
@ -146,19 +130,4 @@ const shiftSlotFilter = computed(() => {
}
return () => false;
});
const eventSlots = computed(() => {
let slots = [...schedule.value.eventSlots.values()].filter(slot => !slot.deleted && eventSlotFilter.value(slot));
if (hidePastEvents.value) {
const nowMs = Date.now();
slots = slots.filter(slot => slot.end.toMillis() >= nowMs);
}
slots.sort((a, b) => a.start.toMillis() - b.start.toMillis() || a.end.toMillis() - b.end.toMillis());
return slots.map(slot => ({ slot, event: schedule.value.events.get(slot.eventId!) }));
});
const timetable = useTemplateRef("timetable");
onMounted(() => {
timetable.value?.scrollToNow();
});
</script>

View file

@ -21,12 +21,6 @@ export function queryToNumber(item?: null | LocationQueryValue | LocationQueryVa
return Number.parseInt(item, 10);
}
export function queryToBoolean(item?: null | LocationQueryValue | LocationQueryValue[]) {
if (item === undefined)
return false;
return true;
}
export function idMap<T extends { id: Id }>(entities: T[]) {
return new Map(entities.map(entity => [entity.id, entity]));
}