WizardStep güncellemeleri
This commit is contained in:
parent
f2652dbb44
commit
4b2fceb404
15 changed files with 833 additions and 242 deletions
|
|
@ -1,6 +1,9 @@
|
|||
namespace Sozsoft.Platform.ListForms;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sozsoft.Platform.ListForms;
|
||||
|
||||
public interface IListFormWizardAppService
|
||||
{
|
||||
Task Create(WizardCreateInputDto input);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<WizardColumnItemInputDto> Items { get; set; } = new();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<WizardColumnGroupInputDto> Groups { get; set; } = new();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ListForm, Guid> repoListForm,
|
||||
IRepository<ListFormField, Guid> repoListFormField,
|
||||
IRepository<DataSource, Guid> repoDataSource,
|
||||
IRepository<LanguageKey, Guid> repoLangKey,
|
||||
IRepository<LanguageText, Guid> repoLangText,
|
||||
|
|
@ -30,6 +35,7 @@ public class ListFormWizardAppService(
|
|||
) : PlatformAppService(), IListFormWizardAppService
|
||||
{
|
||||
private readonly IRepository<ListForm, Guid> repoListForm = repoListForm;
|
||||
private readonly IRepository<ListFormField, Guid> repoListFormField = repoListFormField;
|
||||
private readonly IRepository<DataSource, Guid> repoDataSource = repoDataSource;
|
||||
private readonly IRepository<LanguageKey, Guid> repoLangKey = repoLangKey;
|
||||
private readonly IRepository<LanguageText, Guid> 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<LanguageKey> 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<Notification type="success" duration={2000}>
|
||||
{translate('::ListForms.FormBilgileriKaydedildi')}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="pb-20">
|
||||
{/* Wizard Name */}
|
||||
|
|
@ -848,9 +858,16 @@ const WizardStep1 = ({
|
|||
</div>
|
||||
|
||||
{/* ─── Fixed Footer ─────────────────────────────── */}
|
||||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-3">
|
||||
<div className="max-w-sm mx-auto">
|
||||
<Button block variant="solid" type="button" onClick={onNext}>
|
||||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-0 h-16 flex items-center">
|
||||
<div className="flex items-center gap-3 w-full">
|
||||
<div className="flex-1 flex items-center gap-2">
|
||||
{!step1CanGo && (
|
||||
<span className="text-xs text-amber-600 dark:text-amber-400 font-medium">
|
||||
⚠ Zorunlu: {step1Missing.join(', ')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="solid" type="button" icon={<FaArrowRight />} disabled={!step1CanGo} onClick={onNext}>
|
||||
{translate('::Next') || 'Next'}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="pb-20">
|
||||
{/* ListForm Code + Data Source */}
|
||||
|
|
@ -445,14 +429,21 @@ const WizardStep2 = ({
|
|||
</FormItem>
|
||||
|
||||
{/* ─── Fixed Footer ─────────────────────────────── */}
|
||||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-3">
|
||||
<div className="max-w-sm mx-auto flex gap-3">
|
||||
<Button block variant="default" type="button" onClick={onBack}>
|
||||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-0 h-16 flex items-center">
|
||||
<div className="flex items-center gap-3 w-full">
|
||||
<Button variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
|
||||
{translate('::Back') || 'Back'}
|
||||
</Button>
|
||||
<Button block variant="solid" type="button" onClick={onNext}>
|
||||
{translate('::Next') || 'Next'}
|
||||
</Button>
|
||||
<div className="flex-1 flex items-center justify-end gap-3">
|
||||
{!step2CanGo && (
|
||||
<span className="text-xs text-amber-600 dark:text-amber-400 font-medium">
|
||||
⚠ Zorunlu: {step2Missing.join(', ')}
|
||||
</span>
|
||||
)}
|
||||
<Button variant="solid" type="button" icon={<FaArrowRight />} disabled={!step2CanGo} onClick={onNext}>
|
||||
{translate('::Next') || 'Next'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
|
|
@ -740,23 +749,22 @@ const WizardStep3 = ({
|
|||
</Dialog>
|
||||
|
||||
{/* ── Fixed Footer ─────────────────────────────────────────────────── */}
|
||||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-3">
|
||||
<div className="max-w-sm mx-auto flex gap-3">
|
||||
<Button
|
||||
block
|
||||
variant="default"
|
||||
type="button"
|
||||
onClick={() => setIsHelperOpen(true)}
|
||||
title="Helper Codes"
|
||||
>
|
||||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-0 h-16 flex items-center">
|
||||
<div className="flex items-center gap-3 w-full">
|
||||
<Button variant="default" type="button" icon={<FaCode />} onClick={() => setIsHelperOpen(true)}>
|
||||
{translate('::Helper Codes') || 'Helper Codes'}
|
||||
</Button>
|
||||
<Button block variant="default" type="button" onClick={onBack}>
|
||||
<Button variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
|
||||
{translate('::Back') || 'Back'}
|
||||
</Button>
|
||||
<Button block variant="solid" type="button" onClick={onNext}>
|
||||
{translate('::Next') || 'Next'}
|
||||
</Button>
|
||||
<div className="flex-1 flex items-center justify-end gap-3">
|
||||
{!canProceed && (
|
||||
<span className="text-xs text-amber-600 dark:text-amber-400 font-medium">⚠ {validationMsg}</span>
|
||||
)}
|
||||
<Button variant="solid" type="button" icon={<FaArrowRight />} disabled={!canProceed} onClick={onNext}>
|
||||
{translate('::Next') || 'Next'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DndContext>
|
||||
|
|
|
|||
|
|
@ -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<LogEntry, 'status'>[] {
|
||||
function buildLogSteps(
|
||||
values: ListFormWizardDto,
|
||||
groups: WizardGroup[],
|
||||
): Omit<LogEntry, 'status'>[] {
|
||||
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 (
|
||||
<div className="flex gap-2 py-1 border-b border-gray-100 dark:border-gray-800 last:border-0">
|
||||
<span className="text-xs text-gray-400 w-40 shrink-0">{label}</span>
|
||||
<span className="text-xs text-gray-700 dark:text-gray-200 font-medium break-all">{value}</span>
|
||||
<span className="text-xs text-gray-700 dark:text-gray-200 font-medium break-all">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── WizardStep4 ──────────────────────────────────────────────────────────────
|
||||
|
||||
const WizardStep4 = ({
|
||||
values,
|
||||
wizardName,
|
||||
|
|
@ -179,42 +197,43 @@ const WizardStep4 = ({
|
|||
const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0)
|
||||
|
||||
return (
|
||||
<div className="flex gap-6 pb-24">
|
||||
{/* ── Left: Summary ────────────────────────────────────────────── */}
|
||||
<div className="flex-1 flex flex-col gap-3 overflow-y-auto max-h-[calc(100vh-220px)]">
|
||||
<h6 className="text-sm font-bold text-gray-600 dark:text-gray-300 uppercase tracking-wide mb-1">
|
||||
Özet
|
||||
</h6>
|
||||
<div className="grid grid-cols-[3fr_2fr] gap-5 pb-24 items-start">
|
||||
{/* ── Left: Summary ──────────────────────────────────────────── */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="grid grid-cols-2 gap-3 items-start">
|
||||
<Section title="Menü Bilgileri">
|
||||
<Row label="Wizard Adı" value={wizardName} />
|
||||
<Row label="Menu Code" value={values.menuCode} />
|
||||
<Row label="Menu Parent" value={values.menuParentCode} />
|
||||
<Row label="İzin Grubu" value={values.permissionGroupName} />
|
||||
<Row label="İkon" value={values.menuIcon} />
|
||||
<Row label="Menü (TR)" value={values.languageTextMenuTr} />
|
||||
<Row label="Menü (EN)" value={values.languageTextMenuEn} />
|
||||
<Row label="Menü Parent (TR)" value={values.languageTextMenuParentTr} />
|
||||
<Row label="Menü Parent (EN)" value={values.languageTextMenuParentEn} />
|
||||
</Section>
|
||||
|
||||
{/* Step 1 Summary */}
|
||||
<Section title="Menü Bilgileri">
|
||||
<Row label="Wizard Adı" value={wizardName} />
|
||||
<Row label="Menu Code" value={values.menuCode} />
|
||||
<Row label="Menu Parent" value={values.menuParentCode} />
|
||||
<Row label="İzin Grubu" value={values.permissionGroupName} />
|
||||
<Row label="İkon" value={values.menuIcon} />
|
||||
<Row label="Menü (TR)" value={values.languageTextMenuTr} />
|
||||
<Row label="Menü (EN)" value={values.languageTextMenuEn} />
|
||||
<Row label="Menü Parent (TR)" value={values.languageTextMenuParentTr} />
|
||||
<Row label="Menü Parent (EN)" value={values.languageTextMenuParentEn} />
|
||||
</Section>
|
||||
<Section title="ListForm Ayarları">
|
||||
<Row label="ListForm Code" value={values.listFormCode} />
|
||||
<Row label="Başlık (TR)" value={values.languageTextTitleTr} />
|
||||
<Row label="Başlık (EN)" value={values.languageTextTitleEn} />
|
||||
<Row label="Açıklama (TR)" value={values.languageTextDescTr} />
|
||||
<Row label="Açıklama (EN)" value={values.languageTextDescEn} />
|
||||
<Row label="Veri Kaynağı" value={values.dataSourceCode} />
|
||||
<Row label="Connection String" value={values.dataSourceConnectionString} />
|
||||
<Row
|
||||
label="Komut Tipi"
|
||||
value={
|
||||
selectCommandTypeOptions.find((o) => o.value === values.selectCommandType)?.label ||
|
||||
values.selectCommandType
|
||||
}
|
||||
/>
|
||||
<Row label="Select Command" value={values.selectCommand} />
|
||||
<Row label="Key Field" value={values.keyFieldName} />
|
||||
<Row label="Key Field Tipi" value={String(values.keyFieldDbSourceType)} />
|
||||
</Section>
|
||||
</div>
|
||||
|
||||
{/* Step 2 Summary */}
|
||||
<Section title="ListForm Ayarları">
|
||||
<Row label="ListForm Code" value={values.listFormCode} />
|
||||
<Row label="Başlık (TR)" value={values.languageTextTitleTr} />
|
||||
<Row label="Başlık (EN)" value={values.languageTextTitleEn} />
|
||||
<Row label="Açıklama (TR)" value={values.languageTextDescTr} />
|
||||
<Row label="Açıklama (EN)" value={values.languageTextDescEn} />
|
||||
<Row label="Veri Kaynağı" value={values.dataSourceCode} />
|
||||
<Row label="Connection String" value={values.dataSourceConnectionString} />
|
||||
<Row label="Komut Tipi" value={values.selectCommandType} />
|
||||
<Row label="Select Command" value={values.selectCommand} />
|
||||
<Row label="Key Field" value={values.keyFieldName} />
|
||||
<Row label="Key Field Tipi" value={String(values.keyFieldDbSourceType)} />
|
||||
</Section>
|
||||
|
||||
{/* Columns */}
|
||||
<Section title="Seçili Sütunlar" badge={selectedColumns.size}>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{[...selectedColumns].map((col) => {
|
||||
|
|
@ -226,7 +245,7 @@ const WizardStep4 = ({
|
|||
>
|
||||
{col}
|
||||
{meta?.dataType && (
|
||||
<span className="text-[10px] text-indigo-400">{meta.dataType}</span>
|
||||
<span className="text-[10px] text-indigo-400 opacity-70">{meta.dataType}</span>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
|
|
@ -234,11 +253,15 @@ const WizardStep4 = ({
|
|||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Step 3 – Groups */}
|
||||
<Section title="Form Grupları" badge={groups.length} defaultOpen={true}>
|
||||
<Section title="Form Grupları" badge={groups.length}>
|
||||
<div className="flex flex-col gap-3">
|
||||
{groups.map((g) => (
|
||||
<Section key={g.id} title={g.caption || '(Grup)'} badge={`${g.items.length} alan · ${g.colCount} sütun`} defaultOpen={false}>
|
||||
<Section
|
||||
key={g.id}
|
||||
title={g.caption || '(Grup)'}
|
||||
badge={`${g.items.length} alan · ${g.colCount} sütun`}
|
||||
defaultOpen={false}
|
||||
>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{g.items.length === 0 ? (
|
||||
<span className="text-xs text-gray-300 italic">Alan yok</span>
|
||||
|
|
@ -254,7 +277,7 @@ const WizardStep4 = ({
|
|||
<span className="text-[10px] text-gray-400 bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded">
|
||||
{item.editorType}
|
||||
</span>
|
||||
<span className="text-[10px] text-gray-400 ml-auto">
|
||||
<span className="text-[10px] text-gray-400 ml-auto shrink-0">
|
||||
span:{item.colSpan}
|
||||
{item.isRequired && (
|
||||
<span className="ml-1 text-red-400 font-semibold">*</span>
|
||||
|
|
@ -270,9 +293,9 @@ const WizardStep4 = ({
|
|||
</Section>
|
||||
</div>
|
||||
|
||||
{/* ── Right: Deploy ────────────────────────────────────────────── */}
|
||||
<div className="w-96 shrink-0 flex flex-col gap-4">
|
||||
{/* Stats bar */}
|
||||
{/* ── Right: Deploy ──────────────────────────────────────────── */}
|
||||
<div className="sticky top-4 flex flex-col gap-3 max-h-[calc(100vh-200px)]">
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{[
|
||||
{ label: 'Grup', value: groups.length },
|
||||
|
|
@ -281,18 +304,23 @@ const WizardStep4 = ({
|
|||
].map((s) => (
|
||||
<div
|
||||
key={s.label}
|
||||
className="rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 p-3 text-center"
|
||||
className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-3 text-center shadow-sm"
|
||||
>
|
||||
<div className="text-xl font-bold text-indigo-600 dark:text-indigo-400">{s.value}</div>
|
||||
<div className="text-2xl font-bold text-indigo-600 dark:text-indigo-400">
|
||||
{s.value}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400 mt-0.5">{s.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Log panel */}
|
||||
<div className="rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col flex-1">
|
||||
<div className="px-4 py-2.5 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200">Deploy Log</span>
|
||||
{/* Log panel — grows to fill remaining height */}
|
||||
<div className="rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col flex-1 min-h-0">
|
||||
<div className="px-4 py-2.5 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between shrink-0">
|
||||
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200 flex items-center gap-2">
|
||||
<FaRocket className="text-indigo-400 text-xs" />
|
||||
Deploy Log
|
||||
</span>
|
||||
{isDone && (
|
||||
<span className="text-xs text-emerald-500 font-semibold flex items-center gap-1">
|
||||
<FaCheckCircle /> Başarılı
|
||||
|
|
@ -305,26 +333,33 @@ const WizardStep4 = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-h-[280px] max-h-[calc(100vh-380px)] overflow-y-auto p-3 bg-gray-950 dark:bg-black font-mono">
|
||||
<div className="flex-1 overflow-y-auto p-4 bg-[#0d1117] dark:bg-black font-mono min-h-[360px]">
|
||||
{logs.length === 0 ? (
|
||||
<div className="text-xs text-gray-500 italic py-4 text-center select-none">
|
||||
Deploy başlatmak için butona tıklayın
|
||||
<div className="flex flex-col items-center justify-center h-full gap-3 py-10 select-none">
|
||||
<FaRocket className="text-gray-700 text-3xl" />
|
||||
<span className="text-xs text-gray-600 italic text-center">
|
||||
Tüm bilgiler hazır.
|
||||
<br />
|
||||
Deploy başlatmak için butona tıklayın.
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="flex flex-col gap-2">
|
||||
{logs.map((log) => (
|
||||
<div key={log.id} className="flex items-start gap-2">
|
||||
<LogIcon status={log.status} />
|
||||
<div key={log.id} className="flex items-start gap-2.5">
|
||||
<span className="mt-0.5 shrink-0">
|
||||
<LogIcon status={log.status} />
|
||||
</span>
|
||||
<div>
|
||||
<span
|
||||
className={`text-xs ${
|
||||
className={`text-xs leading-relaxed ${
|
||||
log.status === 'success'
|
||||
? 'text-emerald-400'
|
||||
: log.status === 'error'
|
||||
? 'text-red-400'
|
||||
: log.status === 'running'
|
||||
? 'text-indigo-300'
|
||||
: 'text-gray-500'
|
||||
? 'text-yellow-300'
|
||||
: 'text-gray-600'
|
||||
}`}
|
||||
>
|
||||
{log.label}
|
||||
|
|
@ -335,44 +370,35 @@ const WizardStep4 = ({
|
|||
</div>
|
||||
</div>
|
||||
))}
|
||||
{isDone && (
|
||||
<div className="mt-4 rounded-lg border border-emerald-800 bg-emerald-950/40 px-4 py-2.5 text-xs text-emerald-400 text-center font-semibold">
|
||||
🎉 ListForm başarıyla oluşturuldu ve deploy edildi!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isDone && (
|
||||
<div className="rounded-xl border border-emerald-200 dark:border-emerald-800 bg-emerald-50 dark:bg-emerald-900/20 p-4 text-sm text-emerald-700 dark:text-emerald-300 text-center font-medium">
|
||||
🎉 ListForm başarıyla oluşturuldu ve deploy edildi!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ── Fixed Footer ─────────────────────────────────────────────── */}
|
||||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-3">
|
||||
<div className="max-w-5xl mx-auto flex gap-3">
|
||||
<Button
|
||||
variant="default"
|
||||
type="button"
|
||||
onClick={onBack}
|
||||
disabled={isDeploying}
|
||||
>
|
||||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-0 h-16 flex items-center">
|
||||
<div className="flex items-center gap-3 w-full">
|
||||
<Button variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack} disabled={isDeploying}>
|
||||
{translate('::Back') || 'Back'}
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
variant="solid"
|
||||
type="button"
|
||||
icon={<FaRocket />}
|
||||
loading={isDeploying}
|
||||
disabled={isDeploying || isDone}
|
||||
onClick={runDeploy}
|
||||
>
|
||||
{isDeploying
|
||||
? 'Deploy ediliyor…'
|
||||
: isDone
|
||||
? 'Tamamlandı'
|
||||
: translate('::Save') || 'Deploy & Save'}
|
||||
</Button>
|
||||
<div className="flex-1 flex items-center justify-end">
|
||||
<Button
|
||||
variant="solid"
|
||||
type="button"
|
||||
icon={<FaRocket />}
|
||||
loading={isDeploying}
|
||||
disabled={isDeploying || isDone}
|
||||
onClick={runDeploy}
|
||||
>
|
||||
{isDeploying ? 'Deploy ediliyor…' : isDone ? '✓ Tamamlandı' : 'Deploy & Kaydet'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<SelectBoxOption[]>([])
|
||||
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<SqlObjectExplorerDto | null>(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<DatabaseColumnDto[]>([])
|
||||
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 (
|
||||
<Container>
|
||||
|
|
@ -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>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceSelectCommandType')}
|
||||
invalid={errors.selectCommandType && touched.selectCommandType}
|
||||
errorMessage={errors.selectCommandType}
|
||||
>
|
||||
<Field
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
name="selectCommandType"
|
||||
placeholder={translate(
|
||||
'::ListForms.ListFormEdit.DatabaseDataSourceSelectCommandType',
|
||||
)}
|
||||
>
|
||||
{({ field, form }: FieldProps<SelectCommandTypeEnum>) => (
|
||||
<Select
|
||||
field={field}
|
||||
form={form}
|
||||
isClearable={true}
|
||||
options={selectCommandTypeOptions}
|
||||
value={selectCommandTypeOptions?.filter(
|
||||
(option: any) => option.value === values.selectCommandType,
|
||||
)}
|
||||
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||
onChange={(option) => {
|
||||
const val = option?.value ?? ''
|
||||
form.setFieldValue(field.name, val)
|
||||
setCurrentDataSource(val)
|
||||
// reset dependent fields
|
||||
form.setFieldValue('selectCommand', '')
|
||||
form.setFieldValue('selectCommandType', undefined)
|
||||
form.setFieldValue('keyFieldName', '')
|
||||
form.setFieldValue('keyFieldDbSourceType', undefined)
|
||||
setSelectCommandColumns([])
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
|
@ -152,16 +205,111 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
|||
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceSelectCommand')}
|
||||
invalid={errors.selectCommand && touched.selectCommand}
|
||||
errorMessage={errors.selectCommand}
|
||||
extra={
|
||||
values.selectCommandType != null ? (
|
||||
<span className="ml-2 text-xs px-2 py-0.5 rounded bg-indigo-100 text-indigo-600 dark:bg-indigo-900/40 dark:text-indigo-300">
|
||||
{selectCommandTypeOptions.find(
|
||||
(o: any) => o.value === values.selectCommandType,
|
||||
)?.label ?? String(values.selectCommandType)}
|
||||
</span>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<Field
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
name="selectCommand"
|
||||
placeholder={translate(
|
||||
'::ListForms.ListFormEdit.DatabaseDataSourceSelectCommand',
|
||||
)}
|
||||
component={Input}
|
||||
/>
|
||||
<Field type="text" autoComplete="off" name="selectCommand">
|
||||
{({ field, form }: FieldProps<string>) => {
|
||||
const grouped = dbObjects
|
||||
? [
|
||||
{
|
||||
label: 'Tables',
|
||||
options: dbObjects.tables.map((t) => ({
|
||||
label: t.tableName,
|
||||
value: t.tableName,
|
||||
__type: SelectCommandTypeEnum.Table,
|
||||
__schema: t.schemaName,
|
||||
__rawName: t.tableName,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'Views',
|
||||
options: dbObjects.views.map((v) => ({
|
||||
label: v.viewName,
|
||||
value: v.viewName,
|
||||
__type: SelectCommandTypeEnum.View,
|
||||
__schema: v.schemaName,
|
||||
__rawName: v.viewName,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'Functions',
|
||||
options: dbObjects.functions.map((f) => ({
|
||||
label: f.functionName,
|
||||
value: f.functionName,
|
||||
__type: SelectCommandTypeEnum.TableValuedFunction,
|
||||
__schema: f.schemaName,
|
||||
__rawName: f.functionName,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'Stored Procedures',
|
||||
options: dbObjects.storedProcedures.map((p) => ({
|
||||
label: p.procedureName,
|
||||
value: p.procedureName,
|
||||
__type: SelectCommandTypeEnum.StoredProcedure,
|
||||
__schema: p.schemaName,
|
||||
__rawName: p.procedureName,
|
||||
})),
|
||||
},
|
||||
]
|
||||
: []
|
||||
return (
|
||||
<Select
|
||||
componentAs={CreatableSelect}
|
||||
field={field}
|
||||
form={form}
|
||||
isClearable
|
||||
isLoading={isLoadingDbObjects}
|
||||
options={grouped}
|
||||
placeholder={
|
||||
isLoadingDbObjects
|
||||
? translate('::Loading')
|
||||
: translate(
|
||||
'::ListForms.ListFormEdit.DatabaseDataSourceSelectCommand',
|
||||
)
|
||||
}
|
||||
value={field.value ? { label: field.value, value: field.value } : null}
|
||||
onChange={(option: any) => {
|
||||
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([])
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</Field>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceTableName')}
|
||||
|
|
@ -175,47 +323,64 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
|||
placeholder={translate('::ListForms.ListFormEdit.DatabaseDataSourceTableName')}
|
||||
component={Input}
|
||||
/>
|
||||
</FormItem>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={translate('::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldName')}
|
||||
invalid={errors.keyFieldName && touched.keyFieldName}
|
||||
errorMessage={errors.keyFieldName}
|
||||
extra={
|
||||
values.keyFieldName && values.keyFieldDbSourceType != null ? (
|
||||
<span className="ml-2 text-xs px-2 py-0.5 rounded bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300">
|
||||
{dbSourceTypeOptions.find(
|
||||
(o: any) => o.value === values.keyFieldDbSourceType,
|
||||
)?.label ?? String(values.keyFieldDbSourceType)}
|
||||
</span>
|
||||
) : selectCommandColumns.length === 0 && !isLoadingColumns ? (
|
||||
<span className="text-xs ml-2 text-gray-400">
|
||||
{translate('::ListForms.ListFormEdit.DatabaseDataSourceSelectCommand') +
|
||||
' seçince sütunlar yüklenir'}
|
||||
</span>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<Field
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
name="keyFieldName"
|
||||
placeholder={translate(
|
||||
'::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldName',
|
||||
)}
|
||||
component={Input}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={translate(
|
||||
'::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldDbSourceType',
|
||||
)}
|
||||
// invalid={errors.keyFieldDbSourceType && touched.keyFieldDbSourceType}
|
||||
// errorMessage={errors.keyFieldDbSourceType}
|
||||
>
|
||||
<Field
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
name="keyFieldDbSourceType"
|
||||
placeholder={translate(
|
||||
'::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldDbSourceType',
|
||||
)}
|
||||
>
|
||||
{({ field, form }: FieldProps<any>) => (
|
||||
<Field type="text" autoComplete="off" name="keyFieldName">
|
||||
{({ field, form }: FieldProps<string>) => (
|
||||
<Select
|
||||
componentAs={CreatableSelect}
|
||||
field={field}
|
||||
form={form}
|
||||
isClearable={true}
|
||||
options={dbSourceTypeOptions}
|
||||
value={dbSourceTypeOptions?.filter(
|
||||
(option: any) => option.value === values.keyFieldDbSourceType,
|
||||
)}
|
||||
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||
isClearable
|
||||
isLoading={isLoadingColumns}
|
||||
placeholder={
|
||||
isLoadingColumns
|
||||
? translate('::Loading')
|
||||
: translate(
|
||||
'::ListForms.ListFormEdit.DatabaseDataSourceKeyFieldName',
|
||||
)
|
||||
}
|
||||
options={selectCommandColumns.map((c) => ({
|
||||
label: `${c.columnName} (${c.dataType})`,
|
||||
value: c.columnName,
|
||||
__dataType: c.dataType,
|
||||
}))}
|
||||
value={field.value ? { label: field.value, value: field.value } : null}
|
||||
onChange={(option: any) => {
|
||||
if (!option) {
|
||||
form.setFieldValue(field.name, '')
|
||||
form.setFieldValue('keyFieldDbSourceType', undefined)
|
||||
return
|
||||
}
|
||||
form.setFieldValue(field.name, option.value)
|
||||
if (!option.__isNew__ && option.__dataType) {
|
||||
form.setFieldValue(
|
||||
'keyFieldDbSourceType',
|
||||
sqlDataTypeToDbType(option.__dataType),
|
||||
)
|
||||
}
|
||||
}}
|
||||
onCreateOption={(inputValue: string) => {
|
||||
form.setFieldValue(field.name, inputValue)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
|
|
|||
|
|
@ -36,11 +36,13 @@ import setNull from '@/utils/setNull'
|
|||
import classNames from 'classnames'
|
||||
import { Field, FieldProps, Form, Formik, FormikHelpers } from 'formik'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { FaFileMedical, FaCopy, FaEyeSlash, FaMinus, FaTimes } from 'react-icons/fa'
|
||||
import { FaFileMedical, FaCopy, FaEyeSlash, FaMinus, FaTimes, FaTable } from 'react-icons/fa'
|
||||
import { number, object, string } from 'yup'
|
||||
import FormFieldEdit from './FormFieldEdit'
|
||||
import { dbSourceTypeOptions } from '../options'
|
||||
import { dbSourceTypeOptions, sqlDataTypeToDbType } from '../options'
|
||||
import { IdentityRoleDto, IdentityUserDto } from '@/proxy/admin/models'
|
||||
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
||||
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||
|
||||
export interface FormFieldEditProps {
|
||||
onSubmit: (
|
||||
|
|
@ -87,6 +89,103 @@ function FormFields({
|
|||
const [fieldList, setFieldList] = useState<SelectBoxOption[]>([])
|
||||
const cultureName = useStoreState((state) => state.locale.currentLang)
|
||||
|
||||
// ── All DB Columns (for "Tüm Sütunları Ekle") ───────────────────────────────
|
||||
const listFormValues = useStoreState((s) => s.admin.lists.values)
|
||||
const [allDbColumns, setAllDbColumns] = useState<DatabaseColumnDto[]>([])
|
||||
const [isAddingAllColumns, setIsAddingAllColumns] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const dsCode = listFormValues?.dataSourceCode
|
||||
const cmd = listFormValues?.selectCommand
|
||||
if (!dsCode || !cmd) {
|
||||
setAllDbColumns([])
|
||||
return
|
||||
}
|
||||
const load = async () => {
|
||||
try {
|
||||
const objRes = await sqlObjectManagerService.getAllObjects(dsCode)
|
||||
const dbObjects = objRes.data
|
||||
if (!dbObjects) return
|
||||
let schema = ''
|
||||
let name = ''
|
||||
const table = dbObjects.tables.find((t) => t.tableName === cmd)
|
||||
if (table) {
|
||||
schema = table.schemaName
|
||||
name = table.tableName
|
||||
} else {
|
||||
const view = dbObjects.views.find((v) => v.viewName === cmd)
|
||||
if (view) {
|
||||
schema = view.schemaName
|
||||
name = view.viewName
|
||||
} else {
|
||||
const fn = dbObjects.functions.find((f) => f.functionName === cmd)
|
||||
if (fn) {
|
||||
schema = fn.schemaName
|
||||
name = fn.functionName
|
||||
} else {
|
||||
const sp = dbObjects.storedProcedures.find((p) => p.procedureName === cmd)
|
||||
if (sp) {
|
||||
schema = sp.schemaName
|
||||
name = sp.procedureName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!name) {
|
||||
setAllDbColumns([])
|
||||
return
|
||||
}
|
||||
const colRes = await sqlObjectManagerService.getTableColumns(dsCode, schema, name)
|
||||
setAllDbColumns(colRes.data ?? [])
|
||||
} catch {
|
||||
setAllDbColumns([])
|
||||
}
|
||||
}
|
||||
load()
|
||||
}, [listFormValues?.dataSourceCode, listFormValues?.selectCommand])
|
||||
|
||||
const addAllColumns = async () => {
|
||||
if (!allDbColumns.length || !listFormCode) return
|
||||
const existingNames = new Set((fields ?? []).map((f) => f.fieldName))
|
||||
const newCols = allDbColumns.filter((c) => !existingNames.has(c.columnName))
|
||||
if (newCols.length === 0) {
|
||||
toast.push(
|
||||
<Notification type="info" duration={2000}>
|
||||
Tüm sütunlar zaten eklenmiş.
|
||||
</Notification>,
|
||||
{ placement: 'top-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
setIsAddingAllColumns(true)
|
||||
try {
|
||||
for (const col of newCols) {
|
||||
await postListFormField({
|
||||
listFormCode,
|
||||
fieldName: col.columnName,
|
||||
sourceDbType: sqlDataTypeToDbType(col.dataType) as any,
|
||||
cultureName,
|
||||
})
|
||||
}
|
||||
toast.push(
|
||||
<Notification type="success" duration={2000}>
|
||||
{newCols.length} sütun eklendi.
|
||||
</Notification>,
|
||||
{ placement: 'top-end' },
|
||||
)
|
||||
await getFields()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata: <code>{error.toString()}</code>
|
||||
</Notification>,
|
||||
{ placement: 'top-end' },
|
||||
)
|
||||
} finally {
|
||||
setIsAddingAllColumns(false)
|
||||
}
|
||||
}
|
||||
|
||||
const NewField: Partial<ColumnFormatEditDto> = {
|
||||
fieldName: '',
|
||||
cultureName,
|
||||
|
|
@ -324,6 +423,18 @@ function FormFields({
|
|||
setIsCopyField(true)
|
||||
}}
|
||||
/>
|
||||
{allDbColumns.length > 0 && (
|
||||
<Button
|
||||
shape="circle"
|
||||
variant="plain"
|
||||
type="button"
|
||||
size="xs"
|
||||
title="Tüm Sütunları Ekle"
|
||||
icon={<FaTable />}
|
||||
loading={isAddingAllColumns}
|
||||
onClick={addAllColumns}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Th>
|
||||
<Th>Field</Th>
|
||||
|
|
|
|||
|
|
@ -433,6 +433,30 @@ export const firstDayOfWeekOptions = [
|
|||
{ value: 6, label: 'Saturday' },
|
||||
]
|
||||
|
||||
export const 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
|
||||
}
|
||||
|
||||
export const dbSourceTypeOptions = enumToList(DbTypeEnum)
|
||||
export const dataSourceTypeOptions = enumToList(DataSourceTypeEnum)
|
||||
export const selectCommandTypeOptions = enumToList(SelectCommandTypeEnum)
|
||||
|
|
|
|||
Loading…
Reference in a new issue