ListFormWizard
This commit is contained in:
parent
009c1a8416
commit
503c45282b
12 changed files with 932 additions and 109 deletions
|
|
@ -20,6 +20,14 @@ public class ListFormWizardDto
|
||||||
public bool AllowDetail { get; set; }
|
public bool AllowDetail { get; set; }
|
||||||
public bool ConfirmDelete { get; set; }
|
public bool ConfirmDelete { get; set; }
|
||||||
|
|
||||||
|
public string DefaultLayout { get; set; }
|
||||||
|
public bool Grid { get; set; }
|
||||||
|
public bool Pivot { get; set; }
|
||||||
|
public bool Tree { get; set; }
|
||||||
|
public bool Chart { get; set; }
|
||||||
|
public bool Gantt { get; set; }
|
||||||
|
public bool Scheduler { get; set; }
|
||||||
|
|
||||||
public string LanguageTextMenuEn { get; set; }
|
public string LanguageTextMenuEn { get; set; }
|
||||||
public string LanguageTextMenuTr { get; set; }
|
public string LanguageTextMenuTr { get; set; }
|
||||||
public string LanguageTextTitleEn { get; set; }
|
public string LanguageTextTitleEn { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public class WizardColumnItemInputDto
|
public class WizardColumnItemInputDto
|
||||||
{
|
{
|
||||||
public string DataField { get; set; }
|
public string DataField { get; set; }
|
||||||
|
public string CaptionName { get; set; }
|
||||||
public string EditorType { get; set; }
|
public string EditorType { get; set; }
|
||||||
public string EditorOptions { get; set; }
|
public string EditorOptions { get; set; }
|
||||||
public string EditorScript { get; set; }
|
public string EditorScript { get; set; }
|
||||||
|
|
@ -13,4 +15,8 @@ public class WizardColumnItemInputDto
|
||||||
public DbType DbSourceType { get; set; } = DbType.String;
|
public DbType DbSourceType { get; set; } = DbType.String;
|
||||||
public string TurkishCaption { get; set; }
|
public string TurkishCaption { get; set; }
|
||||||
public string EnglishCaption { get; set; }
|
public string EnglishCaption { get; set; }
|
||||||
|
public UiLookupDataSourceTypeEnum LookupDataSourceType { get; set; }
|
||||||
|
public string ValueExpr { get; set; }
|
||||||
|
public string DisplayExpr { get; set; }
|
||||||
|
public string LookupQuery { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -16,6 +17,7 @@ using static Sozsoft.Platform.PlatformConsts;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Sozsoft.Languages;
|
using Sozsoft.Languages;
|
||||||
|
using Sozsoft.Platform.DynamicData;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
|
|
@ -30,7 +32,8 @@ public class ListFormWizardAppService(
|
||||||
IRepository<Menu, Guid> repoMenu,
|
IRepository<Menu, Guid> repoMenu,
|
||||||
IPermissionGrantRepository permissionGrantRepository,
|
IPermissionGrantRepository permissionGrantRepository,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
LanguageTextAppService languageTextAppService
|
LanguageTextAppService languageTextAppService,
|
||||||
|
IDynamicDataManager dynamicDataManager
|
||||||
) : PlatformAppService(), IListFormWizardAppService
|
) : PlatformAppService(), IListFormWizardAppService
|
||||||
{
|
{
|
||||||
private readonly IRepository<ListForm, Guid> repoListForm = repoListForm;
|
private readonly IRepository<ListForm, Guid> repoListForm = repoListForm;
|
||||||
|
|
@ -44,6 +47,7 @@ public class ListFormWizardAppService(
|
||||||
private readonly IPermissionGrantRepository permissionGrantRepository = permissionGrantRepository;
|
private readonly IPermissionGrantRepository permissionGrantRepository = permissionGrantRepository;
|
||||||
private readonly IConfiguration _configuration = configuration;
|
private readonly IConfiguration _configuration = configuration;
|
||||||
private readonly LanguageTextAppService _languageTextAppService = languageTextAppService;
|
private readonly LanguageTextAppService _languageTextAppService = languageTextAppService;
|
||||||
|
private readonly IDynamicDataManager _dynamicDataManager = dynamicDataManager;
|
||||||
private readonly string cultureNameDefault = PlatformConsts.DefaultLanguage;
|
private readonly string cultureNameDefault = PlatformConsts.DefaultLanguage;
|
||||||
|
|
||||||
[UnitOfWork]
|
[UnitOfWork]
|
||||||
|
|
@ -194,6 +198,10 @@ public class ListFormWizardAppService(
|
||||||
await repoListFormField.DeleteManyAsync(existingListFormFields, autoSave: true);
|
await repoListFormField.DeleteManyAsync(existingListFormFields, autoSave: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tableColumns = await GetTableColumnNamesAsync(input.DataSourceCode, input.SelectCommandType, input.SelectCommand);
|
||||||
|
var isDeleted = tableColumns.Contains("IsDeleted");
|
||||||
|
var isCreated = tableColumns.Contains("CreatorId");
|
||||||
|
|
||||||
var listForm = await repoListForm.InsertAsync(new ListForm
|
var listForm = await repoListForm.InsertAsync(new ListForm
|
||||||
{
|
{
|
||||||
ListFormType = ListFormTypeEnum.List,
|
ListFormType = ListFormTypeEnum.List,
|
||||||
|
|
@ -201,7 +209,7 @@ public class ListFormWizardAppService(
|
||||||
ExportJson = WizardConsts.DefaultExportJson,
|
ExportJson = WizardConsts.DefaultExportJson,
|
||||||
IsSubForm = false,
|
IsSubForm = false,
|
||||||
ShowNote = true,
|
ShowNote = true,
|
||||||
LayoutJson = WizardConsts.DefaultLayoutJson(),
|
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler),
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
ListFormCode = input.ListFormCode,
|
ListFormCode = input.ListFormCode,
|
||||||
Name = nameLangKey,
|
Name = nameLangKey,
|
||||||
|
|
@ -215,7 +223,7 @@ public class ListFormWizardAppService(
|
||||||
SelectCommand = input.SelectCommand,
|
SelectCommand = input.SelectCommand,
|
||||||
KeyFieldName = input.KeyFieldName,
|
KeyFieldName = input.KeyFieldName,
|
||||||
KeyFieldDbSourceType = input.KeyFieldDbSourceType,
|
KeyFieldDbSourceType = input.KeyFieldDbSourceType,
|
||||||
DefaultFilter = WizardConsts.DefaultFilterJson,
|
DefaultFilter = isDeleted ? WizardConsts.DefaultFilterJson : null,
|
||||||
SortMode = GridOptions.SortModeSingle,
|
SortMode = GridOptions.SortModeSingle,
|
||||||
FilterRowJson = WizardConsts.DefaultFilterRowJson,
|
FilterRowJson = WizardConsts.DefaultFilterRowJson,
|
||||||
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
|
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
|
||||||
|
|
@ -224,9 +232,9 @@ public class ListFormWizardAppService(
|
||||||
SelectionJson = WizardConsts.DefaultSelectionSingleJson,
|
SelectionJson = WizardConsts.DefaultSelectionSingleJson,
|
||||||
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
||||||
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
||||||
DeleteCommand = WizardConsts.DefaultDeleteCommand(input.SelectCommand),
|
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
||||||
DeleteFieldsDefaultValueJson = WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType),
|
DeleteFieldsDefaultValueJson = isDeleted ? WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType),
|
||||||
InsertFieldsDefaultValueJson = WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType),
|
InsertFieldsDefaultValueJson = isCreated ? WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType),
|
||||||
PagerOptionJson = WizardConsts.DefaultPagerOptionJson,
|
PagerOptionJson = WizardConsts.DefaultPagerOptionJson,
|
||||||
EditingOptionJson = WizardConsts.DefaultEditingOptionJson(titleLangKey, 600, 500, input.AllowDeleting, input.AllowAdding, input.AllowUpdating, input.ConfirmDelete, false, input.AllowDetail),
|
EditingOptionJson = WizardConsts.DefaultEditingOptionJson(titleLangKey, 600, 500, input.AllowDeleting, input.AllowAdding, input.AllowUpdating, input.ConfirmDelete, false, input.AllowDetail),
|
||||||
EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null,
|
EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null,
|
||||||
|
|
@ -234,18 +242,16 @@ public class ListFormWizardAppService(
|
||||||
|
|
||||||
// ListFormField - each item in each group becomes a visible field record
|
// ListFormField - each item in each group becomes a visible field record
|
||||||
var fieldOrder = 0;
|
var fieldOrder = 0;
|
||||||
var captionName = string.Empty;
|
|
||||||
foreach (var group in input.Groups)
|
foreach (var group in input.Groups)
|
||||||
{
|
{
|
||||||
foreach (var item in group.Items)
|
foreach (var item in group.Items)
|
||||||
{
|
{
|
||||||
fieldOrder++;
|
fieldOrder++;
|
||||||
captionName = $"App.Listform.ListformField.{item.DataField}";
|
|
||||||
await repoListFormField.InsertAsync(new ListFormField
|
await repoListFormField.InsertAsync(new ListFormField
|
||||||
{
|
{
|
||||||
ListFormCode = input.ListFormCode,
|
ListFormCode = input.ListFormCode,
|
||||||
FieldName = item.DataField,
|
FieldName = item.DataField,
|
||||||
CaptionName = captionName,
|
CaptionName = item.CaptionName,
|
||||||
Visible = item.DataField != input.KeyFieldName,
|
Visible = item.DataField != input.KeyFieldName,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
AllowSearch = true,
|
AllowSearch = true,
|
||||||
|
|
@ -256,9 +262,10 @@ public class ListFormWizardAppService(
|
||||||
ColumnCustomizationJson = WizardConsts.DefaultColumnCustomizationJson,
|
ColumnCustomizationJson = WizardConsts.DefaultColumnCustomizationJson,
|
||||||
ColumnFilterJson = WizardConsts.DefaultColumnFilteringJson,
|
ColumnFilterJson = WizardConsts.DefaultColumnFilteringJson,
|
||||||
PivotSettingsJson = WizardConsts.DefaultPivotSettingsJson,
|
PivotSettingsJson = WizardConsts.DefaultPivotSettingsJson,
|
||||||
}, autoSave: false);
|
LookupJson = item.LookupQuery.Length > 0 ? WizardConsts.DefaultLookupJson(item.LookupDataSourceType, item.DisplayExpr, item.ValueExpr, item.LookupQuery) : null,
|
||||||
|
}, autoSave: true);
|
||||||
|
|
||||||
await CreateLangKey(captionName, item.EnglishCaption, item.TurkishCaption);
|
await CreateLangKey(item.CaptionName, item.EnglishCaption, item.TurkishCaption);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -267,24 +274,60 @@ public class ListFormWizardAppService(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<HashSet<string>> GetTableColumnNamesAsync(string dataSourceCode, SelectCommandTypeEnum commandType, string selectCommand)
|
||||||
|
{
|
||||||
|
if (commandType == SelectCommandTypeEnum.Query || commandType == SelectCommandTypeEnum.StoredProcedure)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var parts = selectCommand.Split('.');
|
||||||
|
var schemaName = parts.Length > 1 ? parts[0].Trim('"', '[', ']') : "dbo";
|
||||||
|
var tableName = parts[parts.Length - 1].Trim('"', '[', ']');
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (repo, connectionString, dbType) = await _dynamicDataManager.GetAsync(false, dataSourceCode);
|
||||||
|
|
||||||
|
var query = dbType == Sozsoft.Platform.Enums.DataSourceTypeEnum.Postgresql
|
||||||
|
? $"SELECT column_name FROM information_schema.columns WHERE table_schema = '{schemaName}' AND table_name = '{tableName}'"
|
||||||
|
: $"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{schemaName}' AND TABLE_NAME = '{tableName}'";
|
||||||
|
|
||||||
|
var rows = await repo.QueryAsync(query, connectionString);
|
||||||
|
|
||||||
|
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
var dict = row as IDictionary<string, object>;
|
||||||
|
if (dict != null)
|
||||||
|
{
|
||||||
|
var key = dict.ContainsKey("COLUMN_NAME") ? "COLUMN_NAME" : "column_name";
|
||||||
|
if (dict.TryGetValue(key, out var col) && col != null)
|
||||||
|
columns.Add(col.ToString()!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<LanguageKey> CreateLangKey(string key, string textEn, string textTr)
|
private async Task<LanguageKey> CreateLangKey(string key, string textEn, string textTr)
|
||||||
{
|
{
|
||||||
var res = PlatformConsts.AppName;
|
var res = PlatformConsts.AppName;
|
||||||
|
|
||||||
var keyQueryable = await repoLangKey.GetQueryableAsync();
|
var langKey = await repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == res && a.Key == key)
|
||||||
var langKey = await AsyncExecuter.FirstOrDefaultAsync(keyQueryable.Where(a => a.ResourceName == res && a.Key == key))
|
?? await repoLangKey.InsertAsync(new LanguageKey { ResourceName = res, Key = key }, autoSave: true);
|
||||||
?? await repoLangKey.InsertAsync(new LanguageKey { ResourceName = res, Key = key }, autoSave: false);
|
|
||||||
|
|
||||||
var textQueryable = await repoLangText.GetQueryableAsync();
|
var existingTexts = await repoLangText.GetListAsync(a => a.ResourceName == res && a.Key == langKey.Key);
|
||||||
var existingTexts = await AsyncExecuter.ToListAsync(
|
|
||||||
textQueryable.Where(a => a.ResourceName == res && a.Key == langKey.Key)
|
|
||||||
);
|
|
||||||
|
|
||||||
var langTextEn = existingTexts.FirstOrDefault(a => a.CultureName == cultureNameDefault)
|
var existingEn = existingTexts.FirstOrDefault(a => a.CultureName == cultureNameDefault);
|
||||||
?? await repoLangText.InsertAsync(new LanguageText { ResourceName = res, Key = langKey.Key, CultureName = cultureNameDefault, Value = textEn }, autoSave: false);
|
if (existingEn != null) await repoLangText.DeleteAsync(existingEn, autoSave: true);
|
||||||
|
await repoLangText.InsertAsync(new LanguageText { ResourceName = res, Key = langKey.Key, CultureName = cultureNameDefault, Value = textEn }, autoSave: true);
|
||||||
|
|
||||||
var langTextTr = existingTexts.FirstOrDefault(a => a.CultureName == LanguageCodes.Tr)
|
var existingTr = existingTexts.FirstOrDefault(a => a.CultureName == LanguageCodes.Tr);
|
||||||
?? await repoLangText.InsertAsync(new LanguageText { ResourceName = res, Key = langKey.Key, CultureName = LanguageCodes.Tr, Value = textTr }, autoSave: false);
|
if (existingTr != null) await repoLangText.DeleteAsync(existingTr, autoSave: true);
|
||||||
|
await repoLangText.InsertAsync(new LanguageText { ResourceName = res, Key = langKey.Key, CultureName = LanguageCodes.Tr, Value = textTr }, autoSave: true);
|
||||||
|
|
||||||
return langKey;
|
return langKey;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16280,6 +16280,42 @@
|
||||||
"en": "Columns load after selecting a Select Command",
|
"en": "Columns load after selecting a Select Command",
|
||||||
"tr": "Select Command seçince sütunlar yüklenir"
|
"tr": "Select Command seçince sütunlar yüklenir"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.Wizard.Step3.GenerateFromTable",
|
||||||
|
"en": "Generate From Table",
|
||||||
|
"tr": "Tablodan Üret"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.Wizard.Step3.LookupDataSourceType",
|
||||||
|
"en": "Lookup Data Source Type",
|
||||||
|
"tr": "Veri Kaynağı Türü"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.Wizard.Step3.LookupValueExpression",
|
||||||
|
"en": "Lookup Value Expression",
|
||||||
|
"tr": "Değer İfadesi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.Wizard.Step3.LookupDisplayExpression",
|
||||||
|
"en": "Lookup Display Expression",
|
||||||
|
"tr": "Görüntü İfadesi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.Wizard.Step3.LookupLookupQuery",
|
||||||
|
"en": "Lookup Query",
|
||||||
|
"tr": "Lookup Sorgusu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.Wizard.Step3.LanguageKey",
|
||||||
|
"en": "Language Key",
|
||||||
|
"tr": "Dil Anahtarı"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.Wizard.Step3.TurkishCaption",
|
"key": "ListForms.Wizard.Step3.TurkishCaption",
|
||||||
|
|
@ -16292,6 +16328,12 @@
|
||||||
"en": "English Caption",
|
"en": "English Caption",
|
||||||
"tr": "İngilizce Başlık"
|
"tr": "İngilizce Başlık"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.Wizard.Step3.EditorType",
|
||||||
|
"en": "Editor Type",
|
||||||
|
"tr": "Editör Türü"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.Wizard.Step3.EditorOptions",
|
"key": "ListForms.Wizard.Step3.EditorOptions",
|
||||||
|
|
|
||||||
|
|
@ -78,14 +78,14 @@ public static class WizardConsts
|
||||||
Margin = 10
|
Margin = 10
|
||||||
});
|
});
|
||||||
|
|
||||||
public static string DefaultLayoutJson(string DefaultLayout = "grid") => JsonSerializer.Serialize(new
|
public static string DefaultLayoutJson(string DefaultLayout = "grid", bool Grid = true, bool Pivot = true, bool Chart = true, bool Tree = true, bool Gantt = true, bool Scheduler = true) => JsonSerializer.Serialize(new
|
||||||
{
|
{
|
||||||
Grid = true,
|
Grid = Grid,
|
||||||
Pivot = true,
|
Pivot = Pivot,
|
||||||
Chart = true,
|
Chart = Chart,
|
||||||
Tree = true,
|
Tree = Tree,
|
||||||
Gantt = true,
|
Gantt = Gantt,
|
||||||
Scheduler = true,
|
Scheduler = Scheduler,
|
||||||
DefaultLayout = DefaultLayout,
|
DefaultLayout = DefaultLayout,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -153,6 +153,14 @@ public static class WizardConsts
|
||||||
IsPivot = true
|
IsPivot = true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
public static string DefaultLookupJson(UiLookupDataSourceTypeEnum dataSourceType, string displayExpr = "name", string valueExpr = "key", string lookupQuery = "") => JsonSerializer.Serialize(new
|
||||||
|
{
|
||||||
|
DataSourceType = dataSourceType,
|
||||||
|
DisplayExpr = displayExpr,
|
||||||
|
ValueExpr = valueExpr,
|
||||||
|
LookupQuery = lookupQuery,
|
||||||
|
});
|
||||||
|
|
||||||
public static string DefaultDeleteCommand(string tableName)
|
public static string DefaultDeleteCommand(string tableName)
|
||||||
{
|
{
|
||||||
return $"UPDATE \"{tableName}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id";
|
return $"UPDATE \"{tableName}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id";
|
||||||
|
|
@ -178,6 +186,14 @@ public static class WizardConsts
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string DefaultFieldsJsonOnlyId(DbType dbType = DbType.Guid)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(new[]
|
||||||
|
{
|
||||||
|
new { FieldName = "Id", FieldDbType = dbType, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly string DefaultPagerOptionJson = JsonSerializer.Serialize(new
|
public static readonly string DefaultPagerOptionJson = JsonSerializer.Serialize(new
|
||||||
{
|
{
|
||||||
Visible = true,
|
Visible = true,
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ BEGIN
|
||||||
[DeletionTime] datetime2 NULL,
|
[DeletionTime] datetime2 NULL,
|
||||||
[DeleterId] uniqueidentifier NULL,
|
[DeleterId] uniqueidentifier NULL,
|
||||||
[TenantId] uniqueidentifier NULL,
|
[TenantId] uniqueidentifier NULL,
|
||||||
[BranchId] uniqueidentifier NOT NULL,
|
[BranchId] uniqueidentifier NULL,
|
||||||
[Name] nvarchar(64) NOT NULL,
|
[Name] nvarchar(64) NOT NULL,
|
||||||
[ParentId] uniqueidentifier NULL,
|
[ParentId] uniqueidentifier NULL,
|
||||||
CONSTRAINT [PK_Adm_T_Department] PRIMARY KEY CLUSTERED
|
CONSTRAINT [PK_Adm_T_Department] PRIMARY KEY CLUSTERED
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,15 @@ export interface ListFormWizardDto {
|
||||||
allowDeleting: boolean
|
allowDeleting: boolean
|
||||||
confirmDelete: boolean
|
confirmDelete: boolean
|
||||||
allowDetail: boolean
|
allowDetail: boolean
|
||||||
|
|
||||||
|
defaultLayout: ListViewLayoutType
|
||||||
|
grid: boolean
|
||||||
|
pivot: boolean
|
||||||
|
tree: boolean
|
||||||
|
chart: boolean
|
||||||
|
gantt: boolean
|
||||||
|
scheduler: boolean
|
||||||
|
|
||||||
languageTextMenuEn: string
|
languageTextMenuEn: string
|
||||||
languageTextMenuTr: string
|
languageTextMenuTr: string
|
||||||
languageTextTitleEn: string
|
languageTextTitleEn: string
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,13 @@ const initialValues: ListFormWizardDto = {
|
||||||
allowDeleting: true,
|
allowDeleting: true,
|
||||||
confirmDelete: true,
|
confirmDelete: true,
|
||||||
allowDetail: false,
|
allowDetail: false,
|
||||||
|
defaultLayout: 'grid',
|
||||||
|
grid: true,
|
||||||
|
pivot: true,
|
||||||
|
tree: true,
|
||||||
|
chart: true,
|
||||||
|
gantt: true,
|
||||||
|
scheduler: true,
|
||||||
languageTextMenuEn: '',
|
languageTextMenuEn: '',
|
||||||
languageTextMenuTr: '',
|
languageTextMenuTr: '',
|
||||||
languageTextTitleEn: '',
|
languageTextTitleEn: '',
|
||||||
|
|
@ -148,8 +155,7 @@ const Wizard = () => {
|
||||||
'deleterid',
|
'deleterid',
|
||||||
])
|
])
|
||||||
|
|
||||||
const isAuditColumn = (columnName: string) =>
|
const isAuditColumn = (columnName: string) => AUDIT_COLUMNS.has(columnName.toLowerCase())
|
||||||
AUDIT_COLUMNS.has(columnName.toLowerCase())
|
|
||||||
|
|
||||||
const loadColumns = async (dsCode: string, schema: string, name: string) => {
|
const loadColumns = async (dsCode: string, schema: string, name: string) => {
|
||||||
if (!dsCode || !name) {
|
if (!dsCode || !name) {
|
||||||
|
|
@ -268,7 +274,6 @@ const Wizard = () => {
|
||||||
.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2')
|
.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2')
|
||||||
.trim()
|
.trim()
|
||||||
|
|
||||||
|
|
||||||
const handleWizardNameChange = (name: string) => {
|
const handleWizardNameChange = (name: string) => {
|
||||||
const spacedLabel = toSpacedLabel(name)
|
const spacedLabel = toSpacedLabel(name)
|
||||||
const derived = deriveListFormCode(name)
|
const derived = deriveListFormCode(name)
|
||||||
|
|
@ -379,6 +384,11 @@ const Wizard = () => {
|
||||||
dbSourceType: col ? sqlDataTypeToDbType(col.dataType) : 12,
|
dbSourceType: col ? sqlDataTypeToDbType(col.dataType) : 12,
|
||||||
turkishCaption: item.turkishCaption,
|
turkishCaption: item.turkishCaption,
|
||||||
englishCaption: item.englishCaption,
|
englishCaption: item.englishCaption,
|
||||||
|
captionName: item.captionName,
|
||||||
|
lookupDataSourceType: item.lookupDataSourceType,
|
||||||
|
displayExpr: item.displayExpr,
|
||||||
|
valueExpr: item.valueExpr,
|
||||||
|
lookupQuery: item.lookupQuery,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
|
|
@ -388,7 +398,9 @@ const Wizard = () => {
|
||||||
await getConfig(true)
|
await getConfig(true)
|
||||||
|
|
||||||
// ✅ sonra navigate
|
// ✅ sonra navigate
|
||||||
navigate(ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode), { replace: true })
|
navigate(ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode), {
|
||||||
|
replace: true,
|
||||||
|
})
|
||||||
|
|
||||||
// ✅ en son kullanıcıya mesaj
|
// ✅ en son kullanıcıya mesaj
|
||||||
toast.push(
|
toast.push(
|
||||||
|
|
@ -438,7 +450,10 @@ const Wizard = () => {
|
||||||
await getConfig(true)
|
await getConfig(true)
|
||||||
|
|
||||||
// 🔴 3. Navigate
|
// 🔴 3. Navigate
|
||||||
navigate(ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode), { replace: true })
|
navigate(
|
||||||
|
ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode),
|
||||||
|
{ replace: true },
|
||||||
|
)
|
||||||
|
|
||||||
// 🔴 4. Toast (istersen navigate öncesi de olabilir)
|
// 🔴 4. Toast (istersen navigate öncesi de olabilir)
|
||||||
toast.push(
|
toast.push(
|
||||||
|
|
@ -518,6 +533,9 @@ const Wizard = () => {
|
||||||
selectCommandColumns={selectCommandColumns}
|
selectCommandColumns={selectCommandColumns}
|
||||||
groups={editingGroups}
|
groups={editingGroups}
|
||||||
onGroupsChange={setEditingGroups}
|
onGroupsChange={setEditingGroups}
|
||||||
|
dbObjects={dbObjects}
|
||||||
|
isLoadingDbObjects={isLoadingDbObjects}
|
||||||
|
dsCode={currentDataSource}
|
||||||
translate={translate}
|
translate={translate}
|
||||||
onBack={() => setCurrentStep(1)}
|
onBack={() => setCurrentStep(1)}
|
||||||
onNext={() => setCurrentStep(3)}
|
onNext={() => setCurrentStep(3)}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,12 @@ import { ListFormWizardDto } from '@/proxy/admin/list-form/models'
|
||||||
import { SelectCommandTypeEnum } from '@/proxy/form/models'
|
import { SelectCommandTypeEnum } from '@/proxy/form/models'
|
||||||
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
|
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
|
||||||
import { SelectBoxOption } from '@/types/shared'
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
import { dbSourceTypeOptions, selectCommandTypeOptions, sqlDataTypeToDbType } from './edit/options'
|
import {
|
||||||
|
dbSourceTypeOptions,
|
||||||
|
listFormDefaultLayoutOptions,
|
||||||
|
selectCommandTypeOptions,
|
||||||
|
sqlDataTypeToDbType,
|
||||||
|
} from './edit/options'
|
||||||
import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
|
import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
|
||||||
import CreatableSelect from 'react-select/creatable'
|
import CreatableSelect from 'react-select/creatable'
|
||||||
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
|
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
|
||||||
|
|
@ -395,6 +400,121 @@ const WizardStep2 = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6">
|
||||||
|
<div className="flex flex-wrap gap-6">
|
||||||
|
<FormItem
|
||||||
|
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout')}
|
||||||
|
invalid={errors?.defaultLayout && touched?.defaultLayout}
|
||||||
|
errorMessage={errors?.defaultLayout}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
name="defaultLayout"
|
||||||
|
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.DefaultLayout')}
|
||||||
|
>
|
||||||
|
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
isClearable={true}
|
||||||
|
options={listFormDefaultLayoutOptions}
|
||||||
|
value={listFormDefaultLayoutOptions?.filter(
|
||||||
|
(option) => option.value === values.defaultLayout,
|
||||||
|
)}
|
||||||
|
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-6">
|
||||||
|
<FormItem
|
||||||
|
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GridLayout')}
|
||||||
|
invalid={errors?.grid && touched?.grid}
|
||||||
|
errorMessage={errors?.grid}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
className="w-20"
|
||||||
|
autoComplete="off"
|
||||||
|
name="grid"
|
||||||
|
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GridLayout')}
|
||||||
|
component={Checkbox}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem
|
||||||
|
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')}
|
||||||
|
invalid={errors?.pivot && touched?.pivot}
|
||||||
|
errorMessage={errors?.pivot}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
className="w-20"
|
||||||
|
autoComplete="off"
|
||||||
|
name="pivot"
|
||||||
|
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.PivotLayout')}
|
||||||
|
component={Checkbox}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem
|
||||||
|
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.ChartLayout')}
|
||||||
|
invalid={errors?.chart && touched?.chart}
|
||||||
|
errorMessage={errors?.chart}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
className="w-20"
|
||||||
|
autoComplete="off"
|
||||||
|
name="chart"
|
||||||
|
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.ChartLayout')}
|
||||||
|
component={Checkbox}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem
|
||||||
|
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.TreeLayout')}
|
||||||
|
invalid={errors?.tree && touched?.tree}
|
||||||
|
errorMessage={errors?.tree}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
className="w-20"
|
||||||
|
autoComplete="off"
|
||||||
|
name="tree"
|
||||||
|
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.TreeLayout')}
|
||||||
|
component={Checkbox}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem
|
||||||
|
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout')}
|
||||||
|
invalid={errors?.gantt && touched?.gantt}
|
||||||
|
errorMessage={errors?.gantt}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
className="w-20"
|
||||||
|
autoComplete="off"
|
||||||
|
name="gantt"
|
||||||
|
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.GanttLayout')}
|
||||||
|
component={Checkbox}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem
|
||||||
|
label={translate('::ListForms.ListFormEdit.DetailsLayoutDto.SchedulerLayout')}
|
||||||
|
invalid={errors?.scheduler && touched?.scheduler}
|
||||||
|
errorMessage={errors?.scheduler}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
className="w-20"
|
||||||
|
autoComplete="off"
|
||||||
|
name="scheduler"
|
||||||
|
placeholder={translate('::ListForms.ListFormEdit.DetailsLayoutDto.SchedulerLayout')}
|
||||||
|
component={Checkbox}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6">
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.Wizard.Step2.TitleTextEnglish')}
|
label={translate('::ListForms.Wizard.Step2.TitleTextEnglish')}
|
||||||
|
|
@ -474,14 +594,14 @@ const WizardStep2 = ({
|
||||||
selectCommandColumns.length > 0 ? (
|
selectCommandColumns.length > 0 ? (
|
||||||
<div className="flex items-center gap-2 ml-3">
|
<div className="flex items-center gap-2 ml-3">
|
||||||
<Button
|
<Button
|
||||||
variant='solid'
|
variant="solid"
|
||||||
onClick={() => onToggleAllColumns(true)}
|
onClick={() => onToggleAllColumns(true)}
|
||||||
className="text-xs px-2 py-0.5 rounded bg-indigo-500 text-white hover:bg-indigo-600"
|
className="text-xs px-2 py-0.5 rounded bg-indigo-500 text-white hover:bg-indigo-600"
|
||||||
>
|
>
|
||||||
{translate('::ListForms.Wizard.Step2.SelectAll') || 'Tümünü Seç'}
|
{translate('::ListForms.Wizard.Step2.SelectAll') || 'Tümünü Seç'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant='default'
|
variant="default"
|
||||||
onClick={() => onToggleAllColumns(false)}
|
onClick={() => onToggleAllColumns(false)}
|
||||||
className="text-xs px-2 py-0.5 rounded border border-gray-300 dark:border-gray-600 text-gray-500 hover:text-red-500 hover:border-red-400"
|
className="text-xs px-2 py-0.5 rounded border border-gray-300 dark:border-gray-600 text-gray-500 hover:text-red-500 hover:border-red-400"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import { Button, Dialog } from '@/components/ui'
|
import { Button, Dialog } from '@/components/ui'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { columnEditorTypeListOptions } from '@/views/admin/listForm/edit/options'
|
import {
|
||||||
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
columnEditorTypeListOptions,
|
||||||
|
columnLookupDataSourceTypeListOptions,
|
||||||
|
} from '@/views/admin/listForm/edit/options'
|
||||||
|
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
|
||||||
|
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
DragEndEvent,
|
DragEndEvent,
|
||||||
|
|
@ -16,7 +20,16 @@ import {
|
||||||
import { SortableContext, arrayMove, useSortable, rectSortingStrategy } from '@dnd-kit/sortable'
|
import { SortableContext, arrayMove, useSortable, rectSortingStrategy } from '@dnd-kit/sortable'
|
||||||
import { CSS } from '@dnd-kit/utilities'
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { FaArrowLeft, FaGripVertical, FaPlus, FaTimes, FaTrash, FaArrowRight, FaCode } from 'react-icons/fa'
|
import {
|
||||||
|
FaArrowLeft,
|
||||||
|
FaGripVertical,
|
||||||
|
FaPlus,
|
||||||
|
FaTimes,
|
||||||
|
FaTrash,
|
||||||
|
FaArrowRight,
|
||||||
|
FaCode,
|
||||||
|
} from 'react-icons/fa'
|
||||||
|
import { UiLookupDataSourceTypeEnum } from '@/proxy/form/models'
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
export interface WizardGroupItem {
|
export interface WizardGroupItem {
|
||||||
|
|
@ -29,6 +42,11 @@ export interface WizardGroupItem {
|
||||||
isRequired: boolean
|
isRequired: boolean
|
||||||
turkishCaption?: string
|
turkishCaption?: string
|
||||||
englishCaption?: string
|
englishCaption?: string
|
||||||
|
captionName?: string
|
||||||
|
lookupDataSourceType?: UiLookupDataSourceTypeEnum
|
||||||
|
valueExpr?: string
|
||||||
|
displayExpr?: string
|
||||||
|
lookupQuery?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WizardGroup {
|
export interface WizardGroup {
|
||||||
|
|
@ -43,6 +61,9 @@ export interface WizardStep3Props {
|
||||||
selectCommandColumns: DatabaseColumnDto[]
|
selectCommandColumns: DatabaseColumnDto[]
|
||||||
groups: WizardGroup[]
|
groups: WizardGroup[]
|
||||||
onGroupsChange: (groups: WizardGroup[]) => void
|
onGroupsChange: (groups: WizardGroup[]) => void
|
||||||
|
dbObjects: SqlObjectExplorerDto | null
|
||||||
|
isLoadingDbObjects: boolean
|
||||||
|
dsCode: string
|
||||||
translate: (key: string) => string
|
translate: (key: string) => string
|
||||||
onBack: () => void
|
onBack: () => void
|
||||||
onNext: () => void
|
onNext: () => void
|
||||||
|
|
@ -73,16 +94,15 @@ function inferEditorType(sqlType: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatLabel = (text: string) => {
|
const formatLabel = (text: string) => {
|
||||||
return text
|
return (
|
||||||
|
text
|
||||||
// CamelCase → kelimelere ayır
|
// CamelCase → kelimelere ayır
|
||||||
.split(/(?=[A-Z])/)
|
.split(/(?=[A-Z])/)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map(word =>
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||||
word.charAt(0).toUpperCase() +
|
.join(' ')
|
||||||
word.slice(1).toLowerCase()
|
|
||||||
)
|
)
|
||||||
.join(" ");
|
}
|
||||||
};
|
|
||||||
|
|
||||||
function newGroupItem(colName: string, meta?: DatabaseColumnDto): WizardGroupItem {
|
function newGroupItem(colName: string, meta?: DatabaseColumnDto): WizardGroupItem {
|
||||||
const sqlType = meta?.dataType ?? ''
|
const sqlType = meta?.dataType ?? ''
|
||||||
|
|
@ -96,6 +116,11 @@ function newGroupItem(colName: string, meta?: DatabaseColumnDto): WizardGroupIte
|
||||||
isRequired: meta?.isNullable === false,
|
isRequired: meta?.isNullable === false,
|
||||||
turkishCaption: formatLabel(colName),
|
turkishCaption: formatLabel(colName),
|
||||||
englishCaption: formatLabel(colName),
|
englishCaption: formatLabel(colName),
|
||||||
|
captionName: `App.Listform.ListformField.${colName}`,
|
||||||
|
lookupDataSourceType: UiLookupDataSourceTypeEnum.StaticData,
|
||||||
|
valueExpr: 'Key',
|
||||||
|
displayExpr: 'Name',
|
||||||
|
lookupQuery: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,11 +165,18 @@ function AvailableColumnChip({ colName }: { colName: string }) {
|
||||||
interface SortableItemProps {
|
interface SortableItemProps {
|
||||||
item: WizardGroupItem
|
item: WizardGroupItem
|
||||||
groupColCount: number
|
groupColCount: number
|
||||||
|
dbObjects: SqlObjectExplorerDto | null
|
||||||
|
dsCode: string
|
||||||
onTurkishCaptionChange: (val: string) => void
|
onTurkishCaptionChange: (val: string) => void
|
||||||
onEnglishCaptionChange: (val: string) => void
|
onEnglishCaptionChange: (val: string) => void
|
||||||
onEditorTypeChange: (val: string) => void
|
onEditorTypeChange: (val: string) => void
|
||||||
onEditorOptionsChange: (val: string) => void
|
onEditorOptionsChange: (val: string) => void
|
||||||
onEditorScriptChange: (val: string) => void
|
onEditorScriptChange: (val: string) => void
|
||||||
|
onCaptionNameChange: (val: string) => void
|
||||||
|
onLookupDataSourceTypeChange: (val: UiLookupDataSourceTypeEnum) => void
|
||||||
|
onDisplayExprChange: (val: string) => void
|
||||||
|
onValueExprChange: (val: string) => void
|
||||||
|
onLookupQueryChange: (val: string) => void
|
||||||
onColSpanChange: (val: number) => void
|
onColSpanChange: (val: number) => void
|
||||||
onRequiredChange: (val: boolean) => void
|
onRequiredChange: (val: boolean) => void
|
||||||
onRemove: () => void
|
onRemove: () => void
|
||||||
|
|
@ -153,6 +185,8 @@ interface SortableItemProps {
|
||||||
function SortableItem({
|
function SortableItem({
|
||||||
item,
|
item,
|
||||||
groupColCount,
|
groupColCount,
|
||||||
|
dbObjects,
|
||||||
|
dsCode,
|
||||||
onTurkishCaptionChange,
|
onTurkishCaptionChange,
|
||||||
onEnglishCaptionChange,
|
onEnglishCaptionChange,
|
||||||
onEditorTypeChange,
|
onEditorTypeChange,
|
||||||
|
|
@ -160,9 +194,24 @@ function SortableItem({
|
||||||
onEditorScriptChange,
|
onEditorScriptChange,
|
||||||
onColSpanChange,
|
onColSpanChange,
|
||||||
onRequiredChange,
|
onRequiredChange,
|
||||||
|
onCaptionNameChange,
|
||||||
|
onLookupDataSourceTypeChange,
|
||||||
|
onDisplayExprChange,
|
||||||
|
onValueExprChange,
|
||||||
|
onLookupQueryChange,
|
||||||
onRemove,
|
onRemove,
|
||||||
}: SortableItemProps) {
|
}: SortableItemProps) {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
const [isTablePickerOpen, setIsTablePickerOpen] = useState(false)
|
||||||
|
const [tableSearch, setTableSearch] = useState('')
|
||||||
|
const [pickerStep, setPickerStep] = useState<'table' | 'columns'>('table')
|
||||||
|
const [pickerTable, setPickerTable] = useState<{ schemaName: string; tableName: string } | null>(
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
const [pickerColumns, setPickerColumns] = useState<DatabaseColumnDto[]>([])
|
||||||
|
const [isLoadingPickerColumns, setIsLoadingPickerColumns] = useState(false)
|
||||||
|
const [pickerKeyCol, setPickerKeyCol] = useState('')
|
||||||
|
const [pickerNameCol, setPickerNameCol] = useState('')
|
||||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||||
id: `${ITM_PREFIX}${item.id}`,
|
id: `${ITM_PREFIX}${item.id}`,
|
||||||
})
|
})
|
||||||
|
|
@ -206,7 +255,46 @@ function SortableItem({
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Language Key + Turkish Caption + English Caption */}
|
||||||
|
<div className="flex flex-row flex-wrap gap-1.5">
|
||||||
|
<div className="flex flex-col gap-0.5 min-w-[140px] w-1/2 flex-1">
|
||||||
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
|
{translate('::ListForms.Wizard.Step3.LanguageKey')}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
value={item.captionName}
|
||||||
|
disabled
|
||||||
|
onChange={(e) => onCaptionNameChange(e.target.value)}
|
||||||
|
className="w-full text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-0.5 min-w-[100px] flex-1">
|
||||||
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
|
{translate('::ListForms.Wizard.Step3.TurkishCaption')}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
value={item.turkishCaption}
|
||||||
|
onChange={(e) => onTurkishCaptionChange(e.target.value)}
|
||||||
|
className="w-full text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-0.5 min-w-[100px] flex-1">
|
||||||
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
|
{translate('::ListForms.Wizard.Step3.EnglishCaption')}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
value={item.englishCaption}
|
||||||
|
onChange={(e) => onEnglishCaptionChange(e.target.value)}
|
||||||
|
className="w-full text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Editor type select */}
|
{/* Editor type select */}
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
|
{translate('::ListForms.Wizard.Step3.EditorType')}
|
||||||
|
</span>
|
||||||
<select
|
<select
|
||||||
value={item.editorType}
|
value={item.editorType}
|
||||||
onChange={(e) => onEditorTypeChange(e.target.value)}
|
onChange={(e) => onEditorTypeChange(e.target.value)}
|
||||||
|
|
@ -218,30 +306,240 @@ function SortableItem({
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Turkish Caption */}
|
<div className="flex flex-row flex-wrap gap-1.5">
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5 min-w-[140px] w-1/2 flex-1">
|
||||||
<span className="text-[10px] text-gray-400 font-medium">{translate('::ListForms.Wizard.Step3.TurkishCaption')}</span>
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
|
{translate('::ListForms.Wizard.Step3.LookupDataSourceType')}
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
value={item.lookupDataSourceType}
|
||||||
|
onChange={(e) =>
|
||||||
|
onLookupDataSourceTypeChange(e.target.value as unknown as UiLookupDataSourceTypeEnum)
|
||||||
|
}
|
||||||
|
className="w-full text-xs h-7 px-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||||
|
>
|
||||||
|
{columnLookupDataSourceTypeListOptions.map((opt) => (
|
||||||
|
<option key={opt.value} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-0.5 min-w-[100px] flex-1">
|
||||||
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
|
{translate('::ListForms.Wizard.Step3.LookupValueExpression')}
|
||||||
|
</span>
|
||||||
<input
|
<input
|
||||||
value={item.turkishCaption}
|
value={item.valueExpr}
|
||||||
onChange={(e) => onTurkishCaptionChange(e.target.value)}
|
onChange={(e) => onValueExprChange(e.target.value)}
|
||||||
className="w-full text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
|
className="w-full text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* English Caption */}
|
<div className="flex flex-col gap-0.5 min-w-[100px] flex-1">
|
||||||
<div className="flex flex-col gap-0.5">
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
<span className="text-[10px] text-gray-400 font-medium">{translate('::ListForms.Wizard.Step3.EnglishCaption')}</span>
|
{translate('::ListForms.Wizard.Step3.LookupDisplayExpression')}
|
||||||
|
</span>
|
||||||
<input
|
<input
|
||||||
value={item.englishCaption}
|
value={item.displayExpr}
|
||||||
onChange={(e) => onEnglishCaptionChange(e.target.value)}
|
onChange={(e) => onDisplayExprChange(e.target.value)}
|
||||||
className="w-full text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
|
className="w-full text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* LookupQuery */}
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="text-[10px] text-gray-400 font-medium flex-1">
|
||||||
|
{translate('::ListForms.Wizard.Step3.LookupLookupQuery')}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsTablePickerOpen(true)}
|
||||||
|
className="flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded border border-indigo-200 dark:border-indigo-700 bg-indigo-50 dark:bg-indigo-900/20 text-indigo-600 dark:text-indigo-400 hover:bg-indigo-100 dark:hover:bg-indigo-800/40 transition-colors shrink-0"
|
||||||
|
>
|
||||||
|
<FaPlus className="text-[8px]" />
|
||||||
|
{translate('::ListForms.Wizard.Step3.GenerateFromTable') || 'Tablodan Oluştur'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
rows={2}
|
||||||
|
value={item.lookupQuery}
|
||||||
|
onChange={(e) => onLookupQueryChange(e.target.value)}
|
||||||
|
className="w-full text-xs px-1.5 py-1 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400 resize-none font-mono"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Table Picker Modal */}
|
||||||
|
{isTablePickerOpen && (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40"
|
||||||
|
onClick={() => setIsTablePickerOpen(false)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="bg-white dark:bg-gray-900 rounded-xl shadow-2xl w-full max-w-md mx-4 flex flex-col max-h-[80vh]"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{pickerStep === 'columns' && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setPickerStep('table')}
|
||||||
|
className="text-gray-400 hover:text-indigo-500 transition-colors"
|
||||||
|
>
|
||||||
|
<FaArrowLeft className="text-xs" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200">
|
||||||
|
{pickerStep === 'table'
|
||||||
|
? translate('::ListForms.Wizard.Step3.SelectTable') || 'Tablo Seç'
|
||||||
|
: (pickerTable?.tableName ?? '')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsTablePickerOpen(false)}
|
||||||
|
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200"
|
||||||
|
>
|
||||||
|
<FaTimes />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 1: Table list */}
|
||||||
|
{pickerStep === 'table' && (
|
||||||
|
<>
|
||||||
|
<div className="px-4 py-2 border-b border-gray-100 dark:border-gray-800">
|
||||||
|
<input
|
||||||
|
autoFocus
|
||||||
|
value={tableSearch}
|
||||||
|
onChange={(e) => setTableSearch(e.target.value)}
|
||||||
|
placeholder={translate('::Search') || 'Ara...'}
|
||||||
|
className="w-full text-xs px-2 py-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="overflow-y-auto flex-1 p-2">
|
||||||
|
{!dbObjects ? (
|
||||||
|
<div className="text-xs text-gray-400 text-center py-6">
|
||||||
|
{translate('::ListForms.Wizard.Step3.NoTablesAvailable') ||
|
||||||
|
'Tablo bulunamadı'}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
dbObjects.tables
|
||||||
|
.filter((t) => t.tableName.toLowerCase().includes(tableSearch.toLowerCase()))
|
||||||
|
.map((t) => (
|
||||||
|
<button
|
||||||
|
key={t.fullName}
|
||||||
|
type="button"
|
||||||
|
onClick={async () => {
|
||||||
|
setPickerTable(t)
|
||||||
|
setPickerStep('columns')
|
||||||
|
setPickerKeyCol('')
|
||||||
|
setPickerNameCol('')
|
||||||
|
setPickerColumns([])
|
||||||
|
setIsLoadingPickerColumns(true)
|
||||||
|
try {
|
||||||
|
const res = await sqlObjectManagerService.getTableColumns(
|
||||||
|
dsCode,
|
||||||
|
t.schemaName,
|
||||||
|
t.tableName,
|
||||||
|
)
|
||||||
|
setPickerColumns(res.data ?? [])
|
||||||
|
} catch {
|
||||||
|
setPickerColumns([])
|
||||||
|
} finally {
|
||||||
|
setIsLoadingPickerColumns(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="w-full text-left text-xs px-3 py-2 rounded hover:bg-indigo-50 dark:hover:bg-indigo-900/30 text-gray-700 dark:text-gray-200 font-mono transition-colors"
|
||||||
|
>
|
||||||
|
<span className="text-gray-400 mr-1">{t.schemaName}.</span>
|
||||||
|
{t.tableName}
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Step 2: Column selection */}
|
||||||
|
{pickerStep === 'columns' && (
|
||||||
|
<div className="flex flex-col gap-3 p-4">
|
||||||
|
{isLoadingPickerColumns ? (
|
||||||
|
<div className="text-xs text-gray-400 text-center py-6">
|
||||||
|
{translate('::Loading') || 'Yükleniyor...'}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-[10px] font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
Key Sütunu
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={pickerKeyCol}
|
||||||
|
onChange={(e) => setPickerKeyCol(e.target.value)}
|
||||||
|
className="w-full text-xs h-7 px-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||||
|
>
|
||||||
|
<option value="">-- Seçiniz --</option>
|
||||||
|
{pickerColumns.map((c) => (
|
||||||
|
<option key={c.columnName} value={c.columnName}>
|
||||||
|
{c.columnName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-[10px] font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
Name Sütunu
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={pickerNameCol}
|
||||||
|
onChange={(e) => setPickerNameCol(e.target.value)}
|
||||||
|
className="w-full text-xs h-7 px-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||||
|
>
|
||||||
|
<option value="">-- Seçiniz --</option>
|
||||||
|
{pickerColumns.map((c) => (
|
||||||
|
<option key={c.columnName} value={c.columnName}>
|
||||||
|
{c.columnName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{pickerKeyCol && pickerNameCol && (
|
||||||
|
<div className="rounded bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-[10px] font-mono text-gray-500 dark:text-gray-400 break-all">
|
||||||
|
{`SELECT "${pickerKeyCol}" AS "Key", "${pickerNameCol}" AS "Name" FROM "${pickerTable?.tableName}" ORDER BY "${pickerNameCol}";`}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={!pickerKeyCol || !pickerNameCol}
|
||||||
|
onClick={() => {
|
||||||
|
const q = `SELECT "${pickerKeyCol}" AS "Key", "${pickerNameCol}" AS "Name" FROM "${pickerTable?.tableName}" ORDER BY "${pickerNameCol}";`
|
||||||
|
onLookupQueryChange(q)
|
||||||
|
setIsTablePickerOpen(false)
|
||||||
|
}}
|
||||||
|
className="mt-1 w-full py-1.5 text-xs font-semibold rounded bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
Tamam
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Editor Options */}
|
{/* Editor Options */}
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<span className="text-[10px] text-gray-400 font-medium">{translate('::ListForms.Wizard.Step3.EditorOptions')}</span>
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
|
{translate('::ListForms.Wizard.Step3.EditorOptions')}
|
||||||
|
</span>
|
||||||
<input
|
<input
|
||||||
value={item.editorOptions}
|
value={item.editorOptions}
|
||||||
onChange={(e) => onEditorOptionsChange(e.target.value)}
|
onChange={(e) => onEditorOptionsChange(e.target.value)}
|
||||||
|
|
@ -252,7 +550,9 @@ function SortableItem({
|
||||||
|
|
||||||
{/* Editor Script */}
|
{/* Editor Script */}
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<span className="text-[10px] text-gray-400 font-medium">{translate('::ListForms.Wizard.Step3.EditorScript')}</span>
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
|
{translate('::ListForms.Wizard.Step3.EditorScript')}
|
||||||
|
</span>
|
||||||
<input
|
<input
|
||||||
value={item.editorScript}
|
value={item.editorScript}
|
||||||
onChange={(e) => onEditorScriptChange(e.target.value)}
|
onChange={(e) => onEditorScriptChange(e.target.value)}
|
||||||
|
|
@ -264,7 +564,9 @@ function SortableItem({
|
||||||
{/* Bottom row: ColSpan + Required */}
|
{/* Bottom row: ColSpan + Required */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span className="text-[10px] text-gray-400">{translate('::ListForms.Wizard.Step3.Span')}</span>
|
<span className="text-[10px] text-gray-400">
|
||||||
|
{translate('::ListForms.Wizard.Step3.Span')}
|
||||||
|
</span>
|
||||||
<select
|
<select
|
||||||
value={item.colSpan}
|
value={item.colSpan}
|
||||||
onChange={(e) => onColSpanChange(Number(e.target.value))}
|
onChange={(e) => onColSpanChange(Number(e.target.value))}
|
||||||
|
|
@ -284,7 +586,9 @@ function SortableItem({
|
||||||
onChange={(e) => onRequiredChange(e.target.checked)}
|
onChange={(e) => onRequiredChange(e.target.checked)}
|
||||||
className="w-3 h-3 accent-red-500"
|
className="w-3 h-3 accent-red-500"
|
||||||
/>
|
/>
|
||||||
<span className="text-[10px] text-gray-400">{translate('::ListForms.Wizard.Step3.Required') || 'Required'}</span>
|
<span className="text-[10px] text-gray-400">
|
||||||
|
{translate('::ListForms.Wizard.Step3.Required') || 'Required'}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -295,6 +599,8 @@ interface GroupCardProps {
|
||||||
group: WizardGroup
|
group: WizardGroup
|
||||||
isOver: boolean
|
isOver: boolean
|
||||||
hasAvailable: boolean
|
hasAvailable: boolean
|
||||||
|
dbObjects: SqlObjectExplorerDto | null
|
||||||
|
dsCode: string
|
||||||
onCaptionChange: (val: string) => void
|
onCaptionChange: (val: string) => void
|
||||||
onColCountChange: (val: number) => void
|
onColCountChange: (val: number) => void
|
||||||
onItemChange: (itemId: string, patch: Partial<WizardGroupItem>) => void
|
onItemChange: (itemId: string, patch: Partial<WizardGroupItem>) => void
|
||||||
|
|
@ -307,6 +613,8 @@ function GroupCard({
|
||||||
group,
|
group,
|
||||||
isOver,
|
isOver,
|
||||||
hasAvailable,
|
hasAvailable,
|
||||||
|
dbObjects,
|
||||||
|
dsCode,
|
||||||
onCaptionChange,
|
onCaptionChange,
|
||||||
onColCountChange,
|
onColCountChange,
|
||||||
onItemChange,
|
onItemChange,
|
||||||
|
|
@ -328,17 +636,19 @@ function GroupCard({
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{/* Group Header */}
|
{/* Group Header */}
|
||||||
<div className="flex items-center gap-2 px-3 pt-3 pb-2">
|
<div className="flex flex-wrap items-center gap-2 px-3 pt-3 pb-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={group.caption}
|
value={group.caption}
|
||||||
onChange={(e) => onCaptionChange(e.target.value)}
|
onChange={(e) => onCaptionChange(e.target.value)}
|
||||||
placeholder="Group caption…"
|
placeholder="Group caption…"
|
||||||
className="flex-1 text-sm font-semibold h-7 px-2 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-100 focus:outline-none focus:border-indigo-400"
|
className="flex-1 min-w-[120px] text-sm font-semibold h-7 px-2 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-100 focus:outline-none focus:border-indigo-400"
|
||||||
/>
|
/>
|
||||||
{/* ColCount */}
|
{/* ColCount */}
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
<span className="text-xs text-gray-400">{translate('::ListForms.Wizard.Step3.Cols') || 'Cols:'}</span>
|
<span className="text-xs text-gray-400">
|
||||||
|
{translate('::ListForms.Wizard.Step3.Cols') || 'Cols:'}
|
||||||
|
</span>
|
||||||
{[1, 2, 3, 4].map((n) => (
|
{[1, 2, 3, 4].map((n) => (
|
||||||
<button
|
<button
|
||||||
key={n}
|
key={n}
|
||||||
|
|
@ -359,7 +669,10 @@ function GroupCard({
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onAddAll}
|
onClick={onAddAll}
|
||||||
className="flex items-center gap-1 h-6 px-2 text-[11px] font-medium rounded border border-indigo-200 dark:border-indigo-700 bg-indigo-50 dark:bg-indigo-900/20 text-indigo-600 dark:text-indigo-400 hover:bg-indigo-100 dark:hover:bg-indigo-900/40 transition-colors shrink-0"
|
className="flex items-center gap-1 h-6 px-2 text-[11px] font-medium rounded border border-indigo-200 dark:border-indigo-700 bg-indigo-50 dark:bg-indigo-900/20 text-indigo-600 dark:text-indigo-400 hover:bg-indigo-100 dark:hover:bg-indigo-900/40 transition-colors shrink-0"
|
||||||
title={translate('::ListForms.Wizard.Step3.AddAllToGroupTitle') || 'Tüm mevcut sütunları bu gruba ekle'}
|
title={
|
||||||
|
translate('::ListForms.Wizard.Step3.AddAllToGroupTitle') ||
|
||||||
|
'Tüm mevcut sütunları bu gruba ekle'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<FaArrowRight className="text-[9px]" />
|
<FaArrowRight className="text-[9px]" />
|
||||||
{translate('::ListForms.Wizard.Step3.AddAll') || 'Tümünü Ekle'}
|
{translate('::ListForms.Wizard.Step3.AddAll') || 'Tümünü Ekle'}
|
||||||
|
|
@ -400,6 +713,9 @@ function GroupCard({
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
groupColCount={group.colCount}
|
groupColCount={group.colCount}
|
||||||
|
dbObjects={dbObjects}
|
||||||
|
dsCode={dsCode}
|
||||||
|
onCaptionNameChange={(val) => onItemChange(item.id, { captionName: val })}
|
||||||
onTurkishCaptionChange={(val) => onItemChange(item.id, { turkishCaption: val })}
|
onTurkishCaptionChange={(val) => onItemChange(item.id, { turkishCaption: val })}
|
||||||
onEnglishCaptionChange={(val) => onItemChange(item.id, { englishCaption: val })}
|
onEnglishCaptionChange={(val) => onItemChange(item.id, { englishCaption: val })}
|
||||||
onEditorTypeChange={(val) => onItemChange(item.id, { editorType: val })}
|
onEditorTypeChange={(val) => onItemChange(item.id, { editorType: val })}
|
||||||
|
|
@ -407,6 +723,12 @@ function GroupCard({
|
||||||
onEditorScriptChange={(val) => onItemChange(item.id, { editorScript: val })}
|
onEditorScriptChange={(val) => onItemChange(item.id, { editorScript: val })}
|
||||||
onColSpanChange={(val) => onItemChange(item.id, { colSpan: val })}
|
onColSpanChange={(val) => onItemChange(item.id, { colSpan: val })}
|
||||||
onRequiredChange={(val) => onItemChange(item.id, { isRequired: val })}
|
onRequiredChange={(val) => onItemChange(item.id, { isRequired: val })}
|
||||||
|
onLookupDataSourceTypeChange={(val: UiLookupDataSourceTypeEnum) =>
|
||||||
|
onItemChange(item.id, { lookupDataSourceType: val })
|
||||||
|
}
|
||||||
|
onDisplayExprChange={(val) => onItemChange(item.id, { displayExpr: val })}
|
||||||
|
onValueExprChange={(val) => onItemChange(item.id, { valueExpr: val })}
|
||||||
|
onLookupQueryChange={(val) => onItemChange(item.id, { lookupQuery: val })}
|
||||||
onRemove={() => onRemoveItem(item.id)}
|
onRemove={() => onRemoveItem(item.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
@ -423,6 +745,9 @@ const WizardStep3 = ({
|
||||||
selectCommandColumns,
|
selectCommandColumns,
|
||||||
groups,
|
groups,
|
||||||
onGroupsChange,
|
onGroupsChange,
|
||||||
|
dbObjects,
|
||||||
|
isLoadingDbObjects: _isLoadingDbObjects,
|
||||||
|
dsCode,
|
||||||
translate,
|
translate,
|
||||||
onBack,
|
onBack,
|
||||||
onNext,
|
onNext,
|
||||||
|
|
@ -662,7 +987,8 @@ const WizardStep3 = ({
|
||||||
const validationMsg = hasNoGroups
|
const validationMsg = hasNoGroups
|
||||||
? translate('::ListForms.Wizard.Step3.AtLeastOneGroup') || 'En az bir grup eklemelisiniz.'
|
? translate('::ListForms.Wizard.Step3.AtLeastOneGroup') || 'En az bir grup eklemelisiniz.'
|
||||||
: hasEmptyGroup
|
: hasEmptyGroup
|
||||||
? translate('::ListForms.Wizard.Step3.AtLeastOneColumn') || 'Her gruba en az bir sütun eklemelisiniz.'
|
? translate('::ListForms.Wizard.Step3.AtLeastOneColumn') ||
|
||||||
|
'Her gruba en az bir sütun eklemelisiniz.'
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -673,10 +999,10 @@ const WizardStep3 = ({
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
>
|
>
|
||||||
<div className="pb-20">
|
<div className="pb-20">
|
||||||
<div className="flex gap-4">
|
<div className="flex flex-col lg:flex-row gap-4">
|
||||||
{/* ── Left: Available Columns ─────────────────────────────────── */}
|
{/* ── Left: Available Columns ─────────────────────────────────── */}
|
||||||
<div className="w-72 shrink-0">
|
<div className="w-full lg:w-64 lg:shrink-0">
|
||||||
<div className="sticky top-4">
|
<div className="lg:sticky lg:top-4">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">
|
<span className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">
|
||||||
{translate('::App.Listform.ListformField.Column')}
|
{translate('::App.Listform.ListformField.Column')}
|
||||||
|
|
@ -685,10 +1011,11 @@ const WizardStep3 = ({
|
||||||
{availableColumns.length}/{selectedColumns.size}
|
{availableColumns.length}/{selectedColumns.size}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1.5 max-h-[calc(100vh-280px)] overflow-y-auto pr-1">
|
<div className="flex flex-row flex-wrap lg:flex-col gap-1.5 max-h-40 lg:max-h-[calc(100vh-280px)] overflow-y-auto pr-1">
|
||||||
{availableColumns.length === 0 ? (
|
{availableColumns.length === 0 ? (
|
||||||
<div className="text-xs text-gray-300 dark:text-gray-600 py-4 text-center select-none">
|
<div className="text-xs text-gray-300 dark:text-gray-600 py-4 text-center select-none w-full">
|
||||||
{translate('::ListForms.Wizard.Step3.AllColumnsAdded') || 'Tüm sütunlar gruplara eklendi'}
|
{translate('::ListForms.Wizard.Step3.AllColumnsAdded') ||
|
||||||
|
'Tüm sütunlar gruplara eklendi'}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
availableColumns.map((col) => <AvailableColumnChip key={col} colName={col} />)
|
availableColumns.map((col) => <AvailableColumnChip key={col} colName={col} />)
|
||||||
|
|
@ -701,7 +1028,8 @@ const WizardStep3 = ({
|
||||||
<div className="flex-1 flex flex-col gap-3">
|
<div className="flex-1 flex flex-col gap-3">
|
||||||
{groups.length === 0 && (
|
{groups.length === 0 && (
|
||||||
<div className="rounded-xl border-2 border-dashed border-gray-200 dark:border-gray-700 flex items-center justify-center h-36 text-sm text-gray-300 dark:text-gray-600 select-none">
|
<div className="rounded-xl border-2 border-dashed border-gray-200 dark:border-gray-700 flex items-center justify-center h-36 text-sm text-gray-300 dark:text-gray-600 select-none">
|
||||||
{translate('::ListForms.Wizard.Step3.NoGroupsYet') || 'Henüz grup yok — aşağıdan grup ekleyin'}
|
{translate('::ListForms.Wizard.Step3.NoGroupsYet') ||
|
||||||
|
'Henüz grup yok — aşağıdan grup ekleyin'}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -711,6 +1039,8 @@ const WizardStep3 = ({
|
||||||
group={group}
|
group={group}
|
||||||
isOver={overGroupId === group.id}
|
isOver={overGroupId === group.id}
|
||||||
hasAvailable={availableColumns.length > 0}
|
hasAvailable={availableColumns.length > 0}
|
||||||
|
dbObjects={dbObjects}
|
||||||
|
dsCode={dsCode}
|
||||||
onCaptionChange={(val) => updateGroup(group.id, { caption: val })}
|
onCaptionChange={(val) => updateGroup(group.id, { caption: val })}
|
||||||
onColCountChange={(val) => updateGroup(group.id, { colCount: val })}
|
onColCountChange={(val) => updateGroup(group.id, { colCount: val })}
|
||||||
onItemChange={(itemId, patch) => updateItem(group.id, itemId, patch)}
|
onItemChange={(itemId, patch) => updateItem(group.id, itemId, patch)}
|
||||||
|
|
@ -793,19 +1123,34 @@ const WizardStep3 = ({
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{/* ── Fixed Footer ─────────────────────────────────────────────────── */}
|
{/* ── 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-0 h-16 flex items-center">
|
<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-4 py-2 min-h-16 flex items-center">
|
||||||
<div className="flex items-center gap-3 w-full">
|
<div className="flex flex-wrap items-center gap-2 w-full">
|
||||||
<Button size='sm' variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
|
<Button size="sm" variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
|
||||||
{translate('::Back') || 'Back'}
|
{translate('::Back') || 'Back'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size='sm' variant="default" type="button" icon={<FaCode />} onClick={() => setIsHelperOpen(true)}>
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="default"
|
||||||
|
type="button"
|
||||||
|
icon={<FaCode />}
|
||||||
|
onClick={() => setIsHelperOpen(true)}
|
||||||
|
>
|
||||||
{translate('::Helper Codes') || 'Helper Codes'}
|
{translate('::Helper Codes') || 'Helper Codes'}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex-1 flex items-center justify-end gap-3">
|
<div className="flex-1 flex items-center justify-end gap-3">
|
||||||
{!canProceed && (
|
{!canProceed && (
|
||||||
<span className="text-xs text-amber-600 dark:text-amber-400 font-medium">⚠ {validationMsg}</span>
|
<span className="text-xs text-amber-600 dark:text-amber-400 font-medium">
|
||||||
|
⚠ {validationMsg}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
<Button size='sm' variant="solid" type="button" icon={<FaArrowRight />} disabled={!canProceed} onClick={onNext}>
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="solid"
|
||||||
|
type="button"
|
||||||
|
icon={<FaArrowRight />}
|
||||||
|
disabled={!canProceed}
|
||||||
|
onClick={onNext}
|
||||||
|
>
|
||||||
{translate('::Next') || 'Next'}
|
{translate('::Next') || 'Next'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
import { Container } from '@/components/shared'
|
import { Container } from '@/components/shared'
|
||||||
import { Button, Checkbox, FormContainer, FormItem, Input, Select } from '@/components/ui'
|
import { Button, Checkbox, FormContainer, FormItem, Input, Select } from '@/components/ui'
|
||||||
import { ColumnFormatEditDto, ListFormFieldEditTabs } from '@/proxy/admin/list-form-field/models'
|
import { ColumnFormatEditDto, ListFormFieldEditTabs } from '@/proxy/admin/list-form-field/models'
|
||||||
|
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
|
||||||
|
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
||||||
import { SelectBoxOption } from '@/types/shared'
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import { useStoreState } from '@/store/store'
|
||||||
import { Field, FieldProps, Form, Formik } from 'formik'
|
import { Field, FieldProps, Form, Formik } from 'formik'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { FaArrowLeft, FaPlus, FaTimes } from 'react-icons/fa'
|
||||||
import { number, object, string } from 'yup'
|
import { number, object, string } from 'yup'
|
||||||
import { cascadeFilterOperator, columnLookupDataSourceTypeListOptions } from '../options'
|
import { cascadeFilterOperator, columnLookupDataSourceTypeListOptions } from '../options'
|
||||||
import { FormFieldEditProps } from './FormFields'
|
import { FormFieldEditProps } from './FormFields'
|
||||||
|
|
@ -26,6 +31,177 @@ const schema = object().shape({
|
||||||
.required(),
|
.required(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ─── Table Picker Modal ───────────────────────────────────────────────────────
|
||||||
|
function TablePickerModal({
|
||||||
|
dsCode,
|
||||||
|
dbObjects,
|
||||||
|
onSelect,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
dsCode: string
|
||||||
|
dbObjects: SqlObjectExplorerDto | null
|
||||||
|
onSelect: (query: string) => void
|
||||||
|
onClose: () => void
|
||||||
|
}) {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
const [step, setStep] = useState<'table' | 'columns'>('table')
|
||||||
|
const [tableSearch, setTableSearch] = useState('')
|
||||||
|
const [pickerTable, setPickerTable] = useState<{ schemaName: string; tableName: string } | null>(null)
|
||||||
|
const [pickerColumns, setPickerColumns] = useState<DatabaseColumnDto[]>([])
|
||||||
|
const [isLoadingColumns, setIsLoadingColumns] = useState(false)
|
||||||
|
const [keyCol, setKeyCol] = useState('')
|
||||||
|
const [nameCol, setNameCol] = useState('')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="bg-white dark:bg-gray-900 rounded-xl shadow-2xl w-full max-w-md mx-4 flex flex-col max-h-[80vh]"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{step === 'columns' && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setStep('table')}
|
||||||
|
className="text-gray-400 hover:text-indigo-500 transition-colors"
|
||||||
|
>
|
||||||
|
<FaArrowLeft className="text-xs" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200">
|
||||||
|
{step === 'table'
|
||||||
|
? translate('::ListForms.Wizard.Step3.SelectTable') || 'Tablo Seç'
|
||||||
|
: pickerTable?.tableName ?? ''}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200"
|
||||||
|
>
|
||||||
|
<FaTimes />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 1: Table list */}
|
||||||
|
{step === 'table' && (
|
||||||
|
<>
|
||||||
|
<div className="px-4 py-2 border-b border-gray-100 dark:border-gray-800">
|
||||||
|
<input
|
||||||
|
autoFocus
|
||||||
|
value={tableSearch}
|
||||||
|
onChange={(e) => setTableSearch(e.target.value)}
|
||||||
|
placeholder={translate('::Search') || 'Ara...'}
|
||||||
|
className="w-full text-xs px-2 py-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="overflow-y-auto flex-1 p-2">
|
||||||
|
{!dbObjects ? (
|
||||||
|
<div className="text-xs text-gray-400 text-center py-6">
|
||||||
|
{translate('::ListForms.Wizard.Step3.NoTablesAvailable') || 'Tablo bulunamadı'}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
dbObjects.tables
|
||||||
|
.filter((t) => t.tableName.toLowerCase().includes(tableSearch.toLowerCase()))
|
||||||
|
.map((t) => (
|
||||||
|
<button
|
||||||
|
key={t.fullName}
|
||||||
|
type="button"
|
||||||
|
onClick={async () => {
|
||||||
|
setPickerTable(t)
|
||||||
|
setStep('columns')
|
||||||
|
setKeyCol('')
|
||||||
|
setNameCol('')
|
||||||
|
setPickerColumns([])
|
||||||
|
setIsLoadingColumns(true)
|
||||||
|
try {
|
||||||
|
const res = await sqlObjectManagerService.getTableColumns(dsCode, t.schemaName, t.tableName)
|
||||||
|
setPickerColumns(res.data ?? [])
|
||||||
|
} catch {
|
||||||
|
setPickerColumns([])
|
||||||
|
} finally {
|
||||||
|
setIsLoadingColumns(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="w-full text-left text-xs px-3 py-2 rounded hover:bg-indigo-50 dark:hover:bg-indigo-900/30 text-gray-700 dark:text-gray-200 font-mono transition-colors"
|
||||||
|
>
|
||||||
|
<span className="text-gray-400 mr-1">{t.schemaName}.</span>
|
||||||
|
{t.tableName}
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Step 2: Column selection */}
|
||||||
|
{step === 'columns' && (
|
||||||
|
<div className="flex flex-col gap-3 p-4">
|
||||||
|
{isLoadingColumns ? (
|
||||||
|
<div className="text-xs text-gray-400 text-center py-6">
|
||||||
|
{translate('::Loading') || 'Yükleniyor...'}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-[11px] font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
Key Sütunu
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={keyCol}
|
||||||
|
onChange={(e) => setKeyCol(e.target.value)}
|
||||||
|
className="w-full text-xs h-8 px-2 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||||
|
>
|
||||||
|
<option value="">-- Seçiniz --</option>
|
||||||
|
{pickerColumns.map((c) => (
|
||||||
|
<option key={c.columnName} value={c.columnName}>{c.columnName}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-[11px] font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
Name Sütunu
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={nameCol}
|
||||||
|
onChange={(e) => setNameCol(e.target.value)}
|
||||||
|
className="w-full text-xs h-8 px-2 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||||
|
>
|
||||||
|
<option value="">-- Seçiniz --</option>
|
||||||
|
{pickerColumns.map((c) => (
|
||||||
|
<option key={c.columnName} value={c.columnName}>{c.columnName}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{keyCol && nameCol && (
|
||||||
|
<div className="rounded bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-[10px] font-mono text-gray-500 dark:text-gray-400 break-all">
|
||||||
|
{`SELECT "${keyCol}" AS "Key", "${nameCol}" AS "Name" FROM "${pickerTable?.tableName}" ORDER BY "${nameCol}";`}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={!keyCol || !nameCol}
|
||||||
|
onClick={() => {
|
||||||
|
onSelect(`SELECT "${keyCol}" AS "Key", "${nameCol}" AS "Name" FROM "${pickerTable?.tableName}" ORDER BY "${nameCol}";`)
|
||||||
|
}}
|
||||||
|
className="mt-1 w-full py-1.5 text-xs font-semibold rounded bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
Tamam
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function getNormalizedInitialValues(initialValues: ColumnFormatEditDto) {
|
function getNormalizedInitialValues(initialValues: ColumnFormatEditDto) {
|
||||||
return {
|
return {
|
||||||
...initialValues,
|
...initialValues,
|
||||||
|
|
@ -52,6 +228,22 @@ function FormFieldTabLookup({
|
||||||
initialValues: ColumnFormatEditDto
|
initialValues: ColumnFormatEditDto
|
||||||
} & FormFieldEditProps) {
|
} & FormFieldEditProps) {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
const listFormValues = useStoreState((s) => s.admin.lists.values)
|
||||||
|
const dsCode = listFormValues?.dataSourceCode ?? ''
|
||||||
|
const [dbObjects, setDbObjects] = useState<SqlObjectExplorerDto | null>(null)
|
||||||
|
const [isTablePickerOpen, setIsTablePickerOpen] = useState(false)
|
||||||
|
|
||||||
|
const openTablePicker = async () => {
|
||||||
|
if (dsCode && !dbObjects) {
|
||||||
|
try {
|
||||||
|
const res = await sqlObjectManagerService.getAllObjects(dsCode)
|
||||||
|
setDbObjects(res.data)
|
||||||
|
} catch {
|
||||||
|
setDbObjects(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setIsTablePickerOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="grid xl:grid-cols-2">
|
<Container className="grid xl:grid-cols-2">
|
||||||
|
|
@ -94,17 +286,42 @@ function FormFieldTabLookup({
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.ListFormFieldEdit.LookupLookupQuery')}
|
label={translate('::ListForms.ListFormFieldEdit.LookupLookupQuery')}
|
||||||
|
extra={
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={openTablePicker}
|
||||||
|
className="ml-2 flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded border border-indigo-200 dark:border-indigo-700 bg-indigo-50 dark:bg-indigo-900/20 text-indigo-600 dark:text-indigo-400 hover:bg-indigo-100 dark:hover:bg-indigo-800/40 transition-colors"
|
||||||
|
>
|
||||||
|
<FaPlus className="text-[8px]" />
|
||||||
|
{translate('::ListForms.Wizard.Step3.GenerateFromTable') || 'Tablodan Oluştur'}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
invalid={errors.lookupDto?.lookupQuery && touched.lookupDto?.lookupQuery}
|
invalid={errors.lookupDto?.lookupQuery && touched.lookupDto?.lookupQuery}
|
||||||
errorMessage={errors.lookupDto?.lookupQuery}
|
errorMessage={errors.lookupDto?.lookupQuery}
|
||||||
>
|
>
|
||||||
<Field
|
<Field name="lookupDto.lookupQuery">
|
||||||
type="text"
|
{({ field, form }: FieldProps<string>) => (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
textArea
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
name="lookupDto.lookupQuery"
|
|
||||||
placeholder={translate('::ListForms.ListFormFieldEdit.LookupLookupQuery')}
|
placeholder={translate('::ListForms.ListFormFieldEdit.LookupLookupQuery')}
|
||||||
component={Input}
|
|
||||||
textArea={true}
|
|
||||||
/>
|
/>
|
||||||
|
{isTablePickerOpen && (
|
||||||
|
<TablePickerModal
|
||||||
|
dsCode={dsCode}
|
||||||
|
dbObjects={dbObjects}
|
||||||
|
onSelect={(q) => {
|
||||||
|
form.setFieldValue(field.name, q)
|
||||||
|
setIsTablePickerOpen(false)
|
||||||
|
}}
|
||||||
|
onClose={() => setIsTablePickerOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,6 @@ export function MenuAddDialog({
|
||||||
}, [isOpen, initialParentCode, initialOrder])
|
}, [isOpen, initialParentCode, initialOrder])
|
||||||
|
|
||||||
const shortNameRequired = !form.parentCode.trim()
|
const shortNameRequired = !form.parentCode.trim()
|
||||||
console.log(form.parentCode.length)
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!form.code.trim() || !form.menuTextEn.trim()) return
|
if (!form.code.trim() || !form.menuTextEn.trim()) return
|
||||||
if (shortNameRequired && !form.shortName.trim()) return
|
if (shortNameRequired && !form.shortName.trim()) return
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue