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 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 LanguageTextMenuTr { get; set; }
|
||||
public string LanguageTextTitleEn { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
using System.Data;
|
||||
using Sozsoft.Platform.Enums;
|
||||
|
||||
namespace Sozsoft.Platform.ListForms;
|
||||
|
||||
public class WizardColumnItemInputDto
|
||||
{
|
||||
public string DataField { get; set; }
|
||||
public string CaptionName { get; set; }
|
||||
public string EditorType { get; set; }
|
||||
public string EditorOptions { get; set; }
|
||||
public string EditorScript { get; set; }
|
||||
|
|
@ -13,4 +15,8 @@ public class WizardColumnItemInputDto
|
|||
public DbType DbSourceType { get; set; } = DbType.String;
|
||||
public string TurkishCaption { 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -16,6 +17,7 @@ using static Sozsoft.Platform.PlatformConsts;
|
|||
using System.Data;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Sozsoft.Languages;
|
||||
using Sozsoft.Platform.DynamicData;
|
||||
|
||||
namespace Sozsoft.Platform.ListForms;
|
||||
|
||||
|
|
@ -30,7 +32,8 @@ public class ListFormWizardAppService(
|
|||
IRepository<Menu, Guid> repoMenu,
|
||||
IPermissionGrantRepository permissionGrantRepository,
|
||||
IConfiguration configuration,
|
||||
LanguageTextAppService languageTextAppService
|
||||
LanguageTextAppService languageTextAppService,
|
||||
IDynamicDataManager dynamicDataManager
|
||||
) : PlatformAppService(), IListFormWizardAppService
|
||||
{
|
||||
private readonly IRepository<ListForm, Guid> repoListForm = repoListForm;
|
||||
|
|
@ -44,6 +47,7 @@ public class ListFormWizardAppService(
|
|||
private readonly IPermissionGrantRepository permissionGrantRepository = permissionGrantRepository;
|
||||
private readonly IConfiguration _configuration = configuration;
|
||||
private readonly LanguageTextAppService _languageTextAppService = languageTextAppService;
|
||||
private readonly IDynamicDataManager _dynamicDataManager = dynamicDataManager;
|
||||
private readonly string cultureNameDefault = PlatformConsts.DefaultLanguage;
|
||||
|
||||
[UnitOfWork]
|
||||
|
|
@ -194,6 +198,10 @@ public class ListFormWizardAppService(
|
|||
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
|
||||
{
|
||||
ListFormType = ListFormTypeEnum.List,
|
||||
|
|
@ -201,7 +209,7 @@ public class ListFormWizardAppService(
|
|||
ExportJson = WizardConsts.DefaultExportJson,
|
||||
IsSubForm = false,
|
||||
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,
|
||||
ListFormCode = input.ListFormCode,
|
||||
Name = nameLangKey,
|
||||
|
|
@ -215,7 +223,7 @@ public class ListFormWizardAppService(
|
|||
SelectCommand = input.SelectCommand,
|
||||
KeyFieldName = input.KeyFieldName,
|
||||
KeyFieldDbSourceType = input.KeyFieldDbSourceType,
|
||||
DefaultFilter = WizardConsts.DefaultFilterJson,
|
||||
DefaultFilter = isDeleted ? WizardConsts.DefaultFilterJson : null,
|
||||
SortMode = GridOptions.SortModeSingle,
|
||||
FilterRowJson = WizardConsts.DefaultFilterRowJson,
|
||||
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
|
||||
|
|
@ -224,9 +232,9 @@ public class ListFormWizardAppService(
|
|||
SelectionJson = WizardConsts.DefaultSelectionSingleJson,
|
||||
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
||||
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
||||
DeleteCommand = WizardConsts.DefaultDeleteCommand(input.SelectCommand),
|
||||
DeleteFieldsDefaultValueJson = WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType),
|
||||
InsertFieldsDefaultValueJson = WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType),
|
||||
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
||||
DeleteFieldsDefaultValueJson = isDeleted ? WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType),
|
||||
InsertFieldsDefaultValueJson = isCreated ? WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType) : WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType),
|
||||
PagerOptionJson = WizardConsts.DefaultPagerOptionJson,
|
||||
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,
|
||||
|
|
@ -234,18 +242,16 @@ public class ListFormWizardAppService(
|
|||
|
||||
// ListFormField - each item in each group becomes a visible field record
|
||||
var fieldOrder = 0;
|
||||
var captionName = string.Empty;
|
||||
foreach (var group in input.Groups)
|
||||
{
|
||||
foreach (var item in group.Items)
|
||||
{
|
||||
fieldOrder++;
|
||||
captionName = $"App.Listform.ListformField.{item.DataField}";
|
||||
await repoListFormField.InsertAsync(new ListFormField
|
||||
{
|
||||
ListFormCode = input.ListFormCode,
|
||||
FieldName = item.DataField,
|
||||
CaptionName = captionName,
|
||||
CaptionName = item.CaptionName,
|
||||
Visible = item.DataField != input.KeyFieldName,
|
||||
IsActive = true,
|
||||
AllowSearch = true,
|
||||
|
|
@ -256,9 +262,10 @@ public class ListFormWizardAppService(
|
|||
ColumnCustomizationJson = WizardConsts.DefaultColumnCustomizationJson,
|
||||
ColumnFilterJson = WizardConsts.DefaultColumnFilteringJson,
|
||||
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)
|
||||
{
|
||||
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 langKey = await repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == res && a.Key == key)
|
||||
?? await repoLangKey.InsertAsync(new LanguageKey { ResourceName = res, Key = key }, autoSave: true);
|
||||
|
||||
var textQueryable = await repoLangText.GetQueryableAsync();
|
||||
var existingTexts = await AsyncExecuter.ToListAsync(
|
||||
textQueryable.Where(a => a.ResourceName == res && a.Key == langKey.Key)
|
||||
);
|
||||
var existingTexts = await repoLangText.GetListAsync(a => a.ResourceName == res && a.Key == langKey.Key);
|
||||
|
||||
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 existingEn = existingTexts.FirstOrDefault(a => a.CultureName == cultureNameDefault);
|
||||
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)
|
||||
?? await repoLangText.InsertAsync(new LanguageText { ResourceName = res, Key = langKey.Key, CultureName = LanguageCodes.Tr, Value = textTr }, autoSave: false);
|
||||
var existingTr = existingTexts.FirstOrDefault(a => a.CultureName == LanguageCodes.Tr);
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16280,6 +16280,42 @@
|
|||
"en": "Columns load after selecting a Select Command",
|
||||
"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",
|
||||
"key": "ListForms.Wizard.Step3.TurkishCaption",
|
||||
|
|
@ -16292,6 +16328,12 @@
|
|||
"en": "English Caption",
|
||||
"tr": "İngilizce Başlık"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "ListForms.Wizard.Step3.EditorType",
|
||||
"en": "Editor Type",
|
||||
"tr": "Editör Türü"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "ListForms.Wizard.Step3.EditorOptions",
|
||||
|
|
|
|||
|
|
@ -78,14 +78,14 @@ public static class WizardConsts
|
|||
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,
|
||||
Pivot = true,
|
||||
Chart = true,
|
||||
Tree = true,
|
||||
Gantt = true,
|
||||
Scheduler = true,
|
||||
Grid = Grid,
|
||||
Pivot = Pivot,
|
||||
Chart = Chart,
|
||||
Tree = Tree,
|
||||
Gantt = Gantt,
|
||||
Scheduler = Scheduler,
|
||||
DefaultLayout = DefaultLayout,
|
||||
});
|
||||
|
||||
|
|
@ -153,6 +153,14 @@ public static class WizardConsts
|
|||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
Visible = true,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ BEGIN
|
|||
[DeletionTime] datetime2 NULL,
|
||||
[DeleterId] uniqueidentifier NULL,
|
||||
[TenantId] uniqueidentifier NULL,
|
||||
[BranchId] uniqueidentifier NOT NULL,
|
||||
[BranchId] uniqueidentifier NULL,
|
||||
[Name] nvarchar(64) NOT NULL,
|
||||
[ParentId] uniqueidentifier NULL,
|
||||
CONSTRAINT [PK_Adm_T_Department] PRIMARY KEY CLUSTERED
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@ export interface ListFormWizardDto {
|
|||
allowDeleting: boolean
|
||||
confirmDelete: boolean
|
||||
allowDetail: boolean
|
||||
|
||||
defaultLayout: ListViewLayoutType
|
||||
grid: boolean
|
||||
pivot: boolean
|
||||
tree: boolean
|
||||
chart: boolean
|
||||
gantt: boolean
|
||||
scheduler: boolean
|
||||
|
||||
languageTextMenuEn: string
|
||||
languageTextMenuTr: string
|
||||
languageTextTitleEn: string
|
||||
|
|
|
|||
|
|
@ -43,6 +43,13 @@ const initialValues: ListFormWizardDto = {
|
|||
allowDeleting: true,
|
||||
confirmDelete: true,
|
||||
allowDetail: false,
|
||||
defaultLayout: 'grid',
|
||||
grid: true,
|
||||
pivot: true,
|
||||
tree: true,
|
||||
chart: true,
|
||||
gantt: true,
|
||||
scheduler: true,
|
||||
languageTextMenuEn: '',
|
||||
languageTextMenuTr: '',
|
||||
languageTextTitleEn: '',
|
||||
|
|
@ -148,8 +155,7 @@ const Wizard = () => {
|
|||
'deleterid',
|
||||
])
|
||||
|
||||
const isAuditColumn = (columnName: string) =>
|
||||
AUDIT_COLUMNS.has(columnName.toLowerCase())
|
||||
const isAuditColumn = (columnName: string) => AUDIT_COLUMNS.has(columnName.toLowerCase())
|
||||
|
||||
const loadColumns = async (dsCode: string, schema: string, name: string) => {
|
||||
if (!dsCode || !name) {
|
||||
|
|
@ -268,7 +274,6 @@ const Wizard = () => {
|
|||
.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2')
|
||||
.trim()
|
||||
|
||||
|
||||
const handleWizardNameChange = (name: string) => {
|
||||
const spacedLabel = toSpacedLabel(name)
|
||||
const derived = deriveListFormCode(name)
|
||||
|
|
@ -379,6 +384,11 @@ const Wizard = () => {
|
|||
dbSourceType: col ? sqlDataTypeToDbType(col.dataType) : 12,
|
||||
turkishCaption: item.turkishCaption,
|
||||
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)
|
||||
|
||||
// ✅ 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
|
||||
toast.push(
|
||||
|
|
@ -438,7 +450,10 @@ const Wizard = () => {
|
|||
await getConfig(true)
|
||||
|
||||
// 🔴 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)
|
||||
toast.push(
|
||||
|
|
@ -518,6 +533,9 @@ const Wizard = () => {
|
|||
selectCommandColumns={selectCommandColumns}
|
||||
groups={editingGroups}
|
||||
onGroupsChange={setEditingGroups}
|
||||
dbObjects={dbObjects}
|
||||
isLoadingDbObjects={isLoadingDbObjects}
|
||||
dsCode={currentDataSource}
|
||||
translate={translate}
|
||||
onBack={() => setCurrentStep(1)}
|
||||
onNext={() => setCurrentStep(3)}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@ import { ListFormWizardDto } from '@/proxy/admin/list-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, sqlDataTypeToDbType } from './edit/options'
|
||||
import {
|
||||
dbSourceTypeOptions,
|
||||
listFormDefaultLayoutOptions,
|
||||
selectCommandTypeOptions,
|
||||
sqlDataTypeToDbType,
|
||||
} from './edit/options'
|
||||
import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
|
||||
import CreatableSelect from 'react-select/creatable'
|
||||
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
|
||||
|
|
@ -395,6 +400,121 @@ const WizardStep2 = ({
|
|||
</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">
|
||||
<FormItem
|
||||
label={translate('::ListForms.Wizard.Step2.TitleTextEnglish')}
|
||||
|
|
@ -474,14 +594,14 @@ const WizardStep2 = ({
|
|||
selectCommandColumns.length > 0 ? (
|
||||
<div className="flex items-center gap-2 ml-3">
|
||||
<Button
|
||||
variant='solid'
|
||||
variant="solid"
|
||||
onClick={() => onToggleAllColumns(true)}
|
||||
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ç'}
|
||||
</Button>
|
||||
<Button
|
||||
variant='default'
|
||||
variant="default"
|
||||
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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { Button, Dialog } from '@/components/ui'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { columnEditorTypeListOptions } from '@/views/admin/listForm/edit/options'
|
||||
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||
import {
|
||||
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 {
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
|
|
@ -16,7 +20,16 @@ import {
|
|||
import { SortableContext, arrayMove, useSortable, rectSortingStrategy } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
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 ────────────────────────────────────────────────────────────────────
|
||||
export interface WizardGroupItem {
|
||||
|
|
@ -29,6 +42,11 @@ export interface WizardGroupItem {
|
|||
isRequired: boolean
|
||||
turkishCaption?: string
|
||||
englishCaption?: string
|
||||
captionName?: string
|
||||
lookupDataSourceType?: UiLookupDataSourceTypeEnum
|
||||
valueExpr?: string
|
||||
displayExpr?: string
|
||||
lookupQuery?: string
|
||||
}
|
||||
|
||||
export interface WizardGroup {
|
||||
|
|
@ -43,6 +61,9 @@ export interface WizardStep3Props {
|
|||
selectCommandColumns: DatabaseColumnDto[]
|
||||
groups: WizardGroup[]
|
||||
onGroupsChange: (groups: WizardGroup[]) => void
|
||||
dbObjects: SqlObjectExplorerDto | null
|
||||
isLoadingDbObjects: boolean
|
||||
dsCode: string
|
||||
translate: (key: string) => string
|
||||
onBack: () => void
|
||||
onNext: () => void
|
||||
|
|
@ -73,16 +94,15 @@ function inferEditorType(sqlType: string): string {
|
|||
}
|
||||
|
||||
const formatLabel = (text: string) => {
|
||||
return text
|
||||
return (
|
||||
text
|
||||
// CamelCase → kelimelere ayır
|
||||
.split(/(?=[A-Z])/)
|
||||
.filter(Boolean)
|
||||
.map(word =>
|
||||
word.charAt(0).toUpperCase() +
|
||||
word.slice(1).toLowerCase()
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(' ')
|
||||
)
|
||||
.join(" ");
|
||||
};
|
||||
}
|
||||
|
||||
function newGroupItem(colName: string, meta?: DatabaseColumnDto): WizardGroupItem {
|
||||
const sqlType = meta?.dataType ?? ''
|
||||
|
|
@ -96,6 +116,11 @@ function newGroupItem(colName: string, meta?: DatabaseColumnDto): WizardGroupIte
|
|||
isRequired: meta?.isNullable === false,
|
||||
turkishCaption: 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 {
|
||||
item: WizardGroupItem
|
||||
groupColCount: number
|
||||
dbObjects: SqlObjectExplorerDto | null
|
||||
dsCode: string
|
||||
onTurkishCaptionChange: (val: string) => void
|
||||
onEnglishCaptionChange: (val: string) => void
|
||||
onEditorTypeChange: (val: string) => void
|
||||
onEditorOptionsChange: (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
|
||||
onRequiredChange: (val: boolean) => void
|
||||
onRemove: () => void
|
||||
|
|
@ -153,6 +185,8 @@ interface SortableItemProps {
|
|||
function SortableItem({
|
||||
item,
|
||||
groupColCount,
|
||||
dbObjects,
|
||||
dsCode,
|
||||
onTurkishCaptionChange,
|
||||
onEnglishCaptionChange,
|
||||
onEditorTypeChange,
|
||||
|
|
@ -160,9 +194,24 @@ function SortableItem({
|
|||
onEditorScriptChange,
|
||||
onColSpanChange,
|
||||
onRequiredChange,
|
||||
onCaptionNameChange,
|
||||
onLookupDataSourceTypeChange,
|
||||
onDisplayExprChange,
|
||||
onValueExprChange,
|
||||
onLookupQueryChange,
|
||||
onRemove,
|
||||
}: SortableItemProps) {
|
||||
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({
|
||||
id: `${ITM_PREFIX}${item.id}`,
|
||||
})
|
||||
|
|
@ -206,7 +255,46 @@ function SortableItem({
|
|||
</button>
|
||||
</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 */}
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-[10px] text-gray-400 font-medium">
|
||||
{translate('::ListForms.Wizard.Step3.EditorType')}
|
||||
</span>
|
||||
<select
|
||||
value={item.editorType}
|
||||
onChange={(e) => onEditorTypeChange(e.target.value)}
|
||||
|
|
@ -218,30 +306,240 @@ function SortableItem({
|
|||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Turkish Caption */}
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-[10px] text-gray-400 font-medium">{translate('::ListForms.Wizard.Step3.TurkishCaption')}</span>
|
||||
<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.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
|
||||
value={item.turkishCaption}
|
||||
onChange={(e) => onTurkishCaptionChange(e.target.value)}
|
||||
value={item.valueExpr}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* English Caption */}
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-[10px] text-gray-400 font-medium">{translate('::ListForms.Wizard.Step3.EnglishCaption')}</span>
|
||||
<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.LookupDisplayExpression')}
|
||||
</span>
|
||||
<input
|
||||
value={item.englishCaption}
|
||||
onChange={(e) => onEnglishCaptionChange(e.target.value)}
|
||||
value={item.displayExpr}
|
||||
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"
|
||||
/>
|
||||
</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 */}
|
||||
<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
|
||||
value={item.editorOptions}
|
||||
onChange={(e) => onEditorOptionsChange(e.target.value)}
|
||||
|
|
@ -252,7 +550,9 @@ function SortableItem({
|
|||
|
||||
{/* Editor Script */}
|
||||
<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
|
||||
value={item.editorScript}
|
||||
onChange={(e) => onEditorScriptChange(e.target.value)}
|
||||
|
|
@ -264,7 +564,9 @@ function SortableItem({
|
|||
{/* Bottom row: ColSpan + Required */}
|
||||
<div className="flex items-center gap-2">
|
||||
<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
|
||||
value={item.colSpan}
|
||||
onChange={(e) => onColSpanChange(Number(e.target.value))}
|
||||
|
|
@ -284,7 +586,9 @@ function SortableItem({
|
|||
onChange={(e) => onRequiredChange(e.target.checked)}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -295,6 +599,8 @@ interface GroupCardProps {
|
|||
group: WizardGroup
|
||||
isOver: boolean
|
||||
hasAvailable: boolean
|
||||
dbObjects: SqlObjectExplorerDto | null
|
||||
dsCode: string
|
||||
onCaptionChange: (val: string) => void
|
||||
onColCountChange: (val: number) => void
|
||||
onItemChange: (itemId: string, patch: Partial<WizardGroupItem>) => void
|
||||
|
|
@ -307,6 +613,8 @@ function GroupCard({
|
|||
group,
|
||||
isOver,
|
||||
hasAvailable,
|
||||
dbObjects,
|
||||
dsCode,
|
||||
onCaptionChange,
|
||||
onColCountChange,
|
||||
onItemChange,
|
||||
|
|
@ -328,17 +636,19 @@ function GroupCard({
|
|||
}`}
|
||||
>
|
||||
{/* 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
|
||||
type="text"
|
||||
value={group.caption}
|
||||
onChange={(e) => onCaptionChange(e.target.value)}
|
||||
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 */}
|
||||
<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) => (
|
||||
<button
|
||||
key={n}
|
||||
|
|
@ -359,7 +669,10 @@ function GroupCard({
|
|||
type="button"
|
||||
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"
|
||||
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]" />
|
||||
{translate('::ListForms.Wizard.Step3.AddAll') || 'Tümünü Ekle'}
|
||||
|
|
@ -400,6 +713,9 @@ function GroupCard({
|
|||
key={item.id}
|
||||
item={item}
|
||||
groupColCount={group.colCount}
|
||||
dbObjects={dbObjects}
|
||||
dsCode={dsCode}
|
||||
onCaptionNameChange={(val) => onItemChange(item.id, { captionName: val })}
|
||||
onTurkishCaptionChange={(val) => onItemChange(item.id, { turkishCaption: val })}
|
||||
onEnglishCaptionChange={(val) => onItemChange(item.id, { englishCaption: val })}
|
||||
onEditorTypeChange={(val) => onItemChange(item.id, { editorType: val })}
|
||||
|
|
@ -407,6 +723,12 @@ function GroupCard({
|
|||
onEditorScriptChange={(val) => onItemChange(item.id, { editorScript: val })}
|
||||
onColSpanChange={(val) => onItemChange(item.id, { colSpan: 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)}
|
||||
/>
|
||||
))}
|
||||
|
|
@ -423,6 +745,9 @@ const WizardStep3 = ({
|
|||
selectCommandColumns,
|
||||
groups,
|
||||
onGroupsChange,
|
||||
dbObjects,
|
||||
isLoadingDbObjects: _isLoadingDbObjects,
|
||||
dsCode,
|
||||
translate,
|
||||
onBack,
|
||||
onNext,
|
||||
|
|
@ -662,7 +987,8 @@ const WizardStep3 = ({
|
|||
const validationMsg = hasNoGroups
|
||||
? translate('::ListForms.Wizard.Step3.AtLeastOneGroup') || 'En az bir grup eklemelisiniz.'
|
||||
: 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 (
|
||||
|
|
@ -673,10 +999,10 @@ const WizardStep3 = ({
|
|||
onDragEnd={onDragEnd}
|
||||
>
|
||||
<div className="pb-20">
|
||||
<div className="flex gap-4">
|
||||
<div className="flex flex-col lg:flex-row gap-4">
|
||||
{/* ── Left: Available Columns ─────────────────────────────────── */}
|
||||
<div className="w-72 shrink-0">
|
||||
<div className="sticky top-4">
|
||||
<div className="w-full lg:w-64 lg:shrink-0">
|
||||
<div className="lg:sticky lg:top-4">
|
||||
<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">
|
||||
{translate('::App.Listform.ListformField.Column')}
|
||||
|
|
@ -685,10 +1011,11 @@ const WizardStep3 = ({
|
|||
{availableColumns.length}/{selectedColumns.size}
|
||||
</span>
|
||||
</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 ? (
|
||||
<div className="text-xs text-gray-300 dark:text-gray-600 py-4 text-center select-none">
|
||||
{translate('::ListForms.Wizard.Step3.AllColumnsAdded') || 'Tüm sütunlar gruplara eklendi'}
|
||||
<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'}
|
||||
</div>
|
||||
) : (
|
||||
availableColumns.map((col) => <AvailableColumnChip key={col} colName={col} />)
|
||||
|
|
@ -701,7 +1028,8 @@ const WizardStep3 = ({
|
|||
<div className="flex-1 flex flex-col gap-3">
|
||||
{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">
|
||||
{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>
|
||||
)}
|
||||
|
||||
|
|
@ -711,6 +1039,8 @@ const WizardStep3 = ({
|
|||
group={group}
|
||||
isOver={overGroupId === group.id}
|
||||
hasAvailable={availableColumns.length > 0}
|
||||
dbObjects={dbObjects}
|
||||
dsCode={dsCode}
|
||||
onCaptionChange={(val) => updateGroup(group.id, { caption: val })}
|
||||
onColCountChange={(val) => updateGroup(group.id, { colCount: val })}
|
||||
onItemChange={(itemId, patch) => updateItem(group.id, itemId, patch)}
|
||||
|
|
@ -793,19 +1123,34 @@ 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-0 h-16 flex items-center">
|
||||
<div className="flex items-center gap-3 w-full">
|
||||
<Button size='sm' variant="default" type="button" icon={<FaArrowLeft />} 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-4 py-2 min-h-16 flex items-center">
|
||||
<div className="flex flex-wrap items-center gap-2 w-full">
|
||||
<Button size="sm" variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
|
||||
{translate('::Back') || 'Back'}
|
||||
</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'}
|
||||
</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>
|
||||
<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'}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import { Container } from '@/components/shared'
|
||||
import { Button, Checkbox, FormContainer, FormItem, Input, Select } from '@/components/ui'
|
||||
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 { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { useStoreState } from '@/store/store'
|
||||
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 { cascadeFilterOperator, columnLookupDataSourceTypeListOptions } from '../options'
|
||||
import { FormFieldEditProps } from './FormFields'
|
||||
|
|
@ -26,6 +31,177 @@ const schema = object().shape({
|
|||
.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) {
|
||||
return {
|
||||
...initialValues,
|
||||
|
|
@ -52,6 +228,22 @@ function FormFieldTabLookup({
|
|||
initialValues: ColumnFormatEditDto
|
||||
} & FormFieldEditProps) {
|
||||
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 (
|
||||
<Container className="grid xl:grid-cols-2">
|
||||
|
|
@ -94,17 +286,42 @@ function FormFieldTabLookup({
|
|||
|
||||
<FormItem
|
||||
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}
|
||||
errorMessage={errors.lookupDto?.lookupQuery}
|
||||
>
|
||||
<Field
|
||||
type="text"
|
||||
<Field name="lookupDto.lookupQuery">
|
||||
{({ field, form }: FieldProps<string>) => (
|
||||
<>
|
||||
<Input
|
||||
{...field}
|
||||
textArea
|
||||
autoComplete="off"
|
||||
name="lookupDto.lookupQuery"
|
||||
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
|
||||
|
|
|
|||
|
|
@ -167,7 +167,6 @@ export function MenuAddDialog({
|
|||
}, [isOpen, initialParentCode, initialOrder])
|
||||
|
||||
const shortNameRequired = !form.parentCode.trim()
|
||||
console.log(form.parentCode.length)
|
||||
const handleSave = async () => {
|
||||
if (!form.code.trim() || !form.menuTextEn.trim()) return
|
||||
if (shortNameRequired && !form.shortName.trim()) return
|
||||
|
|
|
|||
Loading…
Reference in a new issue