Skip to Content

Workspaces

Workspaces are side panels that slide in from the right side of the screen, typically used for forms and detailed views in the patient chart. They provide a consistent way to display contextual information and forms without navigating away from the current page.

  • New workspaces should use Workspace v2. For general workspaces, type the component with Workspace2DefinitionProps from @openmrs/esm-framework. For patient chart workspaces, use PatientWorkspace2DefinitionProps from @openmrs/esm-patient-common-lib so the component receives the patient chart group props:

    import { Workspace2 } from '@openmrs/esm-framework'; import { type PatientWorkspace2DefinitionProps } from '@openmrs/esm-patient-common-lib'; interface MyWorkspaceProps { itemId?: string; } export default function MyWorkspace({ closeWorkspace, workspaceProps, groupProps, }: PatientWorkspace2DefinitionProps<MyWorkspaceProps, object>) { const { itemId } = workspaceProps ?? {}; const patientUuid = groupProps?.patientUuid; const hasUnsavedChanges = false; return ( <Workspace2 title={t('myWorkspace', 'My workspace')} hasUnsavedChanges={hasUnsavedChanges}> {/* Use patientUuid and itemId to render workspace content */} </Workspace2> ); }
  • Use the hasUnsavedChanges prop on Workspace2 to warn users before they close a workspace with unsaved changes. For forms, wire this to React Hook Form’s isDirty state:

    const { formState: { isDirty }, } = useForm(...); return ( <Workspace2 title={t('editItem', 'Edit item')} hasUnsavedChanges={isDirty}> {/* Form content */} </Workspace2> );

    For non-form workspaces, you can use any condition that indicates unsaved changes:

    <Workspace2 title={t('editFlags', 'Edit flags')} hasUnsavedChanges={pendingChanges.size > 0}> {/* Workspace content */} </Workspace2>
  • Use closeWorkspace({ discardUnsavedChanges: true }) after data is successfully saved, and closeWorkspace() when the user cancels or closes without saving:

    const handleSave = async () => { try { await saveData(); await mutate(); // Update SWR cache await closeWorkspace({ discardUnsavedChanges: true }); showSnackbar({ title: t('saved', 'Saved successfully') }); } catch (error) { showSnackbar({ kind: 'error', title: t('errorSaving', 'Error saving'), subtitle: error instanceof Error ? error.message : t('unknownError', 'Unknown error'), }); } }; // Cancel button <Button kind="secondary" onClick={() => closeWorkspace()}> {t('cancel', 'Cancel')} </Button>
  • Export the workspace lifecycle from index.ts, then register the workspace and its window in routes.json. The component value in routes.json must match the exported lifecycle name:

    export const myWorkspace = getAsyncLifecycle( () => import('./my-workspace.workspace'), options );
    { "workspaces2": [ { "name": "my-workspace", "component": "myWorkspace", "window": "my-workspace-window" } ], "workspaceWindows2": [ { "name": "my-workspace-window", "group": "patient-chart", "width": "narrow", "canMaximize": true } ] }
  • Use launchWorkspace2 to open Workspace v2 workspaces. If the workspace requires an active visit, use the patient common helpers such as useLaunchWorkspaceRequiringVisit when appropriate:

    import { launchWorkspace2 } from '@openmrs/esm-framework'; launchWorkspace2('my-workspace', { itemId });
  • Workspace files should use the .workspace.tsx suffix to clearly indicate their purpose. If the workspace contains translatable strings, make sure the package’s extract-translations script includes src/**/*.workspace.tsx.

  • You can use ExtensionSlot components within workspaces to allow other modules to extend the workspace with additional functionality:

    import { ExtensionSlot } from '@openmrs/esm-framework'; <ExtensionSlot name="my-workspace-header-slot" state={{ patientUuid, formData }} />
  • Workspace forms should handle both create and edit modes when appropriate. Use conditional logic based on whether an ID or existing data is provided:

    const isEditMode = Boolean(itemId); const existingItem = isEditMode ? data?.find(item => item.id === itemId) : null; const defaultValues = isEditMode && existingItem ? { name: existingItem.name, ...existingItem } : { name: '', ...emptyDefaults };
Last updated on