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",
"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",
"Code": "App.Routes",
"DisplayName": "App.Routes",
"Order": 6,
"Order": 5,
"Url": "/admin/list/App.Routes",
"Icon": "FaSynagogue",
"RequiredPermissionName": "App.Routes",
@ -903,7 +893,7 @@
"ParentCode": "App.Administration",
"Code": "App.Menus",
"DisplayName": "App.Menus",
"Order": 7,
"Order": 6,
"Url": null,
"Icon": "FaSchlix",
"RequiredPermissionName": null,
@ -933,7 +923,7 @@
"ParentCode": "App.Administration",
"Code": "App.Files",
"DisplayName": "App.Files",
"Order": 8,
"Order": 7,
"Url": "/admin/files",
"Icon": "FcFolder",
"RequiredPermissionName": "App.Files",
@ -943,7 +933,7 @@
"ParentCode": "App.Administration",
"Code": "App.Reports.Management",
"DisplayName": "App.Reports.Management",
"Order": 9,
"Order": 8,
"Url": null,
"Icon": "FcDocument",
"RequiredPermissionName": null,
@ -973,7 +963,7 @@
"ParentCode": "App.Administration",
"Code": "App.DeveloperKit",
"DisplayName": "App.DeveloperKit",
"Order": 10,
"Order": 9,
"Url": null,
"Icon": "FcAndroidOs",
"RequiredPermissionName": null,
@ -1059,6 +1049,16 @@
"RequiredPermissionName": "App.SqlQueryManager",
"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",
"Code": "App.Forum",

View file

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