diff --git a/spiffworkflow-frontend/package-lock.json b/spiffworkflow-frontend/package-lock.json index 4898b19cf..0dfe841ab 100644 --- a/spiffworkflow-frontend/package-lock.json +++ b/spiffworkflow-frontend/package-lock.json @@ -24,7 +24,6 @@ "@rjsf/mui": "^5.0.0-beta.13", "@rjsf/utils": "^5.0.0-beta.13", "@rjsf/validator-ajv6": "^5.0.0-beta.13", - "@rjsf/validator-ajv8": "^5.0.0-beta.13", "@tanstack/react-table": "^8.2.2", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.3.0", @@ -4950,23 +4949,6 @@ "@rjsf/utils": "^5.0.0-beta.1" } }, - "node_modules/@rjsf/validator-ajv8": { - "version": "5.0.0-beta.13", - "resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.0.0-beta.13.tgz", - "integrity": "sha512-/hrYbiwgCvEqw1Z7YZTWvd+ZAiX5vSN0WAI2hJTJTqKuCTcIH0fqNDCaOg3FBR38BL7seZrUmibIUcPU66iJ1w==", - "dependencies": { - "ajv-formats": "^2.1.1", - "ajv8": "npm:ajv@^8.11.0", - "lodash": "^4.17.15", - "lodash-es": "^4.17.15" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@rjsf/utils": "^5.0.0-beta.12" - } - }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -6840,27 +6822,6 @@ "ajv": "^6.9.1" } }, - "node_modules/ajv8": { - "name": "ajv", - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv8/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -34883,17 +34844,6 @@ "lodash-es": "^4.17.15" } }, - "@rjsf/validator-ajv8": { - "version": "5.0.0-beta.13", - "resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.0.0-beta.13.tgz", - "integrity": "sha512-/hrYbiwgCvEqw1Z7YZTWvd+ZAiX5vSN0WAI2hJTJTqKuCTcIH0fqNDCaOg3FBR38BL7seZrUmibIUcPU66iJ1w==", - "requires": { - "ajv-formats": "^2.1.1", - "ajv8": "npm:ajv@^8.11.0", - "lodash": "^4.17.15", - "lodash-es": "^4.17.15" - } - }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -36367,24 +36317,6 @@ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "requires": {} }, - "ajv8": { - "version": "npm:ajv@8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "dependencies": { - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - } - } - }, "ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index a13fd0163..163a2fc4f 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -20,12 +20,16 @@ import { TableHeader, TableHead, TableRow, + TimePicker, // @ts-ignore } from '@carbon/react'; import { PROCESS_STATUSES, DATE_FORMAT, DATE_FORMAT_CARBON } from '../config'; import { - convertDateStringToSeconds, - convertSecondsToFormattedDate, + convertDateAndTimeStringsToSeconds, + convertDateObjectToFormattedHoursMinutes, + convertSecondsToFormattedDateString, + convertSecondsToFormattedDateTime, + convertSecondsToFormattedTimeHoursMinutes, getPageInfoFromSearchParams, getProcessModelFullIdentifierFromSearchParams, modifyProcessModelPath, @@ -49,6 +53,10 @@ type OwnProps = { perPageOptions?: number[]; }; +interface dateParameters { + [key: string]: ((..._args: any[]) => any)[]; +} + export default function ProcessInstanceListTable({ filtersEnabled = true, processModelFullIdentifier, @@ -66,11 +74,20 @@ export default function ProcessInstanceListTable({ const oneHourInSeconds = 3600; const oneMonthInSeconds = oneHourInSeconds * 24 * 30; - const [startFrom, setStartFrom] = useState(''); - const [startTo, setStartTo] = useState(''); - const [endFrom, setEndFrom] = useState(''); - const [endTo, setEndTo] = useState(''); + const [startFromDate, setStartFromDate] = useState(''); + const [startToDate, setStartToDate] = useState(''); + const [endFromDate, setEndFromDate] = useState(''); + const [endToDate, setEndToDate] = useState(''); + const [startFromTime, setStartFromTime] = useState(''); + const [startToTime, setStartToTime] = useState(''); + const [endFromTime, setEndFromTime] = useState(''); + const [endToTime, setEndToTime] = useState(''); const [showFilterOptions, setShowFilterOptions] = useState(false); + const [startFromTimeInvalid, setStartFromTimeInvalid] = + useState(false); + const [startToTimeInvalid, setStartToTimeInvalid] = useState(false); + const [endFromTimeInvalid, setEndFromTimeInvalid] = useState(false); + const [endToTimeInvalid, setEndToTimeInvalid] = useState(false); const setErrorMessage = (useContext as any)(ErrorContext)[1]; @@ -86,14 +103,23 @@ export default function ProcessInstanceListTable({ const [processModelSelection, setProcessModelSelection] = useState(null); - const parametersToAlwaysFilterBy = useMemo(() => { + const dateParametersToAlwaysFilterBy: dateParameters = useMemo(() => { return { - start_from: setStartFrom, - start_to: setStartTo, - end_from: setEndFrom, - end_to: setEndTo, + start_from: [setStartFromDate, setStartFromTime], + start_to: [setStartToDate, setStartToTime], + end_from: [setEndFromDate, setEndFromTime], + end_to: [setEndToDate, setEndToTime], }; - }, [setStartFrom, setStartTo, setEndFrom, setEndTo]); + }, [ + setStartFromDate, + setStartFromTime, + setStartToDate, + setStartToTime, + setEndFromDate, + setEndFromTime, + setEndToDate, + setEndToTime, + ]); const parametersToGetFromSearchParams = useMemo(() => { return { @@ -130,19 +156,27 @@ export default function ProcessInstanceListTable({ queryParamString += `&user_filter=${userAppliedFilter}`; } - Object.keys(parametersToAlwaysFilterBy).forEach((paramName: string) => { - // @ts-expect-error TS(7053) FIXME: - const functionToCall = parametersToAlwaysFilterBy[paramName]; - const searchParamValue = searchParams.get(paramName); - if (searchParamValue) { - queryParamString += `&${paramName}=${searchParamValue}`; - const dateString = convertSecondsToFormattedDate( - searchParamValue as any - ); - functionToCall(dateString); - setShowFilterOptions(true); + Object.keys(dateParametersToAlwaysFilterBy).forEach( + (paramName: string) => { + const dateFunctionToCall = + dateParametersToAlwaysFilterBy[paramName][0]; + const timeFunctionToCall = + dateParametersToAlwaysFilterBy[paramName][1]; + const searchParamValue = searchParams.get(paramName); + if (searchParamValue) { + queryParamString += `&${paramName}=${searchParamValue}`; + const dateString = convertSecondsToFormattedDateString( + searchParamValue as any + ); + dateFunctionToCall(dateString); + const timeString = convertSecondsToFormattedTimeHoursMinutes( + searchParamValue as any + ); + timeFunctionToCall(timeString); + setShowFilterOptions(true); + } } - }); + ); Object.keys(parametersToGetFromSearchParams).forEach( (paramName: string) => { @@ -211,7 +245,7 @@ export default function ProcessInstanceListTable({ params, oneMonthInSeconds, oneHourInSeconds, - parametersToAlwaysFilterBy, + dateParametersToAlwaysFilterBy, parametersToGetFromSearchParams, filtersEnabled, paginationQueryParamPrefix, @@ -219,16 +253,25 @@ export default function ProcessInstanceListTable({ perPageOptions, ]); + // This sets the filter data using the saved reports returned from the initial instance_list query. + // This could probably be merged into the main useEffect but it works here now. useEffect(() => { const filters = processInstanceFilters as any; - Object.keys(parametersToAlwaysFilterBy).forEach((paramName: string) => { - // @ts-expect-error TS(7053) FIXME: - const functionToCall = parametersToAlwaysFilterBy[paramName]; + Object.keys(dateParametersToAlwaysFilterBy).forEach((paramName: string) => { + const dateFunctionToCall = dateParametersToAlwaysFilterBy[paramName][0]; + const timeFunctionToCall = dateParametersToAlwaysFilterBy[paramName][1]; const paramValue = filters[paramName]; - functionToCall(''); + dateFunctionToCall(''); + timeFunctionToCall(''); if (paramValue) { - const dateString = convertSecondsToFormattedDate(paramValue as any); - functionToCall(dateString); + const dateString = convertSecondsToFormattedDateString( + paramValue as any + ); + dateFunctionToCall(dateString); + const timeString = convertSecondsToFormattedTimeHoursMinutes( + paramValue as any + ); + timeFunctionToCall(timeString); setShowFilterOptions(true); } }); @@ -253,7 +296,7 @@ export default function ProcessInstanceListTable({ setProcessStatusSelection(processStatusSelectedArray); }, [ processInstanceFilters, - parametersToAlwaysFilterBy, + dateParametersToAlwaysFilterBy, parametersToGetFromSearchParams, processModelAvailableItems, ]); @@ -285,10 +328,22 @@ export default function ProcessInstanceListTable({ ); let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`; - const startFromSeconds = convertDateStringToSeconds(startFrom); - const endFromSeconds = convertDateStringToSeconds(endFrom); - const startToSeconds = convertDateStringToSeconds(startTo); - const endToSeconds = convertDateStringToSeconds(endTo); + const startFromSeconds = convertDateAndTimeStringsToSeconds( + startFromDate, + startFromTime + ); + const startToSeconds = convertDateAndTimeStringsToSeconds( + startToDate, + startToTime + ); + const endFromSeconds = convertDateAndTimeStringsToSeconds( + endFromDate, + endFromTime + ); + const endToSeconds = convertDateAndTimeStringsToSeconds( + endToDate, + endToTime + ); if (isTrueComparison(startFromSeconds, '>', startToSeconds)) { setErrorMessage({ message: '"Start date from" cannot be after "start date to"', @@ -342,24 +397,50 @@ export default function ProcessInstanceListTable({ labelString: any, name: any, initialDate: any, - onChangeFunction: any + initialTime: string, + onChangeDateFunction: any, + onChangeTimeFunction: any, + timeInvalid: boolean, + setTimeInvalid: any ) => { return ( - - { - onChangeFunction(dateChangeEvent.srcElement.value); + <> + + { + if (!initialDate && !initialTime) { + onChangeTimeFunction( + convertDateObjectToFormattedHoursMinutes(new Date()) + ); + } + onChangeDateFunction(dateChangeEvent.srcElement.value); + }} + value={initialDate} + /> + + { + if (event.srcElement.validity.valid) { + setTimeInvalid(false); + } else { + setTimeInvalid(true); + } + onChangeTimeFunction(event.srcElement.value); }} - value={initialDate} /> - + ); }; @@ -386,10 +467,14 @@ export default function ProcessInstanceListTable({ const clearFilters = () => { setProcessModelSelection(null); setProcessStatusSelection([]); - setStartFrom(''); - setStartTo(''); - setEndFrom(''); - setEndTo(''); + setStartFromDate(''); + setStartFromTime(''); + setStartToDate(''); + setStartToTime(''); + setEndFromDate(''); + setEndFromTime(''); + setEndToDate(''); + setEndToTime(''); }; const filterOptions = () => { @@ -415,18 +500,49 @@ export default function ProcessInstanceListTable({ {dateComponent( 'Start date from', 'start-from', - startFrom, - setStartFrom + startFromDate, + startFromTime, + setStartFromDate, + setStartFromTime, + startFromTimeInvalid, + setStartFromTimeInvalid )} - {dateComponent('Start date to', 'start-to', startTo, setStartTo)} + {dateComponent( + 'Start date to', + 'start-to', + startToDate, + startToTime, + setStartToDate, + setStartToTime, + startToTimeInvalid, + setStartToTimeInvalid + )} - {dateComponent('End date from', 'end-from', endFrom, setEndFrom)} + {dateComponent( + 'End date from', + 'end-from', + endFromDate, + endFromTime, + setEndFromDate, + setEndFromTime, + endFromTimeInvalid, + setEndFromTimeInvalid + )} - {dateComponent('End date to', 'end-to', endTo, setEndTo)} + {dateComponent( + 'End date to', + 'end-to', + endToDate, + endToTime, + setEndToDate, + setEndToTime, + endToTimeInvalid, + setEndToTimeInvalid + )} @@ -493,7 +609,7 @@ export default function ProcessInstanceListTable({ ); }; const formatSecondsForDisplay = (_row: any, seconds: any) => { - return convertSecondsToFormattedDate(seconds) || '-'; + return convertSecondsToFormattedDateTime(seconds) || '-'; }; const defaultFormatter = (_row: any, value: any) => { return value; diff --git a/spiffworkflow-frontend/src/config.tsx b/spiffworkflow-frontend/src/config.tsx index 47ff5025a..5e7e96feb 100644 --- a/spiffworkflow-frontend/src/config.tsx +++ b/spiffworkflow-frontend/src/config.tsx @@ -18,5 +18,6 @@ export const PROCESS_STATUSES = [ // with time: yyyy-MM-dd HH:mm:ss export const DATE_TIME_FORMAT = 'yyyy-MM-dd HH:mm:ss'; +export const TIME_FORMAT_HOURS_MINUTES = 'HH:mm'; export const DATE_FORMAT = 'yyyy-MM-dd'; export const DATE_FORMAT_CARBON = 'Y-m-d'; diff --git a/spiffworkflow-frontend/src/helpers.test.tsx b/spiffworkflow-frontend/src/helpers.test.tsx index 1031fc46b..5a0352b82 100644 --- a/spiffworkflow-frontend/src/helpers.test.tsx +++ b/spiffworkflow-frontend/src/helpers.test.tsx @@ -1,4 +1,4 @@ -import { convertSecondsToFormattedDate, slugifyString } from './helpers'; +import { convertSecondsToFormattedDateString, slugifyString } from './helpers'; test('it can slugify a string', () => { expect(slugifyString('hello---world_ and then Some such-')).toEqual( @@ -7,6 +7,6 @@ test('it can slugify a string', () => { }); test('it can keep the correct date when converting seconds to date', () => { - const dateString = convertSecondsToFormattedDate(1666325400); + const dateString = convertSecondsToFormattedDateString(1666325400); expect(dateString).toEqual('2022-10-21'); }); diff --git a/spiffworkflow-frontend/src/helpers.tsx b/spiffworkflow-frontend/src/helpers.tsx index 37286c836..964787906 100644 --- a/spiffworkflow-frontend/src/helpers.tsx +++ b/spiffworkflow-frontend/src/helpers.tsx @@ -1,5 +1,9 @@ import { format } from 'date-fns'; -import { DATE_TIME_FORMAT, DATE_FORMAT } from './config'; +import { + DATE_TIME_FORMAT, + DATE_FORMAT, + TIME_FORMAT_HOURS_MINUTES, +} from './config'; import { DEFAULT_PER_PAGE, DEFAULT_PAGE, @@ -42,27 +46,72 @@ export const convertDateToSeconds = ( return null; }; +export const convertDateObjectToFormattedString = (dateObject: Date) => { + if (dateObject) { + return format(dateObject, DATE_FORMAT); + } + return null; +}; + +export const convertDateAndTimeStringsToDate = ( + dateString: string, + timeString: string +) => { + if (dateString && timeString) { + return new Date(`${dateString}T${timeString}`); + } + return null; +}; + +export const convertDateAndTimeStringsToSeconds = ( + dateString: string, + timeString: string +) => { + const dateObject = convertDateAndTimeStringsToDate(dateString, timeString); + if (dateObject) { + return convertDateToSeconds(dateObject); + } + return null; +}; + export const convertStringToDate = (dateString: string) => { - if (dateString) { - // add midnight time to the date so it c uses the correct date - // after converting to timezone - return new Date(`${dateString}T00:10:00`); + return convertDateAndTimeStringsToSeconds(dateString, '00:10:00'); +}; + +export const convertSecondsToDateObject = (seconds: number) => { + if (seconds) { + return new Date(seconds * 1000); } return null; }; export const convertSecondsToFormattedDateTime = (seconds: number) => { - if (seconds) { - const dateObject = new Date(seconds * 1000); + const dateObject = convertSecondsToDateObject(seconds); + if (dateObject) { return format(dateObject, DATE_TIME_FORMAT); } return null; }; -export const convertSecondsToFormattedDate = (seconds: number) => { - if (seconds) { - const dateObject = new Date(seconds * 1000); - return format(dateObject, DATE_FORMAT); +export const convertDateObjectToFormattedHoursMinutes = (dateObject: Date) => { + if (dateObject) { + return format(dateObject, TIME_FORMAT_HOURS_MINUTES); + } + return null; +}; + +export const convertSecondsToFormattedTimeHoursMinutes = (seconds: number) => { + const dateObject = convertSecondsToDateObject(seconds); + if (dateObject) { + return convertDateObjectToFormattedHoursMinutes(dateObject); + } + return null; +}; + +export const convertSecondsToFormattedDateString = (seconds: number) => { + const dateObject = convertSecondsToDateObject(seconds); + if (dateObject) { + return convertDateObjectToFormattedString(dateObject); } return null; }; diff --git a/spiffworkflow-frontend/src/routes/MessageInstanceList.tsx b/spiffworkflow-frontend/src/routes/MessageInstanceList.tsx index 09d283167..3ead54625 100644 --- a/spiffworkflow-frontend/src/routes/MessageInstanceList.tsx +++ b/spiffworkflow-frontend/src/routes/MessageInstanceList.tsx @@ -5,7 +5,7 @@ import { Link, useParams, useSearchParams } from 'react-router-dom'; import PaginationForTable from '../components/PaginationForTable'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import { - convertSecondsToFormattedDate, + convertSecondsToFormattedDateString, getPageInfoFromSearchParams, modifyProcessModelPath, unModifyProcessModelPath, @@ -68,7 +68,9 @@ export default function MessageInstanceList() { {rowToUse.failure_cause || '-'} {rowToUse.status} - {convertSecondsToFormattedDate(rowToUse.created_at_in_seconds)} + {convertSecondsToFormattedDateString( + rowToUse.created_at_in_seconds + )} ); diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceLogList.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceLogList.tsx index 9f2d65b30..bb4468b44 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInstanceLogList.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInstanceLogList.tsx @@ -6,7 +6,7 @@ import PaginationForTable from '../components/PaginationForTable'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import { getPageInfoFromSearchParams, - convertSecondsToFormattedDate, + convertSecondsToFormattedDateString, modifyProcessModelPath, unModifyProcessModelPath, } from '../helpers'; @@ -49,7 +49,7 @@ export default function ProcessInstanceLogList() { data-qa="process-instance-show-link" to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${rowToUse.process_instance_id}/${rowToUse.spiff_step}`} > - {convertSecondsToFormattedDate(rowToUse.timestamp)} + {convertSecondsToFormattedDateString(rowToUse.timestamp)} diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index 9da0e30a7..1309b8f3a 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -1,5 +1,5 @@ import { useContext, useEffect, useState } from 'react'; -import { Link, useNavigate, useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; // FIXME: npm install @rjsf/validator-ajv8 and use it as soon as // rawErrors is fixed. @@ -9,8 +9,14 @@ import { Link, useNavigate, useParams } from 'react-router-dom'; // https://github.com/rjsf-team/react-jsonschema-form/blob/main/docs/api-reference/uiSchema.md talks about rawErrors import validator from '@rjsf/validator-ajv6'; -// @ts-ignore -import { Button, Stack } from '@carbon/react'; +import { + TabList, + Tab, + Tabs, + Grid, + Column, + // @ts-ignore +} from '@carbon/react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; @@ -73,39 +79,52 @@ export default function TaskShow() { const buildTaskNavigation = () => { let userTasksElement; + let selectedTabIndex = 0; if (userTasks) { userTasksElement = (userTasks as any).map(function getUserTasksElement( - userTask: any + userTask: any, + index: number ) { const taskUrl = `/tasks/${params.process_instance_id}/${userTask.id}`; if (userTask.id === params.task_id) { - return {userTask.name}; + selectedTabIndex = index; + return {userTask.title}; } if (userTask.state === 'COMPLETED') { return ( - - {userTask.name} - + navigate(taskUrl)} + data-qa={`form-nav-${userTask.name}`} + > + {userTask.title} + ); } if (userTask.state === 'FUTURE') { - return {userTask.name}; + return {userTask.title}; } if (userTask.state === 'READY') { return ( - - {userTask.name} - Current - + navigate(taskUrl)} + data-qa={`form-nav-${userTask.name}`} + > + {userTask.title} + ); } return null; }); } return ( - - - {userTasksElement} - + + + {userTasksElement} + + ); }; @@ -149,15 +168,19 @@ export default function TaskShow() { } return ( -
- {reactFragmentToHideSubmitButton} -
+ + +
+ {reactFragmentToHideSubmitButton} +
+
+
); }; @@ -175,7 +198,7 @@ export default function TaskShow() { ); }; - if (task) { + if (task && userTasks) { const taskToUse = task as any; let statusString = ''; if (taskToUse.state !== 'READY') { diff --git a/spiffworkflow-frontend/src/themes/carbon/BaseInputTemplate/BaseInputTemplate.tsx b/spiffworkflow-frontend/src/themes/carbon/BaseInputTemplate/BaseInputTemplate.tsx index 90cc3f0f8..0670c69d6 100644 --- a/spiffworkflow-frontend/src/themes/carbon/BaseInputTemplate/BaseInputTemplate.tsx +++ b/spiffworkflow-frontend/src/themes/carbon/BaseInputTemplate/BaseInputTemplate.tsx @@ -82,6 +82,9 @@ export default function BaseInputTemplate< } else if (schema && schema.title) { labelToUse = schema.title; } + if (required) { + labelToUse = `${labelToUse}*`; + } let invalid = false; let errorMessageForField = null;