diff --git a/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/index.tsx b/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/index.tsx index f0fdc1239..4c0ea5e3e 100644 --- a/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/index.tsx +++ b/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/index.tsx @@ -6,3 +6,4 @@ export { default as Toggle } from './toggle/toggle.component'; export { default as UiSelectExtended } from './ui-select-extended/ui-select-extended.component'; export { default as Markdown } from './markdown/markdown.component'; export { default as SelectAnswers } from './select/select-answers.component'; +export { default as WorkspaceLauncher } from './workspace-launcher/workspace-launcher.component'; diff --git a/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/workspace-launcher/workspace-launcher.component.tsx b/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/workspace-launcher/workspace-launcher.component.tsx new file mode 100644 index 000000000..1496b66dc --- /dev/null +++ b/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/workspace-launcher/workspace-launcher.component.tsx @@ -0,0 +1,60 @@ +import React, { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { TextInput } from '@carbon/react'; +import { useFormField } from '../../../../form-field-context'; + +const WorkspaceLauncher: React.FC = () => { + const { t } = useTranslation(); + const { formField, setFormField } = useFormField(); + + const { buttonLabel, workspaceName } = useMemo( + () => ({ + buttonLabel: formField.questionOptions?.buttonLabel ?? '', + workspaceName: formField.questionOptions?.workspaceName ?? '', + }), + [formField.questionOptions?.buttonLabel, formField.questionOptions?.workspaceName], + ); + + const handleButtonLabelChange = useCallback( + (event: React.ChangeEvent) => { + setFormField((prevFormField) => ({ + ...prevFormField, + questionOptions: { ...prevFormField.questionOptions, buttonLabel: event.target.value }, + })); + }, + [setFormField], + ); + + const handleWorkspaceNameChange = useCallback( + (event: React.ChangeEvent) => { + setFormField((prevFormField) => ({ + ...prevFormField, + questionOptions: { ...prevFormField.questionOptions, workspaceName: event.target.value }, + })); + }, + [setFormField], + ); + + return ( + <> + + + + ); +}; + +export default React.memo(WorkspaceLauncher); diff --git a/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/workspace-launcher/workspace-launcher.test.tsx b/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/workspace-launcher/workspace-launcher.test.tsx new file mode 100644 index 000000000..f56481197 --- /dev/null +++ b/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/workspace-launcher/workspace-launcher.test.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import WorkspaceLauncher from './workspace-launcher.component'; +import { FormFieldProvider } from '../../../../form-field-context'; +import type { FormField } from '@openmrs/esm-form-engine-lib'; + +const mockSetFormField = jest.fn(); +const formField: FormField = { + type: 'obs', + questionOptions: { + rendering: 'workspace-launcher' as const, + buttonLabel: '', + workspaceName: '', + }, + id: '1', +}; + +jest.mock('../../../../form-field-context', () => ({ + ...jest.requireActual('../../../../form-field-context'), + useFormField: () => ({ formField, setFormField: mockSetFormField }), +})); + +describe('WorkspaceLauncher', () => { + it('renders workspace launcher inputs', () => { + renderWorkspaceLauncher(); + expect(screen.getByLabelText(/Button Label/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/Workspace Name/i)).toBeInTheDocument(); + }); + + it('displays placeholder text for both inputs', () => { + renderWorkspaceLauncher(); + expect(screen.getByPlaceholderText(/Enter text to display on the button/i)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(/Enter the name of the workspace to launch/i)).toBeInTheDocument(); + }); + + it('marks both inputs as required', () => { + renderWorkspaceLauncher(); + const buttonLabelInput = screen.getByLabelText(/Button Label/i); + const workspaceNameInput = screen.getByLabelText(/Workspace Name/i); + + expect(buttonLabelInput).toBeRequired(); + expect(workspaceNameInput).toBeRequired(); + }); + + it('updates button label when input changes', async () => { + renderWorkspaceLauncher(); + const user = userEvent.setup(); + const buttonLabelInput = screen.getByLabelText(/Button Label/i); + const newValue = 'Launch Patient Dashboard'; + + await user.clear(buttonLabelInput); + await user.type(buttonLabelInput, newValue); + + expect(mockSetFormField).toHaveBeenCalled(); + }); + + it('updates workspace name when input changes', async () => { + renderWorkspaceLauncher(); + const user = userEvent.setup(); + const workspaceNameInput = screen.getByLabelText(/Workspace Name/i); + const newValue = 'patient-dashboard'; + + await user.clear(workspaceNameInput); + await user.type(workspaceNameInput, newValue); + + expect(mockSetFormField).toHaveBeenCalled(); + }); + + it('handles pasting text correctly', async () => { + renderWorkspaceLauncher(); + const user = userEvent.setup(); + const workspaceNameInput = screen.getByLabelText(/Workspace Name/i); + const textToPaste = 'pasted-workspace-name'; + + await user.clear(workspaceNameInput); + await user.paste(textToPaste); + + expect(mockSetFormField).toHaveBeenCalled(); + }); +}); + +function renderWorkspaceLauncher() { + render( + + + , + ); +} diff --git a/src/components/interactive-builder/modals/question/question-form/rendering-types/rendering-type.component.tsx b/src/components/interactive-builder/modals/question/question-form/rendering-types/rendering-type.component.tsx index a2939666b..a4bdb2f9e 100644 --- a/src/components/interactive-builder/modals/question/question-form/rendering-types/rendering-type.component.tsx +++ b/src/components/interactive-builder/modals/question/question-form/rendering-types/rendering-type.component.tsx @@ -1,5 +1,15 @@ import React from 'react'; -import { Date, Markdown, Number, SelectAnswers, Text, TextArea, Toggle, UiSelectExtended } from './inputs'; +import { + Date, + Markdown, + Number, + SelectAnswers, + Text, + TextArea, + Toggle, + UiSelectExtended, + WorkspaceLauncher, +} from './inputs'; import { useFormField } from '../../form-field-context'; import type { RenderType } from '@openmrs/esm-form-engine-lib'; import { renderTypeOptions, renderingTypes } from '@constants'; @@ -16,6 +26,7 @@ const componentMap: Partial> = { select: SelectAnswers, radio: SelectAnswers, checkbox: SelectAnswers, + 'workspace-launcher': WorkspaceLauncher, }; const RenderTypeComponent: React.FC = () => { diff --git a/src/types.ts b/src/types.ts index 20fdba859..ab6c3a85b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -153,6 +153,8 @@ export interface QuestionOptions { labelTrue: string; labelFalse: string; }; + buttonLabel?: string; // Text to display on the button for workspace-launcher rendering type + workspaceName?: string; // Name of the workspace to launch for workspace-launcher rendering type } export interface Answer { diff --git a/translations/en.json b/translations/en.json index 88631f532..7275e1d0c 100644 --- a/translations/en.json +++ b/translations/en.json @@ -12,6 +12,8 @@ "auditDetails": "Audit Details", "autogeneratedUuid": "UUID (auto-generated)", "backToDashboard": "Back to dashboard", + "buttonLabel": "Button Label", + "buttonLabelPlaceholder": "Enter text to display on the button", "calendarAndTimer": "Calendar and timer", "calendarOnly": "Calendar only", "cancel": "Cancel", @@ -253,5 +255,7 @@ "viewErrors": "View the errors in the interactive builder", "welcomeExplainer": "Add pages, sections and questions to your form. The Preview tab automatically updates as you build your form. For a detailed explanation of what constitutes an OpenMRS form schema, please read through the ", "welcomeHeading": "Interactive schema builder", + "workspaceName": "Workspace Name", + "workspaceNamePlaceholder": "Enter the name of the workspace to launch", "yes": "Yes" }