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 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>
|
||||
public string CustomComponentName { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,11 @@ public class ListFormWizardAppService(
|
|||
}
|
||||
|
||||
//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))
|
||||
{
|
||||
await repoPermGroup.InsertAsync(new PermissionGroupDefinitionRecord(GuidGenerator.Create(), groupName, groupName), autoSave: false);
|
||||
|
|
@ -208,6 +212,15 @@ public class ListFormWizardAppService(
|
|||
await permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: false);
|
||||
}
|
||||
|
||||
// Menunun acacagi adres: Custom yolunda CustomComponent'in kendi rotasi (bu rota
|
||||
// mapCustomComponentRoutes tarafindan zaten kayitlidir), aksi halde ListForm adresi.
|
||||
// Seed dosyasindan da gorulebilmesi icin input'a geri yazilir; menu uretilmese de
|
||||
// ListForm'un adresi degismedigi icin ayni sekilde hesaplanir.
|
||||
input.MenuUrl = isCustomComponent ? customRoutePath : WizardConsts.MenuUrl(code);
|
||||
|
||||
// Menusuz wizard: menu (ve ust menu) kaydi hic uretilmez, geri kalan her sey aynidir.
|
||||
if (input.CreateMenu)
|
||||
{
|
||||
//Menu Parent
|
||||
var menuQueryable = await repoMenu.GetQueryableAsync();
|
||||
var menuParent = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == input.MenuParentCode));
|
||||
|
|
@ -242,11 +255,7 @@ public class ListFormWizardAppService(
|
|||
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 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 menuUrl = input.MenuUrl;
|
||||
var existingMenu = await AsyncExecuter.FirstOrDefaultAsync(menuQueryable.Where(a => a.Code == code));
|
||||
if (existingMenu == null)
|
||||
{
|
||||
|
|
@ -272,6 +281,7 @@ public class ListFormWizardAppService(
|
|||
existingMenu.Url = menuUrl;
|
||||
await repoMenu.UpdateAsync(existingMenu, autoSave: false);
|
||||
}
|
||||
}
|
||||
|
||||
// Custom yolunda ListForm ekosistemi üretilmez: veri kaynağı, ListForm, alanlar,
|
||||
// subform/widget/workflow kayıtlarının hiçbirine ihtiyaç yoktur.
|
||||
|
|
|
|||
|
|
@ -1503,8 +1503,8 @@
|
|||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Listforms.Listform",
|
||||
"en": "Listforms",
|
||||
"tr": "Listformlar"
|
||||
"en": "ListForm Components",
|
||||
"tr": "List Form Bileşenleri"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
|
|
@ -16326,6 +16326,18 @@
|
|||
"en": "Automatically assigned, editable",
|
||||
"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",
|
||||
"key": "App.Platform.Add",
|
||||
|
|
@ -16836,12 +16848,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.WizardStepComponent.ListComponent",
|
||||
"en": "List Component",
|
||||
"tr": "Liste Bileşeni"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.WizardStepComponent.ListComponentHint",
|
||||
|
|
|
|||
|
|
@ -797,16 +797,6 @@
|
|||
"RequiredPermissionName": "App.DataSource",
|
||||
"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",
|
||||
"Code": "App.Notifications",
|
||||
|
|
@ -978,6 +968,16 @@
|
|||
"RequiredPermissionName": "App.DeveloperKit.Components",
|
||||
"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",
|
||||
"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
|
||||
// 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
|
||||
? 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);
|
||||
|
||||
if (alreadySeeded)
|
||||
|
|
@ -181,7 +185,9 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
}
|
||||
|
||||
// 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))
|
||||
{
|
||||
await _repoPermGroup.InsertAsync(
|
||||
|
|
@ -249,6 +255,16 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
// await _permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: true);
|
||||
// }
|
||||
|
||||
// Menunun acacagi adres; menu uretilmese de seed dosyasindaki degeri korunur.
|
||||
input.MenuUrl = isCustomComponent
|
||||
? customRoutePath
|
||||
: !string.IsNullOrWhiteSpace(input.MenuUrl)
|
||||
? input.MenuUrl.Trim()
|
||||
: WizardConsts.MenuUrl(code);
|
||||
|
||||
// Menusuz wizard: menu (ve ust menu) kaydi hic uretilmez.
|
||||
if (input.CreateMenu)
|
||||
{
|
||||
// Menu Parent
|
||||
var menuQueryable = await _repoMenu.GetQueryableAsync();
|
||||
var menuParent = await _repoMenu.FirstOrDefaultAsync(a => a.Code == input.MenuParentCode);
|
||||
|
|
@ -276,14 +292,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
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
|
||||
: !string.IsNullOrWhiteSpace(input.MenuUrl)
|
||||
? input.MenuUrl.Trim()
|
||||
: WizardConsts.MenuUrl(code);
|
||||
input.MenuUrl = menuUrl;
|
||||
var menuUrl = input.MenuUrl;
|
||||
var existingMenu = await _repoMenu.FirstOrDefaultAsync(a => a.Code == code);
|
||||
if (existingMenu == null)
|
||||
{
|
||||
|
|
@ -308,6 +317,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
existingMenu.Url = menuUrl;
|
||||
await _repoMenu.UpdateAsync(existingMenu, autoSave: true);
|
||||
}
|
||||
}
|
||||
|
||||
// Custom yolunda ListForm ekosistemi üretilmez.
|
||||
if (isCustomComponent)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,12 @@ export interface ListFormWizardDto {
|
|||
menuCode: string
|
||||
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
|
||||
/** Menünün açtığı adres; Custom yolunda seçilen bileşenin rota yolu. */
|
||||
menuUrl: string
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ const initialValues: ListFormWizardDto = {
|
|||
listFormCode: '',
|
||||
menuCode: '',
|
||||
menuOrder: 0,
|
||||
createMenu: true,
|
||||
componentKind: WizardComponentKindEnum.List,
|
||||
customComponentName: '',
|
||||
menuUrl: '',
|
||||
|
|
@ -174,7 +175,16 @@ const initialValues: ListFormWizardDto = {
|
|||
const step1ValidationSchema = Yup.object().shape({
|
||||
wizardName: 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(),
|
||||
languageTextMenuEn: Yup.string().required(),
|
||||
languageTextMenuTr: Yup.string().required(),
|
||||
|
|
@ -595,6 +605,8 @@ const Wizard = () => {
|
|||
wizardName: w.wizardName ?? '',
|
||||
listFormCode: w.listFormCode ?? '',
|
||||
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.
|
||||
componentKind: w.componentKind ?? WizardComponentKindEnum.List,
|
||||
customComponentName: w.customComponentName ?? '',
|
||||
|
|
@ -830,6 +842,23 @@ const Wizard = () => {
|
|||
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 formik = formikRef.current
|
||||
const parentChanged = formik?.values.menuParentCode !== code
|
||||
|
|
@ -1180,6 +1209,7 @@ const Wizard = () => {
|
|||
menuTree={menuTree}
|
||||
isLoadingMenu={isLoadingMenu}
|
||||
onMenuParentChange={handleMenuParentChange}
|
||||
onCreateMenuChange={handleCreateMenuChange}
|
||||
onClearMenuParent={() => {
|
||||
formikRef.current?.setFieldValue('menuParentCode', '')
|
||||
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"
|
||||
>
|
||||
<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">
|
||||
{/* 1. satır: wizard adı */}
|
||||
<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}
|
||||
>
|
||||
{f.wizardName || f.fileName}
|
||||
|
|
@ -368,14 +368,9 @@ const WizardFileManager = () => {
|
|||
<div className="text-xs text-gray-400 truncate font-mono" title={f.fileName}>
|
||||
{f.fileName}
|
||||
</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">
|
||||
<div className="flex flex-wrap pt-1 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',
|
||||
|
|
@ -388,10 +383,15 @@ const WizardFileManager = () => {
|
|||
{translate(
|
||||
isCustom
|
||||
? '::App.WizardStepComponent.CustomComponent'
|
||||
: '::App.WizardStepComponent.ListComponent',
|
||||
: '::App.Listforms.Listform',
|
||||
)}
|
||||
</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. */}
|
||||
{!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">
|
||||
|
|
@ -409,6 +409,8 @@ const WizardFileManager = () => {
|
|||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{renderActions(
|
||||
f,
|
||||
|
|
@ -419,37 +421,63 @@ const WizardFileManager = () => {
|
|||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<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 (
|
||||
<div
|
||||
<tr
|
||||
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"
|
||||
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}
|
||||
>
|
||||
<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 */}
|
||||
<div className="flex flex-wrap items-center gap-1.5 text-xs">
|
||||
</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 font-medium',
|
||||
'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',
|
||||
|
|
@ -459,31 +487,27 @@ const WizardFileManager = () => {
|
|||
{translate(
|
||||
isCustom
|
||||
? '::App.WizardStepComponent.CustomComponent'
|
||||
: '::App.WizardStepComponent.ListComponent',
|
||||
: '::App.Listforms.Listform',
|
||||
)}
|
||||
</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 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">
|
||||
<FaLink className="shrink-0" />
|
||||
</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>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{renderActions(f, 'flex items-center gap-1 shrink-0 sm:ml-3')}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-2">
|
||||
{renderActions(f, 'flex items-center justify-end gap-1')}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ const WizardStepComponent = ({
|
|||
<KindCard
|
||||
active={!isCustom}
|
||||
icon={<FaTable />}
|
||||
title={translate('::App.WizardStepComponent.ListComponent')}
|
||||
title={translate('::App.Listforms.Listform')}
|
||||
description={translate('::App.WizardStepComponent.ListComponentHint')}
|
||||
onSelect={() => onKindChange(WizardComponentKindEnum.List)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -69,10 +69,13 @@ function buildLogSteps(
|
|||
translate: (key: string, params?: Record<string, string | number>) => string,
|
||||
): Omit<LogEntry, 'status'>[] {
|
||||
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.
|
||||
if (values.componentKind === WizardComponentKindEnum.Custom) {
|
||||
return [
|
||||
return withoutMenuStep([
|
||||
{ id: 1, label: translate('::App.Step4Log.ValidatingConfig') },
|
||||
{
|
||||
id: 2,
|
||||
|
|
@ -99,10 +102,10 @@ function buildLogSteps(
|
|||
},
|
||||
{ id: 8, label: translate('::App.Step4Log.Deploying') },
|
||||
{ id: 9, label: translate('::App.Step4Log.Completed') },
|
||||
]
|
||||
])
|
||||
}
|
||||
|
||||
return [
|
||||
return withoutMenuStep([
|
||||
{ id: 1, label: translate('::App.Step4Log.ValidatingConfig') },
|
||||
{
|
||||
id: 2,
|
||||
|
|
@ -140,7 +143,7 @@ function buildLogSteps(
|
|||
},
|
||||
{ id: 8, label: translate('::App.Step4Log.Deploying') },
|
||||
{ id: 9, label: translate('::App.Step4Log.Completed') },
|
||||
]
|
||||
])
|
||||
}
|
||||
|
||||
// ─── 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 { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
import { MenuItem } from '@/proxy/menus/menu'
|
||||
|
|
@ -366,6 +367,8 @@ export interface WizardStepMenuProps {
|
|||
menuTree: MenuTreeNode[]
|
||||
isLoadingMenu: boolean
|
||||
onMenuParentChange: (code: string) => void
|
||||
/** Menuye eklenip eklenmeyecegi; false iken menuye ozel alanlar hic sorulmaz. */
|
||||
onCreateMenuChange: (createMenu: boolean) => void
|
||||
onClearMenuParent: () => void
|
||||
onMenuCreated: (menu: {
|
||||
code: string
|
||||
|
|
@ -399,6 +402,7 @@ const WizardStepMenu = ({
|
|||
menuTree,
|
||||
isLoadingMenu,
|
||||
onMenuParentChange,
|
||||
onCreateMenuChange,
|
||||
onClearMenuParent,
|
||||
onMenuCreated,
|
||||
onReloadMenu,
|
||||
|
|
@ -446,16 +450,22 @@ const WizardStepMenu = ({
|
|||
? `/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 = [
|
||||
!wizardName && translate('::App.WizardStep1.WizardName'),
|
||||
!values.menuCode && translate('::App.WizardStep1.MenuCode'),
|
||||
// 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.
|
||||
!(Number(values.menuOrder) >= 1) && translate('::App.WizardStep1.MenuOrder'),
|
||||
createMenu && !(Number(values.menuOrder) >= 1) && translate('::App.WizardStep1.MenuOrder'),
|
||||
!values.permissionGroupName && translate('::App.WizardStep1.PermissionGroupName'),
|
||||
!values.languageTextMenuEn && translate('::App.WizardStep4.MenuEn'),
|
||||
!values.languageTextMenuTr && translate('::App.WizardStep4.MenuTr'),
|
||||
!values.menuIcon && translate('::App.WizardStep4.MenuIcon'),
|
||||
createMenu && !values.menuIcon && translate('::App.WizardStep4.MenuIcon'),
|
||||
].filter(Boolean) as string[]
|
||||
// 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
|
||||
|
|
@ -474,6 +484,7 @@ const WizardStepMenu = ({
|
|||
</span>
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
|
|
@ -483,21 +494,44 @@ const WizardStepMenu = ({
|
|||
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>
|
||||
|
||||
{/* 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">
|
||||
{/* Col 1 */}
|
||||
<div
|
||||
className={classNames('grid grid-cols-1 gap-x-6 gap-y-4', createMenu && 'md:grid-cols-2')}
|
||||
>
|
||||
{/* Col 1 — yalnizca menuye eklenen wizard'da: ust menu agaci */}
|
||||
{createMenu && (
|
||||
<div className="flex flex-col">
|
||||
{/* Menu Parent */}
|
||||
<FormItem
|
||||
/* FormItem'in içerik kutusu da esnesin ki ağaç sağ kolon yüksekliğini doldursun. */
|
||||
className="flex flex-1 flex-col [&>div]:flex [&>div]:min-h-0 [&>div]:flex-1 [&>div]:flex-col"
|
||||
label={translate('::App.Platform.MenuParent')}
|
||||
/* extra icerigi label satirinin tamamini kaplasin ki menu kodu en saga yaslanabilsin. */
|
||||
labelClass="flex w-full items-center [&>span]:min-w-0 [&>span]:flex-1"
|
||||
invalid={false}
|
||||
errorMessage={undefined}
|
||||
extra={
|
||||
<div className="flex items-center gap-2 ml-3">
|
||||
/* Butonlar <label> icinde oldugu icin label'in bos alanina tiklamak ilk
|
||||
kontrolu (Add) tetikliyordu; varsayilan davranis burada durdurulur. */
|
||||
<div
|
||||
className="ml-3 flex flex-1 items-center gap-2"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
|
|
@ -531,6 +565,18 @@ const WizardStepMenu = ({
|
|||
{translate('::App.Platform.ClearSelection')}
|
||||
</Button>
|
||||
)}
|
||||
{/* Menu Code: kullanici duzenlemez, parent satirinin en saginda gosterilir. */}
|
||||
<Field
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
name="menuCode"
|
||||
placeholder="App.Wizard.Routes"
|
||||
component={Input}
|
||||
size="xs"
|
||||
disabled={true}
|
||||
title={translate('::App.Platform.MenuCodeHint')}
|
||||
className="ml-auto max-w-[16rem] font-mono text-xs"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
|
@ -572,71 +618,10 @@ const WizardStepMenu = ({
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Col 2 */}
|
||||
<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) */}
|
||||
<FormItem
|
||||
label={translate('::App.Platform.DisplayNameEnglish')}
|
||||
|
|
@ -669,7 +654,7 @@ const WizardStepMenu = ({
|
|||
/>
|
||||
</FormItem>
|
||||
|
||||
{/* Permission Group Name */}
|
||||
{/* Permission grubu menuden bagimsizdir: izinler her iki durumda da uretilir. */}
|
||||
<FormItem
|
||||
label={translate('::App.WizardStep1.PermissionGroupName')}
|
||||
invalid={!!(errors.permissionGroupName && touched.permissionGroupName)}
|
||||
|
|
@ -702,6 +687,48 @@ const WizardStepMenu = ({
|
|||
</Field>
|
||||
</FormItem>
|
||||
|
||||
{createMenu && (
|
||||
<>
|
||||
{/* 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 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')}
|
||||
|
|
@ -719,6 +746,8 @@ const WizardStepMenu = ({
|
|||
{menuUrl || '—'}
|
||||
</div>
|
||||
</FormItem>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue