Compare commits
No commits in common. "732566a29c08d30101a343f79071bdaef4bfd901" and "52973ffa9aaebe07abcddd43c7d8e00d00ef1302" have entirely different histories.
732566a29c
...
52973ffa9a
23 changed files with 80 additions and 363 deletions
|
@ -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
|
||||||
|
|
|
@ -83,7 +83,3 @@ label>* {
|
||||||
label + label {
|
label + label {
|
||||||
margin-block-start: 0.5rem;
|
margin-block-start: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preWrap {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -15,12 +15,7 @@
|
||||||
:key="index"
|
:key="index"
|
||||||
:class="type"
|
:class="type"
|
||||||
>
|
>
|
||||||
<div class="symbol">
|
{{ text }}
|
||||||
{{ { "removed": "- ", "added": "+ "}[type] }}
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
{{ text }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}`">
|
{{ user.name }}
|
||||||
<template v-if="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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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);
|
|
||||||
})
|
|
|
@ -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")),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue