mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
209 lines
6.1 KiB
TypeScript
209 lines
6.1 KiB
TypeScript
import { useLingui } from '@lingui/react';
|
|
import type { DocumentMeta, Signature } from '@prisma/client';
|
|
import { FieldType } from '@prisma/client';
|
|
import { ChevronDown } from 'lucide-react';
|
|
|
|
import {
|
|
DEFAULT_DOCUMENT_DATE_FORMAT,
|
|
convertToLocalSystemFormat,
|
|
} from '@documenso/lib/constants/date-formats';
|
|
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
|
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
|
|
|
|
import { cn } from '../../lib/utils';
|
|
import { Checkbox } from '../checkbox';
|
|
import { Label } from '../label';
|
|
import { RadioGroup, RadioGroupItem } from '../radio-group';
|
|
import { FRIENDLY_FIELD_TYPE } from './types';
|
|
|
|
type FieldIconProps = {
|
|
/**
|
|
* Loose field type since this is used for partial fields.
|
|
*/
|
|
field: {
|
|
inserted?: boolean;
|
|
customText?: string;
|
|
type: FieldType;
|
|
fieldMeta?: TFieldMetaSchema | null;
|
|
signature?: Signature | null;
|
|
};
|
|
documentMeta?: Pick<DocumentMeta, 'dateFormat'>;
|
|
};
|
|
|
|
/**
|
|
* Renders the content inside field containers prior to sealing.
|
|
*/
|
|
export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
|
|
const { _ } = useLingui();
|
|
|
|
const { type, fieldMeta } = field;
|
|
|
|
// Render checkbox layout for checkbox fields, even if no values exist yet
|
|
if (field.type === FieldType.CHECKBOX && field.fieldMeta?.type === 'checkbox') {
|
|
let checkedValues: string[] = [];
|
|
|
|
try {
|
|
checkedValues = fromCheckboxValue(field.customText ?? '');
|
|
} catch (err) {
|
|
// Do nothing.
|
|
|
|
console.error(err);
|
|
}
|
|
|
|
// If no values exist yet, show a placeholder checkbox
|
|
if (!field.fieldMeta.values || field.fieldMeta.values.length === 0) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex gap-1 py-0.5',
|
|
field.fieldMeta.direction === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col gap-y-1',
|
|
)}
|
|
>
|
|
<div className="flex items-center">
|
|
<Checkbox className="h-3 w-3" disabled />
|
|
<Label className="text-foreground ml-1.5 text-xs font-normal opacity-50">
|
|
Checkbox option
|
|
</Label>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex gap-1 py-0.5',
|
|
field.fieldMeta.direction === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col gap-y-1',
|
|
)}
|
|
>
|
|
{field.fieldMeta.values.map((item, index) => (
|
|
<div key={index} className="flex items-center">
|
|
<Checkbox
|
|
className="h-3 w-3"
|
|
id={`checkbox-${index}`}
|
|
checked={checkedValues.includes(
|
|
item.value === '' ? `empty-value-${index + 1}` : item.value, // I got no idea...
|
|
)}
|
|
/>
|
|
|
|
{item.value && (
|
|
<Label
|
|
htmlFor={`checkbox-${index}`}
|
|
className="text-foreground ml-1.5 text-xs font-normal"
|
|
>
|
|
{item.value}
|
|
</Label>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Only render radio if values exist, otherwise render the empty radio field content.
|
|
if (
|
|
field.type === FieldType.RADIO &&
|
|
field.fieldMeta?.type === 'radio' &&
|
|
field.fieldMeta.values &&
|
|
field.fieldMeta.values.length > 0
|
|
) {
|
|
return (
|
|
<div className="flex flex-col gap-y-2 py-0.5">
|
|
<RadioGroup className="gap-y-1">
|
|
{field.fieldMeta.values.map((item, index) => (
|
|
<div key={index} className="flex items-center">
|
|
<RadioGroupItem
|
|
className="pointer-events-none h-3 w-3"
|
|
value={item.value}
|
|
id={`option-${index}`}
|
|
checked={item.value === field.customText}
|
|
/>
|
|
{item.value && (
|
|
<Label
|
|
htmlFor={`option-${index}`}
|
|
className="text-foreground ml-1.5 text-xs font-normal"
|
|
>
|
|
{item.value}
|
|
</Label>
|
|
)}
|
|
</div>
|
|
))}
|
|
</RadioGroup>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (
|
|
field.type === FieldType.DROPDOWN &&
|
|
field.fieldMeta?.type === 'dropdown' &&
|
|
!field.inserted
|
|
) {
|
|
return (
|
|
<div className="text-field-card-foreground flex flex-row items-center py-0.5 text-[clamp(0.07rem,25cqw,0.825rem)] text-sm">
|
|
<p>Select</p>
|
|
<ChevronDown className="h-4 w-4" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (
|
|
field.type === FieldType.SIGNATURE &&
|
|
field.signature?.signatureImageAsBase64 &&
|
|
field.inserted
|
|
) {
|
|
return (
|
|
<img
|
|
src={field.signature.signatureImageAsBase64}
|
|
alt="Signature"
|
|
className="h-full w-full object-contain"
|
|
/>
|
|
);
|
|
}
|
|
|
|
const labelToDisplay = fieldMeta?.label || _(FRIENDLY_FIELD_TYPE[type]) || '';
|
|
let textToDisplay: string | undefined;
|
|
|
|
const isSignatureField =
|
|
field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE;
|
|
|
|
if (field.type === FieldType.TEXT && field.fieldMeta?.type === 'text' && field.fieldMeta?.text) {
|
|
textToDisplay = field.fieldMeta.text;
|
|
}
|
|
|
|
if (field.inserted) {
|
|
if (field.customText) {
|
|
textToDisplay = field.customText;
|
|
}
|
|
|
|
if (field.type === FieldType.DATE) {
|
|
textToDisplay = convertToLocalSystemFormat(
|
|
field.customText ?? '',
|
|
documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
|
|
);
|
|
}
|
|
|
|
if (isSignatureField && field.signature?.typedSignature) {
|
|
textToDisplay = field.signature.typedSignature;
|
|
}
|
|
}
|
|
|
|
const textAlign = fieldMeta && 'textAlign' in fieldMeta ? fieldMeta.textAlign : 'left';
|
|
|
|
return (
|
|
<div className="flex h-full w-full items-center overflow-hidden">
|
|
<p
|
|
className={cn(
|
|
'text-foreground w-full whitespace-pre-wrap text-left text-[clamp(0.07rem,25cqw,0.825rem)] duration-200',
|
|
{
|
|
'!text-center': textAlign === 'center' || !textToDisplay,
|
|
'!text-right': textAlign === 'right',
|
|
'font-signature text-[clamp(0.07rem,25cqw,1.125rem)]': isSignatureField,
|
|
},
|
|
)}
|
|
>
|
|
{textToDisplay || labelToDisplay}
|
|
</p>
|
|
</div>
|
|
);
|
|
};
|