diff --git a/.github/instructions/lowcode-reference.instructions.md b/.github/instructions/lowcode-reference.instructions.md
index 3a878ff2..78086d32 100644
--- a/.github/instructions/lowcode-reference.instructions.md
+++ b/.github/instructions/lowcode-reference.instructions.md
@@ -774,7 +774,7 @@ sessizce yok sayılır:
| --- | --- | --- |
| `GridBoxOptionsDto` | `dxGridBox` | `Columns[]` (ilk kolon `Key` ile aynı adda olmalı), `SelectionMode` (`"single"`), `FilterRowVisible` (`false`), `AcceptCustomValue` (`false`), `ShowClearButton` (`true`), `Height` (`250`), `Width` (`500`) |
| `TagBoxOptionsDto` | `dxTagBox` | `ShowSelectionControls` (`true`), `MaxDisplayedTags` (`3`), `ShowMultiTagOnly` (`false`), `ApplyValueMode` (`"useButtons"`), `SearchEnabled` (`true`), `AcceptCustomValue` (`false`), `ShowClearButton` (`true`) — değer tek kolonda `\|` ile saklanır, bkz. §4.3 |
-| `ImageUploadOptionsDto` | `dxImageUpload` · `dxImageViewer` | `UploadUrl`, `Accept`, `Multiple`, `MaxFileSize` (bayt), `Width`, `Height` — hepsi nullable, varsayılanı yok |
+| `ImageUploadOptionsDto` | `dxImageUpload` · `dxImageViewer` | `UploadUrl`, `Accept`, `Multiple`, `MaxFileSize` (bayt), `Width`, `Height`, `Capture` (`environment`/`user` — mobilde dosya seçici yerine **yalnızca** kamerayı açar; boş bırakılırsa dokunmatik cihazda dosya seçicinin yanına ayrı bir kamera butonu gelir) — hepsi nullable, varsayılanı yok |
Çözüm **yalnızca alanın `EditorType2` değeri eşleşiyorsa** yapılır: `dxTextBox` bir alana
`columns` yazmak `GridBoxOptions`'ı doldurmaz. Ayrıca `ListFormField.EditorOptions`'ın ham
@@ -1017,6 +1017,7 @@ alır):
| `Knob` | `value`/`defaultValue` (`0`), `min`/`max` (`0/100`), `step` (`1`), `size` (`100`), `strokeWidth` (`14`), `valueColor`/`rangeColor`/`textColor`, `valueTemplate` (`'{value}'`), `showValue` (`true`), `readOnly`, `disabled`, `name` |
| `Rate` | `count` (`5`), `value`/`defaultValue`, `allowHalf` (`false`), `allowClear` (`true`), `color` (`amber`), `size` (`20`), `gap` (`4`), `tooltips[]`, `disabled`, `readOnly` |
| `Upload` | `accept`, `multiple`, `draggable` (`false`), `uploadLimit`, `showList` (`true`), `tip`, `fileList[]`, `disabled` — dosyanın **gerçek** yüklenmesi `beforeUpload`/`onChange` script'inde `api.post` ile yapılır |
+| `BarcodeScanField` | `label`, `name` (`'barcode'`), `placeholder`, `buttonText`, `continuous` (`false` — açıkken diyalog okumadan sonra kapanmaz), `readOnly` (`true`), `className`; **olay**: `onScan(event)` — `event` okunan kodun metin hâli (Event workspace'te Input'un `onChange`'i gibi düzenlenir). Kamerayı `BarcodeDetector` (Android/Chromium) ya da lazy `zxing-wasm` (iOS/Firefox) ile okur — 1D + QR. **HTTPS şart**; iOS'ta yüklü PWA içinde yalnız 16.4+ çalışır, bileşen aksini kullanıcıya söyler |
**Gösterim/geri bildirim bileşenleri:**
diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md
index 4c1a0f41..871fd2b5 100644
--- a/.github/instructions/lowcode.instructions.md
+++ b/.github/instructions/lowcode.instructions.md
@@ -806,7 +806,7 @@ anahtarları listeler; ortak anahtarlar (§4.3.3) her editörde geçerlidir.
| `dxSlider` | Tek değerli kaydırıcı | Sayı | `min`, `max`, `tooltip.showMode` |
| `dxRangeSlider` | Aralık kaydırıcı | Sayı | `min`, `max`, `showRange` |
| `dxHtmlEditor` | Zengin metin | String (16) | `toolbar.items`, `toolbar.multiline`, `valueType` (`html`/`markdown`), `mediaResizing.enabled`, `imageUpload.uploadUrl`, `imageUpload.fileUploadMode` |
-| `dxImageUpload` | Görsel yükleme (platform) | String (16) | `uploadUrl`\*, `accept`\*, `multiple`\*, `maxFileSize`\*, `width`\*, `height`\* |
+| `dxImageUpload` | Görsel yükleme (platform) | String (16) | `uploadUrl`\*, `accept`\*, `capture`\*, `multiple`\*, `maxFileSize`\*, `width`\*, `height`\* |
| `dxImageViewer` | Görsel gösterimi (platform) | String (16) | `width`, `height` |
`\*` ile işaretli anahtarlar backend'de **tipli DTO'ya** çözülür:
@@ -1537,6 +1537,12 @@ Kurallar:
her diyalog `open` + `onDialogClose` prop'larını alır.
Kendi ekranını açmak istiyorsan **Adres** modunu kullan (custom component route'u ya da
`/admin/list/{ListFormCode}`).
+- `DialogName: "BarcodeScanDialog"` **toolbar butonuna özeldir** (`ButtonPosition: 1`) ve sürekli
+ barkod taramasını açar: okunan her kod listeye yeni bir satır olarak eklenir.
+ `DialogParameters`'ta `field` zorunludur (barkodun yazılacağı kolon); kalan anahtarlar her
+ satıra sabit değer olarak yazılır (`{"field":"Barcode","WarehouseId":"1"}`). Satır ekleme
+ ekranın kendi `InsertServiceAddress` ucundan geçer — yetki ve kiracı izolasyonu normal satır
+ eklemeyle aynıdır. Kamera HTTPS ister.
- Onay akışının kendi butonları (Onayla/Reddet) `WorkflowJson` üzerinden **otomatik** gelir;
onları `CommandColumnJson` ile tekrar tanımlama.
@@ -1719,6 +1725,7 @@ bağlıdır.
| interaction | `ask` | message | `if (!ask('Emin misiniz?')) return` — vazgeçilirse alan eski değerine döner ve script durur |
| interaction | `openUrl` | url, target? | `openUrl('/report?id={Id}')` · `_self` verilirse ikinci argüman eklenir |
| integration | `apiToField` | target, url, path? | `set('T', await api('/api/x/{value}', 'data.name'))` |
+| integration | `location` | lat, lng, accuracy? | `await geoTo('Enlem', 'Boylam')` — cihaz konumunu alanlara yazar; HTTPS ve kullanıcı izni ister |
| integration | `custom` | code | Satırın kendisi. Son çare; sondaki `;` atılır |
`sum` ve `clear` çoklu alan alır (`fields` listesi); diğerlerinde her parametre tek alandır.
@@ -1791,6 +1798,7 @@ Script `AsyncFunction` ile derlenir, aşağıdaki adlar parametre olarak enjekte
| `revert()` | Alanı değişiklik öncesi değerine döndürür (editöre de yazar) |
| `openUrl('/adres', '_blank')` | Yeni sekme/pencere |
| `await api(url, 'data.name', options?)` | Ham `fetch`. **Kimlik doğrulama başlığı eklemez**; korumalı bir uç için Custom Endpoint tarafında erişim açık olmalıdır |
+| `await geoTo('Lat', 'Lng', 'Acc'?)` | Cihaz konumunu alanlara yazar. İzin yoksa/okunamazsa alanlara **dokunmaz** ve uyarı gösterir, `false` döner |
| `$` | Tüm yardımcıların tek nesne hâli |
Eski (uzun) script'ler bozulmasın diye geriye dönük uyum vardır: script içinde yardımcı adlarıyla
@@ -1841,7 +1849,7 @@ if (isReady) {
| copy / setValue / clear | `Copy(sourcePath, target)` · `SetValue(target, text)` · `Clear("Il", "Ilce")` |
| readOnly | `ReadOnly(target, invert)` |
| notify / ask / openUrl | `Notify(message)` · `Ask(message)` · `OpenUrl(url, target)` |
-| apiToField / custom | `ApiToField(target, url, responsePath)` · `Custom(code)` |
+| apiToField / location / custom | `ApiToField(target, url, responsePath)` · `Location(latField, lngField, accuracyField)` · `Custom(code)` |
Akıcı ekler: `.When(...)` (hepsi sağlanmalı), `.WhenAny(...)` (biri yeterli),
`.OnChange()` / `.OnOpen()` / `.OnOpenAndChange()` / `.WithTrigger(EditorScriptTrigger.X)`,
@@ -1938,7 +1946,7 @@ Dizi elemanları yaprak sayılmaz; `toolbar.items` tek bir `json` alanıdır.
| Açılır liste | `searchEnabled`\*, `searchMode`, `searchExpr`, `searchTimeout`, `minSearchLength`, `showDataBeforeSearch`, `acceptCustomValue`\*, `noDataText`, `deferRendering`, `wrapItemText`, `dropDownOptions.width/height/hideOnOutsideClick` | `dxSelectBox`, `dxLookup`, `dxTagBox`, `dxDropDownBox`, `dxGridBox`, `dxAutocomplete` |
| TagBox | `showSelectionControls`\*, `maxDisplayedTags`\*, `showMultiTagOnly`\*, `applyValueMode`\*, `multiline`, `hideSelectedItems` | `dxTagBox` |
| GridBox | `columns`\*, `selectionMode`\*, `filterRowVisible`\* | `dxGridBox` |
-| Görsel | `uploadUrl`\*, `fileFieldName`, `accept`\*, `multiple`\*, `maxFileSize`\* | `dxImageUpload`, `dxImageViewer` |
+| Görsel | `uploadUrl`\*, `fileFieldName`, `accept`\*, `capture`\*, `multiple`\*, `maxFileSize`\* | `dxImageUpload`, `dxImageViewer` |
| Seçim | `text` (`dxCheckBox`), `switchedOnText`/`switchedOffText` (`dxSwitch`), `layout` (`dxRadioGroup`), `editAlphaChannel`/`keyStep` (`dxColorBox`) | ilgili editör |
| Kaydırıcı | `min`, `max`, `step`, `tooltip.enabled`, `tooltip.showMode`, `showRange` | `dxSlider`, `dxRangeSlider` |
| HTML | `valueType`, `toolbar.multiline`, `toolbar.items`, `mediaResizing.enabled`, `imageUpload.fileUploadMode`, `imageUpload.uploadUrl` | `dxHtmlEditor` |
@@ -1966,6 +1974,8 @@ Preset mevcut JSON ile **birleşir**, diğer ayarları silmez; aynı anahtar var
| Çoklu görsel | `width`, `height`, `multiple`, `accept:image/*` |
| HTML tam araç çubuğu | `toolbar.items` (tam liste), `toolbar.multiline`, `mediaResizing`, `imageUpload` |
| Yükseklik 100 / 200 | `{"height":100}` · `{"height":200}` |
+| Barkod okutma butonu | `buttons[]` — kamera ikonlu buton; `onClick` = `"@SCAN_BARCODE"` token'ı. Yalnız `dxTextBox`. Token'ı `editingForm.ts` kamera diyaloğuna bağlar, okunan kod alana yazılır. HTTPS şart. |
+| Kamerayla foto çek | `{"accept":"image/*","capture":"environment"}` — yalnız `dxImageUpload`. Mobilde dosya seçici yerine **yalnızca** kamera açılır (`user` = ön kamera); masaüstünde yok sayılır. Ayarlanmazsa dokunmatik cihazda dosya seçicinin yanına ayrı bir kamera butonu gelir, yani ikisi birden kullanılabilir — bu kalıp kamerayı **zorunlu** kılmak içindir. |
### 6.5 Seeder tarafı (C#)
diff --git a/README.md b/README.md
index b46e1a1f..93ab7ec9 100644
--- a/README.md
+++ b/README.md
@@ -443,7 +443,7 @@ buradaki karşılığı da güncellenmelidir.
| Tip | Kullanım |
| --- | --- |
-| `EditorScript` | `Multiply`, `Subtract`, `Percent`, `Sum`, `Formula`, `Today`, `Days`, `Hours`, `Copy`, `SetValue`, `Clear`, `ReadOnly`, `Notify`, `Ask`, `OpenUrl`, `ApiToField`, `Custom` → `EditorScript.Build(rules)` |
+| `EditorScript` | `Multiply`, `Subtract`, `Percent`, `Sum`, `Formula`, `Today`, `Days`, `Hours`, `Copy`, `SetValue`, `Clear`, `ReadOnly`, `Notify`, `Ask`, `OpenUrl`, `ApiToField`, `Location`, `Custom` → `EditorScript.Build(rules)` |
| `EditorScriptRule` | `.When(...)` / `.WhenAny(...)` koşulları, `.OnChange()` / `.OnOpen()` / `.OnOpenAndChange()` tetikleyicisi; `string`'e örtük dönüşür |
| `EditorScriptCondition` | `Is`, `IsNot`, `Contains`, `IsEmpty`, `IsNotEmpty`, `GreaterThan`, `LessThan`, `IsTrue`, `IsFalse`, `Always` |
| `EditorOptions` | Hazır başlangıçlar: `Disabled()`, `ReadOnly()`, `ShowClearButton()`, `Multiline(h)`, `Number(precision)`, `Percent()`, `Date()`, `DateTime()`, `Time(interval)`, `Phone()`, `Slider()`, `ImageUpload()`, `Html(h)` |
@@ -1019,8 +1019,8 @@ dosyası yoktur; liste her `npm run build` / `npm start` ile tazelenir.
**Katalog düğümleri.** Kanvasın doğrudan çizdiği, derlenen bileşen listesinde yer almayan
bileşenler tek bir sözlükte durur (`RUNTIME_NODE_COMPONENTS`): kanvas onu `VisualCanvas` içinde,
üretilen kod ise runtime kapsamı üzerinden kullanır — iki taraf aynı kaynaktan beslendiği için
-tasarım ve çalışma zamanı ayrışamaz. Sözlükte `PlatformIcon` ve public site parçaları
-(`components/publicSite`) bulunur. Yeni bir düğüm eklerken: bileşen `components/publicSite` (ya da
+tasarım ve çalışma zamanı ayrışamaz. Sözlükte `PlatformIcon`, `BarcodeScanField`
+(`components/shared/BarcodeScanner`) ve public site parçaları (`components/publicSite`) bulunur. Yeni bir düğüm eklerken: bileşen `components/publicSite` (ya da
uygun klasör) → `runtimeNodes.ts` sözlüğü → `catalog.ts` tanımı (`kind: 'custom'`) → `LanguagesData.json`
açıklama anahtarı.
diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/ImageUploadOptionsDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/ImageUploadOptionsDto.cs
index 17878adf..9c2bdae0 100644
--- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/ImageUploadOptionsDto.cs
+++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/ImageUploadOptionsDto.cs
@@ -8,4 +8,10 @@ public class ImageUploadOptionsDto
public long? MaxFileSize { get; set; }
public int? Width { get; set; }
public int? Height { get; set; }
+
+ ///
+ /// Mobil cihazda dosya secici yerine dogrudan kamerayi acar:
+ /// "environment" arka, "user" on kamera. Bos birakilirsa dosya secici acilir.
+ ///
+ public string Capture { get; set; }
}
diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json
index de7edf5b..dec99ce7 100644
--- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json
+++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json
@@ -28895,6 +28895,198 @@
"key": "App.Listform.ListformField.OrderId",
"en": "Order",
"tr": "Sipariş"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.Title",
+ "en": "Scan barcode",
+ "tr": "Barkod okut"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.Starting",
+ "en": "Starting the camera...",
+ "tr": "Kamera açılıyor..."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.Aim",
+ "en": "Hold the barcode inside the frame.",
+ "tr": "Barkodu çerçevenin içinde tutun."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.PermissionDenied",
+ "en": "Camera permission was denied. Allow camera access for this site and try again.",
+ "tr": "Kamera izni verilmedi. Bu site için kamera erişimine izin verip tekrar deneyin."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.InsecureContext",
+ "en": "The camera is only available over HTTPS.",
+ "tr": "Kamera yalnızca HTTPS üzerinden kullanılabilir."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.StandaloneIos",
+ "en": "On this iOS version the camera does not work in the installed app. Open the page in Safari.",
+ "tr": "Bu iOS sürümünde kamera yüklü uygulama içinde çalışmıyor. Sayfayı Safari'de açın."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.CameraError",
+ "en": "The camera could not be started.",
+ "tr": "Kamera başlatılamadı."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.SaveFailed",
+ "en": "The scanned code could not be saved.",
+ "tr": "Okunan kod kaydedilemedi."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.ScannedCount",
+ "en": "Scanned",
+ "tr": "Okunan"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.NotConfigured",
+ "en": "The barcode button is not configured: a target field and an insert address are required.",
+ "tr": "Barkod butonu yapılandırılmamış: hedef alan ve ekleme adresi gerekli."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.Placeholder",
+ "en": "Scan a barcode",
+ "tr": "Barkod okutun"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BarcodeScanner.Scan",
+ "en": "Scan",
+ "tr": "Okut"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.PresetBarcodeScan.BarcodeScanLabel",
+ "en": "Barcode scan button",
+ "tr": "Barkod okutma butonu"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.PresetBarcodeScan.BarcodeScanDescription",
+ "en": "Adds a camera button next to the text box; the scanned code is written into the field.",
+ "tr": "Metin kutusunun yanına kamera butonu ekler; okunan kod alana yazılır."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.CatalogBarcodeScanField.BarcodeScanFieldDescription",
+ "en": "Text field with a camera button that reads barcodes and QR codes.",
+ "tr": "Barkod ve QR okuyan kamera butonlu metin alanı."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.Geolocation.Unsupported",
+ "en": "This device does not provide location information.",
+ "tr": "Bu cihaz konum bilgisi vermiyor."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.Geolocation.Failed",
+ "en": "Location could not be read. Allow location access and try again.",
+ "tr": "Konum okunamadı. Konum erişimine izin verip tekrar deneyin."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.ScriptBuilderLocation.LocationLabel",
+ "en": "Write device location",
+ "tr": "Cihaz konumunu yaz"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.ScriptBuilderLocation.LocationSummary",
+ "en": "Reads the device location once and writes it into the latitude/longitude fields. Requires HTTPS and the user's permission.",
+ "tr": "Cihaz konumunu tek seferde okur ve enlem/boylam alanlarına yazar. HTTPS ve kullanıcı izni gerekir."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.ScriptBuilderLocation.LatLabel",
+ "en": "Latitude field",
+ "tr": "Enlem alanı"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.ScriptBuilderLocation.LngLabel",
+ "en": "Longitude field",
+ "tr": "Boylam alanı"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.ScriptBuilderLocation.AccuracyLabel",
+ "en": "Accuracy field (m)",
+ "tr": "Doğruluk alanı (m)"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.ScriptBuilderLocation.AccuracyHelp",
+ "en": "Optional. The accuracy radius reported by the device, in metres.",
+ "tr": "İsteğe bağlı. Cihazın bildirdiği doğruluk yarıçapı, metre."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.ScriptBuilderDescribe.Location",
+ "en": "Writes the device location into {lat} and {lng}.",
+ "tr": "Cihaz konumunu {lat} ve {lng} alanlarına yazar."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.SpecCapture.Help",
+ "en": "On mobile, opens the camera directly instead of the file picker. Leave empty for the file picker.",
+ "tr": "Mobilde dosya seçici yerine doğrudan kamerayı açar. Boş bırakılırsa dosya seçici açılır."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.SpecCapture.Environment",
+ "en": "Rear camera",
+ "tr": "Arka kamera"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.SpecCapture.User",
+ "en": "Front camera",
+ "tr": "Ön kamera"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.PresetPhotoCapture.PhotoCaptureLabel",
+ "en": "Take photo with camera",
+ "tr": "Kamerayla foto çek"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.PresetPhotoCapture.PhotoCaptureDescription",
+ "en": "accept: image/* + capture: environment — on mobile the rear camera opens directly.",
+ "tr": "accept: image/* + capture: environment — mobilde doğrudan arka kamera açılır."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.CatalogBarcodeScanField.OnScanDescription",
+ "en": "Runs on every successful scan; event is the scanned code as text.",
+ "tr": "Her başarılı okumada çalışır; event okunan kodun metin halidir."
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.ImageUpload.Upload",
+ "en": "Upload image",
+ "tr": "Görsel yükle"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.ImageUpload.TakePhoto",
+ "en": "Take photo with camera",
+ "tr": "Kamerayla fotoğraf çek"
}
]
}
diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorScript.cs b/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorScript.cs
index ccef837f..dfd98da1 100644
--- a/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorScript.cs
+++ b/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorScript.cs
@@ -136,6 +136,14 @@ public static class EditorScript
public static EditorScriptRule ApiToField(string target, string url, string responsePath = null)
=> new EditorScriptRule("apiToField").Set("target", target).Set("url", url).Set("path", responsePath);
+ ///
+ /// Cihazin konumunu alanlara yazar. accuracyField bos birakilirsa dogruluk
+ /// yarcapi yazilmaz. Kamera gibi konum da HTTPS ister.
+ ///
+ public static EditorScriptRule Location(string latField, string lngField, string accuracyField = null)
+ => new EditorScriptRule("location")
+ .Set("lat", latField).Set("lng", lngField).Set("accuracy", accuracyField);
+
/// Serbest JS satiri. Runtime yardimcilari (get/set/num/...) burada da kullanilabilir.
public static EditorScriptRule Custom(string code)
=> new EditorScriptRule("custom").Set("code", code);
diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorScriptRule.cs b/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorScriptRule.cs
index 9815c538..2e490b5e 100644
--- a/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorScriptRule.cs
+++ b/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorScriptRule.cs
@@ -29,6 +29,7 @@ public sealed class EditorScriptRule
["ask"] = new[] { "message" },
["openUrl"] = new[] { "url" },
["apiToField"] = new[] { "target", "url" },
+ ["location"] = new[] { "lat", "lng" },
["custom"] = new[] { "code" }
};
@@ -59,6 +60,7 @@ public sealed class EditorScriptRule
["ask"] = EditorScriptTrigger.Change,
["openUrl"] = EditorScriptTrigger.Change,
["apiToField"] = EditorScriptTrigger.Change,
+ ["location"] = EditorScriptTrigger.Change,
["custom"] = EditorScriptTrigger.Change
};
@@ -382,6 +384,10 @@ public sealed class EditorScriptRule
return "set(" + q(Param("target")) + ", await api(" + q(Param("url")) +
(Param("path").Length > 0 ? ", " + q(Param("path")) : string.Empty) + "))";
+ case "location":
+ return "await geoTo(" + q(Param("lat")) + ", " + q(Param("lng")) +
+ (Param("accuracy").Length > 0 ? ", " + q(Param("accuracy")) : string.Empty) + ")";
+
case "custom":
return Param("code").Trim().TrimEnd(';');
diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents.json b/configs/seeds/host/data/App.DeveloperKit.CustomComponents.json
index 3eee97a4..e72b046b 100644
--- a/configs/seeds/host/data/App.DeveloperKit.CustomComponents.json
+++ b/configs/seeds/host/data/App.DeveloperKit.CustomComponents.json
@@ -1,7 +1,7 @@
{
"ListFormCode": "App.DeveloperKit.CustomComponents",
"KeyFieldName": "Id",
- "GeneratedAt": "2026-09-08T13:11:35.4554636Z",
+ "GeneratedAt": "2026-09-21T14:46:03.8784173Z",
"Order": 0,
"Rows": [
{
@@ -47,92 +47,10 @@
"RoutePath": "/admin/roles",
"Description": "",
"IsActive": true,
- "Dependencies": [],
- "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%22%3A%3AApp.Platform.Name%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%22%22%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 = () => {\n const injected = typeof getCulture === \"function\" ? getCulture() : \"\"\n if (injected) return injected\n if (typeof document !== \"undefined\" && document.documentElement.lang) return document.documentElement.lang\n return undefined\n }\n // Patterns come from the culture itself; a fixed day/month/year order would\n // print the same layout in every language.\n const localeDateOptions = { dateStyle: \"short\" }\n const localeTimeOptions = { dateStyle: \"short\", timeStyle: \"short\" }\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 // Grouping and the decimal separator are the culture's decision; only the\n // precision guard stays, so a long decimal is not rounded away.\n return new Intl.NumberFormat(culture, { 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 = true\n const sqlCanInsert_cmp_msyfmegt_jkkn3 = true\n const sqlCanUpdate_cmp_msyfmegt_jkkn3 = true\n const sqlCanDelete_cmp_msyfmegt_jkkn3 = true\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 {translate(\"::App.Platform.Previous\")} \n {`${sqlIndex_cmp_msyfmegt_jkkn3 + 1} / ${sqlRows_cmp_msyfmegt_jkkn3.length}`} \n = sqlRows_cmp_msyfmegt_jkkn3.length - 1} onClick={sqlNext_cmp_msyfmegt_jkkn3}>{translate(\"::App.Platform.Next\")} \n \n >\n ) : null}\n {sqlinsert_cmp_msyfmegt_jkkn3 && sqlCanInsert_cmp_msyfmegt_jkkn3 ? {translate(\"::App.Platform.New\")} : null}\n {(sqlMode_cmp_msyfmegt_jkkn3 === \"new\" ? sqlinsert_cmp_msyfmegt_jkkn3 && sqlCanInsert_cmp_msyfmegt_jkkn3 : sqlupdate_cmp_msyfmegt_jkkn3 && sqlCanUpdate_cmp_msyfmegt_jkkn3) ? { void sqlSave_cmp_msyfmegt_jkkn3() }}>{translate(\"::App.Platform.Save\")} : null}\n {sqldelete_cmp_msyfmegt_jkkn3 && sqlCanDelete_cmp_msyfmegt_jkkn3 ? { void sqlDelete_cmp_msyfmegt_jkkn3() }}>{translate(\"::App.Platform.Delete\")} : null}\n {sqlselect_cmp_msyfmegt_jkkn3 && sqlCanSelect_cmp_msyfmegt_jkkn3 ? { void sqlRefresh_cmp_msyfmegt_jkkn3() }}>{translate(\"::App.Platform.Refresh\")} : 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) => {(column === \"value\" ? \"Value\" : 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 {text} \n })}\n \n ))}\n \n
\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\":\"::App.Platform.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\":\"\"}}",
- "DataSources": [
- {
- "Name": "AbpRoles · GetList",
- "Method": "GET",
- "Url": "/api/app/crudendpoint/AbpRoles",
- "ResponsePath": null,
- "EntityName": "AbpRoles",
- "OperationType": "GetList",
- "SeedFile": "crud/AbpRoles.json"
- },
- {
- "Name": "AbpRoles · GetById",
- "Method": "GET",
- "Url": "/api/app/crudendpoint/AbpRoles/{id}",
- "ResponsePath": null,
- "EntityName": "AbpRoles",
- "OperationType": "GetById",
- "SeedFile": "crud/AbpRoles.json"
- },
- {
- "Name": "AbpRoles · Delete",
- "Method": "DELETE",
- "Url": "/api/app/crudendpoint/AbpRoles/{id}",
- "ResponsePath": null,
- "EntityName": "AbpRoles",
- "OperationType": "Delete",
- "SeedFile": "crud/AbpRoles.json"
- },
- {
- "Name": "AbpRoles · Create",
- "Method": "POST",
- "Url": "/api/app/crudendpoint/AbpRoles",
- "ResponsePath": null,
- "EntityName": "AbpRoles",
- "OperationType": "Create",
- "SeedFile": "crud/AbpRoles.json"
- },
- {
- "Name": "AbpRoles · Update",
- "Method": "PUT",
- "Url": "/api/app/crudendpoint/AbpRoles/{id}",
- "ResponsePath": null,
- "EntityName": "AbpRoles",
- "OperationType": "Update",
- "SeedFile": "crud/AbpRoles.json"
- },
- {
- "Name": "AbpUserRoles · GetList",
- "Method": "GET",
- "Url": "/api/app/crudendpoint/AbpUserRoles",
- "ResponsePath": null,
- "EntityName": "AbpUserRoles",
- "OperationType": "GetList",
- "SeedFile": "crud/AbpUserRoles.json"
- },
- {
- "Name": "AbpUserRoles · GetById",
- "Method": "GET",
- "Url": "/api/app/crudendpoint/AbpUserRoles/{id}",
- "ResponsePath": null,
- "EntityName": "AbpUserRoles",
- "OperationType": "GetById",
- "SeedFile": "crud/AbpUserRoles.json"
- },
- {
- "Name": "AbpUsers · GetList",
- "Method": "GET",
- "Url": "/api/app/crudendpoint/AbpUsers",
- "ResponsePath": null,
- "EntityName": "AbpUsers",
- "OperationType": "GetList",
- "SeedFile": "crud/AbpUsers.json"
- },
- {
- "Name": "AbpUsers · GetById",
- "Method": "GET",
- "Url": "/api/app/crudendpoint/AbpUsers/{id}",
- "ResponsePath": null,
- "EntityName": "AbpUsers",
- "OperationType": "GetById",
- "SeedFile": "crud/AbpUsers.json"
- }
- ]
+ "Dependencies": "[]",
+ "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%22%3A%3AApp.Platform.Name%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_mubctlbp_p6ajt%22%2C%22type%22%3A%22BarcodeScanField%22%2C%22kind%22%3A%22custom%22%2C%22props%22%3A%7B%22label%22%3A%22%22%2C%22name%22%3A%22barcode%22%2C%22placeholder%22%3A%22%22%2C%22buttonText%22%3A%22%22%2C%22continuous%22%3Atrue%2C%22readOnly%22%3Atrue%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%22barcodeScanField1%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%22%22%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 = () => {\n const injected = typeof getCulture === \"function\" ? getCulture() : \"\"\n if (injected) return injected\n if (typeof document !== \"undefined\" && document.documentElement.lang) return document.documentElement.lang\n return undefined\n }\n // Patterns come from the culture itself; a fixed day/month/year order would\n // print the same layout in every language.\n const localeDateOptions = { dateStyle: \"short\" }\n const localeTimeOptions = { dateStyle: \"short\", timeStyle: \"short\" }\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 // Grouping and the decimal separator are the culture's decision; only the\n // precision guard stays, so a long decimal is not rounded away.\n return new Intl.NumberFormat(culture, { 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 = true\n const sqlCanInsert_cmp_msyfmegt_jkkn3 = true\n const sqlCanUpdate_cmp_msyfmegt_jkkn3 = true\n const sqlCanDelete_cmp_msyfmegt_jkkn3 = true\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 {translate(\"::App.Platform.Previous\")} \n {`${sqlIndex_cmp_msyfmegt_jkkn3 + 1} / ${sqlRows_cmp_msyfmegt_jkkn3.length}`} \n = sqlRows_cmp_msyfmegt_jkkn3.length - 1} onClick={sqlNext_cmp_msyfmegt_jkkn3}>{translate(\"::App.Platform.Next\")} \n \n >\n ) : null}\n {sqlinsert_cmp_msyfmegt_jkkn3 && sqlCanInsert_cmp_msyfmegt_jkkn3 ? {translate(\"::App.Platform.New\")} : null}\n {(sqlMode_cmp_msyfmegt_jkkn3 === \"new\" ? sqlinsert_cmp_msyfmegt_jkkn3 && sqlCanInsert_cmp_msyfmegt_jkkn3 : sqlupdate_cmp_msyfmegt_jkkn3 && sqlCanUpdate_cmp_msyfmegt_jkkn3) ? { void sqlSave_cmp_msyfmegt_jkkn3() }}>{translate(\"::App.Platform.Save\")} : null}\n {sqldelete_cmp_msyfmegt_jkkn3 && sqlCanDelete_cmp_msyfmegt_jkkn3 ? { void sqlDelete_cmp_msyfmegt_jkkn3() }}>{translate(\"::App.Platform.Delete\")} : null}\n {sqlselect_cmp_msyfmegt_jkkn3 && sqlCanSelect_cmp_msyfmegt_jkkn3 ? { void sqlRefresh_cmp_msyfmegt_jkkn3() }}>{translate(\"::App.Platform.Refresh\")} : 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 {(() => {\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) => {(column === \"value\" ? \"Value\" : 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 {text} \n })}\n \n ))}\n \n
\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\":\"::App.Platform.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_mubctlbp_p6ajt\",\"type\":\"BarcodeScanField\",\"kind\":\"custom\",\"props\":{\"label\":\"\",\"name\":\"barcode\",\"placeholder\":\"\",\"buttonText\":\"\",\"continuous\":true,\"readOnly\":true,\"className\":\"mt-4\"},\"events\":{},\"bindings\":{},\"children\":[],\"ref\":\"barcodeScanField1\"},{\"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\":\"\"}}",
+ "DataSources": "[\r\n {\r\n \"Name\": \"AbpRoles · GetList\",\r\n \"Method\": \"GET\",\r\n \"Url\": \"/api/app/crudendpoint/AbpRoles\",\r\n \"ResponsePath\": null,\r\n \"EntityName\": \"AbpRoles\",\r\n \"OperationType\": \"GetList\",\r\n \"SeedFile\": \"crud/AbpRoles.json\"\r\n },\r\n {\r\n \"Name\": \"AbpRoles · GetById\",\r\n \"Method\": \"GET\",\r\n \"Url\": \"/api/app/crudendpoint/AbpRoles/{id}\",\r\n \"ResponsePath\": null,\r\n \"EntityName\": \"AbpRoles\",\r\n \"OperationType\": \"GetById\",\r\n \"SeedFile\": \"crud/AbpRoles.json\"\r\n },\r\n {\r\n \"Name\": \"AbpRoles · Delete\",\r\n \"Method\": \"DELETE\",\r\n \"Url\": \"/api/app/crudendpoint/AbpRoles/{id}\",\r\n \"ResponsePath\": null,\r\n \"EntityName\": \"AbpRoles\",\r\n \"OperationType\": \"Delete\",\r\n \"SeedFile\": \"crud/AbpRoles.json\"\r\n },\r\n {\r\n \"Name\": \"AbpRoles · Create\",\r\n \"Method\": \"POST\",\r\n \"Url\": \"/api/app/crudendpoint/AbpRoles\",\r\n \"ResponsePath\": null,\r\n \"EntityName\": \"AbpRoles\",\r\n \"OperationType\": \"Create\",\r\n \"SeedFile\": \"crud/AbpRoles.json\"\r\n },\r\n {\r\n \"Name\": \"AbpRoles · Update\",\r\n \"Method\": \"PUT\",\r\n \"Url\": \"/api/app/crudendpoint/AbpRoles/{id}\",\r\n \"ResponsePath\": null,\r\n \"EntityName\": \"AbpRoles\",\r\n \"OperationType\": \"Update\",\r\n \"SeedFile\": \"crud/AbpRoles.json\"\r\n },\r\n {\r\n \"Name\": \"AbpUserRoles · GetList\",\r\n \"Method\": \"GET\",\r\n \"Url\": \"/api/app/crudendpoint/AbpUserRoles\",\r\n \"ResponsePath\": null,\r\n \"EntityName\": \"AbpUserRoles\",\r\n \"OperationType\": \"GetList\",\r\n \"SeedFile\": \"crud/AbpUserRoles.json\"\r\n },\r\n {\r\n \"Name\": \"AbpUserRoles · GetById\",\r\n \"Method\": \"GET\",\r\n \"Url\": \"/api/app/crudendpoint/AbpUserRoles/{id}\",\r\n \"ResponsePath\": null,\r\n \"EntityName\": \"AbpUserRoles\",\r\n \"OperationType\": \"GetById\",\r\n \"SeedFile\": \"crud/AbpUserRoles.json\"\r\n },\r\n {\r\n \"Name\": \"AbpUsers · GetList\",\r\n \"Method\": \"GET\",\r\n \"Url\": \"/api/app/crudendpoint/AbpUsers\",\r\n \"ResponsePath\": null,\r\n \"EntityName\": \"AbpUsers\",\r\n \"OperationType\": \"GetList\",\r\n \"SeedFile\": \"crud/AbpUsers.json\"\r\n },\r\n {\r\n \"Name\": \"AbpUsers · GetById\",\r\n \"Method\": \"GET\",\r\n \"Url\": \"/api/app/crudendpoint/AbpUsers/{id}\",\r\n \"ResponsePath\": null,\r\n \"EntityName\": \"AbpUsers\",\r\n \"OperationType\": \"GetById\",\r\n \"SeedFile\": \"crud/AbpUsers.json\"\r\n }\r\n]"
},
{
"Id": "8f1c2d40-0000-4000-8000-000000000101",
@@ -190,4 +108,4 @@
"DataSources": ""
}
]
-}
+}
\ No newline at end of file
diff --git a/ui/package-lock.json b/ui/package-lock.json
index ba6a8f52..3675d70a 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -36,7 +36,8 @@
"react-modal": "^3.16.3",
"react-router-dom": "^7.18.2",
"react-select": "^5.10.2",
- "yup": "^1.7.1"
+ "yup": "^1.7.1",
+ "zxing-wasm": "^3.1.4"
},
"devDependencies": {
"@eslint/js": "^9.39.5",
@@ -3263,6 +3264,12 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/emscripten": {
+ "version": "1.41.6",
+ "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.6.tgz",
+ "integrity": "sha512-uN+9i8bFT5CUcZfyIEYDrSueACEyKGbUs5kC/72DGlZZoinh84sJfVV0i8UOJD1asdzkvLPBRrKs41kZ8MdEXg==",
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -11692,6 +11699,18 @@
"integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==",
"license": "MIT"
},
+ "node_modules/tagged-tag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
+ "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/tailwindcss": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
@@ -12126,6 +12145,21 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/type-fest": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.10.0.tgz",
+ "integrity": "sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==",
+ "license": "(MIT OR CC0-1.0)",
+ "dependencies": {
+ "tagged-tag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/typed-array-buffer": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
@@ -13175,6 +13209,19 @@
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
+ },
+ "node_modules/zxing-wasm": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-3.1.4.tgz",
+ "integrity": "sha512-1+v8p7sauddySVNG+ye7GD/0TS95BG2ycdkrC+EnBrFBFkHeIghzUt2yVjtx3NucJeNrQnhT8yGAIz60Vx9o0w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/emscripten": "^1.41.6",
+ "type-fest": "^5.9.0"
+ },
+ "peerDependencies": {
+ "@types/emscripten": ">=1.39.6"
+ }
}
}
}
diff --git a/ui/package.json b/ui/package.json
index 475392f2..12101153 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -54,7 +54,8 @@
"react-modal": "^3.16.3",
"react-router-dom": "^7.18.2",
"react-select": "^5.10.2",
- "yup": "^1.7.1"
+ "yup": "^1.7.1",
+ "zxing-wasm": "^3.1.4"
},
"devDependencies": {
"@eslint/js": "^9.39.5",
diff --git a/ui/src/components/componentEditor/runtimeNodes.ts b/ui/src/components/componentEditor/runtimeNodes.ts
index 4337fb07..e013806a 100644
--- a/ui/src/components/componentEditor/runtimeNodes.ts
+++ b/ui/src/components/componentEditor/runtimeNodes.ts
@@ -1,5 +1,6 @@
import type React from 'react'
import PlatformIcon from './PlatformIcon'
+import { BarcodeScanField } from '@/components/shared'
import {
PublicDemoButton,
PublicHeaderBar,
@@ -17,6 +18,7 @@ import {
*/
export const RUNTIME_NODE_COMPONENTS = {
PlatformIcon,
+ BarcodeScanField,
PublicHeaderBar,
PublicLogo,
PublicLanguageSelector,
diff --git a/ui/src/components/shared/BarcodeScanner/BarcodeScanField.tsx b/ui/src/components/shared/BarcodeScanner/BarcodeScanField.tsx
new file mode 100644
index 00000000..4e669989
--- /dev/null
+++ b/ui/src/components/shared/BarcodeScanner/BarcodeScanField.tsx
@@ -0,0 +1,83 @@
+import { useState } from 'react'
+import { Button, Input } from '@/components/ui'
+import { useLocalization } from '@/utils/hooks/useLocalization'
+import { isCameraAvailable } from '@/utils/device'
+import BarcodeScannerDialog from './BarcodeScannerDialog'
+
+export type BarcodeScanFieldProps = {
+ label?: string
+ placeholder?: string
+ buttonText?: string
+ /** Okunan kod formda bu adla taşınır; üretilen kod da aynı adı kullanır. */
+ name?: string
+ /** Açıkken diyalog okumadan sonra kapanmaz, arka arkaya okur. */
+ continuous?: boolean
+ readOnly?: boolean
+ className?: string
+ value?: string
+ onScan?: (code: string) => void
+}
+
+/**
+ * Tasarımcıdan sürüklenip bırakılabilen barkod alanı. Kanvas ve üretilen kod
+ * aynı bileşeni çizer; okunan değer hem içeride tutulur hem `onScan` ile dışarı
+ * verilir, böylece hem tek başına hem bir formun parçası olarak kullanılabilir.
+ */
+const BarcodeScanField = ({
+ label,
+ placeholder,
+ buttonText,
+ name,
+ continuous = false,
+ readOnly = true,
+ className,
+ value,
+ onScan,
+}: BarcodeScanFieldProps) => {
+ const { translate } = useLocalization()
+ const [open, setOpen] = useState(false)
+ const [scanned, setScanned] = useState('')
+ const current = value ?? scanned
+
+ const handleDetect = (code: string) => {
+ setScanned(code)
+ onScan?.(code)
+ }
+
+ return (
+
+ {label &&
{label} }
+
+ {
+ setScanned(event.target.value)
+ onScan?.(event.target.value)
+ }}
+ />
+ setOpen(true)}
+ >
+ {buttonText ?? translate('::App.BarcodeScanner.Scan')}
+
+
+
+ {open && (
+
setOpen(false)}
+ />
+ )}
+
+ )
+}
+
+export default BarcodeScanField
diff --git a/ui/src/components/shared/BarcodeScanner/BarcodeScannerDialog.tsx b/ui/src/components/shared/BarcodeScanner/BarcodeScannerDialog.tsx
new file mode 100644
index 00000000..9503309e
--- /dev/null
+++ b/ui/src/components/shared/BarcodeScanner/BarcodeScannerDialog.tsx
@@ -0,0 +1,111 @@
+import { useState } from 'react'
+import { Button, Dialog } from '@/components/ui'
+import { useLocalization } from '@/utils/hooks/useLocalization'
+import { useBarcodeReader, type BarcodeReaderStatus } from './useBarcodeReader'
+
+export type BarcodeScannerDialogProps = {
+ open: boolean
+ onDialogClose: () => void
+ /**
+ * Okunan kod. `false` dönerse kod reddedilmiş sayılır ve listeye yazılmaz;
+ * sürekli modda yazma hatasını kullanıcıya göstermek için kullanılır.
+ */
+ onDetect: (code: string) => boolean | void | Promise
+ /** Açıkken diyalog okumadan sonra kapanmaz, okunanları listeler. */
+ continuous?: boolean
+ title?: string
+}
+
+const statusMessageKeys: Record = {
+ starting: '::App.BarcodeScanner.Starting',
+ scanning: '::App.BarcodeScanner.Aim',
+ denied: '::App.BarcodeScanner.PermissionDenied',
+ insecure: '::App.BarcodeScanner.InsecureContext',
+ standaloneIos: '::App.BarcodeScanner.StandaloneIos',
+ error: '::App.BarcodeScanner.CameraError',
+}
+
+/**
+ * Kamerayla barkod/QR okuma diyaloğu. Tek okuma ve sürekli tarama aynı
+ * bileşendir; fark yalnızca okumadan sonra kapanıp kapanmadığıdır.
+ */
+const BarcodeScannerDialog = ({
+ open,
+ onDialogClose,
+ onDetect,
+ continuous = false,
+ title,
+}: BarcodeScannerDialogProps) => {
+ const { translate } = useLocalization()
+ const [scanned, setScanned] = useState([])
+ const [failed, setFailed] = useState()
+
+ const handleDetect = async (code: string) => {
+ const accepted = await onDetect(code)
+ if (accepted === false) {
+ setFailed(code)
+ return
+ }
+
+ if (!continuous) {
+ onDialogClose()
+ return
+ }
+
+ setFailed(undefined)
+ setScanned((previous) => [code, ...previous])
+ }
+
+ const { videoRef, status } = useBarcodeReader({ active: open, onDetect: handleDetect })
+
+ return (
+
+ {title ?? translate('::App.BarcodeScanner.Title')}
+
+
+
+ {status === 'scanning' && (
+ // Nişangah: kullanıcıya kodu nereye tutacağını gösterir.
+
+ )}
+
+
+ {translate(statusMessageKeys[status])}
+
+ {failed && (
+
+ {translate('::App.BarcodeScanner.SaveFailed')} ({failed})
+
+ )}
+
+ {continuous && scanned.length > 0 && (
+
+
+ {translate('::App.BarcodeScanner.ScannedCount')}: {scanned.length}
+
+
+ {scanned.map((code, index) => (
+
+ {code}
+
+ ))}
+
+
+ )}
+
+
+
+ {translate(continuous ? '::App.Platform.Close' : '::App.Platform.Cancel')}
+
+
+
+ )
+}
+
+export default BarcodeScannerDialog
diff --git a/ui/src/components/shared/BarcodeScanner/BarcodeScannerHost.tsx b/ui/src/components/shared/BarcodeScanner/BarcodeScannerHost.tsx
new file mode 100644
index 00000000..bdc81adf
--- /dev/null
+++ b/ui/src/components/shared/BarcodeScanner/BarcodeScannerHost.tsx
@@ -0,0 +1,33 @@
+import { useEffect, useState } from 'react'
+import BarcodeScannerDialog from './BarcodeScannerDialog'
+import { registerBarcodeScanner } from './scanBarcode'
+
+/**
+ * `scanBarcode()` çağrılarını karşılayan tekil diyalog. Uygulama kökünde bir
+ * kez mount edilir; React ağacı dışından açılabilmesinin tek yolu budur.
+ */
+const BarcodeScannerHost = () => {
+ const [resolve, setResolve] = useState<((code: string | null) => void) | null>(null)
+
+ useEffect(() => {
+ registerBarcodeScanner((next) => setResolve(() => next))
+ return () => registerBarcodeScanner(undefined)
+ }, [])
+
+ const finish = (code: string | null) => {
+ resolve?.(code)
+ setResolve(null)
+ }
+
+ if (!resolve) return null
+
+ return (
+ finish(code)}
+ onDialogClose={() => finish(null)}
+ />
+ )
+}
+
+export default BarcodeScannerHost
diff --git a/ui/src/components/shared/BarcodeScanner/index.ts b/ui/src/components/shared/BarcodeScanner/index.ts
new file mode 100644
index 00000000..c56c9cbe
--- /dev/null
+++ b/ui/src/components/shared/BarcodeScanner/index.ts
@@ -0,0 +1,3 @@
+export { default as BarcodeScanField } from './BarcodeScanField'
+export { default as BarcodeScannerDialog } from './BarcodeScannerDialog'
+export { default as BarcodeScannerHost } from './BarcodeScannerHost'
diff --git a/ui/src/components/shared/BarcodeScanner/scanBarcode.ts b/ui/src/components/shared/BarcodeScanner/scanBarcode.ts
new file mode 100644
index 00000000..c1229ab7
--- /dev/null
+++ b/ui/src/components/shared/BarcodeScanner/scanBarcode.ts
@@ -0,0 +1,22 @@
+type ScanRequest = (resolve: (code: string | null) => void) => void
+
+let requestScan: ScanRequest | undefined
+
+/** Diyaloğu tutan host bileşen kendini burada kaydeder. */
+export const registerBarcodeScanner = (request?: ScanRequest) => {
+ requestScan = request
+}
+
+/**
+ * React ağacı dışından (DevExtreme editör butonu, editorScript) barkod okutur.
+ * Vazgeçilirse `null` döner. Host mount edilmemişse sessizce `null` döner ki
+ * çağıran taraf hata yönetmek zorunda kalmasın.
+ */
+export const scanBarcode = () =>
+ new Promise((resolve) => {
+ if (!requestScan) {
+ resolve(null)
+ return
+ }
+ requestScan(resolve)
+ })
diff --git a/ui/src/components/shared/BarcodeScanner/useBarcodeReader.ts b/ui/src/components/shared/BarcodeScanner/useBarcodeReader.ts
new file mode 100644
index 00000000..79bc53f2
--- /dev/null
+++ b/ui/src/components/shared/BarcodeScanner/useBarcodeReader.ts
@@ -0,0 +1,123 @@
+import { useEffect, useRef, useState } from 'react'
+import {
+ createBarcodeDecoder,
+ openCameraStream,
+ stopCameraStream,
+ vibrateOnScan,
+} from '@/utils/barcode'
+import { isCameraAvailable, isCameraBlockedByStandaloneIos } from '@/utils/device'
+
+export type BarcodeReaderStatus =
+ | 'starting'
+ | 'scanning'
+ | 'denied'
+ | 'insecure'
+ | 'standaloneIos'
+ | 'error'
+
+/** Kare çözme aralığı. Her karede çözmek mobilde pili hızla tüketir. */
+const DECODE_INTERVAL_MS = 180
+
+/** Aynı kodun arka arkaya birden çok kez okunmasını engelleyen pencere. */
+const DUPLICATE_WINDOW_MS = 1500
+
+/**
+ * Kamerayı açar, kareleri çözer ve okunan kodu bildirir.
+ *
+ * `onDetect` her okumada yeniden bağlanabildiği için ref üzerinden tutulur;
+ * aksi hâlde her render kamerayı kapatıp yeniden açardı.
+ */
+export const useBarcodeReader = ({
+ active,
+ onDetect,
+}: {
+ active: boolean
+ onDetect: (code: string) => void
+}) => {
+ const videoRef = useRef(null)
+ const onDetectRef = useRef(onDetect)
+ const [status, setStatus] = useState('starting')
+
+ onDetectRef.current = onDetect
+
+ useEffect(() => {
+ if (!active) return
+
+ if (isCameraBlockedByStandaloneIos()) {
+ setStatus('standaloneIos')
+ return
+ }
+
+ if (!isCameraAvailable()) {
+ setStatus('insecure')
+ return
+ }
+
+ const video = videoRef.current
+ let stream: MediaStream | undefined
+ let timer: number | undefined
+ let cancelled = false
+ let lastCode = ''
+ let lastCodeAt = 0
+
+ const start = async () => {
+ setStatus('starting')
+ try {
+ const [cameraStream, decode] = await Promise.all([
+ openCameraStream(),
+ createBarcodeDecoder(),
+ ])
+
+ if (cancelled) {
+ stopCameraStream(cameraStream)
+ return
+ }
+
+ if (!video) return
+ stream = cameraStream
+
+ video.srcObject = cameraStream
+ await video.play()
+ setStatus('scanning')
+
+ const tick = async () => {
+ if (cancelled) return
+
+ try {
+ const code = await decode(video)
+ const now = Date.now()
+ if (code && (code !== lastCode || now - lastCodeAt > DUPLICATE_WINDOW_MS)) {
+ lastCode = code
+ lastCodeAt = now
+ vibrateOnScan()
+ onDetectRef.current(code)
+ }
+ } catch {
+ // Tek bir karenin çözülememesi tarama döngüsünü durdurmaz.
+ }
+
+ if (!cancelled) {
+ timer = window.setTimeout(tick, DECODE_INTERVAL_MS)
+ }
+ }
+
+ tick()
+ } catch (error) {
+ if (cancelled) return
+ const name = (error as DOMException)?.name
+ setStatus(name === 'NotAllowedError' || name === 'SecurityError' ? 'denied' : 'error')
+ }
+ }
+
+ start()
+
+ return () => {
+ cancelled = true
+ if (timer) window.clearTimeout(timer)
+ stopCameraStream(stream)
+ if (video) video.srcObject = null
+ }
+ }, [active])
+
+ return { videoRef, status }
+}
diff --git a/ui/src/components/shared/index.ts b/ui/src/components/shared/index.ts
index bcb01091..ec936259 100644
--- a/ui/src/components/shared/index.ts
+++ b/ui/src/components/shared/index.ts
@@ -2,6 +2,7 @@ export { default as ActionLink } from './ActionLink'
export { default as AdaptableCard } from './AdaptableCard'
export { default as Affix } from './Affix'
export { default as AuthorityCheck } from './AuthorityCheck'
+export { BarcodeScanField, BarcodeScannerDialog, BarcodeScannerHost } from './BarcodeScanner'
export { default as PermissionCheck } from './PermissionCheck'
export { default as ConfirmDialog } from './ConfirmDialog'
export { default as Container } from './Container'
diff --git a/ui/src/components/visualDesigner/catalog.ts b/ui/src/components/visualDesigner/catalog.ts
index 58f2f7ed..8017dcf8 100644
--- a/ui/src/components/visualDesigner/catalog.ts
+++ b/ui/src/components/visualDesigner/catalog.ts
@@ -110,6 +110,7 @@ const UI_PROPERTY_ALIASES: Record> = {
}
export const DESIGNER_PRIMARY_EVENTS: Record = {
AutoComplete: ['onInputChange', 'onSelect'],
+ BarcodeScanField: ['onScan'],
Button: ['onClick'],
Checkbox: ['onChange'],
Dropdown: ['onSelect'],
@@ -129,6 +130,11 @@ const DESIGNER_EVENT_SNIPPETS: Record> = {
// refs.input1.setEnabled(false)
// refs.card1.setVisible(false)
console.log('Button clicked', event)`,
+ },
+ BarcodeScanField: {
+ onScan: `// Okunan barkod/QR değeridir (metin).
+const code = event
+refs.input1.setValue(code)`,
},
Checkbox: {
onChange: `// Checkbox bileşeninin yeni checked değeri.
@@ -1217,6 +1223,35 @@ export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [
],
hooks: [],
},
+ {
+ // Kamerayla barkod/QR okuyan alan. Kanvas ve uretilen kod ayni bileseni
+ // cizer (`components/shared/BarcodeScanner`).
+ name: 'BarcodeScanField',
+ icon: 'ScanLine',
+ category: 'basic',
+ kind: 'custom',
+ toolboxGroup: 'ui',
+ description: 'App.CatalogBarcodeScanField.BarcodeScanFieldDescription',
+ properties: [
+ { name: 'label', type: 'string', value: '', category: 'properties' },
+ { name: 'name', type: 'string', value: 'barcode', category: 'properties' },
+ { name: 'placeholder', type: 'string', value: '', category: 'properties' },
+ { name: 'buttonText', type: 'string', value: '', category: 'properties' },
+ { name: 'continuous', type: 'boolean', value: false, category: 'properties' },
+ { name: 'readOnly', type: 'boolean', value: true, category: 'properties' },
+ { name: 'className', type: 'string', value: '', category: 'styling' },
+ // `category: 'events'` alanı Özellikler panelinden çıkarıp JavaScript
+ // sekmesine taşır; orada değer değil script olarak düzenlenir.
+ {
+ name: 'onScan',
+ type: 'function',
+ value: '',
+ category: 'events',
+ description: 'App.CatalogBarcodeScanField.OnScanDescription',
+ },
+ ],
+ hooks: [],
+ },
{
name: 'Spacer',
icon: 'Space',
diff --git a/ui/src/proxy/form/models.ts b/ui/src/proxy/form/models.ts
index 0cd63506..83f8e0f1 100644
--- a/ui/src/proxy/form/models.ts
+++ b/ui/src/proxy/form/models.ts
@@ -341,6 +341,8 @@ export interface ImageUploadOptionsDto {
maxFileSize?: number
width?: number
height?: number
+ /** Mobilde dosya seçici yerine kamerayı açar. */
+ capture?: 'environment' | 'user'
}
export interface RowDto {
diff --git a/ui/src/utils/barcode.ts b/ui/src/utils/barcode.ts
new file mode 100644
index 00000000..d3bc740e
--- /dev/null
+++ b/ui/src/utils/barcode.ts
@@ -0,0 +1,119 @@
+/**
+ * Barkod okuma altyapısı.
+ *
+ * Tarayıcıda iki çözücü yolu var: Chromium tabanlı tarayıcılar `BarcodeDetector`
+ * ile yerleşik (WASM indirmeden, donanım hızlandırmalı) okur; iOS Safari ve
+ * Firefox'ta bu API yok, o yüzden `zxing-wasm` yalnızca gerektiğinde lazy
+ * yüklenir. Böylece Android cihazlarda ek bir byte inmez.
+ */
+
+/** `BarcodeDetector` format adları (snake_case). 1D + QR. */
+const DETECTOR_FORMATS = [
+ 'qr_code',
+ 'code_128',
+ 'code_39',
+ 'code_93',
+ 'ean_13',
+ 'ean_8',
+ 'upc_a',
+ 'upc_e',
+ 'itf',
+] as const
+
+/** Aynı küme zxing adlandırmasıyla. İki liste birlikte değişir. */
+const ZXING_FORMATS = [
+ 'QRCode',
+ 'Code128',
+ 'Code39',
+ 'Code93',
+ 'EAN13',
+ 'EAN8',
+ 'UPCA',
+ 'UPCE',
+ 'ITF',
+] as const
+
+type BarcodeDetectorLike = {
+ detect: (source: CanvasImageSource) => Promise<{ rawValue: string }[]>
+}
+
+type DetectorWindow = Window & {
+ BarcodeDetector?: new (options?: { formats?: readonly string[] }) => BarcodeDetectorLike
+}
+
+export type BarcodeDecoder = (frame: HTMLVideoElement) => Promise
+
+type ZxingReader = (imageData: ImageData) => Promise
+
+let zxingReader: Promise | undefined
+
+/**
+ * zxing-wasm'ı bir kez yükler. WASM dosyası paketten `?url` ile alınır; paketin
+ * varsayılanı jsDelivr CDN'idir ve çevrimdışı/kapalı ağ kurulumlarında çalışmaz.
+ */
+const getZxingReader = () => {
+ zxingReader ??= (async () => {
+ const [{ prepareZXingModule, readBarcodesFromImageData }, { default: wasmUrl }] =
+ await Promise.all([
+ import('zxing-wasm/reader'),
+ import('zxing-wasm/reader/zxing_reader.wasm?url'),
+ ])
+
+ prepareZXingModule({ overrides: { locateFile: () => wasmUrl } })
+
+ return async (imageData: ImageData) => {
+ const results = await readBarcodesFromImageData(imageData, {
+ formats: [...ZXING_FORMATS],
+ tryHarder: false,
+ })
+ return results.find((result) => result.text)?.text
+ }
+ })()
+ return zxingReader
+}
+
+/**
+ * Ortama göre çözücü seçer. Çağıran taraf hangi yolun kullanıldığını bilmez;
+ * her iki yol da video karesini alıp okunan metni döner.
+ */
+export const createBarcodeDecoder = async (): Promise => {
+ const DetectorCtor = (window as DetectorWindow).BarcodeDetector
+
+ if (DetectorCtor) {
+ const detector = new DetectorCtor({ formats: DETECTOR_FORMATS })
+ return async (frame) => {
+ const codes = await detector.detect(frame)
+ return codes.find((code) => code.rawValue)?.rawValue
+ }
+ }
+
+ const read = await getZxingReader()
+ // WASM yolunda kare canvas'a indirgenir; canvas tek kez oluşturulup yeniden kullanılır.
+ const canvas = document.createElement('canvas')
+ const context = canvas.getContext('2d', { willReadFrequently: true })
+
+ return async (frame) => {
+ if (!context || !frame.videoWidth || !frame.videoHeight) return undefined
+ canvas.width = frame.videoWidth
+ canvas.height = frame.videoHeight
+ context.drawImage(frame, 0, 0)
+ return read(context.getImageData(0, 0, canvas.width, canvas.height))
+ }
+}
+
+/** Arka kamerayı tercih eden akış. İzin reddedilirse hata çağırana bırakılır. */
+export const openCameraStream = () =>
+ navigator.mediaDevices.getUserMedia({
+ video: {
+ facingMode: { ideal: 'environment' },
+ width: { ideal: 1280 },
+ height: { ideal: 720 },
+ },
+ audio: false,
+ })
+
+export const stopCameraStream = (stream?: MediaStream | null) =>
+ stream?.getTracks().forEach((track) => track.stop())
+
+/** Okuma geri bildirimi. Desteklemeyen cihazda sessizce atlanır. */
+export const vibrateOnScan = () => navigator.vibrate?.(60)
diff --git a/ui/src/utils/device.ts b/ui/src/utils/device.ts
new file mode 100644
index 00000000..6749d882
--- /dev/null
+++ b/ui/src/utils/device.ts
@@ -0,0 +1,35 @@
+/**
+ * Cihaz yetenekleri. Kamera/konum gibi özellikler tarayıcıya ve bağlama göre
+ * değiştiği için tek yerden sorulur; ekranlar `navigator` kontrolü yapmaz.
+ */
+
+/** Kamera güvenli bağlam ister; HTTP üzerinden getUserMedia hiç tanımlı değildir. */
+export const isCameraAvailable = () =>
+ typeof window !== 'undefined' && window.isSecureContext && !!navigator.mediaDevices?.getUserMedia
+
+/**
+ * iOS'ta ana ekrana eklenmiş PWA içinde getUserMedia yalnızca 16.4+ sürümlerde
+ * çalışır; altındaki sürümlerde kamera sessizce boş akış döner. Kullanıcıya
+ * "Safari'de aç" diyebilmek için ayrıca tespit ediyoruz.
+ */
+export const isCameraBlockedByStandaloneIos = () => {
+ if (typeof navigator === 'undefined') return false
+ if (!/iPad|iPhone|iPod/.test(navigator.userAgent)) return false
+ if ((navigator as Navigator & { standalone?: boolean }).standalone !== true) return false
+
+ const version = /OS (\d+)_(\d+)/.exec(navigator.userAgent)
+ if (!version) return false
+ const [major, minor] = [Number(version[1]), Number(version[2])]
+ return major < 16 || (major === 16 && minor < 4)
+}
+
+/**
+ * ` ` yalnızca dokunmatik cihazlarda kamerayı açar; masaüstünde
+ * sessizce yok sayılır ve aynı dosya seçicisini ikinci kez açan bir buton
+ * bırakırdı. Bu yüzden kamera butonu yalnızca kaba işaretçili (parmak) ve
+ * arka kamerası olması beklenen cihazlarda gösterilir.
+ */
+export const supportsPhotoCapture = () =>
+ typeof window !== 'undefined' &&
+ typeof window.matchMedia === 'function' &&
+ window.matchMedia('(pointer: coarse)').matches
diff --git a/ui/src/utils/editorScriptRuntime.ts b/ui/src/utils/editorScriptRuntime.ts
index 0cdf1243..97453d5e 100644
--- a/ui/src/utils/editorScriptRuntime.ts
+++ b/ui/src/utils/editorScriptRuntime.ts
@@ -13,6 +13,9 @@
* yeniden derlenir.
*/
+import { getLocalization } from '@/services/localization.service'
+import { store } from '@/store'
+
export type EditorScriptRuntimeContext = {
formData: Record
e: any
@@ -56,6 +59,7 @@ const HELPER_PARAMS = [
'ask',
'openUrl',
'api',
+ 'geoTo',
'revert',
]
@@ -63,6 +67,12 @@ const HELPER_PARAMS = [
* Ortak yardımcılar
* ------------------------------------------------------------------ */
+/** Script runtime React ağacının dışındadır; dil metni store'dan okunur. */
+const translateRuntime = (key: string) => {
+ const { texts, config } = store.getState().abpConfig
+ return getLocalization(texts ?? {}, config?.localization?.defaultResourceName, key)
+}
+
/** Alan adını büyük/küçük harf duyarsız arar; SQL kolonları tutarsız gelebiliyor. */
const resolveKey = (source: any, name: string): string | undefined => {
if (!source || typeof source !== 'object') return undefined
@@ -243,6 +253,36 @@ const buildHelpers = ({
return responsePath ? pickPath(json, responsePath) : json
}
+ /**
+ * Cihazın konumunu alanlara yazar. Tek atıştır; izin verilmezse ya da konum
+ * alınamazsa alanlara dokunulmaz ve kullanıcıya mesaj gösterilir — sessizce
+ * boş değer yazmak, kullanıcının konumu kaydettiğini sanmasına yol açardı.
+ */
+ const geoTo = async (latField: string, lngField?: string, accuracyField?: string) => {
+ if (!navigator.geolocation) {
+ notify(translateRuntime('::App.Geolocation.Unsupported'))
+ return false
+ }
+
+ try {
+ const position = await new Promise((resolve, reject) => {
+ navigator.geolocation.getCurrentPosition(resolve, reject, {
+ enableHighAccuracy: true,
+ timeout: 10000,
+ maximumAge: 0,
+ })
+ })
+
+ set(latField, position.coords.latitude)
+ if (lngField) set(lngField, position.coords.longitude)
+ if (accuracyField) set(accuracyField, Math.round(position.coords.accuracy))
+ return true
+ } catch {
+ notify(translateRuntime('::App.Geolocation.Failed'))
+ return false
+ }
+ }
+
const helpers = {
value: e?.value,
field,
@@ -270,6 +310,7 @@ const buildHelpers = ({
ask,
openUrl,
api,
+ geoTo,
revert,
}
@@ -304,6 +345,7 @@ const buildHelpers = ({
helpers.ask,
helpers.openUrl,
helpers.api,
+ helpers.geoTo,
helpers.revert,
],
}
diff --git a/ui/src/views/Views.tsx b/ui/src/views/Views.tsx
index f19afdc6..fc5773ab 100644
--- a/ui/src/views/Views.tsx
+++ b/ui/src/views/Views.tsx
@@ -1,4 +1,5 @@
import type { LayoutType } from '@/proxy/theme/models'
+import { BarcodeScannerHost } from '@/components/shared'
import Loading from '@/components/shared/Loading'
import { Alert, Button } from '@/components/ui'
import { store, useStoreState } from '@/store'
@@ -89,6 +90,7 @@ const Views = (props: ViewsProps) => {
{e.cid}
))}
+
diff --git a/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/optionSpecs.ts b/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/optionSpecs.ts
index f9ea3eee..41b676fe 100644
--- a/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/optionSpecs.ts
+++ b/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/optionSpecs.ts
@@ -782,6 +782,19 @@ export const optionSpecs: OptionSpec[] = [
platform: true,
placeholder: 'image/*',
},
+ {
+ path: 'capture',
+ label: 'capture',
+ type: 'select',
+ group: 'image',
+ editors: IMAGE_EDITORS,
+ platform: true,
+ help: 'App.SpecCapture.Help',
+ choices: [
+ { value: 'environment', label: 'App.SpecCapture.Environment' },
+ { value: 'user', label: 'App.SpecCapture.User' },
+ ],
+ },
{
path: 'multiple',
label: 'multiple',
diff --git a/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts b/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts
index 056894e6..7d950752 100644
--- a/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts
+++ b/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts
@@ -264,6 +264,29 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
editors: ['dxHtmlEditor'],
value: buildHtmlEditorOptions(),
},
+ {
+ key: 'photoCapture',
+ label: 'App.PresetPhotoCapture.PhotoCaptureLabel',
+ description: 'App.PresetPhotoCapture.PhotoCaptureDescription',
+ editors: ['dxImageUpload'],
+ value: { accept: 'image/*', capture: 'environment' },
+ },
+ {
+ key: 'barcodeScan',
+ label: 'App.PresetBarcodeScan.BarcodeScanLabel',
+ description: 'App.PresetBarcodeScan.BarcodeScanDescription',
+ editors: ['dxTextBox'],
+ value: {
+ buttons: [
+ {
+ name: 'scanBarcode',
+ location: 'after',
+ // '@SCAN_BARCODE' token'ini editingForm.ts kamera diyaloguna baglar.
+ options: { icon: 'photo', stylingMode: 'text', onClick: '@SCAN_BARCODE' },
+ },
+ ],
+ },
+ },
{
key: 'height100',
label: 'App.PresetHeight100.Height100Label',
diff --git a/ui/src/views/admin/listForm/edit/json-row-operations/editor-script/scriptRecipes.ts b/ui/src/views/admin/listForm/edit/json-row-operations/editor-script/scriptRecipes.ts
index 8879f6ef..e32e768e 100644
--- a/ui/src/views/admin/listForm/edit/json-row-operations/editor-script/scriptRecipes.ts
+++ b/ui/src/views/admin/listForm/edit/json-row-operations/editor-script/scriptRecipes.ts
@@ -118,6 +118,7 @@ export type RecipeId =
| 'ask'
| 'openUrl'
| 'apiToField'
+ | 'location'
| 'custom'
export const recipeGroups: { key: RecipeGroupKey; title: string; description: string }[] = [
@@ -667,6 +668,33 @@ export const recipes: Recipe[] = [
target: label(params.target, t('::App.Listform.ListformField.Target')),
}),
},
+ {
+ id: 'location',
+ label: 'App.ScriptBuilderLocation.LocationLabel',
+ group: 'integration',
+ summary: 'App.ScriptBuilderLocation.LocationSummary',
+ example: "await geoTo('Enlem', 'Boylam')",
+ params: [
+ { key: 'lat', label: 'App.ScriptBuilderLocation.LatLabel', type: 'field' },
+ { key: 'lng', label: 'App.ScriptBuilderLocation.LngLabel', type: 'field' },
+ {
+ key: 'accuracy',
+ label: 'App.ScriptBuilderLocation.AccuracyLabel',
+ type: 'field',
+ optional: true,
+ help: 'App.ScriptBuilderLocation.AccuracyHelp',
+ },
+ ],
+ build: ({ params }) =>
+ `await geoTo(${q(params.lat)}, ${q(params.lng)}${
+ params.accuracy ? `, ${q(params.accuracy)}` : ''
+ })`,
+ describe: ({ params }, t) =>
+ t('::App.ScriptBuilderDescribe.Location', {
+ lat: label(params.lat, t('::App.ScriptBuilderLocation.LatLabel')),
+ lng: label(params.lng, t('::App.ScriptBuilderLocation.LngLabel')),
+ }),
+ },
{
id: 'custom',
label: 'App.ScriptBuilderCustom.CustomLabel',
diff --git a/ui/src/views/developerKit/VisualComponentDesigner.tsx b/ui/src/views/developerKit/VisualComponentDesigner.tsx
index 6c282ac5..67e1b99e 100644
--- a/ui/src/views/developerKit/VisualComponentDesigner.tsx
+++ b/ui/src/views/developerKit/VisualComponentDesigner.tsx
@@ -5732,6 +5732,13 @@ const VisualComponentDesigner = () => {
{target.node.type}
+ {/* Aynı tipten birden çok bileşen listede ayırt edilemiyordu;
+ ref adı script içinde kullanılan addır. */}
+ {target.node.ref && (
+
+ ({target.node.ref})
+
+ )}
{target.eventNames.length}
diff --git a/ui/src/views/form/editors/ImageUploadEditorComponent.tsx b/ui/src/views/form/editors/ImageUploadEditorComponent.tsx
index 8e289345..90ccbc21 100644
--- a/ui/src/views/form/editors/ImageUploadEditorComponent.tsx
+++ b/ui/src/views/form/editors/ImageUploadEditorComponent.tsx
@@ -1,6 +1,6 @@
import { ImageUploadOptionsDto } from '@/proxy/form/models'
import { ReactElement, useEffect, useMemo, useRef, useState } from 'react'
-import { FaSpinner, FaUpload } from 'react-icons/fa'
+import { FaCamera, FaSpinner, FaUpload } from 'react-icons/fa'
import { Button } from '@/components/ui'
import { useLocalization } from '@/utils/hooks/useLocalization'
import {
@@ -10,6 +10,7 @@ import {
} from './imageHoverPreview'
import { normalizeImageSize } from '../../list/shared/imageValue'
import Input from '@/components/ui/Input'
+import { supportsPhotoCapture } from '@/utils/device'
const ImageUploadEditorComponent = ({
value,
@@ -34,6 +35,9 @@ const ImageUploadEditorComponent = ({
const thumbW = normalizeImageSize(resolvedOptions.width, 40)
const thumbH = normalizeImageSize(resolvedOptions.height, 40)
+ // Alanda `capture` acikca ayarliysa kullanicinin istedigi tek davranis odur;
+ // aksi halde dokunmatik cihazda dosya seciciye ek olarak kamera sunulur.
+ const showCameraButton = !resolvedOptions.capture && supportsPhotoCapture()
const valueUrls = useMemo(
() => (Array.isArray(value) ? value : [value]).filter(Boolean) as string[],
@@ -43,6 +47,7 @@ const ImageUploadEditorComponent = ({
const [urls, setUrls] = useState(valueUrls)
const [uploading, setUploading] = useState(false)
const inputRef = useRef(null)
+ const cameraInputRef = useRef(null)
// Değer dışarıdan (editör script'i, veri yenileme…) değiştiğinde önizlemeler
// güncellenmiyordu; state yalnızca ilk render'da kuruluyordu.
@@ -98,6 +103,8 @@ const ImageUploadEditorComponent = ({
} finally {
setUploading(false)
if (inputRef.current) inputRef.current.value = ''
+ // Ayni fotografi ikinci kez cekmek degisiklik olayini tetiklemezdi.
+ if (cameraInputRef.current) cameraInputRef.current.value = ''
}
}
@@ -159,13 +166,27 @@ const ImageUploadEditorComponent = ({
ref={inputRef}
type="file"
accept={resolvedOptions.accept ?? 'image/*'}
+ capture={resolvedOptions.capture}
multiple={isMultiple}
style={{ display: 'none' }}
onChange={(e) => handleFiles(e.target.files)}
/>
+ {/* Kamera ayri bir input'tur: `capture` dosya seciciyi kapattigi icin
+ tek input ile hem galeri hem kamera sunulamaz. */}
+ {showCameraButton && (
+ handleFiles(e.target.files)}
+ />
+ )}
inputRef.current?.click()}
variant="solid"
@@ -190,6 +211,19 @@ const ImageUploadEditorComponent = ({
)}
+ {showCameraButton && (
+ cameraInputRef.current?.click()}
+ >
+
+
+ )}
>
)}
diff --git a/ui/src/views/list/editors/ImageUploadEditorComponent.tsx b/ui/src/views/list/editors/ImageUploadEditorComponent.tsx
index dbf08b43..d15c0908 100644
--- a/ui/src/views/list/editors/ImageUploadEditorComponent.tsx
+++ b/ui/src/views/list/editors/ImageUploadEditorComponent.tsx
@@ -1,6 +1,6 @@
import type { ReactElement } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
-import { FaSpinner, FaUpload } from 'react-icons/fa'
+import { FaCamera, FaSpinner, FaUpload } from 'react-icons/fa'
import { Button } from '@/components/ui'
import { useLocalization } from '@/utils/hooks/useLocalization'
import {
@@ -10,9 +10,12 @@ import {
} from '../../form/editors/imageHoverPreview'
import { normalizeImageSize, parseJsonObject } from '../shared/imageValue'
import Input from '@/components/ui/Input'
+import { supportsPhotoCapture } from '@/utils/device'
type ImageUploadOptions = {
accept?: string
+ /** Mobilde dosya seçici yerine kamerayı açar. */
+ capture?: 'environment' | 'user'
fileFieldName?: string
height?: number | string
multiple?: boolean | string
@@ -79,6 +82,7 @@ const ImageUploadEditorComponent = (cellElement: any): ReactElement => {
const col = cellElement.column
const [uploading, setUploading] = useState(false)
const inputRef = useRef(null)
+ const cameraInputRef = useRef(null)
const [urls, setUrls] = useState(() => normalizeUploadValue(cellElement.value))
useEffect(() => {
@@ -96,6 +100,9 @@ const ImageUploadEditorComponent = (cellElement: any): ReactElement => {
const thumbW = normalizeImageSize(options.width, 40)
const thumbH = normalizeImageSize(options.height, 40)
+ // Alanda `capture` acikca ayarliysa kullanicinin istedigi tek davranis odur;
+ // aksi halde dokunmatik cihazda dosya seciciye ek olarak kamera sunulur.
+ const showCameraButton = !options.capture && supportsPhotoCapture()
const removeImage = (index: number) => {
const newUrls = urls.filter((_, i) => i !== index)
@@ -141,6 +148,8 @@ const ImageUploadEditorComponent = (cellElement: any): ReactElement => {
} finally {
setUploading(false)
if (inputRef.current) inputRef.current.value = ''
+ // Ayni fotografi ikinci kez cekmek degisiklik olayini tetiklemezdi.
+ if (cameraInputRef.current) cameraInputRef.current.value = ''
}
}
@@ -198,13 +207,27 @@ const ImageUploadEditorComponent = (cellElement: any): ReactElement => {
ref={inputRef}
type="file"
accept={options.accept ?? 'image/*'}
+ capture={options.capture}
multiple={isMultiple}
style={{ display: 'none' }}
onChange={(e) => handleFiles(e.target.files)}
/>
+ {/* Kamera ayrı bir input'tur: `capture` dosya seçiciyi kapattığı için
+ tek input ile hem galeri hem kamera sunulamaz. */}
+ {showCameraButton && (
+ handleFiles(e.target.files)}
+ />
+ )}
{
)}
+ {showCameraButton && (
+ cameraInputRef.current?.click()}
+ >
+
+
+ )}
)
}
diff --git a/ui/src/views/list/shared/editingForm.ts b/ui/src/views/list/shared/editingForm.ts
index 69068ff4..329e06e3 100644
--- a/ui/src/views/list/shared/editingForm.ts
+++ b/ui/src/views/list/shared/editingForm.ts
@@ -13,6 +13,7 @@ import {
GridDto,
PlatformEditorTypes,
} from '@/proxy/form/models'
+import { scanBarcode } from '@/components/shared/BarcodeScanner/scanBarcode'
import { getNextSequenceValue } from '@/services/form.service'
import type { EditorType2, RowMode, SimpleItemWithColData } from '../../form/types'
import {
@@ -26,6 +27,21 @@ import {
export type EditorOptionsWithButtons = { buttons?: any[] } & Record
+/**
+ * `editorOptions.buttons` içinde bu değeri taşıyan `onClick`, kamerayla barkod
+ * okuyup değeri alana yazar. Token kullanılmasının nedeni, buton tanımının
+ * veritabanında JSON olarak durması ve oradan fonksiyon taşınamamasıdır.
+ */
+const SCAN_BARCODE_TOKEN = '@SCAN_BARCODE'
+
+/** Token'ın arkasındaki gerçek davranış. Okuma iptal edilirse alana dokunulmaz. */
+const scanBarcodeIntoEditor = async (e: any) => {
+ const code = await scanBarcode()
+ if (code !== null) {
+ e?.component?.option('value', code)
+ }
+}
+
/* ------------------------------------------------------------------ *
* Satır anahtarı yardımcıları
* ------------------------------------------------------------------ */
@@ -476,7 +492,9 @@ export const createFormItemMapper = ({
if (editorOptions.buttons) {
editorOptions.buttons = editorOptions.buttons.map((btn: any) => {
- if (typeof btn?.options?.onClick === 'string') {
+ if (btn?.options?.onClick === SCAN_BARCODE_TOKEN) {
+ btn.options.onClick = scanBarcodeIntoEditor
+ } else if (typeof btn?.options?.onClick === 'string') {
// Yapılandırmadan gelen fonksiyon tanımı (platform tasarımı gereği).
btn.options.onClick = eval(`(${btn.options.onClick})`)
}
diff --git a/ui/src/views/list/useToolbar.tsx b/ui/src/views/list/useToolbar.tsx
index 84c72c6e..f05df674 100644
--- a/ui/src/views/list/useToolbar.tsx
+++ b/ui/src/views/list/useToolbar.tsx
@@ -15,6 +15,10 @@ import { workflowService } from '@/services/workflow.service'
import type { WorkflowRunResultDto } from '@/services/workflow.service'
import { splitWorkflowApprovers, translateWorkflowLabel } from '@/utils/workflow/workflowHelpers'
import { safeJsonParse } from './Utils'
+import { BarcodeScannerDialog } from '@/components/shared'
+
+/** Toolbar butonunun `dialogName` degeri; surekli barkod taramasini acar. */
+const BARCODE_SCAN_DIALOG = 'BarcodeScanDialog'
type ToolbarModalData = {
open: boolean
@@ -87,6 +91,56 @@ const useToolbar = ({
const [toolbarModalData, setToolbarModalData] = useState()
const grdOpt = gridDto?.gridOptions
+ /**
+ * Surekli barkod taramasi. Okunan her kod listeye yeni bir satir olarak
+ * yazilir; ekranin kendi insert ucundan gectigi icin kiracin izolasyonu ve
+ * yetki kontrolu normal satir eklemeyle aynidir.
+ */
+ const openBarcodeScan = (action: { dialogParameters?: string }) => {
+ const parameters = safeJsonParse>(action.dialogParameters, {})
+ const targetField = parameters.field
+ const insertUrl = grdOpt?.insertServiceAddress
+
+ if (!targetField || !insertUrl) {
+ toast.push(
+
+ {translate('::App.BarcodeScanner.NotConfigured')}
+ ,
+ { placement: 'bottom-end' },
+ )
+ return
+ }
+
+ // Barkod disindaki sabit alanlar (durum, depo vb.) dialogParameters'tan gelir.
+ const { field: _field, ...defaults } = parameters
+
+ setToolbarModalData({
+ open: true,
+ content: (
+ {
+ try {
+ await dynamicFetch(insertUrl, 'POST', undefined, {
+ data: { ...defaults, [targetField]: code },
+ listFormCode,
+ })
+ return true
+ } catch {
+ // Diyalog acik kalir; kullanici kodu tekrar okutabilir.
+ return false
+ }
+ }}
+ onDialogClose={() => {
+ setToolbarModalData(undefined)
+ refreshData()
+ }}
+ />
+ ),
+ })
+ }
+
function getToolbarData() {
const items: ToolbarItem[] = []
@@ -673,7 +727,9 @@ const useToolbar = ({
if (typeof e.event?.preventDefault === 'function') {
e?.event?.preventDefault()
}
- if (action.url) {
+ if (action.dialogName === BARCODE_SCAN_DIALOG) {
+ openBarcodeScan(action)
+ } else if (action.url) {
let url = action.url
// griddeki secili satirlari al
const selectedRowsData = getSelectedRowsData()