@pega/react-sdk-overrides 0.25.7 → 0.25.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/lib/designSystemExtension/AlertBanner/AlertBanner.css +46 -0
  2. package/lib/designSystemExtension/AlertBanner/AlertBanner.tsx +37 -20
  3. package/lib/designSystemExtension/FieldGroupList/FieldGroupList.tsx +1 -1
  4. package/lib/infra/Assignment/Assignment.tsx +12 -3
  5. package/lib/infra/Assignment/useValidationBanner.ts +29 -0
  6. package/lib/infra/Containers/FlowContainer/FlowContainer.tsx +4 -5
  7. package/lib/infra/Containers/ModalViewContainer/ModalViewContainer.tsx +6 -1
  8. package/lib/infra/Containers/ViewContainer/ViewContainer.tsx +4 -8
  9. package/lib/infra/DeferLoad/DeferLoad.tsx +24 -9
  10. package/lib/infra/NavBar/NavBar.tsx +19 -5
  11. package/lib/infra/Reference/Reference.tsx +5 -0
  12. package/lib/infra/RootContainer/RootContainer.tsx +4 -5
  13. package/lib/infra/Stages/Stages.tsx +2 -3
  14. package/lib/infra/View/View.tsx +5 -6
  15. package/lib/template/AppShell/AppShell.css +0 -4
  16. package/lib/template/CaseView/CaseView.tsx +3 -2
  17. package/lib/template/FieldGroupTemplate/FieldGroupTemplate.tsx +10 -3
  18. package/lib/template/HierarchicalForm/HierarchicalForm.tsx +58 -0
  19. package/lib/template/HierarchicalForm/hooks.ts +224 -0
  20. package/lib/template/HierarchicalForm/index.tsx +1 -0
  21. package/lib/template/ListView/ListView.tsx +73 -15
  22. package/lib/template/MultiReferenceReadOnly/MultiReferenceReadOnly.tsx +16 -1
  23. package/lib/template/ObjectPage/index.tsx +1 -0
  24. package/lib/template/SelfServiceCaseView/SelfServiceCaseView.tsx +51 -43
  25. package/lib/template/SimpleTable/SimpleTableManual/SimpleTableManual.tsx +14 -6
  26. package/lib/template/SimpleTable/SimpleTableSelectReadonly/SimpleTableSelectReadonly.tsx +179 -0
  27. package/lib/template/SimpleTable/SimpleTableSelectReadonly/index.tsx +1 -0
  28. package/package.json +1 -1
@@ -0,0 +1,46 @@
1
+ .alert-banner-title {
2
+ font-weight: 700;
3
+ font-size: 1rem;
4
+ display: flex;
5
+ align-items: center;
6
+ gap: 0.25rem;
7
+ }
8
+
9
+ .alert-banner-badge {
10
+ display: inline-flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ min-width: 1rem;
14
+ height: 1rem;
15
+ padding: 0 0.2rem;
16
+ border-radius: 999px;
17
+ font-size: 0.6rem;
18
+ font-weight: 700;
19
+ line-height: 1;
20
+ vertical-align: middle;
21
+ }
22
+
23
+ .alert-banner-badge--urgent {
24
+ background: var(--app-error-dark-color);
25
+ color: #fff;
26
+ }
27
+
28
+ .alert-banner-badge--warning {
29
+ background: var(--app-warning-color-dark);
30
+ color: #fff;
31
+ }
32
+
33
+ .alert-banner-badge--success {
34
+ background: var(--stepper-completed-bg-color);
35
+ color: #fff;
36
+ }
37
+
38
+ .alert-banner-badge--info {
39
+ background: var(--link-button-color);
40
+ color: #fff;
41
+ }
42
+
43
+ .alert-banner-list {
44
+ margin: 0;
45
+ padding-left: 1.25rem;
46
+ }
@@ -1,39 +1,56 @@
1
- import { Alert } from '@mui/material';
1
+ import { Alert, AlertTitle } from '@mui/material';
2
+ import './AlertBanner.css';
2
3
 
