import { checkIsIsoDate, checkIsUrl, isNullish, isObject, valueOrNA } from '@ballerine/common'; import { checkIsDate, JsonDialog } from '@ballerine/ui'; import { FileJson2 } from 'lucide-react'; import { ChangeEvent, ComponentProps, FunctionComponent, useCallback, useEffect, useState, useMemo, } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { toTitleCase } from 'string-ts'; import { Button, buttonVariants } from '../../../../common/components/atoms/Button/Button'; import { Input } from '../../../../common/components/atoms/Input/Input'; import { Select } from '../../../../common/components/atoms/Select/Select'; import { SelectContent } from '../../../../common/components/atoms/Select/Select.Content'; import { SelectItem } from '../../../../common/components/atoms/Select/Select.Item'; import { SelectTrigger } from '../../../../common/components/atoms/Select/Select.Trigger'; import { SelectValue } from '../../../../common/components/atoms/Select/Select.Value'; import { Form } from '../../../../common/components/organisms/Form/Form'; import { FormControl } from '../../../../common/components/organisms/Form/Form.Control'; import { FormField } from '../../../../common/components/organisms/Form/Form.Field'; import { FormItem } from '../../../../common/components/organisms/Form/Form.Item'; import { FormLabel } from '../../../../common/components/organisms/Form/Form.Label'; import { FormMessage } from '../../../../common/components/organisms/Form/Form.Message'; import { AnyRecord } from '../../../../common/types'; import { ctw } from '../../../../common/utils/ctw/ctw'; import { keyFactory } from '../../../../common/utils/key-factory/key-factory'; import { useUpdateDocumentByIdMutation } from '../../../../domains/workflows/hooks/mutations/useUpdateDocumentByIdMutation/useUpdateDocumentByIdMutation'; import { useWatchDropdownOptions } from './hooks/useWatchDropdown'; import { IEditableDetails } from './interfaces'; import { isValidDatetime } from '../../../../common/utils/is-valid-datetime'; import dayjs from 'dayjs'; import { useUpdateDocumentByIdMutation as useUpdateDocumentByIdV2Mutation } from '@/domains/documents/hooks/mutations/useUpdateDocumentById/useUpdateDocumentById'; const useInitialCategorySetValue = ({ form, data }) => { useEffect(() => { const categoryValue = form.getValues('category'); form.setValue('category', categoryValue); }, [form, data]); }; interface IDetailProps extends ComponentProps<'div'> { type: string; children: string; isDecisionComponent: boolean; isDecisionPositive: (isDecisionComponent: boolean, value: string) => boolean; isDecisionNegative: (isDecisionComponent: boolean, value: string) => boolean; } export const Detail: FunctionComponent = ({ type, children, isDecisionPositive, isDecisionComponent, isDecisionNegative, className, ...props }) => { const getValue = (value: unknown) => { if (type === 'datetime-local') { return dayjs(value).utc().format('DD/MM/YYYY HH:mm'); } if (checkIsDate(value, { isStrict: false }) || checkIsIsoDate(value)) { return dayjs(value).format('DD/MM/YYYY'); } if (typeof value === 'boolean') { return value.toString(); } return value; }; const value = getValue(children); return (
{value === 0 ? 0 : valueOrNA(value)}
); }; export const EditableDetails: FunctionComponent = ({ data, valueId, id, directorId, documents, title, workflowId, isSaveDisabled, contextUpdateMethod = 'base', onSubmit: onSubmitCallback, isDocumentsV2, }) => { const [formData, setFormData] = useState(data); const POSITIVE_VALUE_INDICATOR = ['approved']; const NEGATIVE_VALUE_INDICATOR = ['revision', 'rejected', 'declined']; const isDecisionPositive = (isDecisionComponent: boolean, value: string) => { if (typeof value !== 'string') { return false; } return isDecisionComponent && !!value && POSITIVE_VALUE_INDICATOR.includes(value.toLowerCase()); }; const isDecisionNegative = (isDecisionComponent: boolean, value: string) => { if (typeof value !== 'string') { return false; } return isDecisionComponent && !!value && NEGATIVE_VALUE_INDICATOR.includes(value.toLowerCase()); }; const formValues = useMemo(() => { return data?.reduce((acc, curr) => { acc[curr.title] = curr.value; return acc; }, {}); }, [data]); const form = useForm({ values: formValues, }); const { mutate: mutateUpdateWorkflowById } = useUpdateDocumentByIdMutation({ directorId, workflowId, documentId: valueId, }); const { mutate: mutateUpdateDocumentByIdV2 } = useUpdateDocumentByIdV2Mutation(); const onMutateDocumentPropertiesById = ({ document, action, contextUpdateMethod, }: { document: AnyRecord; action: Parameters[0]['action']; contextUpdateMethod: 'base' | 'director'; }) => { if (isDocumentsV2) { mutateUpdateDocumentByIdV2({ documentId: valueId, data: { type: document.type, category: document.category, properties: document.properties, }, }); return; } mutateUpdateWorkflowById({ document, action, contextUpdateMethod, }); }; const onSubmit: SubmitHandler> = formData => { const document = documents?.find(document => document?.id === valueId); const properties = Object.keys(document?.propertiesSchema?.properties ?? {}).reduce( (acc, curr) => { let propertyValue = formData?.[curr]; const isDateTimeProperty = document?.propertiesSchema?.properties?.[curr]?.format === 'date-time'; const isDateProperty = document?.propertiesSchema?.properties?.[curr]?.format === 'date'; const isDateOrDateTimeProperty = isDateTimeProperty || isDateProperty; if (isNullish(propertyValue) || (isDateOrDateTimeProperty && propertyValue === '')) { return acc; } // In case when date value is cleared its value should be set to null to perform successful merge. // Currently, date value could not be cleared because schemas doesnt not allow date values to be nullable. // if (isDateOrDateTimeProperty && !propertyValue) { // acc[curr] = null; // return acc; // } if ( isDateTimeProperty && typeof propertyValue === 'string' && propertyValue?.length === 16 ) { propertyValue = `${propertyValue}:00`; } acc[curr] = propertyValue; return acc; }, {}, ); const newDocument = { ...document, type: formData.type, category: formData.category, properties, }; onSubmitCallback?.(newDocument); return onMutateDocumentPropertiesById({ document: newDocument, action: 'update_document_properties', contextUpdateMethod, }); }; const isDecisionComponent = title === 'Decision'; const getInputType = useCallback( ({ format, type, value, }: { format: string | undefined; type: string | undefined; value: unknown; }) => { if (format === 'date-time' || isValidDatetime(value)) { return 'datetime-local'; } if (format) { return format; } if (type === 'string') { return 'text'; } if (type === 'boolean') { return 'checkbox'; } if (checkIsDate(value, { isStrict: false }) || checkIsIsoDate(value) || type === 'date') { return 'date'; } if (!type) { return 'text'; } return type; }, [], ); useWatchDropdownOptions({ form, data, setFormData }); useInitialCategorySetValue({ form, data, }); return (
{title}
{formData?.map( ({ title, isEditable, type, format, minimum, maximum, pattern, value, valueAlias, dropdownOptions, }) => { const originalValue = form.watch(title); const displayValue = (value: unknown) => { if (isEditable) { return originalValue; } return isNullish(value) || value === '' ? 'N/A' : value; }; const handleInputChange = (event: ChangeEvent) => { const isCheckbox = event.target.type === 'checkbox'; const inputValue = isCheckbox ? event.target.checked : event.target.value; form.setValue(title, inputValue === 'N/A' ? '' : inputValue); }; return ( { if (isDecisionComponent && !value) { return null; } const isInput = [ !checkIsUrl(value) || isEditable, !isObject(value), !Array.isArray(value), ].every(Boolean); const isSelect = isInput && !!dropdownOptions; const inputType = getInputType({ format, type, value, }); return ( {toTitleCase(title)} {(isObject(value) || Array.isArray(value)) && (
} dialogButtonText={`View Information`} json={JSON.stringify(value)} />
)} {checkIsUrl(value) && !isEditable && ( {(valueAlias as string) ?? value} )} {isSelect && ( )} {!isEditable && inputType !== 'checkbox' && isInput && !isSelect && ( {value} )} {(isEditable || inputType === 'checkbox') && isInput && !isSelect && ( )}
); }} /> ); }, )}
{data?.some(({ isEditable }) => isEditable) && ( )}
); };