Skip to content

Commit 3412917

Browse files
simponmbrage-andreas
authored andcommitted
added notification dashboard
1 parent 9884c96 commit 3412917

21 files changed

Lines changed: 531 additions & 8 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { FC } from "react"
2+
import { useEditNotificationMutation } from "../mutations"
3+
import { useNotificationWriteForm } from "../write-form"
4+
import { useNotificationDetailsContext } from "./provider"
5+
export const NotificationEditCard: FC = () => {
6+
const { notification } = useNotificationDetailsContext()
7+
const edit = useEditNotificationMutation()
8+
9+
const FormComponent = useNotificationWriteForm({
10+
label: "Oppdater varsling",
11+
onSubmit: (data) => {
12+
const { ...notificationData } = data
13+
edit.mutate({ id: notification.id, input: notificationData})
14+
},
15+
defaultValues: { ...notification }
16+
})
17+
return <FormComponent />
18+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"use client"
2+
import { Loader } from "@mantine/core"
3+
import { type PropsWithChildren, use, useMemo } from "react"
4+
import { NotificationDetailsContext } from "./provider"
5+
6+
import { useTRPC } from "@/lib/trpc-client"
7+
import { useQuery } from "@tanstack/react-query"
8+
9+
export default function NotificationDetailsLayout({
10+
children,
11+
params,
12+
}: PropsWithChildren<{ params: Promise<{ id: string }> }>) {
13+
const trpc = useTRPC()
14+
const { id } = use(params)
15+
const { data, isLoading } = useQuery(trpc.notification.get.queryOptions(id))
16+
const value = useMemo(
17+
() =>
18+
!data || isLoading
19+
? null
20+
: {
21+
notification: data,
22+
},
23+
[data, isLoading]
24+
)
25+
26+
if (value === null) {
27+
return <Loader />
28+
}
29+
30+
return <NotificationDetailsContext.Provider value={value}>{children}</NotificationDetailsContext.Provider>
31+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"use client"
2+
3+
import { Box, CloseButton, Group, Tabs, Title } from "@mantine/core"
4+
import { IconPhoto } from "@tabler/icons-react"
5+
import { useRouter, useSearchParams } from "next/navigation"
6+
import { NotificationEditCard } from "./edit-card"
7+
import { useNotificationDetailsContext } from "./provider"
8+
9+
const SIDEBAR_LINKS = [
10+
{
11+
icon: IconPhoto,
12+
label: "Info",
13+
slug: "info",
14+
component: NotificationEditCard,
15+
},
16+
] as const
17+
18+
export default function NotificationDetailsPage() {
19+
const { notification } = useNotificationDetailsContext()
20+
const router = useRouter()
21+
22+
const searchParams = useSearchParams()
23+
const currentTab = searchParams.get("tab") || SIDEBAR_LINKS[0].slug
24+
25+
const handleTabChange = (value: string | null) => {
26+
const params = new URLSearchParams(searchParams.toString())
27+
params.set("tab", value ?? SIDEBAR_LINKS[0].slug)
28+
router.replace(`/varsler/${notification.id}?${params.toString()}`)
29+
}
30+
31+
return (
32+
<Box>
33+
<Group>
34+
<CloseButton onClick={() => router.back()} />
35+
<Title>{notification.title}</Title>
36+
</Group>
37+
38+
<Tabs defaultValue={currentTab} onChange={handleTabChange}>
39+
<Tabs.List>
40+
{SIDEBAR_LINKS.map(({ label, icon: Icon, slug }) => (
41+
<Tabs.Tab key={slug} value={slug} leftSection={<Icon width={14} height={14} />}>
42+
{label}
43+
</Tabs.Tab>
44+
))}
45+
</Tabs.List>
46+
{SIDEBAR_LINKS.map(({ slug, component: Component }) => (
47+
<Tabs.Panel mt="md" key={slug} value={slug}>
48+
<Component />
49+
</Tabs.Panel>
50+
))}
51+
</Tabs>
52+
</Box>
53+
)
54+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use client"
2+
3+
import type { Notification } from "@dotkomonline/types"
4+
import { createContext, useContext } from "react"
5+
6+
export const NotificationDetailsContext = createContext<{
7+
notification: Notification
8+
} | null>(null)
9+
10+
export const useNotificationDetailsContext = () => {
11+
const ctx = useContext(NotificationDetailsContext)
12+
if (ctx === null) {
13+
throw new Error("useNotificationDetailsContext called without Provider in tree")
14+
}
15+
return ctx
16+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { GenericTable } from "@/components/GenericTable"
2+
import { Anchor } from "@mantine/core"
3+
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"
4+
import Link from "next/link"
5+
import { useMemo } from "react"
6+
import { mapNotificationPayloadTypeToLabel, mapNotificationTypeToLabel, Notification } from "node_modules/@dotkomonline/rpc/src/modules/notification/notification"
7+
import { DateTooltip } from "@/components/DateTooltip"
8+
9+
interface AllNotificationsTableProps {
10+
notifications: Notification[]
11+
}
12+
13+
export const AllNotificationsTable = ({ notifications }: AllNotificationsTableProps) => {
14+
const columnHelper = createColumnHelper<Notification>()
15+
const columns = useMemo(
16+
() => [
17+
columnHelper.accessor((notification) => notification.title, {
18+
id: "title",
19+
header: () => "Tittel",
20+
sortingFn: "alphanumeric",
21+
cell: (info) => (
22+
<Anchor component={Link} size="sm" href={`/varsler/${info.row.original.id}`}>
23+
{info.getValue()}
24+
</Anchor>
25+
),
26+
}),
27+
28+
columnHelper.accessor((notification) => notification.shortDescription, {
29+
id: "shortDescription",
30+
header: () => "Kort beskrivelse",
31+
cell: (info) => info.getValue(),
32+
sortingFn: "alphanumeric",
33+
}),
34+
35+
columnHelper.accessor((notification) => notification.type, {
36+
id: "type",
37+
header: () => "Type",
38+
cell: (info) => mapNotificationTypeToLabel(info.getValue()),
39+
sortingFn: "alphanumeric",
40+
}),
41+
columnHelper.accessor((notification) => notification.payloadType, {
42+
id: "payloadType",
43+
header: () => "Payload type",
44+
cell: (info) => mapNotificationPayloadTypeToLabel(info.getValue()),
45+
sortingFn: "alphanumeric",
46+
}),
47+
columnHelper.accessor((notification) => notification.createdAt, {
48+
id: "createdAt",
49+
header: () => "Opprettet",
50+
cell: (info) => <DateTooltip date={info.getValue()} />,
51+
sortingFn: "datetime",
52+
}),
53+
],
54+
[columnHelper]
55+
56+
)
57+
58+
const tableOptions = useMemo(
59+
() => ({
60+
data: notifications,
61+
getCoreRowModel: getCoreRowModel(),
62+
columns,
63+
}),
64+
[notifications, columns]
65+
)
66+
67+
const table = useReactTable(tableOptions)
68+
return (
69+
<GenericTable
70+
table = {table}
71+
/>
72+
)
73+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { type ContextModalProps, modals } from "@mantine/modals"
2+
import type { FC } from "react"
3+
import { useCreateNotificationMutation } from "../mutations"
4+
import { useNotificationWriteForm } from "../write-form"
5+
6+
export const CreateNotificationModal: FC<ContextModalProps> = ({ context, id }) => {
7+
const close = () => context.closeModal(id)
8+
const create = useCreateNotificationMutation()
9+
const FormComponent = useNotificationWriteForm({
10+
onSubmit: (data) => {
11+
create.mutate(
12+
data
13+
),
14+
close()
15+
},
16+
})
17+
return <FormComponent />
18+
}
19+
20+
export const useCreateNotificationModal = () => () =>
21+
modals.openContextModal({
22+
modal: "notification/create",
23+
title: "Legg inn ny varsling",
24+
size: "lg",
25+
innerProps: {},
26+
})
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { env } from "@/lib/env"
2+
import { useQueryNotification } from "@/lib/notifications"
3+
import { useTRPC } from "@/lib/trpc-client"
4+
import { useMutation, useQueryClient } from "@tanstack/react-query"
5+
6+
export const useCreateNotificationMutation = () => {
7+
const trpc = useTRPC()
8+
const queryClient = useQueryClient()
9+
const notification = useQueryNotification()
10+
return useMutation(
11+
trpc.notification.create.mutationOptions({
12+
onMutate: () => {
13+
notification.loading({
14+
title: "Lager varsling...",
15+
message: "Varslingen blir opprettet, og du vil bli videresendt til varslingssiden.",
16+
})
17+
},
18+
onSuccess: async (data) => {
19+
await queryClient.invalidateQueries({ queryKey: trpc.notification.findMany.queryKey() });
20+
21+
22+
notification.complete({
23+
title: "Varsling opprettet",
24+
message: `Varslingen "${data.title}" har blitt opprettet.`,
25+
})
26+
},
27+
onError: (err) => {
28+
notification.fail({
29+
title: "Feil oppsto",
30+
message: `En feil oppsto under opprettelse av varslingen: ${err.toString()}.`,
31+
})
32+
},
33+
})
34+
)
35+
}
36+
37+
export const useEditNotificationMutation = () => {
38+
const trpc = useTRPC()
39+
const queryClient = useQueryClient()
40+
const notification = useQueryNotification()
41+
42+
return useMutation(
43+
trpc.notification.edit.mutationOptions({
44+
onMutate: () => {
45+
notification.loading({
46+
title: "Oppdaterer varsling...",
47+
message: "Varslingen blir oppdatert.",
48+
})
49+
},
50+
onSuccess: async (data) => {
51+
await queryClient.invalidateQueries(trpc.notification.get.queryOptions(data.id))
52+
53+
notification.complete({
54+
title: "Varslingen oppdatert",
55+
message: `Varslingen "${data.title}" har blitt oppdatert.`,
56+
})
57+
},
58+
onError: (err) => {
59+
notification.fail({
60+
title: "Feil oppsto",
61+
message: `En feil oppsto under oppdatering av varslingen: ${err.toString()}.`,
62+
})
63+
},
64+
})
65+
)
66+
}
67+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"use client"
2+
3+
import { Box, Button, Skeleton, Stack } from "@mantine/core"
4+
import { AllNotificationsTable } from "./all-notification-table"
5+
import { useCreateNotificationModal } from "./modals/create-notification"
6+
import { useNotificationAllQuery } from "./queries"
7+
8+
9+
export default function NotificationPage() {
10+
const { notifications, isLoading: isNotificationsLoading } = useNotificationAllQuery()
11+
const open = useCreateNotificationModal()
12+
13+
return (
14+
<Skeleton visible={isNotificationsLoading}>
15+
<Stack>
16+
<Box>
17+
<Button onClick={open}>Opprett varsling</Button>
18+
</Box>
19+
<AllNotificationsTable notifications={notifications} />
20+
</Stack>
21+
</Skeleton>
22+
)
23+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useTRPC } from "@/lib/trpc-client"
2+
import { Pageable } from "@dotkomonline/rpc"
3+
import { useInfiniteQuery } from "@tanstack/react-query"
4+
import { useMemo } from "react"
5+
6+
export const useNotificationAllQuery = (page?: Pageable) => {
7+
const trpc = useTRPC()
8+
9+
const { data, ...query } = useInfiniteQuery({
10+
...trpc.notification.findMany.infiniteQueryOptions({ ...page }),
11+
getNextPageParam: (lastPage) => lastPage.nextCursor,
12+
select: (res) => res.pages.flatMap((p) => p.items),
13+
})
14+
15+
return { notifications: useMemo(() => data ?? [], [data]), ...query }
16+
}
17+
18+

0 commit comments

Comments
 (0)