feat: add channel feed notification
This commit is contained in:
parent
2f6e92d166
commit
67bfda30bc
@ -16,9 +16,19 @@ import { Input } from '@/components/ui/input';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import React from 'react';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '../ui/select';
|
||||
import { NotificationPicker } from '../notification/NotificationPicker';
|
||||
|
||||
const addFormSchema = z.object({
|
||||
name: z.string(),
|
||||
notificationIds: z.array(z.string()).default([]),
|
||||
notifyFrequency: z.enum(['event', 'day', 'week', 'month']),
|
||||
});
|
||||
|
||||
export type FeedChannelEditFormValues = z.infer<typeof addFormSchema>;
|
||||
@ -35,6 +45,8 @@ export const FeedChannelEditForm: React.FC<FeedChannelEditFormProps> =
|
||||
resolver: zodResolver(addFormSchema),
|
||||
defaultValues: props.defaultValues ?? {
|
||||
name: 'New Channel',
|
||||
notificationIds: [],
|
||||
notifyFrequency: 'day',
|
||||
},
|
||||
});
|
||||
|
||||
@ -66,6 +78,61 @@ export const FeedChannelEditForm: React.FC<FeedChannelEditFormProps> =
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="notificationIds"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Notification')}</FormLabel>
|
||||
<FormControl>
|
||||
<NotificationPicker
|
||||
className="w-full"
|
||||
{...field}
|
||||
allowClear={true}
|
||||
mode="multiple"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Select Notification for send')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="notifyFrequency"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Notification Frequency')}</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="event">
|
||||
{t('Every Event')}
|
||||
</SelectItem>
|
||||
<SelectItem value="day">{t('Every Day')}</SelectItem>
|
||||
<SelectItem value="week">
|
||||
{t('Every Week')}
|
||||
</SelectItem>
|
||||
<SelectItem value="month">
|
||||
{t('Every Month')}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter>
|
||||
|
@ -7,7 +7,7 @@ import { PlusOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
|
||||
interface NotificationPickerProps extends SelectProps<string> {}
|
||||
interface NotificationPickerProps extends SelectProps<string[]> {}
|
||||
export const NotificationPicker: React.FC<NotificationPickerProps> = React.memo(
|
||||
(props) => {
|
||||
const { t } = useTranslation();
|
||||
|
@ -26,8 +26,8 @@ function PageComponent() {
|
||||
|
||||
const onSubmit = useEvent(async (values: FeedChannelEditFormValues) => {
|
||||
const res = await createMutation.mutateAsync({
|
||||
...values,
|
||||
workspaceId,
|
||||
name: values.name,
|
||||
});
|
||||
|
||||
utils.feed.channels.refetch();
|
||||
|
@ -0,0 +1,20 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "FeedChannel" ADD COLUMN "notifyFrequency" TEXT NOT NULL DEFAULT 'day';
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_FeedChannelToNotification" (
|
||||
"A" VARCHAR(30) NOT NULL,
|
||||
"B" VARCHAR(30) NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_FeedChannelToNotification_AB_unique" ON "_FeedChannelToNotification"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_FeedChannelToNotification_B_index" ON "_FeedChannelToNotification"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_FeedChannelToNotification" ADD CONSTRAINT "_FeedChannelToNotification_A_fkey" FOREIGN KEY ("A") REFERENCES "FeedChannel"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_FeedChannelToNotification" ADD CONSTRAINT "_FeedChannelToNotification_B_fkey" FOREIGN KEY ("B") REFERENCES "Notification"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -280,7 +280,8 @@ model Notification {
|
||||
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||
|
||||
monitors Monitor[]
|
||||
monitors Monitor[]
|
||||
feedChannels FeedChannel[]
|
||||
|
||||
@@index([workspaceId])
|
||||
}
|
||||
@ -446,14 +447,16 @@ model SurveyResult {
|
||||
}
|
||||
|
||||
model FeedChannel {
|
||||
id String @id @default(cuid()) @db.VarChar(30)
|
||||
workspaceId String @db.VarChar(30)
|
||||
name String
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||
id String @id @default(cuid()) @db.VarChar(30)
|
||||
workspaceId String @db.VarChar(30)
|
||||
name String
|
||||
notifyFrequency String @default("day")
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||
events FeedEvent[]
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||
events FeedEvent[]
|
||||
notifications Notification[]
|
||||
|
||||
@@index([workspaceId])
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import * as z from "zod"
|
||||
import * as imports from "./schemas"
|
||||
import { CompleteWorkspace, RelatedWorkspaceModelSchema, CompleteFeedEvent, RelatedFeedEventModelSchema } from "./index"
|
||||
import { CompleteWorkspace, RelatedWorkspaceModelSchema, CompleteFeedEvent, RelatedFeedEventModelSchema, CompleteNotification, RelatedNotificationModelSchema } from "./index"
|
||||
|
||||
export const FeedChannelModelSchema = z.object({
|
||||
id: z.string(),
|
||||
workspaceId: z.string(),
|
||||
name: z.string(),
|
||||
notifyFrequency: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
})
|
||||
@ -13,6 +14,7 @@ export const FeedChannelModelSchema = z.object({
|
||||
export interface CompleteFeedChannel extends z.infer<typeof FeedChannelModelSchema> {
|
||||
workspace: CompleteWorkspace
|
||||
events: CompleteFeedEvent[]
|
||||
notifications: CompleteNotification[]
|
||||
}
|
||||
|
||||
/**
|
||||
@ -23,4 +25,5 @@ export interface CompleteFeedChannel extends z.infer<typeof FeedChannelModelSche
|
||||
export const RelatedFeedChannelModelSchema: z.ZodSchema<CompleteFeedChannel> = z.lazy(() => FeedChannelModelSchema.extend({
|
||||
workspace: RelatedWorkspaceModelSchema,
|
||||
events: RelatedFeedEventModelSchema.array(),
|
||||
notifications: RelatedNotificationModelSchema.array(),
|
||||
}))
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as z from "zod"
|
||||
import * as imports from "./schemas"
|
||||
import { CompleteWorkspace, RelatedWorkspaceModelSchema, CompleteMonitor, RelatedMonitorModelSchema } from "./index"
|
||||
import { CompleteWorkspace, RelatedWorkspaceModelSchema, CompleteMonitor, RelatedMonitorModelSchema, CompleteFeedChannel, RelatedFeedChannelModelSchema } from "./index"
|
||||
|
||||
// Helper schema for JSON fields
|
||||
type Literal = boolean | number | string
|
||||
@ -23,6 +23,7 @@ export const NotificationModelSchema = z.object({
|
||||
export interface CompleteNotification extends z.infer<typeof NotificationModelSchema> {
|
||||
workspace: CompleteWorkspace
|
||||
monitors: CompleteMonitor[]
|
||||
feedChannels: CompleteFeedChannel[]
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,4 +34,5 @@ export interface CompleteNotification extends z.infer<typeof NotificationModelSc
|
||||
export const RelatedNotificationModelSchema: z.ZodSchema<CompleteNotification> = z.lazy(() => NotificationModelSchema.extend({
|
||||
workspace: RelatedWorkspaceModelSchema,
|
||||
monitors: RelatedMonitorModelSchema.array(),
|
||||
feedChannels: RelatedFeedChannelModelSchema.array(),
|
||||
}))
|
||||
|
@ -67,18 +67,39 @@ export const feedRouter = router({
|
||||
channelId: z.string(),
|
||||
})
|
||||
)
|
||||
.output(FeedChannelModelSchema.nullable())
|
||||
.output(
|
||||
z
|
||||
.object({
|
||||
notificationIds: z.array(z.string()),
|
||||
})
|
||||
.merge(FeedChannelModelSchema)
|
||||
.nullable()
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const { channelId, workspaceId } = input;
|
||||
|
||||
const channel = prisma.feedChannel.findFirst({
|
||||
const channel = await prisma.feedChannel.findFirst({
|
||||
where: {
|
||||
workspaceId,
|
||||
id: channelId,
|
||||
},
|
||||
include: {
|
||||
notifications: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return channel;
|
||||
if (!channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...channel,
|
||||
notificationIds: channel?.notifications.map((n) => n.id),
|
||||
};
|
||||
}),
|
||||
updateChannelInfo: workspaceProcedure
|
||||
.meta(
|
||||
@ -91,28 +112,48 @@ export const feedRouter = router({
|
||||
z
|
||||
.object({
|
||||
channelId: z.string(),
|
||||
notificationIds: z.array(z.string()).default([]),
|
||||
})
|
||||
.merge(
|
||||
FeedChannelModelSchema.pick({
|
||||
name: true,
|
||||
notifyFrequency: true,
|
||||
})
|
||||
)
|
||||
)
|
||||
.output(FeedChannelModelSchema.nullable())
|
||||
.output(
|
||||
z
|
||||
.object({
|
||||
notificationIds: z.array(z.string()),
|
||||
})
|
||||
.merge(FeedChannelModelSchema)
|
||||
.nullable()
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const { channelId, workspaceId, name } = input;
|
||||
const { channelId, workspaceId, name, notifyFrequency, notificationIds } =
|
||||
input;
|
||||
|
||||
const channel = prisma.feedChannel.update({
|
||||
const channel = await prisma.feedChannel.update({
|
||||
where: {
|
||||
workspaceId,
|
||||
id: channelId,
|
||||
},
|
||||
data: {
|
||||
name,
|
||||
notifyFrequency,
|
||||
notifications: {
|
||||
set: notificationIds.map((id) => ({
|
||||
id,
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return channel;
|
||||
if (!channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { ...channel, notificationIds };
|
||||
}),
|
||||
fetchEventsByCursor: workspaceProcedure
|
||||
.meta(
|
||||
@ -161,20 +202,45 @@ export const feedRouter = router({
|
||||
.input(
|
||||
FeedChannelModelSchema.pick({
|
||||
name: true,
|
||||
})
|
||||
notifyFrequency: true,
|
||||
}).merge(
|
||||
z.object({
|
||||
notificationIds: z.array(z.string()).default([]),
|
||||
})
|
||||
)
|
||||
)
|
||||
.output(
|
||||
z
|
||||
.object({
|
||||
notificationIds: z.array(z.string()),
|
||||
})
|
||||
.merge(FeedChannelModelSchema)
|
||||
)
|
||||
.output(FeedChannelModelSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
const { name, workspaceId } = input;
|
||||
const { name, workspaceId, notifyFrequency, notificationIds } = input;
|
||||
|
||||
const channel = await prisma.feedChannel.create({
|
||||
data: {
|
||||
workspaceId,
|
||||
name,
|
||||
notifyFrequency,
|
||||
notifications: {
|
||||
connect: notificationIds.map((id) => ({ id })),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
notifications: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return channel;
|
||||
return {
|
||||
...channel,
|
||||
notificationIds: channel?.notifications.map((n) => n.id),
|
||||
};
|
||||
}),
|
||||
deleteChannel: workspaceOwnerProcedure
|
||||
.meta(
|
||||
|
Loading…
Reference in New Issue
Block a user