Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat)O3-2210: Add support for file picker to form engine #103

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ export interface OHRIFormQuestionOptions {
};
isDateTime?: { labelTrue: boolean; labelFalse: boolean };
usePreviousValueDisabled?: boolean;
allowedFileTypes?: Array<string>;
allowMultiple?: boolean; //Allow Single File Attachments and Multiple file attachments
datasource?: { id: string; config?: Record<string, any> };
datasource?: { name: string; config?: Record<string, any> };
isSearchable?: boolean;
}
Expand All @@ -184,7 +187,8 @@ export type RenderType =
| 'encounter-location'
| 'textarea'
| 'toggle'
| 'fixed-value';
| 'fixed-value'
| 'file'; //allow the form engine to recognize and handle the new file component

export interface PostSubmissionAction {
applyAction(
Expand Down
56 changes: 56 additions & 0 deletions src/components/inputs/file/file.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useState } from 'react';
import { FileUploader } from '@carbon/react';
import { useTranslation } from 'react-i18next';
import { OHRIFormFieldProps } from '../../../api/types';
import { useField } from 'formik';
import { OHRIFormContext } from '../../../ohri-form-context';

interface FileProps extends OHRIFormFieldProps {}

const File: React.FC<FileProps> = ({ question, onChange, handler }) => {
const { t } = useTranslation();
const [field, meta] = useField(question.id);
const { setFieldValue } = React.useContext(OHRIFormContext);

const labelDescription = question.questionOptions.allowedFileTypes
? t('fileUploadDescription', `Upload one of the following file types: ${question.questionOptions.allowedFileTypes}`)
: t('fileUploadDescriptionAny', 'Upload any file type');

const [selectedFiles, setSelectedFiles] = useState([]); // Add state for selected files

const handleFileChange = (event) => {
const newSelectedFiles = Array.from(event.target.files);
setSelectedFiles(newSelectedFiles);
setFieldValue(question.id, newSelectedFiles); // Update form field value
};

return (
<div>
<FileUploader
accept={question.questionOptions.allowedFileTypes ?? []}
buttonKind="primary"
buttonLabel={t('addFile', 'Add files')}
filenameStatus="edit"
iconDescription="Clear file"
labelDescription={labelDescription}
labelTitle={t('fileUploadTitle', 'Upload')}
multiple={question.questionOptions.allowMultiple}
onChange={handleFileChange} // Use handleFileChange to update selectedFiles
/>

{/* Display previews of selected files */}
{selectedFiles.length > 0 && (
<div className="file-previews">
{selectedFiles.map((file, index) => (
<div key={index} className="file-preview">
<p>{file.name}</p>
{file.type.includes('image') ? <img src={URL.createObjectURL(file)} alt="Preview" /> : <span>File Icon</span>}
</div>
))}
</div>
)}
</div>
);
};

export default File;
170 changes: 170 additions & 0 deletions src/registry/registry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { DataSource, FieldValidator, OHRIFormFieldProps, PostSubmissionAction, SubmissionHandler } from '../api/types';
import { getGlobalStore } from '@openmrs/esm-framework';
import { OHRIFormsStore } from '../constants';
import OHRIExtensionParcel from '../components/extension/ohri-extension-parcel.component';
import { EncounterDatetimeHandler } from '../submission-handlers/encounterDatetimeHandler';
import File from '../components/inputs/file/file.component';
import { UISelectExtended } from '../components/inputs/ui-select-extended/ui-select-extended';


export interface RegistryItem {
id: string;
component: any;
import { inbuiltControls } from './inbuilt-components/inbuiltControls';
import { inbuiltFieldSubmissionHandlers } from './inbuilt-components/inbuiltFieldSubmissionHandlers';
import { inbuiltValidators } from './inbuilt-components/inbuiltValidators';
Expand Down Expand Up @@ -32,6 +41,167 @@ export interface FieldSubmissionHandlerRegistration extends ComponentRegistratio
}

export interface FormsRegistryStoreState {
customControls: Array<CustomControlRegistration>;
postSubmissionActions: Array<PostSubmissionActionRegistration>;
}

export const baseFieldComponents: Array<CustomControlRegistration> = [
{
id: 'OHRIText',
loadControl: () => Promise.resolve({ default: OHRIText }),
type: 'text',
alias: '',
},
{
id: 'OHRIRadio',
loadControl: () => Promise.resolve({ default: OHRIRadio }),
type: 'radio',
alias: '',
},
{
id: 'OHRIDate',
loadControl: () => Promise.resolve({ default: OHRIDate }),
type: 'date',
alias: '',
},
{
id: 'OHRINumber',
loadControl: () => Promise.resolve({ default: OHRINumber }),
type: 'number',
alias: 'numeric',
},
{
id: 'OHRIMultiSelect',
loadControl: () => Promise.resolve({ default: OHRIMultiSelect }),
type: 'checkbox',
alias: 'multiCheckbox',
},
{
id: 'OHRIContentSwitcher',
loadControl: () => Promise.resolve({ default: OHRIContentSwitcher }),
type: 'content-switcher',
alias: '',
},
{
id: 'OHRIEncounterLocationPicker',
loadControl: () => Promise.resolve({ default: OHRIEncounterLocationPicker }),
type: 'encounter-location',
alias: '',
},
{
id: 'UISelectExtended',
loadControl: () => Promise.resolve({ default: UISelectExtended }),
type: 'ui-select-extended',
},
{
id: 'OHRIDropdown',
loadControl: () => Promise.resolve({ default: OHRIDropdown }),
type: 'select',
alias: '',
},
{
id: 'OHRITextArea',
loadControl: () => Promise.resolve({ default: OHRITextArea }),
type: 'textarea',
alias: '',
},
{
id: 'OHRIToggle',
loadControl: () => Promise.resolve({ default: OHRIToggle }),
type: 'toggle',
alias: '',
},
{
id: 'OHRIObsGroup',
loadControl: () => Promise.resolve({ default: OHRIObsGroup }),
type: 'group',
alias: '',
},
{
id: 'OHRIRepeat',
loadControl: () => Promise.resolve({ default: OHRIRepeat }),
type: 'repeating',
alias: '',
},
{
id: 'OHRIFixedValue',
loadControl: () => Promise.resolve({ default: OHRIFixedValue }),
type: 'fixed-value',
alias: '',
},
{
id: 'OHRIMarkdown',
loadControl: () => Promise.resolve({ default: OHRIMarkdown }),
type: 'markdown',
alias: '',
},
{
id: 'OHRIExtensionParcel',
loadControl: () => Promise.resolve({ default: OHRIExtensionParcel }),
type: 'extension-widget',
alias: '',
},
{
id: 'OHRIDateTime',
loadControl: () => Promise.resolve({ default: OHRIDate }),
type: 'datetime',
alias: '',
},
{
id: 'file',
loadControl: () => Promise.resolve({ default: File }),
type: 'file',
alias: '',
},
];

const baseHandlers: Array<RegistryItem> = [
{
id: 'ObsSubmissionHandler',
component: ObsSubmissionHandler,
type: 'obs',
},
{
id: 'ObsGroupHandler',
component: ObsSubmissionHandler,
type: 'obsGroup',
},
{
id: 'EncounterLocationSubmissionHandler',
component: EncounterLocationSubmissionHandler,
type: 'encounterLocation',
},
{
id: 'EncounterDatetimeHandler',
component: EncounterDatetimeHandler,
type: 'encounterDatetime',
},
];

const fieldValidators: Array<ValidatorRegistryItem> = [
{
id: 'OHRIBaseValidator',
component: OHRIFieldValidator,
},
{
id: 'date',
component: OHRIDateValidator,
},
{
id: 'js_expression',
component: OHRIJSExpressionValidator,
},
];

const dataSources: Array<DataSourceRegistryItem> = [];

export const getFieldComponent = renderType => {
let lazy = baseFieldComponents.find(item => item.type == renderType || item?.alias == renderType)?.loadControl;
if (!lazy) {
lazy = getOHRIFormsStore().customControls.find(item => item.type == renderType || item?.alias == renderType)
?.loadControl;
}
return lazy?.();
controls: CustomControlRegistration[];
fieldValidators: ComponentRegistration<FieldValidator>[];
fieldSubmissionHandlers: FieldSubmissionHandlerRegistration[];
Expand Down