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
Workspace2DefinitionPropsfrom@openmrs/esm-framework. For patient chart workspaces, usePatientWorkspace2DefinitionPropsfrom@openmrs/esm-patient-common-libso 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
hasUnsavedChangesprop onWorkspace2to warn users before they close a workspace with unsaved changes. For forms, wire this to React Hook Form’sisDirtystate: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, andcloseWorkspace()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 inroutes.json. Thecomponentvalue inroutes.jsonmust 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
launchWorkspace2to open Workspace v2 workspaces. If the workspace requires an active visit, use the patient common helpers such asuseLaunchWorkspaceRequiringVisitwhen appropriate:import { launchWorkspace2 } from '@openmrs/esm-framework'; launchWorkspace2('my-workspace', { itemId }); -
Workspace files should use the
.workspace.tsxsuffix to clearly indicate their purpose. If the workspace contains translatable strings, make sure the package’sextract-translationsscript includessrc/**/*.workspace.tsx. -
You can use
ExtensionSlotcomponents 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 };