feat: add employee push test button

This commit is contained in:
2026-09-09 12:55:26 +02:00
parent b3dd46195d
commit 5b6579423b
3 changed files with 117 additions and 1 deletions

View File

@@ -1,6 +1,6 @@
import { FastifyInstance } from "fastify" import { FastifyInstance } from "fastify"
import { and, eq, isNull } from "drizzle-orm" import { and, eq, isNull } from "drizzle-orm"
import { authUsers, notificationMobilePushDevices } from "../../db/schema" import { authProfiles, authUsers, notificationMobilePushDevices } from "../../db/schema"
import { NotificationService, UserDirectory } from "../modules/notification.service" import { NotificationService, UserDirectory } from "../modules/notification.service"
import { pushServerClient } from "../modules/push-server.client" import { pushServerClient } from "../modules/push-server.client"
@@ -174,6 +174,60 @@ export default async function notificationsRoutes(server: FastifyInstance) {
}) })
}) })
server.post("/notifications/test-push/profile/:profileId", async (req, reply) => {
const tenantId = requireTenant(req.user.tenant_id)
const { profileId } = req.params as { profileId: string }
const [profile] = await server.db
.select({
userId: authProfiles.user_id,
})
.from(authProfiles)
.where(and(
eq(authProfiles.id, profileId),
eq(authProfiles.tenant_id, tenantId)
))
.limit(1)
if (!profile) {
return reply.code(404).send({ error: "Mitarbeiter nicht gefunden" })
}
if (!profile.userId) {
return reply.code(409).send({
error: "Der Mitarbeiter ist noch nicht mit einem Benutzerkonto verknüpft",
})
}
const result = await svc.trigger({
tenantId,
userId: profile.userId,
eventType: "system.test_push",
title: "FEDEO Push ist aktiv",
message: "Diese Testbenachrichtigung wurde einmalig über das Mitarbeiterprofil ausgelöst.",
payload: {
link: "/",
icon: "/favicon.ico",
},
channels: ["push"],
})
if (result.created === 0) {
return reply.code(409).send({
error: "Pushnachrichten sind für diesen Mitarbeiter deaktiviert",
result,
})
}
if (!result.success || result.delivered === 0) {
return reply.code(424).send({
error: "Die Pushnachricht konnte an kein aktives Gerät zugestellt werden",
result,
})
}
return result
})
server.post("/notifications/trigger", async (req, reply) => { server.post("/notifications/trigger", async (req, reply) => {
try { try {
const body = req.body as any const body = req.body as any

View File

@@ -76,6 +76,12 @@ export const useDesktopPush = () => {
}) })
} }
const sendTestPushToProfile = async (profileId) => {
return await $api(`/api/notifications/test-push/profile/${encodeURIComponent(profileId)}`, {
method: "POST",
})
}
return { return {
supported, supported,
permission, permission,
@@ -85,5 +91,6 @@ export const useDesktopPush = () => {
loadConfig, loadConfig,
subscribe, subscribe,
sendTestPush, sendTestPush,
sendTestPushToProfile,
} }
} }

View File

@@ -4,6 +4,7 @@ const route = useRoute()
const toast = useToast() const toast = useToast()
const auth = useAuthStore() const auth = useAuthStore()
const admin = useAdmin() const admin = useAdmin()
const desktopPush = useDesktopPush()
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
@@ -17,6 +18,7 @@ const creatingLinkedUser = ref(false)
const createLinkedUserModalOpen = ref(false) const createLinkedUserModalOpen = ref(false)
const createdLinkedUserPassword = ref("") const createdLinkedUserPassword = ref("")
const generatingCalendarSubscription = ref(false) const generatingCalendarSubscription = ref(false)
const sendingTestPush = ref(false)
const createLinkedUserForm = reactive({ const createLinkedUserForm = reactive({
email: "", email: "",
}) })
@@ -270,6 +272,30 @@ async function copyCalendarSubscriptionUrl(value: string, successTitle: string)
} }
} }
async function sendTestPush() {
if (!profile.value?.user_id || sendingTestPush.value) return
sendingTestPush.value = true
try {
await desktopPush.sendTestPushToProfile(profile.value.id)
toast.add({
title: "Test-Push gesendet",
description: `Die Nachricht wurde einmalig an ${profile.value.full_name || "den Mitarbeiter"} gesendet.`,
color: "green"
})
} catch (err: any) {
console.error("[sendTestPush]", err)
toast.add({
title: "Test-Push fehlgeschlagen",
description: err?.data?.error || err?.message || "Die Testnachricht konnte nicht zugestellt werden.",
color: "red"
})
} finally {
sendingTestPush.value = false
}
}
const weekdays = [ const weekdays = [
{ key: '1', label: 'Montag' }, { key: '1', label: 'Montag' },
{ key: '2', label: 'Dienstag' }, { key: '2', label: 'Dienstag' },
@@ -456,6 +482,35 @@ onMounted(async () => {
description="Lege fest, unter welcher Nebenstelle dieser Benutzer erreichbar ist." description="Lege fest, unter welcher Nebenstelle dieser Benutzer erreichbar ist."
/> />
<UCard v-if="!pending && profile" class="mt-3">
<USeparator label="Push-Benachrichtigung" />
<div class="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div>
<p class="text-sm font-medium text-gray-900 dark:text-white">Push-Zustellung testen</p>
<p class="mt-1 text-sm text-gray-500">
<template v-if="profile.user_id">
Sendet einmalig eine Testnachricht an alle aktiven Push-Geräte dieses Mitarbeiters.
</template>
<template v-else>
Für einen Push-Test muss der Mitarbeiter zuerst mit einem Benutzerkonto verknüpft werden.
</template>
</p>
</div>
<UButton
icon="i-heroicons-paper-airplane"
color="neutral"
variant="outline"
:loading="sendingTestPush"
:disabled="!profile.user_id"
@click="sendTestPush"
>
Test-Push senden
</UButton>
</div>
</UCard>
<UCard v-if="!pending && profile" class="mt-3"> <UCard v-if="!pending && profile" class="mt-3">
<USeparator label="Vertragsinformationen" /> <USeparator label="Vertragsinformationen" />