Wizard Düzenlemeleri

This commit is contained in:
Sedat Öztürk 2026-03-01 00:37:34 +03:00
parent 4b2fceb404
commit 85b498a477
17 changed files with 440 additions and 205 deletions

View file

@ -4,6 +4,6 @@ namespace Sozsoft.Platform.ListForms;
public interface IListFormWizardAppService
{
Task Create(WizardCreateInputDto input);
Task Create(ListFormWizardDto input);
}

View file

@ -4,11 +4,15 @@ using System.Data;
namespace Sozsoft.Platform.ListForms;
public class WizardCreateInputDto
public class ListFormWizardDto
{
public string WizardName { get; set; }
public string ListFormCode { get; set; }
public string MenuCode { get; set; }
public bool IsTenant { get; set; }
public bool IsBranch { get; set; }
public string LanguageTextMenuEn { get; set; }
public string LanguageTextMenuTr { get; set; }
public string LanguageTextTitleEn { get; set; }

View file

@ -22,5 +22,8 @@ public class MenuDto : FullAuditedEntityDto<Guid>
public string UserId { get; set; } // External kullanici id (orn: ali.akman. ihtiyaca gore guid veya int de olabilir)
public string RoleId { get; set; } // External role id (orn: ihracat)
public string CultureName { get; set; } // Bu tanim hangi dil icin "en", "tr"
public string MenuTextTr { get; set; }
public string MenuTextEn { get; set; }
}

View file

