From 4b2fceb404ea4c688904dfaeca9aae8682eede19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Sat, 28 Feb 2026 01:59:50 +0300 Subject: [PATCH] =?UTF-8?q?WizardStep=20g=C3=BCncellemeleri?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Wizard/IListFormWizardAppService.cs | 5 +- .../Wizard/WizardColumnGroupInputDto.cs | 10 + .../Wizard/WizardColumnItemInputDto.cs | 14 + .../ListForms/Wizard/WizardCreateInputDto.cs | 3 + .../ListForms/ListFormWizardAppService.cs | 113 +++++-- .../PlatformConsts.cs | 112 ++++++- ui/src/proxy/admin/list-form/models.ts | 17 + ui/src/views/admin/listForm/Wizard.tsx | 23 +- ui/src/views/admin/listForm/WizardStep1.tsx | 23 +- ui/src/views/admin/listForm/WizardStep2.tsx | 59 ++-- ui/src/views/admin/listForm/WizardStep3.tsx | 36 +- ui/src/views/admin/listForm/WizardStep4.tsx | 210 ++++++------ .../edit/FormTabDatabaseDataSource.tsx | 311 ++++++++++++++---- .../listForm/edit/form-fields/FormFields.tsx | 115 ++++++- ui/src/views/admin/listForm/edit/options.ts | 24 ++ 15 files changed, 833 insertions(+), 242 deletions(-) create mode 100644 api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardColumnGroupInputDto.cs create mode 100644 api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardColumnItemInputDto.cs diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/IListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/IListFormWizardAppService.cs index 0f2ed9b..9176ec7 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/IListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/IListFormWizardAppService.cs @@ -1,6 +1,9 @@ -namespace Sozsoft.Platform.ListForms; +using System.Threading.Tasks; + +namespace Sozsoft.Platform.ListForms; public interface IListFormWizardAppService { + Task Create(WizardCreateInputDto input); } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardColumnGroupInputDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardColumnGroupInputDto.cs new file mode 100644 index 0000000..769cadf --- /dev/null +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardColumnGroupInputDto.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Sozsoft.Platform.ListForms; + +public class WizardColumnGroupInputDto +{ + public string Caption { get; set; } + public int ColCount { get; set; } = 2; + public List Items { get; set; } = new(); +} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardColumnItemInputDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardColumnItemInputDto.cs new file mode 100644 index 0000000..231b527 --- /dev/null +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardColumnItemInputDto.cs @@ -0,0 +1,14 @@ +using System.Data; + +namespace Sozsoft.Platform.ListForms; + +public class WizardColumnItemInputDto +{ + public string DataField { get; set; } + public string EditorType { get; set; } + public string EditorOptions { get; set; } + public string EditorScript { get; set; } + public int ColSpan { get; set; } = 1; + public bool IsRequired { get; set; } + public DbType DbSourceType { get; set; } = DbType.String; +} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardCreateInputDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardCreateInputDto.cs index 755a749..6e2f0bf 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardCreateInputDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardCreateInputDto.cs @@ -1,4 +1,5 @@ using Sozsoft.Platform.Enums; +using System.Collections.Generic; using System.Data; namespace Sozsoft.Platform.ListForms; @@ -27,5 +28,7 @@ public class WizardCreateInputDto public string SelectCommand { get; set; } public string KeyFieldName { get; set; } public DbType KeyFieldDbSourceType { get; set; } + + public List Groups { get; set; } = new(); } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index 62f2e6a..07488d7 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading.Tasks; @@ -12,11 +13,15 @@ using Volo.Abp.Identity; using Volo.Abp.MultiTenancy; using Volo.Abp.PermissionManagement; using Volo.Abp.Uow; +using static Sozsoft.Platform.PlatformConsts; +using System.Data; +using Sozsoft.Platform.Data.Seeds; namespace Sozsoft.Platform.ListForms; public class ListFormWizardAppService( IRepository repoListForm, + IRepository repoListFormField, IRepository repoDataSource, IRepository repoLangKey, IRepository repoLangText, @@ -30,6 +35,7 @@ public class ListFormWizardAppService( ) : PlatformAppService(), IListFormWizardAppService { private readonly IRepository repoListForm = repoListForm; + private readonly IRepository repoListFormField = repoListFormField; private readonly IRepository repoDataSource = repoDataSource; private readonly IRepository repoLangKey = repoLangKey; private readonly IRepository repoLangText = repoLangText; @@ -71,24 +77,30 @@ public class ListFormWizardAppService( var permRead = existingPerms.FirstOrDefault(a => a.Name == code) ?? await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both), autoSave: false); - + var permCreate = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermCreate(listFormCode)) ?? await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermCreate(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyCreate, true, MultiTenancySides.Both), autoSave: false); - + var permUpdate = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermUpdate(listFormCode)) ?? await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermUpdate(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: false); - + var permDelete = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermDelete(listFormCode)) ?? await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermDelete(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyDelete, true, MultiTenancySides.Both), autoSave: false); - + var permExport = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermExport(listFormCode)) ?? await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermExport(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyExport, true, MultiTenancySides.Both), autoSave: false); + var permImport = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermImport(listFormCode)) ?? + await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermImport(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyImport, true, MultiTenancySides.Both), autoSave: false); + + var PermNote = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermNote(listFormCode)) ?? + await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermNote(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyNote, true, MultiTenancySides.Both), autoSave: false); + // Permission Grants - Bulk Insert var adminUserName = PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue; var adminUser = await userRepository.FindByNormalizedUserNameAsync(lookupNormalizer.NormalizeName(adminUserName)); var adminRole = await roleRepository.FindByNormalizedNameAsync(lookupNormalizer.NormalizeName(PlatformConsts.AbpIdentity.User.AdminRoleName)); - + await permissionGrantRepository.InsertManyAsync( [ new PermissionGrant(Guid.NewGuid(), permRead.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName), @@ -111,7 +123,7 @@ public class ListFormWizardAppService( IsDisabled = false, }, autoSave: false); } - + //Menu var menu = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == code)) ?? await repoMenu.InsertAsync(new Menu @@ -128,7 +140,7 @@ public class ListFormWizardAppService( RequiredPermissionName = permRead.Name }, autoSave: false); - //Data Source + //DataSource kodu ile iligli kod blogu var dataSourceQueryable = await repoDataSource.GetQueryableAsync(); var dataSource = await AsyncExecuter.FirstOrDefaultAsync(dataSourceQueryable.Where(a => a.Code == input.DataSourceCode)); if (dataSource is null) @@ -141,37 +153,98 @@ public class ListFormWizardAppService( }, autoSave: false); } + // Build EditingFormJson from wizard groups + var editingFormDtos = input.Groups + .Select((g, gi) => new EditingFormDto + { + Order = gi + 1, + Caption = g.Caption, + ColCount = g.ColCount, + ColSpan = g.ColCount, + ItemType = "group", + Items = g.Items + .Select((it, ii) => new EditingFormItemDto + { + Order = ii + 1, + DataField = it.DataField, + EditorType2 = it.EditorType, + ColSpan = it.ColSpan, + EditorOptions = string.IsNullOrWhiteSpace(it.EditorOptions) ? null : it.EditorOptions, + EditorScript = string.IsNullOrWhiteSpace(it.EditorScript) ? null : it.EditorScript, + IsRequired = it.IsRequired, + }) + .ToArray() + }) + .ToList(); + //ListForm var listForm = await repoListForm.InsertAsync(new ListForm { + ListFormType = ListFormTypeEnum.List, + PageSize = 10, + ExportJson = Wizard.DefaultExportJson, + IsSubForm = false, + ShowNote = true, + LayoutJson = Wizard.DefaultLayoutJson(), + CultureName = LanguageCodes.En, ListFormCode = listFormCode, - DataSourceCode = input.DataSourceCode, Name = nameLangKey, Title = titleLangKey, - CultureName = PlatformConsts.DefaultLanguage, - Description = PlatformConsts.Wizard.WizardKeyDesc(listFormCode), + DataSourceCode = input.DataSourceCode, + IsTenant = false, + IsBranch = false, + IsOrganizationUnit = false, + Description = Wizard.WizardKeyDesc(listFormCode), SelectCommandType = input.SelectCommandType, SelectCommand = input.SelectCommand, KeyFieldName = input.KeyFieldName, KeyFieldDbSourceType = input.KeyFieldDbSourceType, - PermissionJson = JsonSerializer.Serialize(new PermissionCrudDto - { - C = permCreate.Name, - R = permRead.Name, - U = permUpdate.Name, - D = permDelete.Name - }), + DefaultFilter = Wizard.DefaultFilterJson, + SortMode = GridOptions.SortModeSingle, + FilterRowJson = Wizard.DefaultFilterRowJson, + HeaderFilterJson = Wizard.DefaultHeaderFilterJson, + SearchPanelJson = Wizard.DefaultSearchPanelJson, + GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }), + SelectionJson = Wizard.DefaultSelectionSingleJson, + ColumnOptionJson = Wizard.DefaultColumnOptionJson(), + PermissionJson = Wizard.DefaultPermissionJson(listFormCode), + DeleteCommand = Wizard.DefaultDeleteCommand(nameof(TableNameEnum.Country)), + DeleteFieldsDefaultValueJson = Wizard.DefaultDeleteFieldsDefaultValueJson(), + PagerOptionJson = Wizard.DefaultPagerOptionJson, + EditingOptionJson = Wizard.DefaultEditingOptionJson(listFormCode, 600, 500, true, true, true, true, false), + EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null, }, autoSave: true); + + // ListFormField - each item in each group becomes a visible field record + var fieldOrder = 0; + foreach (var group in input.Groups) + { + foreach (var item in group.Items) + { + fieldOrder++; + await repoListFormField.InsertAsync(new ListFormField + { + ListFormCode = listFormCode, + FieldName = item.DataField, + CaptionName = item.DataField, + Visible = true, + IsActive = true, + ListOrderNo = fieldOrder, + SourceDbType = item.DbSourceType, + CultureName = PlatformConsts.DefaultLanguage, + }, autoSave: false); + } + } } private async Task CreateLangKey(string key, string textEn, string textTr) { var res = PlatformConsts.AppName; - + var keyQueryable = await repoLangKey.GetQueryableAsync(); var langKey = await AsyncExecuter.FirstOrDefaultAsync(keyQueryable.Where(a => a.ResourceName == res && a.Key == key)) ?? await repoLangKey.InsertAsync(new LanguageKey { ResourceName = res, Key = key }, autoSave: false); - + var textQueryable = await repoLangText.GetQueryableAsync(); var existingTexts = await AsyncExecuter.ToListAsync( textQueryable.Where(a => a.ResourceName == res && a.Key == langKey.Key) @@ -179,7 +252,7 @@ public class ListFormWizardAppService( var langTextEn = existingTexts.FirstOrDefault(a => a.CultureName == cultureNameDefault) ?? await repoLangText.InsertAsync(new LanguageText { ResourceName = res, Key = langKey.Key, CultureName = cultureNameDefault, Value = textEn }, autoSave: false); - + var langTextTr = existingTexts.FirstOrDefault(a => a.CultureName == LanguageCodes.Tr) ?? await repoLangText.InsertAsync(new LanguageText { ResourceName = res, Key = langKey.Key, CultureName = LanguageCodes.Tr, Value = textTr }, autoSave: false); diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs index aa061a2..94510b0 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Data; +using System.Text.Json; using Sozsoft.Languages.Languages; using Sozsoft.Platform.Enums; using Volo.Abp.Reflection; @@ -414,14 +416,122 @@ public static class PlatformConsts public static string PermUpdate(string code) => $"{WizardKey(code)}.Update"; public static string PermDelete(string code) => $"{WizardKey(code)}.Delete"; public static string PermExport(string code) => $"{WizardKey(code)}.Export"; + public static string PermImport(string code) => $"{WizardKey(code)}.Import"; + public static string PermNote(string code) => $"{WizardKey(code)}.Note"; public static string LangKeyCreate => $"{Prefix.App}.Create"; public static string LangKeyUpdate => $"{Prefix.App}.Update"; public static string LangKeyDelete => $"{Prefix.App}.Delete"; public static string LangKeyExport => $"{Prefix.App}.Export"; + public static string LangKeyImport => $"{Prefix.App}.Import"; + public static string LangKeyNote => $"{Prefix.App}.Note"; - public static string MenuUrl(string code) => $"/list/{code}"; + public static string MenuUrl(string code) => $"/admin/list/{code}"; public static string MenuIcon => "FcList"; + public static readonly string DefaultExportJson = JsonSerializer.Serialize(new + { + Enabled = true, + AllowExportSelectedData = false, + PrintingEnabled = true, + BackgroundColor = "#FFFFFF", + Margin = 10 + }); + + public static string DefaultLayoutJson(string DefaultLayout = "grid") => JsonSerializer.Serialize(new + { + Grid = true, + Pivot = true, + Chart = true, + Tree = true, + Gantt = true, + Scheduler = true, + DefaultLayout = DefaultLayout, + }); + + public static readonly string DefaultFilterJson = "\"IsDeleted\" = 'false'"; + public static readonly string DefaultFilterRowJson = JsonSerializer.Serialize(new { Visible = true }); + public static readonly string DefaultHeaderFilterJson = JsonSerializer.Serialize(new { Visible = true }); + public static readonly string DefaultSearchPanelJson = JsonSerializer.Serialize(new { Visible = true }); + public static readonly string DefaultGroupPanelJson = JsonSerializer.Serialize(new { Visible = true }); + + public static readonly string DefaultSelectionSingleJson = JsonSerializer.Serialize(new + { + Mode = GridOptions.SelectionModeNone, + AllowSelectAll = false + }); + + public static string DefaultColumnOptionJson(bool FocusedRowEnabled = true) => JsonSerializer.Serialize(new + { + ColumnFixingEnabled = true, + ColumnAutoWidth = true, + ColumnChooserEnabled = true, + AllowColumnResizing = true, + AllowColumnReordering = true, + ColumnResizingMode = "widget", + FocusedRowEnabled = FocusedRowEnabled, + }); + + public static string DefaultPermissionJson(string permissionName) + { + return JsonSerializer.Serialize(new + { + C = permissionName + ".Create", + R = permissionName, + U = permissionName + ".Update", + D = permissionName + ".Delete", + E = permissionName + ".Export", + I = permissionName + ".Import", + N = permissionName + ".Note", + }); + } + + public static string DefaultDeleteCommand(string tableName) + { + return $"UPDATE \"{TableNameResolver.GetFullTableName(tableName)}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id"; + } + + public static string DefaultDeleteFieldsDefaultValueJson(DbType dbType = DbType.Guid) + { + return JsonSerializer.Serialize(new[] + { + new { FieldName = "DeleterId", FieldDbType = DbType.Guid.ToString(), Value = "@USERID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }, + new { FieldName = "Id", FieldDbType = dbType.ToString(), Value = "@ID", CustomValueType = FieldCustomValueTypeEnum.CustomKey } + }); + } + + public static readonly string DefaultPagerOptionJson = JsonSerializer.Serialize(new + { + Visible = true, + AllowedPageSizes = "10,20,50,100", + ShowPageSizeSelector = true, + ShowNavigationButtons = true, + ShowInfo = false, + InfoText = "Page {0} of {1} ({2} items)", + DisplayMode = GridColumnOptions.PagerDisplayModeAdaptive, + ScrollingMode = GridColumnOptions.ScrollingModeStandard, + LoadPanelEnabled = "auto", + LoadPanelText = "Loading..." + }); + + public static string DefaultEditingOptionJson( + string Title, + int Width, + int Height, + bool AllowDeleting, + bool AllowAdding, + bool AllowEditing, + bool ConfirmDelete, + bool SendOnlyChangedFormValuesUpdate, + bool AllowDetail = false) => JsonSerializer.Serialize(new + { + Popup = new { Title = Title, Width = Width, Height = Height }, + AllowDeleting = AllowDeleting, + AllowAdding = AllowAdding, + AllowEditing = AllowEditing, + ConfirmDelete = ConfirmDelete, + SendOnlyChangedFormValuesUpdate = SendOnlyChangedFormValuesUpdate, + AllowDetail = AllowDetail + }); } public static class AppErrorCodes diff --git a/ui/src/proxy/admin/list-form/models.ts b/ui/src/proxy/admin/list-form/models.ts index b1b109f..5cac877 100644 --- a/ui/src/proxy/admin/list-form/models.ts +++ b/ui/src/proxy/admin/list-form/models.ts @@ -15,6 +15,22 @@ import { ChartValueAxisDto, } 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 { listFormCode: string menuCode: string @@ -35,6 +51,7 @@ export interface ListFormWizardDto { selectCommand: string keyFieldName: string keyFieldDbSourceType: number + groups?: ListFormWizardColumnGroupDto[] } export interface ListFormJsonRowDto { diff --git a/ui/src/views/admin/listForm/Wizard.tsx b/ui/src/views/admin/listForm/Wizard.tsx index 6f1b554..68c7b64 100644 --- a/ui/src/views/admin/listForm/Wizard.tsx +++ b/ui/src/views/admin/listForm/Wizard.tsx @@ -23,10 +23,11 @@ import WizardStep1, { filterNonLinkNodes, findRootCode, } from './WizardStep1' -import WizardStep2, { sqlDataTypeToDbType } from './WizardStep2' +import WizardStep2 from './WizardStep2' import WizardStep3, { WizardGroup } from './WizardStep3' import WizardStep4 from './WizardStep4' import { Container } from '@/components/shared' +import { sqlDataTypeToDbType } from './edit/options' // ─── Formik initial values & validation ────────────────────────────────────── const initialValues: ListFormWizardDto = { @@ -296,7 +297,25 @@ const Wizard = () => { const handleDeploy = async () => { if (!formikRef.current) throw new Error('Form bulunamadı') const values = formikRef.current.values - await postListFormWizard({ ...values }) + await postListFormWizard({ + ...values, + groups: editingGroups.map((g) => ({ + caption: g.caption, + colCount: g.colCount, + items: g.items.map((item) => { + const col = selectCommandColumns.find((c) => c.columnName === item.dataField) + return { + dataField: item.dataField, + editorType: item.editorType, + editorOptions: item.editorOptions ?? '', + editorScript: item.editorScript ?? '', + colSpan: item.colSpan, + isRequired: item.isRequired, + dbSourceType: col ? sqlDataTypeToDbType(col.dataType) : 12, // 12 = DbType.String + } + }), + })), + }) toast.push( {translate('::ListForms.FormBilgileriKaydedildi')} diff --git a/ui/src/views/admin/listForm/WizardStep1.tsx b/ui/src/views/admin/listForm/WizardStep1.tsx index 69ae73a..ca3c3ce 100644 --- a/ui/src/views/admin/listForm/WizardStep1.tsx +++ b/ui/src/views/admin/listForm/WizardStep1.tsx @@ -9,6 +9,7 @@ import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik' import { useEffect, useRef, useState } from 'react' import CreatableSelect from 'react-select/creatable' import { + FaArrowRight, FaChevronDown, FaChevronRight, FaEdit, @@ -670,6 +671,15 @@ const WizardStep1 = ({ const [menuDialogParentCode, setMenuDialogParentCode] = useState('') const [menuDialogInitialOrder, setMenuDialogInitialOrder] = useState(999) + const step1Missing = [ + !wizardName && 'Wizard Adı', + !values.menuParentCode && 'Menu Parent', + !values.permissionGroupName && 'İzin Grubu', + !values.languageTextMenuEn && 'Menü (EN)', + !values.languageTextMenuTr && 'Menü (TR)', + ].filter(Boolean) as string[] + const step1CanGo = step1Missing.length === 0 + return (
{/* Wizard Name */} @@ -848,9 +858,16 @@ const WizardStep1 = ({
{/* ─── Fixed Footer ─────────────────────────────── */} -
-
-
diff --git a/ui/src/views/admin/listForm/WizardStep2.tsx b/ui/src/views/admin/listForm/WizardStep2.tsx index 3517d7e..8d41b63 100644 --- a/ui/src/views/admin/listForm/WizardStep2.tsx +++ b/ui/src/views/admin/listForm/WizardStep2.tsx @@ -1,37 +1,12 @@ import { Button, FormItem, Input, Select } from '@/components/ui' import { ListFormWizardDto } from '@/proxy/admin/list-form/models' -import { DbTypeEnum, SelectCommandTypeEnum } from '@/proxy/form/models' +import { SelectCommandTypeEnum } from '@/proxy/form/models' import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models' import { SelectBoxOption } from '@/types/shared' -import { dbSourceTypeOptions, selectCommandTypeOptions } from './edit/options' +import { dbSourceTypeOptions, selectCommandTypeOptions, sqlDataTypeToDbType } from './edit/options' import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik' import CreatableSelect from 'react-select/creatable' - -// ─── SQL dataType → DbTypeEnum mapper ──────────────────────────────────────── - -export function sqlDataTypeToDbType(sqlType: string): DbTypeEnum { - const t = sqlType - .toLowerCase() - .replace(/\s*\(.*\)/, '') - .trim() - if (['int', 'integer', 'int32'].includes(t)) return DbTypeEnum.Int32 - if (['bigint', 'int64'].includes(t)) return DbTypeEnum.Int64 - if (['smallint', 'int16'].includes(t)) return DbTypeEnum.Int16 - if (['tinyint', 'byte'].includes(t)) return DbTypeEnum.Byte - if (['bit', 'boolean', 'bool'].includes(t)) return DbTypeEnum.Boolean - if (['float', 'real', 'double', 'double precision'].includes(t)) return DbTypeEnum.Double - if (['decimal', 'numeric', 'money', 'smallmoney'].includes(t)) return DbTypeEnum.Decimal - if (['uniqueidentifier'].includes(t)) return DbTypeEnum.Guid - if (['datetime2', 'smalldatetime', 'datetime'].includes(t)) return DbTypeEnum.DateTime - if (['date'].includes(t)) return DbTypeEnum.Date - if (['time'].includes(t)) return DbTypeEnum.Time - if (['datetimeoffset'].includes(t)) return DbTypeEnum.DateTimeOffset - if (['nvarchar', 'varchar', 'char', 'nchar', 'text', 'ntext', 'string'].includes(t)) - return DbTypeEnum.String - if (['xml'].includes(t)) return DbTypeEnum.Xml - if (['binary', 'varbinary', 'image'].includes(t)) return DbTypeEnum.Binary - return DbTypeEnum.String -} +import { FaArrowLeft, FaArrowRight } from 'react-icons/fa' // ─── Props ──────────────────────────────────────────────────────────────────── @@ -86,6 +61,15 @@ const WizardStep2 = ({ onBack, onNext, }: WizardStep2Props) => { + const step2Missing = [ + !values.listFormCode && 'ListForm Code', + !values.dataSourceCode && 'Veri Kaynağı', + !values.selectCommand && 'Select Command', + !values.keyFieldName && 'Key Field', + selectedColumns.size === 0 && 'Sütun seçimi', + ].filter(Boolean) as string[] + const step2CanGo = step2Missing.length === 0 + return (
{/* ListForm Code + Data Source */} @@ -445,14 +429,21 @@ const WizardStep2 = ({ {/* ─── Fixed Footer ─────────────────────────────── */} -
-
- - +
+ {!step2CanGo && ( + + ⚠ Zorunlu: {step2Missing.join(', ')} + + )} + +
diff --git a/ui/src/views/admin/listForm/WizardStep3.tsx b/ui/src/views/admin/listForm/WizardStep3.tsx index 9437c65..baa547f 100644 --- a/ui/src/views/admin/listForm/WizardStep3.tsx +++ b/ui/src/views/admin/listForm/WizardStep3.tsx @@ -15,7 +15,7 @@ import { import { SortableContext, arrayMove, useSortable, rectSortingStrategy } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import { useEffect, useState } from 'react' -import { FaGripVertical, FaPlus, FaTimes, FaTrash, FaArrowRight, FaCode } from 'react-icons/fa' +import { FaArrowLeft, FaGripVertical, FaPlus, FaTimes, FaTrash, FaArrowRight, FaCode } from 'react-icons/fa' // ─── Types ──────────────────────────────────────────────────────────────────── export interface WizardGroupItem { @@ -612,6 +612,15 @@ const WizardStep3 = ({ return null } + const hasNoGroups = groups.length === 0 + const hasEmptyGroup = groups.some((g) => g.items.length === 0) + const canProceed = !hasNoGroups && !hasEmptyGroup + const validationMsg = hasNoGroups + ? 'En az bir grup eklemelisiniz.' + : hasEmptyGroup + ? 'Her gruba en az bir sütun eklemelisiniz.' + : '' + return ( {/* ── Fixed Footer ─────────────────────────────────────────────────── */} -
-
- - - +
+ {!canProceed && ( + ⚠ {validationMsg} + )} + +
diff --git a/ui/src/views/admin/listForm/WizardStep4.tsx b/ui/src/views/admin/listForm/WizardStep4.tsx index ed00066..6585210 100644 --- a/ui/src/views/admin/listForm/WizardStep4.tsx +++ b/ui/src/views/admin/listForm/WizardStep4.tsx @@ -4,6 +4,7 @@ import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models' import type { WizardGroup } from './WizardStep3' import { useState } from 'react' import { + FaArrowLeft, FaCheckCircle, FaChevronDown, FaChevronRight, @@ -12,6 +13,7 @@ import { FaRocket, FaSpinner, } from 'react-icons/fa' +import { selectCommandTypeOptions } from './edit/options' // ─── Types ──────────────────────────────────────────────────────────────────── @@ -37,15 +39,30 @@ interface LogEntry { // ─── Deploy log steps ───────────────────────────────────────────────────────── -function buildLogSteps(values: ListFormWizardDto, groups: WizardGroup[]): Omit[] { +function buildLogSteps( + values: ListFormWizardDto, + groups: WizardGroup[], +): Omit[] { const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0) return [ { id: 1, label: 'Konfigürasyon doğrulanıyor…' }, - { id: 2, label: `Menü oluşturuluyor: ${values.menuCode}`, detail: `Parent: ${values.menuParentCode}` }, - { id: 3, label: 'Dil metinleri kaydediliyor', detail: `EN: ${values.languageTextMenuEn} / TR: ${values.languageTextMenuTr}` }, + { + id: 2, + label: `Menü oluşturuluyor: ${values.menuCode}`, + detail: `Parent: ${values.menuParentCode}`, + }, + { + id: 3, + label: 'Dil metinleri kaydediliyor', + detail: `EN: ${values.languageTextMenuEn} / TR: ${values.languageTextMenuTr}`, + }, { id: 4, label: `İzin grubu yapılandırılıyor: ${values.permissionGroupName}` }, { id: 5, label: `Veri kaynağı bağlanıyor: ${values.dataSourceCode}` }, - { id: 6, label: `ListForm oluşturuluyor: ${values.listFormCode}`, detail: `Key: ${values.keyFieldName}` }, + { + id: 6, + label: `ListForm oluşturuluyor: ${values.listFormCode}`, + detail: `Key: ${values.keyFieldName}`, + }, { id: 7, label: `Form grupları kaydediliyor (${groups.length} grup, ${totalFields} alan)` }, { id: 8, label: 'Sunucuya deploy ediliyor…' }, { id: 9, label: 'Tamamlandı ✓' }, @@ -101,13 +118,14 @@ function Row({ label, value }: { label: string; value?: string | number }) { return (
{label} - {value} + + {value} +
) } // ─── WizardStep4 ────────────────────────────────────────────────────────────── - const WizardStep4 = ({ values, wizardName, @@ -179,42 +197,43 @@ const WizardStep4 = ({ const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0) return ( -
- {/* ── Left: Summary ────────────────────────────────────────────── */} -
-
- Özet -
+
+ {/* ── Left: Summary ──────────────────────────────────────────── */} +
+
+
+ + + + + + + + + +
- {/* Step 1 Summary */} -
- - - - - - - - - -
+
+ + + + + + + + o.value === values.selectCommandType)?.label || + values.selectCommandType + } + /> + + + +
+
- {/* Step 2 Summary */} -
- - - - - - - - - - - -
- - {/* Columns */}
{[...selectedColumns].map((col) => { @@ -226,7 +245,7 @@ const WizardStep4 = ({ > {col} {meta?.dataType && ( - {meta.dataType} + {meta.dataType} )} ) @@ -234,11 +253,15 @@ const WizardStep4 = ({
- {/* Step 3 – Groups */} -
+
{groups.map((g) => ( -
+
{g.items.length === 0 ? ( Alan yok @@ -254,7 +277,7 @@ const WizardStep4 = ({ {item.editorType} - + span:{item.colSpan} {item.isRequired && ( * @@ -270,9 +293,9 @@ const WizardStep4 = ({
- {/* ── Right: Deploy ────────────────────────────────────────────── */} -
- {/* Stats bar */} + {/* ── Right: Deploy ──────────────────────────────────────────── */} +
+ {/* Stats */}
{[ { label: 'Grup', value: groups.length }, @@ -281,18 +304,23 @@ const WizardStep4 = ({ ].map((s) => (
-
{s.value}
+
+ {s.value} +
{s.label}
))}
- {/* Log panel */} -
-
- Deploy Log + {/* Log panel — grows to fill remaining height */} +
+
+ + + Deploy Log + {isDone && ( Başarılı @@ -305,26 +333,33 @@ const WizardStep4 = ({ )}
-
+
{logs.length === 0 ? ( -
- Deploy başlatmak için butona tıklayın +
+ + + Tüm bilgiler hazır. +
+ Deploy başlatmak için butona tıklayın. +
) : ( -
+
{logs.map((log) => ( -
- +
+ + +
{log.label} @@ -335,44 +370,35 @@ const WizardStep4 = ({
))} + {isDone && ( +
+ 🎉 ListForm başarıyla oluşturuldu ve deploy edildi! +
+ )}
)}
- - {isDone && ( -
- 🎉 ListForm başarıyla oluşturuldu ve deploy edildi! -
- )}
{/* ── Fixed Footer ─────────────────────────────────────────────── */} -
-
- - +
+ +
diff --git a/ui/src/views/admin/listForm/edit/FormTabDatabaseDataSource.tsx b/ui/src/views/admin/listForm/edit/FormTabDatabaseDataSource.tsx index 08b344c..7c1704f 100644 --- a/ui/src/views/admin/listForm/edit/FormTabDatabaseDataSource.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabDatabaseDataSource.tsx @@ -8,9 +8,12 @@ import { Field, FieldProps, Form, Formik } from 'formik' import { useEffect, useState } from 'react' import * as Yup from 'yup' import { FormEditProps } from './FormEdit' -import { dbSourceTypeOptions, selectCommandTypeOptions } from './options' +import { dbSourceTypeOptions, selectCommandTypeOptions, sqlDataTypeToDbType } from './options' import { DataSourceTypeEnum, SelectCommandTypeEnum } from '@/proxy/form/models' import { getDataSources } from '@/services/data-source.service' +import { sqlObjectManagerService } from '@/services/sql-query-manager.service' +import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models' +import CreatableSelect from 'react-select/creatable' const schema = Yup.object().shape({ isOrganizationUnit: Yup.bool(), @@ -25,6 +28,10 @@ const schema = Yup.object().shape({ }) function FormTabDatabaseDataSource(props: FormEditProps) { + const initialValues = useStoreState((s) => s.admin.lists.values) + const { translate } = useLocalization() + + // ── Data Source List ──────────────────────────────────────────────────────── const [dataSourceList, setDataSourceList] = useState([]) const getDataSourceList = async () => { const response = await getDataSources() @@ -41,11 +48,74 @@ function FormTabDatabaseDataSource(props: FormEditProps) { getDataSourceList() }, []) - const initialValues = useStoreState((s) => s.admin.lists.values) + // ── DB Objects ────────────────────────────────────────────────────────────── + const [dbObjects, setDbObjects] = useState(null) + const [isLoadingDbObjects, setIsLoadingDbObjects] = useState(false) + // Mevcut kayıttaki dataSourceCode ile başlat + const [currentDataSource, setCurrentDataSource] = useState(initialValues?.dataSourceCode ?? '') + + const loadDbObjects = async (dsCode: string) => { + if (!dsCode) { + setDbObjects(null) + return + } + setIsLoadingDbObjects(true) + try { + const res = await sqlObjectManagerService.getAllObjects(dsCode) + setDbObjects(res.data) + } catch { + setDbObjects(null) + } finally { + setIsLoadingDbObjects(false) + } + } + + useEffect(() => { + loadDbObjects(currentDataSource) + }, [currentDataSource]) + + // ── Columns ───────────────────────────────────────────────────────────────── + const [selectCommandColumns, setSelectCommandColumns] = useState([]) + const [isLoadingColumns, setIsLoadingColumns] = useState(false) + + const loadColumns = async (dsCode: string, schema: string, name: string) => { + if (!dsCode || !name) { + setSelectCommandColumns([]) + return + } + setIsLoadingColumns(true) + try { + const res = await sqlObjectManagerService.getTableColumns(dsCode, schema, name) + setSelectCommandColumns(res.data ?? []) + } catch { + setSelectCommandColumns([]) + } finally { + setIsLoadingColumns(false) + } + } + + // Mevcut selectCommand'a göre sütunları otomatik yükle (dbObjects hazır olunca) + useEffect(() => { + const dsCode = initialValues?.dataSourceCode + const cmd = initialValues?.selectCommand + if (!dbObjects || !dsCode || !cmd) return + // Daha önce yüklenmiş sütun varsa tekrar yükleme + if (selectCommandColumns.length > 0) return + + const table = dbObjects.tables.find((t) => t.tableName === cmd) + if (table) { loadColumns(dsCode, table.schemaName, table.tableName); return } + const view = dbObjects.views.find((v) => v.viewName === cmd) + if (view) { loadColumns(dsCode, view.schemaName, view.viewName); return } + const fn = dbObjects.functions.find((f) => f.functionName === cmd) + if (fn) { loadColumns(dsCode, fn.schemaName, fn.functionName); return } + const sp = dbObjects.storedProcedures.find((p) => p.procedureName === cmd) + if (sp) { loadColumns(dsCode, sp.schemaName, sp.procedureName); return } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dbObjects]) + if (!initialValues) { return null } - const { translate } = useLocalization() return ( @@ -116,34 +186,17 @@ function FormTabDatabaseDataSource(props: FormEditProps) { value={dataSourceList?.filter( (option) => option.value === values.dataSourceCode, )} - onChange={(option) => form.setFieldValue(field.name, option?.value)} - /> - )} - - - - - {({ field, form }: FieldProps) => ( - { + if (!option) { + form.setFieldValue(field.name, '') + form.setFieldValue('selectCommandType', undefined) + form.setFieldValue('keyFieldName', '') + form.setFieldValue('keyFieldDbSourceType', undefined) + setSelectCommandColumns([]) + return + } + form.setFieldValue(field.name, option.value) + const type = option.__isNew__ + ? SelectCommandTypeEnum.Query + : (option.__type ?? SelectCommandTypeEnum.Query) + form.setFieldValue('selectCommandType', type) + form.setFieldValue('keyFieldName', '') + form.setFieldValue('keyFieldDbSourceType', undefined) + if (!option.__isNew__ && option.__schema != null && option.__rawName) { + loadColumns(values.dataSourceCode ?? '', option.__schema, option.__rawName) + } else { + setSelectCommandColumns([]) + } + }} + onCreateOption={(inputValue: string) => { + form.setFieldValue(field.name, inputValue) + form.setFieldValue('selectCommandType', SelectCommandTypeEnum.Query) + form.setFieldValue('keyFieldName', '') + form.setFieldValue('keyFieldDbSourceType', undefined) + setSelectCommandColumns([]) + }} + /> + ) + }} + - + + {dbSourceTypeOptions.find( + (o: any) => o.value === values.keyFieldDbSourceType, + )?.label ?? String(values.keyFieldDbSourceType)} + + ) : selectCommandColumns.length === 0 && !isLoadingColumns ? ( + + {translate('::ListForms.ListFormEdit.DatabaseDataSourceSelectCommand') + + ' seçince sütunlar yüklenir'} + + ) : null + } > - - - - - {({ field, form }: FieldProps) => ( + + {({ field, form }: FieldProps) => (