From baef565dd48e82ee55581136fadf2613bfba01b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Fri, 21 Aug 2026 15:21:58 +0300 Subject: [PATCH] =?UTF-8?q?Custom=20Component=20ve=20Wizard=20i=C3=A7in=20?= =?UTF-8?q?CRUD=20yetkiler=20tan=C4=B1mland=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeveloperKit/CustomEndpointAppService.cs | 2 +- .../ListForms/ListFormWizardAppService.cs | 38 +- .../Migrations/LanguagesData.json | 178 ++++++- .../ListFormSeeder_Administration.cs | 18 + .../Migrations/WizardDataSeeder.cs | 28 +- configs/seeds/host/custom/RoleComponent.json | 6 +- .../host/wizard/20260819131500_RoleList.json | 7 +- .../componentEditor/DynamicRenderer.tsx | 38 +- .../DesignerScriptBuilderDialog.tsx | 8 +- .../visualDesigner/VisualCanvas.tsx | 335 +++++++------ ui/src/components/visualDesigner/catalog.ts | 72 +-- .../visualDesigner/codeGenerator.ts | 268 +++++++---- .../visualDesigner/designerScriptDialect.ts | 8 +- .../visualDesigner/designerScriptRecipes.ts | 42 +- ui/src/components/visualDesigner/types.ts | 170 ++++--- ui/src/contexts/ComponentContext.tsx | 15 +- .../developerKit/VisualComponentDesigner.tsx | 445 ++++++++++++++---- 17 files changed, 1155 insertions(+), 523 deletions(-) diff --git a/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomEndpointAppService.cs b/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomEndpointAppService.cs index 91a45f2c..d6fb262e 100644 --- a/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomEndpointAppService.cs +++ b/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomEndpointAppService.cs @@ -215,7 +215,7 @@ public class CustomEndpointAppService : PlatformAppService /// /// Body parameters of a POST/PUT call. Both form encoded and JSON payloads are - /// accepted: the visual designer's SqlDataSource sends the edited record as JSON, + /// accepted: the visual designer's Form sends the edited record as JSON, /// while older callers keep posting forms. /// private async Task> ReadBodyValuesAsync() diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index f5eb6a25..0a28c09e 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -159,34 +159,42 @@ public class ListFormWizardAppService( inserted.PermissionNames.Add(permRead.Name); } - // Read altındaki alt yetkiler: (suffix, dil anahtarı) - var childPermissions = new[] + // Read altındaki alt yetkiler: (suffix, dil anahtarı). CRUD üçlüsü her iki + // yolda da üretilir; Custom component de Form üzerinden kayıt + // ekler, günceller ve siler, ve tasarımcı butonları bu yetkilere bağlar. + var crudPermissions = new[] { ("Create", WizardConsts.LangKeyCreate), ("Update", WizardConsts.LangKeyUpdate), ("Delete", WizardConsts.LangKeyDelete), + }; + + // Export/Import/Note yalnızca ListForm ekosisteminin sunduğu işlemlerdir; + // Custom yolunda karşılığı olmadığı için üretilmez. + var listOnlyPermissions = new[] + { ("Export", WizardConsts.LangKeyExport), ("Import", WizardConsts.LangKeyImport), ("Note", WizardConsts.LangKeyNote), }; - // Custom yolunda kayıt üzerinde CRUD yapılmadığı için yalnızca read izni üretilir. + var childPermissions = isCustomComponent + ? crudPermissions + : [.. crudPermissions, .. listOnlyPermissions]; + var permissionNames = new List { permRead.Name }; - if (!isCustomComponent) + foreach (var (suffix, langKey) in childPermissions) { - foreach (var (suffix, langKey) in childPermissions) - { - var permissionName = $"{code}.{suffix}"; - permissionNames.Add(permissionName); + var permissionName = $"{code}.{suffix}"; + permissionNames.Add(permissionName); - if (existingPerms.Any(a => a.Name == permissionName)) - continue; + if (existingPerms.Any(a => a.Name == permissionName)) + continue; - await repoPerm.InsertAsync( - new PermissionDefinitionRecord(GuidGenerator.Create(), groupName, permissionName, permRead.Name, langKey, true, MultiTenancySides.Both), - autoSave: false); - inserted.PermissionNames.Add(permissionName); - } + await repoPerm.InsertAsync( + new PermissionDefinitionRecord(GuidGenerator.Create(), groupName, permissionName, permRead.Name, langKey, true, MultiTenancySides.Both), + autoSave: false); + inserted.PermissionNames.Add(permissionName); } // Permission Grants - Bulk Insert (only missing ones) diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json index 3d01bad9..aad60032 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json @@ -19602,6 +19602,18 @@ "en": "Filters are sent as query parameters to the endpoint and apply to every component bound to it. The value can be static, come from the page URL, or come from the record of another Form.", "tr": "Filtreler endpointe query parametresi olarak gönderilir ve bu endpointe bağlı tüm komponentler için geçerlidir. Değer sabit olabilir, sayfa adresinden gelebilir veya başka bir Form kaydından okunabilir." }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.KeySourceQuery", + "en": "URL query string (?id=…)", + "tr": "URL query string (?id=…)" + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.KeySourceRoute", + "en": "Last URL segment (/…/5)", + "tr": "URL son segmenti (/…/5)" + }, { "resourceName": "Platform", "key": "App.DeveloperKitComponentDesigner.NoFilter", @@ -19772,7 +19784,7 @@ }, { "resourceName": "Platform", - "key": "App.DeveloperKitComponentDesigner.BoundToSqlDataSource", + "key": "App.DeveloperKitComponentDesigner.BoundToForm", "en": "This component is bound to a Form record; fields come from the columns in the Select (GET) response.", "tr": "Bu komponent Form kaydına bağlıdır; alanlar Select (GET) cevabındaki sütunlardan gelir." }, @@ -20576,7 +20588,7 @@ }, { "resourceName": "Platform", - "key": "App.DeveloperKitComponentDesigner.SqlDataSourceRecord", + "key": "App.DeveloperKitComponentDesigner.FormRecord", "en": "Form record · {field}", "tr": "Form kaydı · {field}" }, @@ -24966,6 +24978,12 @@ "en": "Sözsoft dynamic Gantt view", "tr": "Sözsoft dinamik Gantt görünümü" }, + { + "resourceName": "Platform", + "key": "App.CatalogGroup.Form", + "en": "Form", + "tr": "Form" + }, { "resourceName": "Platform", "key": "App.CatalogGroup.ApiCalls", @@ -24984,12 +25002,6 @@ "en": "Event and page", "tr": "Event ve sayfa" }, - { - "resourceName": "Platform", - "key": "App.CatalogGroup.SqlDataSource", - "en": "Form", - "tr": "Form" - }, { "resourceName": "Platform", "key": "App.CatalogHoverable.HoverableDescription", @@ -25100,73 +25112,73 @@ }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceDescription", + "key": "App.CatalogForm.FormDescription", "en": "A data container that manages GET/POST/PUT/DELETE endpoints on a single record; the components dropped inside bind to its columns", "tr": "GET/POST/PUT/DELETE endpointlerini tek kayıt üzerinde yöneten veri kabı; içine bırakılan komponentler sütunlara bağlanır" }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceDescription2", + "key": "App.CatalogForm.FormDescription2", "en": "The field name is the column name in the Select result.", "tr": "Alan adı Select sonucundaki sütun adıdır." }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceDescription3", + "key": "App.CatalogForm.FormDescription3", "en": "When Select returns more than one row.", "tr": "Select birden fazla satır döndürdüğünde." }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceDescription4", + "key": "App.CatalogForm.FormDescription4", "en": "Stops the operation if a required field is empty.", "tr": "Zorunlu alan boşsa işlemi durdurur." }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceDescription5", + "key": "App.CatalogForm.FormDescription5", "en": "New record / editing, busy and error state.", "tr": "Yeni kayıt / düzenleme, meşgul ve hata durumu." }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceTitleDescription", + "key": "App.CatalogForm.FormTitleDescription", "en": "Header text of the card wrapping the container; leave empty to hide the header. A value starting with :: is resolved as a localization key.", "tr": "Kabı saran kartın başlık metni; boş bırakılırsa başlık gizlenir. :: ile başlayan değer dil anahtarı olarak çözümlenir." }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceColumnCountDescription", + "key": "App.CatalogForm.FormColumnCountDescription", "en": "How many components are placed side by side inside the form. 1 keeps every component on its own row; 2 places two components per row and wraps to the next one.", "tr": "Form içindeki bileşenlerin yan yana kaç sütun halinde dizileceği. 1 seçilirse her bileşen alt alta durur; 2 seçilirse her satıra iki bileşen yerleşir ve sonraki satıra geçer." }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceTitle", + "key": "App.CatalogForm.FormTitle", "en": "Save / delete / reload", "tr": "Kaydet / sil / yenile" }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceTitle2", + "key": "App.CatalogForm.FormTitle2", "en": "Reading and writing the active record", "tr": "Aktif kaydı okuma ve yazma" }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceTitle3", + "key": "App.CatalogForm.FormTitle3", "en": "Navigating between records", "tr": "Kayıtlar arasında gezinme" }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceTitle4", + "key": "App.CatalogForm.FormTitle4", "en": "Validating before saving", "tr": "Kaydetmeden önce doğrulama" }, { "resourceName": "Platform", - "key": "App.CatalogSqlDataSource.SqlDataSourceTitle5", + "key": "App.CatalogForm.FormTitle5", "en": "Status information", "tr": "Durum bilgisi" }, @@ -25596,12 +25608,6 @@ "en": "Error occurred", "tr": "Hata oluştu" }, - { - "resourceName": "Platform", - "key": "App.EventOnError.OnErrorLabel2", - "en": "Last URL segment (/…/5)", - "tr": "URL son segmenti (/…/5)" - }, { "resourceName": "Platform", "key": "App.EventOnFieldChange.OnFieldChangeDescription", @@ -26753,6 +26759,126 @@ "key": "App.ListForm.WizardImportRollbackFailed", "en": "Rollback could not be completed", "tr": "Geri alma tamamlanamadı" + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.EndpointPermission", + "en": "Permission", + "tr": "Yetki" + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.UseCustomPermission", + "en": "Use custom permission: {permission}", + "tr": "Özel yetki kullan: {permission}" + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.PermissionHintRead", + "en": "Read permission: set, the whole container is hidden from users without it.", + "tr": "Read yetkisi: doluysa yetki verilmemiş kullanıcıda konteyner hiç gösterilmez." + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.PermissionHintInsert", + "en": "Insert permission: empty means the endpoint is enough; set, the New button is hidden from users without it.", + "tr": "Insert yetkisi: boşsa endpoint yeterli, doluysa yetki verilmemiş kullanıcıda Yeni butonu görünmez." + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.PermissionHintUpdate", + "en": "Update permission: empty means the endpoint is enough; set, the Save button is hidden from users without it.", + "tr": "Update yetkisi: boşsa endpoint yeterli, doluysa yetki verilmemiş kullanıcıda Kaydet butonu görünmez." + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.PermissionHintDelete", + "en": "Delete permission: empty means the endpoint is enough; set, the Delete button is hidden from users without it.", + "tr": "Delete yetkisi: boşsa endpoint yeterli, doluysa yetki verilmemiş kullanıcıda Sil butonu görünmez." + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.PermissionHintComponentAuto", + "en": "Automatic: no separate check is made for this component; the page's own menu permission decides.", + "tr": "Otomatik: bu komponent için ayrıca yetki kontrolü yapılmaz; sayfanın kendi menü yetkisi geçerlidir." + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.PermissionHintComponent", + "en": "The component is not rendered for anyone without the permission picked above.", + "tr": "Yukarıda seçilen yetkiye sahip olmayan kullanıcıya komponent hiç gösterilmez." + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.PermissionTypeAuto", + "en": "Automatic", + "tr": "Otomatik" + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.PermissionTypeCustom", + "en": "Custom", + "tr": "Custom" + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.PermissionHintAuto", + "en": "Automatic: the permission the Wizard created for this component is used (read for Select, .Create / .Update / .Delete for the commands). If no menu opens this component, nothing is checked.", + "tr": "Otomatik: Wizard'ın bu component için ürettiği yetki kullanılır (Select için read, komutlar için .Create / .Update / .Delete). Component'i açan bir menü yoksa kontrol yapılmaz." + }, + { + "resourceName": "Platform", + "key": "App.DeveloperKitComponentDesigner.PermissionPlaceholder", + "en": "Pick a permission", + "tr": "Bir yetki seçin" + }, + { + "resourceName": "Platform", + "key": "App.Platform.NewRecord", + "en": "New record", + "tr": "Yeni kayıt" + }, + { + "resourceName": "Platform", + "key": "App.Form.NoChanges", + "en": "There is nothing to save.", + "tr": "Kaydedilecek bir değişiklik yok." + }, + { + "resourceName": "Platform", + "key": "App.Form.InsertEndpointMissing", + "en": "No POST endpoint is defined for Insert.", + "tr": "Insert için POST endpointi tanımlı değil." + }, + { + "resourceName": "Platform", + "key": "App.Form.UpdateEndpointMissing", + "en": "No PUT endpoint is defined for Update.", + "tr": "Update için PUT endpointi tanımlı değil." + }, + { + "resourceName": "Platform", + "key": "App.Form.MissingKeyParam", + "en": "The Select endpoint expects the {0} parameter; it was not found in the page address.", + "tr": "Select endpointi {0} parametresini bekliyor; sayfa adresinde bulunamadı." + }, + { + "resourceName": "Platform", + "key": "App.Form.DeleteConfirm", + "en": "The record {0} will be deleted.\n\nDo you confirm?", + "tr": "{0} kaydı silinecek.\n\nOnaylıyor musunuz?" + }, + { + "resourceName": "Platform", + "key": "App.Platform.OptionFallback", + "en": "Option {0}", + "tr": "Seçenek {0}" + }, + { + "resourceName": "Platform", + "key": "App.CatalogForm.FormPermissionDescription", + "en": "Permission guarding this command. Empty leaves it open: the bound endpoint alone is enough for its button.", + "tr": "Bu komutu koruyan yetki. Boş bırakılırsa komut serbesttir: butonun görünmesi için bağlı endpoint yeterlidir." } ] } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs index a53edcdc..f80d8e75 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs @@ -820,6 +820,24 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep Visible = true, IsActive = true, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(PlatformConsts.IdentityPermissions.Roles.Create, PlatformConsts.IdentityPermissions.Roles.Default, PlatformConsts.IdentityPermissions.Roles.Update, true, true, false), + PivotSettingsJson = DefaultPivotSettingsJson + }, + new() { + ListFormCode = listForm.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Guid, + FieldName = "Id", + ColSpan = 1, + CaptionName = "App.Listform.ListformField.Id", + Width = 0, + ListOrderNo = 4, + Visible = false, + IsActive = true, + ColumnCustomizationJson = DefaultColumnCustomizationJson, PermissionJson = DefaultFieldPermissionJson(PlatformConsts.IdentityPermissions.Roles.Create, PlatformConsts.IdentityPermissions.Roles.Default, PlatformConsts.IdentityPermissions.Roles.Update, true, true, false), PivotSettingsJson = DefaultPivotSettingsJson diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs index 8139d40d..5d624847 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs @@ -201,21 +201,23 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency permRead = await _repoPerm.InsertAsync(new PermissionDefinitionRecord( Guid.NewGuid(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both), autoSave: true); - // Custom yolunda kayıt üzerinde CRUD yapılmadığı için yalnızca read izni üretilir. + // CRUD üçlüsü her iki yolda da üretilir: Custom component de Form + // üzerinden kayıt ekler, günceller ve siler (AppService.Create ile aynı davranış). + if (!existingPerms.Any(a => a.Name == permCreateName)) + await _repoPerm.InsertAsync(new PermissionDefinitionRecord( + Guid.NewGuid(), groupName, permCreateName, permRead.Name, WizardConsts.LangKeyCreate, true, MultiTenancySides.Both), autoSave: true); + + if (!existingPerms.Any(a => a.Name == permUpdateName)) + await _repoPerm.InsertAsync(new PermissionDefinitionRecord( + Guid.NewGuid(), groupName, permUpdateName, permRead.Name, WizardConsts.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: true); + + if (!existingPerms.Any(a => a.Name == permDeleteName)) + await _repoPerm.InsertAsync(new PermissionDefinitionRecord( + Guid.NewGuid(), groupName, permDeleteName, permRead.Name, WizardConsts.LangKeyDelete, true, MultiTenancySides.Both), autoSave: true); + + // Export/Import/Note yalnızca ListForm ekosisteminde karşılığı olan işlemlerdir. if (!isCustomComponent) { - if (!existingPerms.Any(a => a.Name == permCreateName)) - await _repoPerm.InsertAsync(new PermissionDefinitionRecord( - Guid.NewGuid(), groupName, permCreateName, permRead.Name, WizardConsts.LangKeyCreate, true, MultiTenancySides.Both), autoSave: true); - - if (!existingPerms.Any(a => a.Name == permUpdateName)) - await _repoPerm.InsertAsync(new PermissionDefinitionRecord( - Guid.NewGuid(), groupName, permUpdateName, permRead.Name, WizardConsts.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: true); - - if (!existingPerms.Any(a => a.Name == permDeleteName)) - await _repoPerm.InsertAsync(new PermissionDefinitionRecord( - Guid.NewGuid(), groupName, permDeleteName, permRead.Name, WizardConsts.LangKeyDelete, true, MultiTenancySides.Both), autoSave: true); - if (!existingPerms.Any(a => a.Name == permExportName)) await _repoPerm.InsertAsync(new PermissionDefinitionRecord( Guid.NewGuid(), groupName, permExportName, permRead.Name, WizardConsts.LangKeyExport, true, MultiTenancySides.Both), autoSave: true); diff --git a/configs/seeds/host/custom/RoleComponent.json b/configs/seeds/host/custom/RoleComponent.json index 294c1fa7..921304c1 100644 --- a/configs/seeds/host/custom/RoleComponent.json +++ b/configs/seeds/host/custom/RoleComponent.json @@ -1,11 +1,11 @@ { - "GeneratedAt": "2026-08-20T06:39:29.3988639Z", + "GeneratedAt": "2026-08-21T11:15:50.5342376Z", "CustomComponents": [ { "Name": "RoleComponent", "RoutePath": "/admin/roles", - "Code": "/*__SOZSOFT_VISUAL_DESIGNER__%7B%22version%22%3A1%2C%22sourceMode%22%3A%22visual%22%2C%22nodes%22%3A%5B%7B%22id%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22type%22%3A%22Form%22%2C%22kind%22%3A%22layout%22%2C%22props%22%3A%7B%22selectEndpoint%22%3A%22source_5w_5ibpv%22%2C%22insertEndpoint%22%3A%22source_h3_484qa%22%2C%22updateEndpoint%22%3A%22source_gb_d9cvs%22%2C%22deleteEndpoint%22%3A%22source_0t_avok9%22%2C%22keyFieldName%22%3A%22id%22%2C%22collectionPath%22%3A%22%22%2C%22keySource%22%3A%22query%22%2C%22keyParamName%22%3A%22id%22%2C%22previewKeyValue%22%3A%22%22%2C%22autoLoad%22%3Atrue%2C%22showToolbar%22%3Atrue%2C%22gap%22%3A16%2C%22className%22%3A%22%22%2C%22title%22%3A%22%3A%3AAbpIdentity.Roles%22%2C%22columnCount%22%3A2%7D%2C%22events%22%3A%7B%22onLoad%22%3A%22%22%2C%22onRecordChange%22%3A%22%22%2C%22onFieldChange%22%3A%22%22%2C%22onNewRecord%22%3A%22%22%2C%22onModeChange%22%3A%22%22%2C%22onBeforeSave%22%3A%22%22%2C%22onAfterSave%22%3A%22%22%2C%22onBeforeDelete%22%3A%22%22%2C%22onAfterDelete%22%3A%22%22%2C%22onError%22%3A%22%22%7D%2C%22bindings%22%3A%7B%7D%2C%22children%22%3A%5B%7B%22id%22%3A%22cmp_msyfpfia_ifhdq%22%2C%22type%22%3A%22Input%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22asElement%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22invalid%22%3Afalse%2C%22rows%22%3A3%2C%22prefix%22%3A%22Name%20%3A%22%2C%22size%22%3A%22md%22%2C%22suffix%22%3A%22%22%2C%22textArea%22%3Afalse%2C%22type%22%3A%22text%22%2C%22unstyle%22%3Afalse%2C%22field%22%3A%22%22%2C%22form%22%3A%22%22%2C%22className%22%3A%22%22%2C%22children%22%3A%22%22%2C%22style%22%3A%7B%7D%2C%22placeholder%22%3A%22Metin%20girin...%22%2C%22value%22%3A%22%22%2C%22name%22%3A%22%22%2C%22autoComplete%22%3A%22off%22%2C%22autoFocus%22%3Afalse%2C%22readOnly%22%3Afalse%2C%22required%22%3Afalse%2C%22maxLength%22%3A%22%22%2C%22minLength%22%3A%22%22%2C%22pattern%22%3A%22%22%2C%22sqlDefaultValue%22%3A%22%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22value%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22Name%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22input1%22%7D%2C%7B%22id%22%3A%22cmp_msyjl707_crcn7%22%2C%22type%22%3A%22Input%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22asElement%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22invalid%22%3Afalse%2C%22rows%22%3A3%2C%22prefix%22%3A%22Normalized%20Name%20%3A%22%2C%22size%22%3A%22md%22%2C%22suffix%22%3A%22%22%2C%22textArea%22%3Afalse%2C%22type%22%3A%22text%22%2C%22unstyle%22%3Afalse%2C%22field%22%3A%22%22%2C%22form%22%3A%22%22%2C%22className%22%3A%22%22%2C%22children%22%3A%22%22%2C%22style%22%3A%7B%7D%2C%22placeholder%22%3A%22Metin%20girin...%22%2C%22value%22%3A%22%22%2C%22name%22%3A%22%22%2C%22autoComplete%22%3A%22off%22%2C%22autoFocus%22%3Afalse%2C%22readOnly%22%3Afalse%2C%22required%22%3Afalse%2C%22maxLength%22%3A%22%22%2C%22minLength%22%3A%22%22%2C%22pattern%22%3A%22%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22value%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22NormalizedName%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22input2%22%7D%2C%7B%22id%22%3A%22cmp_msyjdodn_u3o74%22%2C%22type%22%3A%22Input%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22asElement%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22invalid%22%3Afalse%2C%22rows%22%3A3%2C%22prefix%22%3A%22Concurrent%20Stamp%20%3A%20%22%2C%22size%22%3A%22md%22%2C%22suffix%22%3A%22%22%2C%22textArea%22%3Afalse%2C%22type%22%3A%22text%22%2C%22unstyle%22%3Afalse%2C%22field%22%3A%22%22%2C%22form%22%3A%22%22%2C%22className%22%3A%22%22%2C%22children%22%3A%22%22%2C%22style%22%3A%7B%7D%2C%22placeholder%22%3A%22Metin%20girin...%22%2C%22value%22%3A%22%22%2C%22name%22%3A%22%22%2C%22autoComplete%22%3A%22off%22%2C%22autoFocus%22%3Afalse%2C%22readOnly%22%3Afalse%2C%22required%22%3Afalse%2C%22maxLength%22%3A%22%22%2C%22minLength%22%3A%22%22%2C%22pattern%22%3A%22%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22value%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22ConcurrencyStamp%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22input3%22%7D%2C%7B%22id%22%3A%22cmp_msyje9ys_2c08a%22%2C%22type%22%3A%22Checkbox%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22checked%22%3Afalse%2C%22color%22%3A%22blue-500%22%2C%22disabled%22%3Afalse%2C%22labelRef%22%3A%22%22%2C%22name%22%3A%22%22%2C%22readOnly%22%3Afalse%2C%22value%22%3A%22%22%2C%22field%22%3Anull%2C%22className%22%3A%22%22%2C%22children%22%3A%22Is%20Default%22%2C%22style%22%3A%7B%7D%2C%22sqlDefaultValue%22%3A%22true%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22checked%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22IsDefault%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22checkbox1%22%7D%2C%7B%22id%22%3A%22cmp_msyjebu2_27tqi%22%2C%22type%22%3A%22Checkbox%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22checked%22%3Afalse%2C%22color%22%3A%22blue-500%22%2C%22disabled%22%3Afalse%2C%22labelRef%22%3A%22%22%2C%22name%22%3A%22%22%2C%22readOnly%22%3Afalse%2C%22value%22%3A%22%22%2C%22field%22%3Anull%2C%22className%22%3A%22%22%2C%22children%22%3A%22Is%20Static%22%2C%22style%22%3A%7B%7D%2C%22sqlDefaultValue%22%3A%22true%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22checked%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22IsStatic%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22checkbox2%22%7D%2C%7B%22id%22%3A%22cmp_msyjmetn_gojol%22%2C%22type%22%3A%22Checkbox%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22checked%22%3Afalse%2C%22color%22%3A%22blue-500%22%2C%22disabled%22%3Afalse%2C%22labelRef%22%3A%22%22%2C%22name%22%3A%22%22%2C%22readOnly%22%3Afalse%2C%22value%22%3A%22%22%2C%22field%22%3Anull%2C%22className%22%3A%22%22%2C%22children%22%3A%22Is%20Public%22%2C%22style%22%3A%7B%7D%2C%22sqlDefaultValue%22%3A%22false%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22checked%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22IsPublic%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22checkbox3%22%7D%2C%7B%22id%22%3A%22cmp_msyjo0g8_oathk%22%2C%22type%22%3A%22Input%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22asElement%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22invalid%22%3Afalse%2C%22rows%22%3A3%2C%22prefix%22%3A%22Entity%20Version%20%3A%20%22%2C%22size%22%3A%22md%22%2C%22suffix%22%3A%22%22%2C%22textArea%22%3Afalse%2C%22type%22%3A%22number%22%2C%22unstyle%22%3Afalse%2C%22field%22%3A%22%22%2C%22form%22%3A%22%22%2C%22className%22%3A%22%22%2C%22children%22%3A%22%22%2C%22style%22%3A%7B%7D%2C%22placeholder%22%3A%22Metin%20girin...%22%2C%22value%22%3A%22%22%2C%22name%22%3A%22%22%2C%22autoComplete%22%3A%22off%22%2C%22autoFocus%22%3Afalse%2C%22readOnly%22%3Afalse%2C%22required%22%3Afalse%2C%22maxLength%22%3A%22%22%2C%22minLength%22%3A%22%22%2C%22pattern%22%3A%22%22%2C%22sqlDefaultValue%22%3A%223%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22value%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22EntityVersion%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22input4%22%7D%2C%7B%22id%22%3A%22cmp_msyjplfq_py8lz%22%2C%22type%22%3A%22Input%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22asElement%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22invalid%22%3Afalse%2C%22rows%22%3A3%2C%22prefix%22%3A%22Extra%20Properties%20%3A%22%2C%22size%22%3A%22md%22%2C%22suffix%22%3A%22%22%2C%22textArea%22%3Afalse%2C%22type%22%3A%22text%22%2C%22unstyle%22%3Afalse%2C%22field%22%3A%22%22%2C%22form%22%3A%22%22%2C%22className%22%3A%22%22%2C%22children%22%3A%22%22%2C%22style%22%3A%7B%7D%2C%22placeholder%22%3A%22Metin%20girin...%22%2C%22value%22%3A%22%22%2C%22name%22%3A%22%22%2C%22autoComplete%22%3A%22off%22%2C%22autoFocus%22%3Afalse%2C%22readOnly%22%3Afalse%2C%22required%22%3Afalse%2C%22maxLength%22%3A%22%22%2C%22minLength%22%3A%22%22%2C%22pattern%22%3A%22%22%2C%22sqlDefaultValue%22%3A%22%7B%7D%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22value%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22ExtraProperties%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22input5%22%7D%5D%2C%22ref%22%3A%22form1%22%7D%2C%7B%22id%22%3A%22cmp_mt023r5u_rb2jv%22%2C%22type%22%3A%22Grid%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22hoverable%22%3Atrue%2C%22overflow%22%3Atrue%2C%22borderlessRow%22%3Afalse%2C%22compact%22%3Afalse%2C%22cols%22%3A%223%22%2C%22rows%22%3A%222%22%2C%22gap%22%3A%224%22%2C%22colGap%22%3A%224%22%2C%22rowGap%22%3A%224%22%2C%22responsive%22%3Atrue%2C%22smCols%22%3A%221%22%2C%22mdCols%22%3A%222%22%2C%22lgCols%22%3A%223%22%2C%22xlCols%22%3A%224%22%2C%22autoFit%22%3Afalse%2C%22minColWidth%22%3A%22200px%22%2C%22height%22%3A%22auto%22%2C%22className%22%3A%22mt-4%22%2C%22items%22%3A%5B%7B%22id%22%3A%22grid-item-1%22%2C%22content%22%3A%22Grid%20Item%201%22%2C%22colSpan%22%3A1%2C%22rowSpan%22%3A1%2C%22colStart%22%3A1%2C%22rowStart%22%3A1%2C%22className%22%3A%22bg-blue-100%20p-4%20border%20rounded%22%7D%2C%7B%22id%22%3A%22grid-item-2%22%2C%22content%22%3A%22Grid%20Item%202%22%2C%22colSpan%22%3A1%2C%22rowSpan%22%3A1%2C%22colStart%22%3A2%2C%22rowStart%22%3A1%2C%22className%22%3A%22bg-green-100%20p-4%20border%20rounded%22%7D%2C%7B%22id%22%3A%22grid-item-3%22%2C%22content%22%3A%22Grid%20Item%203%22%2C%22colSpan%22%3A1%2C%22rowSpan%22%3A1%2C%22colStart%22%3A3%2C%22rowStart%22%3A1%2C%22className%22%3A%22bg-yellow-100%20p-4%20border%20rounded%22%7D%2C%7B%22id%22%3A%22grid-item-4%22%2C%22content%22%3A%22Grid%20Item%204%22%2C%22colSpan%22%3A2%2C%22rowSpan%22%3A1%2C%22colStart%22%3A1%2C%22rowStart%22%3A2%2C%22className%22%3A%22bg-purple-100%20p-4%20border%20rounded%22%7D%2C%7B%22id%22%3A%22grid-item-5%22%2C%22content%22%3A%22Grid%20Item%205%22%2C%22colSpan%22%3A1%2C%22rowSpan%22%3A1%2C%22colStart%22%3A3%2C%22rowStart%22%3A2%2C%22className%22%3A%22bg-red-100%20p-4%20border%20rounded%22%7D%5D%2C%22dataColumns%22%3A%5B%22UserId%22%5D%2C%22columnLookups%22%3A%7B%22UserId%22%3A%7B%22sourceId%22%3A%22source_gq_0q1dz%22%2C%22path%22%3A%22%22%2C%22valueField%22%3A%22Id%22%2C%22textField%22%3A%22UserName%22%7D%7D%2C%22columnCaptions%22%3A%7B%7D%7D%2C%22events%22%3A%7B%7D%2C%22bindings%22%3A%7B%22items%22%3A%7B%22sourceId%22%3A%22source_tq_veb4m%22%2C%22path%22%3A%22%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22grid1%22%7D%2C%7B%22id%22%3A%22cmp_mt0288i3_ebpj5%22%2C%22type%22%3A%22ListView%22%2C%22kind%22%3A%22platform%22%2C%22props%22%3A%7B%22listFormCode%22%3A%22AbpIdentity.Roles%22%2C%22height%22%3A%22520px%22%2C%22filters%22%3A%5B%7B%22id%22%3A%22filter_y9_tr73w%22%2C%22field%22%3A%22Id%22%2C%22operator%22%3A%22eq%22%2C%22source%22%3A%22record%22%2C%22value%22%3A%22form1.id%22%2C%22previewValue%22%3A%22%22%2C%22required%22%3Atrue%7D%5D%2C%22className%22%3A%22mt-4%22%7D%2C%22events%22%3A%7B%7D%2C%22bindings%22%3A%7B%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22listView1%22%7D%5D%2C%22canvas%22%3A%7B%22width%22%3A%22responsive%22%7D%2C%22lifecycle%22%3A%7B%22onMount%22%3A%22%22%7D%2C%22dataSources%22%3A%5B%7B%22id%22%3A%22source_5w_5ibpv%22%2C%22name%22%3A%22AbpRoles%20%C2%B7%20GetList%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpRoles%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_i7_msox4%22%2C%22name%22%3A%22AbpRoles%20%C2%B7%20GetById%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpRoles%2F%7Bid%7D%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_0t_avok9%22%2C%22name%22%3A%22AbpRoles%20%C2%B7%20Delete%22%2C%22method%22%3A%22DELETE%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpRoles%2F%7Bid%7D%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_h3_484qa%22%2C%22name%22%3A%22AbpRoles%20%C2%B7%20Create%22%2C%22method%22%3A%22POST%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpRoles%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_gb_d9cvs%22%2C%22name%22%3A%22AbpRoles%20%C2%B7%20Update%22%2C%22method%22%3A%22PUT%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpRoles%2F%7Bid%7D%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_tq_veb4m%22%2C%22name%22%3A%22AbpUserRoles%20%C2%B7%20GetList%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpUserRoles%22%2C%22responsePath%22%3A%22%22%2C%22filters%22%3A%5B%7B%22id%22%3A%22filter_sw_86e0q%22%2C%22field%22%3A%22RoleId%22%2C%22operator%22%3A%22eq%22%2C%22source%22%3A%22record%22%2C%22value%22%3A%22form1.id%22%2C%22previewValue%22%3A%22%22%2C%22required%22%3Atrue%7D%5D%7D%2C%7B%22id%22%3A%22source_d2_bri5s%22%2C%22name%22%3A%22AbpUserRoles%20%C2%B7%20GetById%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpUserRoles%2F%7Bid%7D%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_gq_0q1dz%22%2C%22name%22%3A%22AbpUsers%20%C2%B7%20GetList%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpUsers%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_u8_nu1lu%22%2C%22name%22%3A%22AbpUsers%20%C2%B7%20GetById%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpUsers%2F%7Bid%7D%22%2C%22responsePath%22%3A%22%22%7D%5D%7D__*/\nconst RoleComponent = () => {\n const getByPath = (value, path) => {\n if (!path) return value\n const readPath = (target, targetPath) => targetPath.split('.').filter(Boolean).reduce((current, key) => current?.[key], target)\n const directValue = readPath(value, path)\n if (directValue !== undefined) return directValue\n if (path.startsWith('result.')) return readPath(value, path.slice('result.'.length))\n return value?.result === undefined ? undefined : readPath(value.result, path)\n }\n\n const resolveLookupText = (lookup, value) => {\n if (!lookup || value === null || value === undefined || value === \"\") return undefined\n const rows = getByPath(lookup.rows, lookup.path)\n if (!Array.isArray(rows)) return undefined\n const key = String(value)\n const row = rows.find((item) => item && typeof item === \"object\" && String(getByPath(item, lookup.value) ?? \"\") === key)\n if (!row) return undefined\n const text = getByPath(row, lookup.text)\n return text === null || text === undefined || text === \"\" ? undefined : text\n }\n\n const localeCulture = () => (typeof document !== \"undefined\" && document.documentElement.lang) || \"en\"\n const localeDateOptions = { year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }\n const localeTimeOptions = { year: \"numeric\", month: \"2-digit\", day: \"2-digit\", hour: \"2-digit\", minute: \"2-digit\" }\n const localeIsoPattern = /^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ](\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?)?$/\n const formatLocaleValue = (value) => {\n if (value === null || value === undefined) return \"\"\n const culture = localeCulture()\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) return String(value)\n return new Intl.NumberFormat(culture, { useGrouping: false, maximumFractionDigits: 20 }).format(value)\n }\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? \"\" : new Intl.DateTimeFormat(culture, localeTimeOptions).format(value)\n }\n if (typeof value !== \"string\") return String(value)\n const parts = localeIsoPattern.exec(value.trim())\n if (!parts) return value\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return value\n if (parts[4] === undefined) {\n const day = new Date(Number(parts[1]), Number(parts[2]) - 1, Number(parts[3]))\n return new Intl.DateTimeFormat(culture, localeDateOptions).format(day)\n }\n return new Intl.DateTimeFormat(culture, localeTimeOptions).format(parsed)\n }\n\n const readSqlField = (record, field) => {\n if (!record || typeof record !== \"object\" || Array.isArray(record)) return undefined\n if (field in record) return record[field]\n const matched = Object.keys(record).find((key) => key.toLowerCase() === String(field).toLowerCase())\n return matched === undefined ? undefined : record[matched]\n }\n const toSqlRows = (value, collectionPath) => {\n const source = collectionPath ? getByPath(value, collectionPath) : value\n const rows = Array.isArray(source)\n ? source\n : source && typeof source === \"object\"\n ? (Array.isArray(source.items) ? source.items : [source])\n : []\n return rows.filter((item) => item && typeof item === \"object\" && !Array.isArray(item))\n }\n const setSqlField = (record, path, value) => {\n const keys = String(path || \"\").split(\".\").filter(Boolean)\n if (!keys.length) return record\n const next = { ...(record || {}) }\n let target = next\n for (let index = 0; index < keys.length - 1; index += 1) {\n const child = target[keys[index]]\n target[keys[index]] = child && typeof child === \"object\" && !Array.isArray(child) ? { ...child } : {}\n target = target[keys[index]]\n }\n target[keys[keys.length - 1]] = value\n return next\n }\n const bindSqlUrl = (url, record, keyField) => {\n let keyBound = false\n const boundUrl = String(url).replace(/\\{([^}]+)\\}|(?<=\\/):([A-Za-z_][A-Za-z0-9_]*)/g, (match, braced, colon) => {\n const name = (braced || colon || \"\").trim()\n if (!name) return match\n const value = readSqlField(record, name) ?? readSqlField(record, keyField)\n if (value === undefined || value === null || value === \"\") return match\n if (name.toLowerCase() === String(keyField).toLowerCase()) keyBound = true\n return encodeURIComponent(String(value))\n })\n return { url: boundUrl, keyBound }\n }\n const appendQueryParam = (url, name, value) =>\n url + (url.includes(\"?\") ? \"&\" : \"?\") + encodeURIComponent(name) + \"=\" + encodeURIComponent(value)\n const hasSqlUrlParams = (url) => /\\{[^}]+\\}|(?<=\\/):[A-Za-z_][A-Za-z0-9_]*/.test(String(url))\n const readUrlKey = (source, name) => {\n if (typeof window === \"undefined\") return \"\"\n if (source === \"query\") return new URLSearchParams(window.location.search).get(name) ?? \"\"\n const segments = window.location.pathname.split(\"/\").filter(Boolean)\n return segments.length ? decodeURIComponent(segments[segments.length - 1]) : \"\"\n }\n const filterSqlRowsByKey = (rows, keyField, keyValue) => {\n if (!keyValue) return rows\n const matched = rows.filter((row) => String(readSqlField(row, keyField) ?? \"\") === String(keyValue))\n // No match means the endpoint already filtered server side, or the key column\n // is named differently — keeping the rows beats showing an empty form.\n return matched.length ? matched : rows\n }\n // Update payload: only the columns the user actually edited, plus the key.\n const toSqlChanges = (original, current, keyField) => {\n const changes = {}\n const isSame = (left, right) => JSON.stringify(left ?? null) === JSON.stringify(right ?? null)\n Object.keys(current || {}).forEach((column) => {\n if (!isSame(original ? original[column] : undefined, current[column])) changes[column] = current[column]\n })\n const keyColumn = Object.keys(current || {}).find((column) => column.toLowerCase() === String(keyField).toLowerCase())\n if (keyColumn !== undefined && changes[keyColumn] === undefined) changes[keyColumn] = current[keyColumn]\n return changes\n }\n const callSqlEndpoint = (target, record, keyField, payload) => {\n // The URL is always bound from the full record: a placeholder may reference a\n // column that the update payload does not carry.\n const bound = bindSqlUrl(target.url, record, keyField)\n const request = { url: bound.url, method: target.method }\n if (target.method === \"POST\" || target.method === \"PUT\") request.data = payload === undefined ? record : payload\n const keyValue = readSqlField(record, keyField)\n const hasKey = keyValue !== undefined && keyValue !== null && keyValue !== \"\"\n // The key still has to reach the endpoint when the URL carries no placeholder.\n if (!bound.keyBound && hasKey && target.method !== \"POST\") request.params = { [keyField]: keyValue }\n return apiService.fetchData(request)\n }\n // A new record starts at the top of the form: the first enabled editor takes the\n // cursor, so a Yeni click is immediately followed by typing.\n const focusFirstSqlField = (host) => {\n if (typeof window === \"undefined\") return\n window.requestAnimationFrame(() => {\n const field = host.current?.querySelector(\"input:not([type='hidden']):not([disabled]):not([readonly]), textarea:not([disabled]):not([readonly]), select:not([disabled])\")\n if (!field) return\n field.focus()\n if (typeof field.select === \"function\") field.select()\n })\n }\n // Defaults declared on the bound children; only a new record starts from them.\n const buildSqlDefaults = (fields) => fields.reduce((record, field) => setSqlField(record, field.path, resolveSqlDefault(field)), {})\n const resolveSqlDefault = (field) => {\n if (field.token !== \"today\" && field.token !== \"now\") return field.value\n const now = new Date()\n const pad = (value) => String(value).padStart(2, \"0\")\n const day = now.getFullYear() + \"-\" + pad(now.getMonth() + 1) + \"-\" + pad(now.getDate())\n if (field.token === \"today\" || field.dateOnly) return day\n return day + \"T\" + pad(now.getHours()) + \":\" + pad(now.getMinutes()) + \":\" + pad(now.getSeconds())\n }\n const toSqlErrorMessage = (error) => error?.response?.data?.error?.message || error?.response?.data?.message || error?.message || \"İşlem tamamlanamadı.\"\n\n // Value of a filter that reads the page URL: a query string parameter, or a\n // path segment — the one after a segment named like the parameter, otherwise\n // the last one, which is the /orders/{id} detail page shape.\n const readDesignerUrlValue = (kind, name) => {\n if (typeof window === \"undefined\") return \"\"\n if (kind === \"query\") return new URLSearchParams(window.location.search).get(name) ?? \"\"\n const segments = window.location.pathname.split(\"/\").filter(Boolean)\n if (!segments.length) return \"\"\n const index = name ? segments.findIndex((segment) => segment.toLowerCase() === String(name).toLowerCase()) : -1\n if (index >= 0 && index < segments.length - 1) return decodeURIComponent(segments[index + 1])\n return decodeURIComponent(segments[segments.length - 1])\n }\n // Filters are appended as query parameters: `Column` for an equality and\n // `Column.operator` for anything else, which is what the endpoint parses.\n // Returns \"\" when a required filter has no value yet — the caller must skip the\n // request instead of loading the unfiltered collection.\n const buildFilteredUrl = (url, filters) => {\n const parts = []\n for (const filter of filters || []) {\n const value = filter.value === undefined || filter.value === null ? \"\" : String(filter.value)\n if (!value) {\n if (filter.required) return \"\"\n continue\n }\n parts.push(encodeURIComponent(filter.param) + \"=\" + encodeURIComponent(value))\n }\n if (!parts.length) return url\n return url + (url.includes(\"?\") ? \"&\" : \"?\") + parts.join(\"&\")\n }\n\n const buildPlatformFilterParams = (filters) => {\n const expressions = []\n for (const filter of filters || []) {\n if (filter.kind === \"isnull\" || filter.kind === \"notnull\") {\n expressions.push([filter.field, filter.operator, null])\n continue\n }\n const value = filter.value === undefined || filter.value === null ? \"\" : String(filter.value)\n if (!value) {\n if (filter.required) return null\n continue\n }\n if (filter.kind === \"in\") {\n const entries = value.split(\",\").map((entry) => entry.trim()).filter(Boolean).map((entry) => [filter.field, \"=\", entry])\n if (!entries.length) continue\n expressions.push(entries.reduce((result, entry) => (result ? [result, \"or\", entry] : entry), null))\n continue\n }\n expressions.push([filter.field, filter.operator, value])\n }\n const params = new URLSearchParams()\n const merged = expressions.reduce((result, expression) => (result ? [result, \"and\", expression] : expression), null)\n if (merged) params.set(\"filter\", JSON.stringify(merged))\n return params\n }\n\n const [data_source_5w_5ibpv, setData_source_5w_5ibpv] = React.useState(null)\n const [data_source_i7_msox4, setData_source_i7_msox4] = React.useState(null)\n const [data_source_0t_avok9] = React.useState(null)\n const [data_source_h3_484qa] = React.useState(null)\n const [data_source_gb_d9cvs] = React.useState(null)\n const [data_source_tq_veb4m, setData_source_tq_veb4m] = React.useState(null)\n const [data_source_d2_bri5s, setData_source_d2_bri5s] = React.useState(null)\n const [data_source_gq_0q1dz, setData_source_gq_0q1dz] = React.useState(null)\n const [data_source_u8_nu1lu, setData_source_u8_nu1lu] = React.useState(null)\n\n const sqlselect_cmp_msyfmegt_jkkn3 = { url: \"/api/app/crudendpoint/AbpRoles\", method: \"GET\", responsePath: \"\" }\n const sqlinsert_cmp_msyfmegt_jkkn3 = { url: \"/api/app/crudendpoint/AbpRoles\", method: \"POST\", responsePath: \"\" }\n const sqlupdate_cmp_msyfmegt_jkkn3 = { url: \"/api/app/crudendpoint/AbpRoles/{id}\", method: \"PUT\", responsePath: \"\" }\n const sqldelete_cmp_msyfmegt_jkkn3 = { url: \"/api/app/crudendpoint/AbpRoles/{id}\", method: \"DELETE\", responsePath: \"\" }\n const sqlKey_cmp_msyfmegt_jkkn3 = \"id\"\n const sqlKeyParam_cmp_msyfmegt_jkkn3 = \"id\"\n const [data_cmp_msyfmegt_jkkn3, setData_cmp_msyfmegt_jkkn3] = React.useState({})\n const [sqlOriginal_cmp_msyfmegt_jkkn3, setSqlOriginal_cmp_msyfmegt_jkkn3] = React.useState({})\n const [sqlMode_cmp_msyfmegt_jkkn3, setSqlMode_cmp_msyfmegt_jkkn3] = React.useState(\"edit\")\n const [sqlIndex_cmp_msyfmegt_jkkn3, setSqlIndex_cmp_msyfmegt_jkkn3] = React.useState(0)\n const [sqlBusy_cmp_msyfmegt_jkkn3, setSqlBusy_cmp_msyfmegt_jkkn3] = React.useState(false)\n const [sqlError_cmp_msyfmegt_jkkn3, setSqlError_cmp_msyfmegt_jkkn3] = React.useState(\"\")\n // Container element, so New can hand the cursor to the first field of the form.\n const sqlHost_cmp_msyfmegt_jkkn3 = React.useRef(null)\n // The Select key can come from the page URL, which is how a detail page reads\n // /api/app/orders/{id} or /api/app/orders?id=… for a single record.\n const sqlUrlKey_cmp_msyfmegt_jkkn3 = readUrlKey(\"query\", sqlKeyParam_cmp_msyfmegt_jkkn3)\n const sqlSelectUrl_cmp_msyfmegt_jkkn3 = React.useMemo(() => {\n if (!sqlselect_cmp_msyfmegt_jkkn3) return \"\"\n const base = sqlUrlKey_cmp_msyfmegt_jkkn3\n ? (() => {\n const bound = bindSqlUrl(sqlselect_cmp_msyfmegt_jkkn3.url, { [sqlKeyParam_cmp_msyfmegt_jkkn3]: sqlUrlKey_cmp_msyfmegt_jkkn3 }, sqlKeyParam_cmp_msyfmegt_jkkn3)\n return bound.keyBound ? bound.url : appendQueryParam(bound.url, sqlKeyParam_cmp_msyfmegt_jkkn3, sqlUrlKey_cmp_msyfmegt_jkkn3)\n })()\n : sqlselect_cmp_msyfmegt_jkkn3.url\n return base\n }, [sqlUrlKey_cmp_msyfmegt_jkkn3])\n // A key in the page URL always narrows the result: a list endpoint ignores the\n // parameter server side, so the requested record is picked out here. Without a\n // key every row is kept and the navigation below takes over.\n const sqlRows_cmp_msyfmegt_jkkn3 = React.useMemo(\n () => filterSqlRowsByKey(toSqlRows(data_source_5w_5ibpv, \"\"), sqlKey_cmp_msyfmegt_jkkn3, sqlUrlKey_cmp_msyfmegt_jkkn3),\n [data_source_5w_5ibpv, sqlUrlKey_cmp_msyfmegt_jkkn3],\n )\n // A fresh result set resets the position; New mode is left untouched so an\n // unsaved draft is not overwritten by a re-render.\n React.useEffect(() => {\n setSqlIndex_cmp_msyfmegt_jkkn3((current) => (current < sqlRows_cmp_msyfmegt_jkkn3.length ? current : 0))\n }, [sqlRows_cmp_msyfmegt_jkkn3])\n React.useEffect(() => {\n const row = sqlRows_cmp_msyfmegt_jkkn3[sqlIndex_cmp_msyfmegt_jkkn3]\n if (!row) return\n setData_cmp_msyfmegt_jkkn3(row)\n setSqlOriginal_cmp_msyfmegt_jkkn3(row)\n setSqlMode_cmp_msyfmegt_jkkn3(\"edit\")\n }, [sqlRows_cmp_msyfmegt_jkkn3, sqlIndex_cmp_msyfmegt_jkkn3])\n const sqlHasKey_cmp_msyfmegt_jkkn3 = (() => {\n const value = readSqlField(data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3)\n return value !== undefined && value !== null && value !== \"\"\n })()\n const sqlSetField_cmp_msyfmegt_jkkn3 = React.useCallback((path, value) => setData_cmp_msyfmegt_jkkn3((current) => setSqlField(current, path, value)), [])\n const sqlReload_cmp_msyfmegt_jkkn3 = React.useCallback(async () => {\n if (!sqlSelectUrl_cmp_msyfmegt_jkkn3) return\n // Calling a URL that still holds a placeholder is a guaranteed 400, so the\n // missing key is reported in the component instead.\n if (hasSqlUrlParams(sqlSelectUrl_cmp_msyfmegt_jkkn3)) {\n throw new Error(\"Select endpointi \" + sqlKeyParam_cmp_msyfmegt_jkkn3 + \" parametresini bekliyor; sayfa adresinde bulunamadı.\")\n }\n const response = await apiService.fetchData({ url: sqlSelectUrl_cmp_msyfmegt_jkkn3, method: \"GET\" })\n setData_source_5w_5ibpv(getByPath(response.data, sqlselect_cmp_msyfmegt_jkkn3.responsePath))\n }, [sqlSelectUrl_cmp_msyfmegt_jkkn3])\n // Reload wrapped with the busy/error handling the toolbar and mount effect need,\n // so a failing Select is reported in the component instead of the console.\n const sqlRefresh_cmp_msyfmegt_jkkn3 = React.useCallback(async () => {\n setSqlBusy_cmp_msyfmegt_jkkn3(true)\n setSqlError_cmp_msyfmegt_jkkn3(\"\")\n try {\n await sqlReload_cmp_msyfmegt_jkkn3()\n } catch (error) {\n setSqlError_cmp_msyfmegt_jkkn3(toSqlErrorMessage(error))\n } finally {\n setSqlBusy_cmp_msyfmegt_jkkn3(false)\n }\n }, [sqlReload_cmp_msyfmegt_jkkn3])\n const sqlNew_cmp_msyfmegt_jkkn3 = React.useCallback(() => {\n setSqlError_cmp_msyfmegt_jkkn3(\"\")\n // Defaults are resolved on every New, so an @today column carries the day the\n // record was opened rather than the day the component was generated.\n const sqlDraft_cmp_msyfmegt_jkkn3 = buildSqlDefaults([{\"path\":\"IsDefault\",\"value\":true},{\"path\":\"IsStatic\",\"value\":true},{\"path\":\"IsPublic\",\"value\":false},{\"path\":\"EntityVersion\",\"value\":3},{\"path\":\"ExtraProperties\",\"value\":\"{}\"}])\n setData_cmp_msyfmegt_jkkn3(sqlDraft_cmp_msyfmegt_jkkn3)\n setSqlOriginal_cmp_msyfmegt_jkkn3({})\n setSqlMode_cmp_msyfmegt_jkkn3(\"new\")\n // After the re-render that paints the empty form, not before it.\n focusFirstSqlField(sqlHost_cmp_msyfmegt_jkkn3)\n }, [])\n const sqlPrev_cmp_msyfmegt_jkkn3 = React.useCallback(() => setSqlIndex_cmp_msyfmegt_jkkn3((current) => Math.max(0, current - 1)), [])\n const sqlNext_cmp_msyfmegt_jkkn3 = React.useCallback(() => setSqlIndex_cmp_msyfmegt_jkkn3((current) => Math.min(sqlRows_cmp_msyfmegt_jkkn3.length - 1, current + 1)), [sqlRows_cmp_msyfmegt_jkkn3])\n const sqlSave_cmp_msyfmegt_jkkn3 = async () => {\n const isNew = sqlMode_cmp_msyfmegt_jkkn3 === \"new\"\n const target = isNew ? sqlinsert_cmp_msyfmegt_jkkn3 : sqlupdate_cmp_msyfmegt_jkkn3\n if (!target) {\n setSqlError_cmp_msyfmegt_jkkn3(isNew ? \"Insert için POST endpointi tanımlı değil.\" : \"Update için PUT endpointi tanımlı değil.\")\n return\n }\n // Update carries only the edited columns plus the key; Insert sends the record.\n const payload = isNew ? data_cmp_msyfmegt_jkkn3 : toSqlChanges(sqlOriginal_cmp_msyfmegt_jkkn3, data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3)\n if (!isNew && Object.keys(payload).filter((column) => column.toLowerCase() !== String(sqlKey_cmp_msyfmegt_jkkn3).toLowerCase()).length === 0) {\n setSqlError_cmp_msyfmegt_jkkn3(\"Kaydedilecek bir değişiklik yok.\")\n return\n }\n setSqlBusy_cmp_msyfmegt_jkkn3(true)\n setSqlError_cmp_msyfmegt_jkkn3(\"\")\n try {\n const response = await callSqlEndpoint(target, data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3, payload)\n // With a Select endpoint the reload is the source of truth and its effect\n // repopulates the form; without one, the response row is all there is.\n if (sqlselect_cmp_msyfmegt_jkkn3) {\n await sqlReload_cmp_msyfmegt_jkkn3()\n } else {\n const saved = toSqlRows(response?.data, \"\")[0]\n if (saved) {\n setData_cmp_msyfmegt_jkkn3(saved)\n setSqlOriginal_cmp_msyfmegt_jkkn3(saved)\n }\n }\n setSqlMode_cmp_msyfmegt_jkkn3(\"edit\")\n } catch (error) {\n setSqlError_cmp_msyfmegt_jkkn3(toSqlErrorMessage(error))\n } finally {\n setSqlBusy_cmp_msyfmegt_jkkn3(false)\n }\n }\n const sqlDelete_cmp_msyfmegt_jkkn3 = async () => {\n if (!sqldelete_cmp_msyfmegt_jkkn3 || !sqlHasKey_cmp_msyfmegt_jkkn3 || sqlMode_cmp_msyfmegt_jkkn3 === \"new\") return\n // Deleting cannot be undone from the form, so it always asks first.\n const confirmMessage = sqlKey_cmp_msyfmegt_jkkn3 + \" = \" + readSqlField(data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3) + \" kaydı silinecek.\\n\\nOnaylıyor musunuz?\"\n if (typeof window !== \"undefined\" && !window.confirm(confirmMessage)) return\n setSqlBusy_cmp_msyfmegt_jkkn3(true)\n setSqlError_cmp_msyfmegt_jkkn3(\"\")\n try {\n await callSqlEndpoint(sqldelete_cmp_msyfmegt_jkkn3, data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3)\n setData_cmp_msyfmegt_jkkn3({})\n setSqlOriginal_cmp_msyfmegt_jkkn3({})\n setSqlMode_cmp_msyfmegt_jkkn3(\"new\")\n await sqlReload_cmp_msyfmegt_jkkn3()\n } catch (error) {\n setSqlError_cmp_msyfmegt_jkkn3(toSqlErrorMessage(error))\n } finally {\n setSqlBusy_cmp_msyfmegt_jkkn3(false)\n }\n }\n React.useEffect(() => { void sqlRefresh_cmp_msyfmegt_jkkn3() }, [sqlRefresh_cmp_msyfmegt_jkkn3])\n\n const filters_source_tq_veb4m = [{ param: \"RoleId\", value: readSqlField(data_cmp_msyfmegt_jkkn3, \"id\"), required: true }]\n const filterKey_source_tq_veb4m = JSON.stringify(filters_source_tq_veb4m)\n React.useEffect(() => {\n const url = buildFilteredUrl(\"/api/app/crudendpoint/AbpUserRoles\", filters_source_tq_veb4m)\n // An unmet required filter leaves the component empty on purpose.\n if (!url) {\n setData_source_tq_veb4m(null)\n return\n }\n let active = true\n apiService.fetchData({ url, method: 'GET' }).then((response) => {\n if (active) setData_source_tq_veb4m(getByPath(response.data, \"\"))\n }).catch((error) => console.error(\"AbpUserRoles · GetList endpoint error:\", error))\n return () => { active = false }\n }, [filterKey_source_tq_veb4m])\n\n React.useEffect(() => {\n let active = true\n apiService.fetchData({ url: \"/api/app/crudendpoint/AbpUsers\", method: 'GET' }).then((response) => {\n if (active) setData_source_gq_0q1dz(getByPath(response.data, \"\"))\n }).catch((error) => console.error(\"AbpUsers · GetList endpoint error:\", error))\n return () => { active = false }\n }, [])\n\n const platformFilters_cmp_mt0288i3_ebpj5 = [{ field: \"Id\", operator: \"=\", kind: \"eq\", value: readSqlField(data_cmp_msyfmegt_jkkn3, \"id\"), required: true }]\n const platformKey_cmp_mt0288i3_ebpj5 = JSON.stringify(platformFilters_cmp_mt0288i3_ebpj5)\n // The views keep the params object in their own state and write back to it, so\n // it must stay the same object until a filter value actually changes.\n const platformParams_cmp_mt0288i3_ebpj5 = React.useMemo(() => buildPlatformFilterParams(platformFilters_cmp_mt0288i3_ebpj5), [platformKey_cmp_mt0288i3_ebpj5])\n\n const handle_cmp_msyfpfia_ifhdq_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"Name\", valueOrEvent?.target?.value ?? valueOrEvent)\n const event = valueOrEvent\n }\n\n const handle_cmp_msyjl707_crcn7_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"NormalizedName\", valueOrEvent?.target?.value ?? valueOrEvent)\n const event = valueOrEvent\n }\n\n const handle_cmp_msyjdodn_u3o74_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"ConcurrencyStamp\", valueOrEvent?.target?.value ?? valueOrEvent)\n const event = valueOrEvent\n }\n\n const handle_cmp_msyje9ys_2c08a_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"IsDefault\", typeof originalEvent === \"object\" && originalEvent?.target ? Boolean(originalEvent.target.checked) : typeof valueOrEvent === \"object\" && valueOrEvent?.target ? Boolean(valueOrEvent.target.checked) : Boolean(valueOrEvent))\n const event = { checked: Boolean(valueOrEvent), originalEvent, target: originalEvent?.target }\n }\n\n const handle_cmp_msyjebu2_27tqi_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"IsStatic\", typeof originalEvent === \"object\" && originalEvent?.target ? Boolean(originalEvent.target.checked) : typeof valueOrEvent === \"object\" && valueOrEvent?.target ? Boolean(valueOrEvent.target.checked) : Boolean(valueOrEvent))\n const event = { checked: Boolean(valueOrEvent), originalEvent, target: originalEvent?.target }\n }\n\n const handle_cmp_msyjmetn_gojol_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"IsPublic\", typeof originalEvent === \"object\" && originalEvent?.target ? Boolean(originalEvent.target.checked) : typeof valueOrEvent === \"object\" && valueOrEvent?.target ? Boolean(valueOrEvent.target.checked) : Boolean(valueOrEvent))\n const event = { checked: Boolean(valueOrEvent), originalEvent, target: originalEvent?.target }\n }\n\n const handle_cmp_msyjo0g8_oathk_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"EntityVersion\", valueOrEvent?.target?.value ?? valueOrEvent)\n const event = valueOrEvent\n }\n\n const handle_cmp_msyjplfq_py8lz_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"ExtraProperties\", valueOrEvent?.target?.value ?? valueOrEvent)\n const event = valueOrEvent\n }\n\n return (\n <>\n {sqlHasKey_cmp_msyfmegt_jkkn3 ? sqlKey_cmp_msyfmegt_jkkn3 + \": \" + readSqlField(data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3) : sqlMode_cmp_msyfmegt_jkkn3 === \"new\" ? \"Yeni kayıt\" : \"\"}}>\n
\n
\n \n \n \n \n {\"Is Default\"}\n \n \n {\"Is Static\"}\n \n \n {\"Is Public\"}\n \n \n \n
\n
\n {sqlRows_cmp_msyfmegt_jkkn3.length > 1 ? (\n <>\n \n {`${sqlIndex_cmp_msyfmegt_jkkn3 + 1} / ${sqlRows_cmp_msyfmegt_jkkn3.length}`}\n \n \n \n ) : null}\n \n \n \n \n {sqlMode_cmp_msyfmegt_jkkn3 === \"new\" ? \"Yeni kayıt\" : \"Düzenleme\"}\n
\n {sqlError_cmp_msyfmegt_jkkn3 ?
{sqlError_cmp_msyfmegt_jkkn3}
: null}\n
\n
\n
\n {(() => {\n const gridItems_cmp_mt023r5u_rb2jv = (Array.isArray(getByPath(data_source_tq_veb4m, \"\")) ? getByPath(data_source_tq_veb4m, \"\") : Array.isArray(data_source_tq_veb4m) ? data_source_tq_veb4m : null)\n // Nothing loaded yet — an endpoint still in flight, or a required filter\n // holding the request back — is an empty grid, not a binding error.\n if (gridItems_cmp_mt023r5u_rb2jv === null || gridItems_cmp_mt023r5u_rb2jv === undefined) return null\n if (!Array.isArray(gridItems_cmp_mt023r5u_rb2jv)) return
Grid items bağlantısı bir koleksiyon döndürmelidir.
\n const gridColumns_cmp_mt023r5u_rb2jv = [\"UserId\"]\n const gridLookups_cmp_mt023r5u_rb2jv = { \"UserId\": { rows: data_source_gq_0q1dz, path: \"\", value: \"Id\", text: \"UserName\" } }\n if (!gridColumns_cmp_mt023r5u_rb2jv.length) return
Preview için en az bir sütun seçin.
\n return (\n
\n \n \n {gridColumns_cmp_mt023r5u_rb2jv.map((column) => )}\n \n \n {gridItems_cmp_mt023r5u_rb2jv.map((gridItem_cmp_mt023r5u_rb2jv, rowIndex) => (\n \n {gridColumns_cmp_mt023r5u_rb2jv.map((column) => {\n const cellValue_cmp_mt023r5u_rb2jv = column === \"value\" ? gridItem_cmp_mt023r5u_rb2jv : getByPath(gridItem_cmp_mt023r5u_rb2jv, column)\n const lookedUp = resolveLookupText(gridLookups_cmp_mt023r5u_rb2jv[column], cellValue_cmp_mt023r5u_rb2jv)\n const cellText = lookedUp === undefined ? cellValue_cmp_mt023r5u_rb2jv : lookedUp\n const text = typeof cellText === \"object\" && cellText !== null && !(cellText instanceof Date) ? JSON.stringify(cellText) : formatLocaleValue(cellText) || \"—\"\n return \n })}\n \n ))}\n \n
{(column === \"value\" ? \"Value\" : column)}
{text}
\n
\n )\n })()}\n
\n {platformParams_cmp_mt0288i3_ebpj5 === null ? null : (\n \n )}\n \n )\n}\n\nexport default RoleComponent", - "Props": "{\"visualDesigner\":{\"version\":1,\"sourceMode\":\"visual\",\"nodes\":[{\"id\":\"cmp_msyfmegt_jkkn3\",\"type\":\"Form\",\"kind\":\"layout\",\"props\":{\"selectEndpoint\":\"source_5w_5ibpv\",\"insertEndpoint\":\"source_h3_484qa\",\"updateEndpoint\":\"source_gb_d9cvs\",\"deleteEndpoint\":\"source_0t_avok9\",\"keyFieldName\":\"id\",\"collectionPath\":\"\",\"keySource\":\"query\",\"keyParamName\":\"id\",\"previewKeyValue\":\"\",\"autoLoad\":true,\"showToolbar\":true,\"gap\":16,\"className\":\"\",\"title\":\"::AbpIdentity.Roles\",\"columnCount\":2},\"events\":{\"onLoad\":\"\",\"onRecordChange\":\"\",\"onFieldChange\":\"\",\"onNewRecord\":\"\",\"onModeChange\":\"\",\"onBeforeSave\":\"\",\"onAfterSave\":\"\",\"onBeforeDelete\":\"\",\"onAfterDelete\":\"\",\"onError\":\"\"},\"bindings\":{},\"children\":[{\"id\":\"cmp_msyfpfia_ifhdq\",\"type\":\"Input\",\"kind\":\"ui\",\"props\":{\"asElement\":\"\",\"disabled\":false,\"invalid\":false,\"rows\":3,\"prefix\":\"Name :\",\"size\":\"md\",\"suffix\":\"\",\"textArea\":false,\"type\":\"text\",\"unstyle\":false,\"field\":\"\",\"form\":\"\",\"className\":\"\",\"children\":\"\",\"style\":{},\"placeholder\":\"Metin girin...\",\"value\":\"\",\"name\":\"\",\"autoComplete\":\"off\",\"autoFocus\":false,\"readOnly\":false,\"required\":false,\"maxLength\":\"\",\"minLength\":\"\",\"pattern\":\"\",\"sqlDefaultValue\":\"\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"value\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"Name\"}},\"children\":[],\"ref\":\"input1\"},{\"id\":\"cmp_msyjl707_crcn7\",\"type\":\"Input\",\"kind\":\"ui\",\"props\":{\"asElement\":\"\",\"disabled\":false,\"invalid\":false,\"rows\":3,\"prefix\":\"Normalized Name :\",\"size\":\"md\",\"suffix\":\"\",\"textArea\":false,\"type\":\"text\",\"unstyle\":false,\"field\":\"\",\"form\":\"\",\"className\":\"\",\"children\":\"\",\"style\":{},\"placeholder\":\"Metin girin...\",\"value\":\"\",\"name\":\"\",\"autoComplete\":\"off\",\"autoFocus\":false,\"readOnly\":false,\"required\":false,\"maxLength\":\"\",\"minLength\":\"\",\"pattern\":\"\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"value\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"NormalizedName\"}},\"children\":[],\"ref\":\"input2\"},{\"id\":\"cmp_msyjdodn_u3o74\",\"type\":\"Input\",\"kind\":\"ui\",\"props\":{\"asElement\":\"\",\"disabled\":false,\"invalid\":false,\"rows\":3,\"prefix\":\"Concurrent Stamp : \",\"size\":\"md\",\"suffix\":\"\",\"textArea\":false,\"type\":\"text\",\"unstyle\":false,\"field\":\"\",\"form\":\"\",\"className\":\"\",\"children\":\"\",\"style\":{},\"placeholder\":\"Metin girin...\",\"value\":\"\",\"name\":\"\",\"autoComplete\":\"off\",\"autoFocus\":false,\"readOnly\":false,\"required\":false,\"maxLength\":\"\",\"minLength\":\"\",\"pattern\":\"\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"value\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"ConcurrencyStamp\"}},\"children\":[],\"ref\":\"input3\"},{\"id\":\"cmp_msyje9ys_2c08a\",\"type\":\"Checkbox\",\"kind\":\"ui\",\"props\":{\"checked\":false,\"color\":\"blue-500\",\"disabled\":false,\"labelRef\":\"\",\"name\":\"\",\"readOnly\":false,\"value\":\"\",\"field\":null,\"className\":\"\",\"children\":\"Is Default\",\"style\":{},\"sqlDefaultValue\":\"true\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"checked\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"IsDefault\"}},\"children\":[],\"ref\":\"checkbox1\"},{\"id\":\"cmp_msyjebu2_27tqi\",\"type\":\"Checkbox\",\"kind\":\"ui\",\"props\":{\"checked\":false,\"color\":\"blue-500\",\"disabled\":false,\"labelRef\":\"\",\"name\":\"\",\"readOnly\":false,\"value\":\"\",\"field\":null,\"className\":\"\",\"children\":\"Is Static\",\"style\":{},\"sqlDefaultValue\":\"true\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"checked\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"IsStatic\"}},\"children\":[],\"ref\":\"checkbox2\"},{\"id\":\"cmp_msyjmetn_gojol\",\"type\":\"Checkbox\",\"kind\":\"ui\",\"props\":{\"checked\":false,\"color\":\"blue-500\",\"disabled\":false,\"labelRef\":\"\",\"name\":\"\",\"readOnly\":false,\"value\":\"\",\"field\":null,\"className\":\"\",\"children\":\"Is Public\",\"style\":{},\"sqlDefaultValue\":\"false\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"checked\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"IsPublic\"}},\"children\":[],\"ref\":\"checkbox3\"},{\"id\":\"cmp_msyjo0g8_oathk\",\"type\":\"Input\",\"kind\":\"ui\",\"props\":{\"asElement\":\"\",\"disabled\":false,\"invalid\":false,\"rows\":3,\"prefix\":\"Entity Version : \",\"size\":\"md\",\"suffix\":\"\",\"textArea\":false,\"type\":\"number\",\"unstyle\":false,\"field\":\"\",\"form\":\"\",\"className\":\"\",\"children\":\"\",\"style\":{},\"placeholder\":\"Metin girin...\",\"value\":\"\",\"name\":\"\",\"autoComplete\":\"off\",\"autoFocus\":false,\"readOnly\":false,\"required\":false,\"maxLength\":\"\",\"minLength\":\"\",\"pattern\":\"\",\"sqlDefaultValue\":\"3\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"value\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"EntityVersion\"}},\"children\":[],\"ref\":\"input4\"},{\"id\":\"cmp_msyjplfq_py8lz\",\"type\":\"Input\",\"kind\":\"ui\",\"props\":{\"asElement\":\"\",\"disabled\":false,\"invalid\":false,\"rows\":3,\"prefix\":\"Extra Properties :\",\"size\":\"md\",\"suffix\":\"\",\"textArea\":false,\"type\":\"text\",\"unstyle\":false,\"field\":\"\",\"form\":\"\",\"className\":\"\",\"children\":\"\",\"style\":{},\"placeholder\":\"Metin girin...\",\"value\":\"\",\"name\":\"\",\"autoComplete\":\"off\",\"autoFocus\":false,\"readOnly\":false,\"required\":false,\"maxLength\":\"\",\"minLength\":\"\",\"pattern\":\"\",\"sqlDefaultValue\":\"{}\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"value\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"ExtraProperties\"}},\"children\":[],\"ref\":\"input5\"}],\"ref\":\"form1\"},{\"id\":\"cmp_mt023r5u_rb2jv\",\"type\":\"Grid\",\"kind\":\"ui\",\"props\":{\"hoverable\":true,\"overflow\":true,\"borderlessRow\":false,\"compact\":false,\"cols\":\"3\",\"rows\":\"2\",\"gap\":\"4\",\"colGap\":\"4\",\"rowGap\":\"4\",\"responsive\":true,\"smCols\":\"1\",\"mdCols\":\"2\",\"lgCols\":\"3\",\"xlCols\":\"4\",\"autoFit\":false,\"minColWidth\":\"200px\",\"height\":\"auto\",\"className\":\"mt-4\",\"items\":[{\"id\":\"grid-item-1\",\"content\":\"Grid Item 1\",\"colSpan\":1,\"rowSpan\":1,\"colStart\":1,\"rowStart\":1,\"className\":\"bg-blue-100 p-4 border rounded\"},{\"id\":\"grid-item-2\",\"content\":\"Grid Item 2\",\"colSpan\":1,\"rowSpan\":1,\"colStart\":2,\"rowStart\":1,\"className\":\"bg-green-100 p-4 border rounded\"},{\"id\":\"grid-item-3\",\"content\":\"Grid Item 3\",\"colSpan\":1,\"rowSpan\":1,\"colStart\":3,\"rowStart\":1,\"className\":\"bg-yellow-100 p-4 border rounded\"},{\"id\":\"grid-item-4\",\"content\":\"Grid Item 4\",\"colSpan\":2,\"rowSpan\":1,\"colStart\":1,\"rowStart\":2,\"className\":\"bg-purple-100 p-4 border rounded\"},{\"id\":\"grid-item-5\",\"content\":\"Grid Item 5\",\"colSpan\":1,\"rowSpan\":1,\"colStart\":3,\"rowStart\":2,\"className\":\"bg-red-100 p-4 border rounded\"}],\"dataColumns\":[\"UserId\"],\"columnLookups\":{\"UserId\":{\"sourceId\":\"source_gq_0q1dz\",\"path\":\"\",\"valueField\":\"Id\",\"textField\":\"UserName\"}},\"columnCaptions\":{}},\"events\":{},\"bindings\":{\"items\":{\"sourceId\":\"source_tq_veb4m\",\"path\":\"\"}},\"children\":[],\"ref\":\"grid1\"},{\"id\":\"cmp_mt0288i3_ebpj5\",\"type\":\"ListView\",\"kind\":\"platform\",\"props\":{\"listFormCode\":\"AbpIdentity.Roles\",\"height\":\"520px\",\"filters\":[{\"id\":\"filter_y9_tr73w\",\"field\":\"Id\",\"operator\":\"eq\",\"source\":\"record\",\"value\":\"form1.id\",\"previewValue\":\"\",\"required\":true}],\"className\":\"mt-4\"},\"events\":{},\"bindings\":{},\"children\":[],\"ref\":\"listView1\"}],\"canvas\":{\"width\":\"responsive\"},\"lifecycle\":{\"onMount\":\"\"},\"dataSources\":[{\"id\":\"source_5w_5ibpv\",\"name\":\"AbpRoles · GetList\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpRoles\",\"responsePath\":\"\"},{\"id\":\"source_i7_msox4\",\"name\":\"AbpRoles · GetById\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpRoles/{id}\",\"responsePath\":\"\"},{\"id\":\"source_0t_avok9\",\"name\":\"AbpRoles · Delete\",\"method\":\"DELETE\",\"url\":\"/api/app/crudendpoint/AbpRoles/{id}\",\"responsePath\":\"\"},{\"id\":\"source_h3_484qa\",\"name\":\"AbpRoles · Create\",\"method\":\"POST\",\"url\":\"/api/app/crudendpoint/AbpRoles\",\"responsePath\":\"\"},{\"id\":\"source_gb_d9cvs\",\"name\":\"AbpRoles · Update\",\"method\":\"PUT\",\"url\":\"/api/app/crudendpoint/AbpRoles/{id}\",\"responsePath\":\"\"},{\"id\":\"source_tq_veb4m\",\"name\":\"AbpUserRoles · GetList\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpUserRoles\",\"responsePath\":\"\",\"filters\":[{\"id\":\"filter_sw_86e0q\",\"field\":\"RoleId\",\"operator\":\"eq\",\"source\":\"record\",\"value\":\"form1.id\",\"previewValue\":\"\",\"required\":true}]},{\"id\":\"source_d2_bri5s\",\"name\":\"AbpUserRoles · GetById\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpUserRoles/{id}\",\"responsePath\":\"\"},{\"id\":\"source_gq_0q1dz\",\"name\":\"AbpUsers · GetList\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpUsers\",\"responsePath\":\"\"},{\"id\":\"source_u8_nu1lu\",\"name\":\"AbpUsers · GetById\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpUsers/{id}\",\"responsePath\":\"\"}]}}", + "Code": "/*__SOZSOFT_VISUAL_DESIGNER__%7B%22version%22%3A1%2C%22sourceMode%22%3A%22visual%22%2C%22nodes%22%3A%5B%7B%22id%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22type%22%3A%22Form%22%2C%22kind%22%3A%22layout%22%2C%22props%22%3A%7B%22selectEndpoint%22%3A%22source_5w_5ibpv%22%2C%22insertEndpoint%22%3A%22source_h3_484qa%22%2C%22updateEndpoint%22%3A%22source_gb_d9cvs%22%2C%22deleteEndpoint%22%3A%22source_0t_avok9%22%2C%22keyFieldName%22%3A%22id%22%2C%22collectionPath%22%3A%22%22%2C%22keySource%22%3A%22query%22%2C%22keyParamName%22%3A%22id%22%2C%22previewKeyValue%22%3A%22%22%2C%22autoLoad%22%3Atrue%2C%22showToolbar%22%3Atrue%2C%22gap%22%3A16%2C%22className%22%3A%22%22%2C%22title%22%3A%22%3A%3AAbpIdentity.Roles%22%2C%22columnCount%22%3A2%2C%22selectPermission%22%3A%22%22%2C%22insertPermission%22%3A%22%22%7D%2C%22events%22%3A%7B%22onLoad%22%3A%22%22%2C%22onRecordChange%22%3A%22%22%2C%22onFieldChange%22%3A%22%22%2C%22onNewRecord%22%3A%22%22%2C%22onModeChange%22%3A%22%22%2C%22onBeforeSave%22%3A%22%22%2C%22onAfterSave%22%3A%22%22%2C%22onBeforeDelete%22%3A%22%22%2C%22onAfterDelete%22%3A%22%22%2C%22onError%22%3A%22%22%7D%2C%22bindings%22%3A%7B%7D%2C%22children%22%3A%5B%7B%22id%22%3A%22cmp_msyfpfia_ifhdq%22%2C%22type%22%3A%22Input%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22asElement%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22invalid%22%3Afalse%2C%22rows%22%3A3%2C%22prefix%22%3A%22Name%20%3A%22%2C%22size%22%3A%22md%22%2C%22suffix%22%3A%22%22%2C%22textArea%22%3Afalse%2C%22type%22%3A%22text%22%2C%22unstyle%22%3Afalse%2C%22field%22%3A%22%22%2C%22form%22%3A%22%22%2C%22className%22%3A%22%22%2C%22children%22%3A%22%22%2C%22style%22%3A%7B%7D%2C%22placeholder%22%3A%22Metin%20girin...%22%2C%22value%22%3A%22%22%2C%22name%22%3A%22%22%2C%22autoComplete%22%3A%22off%22%2C%22autoFocus%22%3Afalse%2C%22readOnly%22%3Afalse%2C%22required%22%3Afalse%2C%22maxLength%22%3A%22%22%2C%22minLength%22%3A%22%22%2C%22pattern%22%3A%22%22%2C%22sqlDefaultValue%22%3A%22%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22value%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22Name%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22input1%22%7D%2C%7B%22id%22%3A%22cmp_msyjl707_crcn7%22%2C%22type%22%3A%22Input%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22asElement%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22invalid%22%3Afalse%2C%22rows%22%3A3%2C%22prefix%22%3A%22Normalized%20Name%20%3A%22%2C%22size%22%3A%22md%22%2C%22suffix%22%3A%22%22%2C%22textArea%22%3Afalse%2C%22type%22%3A%22text%22%2C%22unstyle%22%3Afalse%2C%22field%22%3A%22%22%2C%22form%22%3A%22%22%2C%22className%22%3A%22%22%2C%22children%22%3A%22%22%2C%22style%22%3A%7B%7D%2C%22placeholder%22%3A%22Metin%20girin...%22%2C%22value%22%3A%22%22%2C%22name%22%3A%22%22%2C%22autoComplete%22%3A%22off%22%2C%22autoFocus%22%3Afalse%2C%22readOnly%22%3Afalse%2C%22required%22%3Afalse%2C%22maxLength%22%3A%22%22%2C%22minLength%22%3A%22%22%2C%22pattern%22%3A%22%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22value%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22NormalizedName%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22input2%22%7D%2C%7B%22id%22%3A%22cmp_msyjdodn_u3o74%22%2C%22type%22%3A%22Input%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22asElement%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22invalid%22%3Afalse%2C%22rows%22%3A3%2C%22prefix%22%3A%22Concurrent%20Stamp%20%3A%20%22%2C%22size%22%3A%22md%22%2C%22suffix%22%3A%22%22%2C%22textArea%22%3Afalse%2C%22type%22%3A%22text%22%2C%22unstyle%22%3Afalse%2C%22field%22%3A%22%22%2C%22form%22%3A%22%22%2C%22className%22%3A%22%22%2C%22children%22%3A%22%22%2C%22style%22%3A%7B%7D%2C%22placeholder%22%3A%22Metin%20girin...%22%2C%22value%22%3A%22%22%2C%22name%22%3A%22%22%2C%22autoComplete%22%3A%22off%22%2C%22autoFocus%22%3Afalse%2C%22readOnly%22%3Afalse%2C%22required%22%3Afalse%2C%22maxLength%22%3A%22%22%2C%22minLength%22%3A%22%22%2C%22pattern%22%3A%22%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22value%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22ConcurrencyStamp%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22input3%22%7D%2C%7B%22id%22%3A%22cmp_msyje9ys_2c08a%22%2C%22type%22%3A%22Checkbox%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22checked%22%3Afalse%2C%22color%22%3A%22blue-500%22%2C%22disabled%22%3Afalse%2C%22labelRef%22%3A%22%22%2C%22name%22%3A%22%22%2C%22readOnly%22%3Afalse%2C%22value%22%3A%22%22%2C%22field%22%3Anull%2C%22className%22%3A%22%22%2C%22children%22%3A%22Is%20Default%22%2C%22style%22%3A%7B%7D%2C%22sqlDefaultValue%22%3A%22true%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22checked%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22IsDefault%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22checkbox1%22%7D%2C%7B%22id%22%3A%22cmp_msyjebu2_27tqi%22%2C%22type%22%3A%22Checkbox%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22checked%22%3Afalse%2C%22color%22%3A%22blue-500%22%2C%22disabled%22%3Afalse%2C%22labelRef%22%3A%22%22%2C%22name%22%3A%22%22%2C%22readOnly%22%3Afalse%2C%22value%22%3A%22%22%2C%22field%22%3Anull%2C%22className%22%3A%22%22%2C%22children%22%3A%22Is%20Static%22%2C%22style%22%3A%7B%7D%2C%22sqlDefaultValue%22%3A%22true%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22checked%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22IsStatic%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22checkbox2%22%7D%2C%7B%22id%22%3A%22cmp_msyjmetn_gojol%22%2C%22type%22%3A%22Checkbox%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22checked%22%3Afalse%2C%22color%22%3A%22blue-500%22%2C%22disabled%22%3Afalse%2C%22labelRef%22%3A%22%22%2C%22name%22%3A%22%22%2C%22readOnly%22%3Afalse%2C%22value%22%3A%22%22%2C%22field%22%3Anull%2C%22className%22%3A%22%22%2C%22children%22%3A%22Is%20Public%22%2C%22style%22%3A%7B%7D%2C%22sqlDefaultValue%22%3A%22false%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22checked%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22IsPublic%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22checkbox3%22%7D%2C%7B%22id%22%3A%22cmp_msyjo0g8_oathk%22%2C%22type%22%3A%22Input%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22asElement%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22invalid%22%3Afalse%2C%22rows%22%3A3%2C%22prefix%22%3A%22Entity%20Version%20%3A%20%22%2C%22size%22%3A%22md%22%2C%22suffix%22%3A%22%22%2C%22textArea%22%3Afalse%2C%22type%22%3A%22number%22%2C%22unstyle%22%3Afalse%2C%22field%22%3A%22%22%2C%22form%22%3A%22%22%2C%22className%22%3A%22%22%2C%22children%22%3A%22%22%2C%22style%22%3A%7B%7D%2C%22placeholder%22%3A%22Metin%20girin...%22%2C%22value%22%3A%22%22%2C%22name%22%3A%22%22%2C%22autoComplete%22%3A%22off%22%2C%22autoFocus%22%3Afalse%2C%22readOnly%22%3Afalse%2C%22required%22%3Afalse%2C%22maxLength%22%3A%22%22%2C%22minLength%22%3A%22%22%2C%22pattern%22%3A%22%22%2C%22sqlDefaultValue%22%3A%223%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22value%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22EntityVersion%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22input4%22%7D%2C%7B%22id%22%3A%22cmp_msyjplfq_py8lz%22%2C%22type%22%3A%22Input%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22asElement%22%3A%22%22%2C%22disabled%22%3Afalse%2C%22invalid%22%3Afalse%2C%22rows%22%3A3%2C%22prefix%22%3A%22Extra%20Properties%20%3A%22%2C%22size%22%3A%22md%22%2C%22suffix%22%3A%22%22%2C%22textArea%22%3Afalse%2C%22type%22%3A%22text%22%2C%22unstyle%22%3Afalse%2C%22field%22%3A%22%22%2C%22form%22%3A%22%22%2C%22className%22%3A%22%22%2C%22children%22%3A%22%22%2C%22style%22%3A%7B%7D%2C%22placeholder%22%3A%22Metin%20girin...%22%2C%22value%22%3A%22%22%2C%22name%22%3A%22%22%2C%22autoComplete%22%3A%22off%22%2C%22autoFocus%22%3Afalse%2C%22readOnly%22%3Afalse%2C%22required%22%3Afalse%2C%22maxLength%22%3A%22%22%2C%22minLength%22%3A%22%22%2C%22pattern%22%3A%22%22%2C%22sqlDefaultValue%22%3A%22%7B%7D%22%7D%2C%22events%22%3A%7B%22onChange%22%3A%22%22%7D%2C%22bindings%22%3A%7B%22value%22%3A%7B%22sourceId%22%3A%22cmp_msyfmegt_jkkn3%22%2C%22path%22%3A%22ExtraProperties%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22input5%22%7D%5D%2C%22ref%22%3A%22form1%22%7D%2C%7B%22id%22%3A%22cmp_mt023r5u_rb2jv%22%2C%22type%22%3A%22Grid%22%2C%22kind%22%3A%22ui%22%2C%22props%22%3A%7B%22hoverable%22%3Atrue%2C%22overflow%22%3Atrue%2C%22borderlessRow%22%3Afalse%2C%22compact%22%3Afalse%2C%22cols%22%3A%223%22%2C%22rows%22%3A%222%22%2C%22gap%22%3A%224%22%2C%22colGap%22%3A%224%22%2C%22rowGap%22%3A%224%22%2C%22responsive%22%3Atrue%2C%22smCols%22%3A%221%22%2C%22mdCols%22%3A%222%22%2C%22lgCols%22%3A%223%22%2C%22xlCols%22%3A%224%22%2C%22autoFit%22%3Afalse%2C%22minColWidth%22%3A%22200px%22%2C%22height%22%3A%22auto%22%2C%22className%22%3A%22mt-4%22%2C%22items%22%3A%5B%7B%22id%22%3A%22grid-item-1%22%2C%22content%22%3A%22Grid%20Item%201%22%2C%22colSpan%22%3A1%2C%22rowSpan%22%3A1%2C%22colStart%22%3A1%2C%22rowStart%22%3A1%2C%22className%22%3A%22bg-blue-100%20p-4%20border%20rounded%22%7D%2C%7B%22id%22%3A%22grid-item-2%22%2C%22content%22%3A%22Grid%20Item%202%22%2C%22colSpan%22%3A1%2C%22rowSpan%22%3A1%2C%22colStart%22%3A2%2C%22rowStart%22%3A1%2C%22className%22%3A%22bg-green-100%20p-4%20border%20rounded%22%7D%2C%7B%22id%22%3A%22grid-item-3%22%2C%22content%22%3A%22Grid%20Item%203%22%2C%22colSpan%22%3A1%2C%22rowSpan%22%3A1%2C%22colStart%22%3A3%2C%22rowStart%22%3A1%2C%22className%22%3A%22bg-yellow-100%20p-4%20border%20rounded%22%7D%2C%7B%22id%22%3A%22grid-item-4%22%2C%22content%22%3A%22Grid%20Item%204%22%2C%22colSpan%22%3A2%2C%22rowSpan%22%3A1%2C%22colStart%22%3A1%2C%22rowStart%22%3A2%2C%22className%22%3A%22bg-purple-100%20p-4%20border%20rounded%22%7D%2C%7B%22id%22%3A%22grid-item-5%22%2C%22content%22%3A%22Grid%20Item%205%22%2C%22colSpan%22%3A1%2C%22rowSpan%22%3A1%2C%22colStart%22%3A3%2C%22rowStart%22%3A2%2C%22className%22%3A%22bg-red-100%20p-4%20border%20rounded%22%7D%5D%2C%22dataColumns%22%3A%5B%22UserId%22%5D%2C%22columnLookups%22%3A%7B%22UserId%22%3A%7B%22sourceId%22%3A%22source_gq_0q1dz%22%2C%22path%22%3A%22%22%2C%22valueField%22%3A%22Id%22%2C%22textField%22%3A%22UserName%22%7D%7D%2C%22columnCaptions%22%3A%7B%7D%7D%2C%22events%22%3A%7B%7D%2C%22bindings%22%3A%7B%22items%22%3A%7B%22sourceId%22%3A%22source_tq_veb4m%22%2C%22path%22%3A%22%22%7D%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22grid1%22%7D%2C%7B%22id%22%3A%22cmp_mt0288i3_ebpj5%22%2C%22type%22%3A%22ListView%22%2C%22kind%22%3A%22platform%22%2C%22props%22%3A%7B%22listFormCode%22%3A%22AbpIdentity.Roles%22%2C%22height%22%3A%22520px%22%2C%22filters%22%3A%5B%7B%22id%22%3A%22filter_y9_tr73w%22%2C%22field%22%3A%22Id%22%2C%22operator%22%3A%22eq%22%2C%22source%22%3A%22record%22%2C%22value%22%3A%22form1.Id%22%2C%22previewValue%22%3A%22%22%2C%22required%22%3Atrue%7D%5D%2C%22className%22%3A%22mt-4%22%7D%2C%22events%22%3A%7B%7D%2C%22bindings%22%3A%7B%7D%2C%22children%22%3A%5B%5D%2C%22ref%22%3A%22listView1%22%7D%5D%2C%22canvas%22%3A%7B%22width%22%3A%22responsive%22%7D%2C%22lifecycle%22%3A%7B%22onMount%22%3A%22%22%7D%2C%22dataSources%22%3A%5B%7B%22id%22%3A%22source_5w_5ibpv%22%2C%22name%22%3A%22AbpRoles%20%C2%B7%20GetList%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpRoles%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_i7_msox4%22%2C%22name%22%3A%22AbpRoles%20%C2%B7%20GetById%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpRoles%2F%7Bid%7D%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_0t_avok9%22%2C%22name%22%3A%22AbpRoles%20%C2%B7%20Delete%22%2C%22method%22%3A%22DELETE%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpRoles%2F%7Bid%7D%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_h3_484qa%22%2C%22name%22%3A%22AbpRoles%20%C2%B7%20Create%22%2C%22method%22%3A%22POST%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpRoles%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_gb_d9cvs%22%2C%22name%22%3A%22AbpRoles%20%C2%B7%20Update%22%2C%22method%22%3A%22PUT%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpRoles%2F%7Bid%7D%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_tq_veb4m%22%2C%22name%22%3A%22AbpUserRoles%20%C2%B7%20GetList%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpUserRoles%22%2C%22responsePath%22%3A%22%22%2C%22filters%22%3A%5B%7B%22id%22%3A%22filter_sw_86e0q%22%2C%22field%22%3A%22RoleId%22%2C%22operator%22%3A%22eq%22%2C%22source%22%3A%22record%22%2C%22value%22%3A%22form1.id%22%2C%22previewValue%22%3A%22%22%2C%22required%22%3Atrue%7D%5D%7D%2C%7B%22id%22%3A%22source_d2_bri5s%22%2C%22name%22%3A%22AbpUserRoles%20%C2%B7%20GetById%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpUserRoles%2F%7Bid%7D%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_gq_0q1dz%22%2C%22name%22%3A%22AbpUsers%20%C2%B7%20GetList%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpUsers%22%2C%22responsePath%22%3A%22%22%7D%2C%7B%22id%22%3A%22source_u8_nu1lu%22%2C%22name%22%3A%22AbpUsers%20%C2%B7%20GetById%22%2C%22method%22%3A%22GET%22%2C%22url%22%3A%22%2Fapi%2Fapp%2Fcrudendpoint%2FAbpUsers%2F%7Bid%7D%22%2C%22responsePath%22%3A%22%22%7D%5D%2C%22permissionCode%22%3A%22App.Wizard.RoleList%22%7D__*/\nconst RoleComponent = () => {\n const designerHasPermission = (permission) => {\n const name = String(permission || \"\").trim()\n if (!name) return true\n return typeof checkPermission === \"function\" ? checkPermission(name) === true : true\n }\n\n const getByPath = (value, path) => {\n if (!path) return value\n const readPath = (target, targetPath) => targetPath.split('.').filter(Boolean).reduce((current, key) => current?.[key], target)\n const directValue = readPath(value, path)\n if (directValue !== undefined) return directValue\n if (path.startsWith('result.')) return readPath(value, path.slice('result.'.length))\n return value?.result === undefined ? undefined : readPath(value.result, path)\n }\n\n const resolveLookupText = (lookup, value) => {\n if (!lookup || value === null || value === undefined || value === \"\") return undefined\n const rows = getByPath(lookup.rows, lookup.path)\n if (!Array.isArray(rows)) return undefined\n const key = String(value)\n const row = rows.find((item) => item && typeof item === \"object\" && String(getByPath(item, lookup.value) ?? \"\") === key)\n if (!row) return undefined\n const text = getByPath(row, lookup.text)\n return text === null || text === undefined || text === \"\" ? undefined : text\n }\n\n const localeCulture = () => (typeof document !== \"undefined\" && document.documentElement.lang) || \"en\"\n const localeDateOptions = { year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }\n const localeTimeOptions = { year: \"numeric\", month: \"2-digit\", day: \"2-digit\", hour: \"2-digit\", minute: \"2-digit\" }\n const localeIsoPattern = /^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ](\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?)?$/\n const formatLocaleValue = (value) => {\n if (value === null || value === undefined) return \"\"\n const culture = localeCulture()\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) return String(value)\n return new Intl.NumberFormat(culture, { useGrouping: false, maximumFractionDigits: 20 }).format(value)\n }\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? \"\" : new Intl.DateTimeFormat(culture, localeTimeOptions).format(value)\n }\n if (typeof value !== \"string\") return String(value)\n const parts = localeIsoPattern.exec(value.trim())\n if (!parts) return value\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return value\n if (parts[4] === undefined) {\n const day = new Date(Number(parts[1]), Number(parts[2]) - 1, Number(parts[3]))\n return new Intl.DateTimeFormat(culture, localeDateOptions).format(day)\n }\n return new Intl.DateTimeFormat(culture, localeTimeOptions).format(parsed)\n }\n\n const readSqlField = (record, field) => {\n if (!record || typeof record !== \"object\" || Array.isArray(record)) return undefined\n if (field in record) return record[field]\n const matched = Object.keys(record).find((key) => key.toLowerCase() === String(field).toLowerCase())\n return matched === undefined ? undefined : record[matched]\n }\n const toSqlRows = (value, collectionPath) => {\n const source = collectionPath ? getByPath(value, collectionPath) : value\n const rows = Array.isArray(source)\n ? source\n : source && typeof source === \"object\"\n ? (Array.isArray(source.items) ? source.items : [source])\n : []\n return rows.filter((item) => item && typeof item === \"object\" && !Array.isArray(item))\n }\n const setSqlField = (record, path, value) => {\n const keys = String(path || \"\").split(\".\").filter(Boolean)\n if (!keys.length) return record\n const next = { ...(record || {}) }\n let target = next\n for (let index = 0; index < keys.length - 1; index += 1) {\n const child = target[keys[index]]\n target[keys[index]] = child && typeof child === \"object\" && !Array.isArray(child) ? { ...child } : {}\n target = target[keys[index]]\n }\n target[keys[keys.length - 1]] = value\n return next\n }\n const bindSqlUrl = (url, record, keyField) => {\n let keyBound = false\n const boundUrl = String(url).replace(/\\{([^}]+)\\}|(?<=\\/):([A-Za-z_][A-Za-z0-9_]*)/g, (match, braced, colon) => {\n const name = (braced || colon || \"\").trim()\n if (!name) return match\n const value = readSqlField(record, name) ?? readSqlField(record, keyField)\n if (value === undefined || value === null || value === \"\") return match\n if (name.toLowerCase() === String(keyField).toLowerCase()) keyBound = true\n return encodeURIComponent(String(value))\n })\n return { url: boundUrl, keyBound }\n }\n const appendQueryParam = (url, name, value) =>\n url + (url.includes(\"?\") ? \"&\" : \"?\") + encodeURIComponent(name) + \"=\" + encodeURIComponent(value)\n const hasSqlUrlParams = (url) => /\\{[^}]+\\}|(?<=\\/):[A-Za-z_][A-Za-z0-9_]*/.test(String(url))\n const readUrlKey = (source, name) => {\n if (typeof window === \"undefined\") return \"\"\n if (source === \"query\") return new URLSearchParams(window.location.search).get(name) ?? \"\"\n const segments = window.location.pathname.split(\"/\").filter(Boolean)\n return segments.length ? decodeURIComponent(segments[segments.length - 1]) : \"\"\n }\n const filterSqlRowsByKey = (rows, keyField, keyValue) => {\n if (!keyValue) return rows\n const matched = rows.filter((row) => String(readSqlField(row, keyField) ?? \"\") === String(keyValue))\n // No match means the endpoint already filtered server side, or the key column\n // is named differently — keeping the rows beats showing an empty form.\n return matched.length ? matched : rows\n }\n // Update payload: only the columns the user actually edited, plus the key.\n const toSqlChanges = (original, current, keyField) => {\n const changes = {}\n const isSame = (left, right) => JSON.stringify(left ?? null) === JSON.stringify(right ?? null)\n Object.keys(current || {}).forEach((column) => {\n if (!isSame(original ? original[column] : undefined, current[column])) changes[column] = current[column]\n })\n const keyColumn = Object.keys(current || {}).find((column) => column.toLowerCase() === String(keyField).toLowerCase())\n if (keyColumn !== undefined && changes[keyColumn] === undefined) changes[keyColumn] = current[keyColumn]\n return changes\n }\n const callSqlEndpoint = (target, record, keyField, payload) => {\n // The URL is always bound from the full record: a placeholder may reference a\n // column that the update payload does not carry.\n const bound = bindSqlUrl(target.url, record, keyField)\n const request = { url: bound.url, method: target.method }\n if (target.method === \"POST\" || target.method === \"PUT\") request.data = payload === undefined ? record : payload\n const keyValue = readSqlField(record, keyField)\n const hasKey = keyValue !== undefined && keyValue !== null && keyValue !== \"\"\n // The key still has to reach the endpoint when the URL carries no placeholder.\n if (!bound.keyBound && hasKey && target.method !== \"POST\") request.params = { [keyField]: keyValue }\n return apiService.fetchData(request)\n }\n // A new record starts at the top of the form: the first enabled editor takes the\n // cursor, so a Yeni click is immediately followed by typing.\n const focusFirstSqlField = (host) => {\n if (typeof window === \"undefined\") return\n window.requestAnimationFrame(() => {\n const field = host.current?.querySelector(\"input:not([type='hidden']):not([disabled]):not([readonly]), textarea:not([disabled]):not([readonly]), select:not([disabled])\")\n if (!field) return\n field.focus()\n if (typeof field.select === \"function\") field.select()\n })\n }\n // Defaults declared on the bound children; only a new record starts from them.\n const buildSqlDefaults = (fields) => fields.reduce((record, field) => setSqlField(record, field.path, resolveSqlDefault(field)), {})\n const resolveSqlDefault = (field) => {\n if (field.token !== \"today\" && field.token !== \"now\") return field.value\n const now = new Date()\n const pad = (value) => String(value).padStart(2, \"0\")\n const day = now.getFullYear() + \"-\" + pad(now.getMonth() + 1) + \"-\" + pad(now.getDate())\n if (field.token === \"today\" || field.dateOnly) return day\n return day + \"T\" + pad(now.getHours()) + \":\" + pad(now.getMinutes()) + \":\" + pad(now.getSeconds())\n }\n const toSqlErrorMessage = (error) => error?.response?.data?.error?.message || error?.response?.data?.message || error?.message || translate(\"::App.Platform.IslemBasarisiz\")\n\n // Value of a filter that reads the page URL: a query string parameter, or a\n // path segment — the one after a segment named like the parameter, otherwise\n // the last one, which is the /orders/{id} detail page shape.\n const readDesignerUrlValue = (kind, name) => {\n if (typeof window === \"undefined\") return \"\"\n if (kind === \"query\") return new URLSearchParams(window.location.search).get(name) ?? \"\"\n const segments = window.location.pathname.split(\"/\").filter(Boolean)\n if (!segments.length) return \"\"\n const index = name ? segments.findIndex((segment) => segment.toLowerCase() === String(name).toLowerCase()) : -1\n if (index >= 0 && index < segments.length - 1) return decodeURIComponent(segments[index + 1])\n return decodeURIComponent(segments[segments.length - 1])\n }\n // Filters are appended as query parameters: `Column` for an equality and\n // `Column.operator` for anything else, which is what the endpoint parses.\n // Returns \"\" when a required filter has no value yet — the caller must skip the\n // request instead of loading the unfiltered collection.\n const buildFilteredUrl = (url, filters) => {\n const parts = []\n for (const filter of filters || []) {\n const value = filter.value === undefined || filter.value === null ? \"\" : String(filter.value)\n if (!value) {\n if (filter.required) return \"\"\n continue\n }\n parts.push(encodeURIComponent(filter.param) + \"=\" + encodeURIComponent(value))\n }\n if (!parts.length) return url\n return url + (url.includes(\"?\") ? \"&\" : \"?\") + parts.join(\"&\")\n }\n\n const buildPlatformFilterParams = (filters) => {\n const expressions = []\n for (const filter of filters || []) {\n if (filter.kind === \"isnull\" || filter.kind === \"notnull\") {\n expressions.push([filter.field, filter.operator, null])\n continue\n }\n const value = filter.value === undefined || filter.value === null ? \"\" : String(filter.value)\n if (!value) {\n if (filter.required) return null\n continue\n }\n if (filter.kind === \"in\") {\n const entries = value.split(\",\").map((entry) => entry.trim()).filter(Boolean).map((entry) => [filter.field, \"=\", entry])\n if (!entries.length) continue\n expressions.push(entries.reduce((result, entry) => (result ? [result, \"or\", entry] : entry), null))\n continue\n }\n expressions.push([filter.field, filter.operator, value])\n }\n const params = new URLSearchParams()\n const merged = expressions.reduce((result, expression) => (result ? [result, \"and\", expression] : expression), null)\n if (merged) params.set(\"filter\", JSON.stringify(merged))\n return params\n }\n\n const [data_source_5w_5ibpv, setData_source_5w_5ibpv] = React.useState(null)\n const [data_source_i7_msox4, setData_source_i7_msox4] = React.useState(null)\n const [data_source_0t_avok9] = React.useState(null)\n const [data_source_h3_484qa] = React.useState(null)\n const [data_source_gb_d9cvs] = React.useState(null)\n const [data_source_tq_veb4m, setData_source_tq_veb4m] = React.useState(null)\n const [data_source_d2_bri5s, setData_source_d2_bri5s] = React.useState(null)\n const [data_source_gq_0q1dz, setData_source_gq_0q1dz] = React.useState(null)\n const [data_source_u8_nu1lu, setData_source_u8_nu1lu] = React.useState(null)\n\n const sqlselect_cmp_msyfmegt_jkkn3 = { url: \"/api/app/crudendpoint/AbpRoles\", method: \"GET\", responsePath: \"\" }\n const sqlinsert_cmp_msyfmegt_jkkn3 = { url: \"/api/app/crudendpoint/AbpRoles\", method: \"POST\", responsePath: \"\" }\n const sqlupdate_cmp_msyfmegt_jkkn3 = { url: \"/api/app/crudendpoint/AbpRoles/{id}\", method: \"PUT\", responsePath: \"\" }\n const sqldelete_cmp_msyfmegt_jkkn3 = { url: \"/api/app/crudendpoint/AbpRoles/{id}\", method: \"DELETE\", responsePath: \"\" }\n const sqlCanSelect_cmp_msyfmegt_jkkn3 = designerHasPermission(\"App.Wizard.RoleList\")\n const sqlCanInsert_cmp_msyfmegt_jkkn3 = designerHasPermission(\"App.Wizard.RoleList.Create\")\n const sqlCanUpdate_cmp_msyfmegt_jkkn3 = designerHasPermission(\"App.Wizard.RoleList.Update\")\n const sqlCanDelete_cmp_msyfmegt_jkkn3 = designerHasPermission(\"App.Wizard.RoleList.Delete\")\n const sqlKey_cmp_msyfmegt_jkkn3 = \"id\"\n const sqlKeyParam_cmp_msyfmegt_jkkn3 = \"id\"\n const [data_cmp_msyfmegt_jkkn3, setData_cmp_msyfmegt_jkkn3] = React.useState({})\n const [sqlOriginal_cmp_msyfmegt_jkkn3, setSqlOriginal_cmp_msyfmegt_jkkn3] = React.useState({})\n const [sqlMode_cmp_msyfmegt_jkkn3, setSqlMode_cmp_msyfmegt_jkkn3] = React.useState(\"edit\")\n const [sqlIndex_cmp_msyfmegt_jkkn3, setSqlIndex_cmp_msyfmegt_jkkn3] = React.useState(0)\n const [sqlBusy_cmp_msyfmegt_jkkn3, setSqlBusy_cmp_msyfmegt_jkkn3] = React.useState(false)\n const [sqlError_cmp_msyfmegt_jkkn3, setSqlError_cmp_msyfmegt_jkkn3] = React.useState(\"\")\n // Container element, so New can hand the cursor to the first field of the form.\n const sqlHost_cmp_msyfmegt_jkkn3 = React.useRef(null)\n // The Select key can come from the page URL, which is how a detail page reads\n // /api/app/orders/{id} or /api/app/orders?id=… for a single record.\n const sqlUrlKey_cmp_msyfmegt_jkkn3 = readUrlKey(\"query\", sqlKeyParam_cmp_msyfmegt_jkkn3)\n const sqlSelectUrl_cmp_msyfmegt_jkkn3 = React.useMemo(() => {\n if (!sqlselect_cmp_msyfmegt_jkkn3) return \"\"\n const base = sqlUrlKey_cmp_msyfmegt_jkkn3\n ? (() => {\n const bound = bindSqlUrl(sqlselect_cmp_msyfmegt_jkkn3.url, { [sqlKeyParam_cmp_msyfmegt_jkkn3]: sqlUrlKey_cmp_msyfmegt_jkkn3 }, sqlKeyParam_cmp_msyfmegt_jkkn3)\n return bound.keyBound ? bound.url : appendQueryParam(bound.url, sqlKeyParam_cmp_msyfmegt_jkkn3, sqlUrlKey_cmp_msyfmegt_jkkn3)\n })()\n : sqlselect_cmp_msyfmegt_jkkn3.url\n return base\n }, [sqlUrlKey_cmp_msyfmegt_jkkn3])\n // A key in the page URL always narrows the result: a list endpoint ignores the\n // parameter server side, so the requested record is picked out here. Without a\n // key every row is kept and the navigation below takes over.\n const sqlRows_cmp_msyfmegt_jkkn3 = React.useMemo(\n () => filterSqlRowsByKey(toSqlRows(data_source_5w_5ibpv, \"\"), sqlKey_cmp_msyfmegt_jkkn3, sqlUrlKey_cmp_msyfmegt_jkkn3),\n [data_source_5w_5ibpv, sqlUrlKey_cmp_msyfmegt_jkkn3],\n )\n // A fresh result set resets the position; New mode is left untouched so an\n // unsaved draft is not overwritten by a re-render.\n React.useEffect(() => {\n setSqlIndex_cmp_msyfmegt_jkkn3((current) => (current < sqlRows_cmp_msyfmegt_jkkn3.length ? current : 0))\n }, [sqlRows_cmp_msyfmegt_jkkn3])\n React.useEffect(() => {\n const row = sqlRows_cmp_msyfmegt_jkkn3[sqlIndex_cmp_msyfmegt_jkkn3]\n if (!row) return\n setData_cmp_msyfmegt_jkkn3(row)\n setSqlOriginal_cmp_msyfmegt_jkkn3(row)\n setSqlMode_cmp_msyfmegt_jkkn3(\"edit\")\n }, [sqlRows_cmp_msyfmegt_jkkn3, sqlIndex_cmp_msyfmegt_jkkn3])\n const sqlHasKey_cmp_msyfmegt_jkkn3 = (() => {\n const value = readSqlField(data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3)\n return value !== undefined && value !== null && value !== \"\"\n })()\n const sqlSetField_cmp_msyfmegt_jkkn3 = React.useCallback((path, value) => setData_cmp_msyfmegt_jkkn3((current) => setSqlField(current, path, value)), [])\n const sqlReload_cmp_msyfmegt_jkkn3 = React.useCallback(async () => {\n // Without the Read permission the container is not rendered at all; the\n // request is dropped here so it is not fired for a form nobody can see.\n if (!sqlCanSelect_cmp_msyfmegt_jkkn3) return\n if (!sqlSelectUrl_cmp_msyfmegt_jkkn3) return\n // Calling a URL that still holds a placeholder is a guaranteed 400, so the\n // missing key is reported in the component instead.\n if (hasSqlUrlParams(sqlSelectUrl_cmp_msyfmegt_jkkn3)) {\n throw new Error(translate(\"::App.Form.MissingKeyParam\", { 0: sqlKeyParam_cmp_msyfmegt_jkkn3 }))\n }\n const response = await apiService.fetchData({ url: sqlSelectUrl_cmp_msyfmegt_jkkn3, method: \"GET\" })\n setData_source_5w_5ibpv(getByPath(response.data, sqlselect_cmp_msyfmegt_jkkn3.responsePath))\n }, [sqlSelectUrl_cmp_msyfmegt_jkkn3])\n // Reload wrapped with the busy/error handling the toolbar and mount effect need,\n // so a failing Select is reported in the component instead of the console.\n const sqlRefresh_cmp_msyfmegt_jkkn3 = React.useCallback(async () => {\n setSqlBusy_cmp_msyfmegt_jkkn3(true)\n setSqlError_cmp_msyfmegt_jkkn3(\"\")\n try {\n await sqlReload_cmp_msyfmegt_jkkn3()\n } catch (error) {\n setSqlError_cmp_msyfmegt_jkkn3(toSqlErrorMessage(error))\n } finally {\n setSqlBusy_cmp_msyfmegt_jkkn3(false)\n }\n }, [sqlReload_cmp_msyfmegt_jkkn3])\n const sqlNew_cmp_msyfmegt_jkkn3 = React.useCallback(() => {\n if (!sqlCanInsert_cmp_msyfmegt_jkkn3) return\n setSqlError_cmp_msyfmegt_jkkn3(\"\")\n // Defaults are resolved on every New, so an @today column carries the day the\n // record was opened rather than the day the component was generated.\n const sqlDraft_cmp_msyfmegt_jkkn3 = buildSqlDefaults([{\"path\":\"IsDefault\",\"value\":true},{\"path\":\"IsStatic\",\"value\":true},{\"path\":\"IsPublic\",\"value\":false},{\"path\":\"EntityVersion\",\"value\":3},{\"path\":\"ExtraProperties\",\"value\":\"{}\"}])\n setData_cmp_msyfmegt_jkkn3(sqlDraft_cmp_msyfmegt_jkkn3)\n setSqlOriginal_cmp_msyfmegt_jkkn3({})\n setSqlMode_cmp_msyfmegt_jkkn3(\"new\")\n // After the re-render that paints the empty form, not before it.\n focusFirstSqlField(sqlHost_cmp_msyfmegt_jkkn3)\n }, [])\n const sqlPrev_cmp_msyfmegt_jkkn3 = React.useCallback(() => setSqlIndex_cmp_msyfmegt_jkkn3((current) => Math.max(0, current - 1)), [])\n const sqlNext_cmp_msyfmegt_jkkn3 = React.useCallback(() => setSqlIndex_cmp_msyfmegt_jkkn3((current) => Math.min(sqlRows_cmp_msyfmegt_jkkn3.length - 1, current + 1)), [sqlRows_cmp_msyfmegt_jkkn3])\n const sqlSave_cmp_msyfmegt_jkkn3 = async () => {\n const isNew = sqlMode_cmp_msyfmegt_jkkn3 === \"new\"\n const target = isNew ? sqlinsert_cmp_msyfmegt_jkkn3 : sqlupdate_cmp_msyfmegt_jkkn3\n // The button is already hidden without the permission; a script calling save\n // through a ref must not be able to walk around it either.\n if (!(isNew ? sqlCanInsert_cmp_msyfmegt_jkkn3 : sqlCanUpdate_cmp_msyfmegt_jkkn3)) return\n if (!target) {\n setSqlError_cmp_msyfmegt_jkkn3(isNew ? translate(\"::App.Form.InsertEndpointMissing\") : translate(\"::App.Form.UpdateEndpointMissing\"))\n return\n }\n // Update carries only the edited columns plus the key; Insert sends the record.\n const payload = isNew ? data_cmp_msyfmegt_jkkn3 : toSqlChanges(sqlOriginal_cmp_msyfmegt_jkkn3, data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3)\n if (!isNew && Object.keys(payload).filter((column) => column.toLowerCase() !== String(sqlKey_cmp_msyfmegt_jkkn3).toLowerCase()).length === 0) {\n setSqlError_cmp_msyfmegt_jkkn3(translate(\"::App.Form.NoChanges\"))\n return\n }\n setSqlBusy_cmp_msyfmegt_jkkn3(true)\n setSqlError_cmp_msyfmegt_jkkn3(\"\")\n try {\n const response = await callSqlEndpoint(target, data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3, payload)\n // With a Select endpoint the reload is the source of truth and its effect\n // repopulates the form; without one, the response row is all there is.\n if (sqlselect_cmp_msyfmegt_jkkn3) {\n await sqlReload_cmp_msyfmegt_jkkn3()\n } else {\n const saved = toSqlRows(response?.data, \"\")[0]\n if (saved) {\n setData_cmp_msyfmegt_jkkn3(saved)\n setSqlOriginal_cmp_msyfmegt_jkkn3(saved)\n }\n }\n setSqlMode_cmp_msyfmegt_jkkn3(\"edit\")\n } catch (error) {\n setSqlError_cmp_msyfmegt_jkkn3(toSqlErrorMessage(error))\n } finally {\n setSqlBusy_cmp_msyfmegt_jkkn3(false)\n }\n }\n const sqlDelete_cmp_msyfmegt_jkkn3 = async () => {\n if (!sqlCanDelete_cmp_msyfmegt_jkkn3 || !sqldelete_cmp_msyfmegt_jkkn3 || !sqlHasKey_cmp_msyfmegt_jkkn3 || sqlMode_cmp_msyfmegt_jkkn3 === \"new\") return\n // Deleting cannot be undone from the form, so it always asks first.\n const confirmMessage = translate(\"::App.Form.DeleteConfirm\", { 0: sqlKey_cmp_msyfmegt_jkkn3 + \" = \" + readSqlField(data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3) })\n if (typeof window !== \"undefined\" && !window.confirm(confirmMessage)) return\n setSqlBusy_cmp_msyfmegt_jkkn3(true)\n setSqlError_cmp_msyfmegt_jkkn3(\"\")\n try {\n await callSqlEndpoint(sqldelete_cmp_msyfmegt_jkkn3, data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3)\n setData_cmp_msyfmegt_jkkn3({})\n setSqlOriginal_cmp_msyfmegt_jkkn3({})\n setSqlMode_cmp_msyfmegt_jkkn3(\"new\")\n await sqlReload_cmp_msyfmegt_jkkn3()\n } catch (error) {\n setSqlError_cmp_msyfmegt_jkkn3(toSqlErrorMessage(error))\n } finally {\n setSqlBusy_cmp_msyfmegt_jkkn3(false)\n }\n }\n React.useEffect(() => { void sqlRefresh_cmp_msyfmegt_jkkn3() }, [sqlRefresh_cmp_msyfmegt_jkkn3])\n\n const filters_source_tq_veb4m = [{ param: \"RoleId\", value: readSqlField(data_cmp_msyfmegt_jkkn3, \"id\"), required: true }]\n const filterKey_source_tq_veb4m = JSON.stringify(filters_source_tq_veb4m)\n React.useEffect(() => {\n const url = buildFilteredUrl(\"/api/app/crudendpoint/AbpUserRoles\", filters_source_tq_veb4m)\n // An unmet required filter leaves the component empty on purpose.\n if (!url) {\n setData_source_tq_veb4m(null)\n return\n }\n let active = true\n apiService.fetchData({ url, method: 'GET' }).then((response) => {\n if (active) setData_source_tq_veb4m(getByPath(response.data, \"\"))\n }).catch((error) => console.error(\"AbpUserRoles · GetList endpoint error:\", error))\n return () => { active = false }\n }, [filterKey_source_tq_veb4m])\n\n React.useEffect(() => {\n let active = true\n apiService.fetchData({ url: \"/api/app/crudendpoint/AbpUsers\", method: 'GET' }).then((response) => {\n if (active) setData_source_gq_0q1dz(getByPath(response.data, \"\"))\n }).catch((error) => console.error(\"AbpUsers · GetList endpoint error:\", error))\n return () => { active = false }\n }, [])\n\n const platformFilters_cmp_mt0288i3_ebpj5 = [{ field: \"Id\", operator: \"=\", kind: \"eq\", value: readSqlField(data_cmp_msyfmegt_jkkn3, \"Id\"), required: true }]\n const platformKey_cmp_mt0288i3_ebpj5 = JSON.stringify(platformFilters_cmp_mt0288i3_ebpj5)\n // The views keep the params object in their own state and write back to it, so\n // it must stay the same object until a filter value actually changes.\n const platformParams_cmp_mt0288i3_ebpj5 = React.useMemo(() => buildPlatformFilterParams(platformFilters_cmp_mt0288i3_ebpj5), [platformKey_cmp_mt0288i3_ebpj5])\n\n const handle_cmp_msyfpfia_ifhdq_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"Name\", valueOrEvent?.target?.value ?? valueOrEvent)\n const event = valueOrEvent\n }\n\n const handle_cmp_msyjl707_crcn7_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"NormalizedName\", valueOrEvent?.target?.value ?? valueOrEvent)\n const event = valueOrEvent\n }\n\n const handle_cmp_msyjdodn_u3o74_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"ConcurrencyStamp\", valueOrEvent?.target?.value ?? valueOrEvent)\n const event = valueOrEvent\n }\n\n const handle_cmp_msyje9ys_2c08a_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"IsDefault\", typeof originalEvent === \"object\" && originalEvent?.target ? Boolean(originalEvent.target.checked) : typeof valueOrEvent === \"object\" && valueOrEvent?.target ? Boolean(valueOrEvent.target.checked) : Boolean(valueOrEvent))\n const event = { checked: Boolean(valueOrEvent), originalEvent, target: originalEvent?.target }\n }\n\n const handle_cmp_msyjebu2_27tqi_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"IsStatic\", typeof originalEvent === \"object\" && originalEvent?.target ? Boolean(originalEvent.target.checked) : typeof valueOrEvent === \"object\" && valueOrEvent?.target ? Boolean(valueOrEvent.target.checked) : Boolean(valueOrEvent))\n const event = { checked: Boolean(valueOrEvent), originalEvent, target: originalEvent?.target }\n }\n\n const handle_cmp_msyjmetn_gojol_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"IsPublic\", typeof originalEvent === \"object\" && originalEvent?.target ? Boolean(originalEvent.target.checked) : typeof valueOrEvent === \"object\" && valueOrEvent?.target ? Boolean(valueOrEvent.target.checked) : Boolean(valueOrEvent))\n const event = { checked: Boolean(valueOrEvent), originalEvent, target: originalEvent?.target }\n }\n\n const handle_cmp_msyjo0g8_oathk_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"EntityVersion\", valueOrEvent?.target?.value ?? valueOrEvent)\n const event = valueOrEvent\n }\n\n const handle_cmp_msyjplfq_py8lz_onChange = (valueOrEvent, originalEvent) => {\n sqlSetField_cmp_msyfmegt_jkkn3(\"ExtraProperties\", valueOrEvent?.target?.value ?? valueOrEvent)\n const event = valueOrEvent\n }\n\n return (\n <>\n {sqlCanSelect_cmp_msyfmegt_jkkn3 ? (\n {sqlHasKey_cmp_msyfmegt_jkkn3 ? sqlKey_cmp_msyfmegt_jkkn3 + \": \" + readSqlField(data_cmp_msyfmegt_jkkn3, sqlKey_cmp_msyfmegt_jkkn3) : sqlMode_cmp_msyfmegt_jkkn3 === \"new\" ? translate(\"::App.Platform.NewRecord\") : \"\"}}>\n
\n
\n \n \n \n \n {\"Is Default\"}\n \n \n {\"Is Static\"}\n \n \n {\"Is Public\"}\n \n \n \n
\n
\n {sqlRows_cmp_msyfmegt_jkkn3.length > 1 ? (\n <>\n \n {`${sqlIndex_cmp_msyfmegt_jkkn3 + 1} / ${sqlRows_cmp_msyfmegt_jkkn3.length}`}\n \n \n \n ) : null}\n {sqlinsert_cmp_msyfmegt_jkkn3 && sqlCanInsert_cmp_msyfmegt_jkkn3 ? : null}\n {(sqlMode_cmp_msyfmegt_jkkn3 === \"new\" ? sqlinsert_cmp_msyfmegt_jkkn3 && sqlCanInsert_cmp_msyfmegt_jkkn3 : sqlupdate_cmp_msyfmegt_jkkn3 && sqlCanUpdate_cmp_msyfmegt_jkkn3) ? : null}\n {sqldelete_cmp_msyfmegt_jkkn3 && sqlCanDelete_cmp_msyfmegt_jkkn3 ? : null}\n {sqlselect_cmp_msyfmegt_jkkn3 && sqlCanSelect_cmp_msyfmegt_jkkn3 ? : null}\n {sqlMode_cmp_msyfmegt_jkkn3 === \"new\" ? translate(\"::App.Platform.NewRecord\") : translate(\"::App.Platform.Editing\")}\n
\n {sqlError_cmp_msyfmegt_jkkn3 ?
{sqlError_cmp_msyfmegt_jkkn3}
: null}\n
\n
\n ) : null}\n
\n {(() => {\n const gridItems_cmp_mt023r5u_rb2jv = (Array.isArray(getByPath(data_source_tq_veb4m, \"\")) ? getByPath(data_source_tq_veb4m, \"\") : Array.isArray(data_source_tq_veb4m) ? data_source_tq_veb4m : null)\n // Nothing loaded yet — an endpoint still in flight, or a required filter\n // holding the request back — is an empty grid, not a binding error.\n if (gridItems_cmp_mt023r5u_rb2jv === null || gridItems_cmp_mt023r5u_rb2jv === undefined) return null\n if (!Array.isArray(gridItems_cmp_mt023r5u_rb2jv)) return
Grid items bağlantısı bir koleksiyon döndürmelidir.
\n const gridColumns_cmp_mt023r5u_rb2jv = [\"UserId\"]\n const gridLookups_cmp_mt023r5u_rb2jv = { \"UserId\": { rows: data_source_gq_0q1dz, path: \"\", value: \"Id\", text: \"UserName\" } }\n if (!gridColumns_cmp_mt023r5u_rb2jv.length) return
Preview için en az bir sütun seçin.
\n return (\n
\n \n \n {gridColumns_cmp_mt023r5u_rb2jv.map((column) => )}\n \n \n {gridItems_cmp_mt023r5u_rb2jv.map((gridItem_cmp_mt023r5u_rb2jv, rowIndex) => (\n \n {gridColumns_cmp_mt023r5u_rb2jv.map((column) => {\n const cellValue_cmp_mt023r5u_rb2jv = column === \"value\" ? gridItem_cmp_mt023r5u_rb2jv : getByPath(gridItem_cmp_mt023r5u_rb2jv, column)\n const lookedUp = resolveLookupText(gridLookups_cmp_mt023r5u_rb2jv[column], cellValue_cmp_mt023r5u_rb2jv)\n const cellText = lookedUp === undefined ? cellValue_cmp_mt023r5u_rb2jv : lookedUp\n const text = typeof cellText === \"object\" && cellText !== null && !(cellText instanceof Date) ? JSON.stringify(cellText) : formatLocaleValue(cellText) || \"—\"\n return \n })}\n \n ))}\n \n
{(column === \"value\" ? \"Value\" : column)}
{text}
\n
\n )\n })()}\n
\n {platformParams_cmp_mt0288i3_ebpj5 === null ? null : (\n \n )}\n \n )\n}\n\nexport default RoleComponent", + "Props": "{\"visualDesigner\":{\"version\":1,\"sourceMode\":\"visual\",\"nodes\":[{\"id\":\"cmp_msyfmegt_jkkn3\",\"type\":\"Form\",\"kind\":\"layout\",\"props\":{\"selectEndpoint\":\"source_5w_5ibpv\",\"insertEndpoint\":\"source_h3_484qa\",\"updateEndpoint\":\"source_gb_d9cvs\",\"deleteEndpoint\":\"source_0t_avok9\",\"keyFieldName\":\"id\",\"collectionPath\":\"\",\"keySource\":\"query\",\"keyParamName\":\"id\",\"previewKeyValue\":\"\",\"autoLoad\":true,\"showToolbar\":true,\"gap\":16,\"className\":\"\",\"title\":\"::AbpIdentity.Roles\",\"columnCount\":2,\"selectPermission\":\"\",\"insertPermission\":\"\"},\"events\":{\"onLoad\":\"\",\"onRecordChange\":\"\",\"onFieldChange\":\"\",\"onNewRecord\":\"\",\"onModeChange\":\"\",\"onBeforeSave\":\"\",\"onAfterSave\":\"\",\"onBeforeDelete\":\"\",\"onAfterDelete\":\"\",\"onError\":\"\"},\"bindings\":{},\"children\":[{\"id\":\"cmp_msyfpfia_ifhdq\",\"type\":\"Input\",\"kind\":\"ui\",\"props\":{\"asElement\":\"\",\"disabled\":false,\"invalid\":false,\"rows\":3,\"prefix\":\"Name :\",\"size\":\"md\",\"suffix\":\"\",\"textArea\":false,\"type\":\"text\",\"unstyle\":false,\"field\":\"\",\"form\":\"\",\"className\":\"\",\"children\":\"\",\"style\":{},\"placeholder\":\"Metin girin...\",\"value\":\"\",\"name\":\"\",\"autoComplete\":\"off\",\"autoFocus\":false,\"readOnly\":false,\"required\":false,\"maxLength\":\"\",\"minLength\":\"\",\"pattern\":\"\",\"sqlDefaultValue\":\"\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"value\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"Name\"}},\"children\":[],\"ref\":\"input1\"},{\"id\":\"cmp_msyjl707_crcn7\",\"type\":\"Input\",\"kind\":\"ui\",\"props\":{\"asElement\":\"\",\"disabled\":false,\"invalid\":false,\"rows\":3,\"prefix\":\"Normalized Name :\",\"size\":\"md\",\"suffix\":\"\",\"textArea\":false,\"type\":\"text\",\"unstyle\":false,\"field\":\"\",\"form\":\"\",\"className\":\"\",\"children\":\"\",\"style\":{},\"placeholder\":\"Metin girin...\",\"value\":\"\",\"name\":\"\",\"autoComplete\":\"off\",\"autoFocus\":false,\"readOnly\":false,\"required\":false,\"maxLength\":\"\",\"minLength\":\"\",\"pattern\":\"\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"value\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"NormalizedName\"}},\"children\":[],\"ref\":\"input2\"},{\"id\":\"cmp_msyjdodn_u3o74\",\"type\":\"Input\",\"kind\":\"ui\",\"props\":{\"asElement\":\"\",\"disabled\":false,\"invalid\":false,\"rows\":3,\"prefix\":\"Concurrent Stamp : \",\"size\":\"md\",\"suffix\":\"\",\"textArea\":false,\"type\":\"text\",\"unstyle\":false,\"field\":\"\",\"form\":\"\",\"className\":\"\",\"children\":\"\",\"style\":{},\"placeholder\":\"Metin girin...\",\"value\":\"\",\"name\":\"\",\"autoComplete\":\"off\",\"autoFocus\":false,\"readOnly\":false,\"required\":false,\"maxLength\":\"\",\"minLength\":\"\",\"pattern\":\"\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"value\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"ConcurrencyStamp\"}},\"children\":[],\"ref\":\"input3\"},{\"id\":\"cmp_msyje9ys_2c08a\",\"type\":\"Checkbox\",\"kind\":\"ui\",\"props\":{\"checked\":false,\"color\":\"blue-500\",\"disabled\":false,\"labelRef\":\"\",\"name\":\"\",\"readOnly\":false,\"value\":\"\",\"field\":null,\"className\":\"\",\"children\":\"Is Default\",\"style\":{},\"sqlDefaultValue\":\"true\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"checked\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"IsDefault\"}},\"children\":[],\"ref\":\"checkbox1\"},{\"id\":\"cmp_msyjebu2_27tqi\",\"type\":\"Checkbox\",\"kind\":\"ui\",\"props\":{\"checked\":false,\"color\":\"blue-500\",\"disabled\":false,\"labelRef\":\"\",\"name\":\"\",\"readOnly\":false,\"value\":\"\",\"field\":null,\"className\":\"\",\"children\":\"Is Static\",\"style\":{},\"sqlDefaultValue\":\"true\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"checked\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"IsStatic\"}},\"children\":[],\"ref\":\"checkbox2\"},{\"id\":\"cmp_msyjmetn_gojol\",\"type\":\"Checkbox\",\"kind\":\"ui\",\"props\":{\"checked\":false,\"color\":\"blue-500\",\"disabled\":false,\"labelRef\":\"\",\"name\":\"\",\"readOnly\":false,\"value\":\"\",\"field\":null,\"className\":\"\",\"children\":\"Is Public\",\"style\":{},\"sqlDefaultValue\":\"false\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"checked\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"IsPublic\"}},\"children\":[],\"ref\":\"checkbox3\"},{\"id\":\"cmp_msyjo0g8_oathk\",\"type\":\"Input\",\"kind\":\"ui\",\"props\":{\"asElement\":\"\",\"disabled\":false,\"invalid\":false,\"rows\":3,\"prefix\":\"Entity Version : \",\"size\":\"md\",\"suffix\":\"\",\"textArea\":false,\"type\":\"number\",\"unstyle\":false,\"field\":\"\",\"form\":\"\",\"className\":\"\",\"children\":\"\",\"style\":{},\"placeholder\":\"Metin girin...\",\"value\":\"\",\"name\":\"\",\"autoComplete\":\"off\",\"autoFocus\":false,\"readOnly\":false,\"required\":false,\"maxLength\":\"\",\"minLength\":\"\",\"pattern\":\"\",\"sqlDefaultValue\":\"3\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"value\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"EntityVersion\"}},\"children\":[],\"ref\":\"input4\"},{\"id\":\"cmp_msyjplfq_py8lz\",\"type\":\"Input\",\"kind\":\"ui\",\"props\":{\"asElement\":\"\",\"disabled\":false,\"invalid\":false,\"rows\":3,\"prefix\":\"Extra Properties :\",\"size\":\"md\",\"suffix\":\"\",\"textArea\":false,\"type\":\"text\",\"unstyle\":false,\"field\":\"\",\"form\":\"\",\"className\":\"\",\"children\":\"\",\"style\":{},\"placeholder\":\"Metin girin...\",\"value\":\"\",\"name\":\"\",\"autoComplete\":\"off\",\"autoFocus\":false,\"readOnly\":false,\"required\":false,\"maxLength\":\"\",\"minLength\":\"\",\"pattern\":\"\",\"sqlDefaultValue\":\"{}\"},\"events\":{\"onChange\":\"\"},\"bindings\":{\"value\":{\"sourceId\":\"cmp_msyfmegt_jkkn3\",\"path\":\"ExtraProperties\"}},\"children\":[],\"ref\":\"input5\"}],\"ref\":\"form1\"},{\"id\":\"cmp_mt023r5u_rb2jv\",\"type\":\"Grid\",\"kind\":\"ui\",\"props\":{\"hoverable\":true,\"overflow\":true,\"borderlessRow\":false,\"compact\":false,\"cols\":\"3\",\"rows\":\"2\",\"gap\":\"4\",\"colGap\":\"4\",\"rowGap\":\"4\",\"responsive\":true,\"smCols\":\"1\",\"mdCols\":\"2\",\"lgCols\":\"3\",\"xlCols\":\"4\",\"autoFit\":false,\"minColWidth\":\"200px\",\"height\":\"auto\",\"className\":\"mt-4\",\"items\":[{\"id\":\"grid-item-1\",\"content\":\"Grid Item 1\",\"colSpan\":1,\"rowSpan\":1,\"colStart\":1,\"rowStart\":1,\"className\":\"bg-blue-100 p-4 border rounded\"},{\"id\":\"grid-item-2\",\"content\":\"Grid Item 2\",\"colSpan\":1,\"rowSpan\":1,\"colStart\":2,\"rowStart\":1,\"className\":\"bg-green-100 p-4 border rounded\"},{\"id\":\"grid-item-3\",\"content\":\"Grid Item 3\",\"colSpan\":1,\"rowSpan\":1,\"colStart\":3,\"rowStart\":1,\"className\":\"bg-yellow-100 p-4 border rounded\"},{\"id\":\"grid-item-4\",\"content\":\"Grid Item 4\",\"colSpan\":2,\"rowSpan\":1,\"colStart\":1,\"rowStart\":2,\"className\":\"bg-purple-100 p-4 border rounded\"},{\"id\":\"grid-item-5\",\"content\":\"Grid Item 5\",\"colSpan\":1,\"rowSpan\":1,\"colStart\":3,\"rowStart\":2,\"className\":\"bg-red-100 p-4 border rounded\"}],\"dataColumns\":[\"UserId\"],\"columnLookups\":{\"UserId\":{\"sourceId\":\"source_gq_0q1dz\",\"path\":\"\",\"valueField\":\"Id\",\"textField\":\"UserName\"}},\"columnCaptions\":{}},\"events\":{},\"bindings\":{\"items\":{\"sourceId\":\"source_tq_veb4m\",\"path\":\"\"}},\"children\":[],\"ref\":\"grid1\"},{\"id\":\"cmp_mt0288i3_ebpj5\",\"type\":\"ListView\",\"kind\":\"platform\",\"props\":{\"listFormCode\":\"AbpIdentity.Roles\",\"height\":\"520px\",\"filters\":[{\"id\":\"filter_y9_tr73w\",\"field\":\"Id\",\"operator\":\"eq\",\"source\":\"record\",\"value\":\"form1.Id\",\"previewValue\":\"\",\"required\":true}],\"className\":\"mt-4\"},\"events\":{},\"bindings\":{},\"children\":[],\"ref\":\"listView1\"}],\"canvas\":{\"width\":\"responsive\"},\"lifecycle\":{\"onMount\":\"\"},\"dataSources\":[{\"id\":\"source_5w_5ibpv\",\"name\":\"AbpRoles · GetList\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpRoles\",\"responsePath\":\"\"},{\"id\":\"source_i7_msox4\",\"name\":\"AbpRoles · GetById\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpRoles/{id}\",\"responsePath\":\"\"},{\"id\":\"source_0t_avok9\",\"name\":\"AbpRoles · Delete\",\"method\":\"DELETE\",\"url\":\"/api/app/crudendpoint/AbpRoles/{id}\",\"responsePath\":\"\"},{\"id\":\"source_h3_484qa\",\"name\":\"AbpRoles · Create\",\"method\":\"POST\",\"url\":\"/api/app/crudendpoint/AbpRoles\",\"responsePath\":\"\"},{\"id\":\"source_gb_d9cvs\",\"name\":\"AbpRoles · Update\",\"method\":\"PUT\",\"url\":\"/api/app/crudendpoint/AbpRoles/{id}\",\"responsePath\":\"\"},{\"id\":\"source_tq_veb4m\",\"name\":\"AbpUserRoles · GetList\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpUserRoles\",\"responsePath\":\"\",\"filters\":[{\"id\":\"filter_sw_86e0q\",\"field\":\"RoleId\",\"operator\":\"eq\",\"source\":\"record\",\"value\":\"form1.id\",\"previewValue\":\"\",\"required\":true}]},{\"id\":\"source_d2_bri5s\",\"name\":\"AbpUserRoles · GetById\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpUserRoles/{id}\",\"responsePath\":\"\"},{\"id\":\"source_gq_0q1dz\",\"name\":\"AbpUsers · GetList\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpUsers\",\"responsePath\":\"\"},{\"id\":\"source_u8_nu1lu\",\"name\":\"AbpUsers · GetById\",\"method\":\"GET\",\"url\":\"/api/app/crudendpoint/AbpUsers/{id}\",\"responsePath\":\"\"}],\"permissionCode\":\"App.Wizard.RoleList\"}}", "Description": "", "IsActive": true, "Dependencies": [], diff --git a/configs/seeds/host/wizard/20260819131500_RoleList.json b/configs/seeds/host/wizard/20260819131500_RoleList.json index f8ea0c75..3db37a3b 100644 --- a/configs/seeds/host/wizard/20260819131500_RoleList.json +++ b/configs/seeds/host/wizard/20260819131500_RoleList.json @@ -1,4 +1,4 @@ -{ +{ "Wizard": { "ComponentKind": 1, "WizardName": "RoleList", @@ -136,7 +136,10 @@ ], "PermissionGroupNames": [], "PermissionNames": [ - "App.Wizard.RoleList" + "App.Wizard.RoleList", + "App.Wizard.RoleList.Create", + "App.Wizard.RoleList.Update", + "App.Wizard.RoleList.Delete" ], "MenuCodes": [ "App.Wizard.RoleList" diff --git a/ui/src/components/componentEditor/DynamicRenderer.tsx b/ui/src/components/componentEditor/DynamicRenderer.tsx index 1bc82db9..fcc3f4f4 100644 --- a/ui/src/components/componentEditor/DynamicRenderer.tsx +++ b/ui/src/components/componentEditor/DynamicRenderer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import axios from 'axios' import DOMPurify from 'dompurify' import apiService from '@/services/api.service' @@ -9,6 +9,8 @@ import { toErrorMessage, type BabelLike, } from '@/contexts/componentRuntime' +import { useLocalization } from '@/utils/hooks/useLocalization' +import { usePermission } from '@/utils/hooks/usePermission' import { useComponents } from '../../contexts/ComponentContext' import ErrorBoundary from './ErrorBoundary' import PlatformIcon from './PlatformIcon' @@ -76,6 +78,23 @@ const DynamicRenderer: React.FC = ({ const [Component, setComponent] = useState | null>(null) const [error, setError] = useState(null) const { getComponentByName, components } = useComponents() + const { checkPermission } = usePermission() + const { translate } = useLocalization() + + // Read through refs so the granted policies and the active language can change + // without recompiling the component; generated code calls both on every render. + const checkPermissionRef = useRef(checkPermission) + checkPermissionRef.current = checkPermission + const stableCheckPermission = useCallback( + (permission?: string) => checkPermissionRef.current(permission), + [], + ) + const translateRef = useRef(translate) + translateRef.current = translate + const stableTranslate = useCallback( + (...args: Parameters) => translateRef.current(...args), + [], + ) useEffect(() => { let cancelled = false @@ -126,7 +145,13 @@ const DynamicRenderer: React.FC = ({ const component = compileComponent( entry.code, - { React, ...staticComponents, ...deps }, + { + React, + ...staticComponents, + checkPermission: stableCheckPermission, + translate: stableTranslate, + ...deps, + }, Babel, ) if (!component) throw new Error(`Component ${name} bir default export döndürmüyor`) @@ -149,7 +174,14 @@ const DynamicRenderer: React.FC = ({ return () => { cancelled = true } - }, [componentName, externalDeps, components, getComponentByName]) + }, [ + componentName, + externalDeps, + components, + getComponentByName, + stableCheckPermission, + stableTranslate, + ]) if (error) { return ( diff --git a/ui/src/components/visualDesigner/DesignerScriptBuilderDialog.tsx b/ui/src/components/visualDesigner/DesignerScriptBuilderDialog.tsx index 21947a30..35221508 100644 --- a/ui/src/components/visualDesigner/DesignerScriptBuilderDialog.tsx +++ b/ui/src/components/visualDesigner/DesignerScriptBuilderDialog.tsx @@ -2,19 +2,19 @@ import ScriptBuilderDialog from '@/components/scriptBuilder/ScriptBuilderDialog' import { useMemo } from 'react' import { useLocalization } from '@/utils/hooks/useLocalization' import { createDesignerScriptDialect } from './designerScriptDialect' -import type { SqlDataSourceEvent } from './types' +import type { FormEvent } from './types' export type DesignerScriptBuilderDialogProps = { isOpen: boolean value?: string /** Düzenlenen event: başlıkta ve event yolu seçicilerinde kullanılır. */ eventName: string - eventInfo?: SqlDataSourceEvent + eventInfo?: FormEvent /** Event'in sahibi komponentin adı, yalnızca başlık için. */ componentLabel?: string - /** Kayıt tariflerinin yazacağı SqlDataSource ref adı. */ + /** Kayıt tariflerinin yazacağı Form ref adı. */ sqlRef: string - /** SqlDataSource Select sonucundaki sütun adları. */ + /** Form Select sonucundaki sütun adları. */ recordFields: string[] /** Sayfadaki tüm ref adları. */ refNames: string[] diff --git a/ui/src/components/visualDesigner/VisualCanvas.tsx b/ui/src/components/visualDesigner/VisualCanvas.tsx index 910ed95a..366dbc09 100644 --- a/ui/src/components/visualDesigner/VisualCanvas.tsx +++ b/ui/src/components/visualDesigner/VisualCanvas.tsx @@ -20,13 +20,13 @@ import { getDesignerCollectionProperty, getDesignerTabSlot, getDesignerValueByPath, - getSqlDataSourceEndpointId, - getSqlDataSourceColumnCount, + getFormEndpointId, + getFormColumnCount, getDesignerColumnCaptions, getUsableDesignerColumnLookups, buildDesignerLookupIndex, resolveDesignerLookupText, - getSqlDataSourceKeyField, + getFormKeyField, getSqlFormValueProperty, isDesignerDateComponent, isDesignerDateProperty, @@ -34,26 +34,26 @@ import { DESIGNER_ICON_PROPERTIES, DESIGNER_SELECT_COMPONENT_PROPERTY, flexRowStyle, - isSqlDataSourceNode, - LEGACY_SQL_DATA_SOURCE_TYPE, + isFormNode, normalizeDesignerKeyList, - readSqlDataSourceField, + readFormField, resolveDesignerDropdownTitle, resolveDesignerPreviewFilterValue, resolveDesignerTabValue, - resolveSqlDataSourceRows, + resolveFormRows, setDesignerRecordField, - SQL_DATA_SOURCE_TYPE, + FORM_TYPE, SQL_DEFAULT_VALUE_PROP, toDesignerDate, type DesignerBinding, type DesignerDataSourceFilter, + type DesignerDocument, type DesignerNode, - type SqlDataSourceMode, + type FormMode, } from './types' /** - * Editing scope opened by a SqlDataSource: a descendant whose `value`/`checked` + * Editing scope opened by a Form: a descendant whose `value`/`checked` * is bound to `sourceId` writes back into the record instead of into its own * static prop, which is what makes the container behave like an ASP.NET FormView. */ @@ -173,6 +173,12 @@ interface VisualCanvasProps { onNodePropChange?: (id: string, propertyName: string, value: unknown) => void renderCustomComponent?: (name: string, props?: Record) => React.ReactNode dataValues?: Record + /** + * Device the canvas is simulating. A Form lays its fields out in one column on + * a phone however many columns are configured, and the preview has to show + * that rather than leave the designer guessing. + */ + canvasWidth?: DesignerDocument['canvas']['width'] } class PreviewBoundary extends React.Component< @@ -270,7 +276,7 @@ const buildDesignerRefs = (store: DesignerRefStore | null) => { store.patch(ref, { props: { readOnly: readOnly !== false } }), setText: (text: unknown) => store.patch(ref, { props: { children: text } }), reset: () => store.patch(ref, { props: null as unknown as Record }), - ...(isSqlDataSourceNode(node.type) + ...(isFormNode(node.type) ? { getRecord: () => ({}), setRecord: designTimeOnly('setRecord'), @@ -282,7 +288,7 @@ const buildDesignerRefs = (store: DesignerRefStore | null) => { getChanges: () => ({}), hasChanges: () => false, getKey: () => undefined, - getKeyField: () => getSqlDataSourceKeyField(node), + getKeyField: () => getFormKeyField(node), getRows: () => [], getRowCount: () => 0, getIndex: () => 0, @@ -484,7 +490,7 @@ const getSelectMenuProps = (props: Record) => { const getTabOptions = (props: Record) => Array.isArray(props.items) ? (props.items as Array>) : [] -/** Prop of a node that a SqlDataSource scope is allowed to write back to. */ +/** Prop of a node that a Form scope is allowed to write back to. */ const getFormScopeProperty = (node: DesignerNode, formScope?: DesignerFormScope) => getSqlFormValueProperty(node, formScope?.sourceId) @@ -623,7 +629,7 @@ const getPreviewProps = ( : value && typeof value === 'object' && 'target' in value ? Boolean((value as { target?: { checked?: unknown } }).target?.checked) : Boolean(value) - // Inside a SqlDataSource the edited value belongs to the record, not to the + // Inside a Form the edited value belongs to the record, not to the // node's static prop — otherwise typing into a bound Input would be discarded // on the next render because the binding always wins. const formScopeProperty = getFormScopeProperty(node, formScope) @@ -868,7 +874,15 @@ const focusFirstFormField = (host: React.RefObject) => { }) } -const SqlDataSourceView = ({ +/** + * Device the canvas frame is simulating. The runtime lays a Form out from CSS + * breakpoints, which answer to the browser window rather than to the frame the + * designer is looking at — so the preview reads the simulated device from here + * instead, and shows what a phone would actually get. + */ +const CanvasWidthContext = React.createContext('responsive') + +const FormView = ({ node, dataValues, interactive, @@ -883,18 +897,22 @@ const SqlDataSourceView = ({ ) => React.ReactNode }) => { const { translate } = useLocalization() - const selectId = getSqlDataSourceEndpointId(node, 'selectEndpoint') - const keyField = getSqlDataSourceKeyField(node) + const selectId = getFormEndpointId(node, 'selectEndpoint') + const keyField = getFormKeyField(node) const collectionPath = String(node.props.collectionPath ?? '') - const columnCount = getSqlDataSourceColumnCount(node) + const canvasWidth = React.useContext(CanvasWidthContext) + const isNarrowCanvas = canvasWidth === 'mobile' + // Matches the generated component: below the `md` breakpoint every field takes + // the full width, however many columns are configured. + const columnCount = isNarrowCanvas ? 1 : getFormColumnCount(node) const rows = React.useMemo( - () => (selectId ? resolveSqlDataSourceRows(dataValues[selectId], collectionPath) : []), + () => (selectId ? resolveFormRows(dataValues[selectId], collectionPath) : []), [collectionPath, dataValues, selectId], ) // Container element, so New can hand the cursor to the first field of the form. const hostRef = React.useRef(null) const [rowIndex, setRowIndex] = React.useState(0) - const [mode, setMode] = React.useState('edit') + const [mode, setMode] = React.useState('edit') // Holds the whole record while editing, so New mode can show an empty form // instead of falling back to the loaded row. const [draft, setDraft] = React.useState | null>(null) @@ -927,11 +945,11 @@ const SqlDataSourceView = ({ setDraft(null) setMode('edit') } - const keyValue = readSqlDataSourceField(record, keyField) + const keyValue = readFormField(record, keyField) const hasKey = keyValue !== undefined && keyValue !== null && keyValue !== '' - const canInsert = Boolean(getSqlDataSourceEndpointId(node, 'insertEndpoint')) - const canUpdate = Boolean(getSqlDataSourceEndpointId(node, 'updateEndpoint')) - const canDelete = Boolean(getSqlDataSourceEndpointId(node, 'deleteEndpoint')) + const canInsert = Boolean(getFormEndpointId(node, 'insertEndpoint')) + const canUpdate = Boolean(getFormEndpointId(node, 'updateEndpoint')) + const canDelete = Boolean(getFormEndpointId(node, 'deleteEndpoint')) // Save follows the explicit mode, exactly like the generated runtime does. const canSave = mode === 'new' ? canInsert : canUpdate const designTimeTitle = translate('::App.VisualDesignerCanvas.DesignTimeNoCall') @@ -980,7 +998,7 @@ const SqlDataSourceView = ({ > {interactive && (
- {SQL_DATA_SOURCE_TYPE} + {FORM_TYPE} key: {keyField} @@ -991,9 +1009,7 @@ const SqlDataSourceView = ({ {translate( - mode === 'new' - ? '::App.VisualDesignerCanvas.NewRecord' - : '::App.Platform.Editing', + mode === 'new' ? '::App.VisualDesignerCanvas.NewRecord' : '::App.Platform.Editing', )} {rows.length > 1 && ( @@ -1111,7 +1127,14 @@ const SqlDataSourceView = ({ {headerExtra} : undefined} > {body} @@ -1185,7 +1208,7 @@ const renderElement = ( ) => { // Built in NodeView so the container can own the record state and expose it to // its children through an augmented `dataValues` map. - if (isSqlDataSourceNode(node.type)) return <>{children} + if (isFormNode(node.type)) return <>{children} if (node.type === 'Spacer') { return (
) : null - const renderedChildren = sqlDataSourceContent - ? sqlDataSourceContent + const renderedChildren = formContent + ? formContent : node.type === 'Tabs' ? tabsContent : node.type === 'Table' @@ -2158,8 +2180,8 @@ const NodeView = ({ acceptsDroppedChildren && node.type !== 'Card' && node.type !== 'Tabs' && - // SqlDataSource renders its own placeholder below the toolbar. - !isSqlDataSourceNode(node.type) && ( + // Form renders its own placeholder below the toolbar. + !isFormNode(node.type) && (
{ const { translate } = useLocalization() const previewDataValues = dataValues @@ -2249,127 +2272,129 @@ const VisualCanvas = ({ ) return ( - -
interactive && onSelect?.('')} - onDragOver={(event) => interactive && event.preventDefault()} - onDrop={(event) => { - if (!interactive) return - event.preventDefault() - endDesignerDrag() - const raw = - event.dataTransfer.getData(DESIGNER_DRAG_TYPE) || - event.dataTransfer.getData('text/plain') - if (!raw) return - const payload = JSON.parse(raw) - if (payload.source === 'library') { - onDropComponent?.(payload.name, null) - } else if (payload.source === 'canvas' && payload.nodeId) { - // Dropping on empty canvas takes the node out of its container. - onMoveIntoContainer?.(payload.nodeId, null) - } - }} - > - {drag && interactive && ( -
- - {drag.label || drag.name}{' '} - {translate('::App.VisualDesignerCanvas.DraggingHint')} - -
- )} - {nodes.length ? ( -
- {nodes.map((node, index) => ( - - ))} - {/* Sürükleme sürerken kök seviye açık bir hedef olur; aksi halde bir + + +
interactive && onSelect?.('')} + onDragOver={(event) => interactive && event.preventDefault()} + onDrop={(event) => { + if (!interactive) return + event.preventDefault() + endDesignerDrag() + const raw = + event.dataTransfer.getData(DESIGNER_DRAG_TYPE) || + event.dataTransfer.getData('text/plain') + if (!raw) return + const payload = JSON.parse(raw) + if (payload.source === 'library') { + onDropComponent?.(payload.name, null) + } else if (payload.source === 'canvas' && payload.nodeId) { + // Dropping on empty canvas takes the node out of its container. + onMoveIntoContainer?.(payload.nodeId, null) + } + }} + > + {drag && interactive && ( +
+ + {drag.label || drag.name}{' '} + {translate('::App.VisualDesignerCanvas.DraggingHint')} + +
+ )} + {nodes.length ? ( +
+ {nodes.map((node, index) => ( + + ))} + {/* Sürükleme sürerken kök seviye açık bir hedef olur; aksi halde bir konteynerin dışına çıkmanın görünür bir yolu yok. */} - {drag && interactive && ( -
- {translate('::App.VisualDesignerCanvas.DropAtPageEnd')} -
- )} -
- ) : ( -
-
-
- + -
-

- {translate('::App.VisualDesignerCanvas.ChoosePageLayout')} -

-

- {translate('::App.VisualDesignerCanvas.ChoosePageLayoutHint')} -

- {interactive && ( -
- {[ - // The component name itself is the label, so a layout is - // called the same here as it is in the toolbox. - ['PageContainer', '□'], - ['FlexRow', '▥'], - ['Table', '▦'], - ].map(([name, icon]) => ( - - ))} + {drag && interactive && ( +
+ {translate('::App.VisualDesignerCanvas.DropAtPageEnd')}
)}
-
- )} -
- + ) : ( +
+
+
+ + +
+

+ {translate('::App.VisualDesignerCanvas.ChoosePageLayout')} +

+

+ {translate('::App.VisualDesignerCanvas.ChoosePageLayoutHint')} +

+ {interactive && ( +
+ {[ + // The component name itself is the label, so a layout is + // called the same here as it is in the toolbox. + ['PageContainer', '□'], + ['FlexRow', '▥'], + ['Table', '▦'], + ].map(([name, icon]) => ( + + ))} +
+ )} +
+
+ )} +
+
+
) } diff --git a/ui/src/components/visualDesigner/catalog.ts b/ui/src/components/visualDesigner/catalog.ts index 2ff4670c..704ee8e9 100644 --- a/ui/src/components/visualDesigner/catalog.ts +++ b/ui/src/components/visualDesigner/catalog.ts @@ -2,9 +2,9 @@ import { CUSTOM_COMPONENTS, HTML_ELEMENTS } from '@/components/codeLayout/data/c import generatedComponentProps from './generated/componentProps.json' import { DESIGNER_DATA_COMPONENT_NAMES, - SQL_DATA_SOURCE_EVENTS, - LEGACY_SQL_DATA_SOURCE_TYPE, - SQL_DATA_SOURCE_TYPE, + FORM_EVENTS, + FORM_SLOTS, + FORM_TYPE, getDesignerCollectionProperty, isDesignerOptionComponent, isDesignerTabularComponent, @@ -162,7 +162,7 @@ const selectedValue = event?.value ?? null const selectedLabel = event?.label ?? '' console.log({ selectedValue, selectedLabel })`, }, - [SQL_DATA_SOURCE_TYPE]: { + [FORM_TYPE]: { onLoad: `// Select cevabı forma yerleştikten sonra çalışır. // event: { rows, count, record, index } if (!event.count) notify('Kayıt bulunamadı', 'warning')`, @@ -217,7 +217,7 @@ export interface DesignerScriptRecipe { export const DESIGNER_SCRIPT_RECIPE_GROUPS = [ 'App.CatalogGroup.ComponentAccess', 'App.CatalogGroup.ApiCalls', - 'App.CatalogGroup.SqlDataSource', + 'App.CatalogGroup.Form', 'App.CatalogGroup.EventAndPage', ] as const @@ -354,33 +354,33 @@ refs.{{sql}}.setFields({ UnitPrice: fiyat, Stock: stok })`, console.log(secilen, refs.{{ref}}.getLabel())`, }, { - group: 'App.CatalogGroup.SqlDataSource', - title: 'App.CatalogSqlDataSource.SqlDataSourceTitle', - description: 'App.CatalogSqlDataSource.SqlDataSourceDescription', + group: 'App.CatalogGroup.Form', + title: 'App.CatalogForm.FormTitle', + description: 'App.CatalogForm.FormDescription', code: `await refs.{{sql}}.save() // await refs.{{sql}}.remove() // await refs.{{sql}}.reload() // refs.{{sql}}.newRecord()`, }, { - group: 'App.CatalogGroup.SqlDataSource', - title: 'App.CatalogSqlDataSource.SqlDataSourceTitle2', - description: 'App.CatalogSqlDataSource.SqlDataSourceDescription2', + group: 'App.CatalogGroup.Form', + title: 'App.CatalogForm.FormTitle2', + description: 'App.CatalogForm.FormDescription2', code: `const record = refs.{{sql}}.getRecord() console.log(record) refs.{{sql}}.setField('name', 'Yeni ad')`, }, { - group: 'App.CatalogGroup.SqlDataSource', - title: 'App.CatalogSqlDataSource.SqlDataSourceTitle3', - description: 'App.CatalogSqlDataSource.SqlDataSourceDescription3', + group: 'App.CatalogGroup.Form', + title: 'App.CatalogForm.FormTitle3', + description: 'App.CatalogForm.FormDescription3', code: `const rows = refs.{{sql}}.getRows() refs.{{sql}}.goToRow(rows.length - 1)`, }, { - group: 'App.CatalogGroup.SqlDataSource', - title: 'App.CatalogSqlDataSource.SqlDataSourceTitle4', - description: 'App.CatalogSqlDataSource.SqlDataSourceDescription4', + group: 'App.CatalogGroup.Form', + title: 'App.CatalogForm.FormTitle4', + description: 'App.CatalogForm.FormDescription4', code: `const record = refs.{{sql}}.getRecord() if (!record.name) { notify('Ad alanı zorunludur', 'warning') @@ -389,9 +389,9 @@ if (!record.name) { await refs.{{sql}}.save()`, }, { - group: 'App.CatalogGroup.SqlDataSource', - title: 'App.CatalogSqlDataSource.SqlDataSourceTitle5', - description: 'App.CatalogSqlDataSource.SqlDataSourceDescription5', + group: 'App.CatalogGroup.Form', + title: 'App.CatalogForm.FormTitle5', + description: 'App.CatalogForm.FormDescription5', code: `console.log(refs.{{sql}}.getMode(), refs.{{sql}}.isBusy(), refs.{{sql}}.getError())`, }, { @@ -425,11 +425,7 @@ console.log(id)`, ] export const getDesignerEventSnippet = (componentName: string, eventName: string) => - // A page saved before the container was renamed still carries the old type, - // and its event scripts deserve the same starting snippet. - DESIGNER_EVENT_SNIPPETS[ - componentName === LEGACY_SQL_DATA_SOURCE_TYPE ? SQL_DATA_SOURCE_TYPE : componentName - ]?.[eventName] || + DESIGNER_EVENT_SNIPPETS[componentName]?.[eventName] || `// ${componentName}.${eventName}(event)\nconsole.log(event)` const COLLECTION_ITEMS_PROPERTY = { name: 'items', @@ -901,18 +897,18 @@ export const PLATFORM_COMPONENTS: DesignerComponentDefinition[] = [ ] /** - * ASP.NET's SqlDataSource + FormView in one component: it owns the four CRUD + * ASP.NET's Form + FormView in one component: it owns the four CRUD * endpoints and acts as a container, so every component dropped inside it can * bind to a column of the Select result and write back through Save/Delete. * The endpoints are configured from the inspector's Data tab, not here. */ -export const SQL_DATA_SOURCE_DEFINITION: DesignerComponentDefinition = { - name: SQL_DATA_SOURCE_TYPE, +export const FORM_DEFINITION: DesignerComponentDefinition = { + name: FORM_TYPE, icon: 'Database', category: 'data', kind: 'layout', toolboxGroup: 'data', - description: 'App.CatalogSqlDataSource.SqlDataSourceDescription', + description: 'App.CatalogForm.FormDescription', acceptsChildren: true, properties: [ // Filled in, the container renders inside a Card whose header carries this @@ -923,7 +919,7 @@ export const SQL_DATA_SOURCE_DEFINITION: DesignerComponentDefinition = { type: 'string', value: '', category: 'properties', - description: 'App.CatalogSqlDataSource.SqlDataSourceTitleDescription', + description: 'App.CatalogForm.FormTitleDescription', }, { name: 'selectEndpoint', @@ -953,6 +949,16 @@ export const SQL_DATA_SOURCE_DEFINITION: DesignerComponentDefinition = { category: 'properties', description: 'App.CatalogString.StringDescription4', }, + // Permission of each command, configured next to its endpoint in the Data + // tab. Empty leaves the command open: a bound URL is then enough for the + // button to appear, which is how every container built so far behaves. + ...FORM_SLOTS.map((slot) => ({ + name: slot.permissionProperty, + type: 'string' as const, + value: '', + category: 'properties' as const, + description: 'App.CatalogForm.FormPermissionDescription', + })), { name: 'keyFieldName', type: 'string', @@ -1011,7 +1017,7 @@ export const SQL_DATA_SOURCE_DEFINITION: DesignerComponentDefinition = { type: 'number', value: 1, category: 'styling', - description: 'App.CatalogSqlDataSource.SqlDataSourceColumnCountDescription', + description: 'App.CatalogForm.FormColumnCountDescription', }, { name: 'gap', @@ -1023,7 +1029,7 @@ export const SQL_DATA_SOURCE_DEFINITION: DesignerComponentDefinition = { // Record lifecycle. `category: 'events'` keeps them out of the Properties // panel and moves them into the JavaScript tab, where they are edited as // scripts rather than as values. - ...SQL_DATA_SOURCE_EVENTS.map((event) => ({ + ...FORM_EVENTS.map((event) => ({ name: event.name, type: 'function' as const, value: '', @@ -1138,7 +1144,7 @@ export const getDesignerCatalog = (customNames: string[] = []): DesignerComponen return [ ...DESIGNER_EXTRAS, - SQL_DATA_SOURCE_DEFINITION, + FORM_DEFINITION, ...PLATFORM_COMPONENTS, ...html, ...ui, diff --git a/ui/src/components/visualDesigner/codeGenerator.ts b/ui/src/components/visualDesigner/codeGenerator.ts index b2f752a3..344a449e 100644 --- a/ui/src/components/visualDesigner/codeGenerator.ts +++ b/ui/src/components/visualDesigner/codeGenerator.ts @@ -5,16 +5,19 @@ import { getDesignerTabSlotValue, getDesignerDataSourceFilters, getDesignerNodeFilters, - getSqlDataSourceColumnCount, + getDesignerNodePermission, + resolveFormPermission, + DESIGNER_PERMISSION_PROP, + getFormColumnCount, getDesignerColumnCaptions, getUsableDesignerColumnLookups, - getSqlDataSourceEndpointId, - getSqlDataSourceKeyField, - getSqlDataSourceKeyParam, - getSqlDataSourceKeySource, - getSqlDataSourceScriptedEvents, + getFormEndpointId, + getFormKeyField, + getFormKeyParam, + getFormKeySource, + getFormScriptedEvents, getSqlFormValueProperty, - hasSqlDataSourceUrlParams, + hasFormUrlParams, isDesignerDateComponent, isDesignerDateOnlyComponent, isDesignerDateProperty, @@ -22,13 +25,13 @@ import { DESIGNER_ICON_PROPERTIES, DESIGNER_SELECT_COMPONENT_PROPERTY, flexRowStyle, - isSqlDataSourceNode, + isFormNode, isValuelessDesignerFilter, DESIGNER_FILTER_DX_OPERATORS, toDesignerFilterParamName, normalizeDesignerKeyList, DESIGNER_DROPDOWN_PLACEHOLDER, - SQL_DATA_SOURCE_SLOTS, + FORM_SLOTS, SQL_DEFAULT_VALUE_PROP, type DesignerDataSource, type DesignerDataSourceFilter, @@ -37,7 +40,7 @@ import { } from './types' /** - * Record scope opened by a SqlDataSource. The record state is named + * Record scope opened by a Form. The record state is named * `data_`, which is exactly what `bindingExpression` emits for a binding * whose `sourceId` is the container's node id — so children bind to columns * through the regular binding machinery and write back through `setterName`. @@ -60,6 +63,40 @@ const safeIdentifier = (value: string) => { */ let refRuntimeEnabled = false +/** + * Wizard permission of the component being generated. It is the base of the + * `Otomatik` mode, so it is set for the whole pass rather than threaded through + * every emitter. Empty for a component no menu opens. + */ +let autoPermissionCode = '' + +/** + * Guard expression of a permission name, or `''` when nothing guards it. An + * unguarded command keeps its previous behaviour: the bound URL alone decides + * whether its button is there. + */ +const permissionGuardExpression = (permission: string) => + permission ? `designerHasPermission(${JSON.stringify(permission)})` : '' + +/** + * Guard of a node's own visibility permission. A component whose data the user + * is not allowed to read is not shown disabled — it is not shown. + */ +const nodePermissionGuard = (node: DesignerNode) => + permissionGuardExpression(getDesignerNodePermission(node)) + +/** True when any command of any Form or any node carries a permission. */ +const documentUsesPermissions = (nodes: DesignerNode[]): boolean => + nodes.some( + (node) => + Boolean(getDesignerNodePermission(node)) || + (isFormNode(node.type) && + FORM_SLOTS.some((slot) => + resolveFormPermission(node, slot, autoPermissionCode), + )) || + documentUsesPermissions(node.children), + ) + /** Prop that turns a component off; react-select spells it differently. */ const getDisabledProperty = (type: string) => (type === 'Select' ? 'isDisabled' : 'disabled') @@ -165,7 +202,7 @@ const getRuntimeStateSpec = (node: DesignerNode): RuntimeStateSpec | null => { } } -/** Prop of a node that the enclosing SqlDataSource writes the record back from. */ +/** Prop of a node that the enclosing Form writes the record back from. */ const getFormScopeField = (node: DesignerNode, formScope?: FormScope) => { if (!formScope) return null const propertyName = getSqlFormValueProperty(node, formScope.sourceId) @@ -220,7 +257,7 @@ const propsToCode = ( const runtimeState = getRuntimeStateSpec(node) const formField = getFormScopeField(node, formScope) const hasBoundInputValue = node.type === 'Input' && Boolean(node.bindings?.value?.sourceId) - // Inside a SqlDataSource the bound value is editable: the change is written to + // Inside a Form the bound value is editable: the change is written to // the record, so the input must not be forced read-only. const hasInputChangeHandler = Boolean(node.events?.onChange?.trim()) || Boolean(formField) const props = Object.entries(node.props) @@ -228,12 +265,14 @@ const propsToCode = ( ([key, value]) => key !== 'children' && key !== runtimeState?.propertyName && - // Seeds a new SqlDataSource record; it is not a prop of the component. + // Seeds a new Form record; it is not a prop of the component. key !== SQL_DEFAULT_VALUE_PROP && + // Decides whether the node is rendered; it is not a prop of the component. + key !== DESIGNER_PERMISSION_PROP && !omitted.has(key) && !(node.type === 'Checkbox' && key === 'defaultChecked' && 'checked' in node.props) && !(hasBoundInputValue && !hasInputChangeHandler && key === 'readOnly') && - // A field the SqlDataSource writes back must stay editable, whatever the + // A field the Form writes back must stay editable, whatever the // node happens to carry from the toolbox defaults. !(formField && (key === 'readOnly' || key === 'disabled')) && !node.bindings?.[key]?.sourceId && @@ -352,14 +391,18 @@ const sqlIdentifiers = (node: DesignerNode) => { recordRef: `sqlRecordRef_${identifier}`, modeRef: `sqlModeRef_${identifier}`, slot: (property: string) => `sql${property.replace(/Endpoint$/, '')}_${identifier}`, + // Permission flags of the four commands. Always declared, so the markup can + // reference them without knowing whether an endpoint carries a permission. + can: (property: string) => + `sqlCan${property.replace(/Endpoint$/, '').replace(/^./, (letter) => letter.toUpperCase())}_${identifier}`, } } -/** Handler name of a scripted SqlDataSource event, or `''` when it has no script. */ +/** Handler name of a scripted Form event, or `''` when it has no script. */ const sqlEventHandler = (node: DesignerNode, eventName: string) => node.events?.[eventName]?.trim() ? `handle_${safeIdentifier(node.id)}_${eventName}` : '' -const hasSqlEvents = (node: DesignerNode) => getSqlDataSourceScriptedEvents(node).length > 0 +const hasSqlEvents = (node: DesignerNode) => getFormScriptedEvents(node).length > 0 /** * `await`ed call of an event script, emitted only when the event carries one. @@ -390,7 +433,7 @@ const sqlErrorEventCall = (node: DesignerNode, action: string) => { /** * Runtime expression a filter reads its value from. A `record` filter addresses - * another SqlDataSource by its ref, which is what turns a master container and a + * another Form by its ref, which is what turns a master container and a * detail Grid into a filtered pair without a line of script. */ const filterValueExpression = ( @@ -458,21 +501,21 @@ const sqlSlotLiteral = ( dataSources: DesignerDataSource[], ) => { const source = dataSources.find( - (candidate) => candidate.id === getSqlDataSourceEndpointId(node, property), + (candidate) => candidate.id === getFormEndpointId(node, property), ) if (!source?.url.trim()) return 'null' return `{ url: ${JSON.stringify(source.url.trim())}, method: ${JSON.stringify(method)}, responsePath: ${JSON.stringify(source.responsePath || '')} }` } -const sqlDataSourceHooks = ( +const formHooks = ( node: DesignerNode, dataSources: DesignerDataSource[], sqlRecordByRef: Map, ) => { const names = sqlIdentifiers(node) - const keyField = getSqlDataSourceKeyField(node) + const keyField = getFormKeyField(node) const selectSource = dataSources.find( - (candidate) => candidate.id === getSqlDataSourceEndpointId(node, 'selectEndpoint'), + (candidate) => candidate.id === getFormEndpointId(node, 'selectEndpoint'), ) const selectFilters = filterEntriesExpression(selectSource, sqlRecordByRef) // The Select endpoint already owns a fetch hook, so the container reuses that @@ -484,16 +527,27 @@ const sqlDataSourceHooks = ( selectSource?.method === 'GET' && selectSource.url.trim().startsWith('/api/') ? `setData_${safeIdentifier(selectSource.id)}` : '' - const keySource = getSqlDataSourceKeySource(node) + const keySource = getFormKeySource(node) const rowsExpression = `toSqlRows(${selectData}, ${JSON.stringify(String(node.props.collectionPath ?? ''))})` - const slots = SQL_DATA_SOURCE_SLOTS.map( + const slots = FORM_SLOTS.map( (slot) => ` const ${names.slot(slot.property)} = ${sqlSlotLiteral(node, slot.property, slot.method, dataSources)}`, ).join('\n') + // One flag per command. An endpoint without a permission is always allowed, so + // a container configured before permissions existed behaves as it always did. + const permissions = FORM_SLOTS.map( + (slot) => + ` const ${names.can(slot.property)} = ${ + permissionGuardExpression( + resolveFormPermission(node, slot, autoPermissionCode), + ) || 'true' + }`, + ).join('\n') return `${slots} +${permissions} const ${names.key} = ${JSON.stringify(keyField)} - const ${names.keyParam} = ${JSON.stringify(getSqlDataSourceKeyParam(node))} + const ${names.keyParam} = ${JSON.stringify(getFormKeyParam(node))} const [${names.record}, ${names.setRecord}] = React.useState({}) const [${names.original}, ${names.setOriginal}] = React.useState({}) const [${names.mode}, ${names.setMode}] = React.useState("edit") @@ -601,11 +655,14 @@ ${ const ${names.reload} = React.useCallback(async () => {${ setSelectData ? ` + // Without the Read permission the container is not rendered at all; the + // request is dropped here so it is not fired for a form nobody can see. + if (!${names.can('selectEndpoint')}) return if (!${names.selectUrl}) return // Calling a URL that still holds a placeholder is a guaranteed 400, so the // missing key is reported in the component instead. if (hasSqlUrlParams(${names.selectUrl})) { - throw new Error("Select endpointi " + ${names.keyParam} + " parametresini bekliyor; sayfa adresinde bulunamadı.") + throw new Error(${translateExpression('::App.Form.MissingKeyParam', `{ 0: ${names.keyParam} }`)}) } const response = await apiService.fetchData({ url: ${names.selectUrl}, method: "GET" }) ${setSelectData}(getByPath(response.data, ${names.slot('selectEndpoint')}.responsePath))` @@ -626,6 +683,7 @@ ${ } }, [${names.reload}]) const ${names.create} = React.useCallback(${sqlEventHandler(node, 'onNewRecord') ? 'async ' : ''}() => { + if (!${names.can('insertEndpoint')}) return ${names.setError}("") // Defaults are resolved on every New, so an @today column carries the day the // record was opened rather than the day the component was generated. @@ -646,14 +704,17 @@ ${ const ${names.save} = async () => { const isNew = ${names.mode} === "new" const target = isNew ? ${names.slot('insertEndpoint')} : ${names.slot('updateEndpoint')} + // The button is already hidden without the permission; a script calling save + // through a ref must not be able to walk around it either. + if (!(isNew ? ${names.can('insertEndpoint')} : ${names.can('updateEndpoint')})) return if (!target) { - ${names.setError}(isNew ? "Insert için POST endpointi tanımlı değil." : "Update için PUT endpointi tanımlı değil.") + ${names.setError}(isNew ? ${translateExpression('::App.Form.InsertEndpointMissing')} : ${translateExpression('::App.Form.UpdateEndpointMissing')}) return } // Update carries only the edited columns plus the key; Insert sends the record. const payload = isNew ? ${names.record} : toSqlChanges(${names.original}, ${names.record}, ${names.key}) if (!isNew && Object.keys(payload).filter((column) => column.toLowerCase() !== String(${names.key}).toLowerCase()).length === 0) { - ${names.setError}("Kaydedilecek bir değişiklik yok.") + ${names.setError}(${translateExpression('::App.Form.NoChanges')}) return }${ sqlEventHandler(node, 'onBeforeSave') @@ -691,7 +752,7 @@ ${ } } const ${names.remove} = async () => { - if (!${names.slot('deleteEndpoint')} || !${names.hasKey} || ${names.mode} === "new") return${ + if (!${names.can('deleteEndpoint')} || !${names.slot('deleteEndpoint')} || !${names.hasKey} || ${names.mode} === "new") return${ sqlEventHandler(node, 'onBeforeDelete') ? ` // Asked before the confirm dialog, so a script can block the delete without @@ -700,7 +761,7 @@ ${ : '' } // Deleting cannot be undone from the form, so it always asks first. - const confirmMessage = ${names.key} + " = " + readSqlField(${names.record}, ${names.key}) + " kaydı silinecek.\\n\\nOnaylıyor musunuz?" + const confirmMessage = ${translateExpression('::App.Form.DeleteConfirm', `{ 0: ${names.key} + " = " + readSqlField(${names.record}, ${names.key}) }`)} if (typeof window !== "undefined" && !window.confirm(confirmMessage)) return ${names.setBusy}(true) ${names.setError}("") @@ -733,13 +794,22 @@ ${ }` } +/** + * `translate()` call for a fixed localization key. The container's own chrome — + * its buttons, its mode badge, its error messages — is platform text, so it + * follows the active language exactly like the rest of the application instead + * of being frozen in the language the component was designed in. + */ +const translateExpression = (key: string, params?: string) => + `translate(${JSON.stringify(key)}${params ? `, ${params}` : ''})` + const SQL_TOOLBAR_BUTTON_CLASS = 'rounded-md px-3 py-1.5 text-xs font-semibold transition disabled:cursor-not-allowed disabled:opacity-40' -const sqlDataSourceToCode = (node: DesignerNode, level: number, itemVariable?: string) => { +const formToCode = (node: DesignerNode, level: number, itemVariable?: string) => { const names = sqlIdentifiers(node) const formScope: FormScope = { sourceId: node.id, setterName: names.setField } - const columnCount = getSqlDataSourceColumnCount(node) + const columnCount = getFormColumnCount(node) const gap = Number(node.props.gap) || 0 // Children sit one level deeper once they are wrapped in the column grid. const children = node.children @@ -750,19 +820,24 @@ const sqlDataSourceToCode = (node: DesignerNode, level: number, itemVariable?: s // The toolbar and the error line stay in the outer column, so only the fields // are laid out side by side. A single column emits no wrapper at all, which // keeps the markup of every page saved before the setting existed unchanged. - const gridStyle = `{ display: "grid", gridTemplateColumns: "repeat(${columnCount}, minmax(0, 1fr))", gap: ${gap}, alignItems: "start" }` + // Columns come from utility classes rather than from an inline + // `gridTemplateColumns`, because an inline value cannot carry a breakpoint: a + // two column form has to fall back to a single column on a phone, where two + // fields side by side leave neither of them readable. + const gridClassName = `grid grid-cols-1 md:grid-cols-${columnCount}` + const gridStyle = `{ gap: ${gap}, alignItems: "start" }` const content = columnCount > 1 - ? `${indent(`
`, level + 2)}\n${children}\n${indent('
', level + 2)}` + ? `${indent(`
`, level + 2)}\n${children}\n${indent('
', level + 2)}` : children const plainButtonClass = `${SQL_TOOLBAR_BUTTON_CLASS} border border-slate-300 text-slate-600 hover:border-sky-400 hover:text-sky-700 dark:border-slate-700 dark:text-slate-300` // Navigation appears on its own whenever there is more than one record to walk. const navigation = ` ${indent(`{${names.rows}.length > 1 ? (`, level + 3)} ${indent('<>', level + 4)} -${indent(``, level + 5)} +${indent(``, level + 5)} ${indent(`{\`\${${names.index} + 1} / \${${names.rows}.length}\`}`, level + 5)} -${indent(``, level + 5)} +${indent(``, level + 5)} ${indent('', level + 5)} ${indent('', level + 4)} ${indent(') : null}', level + 3)}` @@ -771,11 +846,11 @@ ${indent(') : null}', level + 3)}` ? '' : ` ${indent('
', level + 2)}${navigation} -${indent(``, level + 3)} -${indent(``, level + 3)} -${indent(``, level + 3)} -${indent(``, level + 3)} -${indent(`{${names.mode} === "new" ? "Yeni kayıt" : "Düzenleme"}`, level + 3)} +${indent(`{${names.slot('insertEndpoint')} && ${names.can('insertEndpoint')} ? : null}`, level + 3)} +${indent(`{(${names.mode} === "new" ? ${names.slot('insertEndpoint')} && ${names.can('insertEndpoint')} : ${names.slot('updateEndpoint')} && ${names.can('updateEndpoint')}) ? : null}`, level + 3)} +${indent(`{${names.slot('deleteEndpoint')} && ${names.can('deleteEndpoint')} ? : null}`, level + 3)} +${indent(`{${names.slot('selectEndpoint')} && ${names.can('selectEndpoint')} ? : null}`, level + 3)} +${indent(`{${names.mode} === "new" ? ${translateExpression('::App.Platform.NewRecord')} : ${translateExpression('::App.Platform.Editing')}}`, level + 3)} ${indent('
', level + 2)}` const error = ` ${indent(`{${names.error} ?
{${names.error}}
: null}`, level + 2)}` @@ -784,16 +859,26 @@ ${indent(`{${names.error} ?
${headerExtra}}>` + ? `${headerExtra}}>` : '' - return `${indent(cardStart, level)} -${indent(`
`, level + 1)} + // Read permission decides whether the container exists on the page at all: a + // form whose records the user may not see is not rendered empty, it is not + // rendered. + return `${indent(`{${names.can('selectEndpoint')} ? (`, level)} +${indent(cardStart, level + 1)} +${indent(`
`, level + 2)} ${content}${toolbar}${error} -${indent('
', level + 1)} -${indent('', level)}` +${indent('
', level + 2)} +${indent('
', level + 1)} +${indent(') : null}', level)}` } /** @@ -803,15 +888,22 @@ ${indent('
', level)}` * JSX child. */ const withVisibilityGuard = (code: string, node: DesignerNode, level: number) => { - if (!isRefControlled(node)) return code + // A component the user has no permission for is not rendered — that guard sits + // next to the ref guard, so both share one wrapper. + const permissionGuard = nodePermissionGuard(node) + const conditions = [ + ...(isRefControlled(node) ? [`!designerRefHidden(${JSON.stringify(node.ref)})`] : []), + ...(permissionGuard ? [permissionGuard] : []), + ] + if (!conditions.length) return code const trimmed = code.trim() const body = trimmed.startsWith('{') && trimmed.endsWith('}') ? code.replace(/^(\s*)\{/, '$1(').replace(/\}(\s*)$/, ')$1') : code - return `${indent(`{designerRefHidden(${JSON.stringify(node.ref)}) ? null : (`, level)} + return `${indent(`{${conditions.join(' && ')} ? (`, level)} ${body} -${indent(')}', level)}` +${indent(') : null}', level)}` } const nodeToCode = ( @@ -821,7 +913,12 @@ const nodeToCode = ( formScope?: FormScope, ): string => withVisibilityGuard( - renderNodeToCode(node, isRefControlled(node) ? level + 1 : level, itemVariable, formScope), + renderNodeToCode( + node, + isRefControlled(node) || nodePermissionGuard(node) ? level + 1 : level, + itemVariable, + formScope, + ), node, level, ) @@ -832,7 +929,7 @@ const renderNodeToCode = ( itemVariable?: string, formScope?: FormScope, ): string => { - if (isSqlDataSourceNode(node.type)) return sqlDataSourceToCode(node, level, itemVariable) + if (isFormNode(node.type)) return formToCode(node, level, itemVariable) if (node.type === 'Spacer') { return indent( `