@ -15,7 +15,6 @@ using Volo.Abp.PermissionManagement;
using Volo.Abp.Uow;
using static Sozsoft.Platform.PlatformConsts;
using System.Data;
using Sozsoft.Platform.Data.Seeds;
namespace Sozsoft.Platform.ListForms;
@ -49,17 +48,19 @@ public class ListFormWizardAppService(
private readonly string cultureNameDefault = PlatformConsts.DefaultLanguage;
[UnitOfWork]
public async Task Create(WizardCreateInputDto input)
public async Task Create(ListFormWizardDto input)
{
var listFormCode = input.ListFormCode;
var nameLangKey = PlatformConsts.Wizard.WizardKey(listFormCode);
var titleLangKey = PlatformConsts.Wizard.WizardKeyTitle(listFormCode);
var code = PlatformConsts.Wizard.WizardKey(listFormCode);
var wizardName = input.WizardName.Trim();
var titleLangKey = PlatformConsts.Wizard.WizardKeyTitle(wizardName);
var nameLangKey = PlatformConsts.Wizard.WizardKey(wizardName);
var descLangKey = PlatformConsts.Wizard.WizardKeyDesc(wizardName);
var code = PlatformConsts.Wizard.WizardKey(wizardName);
//Dil - Language Keys
await CreateLangKey(nameLangKey, input.LanguageTextMenuEn, input.LanguageTextMenuTr);
await CreateLangKey(titleLangKey, input.LanguageTextTitleEn, input.LanguageTextTitleTr);
await CreateLangKey(PlatformConsts.Wizard.WizardKeyDesc(listFormCode), input.LanguageTextDescEn, input.LanguageTextDescTr);
await CreateLangKey(descLangKey, input.LanguageTextDescEn, input.LanguageTextDescTr);
//Permission Group
var groupName = input.PermissionGroupName ?? PlatformConsts.AppName;
@ -78,23 +79,23 @@ public class ListFormWizardAppService(
var permRead = existingPerms.FirstOrDefault(a => a.Name == code) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both), autoSave: false);
var permCreate = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermCreate(listFormCode)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermCreate(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyCreate, true, MultiTenancySides.Both), autoSave: false);
var permCreate = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermCreate(wizardName)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermCreate(wizardName), permRead.Name, PlatformConsts.Wizard.LangKeyCreate, true, MultiTenancySides.Both), autoSave: false);
var permUpdate = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermUpdate(listFormCode)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermUpdate(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: false);
var permUpdate = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermUpdate(wizardName)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermUpdate(wizardName), permRead.Name, PlatformConsts.Wizard.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: false);
var permDelete = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermDelete(listFormCode)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermDelete(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyDelete, true, MultiTenancySides.Both), autoSave: false);
var permDelete = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermDelete(wizardName)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermDelete(wizardName), permRead.Name, PlatformConsts.Wizard.LangKeyDelete, true, MultiTenancySides.Both), autoSave: false);
var permExport = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermExport(listFormCode)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermExport(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyExport, true, MultiTenancySides.Both), autoSave: false);
var permExport = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermExport(wizardName)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermExport(wizardName), permRead.Name, PlatformConsts.Wizard.LangKeyExport, true, MultiTenancySides.Both), autoSave: false);
var permImport = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermImport(listFormCode)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermImport(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyImport, true, MultiTenancySides.Both), autoSave: false);
var permImport = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermImport(wizardName)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermImport(wizardName), permRead.Name, PlatformConsts.Wizard.LangKeyImport, true, MultiTenancySides.Both), autoSave: false);
var PermNote = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermNote(listFormCode)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermNote(listFormCode), permRead.Name, PlatformConsts.Wizard.LangKeyNote, true, MultiTenancySides.Both), autoSave: false);
var permNote = existingPerms.FirstOrDefault(a => a.Name == PlatformConsts.Wizard.PermNote(wizardName)) ??
await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, PlatformConsts.Wizard.PermNote(wizardName), permRead.Name, PlatformConsts.Wizard.LangKeyNote, true, MultiTenancySides.Both), autoSave: false);
// Permission Grants - Bulk Insert
var adminUserName = PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue;
@ -108,6 +109,8 @@ public class ListFormWizardAppService(
new PermissionGrant(Guid.NewGuid(), permUpdate.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName),
new PermissionGrant(Guid.NewGuid(), permDelete.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName),
new PermissionGrant(Guid.NewGuid(), permExport.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName),
new PermissionGrant(Guid.NewGuid(), permImport.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName),
new PermissionGrant(Guid.NewGuid(), permNote.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName),
], autoSave: false);
//Menu Parent
@ -115,11 +118,11 @@ public class ListFormWizardAppService(
var menuParent = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == input.MenuParentCode));
if (menuParent == null)
{
await CreateLangKey(PlatformConsts.Wizard.WizardKeyParent(listFormCode), input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr);
await CreateLangKey(PlatformConsts.Wizard.WizardKeyParent(wizardName), input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr);
menuParent = await repoMenu.InsertAsync(new Menu
{
Code = input.MenuParentCode,
DisplayName = PlatformConsts.Wizard.WizardKeyParent(listFormCode),
DisplayName = PlatformConsts.Wizard.WizardKeyParent(wizardName),
IsDisabled = false,
}, autoSave: false);
}
@ -136,7 +139,7 @@ public class ListFormWizardAppService(
Target = null,
ElementId = null,
CssClass = null,
Url = PlatformConsts.Wizard.MenuUrl(listFormCode),
Url = PlatformConsts.Wizard.MenuUrl(code),
RequiredPermissionName = permRead.Name
}, autoSave: false);
@ -187,14 +190,14 @@ public class ListFormWizardAppService(
ShowNote = true,
LayoutJson = Wizard.DefaultLayoutJson(),
CultureName = LanguageCodes.En,
ListFormCode = listFormCode,
ListFormCode = input.ListFormCode,
Name = nameLangKey,
Title = titleLangKey,
Description = descLangKey,
DataSourceCode = input.DataSourceCode,
IsTenant = false,
IsBranch = false,
IsTenant = input.IsTenant,
IsBranch = input.IsBranch,
IsOrganizationUnit = false,
Description = Wizard.WizardKeyDesc(listFormCode),
SelectCommandType = input.SelectCommandType,
SelectCommand = input.SelectCommand,
KeyFieldName = input.KeyFieldName,
@ -207,11 +210,11 @@ public class ListFormWizardAppService(
GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }),
SelectionJson = Wizard.DefaultSelectionSingleJson,
ColumnOptionJson = Wizard.DefaultColumnOptionJson(),
PermissionJson = Wizard.DefaultPermissionJson(listFormCode),
PermissionJson = Wizard.DefaultPermissionJson(code),
DeleteCommand = Wizard.DefaultDeleteCommand(nameof(TableNameEnum.Country)),
DeleteFieldsDefaultValueJson = Wizard.DefaultDeleteFieldsDefaultValueJson(),
DeleteFieldsDefaultValueJson = Wizard.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType),
PagerOptionJson = Wizard.DefaultPagerOptionJson,
EditingOptionJson = Wizard.DefaultEditingOptionJson(listFormCode, 600, 500, true, true, true, true, false),
EditingOptionJson = Wizard.DefaultEditingOptionJson(titleLangKey, 600, 500, true, true, true, true, false),
EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null,
}, autoSave: true);
@ -224,7 +227,7 @@ public class ListFormWizardAppService(
fieldOrder++;
await repoListFormField.InsertAsync(new ListFormField
{
ListFormCode = listFormCode,
ListFormCode = input.ListFormCode,
FieldName = item.DataField,
CaptionName = item.DataField,
Visible = true,

View file

@ -14,6 +14,7 @@ using Volo.Abp.Domain.Repositories;
using Volo.Abp.PermissionManagement;
using Volo.Abp.TenantManagement;
using static Sozsoft.Platform.Data.Seeds.SeedConsts;
using Sozsoft.Languages;
namespace Sozsoft.Platform.Menus;
@ -26,18 +27,21 @@ public class MenuAppService : CrudAppService<
{
private readonly IRepository<Menu, Guid> _menuRepository;
private readonly IRepository<LanguageKey, Guid> _repositoryKey;
private readonly IRepository<LanguageText, Guid> _repositoryText;
private readonly ITenantRepository _tenantRepository;
private readonly IPermissionDefinitionRecordRepository _permissionRepository;
public MenuAppService(
IRepository<Menu, Guid> menuRepository,
IRepository<LanguageKey, Guid> languageKeyRepository,
IRepository<LanguageText, Guid> languageTextRepository,
ITenantRepository tenantRepository,
IPermissionDefinitionRecordRepository permissionRepository
) : base(menuRepository)
{
_menuRepository = menuRepository;
_repositoryKey = languageKeyRepository;
_repositoryText = languageTextRepository;
_tenantRepository = tenantRepository;
_permissionRepository = permissionRepository;
@ -88,7 +92,7 @@ public class MenuAppService : CrudAppService<
query = ApplyPaging(query, input);
var items = await AsyncExecuter.ToListAsync(query);
// Tüm unique permission'ları topla
var uniquePermissions = items
.Where(x => !string.IsNullOrWhiteSpace(x.RequiredPermissionName))
@ -115,7 +119,7 @@ public class MenuAppService : CrudAppService<
// Sadece yetkili menüleri filtrele
entities = items
.Where(item => string.IsNullOrWhiteSpace(item.RequiredPermissionName) ||
.Where(item => string.IsNullOrWhiteSpace(item.RequiredPermissionName) ||
grantedPermissions.Contains(item.RequiredPermissionName))
.ToList();
@ -183,12 +187,12 @@ public class MenuAppService : CrudAppService<
// Tüm DisplayName'leri topla
var displayNames = inputs.Select(x => x.DisplayName).Distinct().ToList();
// Mevcut key'leri tek sorguda getir
var existingKeys = await AsyncExecuter.ToListAsync(
(await _repositoryKey.GetQueryableAsync())
.Where(a => displayNames.Contains(a.Key) && a.ResourceName == PlatformConsts.AppName));
var existingKeyNames = existingKeys.Select(x => x.Key).ToHashSet();
// Eksik key'leri toplu ekle
@ -244,4 +248,68 @@ public class MenuAppService : CrudAppService<
return entityDtos;
}
public async Task<MenuDto> CreateWithLanguageKeyTextAsync(MenuDto input)
{
await CheckCreatePolicyAsync();
//Dil Key oluşturuluyor.
var keyExists = await _repositoryKey.AnyAsync(
a => a.Key == input.Code &&
a.ResourceName == PlatformConsts.AppName);
if (!keyExists)
{
await _repositoryKey.InsertAsync(new LanguageKey
{
Key = input.Code,
ResourceName = PlatformConsts.AppName
}, autoSave: true);
}
// English text oluşturuluyor veya güncelleniyor.
var existingEnText = await _repositoryText.FirstOrDefaultAsync(
a => a.CultureName == "en" &&
a.Key == input.Code &&
a.ResourceName == PlatformConsts.AppName);
if (existingEnText != null)
{
existingEnText.Value = input.MenuTextEn;
await _repositoryText.UpdateAsync(existingEnText);
}
else
{
await _repositoryText.InsertAsync(new LanguageText
{
Key = input.Code,
CultureName = "en",
Value = input.MenuTextEn,
ResourceName = PlatformConsts.AppName
});
}
// Türkçe text oluşturuluyor veya güncelleniyor.
var existingTrText = await _repositoryText.FirstOrDefaultAsync(
a => a.CultureName == "tr" &&
a.Key == input.Code &&
a.ResourceName == PlatformConsts.AppName);
if (existingTrText != null)
{
existingTrText.Value = input.MenuTextTr;
await _repositoryText.UpdateAsync(existingTrText);
}
else
{
await _repositoryText.InsertAsync(new LanguageText
{
Key = input.Code,
CultureName = "tr",
Value = input.MenuTextTr,
ResourceName = PlatformConsts.AppName
});
}
return await base.CreateAsync(input);
}
}

View file

@ -450,7 +450,7 @@
"code": "Abp.Mailing.DefaultFromDisplayName",
"nameKey": "Abp.Mailing.DefaultFromDisplayName",
"descriptionKey": "Abp.Mailing.DefaultFromDisplayName.Description",
"defaultValue": "Sozsoft",
"defaultValue": "Sözsoft",
"isVisibleToClients": false,
"providers": "T|G|D",
"isInherited": false,

View file

@ -407,7 +407,7 @@ public static class PlatformConsts
public static class Wizard
{
public static string WizardKey(string code) => $"{Prefix.App}.{AppName}.{code}";
public static string WizardKey(string code) => $"{Prefix.App}.Wizard.{code}";
public static string WizardKeyTitle(string code) => $"{WizardKey(code)}.Title";
public static string WizardKeyDesc(string code) => $"{WizardKey(code)}.Desc";
public static string WizardKeyParent(string code) => $"{WizardKey(code)}.Parent";
@ -418,12 +418,12 @@ public static class PlatformConsts
public static string PermExport(string code) => $"{WizardKey(code)}.Export";
public static string PermImport(string code) => $"{WizardKey(code)}.Import";
public static string PermNote(string code) => $"{WizardKey(code)}.Note";
public static string LangKeyCreate => $"{Prefix.App}.Create";
public static string LangKeyUpdate => $"{Prefix.App}.Update";
public static string LangKeyDelete => $"{Prefix.App}.Delete";
public static string LangKeyExport => $"{Prefix.App}.Export";
public static string LangKeyImport => $"{Prefix.App}.Import";
public static string LangKeyNote => $"{Prefix.App}.Note";
public static string LangKeyCreate => "Create";
public static string LangKeyUpdate => "Update";
public static string LangKeyDelete => "Delete";
public static string LangKeyExport => "Export";
public static string LangKeyImport => "Import";
public static string LangKeyNote => "Note";
public static string MenuUrl(string code) => $"/admin/list/{code}";
public static string MenuIcon => "FcList";
@ -494,8 +494,8 @@ public static class PlatformConsts
{
return JsonSerializer.Serialize(new[]
{
new { FieldName = "DeleterId", FieldDbType = DbType.Guid.ToString(), Value = "@USERID", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
new { FieldName = "Id", FieldDbType = dbType.ToString(), Value = "@ID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
new { FieldName = "DeleterId", FieldDbType = DbType.Guid, Value = "@USERID", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
new { FieldName = "Id", FieldDbType = dbType, Value = "@ID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
});
}

View file

@ -401,7 +401,7 @@ const PublicLayout = () => {
<div className="border-t border-gray-800 mt-12 pt-8">
<div className="flex flex-col md:flex-row justify-between items-center">
<p className="text-gray-400 text-sm">
&copy; {currentYear} Sozsoft Platform. {translate('::Public.footer.copyright')}
&copy; {currentYear} Sözsoft Platform. {translate('::Public.footer.copyright')}
</p>
<div className="mt-4 md:mt-0">
<ul className="flex space-x-6 text-sm">

View file

@ -32,8 +32,11 @@ export interface ListFormWizardColumnGroupDto {
}
export interface ListFormWizardDto {
wizardName: string
listFormCode: string
menuCode: string
isTenant: boolean
isBranch: boolean
languageTextMenuEn: string
languageTextMenuTr: string
languageTextTitleEn: string

View file

@ -17,4 +17,6 @@ export interface MenuDto extends FullAuditedEntityDto<string> {
cultureName?: string
group?: string
shortName?: string
menuTextTr?: string
menuTextEn?: string
}

View file

@ -25,6 +25,16 @@ export class MenuService {
{ apiName: this.apiName },
)
createWithLanguageKeyText = (input: MenuDto) =>
apiService.fetchData<MenuDto, MenuDto>(
{
method: 'POST',
url: '/api/app/menu/with-language-key-text',
data: input,
},
{ apiName: this.apiName },
)
delete = (id: string) =>
apiService.fetchData<void>(
{
@ -51,7 +61,7 @@ export class MenuService {
params: {
sorting: input.sorting,
skipCount: input.skipCount,
maxResultCount: input.maxResultCount
maxResultCount: input.maxResultCount,
},
},
{ apiName: this.apiName },
@ -80,11 +90,9 @@ export class MenuService {
export const getMenus = async (skipCount = 0, maxResultCount = 1000, sorting = 'order') => {
const menuService = new MenuService()
return await menuService.getList(
{
sorting,
skipCount,
maxResultCount,
}
)
return await menuService.getList({
sorting,
skipCount,
maxResultCount,
})
}

View file

@ -28,11 +28,15 @@ import WizardStep3, { WizardGroup } from './WizardStep3'
import WizardStep4 from './WizardStep4'
import { Container } from '@/components/shared'
import { sqlDataTypeToDbType } from './edit/options'
import { useStoreActions } from '@/store/store'
// ─── Formik initial values & validation ──────────────────────────────────────
const initialValues: ListFormWizardDto = {
wizardName: '',
listFormCode: '',
menuCode: '',
isTenant: false,
isBranch: false,
languageTextMenuEn: '',
languageTextMenuTr: '',
languageTextTitleEn: '',
@ -53,6 +57,7 @@ const initialValues: ListFormWizardDto = {
}
const step1ValidationSchema = Yup.object().shape({
wizardName: Yup.string().required(),
menuCode: Yup.string().required(),
permissionGroupName: Yup.string().required(),
menuParentCode: Yup.string().required(),
@ -62,16 +67,18 @@ const step1ValidationSchema = Yup.object().shape({
const step2ValidationSchema = Yup.object().shape({
listFormCode: Yup.string().required(),
languageTextTitleEn: Yup.string(),
languageTextTitleTr: Yup.string(),
languageTextDescEn: Yup.string(),
languageTextDescTr: Yup.string(),
languageTextTitleEn: Yup.string().required(),
languageTextTitleTr: Yup.string().required(),
languageTextDescEn: Yup.string().required(),
languageTextDescTr: Yup.string().required(),
dataSourceCode: Yup.string().required(),
dataSourceConnectionString: Yup.string(),
selectCommandType: Yup.string().required(),
selectCommand: Yup.string().required(),
keyFieldName: Yup.string().required(),
keyFieldDbSourceType: Yup.string().required(),
keyFieldDbSourceType: Yup.number().required(),
isTenant: Yup.boolean(),
isBranch: Yup.boolean(),
})
const listFormValidationSchema = step1ValidationSchema.concat(step2ValidationSchema)
@ -80,7 +87,6 @@ const listFormValidationSchema = step1ValidationSchema.concat(step2ValidationSch
const Wizard = () => {
const [currentStep, setCurrentStep] = useState(0)
const [wizardName, setWizardName] = useState('')
// ── Data Source ──
const [isLoadingDataSource, setIsLoadingDataSource] = useState(false)
@ -214,12 +220,12 @@ const Wizard = () => {
// Auto-derive listFormCode from wizardName
const deriveListFormCode = (name: string) => {
const sanitized = name.replace(/[^a-zA-Z0-9]/g, '')
return sanitized ? `App.${sanitized}` : ''
const sanitized = name.replace(/\s/g, '')
return sanitized ? `App.Wizard.${sanitized}` : ''
}
const handleWizardNameChange = (name: string) => {
setWizardName(name)
formikRef.current?.setFieldValue('wizardName', name)
const derived = deriveListFormCode(name)
formikRef.current?.setFieldValue('listFormCode', derived)
formikRef.current?.setFieldValue('menuCode', derived)
@ -274,11 +280,12 @@ const Wizard = () => {
await formikRef.current.setTouched({ ...formikRef.current.touched, ...touchedStep1 })
// Also require wizardName
if (!wizardName.trim() || hasStep1Errors) return
if (hasStep1Errors) return
setCurrentStep(1)
}
const handleBack = () => setCurrentStep(0)
const { getConfig } = useStoreActions((a) => a.abpConfig)
const handleNext2 = async () => {
if (!formikRef.current) return
@ -322,13 +329,10 @@ const Wizard = () => {
</Notification>,
{ placement: 'top-end' },
)
getConfig(true)
setTimeout(() => {
navigate(
ROUTES_ENUM.protected.saas.listFormManagement.edit.replace(
':listFormCode',
values.listFormCode,
),
)
navigate(ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode))
}, 1500)
}
@ -368,14 +372,13 @@ const Wizard = () => {
{ placement: 'top-end' },
)
setSubmitting(false)
getConfig(true)
setTimeout(() => {
navigate(
ROUTES_ENUM.protected.saas.listFormManagement.edit.replace(
':listFormCode',
values.listFormCode,
),
ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode),
)
}, 500)
}, 1500)
} catch (error: any) {
toast.push(<Notification title={error.message} type="danger" />, {
placement: 'top-end',
@ -385,14 +388,16 @@ const Wizard = () => {
>
{({ touched, errors, isSubmitting, values }) => (
<Form>
<FormContainer size={currentStep === 2 ? undefined : currentStep === 3 ? undefined : 'sm'}>
<FormContainer
size={currentStep === 2 ? undefined : currentStep === 3 ? undefined : 'sm'}
>
{/* ─── Step 1: Basic Info ─────────────────────────────── */}
{currentStep === 0 && (
<WizardStep1
values={values}
errors={errors}
touched={touched}
wizardName={wizardName}
wizardName={values.wizardName}
onWizardNameChange={handleWizardNameChange}
rawMenuItems={rawMenuItems}
menuTree={menuTree}
@ -453,7 +458,7 @@ const Wizard = () => {
{currentStep === 3 && (
<WizardStep4
values={values}
wizardName={wizardName}
wizardName={values.wizardName}
selectedColumns={selectedColumns}
selectCommandColumns={selectCommandColumns}
groups={editingGroups}

View file

@ -348,7 +348,7 @@ function MenuTreeInline({
<div
className={`rounded-lg border ${invalid ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'} bg-white dark:bg-gray-800 overflow-hidden`}
>
<div className="h-72 overflow-y-auto py-1">
<div className="h-96 overflow-y-auto py-1">
{isLoading ? (
<div className="px-4 py-3 text-sm text-gray-400">Loading</div>
) : enrichedNodes.length === 0 ? (
@ -498,8 +498,10 @@ function MenuAddDialog({
onSaved,
}: MenuAddDialogProps) {
const [form, setForm] = useState({
name: '',
code: '',
displayName: '',
menuTextEn: '',
menuTextTr: '',
parentCode: initialParentCode,
icon: '',
shortName: '',
@ -510,8 +512,10 @@ function MenuAddDialog({
useEffect(() => {
if (isOpen)
setForm({
name: '',
code: '',
displayName: '',
menuTextEn: '',
menuTextTr: '',
parentCode: initialParentCode,
icon: '',
shortName: '',
@ -520,18 +524,22 @@ function MenuAddDialog({
}, [isOpen, initialParentCode, initialOrder])
const handleSave = async () => {
if (!form.code.trim() || !form.displayName.trim()) return
if (!form.code.trim() || !form.menuTextEn.trim()) return
setSaving(true)
try {
await menuService.create({
// Menü oluşturuluyor
await menuService.createWithLanguageKeyText({
code: form.code.trim(),
displayName: form.displayName.trim(),
displayName: form.code.trim(),
parentCode: form.parentCode.trim() || undefined,
icon: form.icon || undefined,
shortName: form.shortName.trim() || undefined,
order: form.order,
isDisabled: false,
menuTextTr: form.menuTextTr.trim(),
menuTextEn: form.menuTextEn.trim(),
} as MenuDto)
onSaved()
onClose()
} catch (e: any) {
@ -544,74 +552,125 @@ function MenuAddDialog({
// suppress unused warning — rawItems kept for future use
void rawItems
const fieldCls =
'h-11 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-200 outline-none focus:border-indigo-400 w-full'
const disabledCls =
'h-11 px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700 text-sm text-gray-400 dark:text-gray-500 cursor-not-allowed w-full'
const labelCls = 'text-xs font-medium text-gray-500 dark:text-gray-400 mb-1'
return (
<Dialog isOpen={isOpen} onClose={onClose} onRequestClose={onClose} width={520}>
<div className="flex flex-col gap-4 p-4">
<h5 className="text-base font-semibold text-gray-800 dark:text-gray-100">Yeni Menü Ekle</h5>
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-600 dark:text-gray-300">
<Dialog isOpen={isOpen} onClose={onClose} onRequestClose={onClose} width={680}>
<div className="flex flex-col gap-5 p-5">
{/* Header */}
<div className="flex items-center gap-2 pb-1 border-b border-gray-100 dark:border-gray-700">
<FaPlus className="text-green-500 text-sm" />
<h5 className="text-base font-semibold text-gray-800 dark:text-gray-100">Yeni Menü Ekle</h5>
</div>
{/* Row 1 — Name | Code */}
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col">
<label className={labelCls}>
Name <span className="text-red-500">*</span>
</label>
<input
autoFocus
value={form.name}
onChange={(e) =>
setForm((p) => ({
...p,
name: e.target.value.replace(/\s+/g, ''),
code: `App.Wizard.${e.target.value.replace(/\s+/g, '')}`,
}))
}
placeholder="MyMenu"
className={fieldCls}
/>
</div>
<div className="flex flex-col">
<label className={labelCls}>
Code <span className="text-red-500">*</span>
</label>
<input
value={form.code}
onChange={(e) => setForm((p) => ({ ...p, code: e.target.value }))}
placeholder="App.MyMenu"
className="h-11 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-200 outline-none focus:border-indigo-400"
disabled
placeholder="App.Wizard.MyMenu"
className={disabledCls}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-600 dark:text-gray-300">
Display Name <span className="text-red-500">*</span>
</div>
{/* Row 3 — Icon (full width) */}
<div className="flex flex-col">
<label className={labelCls}>
Icon <span className="text-red-500">*</span>
</label>
<IconPickerField
value={form.icon}
onChange={(key) => setForm((p) => ({ ...p, icon: key }))}
/>
</div>
{/* Row 2 — Display Name EN | Display Name TR */}
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col">
<label className={labelCls}>
Display Name (English) <span className="text-red-500">*</span>
</label>
<input
value={form.displayName}
onChange={(e) => setForm((p) => ({ ...p, displayName: e.target.value }))}
value={form.menuTextEn}
onChange={(e) => setForm((p) => ({ ...p, menuTextEn: e.target.value }))}
placeholder="My Menu"
className="h-11 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-200 outline-none focus:border-indigo-400"
className={fieldCls}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-600 dark:text-gray-300">Icon</label>
<IconPickerField
value={form.icon}
onChange={(key) => setForm((p) => ({ ...p, icon: key }))}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-600 dark:text-gray-300">
Menu Parent
<div className="flex flex-col">
<label className={labelCls}>
Display Name (Turkish) <span className="text-red-500">*</span>
</label>
<input
value={form.menuTextTr}
onChange={(e) => setForm((p) => ({ ...p, menuTextTr: e.target.value }))}
placeholder="Menüm"
className={fieldCls}
/>
</div>
</div>
{/* Row 4 — Menu Parent | Order */}
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col">
<label className={labelCls}>Menu Parent</label>
<input
disabled
value={form.parentCode || '(Ana Menü)'}
className="h-11 px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700 text-sm text-gray-400 dark:text-gray-500 cursor-not-allowed"
className={disabledCls}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-600 dark:text-gray-300">
Sıra (Order)
</label>
<div className="flex flex-col">
<label className={labelCls}>Sıra (Order)</label>
<input
type="number"
value={form.order}
onChange={(e) => setForm((p) => ({ ...p, order: Number(e.target.value) }))}
className="h-11 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-200 outline-none focus:border-indigo-400"
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-600 dark:text-gray-300">
Short Name
</label>
<input
value={form.shortName}
onChange={(e) => setForm((p) => ({ ...p, shortName: e.target.value }))}
placeholder="My Menu (short)"
className="h-11 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-200 outline-none focus:border-indigo-400"
className={fieldCls}
/>
</div>
</div>
<div className="flex justify-end gap-2 pt-2">
{/* Row 5 — Short Name (full width) */}
<div className="flex flex-col">
<label className={labelCls}>Short Name</label>
<input
value={form.shortName}
onChange={(e) => setForm((p) => ({ ...p, shortName: e.target.value }))}
placeholder="My Menu (short)"
className={fieldCls}
/>
</div>
{/* Footer */}
<div className="flex justify-end gap-2 pt-1 border-t border-gray-100 dark:border-gray-700">
<Button size="sm" variant="plain" onClick={onClose}>
İptal
</Button>
@ -619,7 +678,12 @@ function MenuAddDialog({
size="sm"
variant="solid"
loading={saving}
disabled={!form.code.trim() || !form.displayName.trim()}
disabled={
!form.code.trim() ||
!form.menuTextEn.trim() ||
!form.menuTextTr.trim() ||
!form.icon.trim()
}
onClick={handleSave}
>
Kaydet
@ -698,7 +762,7 @@ const WizardStep1 = ({
placeholder="e.g. Routes, Products, Orders"
value={wizardName}
autoFocus
onChange={(e) => onWizardNameChange(e.target.value)}
onChange={(e) => onWizardNameChange(e.target.value.replace(/\s/g, ''))}
/>
</FormItem>
@ -785,14 +849,33 @@ const WizardStep1 = ({
type="text"
autoComplete="off"
name="menuCode"
placeholder="e.g. App.Routes, App.Products, App.Orders"
placeholder="e.g. App.Wizard.Routes, App.Wizard.Products, App.Wizard.Orders"
component={Input}
disabled={true}
/>
</FormItem>
{/* Menu Icon */}
<FormItem
label="Menu Icon"
asterisk={true}
invalid={!!(errors.menuIcon && touched.menuIcon)}
errorMessage={errors.menuIcon}
>
<Field name="menuIcon">
{({ field, form }: FieldProps<string>) => (
<IconPickerField
value={field.value}
onChange={(key) => form.setFieldValue(field.name, key)}
invalid={!!(errors.menuIcon && touched.menuIcon)}
/>
)}
</Field>
</FormItem>
{/* Menu Text (En) */}
<FormItem
label="Menu Text (En)"
label="Display Name (English)"
asterisk={true}
invalid={!!(errors.languageTextMenuEn && touched.languageTextMenuEn)}
errorMessage={errors.languageTextMenuEn}
@ -808,7 +891,7 @@ const WizardStep1 = ({
{/* Menu Text (Tr) */}
<FormItem
label="Menu Text (Tr)"
label="Display Name (Turkish)"
asterisk={true}
invalid={!!(errors.languageTextMenuTr && touched.languageTextMenuTr)}
errorMessage={errors.languageTextMenuTr}
@ -867,7 +950,13 @@ const WizardStep1 = ({
</span>
)}
</div>
<Button variant="solid" type="button" icon={<FaArrowRight />} disabled={!step1CanGo} onClick={onNext}>
<Button
variant="solid"
type="button"
icon={<FaArrowRight />}
disabled={!step1CanGo}
onClick={onNext}
>
{translate('::Next') || 'Next'}
</Button>
</div>

View file

@ -1,4 +1,4 @@
import { Button, FormItem, Input, Select } from '@/components/ui'
import { Button, Checkbox, FormItem, Input, Select } from '@/components/ui'
import { ListFormWizardDto } from '@/proxy/admin/list-form/models'
import { SelectCommandTypeEnum } from '@/proxy/form/models'
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
@ -66,6 +66,10 @@ const WizardStep2 = ({
!values.dataSourceCode && 'Veri Kaynağı',
!values.selectCommand && 'Select Command',
!values.keyFieldName && 'Key Field',
!values.languageTextDescEn && 'Description Text (En)',
!values.languageTextDescTr && 'Description Text (Tr)',
!values.languageTextTitleEn && 'Title Text (En)',
!values.languageTextTitleTr && 'Title Text (Tr)',
selectedColumns.size === 0 && 'Sütun seçimi',
].filter(Boolean) as string[]
const step2CanGo = step2Missing.length === 0
@ -89,8 +93,9 @@ const WizardStep2 = ({
type="text"
autoComplete="off"
name="listFormCode"
placeholder="e.g. App.Soutes"
placeholder="e.g. App.Wizard.Routes"
component={Input}
disabled={true}
/>
</FormItem>
@ -145,66 +150,10 @@ const WizardStep2 = ({
/>
</FormItem>
)}
<FormItem
label="Title Text (En)"
invalid={!!(errors.languageTextTitleEn && touched.languageTextTitleEn)}
errorMessage={errors.languageTextTitleEn}
>
<Field
type="text"
autoComplete="off"
name="languageTextTitleEn"
placeholder="English Title Text"
component={Input}
/>
</FormItem>
<FormItem
label="Title Text (Tr)"
invalid={!!(errors.languageTextTitleTr && touched.languageTextTitleTr)}
errorMessage={errors.languageTextTitleTr}
>
<Field
type="text"
autoComplete="off"
name="languageTextTitleTr"
placeholder="Turkish Title Text"
component={Input}
/>
</FormItem>
<FormItem
label="Description Text (En)"
invalid={!!(errors.languageTextDescEn && touched.languageTextDescEn)}
errorMessage={errors.languageTextDescEn}
>
<Field
type="text"
autoComplete="off"
name="languageTextDescEn"
placeholder="English Description Text"
component={Input}
/>
</FormItem>
<FormItem
label="Description Text (Tr)"
invalid={!!(errors.languageTextDescTr && touched.languageTextDescTr)}
errorMessage={errors.languageTextDescTr}
>
<Field
type="text"
autoComplete="off"
name="languageTextDescTr"
placeholder="Turkish Description Text"
component={Input}
/>
</FormItem>
</div>
{/* Select Command + Key Field Name */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6">
<FormItem
label="Select Command"
invalid={!!(errors.selectCommand && touched.selectCommand)}
@ -268,7 +217,6 @@ const WizardStep2 = ({
: []
return (
<Select
componentAs={CreatableSelect}
field={field}
form={form}
isClearable
@ -361,6 +309,94 @@ const WizardStep2 = ({
)}
</Field>
</FormItem>
<FormItem
label="Is Tenant?"
invalid={!!(errors.isTenant && touched.isTenant)}
errorMessage={errors.isTenant}
>
<Field
type="checkbox"
autoComplete="off"
name="isTenant"
component={Checkbox}
/>
</FormItem>
<FormItem
label="Is Branch?"
invalid={!!(errors.isBranch && touched.isBranch)}
errorMessage={errors.isBranch}
>
<Field
type="checkbox"
autoComplete="off"
name="isBranch"
component={Checkbox}
/>
</FormItem>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6">
<FormItem
label="Title Text (English)"
asterisk={true}
invalid={!!(errors.languageTextTitleEn && touched.languageTextTitleEn)}
errorMessage={errors.languageTextTitleEn}
>
<Field
type="text"
autoComplete="off"
name="languageTextTitleEn"
placeholder="English Title Text"
component={Input}
/>
</FormItem>
<FormItem
label="Title Text (Turkish)"
asterisk={true}
invalid={!!(errors.languageTextTitleTr && touched.languageTextTitleTr)}
errorMessage={errors.languageTextTitleTr}
>
<Field
type="text"
autoComplete="off"
name="languageTextTitleTr"
placeholder="Turkish Title Text"
component={Input}
/>
</FormItem>
<FormItem
label="Description Text (English)"
asterisk={true}
invalid={!!(errors.languageTextDescEn && touched.languageTextDescEn)}
errorMessage={errors.languageTextDescEn}
>
<Field
type="text"
autoComplete="off"
name="languageTextDescEn"
placeholder="English Description Text"
component={Input}
/>
</FormItem>
<FormItem
label="Description Text (Turkish)"
asterisk={true}
invalid={!!(errors.languageTextDescTr && touched.languageTextDescTr)}
errorMessage={errors.languageTextDescTr}
>
<Field
type="text"
autoComplete="off"
name="languageTextDescTr"
placeholder="Turkish Description Text"
component={Input}
/>
</FormItem>
</div>
{/* Column Selection Panel */}

View file

@ -751,17 +751,17 @@ const WizardStep3 = ({
{/* ── 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 variant="default" type="button" icon={<FaCode />} onClick={() => setIsHelperOpen(true)}>
{translate('::Helper Codes') || 'Helper Codes'}
</Button>
<Button variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
<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)}>
{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>
)}
<Button 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>

View file

@ -13,7 +13,7 @@ import {
FaRocket,
FaSpinner,
} from 'react-icons/fa'
import { selectCommandTypeOptions } from './edit/options'
import { dbSourceTypeOptions, selectCommandTypeOptions } from './edit/options'
// ─── Types ────────────────────────────────────────────────────────────────────
@ -230,7 +230,13 @@ const WizardStep4 = ({
/>
<Row label="Select Command" value={values.selectCommand} />
<Row label="Key Field" value={values.keyFieldName} />
<Row label="Key Field Tipi" value={String(values.keyFieldDbSourceType)} />
<Row
label="Key Field Tipi"
value={
dbSourceTypeOptions.find((o: any) => o.value === values.keyFieldDbSourceType)
?.label ?? String(values.keyFieldDbSourceType)
}
/>
</Section>
</div>
@ -384,11 +390,19 @@ const WizardStep4 = ({
{/* ── 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 variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack} disabled={isDeploying}>
<Button
size='sm'
variant="default"
type="button"
icon={<FaArrowLeft />}
onClick={onBack}
disabled={isDeploying}
>
{translate('::Back') || 'Back'}
</Button>
<div className="flex-1 flex items-center justify-end">
<Button
size='sm'
variant="solid"
type="button"
icon={<FaRocket />}

View file

@ -86,8 +86,8 @@ export default defineConfig(async ({ mode }) => {
: undefined,
manifest: {
name: 'Sozsoft Platform',
short_name: 'Sozsoft Platform',
name: 'Sözsoft Platform',
short_name: 'Sözsoft Platform',
theme_color: '#FF99C8',
background_color: '#f0e7db',
display: 'standalone',
@ -112,7 +112,7 @@ export default defineConfig(async ({ mode }) => {
},
],
categories: ['business', 'productivity'],
description: 'Sozsoft Platform Application',
description: 'Sözsoft Platform Application',
},
}),
],