Wizard güncellemesi

This commit is contained in:
Sedat ÖZTÜRK 2026-02-27 09:54:25 +03:00
parent 4099ef9079
commit 877d0e7397
2 changed files with 422 additions and 371 deletions

View file

@ -879,21 +879,11 @@
"RequiredPermissionName": "App.Contact", "RequiredPermissionName": "App.Contact",
"IsDisabled": false "IsDisabled": false
}, },
{
"ParentCode": "App.Administration",
"Code": "App.Listforms.Wizard",
"DisplayName": "App.Listforms.Wizard",
"Order": 5,
"Url": "/admin/listform/wizard",
"Icon": "FcFlashAuto",
"RequiredPermissionName": "App.Listforms.Wizard",
"IsDisabled": false
},
{ {
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Routes", "Code": "App.Routes",
"DisplayName": "App.Routes", "DisplayName": "App.Routes",
"Order": 6, "Order": 5,
"Url": "/admin/list/App.Routes", "Url": "/admin/list/App.Routes",
"Icon": "FaSynagogue", "Icon": "FaSynagogue",
"RequiredPermissionName": "App.Routes", "RequiredPermissionName": "App.Routes",
@ -903,7 +893,7 @@
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Menus", "Code": "App.Menus",
"DisplayName": "App.Menus", "DisplayName": "App.Menus",
"Order": 7, "Order": 6,
"Url": null, "Url": null,
"Icon": "FaSchlix", "Icon": "FaSchlix",
"RequiredPermissionName": null, "RequiredPermissionName": null,
@ -933,7 +923,7 @@
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Files", "Code": "App.Files",
"DisplayName": "App.Files", "DisplayName": "App.Files",
"Order": 8, "Order": 7,
"Url": "/admin/files", "Url": "/admin/files",
"Icon": "FcFolder", "Icon": "FcFolder",
"RequiredPermissionName": "App.Files", "RequiredPermissionName": "App.Files",
@ -943,7 +933,7 @@
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Reports.Management", "Code": "App.Reports.Management",
"DisplayName": "App.Reports.Management", "DisplayName": "App.Reports.Management",
"Order": 9, "Order": 8,
"Url": null, "Url": null,
"Icon": "FcDocument", "Icon": "FcDocument",
"RequiredPermissionName": null, "RequiredPermissionName": null,
@ -973,7 +963,7 @@
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.DeveloperKit", "Code": "App.DeveloperKit",
"DisplayName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit",
"Order": 10, "Order": 9,
"Url": null, "Url": null,
"Icon": "FcAndroidOs", "Icon": "FcAndroidOs",
"RequiredPermissionName": null, "RequiredPermissionName": null,
@ -1059,6 +1049,16 @@
"RequiredPermissionName": "App.SqlQueryManager", "RequiredPermissionName": "App.SqlQueryManager",
"IsDisabled": false "IsDisabled": false
}, },
{
"ParentCode": "App.Administration",
"Code": "App.Listforms.Wizard",
"DisplayName": "App.Listforms.Wizard",
"Order": 10,
"Url": "/admin/listform/wizard",
"Icon": "FcFlashAuto",
"RequiredPermissionName": "App.Listforms.Wizard",
"IsDisabled": false
},
{ {
"ParentCode": "App.Administration", "ParentCode": "App.Administration",
"Code": "App.Forum", "Code": "App.Forum",

View file

@ -6,14 +6,15 @@ import {
Input, Input,
Notification, Notification,
Select, Select,
Steps,
toast, toast,
} from '@/components/ui' } from '@/components/ui'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { ListFormWizardDto } from '@/proxy/admin/list-form/models' import { ListFormWizardDto } from '@/proxy/admin/list-form/models'
import { SelectBoxOption } from '@/types/shared' import { SelectBoxOption } from '@/types/shared'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { Field, FieldProps, Form, Formik } from 'formik' import { Field, FieldProps, Form, Formik, FormikProps } from 'formik'
import { useEffect, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import CreatableSelect from 'react-select/creatable' import CreatableSelect from 'react-select/creatable'
@ -47,19 +48,22 @@ const initialValues: ListFormWizardDto = {
keyFieldDbSourceType: DbTypeEnum.Int32, // Select: Enum dbSourceTypeOptions keyFieldDbSourceType: DbTypeEnum.Int32, // Select: Enum dbSourceTypeOptions
} }
const listFormValidationSchema = Yup.object().shape({ const step1ValidationSchema = Yup.object().shape({
listFormCode: Yup.string().required(), listFormCode: Yup.string().required(),
permissionGroupName: Yup.string().required(),
menuParentCode: Yup.string().required(),
menuIcon: Yup.string(),
languageTextMenuEn: Yup.string(), languageTextMenuEn: Yup.string(),
languageTextMenuTr: Yup.string(), languageTextMenuTr: Yup.string(),
languageTextMenuParentEn: Yup.string(),
languageTextMenuParentTr: Yup.string(),
})
const step2ValidationSchema = Yup.object().shape({
languageTextTitleEn: Yup.string(), languageTextTitleEn: Yup.string(),
languageTextTitleTr: Yup.string(), languageTextTitleTr: Yup.string(),
languageTextDescEn: Yup.string(), languageTextDescEn: Yup.string(),
languageTextDescTr: Yup.string(), languageTextDescTr: Yup.string(),
languageTextMenuParentEn: Yup.string(),
languageTextMenuParentTr: Yup.string(),
permissionGroupName: Yup.string().required(),
menuParentCode: Yup.string().required(),
menuIcon: Yup.string(),
dataSourceCode: Yup.string(), dataSourceCode: Yup.string(),
dataSourceConnectionString: Yup.string(), dataSourceConnectionString: Yup.string(),
selectCommandType: Yup.string().required(), selectCommandType: Yup.string().required(),
@ -68,8 +72,12 @@ const listFormValidationSchema = Yup.object().shape({
keyFieldDbSourceType: Yup.string().required(), keyFieldDbSourceType: Yup.string().required(),
}) })
const listFormValidationSchema = step1ValidationSchema.concat(step2ValidationSchema)
const Wizard = () => { const Wizard = () => {
const { translate } = useLocalization() const { translate } = useLocalization()
const [currentStep, setCurrentStep] = useState(0)
const [isLoadingDataSource, setIsLoadingDataSource] = useState(false) const [isLoadingDataSource, setIsLoadingDataSource] = useState(false)
const [dataSourceList, setDataSourceList] = useState<SelectBoxOption[]>([]) const [dataSourceList, setDataSourceList] = useState<SelectBoxOption[]>([])
const [isDataSourceNew, setIsDataSourceNew] = useState(false) const [isDataSourceNew, setIsDataSourceNew] = useState(false)
@ -106,7 +114,6 @@ const Wizard = () => {
const [isLoadingPermissionGroup, setIsLoadingPermissionGroup] = useState(false) const [isLoadingPermissionGroup, setIsLoadingPermissionGroup] = useState(false)
const [permissionGroupList, setPermissionGroupList] = useState<SelectBoxOption[]>([]) const [permissionGroupList, setPermissionGroupList] = useState<SelectBoxOption[]>([])
const [isPermissionGroupNew, setIsPermissionGroupNew] = useState(false)
const getPermissionGroupList = async () => { const getPermissionGroupList = async () => {
setIsLoadingPermissionGroup(true) setIsLoadingPermissionGroup(true)
const response = await getPermissions('R', '') const response = await getPermissions('R', '')
@ -128,6 +135,27 @@ const Wizard = () => {
}, []) }, [])
const navigate = useNavigate() const navigate = useNavigate()
const formikRef = useRef<FormikProps<ListFormWizardDto>>(null)
const handleNext = async () => {
if (!formikRef.current) return
const errors = await formikRef.current.validateForm()
const step1Fields = Object.keys(step1ValidationSchema.fields)
const hasStep1Errors = step1Fields.some((f) => errors[f as keyof ListFormWizardDto])
// Touch all step 1 fields so errors appear
const touchedStep1 = step1Fields.reduce(
(acc, key) => ({ ...acc, [key]: true }),
{} as Record<string, boolean>,
)
formikRef.current.setTouched({ ...formikRef.current.touched, ...touchedStep1 })
if (!hasStep1Errors) {
setCurrentStep(1)
}
}
const handleBack = () => {
setCurrentStep(0)
}
return ( return (
<Container> <Container>
@ -137,8 +165,16 @@ const Wizard = () => {
defaultTitle={APP_NAME} defaultTitle={APP_NAME}
></Helmet> ></Helmet>
<div className="mb-8">
<Steps current={currentStep}>
<Steps.Item title={translate('::ListForms.Wizard.BasicInfo') || 'Basic Info'} />
<Steps.Item title={translate('::ListForms.Wizard.DataSettings') || 'Data Settings'} />
</Steps>
</div>
<div className="grid lg:grid-cols-2 xl:grid-cols-3"> <div className="grid lg:grid-cols-2 xl:grid-cols-3">
<Formik <Formik
innerRef={formikRef}
initialValues={{ ...initialValues }} initialValues={{ ...initialValues }}
validationSchema={listFormValidationSchema} validationSchema={listFormValidationSchema}
onSubmit={async (values, { setSubmitting }) => { onSubmit={async (values, { setSubmitting }) => {
@ -172,6 +208,9 @@ const Wizard = () => {
{({ touched, errors, isSubmitting, values }) => ( {({ touched, errors, isSubmitting, values }) => (
<Form> <Form>
<FormContainer size="sm"> <FormContainer size="sm">
{/* ─── Step 1: Basic Info ─────────────────────────────── */}
{currentStep === 0 && (
<>
<FormItem <FormItem
label="ListForm Code" label="ListForm Code"
invalid={errors.listFormCode && touched.listFormCode} invalid={errors.listFormCode && touched.listFormCode}
@ -186,6 +225,7 @@ const Wizard = () => {
component={Input} component={Input}
/> />
</FormItem> </FormItem>
<FormItem <FormItem
label="Permission Group Name" label="Permission Group Name"
invalid={errors.permissionGroupName && touched.permissionGroupName} invalid={errors.permissionGroupName && touched.permissionGroupName}
@ -204,7 +244,9 @@ const Wizard = () => {
options={permissionGroupList} options={permissionGroupList}
value={ value={
values.permissionGroupName values.permissionGroupName
? (menuList?.find((o) => o.value === values.permissionGroupName) ?? { ? (permissionGroupList?.find(
(o) => o.value === values.permissionGroupName,
) ?? {
label: values.permissionGroupName, label: values.permissionGroupName,
value: values.permissionGroupName, value: values.permissionGroupName,
}) })
@ -212,14 +254,12 @@ const Wizard = () => {
} }
onChange={(option) => { onChange={(option) => {
form.setFieldValue(field.name, option?.value) form.setFieldValue(field.name, option?.value)
setIsPermissionGroupNew(
!!option?.value && !menuList.some((a) => a.value === option?.value),
)
}} }}
/> />
)} )}
</Field> </Field>
</FormItem> </FormItem>
<div className="grid grid-cols-2 gap-1"> <div className="grid grid-cols-2 gap-1">
<FormItem <FormItem
label="Menu Parent Code" label="Menu Parent Code"
@ -248,18 +288,19 @@ const Wizard = () => {
onChange={(option) => { onChange={(option) => {
form.setFieldValue(field.name, option?.value) form.setFieldValue(field.name, option?.value)
setIsMenuNew( setIsMenuNew(
!!option?.value && !menuList.some((a) => a.value === option?.value), !!option?.value &&
!menuList.some((a) => a.value === option?.value),
) )
}} }}
/> />
)} )}
</Field> </Field>
</FormItem> </FormItem>
<FormItem <FormItem
label="Menu Icon" label="Menu Icon"
invalid={errors.menuIcon && touched.menuIcon} invalid={errors.menuIcon && touched.menuIcon}
errorMessage={errors.menuIcon} errorMessage={errors.menuIcon}
asterisk={true}
> >
<Field <Field
type="text" type="text"
@ -270,50 +311,145 @@ const Wizard = () => {
/> />
</FormItem> </FormItem>
</div> </div>
{isMenuNew && ( {isMenuNew && (
<div className="grid grid-cols-2 gap-1"> <div className="grid grid-cols-2 gap-1">
<FormItem <FormItem
label="Parent Menu Code (En)" label="Parent Menu Text (En)"
invalid={errors.languageTextMenuParentEn && touched.languageTextMenuParentEn} invalid={
errors.languageTextMenuParentEn && touched.languageTextMenuParentEn
}
errorMessage={errors.languageTextMenuParentEn} errorMessage={errors.languageTextMenuParentEn}
asterisk={true}
> >
<Field <Field
type="text" type="text"
autoComplete="off" autoComplete="off"
name="languageTextMenuParentEn" name="languageTextMenuParentEn"
placeholder="Parent Menu Code (En)" placeholder="Parent Menu Text (En)"
component={Input} component={Input}
/> />
</FormItem> </FormItem>
<FormItem <FormItem
label="Parent Menu Code (Tr)" label="Parent Menu Text (Tr)"
invalid={errors.languageTextMenuParentTr && touched.languageTextMenuParentTr} invalid={
errors.languageTextMenuParentTr && touched.languageTextMenuParentTr
}
errorMessage={errors.languageTextMenuParentTr} errorMessage={errors.languageTextMenuParentTr}
asterisk={true}
> >
<Field <Field
type="text" type="text"
autoComplete="off" autoComplete="off"
name="languageTextMenuParentTr" name="languageTextMenuParentTr"
placeholder="Parent Menu Code (Tr)" placeholder="Parent Menu Text (Tr)"
component={Input} component={Input}
/> />
</FormItem> </FormItem>
</div> </div>
)} )}
<div className="grid grid-cols-2 gap-1">
<FormItem
label="Menu Text (En)"
invalid={errors.languageTextMenuEn && touched.languageTextMenuEn}
errorMessage={errors.languageTextMenuEn}
>
<Field
type="text"
autoComplete="off"
name="languageTextMenuEn"
placeholder="Menu Text (En)"
component={Input}
/>
</FormItem>
<FormItem
label="Menu Text (Tr)"
invalid={errors.languageTextMenuTr && touched.languageTextMenuTr}
errorMessage={errors.languageTextMenuTr}
>
<Field
type="text"
autoComplete="off"
name="languageTextMenuTr"
placeholder="Menu Text (Tr)"
component={Input}
/>
</FormItem>
</div>
<Button block className="mt-4" variant="solid" type="button" onClick={handleNext}>
{translate('::Next') || 'Next'}
</Button>
</>
)}
{/* ─── Step 2: Data Settings ───────────────────────────── */}
{currentStep === 1 && (
<>
<div className="grid grid-cols-2 gap-1">
<FormItem
label="Title (En)"
invalid={errors.languageTextTitleEn && touched.languageTextTitleEn}
errorMessage={errors.languageTextTitleEn}
>
<Field
type="text"
autoComplete="off"
name="languageTextTitleEn"
placeholder="Title (En)"
component={Input}
/>
</FormItem>
<FormItem
label="Title (Tr)"
invalid={errors.languageTextTitleTr && touched.languageTextTitleTr}
errorMessage={errors.languageTextTitleTr}
>
<Field
type="text"
autoComplete="off"
name="languageTextTitleTr"
placeholder="Title (Tr)"
component={Input}
/>
</FormItem>
</div>
<div className="grid grid-cols-2 gap-1">
<FormItem
label="Description (En)"
invalid={errors.languageTextDescEn && touched.languageTextDescEn}
errorMessage={errors.languageTextDescEn}
>
<Field
type="text"
autoComplete="off"
name="languageTextDescEn"
placeholder="Description (En)"
component={Input}
/>
</FormItem>
<FormItem
label="Description (Tr)"
invalid={errors.languageTextDescTr && touched.languageTextDescTr}
errorMessage={errors.languageTextDescTr}
>
<Field
type="text"
autoComplete="off"
name="languageTextDescTr"
placeholder="Description (Tr)"
component={Input}
/>
</FormItem>
</div>
<div className="grid grid-cols-2 gap-1"> <div className="grid grid-cols-2 gap-1">
<FormItem <FormItem
label="Data Source Code" label="Data Source Code"
invalid={errors.dataSourceCode && touched.dataSourceCode} invalid={errors.dataSourceCode && touched.dataSourceCode}
errorMessage={errors.dataSourceCode} errorMessage={errors.dataSourceCode}
> >
<Field <Field type="text" autoComplete="off" name="dataSourceCode">
type="text"
autoComplete="off"
name="dataSourceCode"
placeholder="Data Source Code"
>
{({ field, form }: FieldProps<string>) => ( {({ field, form }: FieldProps<string>) => (
<Select <Select
componentAs={CreatableSelect} componentAs={CreatableSelect}
@ -325,7 +461,9 @@ const Wizard = () => {
options={dataSourceList} options={dataSourceList}
value={ value={
values.dataSourceCode values.dataSourceCode
? (dataSourceList?.find((o) => o.value === values.dataSourceCode) ?? { ? (dataSourceList?.find(
(o) => o.value === values.dataSourceCode,
) ?? {
label: values.dataSourceCode, label: values.dataSourceCode,
value: values.dataSourceCode, value: values.dataSourceCode,
}) })
@ -342,6 +480,7 @@ const Wizard = () => {
)} )}
</Field> </Field>
</FormItem> </FormItem>
{isDataSourceNew && ( {isDataSourceNew && (
<FormItem <FormItem
label="Connection String" label="Connection String"
@ -360,6 +499,7 @@ const Wizard = () => {
</FormItem> </FormItem>
)} )}
</div> </div>
<div className="grid grid-cols-2 gap-1"> <div className="grid grid-cols-2 gap-1">
<FormItem <FormItem
label="Select Command Type" label="Select Command Type"
@ -367,24 +507,21 @@ const Wizard = () => {
errorMessage={errors.selectCommandType} errorMessage={errors.selectCommandType}
asterisk={true} asterisk={true}
> >
<Field <Field type="text" autoComplete="off" name="selectCommandType">
type="text"
autoComplete="off"
name="selectCommandType"
placeholder="Select Command Type"
component={Input}
>
{({ field, form }: FieldProps<SelectCommandTypeEnum>) => ( {({ field, form }: FieldProps<SelectCommandTypeEnum>) => (
<Select <Select
field={field} field={field}
form={form} form={form}
options={selectCommandTypeOptions} options={selectCommandTypeOptions}
value={selectCommandTypeOptions.find((o: any) => o.value === field.value)} value={selectCommandTypeOptions.find(
(o: any) => o.value === field.value,
)}
onChange={(o) => form.setFieldValue(field.name, o?.value)} onChange={(o) => form.setFieldValue(field.name, o?.value)}
/> />
)} )}
</Field> </Field>
</FormItem> </FormItem>
<FormItem <FormItem
label="Select Command" label="Select Command"
invalid={errors.selectCommand && touched.selectCommand} invalid={errors.selectCommand && touched.selectCommand}
@ -400,6 +537,7 @@ const Wizard = () => {
/> />
</FormItem> </FormItem>
</div> </div>
<div className="grid grid-cols-2 gap-1"> <div className="grid grid-cols-2 gap-1">
<FormItem <FormItem
label="Key Field Name" label="Key Field Name"
@ -415,19 +553,14 @@ const Wizard = () => {
component={Input} component={Input}
/> />
</FormItem> </FormItem>
<FormItem <FormItem
label="Key Field Db Source Type" label="Key Field Db Source Type"
invalid={errors.keyFieldDbSourceType && touched.keyFieldDbSourceType} invalid={errors.keyFieldDbSourceType && touched.keyFieldDbSourceType}
errorMessage={errors.keyFieldDbSourceType} errorMessage={errors.keyFieldDbSourceType}
asterisk={true} asterisk={true}
> >
<Field <Field type="text" autoComplete="off" name="keyFieldDbSourceType">
type="text"
autoComplete="off"
name="keyFieldDbSourceType"
placeholder="Key Field Db Source Type"
component={Input}
>
{({ field, form }: FieldProps<DbTypeEnum>) => ( {({ field, form }: FieldProps<DbTypeEnum>) => (
<Select <Select
field={field} field={field}
@ -442,99 +575,17 @@ const Wizard = () => {
</Field> </Field>
</FormItem> </FormItem>
</div> </div>
<div className="grid grid-cols-2 gap-1">
<FormItem <div className="flex gap-2 mt-4">
label="Menu Text (En)" <Button block variant="default" type="button" onClick={handleBack}>
invalid={errors.languageTextMenuEn && touched.languageTextMenuEn} {translate('::Back') || 'Back'}
errorMessage={errors.languageTextMenuEn} </Button>
asterisk={true} <Button block variant="solid" loading={isSubmitting} type="submit">
>
<Field
type="text"
autoComplete="off"
name="languageTextMenuEn"
placeholder="Menu Text (En)"
component={Input}
/>
</FormItem>
<FormItem
label="Menu Text (Tr)"
invalid={errors.languageTextMenuTr && touched.languageTextMenuTr}
errorMessage={errors.languageTextMenuTr}
asterisk={true}
>
<Field
type="text"
autoComplete="off"
name="languageTextMenuTr"
placeholder="Menu Text (Tr)"
component={Input}
/>
</FormItem>
</div>
<div className="grid grid-cols-2 gap-1">
<FormItem
label="Title (En)"
invalid={errors.languageTextTitleEn && touched.languageTextTitleEn}
errorMessage={errors.languageTextTitleEn}
asterisk={true}
>
<Field
type="text"
autoComplete="off"
name="languageTextTitleEn"
placeholder="Title (En)"
component={Input}
/>
</FormItem>
<FormItem
label="Title (Tr)"
invalid={errors.languageTextTitleTr && touched.languageTextTitleTr}
errorMessage={errors.languageTextTitleTr}
asterisk={true}
>
<Field
type="text"
autoComplete="off"
name="languageTextTitleTr"
placeholder="Title (Tr)"
component={Input}
/>
</FormItem>
</div>
<div className="grid grid-cols-2 gap-1">
<FormItem
label="Description (En)"
invalid={errors.languageTextDescEn && touched.languageTextDescEn}
errorMessage={errors.languageTextDescEn}
asterisk={true}
>
<Field
type="text"
autoComplete="off"
name="languageTextDescEn"
placeholder="Description (En)"
component={Input}
/>
</FormItem>
<FormItem
label="Description (Tr)"
invalid={errors.languageTextDescTr && touched.languageTextDescTr}
errorMessage={errors.languageTextDescTr}
asterisk={true}
>
<Field
type="text"
autoComplete="off"
name="languageTextDescTr"
placeholder="Description (Tr)"
component={Input}
/>
</FormItem>
</div>
<Button block className="mt-4" variant="solid" loading={isSubmitting} type="submit">
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')} {isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button> </Button>
</div>
</>
)}
</FormContainer> </FormContainer>
</Form> </Form>
)} )}