Compare commits

..

No commits in common. "732566a29c08d30101a343f79071bdaef4bfd901" and "52973ffa9aaebe07abcddd43c7d8e00d00ef1302" have entirely different histories.

23 changed files with 80 additions and 363 deletions

View file

@ -2,7 +2,7 @@
# SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no> # SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
# Based on Next.js's docker image example # Based on Next.js's docker image example
FROM node:22-slim AS base FROM node:22 AS base
# Install dependencies only when needed # Install dependencies only when needed
FROM base AS deps FROM base AS deps

View file

@ -83,7 +83,3 @@ label>* {
label + label { label + label {
margin-block-start: 0.5rem; margin-block-start: 0.5rem;
} }
.preWrap {
white-space: pre-wrap;
}

View file

@ -8,15 +8,7 @@
<p v-if=event.host> <p v-if=event.host>
Host: {{ event.host }} Host: {{ event.host }}
</p> </p>
<div v-if="event.notice" class="notice preWrap"> <p>{{ event.description ?? "No description provided" }}</p>
<div class="noticeIcon">
</div>
<p>
{{ event.notice }}
</p>
</div>
<p class="preWrap">{{ event.description ?? "No description provided" }}</p>
<p v-if="event.interested"> <p v-if="event.interested">
{{ event.interested }} interested {{ event.interested }} interested
</p> </p>
@ -87,22 +79,6 @@ async function toggle(type: "event" | "slot", id: number, slotIds?: number[]) {
margin-block-start: 0.5rem; margin-block-start: 0.5rem;
} }
.notice {
display: flex;
width: fit-content;
gap: 0.5rem;
padding: 0.5rem;
margin-block: 0.5rem;
border-radius: 0.25rem;
border: 1px solid color-mix(in oklab, CanvasText, orange 50%);
background-color: color-mix(in oklab, Canvas, orange 40%);
}
.noticeIcon {
flex: 0 0 auto;
align-self: center;
font-size: 1rem;
}
button { button {
padding-inline: 0.2em; padding-inline: 0.2em;
} }

View file

@ -13,15 +13,7 @@
<p v-if=event?.host> <p v-if=event?.host>
Host: {{ event.host }} Host: {{ event.host }}
</p> </p>
<div v-if="event?.notice" class="notice preWrap"> <p>{{ event?.description ?? "No description provided" }}</p>
<div class="noticeIcon">
</div>
<p>
{{ event.notice }}
</p>
</div>
<p class="preWrap">{{ event?.description ?? "No description provided" }}</p>
<p v-if="locations.length"> <p v-if="locations.length">
At {{ locations.map(location => location?.name ?? "unknown").join(" + ") }} At {{ locations.map(location => location?.name ?? "unknown").join(" + ") }}
</p> </p>
@ -70,22 +62,6 @@ function formatTime(time: DateTime) {
margin-block-start: 0.5rem; margin-block-start: 0.5rem;
} }
.notice {
display: flex;
width: fit-content;
gap: 0.5rem;
padding: 0.5rem;
margin-block: 0.5rem;
border-radius: 0.25rem;
border: 1px solid color-mix(in oklab, CanvasText, orange 50%);
background-color: color-mix(in oklab, Canvas, orange 40%);
}
.noticeIcon {
flex: 0 0 auto;
align-self: center;
font-size: 1rem;
}
button { button {
padding-inline: 0.2em; padding-inline: 0.2em;
} }

View file

@ -5,7 +5,7 @@
<template> <template>
<section class="shift"> <section class="shift">
<h3>{{ shift.name }}</h3> <h3>{{ shift.name }}</h3>
<p class="preWrap">{{ shift.description ?? "No description provided" }}</p> <p>{{ shift.description ?? "No description provided" }}</p>
<h4>Timeslots</h4> <h4>Timeslots</h4>
<ul> <ul>

View file

@ -15,14 +15,9 @@
:key="index" :key="index"
:class="type" :class="type"
> >
<div class="symbol">
{{ { "removed": "- ", "added": "+ "}[type] }}
</div>
<div class="content">
{{ text }} {{ text }}
</div> </div>
</div> </div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -38,23 +33,20 @@ defineProps<{
grid-template-columns: 5rem 1fr; grid-template-columns: 5rem 1fr;
column-gap: 1rem; column-gap: 1rem;
} }
:is(.removed, .added) {
display: flex;
grid-column: 2 / 2;
white-space: pre-wrap;
}
:is(.removed, .added) .symbol {
display: block;
font-family: monospace;
flex: 0 0 auto;
}
.removed { .removed {
grid-column: 2 / 2;
color: color-mix(in srgb, CanvasText, red 40%); color: color-mix(in srgb, CanvasText, red 40%);
} }
.removed .content{ .removed::before {
text-decoration-line: line-through; content: "- ";
font-family: monospace;
} }
.added { .added {
grid-column: 2 / 2;
color: color-mix(in srgb, CanvasText, green 40%); color: color-mix(in srgb, CanvasText, green 40%);
} }
.added::before {
content: "+ ";
font-family: monospace;
}
</style> </style>

View file

@ -318,7 +318,6 @@ function newEventSlot(options: { start?: DateTime, end?: DateTime } = {}) {
start, start,
end, end,
new Set(newEventLocationIds.value), new Set(newEventLocationIds.value),
false,
new Set(), new Set(),
0, 0,
); );

View file

@ -10,7 +10,6 @@
<th>id</th> <th>id</th>
<th>name</th> <th>name</th>
<th>host</th> <th>host</th>
<th>notice</th>
<th>description</th> <th>description</th>
<th>p</th> <th>p</th>
<th>s</th> <th>s</th>
@ -40,18 +39,11 @@
> >
</td> </td>
<td> <td>
<textarea <input
rows="1" type="text"
:disabled="!canEdit(event)"
v-model="event.notice"
/>
</td>
<td>
<textarea
rows="1"
:disabled="!canEdit(event)" :disabled="!canEdit(event)"
v-model="event.description" v-model="event.description"
/> >
</td> </td>
<td> <td>
<input <input
@ -86,20 +78,8 @@
<td> <td>
<input <input
type="text" type="text"
v-model="newEventHost"
>
</td>
<td>
<textarea
rows="1"
v-model="newEventNotice"
/>
</td>
<td>
<textarea
rows="1"
v-model="newEventDescription" v-model="newEventDescription"
/> >
</td> </td>
<td> <td>
<input <input
@ -130,8 +110,7 @@
<td>{{ event.id }}</td> <td>{{ event.id }}</td>
<td>{{ event.name }}</td> <td>{{ event.name }}</td>
<td>{{ event.host }}</td> <td>{{ event.host }}</td>
<td class="preWrap">{{ event.notice }}</td> <td>{{ event.description }}</td>
<td class="preWrap">{{ event.description }}</td>
<td>{{ event.crew ? "" : "Yes"}}</td> <td>{{ event.crew ? "" : "Yes"}}</td>
<td>{{ event.slots.size ? event.slots.size : "" }}</td> <td>{{ event.slots.size ? event.slots.size : "" }}</td>
</tr> </tr>
@ -157,8 +136,6 @@ function canEdit(event: ClientScheduleEvent) {
} }
const newEventName = ref(""); const newEventName = ref("");
const newEventHost = ref("");
const newEventNotice = ref("");
const newEventDescription = ref(""); const newEventDescription = ref("");
const newEventPublic = ref(false); const newEventPublic = ref(false);
function eventExists(name: string) { function eventExists(name: string) {
@ -179,9 +156,8 @@ function newEvent() {
schedule.value.nextClientId--, schedule.value.nextClientId--,
newEventName.value, newEventName.value,
!newEventPublic.value, !newEventPublic.value,
newEventHost.value, "",
false, false,
newEventNotice.value,
newEventDescription.value, newEventDescription.value,
0, 0,
new Set(), new Set(),
@ -189,8 +165,6 @@ function newEvent() {
); );
schedule.value.events.add(event); schedule.value.events.add(event);
newEventName.value = ""; newEventName.value = "";
newEventHost.value = "";
newEventNotice.value = "";
newEventDescription.value = ""; newEventDescription.value = "";
newEventPublic.value = false; newEventPublic.value = false;
} }

View file

@ -28,10 +28,10 @@
> >
</td> </td>
<td> <td>
<textarea <input
rows="1" type="text"
v-model="location.description" v-model="location.description"
/> >
</td> </td>
<td> <td>
<button <button
@ -49,7 +49,7 @@
<template v-else> <template v-else>
<td>{{ location.id }}</td> <td>{{ location.id }}</td>
<td>{{ location.name }}</td> <td>{{ location.name }}</td>
<td class="preWrap">{{ location.description }}</td> <td>{{ location.description }}</td>
</template> </template>
</tr> </tr>
<tr v-if='edit'> <tr v-if='edit'>
@ -63,10 +63,10 @@
> >
</td> </td>
<td> <td>
<textarea <input
rows="1" type="text"
v-model="newLocationDescription" v-model="newLocationDescription"
/> >
</td> </td>
<td colspan="1"> <td colspan="1">
<button <button

View file

