>(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
+}
diff --git a/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts b/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts
index a7323927..e7923ade 100644
--- a/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts
+++ b/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts
@@ -3,6 +3,7 @@
* diğer ayarları silmez.
*/
import { buildHtmlEditorDefaults } from '@/views/shared/htmlEditorOptions'
+import { DEFAULT_PHONE_COUNTRY_FIELD, PhoneFormats } from '@/utils/phoneFormat'
import type { JsonObject } from './jsonUtils'
export type EditorOptionsPreset = {
@@ -110,6 +111,31 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
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',
label: 'App.PresetTextAreaAuto.TextAreaAutoLabel',
diff --git a/ui/src/views/admin/listForm/wizard/WizardStepFields.tsx b/ui/src/views/admin/listForm/wizard/WizardStepFields.tsx
index d58a1ff0..011dd851 100644
--- a/ui/src/views/admin/listForm/wizard/WizardStepFields.tsx
+++ b/ui/src/views/admin/listForm/wizard/WizardStepFields.tsx
@@ -1228,6 +1228,9 @@ const WizardStepFields = ({
{`• {"showClearButton":true }`}
{`• {"format": "fixedPoint", "precision": 2}`}
{`• {"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" }`}
+ {`• {"format": "phoneTR"}`}
+ {`• {"format": "phoneIntl"}`}
+ {`• {"format": "phoneCountry", "phoneCountryField": "Country"}`}
{`• {"tooltip": { "enabled": true }}`}
{`• {"height":200}`}
{`• {"type":"date"}`}
diff --git a/ui/src/views/list/Grid.tsx b/ui/src/views/list/Grid.tsx
index a3b09a6c..618264f6 100644
--- a/ui/src/views/list/Grid.tsx
+++ b/ui/src/views/list/Grid.tsx
@@ -71,6 +71,7 @@ import {
attachEditorValueChangedScript,
createEditingFormHandlers,
} from './shared/editingFormScripts'
+import { attachFieldValueBroadcast } from '@/utils/phoneFormat'
import { attachCascadeBehavior, createCascadeMaps } from './shared/cascade'
import { createConditionalCellStyleHandler } from './shared/cellStyles'
import {
@@ -81,7 +82,6 @@ import {
useWidgetGroupHeight,
} from './shared/hooks'
import { startWorkflowForInsertedRow } from './shared/workflowRows'
-import { applyPhoneGlobalFormat } from './shared/columnFormatters'
interface GridProps {
listFormCode: string
@@ -379,6 +379,9 @@ const Grid = (props: GridProps) => {
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.
if (columnFormat?.editOrderNo != null && columnFormat.editorScript) {
attachEditorValueChangedScript({
@@ -417,9 +420,7 @@ const Grid = (props: GridProps) => {
const memoizedColumns = useMemo(() => {
if (!gridDto) return undefined
- const columns = getBandedColumns()
- columns?.forEach(applyPhoneGlobalFormat)
- return columns
+ return getBandedColumns()
// getBandedColumns her render'da yeni referans üretebildiği için bağımlılığa alınmaz.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [gridDto, currentCulture, localizationTexts])
diff --git a/ui/src/views/list/Tree.tsx b/ui/src/views/list/Tree.tsx
index 240eee93..7abcca9f 100644
--- a/ui/src/views/list/Tree.tsx
+++ b/ui/src/views/list/Tree.tsx
@@ -67,6 +67,7 @@ import {
attachEditorValueChangedScript,
createEditingFormHandlers,
} from './shared/editingFormScripts'
+import { attachFieldValueBroadcast } from '@/utils/phoneFormat'
import { attachCascadeBehavior, createCascadeMaps } from './shared/cascade'
import { createConditionalCellStyleHandler } from './shared/cellStyles'
import {
@@ -389,6 +390,9 @@ const Tree = (props: TreeProps) => {
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) {
attachEditorValueChangedScript({
editor,
diff --git a/ui/src/views/list/shared/columnFormatters.ts b/ui/src/views/list/shared/columnFormatters.ts
index e677b913..1fd8e54d 100644
--- a/ui/src/views/list/shared/columnFormatters.ts
+++ b/ui/src/views/list/shared/columnFormatters.ts
@@ -1,31 +1,35 @@
/**
* 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 | undefined) =>
+ editorOptions?.format === PhoneFormats.International ||
+ editorOptions?.format === PhoneFormats.Country
/**
- * 10 haneli yerel numarayı `(XXX) XXX-XXXX` biçiminde gösterir.
- * Ülke kodu (+90 / 0090 / 0) ön ekleri temizlenir; 10 hane değilse boş döner.
+ * Telefon token'ı (`phoneGlobal`, `phoneTR`, `phoneIntl`, `phoneCountry`) taşıyan kolonun
+ * 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) => {
- if (!value) return ''
+export const applyPhoneColumnFormat = (column: any, editorOptions: Record) => {
+ const format = editorOptions?.format
+ if (!isPhoneFormat(format) || !column?.dataField) return
- let digits = String(value).replace(/\D/g, '')
- if (digits.length > 10) digits = digits.slice(-10)
- 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
+ const dataField = column.dataField as string
+ const countryField = editorOptions.phoneCountryField || DEFAULT_PHONE_COUNTRY_FIELD
column.dataType = 'string'
- column.format = { formatter: formatGlobalPhone }
- column.customizeText = (cellInfo: any) => formatGlobalPhone(cellInfo?.value)
+ column.format = undefined
+ column.calculateDisplayValue = (rowData: Record) =>
+ formatPhone(rowData?.[dataField], { format, countryId: rowData?.[countryField] })
+ column.calculateSortValue ??= dataField
+ column.calculateGroupValue ??= dataField
}
diff --git a/ui/src/views/list/shared/editingForm.ts b/ui/src/views/list/shared/editingForm.ts
index b36676bd..ea1ef6c9 100644
--- a/ui/src/views/list/shared/editingForm.ts
+++ b/ui/src/views/list/shared/editingForm.ts
@@ -16,6 +16,7 @@ import {
import { scanBarcode } from '@/components/shared/BarcodeScanner/scanBarcode'
import { getNextSequenceValue } from '@/services/form.service'
import { mergeHtmlEditorOptions } from '@/views/shared/htmlEditorOptions'
+import { withPhoneEditorOptions } from '@/utils/phoneFormat'
import type { EditorType2, RowMode, SimpleItemWithColData } from '../../form/types'
import {
autoNumber,
@@ -431,7 +432,7 @@ export const withEditorTypeDefaults = >(
): T =>
editorType2 === 'dxHtmlEditor'
? mergeHtmlEditorOptions(editorOptions)
- : editorOptions
+ : withPhoneEditorOptions(editorOptions)
type FormItemMapperParams = {
mode: RowMode
diff --git a/ui/src/views/list/useListFormColumns.ts b/ui/src/views/list/useListFormColumns.ts
index 3ce12ff5..f8711328 100644
--- a/ui/src/views/list/useListFormColumns.ts
+++ b/ui/src/views/list/useListFormColumns.ts
@@ -16,7 +16,7 @@ import {
UiCommandButtonPositionTypeEnum,
UiLookupDataSourceTypeEnum,
} from '@/proxy/form/models'
-import { addCss, getEditingFormFields } from './Utils'
+import { addCss, getEditingFormFields, safeJsonParse } from './Utils'
import { clearLookupCache, clearLookupCacheByPrefix } from '../shared/lookupCache'
import { useLookupDataSource } from '../form/useLookupDataSource'
import {
@@ -33,6 +33,8 @@ import {
} from '../form/editors/imageHoverPreview'
import { translateWorkflowLabel } from '@/utils/workflow/workflowHelpers'
import { findLookupColor, getLookupItemColor, tagBadgeColors } from '../shared/lookupColor'
+import { applyPhoneColumnFormat, needsPhoneCountries } from './shared/columnFormatters'
+import { arePhoneCountriesLoaded, loadPhoneCountries } from '@/utils/phoneFormat'
const cellTemplateMultiValue = (
cellElement: HTMLElement,
@@ -219,6 +221,30 @@ const useListFormColumns = ({
clearLookupCache()
}, [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>(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 })
// 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
}
+ applyPhoneColumnFormat(column, editorOptions)
+
// columnCustomizationDto
column.fixed = colData.columnCustomizationDto?.fixed
column.fixedPosition = colData.columnCustomizationDto?.fixedPosition as HorizontalEdge