Skip to content

Commit a57a086

Browse files
ErlendSaebrage-andreas
authored andcommitted
added recipientpage
1 parent 8b03780 commit a57a086

18 files changed

Lines changed: 417 additions & 65 deletions

apps/dashboard/src/app/(internal)/arrangementer/[id]/notification-page.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DateTooltip } from "@/components/DateTooltip"
55
import { mapNotificationPayloadTypeToLabel, mapNotificationTypeToLabel, type Notification } from "@dotkomonline/rpc"
66
import { Anchor, Box, Button, Skeleton, Stack, Title } from "@mantine/core"
77
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"
8+
import { slugify } from "@dotkomonline/utils"
89
import Link from "next/link"
910
import { type FC, useMemo } from "react"
1011
import { useNotificationsByPayloadQuery } from "../queries"
@@ -13,7 +14,9 @@ import { openCreateEventNotificationModal } from "../components/create-event-not
1314

1415
export const NotificationsPage: FC = () => {
1516
const { event } = useEventContext()
16-
const { notifications, isLoading } = useNotificationsByPayloadQuery("EVENT", event.id)
17+
const attendanceId = event.attendanceId ?? undefined
18+
const eventPath = `${slugify(event.title)}/${event.id}`
19+
const { notifications, isLoading } = useNotificationsByPayloadQuery("EVENT", eventPath)
1720

1821
const columnHelper = createColumnHelper<Notification>()
1922

@@ -73,7 +76,10 @@ export const NotificationsPage: FC = () => {
7376
<Stack gap="lg">
7477
<Box>
7578
<Title order={3}>Opprett varsling</Title>
76-
<Button mt="md" onClick={openCreateEventNotificationModal({ eventId: event.id })}>
79+
<Button
80+
mt="md"
81+
onClick={openCreateEventNotificationModal({ eventId: event.id, eventPath, attendanceId })}
82+
>
7783
Legg til ny varsling
7884
</Button>
7985
</Box>
Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,42 @@
1+
"use client"
2+
13
import { useNotificationWriteForm } from "@/app/(internal)/varslinger/write-form"
4+
import { useTRPC } from "@/lib/trpc-client"
5+
import type { AttendanceId } from "@dotkomonline/types"
26
import { type ContextModalProps, modals } from "@mantine/modals"
7+
import { useQuery } from "@tanstack/react-query"
8+
import { skipToken } from "@tanstack/react-query"
39
import type { FC } from "react"
410
import { useCreateEventNotificationMutation } from "../mutations"
511

612
interface CreateEventNotificationModalProps {
713
eventId: string
14+
eventPath: string
15+
attendanceId: AttendanceId | undefined
816
}
917

1018
export const CreateEventNotificationModal: FC<ContextModalProps<CreateEventNotificationModalProps>> = ({
1119
context,
1220
id,
13-
innerProps: { eventId },
21+
innerProps: { eventId, eventPath, attendanceId },
1422
}) => {
1523
const close = () => context.closeModal(id)
1624
const create = useCreateEventNotificationMutation(eventId)
25+
const trpc = useTRPC()
1726

27+
const { data: attendeeUserIds = [] } = useQuery(
28+
trpc.event.attendance.getAttendeeUserIds.queryOptions(attendanceId ?? skipToken)
29+
)
1830
const FormComponent = useNotificationWriteForm({
1931
defaultValues: {
2032
recipientIds: [],
2133
taskId: null,
2234
payloadType: "EVENT",
23-
payload: eventId,
35+
payload: eventPath,
36+
actorGroupId: null,
2437
},
2538
onSubmit: (data) => {
26-
create.mutate(data)
39+
create.mutate({ ...data, recipientIds: attendeeUserIds })
2740
close()
2841
},
2942
})
@@ -32,11 +45,11 @@ export const CreateEventNotificationModal: FC<ContextModalProps<CreateEventNotif
3245
}
3346

3447
export const openCreateEventNotificationModal =
35-
({ eventId }: CreateEventNotificationModalProps) =>
48+
({ eventId, eventPath, attendanceId }: CreateEventNotificationModalProps) =>
3649
() =>
3750
modals.openContextModal({
3851
modal: "event/notification/create",
3952
title: "Legg inn ny varsling",
4053
size: "lg",
41-
innerProps: { eventId },
54+
innerProps: { eventId, eventPath, attendanceId },
4255
})

apps/dashboard/src/app/(internal)/grupper/modals/create-group-notification-modal.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { mapNotificationTypeToLabel, NotificationTypeSchema, NotificationWriteSc
66
import { getActiveGroupMembership } from "@dotkomonline/types"
77
import { type ContextModalProps, modals } from "@mantine/modals"
88
import type { FC } from "react"
9-
import { useGroupAllQuery, useGroupMembersAllQuery } from "../queries"
9+
import { useGroupMembersAllQuery } from "../queries"
1010
import { useCreateGroupNotificationMutation } from "../mutations"
1111

1212
interface CreateGroupNotificationModalProps {
@@ -21,7 +21,6 @@ export const CreateGroupNotificationModal: FC<ContextModalProps<CreateGroupNotif
2121
const close = () => context.closeModal(id)
2222
const create = useCreateGroupNotificationMutation(groupSlug)
2323
const { members } = useGroupMembersAllQuery(groupSlug)
24-
const { groups } = useGroupAllQuery()
2524

2625
const recipientIds = Array.from(members.entries())
2726
.filter(([, member]) => getActiveGroupMembership(member, groupSlug) !== null)
@@ -34,6 +33,7 @@ export const CreateGroupNotificationModal: FC<ContextModalProps<CreateGroupNotif
3433
taskId: null,
3534
payloadType: "GROUP",
3635
payload: groupSlug,
36+
actorGroupId: groupSlug,
3737
},
3838
onSubmit: (data) => {
3939
create.mutate(data)
@@ -43,7 +43,7 @@ export const CreateGroupNotificationModal: FC<ContextModalProps<CreateGroupNotif
4343
fields: {
4444
title: createTextInput({
4545
label: "Tittel",
46-
placeholder: "Juleball Påmeldingen er åpen!",
46+
placeholder: "Nytt oppmøtested!",
4747
required: true,
4848
}),
4949
shortDescription: createTextInput({
@@ -64,13 +64,6 @@ export const CreateGroupNotificationModal: FC<ContextModalProps<CreateGroupNotif
6464
placeholder: "Velg type",
6565
required: true,
6666
}),
67-
actorGroupId: createSelectInput({
68-
label: "Ansvarlig gruppe",
69-
placeholder: "Velg gruppe",
70-
data: groups.map((group) => ({ value: group.slug, label: group.abbreviation })),
71-
searchable: true,
72-
required: true,
73-
}),
7467
},
7568
})
7669

apps/dashboard/src/app/(internal)/varslinger/[id]/edit-card.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ export const NotificationEditCard: FC = () => {
1010
const FormComponent = useNotificationWriteForm({
1111
label: "Oppdater varsling",
1212
onSubmit: (data) => {
13-
const { ...notificationData } = data
14-
edit.mutate({ id: notification.id, input: notificationData })
13+
edit.mutate({ id: notification.id, input: data })
14+
},
15+
defaultValues: {
16+
...notification,
17+
shortDescription: notification.shortDescription ?? "",
18+
payload: notification.payload ?? "",
19+
recipientIds: [],
1520
},
16-
defaultValues: { ...notification },
1721
})
1822
return <FormComponent />
1923
}

apps/dashboard/src/app/(internal)/varslinger/[id]/page.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
"use client"
22

33
import { Box, CloseButton, Group, Tabs, Title } from "@mantine/core"
4-
import { IconPhoto } from "@tabler/icons-react"
4+
import { IconListDetails, IconUsers } from "@tabler/icons-react"
55
import { useRouter, useSearchParams } from "next/navigation"
66
import { NotificationEditCard } from "./edit-card"
7+
import { NotificationRecipientsPage } from "./recipients-page"
78
import { useNotificationDetailsContext } from "./provider"
89

910
const SIDEBAR_LINKS = [
1011
{
11-
icon: IconPhoto,
12+
icon: IconListDetails,
1213
label: "Info",
1314
slug: "info",
1415
component: NotificationEditCard,
1516
},
17+
{
18+
icon: IconUsers,
19+
label: "Mottakere",
20+
slug: "mottakere",
21+
component: NotificationRecipientsPage,
22+
},
1623
] as const
1724

1825
export default function NotificationDetailsPage() {
@@ -25,7 +32,7 @@ export default function NotificationDetailsPage() {
2532
const handleTabChange = (value: string | null) => {
2633
const params = new URLSearchParams(searchParams.toString())
2734
params.set("tab", value ?? SIDEBAR_LINKS[0].slug)
28-
router.replace(`/varsler/${notification.id}?${params.toString()}`)
35+
router.replace(`/varslinger/${notification.id}?${params.toString()}`)
2936
}
3037

3138
return (
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"use client"
2+
3+
import { GenericTable } from "@/components/GenericTable"
4+
import { DateTooltip } from "@/components/DateTooltip"
5+
import { RecipientPickerInput } from "@/components/forms/RecipientPickerInput"
6+
import { Box, Button, Skeleton, Stack, Title } from "@mantine/core"
7+
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"
8+
import { type FC, useMemo, useState } from "react"
9+
import { useNotificationRecipientsQuery } from "../queries"
10+
import { useAddNotificationRecipientsMutation } from "../mutations"
11+
import { useNotificationDetailsContext } from "./provider"
12+
13+
type Recipient = {
14+
id: string
15+
readAt: Date | null
16+
userId: string
17+
user: { id: string; name: string | null }
18+
}
19+
20+
export const NotificationRecipientsPage: FC = () => {
21+
const { notification } = useNotificationDetailsContext()
22+
const { recipients, isLoading } = useNotificationRecipientsQuery(notification.id)
23+
const addRecipients = useAddNotificationRecipientsMutation(notification.id)
24+
const [recipientIds, setRecipientIds] = useState<string[]>([])
25+
26+
const columnHelper = createColumnHelper<Recipient>()
27+
28+
const columns = useMemo(
29+
() => [
30+
columnHelper.accessor((r) => r.user.name ?? r.userId, {
31+
id: "name",
32+
header: () => "Navn",
33+
cell: (info) => info.getValue(),
34+
sortingFn: "alphanumeric",
35+
}),
36+
columnHelper.accessor((r) => r.readAt, {
37+
id: "readAt",
38+
header: () => "Lest",
39+
cell: (info) => {
40+
const value = info.getValue()
41+
return value ? <DateTooltip date={value} /> : "Ulest"
42+
},
43+
sortingFn: "datetime",
44+
}),
45+
],
46+
[columnHelper]
47+
)
48+
49+
const table = useReactTable({
50+
data: recipients,
51+
getCoreRowModel: getCoreRowModel(),
52+
columns,
53+
})
54+
55+
return (
56+
<Stack gap="lg">
57+
<Box>
58+
<Title order={3} mb="sm">
59+
Legg til mottakere
60+
</Title>
61+
<RecipientPickerInput value={recipientIds} onChange={setRecipientIds} />
62+
<Button
63+
mt="md"
64+
disabled={recipientIds.length === 0 || addRecipients.isPending}
65+
onClick={() => {
66+
addRecipients.mutate(
67+
{ notificationId: notification.id, recipientIds },
68+
{ onSuccess: () => setRecipientIds([]) }
69+
)
70+
}}
71+
>
72+
Legg til {recipientIds.length > 0 ? `${recipientIds.length} mottaker(e)` : "mottakere"}
73+
</Button>
74+
</Box>
75+
76+
<Box>
77+
<Title order={3} mb="sm">
78+
Nåværende mottakere ({recipients.length})
79+
</Title>
80+
<Skeleton visible={isLoading}>
81+
<GenericTable table={table} />
82+
</Skeleton>
83+
</Box>
84+
</Stack>
85+
)
86+
}

apps/dashboard/src/app/(internal)/varslinger/all-notification-table.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const AllNotificationsTable = ({ notifications }: AllNotificationsTablePr
1919
header: () => "Tittel",
2020
sortingFn: "alphanumeric",
2121
cell: (info) => (
22-
<Anchor component={Link} size="sm" href={`/varsler/${info.row.original.id}`}>
22+
<Anchor component={Link} size="sm" href={`/varslinger/${info.row.original.id}`}>
2323
{info.getValue()}
2424
</Anchor>
2525
),

apps/dashboard/src/app/(internal)/varslinger/mutations.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,23 @@ export const useCreateNotificationMutation = () => {
3232
)
3333
}
3434

35+
export const useAddNotificationRecipientsMutation = (notificationId: string) => {
36+
const trpc = useTRPC()
37+
const queryClient = useQueryClient()
38+
const notification = useQueryNotification()
39+
return useMutation(
40+
trpc.notification.addRecipients.mutationOptions({
41+
onSuccess: async () => {
42+
await queryClient.invalidateQueries(trpc.notification.getRecipients.queryOptions(notificationId))
43+
notification.complete({ title: "Mottakere lagt til", message: "Mottakerne er lagt til varslingen." })
44+
},
45+
onError: (err) => {
46+
notification.fail({ title: "Feil oppsto", message: err.toString() })
47+
},
48+
})
49+
)
50+
}
51+
3552
export const useEditNotificationMutation = () => {
3653
const trpc = useTRPC()
3754
const queryClient = useQueryClient()

apps/dashboard/src/app/(internal)/varslinger/queries.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { useTRPC } from "@/lib/trpc-client"
22
import type { Pageable } from "@dotkomonline/utils"
3-
import { useInfiniteQuery } from "@tanstack/react-query"
3+
import { useInfiniteQuery, useQuery } from "@tanstack/react-query"
44
import { useMemo } from "react"
55

6+
export const useNotificationRecipientsQuery = (notificationId: string) => {
7+
const trpc = useTRPC()
8+
const { data, ...query } = useQuery(trpc.notification.getRecipients.queryOptions(notificationId))
9+
return { recipients: data ?? [], ...query }
10+
}
11+
612
export const useNotificationAllInfiniteQuery = (page?: Pageable) => {
713
const trpc = useTRPC()
814

0 commit comments

Comments
 (0)