feat: add visible completed fields

This commit is contained in:
David Nguyen
2024-04-18 21:56:31 +07:00
parent 3d3c53db02
commit 6526377f1b
14 changed files with 356 additions and 117 deletions

View File

@ -0,0 +1,29 @@
import { prisma } from '@documenso/prisma';
import { SigningStatus } from '@documenso/prisma/client';
export type GetCompletedFieldsForDocumentOptions = {
documentId: number;
};
export const getCompletedFieldsForDocument = async ({
documentId,
}: GetCompletedFieldsForDocumentOptions) => {
return await prisma.field.findMany({
where: {
documentId,
Recipient: {
signingStatus: SigningStatus.SIGNED,
},
inserted: true,
},
include: {
Signature: true,
Recipient: {
select: {
name: true,
email: true,
},
},
},
});
};

View File

@ -0,0 +1,33 @@
import { prisma } from '@documenso/prisma';
import { SigningStatus } from '@documenso/prisma/client';
export type GetCompletedFieldsForTokenOptions = {
token: string;
};
export const getCompletedFieldsForToken = async ({ token }: GetCompletedFieldsForTokenOptions) => {
return await prisma.field.findMany({
where: {
Document: {
Recipient: {
some: {
token,
},
},
},
Recipient: {
signingStatus: SigningStatus.SIGNED,
},
inserted: true,
},
include: {
Signature: true,
Recipient: {
select: {
name: true,
email: true,
},
},
},
});
};

View File

@ -89,6 +89,10 @@ export const createDocumentFromTemplate = async ({
const documentRecipient = document.Recipient.find((doc) => doc.email === recipient?.email);
if (!documentRecipient) {
throw new Error('Recipient not found.');
}
return {
type: field.type,
page: field.page,
@ -99,7 +103,7 @@ export const createDocumentFromTemplate = async ({
customText: field.customText,
inserted: field.inserted,
documentId: document.id,
recipientId: documentRecipient?.id || null,
recipientId: documentRecipient.id,
};
}),
});

View File

@ -81,6 +81,10 @@ export const duplicateTemplate = async ({
(doc) => doc.email === recipient?.email,
);
if (!duplicatedTemplateRecipient) {
throw new Error('Recipient not found.');
}
return {
type: field.type,
page: field.page,
@ -91,7 +95,7 @@ export const duplicateTemplate = async ({
customText: field.customText,
inserted: field.inserted,
templateId: duplicatedTemplate.id,
recipientId: duplicatedTemplateRecipient?.id || null,
recipientId: duplicatedTemplateRecipient.id,
};
}),
});

View File

@ -0,0 +1,3 @@
import type { getCompletedFieldsForToken } from '../server-only/field/get-completed-fields-for-token';
export type CompletedField = Awaited<ReturnType<typeof getCompletedFieldsForToken>>[number];

View File

@ -0,0 +1,8 @@
/*
Warnings:
- Made the column `recipientId` on table `Field` required. This step will fail if there are existing NULL values in that column.
*/
-- AlterTable
ALTER TABLE "Field" ALTER COLUMN "recipientId" SET NOT NULL;

View File

@ -386,7 +386,7 @@ model Field {
secondaryId String @unique @default(cuid())
documentId Int?
templateId Int?
recipientId Int?
recipientId Int
type FieldType
page Int
positionX Decimal @default(0)
@ -397,7 +397,7 @@ model Field {
inserted Boolean
Document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
Template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade)
Recipient Recipient? @relation(fields: [recipientId], references: [id], onDelete: Cascade)
Recipient Recipient @relation(fields: [recipientId], references: [id], onDelete: Cascade)
Signature Signature?
@@index([documentId])

View File

@ -19,6 +19,7 @@ export type FieldContainerPortalProps = {
field: Field;
className?: string;
children: React.ReactNode;
cardClassName?: string;
};
export function FieldContainerPortal({
@ -44,7 +45,7 @@ export function FieldContainerPortal({
);
}
export function FieldRootContainer({ field, children }: FieldContainerPortalProps) {
export function FieldRootContainer({ field, children, cardClassName }: FieldContainerPortalProps) {
const [isValidating, setIsValidating] = useState(false);
const ref = React.useRef<HTMLDivElement>(null);
@ -78,6 +79,7 @@ export function FieldRootContainer({ field, children }: FieldContainerPortalProp
{
'border-orange-300 ring-1 ring-orange-300': !field.inserted && isValidating,
},
cardClassName,
)}
ref={ref}
data-inserted={field.inserted ? 'true' : 'false'}

View File

@ -30,4 +30,66 @@ const PopoverContent = React.forwardRef<
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };
type PopoverHoverProps = {
trigger: React.ReactNode;
children: React.ReactNode;
contentProps?: React.ComponentPropsWithoutRef<typeof PopoverContent>;
};
const PopoverHover = ({ trigger, children, contentProps }: PopoverHoverProps) => {
const [open, setOpen] = React.useState(false);
const isControlled = React.useRef(false);
const isMouseOver = React.useRef<boolean>(false);
const onMouseEnter = () => {
isMouseOver.current = true;
if (isControlled.current) {
return;
}
setOpen(true);
};
const onMouseLeave = () => {
isMouseOver.current = false;
if (isControlled.current) {
return;
}
setTimeout(() => {
setOpen(isMouseOver.current);
}, 200);
};
const onOpenChange = (newOpen: boolean) => {
isControlled.current = newOpen;
setOpen(newOpen);
};
return (
<Popover open={open} onOpenChange={onOpenChange}>
<PopoverTrigger
className="flex cursor-pointer outline-none"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{trigger}
</PopoverTrigger>
<PopoverContent
side="top"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
{...contentProps}
>
{children}
</PopoverContent>
</Popover>
);
};
export { Popover, PopoverTrigger, PopoverContent, PopoverHover };