Display time in timezone configured on the account
Use the timezone configured on the account, or the default timezone if no timezone is confirude to display the timetable and events in local time.
This commit is contained in:
parent
1ac607a712
commit
41528e8193
2 changed files with 121 additions and 67 deletions
|
@ -18,7 +18,7 @@
|
||||||
<h4>Timeslots</h4>
|
<h4>Timeslots</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="slot in event.slots" :key="slot.id">
|
<li v-for="slot in event.slots" :key="slot.id">
|
||||||
{{ slot.start }} - {{ slot.end }}
|
{{ formatTime(slot.start) }} - {{ formatTime(slot.end) }}
|
||||||
<button
|
<button
|
||||||
v-if="session && event.slots.length > 1"
|
v-if="session && event.slots.length > 1"
|
||||||
class="interested"
|
class="interested"
|
||||||
|
@ -37,14 +37,21 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
import type { ScheduleEvent } from '~/shared/types/schedule';
|
import type { ScheduleEvent } from '~/shared/types/schedule';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
event: ScheduleEvent
|
event: ScheduleEvent
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { data: session, refresh: refreshSession } = useAccountSession();
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
const { data: session, refresh: refreshSession } = await useAccountSession();
|
||||||
const interestedIds = computed(() => new Set(session.value?.account.interestedIds ?? []));
|
const interestedIds = computed(() => new Set(session.value?.account.interestedIds ?? []));
|
||||||
|
const timezone = computed(() => session.value?.account.timezone ?? runtimeConfig.public.defaultTimezone);
|
||||||
|
|
||||||
|
function formatTime(time: string) {
|
||||||
|
return DateTime.fromISO(time, { zone: timezone.value }).toFormat("yyyy-LL-dd HH:mm");
|
||||||
|
}
|
||||||
|
|
||||||
async function toggle(id: string, slotIds?: string[]) {
|
async function toggle(id: string, slotIds?: string[]) {
|
||||||
let newIds = [...session.value!.account.interestedIds ?? []];
|
let newIds = [...session.value!.account.interestedIds ?? []];
|
||||||
|
|
|
@ -2,27 +2,37 @@
|
||||||
<figure class="timetable">
|
<figure class="timetable">
|
||||||
<details>
|
<details>
|
||||||
<summary>Debug</summary>
|
<summary>Debug</summary>
|
||||||
<p><b>Junctions</b></p>
|
<details>
|
||||||
<div v-for="j in junctions" :key="j.ts">
|
<summary><b>Junctions</b></summary>
|
||||||
{{ j.ts }}: {{ j.edges.map(e => `${e.type} ${e.slot.id}`).join(", ") }}
|
<div v-for="j in junctions" :key="j.ts">
|
||||||
</div>
|
{{ j.ts }}: {{ j.edges.map(e => `${e.type} ${e.slot.id}`).join(", ") }}
|
||||||
<p><b>Stretches</b></p>
|
</div>
|
||||||
<ol>
|
</details>
|
||||||
<li v-for="st in stretches" :key="st.start">
|
<details>
|
||||||
<p>Stretch from {{ st.start }} to {{ st.end }}.</p>
|
<summary><b>Stretches</b></summary>
|
||||||
<p>Spans:</p>
|
<ol>
|
||||||
<ul>
|
<li v-for="st in stretches" :key="st.start">
|
||||||
<li v-for="s in st.spans" :key="s.start.ts">
|
<p>Stretch from {{ st.start }} to {{ st.end }}.</p>
|
||||||
{{ s.start.ts }} - {{ s.end.ts }}:
|
<p>Spans:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="[id, slots] in s.locations" :key="id">
|
<li v-for="s in st.spans" :key="s.start.ts">
|
||||||
{{ id }}: {{ [...slots].map(s => s.id).join(", ") }}
|
{{ s.start.ts }} - {{ s.end.ts }}:
|
||||||
</li>
|
<ul>
|
||||||
</ul>
|
<li v-for="[id, slots] in s.locations" :key="id">
|
||||||
</li>
|
{{ id }}: {{ [...slots].map(s => s.id).join(", ") }}
|
||||||
</ul>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
</ol>
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</details>
|
||||||
|
<p>
|
||||||
|
<label>
|
||||||
|
Timezone:
|
||||||
|
<input type="text" v-model="timezone">
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
</details>
|
</details>
|
||||||
<table>
|
<table>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
|
@ -63,6 +73,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { DateTime } from "luxon";
|
||||||
import type { ScheduleEvent, ScheduleLocation, TimeSlot } from "~/shared/types/schedule";
|
import type { ScheduleEvent, ScheduleLocation, TimeSlot } from "~/shared/types/schedule";
|
||||||
|
|
||||||
const oneDayMs = 24 * 60 * 60 * 1000;
|
const oneDayMs = 24 * 60 * 60 * 1000;
|
||||||
|
@ -204,27 +215,10 @@ function* spansFromJunctions(
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStretch(spans: Span[]): Stretch {
|
function createStretch(spans: Span[]): Stretch {
|
||||||
let start = spans[0].start.ts - oneHourMs;
|
|
||||||
let end = spans[spans.length - 1].end.ts + oneHourMs;
|
|
||||||
// Extend stretch to nearest whole hours
|
|
||||||
start = Math.floor(start / oneHourMs) * oneHourMs;
|
|
||||||
end = Math.ceil(end / oneHourMs) * oneHourMs;
|
|
||||||
return {
|
return {
|
||||||
spans: [
|
spans,
|
||||||
{
|
start: spans[0].start.ts,
|
||||||
start: { ts: start, edges: [] },
|
end: spans[spans.length - 1].end.ts,
|
||||||
end: spans[0].start,
|
|
||||||
locations: new Map(),
|
|
||||||
},
|
|
||||||
...spans,
|
|
||||||
{
|
|
||||||
start: spans[spans.length - 1].end,
|
|
||||||
end: { ts: end, edges: [] },
|
|
||||||
locations: new Map(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,41 +241,76 @@ function* stretchesFromSpans(spans: Iterable<Span>, minSeparation: number): Gene
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cuts up a span by whole hours that crosses it */
|
/** Cuts up a span by whole hours that crosses it */
|
||||||
function* cutSpansByHours(span: Span): Generator<Span> {
|
function* cutSpansByHours(span: Span, timezone: string): Generator<Span> {
|
||||||
const startHour = span.start.ts / oneHourMs;
|
const startHour = DateTime.fromMillis(span.start.ts, { zone: timezone })
|
||||||
const endHour = span.end.ts / oneHourMs;
|
.startOf("hour")
|
||||||
|
;
|
||||||
|
const end = span.end.ts;
|
||||||
|
|
||||||
let currentStart = startHour;
|
let currentStart = startHour;
|
||||||
let currentEnd = Math.min(Math.floor(startHour + 1), endHour);
|
let currentEnd = startHour.plus({ hours: 1 });
|
||||||
if (currentEnd === endHour) {
|
if (!startHour.isValid || currentEnd.toMillis() >= end) {
|
||||||
yield span;
|
yield span;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield {
|
yield {
|
||||||
start: span.start,
|
start: span.start,
|
||||||
end: { ts: currentEnd * oneHourMs, edges: [] },
|
end: { ts: currentEnd.toMillis(), edges: [] },
|
||||||
locations: span.locations,
|
locations: span.locations,
|
||||||
}
|
}
|
||||||
|
|
||||||
currentStart = currentEnd;
|
while (true) {
|
||||||
while (++currentEnd < endHour) {
|
currentStart = currentEnd;
|
||||||
|
currentEnd = currentEnd.plus({ hours: 1 });
|
||||||
|
if (currentEnd.toMillis() >= end) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
yield {
|
yield {
|
||||||
start: { ts: currentStart * oneHourMs, edges: [] },
|
start: { ts: currentStart.toMillis(), edges: [] },
|
||||||
end: { ts: currentEnd * oneHourMs, edges: [] },
|
end: { ts: currentEnd.toMillis(), edges: [] },
|
||||||
locations: span.locations,
|
locations: span.locations,
|
||||||
}
|
}
|
||||||
currentStart += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yield {
|
yield {
|
||||||
start: { ts: currentStart * oneHourMs, edges: [] },
|
start: { ts: currentStart.toMillis(), edges: [] },
|
||||||
end: span.end,
|
end: span.end,
|
||||||
locations: span.locations,
|
locations: span.locations,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function padStretch(stretch: Stretch, timezone: string): Stretch {
|
||||||
|
// Pad by one hour and extend it to the nearest whole hour.
|
||||||
|
let start = DateTime.fromMillis(stretch.start, { zone: timezone })
|
||||||
|
.minus(oneHourMs)
|
||||||
|
.startOf("hour")
|
||||||
|
;
|
||||||
|
let end = DateTime.fromMillis(stretch.end, { zone: timezone })
|
||||||
|
.plus(2 * oneHourMs - 1)
|
||||||
|
.startOf("hour")
|
||||||
|
;
|
||||||
|
return {
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
start: { ts: start.toMillis(), edges: [] },
|
||||||
|
end: stretch.spans[0].start,
|
||||||
|
locations: new Map(),
|
||||||
|
},
|
||||||
|
...stretch.spans,
|
||||||
|
{
|
||||||
|
start: stretch.spans[stretch.spans.length - 1].end,
|
||||||
|
end: { ts: end.toMillis(), edges: [] },
|
||||||
|
locations: new Map(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
start: start.toMillis(),
|
||||||
|
end: end.toMillis(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function tableElementsFromStretches(
|
function tableElementsFromStretches(
|
||||||
stretches: Iterable<Stretch>, locations: ScheduleLocation[]
|
stretches: Iterable<Stretch>, locations: ScheduleLocation[], timezone: string,
|
||||||
) {
|
) {
|
||||||
type Col = { minutes?: number };
|
type Col = { minutes?: number };
|
||||||
type DayHead = { span: number, content?: string }
|
type DayHead = { span: number, content?: string }
|
||||||
|
@ -315,18 +344,20 @@ function tableElementsFromStretches(
|
||||||
}
|
}
|
||||||
|
|
||||||
let first = true;
|
let first = true;
|
||||||
for (const stretch of stretches) {
|
for (let stretch of stretches) {
|
||||||
|
stretch = padStretch(stretch, timezone);
|
||||||
|
const startDate = DateTime.fromMillis(stretch.start, { zone: timezone });
|
||||||
if (first) {
|
if (first) {
|
||||||
first = false;
|
first = false;
|
||||||
startColumnGroup();
|
startColumnGroup();
|
||||||
startDay(isoStringFromTs(stretch.start).slice(0, 10));
|
startDay(startDate.toFormat("yyyy-LL-dd"));
|
||||||
startHour(isoStringFromTs(stretch.start).slice(11, 16));
|
startHour(startDate.toFormat("HH:mm"));
|
||||||
for(const location of locations) {
|
for(const location of locations) {
|
||||||
startLocation(location.id);
|
startLocation(location.id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
startColumnGroup("break");
|
startColumnGroup("break");
|
||||||
const dayName = isoStringFromTs(stretch.start).slice(0, 10)
|
const dayName = startDate.toFormat("yyyy-LL-dd");
|
||||||
const lastDayHeader = dayHeaders[dayHeaders.length - 1]
|
const lastDayHeader = dayHeaders[dayHeaders.length - 1]
|
||||||
const sameDay = dayName === lastDayHeader.content && lastDayHeader.span;
|
const sameDay = dayName === lastDayHeader.content && lastDayHeader.span;
|
||||||
if (!sameDay)
|
if (!sameDay)
|
||||||
|
@ -340,14 +371,14 @@ function tableElementsFromStretches(
|
||||||
startColumnGroup();
|
startColumnGroup();
|
||||||
if (!sameDay)
|
if (!sameDay)
|
||||||
startDay(dayName);
|
startDay(dayName);
|
||||||
startHour(isoStringFromTs(stretch.start).slice(11, 16));
|
startHour(startDate.toFormat("HH:mm"));
|
||||||
for(const location of locations) {
|
for(const location of locations) {
|
||||||
startLocation(location.id);
|
startLocation(location.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const span of stretch.spans) {
|
for (const span of stretch.spans) {
|
||||||
for (const cutSpan of cutSpansByHours(span)) {
|
for (const cutSpan of cutSpansByHours(span, timezone)) {
|
||||||
const end = cutSpan.end.ts;
|
const end = cutSpan.end.ts;
|
||||||
const durationMs = end - cutSpan.start.ts;
|
const durationMs = end - cutSpan.start.ts;
|
||||||
|
|
||||||
|
@ -361,11 +392,18 @@ function tableElementsFromStretches(
|
||||||
}
|
}
|
||||||
|
|
||||||
pushColumn(durationMs / oneMinMs);
|
pushColumn(durationMs / oneMinMs);
|
||||||
if (end % oneDayMs === 0) {
|
const endDate = DateTime.fromMillis(end, { zone: timezone });
|
||||||
startDay(isoStringFromTs(cutSpan.end.ts).slice(0, 10));
|
if (end === endDate.startOf("day").toMillis()) {
|
||||||
|
startDay(
|
||||||
|
DateTime.fromMillis(cutSpan.end.ts, { zone: timezone })
|
||||||
|
.toFormat("yyyy-LL-dd")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (end % oneHourMs === 0) {
|
if (end === endDate.startOf("hour").toMillis()) {
|
||||||
startHour(isoStringFromTs(cutSpan.end.ts).slice(11, 16));
|
startHour(
|
||||||
|
DateTime.fromMillis(cutSpan.end.ts, { zone: timezone })
|
||||||
|
.toFormat("HH:mm")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,7 +422,16 @@ const junctions = computed(() => junctionsFromEdges(edgesFromEvents(schedule.val
|
||||||
const stretches = computed(() => [
|
const stretches = computed(() => [
|
||||||
...stretchesFromSpans(spansFromJunctions(junctions.value, schedule.value.locations), oneHourMs * 5)
|
...stretchesFromSpans(spansFromJunctions(junctions.value, schedule.value.locations), oneHourMs * 5)
|
||||||
])
|
])
|
||||||
const elements = computed(() => tableElementsFromStretches(stretches.value, schedule.value.locations));
|
|
||||||
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
const { data: session } = await useAccountSession();
|
||||||
|
const debugTimezone = ref<undefined | string>();
|
||||||
|
const timezone = computed({
|
||||||
|
get: () => debugTimezone.value ?? session.value?.account.timezone ?? runtimeConfig.public.defaultTimezone,
|
||||||
|
set: (value: string) => { debugTimezone.value = value },
|
||||||
|
});
|
||||||
|
|
||||||
|
const elements = computed(() => tableElementsFromStretches(stretches.value, schedule.value.locations, timezone.value));
|
||||||
const columnGroups = computed(() => elements.value.columnGroups);
|
const columnGroups = computed(() => elements.value.columnGroups);
|
||||||
const dayHeaders = computed(() => elements.value.dayHeaders);
|
const dayHeaders = computed(() => elements.value.dayHeaders);
|
||||||
const hourHeaders = computed(() => elements.value.hourHeaders);
|
const hourHeaders = computed(() => elements.value.hourHeaders);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue