-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* basic impementation of a calendar * refactored the design to make it more like the google calendar * Adapted the calendar to changing requirements * tmp * made sure that `should_refresh_token()` is subtraction less * made sure that overfolws don't have ugly scrollbars * implemented a fully working calendar * made sure that some icons are more appropriately sized * renamed
- Loading branch information
1 parent
4c2e388
commit 279bf1e
Showing
18 changed files
with
532 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
<script setup lang="ts"> | ||
import FullCalendar from "@fullcalendar/vue3"; | ||
import type { CalendarOptions, EventInput, EventSourceFuncArg } from "@fullcalendar/core"; | ||
import dayGridPlugin from "@fullcalendar/daygrid"; | ||
import type { components, operations } from "~/api_types"; | ||
import deLocale from "@fullcalendar/core/locales/de"; | ||
import enLocale from "@fullcalendar/core/locales/en-gb"; | ||
type CalendarResponse = components["schemas"]["CalendarResponse"]; | ||
type CalendarBody = operations["calendar"]["requestBody"]["content"]["application/json"]; | ||
type CalendarLocation = components["schemas"]["CalendarLocation"]; | ||
const props = defineProps<{ showing: readonly string[] }>(); | ||
const runtimeConfig = useRuntimeConfig(); | ||
const { locale } = useI18n({ useScope: "local" }); | ||
const earliest_last_sync = defineModel<string | null>("earliest_last_sync"); | ||
const locations = defineModel<Map<string, CalendarLocation>>("locations"); | ||
async function fetchEvents(arg: EventSourceFuncArg): Promise<EventInput[]> { | ||
const body: CalendarBody = { | ||
start_after: arg.startStr, | ||
end_before: arg.endStr, | ||
ids: props.showing, | ||
}; | ||
const url = `${runtimeConfig.public.apiURL}/api/calendar`; | ||
const data = await $fetch<CalendarResponse>(url, { | ||
method: "POST", | ||
body: JSON.stringify(body), | ||
retry: 120, | ||
retryDelay: 5000, | ||
}); | ||
extractInfos(data); | ||
const items = []; | ||
const show_room_names = Object.keys(data).length > 1; | ||
for (const [k, v] of Object.entries(data)) { | ||
items.push( | ||
...v.events.map((e) => { | ||
const title = locale.value == "de" ? e.stp_title_de : e.stp_title_en; | ||
return { | ||
id: e.id.toString(), | ||
title: show_room_names ? `${k} ${title}` : title, | ||
start: new Date(e.start_at), | ||
end: new Date(e.end_at), | ||
classes: [e.entry_type], | ||
}; | ||
}), | ||
); | ||
} | ||
return items; | ||
} | ||
function extractInfos(data: CalendarResponse): void { | ||
earliest_last_sync.value = Object.values(data) | ||
.map((d) => new Date(d.location.last_calendar_scrape_at)) | ||
.reduce((d1, d2) => (d1 < d2 ? d1 : d2)) | ||
.toLocaleString(locale.value, { timeStyle: "short", dateStyle: "short" }); | ||
const tempLocationMap = new Map<string, CalendarLocation>(); | ||
for (const [key, v] of Object.entries(data)) { | ||
tempLocationMap.set(key, v.location); | ||
} | ||
locations.value = tempLocationMap; | ||
} | ||
const calendarOptions: CalendarOptions = { | ||
plugins: [dayGridPlugin], | ||
initialView: "dayGridWeek", | ||
weekends: false, | ||
events: fetchEvents, | ||
headerToolbar: { | ||
left: "prev,next", | ||
center: "title", | ||
right: "dayGridMonth,dayGridWeek,dayGridDay", | ||
}, | ||
locale: locale.value == "de" ? deLocale : enLocale, | ||
height: 700, | ||
eventTimeFormat: { | ||
// like '14:30' | ||
hour: "2-digit", | ||
minute: "2-digit", | ||
meridiem: false, | ||
}, | ||
}; | ||
</script> | ||
|
||
<template> | ||
<div class="flex max-h-[700px] min-h-[700px] grow flex-col"> | ||
<FullCalendar :options="calendarOptions"> | ||
<template #eventContent="arg"> | ||
<b>{{ arg.timeText }}</b> | ||
<i class="ps-1">{{ arg.event.title }}</i> | ||
</template> | ||
</FullCalendar> | ||
</div> | ||
</template> | ||
|
||
<style lang="postcss" scoped> | ||
.fc-daygrid-event-harness { | ||
@apply overflow-x-auto; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
<script setup lang="ts"> | ||
import { useFeedback } from "~/composables/feedback"; | ||
import { useCalendar } from "~/composables/calendar"; | ||
import type { components } from "~/api_types"; | ||
type CalendarLocation = components["schemas"]["CalendarLocation"]; | ||
const feedback = useFeedback(); | ||
const calendar = useCalendar(); | ||
const { t } = useI18n({ useScope: "local" }); | ||
// all the below are updated by the calendar | ||
const earliest_last_sync = ref<string | null>(null); | ||
const locations = ref<Map<string, CalendarLocation>>(new Map()); | ||
</script> | ||
|
||
<template> | ||
<LazyModal v-model="calendar.open" :title="t('title')" class="!min-w-[90vw]"> | ||
<NuxtErrorBoundary> | ||
<template #error="{ error }"> | ||
<Toast level="error"> | ||
<p class="text-md font-bold">{{ t("error.header") }}</p> | ||
<p class="text-sm"> | ||
{{ t("error.reason") }}:<br /> | ||
<code | ||
class="text-red-900 bg-red-200 mb-1 mt-2 inline-flex max-w-full items-center space-x-2 overflow-auto rounded-md px-4 py-3 text-left font-mono text-xs dark:bg-red-50/20" | ||
> | ||
{{ error }} | ||
</code> | ||
</p> | ||
<I18nT class="text-sm" tag="p" keypath="error.call_to_action"> | ||
<template #feedbackForm> | ||
<button | ||
type="button" | ||
class="text-blue-600 bg-transparent visited:text-blue-600 hover:underline" | ||
:aria-label="t('error.feedback-open')" | ||
@click=" | ||
() => { | ||
feedback.open = true; | ||
feedback.data = { | ||
category: 'bug', | ||
subject: 'calendar error', | ||
body: `While viewing the calendar for ${JSON.stringify(calendar.showing)} | ||
I got this error: | ||
\`\`\` | ||
${error} | ||
\`\`\` | ||
In case you have trouble replicating this bug, my environment is PLEASE_INSERT_YOUR_BROWSER_HERE. | ||
I also did PLEASE_INSERT_IF_YOU_DID_SOMETHING_SPECIAL_BEFOREHAND`, | ||
deletion_requested: false, | ||
}; | ||
} | ||
" | ||
> | ||
{{ t("error.feedback-form") }} | ||
</button> | ||
</template> | ||
</I18nT> | ||
</Toast> | ||
</template> | ||
<template #default> | ||
<div> | ||
<Toast level="info" class="mb-3"> | ||
<I18nT class="text-sm" tag="p" keypath="call_for_feedback"> | ||
<template #feedbackForm> | ||
<button | ||
type="button" | ||
class="text-blue-600 bg-transparent visited:text-blue-600 hover:underline" | ||
:aria-label="t('error.feedback-open')" | ||
@click=" | ||
() => { | ||
feedback.open = true; | ||
feedback.data = { | ||
category: 'general', | ||
subject: 'calendar feedback', | ||
body: `Dear OpenSource@TUM, | ||
The calendar for ${JSON.stringify(calendar.showing)} can be improved by | ||
- | ||
Thanks`, | ||
deletion_requested: false, | ||
}; | ||
} | ||
" | ||
> | ||
{{ t("error.feedback-form") }} | ||
</button> | ||
</template> | ||
</I18nT> | ||
</Toast> | ||
<CalendarRoomSelector :data="locations" /> | ||
<CalendarFull | ||
v-model:earliest_last_sync="earliest_last_sync" | ||
v-model:locations="locations" | ||
:showing="calendar.showing" | ||
/> | ||
<p class="pt-2 text-xs"> | ||
{{ t("footer.disclaimer") }} <br /> | ||
{{ t("footer.please_check") }} | ||
<template v-if="earliest_last_sync !== null"> | ||
<br /> | ||
{{ t("footer.last_sync", [earliest_last_sync]) }} | ||
</template> | ||
</p> | ||
</div> | ||
</template> | ||
</NuxtErrorBoundary> | ||
</LazyModal> | ||
</template> | ||
|
||
<i18n lang="yaml"> | ||
de: | ||
title: Kalendar | ||
close: Schließen | ||
error: | ||
header: Beim Versuch, den Kalender anzuzeigen, ist ein Fehler aufgetreten | ||
reason: Der Grund für diesen Fehler ist | ||
call_to_action: Wenn dieses Problem weiterhin besteht, kontaktieren Sie uns bitte über das {feedbackForm}. | ||
feedback-form: Feedback-Formular | ||
feedback-open: Feedback-Formular öffnen | ||
footer: | ||
disclaimer: Stündlich aktualisiert und identische Termine zusammengefasst. | ||
please_check: Im Zweifelsfall prüfe bitte den offiziellen TUMonline-Kalender. | ||
last_sync: Stand {0} | ||
call_for_feedback: Diese Funktion ist neu. Wenn du Feedback dazu hast, nutze doch bitte das {feedbackForm}. | ||
en: | ||
title: Calendar | ||
close: Close | ||
error: | ||
header: Got an error trying to display calendar | ||
reason: Reason for this error is | ||
call_to_action: If this issue persists, please contact us via the {feedbackForm}. | ||
feedback-form: feedback form | ||
feedback-open: open the feedback form | ||
footer: | ||
disclaimer: Updated hourly and identical events are merged. | ||
please_check: If in doubt, please check the official calendar on TUMonline | ||
last_sync: Updated {0} | ||
call_for_feedback: This feature is new. If you have some feedback about it, feel free to use the {feedbackForm}. | ||
</i18n> |
Oops, something went wrong.