Skip to content

Commit

Permalink
feat: [DHIS2-16305] Enrollment Overview Plugins (#3515)
Browse files Browse the repository at this point in the history
  • Loading branch information
eirikhaugstulen authored Feb 5, 2024
1 parent 925dfc4 commit 8e06dbc
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
// @flow

import { useDataEngine } from '@dhis2/app-runtime';
import { useQuery } from 'react-query';
import { useApiMetadataQuery } from '../../../../../../utils/reactQueryHelpers';

type Props = {|
selectedScopeId: string,
|}

const configQuery = {
dataEntryFormConfigQuery: {
resource: 'dataStore/capture/dataEntryForms',
},
resource: 'dataStore/capture/dataEntryForms',
};

export const useDataEntryFormConfig = ({ selectedScopeId }: Props) => {
const dataEngine = useDataEngine();


const { data: dataEntryFormConfig, isFetched: configIsFetched } = useQuery(
['dataEntryFormConfig'],
() => dataEngine.query(configQuery),
const { data: dataEntryFormConfig, isFetched: configIsFetched } = useApiMetadataQuery(
['dataEntryFormConfig', selectedScopeId],
configQuery,
{
enabled: !!selectedScopeId,
select: ({ dataEntryFormConfigQuery }) => dataEntryFormConfigQuery?.[selectedScopeId],
cacheTime: Infinity,
staleTime: Infinity,
select: dataEntryFormConfigQuery => dataEntryFormConfigQuery[selectedScopeId],
},
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,19 @@ type DefaultComponents = 'QuickActions'
| 'ProfileWidget'
| 'EnrollmentWidget';

export type ColumnConfig = {
type: $Values<typeof WidgetTypes>,
export type DefaultWidgetColumnConfig = {
type: typeof WidgetTypes.COMPONENT,
name: DefaultComponents,
settings?: Object,
}

export type PluginWidgetColumnConfig = {
type: typeof WidgetTypes.PLUGIN,
source: string,
}

export type ColumnConfig = DefaultWidgetColumnConfig | PluginWidgetColumnConfig;

export type PageLayoutConfig = {
title?: ?string,
leftColumn: ?Array<ColumnConfig>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,70 +1,18 @@
// @flow
import React, { useCallback, useMemo } from 'react';
import log from 'loglevel';
import { errorCreator } from '../../../../../../../capture-core-utils';

import { WidgetTypes } from '../DefaultEnrollmentLayout.constants';
import type { ColumnConfig, PageLayoutConfig, WidgetConfig } from '../DefaultEnrollmentLayout.types';
import { useCallback, useMemo } from 'react';
import type {
ColumnConfig,
PageLayoutConfig,
WidgetConfig,
} from '../DefaultEnrollmentLayout.types';
import { renderWidgets } from '../renderPageComponents';

type Props = {
pageLayout: PageLayoutConfig,
availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>,
props: Object,
};

const MemoizedWidgets: { [key: string]: React$ComponentType<any> } = {};
const UnsupportedWidgets: { [key: string]: boolean } = {};

const renderWidget = (widget: ColumnConfig, availableWidgets, props) => {
const { type } = widget;

if (type.toLowerCase() === WidgetTypes.COMPONENT) {
const { name, settings = {} } = widget;
const widgetConfig = availableWidgets[name];

if (!widgetConfig) {
if (!UnsupportedWidgets[name]) {
log.error(errorCreator(`Widget ${name} is not supported`)({ name }));
UnsupportedWidgets[name] = true;
}
return null;
}

const { getProps, shouldHideWidget, getCustomSettings } = widgetConfig;

const hideWidget = shouldHideWidget && shouldHideWidget(props);
if (hideWidget) return null;
let widgetProps = {};

// In case the widget is not supported, we don't want to crash the app
try {
widgetProps = getProps(props);
} catch (error) {
log.error(errorCreator(`Error while getting widget props for widget ${name}`)({ error, props }));
return null;
}
const customSettings = getCustomSettings && getCustomSettings(settings);

let Widget = MemoizedWidgets[name];

if (!Widget) {
Widget = widgetConfig.Component;
MemoizedWidgets[name] = React.memo(Widget);
}

return (
<Widget
{...widgetProps}
{...customSettings}
key={name}
/>
);
}

log.error(errorCreator(`Widget type ${type} is not supported`)({ type }));
return null;
};

export const useWidgetColumns = ({
pageLayout,
availableWidgets,
Expand All @@ -76,7 +24,7 @@ export const useWidgetColumns = ({
} = pageLayout;

const createColumnWidgets = useCallback(column =>
column?.map((widget: ColumnConfig) => renderWidget(widget, availableWidgets, props)).filter(Boolean),
column?.map((widget: ColumnConfig) => renderWidgets(widget, availableWidgets, props)).filter(Boolean),
[availableWidgets, props],
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @flow
export { renderWidgets } from './renderPageComponents';
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// @flow
import React from 'react';
import log from 'loglevel';
import type {
ColumnConfig,
DefaultWidgetColumnConfig,
PluginWidgetColumnConfig, WidgetConfig,
} from '../DefaultEnrollmentLayout.types';
import { errorCreator } from '../../../../../../../capture-core-utils';
import { EnrollmentPlugin } from '../../../EnrollmentPlugin';
import { WidgetTypes } from '../DefaultEnrollmentLayout.constants';

const MemoizedWidgets: { [key: string]: React$ComponentType<any> } = {};
const UnsupportedWidgets: { [key: string]: boolean } = {};

const renderComponent = (
widget: ColumnConfig,
availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>,
props: Object,
) => {
// Manually casting the type to DefaultWidgetColumnConfig
const { name, settings = {} } = ((widget: any): DefaultWidgetColumnConfig);
const widgetConfig = availableWidgets[name];

if (!widgetConfig) {
if (!UnsupportedWidgets[name]) {
log.error(errorCreator(`Widget ${name} is not supported`)({ name }));
UnsupportedWidgets[name] = true;
}
return null;
}

const { getProps, shouldHideWidget, getCustomSettings } = widgetConfig;

const hideWidget = shouldHideWidget && shouldHideWidget(props);
if (hideWidget) return null;
let widgetProps = {};

// In case the widget is not supported, we don't want to crash the app
try {
widgetProps = getProps(props);
} catch (error) {
log.error(errorCreator(`Error while getting widget props for widget ${name}`)({ error, props }));
return null;
}
const customSettings = getCustomSettings && getCustomSettings(settings);

let Widget = MemoizedWidgets[name];

if (!Widget) {
Widget = widgetConfig.Component;
MemoizedWidgets[name] = React.memo(Widget);
}

return (
<Widget
{...widgetProps}
{...customSettings}
key={name}
/>
);
};

const getPropsForPlugin = ({ program, enrollmentId, teiId, orgUnitId }) => ({
programId: program.id,
enrollmentId,
teiId,
orgUnitId,
});

const renderPlugin = (
widget: ColumnConfig,
availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>,
props: Object,
) => {
// Manually casting the type to PluginWidgetColumnConfig
const { source } = ((widget: any): PluginWidgetColumnConfig);
let PluginWidget = MemoizedWidgets[source];

if (!PluginWidget) {
PluginWidget = EnrollmentPlugin;
MemoizedWidgets[source] = (PluginWidget);
}
const widgetProps = getPropsForPlugin(props);

return (
<PluginWidget
key={source}
pluginSource={source}
{...widgetProps}
/>
);
};

export const renderWidgets = (
widget: ColumnConfig,
availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>,
props: Object,
) => {
const { type } = widget;

if (type.toLowerCase() === WidgetTypes.COMPONENT) {
return renderComponent(widget, availableWidgets, props);
} else if (type.toLowerCase() === WidgetTypes.PLUGIN) {
return renderPlugin(widget, availableWidgets, props);
}

log.error(errorCreator(`Widget type ${type} is not supported`)({ type }));
return null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// @flow
import React, { useEffect, useRef, useState } from 'react';
import { Plugin } from '@dhis2/app-runtime/build/es/experimental';
import { useHistory } from 'react-router-dom';

type EnrollmentPluginProps = {|
enrollmentId: string,
programId?: string,
teiId: string,
orgUnitId: string,
pluginSource: string,
|};

export const EnrollmentPlugin = ({ pluginSource, ...passOnProps }: EnrollmentPluginProps) => {
const [pluginWidth, setPluginWidth] = useState(undefined);
const history = useHistory();
const containerRef = useRef<?HTMLDivElement>();

useEffect(() => {
const { current: container } = containerRef;
if (!container) return () => {};

const resizeObserver = new ResizeObserver((entries) => {
entries.forEach(entry => setPluginWidth(entry.contentRect.width));
});

resizeObserver.observe(container);

// Cleanup function
return () => {
resizeObserver.unobserve(container);
resizeObserver.disconnect();
};
}, [containerRef]);

return (
<div ref={containerRef}>
<Plugin
pluginSource={pluginSource}
width={pluginWidth}
navigate={history.push}
{...passOnProps}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @flow
export { EnrollmentPlugin } from './EnrollmentPlugin';

0 comments on commit 8e06dbc

Please sign in to comment.