Commit 951cb162 authored by Stefan Probst's avatar Stefan Probst
Browse files

feat: add multipage form for workflows

parent c247a3db
Pipeline #178335 passed with stages
in 11 minutes and 46 seconds
......@@ -106,5 +106,12 @@ export function convertToInitialFormValues(
initialValues.dateLastUpdated = item.dateLastUpdated
}
if (item.category === 'workflow') {
// @ts-expect-error items are not discriminated unions
initialValues.composedOf = item.composedOf?.map((step) =>
convertToInitialFormValues(step),
)
}
return initialValues
}
import type { Config as FormConfig } from 'final-form'
import { useRouter } from 'next/router'
import type { ReactNode } from 'react'
import { Fragment, useState } from 'react'
import { useQueryClient } from 'react-query'
import {
useCreateWorkflow,
......@@ -12,6 +15,7 @@ import { MainFormSection } from '@/components/item/MainFormSection/MainFormSecti
import { PropertiesFormSection } from '@/components/item/PropertiesFormSection/PropertiesFormSection'
import { RelatedItemsFormSection } from '@/components/item/RelatedItemsFormSection/RelatedItemsFormSection'
import { SourceFormSection } from '@/components/item/SourceFormSection/SourceFormSection'
import { WorkflowStepsFormSection } from '@/components/item/WorkflowStepsFormSection/WorkflowStepsFormSection'
import { Button } from '@/elements/Button/Button'
import { useToast } from '@/elements/Toast/useToast'
import { validateCommonFormFields } from '@/lib/sshoc/validateCommonFormFields'
......@@ -108,7 +112,7 @@ export function ItemForm(props: ItemFormProps<ItemFormValues>): JSX.Element {
])
}
function onValidate(values: Partial<ItemFormValues>) {
function onValidateWorkflow(values: Partial<ItemFormValues>) {
const errors: Partial<Record<keyof typeof values, string>> = {}
validateCommonFormFields(values, errors)
......@@ -120,11 +124,60 @@ export function ItemForm(props: ItemFormProps<ItemFormValues>): JSX.Element {
router.push('/')
}
/**
* Multi-page form.
* */
const [state, setState] = useState({ page: 0, values: initialValues })
const pages = [
<FormPage key="workflow-page" onValidate={onValidateWorkflow}>
<MainFormSection />
<ActorsFormSection />
<PropertiesFormSection />
<RelatedItemsFormSection />
<SourceFormSection />
</FormPage>,
<FormPage key="steps-page">
<WorkflowStepsFormSection />
</FormPage>,
]
const activePage = pages[state.page]
const isLastPage = state.page === pages.length - 1
function nextPage(values: Partial<ItemFormValues>) {
setState((state) => ({
values,
page: Math.min(state.page + 1, pages.length - 1),
}))
}
function previousPage() {
setState((state) => ({
values: state.values,
page: Math.max(state.page - 1, 0),
}))
}
function handleSubmit(values: Partial<ItemFormValues>) {
if (isLastPage) {
return onSubmit(values)
} else {
nextPage(values)
}
}
function validate(values: Partial<ItemFormValues>) {
return typeof activePage.props.onValidate === 'function'
? activePage.props.onValidate(values)
: undefined
}
return (
<Form
onSubmit={onSubmit}
validate={onValidate}
initialValues={initialValues}
onSubmit={handleSubmit}
validate={validate}
initialValues={state.values}
>
{({ handleSubmit, form, pristine, invalid, submitting }) => {
return (
......@@ -133,38 +186,45 @@ export function ItemForm(props: ItemFormProps<ItemFormValues>): JSX.Element {
noValidate
className="flex flex-col space-y-12"
>
<MainFormSection />
<ActorsFormSection />
<PropertiesFormSection />
<RelatedItemsFormSection />
<SourceFormSection />
{pages[state.page]}
<div className="flex items-center justify-end space-x-6">
<Button onPress={onCancel} variant="link">
Cancel
</Button>
<Button
type="submit"
onPress={() => {
form.change('draft', true)
}}
isDisabled={
pristine || invalid || submitting || create.isLoading
}
variant="link"
>
Save as draft
</Button>
<Button
type="submit"
onPress={() => {
form.change('draft', undefined)
}}
isDisabled={
pristine || invalid || submitting || create.isLoading
}
>
{isAllowedToPublish ? 'Publish' : 'Submit'}
</Button>
{state.page > 0 ? (
<Button onPress={previousPage}>Previous</Button>
) : null}
{isLastPage ? (
<Fragment>
<Button
type="submit"
onPress={() => {
form.change('draft', true)
}}
isDisabled={
pristine || invalid || submitting || create.isLoading
}
variant="link"
>
Save as draft
</Button>
<Button
type="submit"
onPress={() => {
form.change('draft', undefined)
}}
isDisabled={
pristine || invalid || submitting || create.isLoading
}
>
{isAllowedToPublish ? 'Publish' : 'Submit'}
</Button>
</Fragment>
) : (
<Button type="submit" isDisabled={invalid}>
Next
</Button>
)}
</div>
</form>
)
......@@ -172,3 +232,12 @@ export function ItemForm(props: ItemFormProps<ItemFormValues>): JSX.Element {
</Form>
)
}
interface FormPageProps {
onValidate?: FormConfig<ItemFormValues>['validate']
children: ReactNode
}
function FormPage(props: FormPageProps) {
return <Fragment>{props.children}</Fragment>
}
import type { Config as FormConfig } from 'final-form'
import { useRouter } from 'next/router'
import type { ReactNode } from 'react'
import { Fragment, useState } from 'react'
import { useQueryClient } from 'react-query'
import {
useGetLoggedInUser,
......@@ -12,6 +15,7 @@ import { MainFormSection } from '@/components/item/MainFormSection/MainFormSecti
import { PropertiesFormSection } from '@/components/item/PropertiesFormSection/PropertiesFormSection'
import { RelatedItemsFormSection } from '@/components/item/RelatedItemsFormSection/RelatedItemsFormSection'
import { SourceFormSection } from '@/components/item/SourceFormSection/SourceFormSection'
import { WorkflowStepsFormSection } from '@/components/item/WorkflowStepsFormSection/WorkflowStepsFormSection'
import { Button } from '@/elements/Button/Button'
import { useToast } from '@/elements/Toast/useToast'
import { validateCommonFormFields } from '@/lib/sshoc/validateCommonFormFields'
......@@ -111,7 +115,7 @@ export function ItemForm(props: ItemFormProps<ItemFormValues>): JSX.Element {
])
}
function onValidate(values: Partial<ItemFormValues>) {
function onValidateWorkflow(values: Partial<ItemFormValues>) {
const errors: Partial<Record<keyof typeof values, string>> = {}
validateCommonFormFields(values, errors)
......@@ -123,11 +127,60 @@ export function ItemForm(props: ItemFormProps<ItemFormValues>): JSX.Element {
router.push('/')
}
/**
* Multi-page form.
* */
const [state, setState] = useState({ page: 0, values: initialValues })
const pages = [
<FormPage key="workflow-page" onValidate={onValidateWorkflow}>
<MainFormSection />
<ActorsFormSection initialValues={props.item} />
<PropertiesFormSection initialValues={props.item} />
<RelatedItemsFormSection initialValues={props.item} />
<SourceFormSection />
</FormPage>,
<FormPage key="steps-page">
<WorkflowStepsFormSection />
</FormPage>,
]
const activePage = pages[state.page]
const isLastPage = state.page === pages.length - 1
function nextPage(values: Partial<ItemFormValues>) {
setState((state) => ({
values,
page: Math.min(state.page + 1, pages.length - 1),
}))
}
function previousPage() {
setState((state) => ({
values: state.values,
page: Math.max(state.page - 1, 0),
}))
}
function handleSubmit(values: Partial<ItemFormValues>) {
if (isLastPage) {
return onSubmit(values)
} else {
nextPage(values)
}
}
function validate(values: Partial<ItemFormValues>) {
return typeof activePage.props.onValidate === 'function'
? activePage.props.onValidate(values)
: undefined
}
return (
<Form
onSubmit={onSubmit}
validate={onValidate}
initialValues={initialValues}
onSubmit={handleSubmit}
validate={validate}
initialValues={state.values}
>
{({ handleSubmit, form, pristine, invalid, submitting }) => {
return (
......@@ -136,38 +189,45 @@ export function ItemForm(props: ItemFormProps<ItemFormValues>): JSX.Element {
noValidate
className="flex flex-col space-y-12"
>
<MainFormSection />
<ActorsFormSection initialValues={props.item} />
<PropertiesFormSection initialValues={props.item} />
<RelatedItemsFormSection initialValues={props.item} />
<SourceFormSection />
{pages[state.page]}
<div className="flex items-center justify-end space-x-6">
<Button onPress={onCancel} variant="link">
Cancel
</Button>
<Button
type="submit"
onPress={() => {
form.change('draft', true)
}}
isDisabled={
pristine || invalid || submitting || create.isLoading
}
variant="link"
>
Save as draft
</Button>
<Button
type="submit"
onPress={() => {
form.change('draft', undefined)
}}
isDisabled={
pristine || invalid || submitting || create.isLoading
}
>
{isAllowedToPublish ? 'Publish' : 'Submit'}
</Button>
{state.page > 0 ? (
<Button onPress={previousPage}>Previous</Button>
) : null}
{isLastPage ? (
<Fragment>
<Button
type="submit"
onPress={() => {
form.change('draft', true)
}}
isDisabled={
pristine || invalid || submitting || create.isLoading
}
variant="link"
>
Save as draft
</Button>
<Button
type="submit"
onPress={() => {
form.change('draft', undefined)
}}
isDisabled={
pristine || invalid || submitting || create.isLoading
}
>
{isAllowedToPublish ? 'Publish' : 'Submit'}
</Button>
</Fragment>
) : (
<Button type="submit" isDisabled={invalid}>
Next
</Button>
)}
</div>
</form>
)
......@@ -175,3 +235,12 @@ export function ItemForm(props: ItemFormProps<ItemFormValues>): JSX.Element {
</Form>
)
}
interface FormPageProps {
onValidate?: FormConfig<ItemFormValues>['validate']
children: ReactNode
}
function FormPage(props: FormPageProps) {
return <Fragment>{props.children}</Fragment>
}
import React from 'react'
import { WorkflowCore } from '@/api/sshoc'
import { FormField } from '@/modules/form/FormField'
import { FormFieldArray } from '@/modules/form/FormFieldArray'
export interface ItemFormValues extends WorkflowCore {
draft?: boolean
}
/**
* Form section for workflow steps.
*/
export function WorkflowStepsFormSection(): JSX.Element {
return (
<section>
<FormField name="label" subscription={{ value: true }}>
{({ input }) => {
return <h2>{input.value}</h2>
}}
</FormField>
<strong>
Editing and sorting workflow steps is not yet implemented.
</strong>
<FormFieldArray name="composedOf">
{({ fields }) => {
return (
<div>
{fields.map((name) => {
return (
<FormField
key={name}
name={`${name}.label`}
subscription={{ value: true }}
>
{({ input }) => {
return <h3>{input.value}</h3>
}}
</FormField>
)
})}
</div>
)
}}
</FormFieldArray>
</section>
)
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment