feat: add archive feature
This commit is contained in:
parent
33de808f3e
commit
3270164710
@ -1,4 +1,4 @@
|
|||||||
import { AppRouterOutput } from '@/api/trpc';
|
import { AppRouterOutput, trpc } from '@/api/trpc';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Badge } from '../ui/badge';
|
import { Badge } from '../ui/badge';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@ -6,6 +6,12 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
|||||||
import { MarkdownViewer } from '../MarkdownEditor';
|
import { MarkdownViewer } from '../MarkdownEditor';
|
||||||
import { FeedIcon } from './FeedIcon';
|
import { FeedIcon } from './FeedIcon';
|
||||||
import { cn } from '@/utils/style';
|
import { cn } from '@/utils/style';
|
||||||
|
import { Button } from '../ui/button';
|
||||||
|
import { LuArchive } from 'react-icons/lu';
|
||||||
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
type FeedEventItemType =
|
type FeedEventItemType =
|
||||||
AppRouterOutput['feed']['fetchEventsByCursor']['items'][number];
|
AppRouterOutput['feed']['fetchEventsByCursor']['items'][number];
|
||||||
@ -14,6 +20,21 @@ export const FeedEventItem: React.FC<{
|
|||||||
className?: string;
|
className?: string;
|
||||||
event: FeedEventItemType;
|
event: FeedEventItemType;
|
||||||
}> = React.memo(({ className, event }) => {
|
}> = React.memo(({ className, event }) => {
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const archiveEventMutation = trpc.feed.archiveEvent.useMutation();
|
||||||
|
const trpcUtils = trpc.useUtils();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleArchive = useEvent(async () => {
|
||||||
|
await archiveEventMutation.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
channelId: event.channelId,
|
||||||
|
eventId: event.id,
|
||||||
|
});
|
||||||
|
trpcUtils.feed.fetchEventsByCursor.refetch();
|
||||||
|
toast.success(t('Event archived'));
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -21,16 +42,26 @@ export const FeedEventItem: React.FC<{
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex-1 gap-2 overflow-hidden">
|
<div className="relative flex-1 gap-2 overflow-hidden">
|
||||||
<div className="mb-2 flex w-full items-center gap-2 overflow-hidden text-sm">
|
<div className="mb-2 flex w-full items-center gap-2 overflow-hidden text-sm">
|
||||||
<div className="border-muted rounded-lg border p-2">
|
<div className="border-muted rounded-lg border p-2">
|
||||||
<FeedIcon source={event.source} size={24} />
|
<FeedIcon source={event.source} size={24} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
|
<div className="overflow-hidden">
|
||||||
<MarkdownViewer value={event.eventContent} />
|
<MarkdownViewer value={event.eventContent} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="secondary"
|
||||||
|
className="absolute right-0 top-0 h-6 w-6 overflow-hidden"
|
||||||
|
onClick={handleArchive}
|
||||||
|
>
|
||||||
|
<LuArchive size={12} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<Badge>{event.source}</Badge>
|
<Badge>{event.source}</Badge>
|
||||||
@ -46,7 +77,7 @@ export const FeedEventItem: React.FC<{
|
|||||||
<TooltipTrigger className="cursor-default self-end text-xs opacity-60">
|
<TooltipTrigger className="cursor-default self-end text-xs opacity-60">
|
||||||
<div>{dayjs(event.createdAt).fromNow()}</div>
|
<div>{dayjs(event.createdAt).fromNow()}</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent side="left">
|
||||||
<p>{dayjs(event.createdAt).format('YYYY-MM-DD HH:mm:ss')}</p>
|
<p>{dayjs(event.createdAt).format('YYYY-MM-DD HH:mm:ss')}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "FeedEvent" ADD COLUMN "archived" BOOLEAN NOT NULL DEFAULT false;
|
@ -531,6 +531,7 @@ model FeedEvent {
|
|||||||
senderName String? // use for display who
|
senderName String? // use for display who
|
||||||
url String? // url link
|
url String? // url link
|
||||||
important Boolean
|
important Boolean
|
||||||
|
archived Boolean @default(false) @db.Boolean
|
||||||
|
|
||||||
channel FeedChannel @relation(fields: [channelId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
channel FeedChannel @relation(fields: [channelId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ export const FeedEventModelSchema = z.object({
|
|||||||
senderName: z.string().nullish(),
|
senderName: z.string().nullish(),
|
||||||
url: z.string().nullish(),
|
url: z.string().nullish(),
|
||||||
important: z.boolean(),
|
important: z.boolean(),
|
||||||
|
archived: z.boolean(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export interface CompleteFeedEvent extends z.infer<typeof FeedEventModelSchema> {
|
export interface CompleteFeedEvent extends z.infer<typeof FeedEventModelSchema> {
|
||||||
|
@ -187,6 +187,7 @@ export const feedRouter = router({
|
|||||||
const { items, nextCursor } = await fetchDataByCursor(prisma.feedEvent, {
|
const { items, nextCursor } = await fetchDataByCursor(prisma.feedEvent, {
|
||||||
where: {
|
where: {
|
||||||
channelId,
|
channelId,
|
||||||
|
archived: false,
|
||||||
},
|
},
|
||||||
limit,
|
limit,
|
||||||
cursor,
|
cursor,
|
||||||
@ -307,6 +308,60 @@ export const feedRouter = router({
|
|||||||
|
|
||||||
return event;
|
return event;
|
||||||
}),
|
}),
|
||||||
|
archiveEvent: workspaceOwnerProcedure
|
||||||
|
.meta(
|
||||||
|
buildFeedPublicOpenapi({
|
||||||
|
method: 'PATCH',
|
||||||
|
path: '/{channelId}/{eventId}/archive',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
channelId: z.string(),
|
||||||
|
eventId: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(z.void())
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const { channelId, eventId } = input;
|
||||||
|
|
||||||
|
await prisma.feedEvent.update({
|
||||||
|
data: {
|
||||||
|
archived: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: eventId,
|
||||||
|
channelId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
unarchiveEvent: workspaceOwnerProcedure
|
||||||
|
.meta(
|
||||||
|
buildFeedPublicOpenapi({
|
||||||
|
method: 'PATCH',
|
||||||
|
path: '/{channelId}/{eventId}/unarchive',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
channelId: z.string(),
|
||||||
|
eventId: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(z.void())
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const { channelId, eventId } = input;
|
||||||
|
|
||||||
|
await prisma.feedEvent.update({
|
||||||
|
data: {
|
||||||
|
archived: false,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: eventId,
|
||||||
|
channelId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
integration: feedIntegrationRouter,
|
integration: feedIntegrationRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user