3
4
  // AlertBanner is one of the few components that does NOT have getPConnect.
4
5
  // So, no need to extend PConnProps
5
6
  interface AlertBannerProps {
6
- // If any, enter additional props that only exist on Date here
7
7
  id: string;
8
8
  variant: string;
9
9
  messages: string[];
10
10
  onDismiss?: any;
11
11
  }
12
12
 
13
- const SEVERITY_MAP = {
14
- urgent: 'error',
15
- warning: 'warning',
16
- success: 'success',
17
- info: 'info'
13
+ const VARIANT_MAP: Record<string, { severity: string; label: string }> = {
14
+ urgent: { severity: 'error', label: 'Error' },
15
+ warning: { severity: 'warning', label: 'Warning' },
16
+ success: { severity: 'success', label: 'Success' },
17
+ info: { severity: 'info', label: 'Info' }
18
18
  };
19
19
 
20
- export default function AlertBanner(props: AlertBannerProps) {
21
- const { id, variant, messages, onDismiss } = props;
22
- let additionalProps = {};
20
+ function renderMessage(message: string) {
21
+ const colonIndex = message.indexOf(':');
22
+ if (colonIndex === -1) return message;
23
+ return (
24
+ <>
25
+ <strong>{message.slice(0, colonIndex + 1)}</strong>
26
+ {message.slice(colonIndex + 1)}
27
+ </>
28
+ );
29
+ }
23
30
 
24
- if (onDismiss) {
25
- additionalProps = {
26
- onClose: onDismiss
27
- };
28
- }
31
+ export default function AlertBanner({ id, variant, messages, onDismiss }: AlertBannerProps) {
32
+ const { severity, label } = VARIANT_MAP[variant] ?? VARIANT_MAP.info;
33
+ const isMultiple = messages.length > 1;
29
34
 
30
35
  return (
31
36
  <div id={id}>
32
- {messages.map(message => (
33
- <Alert key={message} variant='outlined' severity={SEVERITY_MAP[variant]} {...additionalProps}>
34
- {message}
35
- </Alert>
36
- ))}
37
+ <Alert variant='outlined' severity={severity as any} {...(onDismiss && { onClose: onDismiss })}>
38
+ {isMultiple && (
39
+ <AlertTitle className='alert-banner-title'>
40
+ {label}
41
+ <span className={`alert-banner-badge alert-banner-badge--${variant}`}>{messages.length}</span>
42
+ </AlertTitle>
43
+ )}
44
+ {isMultiple ? (
45
+ <ul className='alert-banner-list'>
46
+ {messages.map(message => (
47
+ <li key={message}>{renderMessage(message)}</li>
48
+ ))}
49
+ </ul>
50
+ ) : (
51
+ renderMessage(messages[0])
52
+ )}
53
+ </Alert>
37
54
  </div>
38
55
  );
39
56
  }
@@ -24,7 +24,7 @@ export default function FieldGroupList(props: FieldGroupListProps) {
24
24
  <Grid2 style={{ width: '100%' }}>
25
25
  <Grid2 container spacing={1}>
26
26
  {props.items.map(item => (
27
- <Grid2 style={{ width: '100%' }}>
27
+ <Grid2 key={item.name} style={{ width: '100%' }}>
28
28
  <b>{item.name}</b>
29
29
  {props.onDelete && (
30
30
  <button
@@ -5,6 +5,8 @@ import CloseIcon from '@mui/icons-material/Close';
5
5
 
6
6
  import { getComponentFromMap } from '@pega/react-sdk-components/lib/bridge/helpers/sdk_component_map';
7
7
  import { useFocusFirstField, useScrolltoTop } from '@pega/react-sdk-components/lib/hooks';
8
+ import AlertBanner from '@pega/react-sdk-components/lib/components/designSystemExtension/AlertBanner/AlertBanner';
9
+ import { useValidationBanner } from './useValidationBanner';
8
10
 
9
11
  import type { PConnProps } from '@pega/react-sdk-components/lib/types/PConnProps';
10
12
 
@@ -23,6 +25,7 @@ export default function Assignment(props: PropsWithChildren<AssignmentProps>) {
23
25
  const MultiStep = getComponentFromMap('MultiStep');
24
26
 
25
27
  const { getPConnect, children, itemKey = '', isInModal = false, banners = [] } = props;
28
+ const validationMessages = useValidationBanner(itemKey);
26
29
  const thePConn = getPConnect();
27
30
 
28
31
  const [bHasNavigation, setHasNavigation] = useState(false);
@@ -33,8 +36,8 @@ export default function Assignment(props: PropsWithChildren<AssignmentProps>) {
33
36
 
34
37
  const actionsAPI = thePConn.getActionsApi();
35
38
  const localizedVal = PCore.getLocaleUtils().getLocaleValue;
39
+ const localizationService = thePConn.getLocalizationService();
36
40
  const localeCategory = 'Assignment';
37
- const localeReference = getPConnect()?.getCaseLocaleReference();
38
41
 
39
42
  // store off bound functions to above pointers
40
43
  const finishAssignment = actionsAPI.finishAssignment.bind(actionsAPI);
@@ -76,7 +79,7 @@ export default function Assignment(props: PropsWithChildren<AssignmentProps>) {
76
79
  function getStepsInfo(steps, formedSteps: any = []) {
77
80
  steps.forEach(step => {
78
81
  if (step.name) {
79
- step.name = PCore.getLocaleUtils().getLocaleValue(step.name, undefined, localeReference);
82
+ step.name = localizationService.getLocalizedText(step.name);
80
83
  }
81
84
  if (step.steps) {
82
85
  formedSteps = getStepsInfo(step.steps, formedSteps);
@@ -88,8 +91,9 @@ export default function Assignment(props: PropsWithChildren<AssignmentProps>) {
88
91
  }
89
92
 
90
93
  const scrollId = window.location.href.includes('embedded') ? '#pega-part-of-page' : '#portal';
94
+ const currentAssignmentViewName = getPConnect().getCaseInfo().getCurrentAssignmentViewName();
91
95
  useScrolltoTop(scrollId, children);
92
- useFocusFirstField('Assignment', children);
96
+ useFocusFirstField('Assignment', currentAssignmentViewName);
93
97
 
94
98
  useEffect(() => {
95
99
  if (children) {
@@ -310,6 +314,11 @@ export default function Assignment(props: PropsWithChildren<AssignmentProps>) {
310
314
 
311
315
  return (
312
316
  <div id='Assignment'>
317
+ {validationMessages.length > 0 && (
318
+ <div style={{ marginBottom: '1rem' }}>
319
+ <AlertBanner id={`validation-banner-${itemKey}`} variant='urgent' messages={validationMessages} />
320
+ </div>
321
+ )}
313
322
  {banners}
314
323
  {bHasNavigation ? (
315
324
  <>
@@ -0,0 +1,29 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+
3
+ function formatError(error: any, localizedVal: Function): string {
4
+ if (typeof error === 'string') return localizedVal(error, 'Messages');
5
+ const label = error.label?.endsWith(':') ? error.label : `${error.label}:`;
6
+ return localizedVal(`${label} ${error.description}`, 'Messages');
7
+ }
8
+
9
+ /**
10
+ * Subscribes to the PCore store and returns formatted validation error messages
11
+ * for the given itemKey. Reacts to any ADD_MESSAGES / CLEAR_MESSAGES dispatch
12
+ * (blur, tab, submit, etc.) so the banner appears and clears in real time.
13
+ */
14
+ export function useValidationBanner(itemKey: string): string[] {
15
+ const [messages, setMessages] = useState<string[]>([]);
16
+
17
+ const readMessages = useCallback(() => {
18
+ const localizedVal = PCore.getLocaleUtils().getLocaleValue;
19
+ const errors: any[] = PCore.getMessageManager().getValidationErrorMessages(itemKey) || [];
20
+ setMessages(errors.map(error => formatError(error, localizedVal)));
21
+ }, [itemKey]);
22
+
23
+ useEffect(() => {
24
+ readMessages();
25
+ return PCore.getStore().subscribe(readMessages);
26
+ }, [readMessages]);
27
+
28
+ return messages;
29
+ }
@@ -22,11 +22,10 @@ interface FlowContainerProps extends PConnProps {
22
22
  activeContainerItemID: string;
23
23
  }
24
24
 
25
- //
26
- // WARNING: It is not expected that this file should be modified. It is part of infrastructure code that works with
27
- // Redux and creation/update of Redux containers and PConnect. Modifying this code could have undesireable results and
28
- // is totally at your own risk.
29
- //
25
+ /**
26
+ * WARNING: This file is part of the infrastructure component responsible for working with Redux and managing the creation and update of Redux containers and PConnect.
27
+ * You may override Material components within this component if needed, but do not modify any container-related logic. Changing this logic can lead to unexpected behavior.
28
+ */
30
29
 
31
30
  const useStyles = makeStyles(theme => ({
32
31
  root: {
@@ -13,6 +13,11 @@ import { getComponentFromMap } from '@pega/react-sdk-components/lib/bridge/helpe
13
13
  import { getBanners } from '@pega/react-sdk-components/lib/components/helpers/case-utils';
14
14
  import type { PConnProps } from '@pega/react-sdk-components/lib/types/PConnProps';
15
15
 
16
+ /**
17
+ * WARNING: This file is part of the infrastructure component responsible for working with Redux and managing the creation and update of Redux containers and PConnect.
18
+ * You may override Material components within this component if needed, but do not modify any container-related logic. Changing this logic can lead to unexpected behavior.
19
+ */
20
+
16
21
  interface ModalViewContainerProps extends PConnProps {
17
22
  // If any, enter additional props that only exist on this component
18
23
  loadingInfo?: string;
@@ -310,7 +315,7 @@ export default function ModalViewContainer(props: ModalViewContainerProps) {
310
315
 
311
316
  return (
312
317
  <>
313
- <Dialog open={bShowModal} aria-labelledby='form-dialog-title' maxWidth={false}>
318
+ <Dialog open={bShowModal} aria-labelledby='form-dialog-title' maxWidth='sm' fullWidth>
314
319
  <DialogTitle id='form-dialog-title' className={`${classes.dlgTitle} psdk-dialog-title`}>
315
320
  {title}
316
321
  </DialogTitle>
@@ -17,14 +17,10 @@ interface ViewContainerProps extends PConnProps {
17
17
  limit?: number;
18
18
  }
19
19
 
20
- // ViewContainer can emit View
21
- // import View from '../View';
22
-
23
- //
24
- // WARNING: It is not expected that this file should be modified. It is part of infrastructure code that works with
25
- // Redux and creation/update of Redux containers and PConnect. Modifying this code could have undesireable results and
26
- // is totally at your own risk.
27
- //
20
+ /**
21
+ * WARNING: This file is part of the infrastructure component responsible for working with Redux and managing the creation and update of Redux containers and PConnect.
22
+ * You may override Material components within this component if needed, but do not modify any container-related logic. Changing this logic can lead to unexpected behavior.
23
+ */
28
24
 
29
25
  export default function ViewContainer(props: ViewContainerProps) {
30
26
  // const { getPConnect, children, routingInfo, name } = props;
@@ -13,13 +13,13 @@ interface DeferLoadProps extends PConnProps {
13
13
  isTab: boolean;
14
14
  deferLoadId: string;
15
15
  lastUpdateCaseTime: any;
16
+ template?: string;
16
17
  }
17
18
 
18
- //
19
- // WARNING: It is not expected that this file should be modified. It is part of infrastructure code that works with
20
- // Redux and creation/update of Redux containers and PConnect. Modifying this code could have undesireable results and
21
- // is totally at your own risk.
22
- //
19
+ /**
20
+ * WARNING: This file is part of the infrastructure component responsible for working with Redux and managing the creation and update of Redux containers and PConnect.
21
+ * You may override Material components within this component if needed, but do not modify any container-related logic. Changing this logic can lead to unexpected behavior.
22
+ */
23
23
 
24
24
  const useStyles = makeStyles(theme => ({
25
25
  root: {
@@ -35,7 +35,7 @@ const useStyles = makeStyles(theme => ({
35
35
  }));
36
36
 
37
37
  export default function DeferLoad(props: DeferLoadProps) {
38
- const { getPConnect, name, deferLoadId, isTab, lastUpdateCaseTime } = props;
38
+ const { getPConnect, name, deferLoadId, isTab, lastUpdateCaseTime, template } = props;
39
39
  const [content, setContent] = useState<any>(null);
40
40
  const [isLoading, setLoading] = useState(true);
41
41
  const [currentLoadedAssignment, setCurrentLoadedAssignment] = useState('');
@@ -75,7 +75,7 @@ export default function DeferLoad(props: DeferLoadProps) {
75
75
  updateData: isContainerPreview
76
76
  });
77
77
 
78
- const onResponse = data => {
78
+ const onResponse = (data: any, isEditable = false) => {
79
79
  setLoading(false);
80
80
  if (deferLoadId) {
81
81
  PCore.getDeferLoadManager().start(
@@ -92,11 +92,16 @@ export default function DeferLoad(props: DeferLoadProps) {
92
92
  meta: data,
93
93
  options: {
94
94
  context: pConnect.getContextName(),
95
- pageReference: pConnect.getPageReference()
95
+ pageReference: pConnect.getPageReference(),
96
+ target: pConnect.getTarget(),
97
+ hasForm: true,
98
+ viewName: (pConnect as any).viewName
96
99
  }
97
100
  };
98
101
  const configObject = PCore.createPConnect(config);
99
- configObject.getPConnect().setInheritedProp('displayMode', 'DISPLAY_ONLY');
102
+ if (!isEditable) {
103
+ configObject.getPConnect().setInheritedProp('displayMode', 'DISPLAY_ONLY');
104
+ }
100
105
  setContent(createElement(createPConnectComponent(), configObject));
101
106
  if (deferLoadId) {
102
107
  PCore.getDeferLoadManager().stop(deferLoadId, getPConnect().getContextName());
@@ -132,6 +137,16 @@ export default function DeferLoad(props: DeferLoadProps) {
132
137
  .then(data => {
133
138
  onResponse(data);
134
139
  });
140
+ } else if (template === 'HierarchicalForm') {
141
+ const root = {
142
+ config: {
143
+ context: pConnect.getPageReference(),
144
+ name,
145
+ type: 'view'
146
+ },
147
+ type: 'reference'
148
+ };
149
+ onResponse(root, true);
135
150
  } else {
136
151
  getPConnect()
137
152
  .getActionsApi()
@@ -133,19 +133,31 @@ export default function NavBar(props: NavBarProps) {
133
133
  const portalLogoImage = Utils.getIconPath(Utils.getSDKStaticConentUrl()).concat('pzpega-logo-mark.svg');
134
134
  const portalOperator = PCore.getEnvironmentInfo().getOperatorName();
135
135
  const portalApp = PCore.getEnvironmentInfo().getApplicationLabel();
136
-
136
+ // @ts-ignore
137
+ const localeReference = PCore.getLocaleUtils().getPortalLocaleReference() || pConn.getValue('.pyLocaleReference');
137
138
  useEffect(() => {
138
- setNavPages(JSON.parse(JSON.stringify(pages)));
139
+ const updatedPages = pages.map((page: any) => {
140
+ const destinationObject: any = {};
141
+ pConn.resolveConfigProps(
142
+ { defaultHeading: page.pyDefaultHeading || page.pyLabel, localeReference: page.pyLocalizationReference },
143
+ destinationObject
144
+ );
145
+ const name = localeUtils.getLocaleValue(destinationObject.defaultHeading, '', destinationObject.localeReference || localeReference);
146
+ return { ...page, name };
147
+ });
148
+ setNavPages(updatedPages);
139
149
  }, [pages]);
140
150
 
141
151
  function navPanelButtonClick(oPageData: any) {
142
152
  const { pyClassName, pyRuleName } = oPageData;
143
-
144
153
  pConn
145
154
  .getActionsApi()
146
155
  .showPage(pyRuleName, pyClassName)
147
156
  .then(() => {
148
157
  console.log(`${localizedVal('showPage completed', localeCategory)}`);
158
+ })
159
+ .catch(error => {
160
+ console.error('Failed to navigate to page from NavBar', error);
149
161
  });
150
162
  }
151
163
 
@@ -155,12 +167,14 @@ export default function NavBar(props: NavBarProps) {
155
167
  containerName: 'primary',
156
168
  flowType: sFlowType || 'pyStartCase'
157
169
  };
158
-
159
170
  pConn
160
171
  .getActionsApi()
161
172
  .createWork(sCaseType, actionInfo)
162
173
  .then(() => {
163
174
  console.log(`${localizedVal('createWork completed', localeCategory)}`);
175
+ })
176
+ .catch(error => {
177
+ console.error('Failed to create case from NavBar', error);
164
178
  });
165
179
  }
166
180
 
@@ -249,7 +263,7 @@ export default function NavBar(props: NavBarProps) {
249
263
  {navPages.map(page => (
250
264
  <ListItemButton onClick={() => navPanelButtonClick(page)} key={page.pyLabel}>
251
265
  <ListItemIcon>{iconMap[page.pxPageViewIcon]}</ListItemIcon>
252
- <ListItemText primary={localeUtils.getLocaleValue(page.pyLabel, '', localeUtils.getCaseLocaleReference(page.pyClassName))} />
266
+ <ListItemText primary={page.name} />
253
267
  </ListItemButton>
254
268
  ))}
255
269
  </List>
@@ -38,6 +38,11 @@ export default function Reference(props: ReferenceProps) {
38
38
  pageReference: context && context.startsWith('@CLASS') ? '' : context
39
39
  });
40
40
 
41
+ if (referenceConfig.inheritedProps && referenceConfig.inheritedProps.length > 0) {
42
+ const inheritedProps = pConnect.getInheritedProps();
43
+ referenceConfig.inheritedProps = Object.keys(inheritedProps).map(prop => ({ prop, value: inheritedProps[prop] }));
44
+ }
45
+
41
46
  viewComponent.props.getPConnect().setInheritedConfig({
42
47
  ...referenceConfig,
43
48
  readOnly,
@@ -15,11 +15,10 @@ interface RootContainerProps extends PConnProps {
15
15
  httpMessages: any[];
16
16
  }
17
17
 
18
- //
19
- // WARNING: It is not expected that this file should be modified. It is part of infrastructure code that works with
20
- // Redux and creation/update of Redux containers and PConnect. Modifying this code could have undesireable results and
21
- // is totally at your own risk.
22
- //
18
+ /**
19
+ * WARNING: This file is part of the infrastructure component responsible for working with Redux and managing the creation and update of Redux containers and PConnect.
20
+ * You may override Material components within this component if needed, but do not modify any container-related logic. Changing this logic can lead to unexpected behavior.
21
+ */
23
22
 
24
23
  const renderingModes = ['portal', 'view'];
25
24
 
@@ -57,14 +57,13 @@ export default function Stages(props: StagesProps) {
57
57
 
58
58
  const { getPConnect, stages } = props;
59
59
  const pConn = getPConnect();
60
- const key = getPConnect()?.getCaseLocaleReference();
61
-
60
+ const localizationService = pConn.getLocalizationService();
62
61
  const filteredStages = getFilteredStages(stages);
63
62
  const currentStageID = pConn.getValue(PCore.getConstants().CASE_INFO.STAGEID, ''); // 2nd arg empty string until typedef allows optional
64
63
  const stagesObj = filteredStages.map((stage, index, arr) => {
65
64
  const theID = stage.ID || stage.id;
66
65
  return {
67
- name: PCore.getLocaleUtils().getLocaleValue(stage.name, undefined, key),
66
+ name: localizationService.getLocalizedText(stage.name),
68
67
  id: theID,
69
68
  complete: stage.visited_status === 'completed',
70
69
  current: theID === currentStageID,
@@ -21,13 +21,12 @@ interface ViewProps extends PConnProps {
21
21
  type?: any;
22
22
  }
23
23
 
24
- //
25
- // WARNING: It is not expected that this file should be modified. It is part of infrastructure code that works with
26
- // Redux and creation/update of Redux containers and PConnect. Modifying this code could have undesireable results and
27
- // is totally at your own risk.
28
- //
24
+ /**
25
+ * WARNING: This file is part of the infrastructure component responsible for working with Redux and managing the creation and update of Redux containers and PConnect.
26
+ * You may override Material components within this component if needed, but do not modify any container-related logic. Changing this logic can lead to unexpected behavior.
27
+ */
29
28
 
30
- const FORMTEMPLATES = ['OneColumn', 'TwoColumn', 'DefaultForm', 'WideNarrow', 'NarrowWide'];
29
+ const FORMTEMPLATES = ['OneColumn', 'TwoColumn', 'DefaultForm', 'WideNarrow', 'NarrowWide', 'HierarchicalForm'];
31
30
  const NO_HEADER_TEMPLATES = [
32
31
  'SubTabs',
33
32
  'SimpleTable',
@@ -33,7 +33,3 @@
33
33
  .progress-spinner {
34
34
  text-align: center;
35
35
  }
36
-
37
- ::ng-deep snack-bar-container.snackbar-newline {
38
- white-space: pre-line;
39
- }
@@ -70,8 +70,9 @@ export default function CaseView(props: PropsWithChildren<CaseViewProps>) {
70
70
  const editAction = availableActions.find(action => action.ID === 'pyUpdateCaseDetails');
71
71
 
72
72
  const localizedVal = PCore.getLocaleUtils().getLocaleValue;
73
+ const localizationService = thePConn.getLocalizationService();
73
74
  const localeCategory = 'CaseView';
74
- const localeKey = thePConn?.getCaseLocaleReference();
75
+
75
76
  /**
76
77
  *
77
78
  * @param inName the metadata <em>name</em> that will cause a region to be returned
@@ -214,7 +215,7 @@ export default function CaseView(props: PropsWithChildren<CaseViewProps>) {
214
215
  className={classes.caseViewHeader}
215
216
  title={
216
217
  <Typography variant='h6' component='div' id='case-name'>
217
- {PCore.getLocaleUtils().getLocaleValue(header, '', localeKey)}
218
+ {localizationService.getLocalizedText(header)}
218
219
  </Typography>
219
220
  }
220
221
  subheader={
@@ -14,6 +14,7 @@ interface FieldGroupTemplateProps extends PConnProps {
14
14
  displayMode?: string;
15
15
  fieldHeader?: string;
16
16
  allowTableEdit: boolean;
17
+ allowActions?: any;
17
18
  }
18
19
 
19
20
  export default function FieldGroupTemplate(props: FieldGroupTemplateProps) {
@@ -30,6 +31,7 @@ export default function FieldGroupTemplate(props: FieldGroupTemplateProps) {
30
31
  heading = '',
31
32
  displayMode,
32
33
  fieldHeader,
34
+ allowActions,
33
35
  allowTableEdit: allowAddEdit
34
36
  } = props;
35
37
  const pConn = getPConnect();
@@ -39,6 +41,11 @@ export default function FieldGroupTemplate(props: FieldGroupTemplateProps) {
39
41
  const isReadonlyMode = renderMode === 'ReadOnly' || displayMode === 'DISPLAY_ONLY';
40
42
  const HEADING = heading ?? 'Row';
41
43
 
44
+ const hasAllowActions = Object.keys(allowActions ?? {}).length > 0;
45
+ const { allowAdd: actionAdd, allowDelete: actionDelete } = allowActions ?? {};
46
+ const allowAdd = hasAllowActions ? (actionAdd ?? true) : (allowAddEdit ?? true);
47
+ const allowDelete = hasAllowActions ? (actionDelete ?? true) : (allowAddEdit ?? true);
48
+
42
49
  useLayoutEffect(() => {
43
50
  if (!isReadonlyMode) {
44
51
  // @ts-expect-error - Expected 3 arguments, but got 1
@@ -72,7 +79,7 @@ export default function FieldGroupTemplate(props: FieldGroupTemplateProps) {
72
79
  pConn.getListActions().deleteEntry(index);
73
80
  }
74
81
  };
75
- if (referenceList.length === 0 && allowAddEdit !== false) {
82
+ if (referenceList.length === 0 && allowAdd) {
76
83
  addFieldGroupItem();
77
84
  }
78
85
 
@@ -87,8 +94,8 @@ export default function FieldGroupTemplate(props: FieldGroupTemplateProps) {
87
94
  return (
88
95
  <FieldGroupList
89
96
  items={MemoisedChildren}
90
- onAdd={allowAddEdit !== false ? addFieldGroupItem : undefined}
91
- onDelete={allowAddEdit !== false ? deleteFieldGroupItem : undefined}
97
+ onAdd={allowAdd ? addFieldGroupItem : undefined}
98
+ onDelete={allowDelete ? deleteFieldGroupItem : undefined}
92
99
  />
93
100
  );
94
101
  }
@@ -0,0 +1,58 @@
1
+ import { type PropsWithChildren } from 'react';
2
+ import { Tab, Tabs, Box, Badge, Tooltip } from '@mui/material';
3
+ import { TabContext, TabPanel } from '@mui/lab';
4
+
5
+ import { useHierarchicalForm, type HierarchicalFormProps } from './hooks';
6
+
7
+ export default function HierarchicalForm(props: PropsWithChildren<HierarchicalFormProps>) {
8
+ const { currentTabId, handleTabClick, processedTabs, instructions } = useHierarchicalForm(props);
9
+
10
+ if (!currentTabId) {
11
+ return null;
12
+ }
13
+
14
+ return (
15
+ <Box display='flex' flexDirection='column'>
16
+ {instructions && (
17
+ <Box mb={2}>
18
+ <div dangerouslySetInnerHTML={{ __html: instructions }} />
19
+ </Box>
20
+ )}
21
+ <Box sx={{ flexGrow: 1 }}>
22
+ <TabContext value={currentTabId.toString()}>
23
+ <Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
24
+ <Tabs onChange={handleTabClick} value={currentTabId.toString()} variant='scrollable'>
25
+ {processedTabs.map((tab: any) => {
26
+ const tabLabel = tab.name || tab.label;
27
+ return (
28
+ <Tab
29
+ key={tab.id}
30
+ sx={{ textTransform: 'none' }}
31
+ label={
32
+ tab.errors ? (
33
+ <Tooltip title={`${tabLabel} has errors`} placement='top'>
34
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
35
+ <span>{tabLabel}</span>
36
+ <Badge badgeContent={tab.errors} color='error' sx={{ '& .MuiBadge-badge': { position: 'static', transform: 'none' } }} />
37
+ </Box>
38
+ </Tooltip>
39
+ ) : (
40
+ tabLabel
41
+ )
42
+ }
43
+ value={tab.id?.toString()}
44
+ />
45
+ );
46
+ })}
47
+ </Tabs>
48
+ </Box>
49
+ {processedTabs.map((tab: any) => (
50
+ <TabPanel key={tab.id} value={tab.id?.toString()} tabIndex={+tab.id} keepMounted sx={{ px: 0 }}>
51
+ <div>{tab.content ? tab.content : 'No content exists'}</div>
52
+ </TabPanel>
53
+ ))}
54
+ </TabContext>
55
+ </Box>
56
+ </Box>
57
+ );
58
+ }