Menüsüz Wizard ekleme
This commit is contained in:
parent
b1dac82d56
commit
ec6b35d6d5
11 changed files with 517 additions and 391 deletions
|
|
@ -27,6 +27,14 @@ public class ListFormWizardDto
|
||||||
public string MenuCode { get; set; }
|
public string MenuCode { get; set; }
|
||||||
public int MenuOrder { get; set; }
|
public int MenuOrder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wizard'in menuye eklenip eklenmeyecegi. False ise menu (ve gerekiyorsa ust menu) kaydi
|
||||||
|
/// hic uretilmez; ListForm, permission ve dil anahtarlari her iki durumda da uretilir.
|
||||||
|
/// Menusuz wizard'lar baska bir ekranda SubGrid/parca olarak kullanilir.
|
||||||
|
/// Varsayilan true oldugu icin bu alani tasimayan eski seed dosyalari eskisi gibi calisir.
|
||||||
|
/// </summary>
|
||||||
|
public bool CreateMenu { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>Custom yolunda menuye baglanacak CustomComponent'in adi.</summary>
|
/// <summary>Custom yolunda menuye baglanacak CustomComponent'in adi.</summary>
|
||||||
public string CustomComponentName { get; set; }
|
public string CustomComponentName { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,11 @@ public class ListFormWizardAppService(
|
||||||
}
|
}
|
||||||
|
|
||||||
//Permission Group
|
//Permission Group
|
||||||
var groupName = input.PermissionGroupName ?? PlatformConsts.AppName;
|
// Menuye eklenmeyen wizard'da izin grubu sorulmaz; bos geldiginde varsayilan gruba dusar.
|
||||||
|
var groupName = string.IsNullOrWhiteSpace(input.PermissionGroupName)
|
||||||
|
? PlatformConsts.AppName
|
||||||
|
: input.PermissionGroupName.Trim();
|
||||||
|
input.PermissionGroupName = groupName;
|
||||||
if (!await repoPermGroup.AnyAsync(a => a.Name == groupName))
|
if (!await repoPermGroup.AnyAsync(a => a.Name == groupName))
|
||||||
{
|
{
|
||||||
await repoPermGroup.InsertAsync(new PermissionGroupDefinitionRecord(GuidGenerator.Create(), groupName, groupName), autoSave: false);
|
await repoPermGroup.InsertAsync(new PermissionGroupDefinitionRecord(GuidGenerator.Create(), groupName, groupName), autoSave: false);
|
||||||
|
|
@ -208,69 +212,75 @@ public class ListFormWizardAppService(
|
||||||
await permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: false);
|
await permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Menu Parent
|
// Menunun acacagi adres: Custom yolunda CustomComponent'in kendi rotasi (bu rota
|
||||||
var menuQueryable = await repoMenu.GetQueryableAsync();
|
// mapCustomComponentRoutes tarafindan zaten kayitlidir), aksi halde ListForm adresi.
|
||||||
var menuParent = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == input.MenuParentCode));
|
// Seed dosyasindan da gorulebilmesi icin input'a geri yazilir; menu uretilmese de
|
||||||
if (menuParent == null)
|
// ListForm'un adresi degismedigi icin ayni sekilde hesaplanir.
|
||||||
{
|
input.MenuUrl = isCustomComponent ? customRoutePath : WizardConsts.MenuUrl(code);
|
||||||
var maxRootOrder = menuQueryable.Where(a => a.ParentCode == null || a.ParentCode == "").Select(a => (int?)a.Order).Max() ?? 0;
|
|
||||||
var menuParentIcon = !string.IsNullOrWhiteSpace(input.MenuParentIcon)
|
|
||||||
? input.MenuParentIcon
|
|
||||||
: !string.IsNullOrWhiteSpace(input.MenuIcon)
|
|
||||||
? input.MenuIcon
|
|
||||||
: WizardConsts.MenuIcon;
|
|
||||||
await CreateLangKey(input.MenuParentCode, input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr, inserted);
|
|
||||||
menuParent = await repoMenu.InsertAsync(new Menu
|
|
||||||
{
|
|
||||||
Code = input.MenuParentCode,
|
|
||||||
DisplayName = input.MenuParentCode,
|
|
||||||
IsDisabled = false,
|
|
||||||
ShortName = input.MenuParentShortName?.Trim(),
|
|
||||||
Icon = menuParentIcon,
|
|
||||||
Order = maxRootOrder + 1,
|
|
||||||
}, autoSave: false);
|
|
||||||
inserted.MenuCodes.Add(input.MenuParentCode);
|
|
||||||
}
|
|
||||||
else if (string.IsNullOrWhiteSpace(menuParent.ShortName) &&
|
|
||||||
!string.IsNullOrWhiteSpace(input.MenuParentShortName))
|
|
||||||
{
|
|
||||||
menuParent.ShortName = input.MenuParentShortName.Trim();
|
|
||||||
await repoMenu.UpdateAsync(menuParent, autoSave: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Menu
|
// Menusuz wizard: menu (ve ust menu) kaydi hic uretilmez, geri kalan her sey aynidir.
|
||||||
var maxChildOrder = menuQueryable.Where(a => a.ParentCode == menuParent.Code).Select(a => (int?)a.Order).Max() ?? 0;
|
if (input.CreateMenu)
|
||||||
var menuOrder = input.MenuOrder > 0 ? input.MenuOrder : maxChildOrder + 1;
|
|
||||||
input.MenuOrder = menuOrder;
|
|
||||||
// Menünün açacağı adres: Custom yolunda CustomComponent'in kendi rotası (bu rota
|
|
||||||
// mapCustomComponentRoutes tarafından zaten kayıtlıdır), aksi halde ListForm adresi.
|
|
||||||
// Seed dosyasından da görülebilmesi için input'a geri yazılır.
|
|
||||||
var menuUrl = isCustomComponent ? customRoutePath : WizardConsts.MenuUrl(code);
|
|
||||||
input.MenuUrl = menuUrl;
|
|
||||||
var existingMenu = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == code));
|
|
||||||
if (existingMenu == null)
|
|
||||||
{
|
{
|
||||||
await repoMenu.InsertAsync(new Menu
|
//Menu Parent
|
||||||
|
var menuQueryable = await repoMenu.GetQueryableAsync();
|
||||||
|
var menuParent = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == input.MenuParentCode));
|
||||||
|
if (menuParent == null)
|
||||||
{
|
{
|
||||||
Code = code,
|
var maxRootOrder = menuQueryable.Where(a => a.ParentCode == null || a.ParentCode == "").Select(a => (int?)a.Order).Max() ?? 0;
|
||||||
DisplayName = nameLangKey,
|
var menuParentIcon = !string.IsNullOrWhiteSpace(input.MenuParentIcon)
|
||||||
IsDisabled = false,
|
? input.MenuParentIcon
|
||||||
ParentCode = menuParent.Code,
|
: !string.IsNullOrWhiteSpace(input.MenuIcon)
|
||||||
Icon = input.MenuIcon ?? WizardConsts.MenuIcon,
|
? input.MenuIcon
|
||||||
Target = null,
|
: WizardConsts.MenuIcon;
|
||||||
ElementId = null,
|
await CreateLangKey(input.MenuParentCode, input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr, inserted);
|
||||||
CssClass = null,
|
menuParent = await repoMenu.InsertAsync(new Menu
|
||||||
Url = menuUrl,
|
{
|
||||||
RequiredPermissionName = permRead.Name,
|
Code = input.MenuParentCode,
|
||||||
Order = menuOrder,
|
DisplayName = input.MenuParentCode,
|
||||||
}, autoSave: false);
|
IsDisabled = false,
|
||||||
inserted.MenuCodes.Add(code);
|
ShortName = input.MenuParentShortName?.Trim(),
|
||||||
}
|
Icon = menuParentIcon,
|
||||||
else if (existingMenu.Order != menuOrder || existingMenu.Url != menuUrl)
|
Order = maxRootOrder + 1,
|
||||||
{
|
}, autoSave: false);
|
||||||
existingMenu.Order = menuOrder;
|
inserted.MenuCodes.Add(input.MenuParentCode);
|
||||||
existingMenu.Url = menuUrl;
|
}
|
||||||
await repoMenu.UpdateAsync(existingMenu, autoSave: false);
|
else if (string.IsNullOrWhiteSpace(menuParent.ShortName) &&
|
||||||
|
!string.IsNullOrWhiteSpace(input.MenuParentShortName))
|
||||||
|
{
|
||||||
|
menuParent.ShortName = input.MenuParentShortName.Trim();
|
||||||
|
await repoMenu.UpdateAsync(menuParent, autoSave: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Menu
|
||||||
|
var maxChildOrder = menuQueryable.Where(a => a.ParentCode == menuParent.Code).Select(a => (int?)a.Order).Max() ?? 0;
|
||||||
|
var menuOrder = input.MenuOrder > 0 ? input.MenuOrder : maxChildOrder + 1;
|
||||||
|
input.MenuOrder = menuOrder;
|
||||||
|
var menuUrl = input.MenuUrl;
|
||||||
|
var existingMenu = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == code));
|
||||||
|
if (existingMenu == null)
|
||||||
|
{
|
||||||
|
await repoMenu.InsertAsync(new Menu
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
DisplayName = nameLangKey,
|
||||||
|
IsDisabled = false,
|
||||||
|
ParentCode = menuParent.Code,
|
||||||
|
Icon = input.MenuIcon ?? WizardConsts.MenuIcon,
|
||||||
|
Target = null,
|
||||||
|
ElementId = null,
|
||||||
|
CssClass = null,
|
||||||
|
Url = menuUrl,
|
||||||
|
RequiredPermissionName = permRead.Name,
|
||||||
|
Order = menuOrder,
|
||||||
|
}, autoSave: false);
|
||||||
|
inserted.MenuCodes.Add(code);
|
||||||
|
}
|
||||||
|
else if (existingMenu.Order != menuOrder || existingMenu.Url != menuUrl)
|
||||||
|
{
|
||||||
|
existingMenu.Order = menuOrder;
|
||||||
|
existingMenu.Url = menuUrl;
|
||||||
|
await repoMenu.UpdateAsync(existingMenu, autoSave: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom yolunda ListForm ekosistemi üretilmez: veri kaynağı, ListForm, alanlar,
|
// Custom yolunda ListForm ekosistemi üretilmez: veri kaynağı, ListForm, alanlar,
|
||||||
|
|
|
||||||
|
|
@ -1503,8 +1503,8 @@
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.Listforms.Listform",
|
"key": "App.Listforms.Listform",
|
||||||
"en": "Listforms",
|
"en": "ListForm Components",
|
||||||
"tr": "Listformlar"
|
"tr": "List Form Bileşenleri"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
|
|
@ -16326,6 +16326,18 @@
|
||||||
"en": "Automatically assigned, editable",
|
"en": "Automatically assigned, editable",
|
||||||
"tr": "Otomatik atanır, düzenlenebilir"
|
"tr": "Otomatik atanır, düzenlenebilir"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.WizardStep1.CreateMenu",
|
||||||
|
"en": "Show in menu",
|
||||||
|
"tr": "Menüde göster"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.WizardStep1.CreateMenuHint",
|
||||||
|
"en": "If off, no menu record is created; the list is still usable as a sub grid or component",
|
||||||
|
"tr": "Kapalıysa menü kaydı oluşturulmaz; liste yine SubGrid veya bileşen olarak kullanılabilir"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.Platform.Add",
|
"key": "App.Platform.Add",
|
||||||
|
|
@ -16836,12 +16848,6 @@
|
||||||
"en": "Decides whether the wizard generates a list form or links an existing custom component",
|
"en": "Decides whether the wizard generates a list form or links an existing custom component",
|
||||||
"tr": "Wizard'ın liste formu mu üreteceğini yoksa mevcut bir özel bileşene mi bağlanacağını belirler"
|
"tr": "Wizard'ın liste formu mu üreteceğini yoksa mevcut bir özel bileşene mi bağlanacağını belirler"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"resourceName": "Platform",
|
|
||||||
"key": "App.WizardStepComponent.ListComponent",
|
|
||||||
"en": "List Component",
|
|
||||||
"tr": "Liste Bileşeni"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.WizardStepComponent.ListComponentHint",
|
"key": "App.WizardStepComponent.ListComponentHint",
|
||||||
|
|
|
||||||
|
|
@ -797,16 +797,6 @@
|
||||||
"RequiredPermissionName": "App.DataSource",
|
"RequiredPermissionName": "App.DataSource",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ParentCode": "App.Saas",
|
|
||||||
"Code": "App.Listforms.Listform",
|
|
||||||
"DisplayName": "App.Listforms.Listform",
|
|
||||||
"Order": 9,
|
|
||||||
"Url": "/admin/list/App.Listforms.Listform",
|
|
||||||
"Icon": "FcDataSheet",
|
|
||||||
"RequiredPermissionName": "App.Listforms.Listform",
|
|
||||||
"IsDisabled": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ParentCode": "App.Saas",
|
"ParentCode": "App.Saas",
|
||||||
"Code": "App.Notifications",
|
"Code": "App.Notifications",
|
||||||
|
|
@ -978,6 +968,16 @@
|
||||||
"RequiredPermissionName": "App.DeveloperKit.Components",
|
"RequiredPermissionName": "App.DeveloperKit.Components",
|
||||||
"IsDisabled": false
|
"IsDisabled": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ParentCode": "App.DeveloperKit",
|
||||||
|
"Code": "App.Listforms.Listform",
|
||||||
|
"DisplayName": "App.Listforms.Listform",
|
||||||
|
"Order": 6,
|
||||||
|
"Url": "/admin/list/App.Listforms.Listform",
|
||||||
|
"Icon": "FcDataSheet",
|
||||||
|
"RequiredPermissionName": "App.Listforms.Listform",
|
||||||
|
"IsDisabled": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ParentCode": "App.DeveloperKit",
|
"ParentCode": "App.DeveloperKit",
|
||||||
"Code": "App.Listforms.WizardManager",
|
"Code": "App.Listforms.WizardManager",
|
||||||
|
|
|
||||||
|
|
@ -117,8 +117,12 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
|
|
||||||
// Zaten seeded mi kontrol et. Custom yolunda ListForm üretilmediği için
|
// Zaten seeded mi kontrol et. Custom yolunda ListForm üretilmediği için
|
||||||
// idempotency menü kaydı üzerinden değerlendirilir.
|
// idempotency menü kaydı üzerinden değerlendirilir.
|
||||||
|
// Menusuz Custom wizard'da ne ListForm ne de menu kaydi olustugu icin
|
||||||
|
// idempotency okuma permission'i uzerinden degerlendirilir.
|
||||||
var alreadySeeded = seedFile.Wizard.ComponentKind == WizardComponentKindEnum.Custom
|
var alreadySeeded = seedFile.Wizard.ComponentKind == WizardComponentKindEnum.Custom
|
||||||
? await _repoMenu.AnyAsync(a => a.Code == ResolveMenuCode(seedFile.Wizard))
|
? seedFile.Wizard.CreateMenu
|
||||||
|
? await _repoMenu.AnyAsync(a => a.Code == ResolveMenuCode(seedFile.Wizard))
|
||||||
|
: await _repoPerm.AnyAsync(a => a.Name == ResolveMenuCode(seedFile.Wizard))
|
||||||
: await _repoListForm.AnyAsync(a => a.ListFormCode == seedFile.Wizard.ListFormCode);
|
: await _repoListForm.AnyAsync(a => a.ListFormCode == seedFile.Wizard.ListFormCode);
|
||||||
|
|
||||||
if (alreadySeeded)
|
if (alreadySeeded)
|
||||||
|
|
@ -181,7 +185,9 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permission Group
|
// Permission Group
|
||||||
var groupName = input.PermissionGroupName ?? AppName;
|
// Menuye eklenmeyen wizard'da izin grubu sorulmaz; bos geldiginde varsayilan gruba dusar.
|
||||||
|
var groupName = string.IsNullOrWhiteSpace(input.PermissionGroupName) ? AppName : input.PermissionGroupName.Trim();
|
||||||
|
input.PermissionGroupName = groupName;
|
||||||
if (!await _repoPermGroup.AnyAsync(a => a.Name == groupName))
|
if (!await _repoPermGroup.AnyAsync(a => a.Name == groupName))
|
||||||
{
|
{
|
||||||
await _repoPermGroup.InsertAsync(
|
await _repoPermGroup.InsertAsync(
|
||||||
|
|
@ -249,64 +255,68 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
// await _permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: true);
|
// await _permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: true);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Menu Parent
|
// Menunun acacagi adres; menu uretilmese de seed dosyasindaki degeri korunur.
|
||||||
var menuQueryable = await _repoMenu.GetQueryableAsync();
|
input.MenuUrl = isCustomComponent
|
||||||
var menuParent = await _repoMenu.FirstOrDefaultAsync(a => a.Code == input.MenuParentCode);
|
|
||||||
if (menuParent == null)
|
|
||||||
{
|
|
||||||
var maxRootOrder = menuQueryable.Where(a => a.ParentCode == null || a.ParentCode == "").Select(a => (int?)a.Order).Max() ?? 0;
|
|
||||||
var menuParentIcon = !string.IsNullOrWhiteSpace(input.MenuParentIcon)
|
|
||||||
? input.MenuParentIcon
|
|
||||||
: !string.IsNullOrWhiteSpace(input.MenuIcon)
|
|
||||||
? input.MenuIcon
|
|
||||||
: WizardConsts.MenuIcon;
|
|
||||||
await CreateLangKeyAsync(input.MenuParentCode, input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr);
|
|
||||||
menuParent = await _repoMenu.InsertAsync(new Menu
|
|
||||||
{
|
|
||||||
Code = input.MenuParentCode,
|
|
||||||
DisplayName = input.MenuParentCode,
|
|
||||||
IsDisabled = false,
|
|
||||||
ShortName = input.MenuParentShortName,
|
|
||||||
Icon = menuParentIcon,
|
|
||||||
Order = maxRootOrder + 1,
|
|
||||||
}, autoSave: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menu
|
|
||||||
var maxChildOrder = menuQueryable.Where(a => a.ParentCode == menuParent.Code).Select(a => (int?)a.Order).Max() ?? 0;
|
|
||||||
var menuOrder = input.MenuOrder > 0 ? input.MenuOrder : maxChildOrder + 1;
|
|
||||||
input.MenuOrder = menuOrder;
|
|
||||||
// Menünün açacağı adres. Custom yolunda normalize edilmis rota, List yolunda
|
|
||||||
// MenuUrl alani (tasimayan eski dosyalar icin AppService.Create ile aynı kural).
|
|
||||||
var menuUrl = isCustomComponent
|
|
||||||
? customRoutePath
|
? customRoutePath
|
||||||
: !string.IsNullOrWhiteSpace(input.MenuUrl)
|
: !string.IsNullOrWhiteSpace(input.MenuUrl)
|
||||||
? input.MenuUrl.Trim()
|
? input.MenuUrl.Trim()
|
||||||
: WizardConsts.MenuUrl(code);
|
: WizardConsts.MenuUrl(code);
|
||||||
input.MenuUrl = menuUrl;
|
|
||||||
var existingMenu = await _repoMenu.FirstOrDefaultAsync(a => a.Code == code);
|
// Menusuz wizard: menu (ve ust menu) kaydi hic uretilmez.
|
||||||
if (existingMenu == null)
|
if (input.CreateMenu)
|
||||||
{
|
{
|
||||||
await _repoMenu.InsertAsync(new Menu
|
// Menu Parent
|
||||||
|
var menuQueryable = await _repoMenu.GetQueryableAsync();
|
||||||
|
var menuParent = await _repoMenu.FirstOrDefaultAsync(a => a.Code == input.MenuParentCode);
|
||||||
|
if (menuParent == null)
|
||||||
{
|
{
|
||||||
Code = code,
|
var maxRootOrder = menuQueryable.Where(a => a.ParentCode == null || a.ParentCode == "").Select(a => (int?)a.Order).Max() ?? 0;
|
||||||
DisplayName = nameLangKey,
|
var menuParentIcon = !string.IsNullOrWhiteSpace(input.MenuParentIcon)
|
||||||
IsDisabled = false,
|
? input.MenuParentIcon
|
||||||
ParentCode = menuParent.Code,
|
: !string.IsNullOrWhiteSpace(input.MenuIcon)
|
||||||
Icon = input.MenuIcon ?? WizardConsts.MenuIcon,
|
? input.MenuIcon
|
||||||
Target = null,
|
: WizardConsts.MenuIcon;
|
||||||
ElementId = null,
|
await CreateLangKeyAsync(input.MenuParentCode, input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr);
|
||||||
CssClass = null,
|
menuParent = await _repoMenu.InsertAsync(new Menu
|
||||||
Url = menuUrl,
|
{
|
||||||
RequiredPermissionName = permRead.Name,
|
Code = input.MenuParentCode,
|
||||||
Order = menuOrder,
|
DisplayName = input.MenuParentCode,
|
||||||
}, autoSave: true);
|
IsDisabled = false,
|
||||||
}
|
ShortName = input.MenuParentShortName,
|
||||||
else if (existingMenu.Order != menuOrder || existingMenu.Url != menuUrl)
|
Icon = menuParentIcon,
|
||||||
{
|
Order = maxRootOrder + 1,
|
||||||
existingMenu.Order = menuOrder;
|
}, autoSave: true);
|
||||||
existingMenu.Url = menuUrl;
|
}
|
||||||
await _repoMenu.UpdateAsync(existingMenu, autoSave: true);
|
|
||||||
|
// Menu
|
||||||
|
var maxChildOrder = menuQueryable.Where(a => a.ParentCode == menuParent.Code).Select(a => (int?)a.Order).Max() ?? 0;
|
||||||
|
var menuOrder = input.MenuOrder > 0 ? input.MenuOrder : maxChildOrder + 1;
|
||||||
|
input.MenuOrder = menuOrder;
|
||||||
|
var menuUrl = input.MenuUrl;
|
||||||
|
var existingMenu = await _repoMenu.FirstOrDefaultAsync(a => a.Code == code);
|
||||||
|
if (existingMenu == null)
|
||||||
|
{
|
||||||
|
await _repoMenu.InsertAsync(new Menu
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
DisplayName = nameLangKey,
|
||||||
|
IsDisabled = false,
|
||||||
|
ParentCode = menuParent.Code,
|
||||||
|
Icon = input.MenuIcon ?? WizardConsts.MenuIcon,
|
||||||
|
Target = null,
|
||||||
|
ElementId = null,
|
||||||
|
CssClass = null,
|
||||||
|
Url = menuUrl,
|
||||||
|
RequiredPermissionName = permRead.Name,
|
||||||
|
Order = menuOrder,
|
||||||
|
}, autoSave: true);
|
||||||
|
}
|
||||||
|
else if (existingMenu.Order != menuOrder || existingMenu.Url != menuUrl)
|
||||||
|
{
|
||||||
|
existingMenu.Order = menuOrder;
|
||||||
|
existingMenu.Url = menuUrl;
|
||||||
|
await _repoMenu.UpdateAsync(existingMenu, autoSave: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom yolunda ListForm ekosistemi üretilmez.
|
// Custom yolunda ListForm ekosistemi üretilmez.
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,12 @@ export interface ListFormWizardDto {
|
||||||
menuCode: string
|
menuCode: string
|
||||||
menuOrder: number
|
menuOrder: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wizard menüye eklensin mi. False ise menü kaydı hiç üretilmez; ListForm, permission ve
|
||||||
|
* dil anahtarları yine üretilir (SubGrid gibi başka bir ekranın parçası olarak kullanılır).
|
||||||
|
*/
|
||||||
|
createMenu: boolean
|
||||||
|
|
||||||
customComponentName: string
|
customComponentName: string
|
||||||
/** Menünün açtığı adres; Custom yolunda seçilen bileşenin rota yolu. */
|
/** Menünün açtığı adres; Custom yolunda seçilen bileşenin rota yolu. */
|
||||||
menuUrl: string
|
menuUrl: string
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ const initialValues: ListFormWizardDto = {
|
||||||
listFormCode: '',
|
listFormCode: '',
|
||||||
menuCode: '',
|
menuCode: '',
|
||||||
menuOrder: 0,
|
menuOrder: 0,
|
||||||
|
createMenu: true,
|
||||||
componentKind: WizardComponentKindEnum.List,
|
componentKind: WizardComponentKindEnum.List,
|
||||||
customComponentName: '',
|
customComponentName: '',
|
||||||
menuUrl: '',
|
menuUrl: '',
|
||||||
|
|
@ -174,7 +175,16 @@ const initialValues: ListFormWizardDto = {
|
||||||
const step1ValidationSchema = Yup.object().shape({
|
const step1ValidationSchema = Yup.object().shape({
|
||||||
wizardName: Yup.string().required(),
|
wizardName: Yup.string().required(),
|
||||||
menuCode: Yup.string().required(),
|
menuCode: Yup.string().required(),
|
||||||
menuOrder: Yup.number().integer().min(1).required(),
|
// Menüye eklenmeyen wizard'da sıra alanı hiç sorulmaz; zorunluluğu da kalkar.
|
||||||
|
menuOrder: Yup.number()
|
||||||
|
.integer()
|
||||||
|
.when('createMenu', {
|
||||||
|
is: (createMenu: boolean) => createMenu !== false,
|
||||||
|
then: (schema) => schema.min(1).required(),
|
||||||
|
otherwise: (schema) => schema.optional(),
|
||||||
|
}),
|
||||||
|
createMenu: Yup.boolean(),
|
||||||
|
// İzin grubu menüden bağımsızdır: menüsüz wizard'da da izinler üretilir ve bir gruba yazılır.
|
||||||
permissionGroupName: Yup.string().required(),
|
permissionGroupName: Yup.string().required(),
|
||||||
languageTextMenuEn: Yup.string().required(),
|
languageTextMenuEn: Yup.string().required(),
|
||||||
languageTextMenuTr: Yup.string().required(),
|
languageTextMenuTr: Yup.string().required(),
|
||||||
|
|
@ -595,6 +605,8 @@ const Wizard = () => {
|
||||||
wizardName: w.wizardName ?? '',
|
wizardName: w.wizardName ?? '',
|
||||||
listFormCode: w.listFormCode ?? '',
|
listFormCode: w.listFormCode ?? '',
|
||||||
menuCode: w.menuCode ?? '',
|
menuCode: w.menuCode ?? '',
|
||||||
|
// Alanı taşımayan eski seed dosyaları menülü wizard olarak açılır.
|
||||||
|
createMenu: w.createMenu ?? true,
|
||||||
// Bu alanları içermeyen eski seed dosyaları List yolunda açılır.
|
// Bu alanları içermeyen eski seed dosyaları List yolunda açılır.
|
||||||
componentKind: w.componentKind ?? WizardComponentKindEnum.List,
|
componentKind: w.componentKind ?? WizardComponentKindEnum.List,
|
||||||
customComponentName: w.customComponentName ?? '',
|
customComponentName: w.customComponentName ?? '',
|
||||||
|
|
@ -830,6 +842,23 @@ const Wizard = () => {
|
||||||
formikRef.current?.setFieldValue('permissionGroupName', rootCode)
|
formikRef.current?.setFieldValue('permissionGroupName', rootCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menüye eklenmeyecek wizard'da menüye özel alanlar temizlenir; aksi halde seed dosyasına
|
||||||
|
* hiç kullanılmayacak üst menü/ikon/sıra değerleri yazılır ve deploy özeti yanıltır.
|
||||||
|
*/
|
||||||
|
const handleCreateMenuChange = (createMenu: boolean) => {
|
||||||
|
const formik = formikRef.current
|
||||||
|
formik?.setFieldValue('createMenu', createMenu)
|
||||||
|
if (createMenu) return
|
||||||
|
formik?.setFieldValue('menuParentCode', '')
|
||||||
|
formik?.setFieldValue('menuParentShortName', '')
|
||||||
|
formik?.setFieldValue('menuParentIcon', '')
|
||||||
|
formik?.setFieldValue('languageTextMenuParentEn', '')
|
||||||
|
formik?.setFieldValue('languageTextMenuParentTr', '')
|
||||||
|
formik?.setFieldValue('menuIcon', '')
|
||||||
|
formik?.setFieldValue('menuOrder', 0)
|
||||||
|
}
|
||||||
|
|
||||||
const handleMenuParentChange = (code: string) => {
|
const handleMenuParentChange = (code: string) => {
|
||||||
const formik = formikRef.current
|
const formik = formikRef.current
|
||||||
const parentChanged = formik?.values.menuParentCode !== code
|
const parentChanged = formik?.values.menuParentCode !== code
|
||||||
|
|
@ -1180,6 +1209,7 @@ const Wizard = () => {
|
||||||
menuTree={menuTree}
|
menuTree={menuTree}
|
||||||
isLoadingMenu={isLoadingMenu}
|
isLoadingMenu={isLoadingMenu}
|
||||||
onMenuParentChange={handleMenuParentChange}
|
onMenuParentChange={handleMenuParentChange}
|
||||||
|
onCreateMenuChange={handleCreateMenuChange}
|
||||||
onClearMenuParent={() => {
|
onClearMenuParent={() => {
|
||||||
formikRef.current?.setFieldValue('menuParentCode', '')
|
formikRef.current?.setFieldValue('menuParentCode', '')
|
||||||
formikRef.current?.setFieldValue('menuParentShortName', '')
|
formikRef.current?.setFieldValue('menuParentShortName', '')
|
||||||
|
|
|
||||||
|
|
@ -354,11 +354,11 @@ const WizardFileManager = () => {
|
||||||
className="flex flex-col gap-2 p-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800"
|
className="flex flex-col gap-2 p-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800"
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-2 min-w-0">
|
<div className="flex items-start gap-2 min-w-0">
|
||||||
<Icon className="text-indigo-400 shrink-0 text-2xl" />
|
<Icon className="text-indigo-400 shrink-0 text-lg mt-0.5" />
|
||||||
<div className="min-w-0 flex-1 space-y-1">
|
<div className="min-w-0 flex-1 space-y-1">
|
||||||
{/* 1. satır: wizard adı */}
|
{/* 1. satır: wizard adı */}
|
||||||
<div
|
<div
|
||||||
className="font-medium text-sm text-gray-800 dark:text-gray-200 truncate"
|
className="font-medium text-sm leading-6 text-gray-800 dark:text-gray-200 truncate"
|
||||||
title={f.wizardName || f.fileName}
|
title={f.wizardName || f.fileName}
|
||||||
>
|
>
|
||||||
{f.wizardName || f.fileName}
|
{f.wizardName || f.fileName}
|
||||||
|
|
@ -368,85 +368,9 @@ const WizardFileManager = () => {
|
||||||
<div className="text-xs text-gray-400 truncate font-mono" title={f.fileName}>
|
<div className="text-xs text-gray-400 truncate font-mono" title={f.fileName}>
|
||||||
{f.fileName}
|
{f.fileName}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-400 truncate">
|
|
||||||
{isCustom ? f.customComponentName : f.listFormCode}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 3. satır: yol ayrımı (Custom / List), layout ve menü adresi */}
|
|
||||||
<div className="flex flex-wrap items-center gap-1.5 text-xs">
|
|
||||||
<span
|
|
||||||
className={classNames(
|
|
||||||
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-medium',
|
|
||||||
isCustom
|
|
||||||
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300'
|
|
||||||
: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-300',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{isCustom ? <FaCube /> : <FaListUl />}
|
|
||||||
{translate(
|
|
||||||
isCustom
|
|
||||||
? '::App.WizardStepComponent.CustomComponent'
|
|
||||||
: '::App.WizardStepComponent.ListComponent',
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{/* Custom yolunda layout kavramı yoktur. */}
|
|
||||||
{!isCustom && f.defaultLayout && (
|
|
||||||
<span className="inline-flex items-center rounded-full bg-gray-200 px-2 py-0.5 font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-300">
|
|
||||||
{f.defaultLayout}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{f.menuUrl && (
|
|
||||||
<span
|
|
||||||
className="inline-flex min-w-0 max-w-full items-center gap-1 rounded-full bg-gray-200 px-2 py-0.5 font-mono text-gray-600 dark:bg-gray-700 dark:text-gray-300"
|
|
||||||
title={f.menuUrl}
|
|
||||||
>
|
|
||||||
<FaLink className="shrink-0" />
|
|
||||||
<span className="truncate">{f.menuUrl}</span>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{renderActions(
|
|
||||||
f,
|
|
||||||
'flex items-center justify-end gap-1 pt-1 border-t border-gray-200 dark:border-gray-700',
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{filteredFiles.map((f) => {
|
|
||||||
const Icon = navigationIcon[f.menuIcon] ?? FaDatabase
|
|
||||||
const isCustom = f.componentKind === WizardComponentKindEnum.Custom
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={f.fileName}
|
|
||||||
className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 p-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
|
||||||
<Icon className="text-indigo-400 shrink-0 text-3xl" />
|
|
||||||
<div className="min-w-0 space-y-1">
|
|
||||||
{/* 1. satır: wizard adı */}
|
|
||||||
<div className="font-medium text-sm text-gray-800 dark:text-gray-200 truncate">
|
|
||||||
{f.wizardName || f.fileName}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 2. satır: dosya adı ve üretilen kod */}
|
|
||||||
<div className="flex flex-wrap items-center gap-x-2 text-xs text-gray-400">
|
|
||||||
<span className="truncate font-mono">{f.fileName}</span>
|
|
||||||
<span className="text-gray-300 dark:text-gray-600">•</span>
|
|
||||||
<span className="truncate">
|
|
||||||
{isCustom ? f.customComponentName : f.listFormCode}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 3. satır: yol ayrımı (Custom / List), layout ve menü adresi */}
|
{/* 3. satır: yol ayrımı (Custom / List), layout ve menü adresi */}
|
||||||
<div className="flex flex-wrap items-center gap-1.5 text-xs">
|
<div className="flex flex-wrap pt-1 items-center gap-1.5 text-xs">
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-medium',
|
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-medium',
|
||||||
|
|
@ -459,10 +383,15 @@ const WizardFileManager = () => {
|
||||||
{translate(
|
{translate(
|
||||||
isCustom
|
isCustom
|
||||||
? '::App.WizardStepComponent.CustomComponent'
|
? '::App.WizardStepComponent.CustomComponent'
|
||||||
: '::App.WizardStepComponent.ListComponent',
|
: '::App.Listforms.Listform',
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
<div className="text-xs text-gray-400 truncate">
|
||||||
|
{isCustom ? f.customComponentName : f.listFormCode}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap pt-1 items-center gap-1.5 text-xs">
|
||||||
{/* Custom yolunda layout kavramı yoktur. */}
|
{/* Custom yolunda layout kavramı yoktur. */}
|
||||||
{!isCustom && f.defaultLayout && (
|
{!isCustom && f.defaultLayout && (
|
||||||
<span className="inline-flex items-center rounded-full bg-gray-200 px-2 py-0.5 font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-300">
|
<span className="inline-flex items-center rounded-full bg-gray-200 px-2 py-0.5 font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-300">
|
||||||
|
|
@ -471,7 +400,10 @@ const WizardFileManager = () => {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{f.menuUrl && (
|
{f.menuUrl && (
|
||||||
<span className="inline-flex min-w-0 items-center gap-1 rounded-full bg-gray-200 px-2 py-0.5 font-mono text-gray-600 dark:bg-gray-700 dark:text-gray-300">
|
<span
|
||||||
|
className="inline-flex min-w-0 max-w-full items-center gap-1 rounded-full bg-gray-200 px-2 py-0.5 font-mono text-gray-600 dark:bg-gray-700 dark:text-gray-300"
|
||||||
|
title={f.menuUrl}
|
||||||
|
>
|
||||||
<FaLink className="shrink-0" />
|
<FaLink className="shrink-0" />
|
||||||
<span className="truncate">{f.menuUrl}</span>
|
<span className="truncate">{f.menuUrl}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -480,11 +412,103 @@ const WizardFileManager = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{renderActions(f, 'flex items-center gap-1 shrink-0 sm:ml-3')}
|
{renderActions(
|
||||||
|
f,
|
||||||
|
'flex items-center justify-end gap-1 pt-1 border-t border-gray-200 dark:border-gray-700',
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 overflow-x-auto">
|
||||||
|
<table className="w-full text-sm">
|
||||||
|
<thead className="bg-gray-50 dark:bg-gray-800 text-gray-600 dark:text-gray-300">
|
||||||
|
<tr>
|
||||||
|
<th className="text-left font-medium px-4 py-2">
|
||||||
|
{translate('::App.Platform.Name')}
|
||||||
|
</th>
|
||||||
|
<th className="text-left font-medium px-4 py-2">
|
||||||
|
{translate('::App.FileManager.FileName')}
|
||||||
|
</th>
|
||||||
|
<th className="text-left font-medium px-4 py-2">
|
||||||
|
{translate('::App.Platform.Code')}
|
||||||
|
</th>
|
||||||
|
<th className="text-left font-medium px-4 py-2 whitespace-nowrap">
|
||||||
|
{translate('::App.WizardStepComponent.ComponentType')}
|
||||||
|
</th>
|
||||||
|
<th className="text-left font-medium px-4 py-2">
|
||||||
|
{translate('::App.WizardStepComponent.MenuUrl')}
|
||||||
|
</th>
|
||||||
|
<th className="text-right font-medium px-4 py-2">
|
||||||
|
{translate('::App.Platform.Actions')}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{filteredFiles.map((f) => {
|
||||||
|
const Icon = navigationIcon[f.menuIcon] ?? FaDatabase
|
||||||
|
const isCustom = f.componentKind === WizardComponentKindEnum.Custom
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
key={f.fileName}
|
||||||
|
className="border-t border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||||
|
>
|
||||||
|
<td className="px-4 py-2">
|
||||||
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
|
<Icon className="text-indigo-400 shrink-0 text-lg" />
|
||||||
|
<span
|
||||||
|
className="font-medium text-gray-800 dark:text-gray-200 truncate"
|
||||||
|
title={f.wizardName || f.fileName}
|
||||||
|
>
|
||||||
|
{f.wizardName || f.fileName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="px-4 py-2 font-mono text-gray-500 dark:text-gray-400"
|
||||||
|
title={f.fileName}
|
||||||
|
>
|
||||||
|
{f.fileName}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 text-gray-600 dark:text-gray-300">
|
||||||
|
{isCustom ? f.customComponentName : f.listFormCode}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 whitespace-nowrap">
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',
|
||||||
|
isCustom
|
||||||
|
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300'
|
||||||
|
: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-300',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isCustom ? <FaCube /> : <FaListUl />}
|
||||||
|
{translate(
|
||||||
|
isCustom
|
||||||
|
? '::App.WizardStepComponent.CustomComponent'
|
||||||
|
: '::App.Listforms.Listform',
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="px-4 py-2 font-mono text-gray-600 dark:text-gray-300"
|
||||||
|
title={f.menuUrl}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-1 min-w-0">
|
||||||
|
{f.menuUrl && <FaLink className="shrink-0 text-gray-400" />}
|
||||||
|
<span className="truncate">{f.menuUrl}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2">
|
||||||
|
{renderActions(f, 'flex items-center justify-end gap-1')}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ const WizardStepComponent = ({
|
||||||
<KindCard
|
<KindCard
|
||||||
active={!isCustom}
|
active={!isCustom}
|
||||||
icon={<FaTable />}
|
icon={<FaTable />}
|
||||||
title={translate('::App.WizardStepComponent.ListComponent')}
|
title={translate('::App.Listforms.Listform')}
|
||||||
description={translate('::App.WizardStepComponent.ListComponentHint')}
|
description={translate('::App.WizardStepComponent.ListComponentHint')}
|
||||||
onSelect={() => onKindChange(WizardComponentKindEnum.List)}
|
onSelect={() => onKindChange(WizardComponentKindEnum.List)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -69,10 +69,13 @@ function buildLogSteps(
|
||||||
translate: (key: string, params?: Record<string, string | number>) => string,
|
translate: (key: string, params?: Record<string, string | number>) => string,
|
||||||
): Omit<LogEntry, 'status'>[] {
|
): Omit<LogEntry, 'status'>[] {
|
||||||
const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0)
|
const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0)
|
||||||
|
// Menuye eklenmeyen wizard'da menu kaydi uretilmez; adim listesinde de yer almaz.
|
||||||
|
const withoutMenuStep = (steps: Omit<LogEntry, 'status'>[]) =>
|
||||||
|
values.createMenu === false ? steps.filter((step) => step.id !== 2) : steps
|
||||||
|
|
||||||
// Custom yolunda veri kaynağı / ListForm / alan üretimi hiç çalışmaz.
|
// Custom yolunda veri kaynağı / ListForm / alan üretimi hiç çalışmaz.
|
||||||
if (values.componentKind === WizardComponentKindEnum.Custom) {
|
if (values.componentKind === WizardComponentKindEnum.Custom) {
|
||||||
return [
|
return withoutMenuStep([
|
||||||
{ id: 1, label: translate('::App.Step4Log.ValidatingConfig') },
|
{ id: 1, label: translate('::App.Step4Log.ValidatingConfig') },
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
|
@ -99,10 +102,10 @@ function buildLogSteps(
|
||||||
},
|
},
|
||||||
{ id: 8, label: translate('::App.Step4Log.Deploying') },
|
{ id: 8, label: translate('::App.Step4Log.Deploying') },
|
||||||
{ id: 9, label: translate('::App.Step4Log.Completed') },
|
{ id: 9, label: translate('::App.Step4Log.Completed') },
|
||||||
]
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return withoutMenuStep([
|
||||||
{ id: 1, label: translate('::App.Step4Log.ValidatingConfig') },
|
{ id: 1, label: translate('::App.Step4Log.ValidatingConfig') },
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
|
@ -140,7 +143,7 @@ function buildLogSteps(
|
||||||
},
|
},
|
||||||
{ id: 8, label: translate('::App.Step4Log.Deploying') },
|
{ id: 8, label: translate('::App.Step4Log.Deploying') },
|
||||||
{ id: 9, label: translate('::App.Step4Log.Completed') },
|
{ id: 9, label: translate('::App.Step4Log.Completed') },
|
||||||
]
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Mini-components ──────────────────────────────────────────────────────────
|
// ─── Mini-components ──────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Button, FormItem, Input, Notification, Select, toast } from '@/components/ui'
|
import { Button, FormItem, Input, Notification, Select, Switcher, toast } from '@/components/ui'
|
||||||
|
import classNames from 'classnames'
|
||||||
import { SelectBoxOption } from '@/types/shared'
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||||
import { MenuItem } from '@/proxy/menus/menu'
|
import { MenuItem } from '@/proxy/menus/menu'
|
||||||
|
|
@ -366,6 +367,8 @@ export interface WizardStepMenuProps {
|
||||||
menuTree: MenuTreeNode[]
|
menuTree: MenuTreeNode[]
|
||||||
isLoadingMenu: boolean
|
isLoadingMenu: boolean
|
||||||
onMenuParentChange: (code: string) => void
|
onMenuParentChange: (code: string) => void
|
||||||
|
/** Menuye eklenip eklenmeyecegi; false iken menuye ozel alanlar hic sorulmaz. */
|
||||||
|
onCreateMenuChange: (createMenu: boolean) => void
|
||||||
onClearMenuParent: () => void
|
onClearMenuParent: () => void
|
||||||
onMenuCreated: (menu: {
|
onMenuCreated: (menu: {
|
||||||
code: string
|
code: string
|
||||||
|
|
@ -399,6 +402,7 @@ const WizardStepMenu = ({
|
||||||
menuTree,
|
menuTree,
|
||||||
isLoadingMenu,
|
isLoadingMenu,
|
||||||
onMenuParentChange,
|
onMenuParentChange,
|
||||||
|
onCreateMenuChange,
|
||||||
onClearMenuParent,
|
onClearMenuParent,
|
||||||
onMenuCreated,
|
onMenuCreated,
|
||||||
onReloadMenu,
|
onReloadMenu,
|
||||||
|
|
@ -446,16 +450,22 @@ const WizardStepMenu = ({
|
||||||
? `/admin/list/${values.menuCode}`
|
? `/admin/list/${values.menuCode}`
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menuye eklenmeyen wizard: ListForm, permission ve dil anahtarlari uretilir, menu kaydi
|
||||||
|
* uretilmez. Bu durumda ust menu / ikon / sira / adres alanlari hic sorulmaz.
|
||||||
|
*/
|
||||||
|
const createMenu = values.createMenu !== false
|
||||||
|
|
||||||
const step1Missing = [
|
const step1Missing = [
|
||||||
!wizardName && translate('::App.WizardStep1.WizardName'),
|
!wizardName && translate('::App.WizardStep1.WizardName'),
|
||||||
!values.menuCode && translate('::App.WizardStep1.MenuCode'),
|
!values.menuCode && translate('::App.WizardStep1.MenuCode'),
|
||||||
// Yup şeması menuOrder >= 1 istiyor; başlangıç değeri 0 olduğu için
|
// Yup şeması menuOrder >= 1 istiyor; başlangıç değeri 0 olduğu için
|
||||||
// burada kontrol edilmezse "Next" aktif görünüp sessizce çalışmıyordu.
|
// burada kontrol edilmezse "Next" aktif görünüp sessizce çalışmıyordu.
|
||||||
!(Number(values.menuOrder) >= 1) && translate('::App.WizardStep1.MenuOrder'),
|
createMenu && !(Number(values.menuOrder) >= 1) && translate('::App.WizardStep1.MenuOrder'),
|
||||||
!values.permissionGroupName && translate('::App.WizardStep1.PermissionGroupName'),
|
!values.permissionGroupName && translate('::App.WizardStep1.PermissionGroupName'),
|
||||||
!values.languageTextMenuEn && translate('::App.WizardStep4.MenuEn'),
|
!values.languageTextMenuEn && translate('::App.WizardStep4.MenuEn'),
|
||||||
!values.languageTextMenuTr && translate('::App.WizardStep4.MenuTr'),
|
!values.languageTextMenuTr && translate('::App.WizardStep4.MenuTr'),
|
||||||
!values.menuIcon && translate('::App.WizardStep4.MenuIcon'),
|
createMenu && !values.menuIcon && translate('::App.WizardStep4.MenuIcon'),
|
||||||
].filter(Boolean) as string[]
|
].filter(Boolean) as string[]
|
||||||
// Geçersiz ya da çakışan ad deploy'da menü/permission kayıtlarını bozar; adım burada durur.
|
// Geçersiz ya da çakışan ad deploy'da menü/permission kayıtlarını bozar; adım burada durur.
|
||||||
const step1CanGo = step1Missing.length === 0 && !wizardNameError
|
const step1CanGo = step1Missing.length === 0 && !wizardNameError
|
||||||
|
|
@ -474,169 +484,144 @@ const WizardStepMenu = ({
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Input
|
<div className="flex items-center gap-3">
|
||||||
type="text"
|
<Input
|
||||||
autoComplete="off"
|
type="text"
|
||||||
placeholder="Routes, Products, Orders"
|
autoComplete="off"
|
||||||
value={wizardName}
|
placeholder="Routes, Products, Orders"
|
||||||
autoFocus
|
value={wizardName}
|
||||||
invalid={!!wizardNameError}
|
autoFocus
|
||||||
onChange={(e) => onWizardNameChange(e.target.value.replace(/\s/g, ''))}
|
invalid={!!wizardNameError}
|
||||||
/>
|
onChange={(e) => onWizardNameChange(e.target.value.replace(/\s/g, ''))}
|
||||||
|
/>
|
||||||
|
{/* Menuye eklenecek mi: kapaliyken menuye ozel alanlarin hicbiri sorulmaz. */}
|
||||||
|
<div
|
||||||
|
className="flex shrink-0 items-center gap-2 whitespace-nowrap text-sm text-gray-600 dark:text-gray-300"
|
||||||
|
title={translate('::App.WizardStep1.CreateMenuHint')}
|
||||||
|
>
|
||||||
|
{/* Switcher kontrollu kullanimda onChange'e mevcut degeri geri verdigi icin
|
||||||
|
yeni deger burada mevcut degerin tersi olarak hesaplanir. */}
|
||||||
|
<Switcher checked={createMenu} onChange={() => onCreateMenuChange(!createMenu)} />
|
||||||
|
<span className="cursor-pointer" onClick={() => onCreateMenuChange(!createMenu)}>
|
||||||
|
{translate('::App.WizardStep1.CreateMenu')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
{/* Menu Code / Menu Text / Permission Group — 2-column grid */}
|
{/* Menu Code / Menu Text / Permission Group — 2-column grid */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
|
<div
|
||||||
{/* Col 1 */}
|
className={classNames('grid grid-cols-1 gap-x-6 gap-y-4', createMenu && 'md:grid-cols-2')}
|
||||||
<div className="flex flex-col">
|
>
|
||||||
{/* Menu Parent */}
|
{/* Col 1 — yalnizca menuye eklenen wizard'da: ust menu agaci */}
|
||||||
<FormItem
|
{createMenu && (
|
||||||
/* FormItem'in içerik kutusu da esnesin ki ağaç sağ kolon yüksekliğini doldursun. */
|
<div className="flex flex-col">
|
||||||
className="flex flex-1 flex-col [&>div]:flex [&>div]:min-h-0 [&>div]:flex-1 [&>div]:flex-col"
|
{/* Menu Parent */}
|
||||||
label={translate('::App.Platform.MenuParent')}
|
<FormItem
|
||||||
invalid={false}
|
/* FormItem'in içerik kutusu da esnesin ki ağaç sağ kolon yüksekliğini doldursun. */
|
||||||
errorMessage={undefined}
|
className="flex flex-1 flex-col [&>div]:flex [&>div]:min-h-0 [&>div]:flex-1 [&>div]:flex-col"
|
||||||
extra={
|
label={translate('::App.Platform.MenuParent')}
|
||||||
<div className="flex items-center gap-2 ml-3">
|
/* extra icerigi label satirinin tamamini kaplasin ki menu kodu en saga yaslanabilsin. */
|
||||||
<Button
|
labelClass="flex w-full items-center [&>span]:min-w-0 [&>span]:flex-1"
|
||||||
type="button"
|
invalid={false}
|
||||||
onClick={() => {
|
errorMessage={undefined}
|
||||||
setMenuBeingEdited(null)
|
extra={
|
||||||
setMenuDialogParentCode(
|
/* Butonlar <label> icinde oldugu icin label'in bos alanina tiklamak ilk
|
||||||
values.menuParentCode
|
kontrolu (Add) tetikliyordu; varsayilan davranis burada durdurulur. */
|
||||||
? findRootCode(rawMenuItems, values.menuParentCode)
|
<div
|
||||||
: '',
|
className="ml-3 flex flex-1 items-center gap-2"
|
||||||
)
|
onClick={(e) => e.preventDefault()}
|
||||||
setMenuDialogOpen(true)
|
|
||||||
}}
|
|
||||||
size="xs"
|
|
||||||
variant="solid"
|
|
||||||
icon={<FaPlus className="text-xs" />}
|
|
||||||
>
|
>
|
||||||
<span className="whitespace-nowrap">{translate('::App.Platform.Add')}</span>
|
|
||||||
</Button>
|
|
||||||
{values.menuParentCode && (
|
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => {
|
onClick={() => {
|
||||||
e.stopPropagation()
|
setMenuBeingEdited(null)
|
||||||
e.preventDefault()
|
setMenuDialogParentCode(
|
||||||
onClearMenuParent()
|
values.menuParentCode
|
||||||
|
? findRootCode(rawMenuItems, values.menuParentCode)
|
||||||
|
: '',
|
||||||
|
)
|
||||||
|
setMenuDialogOpen(true)
|
||||||
}}
|
}}
|
||||||
size="xs"
|
size="xs"
|
||||||
variant="default"
|
variant="solid"
|
||||||
icon={<FaTimes className="text-xs" />}
|
icon={<FaPlus className="text-xs" />}
|
||||||
className="!inline-flex !h-auto !items-center !justify-center gap-1 !rounded !px-2 !py-0.5 text-xs text-gray-500 whitespace-nowrap hover:border-red-400 hover:text-red-500 dark:border-gray-600"
|
|
||||||
>
|
>
|
||||||
{translate('::App.Platform.ClearSelection')}
|
<span className="whitespace-nowrap">{translate('::App.Platform.Add')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
{values.menuParentCode && (
|
||||||
</div>
|
<Button
|
||||||
}
|
type="button"
|
||||||
>
|
onClick={(e) => {
|
||||||
<Field name="menuParentCode">
|
e.stopPropagation()
|
||||||
{() => (
|
e.preventDefault()
|
||||||
<MenuTreeInline
|
onClearMenuParent()
|
||||||
value={values.menuParentCode}
|
}}
|
||||||
onChange={onMenuParentChange}
|
size="xs"
|
||||||
nodes={menuTree}
|
variant="default"
|
||||||
rawItems={rawMenuItems}
|
icon={<FaTimes className="text-xs" />}
|
||||||
isLoading={isLoadingMenu}
|
className="!inline-flex !h-auto !items-center !justify-center gap-1 !rounded !px-2 !py-0.5 text-xs text-gray-500 whitespace-nowrap hover:border-red-400 hover:text-red-500 dark:border-gray-600"
|
||||||
invalid={false}
|
>
|
||||||
onReload={onReloadMenu}
|
{translate('::App.Platform.ClearSelection')}
|
||||||
onEditMenu={handleEditMenu}
|
</Button>
|
||||||
initialExpanded={
|
)}
|
||||||
values.menuParentCode
|
{/* Menu Code: kullanici duzenlemez, parent satirinin en saginda gosterilir. */}
|
||||||
? getAncestorCodes(rawMenuItems, values.menuParentCode)
|
<Field
|
||||||
: undefined
|
type="text"
|
||||||
}
|
autoComplete="off"
|
||||||
/>
|
name="menuCode"
|
||||||
)}
|
placeholder="App.Wizard.Routes"
|
||||||
</Field>
|
component={Input}
|
||||||
</FormItem>
|
size="xs"
|
||||||
|
disabled={true}
|
||||||
<MenuAddDialog
|
title={translate('::App.Platform.MenuCodeHint')}
|
||||||
isOpen={menuDialogOpen}
|
className="ml-auto max-w-[16rem] font-mono text-xs"
|
||||||
onClose={closeMenuDialog}
|
/>
|
||||||
initialParentCode={menuDialogParentCode}
|
</div>
|
||||||
initialOrder={menuDialogInitialOrder}
|
|
||||||
rawItems={rawMenuItems}
|
|
||||||
editMenu={menuBeingEdited}
|
|
||||||
onSaved={async (menu) => {
|
|
||||||
// Düzenlemede seçili parent'ı değiştirmemek için yalnızca ağaç tazelenir.
|
|
||||||
if (menuBeingEdited) {
|
|
||||||
await onReloadMenu()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
await onMenuCreated(menu)
|
>
|
||||||
}}
|
<Field name="menuParentCode">
|
||||||
/>
|
{() => (
|
||||||
</div>
|
<MenuTreeInline
|
||||||
|
value={values.menuParentCode}
|
||||||
|
onChange={onMenuParentChange}
|
||||||
|
nodes={menuTree}
|
||||||
|
rawItems={rawMenuItems}
|
||||||
|
isLoading={isLoadingMenu}
|
||||||
|
invalid={false}
|
||||||
|
onReload={onReloadMenu}
|
||||||
|
onEditMenu={handleEditMenu}
|
||||||
|
initialExpanded={
|
||||||
|
values.menuParentCode
|
||||||
|
? getAncestorCodes(rawMenuItems, values.menuParentCode)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<MenuAddDialog
|
||||||
|
isOpen={menuDialogOpen}
|
||||||
|
onClose={closeMenuDialog}
|
||||||
|
initialParentCode={menuDialogParentCode}
|
||||||
|
initialOrder={menuDialogInitialOrder}
|
||||||
|
rawItems={rawMenuItems}
|
||||||
|
editMenu={menuBeingEdited}
|
||||||
|
onSaved={async (menu) => {
|
||||||
|
// Düzenlemede seçili parent'ı değiştirmemek için yalnızca ağaç tazelenir.
|
||||||
|
if (menuBeingEdited) {
|
||||||
|
await onReloadMenu()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await onMenuCreated(menu)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Col 2 */}
|
{/* Col 2 */}
|
||||||
<div>
|
<div>
|
||||||
{/* Menu Code */}
|
|
||||||
<FormItem
|
|
||||||
label={translate('::App.WizardStep1.MenuCode')}
|
|
||||||
invalid={!!(errors.menuCode && touched.menuCode)}
|
|
||||||
errorMessage={errors.menuCode}
|
|
||||||
asterisk={true}
|
|
||||||
extra={
|
|
||||||
<span className="text-xs ml-2 text-gray-400">
|
|
||||||
{translate('::App.Platform.MenuCodeHint')}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Field
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
name="menuCode"
|
|
||||||
placeholder="App.Wizard.Routes, App.Wizard.Products, App.Wizard.Orders"
|
|
||||||
component={Input}
|
|
||||||
disabled={true}
|
|
||||||
/>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
{/* Menu Order */}
|
|
||||||
<FormItem
|
|
||||||
label={translate('::App.WizardStep1.MenuOrder')}
|
|
||||||
asterisk={true}
|
|
||||||
invalid={!!(errors.menuOrder && touched.menuOrder)}
|
|
||||||
errorMessage={errors.menuOrder}
|
|
||||||
extra={
|
|
||||||
<span className="text-xs ml-2 text-gray-400">
|
|
||||||
{translate('::App.WizardStep1.MenuOrderHint')}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Field
|
|
||||||
type="number"
|
|
||||||
min={1}
|
|
||||||
step={1}
|
|
||||||
autoComplete="off"
|
|
||||||
name="menuOrder"
|
|
||||||
component={Input}
|
|
||||||
/>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
{/* Menu Icon */}
|
|
||||||
<FormItem
|
|
||||||
label={translate('::App.WizardStep1.MenuIcon')}
|
|
||||||
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) */}
|
{/* Menu Text (En) */}
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::App.Platform.DisplayNameEnglish')}
|
label={translate('::App.Platform.DisplayNameEnglish')}
|
||||||
|
|
@ -669,7 +654,7 @@ const WizardStepMenu = ({
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
{/* Permission Group Name */}
|
{/* Permission grubu menuden bagimsizdir: izinler her iki durumda da uretilir. */}
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::App.WizardStep1.PermissionGroupName')}
|
label={translate('::App.WizardStep1.PermissionGroupName')}
|
||||||
invalid={!!(errors.permissionGroupName && touched.permissionGroupName)}
|
invalid={!!(errors.permissionGroupName && touched.permissionGroupName)}
|
||||||
|
|
@ -702,23 +687,67 @@ const WizardStepMenu = ({
|
||||||
</Field>
|
</Field>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
{/* Deploy sonrası menü ikonunun açacağı adres; seçilen yola göre değişir. */}
|
{createMenu && (
|
||||||
<FormItem
|
<>
|
||||||
label={translate('::App.WizardStepComponent.MenuUrl')}
|
{/* Menu Icon */}
|
||||||
extra={
|
<FormItem
|
||||||
<span className="ml-2 text-xs text-gray-400">
|
label={translate('::App.WizardStep1.MenuIcon')}
|
||||||
{translate(
|
asterisk={true}
|
||||||
isCustomComponent
|
invalid={!!(errors.menuIcon && touched.menuIcon)}
|
||||||
? '::App.WizardStepComponent.MenuUrlFromComponent'
|
errorMessage={errors.menuIcon}
|
||||||
: '::App.WizardStepComponent.MenuUrlFromList',
|
>
|
||||||
)}
|
<Field name="menuIcon">
|
||||||
</span>
|
{({ field, form }: FieldProps<string>) => (
|
||||||
}
|
<IconPickerField
|
||||||
>
|
value={field.value}
|
||||||
<div className="w-full rounded-md border border-gray-300 bg-gray-50 px-3 py-2 font-mono text-sm text-indigo-600 dark:border-gray-600 dark:bg-gray-800 dark:text-indigo-300">
|
onChange={(key) => form.setFieldValue(field.name, key)}
|
||||||
{menuUrl || '—'}
|
invalid={!!(errors.menuIcon && touched.menuIcon)}
|
||||||
</div>
|
/>
|
||||||
</FormItem>
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
{/* Menu Order */}
|
||||||
|
<FormItem
|
||||||
|
label={translate('::App.WizardStep1.MenuOrder')}
|
||||||
|
asterisk={true}
|
||||||
|
invalid={!!(errors.menuOrder && touched.menuOrder)}
|
||||||
|
errorMessage={errors.menuOrder}
|
||||||
|
extra={
|
||||||
|
<span className="text-xs ml-2 text-gray-400">
|
||||||
|
{translate('::App.WizardStep1.MenuOrderHint')}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
step={1}
|
||||||
|
autoComplete="off"
|
||||||
|
name="menuOrder"
|
||||||
|
component={Input}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
{/* Deploy sonrası menü ikonunun açacağı adres; seçilen yola göre değişir. */}
|
||||||
|
<FormItem
|
||||||
|
label={translate('::App.WizardStepComponent.MenuUrl')}
|
||||||
|
extra={
|
||||||
|
<span className="ml-2 text-xs text-gray-400">
|
||||||
|
{translate(
|
||||||
|
isCustomComponent
|
||||||
|
? '::App.WizardStepComponent.MenuUrlFromComponent'
|
||||||
|
: '::App.WizardStepComponent.MenuUrlFromList',
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="w-full rounded-md border border-gray-300 bg-gray-50 px-3 py-2 font-mono text-sm text-indigo-600 dark:border-gray-600 dark:bg-gray-800 dark:text-indigo-300">
|
||||||
|
{menuUrl || '—'}
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue