Ülkeye göre Phone Mask ayarlandı
This commit is contained in:
parent
35118ab2d9
commit
c1454ab818
24 changed files with 950 additions and 116 deletions
33
.github/instructions/lowcode.instructions.md
vendored
33
.github/instructions/lowcode.instructions.md
vendored
|
|
@ -2809,6 +2809,7 @@ Preset mevcut JSON ile **birleşir**, diğer ayarları silmez; aynı anahtar var
|
||||||
| 2 ondalık · Artırmalı sayı · Para | `format.fixedPoint(2)` · + `useMaskBehavior` + `showSpinButtons` · `format.currency TRY` |
|
| 2 ondalık · Artırmalı sayı · Para | `format.fixedPoint(2)` · + `useMaskBehavior` + `showSpinButtons` · `format.currency TRY` |
|
||||||
| Tarih · Tarih-saat · Saat | `type`+`displayFormat`+`dateSerializationFormat` üçlüsü (saatte ayrıca `pickerType:list`, `interval`, `width:100%`) |
|
| Tarih · Tarih-saat · Saat | `type`+`displayFormat`+`dateSerializationFormat` üçlüsü (saatte ayrıca `pickerType:list`, `interval`, `width:100%`) |
|
||||||
| Telefon maskesi | `mask`, `maskRules.X`, `maskInvalidMessage`, `placeholder` |
|
| Telefon maskesi | `mask`, `maskRules.X`, `maskInvalidMessage`, `placeholder` |
|
||||||
|
| Telefon (Türkiye) · (uluslararası) · (ülkeye göre) | `{"format":"phoneTR"}` · `{"format":"phoneIntl"}` · `{"format":"phoneCountry","phoneCountryField":"Country"}` — ayrıntı §6.4.1 |
|
||||||
| Otomatik büyüyen metin | `autoResizeEnabled`, `minHeight`, `maxHeight` |
|
| Otomatik büyüyen metin | `autoResizeEnabled`, `minHeight`, `maxHeight` |
|
||||||
| Aranabilir liste | `searchEnabled`, `searchMode:contains`, `searchTimeout`, `showClearButton` |
|
| Aranabilir liste | `searchEnabled`, `searchMode:contains`, `searchTimeout`, `showClearButton` |
|
||||||
| TagBox anında seçim | `showSelectionControls`, `applyValueMode:instantly`, `searchEnabled` |
|
| TagBox anında seçim | `showSelectionControls`, `applyValueMode:instantly`, `searchEnabled` |
|
||||||
|
|
@ -2819,6 +2820,37 @@ Preset mevcut JSON ile **birleşir**, diğer ayarları silmez; aynı anahtar var
|
||||||
| 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. |
|
| 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. |
|
| 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.4.1 Telefon biçimleri (`format` token'ı)
|
||||||
|
|
||||||
|
Telefon alanı (`dxTextBox`) biçimini `editorOptions.format` token'ından alır; maske, örnek ve liste/kart
|
||||||
|
görüntüsü `ui/src/utils/phoneFormat.ts`'te üretilir (form editörü `withEditorTypeDefaults`, kolon
|
||||||
|
`useListFormColumns` üzerinden — Grid, Tree, Card, Scheduler ve Form sayfası aynı yoldan geçer). Değer
|
||||||
|
her biçimde **yalnızca rakam** saklanır (`useMaskedValue: false`); yapılandırmada açıkça yazılan seçenek
|
||||||
|
varsayılanı ezer.
|
||||||
|
|
||||||
|
| Token | Editör | Saklanan | Listede |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `phoneGlobal` | `(000) 000-0000` | 10 hane | `(555) 123-4567` |
|
||||||
|
| `phoneTR` | `(000) 000 00 00` | 10 hane | `(532) 123 45 67` |
|
||||||
|
| `phoneIntl` | `+` + en çok 15 hane | ülke kodu + numara | ülke kodu `Sas_H_Country.PhoneCode`'dan (en uzun önek) bulunur: `+49 301 234 567` |
|
||||||
|
| `phoneCountry` | formdaki `phoneCountryField` (varsayılan `Country`) alanının ülkesine göre: kod sabit önek, `PhoneNumberFormat` kalıbı, `PhoneNumberMinLength` kadar hane zorunlu, `PhoneNumberMaxLength`'e kadar isteğe bağlı; ülke seçilmemişse **Türkiye** (`+90 (000) 000-0000`) | ulusal numara (kodsuz) | satırın ülkesiyle `+49 301 234 567`; ülkesiz satır Türkiye biçiminde |
|
||||||
|
|
||||||
|
- **Ülke ve telefon alanı olan her ekran `phoneCountry` kullanır** (Kiracılar, Şubeler, Satış Siparişleri,
|
||||||
|
Kontaklar, Adaylar). Ülke alanı olmayan ekran `phoneGlobal`/`phoneTR`/`phoneIntl`'den birini seçer.
|
||||||
|
- Formda telefon alanları adres zincirinden (Ülke › Şehir › İlçe › varsa Mahalle/Posta Kodu) **sonra** gelir
|
||||||
|
(`EditGroupOrderNo` + `EditOrderNo`): kullanıcı önce ülkeyi seçer, maske ona göre açılır.
|
||||||
|
- Ülke alanı olan her ekranda yeni kayıt Türkiye ile açılır: `FormFieldsDefaultValueJson` →
|
||||||
|
`{ FieldName: "Country", FieldDbType: String, Value: "Türkiye", CustomValueType: Value }`.
|
||||||
|
- Ülke alanı değişince maske yenilenir: Card/Scheduler/Form sayfasında formun `fieldDataChanged` olayıyla,
|
||||||
|
Grid/Tree'de `onEditorPreparing`'deki `attachFieldValueBroadcast` olayıyla (DataGrid formu `fieldDataChanged`
|
||||||
|
yayınlamaz; `editorOptions.onValueChanged` da kullanılamaz — editör fabrikası onu kendi değer yazan
|
||||||
|
işleyicisinin üstüne birleştirir). Satır içi düzenlemede (form yok) maske satırın ülkesiyle açılır, değişikliği izlemez.
|
||||||
|
- `phoneCountry` ekranında sabit uzunluk `pattern`'ı (`^[0-9]{10}$`) yazılmaz; uzunluğu maske denetler,
|
||||||
|
gerekirse `^[0-9]{4,15}$`.
|
||||||
|
- Ülke listesi `GET /api/app/public/country`'den bir kez yüklenir ve önbellekte kalır.
|
||||||
|
- Dinamik raporlar (Grid/Tree/Form) aynı biçimi sunucuda `DynamicReportPhoneFormatter` ile basar; `phoneCountry`
|
||||||
|
kolonu satırın ülke kolonunu biçim ifadesine beşinci operand olarak verir. Kural `phoneFormat.ts` ile birebir aynıdır.
|
||||||
|
|
||||||
### 6.5 Seeder tarafı (C#)
|
### 6.5 Seeder tarafı (C#)
|
||||||
|
|
||||||
| İhtiyaç | C# | Ürettiği JSON (özet) |
|
| İhtiyaç | C# | Ürettiği JSON (özet) |
|
||||||
|
|
@ -2830,6 +2862,7 @@ Preset mevcut JSON ile **birleşir**, diğer ayarları silmez; aynı anahtar var
|
||||||
| Yüzde | `EditorOptions.Percent()` | |
|
| Yüzde | `EditorOptions.Percent()` | |
|
||||||
| Tarih / tarih-saat / saat | `EditorOptions.Date()` · `DateTime()` · `Time(15)` | |
|
| Tarih / tarih-saat / saat | `EditorOptions.Date()` · `DateTime()` · `Time(15)` | |
|
||||||
| Telefon | `EditorOptions.Phone()` | global format + maske + örnek |
|
| Telefon | `EditorOptions.Phone()` | global format + maske + örnek |
|
||||||
|
| Telefon (ülkeye göre) | `EditorOptions.PhoneByCountry("Country")` | `{"format":"phoneCountry","phoneCountryField":"Country","showClearButton":true}` |
|
||||||
| Kaydırıcı | `EditorOptions.Slider()` | `{"tooltip":{"enabled":true}}` |
|
| Kaydırıcı | `EditorOptions.Slider()` | `{"tooltip":{"enabled":true}}` |
|
||||||
| Görsel yükleme | `EditorOptions.ImageUpload(multiple: true, 80, 80)` | |
|
| Görsel yükleme | `EditorOptions.ImageUpload(multiple: true, 80, 80)` | |
|
||||||
| Zengin metin | `EditorOptions.Html(240)` | `htmlEditorOptions.ts` varsayılanıyla aynı bloklar (tam araç çubuğu, `imageUpload`, `mediaResizing`, `tableResizing`, `tableContextMenu`; biri değişirse diğeri de değişir) + `encodeHtml` + `elementAttr` `min-height:240px` (sabit yükseklik değil; içerikle büyür) |
|
| Zengin metin | `EditorOptions.Html(240)` | `htmlEditorOptions.ts` varsayılanıyla aynı bloklar (tam araç çubuğu, `imageUpload`, `mediaResizing`, `tableResizing`, `tableContextMenu`; biri değişirse diğeri de değişir) + `encodeHtml` + `elementAttr` `min-height:240px` (sabit yükseklik değil; içerikle büyür) |
|
||||||
|
|
|
||||||
|
|
@ -259,7 +259,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Country",
|
FieldName = "Country",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 13,
|
EditOrderNo = 10,
|
||||||
EditorType2 = EditorTypes.dxSelectBox,
|
EditorType2 = EditorTypes.dxSelectBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.ShowClearButton(),
|
EditorOptions = EditorOptions.ShowClearButton(),
|
||||||
|
|
@ -289,7 +289,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "City",
|
FieldName = "City",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 14,
|
EditOrderNo = 11,
|
||||||
EditorType2 = EditorTypes.dxSelectBox,
|
EditorType2 = EditorTypes.dxSelectBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.ShowClearButton(),
|
EditorOptions = EditorOptions.ShowClearButton(),
|
||||||
|
|
@ -322,7 +322,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "District",
|
FieldName = "District",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 15,
|
EditOrderNo = 12,
|
||||||
EditorType2 = EditorTypes.dxSelectBox,
|
EditorType2 = EditorTypes.dxSelectBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.ShowClearButton(),
|
EditorOptions = EditorOptions.ShowClearButton(),
|
||||||
|
|
@ -355,7 +355,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Township",
|
FieldName = "Township",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 16,
|
EditOrderNo = 13,
|
||||||
EditorType2 = EditorTypes.dxSelectBox,
|
EditorType2 = EditorTypes.dxSelectBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.ShowClearButton(),
|
EditorOptions = EditorOptions.ShowClearButton(),
|
||||||
|
|
@ -429,7 +429,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "PostalCode",
|
FieldName = "PostalCode",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 17,
|
EditOrderNo = 14,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.ShowClearButton(),
|
EditorOptions = EditorOptions.ShowClearButton(),
|
||||||
|
|
@ -450,7 +450,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Email",
|
FieldName = "Email",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 10,
|
EditOrderNo = 7,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
CaptionName = "App.Listform.ListformField.Email",
|
CaptionName = "App.Listform.ListformField.Email",
|
||||||
|
|
@ -471,7 +471,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Website",
|
FieldName = "Website",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 11,
|
EditOrderNo = 8,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
CaptionName = "App.Listform.ListformField.Website",
|
CaptionName = "App.Listform.ListformField.Website",
|
||||||
|
|
@ -492,10 +492,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "PhoneNumber",
|
FieldName = "PhoneNumber",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 7,
|
EditOrderNo = 16,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.Phone(),
|
EditorOptions = EditorOptions.PhoneByCountry(),
|
||||||
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
|
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 16,
|
ListOrderNo = 16,
|
||||||
|
|
@ -514,10 +514,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "MobileNumber",
|
FieldName = "MobileNumber",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 6,
|
EditOrderNo = 15,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.Phone(),
|
EditorOptions = EditorOptions.PhoneByCountry(),
|
||||||
CaptionName = "App.Listform.ListformField.MobileNumber",
|
CaptionName = "App.Listform.ListformField.MobileNumber",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 17,
|
ListOrderNo = 17,
|
||||||
|
|
@ -535,10 +535,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.Int64,
|
SourceDbType = DbType.Int64,
|
||||||
FieldName = "FaxNumber",
|
FieldName = "FaxNumber",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 8,
|
EditOrderNo = 17,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.Phone(),
|
EditorOptions = EditorOptions.PhoneByCountry(),
|
||||||
CaptionName = "App.Listform.ListformField.FaxNumber",
|
CaptionName = "App.Listform.ListformField.FaxNumber",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 18,
|
ListOrderNo = 18,
|
||||||
|
|
@ -556,7 +556,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.Boolean,
|
SourceDbType = DbType.Boolean,
|
||||||
FieldName = "IsActive",
|
FieldName = "IsActive",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 12,
|
EditOrderNo = 9,
|
||||||
EditorType2 = EditorTypes.dxCheckBox,
|
EditorType2 = EditorTypes.dxCheckBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
CaptionName = "App.Listform.ListformField.IsActive",
|
CaptionName = "App.Listform.ListformField.IsActive",
|
||||||
|
|
@ -575,7 +575,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Modules",
|
FieldName = "Modules",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 9,
|
EditOrderNo = 6,
|
||||||
EditorType2 = EditorTypes.dxTagBox,
|
EditorType2 = EditorTypes.dxTagBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
CaptionName = "App.Listform.ListformField.Modules",
|
CaptionName = "App.Listform.ListformField.Modules",
|
||||||
|
|
@ -932,7 +932,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Address1",
|
FieldName = "Address1",
|
||||||
EditGroupOrderNo = 2,
|
EditGroupOrderNo = 2,
|
||||||
EditOrderNo = 7,
|
EditOrderNo = 11,
|
||||||
EditorType2 = EditorTypes.dxTextArea,
|
EditorType2 = EditorTypes.dxTextArea,
|
||||||
ColSpan = 3,
|
ColSpan = 3,
|
||||||
CaptionName = "App.Listform.ListformField.Address1",
|
CaptionName = "App.Listform.ListformField.Address1",
|
||||||
|
|
@ -953,7 +953,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Address2",
|
FieldName = "Address2",
|
||||||
EditGroupOrderNo = 2,
|
EditGroupOrderNo = 2,
|
||||||
EditOrderNo = 8,
|
EditOrderNo = 12,
|
||||||
EditorType2 = EditorTypes.dxTextArea,
|
EditorType2 = EditorTypes.dxTextArea,
|
||||||
ColSpan = 3,
|
ColSpan = 3,
|
||||||
CaptionName = "App.Listform.ListformField.Address2",
|
CaptionName = "App.Listform.ListformField.Address2",
|
||||||
|
|
@ -973,8 +973,8 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "PostalCode",
|
FieldName = "PostalCode",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 2,
|
||||||
EditOrderNo = 9,
|
EditOrderNo = 5,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
CaptionName = "App.Listform.ListformField.PostalCode",
|
CaptionName = "App.Listform.ListformField.PostalCode",
|
||||||
|
|
@ -995,7 +995,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Email",
|
FieldName = "Email",
|
||||||
EditGroupOrderNo = 2,
|
EditGroupOrderNo = 2,
|
||||||
EditOrderNo = 5,
|
EditOrderNo = 9,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
CaptionName = "App.Listform.ListformField.Email",
|
CaptionName = "App.Listform.ListformField.Email",
|
||||||
|
|
@ -1017,7 +1017,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "Website",
|
FieldName = "Website",
|
||||||
EditGroupOrderNo = 2,
|
EditGroupOrderNo = 2,
|
||||||
EditOrderNo = 6,
|
EditOrderNo = 10,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
CaptionName = "App.Listform.ListformField.Website",
|
CaptionName = "App.Listform.ListformField.Website",
|
||||||
|
|
@ -1038,11 +1038,11 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "PhoneNumber",
|
FieldName = "PhoneNumber",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 2,
|
||||||
EditOrderNo = 7,
|
EditOrderNo = 7,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.Phone(),
|
EditorOptions = EditorOptions.PhoneByCountry(),
|
||||||
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
|
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 15,
|
ListOrderNo = 15,
|
||||||
|
|
@ -1061,11 +1061,11 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.String,
|
SourceDbType = DbType.String,
|
||||||
FieldName = "MobileNumber",
|
FieldName = "MobileNumber",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 2,
|
||||||
EditOrderNo = 6,
|
EditOrderNo = 6,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.Phone(),
|
EditorOptions = EditorOptions.PhoneByCountry(),
|
||||||
CaptionName = "App.Listform.ListformField.MobileNumber",
|
CaptionName = "App.Listform.ListformField.MobileNumber",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 16,
|
ListOrderNo = 16,
|
||||||
|
|
@ -1083,11 +1083,11 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.Int64,
|
SourceDbType = DbType.Int64,
|
||||||
FieldName = "FaxNumber",
|
FieldName = "FaxNumber",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 2,
|
||||||
EditOrderNo = 8,
|
EditOrderNo = 8,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.Phone(),
|
EditorOptions = EditorOptions.PhoneByCountry(),
|
||||||
CaptionName = "App.Listform.ListformField.FaxNumber",
|
CaptionName = "App.Listform.ListformField.FaxNumber",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 17,
|
ListOrderNo = 17,
|
||||||
|
|
@ -1106,7 +1106,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SourceDbType = DbType.Boolean,
|
SourceDbType = DbType.Boolean,
|
||||||
FieldName = "IsActive",
|
FieldName = "IsActive",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 10,
|
EditOrderNo = 6,
|
||||||
EditorType2 = EditorTypes.dxCheckBox,
|
EditorType2 = EditorTypes.dxCheckBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
CaptionName = "App.Listform.ListformField.IsActive",
|
CaptionName = "App.Listform.ListformField.IsActive",
|
||||||
|
|
@ -7904,7 +7904,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
EditOrderNo = 14,
|
EditOrderNo = 14,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.Phone(),
|
EditorOptions = EditorOptions.PhoneByCountry(),
|
||||||
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
|
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 18,
|
ListOrderNo = 18,
|
||||||
|
|
@ -7927,7 +7927,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
EditOrderNo = 15,
|
EditOrderNo = 15,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.Phone(),
|
EditorOptions = EditorOptions.PhoneByCountry(),
|
||||||
CaptionName = "App.Listform.ListformField.MobileNumber",
|
CaptionName = "App.Listform.ListformField.MobileNumber",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 19,
|
ListOrderNo = 19,
|
||||||
|
|
@ -7949,7 +7949,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
EditOrderNo = 16,
|
EditOrderNo = 16,
|
||||||
EditorType2 = EditorTypes.dxTextBox,
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
EditorOptions = EditorOptions.Phone(),
|
EditorOptions = EditorOptions.PhoneByCountry(),
|
||||||
CaptionName = "App.Listform.ListformField.FaxNumber",
|
CaptionName = "App.Listform.ListformField.FaxNumber",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 20,
|
ListOrderNo = 20,
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,13 @@ public static class EditorOptions
|
||||||
.Raw("maskRules", "{\"X\":\"[0-9]\"}")
|
.Raw("maskRules", "{\"X\":\"[0-9]\"}")
|
||||||
.Placeholder("(555) 123-4567");
|
.Placeholder("(555) 123-4567");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maskesi formdaki ulke alaninin <c>Sas_H_Country</c> satirindan (kod, bicim, en az/en cok hane)
|
||||||
|
/// gelen telefon; deger ulke kodu olmadan saklanir.
|
||||||
|
/// </summary>
|
||||||
|
public static EditorOptionsBuilder PhoneByCountry(string countryField = "Country")
|
||||||
|
=> New().Format("phoneCountry").Text("phoneCountryField", countryField).ShowClearButton();
|
||||||
|
|
||||||
/// <summary>Kaydirma cubugu; degeri ipucu olarak gosterir.</summary>
|
/// <summary>Kaydirma cubugu; degeri ipucu olarak gosterir.</summary>
|
||||||
public static EditorOptionsBuilder Slider()
|
public static EditorOptionsBuilder Slider()
|
||||||
=> New().Raw("tooltip", "{\"enabled\":true}");
|
=> New().Raw("tooltip", "{\"enabled\":true}");
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,7 @@ public class DynamicFormReport : XtraReport
|
||||||
.OrderBy(x => x.ListOrderNo ?? int.MaxValue)
|
.OrderBy(x => x.ListOrderNo ?? int.MaxValue)
|
||||||
.ThenBy(x => x.CaptionName)
|
.ThenBy(x => x.CaptionName)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
await DynamicReportImageHelper.EnsurePhoneCountriesAsync(serviceProvider, fields);
|
||||||
var customizations = await customizationManager.GetUsersServerCustomizations(listFormCode);
|
var customizations = await customizationManager.GetUsersServerCustomizations(listFormCode);
|
||||||
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager
|
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager
|
||||||
.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
||||||
|
|
@ -524,7 +525,11 @@ public class DynamicFormReport : XtraReport
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DynamicReportImageHelper.TryFormatReportValue(field.Field, rawValue, field.EditorType, out var formattedValue))
|
var phoneCountryField = DynamicReportImageHelper.GetPhoneCountryFieldName(field.Field);
|
||||||
|
var phoneCountry = phoneCountryField != null && DynamicReportImageHelper.TryGetRowValue(row, phoneCountryField, out var country)
|
||||||
|
? country
|
||||||
|
: null;
|
||||||
|
if (DynamicReportImageHelper.TryFormatReportValue(field.Field, rawValue, field.EditorType, phoneCountry, out var formattedValue))
|
||||||
{
|
{
|
||||||
text = formattedValue;
|
text = formattedValue;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ public class DynamicGridReport : XtraReport
|
||||||
.OrderBy(x => x.ListOrderNo ?? int.MaxValue)
|
.OrderBy(x => x.ListOrderNo ?? int.MaxValue)
|
||||||
.ThenBy(x => x.CaptionName)
|
.ThenBy(x => x.CaptionName)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
await DynamicReportImageHelper.EnsurePhoneCountriesAsync(scope.ServiceProvider, fields);
|
||||||
var customizations = await customizationManager.GetUsersServerCustomizations(listFormCode);
|
var customizations = await customizationManager.GetUsersServerCustomizations(listFormCode);
|
||||||
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
||||||
|
|
||||||
|
|
@ -202,7 +203,7 @@ public class DynamicGridReport : XtraReport
|
||||||
editorTypesByField.TryGetValue(field.FieldName, out var editorType);
|
editorTypesByField.TryGetValue(field.FieldName, out var editorType);
|
||||||
return DynamicReportColumnFactory.Create(
|
return DynamicReportColumnFactory.Create(
|
||||||
field,
|
field,
|
||||||
selectFieldsByName[field.FieldName],
|
selectFieldsByName,
|
||||||
defaultValueHelper,
|
defaultValueHelper,
|
||||||
editorType,
|
editorType,
|
||||||
localizer);
|
localizer);
|
||||||
|
|
@ -429,7 +430,8 @@ public class DynamicGridReport : XtraReport
|
||||||
DynamicReportImageHelper.CreateFormatValueExpression(
|
DynamicReportImageHelper.CreateFormatValueExpression(
|
||||||
$"[{groupField.BindingName}]",
|
$"[{groupField.BindingName}]",
|
||||||
groupField.Field,
|
groupField.Field,
|
||||||
groupField.EditorType)));
|
groupField.EditorType,
|
||||||
|
groupField.PhoneCountryExpression)));
|
||||||
|
|
||||||
band.Controls.AddRange([captionLabel, valueLabel]);
|
band.Controls.AddRange([captionLabel, valueLabel]);
|
||||||
bands.Add(band);
|
bands.Add(band);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,13 @@ internal sealed record DynamicReportColumn(
|
||||||
{
|
{
|
||||||
public float ImageWidth => DynamicReportImageHelper.GetGridImageColumnWidth(ImageCount);
|
public float ImageWidth => DynamicReportImageHelper.GetGridImageColumnWidth(ImageCount);
|
||||||
|
|
||||||
|
/// <summary><c>phoneCountry</c> biçimli telefon kolonunda satırın ülke değerini taşıyan kolon.</summary>
|
||||||
|
public string PhoneCountryBindingName { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Biçim ifadesine verilecek ülke operandı; telefon kolonu değilse <c>null</c>.</summary>
|
||||||
|
public string PhoneCountryExpression =>
|
||||||
|
string.IsNullOrWhiteSpace(PhoneCountryBindingName) ? null : $"[{PhoneCountryBindingName}]";
|
||||||
|
|
||||||
public bool HasBindingName =>
|
public bool HasBindingName =>
|
||||||
!string.IsNullOrWhiteSpace(BindingName) && BindingName != "undefined";
|
!string.IsNullOrWhiteSpace(BindingName) && BindingName != "undefined";
|
||||||
}
|
}
|
||||||
|
|
@ -32,6 +39,21 @@ internal sealed record DynamicReportColumn(
|
||||||
internal static class DynamicReportColumnFactory
|
internal static class DynamicReportColumnFactory
|
||||||
{
|
{
|
||||||
public static DynamicReportColumn Create(
|
public static DynamicReportColumn Create(
|
||||||
|
ListFormField field,
|
||||||
|
IReadOnlyDictionary<string, SelectField> selectFieldsByName,
|
||||||
|
DefaultValueHelper defaultValueHelper,
|
||||||
|
string editorType,
|
||||||
|
IStringLocalizer<PlatformResource> localizer)
|
||||||
|
{
|
||||||
|
var column = CreateColumn(field, selectFieldsByName[field.FieldName], defaultValueHelper, editorType, localizer);
|
||||||
|
var phoneCountryField = DynamicReportImageHelper.GetPhoneCountryFieldName(field);
|
||||||
|
|
||||||
|
return phoneCountryField != null && selectFieldsByName.TryGetValue(phoneCountryField, out var countrySelectField)
|
||||||
|
? column with { PhoneCountryBindingName = DynamicReportImageHelper.GetReportFieldName(countrySelectField) }
|
||||||
|
: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DynamicReportColumn CreateColumn(
|
||||||
ListFormField field,
|
ListFormField field,
|
||||||
SelectField selectField,
|
SelectField selectField,
|
||||||
DefaultValueHelper defaultValueHelper,
|
DefaultValueHelper defaultValueHelper,
|
||||||
|
|
|
||||||
|
|
@ -531,17 +531,42 @@ internal static class DynamicReportImageHelper
|
||||||
ListFormField field,
|
ListFormField field,
|
||||||
object value,
|
object value,
|
||||||
string editorType,
|
string editorType,
|
||||||
|
object phoneCountry,
|
||||||
out string text)
|
out string text)
|
||||||
{
|
{
|
||||||
return TryFormatReportValue(value, field.SourceDbType, GetEffectiveFormat(field), editorType, out text);
|
return TryFormatReportValue(value, field.SourceDbType, GetEffectiveFormat(field), editorType, phoneCountry, out text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <param name="phoneCountryExpression">
|
||||||
|
/// <c>phoneCountry</c> biçimli telefon kolonunda satırın ülke değeri; diğer kolonlarda <c>null</c>.
|
||||||
|
/// </param>
|
||||||
public static string CreateFormatValueExpression(
|
public static string CreateFormatValueExpression(
|
||||||
string valueExpression,
|
string valueExpression,
|
||||||
ListFormField field,
|
ListFormField field,
|
||||||
string editorType)
|
string editorType,
|
||||||
|
string phoneCountryExpression = null)
|
||||||
{
|
{
|
||||||
return $"{FormatValueFunctionName}({valueExpression}, '{field.SourceDbType}', '{EscapeExpressionString(GetEffectiveFormat(field))}', '{EscapeExpressionString(editorType)}')";
|
var countryOperand = string.IsNullOrWhiteSpace(phoneCountryExpression) ? string.Empty : $", {phoneCountryExpression}";
|
||||||
|
return $"{FormatValueFunctionName}({valueExpression}, '{field.SourceDbType}', '{EscapeExpressionString(GetEffectiveFormat(field))}', '{EscapeExpressionString(editorType)}'{countryOperand})";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <c>phoneCountry</c> biçimli telefon alanının ülkeyi okuduğu alan (<c>phoneCountryField</c>,
|
||||||
|
/// varsayılan <c>Country</c>); başka biçimde <c>null</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static string GetPhoneCountryFieldName(ListFormField field)
|
||||||
|
{
|
||||||
|
return GetEffectiveFormat(field) == DynamicReportPhoneFormatter.ByCountry
|
||||||
|
? TryGetFormatOption(field.EditorOptions, "phoneCountryField") ?? DynamicReportPhoneFormatter.DefaultCountryField
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ülke tablosu gereken telefon biçimi varsa tabloyu yükler; rapor ifadesi eşzamanlı çalışır.</summary>
|
||||||
|
public static Task EnsurePhoneCountriesAsync(IServiceProvider serviceProvider, IEnumerable<ListFormField> fields)
|
||||||
|
{
|
||||||
|
return fields.Any(x => DynamicReportPhoneFormatter.NeedsCountries(GetEffectiveFormat(x)))
|
||||||
|
? DynamicReportPhoneFormatter.LoadCountriesAsync(serviceProvider)
|
||||||
|
: Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string CreateFontFamilyExpression(string valueExpression)
|
public static string CreateFontFamilyExpression(string valueExpression)
|
||||||
|
|
@ -560,6 +585,7 @@ internal static class DynamicReportImageHelper
|
||||||
DbType dbType,
|
DbType dbType,
|
||||||
string format,
|
string format,
|
||||||
string editorType,
|
string editorType,
|
||||||
|
object phoneCountry,
|
||||||
out string text)
|
out string text)
|
||||||
{
|
{
|
||||||
text = null;
|
text = null;
|
||||||
|
|
@ -576,7 +602,7 @@ internal static class DynamicReportImageHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
var formatOptions = ReportFormatOptions.Parse(format);
|
var formatOptions = ReportFormatOptions.Parse(format);
|
||||||
return TryFormatNamedTextValue(formatOptions, value, out text) ||
|
return TryFormatNamedTextValue(formatOptions, value, phoneCountry, out text) ||
|
||||||
TryFormatDateValue(dbType, formatOptions, value, out text) ||
|
TryFormatDateValue(dbType, formatOptions, value, out text) ||
|
||||||
TryFormatNumericValue(dbType, format, value, out text);
|
TryFormatNumericValue(dbType, format, value, out text);
|
||||||
}
|
}
|
||||||
|
|
@ -669,35 +695,16 @@ internal static class DynamicReportImageHelper
|
||||||
private static bool TryFormatNamedTextValue(
|
private static bool TryFormatNamedTextValue(
|
||||||
ReportFormatOptions formatOptions,
|
ReportFormatOptions formatOptions,
|
||||||
object value,
|
object value,
|
||||||
|
object phoneCountry,
|
||||||
out string text)
|
out string text)
|
||||||
{
|
{
|
||||||
text = null;
|
text = null;
|
||||||
if (!string.Equals(formatOptions.Type, "phoneGlobal", StringComparison.OrdinalIgnoreCase))
|
if (!DynamicReportPhoneFormatter.IsPhoneFormat(formatOptions.Type))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var digits = Regex.Replace(
|
text = DynamicReportPhoneFormatter.Format(value, formatOptions.Type, phoneCountry);
|
||||||
Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty,
|
|
||||||
@"\D",
|
|
||||||
string.Empty);
|
|
||||||
|
|
||||||
if (digits.StartsWith("90", StringComparison.Ordinal) && digits.Length > 10)
|
|
||||||
{
|
|
||||||
digits = digits[^10..];
|
|
||||||
}
|
|
||||||
if (digits.StartsWith("0", StringComparison.Ordinal) && digits.Length > 10)
|
|
||||||
{
|
|
||||||
digits = digits[^10..];
|
|
||||||
}
|
|
||||||
if (digits.Length > 10)
|
|
||||||
{
|
|
||||||
digits = digits[^10..];
|
|
||||||
}
|
|
||||||
|
|
||||||
text = digits.Length == 10
|
|
||||||
? $"({digits[..3]}) {digits.Substring(3, 3)}-{digits[6..]}"
|
|
||||||
: Convert.ToString(value, CultureInfo.CurrentCulture) ?? string.Empty;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1223,7 +1230,10 @@ internal static class DynamicReportImageHelper
|
||||||
PlatformConsts.GridColumnOptions.ColumnValueFormatMinute or
|
PlatformConsts.GridColumnOptions.ColumnValueFormatMinute or
|
||||||
PlatformConsts.GridColumnOptions.ColumnValueFormatSecond or
|
PlatformConsts.GridColumnOptions.ColumnValueFormatSecond or
|
||||||
PlatformConsts.GridColumnOptions.ColumnValueFormatShortDateShortTime or
|
PlatformConsts.GridColumnOptions.ColumnValueFormatShortDateShortTime or
|
||||||
"phoneGlobal";
|
DynamicReportPhoneFormatter.Global or
|
||||||
|
DynamicReportPhoneFormatter.Turkey or
|
||||||
|
DynamicReportPhoneFormatter.International or
|
||||||
|
DynamicReportPhoneFormatter.ByCountry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsDateField(DbType dbType)
|
private static bool IsDateField(DbType dbType)
|
||||||
|
|
@ -1893,7 +1903,7 @@ internal static class DynamicReportImageHelper
|
||||||
{
|
{
|
||||||
public string Name => FormatValueFunctionName;
|
public string Name => FormatValueFunctionName;
|
||||||
public int MinOperandCount => 4;
|
public int MinOperandCount => 4;
|
||||||
public int MaxOperandCount => 4;
|
public int MaxOperandCount => 5;
|
||||||
public FunctionCategory Category => FunctionCategory.Text;
|
public FunctionCategory Category => FunctionCategory.Text;
|
||||||
public string Description => "Formats a report value using the active culture.";
|
public string Description => "Formats a report value using the active culture.";
|
||||||
|
|
||||||
|
|
@ -1903,8 +1913,9 @@ internal static class DynamicReportImageHelper
|
||||||
var dbType = ToDbType(Convert.ToString(operands.ElementAtOrDefault(1), CultureInfo.InvariantCulture));
|
var dbType = ToDbType(Convert.ToString(operands.ElementAtOrDefault(1), CultureInfo.InvariantCulture));
|
||||||
var format = Convert.ToString(operands.ElementAtOrDefault(2), CultureInfo.InvariantCulture);
|
var format = Convert.ToString(operands.ElementAtOrDefault(2), CultureInfo.InvariantCulture);
|
||||||
var editorType = Convert.ToString(operands.ElementAtOrDefault(3), CultureInfo.InvariantCulture);
|
var editorType = Convert.ToString(operands.ElementAtOrDefault(3), CultureInfo.InvariantCulture);
|
||||||
|
var phoneCountry = operands.ElementAtOrDefault(4);
|
||||||
|
|
||||||
return TryFormatReportValue(value, dbType, format, editorType, out var text)
|
return TryFormatReportValue(value, dbType, format, editorType, phoneCountry, out var text)
|
||||||
? text
|
? text
|
||||||
: Convert.ToString(value, CultureInfo.CurrentCulture) ?? string.Empty;
|
: Convert.ToString(value, CultureInfo.CurrentCulture) ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
@ -1916,7 +1927,7 @@ internal static class DynamicReportImageHelper
|
||||||
|
|
||||||
public bool IsValidOperandCount(int count)
|
public bool IsValidOperandCount(int count)
|
||||||
{
|
{
|
||||||
return count == 4;
|
return count is 4 or 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsValidOperandType(int operandIndex, int operandCount, Type type)
|
public bool IsValidOperandType(int operandIndex, int operandCount, Type type)
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,8 @@ internal static class DynamicReportLayoutHelper
|
||||||
var valueExpression = DynamicReportImageHelper.CreateFormatValueExpression(
|
var valueExpression = DynamicReportImageHelper.CreateFormatValueExpression(
|
||||||
$"[{column.BindingName}]",
|
$"[{column.BindingName}]",
|
||||||
column.Field,
|
column.Field,
|
||||||
column.EditorType);
|
column.EditorType,
|
||||||
|
column.PhoneCountryExpression);
|
||||||
var markupExpression = CreateColorMarkupExpression(column);
|
var markupExpression = CreateColorMarkupExpression(column);
|
||||||
cell.AllowMarkupText = markupExpression != null;
|
cell.AllowMarkupText = markupExpression != null;
|
||||||
var displayExpression = markupExpression ?? valueExpression;
|
var displayExpression = markupExpression ?? valueExpression;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Sozsoft.Platform.Entities;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
using Volo.Abp.Uow;
|
||||||
|
|
||||||
|
namespace Sozsoft.Reports.PredefinedReports;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Telefon alanlarının <c>editorOptions.format</c> token'larını raporda listedeki gibi gösterir.
|
||||||
|
/// Kural UI'daki <c>ui/src/utils/phoneFormat.ts</c> ile birebir aynıdır; biri değişirse diğeri de değişir,
|
||||||
|
/// yoksa liste ile rapor aynı numarayı farklı yazar.
|
||||||
|
/// </summary>
|
||||||
|
internal static class DynamicReportPhoneFormatter
|
||||||
|
{
|
||||||
|
public const string Global = "phoneGlobal";
|
||||||
|
public const string Turkey = "phoneTR";
|
||||||
|
public const string International = "phoneIntl";
|
||||||
|
public const string ByCountry = "phoneCountry";
|
||||||
|
public const string DefaultCountryField = "Country";
|
||||||
|
|
||||||
|
private const string GlobalPattern = "(000) 000-0000";
|
||||||
|
private const string TurkeyPattern = "(000) 000 00 00";
|
||||||
|
private const string WideNationalPattern = "000 000 000 000 000";
|
||||||
|
private const int TurkeyDialCode = 90;
|
||||||
|
|
||||||
|
private sealed record PhoneCountry(string Id, int PhoneCode, int? MinLength, int? MaxLength, string Pattern);
|
||||||
|
|
||||||
|
private static readonly PhoneCountry FallbackCountry = new("Türkiye", TurkeyDialCode, 10, 10, GlobalPattern);
|
||||||
|
|
||||||
|
// Ülke tablosu host'a aittir ve kiracıya göre değişmez; rapor ifadesi eşzamanlı çalıştığı için
|
||||||
|
// liste raporu kurulurken yüklenip süreç genelinde tutulur.
|
||||||
|
private static IReadOnlyDictionary<string, PhoneCountry> _countries =
|
||||||
|
new Dictionary<string, PhoneCountry>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public static bool IsPhoneFormat(string format) =>
|
||||||
|
format is Global or Turkey or International or ByCountry;
|
||||||
|
|
||||||
|
public static bool NeedsCountries(string format) => format is International or ByCountry;
|
||||||
|
|
||||||
|
public static async Task LoadCountriesAsync(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
var unitOfWorkManager = serviceProvider.GetRequiredService<IUnitOfWorkManager>();
|
||||||
|
var repository = serviceProvider.GetRequiredService<IReadOnlyRepository<Country, string>>();
|
||||||
|
|
||||||
|
using var unitOfWork = unitOfWorkManager.Begin(requiresNew: true, isTransactional: false);
|
||||||
|
var countries = await repository.GetListAsync();
|
||||||
|
await unitOfWork.CompleteAsync();
|
||||||
|
|
||||||
|
_countries = countries
|
||||||
|
.Select(x => new PhoneCountry(x.Id, x.PhoneCode, x.PhoneNumberMinLength, x.PhoneNumberMaxLength, x.PhoneNumberFormat))
|
||||||
|
.ToDictionary(x => x.Id, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Format(object value, string format, object countryId)
|
||||||
|
{
|
||||||
|
var text = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case Turkey:
|
||||||
|
return FormatLocal(text, TurkeyPattern);
|
||||||
|
case International:
|
||||||
|
{
|
||||||
|
var digits = OnlyDigits(text);
|
||||||
|
var country = FindCountryByDialCode(digits);
|
||||||
|
return country == null
|
||||||
|
? "+" + digits
|
||||||
|
: FormatWithCountry(digits[country.PhoneCode.ToString(CultureInfo.InvariantCulture).Length..], country);
|
||||||
|
}
|
||||||
|
case ByCountry:
|
||||||
|
return FormatWithCountry(text, GetCountryOrDefault(countryId));
|
||||||
|
default:
|
||||||
|
return FormatLocal(text, GlobalPattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PhoneCountry GetCountryOrDefault(object countryId)
|
||||||
|
{
|
||||||
|
var id = Convert.ToString(countryId, CultureInfo.InvariantCulture);
|
||||||
|
if (!string.IsNullOrEmpty(id) && _countries.TryGetValue(id, out var country))
|
||||||
|
{
|
||||||
|
return country;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _countries.Values.FirstOrDefault(x => x.PhoneCode == TurkeyDialCode) ?? FallbackCountry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Rakamlarla başlayan en uzun ülke kodu (1 ile 1268 gibi çakışan kodlarda doğru ülke).</summary>
|
||||||
|
private static PhoneCountry FindCountryByDialCode(string digits) =>
|
||||||
|
_countries.Values
|
||||||
|
.Where(x => digits.StartsWith(x.PhoneCode.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal))
|
||||||
|
.OrderByDescending(x => x.PhoneCode.ToString(CultureInfo.InvariantCulture).Length)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
private static string OnlyDigits(string value) => Regex.Replace(value, @"\D", string.Empty);
|
||||||
|
|
||||||
|
private static int CountSlots(string pattern) => pattern.Count(x => x == '0');
|
||||||
|
|
||||||
|
/// <summary>Kalıp en çok hane sayısına göre kısaltılır ya da uzatılır; maske ile görüntü aynı haneyi taşır.</summary>
|
||||||
|
private static string GetNationalPattern(PhoneCountry country)
|
||||||
|
{
|
||||||
|
var pattern = !string.IsNullOrEmpty(country.Pattern)
|
||||||
|
? country.Pattern
|
||||||
|
: country.PhoneCode == TurkeyDialCode ? GlobalPattern : WideNationalPattern;
|
||||||
|
var slots = CountSlots(pattern);
|
||||||
|
var maxLength = country.MaxLength ?? slots;
|
||||||
|
|
||||||
|
if (maxLength > slots)
|
||||||
|
{
|
||||||
|
return pattern + " " + new string('0', maxLength - slots);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxLength < slots)
|
||||||
|
{
|
||||||
|
var seen = 0;
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
foreach (var character in pattern)
|
||||||
|
{
|
||||||
|
if (character == '0' ? ++seen <= maxLength : seen < maxLength)
|
||||||
|
{
|
||||||
|
builder.Append(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Rakamları kalıbın <c>0</c> yerlerine dizer; rakam bitince sondaki ayraçlar yazılmaz.</summary>
|
||||||
|
private static string ApplyPattern(string digits, string pattern)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
foreach (var character in pattern)
|
||||||
|
{
|
||||||
|
if (index >= digits.Length)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(character == '0' ? digits[index++] : character);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kalıptan uzun numara kesilmez; fazlası sona eklenir.
|
||||||
|
return builder.Append(digits[index..]).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Değerin başında ülke kodu ya da yurt içi <c>0</c> öneki varsa ulusal kısmı döner.</summary>
|
||||||
|
private static string ToNationalDigits(string value, PhoneCountry country)
|
||||||
|
{
|
||||||
|
var digits = OnlyDigits(value);
|
||||||
|
var code = country.PhoneCode.ToString(CultureInfo.InvariantCulture);
|
||||||
|
var maxLength = country.MaxLength ?? CountSlots(GetNationalPattern(country));
|
||||||
|
|
||||||
|
if (digits.Length > maxLength && digits.StartsWith(code, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return digits[code.Length..];
|
||||||
|
}
|
||||||
|
|
||||||
|
return digits.Length > maxLength && digits.StartsWith('0') ? digits[1..] : digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatWithCountry(string value, PhoneCountry country)
|
||||||
|
{
|
||||||
|
var national = ToNationalDigits(value, country);
|
||||||
|
return national.Length == 0
|
||||||
|
? string.Empty
|
||||||
|
: $"+{country.PhoneCode} {ApplyPattern(national, GetNationalPattern(country))}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Yerel 10 haneli biçimler: ülke kodu / <c>0</c> öneki atılır, son 10 hane kalıba dizilir.</summary>
|
||||||
|
private static string FormatLocal(string value, string pattern)
|
||||||
|
{
|
||||||
|
var digits = OnlyDigits(value);
|
||||||
|
if (digits.Length > 10)
|
||||||
|
{
|
||||||
|
digits = digits[^10..];
|
||||||
|
}
|
||||||
|
|
||||||
|
return digits.Length == 10 ? ApplyPattern(digits, pattern) : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -95,6 +95,7 @@ public class DynamicTreeReport : XtraReport
|
||||||
.OrderBy(x => x.ListOrderNo ?? int.MaxValue)
|
.OrderBy(x => x.ListOrderNo ?? int.MaxValue)
|
||||||
.ThenBy(x => x.CaptionName)
|
.ThenBy(x => x.CaptionName)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
await DynamicReportImageHelper.EnsurePhoneCountriesAsync(scope.ServiceProvider, fields);
|
||||||
var customizations = await customizationManager.GetUsersServerCustomizations(listFormCode);
|
var customizations = await customizationManager.GetUsersServerCustomizations(listFormCode);
|
||||||
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
||||||
|
|
||||||
|
|
@ -140,7 +141,7 @@ public class DynamicTreeReport : XtraReport
|
||||||
editorTypesByField.TryGetValue(field.FieldName, out var editorType);
|
editorTypesByField.TryGetValue(field.FieldName, out var editorType);
|
||||||
return DynamicReportColumnFactory.Create(
|
return DynamicReportColumnFactory.Create(
|
||||||
field,
|
field,
|
||||||
selectFieldsByName[field.FieldName],
|
selectFieldsByName,
|
||||||
defaultValueHelper,
|
defaultValueHelper,
|
||||||
editorType,
|
editorType,
|
||||||
localizer);
|
localizer);
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,9 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
||||||
- **`scriptRecipes.ts` (TS) ↔ `Domain.Shared/Editors/*.cs`** — biri değişirse diğeri de değişir.
|
- **`scriptRecipes.ts` (TS) ↔ `Domain.Shared/Editors/*.cs`** — biri değişirse diğeri de değişir.
|
||||||
İkisinin çıktısı **birebir aynı** olmak zorundadır; aksi hâlde seeder ile basılan script
|
İkisinin çıktısı **birebir aynı** olmak zorundadır; aksi hâlde seeder ile basılan script
|
||||||
dialogda "elle düzenlenmiş" sayılır ve kural editörü kapanır.
|
dialogda "elle düzenlenmiş" sayılır ve kural editörü kapanır.
|
||||||
|
- **`phoneFormat.ts` ↔ `DynamicReportPhoneFormatter.cs`** — telefon token'larının (`phoneGlobal`, `phoneTR`,
|
||||||
|
`phoneIntl`, `phoneCountry`) görüntü kuralı iki yerde yazılır: liste/kart istemcide, dinamik rapor sunucuda.
|
||||||
|
Biri değişirse diğeri de değişir; yoksa liste ile rapor aynı numarayı farklı yazar.
|
||||||
- **`htmlEditorOptions.ts` ↔ `EditorOptions.Html`** — HtmlEditor varsayılanı iki yerde yazılır ve
|
- **`htmlEditorOptions.ts` ↔ `EditorOptions.Html`** — HtmlEditor varsayılanı iki yerde yazılır ve
|
||||||
bloklar birebir aynıdır. İstemci iç içe blokları tek seviye birleştirdiği için seed edilen
|
bloklar birebir aynıdır. İstemci iç içe blokları tek seviye birleştirdiği için seed edilen
|
||||||
`toolbar` varsayılanın yerine geçer; biri değişip diğeri değişmezse seed'li ekranlar eski araç
|
`toolbar` varsayılanın yerine geçer; biri değişip diğeri değişmezse seed'li ekranlar eski araç
|
||||||
|
|
|
||||||
|
|
@ -54898,6 +54898,90 @@
|
||||||
"Key": "App.PresetPhoneMask.PhoneMaskDescription",
|
"Key": "App.PresetPhoneMask.PhoneMaskDescription",
|
||||||
"Value": "Maske, yer tutucu ve hata mesajı."
|
"Value": "Maske, yer tutucu ve hata mesajı."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Id": "060c38f8-fd4f-5d57-9c42-4ba5cb0ff106",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneTr.PhoneTrLabel",
|
||||||
|
"Value": "Phone (Türkiye)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "6cb54be6-fd5d-5e78-b7ec-69076b77f358",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneTr.PhoneTrLabel",
|
||||||
|
"Value": "Telefon (Türkiye)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "478644a2-4072-5eba-80d7-a585afa37e61",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneTr.PhoneTrDescription",
|
||||||
|
"Value": "(532) 123 45 67 mask; 10 digits are stored."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "277c27d8-7dbd-5adc-8e89-b26fc103b7c3",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneTr.PhoneTrDescription",
|
||||||
|
"Value": "(532) 123 45 67 maskesi; 10 hane saklanır."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "a2338e30-dfd9-5276-9ace-76b595d4904c",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneIntl.PhoneIntlLabel",
|
||||||
|
"Value": "Phone (international)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "956e8d1f-34fe-57cc-9bde-56af9ff63c3c",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneIntl.PhoneIntlLabel",
|
||||||
|
"Value": "Telefon (uluslararası)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "d6829967-340d-548c-8faf-75b1cda42052",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneIntl.PhoneIntlDescription",
|
||||||
|
"Value": "Number with country code (up to 15 digits); lists show it in the country's format."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "d247d0c8-555a-542a-b345-55132f893f07",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneIntl.PhoneIntlDescription",
|
||||||
|
"Value": "Ülke koduyla numara (en çok 15 hane); listede ülkenin biçimiyle gösterilir."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "13ef2c0e-be5e-517e-8ceb-739777e68348",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneCountry.PhoneCountryLabel",
|
||||||
|
"Value": "Phone (by country)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "04a9844b-6ba8-5521-b2b5-717b0688880b",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneCountry.PhoneCountryLabel",
|
||||||
|
"Value": "Telefon (ülkeye göre)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "6f7483b3-6433-5347-a5ae-a40d2405dd3d",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneCountry.PhoneCountryDescription",
|
||||||
|
"Value": "Mask follows the country selected in the Country field (code, format, min/max digits)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "bf6caee2-9833-545b-8ee9-f386632c9f3f",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.PresetPhoneCountry.PhoneCountryDescription",
|
||||||
|
"Value": "Maske, Ülke alanında seçilen ülkeye göre değişir (kod, biçim, en az/en çok hane)."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Id": "832bc1fd-0fd8-5bcb-8809-5923f1473110",
|
"Id": "832bc1fd-0fd8-5bcb-8809-5923f1473110",
|
||||||
"CultureName": "en",
|
"CultureName": "en",
|
||||||
|
|
|
||||||
|
|
@ -263,10 +263,10 @@
|
||||||
"Id": "f1effbfd-7fec-5056-845c-6223010eb2d6",
|
"Id": "f1effbfd-7fec-5056-845c-6223010eb2d6",
|
||||||
"Code": "App.Definitions.TerminationReason",
|
"Code": "App.Definitions.TerminationReason",
|
||||||
"DisplayName": "App.Definitions.TerminationReason",
|
"DisplayName": "App.Definitions.TerminationReason",
|
||||||
"Order": 9,
|
"Order": 18,
|
||||||
"Url": "/admin/list/App.Definitions.TerminationReason",
|
"Url": "/admin/list/App.Definitions.TerminationReason",
|
||||||
"Icon": "FcExport",
|
"Icon": "FcExport",
|
||||||
"ParentCode": "App.Saas.Definitions",
|
"ParentCode": "App.Hr.Definitions",
|
||||||
"RequiredPermissionName": "App.Definitions.TerminationReason",
|
"RequiredPermissionName": "App.Definitions.TerminationReason",
|
||||||
"CssClass": null,
|
"CssClass": null,
|
||||||
"Target": null,
|
"Target": null,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ Satış ve satınalmanın ortak kullandığı **Kontaklar** kartı (müşteri ve
|
||||||
- Adres, Kiracılar ekranıyla aynı zinciri kullanır: Ülke › Şehir › İlçe › Mahalle; mahalle seçimi posta kodunu doldurur.
|
- Adres, Kiracılar ekranıyla aynı zinciri kullanır: Ülke › Şehir › İlçe › Mahalle; mahalle seçimi posta kodunu doldurur.
|
||||||
- Logo tek görseldir (`multiple: false`) ve formda 120×120 gösterilir.
|
- Logo tek görseldir (`multiple: false`) ve formda 120×120 gösterilir.
|
||||||
- Sektör (`SectorId`) Sektörler ekranının (`App.Definitions.Sector`, `Adm_T_Sector`) kiracıya ait aktif kayıtlarından seçilir. `JobPosition` kolonu tabloda kalır, ekranda yoktur.
|
- Sektör (`SectorId`) Sektörler ekranının (`App.Definitions.Sector`, `Adm_T_Sector`) kiracıya ait aktif kayıtlarından seçilir. `JobPosition` kolonu tabloda kalır, ekranda yoktur.
|
||||||
- Telefon ve cep (`Phone`, `Mobile` — İlgili Kişiler dahil) `phoneGlobal` maskesi ve `pattern` `^[0-9]{10}$` kuralı taşır; web sitesi `pattern`, e-posta `email` doğrulaması taşır.
|
- Kontak telefonu (`Phone`) `phoneCountry` biçimindedir: maske seçilen ülkenin kodu ve numara kalıbıyla değişir, `pattern` `^[0-9]{4,15}$`. Ülke alanı olmayan İlgili Kişiler telefonu `phoneGlobal` maskesi ve `^[0-9]{10}$` kuralı taşır; web sitesi `pattern`, e-posta `email` doğrulaması taşır.
|
||||||
|
|
||||||
## 3. Menü ve ekranlar
|
## 3. Menü ve ekranlar
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,12 +140,6 @@
|
||||||
"En": "E-mail",
|
"En": "E-mail",
|
||||||
"Tr": "E-posta"
|
"Tr": "E-posta"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"FieldName": "PhoneNumber",
|
|
||||||
"CaptionName": "App.Listform.ListformField.PhoneNumber",
|
|
||||||
"En": "Phone",
|
|
||||||
"Tr": "Telefon"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"FieldName": "Gender",
|
"FieldName": "Gender",
|
||||||
"CaptionName": "App.Listform.ListformField.Gender",
|
"CaptionName": "App.Listform.ListformField.Gender",
|
||||||
|
|
@ -170,6 +164,12 @@
|
||||||
"En": "City",
|
"En": "City",
|
||||||
"Tr": "Şehir"
|
"Tr": "Şehir"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"FieldName": "PhoneNumber",
|
||||||
|
"CaptionName": "App.Listform.ListformField.PhoneNumber",
|
||||||
|
"En": "Phone",
|
||||||
|
"Tr": "Telefon"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"FieldName": "Address",
|
"FieldName": "Address",
|
||||||
"CaptionName": "App.Listform.ListformField.Address",
|
"CaptionName": "App.Listform.ListformField.Address",
|
||||||
|
|
@ -378,6 +378,13 @@
|
||||||
"UpdateAfterCommand": "UPDATE a SET CandidateName = c.FirstName + ' ' + c.LastName, PostingTitle = COALESCE(p.PostingNo + ' - ', '') + p.Title FROM Hr_T_JobApplication a INNER JOIN Hr_T_Candidate c ON c.Id = a.CandidateId INNER JOIN Hr_T_JobPosting p ON p.Id = a.JobPostingId WHERE a.IsDeleted = 'false' AND (a.CandidateName IS NULL OR a.PostingTitle IS NULL OR a.CandidateName <> c.FirstName + ' ' + c.LastName OR a.PostingTitle <> COALESCE(p.PostingNo + ' - ', '') + p.Title); UPDATE i SET Title = COALESCE(a.CandidateName, '') + ' - ' + COALESCE(a.PostingTitle, '') FROM Hr_T_Interview i INNER JOIN Hr_T_JobApplication a ON a.Id = i.ApplicationId WHERE i.IsDeleted = 'false' AND (i.Title IS NULL OR i.Title <> COALESCE(a.CandidateName, '') + ' - ' + COALESCE(a.PostingTitle, '')); UPDATE o SET CandidateName = a.CandidateName FROM Hr_T_JobOffer o INNER JOIN Hr_T_JobApplication a ON a.Id = o.ApplicationId WHERE o.IsDeleted = 'false' AND a.CandidateName IS NOT NULL AND (o.CandidateName IS NULL OR o.CandidateName <> a.CandidateName)",
|
"UpdateAfterCommand": "UPDATE a SET CandidateName = c.FirstName + ' ' + c.LastName, PostingTitle = COALESCE(p.PostingNo + ' - ', '') + p.Title FROM Hr_T_JobApplication a INNER JOIN Hr_T_Candidate c ON c.Id = a.CandidateId INNER JOIN Hr_T_JobPosting p ON p.Id = a.JobPostingId WHERE a.IsDeleted = 'false' AND (a.CandidateName IS NULL OR a.PostingTitle IS NULL OR a.CandidateName <> c.FirstName + ' ' + c.LastName OR a.PostingTitle <> COALESCE(p.PostingNo + ' - ', '') + p.Title); UPDATE i SET Title = COALESCE(a.CandidateName, '') + ' - ' + COALESCE(a.PostingTitle, '') FROM Hr_T_Interview i INNER JOIN Hr_T_JobApplication a ON a.Id = i.ApplicationId WHERE i.IsDeleted = 'false' AND (i.Title IS NULL OR i.Title <> COALESCE(a.CandidateName, '') + ' - ' + COALESCE(a.PostingTitle, '')); UPDATE o SET CandidateName = a.CandidateName FROM Hr_T_JobOffer o INNER JOIN Hr_T_JobApplication a ON a.Id = o.ApplicationId WHERE o.IsDeleted = 'false' AND a.CandidateName IS NOT NULL AND (o.CandidateName IS NULL OR o.CandidateName <> a.CandidateName)",
|
||||||
"UpdateFieldsDefaultValueDto": [],
|
"UpdateFieldsDefaultValueDto": [],
|
||||||
"FormFieldsDefaultValueDto": [
|
"FormFieldsDefaultValueDto": [
|
||||||
|
{
|
||||||
|
"FieldName": "Country",
|
||||||
|
"FieldDbType": 16,
|
||||||
|
"Value": "Türkiye",
|
||||||
|
"CustomValueType": 1,
|
||||||
|
"SqlQuery": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"FieldName": "IsActive",
|
"FieldName": "IsActive",
|
||||||
"FieldDbType": 3,
|
"FieldDbType": 3,
|
||||||
|
|
@ -1767,11 +1774,11 @@
|
||||||
},
|
},
|
||||||
"Alignment": "left",
|
"Alignment": "left",
|
||||||
"Format": null,
|
"Format": null,
|
||||||
"EditOrderNo": 5,
|
"EditOrderNo": 9,
|
||||||
"EditGroupOrderNo": 1,
|
"EditGroupOrderNo": 1,
|
||||||
"EditorType2": "dxTextBox",
|
"EditorType2": "dxTextBox",
|
||||||
"ColSpan": 1,
|
"ColSpan": 1,
|
||||||
"EditorOptions": "{\"showClearButton\":true,\"maxLength\":32}",
|
"EditorOptions": "{\"showClearButton\":true,\"format\":\"phoneCountry\",\"phoneCountryField\":\"Country\"}",
|
||||||
"GridBoxOptions": null,
|
"GridBoxOptions": null,
|
||||||
"TagBoxOptions": null,
|
"TagBoxOptions": null,
|
||||||
"ImageUploadOptions": null,
|
"ImageUploadOptions": null,
|
||||||
|
|
@ -1893,7 +1900,7 @@
|
||||||
},
|
},
|
||||||
"Alignment": "left",
|
"Alignment": "left",
|
||||||
"Format": null,
|
"Format": null,
|
||||||
"EditOrderNo": 6,
|
"EditOrderNo": 5,
|
||||||
"EditGroupOrderNo": 1,
|
"EditGroupOrderNo": 1,
|
||||||
"EditorType2": "dxSelectBox",
|
"EditorType2": "dxSelectBox",
|
||||||
"ColSpan": 1,
|
"ColSpan": 1,
|
||||||
|
|
@ -2019,7 +2026,7 @@
|
||||||
},
|
},
|
||||||
"Alignment": "left",
|
"Alignment": "left",
|
||||||
"Format": null,
|
"Format": null,
|
||||||
"EditOrderNo": 7,
|
"EditOrderNo": 6,
|
||||||
"EditGroupOrderNo": 1,
|
"EditGroupOrderNo": 1,
|
||||||
"EditorType2": "dxDateBox",
|
"EditorType2": "dxDateBox",
|
||||||
"ColSpan": 1,
|
"ColSpan": 1,
|
||||||
|
|
@ -2145,7 +2152,7 @@
|
||||||
},
|
},
|
||||||
"Alignment": "left",
|
"Alignment": "left",
|
||||||
"Format": null,
|
"Format": null,
|
||||||
"EditOrderNo": 8,
|
"EditOrderNo": 7,
|
||||||
"EditGroupOrderNo": 1,
|
"EditGroupOrderNo": 1,
|
||||||
"EditorType2": "dxSelectBox",
|
"EditorType2": "dxSelectBox",
|
||||||
"ColSpan": 1,
|
"ColSpan": 1,
|
||||||
|
|
@ -2271,7 +2278,7 @@
|
||||||
},
|
},
|
||||||
"Alignment": "left",
|
"Alignment": "left",
|
||||||
"Format": null,
|
"Format": null,
|
||||||
"EditOrderNo": 9,
|
"EditOrderNo": 8,
|
||||||
"EditGroupOrderNo": 1,
|
"EditGroupOrderNo": 1,
|
||||||
"EditorType2": "dxSelectBox",
|
"EditorType2": "dxSelectBox",
|
||||||
"ColSpan": 1,
|
"ColSpan": 1,
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,22 @@
|
||||||
"UpdateBeforeCommand": null,
|
"UpdateBeforeCommand": null,
|
||||||
"UpdateAfterCommand": null,
|
"UpdateAfterCommand": null,
|
||||||
"UpdateFieldsDefaultValueDto": [],
|
"UpdateFieldsDefaultValueDto": [],
|
||||||
"FormFieldsDefaultValueDto": [],
|
"FormFieldsDefaultValueDto": [
|
||||||
|
{
|
||||||
|
"FieldName": "Country",
|
||||||
|
"FieldDbType": 16,
|
||||||
|
"Value": "Türkiye",
|
||||||
|
"CustomValueType": 1,
|
||||||
|
"SqlQuery": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"FieldName": "IsActive",
|
||||||
|
"FieldDbType": 3,
|
||||||
|
"Value": "true",
|
||||||
|
"CustomValueType": 1,
|
||||||
|
"SqlQuery": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
"WidgetsDto": [
|
"WidgetsDto": [
|
||||||
{
|
{
|
||||||
"ColGap": 3,
|
"ColGap": 3,
|
||||||
|
|
@ -1614,7 +1629,7 @@
|
||||||
"IgnoreEmptyValue": true,
|
"IgnoreEmptyValue": true,
|
||||||
"Reevaluate": true,
|
"Reevaluate": true,
|
||||||
"ComparisonType": "",
|
"ComparisonType": "",
|
||||||
"Pattern": "^[0-9]{10}$",
|
"Pattern": "^[0-9]{4,15}$",
|
||||||
"Max": null,
|
"Max": null,
|
||||||
"Min": null,
|
"Min": null,
|
||||||
"Trim": true
|
"Trim": true
|
||||||
|
|
@ -1647,11 +1662,11 @@
|
||||||
},
|
},
|
||||||
"Alignment": "left",
|
"Alignment": "left",
|
||||||
"Format": null,
|
"Format": null,
|
||||||
"EditOrderNo": 4,
|
"EditOrderNo": 8,
|
||||||
"EditGroupOrderNo": 1,
|
"EditGroupOrderNo": 1,
|
||||||
"EditorType2": "dxTextBox",
|
"EditorType2": "dxTextBox",
|
||||||
"ColSpan": 1,
|
"ColSpan": 1,
|
||||||
"EditorOptions": "{\"showClearButton\":true,\"format\":\"phoneGlobal\",\"mask\":\"(000) 000-0000\",\"useMaskedValue\":false,\"maskRules\":{\"X\":\"[0-9]\"},\"placeholder\":\"(555) 123-4567\"}",
|
"EditorOptions": "{\"showClearButton\":true,\"format\":\"phoneCountry\",\"phoneCountryField\":\"Country\"}",
|
||||||
"GridBoxOptions": null,
|
"GridBoxOptions": null,
|
||||||
"TagBoxOptions": null,
|
"TagBoxOptions": null,
|
||||||
"ImageUploadOptions": null,
|
"ImageUploadOptions": null,
|
||||||
|
|
@ -1773,7 +1788,7 @@
|
||||||
},
|
},
|
||||||
"Alignment": "left",
|
"Alignment": "left",
|
||||||
"Format": null,
|
"Format": null,
|
||||||
"EditOrderNo": 5,
|
"EditOrderNo": 4,
|
||||||
"EditGroupOrderNo": 1,
|
"EditGroupOrderNo": 1,
|
||||||
"EditorType2": "dxSelectBox",
|
"EditorType2": "dxSelectBox",
|
||||||
"ColSpan": 1,
|
"ColSpan": 1,
|
||||||
|
|
@ -1899,7 +1914,7 @@
|
||||||
},
|
},
|
||||||
"Alignment": "left",
|
"Alignment": "left",
|
||||||
"Format": null,
|
"Format": null,
|
||||||
"EditOrderNo": 6,
|
"EditOrderNo": 5,
|
||||||
"EditGroupOrderNo": 1,
|
"EditGroupOrderNo": 1,
|
||||||
"EditorType2": "dxSelectBox",
|
"EditorType2": "dxSelectBox",
|
||||||
"ColSpan": 1,
|
"ColSpan": 1,
|
||||||
|
|
@ -2025,7 +2040,7 @@
|
||||||
},
|
},
|
||||||
"Alignment": "left",
|
"Alignment": "left",
|
||||||
"Format": null,
|
"Format": null,
|
||||||
"EditOrderNo": 7,
|
"EditOrderNo": 6,
|
||||||
"EditGroupOrderNo": 1,
|
"EditGroupOrderNo": 1,
|
||||||
"EditorType2": "dxSelectBox",
|
"EditorType2": "dxSelectBox",
|
||||||
"ColSpan": 1,
|
"ColSpan": 1,
|
||||||
|
|
@ -2151,7 +2166,7 @@
|
||||||
},
|
},
|
||||||
"Alignment": "left",
|
"Alignment": "left",
|
||||||
"Format": null,
|
"Format": null,
|
||||||
"EditOrderNo": 8,
|
"EditOrderNo": 7,
|
||||||
"EditGroupOrderNo": 1,
|
"EditGroupOrderNo": 1,
|
||||||
"EditorType2": "dxTextBox",
|
"EditorType2": "dxTextBox",
|
||||||
"ColSpan": 1,
|
"ColSpan": 1,
|
||||||
|
|
|
||||||
382
ui/src/utils/phoneFormat.ts
Normal file
382
ui/src/utils/phoneFormat.ts
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
/**
|
||||||
|
* Telefon alanlarının `editorOptions.format` token'ları.
|
||||||
|
*
|
||||||
|
* Değer her biçimde yalnızca rakam olarak saklanır (`useMaskedValue: false`); biçim
|
||||||
|
* editörde maskeyle, listede görüntü metniyle uygulanır. Ülkeye bağlı biçimler
|
||||||
|
* `Sas_H_Country` satırındaki `PhoneCode` / `PhoneNumberFormat` / `PhoneNumber{Min,Max}Length`
|
||||||
|
* değerlerini kullanır.
|
||||||
|
*/
|
||||||
|
import Form from 'devextreme/ui/form'
|
||||||
|
import { getCountries, type CountryDto } from '@/services/location.service'
|
||||||
|
|
||||||
|
export const PhoneFormats = {
|
||||||
|
/** 10 haneli yerel numara: `(555) 123-4567`. */
|
||||||
|
Global: 'phoneGlobal',
|
||||||
|
/** Türkiye: `(532) 123 45 67`. */
|
||||||
|
Turkey: 'phoneTR',
|
||||||
|
/** Ülke koduyla uluslararası numara (E.164, en çok 15 hane): `+49 301 234 567`. */
|
||||||
|
International: 'phoneIntl',
|
||||||
|
/** Formdaki ülke alanına göre maske (ülke yoksa Türkiye); ülke kodu değere yazılmaz. */
|
||||||
|
Country: 'phoneCountry',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type PhoneFormat = (typeof PhoneFormats)[keyof typeof PhoneFormats]
|
||||||
|
|
||||||
|
const PHONE_FORMAT_VALUES = new Set<string>(Object.values(PhoneFormats))
|
||||||
|
|
||||||
|
export const isPhoneFormat = (format: unknown): format is PhoneFormat =>
|
||||||
|
typeof format === 'string' && PHONE_FORMAT_VALUES.has(format)
|
||||||
|
|
||||||
|
/** `phoneCountry` biçiminde ülkenin okunduğu alan; `phoneCountryField` verilmezse bu kullanılır. */
|
||||||
|
export const DEFAULT_PHONE_COUNTRY_FIELD = 'Country'
|
||||||
|
|
||||||
|
const GLOBAL_PATTERN = '(000) 000-0000'
|
||||||
|
const TURKEY_PATTERN = '(000) 000 00 00'
|
||||||
|
const TURKEY_DIAL_CODE = 90
|
||||||
|
const MAX_PHONE_DIGITS = 15
|
||||||
|
/** Ülkesi bilinmeyen numara: tek zorunlu, kalanı isteğe bağlı 15 hane. */
|
||||||
|
const INTERNATIONAL_MASK = '+0' + '9'.repeat(MAX_PHONE_DIGITS - 1)
|
||||||
|
|
||||||
|
const STATIC_EDITOR_OPTIONS: Record<Exclude<PhoneFormat, 'phoneCountry'>, Record<string, any>> = {
|
||||||
|
phoneGlobal: { mask: GLOBAL_PATTERN, placeholder: '(555) 123-4567' },
|
||||||
|
phoneTR: { mask: TURKEY_PATTERN, placeholder: '(532) 123 45 67' },
|
||||||
|
phoneIntl: { mask: INTERNATIONAL_MASK, placeholder: '+90 532 123 45 67' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Ülke önbelleği: grid hücresi biçimlendirmesi eşzamanlıdır, liste bir kez yüklenir.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
let countryCache: Map<string, CountryDto> | undefined
|
||||||
|
let countryRequest: Promise<Map<string, CountryDto>> | undefined
|
||||||
|
|
||||||
|
export const loadPhoneCountries = () => {
|
||||||
|
countryRequest ??= getCountries()
|
||||||
|
.then((response) => {
|
||||||
|
countryCache = new Map(response.data.map((country) => [country.id, country]))
|
||||||
|
return countryCache
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
// Başarısız istek önbelleğe yazılmaz; sonraki çağrı yeniden dener.
|
||||||
|
countryRequest = undefined
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
return countryRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
export const arePhoneCountriesLoaded = () => countryCache !== undefined
|
||||||
|
|
||||||
|
const getCountry = (countryId: unknown) =>
|
||||||
|
countryId == null || countryId === '' ? undefined : countryCache?.get(String(countryId))
|
||||||
|
|
||||||
|
/** Ülke tablosu yüklenmeden de maskenin Türkiye biçiminde açılabilmesi için. */
|
||||||
|
const FALLBACK_COUNTRY: CountryDto = {
|
||||||
|
id: 'Türkiye',
|
||||||
|
name: 'Türkiye',
|
||||||
|
phoneCode: TURKEY_DIAL_CODE,
|
||||||
|
phoneNumberMinLength: 10,
|
||||||
|
phoneNumberMaxLength: 10,
|
||||||
|
phoneNumberFormat: GLOBAL_PATTERN,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ülke seçilmemişse ya da bulunamazsa Türkiye (tablodaki satırı, yoksa sabit karşılığı). */
|
||||||
|
const getCountryOrDefault = (countryId: unknown) =>
|
||||||
|
getCountry(countryId) ??
|
||||||
|
[...(countryCache?.values() ?? [])].find((country) => country.phoneCode === TURKEY_DIAL_CODE) ??
|
||||||
|
FALLBACK_COUNTRY
|
||||||
|
|
||||||
|
/** Rakamlarla başlayan en uzun ülke kodu (1 ile 1268 gibi çakışan kodlarda doğru ülke). */
|
||||||
|
const findCountryByDialCode = (digits: string) => {
|
||||||
|
let match: CountryDto | undefined
|
||||||
|
countryCache?.forEach((country) => {
|
||||||
|
const code = String(country.phoneCode ?? '')
|
||||||
|
if (code && digits.startsWith(code) && code.length > String(match?.phoneCode ?? '').length) {
|
||||||
|
match = country
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Biçim
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const countSlots = (pattern: string) => pattern.replace(/[^0]/g, '').length
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ülkenin ulusal numara kalıbı; kalıp en çok hane sayısına göre kısaltılır ya da uzatılır,
|
||||||
|
* böylece maske ile görüntü aynı hane sayısını taşır.
|
||||||
|
*/
|
||||||
|
const getNationalPattern = (country: CountryDto) => {
|
||||||
|
let pattern =
|
||||||
|
country.phoneNumberFormat ||
|
||||||
|
(country.phoneCode === TURKEY_DIAL_CODE ? GLOBAL_PATTERN : '000 000 000 000 000')
|
||||||
|
const slots = countSlots(pattern)
|
||||||
|
const maxLength = country.phoneNumberMaxLength ?? slots
|
||||||
|
|
||||||
|
if (maxLength > slots) {
|
||||||
|
pattern += ' ' + '0'.repeat(maxLength - slots)
|
||||||
|
} else if (maxLength < slots) {
|
||||||
|
let seen = 0
|
||||||
|
pattern = [...pattern]
|
||||||
|
.filter((character) => (character === '0' ? ++seen <= maxLength : seen < maxLength))
|
||||||
|
.join('')
|
||||||
|
}
|
||||||
|
return pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rakamları kalıbın `0` yerlerine dizer; rakam bittiğinde sondaki ayraçlar yazılmaz. */
|
||||||
|
const applyPattern = (digits: string, pattern: string) => {
|
||||||
|
let index = 0
|
||||||
|
let result = ''
|
||||||
|
for (const character of pattern) {
|
||||||
|
if (index >= digits.length) break
|
||||||
|
if (character === '0') {
|
||||||
|
result += digits[index++]
|
||||||
|
} else {
|
||||||
|
result += character
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Kalıptan uzun numara kesilmez; fazlası sona eklenir.
|
||||||
|
return result + digits.slice(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onlyDigits = (value: unknown) => String(value ?? '').replace(/\D/g, '')
|
||||||
|
|
||||||
|
/** Değerin başında ülke kodu ya da yurt içi `0` öneki varsa ulusal kısmı döner. */
|
||||||
|
const toNationalDigits = (value: unknown, country: CountryDto) => {
|
||||||
|
const digits = onlyDigits(value)
|
||||||
|
const code = String(country.phoneCode)
|
||||||
|
const maxLength = country.phoneNumberMaxLength ?? countSlots(getNationalPattern(country))
|
||||||
|
|
||||||
|
if (digits.length > maxLength && digits.startsWith(code)) return digits.slice(code.length)
|
||||||
|
if (digits.length > maxLength && digits.startsWith('0')) return digits.slice(1)
|
||||||
|
return digits
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatWithCountry = (value: unknown, country: CountryDto) => {
|
||||||
|
const national = toNationalDigits(value, country)
|
||||||
|
return national
|
||||||
|
? `+${country.phoneCode} ${applyPattern(national, getNationalPattern(country))}`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Yerel 10 haneli biçimler: ülke kodu / `0` öneki atılır, son 10 hane kalıba dizilir. */
|
||||||
|
const formatLocal = (value: unknown, pattern: string) => {
|
||||||
|
let digits = onlyDigits(value)
|
||||||
|
if (digits.length > 10) digits = digits.slice(-10)
|
||||||
|
return digits.length === 10 ? applyPattern(digits, pattern) : String(value ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
|
type PhoneDisplayOptions = {
|
||||||
|
format: PhoneFormat
|
||||||
|
/** `phoneCountry` için satırdaki ülke değeri. */
|
||||||
|
countryId?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Liste/kart hücresinde gösterilecek metin. */
|
||||||
|
export const formatPhone = (value: unknown, { format, countryId }: PhoneDisplayOptions) => {
|
||||||
|
if (value == null || value === '') return ''
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case PhoneFormats.Turkey:
|
||||||
|
return formatLocal(value, TURKEY_PATTERN)
|
||||||
|
case PhoneFormats.International: {
|
||||||
|
const digits = onlyDigits(value)
|
||||||
|
const country = findCountryByDialCode(digits)
|
||||||
|
return country
|
||||||
|
? formatWithCountry(digits.slice(String(country.phoneCode).length), country)
|
||||||
|
: `+${digits}`
|
||||||
|
}
|
||||||
|
case PhoneFormats.Country: {
|
||||||
|
return formatWithCountry(value, getCountryOrDefault(countryId))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return formatLocal(value, GLOBAL_PATTERN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Editör
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Maske kuralı olan karakterler kalıpta sabit metin olarak kaçışlanır. */
|
||||||
|
const escapeMaskLiteral = (text: string) =>
|
||||||
|
text.replace(/[09#LlCcAa]/g, (character) => `\\${character}`)
|
||||||
|
|
||||||
|
/** Ülkenin maskesi: kod sabit önek, en az hane zorunlu, kalanı isteğe bağlı. */
|
||||||
|
export const getCountryPhoneEditorOptions = (country: CountryDto) => {
|
||||||
|
const pattern = getNationalPattern(country)
|
||||||
|
const minLength = country.phoneNumberMinLength ?? countSlots(pattern)
|
||||||
|
let slot = 0
|
||||||
|
const nationalMask = [...pattern]
|
||||||
|
.map((character) => {
|
||||||
|
if (character === '0') return slot++ < minLength ? '0' : '9'
|
||||||
|
return escapeMaskLiteral(character)
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
const prefix = `+${country.phoneCode} `
|
||||||
|
|
||||||
|
return {
|
||||||
|
mask: escapeMaskLiteral(prefix) + nationalMask,
|
||||||
|
placeholder: prefix + pattern,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Form alanı değer değişikliği; editörden forma doğru kabarcıklanır. */
|
||||||
|
const FIELD_VALUE_CHANGED_EVENT = 'platform:fieldvaluechanged'
|
||||||
|
|
||||||
|
type FieldValueChangedDetail = { dataField: string; value: unknown }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yayınlanan son değerler, form elemanı başına. DataGrid bir alan değişince (ör. cascade alt
|
||||||
|
* alanları temizlerken) form editörlerini yeniden oluşturur; yeni editör formun `formData`'sına
|
||||||
|
* bakarsa düzenleme öncesi değeri görür, çünkü DataGrid değişikliği satıra yazar, forma değil.
|
||||||
|
*/
|
||||||
|
const broadcastValues = new WeakMap<HTMLElement, Map<string, unknown>>()
|
||||||
|
|
||||||
|
const getFormElement = (element: unknown) =>
|
||||||
|
(element as HTMLElement | undefined)?.closest?.('.dx-form') as HTMLElement | null | undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grid/Tree düzenleme formunda editörleri DataGrid kendisi oluşturur ve değeri satıra yazar;
|
||||||
|
* formun `fieldDataChanged` olayı tetiklenmez. `onEditorPreparing` bu fonksiyonla editörün
|
||||||
|
* değişikliğini DOM olayı olarak yayınlar. `editorOptions.onValueChanged`'a yazılmaz: editör
|
||||||
|
* fabrikası onu kendi değer yazan işleyicisinin üstüne birleştirir.
|
||||||
|
*/
|
||||||
|
export const attachFieldValueBroadcast = (editor: {
|
||||||
|
dataField?: string
|
||||||
|
editorOptions: Record<string, any>
|
||||||
|
row?: { data?: Record<string, any> }
|
||||||
|
}) => {
|
||||||
|
const { dataField } = editor
|
||||||
|
if (!dataField) return
|
||||||
|
|
||||||
|
attachRowCountry(editor)
|
||||||
|
|
||||||
|
const previousValueChanged = editor.editorOptions.onValueChanged
|
||||||
|
editor.editorOptions.onValueChanged = (e: any) => {
|
||||||
|
previousValueChanged?.(e)
|
||||||
|
|
||||||
|
const formElement = getFormElement(e?.element)
|
||||||
|
if (formElement) {
|
||||||
|
const values = broadcastValues.get(formElement) ?? new Map<string, unknown>()
|
||||||
|
values.set(dataField, e?.value)
|
||||||
|
broadcastValues.set(formElement, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
;(e?.element as HTMLElement | undefined)?.dispatchEvent(
|
||||||
|
new CustomEvent<FieldValueChangedDetail>(FIELD_VALUE_CHANGED_EVENT, {
|
||||||
|
bubbles: true,
|
||||||
|
detail: { dataField, value: e?.value },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Düzenlenen satırın ülkesi. DataGrid formunun `formData`'sı satır değerini taşımaz; ülke
|
||||||
|
* `onEditorPreparing`'in verdiği satırdan okunur ve editör oluşunca bağlamaya aktarılır.
|
||||||
|
*/
|
||||||
|
const rowCountries = new WeakMap<object, unknown>()
|
||||||
|
|
||||||
|
const attachRowCountry = (editor: {
|
||||||
|
editorOptions: Record<string, any>
|
||||||
|
row?: { data?: Record<string, any> }
|
||||||
|
}) => {
|
||||||
|
// Grid/Tree ekranın ham editorOptions'ını onEditorPreparing'de yeniden birleştirdiği için token burada da görünür.
|
||||||
|
if (editor.editorOptions.format !== PhoneFormats.Country || !editor.row?.data) return
|
||||||
|
|
||||||
|
const countryField = editor.editorOptions.phoneCountryField || DEFAULT_PHONE_COUNTRY_FIELD
|
||||||
|
const countryId = editor.row.data[countryField]
|
||||||
|
if (arePhoneCountriesLoaded()) {
|
||||||
|
Object.assign(
|
||||||
|
editor.editorOptions,
|
||||||
|
getCountryPhoneEditorOptions(getCountryOrDefault(countryId)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousContentReady = editor.editorOptions.onContentReady
|
||||||
|
editor.editorOptions.onContentReady = (e: any) => {
|
||||||
|
if (e?.component) rowCountries.set(e.component, countryId)
|
||||||
|
previousContentReady?.(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const boundEditors = new WeakSet<object>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editörü içinde bulunduğu formun ülke alanına bağlar: ilk değer formda son yayınlanan
|
||||||
|
* değerden, o yoksa düzenlenen satırdan (Grid, Tree), o da yoksa form verisinden; sonraki
|
||||||
|
* değişiklikler formun `fieldDataChanged` olayından (Card, Scheduler, Form sayfası) ya da
|
||||||
|
* `attachFieldValueBroadcast` olayından (Grid, Tree) okunur. Form yoksa (satır içi düzenleme)
|
||||||
|
* Türkiye maskesinde kalır.
|
||||||
|
*/
|
||||||
|
const bindCountryMask = (editor: any, element: HTMLElement, countryField: string) => {
|
||||||
|
if (boundEditors.has(editor)) return
|
||||||
|
boundEditors.add(editor)
|
||||||
|
|
||||||
|
// contentReady anında editör forma henüz yerleşmemiş olabilir.
|
||||||
|
setTimeout(() => {
|
||||||
|
const formElement = getFormElement(element)
|
||||||
|
if (!formElement) return
|
||||||
|
|
||||||
|
let disposed = false
|
||||||
|
const apply = (countryId: unknown) => {
|
||||||
|
loadPhoneCountries()
|
||||||
|
.then(() => {
|
||||||
|
if (!disposed) editor.option(getCountryPhoneEditorOptions(getCountryOrDefault(countryId)))
|
||||||
|
})
|
||||||
|
.catch((error) => console.error('Telefon ülke listesi yüklenemedi', error))
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFieldValueChanged = (event: Event) => {
|
||||||
|
const { dataField, value } = (event as CustomEvent<FieldValueChangedDetail>).detail ?? {}
|
||||||
|
if (dataField === countryField) apply(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFormFieldDataChanged = (e: { dataField?: string; value?: unknown }) => {
|
||||||
|
if (e.dataField === countryField) apply(e.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = Form.getInstance(formElement) as Form | undefined
|
||||||
|
const broadcast = broadcastValues.get(formElement)
|
||||||
|
if (broadcast?.has(countryField)) apply(broadcast.get(countryField))
|
||||||
|
else if (rowCountries.has(editor)) apply(rowCountries.get(editor))
|
||||||
|
else apply((form?.option('formData') as Record<string, unknown> | undefined)?.[countryField])
|
||||||
|
formElement.addEventListener(FIELD_VALUE_CHANGED_EVENT, onFieldValueChanged)
|
||||||
|
form?.on('fieldDataChanged', onFormFieldDataChanged)
|
||||||
|
editor.on('disposing', () => {
|
||||||
|
disposed = true
|
||||||
|
formElement.removeEventListener(FIELD_VALUE_CHANGED_EVENT, onFieldValueChanged)
|
||||||
|
form?.off('fieldDataChanged', onFormFieldDataChanged)
|
||||||
|
})
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Telefon token'ı taşıyan editorOptions'a biçimin maskesini serer; yapılandırmada açıkça
|
||||||
|
* yazılmış seçenek (maske dahil) varsayılanı ezer. `phoneCountry` maskesi forma göre
|
||||||
|
* çalışma anında değiştiği için yapılandırmadaki maske yok sayılır.
|
||||||
|
*/
|
||||||
|
export const withPhoneEditorOptions = <T extends Record<string, any>>(editorOptions: T): T => {
|
||||||
|
const { format, phoneCountryField, ...options } = editorOptions
|
||||||
|
if (!isPhoneFormat(format)) return editorOptions
|
||||||
|
|
||||||
|
if (format !== PhoneFormats.Country) {
|
||||||
|
return { useMaskedValue: false, ...STATIC_EDITOR_OPTIONS[format], ...options } as unknown as T
|
||||||
|
}
|
||||||
|
|
||||||
|
const countryField = phoneCountryField || DEFAULT_PHONE_COUNTRY_FIELD
|
||||||
|
const previousContentReady = options.onContentReady
|
||||||
|
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
...getCountryPhoneEditorOptions(getCountryOrDefault(undefined)),
|
||||||
|
useMaskedValue: false,
|
||||||
|
onContentReady: (e: any) => {
|
||||||
|
if (typeof previousContentReady === 'function') previousContentReady(e)
|
||||||
|
bindCountryMask(e.component, e.element, countryField)
|
||||||
|
},
|
||||||
|
} as unknown as T
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
* diğer ayarları silmez.
|
* diğer ayarları silmez.
|
||||||
*/
|
*/
|
||||||
import { buildHtmlEditorDefaults } from '@/views/shared/htmlEditorOptions'
|
import { buildHtmlEditorDefaults } from '@/views/shared/htmlEditorOptions'
|
||||||
|
import { DEFAULT_PHONE_COUNTRY_FIELD, PhoneFormats } from '@/utils/phoneFormat'
|
||||||
import type { JsonObject } from './jsonUtils'
|
import type { JsonObject } from './jsonUtils'
|
||||||
|
|
||||||
export type EditorOptionsPreset = {
|
export type EditorOptionsPreset = {
|
||||||
|
|
@ -110,6 +111,31 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
|
||||||
placeholder: '(555) 123-4567',
|
placeholder: '(555) 123-4567',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'phoneTr',
|
||||||
|
label: 'App.PresetPhoneTr.PhoneTrLabel',
|
||||||
|
description: 'App.PresetPhoneTr.PhoneTrDescription',
|
||||||
|
editors: ['dxTextBox'],
|
||||||
|
value: { format: PhoneFormats.Turkey, showClearButton: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'phoneIntl',
|
||||||
|
label: 'App.PresetPhoneIntl.PhoneIntlLabel',
|
||||||
|
description: 'App.PresetPhoneIntl.PhoneIntlDescription',
|
||||||
|
editors: ['dxTextBox'],
|
||||||
|
value: { format: PhoneFormats.International, showClearButton: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'phoneCountry',
|
||||||
|
label: 'App.PresetPhoneCountry.PhoneCountryLabel',
|
||||||
|
description: 'App.PresetPhoneCountry.PhoneCountryDescription',
|
||||||
|
editors: ['dxTextBox'],
|
||||||
|
value: {
|
||||||
|
format: PhoneFormats.Country,
|
||||||
|
phoneCountryField: DEFAULT_PHONE_COUNTRY_FIELD,
|
||||||
|
showClearButton: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'textAreaAuto',
|
key: 'textAreaAuto',
|
||||||
label: 'App.PresetTextAreaAuto.TextAreaAutoLabel',
|
label: 'App.PresetTextAreaAuto.TextAreaAutoLabel',
|
||||||
|
|
|
||||||
|
|
@ -1228,6 +1228,9 @@ const WizardStepFields = ({
|
||||||
<pre>{`• {"showClearButton":true }`}</pre>
|
<pre>{`• {"showClearButton":true }`}</pre>
|
||||||
<pre>{`• {"format": "fixedPoint", "precision": 2}`}</pre>
|
<pre>{`• {"format": "fixedPoint", "precision": 2}`}</pre>
|
||||||
<pre>{`• {"format": "phoneGlobal", "mask":"(000) 000-0000", "maskInvalidMessage":"Lütfen geçerli bir telefon numarası girin", "useMaskedValue":false, "maskRules": { "X": "[1-9]" }, "placeholder": "(555) 123-4567" }`}</pre>
|
<pre>{`• {"format": "phoneGlobal", "mask":"(000) 000-0000", "maskInvalidMessage":"Lütfen geçerli bir telefon numarası girin", "useMaskedValue":false, "maskRules": { "X": "[1-9]" }, "placeholder": "(555) 123-4567" }`}</pre>
|
||||||
|
<pre>{`• {"format": "phoneTR"}`}</pre>
|
||||||
|
<pre>{`• {"format": "phoneIntl"}`}</pre>
|
||||||
|
<pre>{`• {"format": "phoneCountry", "phoneCountryField": "Country"}`}</pre>
|
||||||
<pre>{`• {"tooltip": { "enabled": true }}`}</pre>
|
<pre>{`• {"tooltip": { "enabled": true }}`}</pre>
|
||||||
<pre>{`• {"height":200}`}</pre>
|
<pre>{`• {"height":200}`}</pre>
|
||||||
<pre>{`• {"type":"date"}`}</pre>
|
<pre>{`• {"type":"date"}`}</pre>
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ import {
|
||||||
attachEditorValueChangedScript,
|
attachEditorValueChangedScript,
|
||||||
createEditingFormHandlers,
|
createEditingFormHandlers,
|
||||||
} from './shared/editingFormScripts'
|
} from './shared/editingFormScripts'
|
||||||
|
import { attachFieldValueBroadcast } from '@/utils/phoneFormat'
|
||||||
import { attachCascadeBehavior, createCascadeMaps } from './shared/cascade'
|
import { attachCascadeBehavior, createCascadeMaps } from './shared/cascade'
|
||||||
import { createConditionalCellStyleHandler } from './shared/cellStyles'
|
import { createConditionalCellStyleHandler } from './shared/cellStyles'
|
||||||
import {
|
import {
|
||||||
|
|
@ -81,7 +82,6 @@ import {
|
||||||
useWidgetGroupHeight,
|
useWidgetGroupHeight,
|
||||||
} from './shared/hooks'
|
} from './shared/hooks'
|
||||||
import { startWorkflowForInsertedRow } from './shared/workflowRows'
|
import { startWorkflowForInsertedRow } from './shared/workflowRows'
|
||||||
import { applyPhoneGlobalFormat } from './shared/columnFormatters'
|
|
||||||
|
|
||||||
interface GridProps {
|
interface GridProps {
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
|
|
@ -379,6 +379,9 @@ const Grid = (props: GridProps) => {
|
||||||
attachCascadeBehavior({ editor, gridDto, cascadeInfo, maps: cascadeMaps })
|
attachCascadeBehavior({ editor, gridDto, cascadeInfo, maps: cascadeMaps })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// phoneCountry maskesi ülke alanını bu olaydan izler; DataGrid formu fieldDataChanged yayınlamaz.
|
||||||
|
attachFieldValueBroadcast(editor)
|
||||||
|
|
||||||
// Yalnızca düzenleme formunda yer alan alanların script'i çalıştırılır.
|
// Yalnızca düzenleme formunda yer alan alanların script'i çalıştırılır.
|
||||||
if (columnFormat?.editOrderNo != null && columnFormat.editorScript) {
|
if (columnFormat?.editOrderNo != null && columnFormat.editorScript) {
|
||||||
attachEditorValueChangedScript({
|
attachEditorValueChangedScript({
|
||||||
|
|
@ -417,9 +420,7 @@ const Grid = (props: GridProps) => {
|
||||||
const memoizedColumns = useMemo(() => {
|
const memoizedColumns = useMemo(() => {
|
||||||
if (!gridDto) return undefined
|
if (!gridDto) return undefined
|
||||||
|
|
||||||
const columns = getBandedColumns()
|
return getBandedColumns()
|
||||||
columns?.forEach(applyPhoneGlobalFormat)
|
|
||||||
return columns
|
|
||||||
// getBandedColumns her render'da yeni referans üretebildiği için bağımlılığa alınmaz.
|
// getBandedColumns her render'da yeni referans üretebildiği için bağımlılığa alınmaz.
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [gridDto, currentCulture, localizationTexts])
|
}, [gridDto, currentCulture, localizationTexts])
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ import {
|
||||||
attachEditorValueChangedScript,
|
attachEditorValueChangedScript,
|
||||||
createEditingFormHandlers,
|
createEditingFormHandlers,
|
||||||
} from './shared/editingFormScripts'
|
} from './shared/editingFormScripts'
|
||||||
|
import { attachFieldValueBroadcast } from '@/utils/phoneFormat'
|
||||||
import { attachCascadeBehavior, createCascadeMaps } from './shared/cascade'
|
import { attachCascadeBehavior, createCascadeMaps } from './shared/cascade'
|
||||||
import { createConditionalCellStyleHandler } from './shared/cellStyles'
|
import { createConditionalCellStyleHandler } from './shared/cellStyles'
|
||||||
import {
|
import {
|
||||||
|
|
@ -389,6 +390,9 @@ const Tree = (props: TreeProps) => {
|
||||||
attachCascadeBehavior({ editor, gridDto, cascadeInfo, maps: cascadeMaps })
|
attachCascadeBehavior({ editor, gridDto, cascadeInfo, maps: cascadeMaps })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// phoneCountry maskesi ülke alanını bu olaydan izler; DataGrid formu fieldDataChanged yayınlamaz.
|
||||||
|
attachFieldValueBroadcast(editor)
|
||||||
|
|
||||||
if (columnFormat?.editOrderNo != null && columnFormat.editorScript) {
|
if (columnFormat?.editOrderNo != null && columnFormat.editorScript) {
|
||||||
attachEditorValueChangedScript({
|
attachEditorValueChangedScript({
|
||||||
editor,
|
editor,
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,35 @@
|
||||||
/**
|
/**
|
||||||
* Kolon düzeyinde özel görüntüleme biçimleri.
|
* Kolon düzeyinde özel görüntüleme biçimleri.
|
||||||
*/
|
*/
|
||||||
|
import {
|
||||||
|
DEFAULT_PHONE_COUNTRY_FIELD,
|
||||||
|
formatPhone,
|
||||||
|
isPhoneFormat,
|
||||||
|
PhoneFormats,
|
||||||
|
} from '@/utils/phoneFormat'
|
||||||
|
|
||||||
|
/** Görüntüsü ülke tablosundan okunan telefon biçimleri. */
|
||||||
|
export const needsPhoneCountries = (editorOptions: Record<string, any> | undefined) =>
|
||||||
|
editorOptions?.format === PhoneFormats.International ||
|
||||||
|
editorOptions?.format === PhoneFormats.Country
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 10 haneli yerel numarayı `(XXX) XXX-XXXX` biçiminde gösterir.
|
* Telefon token'ı (`phoneGlobal`, `phoneTR`, `phoneIntl`, `phoneCountry`) taşıyan kolonun
|
||||||
* Ülke kodu (+90 / 0090 / 0) ön ekleri temizlenir; 10 hane değilse boş döner.
|
* görüntü metnini üretir. `phoneCountry` ülkeyi satırdan okuduğu için `customizeText`
|
||||||
|
* değil `calculateDisplayValue` kullanılır; sıralama ve gruplama ise saklanan değer
|
||||||
|
* üzerinden kalır, aksi hâlde uzak sıralamaya fonksiyon seçici gider.
|
||||||
*/
|
*/
|
||||||
export const formatGlobalPhone = (value: unknown) => {
|
export const applyPhoneColumnFormat = (column: any, editorOptions: Record<string, any>) => {
|
||||||
if (!value) return ''
|
const format = editorOptions?.format
|
||||||
|
if (!isPhoneFormat(format) || !column?.dataField) return
|
||||||
|
|
||||||
let digits = String(value).replace(/\D/g, '')
|
const dataField = column.dataField as string
|
||||||
if (digits.length > 10) digits = digits.slice(-10)
|
const countryField = editorOptions.phoneCountryField || DEFAULT_PHONE_COUNTRY_FIELD
|
||||||
if (digits.length !== 10) return ''
|
|
||||||
|
|
||||||
const match = digits.match(/^(\d{3})(\d{3})(\d{4})$/)
|
|
||||||
return match ? `(${match[1]}) ${match[2]}-${match[3]}` : digits
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `editorOptions.format === 'phoneGlobal'` olan kolonlara telefon biçimini uygular.
|
|
||||||
* DevExtreme bazı durumlarda `format.formatter`'ı çağırmadığı için
|
|
||||||
* `customizeText` de yedek olarak eklenir.
|
|
||||||
*/
|
|
||||||
export const applyPhoneGlobalFormat = (column: any) => {
|
|
||||||
if (column?.editorOptions?.format !== 'phoneGlobal') return
|
|
||||||
|
|
||||||
column.dataType = 'string'
|
column.dataType = 'string'
|
||||||
column.format = { formatter: formatGlobalPhone }
|
column.format = undefined
|
||||||
column.customizeText = (cellInfo: any) => formatGlobalPhone(cellInfo?.value)
|
column.calculateDisplayValue = (rowData: Record<string, any>) =>
|
||||||
|
formatPhone(rowData?.[dataField], { format, countryId: rowData?.[countryField] })
|
||||||
|
column.calculateSortValue ??= dataField
|
||||||
|
column.calculateGroupValue ??= dataField
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
import { scanBarcode } from '@/components/shared/BarcodeScanner/scanBarcode'
|
import { scanBarcode } from '@/components/shared/BarcodeScanner/scanBarcode'
|
||||||
import { getNextSequenceValue } from '@/services/form.service'
|
import { getNextSequenceValue } from '@/services/form.service'
|
||||||
import { mergeHtmlEditorOptions } from '@/views/shared/htmlEditorOptions'
|
import { mergeHtmlEditorOptions } from '@/views/shared/htmlEditorOptions'
|
||||||
|
import { withPhoneEditorOptions } from '@/utils/phoneFormat'
|
||||||
import type { EditorType2, RowMode, SimpleItemWithColData } from '../../form/types'
|
import type { EditorType2, RowMode, SimpleItemWithColData } from '../../form/types'
|
||||||
import {
|
import {
|
||||||
autoNumber,
|
autoNumber,
|
||||||
|
|
@ -431,7 +432,7 @@ export const withEditorTypeDefaults = <T extends Record<string, any>>(
|
||||||
): T =>
|
): T =>
|
||||||
editorType2 === 'dxHtmlEditor'
|
editorType2 === 'dxHtmlEditor'
|
||||||
? mergeHtmlEditorOptions(editorOptions)
|
? mergeHtmlEditorOptions(editorOptions)
|
||||||
: editorOptions
|
: withPhoneEditorOptions(editorOptions)
|
||||||
|
|
||||||
type FormItemMapperParams = {
|
type FormItemMapperParams = {
|
||||||
mode: RowMode
|
mode: RowMode
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
UiCommandButtonPositionTypeEnum,
|
UiCommandButtonPositionTypeEnum,
|
||||||
UiLookupDataSourceTypeEnum,
|
UiLookupDataSourceTypeEnum,
|
||||||
} from '@/proxy/form/models'
|
} from '@/proxy/form/models'
|
||||||
import { addCss, getEditingFormFields } from './Utils'
|
import { addCss, getEditingFormFields, safeJsonParse } from './Utils'
|
||||||
import { clearLookupCache, clearLookupCacheByPrefix } from '../shared/lookupCache'
|
import { clearLookupCache, clearLookupCacheByPrefix } from '../shared/lookupCache'
|
||||||
import { useLookupDataSource } from '../form/useLookupDataSource'
|
import { useLookupDataSource } from '../form/useLookupDataSource'
|
||||||
import {
|
import {
|
||||||
|
|
@ -33,6 +33,8 @@ import {
|
||||||
} from '../form/editors/imageHoverPreview'
|
} from '../form/editors/imageHoverPreview'
|
||||||
import { translateWorkflowLabel } from '@/utils/workflow/workflowHelpers'
|
import { translateWorkflowLabel } from '@/utils/workflow/workflowHelpers'
|
||||||
import { findLookupColor, getLookupItemColor, tagBadgeColors } from '../shared/lookupColor'
|
import { findLookupColor, getLookupItemColor, tagBadgeColors } from '../shared/lookupColor'
|
||||||
|
import { applyPhoneColumnFormat, needsPhoneCountries } from './shared/columnFormatters'
|
||||||
|
import { arePhoneCountriesLoaded, loadPhoneCountries } from '@/utils/phoneFormat'
|
||||||
|
|
||||||
const cellTemplateMultiValue = (
|
const cellTemplateMultiValue = (
|
||||||
cellElement: HTMLElement,
|
cellElement: HTMLElement,
|
||||||
|
|
@ -219,6 +221,30 @@ const useListFormColumns = ({
|
||||||
clearLookupCache()
|
clearLookupCache()
|
||||||
}, [listFormCode])
|
}, [listFormCode])
|
||||||
|
|
||||||
|
// Ülkeye bağlı telefon görüntüsü eşzamanlı üretilir; liste ilk açıldığında ülkeler
|
||||||
|
// satırlardan sonra gelirse hücreler yüklemeden sonra yeniden çizilir.
|
||||||
|
useEffect(() => {
|
||||||
|
if (arePhoneCountriesLoaded()) return
|
||||||
|
const hasPhoneCountryColumn = gridDto?.columnFormats?.some((colData) =>
|
||||||
|
needsPhoneCountries(safeJsonParse<Record<string, any>>(colData.editorOptions, {})),
|
||||||
|
)
|
||||||
|
if (!hasPhoneCountryColumn) return
|
||||||
|
|
||||||
|
let cancelled = false
|
||||||
|
loadPhoneCountries()
|
||||||
|
.then(() => {
|
||||||
|
if (cancelled) return
|
||||||
|
const instance = gridRef?.current?.instance?.()
|
||||||
|
if (typeof instance?.repaint === 'function') instance.repaint()
|
||||||
|
else instance?.refresh?.()
|
||||||
|
})
|
||||||
|
.catch((error) => console.error('Telefon ülke listesi yüklenemedi', error))
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [gridDto, gridRef])
|
||||||
|
|
||||||
const { getLookupDataSource } = useLookupDataSource({ listFormCode, isSubForm })
|
const { getLookupDataSource } = useLookupDataSource({ listFormCode, isSubForm })
|
||||||
|
|
||||||
// DataGrid lookup'ı satırı `options.data` ile verir; görünen metin yüklemesi satırsız çağırır.
|
// DataGrid lookup'ı satırı `options.data` ile verir; görünen metin yüklemesi satırsız çağırır.
|
||||||
|
|
@ -618,6 +644,8 @@ const useListFormColumns = ({
|
||||||
column.encodeHtml = editorOptions.encodeHtml
|
column.encodeHtml = editorOptions.encodeHtml
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyPhoneColumnFormat(column, editorOptions)
|
||||||
|
|
||||||
// columnCustomizationDto
|
// columnCustomizationDto
|
||||||
column.fixed = colData.columnCustomizationDto?.fixed
|
column.fixed = colData.columnCustomizationDto?.fixed
|
||||||
column.fixedPosition = colData.columnCustomizationDto?.fixedPosition as HorizontalEdge
|
column.fixedPosition = colData.columnCustomizationDto?.fixedPosition as HorizontalEdge
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue