Files
documenso/apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
2026-05-08 16:04:22 +10:00

223 lines
6.7 KiB
TypeScript

import { zEmail } from '@documenso/lib/utils/zod';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { type Field, type Recipient, RecipientRole, type Signature, SigningStatus } from '@prisma/client';
import { useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { useRevalidator } from 'react-router';
import { z } from 'zod';
const RECIPIENT_ROLE_LABELS: Record<RecipientRole, string> = {
[RecipientRole.SIGNER]: 'Signer',
[RecipientRole.APPROVER]: 'Approver',
[RecipientRole.CC]: 'CC',
[RecipientRole.VIEWER]: 'Viewer',
[RecipientRole.ASSISTANT]: 'Assistant',
};
const ZAdminUpdateRecipientFormSchema = z.object({
name: z.string().min(1),
email: zEmail(),
role: z.nativeEnum(RecipientRole),
});
type TAdminUpdateRecipientFormSchema = z.infer<typeof ZAdminUpdateRecipientFormSchema>;
export type RecipientItemProps = {
recipient: Recipient & {
fields: Array<
Field & {
signature: Signature | null;
}
>;
};
};
export const AdminDocumentRecipientItemTable = ({ recipient }: RecipientItemProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const { revalidate } = useRevalidator();
const form = useForm<TAdminUpdateRecipientFormSchema>({
defaultValues: {
name: recipient.name,
email: recipient.email,
role: recipient.role,
},
});
const { mutateAsync: updateRecipient } = trpc.admin.recipient.update.useMutation();
const columns = useMemo(() => {
return [
{
header: _(msg`ID`),
accessorKey: 'id',
cell: ({ row }) => <div>{row.original.id}</div>,
},
{
header: _(msg`Type`),
accessorKey: 'type',
cell: ({ row }) => <div>{row.original.type}</div>,
},
{
header: _(msg`Inserted`),
accessorKey: 'inserted',
cell: ({ row }) => <div>{row.original.inserted ? 'True' : 'False'}</div>,
},
{
header: _(msg`Value`),
accessorKey: 'customText',
cell: ({ row }) => <div>{row.original.customText}</div>,
},
{
header: _(msg`Signature`),
accessorKey: 'signature',
cell: ({ row }) => (
<div>
{row.original.signature?.typedSignature && <span>{row.original.signature.typedSignature}</span>}
{row.original.signature?.signatureImageAsBase64 && (
<img
src={row.original.signature.signatureImageAsBase64}
alt="Signature"
className="h-12 w-full dark:invert"
/>
)}
</div>
),
},
] satisfies DataTableColumnDef<(typeof recipient)['fields'][number]>[];
}, []);
const onUpdateRecipientFormSubmit = async ({ name, email, role }: TAdminUpdateRecipientFormSchema) => {
try {
await updateRecipient({
id: recipient.id,
name,
email,
role,
});
toast({
title: _(msg`Recipient updated`),
description: _(msg`The recipient has been updated successfully`),
});
await revalidate();
} catch (error) {
toast({
title: _(msg`Failed to update recipient`),
description: error.message,
variant: 'destructive',
});
}
};
return (
<div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onUpdateRecipientFormSubmit)}>
<fieldset
className="flex h-full max-w-xl flex-col gap-y-4"
disabled={form.formState.isSubmitting || recipient.signingStatus === SigningStatus.SIGNED}
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel required>
<Trans>Name</Trans>
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel required>
<Trans>Email</Trans>
</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="role"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel required>
<Trans>Role</Trans>
</FormLabel>
<FormControl>
<Select
value={field.value}
onValueChange={field.onChange}
disabled={form.formState.isSubmitting || recipient.signingStatus === SigningStatus.SIGNED}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.values(RecipientRole).map((role) => (
<SelectItem key={role} value={role}>
{RECIPIENT_ROLE_LABELS[role]}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div>
<Button type="submit" loading={form.formState.isSubmitting}>
<Trans>Update Recipient</Trans>
</Button>
</div>
</fieldset>
</form>
</Form>
<hr className="my-4" />
<h2 className="mb-4 font-semibold text-lg">
<Trans>Fields</Trans>
</h2>
<DataTable columns={columns} data={recipient.fields} />
</div>
);
};