diff --git a/packages/react-sdk-components/src/bridge/react_pconnect.jsx b/packages/react-sdk-components/src/bridge/react_pconnect.jsx index e74581b5..96e178fc 100644 --- a/packages/react-sdk-components/src/bridge/react_pconnect.jsx +++ b/packages/react-sdk-components/src/bridge/react_pconnect.jsx @@ -212,6 +212,15 @@ class PConnect extends Component { this.c11nEnv.removeFormField(); setVisibilityForList(this.c11nEnv, false); } + // Remove the component's field node from the context tree on unmount. + // Without this, field references (e.g. Dropdowns, AutoComplete) from a previous + // step persist in ContextTreeManager.rootNodes.references and get incorrectly + // included in subsequent step submission payloads, causing 400 errors. + try { + this.c11nEnv.removeNode(); + } catch { + // Ignore if already removed + } } /* diff --git a/packages/react-sdk-components/src/components/field/SelectableCard/SelectableCard.tsx b/packages/react-sdk-components/src/components/field/SelectableCard/SelectableCard.tsx index 6647837d..89314013 100644 --- a/packages/react-sdk-components/src/components/field/SelectableCard/SelectableCard.tsx +++ b/packages/react-sdk-components/src/components/field/SelectableCard/SelectableCard.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react'; import { Radio, Checkbox, FormControlLabel, Card, CardContent, Typography } from '@mui/material'; import { resolveReferenceFields } from './utils'; @@ -42,7 +43,13 @@ export default function SelectableCard(props) { if (input) input.click(); }; - let radioItemSelected = false; + const radioItemSelected = type === 'radio' && (cardDataSource || []).some(item => radioBtnValue === item[recordKey]); + + useEffect(() => { + if (type === 'radio' && setIsRadioCardSelected) { + setIsRadioCardSelected(radioItemSelected); + } + }, [radioItemSelected, type, setIsRadioCardSelected]); return ( <> @@ -176,14 +183,8 @@ export default function SelectableCard(props) { ); - if (type === 'radio' && radioBtnValue === item[recordKey]) { - radioItemSelected = true; - } - return component; })} - - {type === 'radio' && setIsRadioCardSelected && setIsRadioCardSelected(radioItemSelected)} ); } diff --git a/packages/react-sdk-components/src/components/helpers/simpleTableHelpers.ts b/packages/react-sdk-components/src/components/helpers/simpleTableHelpers.ts index 88f44e75..40a74602 100644 --- a/packages/react-sdk-components/src/components/helpers/simpleTableHelpers.ts +++ b/packages/react-sdk-components/src/components/helpers/simpleTableHelpers.ts @@ -114,9 +114,12 @@ const SUPPORTED_FIELD_TYPES = [ export const getConfigFields = (rawFields, contextClass, primaryFieldsViewIndex) => { let primaryFields: any = []; let configFields: any = []; + const safeRawFields = rawFields || []; if (primaryFieldsViewIndex > -1) { - let primaryFieldVMD: any = PCore.getMetadataUtils().resolveView(PRIMARY_FIELDS); + // @ts-ignore + const qualifiedName = PCore.getNameSpaceUtils().getDefaultQualifiedName(PRIMARY_FIELDS); + let primaryFieldVMD: any = PCore.getMetadataUtils().resolveView(qualifiedName); if (Array.isArray(primaryFieldVMD)) { primaryFieldVMD = primaryFieldVMD.find(primaryFieldView => primaryFieldView.classID === contextClass); primaryFields = primaryFieldVMD?.children?.[0]?.children || []; @@ -129,7 +132,7 @@ export const getConfigFields = (rawFields, contextClass, primaryFieldsViewIndex) } } - configFields = [...rawFields.slice(0, primaryFieldsViewIndex), ...primaryFields, ...rawFields.slice(primaryFieldsViewIndex + 1)]; + configFields = [...safeRawFields.slice(0, primaryFieldsViewIndex), ...primaryFields, ...safeRawFields.slice(primaryFieldsViewIndex + 1)]; // filter duplicate fields after combining raw fields and primary fields return configFields.filter((field, index) => configFields.findIndex(_field => field.config?.value === _field.config?.value) === index); }; @@ -198,9 +201,11 @@ export function getFieldLabel(fieldConfig) { export const updateFieldLabels = (fields, configFields, primaryFieldsViewIndex, pConnect, options) => { const labelsOfFields: any = []; const { columnsRawConfig = [] } = options; + // @ts-ignore + const qualifiedPrimaryFields = PCore.getNameSpaceUtils().getDefaultQualifiedName(PRIMARY_FIELDS); fields.forEach((field, idx) => { const rawColumnConfig = columnsRawConfig[idx]?.config; - if (field.config.value === PRIMARY_FIELDS) { + if (field.config.value === PRIMARY_FIELDS || field.config.value === qualifiedPrimaryFields) { labelsOfFields.push(''); } else if (isFLProperty(rawColumnConfig?.label ?? rawColumnConfig?.caption)) { labelsOfFields.push(getFieldLabel(rawColumnConfig) || field.config.label || field.config.caption); @@ -217,7 +222,7 @@ export const updateFieldLabels = (fields, configFields, primaryFieldsViewIndex, let label = configFields[i].config?.label; if (isFLProperty(label)) { label = getFieldLabel(configFields[i].config); - } else if (label.startsWith('@')) { + } else if (label?.startsWith('@')) { label = label.substring(3); } if (pConnect) { @@ -242,12 +247,19 @@ export const buildFieldsForTable = (configFields, pConnect, showDeleteButton, op }); const fieldDefs = configFields.map((field, index) => { + let name = field.config.value; + if (name.startsWith('@')) { + name = name.substring(name.indexOf(' ') + 1); + } + if (name.startsWith('.')) { + name = name.substring(1); + } return { type: 'text', label: fieldsLabels[index], fillAvailableSpace: !!field.config.fillAvailableSpace, id: `${index}`, - name: field.config.value.substr(4), + name, cellRenderer: TABLE_CELL, sort: false, noContextMenu: true, @@ -256,7 +268,7 @@ export const buildFieldsForTable = (configFields, pConnect, showDeleteButton, op ...field }, // BUG-615253: Workaround for autosize in table with lazy loading components - width: getFieldWidth(field, fields[index].config.label) + width: getFieldWidth(field, fieldsLabels[index]) }; }); diff --git a/packages/react-sdk-components/src/components/helpers/template-utils.ts b/packages/react-sdk-components/src/components/helpers/template-utils.ts index 68537ac2..21469d82 100644 --- a/packages/react-sdk-components/src/components/helpers/template-utils.ts +++ b/packages/react-sdk-components/src/components/helpers/template-utils.ts @@ -61,6 +61,11 @@ export function getInstructions(pConnect, instructions: string = 'casestep'): st return undefined; } + // Handle instructions as object (returned from paragraph processing) + if (typeof instructions === 'object' && instructions !== null) { + return (instructions as any).htmlContent; + } + // If the annotation wasn't processed correctly, don't return any instruction text if (instructions?.startsWith('@PARAGRAPH')) { return undefined; diff --git a/packages/react-sdk-components/src/components/infra/Assignment/Assignment.tsx b/packages/react-sdk-components/src/components/infra/Assignment/Assignment.tsx index 84a059cc..26d147f6 100644 --- a/packages/react-sdk-components/src/components/infra/Assignment/Assignment.tsx +++ b/packages/react-sdk-components/src/components/infra/Assignment/Assignment.tsx @@ -30,7 +30,7 @@ export default function Assignment(props: PropsWithChildren) { const [bHasNavigation, setHasNavigation] = useState(false); const [actionButtons, setActionButtons] = useState([]); - const [bIsVertical, setIsVertical] = useState(false); + const [stepIndicator, setStepIndicator] = useState<'horizontal' | 'vertical' | 'vertical-start'>('horizontal'); const [arCurrentStepIndicies, setArCurrentStepIndicies] = useState([]); const [arNavigationSteps, setArNavigationSteps] = useState([]); @@ -128,9 +128,15 @@ export default function Assignment(props: PropsWithChildren) { return; } - // set vertical navigation - const isVerticalTemplate = navigation.template?.toLowerCase() === 'vertical'; - setIsVertical(isVerticalTemplate); + // set navigation mode + const navTemplate = navigation.template?.toLowerCase(); + if (navTemplate === 'vertical') { + setStepIndicator('vertical'); + } else if (navTemplate === 'vertical-left') { + setStepIndicator('vertical-start'); + } else { + setStepIndicator('horizontal'); + } if (navigation.steps) { const steps = JSON.parse(JSON.stringify(navigation.steps)); @@ -301,10 +307,12 @@ export default function Assignment(props: PropsWithChildren) { refreshProps.forEach(prop => { PCore.getRefreshManager().registerForRefresh( 'PROP_CHANGE', - thePConn.getActionsApi().refreshCaseView.bind(thePConn.getActionsApi(), caseKey, '', pageReference, { - ...refreshOptions, - refreshFor: prop[0] - }), + matchedPath => { + thePConn.getActionsApi().refreshCaseView(caseKey, '', pageReference, { + ...refreshOptions, + refreshFor: matchedPath || prop[0] + }); + }, `${pageReference}.${prop[1]}`, `${context}/${pageReference}`, context @@ -327,7 +335,7 @@ export default function Assignment(props: PropsWithChildren) { itemKey={itemKey} actionButtons={actionButtons} onButtonPress={buttonPress} - bIsVertical={bIsVertical} + stepIndicator={stepIndicator} arCurrentStepIndicies={arCurrentStepIndicies} arNavigationSteps={arNavigationSteps} > diff --git a/packages/react-sdk-components/src/components/infra/Containers/ModalViewContainer/ModalViewContainer.tsx b/packages/react-sdk-components/src/components/infra/Containers/ModalViewContainer/ModalViewContainer.tsx index 09f9260a..43442e98 100644 --- a/packages/react-sdk-components/src/components/infra/Containers/ModalViewContainer/ModalViewContainer.tsx +++ b/packages/react-sdk-components/src/components/infra/Containers/ModalViewContainer/ModalViewContainer.tsx @@ -315,7 +315,7 @@ export default function ModalViewContainer(props: ModalViewContainerProps) { return ( <> - + {title} diff --git a/packages/react-sdk-components/src/components/infra/MultiStep/MultiStep.css b/packages/react-sdk-components/src/components/infra/MultiStep/MultiStep.css index d0435d01..f746a91c 100644 --- a/packages/react-sdk-components/src/components/infra/MultiStep/MultiStep.css +++ b/packages/react-sdk-components/src/components/infra/MultiStep/MultiStep.css @@ -146,6 +146,69 @@ mat-horizontal-stepper { border-left-color: var(--app-neutral-color); } +.psdk-vertical-stepper-container { + display: grid; + gap: 2rem; +} + +.psdk-vertical-stepper-container.stepper-end { + grid-template-columns: 1fr minmax(12rem, auto); + grid-template-areas: 'content stepper'; +} + +.psdk-vertical-stepper-container.stepper-start { + grid-template-columns: minmax(12rem, auto) 1fr; + grid-template-areas: 'stepper content'; +} + +.psdk-stepper-rail { + grid-area: stepper; +} + +.psdk-stepper-content { + grid-area: content; +} + +.psdk-stepper-rail .psdk-vertical-step-header { + height: auto; + overflow: visible; + padding: 0.75rem 0; +} + +.psdk-stepper-rail .psdk-vertical-step-icon { + flex-shrink: 0; +} + +.psdk-stepper-rail .psdk-vertical-step { + position: relative; +} + +.psdk-stepper-rail .psdk-vertical-step-body { + display: none; +} + +.psdk-stepper-rail .psdk-vertical-step:not(:last-child)::after { + content: ''; + position: absolute; + left: 12px; + top: calc(0.75rem + 24px + 4px); + bottom: -4px; + width: 1px; + background-color: var(--app-neutral-color); +} + +.psdk-stepper-rail .psdk-vertical-step-label { + white-space: nowrap; + font-size: 0.875rem; + font-weight: 400; + color: var(--text-secondary-color); +} + +.psdk-stepper-rail .current .psdk-vertical-step-label { + font-weight: 700; + color: var(--app-text-color); +} + .psdk-horizontal-stepper { background-color: transparent; display: block; diff --git a/packages/react-sdk-components/src/components/infra/MultiStep/MultiStep.tsx b/packages/react-sdk-components/src/components/infra/MultiStep/MultiStep.tsx index 19ca0a1a..6883bccf 100644 --- a/packages/react-sdk-components/src/components/infra/MultiStep/MultiStep.tsx +++ b/packages/react-sdk-components/src/components/infra/MultiStep/MultiStep.tsx @@ -10,7 +10,7 @@ interface MultiStepProps extends PConnProps { itemKey: string; actionButtons: any[]; onButtonPress: any; - bIsVertical: boolean; + stepIndicator?: 'horizontal' | 'vertical' | 'vertical-start'; arNavigationSteps: any; } @@ -19,7 +19,9 @@ export default function MultiStep(props: PropsWithChildren) { const AssignmentCard = getComponentFromMap('AssignmentCard'); const { getPConnect, children, itemKey = '', actionButtons, onButtonPress } = props; - const { bIsVertical, arNavigationSteps } = props; + const { stepIndicator = 'horizontal', arNavigationSteps } = props; + + const isVertical = stepIndicator === 'vertical' || stepIndicator === 'vertical-start'; let currentStep = arNavigationSteps.find(({ visited_status: vs }) => vs === 'current'); if (!currentStep) { @@ -56,59 +58,28 @@ export default function MultiStep(props: PropsWithChildren) { return (
- {bIsVertical ? ( -
- {arNavigationSteps.map((mainStep, index) => { - return ( - -
-
-
-
- {index + 1} -
+ {isVertical ? ( +
+
+ {arNavigationSteps.map((mainStep, index) => ( +
+
+
+
+ {index + 1}
-
{mainStep.visited_status === 'current' && mainStep.name}
-
-
- {mainStep?.steps && ( -
    - {mainStep.steps.forEach(subStep => { -
  • -
    - {subStep.visited_status === 'current' && } - {subStep.visited_status !== 'current' && } - {subStep.visited_status === 'current' && } - {subStep.visited_status !== 'current' && } -
    - {subStep.visited_status === 'current' && ( -
    - - {children} - -
    - )} -
  • ; - })} -
- )} - {!mainStep?.steps && mainStep.visited_status === 'current' && ( -
- - {children} - -
- )}
+
{mainStep.name}
- - ); - })} + {!isLastStep(index) &&
} +
+ ))} +
+
+ + {children} + +
) : (
diff --git a/packages/react-sdk-components/src/components/infra/NavBar/NavBar.tsx b/packages/react-sdk-components/src/components/infra/NavBar/NavBar.tsx index c3c81b6c..f3b610c7 100644 --- a/packages/react-sdk-components/src/components/infra/NavBar/NavBar.tsx +++ b/packages/react-sdk-components/src/components/infra/NavBar/NavBar.tsx @@ -56,7 +56,8 @@ const iconMap = { 'pi pi-tablet': , 'pi pi-ambulance': , 'pi pi-ink-solid': , - 'pi pi-columns': + 'pi pi-columns': , + 'case-solid': }; const drawerWidth = 300; diff --git a/packages/react-sdk-components/src/components/infra/View/View.tsx b/packages/react-sdk-components/src/components/infra/View/View.tsx index c6eda009..81b9c45f 100644 --- a/packages/react-sdk-components/src/components/infra/View/View.tsx +++ b/packages/react-sdk-components/src/components/infra/View/View.tsx @@ -50,15 +50,20 @@ export default function View(props: PropsWithChildren) { // Putting this logic here instead of copy/paste in every Form template index.js const inheritedProps: any = getPConnect().getInheritedProps(); // try to remove any when getInheritedProps typedefs are fixed - label = inheritedProps.label || label; - showLabel = inheritedProps.showLabel || showLabel; - const localeUtils = PCore.getLocaleUtils(); const isEmbeddedDataView = mode === 'editable'; // would be better to check the reference child for `context` attribute if possible - if (isEmbeddedDataView && showLabel === undefined) { - showLabel = true; + + // Only apply inherited label settings for embedded data views (per 8.6 design) + if (isEmbeddedDataView) { + label = inheritedProps.label || label; + showLabel = inheritedProps.showLabel || showLabel; + if (showLabel === undefined) { + showLabel = true; + } } + const localeUtils = PCore.getLocaleUtils(); + useEffect(() => { // Get the localized application label let applicationLabel = PCore.getEnvironmentInfo().getApplicationLabel(); @@ -141,7 +146,7 @@ export default function View(props: PropsWithChildren) { return (
- {showLabel && !NO_HEADER_TEMPLATES.includes(template) && ( + {isEmbeddedDataView && showLabel && !NO_HEADER_TEMPLATES.includes(template) && (
{label}
diff --git a/packages/react-sdk-components/src/components/template/SimpleTable/SimpleTableManual/SimpleTableManual.tsx b/packages/react-sdk-components/src/components/template/SimpleTable/SimpleTableManual/SimpleTableManual.tsx index 07fecb8f..3695824b 100644 --- a/packages/react-sdk-components/src/components/template/SimpleTable/SimpleTableManual/SimpleTableManual.tsx +++ b/packages/react-sdk-components/src/components/template/SimpleTable/SimpleTableManual/SimpleTableManual.tsx @@ -56,6 +56,7 @@ interface SimpleTableManualProps extends PConnProps { validatemessage?: string; required?: boolean; targetClassLabel?: string; + uniqueField?: string; } const useStyles = makeStyles((/* theme */) => ({ @@ -127,8 +128,10 @@ export default function SimpleTableManual(props: PropsWithChildren field.config.value === 'pyPrimaryFields'); + // @ts-ignore + const qualifiedPrimaryFields = PCore.getNameSpaceUtils().getDefaultQualifiedName('pyPrimaryFields'); + const primaryFieldsViewIndex = resolvedFields?.findIndex(field => { + const value = field.config.value; + return value === 'pyPrimaryFields' || value === qualifiedPrimaryFields; + }); // NOTE: props has each child.config with datasource and value undefined // but getRawMetadata() has each child.config with datasource and value showing their unresolved values (ex: "@P thePropName") // We need to use the prop name as the "glue" to tie the table dataSource, displayColumns and data together. // So, in the code below, we'll use the unresolved config.value (but replacing the space with an underscore to keep things happy) const rawMetadata: any = getPConnect().getRawMetadata(); - // get raw config since @P and other annotations are processed and don't appear in the resolved config. // Destructure "raw" children into array var: "rawFields" // NOTE: when config.listType == "associated", the property can be found in either @@ -196,14 +204,19 @@ export default function SimpleTableManual(props: PropsWithChildren { - return { ...resolvedFields[index], propName: field.config.value.replace('@P .', '') }; + const fieldsWithPropNames = configFields.map(field => { + let propName = field.config.value; + if (propName.startsWith('@')) { + propName = propName.substring(propName.indexOf(' ') + 1); + } + if (propName.startsWith('.')) { + propName = propName.substring(1); + } + return { ...field, propName }; }); useEffect(() => { - if (!(readOnlyMode && dataPageName)) { - buildElementsForTable(); - } + buildElementsForTable(); if (readOnlyMode || allowEditingInModal) { generateRowsData(); } @@ -221,7 +234,7 @@ export default function SimpleTableManual(props: PropsWithChildren item.name && item.meta?.type !== 'Attachment').map(item => item.name) + fieldDefs.filter(item => item.id && item.meta?.type !== 'Attachment').map(item => item.id) ); } else { - // @ts-expect-error - An argument for 'fields' was not provided - getPConnect().getListActions().initDefaultPageInstructions(getPConnect().getReferenceList()); + // @ts-ignore - fields param is optional, uniqueField passed as 3rd arg for page instructions + getPConnect().getListActions().initDefaultPageInstructions(getPConnect().getReferenceList(), undefined, uniqueField); } }, []); @@ -289,17 +302,14 @@ export default function SimpleTableManual(props: PropsWithChildren { const data = formatRowsData(listData); myRowsRef.current = data || []; setRowData(data); - // Build elements from fetched data since referenceList may be empty for data page tables - if (readOnlyMode) { - buildElementsFromData(listData as any[]); - } }) .catch(e => { console.log(e); @@ -343,19 +353,26 @@ export default function SimpleTableManual(props: PropsWithChildren { if (allowEditingInModal && (defaultView || defaultActionId)) { - pConn.getActionsApi().openEmbeddedDataModal( - defaultView, - // @ts-expect-error - pConn, - referenceListStr, - referenceList.length, - PCore.getConstants().RESOURCE_STATUS.CREATE, - targetClassLabel, - editType, - defaultActionId - ); + pConn + .getActionsApi() + + .openEmbeddedDataModal( + defaultView, + // @ts-expect-error + pConn, + referenceListStr, + referenceList.length, + PCore.getConstants().RESOURCE_STATUS.CREATE, + targetClassLabel, + editType, + defaultActionId + ); } else { - pConn.getListActions().insert({ classID: contextClass }, referenceList.length); + const payload: any = { classID: contextClass }; + if (normalizedUniqueField) { + payload[normalizedUniqueField] = crypto.randomUUID(); + } + pConn.getListActions().insert(payload, referenceList.length); } getPConnect().clearErrorMessages({ @@ -366,17 +383,20 @@ export default function SimpleTableManual(props: PropsWithChildren { setEditAnchorEl(null); if (typeof selectedRowIndex.current === 'number') { - pConn.getActionsApi().openEmbeddedDataModal( - bUseSeparateViewForEdit ? editView : defaultView, - // @ts-expect-error - pConn, - referenceListStr, - selectedRowIndex.current, - PCore.getConstants().RESOURCE_STATUS.UPDATE, - targetClassLabel, - editType, - editActionId - ); + pConn + .getActionsApi() + + .openEmbeddedDataModal( + bUseSeparateViewForEdit ? editView : defaultView, + // @ts-expect-error + pConn, + referenceListStr, + selectedRowIndex.current, + PCore.getConstants().RESOURCE_STATUS.UPDATE, + targetClassLabel, + editType, + editActionId + ); } }; @@ -393,7 +413,7 @@ export default function SimpleTableManual(props: PropsWithChildren { const data: any = []; - rawFields.forEach(item => { + configFields.forEach(item => { // removing label field from config to hide title in the table cell if (!item.config.hide) { item = { ...item, config: { ...item.config, label: '', displayMode: readOnlyMode || allowEditingInModal ? 'DISPLAY_ONLY' : undefined } }; @@ -402,14 +422,13 @@ export default function SimpleTableManual(props: PropsWithChildren { - const data: any = []; - rawFields.forEach(item => { - if (!item.config.hide) { - const propName = item.config.value.replace('@P .', ''); - const val = getRowValue(element, propName); - data.push(createElement('span', { key: `${index}-${propName}` }, val ?? '---')); - } - }); - eleData.push(data); - }); - setElementsData(eleData); - } - const handleRequestSort = (event: React.MouseEvent, property: keyof any) => { const isAsc = orderBy === property && order === 'asc'; setOrder(isAsc ? 'desc' : 'asc'); @@ -642,6 +645,10 @@ export default function SimpleTableManual(props: PropsWithChildren