@ -28,10 +28,10 @@
> >
</td> </td>
<td> <td>
<textarea <input
rows="1" type="text"
v-model="role.description" v-model="role.description"
/> >
</td> </td>
<td> <td>
<button <button
@ -55,10 +55,10 @@
> >
</td> </td>
<td> <td>
<textarea <input
rows="1" type="text"
v-model="newRoleDescription" v-model="newRoleDescription"
/> >
</td> </td>
<td> <td>
<button <button
@ -80,7 +80,7 @@
> >
<td>{{ role.id }}</td> <td>{{ role.id }}</td>
<td>{{ role.name }}</td> <td>{{ role.name }}</td>
<td class="preWrap">{{ role.description }}</td> <td>{{ role.description }}</td>
</tr> </tr>
</template> </template>
</tbody> </tbody>

View file

@ -37,10 +37,10 @@
</td> </td>
<td>{{ shift.slots.size ? shift.slots.size : "" }}</td> <td>{{ shift.slots.size ? shift.slots.size : "" }}</td>
<td> <td>
<textarea <input
rows="1" type="text"
v-model="shift.description" v-model="shift.description"
/> >
</td> </td>
<td> <td>
<button <button
@ -71,10 +71,10 @@
</td> </td>
<td></td> <td></td>
<td> <td>
<textarea <input
rows="1" type="text"
v-model="newShiftDescription" v-model="newShiftDescription"
/> >
</td> </td>
<td> <td>
<button <button
@ -98,7 +98,7 @@
<td>{{ shift.name }}</td> <td>{{ shift.name }}</td>
<td>{{ shift.roleId }}</td> <td>{{ shift.roleId }}</td>
<td>{{ shift.slots.size ? shift.slots.size : "" }}</td> <td>{{ shift.slots.size ? shift.slots.size : "" }}</td>
<td class="preWrap">{{ shift.description }}</td> <td>{{ shift.description }}</td>
</tr> </tr>
</template> </template>
</tbody> </tbody>

View file

@ -17,12 +17,7 @@
<tr v-for="user in usersStore.users.values()"> <tr v-for="user in usersStore.users.values()">
<td>{{ user.id }}</td> <td>{{ user.id }}</td>
<td> <td>
<NuxtLink :to="`/admin/users/${user.id}`">
<template v-if="user.name">
{{ user.name }} {{ user.name }}
</template>
<i v-else>(empty)</i>
</NuxtLink>
</td> </td>
<td> <td>
<select <select
@ -44,7 +39,7 @@
<button <button
v-if="user.isModified()" v-if="user.isModified()"
type="button" type="button"
@click="usersStore.saveUser(user);" @click="saveUser(user);"
>Save</button> >Save</button>
<button <button
v-if="user.isModified()" v-if="user.isModified()"
@ -60,6 +55,18 @@
<script lang="ts" setup> <script lang="ts" setup>
useEventSource(); useEventSource();
const usersStore = useUsersStore(); const usersStore = useUsersStore();
async function saveUser(user: ClientUser) {
try {
await $fetch("/api/admin/user", {
method: "PATCH",
body: user.toApi(),
});
} catch (err: any) {
console.error(err);
alert(err?.data?.message ?? err.message);
}
}
</script> </script>
<style> <style>

View file

@ -105,7 +105,6 @@
:class='{"event": cell.slot, "crew": cell.event?.crew }' :class='{"event": cell.slot, "crew": cell.event?.crew }'
:title="cell.event?.name" :title="cell.event?.name"
> >
{{ cell.event?.notice ? "⚠️" : undefined }}
{{ cell.event?.name }} {{ cell.event?.name }}
</td> </td>
</tr> </tr>

View file

@ -1,94 +0,0 @@
<template>
<main v-if="userDetails.deleted">
<h1>Deleted user {{ id }}</h1>
</main>
<main v-else>
<h1>User {{ user.name }}</h1>
<dl>
<dt>
<label for="user-type">
Type
</label>
</dt>
<dd>
<select
v-if='user.type !== "anonymous"'
v-model="user.type"
>
<option value="regular">Regular</option>
<option value="crew">Crew</option>
<option value="admin">Admin</option>
</select>
<template v-else>
{{ user.type }}
</template>
</dd>
<dt>Interested Events:</dt>
<dd>{{ userDetails.interestedEventIds }}</dd>
<dt>Interested Slots:</dt>
<dd>{{ userDetails.interestedEventSlotIds }}</dd>
<dt>Timezone:</dt>
<dd>{{ userDetails.timezone }}</dd>
<dt>Locale:</dt>
<dd>{{ userDetails.locale }}</dd>
</dl>
<button
:disabled="!user.isModified()"
type="button"
@click="usersStore.saveUser(user);"
>Save</button>
<button
:disabled="!user.isModified()"
type="button"
@click="user.discard()"
>Discard</button>
</main>
</template>
<script lang="ts" setup>
import type { ApiTombstone, ApiUserDetails } from '~/shared/types/api';
useHead({
title: "Admin",
});
useEventSource();
const route = useRoute();
const usersStore = useUsersStore();
await usersStore.fetch();
const id = computed(() => {
const id = queryToNumber(route.params.id);
if (id === undefined) {
throw createError({
statusCode: 400,
statusMessage: "Bad Request",
message: "User id required",
});
}
return id;
});
const user = computed(() => {
const user = usersStore.users.get(id.value);
if (user === undefined) {
throw createError({
statusCode: 404,
statusMessage: "Not Found",
message: "User not found",
});
}
return user;
});
const { pending, data, error } = await useFetch(() => `/api/users/${id.value}/details`);
const userDetails = data as Ref<ApiUserDetails | ApiTombstone>;
</script>
<style>
dl {
display: grid;
grid-template-columns: auto 1fr;
column-gap: 0.5rem;
}
</style>

