Skip to content

Commit

Permalink
feat: implemented EntityFieldGroup (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
chesterkmr committed Jan 31, 2025
1 parent 5721d60 commit 9656b02
Show file tree
Hide file tree
Showing 19 changed files with 515 additions and 63 deletions.
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"react-json-view": "^1.21.3",
"react-phone-input-2": "^2.15.1",
"recharts": "^2.7.2",
"sonner": "^1.4.3",
"string-ts": "^1.2.0",
"tailwind-merge": "^1.10.0",
"zod": "^3.23.4"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,23 @@ const schema: Array<IFormElement<any, any>> = [
{
id: 'document',
element: 'documentfield',
valueDestination: 'users[$0].document',
valueDestination: 'users[$0].documents',
params: {
label: 'Document',
template: {
id: 'document',
},
},
validate: [
{
type: 'document',
value: {
id: 'document',
},
message: 'Document is required',
considerRequired: true,
},
],
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AnyObject } from '@/common';
import { IHttpParams } from '@/common/hooks/useHttp';
import { Button } from '@/components/atoms';
import { useMemo } from 'react';
import { Toaster } from 'sonner';
import { useDynamicForm } from '../../context';
import { useElement, useField } from '../../hooks/external';
import { useMountEvent } from '../../hooks/internal/useMountEvent';
Expand All @@ -11,8 +12,8 @@ import { FieldErrors } from '../../layouts/FieldErrors';
import { FieldPriorityReason } from '../../layouts/FieldPriorityReason';
import { TDynamicFormField } from '../../types';
import { IFieldListParams, useStack } from '../FieldList';
import { EntityFieldGroupDocument } from './components/EntityFieldGroupDocument';
import { EntityFields } from './components/EntityFields';
import { EntityDocument } from './fields/EntityDocument';
import { useEntityFieldGroupList } from './hooks/useEntityFieldGroupList';
import { IEntity } from './types';

Expand All @@ -33,34 +34,36 @@ export const EntityFieldGroup: TDynamicFormField<IEntityFieldGroupParams> = ({ e
const { id: fieldId, hidden } = useElement(element, stack);
const { disabled } = useField(element, stack);
const { addButtonLabel = 'Add Item' } = element.params || {};
const { items, addItem, removeItem } = useEntityFieldGroupList({ element });
const { items, isRemovingEntity, addItem, removeItem } = useEntityFieldGroupList({ element });

const elementsOverride = useMemo(
() => ({
...elementsMap,
documentfield: EntityDocument,
documentfield: EntityFieldGroupDocument,
}),
[elementsMap],
);

console.log('override', elementsOverride);

if (hidden) {
return null;
}

return (
<div className="flex flex-col gap-4" data-testid={`${fieldId}-fieldlist`}>
{items.map((entity: IEntity, index: number) => {
{items?.map((entity: IEntity, index: number) => {
return (
<EntityFields
key={entity.__id}
key={entity.id}
entityId={entity.__id!}
entities={items}
entity={entity}
index={index}
onRemoveClick={() => removeItem(entity.__id!)}
stack={stack}
fieldId={fieldId}
element={element}
elementsOverride={elementsOverride as AnyObject}
isRemovingEntity={isRemovingEntity}
/>
);
})}
Expand All @@ -72,6 +75,7 @@ export const EntityFieldGroup: TDynamicFormField<IEntityFieldGroupParams> = ({ e
<FieldDescription element={element} />
<FieldPriorityReason element={element} />
<FieldErrors element={element} />
<Toaster />
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { ctw } from '@/common';
import { Button } from '@/components/atoms';
import { Input } from '@/components/atoms/Input';
import { createTestId } from '@/components/organisms/Renderer/utils/create-test-id';
import get from 'lodash/get';
import { Upload, XCircle } from 'lucide-react';
import { useCallback, useMemo, useRef } from 'react';
import { useField } from '../../../../hooks/external';
import { useMountEvent } from '../../../../hooks/internal/useMountEvent';
import { useUnmountEvent } from '../../../../hooks/internal/useUnmountEvent';
import { FieldDescription } from '../../../../layouts/FieldDescription';
import { FieldErrors } from '../../../../layouts/FieldErrors';
import { FieldLayout } from '../../../../layouts/FieldLayout';
import { FieldPriorityReason } from '../../../../layouts/FieldPriorityReason';
import { IFormElement, TDynamicFormElement } from '../../../../types';
import { IDocumentFieldParams } from '../../../DocumentField';
import { createOrUpdateFileIdOrFileInDocuments } from '../../../DocumentField/hooks/useDocumentUpload/helpers/create-or-update-fileid-or-file-in-documents';
import { getFileOrFileIdFromDocumentsList } from '../../../DocumentField/hooks/useDocumentUpload/helpers/get-file-or-fileid-from-documents-list';
import { removeDocumentFromListByTemplateId } from '../../../DocumentField/hooks/useDocumentUpload/helpers/remove-document-from-list-by-template-id';
import { useStack } from '../../../FieldList';

export const EntityFieldGroupDocument: TDynamicFormElement<
'documentfield',
IDocumentFieldParams
> = ({ element }) => {
useMountEvent(element);
useUnmountEvent(element);

const { params } = element;
const { placeholder = 'Choose file', acceptFileFormats = undefined } = params || {};

const { stack } = useStack();
const {
value: documentsList,
disabled,
onChange,
onBlur,
onFocus,
} = useField<Array<IDocumentFieldParams['template']> | undefined>(element, stack);
const value = useMemo(
() =>
getFileOrFileIdFromDocumentsList(
documentsList,
element as IFormElement<'documentfield', IDocumentFieldParams>,
),
[documentsList, element],
);

const file = useMemo(() => {
if (value instanceof File) {
return value;
}

if (typeof value === 'string') {
return new File([], value);
}

return undefined;
}, [value]);

const inputRef = useRef<HTMLInputElement>(null);
const focusInputOnContainerClick = useCallback(() => {
inputRef.current?.click();
}, [inputRef]);

const clearFileAndInput = useCallback(() => {
if (!element.params?.template?.id) {
console.warn('Template id is migging in element', element);

return;
}

const updatedDocuments = removeDocumentFromListByTemplateId(
documentsList,
element.params?.template?.id as string,
);

onChange(updatedDocuments);

if (inputRef.current) {
inputRef.current.value = '';
}
}, [documentsList, element, onChange]);

const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const documents = get(documentsList || [], element.valueDestination);
const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
documents,
element,
e.target.files?.[0] as File,
);
onChange(updatedDocuments);
},
[onChange],
);

return (
<FieldLayout element={element}>
<div
className={ctw(
'relative flex h-[56px] flex-row items-center gap-3 rounded-[16px] border bg-white px-4',
{ 'pointer-events-none opacity-50': disabled },
)}
onClick={focusInputOnContainerClick}
data-testid={createTestId(element, stack)}
>
<div className="flex gap-3 text-[#007AFF]">
<Upload />
<span className="select-none whitespace-nowrap text-base font-bold">{placeholder}</span>
</div>
<span className="truncate text-sm">{file ? file.name : 'No File Choosen'}</span>
{file && (
<Button
variant="ghost"
size="icon"
className="h-[28px] w-[28px] rounded-full"
onClick={e => {
e.stopPropagation();
clearFileAndInput();
}}
>
<div className="rounded-full bg-white">
<XCircle />
</div>
</Button>
)}
<Input
data-testid={`${createTestId(element, stack)}-hidden-input`}
type="file"
placeholder={placeholder}
accept={acceptFileFormats}
disabled={disabled}
onChange={handleChange}
onBlur={onBlur}
onFocus={onFocus}
ref={inputRef}
className="hidden"
/>
</div>
<FieldDescription element={element} />
<FieldPriorityReason element={element} />
<FieldErrors element={element} />
</FieldLayout>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './EntityFieldGroupDocument';
Loading

0 comments on commit 9656b02

Please sign in to comment.