Wizard Edit özelliği eklendi

This commit is contained in:
Sedat Öztürk 2026-05-03 12:52:22 +03:00
parent ea3e847490
commit b2dfb04879
16 changed files with 572 additions and 216 deletions

View file

@ -7,6 +7,7 @@ public interface IListFormWizardAppService
{ {
Task Create(ListFormWizardDto input); Task Create(ListFormWizardDto input);
Task<List<WizardFileInfoDto>> GetFiles(); Task<List<WizardFileInfoDto>> GetFiles();
Task<WizardSeedFileDto> GetFile(string fileName);
Task DeleteFile(string fileName); Task DeleteFile(string fileName);
} }

View file

@ -398,6 +398,24 @@ public class ListFormWizardAppService(
return Task.FromResult(result); return Task.FromResult(result);
} }
public async Task<WizardSeedFileDto> GetFile(string fileName)
{
// Güvenlik: sadece dosya adı, path traversal yasak
if (fileName.Contains('/') || fileName.Contains('\\') || fileName.Contains(".."))
throw new Volo.Abp.AbpException("Geçersiz dosya adı.");
var outputPath = ResolveWizardSeedOutputPath();
var filePath = Path.Combine(outputPath, fileName);
if (!File.Exists(filePath))
throw new Volo.Abp.AbpException($"Dosya bulunamadı: {fileName}");
var json = await File.ReadAllTextAsync(filePath);
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var seed = JsonSerializer.Deserialize<WizardSeedFileDto>(json, options);
return seed ?? throw new Volo.Abp.AbpException("Dosya okunamadı.");
}
public async Task DeleteFile(string fileName) public async Task DeleteFile(string fileName)
{ {
// Güvenlik: sadece dosya adı, path traversal yasak // Güvenlik: sadece dosya adı, path traversal yasak

View file

@ -16040,6 +16040,12 @@
"en": "Menu Information", "en": "Menu Information",
"tr": "Menü Bilgileri" "tr": "Menü Bilgileri"
}, },
{
"resourceName": "Platform",
"key": "ListForms.Wizard.AddNewRecord",
"en": "Add New Wizard",
"tr": "Yeni Sihirbaz Ekle"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "ListForms.Wizard.Required", "key": "ListForms.Wizard.Required",

View file

@ -48,7 +48,7 @@
"EditorScript": "", "EditorScript": "",
"ColSpan": 1, "ColSpan": 1,
"IsRequired": true, "IsRequired": true,
"DbSourceType": 9, "DbSourceType": 12,
"TurkishCaption": "Id", "TurkishCaption": "Id",
"EnglishCaption": "Id", "EnglishCaption": "Id",
"LookupDataSourceType": 1, "LookupDataSourceType": 1,
@ -64,7 +64,7 @@
"EditorScript": "", "EditorScript": "",
"ColSpan": 1, "ColSpan": 1,
"IsRequired": false, "IsRequired": false,
"DbSourceType": 9, "DbSourceType": 12,
"TurkishCaption": "Branch Id", "TurkishCaption": "Branch Id",
"EnglishCaption": "Branch Id", "EnglishCaption": "Branch Id",
"LookupDataSourceType": 2, "LookupDataSourceType": 2,
@ -80,7 +80,7 @@
"EditorScript": "", "EditorScript": "",
"ColSpan": 1, "ColSpan": 1,
"IsRequired": true, "IsRequired": true,
"DbSourceType": 16, "DbSourceType": 12,
"TurkishCaption": "Name", "TurkishCaption": "Name",
"EnglishCaption": "Name", "EnglishCaption": "Name",
"LookupDataSourceType": 1, "LookupDataSourceType": 1,
@ -96,7 +96,7 @@
"EditorScript": "", "EditorScript": "",
"ColSpan": 1, "ColSpan": 1,
"IsRequired": false, "IsRequired": false,
"DbSourceType": 9, "DbSourceType": 12,
"TurkishCaption": "Parent Id", "TurkishCaption": "Parent Id",
"EnglishCaption": "Parent Id", "EnglishCaption": "Parent Id",
"LookupDataSourceType": 2, "LookupDataSourceType": 2,

View file

@ -1,4 +1,54 @@
{ {
"commit": "aac3f4a", "commit": "ea3e847",
"releases": [] "releases": [
{
"version": "1.0.5",
"buildDate": "2026-04-28",
"commit": "b9cc68ff41d675fefadab79f5ea2c5e7f376708a",
"changeLog": [
"- AllowAdding özelliği eklendi.",
"- Kulllanıcı bazında Workhour özelliği eklendi.",
"- ConcurrentUser sınırlaması getirildi.",
"- Uygulamadan kullanıcıyı kickleme özelliği getirildi.",
"- Uygulama açıkken DbMigrator çağrısı yapıldı."
]
},
{
"version": "1.0.4",
"buildDate": "2026-03-30",
"commit": "e9ce256c0706de408e08a462bee9e4a72653f2c7",
"changeLog": [
"- Role Yetkilerini kopyalama",
"- Place Holder güncellemesi",
"- Kayıt kopyalama özelliği"
]
},
{
"version": "1.0.3",
"buildDate": "2026-03-28",
"commit": "130d35c37703935e34948419f2ee900cc1b6b28c",
"changeLog": [
"Report Template View And Design"
]
},
{
"version": "1.0.2",
"buildDate": "2026-03-21",
"commit": "196b0dc24b7a815586166679b58ab4f9334bd40e",
"changeLog": [
"Artificial Intelligence Chat Bot güncellemeleri"
]
},
{
"version": "1.0.1",
"buildDate": "2026-03-18",
"commit": "ac7784c030825158641e9042c50dede2067c0486",
"changeLog": [
"Public Site Designer",
"Menu Groups",
"Sql Query Manager",
"Listform Wizard"
]
}
]
} }

View file

@ -4,7 +4,6 @@ import {
EditingFormDto, EditingFormDto,
ExtraFilterEditDto, ExtraFilterEditDto,
FieldsDefaultValueDto, FieldsDefaultValueDto,
SelectCommandTypeEnum,
SubFormDto, SubFormDto,
WidgetEditDto, WidgetEditDto,
} from '../../form/models' } from '../../form/models'
@ -15,71 +14,6 @@ import {
ChartValueAxisDto, ChartValueAxisDto,
} from '../charts/models' } from '../charts/models'
export interface ListFormWizardColumnItemDto {
dataField: string
editorType: string
editorOptions: string
editorScript: string
colSpan: number
isRequired: boolean
dbSourceType: number
}
export interface ListFormWizardColumnGroupDto {
caption: string
colCount: number
items: ListFormWizardColumnItemDto[]
}
export interface ListFormWizardDto {
wizardName: string
listFormCode: string
menuCode: string
isTenant: boolean
isBranch: boolean
isOrganizationUnit: boolean
allowAdding: boolean
allowUpdating: boolean
allowDeleting: boolean
confirmDelete: boolean
allowDetail: boolean
defaultLayout: ListViewLayoutType
grid: boolean
pivot: boolean
tree: boolean
chart: boolean
gantt: boolean
scheduler: boolean
languageTextMenuEn: string
languageTextMenuTr: string
languageTextTitleEn: string
languageTextTitleTr: string
languageTextDescEn: string
languageTextDescTr: string
languageTextMenuParentEn: string
languageTextMenuParentTr: string
permissionGroupName: string
menuParentCode: string
menuIcon: string
dataSourceCode: string
dataSourceConnectionString: string
selectCommandType: SelectCommandTypeEnum
selectCommand: string
keyFieldName: string
keyFieldDbSourceType: number
groups?: ListFormWizardColumnGroupDto[]
}
export interface WizardFileInfoDto {
fileName: string
wizardName: string
listFormCode: string
createdAt: string
hasInsertedRecords: boolean
}
export interface ListFormJsonRowDto { export interface ListFormJsonRowDto {
id?: string id?: string
index: number index: number

View file

@ -0,0 +1,109 @@
import { SelectCommandTypeEnum } from "@/proxy/form/models"
import { ListViewLayoutType } from "@/views/admin/listForm/edit/types"
export interface ListFormWizardColumnItemDto {
dataField: string
editorType: string
editorOptions: string
editorScript: string
colSpan: number
isRequired: boolean
dbSourceType: number
}
export interface ListFormWizardColumnGroupDto {
caption: string
colCount: number
items: ListFormWizardColumnItemDto[]
}
export interface ListFormWizardDto {
wizardName: string
listFormCode: string
menuCode: string
isTenant: boolean
isBranch: boolean
isOrganizationUnit: boolean
allowAdding: boolean
allowUpdating: boolean
allowDeleting: boolean
confirmDelete: boolean
allowDetail: boolean
defaultLayout: ListViewLayoutType
grid: boolean
pivot: boolean
tree: boolean
chart: boolean
gantt: boolean
scheduler: boolean
languageTextMenuEn: string
languageTextMenuTr: string
languageTextTitleEn: string
languageTextTitleTr: string
languageTextDescEn: string
languageTextDescTr: string
languageTextMenuParentEn: string
languageTextMenuParentTr: string
permissionGroupName: string
menuParentCode: string
menuIcon: string
dataSourceCode: string
dataSourceConnectionString: string
selectCommandType: SelectCommandTypeEnum
selectCommand: string
keyFieldName: string
keyFieldDbSourceType: number
groups?: ListFormWizardColumnGroupDto[]
}
export interface WizardFileInfoDto {
fileName: string
wizardName: string
listFormCode: string
createdAt: string
hasInsertedRecords: boolean
}
export interface WizardInsertedRecordsDto {
languageKeys: string[]
permissionGroupNames: string[]
permissionNames: string[]
menuCodes: string[]
dataSourceCodes: string[]
}
export interface WizardSeedFileItemDto {
dataField: string
editorType: string
editorOptions: string
editorScript: string
colSpan: number
isRequired: boolean
dbSourceType: number
turkishCaption?: string
englishCaption?: string
captionName?: string
lookupDataSourceType?: number
valueExpr?: string
displayExpr?: string
lookupQuery?: string
}
export interface WizardSeedFileGroupDto {
caption: string
colCount: number
items: WizardSeedFileItemDto[]
}
export interface WizardSeedFileWizardDto extends Omit<ListFormWizardDto, 'groups'> {
groups?: WizardSeedFileGroupDto[]
}
export interface WizardSeedFileDto {
wizard: WizardSeedFileWizardDto
isDeletedField: boolean
isCreatedField: boolean
insertedRecords: WizardInsertedRecordsDto
}

View file

@ -1,4 +1,4 @@
import { ListFormWizardDto, WizardFileInfoDto } from '../proxy/admin/list-form/models' import { ListFormWizardDto, WizardFileInfoDto, WizardSeedFileDto } from '@/proxy/admin/wizard/models'
import apiService from './api.service' import apiService from './api.service'
export const postListFormWizard = (input: ListFormWizardDto) => export const postListFormWizard = (input: ListFormWizardDto) =>
@ -14,6 +14,13 @@ export const getWizardFiles = () =>
url: `/api/app/list-form-wizard/files`, url: `/api/app/list-form-wizard/files`,
}) })
export const getWizardFile = (fileName: string) =>
apiService.fetchData<WizardSeedFileDto>({
method: 'GET',
url: `/api/app/list-form-wizard/file`,
params: { fileName },
})
export const deleteWizardFile = (fileName: string) => export const deleteWizardFile = (fileName: string) =>
apiService.fetchData({ apiService.fetchData({
method: 'DELETE', method: 'DELETE',

View file

@ -1,12 +1,11 @@
import { FormContainer, Notification, Steps, toast } from '@/components/ui' import { FormContainer, Notification, Steps, toast } 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 { SelectBoxOption } from '@/types/shared' import { SelectBoxOption } from '@/types/shared'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { Form, Formik, FormikProps } from 'formik' import { Form, Formik, FormikProps } from 'formik'
import { useEffect, useRef, 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 { useLocation, useNavigate } from 'react-router-dom'
import * as Yup from 'yup' import * as Yup from 'yup'
import { getMenus } from '@/services/menu.service' import { getMenus } from '@/services/menu.service'
import { getPermissions } from '@/services/identity.service' import { getPermissions } from '@/services/identity.service'
@ -23,12 +22,14 @@ import WizardStep1, {
findRootCode, findRootCode,
} from './WizardStep1' } from './WizardStep1'
import WizardStep2 from './WizardStep2' import WizardStep2 from './WizardStep2'
import WizardStep3, { WizardGroup } from './WizardStep3' import WizardStep3, { WizardGroup, WizardGroupItem } from './WizardStep3'
import WizardStep4 from './WizardStep4' import WizardStep4 from './WizardStep4'
import { Container } from '@/components/shared' import { Container } from '@/components/shared'
import { sqlDataTypeToDbType } from '../edit/options' import { sqlDataTypeToDbType } from '../edit/options'
import { useStoreActions } from '@/store/store' import { useStoreActions } from '@/store/store'
import { postListFormWizard } from '@/services/wizard.service' import { deleteWizardFile, getWizardFile, postListFormWizard } from '@/services/wizard.service'
import { UiLookupDataSourceTypeEnum } from '@/proxy/form/models'
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
// ─── Formik initial values & validation ────────────────────────────────────── // ─── Formik initial values & validation ──────────────────────────────────────
const initialValues: ListFormWizardDto = { const initialValues: ListFormWizardDto = {
@ -106,6 +107,13 @@ const listFormValidationSchema = step1ValidationSchema.concat(step2ValidationSch
const Wizard = () => { const Wizard = () => {
const [currentStep, setCurrentStep] = useState(0) const [currentStep, setCurrentStep] = useState(0)
// ── Edit mode ──
const location = useLocation()
const editFileName: string | null = (location.state as any)?.editFileName ?? null
const isEditMode = !!editFileName
const [isLoadingEditData, setIsLoadingEditData] = useState(false)
const editDataLoadedRef = useRef(false)
// ── Data Source ── // ── Data Source ──
const [isLoadingDataSource, setIsLoadingDataSource] = useState(false) const [isLoadingDataSource, setIsLoadingDataSource] = useState(false)
const [dataSourceList, setDataSourceList] = useState<SelectBoxOption[]>([]) const [dataSourceList, setDataSourceList] = useState<SelectBoxOption[]>([])
@ -259,6 +267,118 @@ const Wizard = () => {
getPermissionGroupList() getPermissionGroupList()
}, []) }, [])
// ── Load edit data after reference lists are ready ──
useEffect(() => {
if (!isEditMode || !editFileName) return
if (editDataLoadedRef.current) return
if (isLoadingMenu || isLoadingDataSource || isLoadingPermissionGroup) return
if (menuTree.length === 0 && dataSourceList.length === 0) return
editDataLoadedRef.current = true
const loadEditData = async () => {
setIsLoadingEditData(true)
try {
const res = await getWizardFile(editFileName)
const seed = res.data
if (!seed?.wizard) return
const w = seed.wizard
// Populate formik values
formikRef.current?.setValues({
wizardName: w.wizardName ?? '',
listFormCode: w.listFormCode ?? '',
menuCode: w.menuCode ?? '',
isTenant: w.isTenant ?? false,
isBranch: w.isBranch ?? false,
isOrganizationUnit: w.isOrganizationUnit ?? false,
allowAdding: w.allowAdding ?? true,
allowUpdating: w.allowUpdating ?? true,
allowDeleting: w.allowDeleting ?? true,
confirmDelete: w.confirmDelete ?? true,
allowDetail: w.allowDetail ?? false,
defaultLayout: w.defaultLayout ?? 'grid',
grid: w.grid ?? true,
pivot: w.pivot ?? true,
tree: w.tree ?? true,
chart: w.chart ?? true,
gantt: w.gantt ?? true,
scheduler: w.scheduler ?? true,
languageTextMenuEn: w.languageTextMenuEn ?? '',
languageTextMenuTr: w.languageTextMenuTr ?? '',
languageTextTitleEn: w.languageTextTitleEn ?? '',
languageTextTitleTr: w.languageTextTitleTr ?? '',
languageTextDescEn: w.languageTextDescEn ?? '',
languageTextDescTr: w.languageTextDescTr ?? '',
languageTextMenuParentEn: w.languageTextMenuParentEn ?? '',
languageTextMenuParentTr: w.languageTextMenuParentTr ?? '',
permissionGroupName: w.permissionGroupName ?? '',
menuParentCode: w.menuParentCode ?? '',
menuIcon: w.menuIcon ?? '',
dataSourceCode: w.dataSourceCode ?? '',
dataSourceConnectionString: w.dataSourceConnectionString ?? '',
selectCommandType: w.selectCommandType ?? SelectCommandTypeEnum.Table,
selectCommand: w.selectCommand ?? '',
keyFieldName: w.keyFieldName ?? '',
keyFieldDbSourceType: w.keyFieldDbSourceType ?? DbTypeEnum.Int32,
})
// Restore datasource to trigger db objects load
if (w.dataSourceCode) {
setCurrentDataSource(w.dataSourceCode)
}
// Restore groups → WizardGroup[]
if (w.groups && w.groups.length > 0) {
const restoredGroups: WizardGroup[] = w.groups.map((g: any, gi: number) => ({
id: `grp_edit_${gi}_${Date.now()}`,
caption: g.caption ?? '',
colCount: g.colCount ?? 2,
items: (g.items ?? []).map(
(it: any, ii: number) =>
({
id: `${it.dataField}_edit_${ii}_${Date.now()}`,
dataField: it.dataField ?? '',
editorType: it.editorType ?? 'dxTextBox',
editorOptions: it.editorOptions ?? '',
editorScript: it.editorScript ?? '',
colSpan: it.colSpan ?? 1,
isRequired: it.isRequired ?? false,
turkishCaption: it.turkishCaption ?? it.dataField,
englishCaption: it.englishCaption ?? it.dataField,
captionName: it.captionName ?? `App.Listform.ListformField.${it.dataField}`,
lookupDataSourceType:
(it.lookupDataSourceType as UiLookupDataSourceTypeEnum) ??
UiLookupDataSourceTypeEnum.StaticData,
valueExpr: it.valueExpr ?? 'Key',
displayExpr: it.displayExpr ?? 'Name',
lookupQuery: it.lookupQuery ?? '',
}) as WizardGroupItem,
),
}))
setEditingGroups(restoredGroups)
// Restore selectedColumns from all group items
const allFields = new Set(restoredGroups.flatMap((g) => g.items.map((i) => i.dataField)))
setSelectedColumns(allFields)
}
} catch {
toast.push(
<Notification type="danger">
{translate('::App.Listforms.WizardFileLoadError') || 'Failed to load wizard file.'}
</Notification>,
{ placement: 'top-end' },
)
} finally {
setIsLoadingEditData(false)
}
}
loadEditData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [editFileName, isLoadingMenu, isLoadingDataSource, isLoadingPermissionGroup])
const navigate = useNavigate() const navigate = useNavigate()
const formikRef = useRef<FormikProps<ListFormWizardDto>>(null) const formikRef = useRef<FormikProps<ListFormWizardDto>>(null)
@ -365,6 +485,11 @@ const Wizard = () => {
const values = formikRef.current.values const values = formikRef.current.values
// Edit modunda: önce eski dosyayı sil (DB kayıtlarını temizler)
if (isEditMode && editFileName) {
await deleteWizardFile(editFileName)
}
// 🔴 Önce kayıt işlemi TAMAMLANSIN // 🔴 Önce kayıt işlemi TAMAMLANSIN
await postListFormWizard({ await postListFormWizard({
...values, ...values,
@ -418,7 +543,7 @@ const Wizard = () => {
<Container> <Container>
<Helmet <Helmet
titleTemplate={`%s | ${APP_NAME}`} titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'App.Listforms.Wizard')} title={translate('::' + (isEditMode ? 'App.Listforms.WizardEdit' : 'App.Listforms.Wizard'))}
defaultTitle={APP_NAME} defaultTitle={APP_NAME}
/> />
@ -435,6 +560,12 @@ const Wizard = () => {
</Steps> </Steps>
</div> </div>
{isLoadingEditData && (
<p className="text-xs text-gray-400 text-center py-4 animate-pulse">
{translate('::App.Platform.Loading') || 'Loading...'}
</p>
)}
<Formik <Formik
innerRef={formikRef} innerRef={formikRef}
initialValues={{ ...initialValues }} initialValues={{ ...initialValues }}
@ -488,7 +619,9 @@ const Wizard = () => {
menuTree={menuTree} menuTree={menuTree}
isLoadingMenu={isLoadingMenu} isLoadingMenu={isLoadingMenu}
onMenuParentChange={handleMenuParentChange} onMenuParentChange={handleMenuParentChange}
onClearMenuParent={() => formikRef.current?.setFieldValue('menuParentCode', '')} onClearMenuParent={() =>
formikRef.current?.setFieldValue('menuParentCode', '')
}
onReloadMenu={getMenuList} onReloadMenu={getMenuList}
permissionGroupList={permissionGroupList} permissionGroupList={permissionGroupList}
isLoadingPermissionGroup={isLoadingPermissionGroup} isLoadingPermissionGroup={isLoadingPermissionGroup}

View file

@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'
import classNames from 'classnames' import classNames from 'classnames'
import { Button, Input, Notification, toast } from '@/components/ui' import { Button, Input, Notification, toast } from '@/components/ui'
import Container from '@/components/shared/Container' import Container from '@/components/shared/Container'
import { WizardFileInfoDto } from '@/proxy/admin/list-form/models' import ConfirmDialog from '@/components/shared/ConfirmDialog'
import { import {
FaTrash, FaTrash,
FaSync, FaSync,
@ -11,12 +11,16 @@ import {
FaPlus, FaPlus,
FaExclamationTriangle, FaExclamationTriangle,
FaSearch, FaSearch,
FaEdit,
} from 'react-icons/fa' } from 'react-icons/fa'
import { FcAcceptDatabase } from 'react-icons/fc'
import { deleteWizardFile, getWizardFiles } from '@/services/wizard.service' import { deleteWizardFile, getWizardFiles } from '@/services/wizard.service'
import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon' import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { useStoreState } from '@/store/store' import { useStoreState } from '@/store/store'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { WizardFileInfoDto } from '@/proxy/admin/wizard/models'
import { UiEvalService } from '@/services/UiEvalService'
// Timestamp formatı: "202605021730" → "2026-05-02 17:30" // Timestamp formatı: "202605021730" → "2026-05-02 17:30"
const formatTimestamp = (raw: string): string => { const formatTimestamp = (raw: string): string => {
@ -45,6 +49,7 @@ const WizardFileManager = () => {
const [deletingFile, setDeletingFile] = useState<string | null>(null) const [deletingFile, setDeletingFile] = useState<string | null>(null)
const [confirm, setConfirm] = useState<ConfirmState | null>(null) const [confirm, setConfirm] = useState<ConfirmState | null>(null)
const [search, setSearch] = useState('') const [search, setSearch] = useState('')
const [showDbMigrateDialog, setShowDbMigrateDialog] = useState(false)
const filteredFiles = useMemo(() => { const filteredFiles = useMemo(() => {
const q = search.trim().toLowerCase() const q = search.trim().toLowerCase()
@ -123,7 +128,7 @@ const WizardFileManager = () => {
<div className="relative"> <div className="relative">
<FaSearch className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 text-xs pointer-events-none" /> <FaSearch className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 text-xs pointer-events-none" />
<Input <Input
size="sm" size="xs"
className="pl-6 w-44" className="pl-6 w-44"
placeholder={translate('::App.Platform.Search') || 'Search...'} placeholder={translate('::App.Platform.Search') || 'Search...'}
value={search} value={search}
@ -131,7 +136,7 @@ const WizardFileManager = () => {
/> />
</div> </div>
<Button <Button
size="sm" size="xs"
variant="default" variant="default"
title={translate('::App.Platform.Refresh') || 'Yenile'} title={translate('::App.Platform.Refresh') || 'Yenile'}
loading={loading} loading={loading}
@ -140,13 +145,22 @@ const WizardFileManager = () => {
<FaSync /> <FaSync />
</Button> </Button>
<Button <Button
size="sm" size="xs"
variant="default"
icon={<FcAcceptDatabase />}
onClick={() => setShowDbMigrateDialog(true)}
title={translate('::App.DbMigrate.StartMessage') || 'Run DB Migration'}
>
{translate('::ListForms.ListForm.DbMigrate') || 'DB Migrate'}
</Button>
<Button
size="xs"
variant="solid" variant="solid"
onClick={() => navigate(ROUTES_ENUM.protected.saas.listFormManagement.wizard)} onClick={() => navigate(ROUTES_ENUM.protected.saas.listFormManagement.wizard)}
className="flex items-center" className="flex items-center"
> >
<FaPlus className="mr-1" /> <FaPlus className="mr-1" />
{translate('::ListForms.ListForm.AddNewRecord') || 'Add New Record'} {translate('::ListForms.Wizard.AddNewRecord') || 'Add New Record'}
</Button> </Button>
</div> </div>
</div> </div>
@ -192,6 +206,20 @@ const WizardFileManager = () => {
<FaExclamationTriangle /> <FaExclamationTriangle />
</span> </span>
)} )}
<Button
size="sm"
variant="plain"
className="text-indigo-500 hover:bg-indigo-50 dark:hover:bg-indigo-900/20"
type="button"
title={translate('::App.Platform.Edit') || 'Edit'}
onClick={() =>
navigate(ROUTES_ENUM.protected.saas.listFormManagement.wizard, {
state: { editFileName: f.fileName },
})
}
>
<FaEdit />
</Button>
<Button <Button
size="sm" size="sm"
variant="plain" variant="plain"
@ -209,7 +237,29 @@ const WizardFileManager = () => {
))} ))}
</div> </div>
{/* Confirm Dialog */} </div>
{/* DB Migrate Confirmation Dialog */}
<ConfirmDialog
isOpen={showDbMigrateDialog}
type="info"
title={translate('::ListForms.ListForm.DbMigrate') || 'DB Migrate'}
cancelText={translate('::Cancel')}
confirmText={translate('::App.Platform.Execute') || 'Çalıştır'}
onCancel={() => setShowDbMigrateDialog(false)}
onClose={() => setShowDbMigrateDialog(false)}
onConfirm={() => {
setShowDbMigrateDialog(false)
UiEvalService.ApiDbMigrate()
}}
>
<p className="text-gray-600 dark:text-gray-400">
{translate('::App.DbMigrate.ConfirmMessage') ||
'Veritabanı migration işlemini başlatmak istediğinize emin misiniz?'}
</p>
</ConfirmDialog>
{/* Delete Confirm Dialog */}
{confirm && ( {confirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-xl p-6 max-w-sm w-full mx-4"> <div className="bg-white dark:bg-gray-900 rounded-xl shadow-xl p-6 max-w-sm w-full mx-4">
@ -239,7 +289,6 @@ const WizardFileManager = () => {
</div> </div>
</div> </div>
)} )}
</div>
</Container> </Container>
) )
} }

View file

@ -1,5 +1,4 @@
import { Button, FormItem, Input, Notification, Select, toast } from '@/components/ui' import { Button, FormItem, Input, Notification, Select, toast } from '@/components/ui'
import { ListFormWizardDto } from '@/proxy/admin/list-form/models'
import { MenuDto } from '@/proxy/menus/models' import { MenuDto } from '@/proxy/menus/models'
import { SelectBoxOption } from '@/types/shared' import { SelectBoxOption } from '@/types/shared'
import navigationIcon from '@/proxy/menus/navigation-icon.config' import navigationIcon from '@/proxy/menus/navigation-icon.config'
@ -20,6 +19,7 @@ import {
} from 'react-icons/fa' } from 'react-icons/fa'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { IconPickerField, MenuAddDialog } from '@/views/shared/MenuAddDialog' import { IconPickerField, MenuAddDialog } from '@/views/shared/MenuAddDialog'
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
// ─── Types (exported for Wizard.tsx) ───────────────────────────────────────── // ─── Types (exported for Wizard.tsx) ─────────────────────────────────────────
@ -256,6 +256,19 @@ function TreeNode({
// ─── MenuTreeInline ─────────────────────────────────────────────────────────── // ─── MenuTreeInline ───────────────────────────────────────────────────────────
/** Returns all ancestor codes of `code` in the flat rawItems list */
function getAncestorCodes(rawItems: MenuItem[], code: string): Set<string> {
const result = new Set<string>()
let current = code
for (let i = 0; i < 50; i++) {
const item = rawItems.find((r) => r.code === current)
if (!item?.parentCode) break
result.add(item.parentCode)
current = item.parentCode
}
return result
}
interface MenuTreeInlineProps { interface MenuTreeInlineProps {
value: string value: string
onChange: (code: string) => void onChange: (code: string) => void
@ -264,6 +277,7 @@ interface MenuTreeInlineProps {
isLoading: boolean isLoading: boolean
invalid?: boolean invalid?: boolean
onReload: () => void onReload: () => void
initialExpanded?: Set<string>
} }
function MenuTreeInline({ function MenuTreeInline({
@ -274,8 +288,21 @@ function MenuTreeInline({
isLoading, isLoading,
invalid, invalid,
onReload, onReload,
initialExpanded,
}: MenuTreeInlineProps) { }: MenuTreeInlineProps) {
const [expanded, setExpanded] = useState<Set<string>>(new Set()) const [expanded, setExpanded] = useState<Set<string>>(() => initialExpanded ?? new Set())
// When value changes (e.g. edit mode loads), expand ancestor nodes
useEffect(() => {
if (!value || rawItems.length === 0) return
const ancestors = getAncestorCodes(rawItems, value)
if (ancestors.size === 0) return
setExpanded((prev) => {
const next = new Set(prev)
ancestors.forEach((c) => next.add(c))
return next
})
}, [value, rawItems])
const [editingCode, setEditingCode] = useState<string | null>(null) const [editingCode, setEditingCode] = useState<string | null>(null)
const [editingValue, setEditingValue] = useState('') const [editingValue, setEditingValue] = useState('')
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
@ -505,6 +532,7 @@ const WizardStep1 = ({
isLoading={isLoadingMenu} isLoading={isLoadingMenu}
invalid={!!(errors.menuParentCode && touched.menuParentCode)} invalid={!!(errors.menuParentCode && touched.menuParentCode)}
onReload={onReloadMenu} onReload={onReloadMenu}
initialExpanded={values.menuParentCode ? getAncestorCodes(rawMenuItems, values.menuParentCode) : undefined}
/> />
)} )}
</Field> </Field>

View file

@ -1,5 +1,4 @@
import { Button, Checkbox, FormItem, Input, Select } from '@/components/ui' import { Button, Checkbox, FormItem, Input, Select } from '@/components/ui'
import { ListFormWizardDto } from '@/proxy/admin/list-form/models'
import { SelectCommandTypeEnum } from '@/proxy/form/models' import { SelectCommandTypeEnum } from '@/proxy/form/models'
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models' import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
import { SelectBoxOption } from '@/types/shared' import { SelectBoxOption } from '@/types/shared'
@ -7,6 +6,7 @@ import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
import CreatableSelect from 'react-select/creatable' import CreatableSelect from 'react-select/creatable'
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa' import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
import { dbSourceTypeOptions, listFormDefaultLayoutOptions, selectCommandTypeOptions, sqlDataTypeToDbType } from '../edit/options' import { dbSourceTypeOptions, listFormDefaultLayoutOptions, selectCommandTypeOptions, sqlDataTypeToDbType } from '../edit/options'
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
// ─── Props ──────────────────────────────────────────────────────────────────── // ─── Props ────────────────────────────────────────────────────────────────────

View file

@ -1,5 +1,4 @@
import { Button } from '@/components/ui' import { Button } from '@/components/ui'
import type { ListFormWizardDto } from '@/proxy/admin/list-form/models'
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models' import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
import { useState } from 'react' import { useState } from 'react'
import { import {
@ -14,6 +13,7 @@ import {
} from 'react-icons/fa' } from 'react-icons/fa'
import { WizardGroup } from './WizardStep3' import { WizardGroup } from './WizardStep3'
import { dbSourceTypeOptions, selectCommandTypeOptions } from '../edit/options' import { dbSourceTypeOptions, selectCommandTypeOptions } from '../edit/options'
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
// ─── Types ──────────────────────────────────────────────────────────────────── // ─── Types ────────────────────────────────────────────────────────────────────

View file

@ -6,7 +6,7 @@ import { getDataSources } from '@/services/data-source.service'
import type { DataSourceDto } from '@/proxy/data-source' import type { DataSourceDto } from '@/proxy/data-source'
import type { SqlQueryExecutionResultDto } from '@/proxy/sql-query-manager/models' import type { SqlQueryExecutionResultDto } from '@/proxy/sql-query-manager/models'
import { sqlObjectManagerService } from '@/services/sql-query-manager.service' import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
import { FaDatabase, FaPlay, FaFileAlt, FaCopy } from 'react-icons/fa' import { FaDatabase, FaPlay, FaFileAlt, FaCopy, FaExclamationTriangle } from 'react-icons/fa'
import { FaCheckCircle } from 'react-icons/fa' import { FaCheckCircle } from 'react-icons/fa'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import SqlObjectExplorer, { type SqlExplorerSelectedObject } from './SqlObjectExplorer' import SqlObjectExplorer, { type SqlExplorerSelectedObject } from './SqlObjectExplorer'
@ -1067,7 +1067,7 @@ GO`,
<Button variant="plain" onClick={handleCancelTemplateReplace}> <Button variant="plain" onClick={handleCancelTemplateReplace}>
{translate('::Cancel')} {translate('::Cancel')}
</Button> </Button>
<Button variant="solid" onClick={handleConfirmTemplateReplace}> <Button variant="solid" onClick={handleConfirmTemplateReplace} icon={<FaExclamationTriangle />} color="red-600">
{translate('::App.Platform.Replace')} {translate('::App.Platform.Replace')}
</Button> </Button>
</div> </div>

View file

@ -31,6 +31,27 @@ const Chart = React.lazy(() => import('./Chart'))
const GanttView = React.lazy(() => import('./GanttView')) const GanttView = React.lazy(() => import('./GanttView'))
const SchedulerView = React.lazy(() => import('./SchedulerView')) const SchedulerView = React.lazy(() => import('./SchedulerView'))
function isLayoutValid(dto: GridDto, layout: ListViewLayoutType | undefined): boolean {
if (!layout || layout === 'grid') return true
if (layout === 'pivot') return !!dto.gridOptions?.layoutDto?.pivot
if (layout === 'chart') return !!dto.gridOptions?.layoutDto?.chart
if (layout === 'tree')
return !!(dto.gridOptions?.layoutDto?.tree && dto.gridOptions?.treeOptionDto?.parentIdExpr)
if (layout === 'gantt')
return !!(
dto.gridOptions?.layoutDto?.gantt &&
dto.gridOptions?.ganttOptionDto?.parentIdExpr &&
dto.gridOptions?.ganttOptionDto?.titleExpr
)
if (layout === 'scheduler')
return !!(
dto.gridOptions?.layoutDto?.scheduler &&
dto.gridOptions?.schedulerOptionDto?.textExpr &&
dto.gridOptions?.schedulerOptionDto?.startDateExpr
)
return false
}
const List: React.FC = () => { const List: React.FC = () => {
const { listFormCode = '' } = useParams() const { listFormCode = '' } = useParams()
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
@ -69,10 +90,10 @@ const List: React.FC = () => {
if (!gridDto) return if (!gridDto) return
const savedLayout = states.find((s) => s.listFormCode === listFormCode)?.layout const savedLayout = states.find((s) => s.listFormCode === listFormCode)?.layout
const defaultLayout = gridDto.gridOptions?.layoutDto?.defaultLayout const defaultLayout = gridDto.gridOptions?.layoutDto?.defaultLayout
const candidate = savedLayout ?? defaultLayout
setViewMode(savedLayout ?? defaultLayout) setViewMode(isLayoutValid(gridDto, candidate) ? candidate : 'grid')
}, [gridDto, states, listFormCode]) }, [gridDto, states, listFormCode])
/* ======================= /* =======================

View file

@ -262,6 +262,14 @@ const useFilters = ({
}) })
} }
if (grdOpt?.columnOptionDto?.columnAutoWidth) {
menus.push({
text: translate('::ListForms.ListForm.FitColumns'),
id: 'fitColumns',
icon: 'columnchooser',
})
}
//Kullanicinin gridstate kaydi yapmasina izin verilmis ise gridState menu elemanlarini ekle //Kullanicinin gridstate kaydi yapmasina izin verilmis ise gridState menu elemanlarini ekle
if (grdOpt?.stateStoringDto?.enabled) { if (grdOpt?.stateStoringDto?.enabled) {
menus.push({ menus.push({
@ -277,14 +285,6 @@ const useFilters = ({
}) })
} }
if (grdOpt?.columnOptionDto?.columnAutoWidth) {
menus.push({
text: translate('::ListForms.ListForm.FitColumns'),
id: 'fitColumns',
icon: 'columnchooser',
})
}
if (checkPermission(gridDto?.gridOptions.permissionDto.i)) { if (checkPermission(gridDto?.gridOptions.permissionDto.i)) {
menus.push({ menus.push({
text: translate('::ListForms.ListForm.ImportManager'), text: translate('::ListForms.ListForm.ImportManager'),