Skip to content

Commit

Permalink
feat: start work on calendar event notifications
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Trost <[email protected]>
  • Loading branch information
galexrt committed Oct 13, 2024
1 parent 7340334 commit 99bb856
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 163 deletions.
253 changes: 162 additions & 91 deletions app/components/auth/account/UserSettingsPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const authStore = useAuthStore();
const { activeChar } = storeToRefs(authStore);
const settings = useSettingsStore();
const { startpage, design, streamerMode, audio } = storeToRefs(settings);
const { startpage, design, streamerMode, audio, calendar } = storeToRefs(settings);
const homepages: { name: string; path: RoutePathSchema; permission?: Perms }[] = [
{ name: t('common.overview'), path: '/overview' },
Expand Down Expand Up @@ -56,99 +56,170 @@ watch(design.value, () => {
ui.primary = design.value.ui.primary;
ui.gray = design.value.ui.gray;
});
const reminderTimes = [300, 600, 900, 1800];
const calendarReminderTimes = [
{ label: t('components.auth.UserSettingsPanel.calendar_notifications.reminder_times.start'), value: 0 },
...reminderTimes.map((n) => ({ label: `${n / 60} ${t('common.time_ago.minute', n / 60)}`, value: n })),
];
const items = [
{
slot: 'settings',
label: t('common.settings'),
icon: 'i-mdi-cog',
},
{
slot: 'notifications',
label: t('common.notification', 2),
icon: 'i-mdi-notification-settings',
},
];
const route = useRoute();
const router = useRouter();
const selectedTab = computed({
get() {
const index = items.findIndex((item) => item.slot === route.query.tab);
if (index === -1) {
return 0;
}
return index;
},
set(value) {
// Hash is specified here to prevent the page from scrolling to the top
router.replace({ query: { tab: items[value]?.slot }, hash: '#' });
},
});
</script>

<template>
<UDashboardPanelContent class="pb-24">
<UDashboardSection :title="$t('common.theme')" :description="$t('components.auth.UserSettingsPanel.customization')">
<template #links>
<UColorModeSelect color="gray" />
<div>
<UTabs v-model="selectedTab" :items="items" class="w-full" :ui="{ list: { rounded: '' } }">
<template #settings>
<UDashboardPanelContent>
<UDashboardSection
:title="$t('common.theme')"
:description="$t('components.auth.UserSettingsPanel.customization')"
>
<template #links>
<UColorModeSelect color="gray" />
</template>

<UFormGroup name="primaryColor" :label="$t('common.color')" class="grid grid-cols-2 items-center gap-2">
<ColorPickerTW v-model="design.ui.primary" name="primaryColor" />
</UFormGroup>

<UFormGroup
name="grayColor"
:label="$t('components.auth.UserSettingsPanel.background_color')"
class="grid grid-cols-2 items-center gap-2"
>
<ColorPickerTW v-model="design.ui.gray" name="grayColor" />
</UFormGroup>

<UFormGroup
name="streamerMode"
:label="$t('components.auth.UserSettingsPanel.streamer_mode.title')"
:description="$t('components.auth.UserSettingsPanel.streamer_mode.description')"
class="grid grid-cols-2 items-center gap-2"
>
<UToggle v-model="streamerMode" name="streamerMode">
<span class="sr-only">{{ $t('components.auth.UserSettingsPanel.streamer_mode.title') }}</span>
</UToggle>
</UFormGroup>

<UFormGroup
name="darkModeActive"
:label="$t('components.auth.UserSettingsPanel.editor_theme.title')"
class="grid grid-cols-2 items-center gap-2"
>
<div class="flex items-center gap-2">
<UToggle v-model="darkModeActive">
<span class="sr-only">{{
$t('components.auth.UserSettingsPanel.editor_theme.title')
}}</span>
</UToggle>

<span>{{ $t('components.auth.UserSettingsPanel.editor_theme.dark_mode') }}</span>
</div>
</UFormGroup>

<UFormGroup
name="designDocumentsListStyle"
:label="$t('components.auth.UserSettingsPanel.documents_lists_style.title')"
class="grid grid-cols-2 items-center gap-2"
>
<div class="inline-flex items-center gap-2 text-sm">
<span>{{ $t('components.auth.UserSettingsPanel.documents_lists_style.single') }}</span>
<UToggle v-model="designDocumentsListStyle">
<span class="sr-only">{{
$t('components.auth.UserSettingsPanel.documents_lists_style.title')
}}</span>
</UToggle>
<span>{{ $t('components.auth.UserSettingsPanel.documents_lists_style.double') }}</span>
</div>
</UFormGroup>

<UFormGroup
name="selectedHomepage"
:label="$t('components.auth.UserSettingsPanel.set_startpage.title')"
class="grid grid-cols-2 items-center gap-2"
>
<ClientOnly v-if="activeChar">
<USelectMenu
v-model="selectedHomepage"
:options="homepages.filter((h) => h.permission === undefined || can(h.permission).value)"
option-attribute="name"
:searchable-placeholder="$t('common.search_field')"
/>
</ClientOnly>
<p v-else class="text-sm">
{{ $t('components.auth.UserSettingsPanel.set_startpage.no_char_selected') }}
</p>
</UFormGroup>
</UDashboardSection>
</UDashboardPanelContent>
</template>

<UFormGroup name="primaryColor" :label="$t('common.color')" class="grid grid-cols-2 items-center gap-2">
<ColorPickerTW v-model="design.ui.primary" name="primaryColor" />
</UFormGroup>

<UFormGroup
name="grayColor"
:label="$t('components.auth.UserSettingsPanel.background_color')"
class="grid grid-cols-2 items-center gap-2"
>
<ColorPickerTW v-model="design.ui.gray" name="grayColor" />
</UFormGroup>

<UFormGroup
name="streamerMode"
:label="$t('components.auth.UserSettingsPanel.streamer_mode.title')"
:description="$t('components.auth.UserSettingsPanel.streamer_mode.description')"
class="grid grid-cols-2 items-center gap-2"
>
<UToggle v-model="streamerMode" name="streamerMode">
<span class="sr-only">{{ $t('components.auth.UserSettingsPanel.streamer_mode.title') }}</span>
</UToggle>
</UFormGroup>

<UFormGroup
name="darkModeActive"
:label="$t('components.auth.UserSettingsPanel.editor_theme.title')"
class="grid grid-cols-2 items-center gap-2"
>
<div class="flex items-center gap-2">
<UToggle v-model="darkModeActive">
<span class="sr-only">{{ $t('components.auth.UserSettingsPanel.editor_theme.title') }}</span>
</UToggle>

<span>{{ $t('components.auth.UserSettingsPanel.editor_theme.dark_mode') }}</span>
</div>
</UFormGroup>

<UFormGroup
name="designDocumentsListStyle"
:label="$t('components.auth.UserSettingsPanel.documents_lists_style.title')"
class="grid grid-cols-2 items-center gap-2"
>
<div class="inline-flex items-center gap-2 text-sm">
<span>{{ $t('components.auth.UserSettingsPanel.documents_lists_style.single') }}</span>
<UToggle v-model="designDocumentsListStyle">
<span class="sr-only">{{ $t('components.auth.UserSettingsPanel.documents_lists_style.title') }}</span>
</UToggle>
<span>{{ $t('components.auth.UserSettingsPanel.documents_lists_style.double') }}</span>
</div>
</UFormGroup>

<UFormGroup
name="selectedHomepage"
:label="$t('components.auth.UserSettingsPanel.set_startpage.title')"
class="grid grid-cols-2 items-center gap-2"
>
<ClientOnly v-if="activeChar">
<USelectMenu
v-model="selectedHomepage"
:options="homepages.filter((h) => h.permission === undefined || can(h.permission).value)"
option-attribute="name"
:searchable-placeholder="$t('common.search_field')"
/>
</ClientOnly>
<p v-else class="text-sm">
{{ $t('components.auth.UserSettingsPanel.set_startpage.no_char_selected') }}
</p>
</UFormGroup>
</UDashboardSection>

<UDivider class="mb-4" />

<UDashboardSection
:title="$t('components.auth.UserSettingsPanel.volumes.title')"
:description="$t('components.auth.UserSettingsPanel.volumes.subtitle')"
>
<UFormGroup
name="notificationsVolume"
:label="$t('components.auth.UserSettingsPanel.volumes.notifications_volume')"
class="grid grid-cols-2 items-center gap-2"
>
<URange v-model="audio.notificationsVolume" :step="0.01" :min="0" :max="1" />
{{ audio.notificationsVolume <= 0 ? 0 : (audio.notificationsVolume * 100).toFixed(0) }}%
</UFormGroup>
</UDashboardSection>
</UDashboardPanelContent>
<template #notifications>
<UDashboardPanelContent>
<UDashboardSection
:title="$t('components.auth.UserSettingsPanel.volumes.title')"
:description="$t('components.auth.UserSettingsPanel.volumes.subtitle')"
>
<UFormGroup
name="notificationsVolume"
:label="$t('components.auth.UserSettingsPanel.volumes.notifications_volume')"
class="grid grid-cols-2 items-center gap-2"
>
<URange v-model="audio.notificationsVolume" :step="0.01" :min="0" :max="1" />
{{ audio.notificationsVolume <= 0 ? 0 : (audio.notificationsVolume * 100).toFixed(0) }}%
</UFormGroup>
</UDashboardSection>

<UDashboardSection
:title="$t('components.auth.UserSettingsPanel.calendar_notifications.title')"
:description="$t('components.auth.UserSettingsPanel.calendar_notifications.subtitle')"
>
<UFormGroup
name="calendarNotifications"
:label="$t('components.auth.UserSettingsPanel.calendar_notifications.reminder_times.name')"
class="grid grid-cols-2 items-center gap-2"
>
<USelectMenu
v-model="calendar.reminderTimes"
multiple
:options="calendarReminderTimes"
value-attribute="value"
>
</USelectMenu>
</UFormGroup>
</UDashboardSection>
</UDashboardPanelContent>
</template>
</UTabs>
</div>
</template>
3 changes: 3 additions & 0 deletions app/components/calendar/FindCalendarsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ async function subscribeToCalendar(calendarId: string, subscribe: boolean): Prom
// Update calendar list and entries if necessary after (un-)subscribing
await calendarStore.listCalendars({
pagination: {
offset: 0,
},
onlyPublic: false,
});
Expand Down
2 changes: 1 addition & 1 deletion app/components/calendar/entry/EntryCreateOrUpdateModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ const onSubmitThrottle = useThrottleFn(async (event: FormSubmitEvent<Schema>) =>
by="userId"
>
<template #option="{ option: user }">
{{ `${user?.lastname} (${user?.dateofbirth})` }}
{{ `${user?.firstname} ${user?.lastname} (${user?.dateofbirth})` }}
</template>
<template #option-empty="{ query: search }">
<q>{{ search }}</q> {{ $t('common.query_not_found') }}
Expand Down
44 changes: 21 additions & 23 deletions app/components/jobs/JobSelfService.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,29 +50,27 @@ onMounted(() => {
</template>

<div class="flex flex-initial flex-col items-center gap-1 md:flex-row">
<UButtonGroup class="inline-flex w-full">
<UButton
v-if="
colleagueSelf?.colleague &&
can('JobsService.SetJobsUserProps').value &&
activeChar?.userId === colleagueSelf.colleague?.userId
"
block
class="flex-1"
icon="i-mdi-island"
@click="
modal.open(SelfServicePropsAbsenceDateModal, {
userId: colleagueSelf.colleague.userId,
userProps: colleagueSelf.colleague.props,
})
"
>
<span>{{ $t('components.jobs.self_service.set_absence_date') }}</span>
</UButton>
<UButton block class="flex-1" icon="i-mdi-camera" @click="modal.open(SelfServicePropsProfilePictureModal, {})">
<span>{{ $t('components.jobs.self_service.set_profile_picture') }}</span>
</UButton>
</UButtonGroup>
<UButton
v-if="
colleagueSelf?.colleague &&
can('JobsService.SetJobsUserProps').value &&
activeChar?.userId === colleagueSelf.colleague?.userId
"
block
class="flex-1"
icon="i-mdi-island"
@click="
modal.open(SelfServicePropsAbsenceDateModal, {
userId: colleagueSelf.colleague.userId,
userProps: colleagueSelf.colleague.props,
})
"
>
<span>{{ $t('components.jobs.self_service.set_absence_date') }}</span>
</UButton>
<UButton block class="flex-1" icon="i-mdi-camera" @click="modal.open(SelfServicePropsProfilePictureModal, {})">
<span>{{ $t('components.jobs.self_service.set_profile_picture') }}</span>
</UButton>
</div>
</UCard>
</template>
6 changes: 6 additions & 0 deletions app/components/jobs/timeclock/TimeclockList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ const columns = computed(() =>
key: 'name',
label: t('common.name'),
},
canAccessAll.value && query.users !== undefined && query.users?.length > 1
? {
key: 'jobGrade',
label: t('common.rank'),
}
: undefined,
{
key: 'time',
label: t('common.time'),
Expand Down
Loading

0 comments on commit 99bb856

Please sign in to comment.