View file

@ -1,34 +0,0 @@
import { z } from "zod/v4-mini";
import { readUsers } from "~/server/database";
import { serverUserToApiDetails } from "~/server/utils/user";
const integerStringSchema = z.pipe(
z.string().check(z.regex(/^\d+/)),
z.transform(Number)
);
const detailsSchema = z.object({
id: integerStringSchema,
});
export default defineEventHandler(async (event) => {
await requireServerSessionWithAdmin(event);
const users = await readUsers();
const { success, error, data: params } = detailsSchema.safeParse(getRouterParams(event));
if (!success) {
throw createError({
status: 400,
statusText: "Bad Request",
message: z.prettifyError(error),
});
}
const user = users.find(user => user.id === params.id);
if (!user) {
throw createError({
statusCode: 404,
statusMessage: "Not found",
});
}
return serverUserToApiDetails(user);
})

View file

@ -9,7 +9,7 @@ const locations = [
{ {
id: 1, id: 1,
name: "Stage", name: "Stage",
description: "Inside the main building.\n\nMind the gap.", description: "Inside the main building.",
updatedAt: "d-1 18:21", updatedAt: "d-1 18:21",
}, },
{ {
@ -43,8 +43,7 @@ let eventId = 1;
const events = [ const events = [
{ {
name: "Arcade", name: "Arcade",
notice: "No food or drinks allowed!\n\nClosed drinking containers are okay.", description: "Play retro games!",
description: "Play retro games!\n\nWe have anything from C64 to PS5.",
slots: [ slots: [
"d1 12:00 4h clubhouse", "d1 12:00 4h clubhouse",
"d2 12:00 4h clubhouse", "d2 12:00 4h clubhouse",
@ -159,14 +158,13 @@ const events = [
const idMedic = 1; const idMedic = 1;
const idSecurity = 2; const idSecurity = 2;
const roles = [ const roles = [
{ id: idMedic, name: "Medic", description: "Helping those in need.\n\nAsk lead if in doubt.", updatedAt: "d-2 12:34" }, { id: idMedic, name: "Medic", updatedAt: "d-2 12:34" },
{ id: idSecurity, name: "Security", description: "Keeping the con safe", updatedAt: "d-2 12:39" }, { id: idSecurity, name: "Security", updatedAt: "d-2 12:39" },
] ]
const shifts = [ const shifts = [
{ {
name: "Medic Early", name: "Medic Early",
description: "Not much happens early.\n\nPrefer strolling outside to be visible.",
roleId: idMedic, roleId: idMedic,
slots: [ slots: [
"d1 12:00 4h", "d1 12:00 4h",
@ -340,11 +338,10 @@ export function generateDemoSchedule(): ApiSchedule {
id: 111, id: 111,
updatedAt: toIso(toDate(origin, "d-2", "10:01")), updatedAt: toIso(toDate(origin, "d-2", "10:01")),
events: events.map( events: events.map(
({ id, name, crew, notice, description, slots }) => ({ ({ id, name, crew, description, slots }) => ({
id, id,
name, name,
crew, crew,
notice,
description, description,
interested: eventCounts.get(id), interested: eventCounts.get(id),
slots: slots.map(shorthand => toSlot(origin, shorthand, slotCounts, eventSlotIdToAssigned)), slots: slots.map(shorthand => toSlot(origin, shorthand, slotCounts, eventSlotIdToAssigned)),
@ -360,18 +357,16 @@ export function generateDemoSchedule(): ApiSchedule {
}) })
), ),
roles: roles.map( roles: roles.map(
({ id, name, description, updatedAt }) => ({ ({ id, name, updatedAt }) => ({
id, id,
name, name,
description,
updatedAt: toIso(toDate(origin, ...(updatedAt.split(" ")) as [string, string])), updatedAt: toIso(toDate(origin, ...(updatedAt.split(" ")) as [string, string])),
}) })
), ),
shifts: shifts.map( shifts: shifts.map(
({ id, name, description, roleId, slots }) => ({ ({ id, name, roleId, slots }) => ({
id, id,
name, name,
description,
roleId, roleId,
slots: slots.map(shorthand => toShift(origin, shorthand, shiftSlotIdToAssigned)), slots: slots.map(shorthand => toShift(origin, shorthand, shiftSlotIdToAssigned)),
updatedAt: toIso(toDate(origin, "d-1", "13:23")), updatedAt: toIso(toDate(origin, "d-1", "13:23")),

View file

@ -15,7 +15,6 @@ import {
} from "~/server/database"; } from "~/server/database";
import { broadcastEvent } from "../streams"; import { broadcastEvent } from "../streams";
import type { ApiAuthenticationProvider, ApiSession } from "~/shared/types/api"; import type { ApiAuthenticationProvider, ApiSession } from "~/shared/types/api";
import { serverUserToApiAccount } from "./user";
async function removeSessionSubscription(sessionId: number) { async function removeSessionSubscription(sessionId: number) {
const subscriptions = await readSubscriptions(); const subscriptions = await readSubscriptions();
@ -183,7 +182,7 @@ export async function serverSessionToApi(event: H3Event, session: ServerSession)
return { return {
id: session.id, id: session.id,
account: serverUserToApiAccount(account), account,
authenticationProvider: session.authenticationProvider, authenticationProvider: session.authenticationProvider,
authenticationName: session.authenticationName, authenticationName: session.authenticationName,
push, push,

View file

@ -3,7 +3,7 @@
SPDX-License-Identifier: AGPL-3.0-or-later SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
import type { ServerUser } from "~/server/database" import type { ServerUser } from "~/server/database"
import type { ApiAccount, ApiTombstone, ApiUser, ApiUserDetails } from "~/shared/types/api"; import type { ApiTombstone, ApiUser } from "~/shared/types/api";
export function serverUserToApi(user: ServerUser): ApiUser | ApiTombstone { export function serverUserToApi(user: ServerUser): ApiUser | ApiTombstone {
if (user.deleted) { if (user.deleted) {
@ -20,37 +20,3 @@ export function serverUserToApi(user: ServerUser): ApiUser | ApiTombstone {
name: user.name, name: user.name,
} }
} }
export function serverUserToApiDetails(user: ServerUser): ApiUserDetails | ApiTombstone {
if (user.deleted) {
return {
id: user.id,
updatedAt: user.updatedAt,
deleted: true,
}
}
return {
id: user.id,
updatedAt: user.updatedAt,
interestedEventIds: user.interestedEventIds,
interestedEventSlotIds: user.interestedEventSlotIds,
timezone: user.timezone,
locale: user.locale,
}
}
export function serverUserToApiAccount(user?: ServerUser): ApiAccount | undefined {
if (!user || user.deleted) {
return undefined;
}
return {
id: user.id,
updatedAt: user.updatedAt,
type: user.type,
name: user.name,
interestedEventIds: user.interestedEventIds,
interestedEventSlotIds: user.interestedEventSlotIds,
timezone: user.timezone,
locale: user.locale,
}
}

View file

@ -35,7 +35,17 @@ export const apiUserTypeSchema = z.union([
]) ])
export type ApiUserType = z.infer<typeof apiUserTypeSchema>; export type ApiUserType = z.infer<typeof apiUserTypeSchema>;
export type ApiAccount = ApiUser & ApiUserDetails export interface ApiAccount {
id: Id,
updatedAt: string,
type: ApiUserType,
/** Name of the account. Not present on anonymous accounts */
name?: string,
interestedEventIds?: number[],
interestedEventSlotIds?: number[],
timezone?: string,
locale?: string,
}
export const apiAccountPatchSchema = z.object({ export const apiAccountPatchSchema = z.object({
name: z.optional(z.string()), name: z.optional(z.string()),
@ -81,7 +91,6 @@ export const apiScheduleEventSlotSchema = z.object({
start: z.string(), start: z.string(),
end: z.string(), end: z.string(),
locationIds: z.array(idSchema), locationIds: z.array(idSchema),
cancelled: z.optional(z.boolean()),
assigned: z.optional(z.array(z.number())), assigned: z.optional(z.array(z.number())),
interested: z.optional(z.number()), interested: z.optional(z.number()),
}); });
@ -92,7 +101,6 @@ export const apiScheduleEventSchema = defineApiEntity({
crew: z.optional(z.boolean()), crew: z.optional(z.boolean()),
host: z.optional(z.string()), host: z.optional(z.string()),
cancelled: z.optional(z.boolean()), cancelled: z.optional(z.boolean()),
notice: z.optional(z.string()),
description: z.optional(z.string()), description: z.optional(z.string()),
interested: z.optional(z.number()), interested: z.optional(z.number()),
slots: z.array(apiScheduleEventSlotSchema), slots: z.array(apiScheduleEventSlotSchema),
@ -143,16 +151,6 @@ export const apiUserPatchSchema = z.object({
}); });
export type ApiUserPatch = z.infer<typeof apiUserPatchSchema>; export type ApiUserPatch = z.infer<typeof apiUserPatchSchema>;
export interface ApiUserDetails {
id: Id,
updatedAt: string,
deleted?: false,
interestedEventIds?: number[],
interestedEventSlotIds?: number[],
timezone?: string,
locale?: string,
}
export interface ApiAccountUpdate { export interface ApiAccountUpdate {
type: "account-update", type: "account-update",
data: ApiAccount, data: ApiAccount,

View file

@ -62,17 +62,6 @@ export const useUsersStore = defineStore("users", () => {
state.fetched.value = false; state.fetched.value = false;
await actions.fetch(); await actions.fetch();
}, },
async saveUser(user: ClientUser) {
try {
await $fetch("/api/admin/user", {
method: "PATCH",
body: user.toApi(),
});
} catch (err: any) {
console.error(err);
alert(err?.data?.message ?? err.message);
}
},
} }
appEventSource?.addEventListener("update", (event) => { appEventSource?.addEventListener("update", (event) => {

View file

@ -21,15 +21,15 @@ function fixtureClientSchedule(multiSlot = false) {
const events = [ const events = [
new ClientScheduleEvent( new ClientScheduleEvent(
1, now, false, "Up", false, "", false, "", "What's Up?", 0, new Set(multiSlot ? [1, 2] : [1]), 1, now, false, "Up", false, "", false, "What's Up?", 0, new Set(multiSlot ? [1, 2] : [1]),
), ),
new ClientScheduleEvent( new ClientScheduleEvent(
2, now, false, "Down", false, "", false, "", "", 0, new Set(multiSlot ? [] : [2]), 2, now, false, "Down", false, "", false, "", 0, new Set(multiSlot ? [] : [2]),
), ),
]; ];
const eventSlots = idMap([ const eventSlots = idMap([
new ClientScheduleEventSlot(1, false, false, 1, now, now.plus({ hours: 1 }), new Set([left.id]), false, new Set(), 0), new ClientScheduleEventSlot(1, false, false, 1, now, now.plus({ hours: 1 }), new Set([left.id]), new Set(), 0),
new ClientScheduleEventSlot(2, false, false, multiSlot ? 1 : 2, now, now.plus({ hours: 2 }), new Set([right.id]), false, new Set(), 0), new ClientScheduleEventSlot(2, false, false, multiSlot ? 1 : 2, now, now.plus({ hours: 2 }), new Set([right.id]), new Set(), 0),
]); ]);
const red = new ClientScheduleRole(1, now, false, "Red", "Is a color."); const red = new ClientScheduleRole(1, now, false, "Red", "Is a color.");
@ -174,7 +174,7 @@ describe("class ClientSchedule", () => {
], ],
[ [
"event", "event",
(schedule) => ClientScheduleEvent.create(schedule, 3, "New location", false, "", false, "", "", 0, new Set(), { zone, locale }) (schedule) => ClientScheduleEvent.create(schedule, 3, "New location", false, "", false, "", 0, new Set(), { zone, locale })
], ],
[ [
"role", "role",
@ -277,7 +277,6 @@ describe("class ClientSchedule", () => {
now, now,
now.plus({ hours: 3 }), now.plus({ hours: 3 }),
new Set(), new Set(),
false,
new Set(), new Set(),
0, 0,
); );

View file

@ -136,7 +136,6 @@ export class ClientScheduleEvent extends ClientEntity<ApiScheduleEvent> {
serverCrew: boolean; serverCrew: boolean;
serverHost: string; serverHost: string;
serverCancelled: boolean; serverCancelled: boolean;
serverNotice: string;
serverDescription: string; serverDescription: string;
serverInterested: number; serverInterested: number;
serverSlotIds: Set<Id>; serverSlotIds: Set<Id>;
@ -149,7 +148,6 @@ export class ClientScheduleEvent extends ClientEntity<ApiScheduleEvent> {
public crew: boolean, public crew: boolean,
public host: string, public host: string,
public cancelled: boolean, public cancelled: boolean,
public notice: string,
public description: string, public description: string,
public interested: number, public interested: number,
public slotIds: Set<Id>, public slotIds: Set<Id>,
@ -159,7 +157,6 @@ export class ClientScheduleEvent extends ClientEntity<ApiScheduleEvent> {
this.serverCrew = crew; this.serverCrew = crew;
this.serverHost = host; this.serverHost = host;
this.serverCancelled = cancelled; this.serverCancelled = cancelled;
this.serverNotice = notice;
this.serverDescription = description; this.serverDescription = description;
this.serverInterested = interested; this.serverInterested = interested;
this.serverSlotIds = new Set(slotIds); this.serverSlotIds = new Set(slotIds);
@ -176,7 +173,6 @@ export class ClientScheduleEvent extends ClientEntity<ApiScheduleEvent> {
|| this.crew !== this.serverCrew || this.crew !== this.serverCrew
|| this.host !== this.serverHost || this.host !== this.serverHost
|| this.cancelled !== this.serverCancelled || this.cancelled !== this.serverCancelled
|| this.notice !== this.serverNotice
|| this.description !== this.serverDescription || this.description !== this.serverDescription
|| this.interested !== this.serverInterested || this.interested !== this.serverInterested
|| !setEquals(this.slotIds, this.serverSlotIds) || !setEquals(this.slotIds, this.serverSlotIds)
@ -194,7 +190,6 @@ export class ClientScheduleEvent extends ClientEntity<ApiScheduleEvent> {
this.crew = this.serverCrew; this.crew = this.serverCrew;
this.host = this.serverHost; this.host = this.serverHost;
this.cancelled = this.serverCancelled; this.cancelled = this.serverCancelled;
this.notice = this.serverNotice;
this.description = this.serverDescription; this.description = this.serverDescription;
this.interested = this.serverInterested; this.interested = this.serverInterested;
for (const id of this.serverSlotIds) { for (const id of this.serverSlotIds) {
@ -213,7 +208,6 @@ export class ClientScheduleEvent extends ClientEntity<ApiScheduleEvent> {
crew: boolean, crew: boolean,
host: string, host: string,
cancelled: boolean, cancelled: boolean,
notice: string,
description: string, description: string,
interested: number, interested: number,
slotIds: Set<Id>, slotIds: Set<Id>,
@ -227,7 +221,6 @@ export class ClientScheduleEvent extends ClientEntity<ApiScheduleEvent> {
crew, crew,
host, host,
cancelled, cancelled,
notice,
description, description,
interested, interested,
slotIds, slotIds,
@ -248,7 +241,6 @@ export class ClientScheduleEvent extends ClientEntity<ApiScheduleEvent> {
api.crew ?? false, api.crew ?? false,
api.host ?? "", api.host ?? "",
api.cancelled ?? false, api.cancelled ?? false,
api.notice ?? "",
api.description ?? "", api.description ?? "",
api.interested ?? 0, api.interested ?? 0,
new Set(api.slots.map(slot => slot.id)), new Set(api.slots.map(slot => slot.id)),
@ -266,7 +258,6 @@ export class ClientScheduleEvent extends ClientEntity<ApiScheduleEvent> {
this.serverCrew = api.crew ?? false; this.serverCrew = api.crew ?? false;
this.serverHost = api.host ?? ""; this.serverHost = api.host ?? "";
this.serverCancelled = api.cancelled ?? false; this.serverCancelled = api.cancelled ?? false;
this.serverNotice = api.notice ?? "";
this.serverDescription = api.description ?? ""; this.serverDescription = api.description ?? "";
this.serverInterested = api.interested ?? 0; this.serverInterested = api.interested ?? 0;
this.serverSlotIds = new Set(api.slots.map(slot => slot.id)); this.serverSlotIds = new Set(api.slots.map(slot => slot.id));
@ -290,7 +281,6 @@ export class ClientScheduleEvent extends ClientEntity<ApiScheduleEvent> {
crew: this.crew || undefined, crew: this.crew || undefined,
host: this.host || undefined, host: this.host || undefined,
cancelled: this.cancelled || undefined, cancelled: this.cancelled || undefined,
notice: this.notice || undefined,
description: this.description || undefined, description: this.description || undefined,
interested: this.interested || undefined, interested: this.interested || undefined,
slots: [...this.slots.values()].filter(slot => !slot.deleted).map(slot => slot.toApi()), slots: [...this.slots.values()].filter(slot => !slot.deleted).map(slot => slot.toApi()),
@ -305,7 +295,6 @@ export class ClientScheduleEventSlot {
serverStart: DateTime; serverStart: DateTime;
serverEnd: DateTime; serverEnd: DateTime;
serverLocationIds: Set<Id>; serverLocationIds: Set<Id>;
serverCancelled: boolean;
serverAssigned: Set<Id>; serverAssigned: Set<Id>;
serverInterested: number; serverInterested: number;
@ -317,7 +306,6 @@ export class ClientScheduleEventSlot {
public start: DateTime, public start: DateTime,
public end: DateTime, public end: DateTime,
public locationIds: Set<Id>, public locationIds: Set<Id>,
public cancelled: boolean,
public assigned: Set<Id>, public assigned: Set<Id>,
public interested: number, public interested: number,
) { ) {
@ -326,7 +314,6 @@ export class ClientScheduleEventSlot {
this.serverStart = start; this.serverStart = start;
this.serverEnd = end; this.serverEnd = end;
this.serverLocationIds = new Set(locationIds); this.serverLocationIds = new Set(locationIds);
this.serverCancelled = cancelled;
this.serverAssigned = new Set(assigned); this.serverAssigned = new Set(assigned);
this.serverInterested = interested; this.serverInterested = interested;
} }
@ -340,7 +327,6 @@ export class ClientScheduleEventSlot {
|| this.start.toMillis() !== this.serverStart.toMillis() || this.start.toMillis() !== this.serverStart.toMillis()
|| this.end.toMillis() !== this.serverEnd.toMillis() || this.end.toMillis() !== this.serverEnd.toMillis()
|| !setEquals(this.locationIds, this.serverLocationIds) || !setEquals(this.locationIds, this.serverLocationIds)
|| this.cancelled !== this.serverCancelled
|| !setEquals(this.assigned, this.serverAssigned) || !setEquals(this.assigned, this.serverAssigned)
|| this.interested !== this.serverInterested || this.interested !== this.serverInterested
); );
@ -363,7 +349,6 @@ export class ClientScheduleEventSlot {
this.start = this.serverStart; this.start = this.serverStart;
this.end = this.serverEnd; this.end = this.serverEnd;
this.locationIds = new Set(this.serverLocationIds); this.locationIds = new Set(this.serverLocationIds);
this.cancelled = this.serverCancelled;
this.assigned = new Set(this.serverAssigned); this.assigned = new Set(this.serverAssigned);
this.interested = this.serverInterested; this.interested = this.serverInterested;
} }
@ -375,7 +360,6 @@ export class ClientScheduleEventSlot {
start: DateTime, start: DateTime,
end: DateTime, end: DateTime,
locationIds: Set<Id>, locationIds: Set<Id>,
cancelled: boolean,
assigned: Set<Id>, assigned: Set<Id>,
interested: number, interested: number,
) { ) {
@ -387,7 +371,6 @@ export class ClientScheduleEventSlot {
start, start,
end, end,
locationIds, locationIds,
cancelled,
assigned, assigned,
interested, interested,
); );
@ -408,7 +391,6 @@ export class ClientScheduleEventSlot {
DateTime.fromISO(api.start, opts), DateTime.fromISO(api.start, opts),
DateTime.fromISO(api.end, opts), DateTime.fromISO(api.end, opts),
new Set(api.locationIds), new Set(api.locationIds),
api.cancelled ?? false,
new Set(api.assigned), new Set(api.assigned),
api.interested ?? 0, api.interested ?? 0,
); );
@ -426,7 +408,6 @@ export class ClientScheduleEventSlot {
this.serverStart = DateTime.fromISO(api.start, opts); this.serverStart = DateTime.fromISO(api.start, opts);
this.serverEnd = DateTime.fromISO(api.end, opts); this.serverEnd = DateTime.fromISO(api.end, opts);
this.serverLocationIds = new Set(api.locationIds); this.serverLocationIds = new Set(api.locationIds);
this.serverCancelled = api.cancelled ?? false;
this.serverAssigned = new Set(api.assigned); this.serverAssigned = new Set(api.assigned);
this.serverInterested = api.interested ?? 0; this.serverInterested = api.interested ?? 0;
if (!wasModified || !this.isModified()) { if (!wasModified || !this.isModified()) {
@ -443,7 +424,6 @@ export class ClientScheduleEventSlot {
start: toIso(this.start), start: toIso(this.start),
end: toIso(this.end), end: toIso(this.end),
locationIds: [...this.locationIds], locationIds: [...this.locationIds],
cancelled: this.cancelled || undefined,
assigned: this.assigned.size ? [...this.assigned] : undefined, assigned: this.assigned.size ? [...this.assigned] : undefined,
interested: this.interested || undefined, interested: this.interested || undefined,
} }