diff --git a/apps/dashboard/src/app/(internal)/varslinger/[id]/edit-card.tsx b/apps/dashboard/src/app/(internal)/varslinger/[id]/edit-card.tsx new file mode 100644 index 0000000000..19b907292c --- /dev/null +++ b/apps/dashboard/src/app/(internal)/varslinger/[id]/edit-card.tsx @@ -0,0 +1,18 @@ +import type { FC } from "react" +import { useEditNotificationMutation } from "../mutations" +import { useNotificationWriteForm } from "../write-form" +import { useNotificationDetailsContext } from "./provider" +export const NotificationEditCard: FC = () => { + const { notification } = useNotificationDetailsContext() + const edit = useEditNotificationMutation() + + const FormComponent = useNotificationWriteForm({ + label: "Oppdater varsling", + onSubmit: (data) => { + const { ...notificationData } = data + edit.mutate({ id: notification.id, input: notificationData}) + }, + defaultValues: { ...notification } + }) + return +} diff --git a/apps/dashboard/src/app/(internal)/varslinger/[id]/layout.tsx b/apps/dashboard/src/app/(internal)/varslinger/[id]/layout.tsx new file mode 100644 index 0000000000..9764e9a6f5 --- /dev/null +++ b/apps/dashboard/src/app/(internal)/varslinger/[id]/layout.tsx @@ -0,0 +1,31 @@ +"use client" +import { Loader } from "@mantine/core" +import { type PropsWithChildren, use, useMemo } from "react" +import { NotificationDetailsContext } from "./provider" + +import { useTRPC } from "@/lib/trpc-client" +import { useQuery } from "@tanstack/react-query" + +export default function NotificationDetailsLayout({ + children, + params, +}: PropsWithChildren<{ params: Promise<{ id: string }> }>) { + const trpc = useTRPC() + const { id } = use(params) + const { data, isLoading } = useQuery(trpc.notification.get.queryOptions(id)) + const value = useMemo( + () => + !data || isLoading + ? null + : { + notification: data, + }, + [data, isLoading] + ) + + if (value === null) { + return + } + + return {children} +} diff --git a/apps/dashboard/src/app/(internal)/varslinger/[id]/page.tsx b/apps/dashboard/src/app/(internal)/varslinger/[id]/page.tsx new file mode 100644 index 0000000000..c50ca2958d --- /dev/null +++ b/apps/dashboard/src/app/(internal)/varslinger/[id]/page.tsx @@ -0,0 +1,54 @@ +"use client" + +import { Box, CloseButton, Group, Tabs, Title } from "@mantine/core" +import { IconPhoto } from "@tabler/icons-react" +import { useRouter, useSearchParams } from "next/navigation" +import { NotificationEditCard } from "./edit-card" +import { useNotificationDetailsContext } from "./provider" + +const SIDEBAR_LINKS = [ + { + icon: IconPhoto, + label: "Info", + slug: "info", + component: NotificationEditCard, + }, +] as const + +export default function NotificationDetailsPage() { + const { notification } = useNotificationDetailsContext() + const router = useRouter() + + const searchParams = useSearchParams() + const currentTab = searchParams.get("tab") || SIDEBAR_LINKS[0].slug + + const handleTabChange = (value: string | null) => { + const params = new URLSearchParams(searchParams.toString()) + params.set("tab", value ?? SIDEBAR_LINKS[0].slug) + router.replace(`/varsler/${notification.id}?${params.toString()}`) + } + + return ( + + + router.back()} /> + {notification.title} + + + + + {SIDEBAR_LINKS.map(({ label, icon: Icon, slug }) => ( + }> + {label} + + ))} + + {SIDEBAR_LINKS.map(({ slug, component: Component }) => ( + + + + ))} + + + ) +} diff --git a/apps/dashboard/src/app/(internal)/varslinger/[id]/provider.tsx b/apps/dashboard/src/app/(internal)/varslinger/[id]/provider.tsx new file mode 100644 index 0000000000..96a7656e1c --- /dev/null +++ b/apps/dashboard/src/app/(internal)/varslinger/[id]/provider.tsx @@ -0,0 +1,16 @@ +"use client" + +import type { Notification } from "@dotkomonline/types" +import { createContext, useContext } from "react" + +export const NotificationDetailsContext = createContext<{ + notification: Notification +} | null>(null) + +export const useNotificationDetailsContext = () => { + const ctx = useContext(NotificationDetailsContext) + if (ctx === null) { + throw new Error("useNotificationDetailsContext called without Provider in tree") + } + return ctx +} diff --git a/apps/dashboard/src/app/(internal)/varslinger/all-notification-table.tsx b/apps/dashboard/src/app/(internal)/varslinger/all-notification-table.tsx new file mode 100644 index 0000000000..dc30fd0ad9 --- /dev/null +++ b/apps/dashboard/src/app/(internal)/varslinger/all-notification-table.tsx @@ -0,0 +1,73 @@ +import { GenericTable } from "@/components/GenericTable" +import { Anchor } from "@mantine/core" +import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table" +import Link from "next/link" +import { useMemo } from "react" +import { mapNotificationPayloadTypeToLabel, mapNotificationTypeToLabel, Notification } from "node_modules/@dotkomonline/rpc/src/modules/notification/notification" +import { DateTooltip } from "@/components/DateTooltip" + +interface AllNotificationsTableProps { + notifications: Notification[] +} + +export const AllNotificationsTable = ({ notifications }: AllNotificationsTableProps) => { + const columnHelper = createColumnHelper() + const columns = useMemo( + () => [ + columnHelper.accessor((notification) => notification.title, { + id: "title", + header: () => "Tittel", + sortingFn: "alphanumeric", + cell: (info) => ( + + {info.getValue()} + + ), + }), + + columnHelper.accessor((notification) => notification.shortDescription, { + id: "shortDescription", + header: () => "Kort beskrivelse", + cell: (info) => info.getValue(), + sortingFn: "alphanumeric", + }), + + columnHelper.accessor((notification) => notification.type, { + id: "type", + header: () => "Type", + cell: (info) => mapNotificationTypeToLabel(info.getValue()), + sortingFn: "alphanumeric", + }), + columnHelper.accessor((notification) => notification.payloadType, { + id: "payloadType", + header: () => "Payload type", + cell: (info) => mapNotificationPayloadTypeToLabel(info.getValue()), + sortingFn: "alphanumeric", + }), + columnHelper.accessor((notification) => notification.createdAt, { + id: "createdAt", + header: () => "Opprettet", + cell: (info) => , + sortingFn: "datetime", + }), + ], + [columnHelper] + + ) + + const tableOptions = useMemo( + () => ({ + data: notifications, + getCoreRowModel: getCoreRowModel(), + columns, + }), + [notifications, columns] + ) + + const table = useReactTable(tableOptions) + return ( + + ) +} diff --git a/apps/dashboard/src/app/(internal)/varslinger/modals/create-notification.tsx b/apps/dashboard/src/app/(internal)/varslinger/modals/create-notification.tsx new file mode 100644 index 0000000000..bf1384fa09 --- /dev/null +++ b/apps/dashboard/src/app/(internal)/varslinger/modals/create-notification.tsx @@ -0,0 +1,26 @@ +import { type ContextModalProps, modals } from "@mantine/modals" +import type { FC } from "react" +import { useCreateNotificationMutation } from "../mutations" +import { useNotificationWriteForm } from "../write-form" + +export const CreateNotificationModal: FC = ({ context, id }) => { + const close = () => context.closeModal(id) + const create = useCreateNotificationMutation() + const FormComponent = useNotificationWriteForm({ + onSubmit: (data) => { + create.mutate( + data + ), + close() + }, + }) + return +} + +export const useCreateNotificationModal = () => () => + modals.openContextModal({ + modal: "notification/create", + title: "Legg inn ny varsling", + size: "lg", + innerProps: {}, + }) diff --git a/apps/dashboard/src/app/(internal)/varslinger/mutations.ts b/apps/dashboard/src/app/(internal)/varslinger/mutations.ts new file mode 100644 index 0000000000..5663479974 --- /dev/null +++ b/apps/dashboard/src/app/(internal)/varslinger/mutations.ts @@ -0,0 +1,67 @@ +import { env } from "@/lib/env" +import { useQueryNotification } from "@/lib/notifications" +import { useTRPC } from "@/lib/trpc-client" +import { useMutation, useQueryClient } from "@tanstack/react-query" + +export const useCreateNotificationMutation = () => { + const trpc = useTRPC() + const queryClient = useQueryClient() + const notification = useQueryNotification() + return useMutation( + trpc.notification.create.mutationOptions({ + onMutate: () => { + notification.loading({ + title: "Lager varsling...", + message: "Varslingen blir opprettet, og du vil bli videresendt til varslingssiden.", + }) + }, + onSuccess: async (data) => { + await queryClient.invalidateQueries({ queryKey: trpc.notification.findMany.queryKey() }); + + + notification.complete({ + title: "Varsling opprettet", + message: `Varslingen "${data.title}" har blitt opprettet.`, + }) + }, + onError: (err) => { + notification.fail({ + title: "Feil oppsto", + message: `En feil oppsto under opprettelse av varslingen: ${err.toString()}.`, + }) + }, + }) + ) +} + +export const useEditNotificationMutation = () => { + const trpc = useTRPC() + const queryClient = useQueryClient() + const notification = useQueryNotification() + + return useMutation( + trpc.notification.edit.mutationOptions({ + onMutate: () => { + notification.loading({ + title: "Oppdaterer varsling...", + message: "Varslingen blir oppdatert.", + }) + }, + onSuccess: async (data) => { + await queryClient.invalidateQueries(trpc.notification.get.queryOptions(data.id)) + + notification.complete({ + title: "Varslingen oppdatert", + message: `Varslingen "${data.title}" har blitt oppdatert.`, + }) + }, + onError: (err) => { + notification.fail({ + title: "Feil oppsto", + message: `En feil oppsto under oppdatering av varslingen: ${err.toString()}.`, + }) + }, + }) + ) +} + diff --git a/apps/dashboard/src/app/(internal)/varslinger/page.tsx b/apps/dashboard/src/app/(internal)/varslinger/page.tsx new file mode 100644 index 0000000000..c107ed80a0 --- /dev/null +++ b/apps/dashboard/src/app/(internal)/varslinger/page.tsx @@ -0,0 +1,23 @@ +"use client" + +import { Box, Button, Skeleton, Stack } from "@mantine/core" +import { AllNotificationsTable } from "./all-notification-table" +import { useCreateNotificationModal } from "./modals/create-notification" +import { useNotificationAllQuery } from "./queries" + + +export default function NotificationPage() { + const { notifications, isLoading: isNotificationsLoading } = useNotificationAllQuery() + const open = useCreateNotificationModal() + + return ( + + + + + + + + + ) +} diff --git a/apps/dashboard/src/app/(internal)/varslinger/queries.ts b/apps/dashboard/src/app/(internal)/varslinger/queries.ts new file mode 100644 index 0000000000..e20309fecc --- /dev/null +++ b/apps/dashboard/src/app/(internal)/varslinger/queries.ts @@ -0,0 +1,18 @@ +import { useTRPC } from "@/lib/trpc-client" +import { Pageable } from "@dotkomonline/rpc" +import { useInfiniteQuery } from "@tanstack/react-query" +import { useMemo } from "react" + +export const useNotificationAllQuery = (page?: Pageable) => { + const trpc = useTRPC() + + const { data, ...query } = useInfiniteQuery({ + ...trpc.notification.findMany.infiniteQueryOptions({ ...page }), + getNextPageParam: (lastPage) => lastPage.nextCursor, + select: (res) => res.pages.flatMap((p) => p.items), + }) + + return { notifications: useMemo(() => data ?? [], [data]), ...query } +} + + diff --git a/apps/dashboard/src/app/(internal)/varslinger/write-form.tsx b/apps/dashboard/src/app/(internal)/varslinger/write-form.tsx new file mode 100644 index 0000000000..48c379147f --- /dev/null +++ b/apps/dashboard/src/app/(internal)/varslinger/write-form.tsx @@ -0,0 +1,83 @@ +import { useFormBuilder } from "@/components/forms/Form" +import { createRichTextInput } from "@/components/forms/RichTextInput/RichTextInput" +import { createSelectInput } from "@/components/forms/SelectInput" +import { createTextInput } from "@/components/forms/TextInput" +import { mapNotificationPayloadTypeToLabel, mapNotificationTypeToLabel, NotificationPayloadTypeSchema, NotificationTypeSchema, NotificationWriteSchema } from "node_modules/@dotkomonline/rpc/src/modules/notification/notification" +import { type z } from "zod" +import { useGroupAllQuery } from "../grupper/queries" + +const NOTIFICATION_FORM_DATA_TYPE = Object.values(NotificationTypeSchema.Values).map((type) => ({ + value: type, + label: mapNotificationTypeToLabel(type), +})) + +const NOTIFICATION_FORM_DATA_PAYLOAD_TYPE = Object.values(NotificationPayloadTypeSchema.Values).map((type) => ({ + value: type, + label: mapNotificationPayloadTypeToLabel(type), +})) + +const NOTIFICATION_FORM_DEFAULT_VALUES: Partial = {recipientIds: [], taskId: null } + +type NotificationWriteFormSchema = z.infer + +interface UseNotificationWriteFormProps { + onSubmit(data: z.infer): void + defaultValues?: Partial + label?: string +} + +export const useNotificationWriteForm = ({ + onSubmit, + label = "Legg inn ny varsling", + defaultValues = NOTIFICATION_FORM_DEFAULT_VALUES, +}: UseNotificationWriteFormProps) => { + + const { groups } = useGroupAllQuery() + + return useFormBuilder({ + schema: NotificationWriteSchema, + defaultValues, + onSubmit, + label, + fields: { + title: createTextInput({ + label: "Tittel", + placeholder: "Juleball Påmeldingen er åpen!", + required: true, + }), + shortDescription: createTextInput({ + label: "Kort beskrivelse", + placeholder: "En kort beskrivelse av varslingen", + required: true, + }), + content: createRichTextInput({ + label: "Innhold", + required: true, + }), + type: createSelectInput ({ + data: NOTIFICATION_FORM_DATA_TYPE, + label: "Type", + placeholder: "Velg type", + required: true, + }), + payload: createTextInput ({ + label: "Payload", + placeholder: "Paylaod", + required: false, + }), + payloadType: createSelectInput ({ + label: "payload type", + data: NOTIFICATION_FORM_DATA_PAYLOAD_TYPE, + placeholder: "Velg type", + required: true, + }), + actorGroupId: createSelectInput({ + label: "Ansvarlig gruppe", + placeholder: "Velg gruppe", + data: groups.map((group) => ({ value: group.slug, label: group.abbreviation })), + searchable: true, + required: true, + }), + }, + }) +} \ No newline at end of file diff --git a/apps/dashboard/src/app/ApplicationShell.tsx b/apps/dashboard/src/app/ApplicationShell.tsx index ab53a3c8cd..40fd0eda93 100644 --- a/apps/dashboard/src/app/ApplicationShell.tsx +++ b/apps/dashboard/src/app/ApplicationShell.tsx @@ -38,6 +38,7 @@ import { IconMoneybag, IconPhoto, IconPhotoShare, + IconBell, IconSkull, IconUserMinus, IconUsersGroup, @@ -99,6 +100,11 @@ const navigations = [ icon: IconUsersGroup, href: "/brukere", }, + { + label: "Varslinger", + icon: IconBell, + href: "/varslinger", + }, { label: "Plakatbestilling", icon: IconPhotoShare, diff --git a/apps/dashboard/src/app/ModalProvider.tsx b/apps/dashboard/src/app/ModalProvider.tsx index 5ae3577eab..af022f4645 100644 --- a/apps/dashboard/src/app/ModalProvider.tsx +++ b/apps/dashboard/src/app/ModalProvider.tsx @@ -25,6 +25,7 @@ import type { FC, PropsWithChildren } from "react" import { QRCodeScannedModal } from "@/app/(internal)/arrangementer/components/qr-code-scanned-modal" import { NotifyAttendeesModal } from "@/app/(internal)/arrangementer/components/notify-attendees-modal" import { CreateGroupMemberModal } from "@/app/(internal)/grupper/modals/create-group-member-modal" +import { CreateNotificationModal } from "@/app/(internal)/varslinger/modals/create-notification" const modals = { "event/attendance/attendee/create": ManualCreateUserAttendModal, @@ -50,6 +51,7 @@ const modals = { "user/membership/create": CreateMembershipModal, "user/membership/update": EditMembershipModal, "image/upload": UploadImageModal, + "notification/create": CreateNotificationModal, } as const export const ModalProvider: FC = ({ children }) => ( diff --git a/apps/dashboard/src/components/forms/Form.tsx b/apps/dashboard/src/components/forms/Form.tsx index fa80445ee7..dcdc8f15b1 100644 --- a/apps/dashboard/src/components/forms/Form.tsx +++ b/apps/dashboard/src/components/forms/Form.tsx @@ -56,9 +56,14 @@ export function useFormBuilder({
{ e.preventDefault() - return form.handleSubmit((values) => { - return onSubmit(values, form) - })(e) + return form.handleSubmit( + (values) => { + return onSubmit(values, form) + }, + (errors) => { + console.log("Form submission failed with validation errors:", errors) + } + )(e) }} > diff --git a/apps/grades-backend/src/index.ts b/apps/grades-backend/src/index.ts index 98c742f704..d4fb75058b 100644 --- a/apps/grades-backend/src/index.ts +++ b/apps/grades-backend/src/index.ts @@ -1,3 +1,2 @@ export type { AppRouter } from "./app-router" - export type * as CourseRouter from "./modules/course/course-router" diff --git a/apps/rpc/src/app-router.ts b/apps/rpc/src/app-router.ts index 2cc69a0f00..43065c42ba 100644 --- a/apps/rpc/src/app-router.ts +++ b/apps/rpc/src/app-router.ts @@ -12,6 +12,7 @@ import { offlineRouter } from "./modules/offline/offline-router" import { rifRouter } from "./modules/rif/rif-router" import { userRouter } from "./modules/user/user-router" import { workspaceRouter } from "./modules/workspace-sync/workspace-router" +import { notificationRouter } from "./modules/notification/notification-router" import { t } from "./trpc" export const appRouter = t.router({ @@ -29,6 +30,7 @@ export const appRouter = t.router({ workspace: workspaceRouter, invoicification: invoicificationRouter, rif: rifRouter, + notification: notificationRouter, }) export type AppRouter = typeof appRouter diff --git a/apps/rpc/src/index.ts b/apps/rpc/src/index.ts index 9be5fcf550..8d92992e2a 100644 --- a/apps/rpc/src/index.ts +++ b/apps/rpc/src/index.ts @@ -13,3 +13,6 @@ export type * as OfflineRouter from "./modules/offline/offline-router" export type * as PersonalMarkRouter from "./modules/mark/personal-mark-router" export type * as WorkspaceRouter from "./modules/workspace-sync/workspace-router" export type * as UserRouter from "./modules/user/user-router" +export type * as NotificationRouter from "./modules/notification/notification-router" + + diff --git a/apps/rpc/src/modules/core.ts b/apps/rpc/src/modules/core.ts index 05aafd4768..530aefb860 100644 --- a/apps/rpc/src/modules/core.ts +++ b/apps/rpc/src/modules/core.ts @@ -220,7 +220,7 @@ export async function createServiceLayer( attendanceRepository ) const feedbackFormAnswerService = getFeedbackFormAnswerService(feedbackFormAnswerRepository, feedbackFormService) - const notificationService = getNotificationService(notificationRepository) + const notificationService = getNotificationService(notificationRepository, userRepository) const taskDiscoveryService = getLocalTaskDiscoveryService(clients.prisma, taskService, recurringTaskService) const attendanceService = getAttendanceService( eventEmitter, diff --git a/apps/rpc/src/modules/notification/notification-repository.ts b/apps/rpc/src/modules/notification/notification-repository.ts index d810abeb54..9cc3defc7f 100644 --- a/apps/rpc/src/modules/notification/notification-repository.ts +++ b/apps/rpc/src/modules/notification/notification-repository.ts @@ -9,9 +9,11 @@ import type { } from "./notification" import type { UserId } from "@dotkomonline/types" +import { Pageable, pageQuery } from "src/query" export interface NotificationRepository { findById(handle: DBHandle, notificationId: NotificationId): Promise + findMany(handle: DBHandle, page: Pageable): Promise create(handle: DBHandle, notificationData: NotificationWrite): Promise update( handle: DBHandle, @@ -45,17 +47,26 @@ export function getNotificationRepository(): NotificationRepository { async create(handle, notificationData) { const { recipientIds, ...data } = notificationData + console.log("IDIOT", notificationData) const notification = await handle.notification.create({ data }) return notification }, async update(handle, notificationId, notificationData) { const { recipientIds, ...data } = notificationData + console.log("IDIOT", notificationData) const notification = await handle.notification.update({ where: { id: notificationId }, data, }) return notification }, + async findMany(handle, page) { + return await handle.notification.findMany({ + ...pageQuery(page), + }) + }, + + async delete(handle, notificationId) { const notification = await handle.notification.findUnique({ where: { id: notificationId }, diff --git a/apps/rpc/src/modules/notification/notification-router.ts b/apps/rpc/src/modules/notification/notification-router.ts index 8e0a6d3894..5343da22ef 100644 --- a/apps/rpc/src/modules/notification/notification-router.ts +++ b/apps/rpc/src/modules/notification/notification-router.ts @@ -4,6 +4,7 @@ import { isEditor } from "../../authorization" import { withAuditLogEntry, withAuthentication, withAuthorization, withDatabaseTransaction } from "../../middlewares" import { procedure, t } from "../../trpc" import { NotificationSchema, NotificationWriteSchema } from "./notification" +import { BasePaginateInputSchema, PaginateInputSchema } from "src/query" export type GetNotificationInput = inferProcedureInput export type GetNotificationOutput = inferProcedureOutput @@ -92,6 +93,20 @@ const markAllAsReadProcedure = procedure return ctx.notificationService.markAllAsRead(ctx.handle, ctx.principal.subject) }) +export type FindNotificationsInput = inferProcedureInput +export type FindNotificationsOutput = inferProcedureOutput +const findNotificationsProcedure = procedure + .input(PaginateInputSchema).use(withAuthentication()).use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .query(async ({ input, ctx }) => { + const items = await ctx.notificationService.findMany(ctx.handle, input) + + return { + items, + nextCursor: items.at(-1)?.id, + } + }) + export const notificationRouter = t.router({ get: getNotificationProcedure, create: createNotificationProcedure, @@ -101,4 +116,5 @@ export const notificationRouter = t.router({ getUnreadCount: getUnreadCountProcedure, markAsRead: markAsReadProcedure, markAllAsRead: markAllAsReadProcedure, + findMany: findNotificationsProcedure, }) diff --git a/apps/rpc/src/modules/notification/notification-service.ts b/apps/rpc/src/modules/notification/notification-service.ts index 0eba9ace0a..3a65d60bc5 100644 --- a/apps/rpc/src/modules/notification/notification-service.ts +++ b/apps/rpc/src/modules/notification/notification-service.ts @@ -9,9 +9,12 @@ import type { } from "./notification" import type { UserId } from "@dotkomonline/types" import type { NotificationRepository } from "./notification-repository" +import { Pageable } from "src/query" +import { UserRepository } from "../user/user-repository" export interface NotificationService { findById(handle: DBHandle, notificationId: NotificationId): Promise + findMany(handle: DBHandle, page: Pageable): Promise create(handle: DBHandle, notificationData: NotificationWrite): Promise update( handle: DBHandle, @@ -34,13 +37,22 @@ export interface NotificationService { markAllAsRead(handle: DBHandle, userId: UserId): Promise } -export function getNotificationService(notificationRepository: NotificationRepository): NotificationService { +export function getNotificationService(notificationRepository: NotificationRepository, userRepository: UserRepository): NotificationService { return { async findById(handle, notificationId) { return await notificationRepository.findById(handle, notificationId) }, + async findMany(handle, page) { + return await notificationRepository.findMany(handle, page) + }, + async create(handle, notificationData) { - return await notificationRepository.create(handle, notificationData) + const data = { ...notificationData } + if (data.recipientIds.length === 0) { + const users = await userRepository.findMany(handle, {}, { take: 10000 }) + data.recipientIds = users.map(user => user.id) + } + return await notificationRepository.create(handle, data) }, async update(handle, notificationId, notificationData) { diff --git a/apps/rpc/src/modules/notification/notification.ts b/apps/rpc/src/modules/notification/notification.ts index 4ca8a9706c..de3e7c3b93 100644 --- a/apps/rpc/src/modules/notification/notification.ts +++ b/apps/rpc/src/modules/notification/notification.ts @@ -1,4 +1,5 @@ import { schemas } from "@dotkomonline/db/schemas" +import { buildSearchFilter } from "@dotkomonline/types" import { z } from "zod" export type Notification = z.infer @@ -31,5 +32,62 @@ export const NotificationWriteSchema = NotificationSchema.pick({ actorGroupId: true, taskId: true, }).extend({ - recipientIds: z.array(z.string().uuid()).min(1), + recipientIds: z.array(z.string().uuid()), }) + + + +export const mapNotificationTypeToLabel = (notificationType: NotificationType) => { + switch (notificationType) { + case "BROADCAST": + return "Generell varsling"; + case "BROADCAST_IMPORTANT": + return "Viktig varsling"; + case "EVENT_REGISTRATION": + return "Påmelding til arrangement"; + case "EVENT_REMINDER": + return "Påminnelse om arrangement"; + case "EVENT_UPDATE": + return "Oppdatering om arrangement"; + case "JOB_LISTING_REMINDER": + return "Påminnelse om stillingsutlysning"; + case "NEW_ARTICLE": + return "Ny artikkel"; + case "NEW_EVENT": + return "Nytt arrangement"; + case "NEW_FEEDBACK_FORM": + return "Nytt tilbakemeldingsskjema"; + case "NEW_INTEREST_GROUP": + return "Ny interessegruppe"; + case "NEW_JOB_LISTING": + return "Ny stillingsutlysning"; + case "NEW_MARK": + return "Ny prikk"; + case "NEW_OFFLINE": + return "Ny offline"; + default: + return "Ukjent"; + } +} + + +export const mapNotificationPayloadTypeToLabel = (payloadType: NotificationPayloadType) => { + switch (payloadType) { + case "URL": + return "Url"; + case "EVENT": + return "Arrangement"; + case "ARTICLE": + return "Artikkel"; + case "GROUP": + return "Gruppe"; + case "USER": + return "Bruker"; + case "OFFLINE": + return "Offline"; + case "JOB_LISTING": + return "Stillingsutlysning"; + case "NONE": + return "Ingen"; + } +} \ No newline at end of file