Helper Codes güncellemeleri ve EditorScript düzenlemesi
This commit is contained in:
parent
96f7091d46
commit
e1c808310d
6 changed files with 863 additions and 1231 deletions
|
|
@ -26,14 +26,11 @@ const boolOptions = [
|
|||
{ key: 'readOnly', label: 'readOnly' },
|
||||
{ key: 'disabled', label: 'disabled' },
|
||||
{ key: 'searchEnabled', label: 'searchEnabled' },
|
||||
{ key: 'showDataBeforeSearch', label: 'showDataBeforeSearch' },
|
||||
{ key: 'acceptCustomValue', label: 'acceptCustomValue' },
|
||||
{ key: 'showSpinButtons', label: 'showSpinButtons' },
|
||||
{ key: 'useMaskBehavior', label: 'useMaskBehavior' },
|
||||
{ key: 'useMaskedValue', label: 'useMaskedValue' },
|
||||
{ key: 'spellcheck', label: 'spellcheck' },
|
||||
{ key: 'openOnFieldClick', label: 'openOnFieldClick' },
|
||||
{ key: 'showDropDownButton', label: 'showDropDownButton' },
|
||||
]
|
||||
|
||||
const htmlToolbarItems = [
|
||||
|
|
@ -302,10 +299,6 @@ function EditorOptionsBuilderDialog({
|
|||
>
|
||||
Editor Options Builder
|
||||
</h5>
|
||||
<FaSlidersH
|
||||
className="text-gray-400"
|
||||
title="Bu dialog editorOptions alanını metin yazmadan oluşturmak için kullanılır."
|
||||
/>
|
||||
</div>
|
||||
|
||||
{parseError && (
|
||||
|
|
@ -314,8 +307,8 @@ function EditorOptionsBuilderDialog({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid min-h-0 grid-cols-1 gap-4 overflow-y-auto pr-1 lg:grid-cols-3">
|
||||
<section className="lg:col-span-2 flex flex-col gap-4">
|
||||
<div className="grid grid-cols-2 min-h-0 gap-4 overflow-y-auto pr-1 ">
|
||||
<section className="flex flex-col gap-4">
|
||||
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
|
||||
<div
|
||||
className="mb-3 text-sm font-semibold"
|
||||
|
|
@ -327,7 +320,7 @@ function EditorOptionsBuilderDialog({
|
|||
{boolOptions.map((option) => (
|
||||
<label
|
||||
key={option.key}
|
||||
className="flex items-center gap-2 rounded border border-gray-100 dark:border-gray-700 px-2 py-2 text-sm"
|
||||
className="flex items-center gap-2 rounded px-1 py-1 text-sm"
|
||||
title={`${option.key}: true/false olarak editorOptions içine yazılır.`}
|
||||
>
|
||||
<input
|
||||
|
|
@ -565,7 +558,7 @@ function EditorOptionsBuilderDialog({
|
|||
>
|
||||
5. NumberBox
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
<div className="grid grid-cols-1 md:grid-cols-5 gap-3">
|
||||
{['min', 'max', 'step'].map((key) => (
|
||||
<label key={key} className="text-xs text-gray-500">
|
||||
{key}
|
||||
|
|
@ -585,7 +578,7 @@ function EditorOptionsBuilderDialog({
|
|||
</label>
|
||||
))}
|
||||
<label className="text-xs text-gray-500">
|
||||
format.type
|
||||
type
|
||||
<select
|
||||
className={baseInputClass}
|
||||
value={toSelectValue(getByPath(options, 'format.type'))}
|
||||
|
|
@ -600,7 +593,7 @@ function EditorOptionsBuilderDialog({
|
|||
</select>
|
||||
</label>
|
||||
<label className="text-xs text-gray-500">
|
||||
format.precision
|
||||
precision
|
||||
<input
|
||||
className={baseInputClass}
|
||||
value={toInputValue(getByPath(options, 'format.precision'))}
|
||||
|
|
@ -706,12 +699,106 @@ function EditorOptionsBuilderDialog({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
|
||||
<div className="flex items-center justify-between gap-2 mb-3">
|
||||
<div>
|
||||
<div
|
||||
className="text-sm font-semibold"
|
||||
title="DevExtreme dokümanındaki herhangi bir editorOptions path'ini eklemek için kullan. Örnek path: toolbar.multiline, maskRules.X, inputAttr.aria-label."
|
||||
>
|
||||
7. Özel Ayarlar
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
icon={<FaPlus />}
|
||||
onClick={() =>
|
||||
setCustomOptions((current) => [
|
||||
...current,
|
||||
{
|
||||
id: `${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
||||
path: '',
|
||||
value: '',
|
||||
type: 'string',
|
||||
},
|
||||
])
|
||||
}
|
||||
>
|
||||
Ekle
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{customOptions.map((option) => (
|
||||
<div key={option.id} className="grid grid-cols-12 gap-2">
|
||||
<input
|
||||
className={`${baseInputClass} col-span-4`}
|
||||
value={option.path}
|
||||
onChange={(event) =>
|
||||
setCustomOptions((current) =>
|
||||
current.map((item) =>
|
||||
item.id === option.id ? { ...item, path: event.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
placeholder="path.to.option"
|
||||
title="Nokta ile nested path yaz. Örnek: toolbar.multiline"
|
||||
/>
|
||||
<select
|
||||
className={`${baseInputClass} col-span-2`}
|
||||
value={option.type}
|
||||
onChange={(event) =>
|
||||
setCustomOptions((current) =>
|
||||
current.map((item) =>
|
||||
item.id === option.id
|
||||
? { ...item, type: event.target.value as CustomOption['type'] }
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="string">string</option>
|
||||
<option value="number">number</option>
|
||||
<option value="boolean">boolean</option>
|
||||
<option value="json">json</option>
|
||||
</select>
|
||||
<input
|
||||
className={`${baseInputClass} col-span-5`}
|
||||
value={option.value}
|
||||
onChange={(event) =>
|
||||
setCustomOptions((current) =>
|
||||
current.map((item) =>
|
||||
item.id === option.id ? { ...item, value: event.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
placeholder={option.type === 'boolean' ? 'true / false' : 'value'}
|
||||
title="Tip JSON ise object/array yazabilirsin. Tip boolean ise true veya false yaz."
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
shape="circle"
|
||||
variant="plain"
|
||||
icon={<FaTrash />}
|
||||
onClick={() =>
|
||||
setCustomOptions((current) =>
|
||||
current.filter((item) => item.id !== option.id),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded border border-gray-200 dark:border-gray-700 p-3 flex flex-col min-h-[420px]">
|
||||
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
|
||||
<div
|
||||
className="mb-3 text-sm font-semibold"
|
||||
title="Daha önce kullandığın hazır editorOptions örnekleri. Tıkladığın preset mevcut JSON ile birleştirilir, yani başka seçenekleri silmeden üzerine ekler."
|
||||
>
|
||||
7. Hazır Ayarlar
|
||||
Hazır Ayarlar
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
|
|
@ -886,108 +973,14 @@ function EditorOptionsBuilderDialog({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
|
||||
<div className="flex items-center justify-between gap-2 mb-3">
|
||||
<div>
|
||||
<div
|
||||
className="text-sm font-semibold"
|
||||
title="DevExtreme dokümanındaki herhangi bir editorOptions path'ini eklemek için kullan. Örnek path: toolbar.multiline, maskRules.X, inputAttr.aria-label."
|
||||
>
|
||||
8. Özel Ayarlar
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
icon={<FaPlus />}
|
||||
onClick={() =>
|
||||
setCustomOptions((current) => [
|
||||
...current,
|
||||
{
|
||||
id: `${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
||||
path: '',
|
||||
value: '',
|
||||
type: 'string',
|
||||
},
|
||||
])
|
||||
}
|
||||
>
|
||||
Ekle
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{customOptions.map((option) => (
|
||||
<div key={option.id} className="grid grid-cols-12 gap-2">
|
||||
<input
|
||||
className={`${baseInputClass} col-span-4`}
|
||||
value={option.path}
|
||||
onChange={(event) =>
|
||||
setCustomOptions((current) =>
|
||||
current.map((item) =>
|
||||
item.id === option.id ? { ...item, path: event.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
placeholder="path.to.option"
|
||||
title="Nokta ile nested path yaz. Örnek: toolbar.multiline"
|
||||
/>
|
||||
<select
|
||||
className={`${baseInputClass} col-span-2`}
|
||||
value={option.type}
|
||||
onChange={(event) =>
|
||||
setCustomOptions((current) =>
|
||||
current.map((item) =>
|
||||
item.id === option.id
|
||||
? { ...item, type: event.target.value as CustomOption['type'] }
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="string">string</option>
|
||||
<option value="number">number</option>
|
||||
<option value="boolean">boolean</option>
|
||||
<option value="json">json</option>
|
||||
</select>
|
||||
<input
|
||||
className={`${baseInputClass} col-span-5`}
|
||||
value={option.value}
|
||||
onChange={(event) =>
|
||||
setCustomOptions((current) =>
|
||||
current.map((item) =>
|
||||
item.id === option.id ? { ...item, value: event.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
placeholder={option.type === 'boolean' ? 'true / false' : 'value'}
|
||||
title="Tip JSON ise object/array yazabilirsin. Tip boolean ise true veya false yaz."
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
shape="circle"
|
||||
variant="plain"
|
||||
icon={<FaTrash />}
|
||||
onClick={() =>
|
||||
setCustomOptions((current) =>
|
||||
current.filter((item) => item.id !== option.id),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded border border-gray-200 dark:border-gray-700 p-3 flex flex-col min-h-[420px]">
|
||||
<div
|
||||
className="flex items-center gap-2 text-sm font-semibold mb-3"
|
||||
className="flex items-center gap-2 text-sm font-semibold mt-3"
|
||||
title="Kaydet/Uygula sonrası editorOptions alanına yazılacak net JSON budur."
|
||||
>
|
||||
<FaCode />
|
||||
JSON Önizleme
|
||||
</div>
|
||||
<pre className="flex-1 overflow-auto rounded bg-gray-50 dark:bg-gray-900 p-3 text-xs whitespace-pre-wrap">
|
||||
<pre className="flex overflow-auto rounded bg-gray-50 dark:bg-gray-900 p-3 text-xs whitespace-pre-wrap">
|
||||
{preview || '{}'}
|
||||
</pre>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Button, Dialog } from '@/components/ui'
|
||||
import { SelectBoxOption } from '@/types/shared'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import Editor from '@monaco-editor/react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { FaBolt, FaCheck, FaCode, FaPlus, FaTimes, FaTrash } from 'react-icons/fa'
|
||||
|
||||
|
|
@ -10,16 +10,6 @@ type CopyMapping = {
|
|||
target: string
|
||||
}
|
||||
|
||||
type ToggleRule = {
|
||||
id: string
|
||||
target: string
|
||||
property: 'visible' | 'disabled' | 'readOnly'
|
||||
source: string
|
||||
operator: 'equals' | 'notEquals' | 'empty' | 'notEmpty'
|
||||
value: string
|
||||
whenTrue: boolean
|
||||
}
|
||||
|
||||
type ConditionalAction = {
|
||||
id: string
|
||||
source: string
|
||||
|
|
@ -35,18 +25,13 @@ type ConditionalAction = {
|
|||
value: string
|
||||
actionType:
|
||||
| 'setField'
|
||||
| 'copySelected'
|
||||
| 'setFieldState'
|
||||
| 'setFieldStyle'
|
||||
| 'apiToField'
|
||||
| 'openUrl'
|
||||
| 'alert'
|
||||
| 'confirm'
|
||||
| 'calculate'
|
||||
targetField: string
|
||||
targetProperty: 'visible' | 'disabled' | 'readOnly' | 'color' | 'backgroundColor' | 'borderColor'
|
||||
textValue: string
|
||||
selectedColumn: string
|
||||
apiUrl: string
|
||||
apiMethod: 'GET' | 'POST'
|
||||
responsePath: string
|
||||
|
|
@ -76,9 +61,7 @@ const createConditionalAction = (): ConditionalAction => ({
|
|||
value: '',
|
||||
actionType: 'setField',
|
||||
targetField: '',
|
||||
targetProperty: 'disabled',
|
||||
textValue: '',
|
||||
selectedColumn: '',
|
||||
apiUrl: '',
|
||||
apiMethod: 'GET',
|
||||
responsePath: '',
|
||||
|
|
@ -98,14 +81,6 @@ function quote(value: string) {
|
|||
return JSON.stringify(value ?? '')
|
||||
}
|
||||
|
||||
function buildCondition(rule: ToggleRule) {
|
||||
const source = `next[${quote(rule.source)}]`
|
||||
if (rule.operator === 'empty') return `!${source}`
|
||||
if (rule.operator === 'notEmpty') return `!!${source}`
|
||||
if (rule.operator === 'notEquals') return `${source} !== ${quote(rule.value)}`
|
||||
return `${source} === ${quote(rule.value)}`
|
||||
}
|
||||
|
||||
function buildGenericCondition(rule: ConditionalAction) {
|
||||
if (rule.operator === 'always') return 'true'
|
||||
const source = `next[${quote(rule.source)}]`
|
||||
|
|
@ -119,11 +94,11 @@ function buildGenericCondition(rule: ConditionalAction) {
|
|||
return `${source} === ${quote(rule.value)}`
|
||||
}
|
||||
|
||||
function conditionNeedsSource(operator: ConditionalAction['operator'] | ToggleRule['operator']) {
|
||||
function conditionNeedsSource(operator: ConditionalAction['operator']) {
|
||||
return operator !== 'always'
|
||||
}
|
||||
|
||||
function conditionNeedsValue(operator: ConditionalAction['operator'] | ToggleRule['operator']) {
|
||||
function conditionNeedsValue(operator: ConditionalAction['operator']) {
|
||||
return (
|
||||
operator === 'equals' ||
|
||||
operator === 'notEquals' ||
|
||||
|
|
@ -138,11 +113,6 @@ function isConditionalActionReady(action: ConditionalAction) {
|
|||
if (conditionNeedsValue(action.operator) && !action.value.trim()) return false
|
||||
if (action.actionType === 'setField')
|
||||
return Boolean(action.targetField && action.textValue.trim())
|
||||
if (action.actionType === 'copySelected')
|
||||
return Boolean(action.targetField && action.selectedColumn)
|
||||
if (action.actionType === 'setFieldState') return Boolean(action.targetField)
|
||||
if (action.actionType === 'setFieldStyle')
|
||||
return Boolean(action.targetField && action.textValue.trim())
|
||||
if (action.actionType === 'apiToField') return Boolean(action.targetField && action.apiUrl.trim())
|
||||
if (action.actionType === 'openUrl') return Boolean(action.textValue.trim())
|
||||
if (action.actionType === 'alert' || action.actionType === 'confirm')
|
||||
|
|
@ -151,20 +121,6 @@ function isConditionalActionReady(action: ConditionalAction) {
|
|||
return false
|
||||
}
|
||||
|
||||
function isToggleRuleReady(rule: ToggleRule) {
|
||||
if (!rule.target || !rule.source) return false
|
||||
if (conditionNeedsValue(rule.operator) && !rule.value.trim()) return false
|
||||
return true
|
||||
}
|
||||
|
||||
function buildValueExpression(value: string) {
|
||||
const trimmed = value.trim()
|
||||
if (!trimmed) return "''"
|
||||
if (trimmed === 'true' || trimmed === 'false') return trimmed
|
||||
if (trimmed.startsWith('=') && trimmed.length > 1) return trimmed.slice(1)
|
||||
return quote(value)
|
||||
}
|
||||
|
||||
function fieldLabel(value: string, fallback: string) {
|
||||
return value || fallback
|
||||
}
|
||||
|
|
@ -172,7 +128,6 @@ function fieldLabel(value: string, fallback: string) {
|
|||
function buildScript({
|
||||
currentField,
|
||||
copyMappings,
|
||||
toggleRules,
|
||||
daysStartField,
|
||||
daysEndField,
|
||||
daysTargetField,
|
||||
|
|
@ -188,11 +143,9 @@ function buildScript({
|
|||
timeTargetField,
|
||||
conditionalActions,
|
||||
serviceCall,
|
||||
customScript,
|
||||
}: {
|
||||
currentField?: string
|
||||
copyMappings: CopyMapping[]
|
||||
toggleRules: ToggleRule[]
|
||||
daysStartField: string
|
||||
daysEndField: string
|
||||
daysTargetField: string
|
||||
|
|
@ -208,12 +161,9 @@ function buildScript({
|
|||
timeTargetField: string
|
||||
conditionalActions: ConditionalAction[]
|
||||
serviceCall: string
|
||||
customScript: string
|
||||
}) {
|
||||
const activeCopyMappings = copyMappings.filter((mapping) => mapping.source && mapping.target)
|
||||
const activeToggleRules = toggleRules.filter(isToggleRuleReady)
|
||||
const hasCopyMappings = activeCopyMappings.length > 0
|
||||
const hasToggleRules = activeToggleRules.length > 0
|
||||
const hasDateDifference = Boolean(daysStartField && daysEndField && daysTargetField)
|
||||
const hasSelectedItemAmount = Boolean(
|
||||
selectedItemAmountEnabled && amountQuantityField && amountUnitPriceField && amountTotalField,
|
||||
|
|
@ -229,7 +179,6 @@ function buildScript({
|
|||
const hasServiceCall = Boolean(serviceCall.trim())
|
||||
const hasBuilderSelection =
|
||||
hasCopyMappings ||
|
||||
hasToggleRules ||
|
||||
hasDateDifference ||
|
||||
hasSelectedItemAmount ||
|
||||
hasRowAmount ||
|
||||
|
|
@ -238,7 +187,7 @@ function buildScript({
|
|||
hasServiceCall
|
||||
|
||||
if (!hasBuilderSelection) {
|
||||
return customScript.trim()
|
||||
return ''
|
||||
}
|
||||
|
||||
const needsSetFormData =
|
||||
|
|
@ -248,17 +197,15 @@ function buildScript({
|
|||
hasRowAmount ||
|
||||
hasTimeDifference ||
|
||||
activeConditionalActions.some((action) =>
|
||||
['setField', 'copySelected', 'apiToField', 'calculate'].includes(action.actionType),
|
||||
['setField', 'apiToField', 'calculate'].includes(action.actionType),
|
||||
)
|
||||
const needsSelectedItem =
|
||||
hasCopyMappings ||
|
||||
hasSelectedItemAmount ||
|
||||
activeConditionalActions.some((action) =>
|
||||
[
|
||||
'copySelected',
|
||||
'calculate',
|
||||
'setField',
|
||||
'setFieldStyle',
|
||||
'apiToField',
|
||||
'openUrl',
|
||||
'alert',
|
||||
|
|
@ -269,30 +216,21 @@ function buildScript({
|
|||
activeCopyMappings.some((mapping) => mapping.source.includes('.')) ||
|
||||
activeConditionalActions.some(
|
||||
(action) =>
|
||||
action.actionType === 'copySelected' ||
|
||||
action.actionType === 'apiToField' ||
|
||||
['setField', 'setFieldStyle', 'openUrl', 'alert', 'confirm'].includes(action.actionType),
|
||||
['setField', 'openUrl', 'alert', 'confirm'].includes(action.actionType),
|
||||
)
|
||||
const needsRenderTemplate = activeConditionalActions.some((action) =>
|
||||
['setField', 'setFieldStyle', 'apiToField', 'openUrl', 'alert', 'confirm'].includes(
|
||||
['setField', 'apiToField', 'openUrl', 'alert', 'confirm'].includes(
|
||||
action.actionType,
|
||||
),
|
||||
)
|
||||
const needsForm =
|
||||
hasToggleRules ||
|
||||
activeConditionalActions.some(
|
||||
(action) => action.actionType === 'setFieldState' || action.actionType === 'setFieldStyle',
|
||||
)
|
||||
const needsFieldState =
|
||||
hasToggleRules ||
|
||||
activeConditionalActions.some((action) => action.actionType === 'setFieldState')
|
||||
const needsFieldStyle = activeConditionalActions.some(
|
||||
(action) => action.actionType === 'setFieldStyle',
|
||||
const needsRollbackCurrentValue = activeConditionalActions.some(
|
||||
(action) => action.actionType === 'confirm',
|
||||
)
|
||||
|
||||
const lines: string[] = ['(async () => {']
|
||||
|
||||
if (needsSetFormData || hasToggleRules || hasConditionalActions) {
|
||||
if (needsSetFormData || hasConditionalActions) {
|
||||
lines.push(
|
||||
" const currentField = (typeof editor !== 'undefined' && editor?.dataField) || e?.dataField || " +
|
||||
quote(currentField || '') +
|
||||
|
|
@ -301,6 +239,12 @@ function buildScript({
|
|||
lines.push(' const next = { ...formData, [currentField]: e?.value };')
|
||||
}
|
||||
|
||||
if (needsRollbackCurrentValue) {
|
||||
lines.push(
|
||||
' const rollbackCurrentValue = () => { const previousFieldValue = typeof previousValue !== "undefined" ? previousValue : (e?.previousValue ?? formData?.[currentField] ?? null); next[currentField] = previousFieldValue; const currentEditor = e?.component?.getEditor?.(currentField); currentEditor?.option?.("value", previousFieldValue); if (!currentEditor) e?.component?.option?.("value", previousFieldValue); if (typeof setFormData === "function") setFormData({ ...next, [currentField]: previousFieldValue }); };',
|
||||
)
|
||||
}
|
||||
|
||||
if (needsSelectedItem) {
|
||||
lines.push(
|
||||
' const selectedItem = e?.component?.option ? e.component.option("selectedItem") : null;',
|
||||
|
|
@ -319,24 +263,6 @@ function buildScript({
|
|||
)
|
||||
}
|
||||
|
||||
if (needsForm) {
|
||||
lines.push(
|
||||
' const form = e?.component?.itemOption ? e.component : ((typeof editor !== "undefined" && editor?.component?.option) ? editor.component.option("editing.form") : null);',
|
||||
)
|
||||
}
|
||||
|
||||
if (needsFieldState) {
|
||||
lines.push(
|
||||
' const setFieldState = (field, prop, flag) => { if (!field) return; if (prop === "visible") { form?.itemOption?.(field, "visible", flag); return; } form?.getEditor?.(field)?.option(prop, flag); const item = form?.itemOption?.(field) || {}; form?.itemOption?.(field, "editorOptions", { ...(item.editorOptions || {}), [prop]: flag }); };',
|
||||
)
|
||||
}
|
||||
|
||||
if (needsFieldStyle) {
|
||||
lines.push(
|
||||
' const setFieldStyle = (field, prop, value) => { const editorInstance = form?.getEditor?.(field); const element = editorInstance?.element?.(); const node = element?.get ? element.get(0) : element; if (node?.style) node.style[prop] = value || ""; const input = node?.querySelector?.("input, textarea, .dx-texteditor-input"); if (input?.style) input.style[prop] = value || ""; };',
|
||||
)
|
||||
}
|
||||
|
||||
activeCopyMappings.forEach((mapping) => {
|
||||
const source = mapping.source.includes('.')
|
||||
? `getByPath(selectedItem, ${quote(mapping.source)})`
|
||||
|
|
@ -421,30 +347,6 @@ function buildScript({
|
|||
)
|
||||
}
|
||||
|
||||
if (action.actionType === 'copySelected' && action.targetField && action.selectedColumn) {
|
||||
lines.push(
|
||||
` next[${quote(action.targetField)}] = selectedItem ? getByPath(selectedItem, ${quote(
|
||||
action.selectedColumn,
|
||||
)}) : next[${quote(action.targetField)}];`,
|
||||
)
|
||||
}
|
||||
|
||||
if (action.actionType === 'setFieldState' && action.targetField) {
|
||||
lines.push(
|
||||
` setFieldState(${quote(action.targetField)}, ${quote(action.targetProperty)}, ${buildValueExpression(
|
||||
action.textValue || 'true',
|
||||
)});`,
|
||||
)
|
||||
}
|
||||
|
||||
if (action.actionType === 'setFieldStyle' && action.targetField && action.textValue) {
|
||||
lines.push(
|
||||
` setFieldStyle(${quote(action.targetField)}, ${quote(
|
||||
action.targetProperty,
|
||||
)}, renderTemplate(${quote(action.textValue)}));`,
|
||||
)
|
||||
}
|
||||
|
||||
if (action.actionType === 'apiToField' && action.apiUrl && action.targetField) {
|
||||
const method = action.apiMethod || 'GET'
|
||||
lines.push(` const apiUrl = renderTemplate(${quote(action.apiUrl)});`)
|
||||
|
|
@ -476,7 +378,11 @@ function buildScript({
|
|||
}
|
||||
|
||||
if (action.actionType === 'confirm' && action.message) {
|
||||
lines.push(` if (!confirm(renderTemplate(${quote(action.message)}))) return;`)
|
||||
lines.push(
|
||||
` if (!confirm(renderTemplate(${quote(
|
||||
action.message,
|
||||
)}))) { rollbackCurrentValue(); return; }`,
|
||||
)
|
||||
}
|
||||
|
||||
if (action.actionType === 'calculate' && action.targetField && action.formula) {
|
||||
|
|
@ -494,33 +400,10 @@ function buildScript({
|
|||
lines.push(' setFormData(next);')
|
||||
}
|
||||
|
||||
if (hasToggleRules) {
|
||||
activeToggleRules.forEach((rule) => {
|
||||
const condition = buildCondition(rule)
|
||||
lines.push(` {`)
|
||||
lines.push(` const flag = (${condition}) ? ${rule.whenTrue} : ${!rule.whenTrue};`)
|
||||
if (rule.property === 'visible') {
|
||||
lines.push(` setFieldState(${quote(rule.target)}, 'visible', flag);`)
|
||||
} else {
|
||||
lines.push(` setFieldState(${quote(rule.target)}, ${quote(rule.property)}, flag);`)
|
||||
}
|
||||
lines.push(` }`)
|
||||
})
|
||||
}
|
||||
|
||||
if (serviceCall.trim()) {
|
||||
lines.push(` ${serviceCall.trim().replace(/;?$/, ';')}`)
|
||||
}
|
||||
|
||||
if (customScript.trim()) {
|
||||
lines.push(' // Custom script')
|
||||
customScript
|
||||
.split('\n')
|
||||
.map((line) => line.trimEnd())
|
||||
.filter(Boolean)
|
||||
.forEach((line) => lines.push(` ${line}`))
|
||||
}
|
||||
|
||||
lines.push('})();')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
|
@ -535,7 +418,6 @@ function EditorScriptBuilderDialog({
|
|||
}: EditorScriptBuilderDialogProps) {
|
||||
const availableFields = useMemo(() => fieldOptions(fields), [fields])
|
||||
const [copyMappings, setCopyMappings] = useState<CopyMapping[]>([])
|
||||
const [toggleRules, setToggleRules] = useState<ToggleRule[]>([])
|
||||
const [daysStartField, setDaysStartField] = useState('')
|
||||
const [daysEndField, setDaysEndField] = useState('')
|
||||
const [daysTargetField, setDaysTargetField] = useState('')
|
||||
|
|
@ -551,13 +433,12 @@ function EditorScriptBuilderDialog({
|
|||
const [timeTargetField, setTimeTargetField] = useState('')
|
||||
const [conditionalActions, setConditionalActions] = useState<ConditionalAction[]>([])
|
||||
const [serviceCall, setServiceCall] = useState('')
|
||||
const [customScript, setCustomScript] = useState('')
|
||||
const { translate } = useLocalization()
|
||||
const [scriptEditorValue, setScriptEditorValue] = useState('')
|
||||
const [lastGeneratedScript, setLastGeneratedScript] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
setCopyMappings([])
|
||||
setToggleRules([])
|
||||
setDaysStartField('')
|
||||
setDaysEndField('')
|
||||
setDaysTargetField('')
|
||||
|
|
@ -573,7 +454,8 @@ function EditorScriptBuilderDialog({
|
|||
setTimeTargetField('')
|
||||
setConditionalActions([])
|
||||
setServiceCall('')
|
||||
setCustomScript('')
|
||||
setScriptEditorValue(value?.trim() || '')
|
||||
setLastGeneratedScript('')
|
||||
}, [isOpen, value])
|
||||
|
||||
const findField = (fieldName: string) =>
|
||||
|
|
@ -592,12 +474,6 @@ function EditorScriptBuilderDialog({
|
|||
setTimeTargetField((current) => current || findField('TotalHours'))
|
||||
}
|
||||
|
||||
const fillDateDefaults = () => {
|
||||
setDaysStartField((current) => current || findField('StartDate'))
|
||||
setDaysEndField((current) => current || findField('EndDate'))
|
||||
setDaysTargetField((current) => current || findField('TotalDays'))
|
||||
}
|
||||
|
||||
const hydrateKnownScript = (script?: string) => {
|
||||
const source = script?.trim()
|
||||
if (!source) return false
|
||||
|
|
@ -649,21 +525,20 @@ function EditorScriptBuilderDialog({
|
|||
if (!isOpen) return
|
||||
const existingScript = value?.trim()
|
||||
if (!existingScript) {
|
||||
setCustomScript('')
|
||||
setScriptEditorValue('')
|
||||
return
|
||||
}
|
||||
|
||||
const hydrated = hydrateKnownScript(existingScript)
|
||||
setCustomScript(hydrated ? '' : existingScript)
|
||||
setScriptEditorValue(hydrated ? '' : existingScript)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [availableFields, isOpen, value])
|
||||
|
||||
const preview = useMemo(
|
||||
const generatedScript = useMemo(
|
||||
() =>
|
||||
buildScript({
|
||||
currentField,
|
||||
copyMappings,
|
||||
toggleRules,
|
||||
daysStartField,
|
||||
daysEndField,
|
||||
daysTargetField,
|
||||
|
|
@ -679,7 +554,6 @@ function EditorScriptBuilderDialog({
|
|||
timeTargetField,
|
||||
conditionalActions,
|
||||
serviceCall,
|
||||
customScript,
|
||||
}),
|
||||
[
|
||||
amountQuantityField,
|
||||
|
|
@ -689,7 +563,6 @@ function EditorScriptBuilderDialog({
|
|||
conditionalActions,
|
||||
copyMappings,
|
||||
currentField,
|
||||
customScript,
|
||||
daysEndField,
|
||||
daysStartField,
|
||||
daysTargetField,
|
||||
|
|
@ -700,10 +573,18 @@ function EditorScriptBuilderDialog({
|
|||
timeEndField,
|
||||
timeStartField,
|
||||
timeTargetField,
|
||||
toggleRules,
|
||||
],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
setScriptEditorValue((current) => {
|
||||
if (current === lastGeneratedScript || !current.trim()) return generatedScript
|
||||
return current
|
||||
})
|
||||
setLastGeneratedScript(generatedScript)
|
||||
}, [generatedScript, isOpen, lastGeneratedScript])
|
||||
|
||||
const renderFieldSelect = (value: string, onChange: (value: string) => void) => (
|
||||
<select
|
||||
className={baseInputClass}
|
||||
|
|
@ -737,7 +618,7 @@ function EditorScriptBuilderDialog({
|
|||
</label>
|
||||
)
|
||||
|
||||
const needsCompareValue = (operator: ConditionalAction['operator'] | ToggleRule['operator']) =>
|
||||
const needsCompareValue = (operator: ConditionalAction['operator']) =>
|
||||
operator === 'equals' ||
|
||||
operator === 'notEquals' ||
|
||||
operator === 'contains' ||
|
||||
|
|
@ -762,19 +643,6 @@ function EditorScriptBuilderDialog({
|
|||
if (action.actionType === 'setField') {
|
||||
return `${condition}, ${target} alanına ${action.textValue || 'değer'} yaz.`
|
||||
}
|
||||
if (action.actionType === 'copySelected') {
|
||||
return `${condition}, seçili kayıttaki ${action.selectedColumn || 'path'} değerini ${target} alanına yaz.`
|
||||
}
|
||||
if (action.actionType === 'setFieldState') {
|
||||
return `${condition}, ${target} alanında ${action.targetProperty} = ${
|
||||
action.textValue || 'true'
|
||||
} yap.`
|
||||
}
|
||||
if (action.actionType === 'setFieldStyle') {
|
||||
return `${condition}, ${target} alanının ${action.targetProperty} stilini ${
|
||||
action.textValue || 'değer'
|
||||
} yap.`
|
||||
}
|
||||
if (action.actionType === 'apiToField') {
|
||||
return `${condition}, ${action.apiMethod} ${action.apiUrl || 'api url'} çağır ve sonucu ${target} alanına yaz.`
|
||||
}
|
||||
|
|
@ -809,16 +677,12 @@ function EditorScriptBuilderDialog({
|
|||
<h5
|
||||
title={`${currentField || 'Geçerli alan'} değiştiğinde çalışacak editorScript üretir. Basit kopyalama ve hazır hesaplamalar için ilk bölümleri, API/URL/alert/özel koşul gibi esnek davranışlar için Koşullu Aksiyon bölümünü kullan.${
|
||||
value?.trim()
|
||||
? ' Mevcut editorScript otomatik olarak özel script alanında korunur. Yeni bir builder seçeneği eklemezsen Uygula aynı scripti geri yazar.'
|
||||
? ' Mevcut editorScript sağdaki TypeScript editor içinde korunur. Yeni bir builder seçeneği eklemezsen Uygula aynı scripti geri yazar.'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
Editor Script Builder
|
||||
</h5>
|
||||
<FaBolt
|
||||
className="text-gray-400"
|
||||
title="Bu dialog editorScript alanını seçimlerle oluşturmak için kullanılır."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-0 grid-cols-1 gap-4 overflow-y-auto pr-1 lg:grid-cols-2">
|
||||
|
|
@ -1005,24 +869,18 @@ function EditorScriptBuilderDialog({
|
|||
}
|
||||
>
|
||||
<option value="setField">Field değerini yaz</option>
|
||||
<option value="copySelected">Seçili kayıttan kopyala</option>
|
||||
<option value="setFieldState">Field durumunu değiştir</option>
|
||||
<option value="setFieldStyle">Field stilini değiştir</option>
|
||||
<option value="apiToField">API sonucunu field'a yaz</option>
|
||||
<option value="openUrl">URL aç</option>
|
||||
<option value="alert">Uyarı göster</option>
|
||||
<option value="confirm">Onay iste</option>
|
||||
<option value="calculate">Formül hesapla</option>
|
||||
<option value="apiToField">API sonucunu field'a yaz</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
{[
|
||||
'setField',
|
||||
'copySelected',
|
||||
'apiToField',
|
||||
'calculate',
|
||||
'setFieldState',
|
||||
'setFieldStyle',
|
||||
].includes(action.actionType) && (
|
||||
<div className="col-span-12 md:col-span-4">
|
||||
{renderLabeledFieldSelect('Hedef field', action.targetField, (value) =>
|
||||
|
|
@ -1043,80 +901,6 @@ function EditorScriptBuilderDialog({
|
|||
/>
|
||||
)}
|
||||
|
||||
{action.actionType === 'copySelected' && (
|
||||
<input
|
||||
className={`${baseInputClass} col-span-4`}
|
||||
value={action.selectedColumn}
|
||||
onChange={(event) =>
|
||||
updateConditionalAction(action.id, {
|
||||
selectedColumn: event.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="selected item path"
|
||||
title="Seçilen lookup kaydından okunacak path. Örnek: UomId veya Customer.Name"
|
||||
/>
|
||||
)}
|
||||
|
||||
{action.actionType === 'setFieldState' && (
|
||||
<>
|
||||
<select
|
||||
className={`${baseInputClass} col-span-2`}
|
||||
value={action.targetProperty}
|
||||
title="Hedef field'ın değiştirilecek özelliği."
|
||||
onChange={(event) =>
|
||||
updateConditionalAction(action.id, {
|
||||
targetProperty: event.target
|
||||
.value as ConditionalAction['targetProperty'],
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="disabled">disabled</option>
|
||||
<option value="readOnly">readOnly</option>
|
||||
<option value="visible">visible</option>
|
||||
</select>
|
||||
<select
|
||||
className={`${baseInputClass} col-span-2`}
|
||||
value={action.textValue || 'true'}
|
||||
title="Koşul sağlanınca property true mu false mu olsun?"
|
||||
onChange={(event) =>
|
||||
updateConditionalAction(action.id, { textValue: event.target.value })
|
||||
}
|
||||
>
|
||||
<option value="true">true</option>
|
||||
<option value="false">false</option>
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
|
||||
{action.actionType === 'setFieldStyle' && (
|
||||
<>
|
||||
<select
|
||||
className={`${baseInputClass} col-span-2`}
|
||||
value={action.targetProperty}
|
||||
title="Koşul sağlandığında hedef field üzerinde uygulanacak CSS style property'si. Örnek: color, backgroundColor, borderColor."
|
||||
onChange={(event) =>
|
||||
updateConditionalAction(action.id, {
|
||||
targetProperty: event.target
|
||||
.value as ConditionalAction['targetProperty'],
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="color">color</option>
|
||||
<option value="backgroundColor">backgroundColor</option>
|
||||
<option value="borderColor">borderColor</option>
|
||||
</select>
|
||||
<input
|
||||
className={`${baseInputClass} col-span-2`}
|
||||
value={action.textValue}
|
||||
onChange={(event) =>
|
||||
updateConditionalAction(action.id, { textValue: event.target.value })
|
||||
}
|
||||
placeholder="#ef4444"
|
||||
title="CSS renk değeri yaz. Örnek: red, #ef4444, rgb(239,68,68). Token da kullanabilirsin: {ColorCode}"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{action.actionType === 'apiToField' && (
|
||||
<>
|
||||
<select
|
||||
|
|
@ -1212,222 +996,19 @@ function EditorScriptBuilderDialog({
|
|||
</div>
|
||||
|
||||
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
|
||||
<div className="flex items-center justify-between gap-2 mb-3">
|
||||
<div>
|
||||
<div
|
||||
className="text-sm font-semibold"
|
||||
title="Bir kaynak alan belirli bir koşulu sağladığında hedef alanı disabled/readOnly/visible yapar. Örnek: PhoneNumber 1000 ise Surname disabled true."
|
||||
>
|
||||
3. Field Durum Kuralları
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
icon={<FaPlus />}
|
||||
title="Basit field state kuralı ekler. Örnek: kaynak alan boşsa hedef alan disabled true olsun."
|
||||
onClick={() =>
|
||||
setToggleRules((current) => [
|
||||
...current,
|
||||
{
|
||||
id: makeId(),
|
||||
target: '',
|
||||
property: 'disabled',
|
||||
source: currentField || '',
|
||||
operator: 'empty',
|
||||
value: '',
|
||||
whenTrue: true,
|
||||
},
|
||||
])
|
||||
}
|
||||
>
|
||||
Ekle
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{toggleRules.map((rule) => (
|
||||
<div
|
||||
key={rule.id}
|
||||
className="rounded border border-gray-100 dark:border-gray-700 p-3"
|
||||
>
|
||||
<div className="mb-3 rounded bg-gray-50 px-2 py-1.5 text-xs text-gray-600 dark:bg-gray-900 dark:text-gray-300">
|
||||
Eğer{' '}
|
||||
<span className="font-semibold">
|
||||
{fieldLabel(rule.source, 'kaynak field')}
|
||||
</span>{' '}
|
||||
<span className="font-semibold">{rule.operator}</span>{' '}
|
||||
{rule.operator === 'equals' || rule.operator === 'notEquals' ? (
|
||||
<span className="font-semibold">{rule.value || 'değer'}</span>
|
||||
) : null}{' '}
|
||||
ise{' '}
|
||||
<span className="font-semibold">
|
||||
{fieldLabel(rule.target, 'hedef field')}
|
||||
</span>{' '}
|
||||
<span className="font-semibold">{rule.property}</span> ={' '}
|
||||
<span className="font-semibold">{String(rule.whenTrue)}</span> olur.
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-2">
|
||||
<div className="col-span-12 md:col-span-4">
|
||||
{renderLabeledFieldSelect(
|
||||
'O zaman: hedef field',
|
||||
rule.target,
|
||||
(nextValue) =>
|
||||
setToggleRules((current) =>
|
||||
current.map((item) =>
|
||||
item.id === rule.id ? { ...item, target: nextValue } : item,
|
||||
),
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
<select
|
||||
className={`${baseInputClass} col-span-12 self-end md:col-span-3`}
|
||||
value={rule.property}
|
||||
title="Hedef alanın hangi özelliği değişecek?"
|
||||
onChange={(event) =>
|
||||
setToggleRules((current) =>
|
||||
current.map((item) =>
|
||||
item.id === rule.id
|
||||
? {
|
||||
...item,
|
||||
property: event.target.value as ToggleRule['property'],
|
||||
}
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="disabled">disabled</option>
|
||||
<option value="readOnly">readOnly</option>
|
||||
<option value="visible">visible</option>
|
||||
</select>
|
||||
<div className="col-span-12 md:col-span-4">
|
||||
{renderLabeledFieldSelect('Eğer: kaynak field', rule.source, (nextValue) =>
|
||||
setToggleRules((current) =>
|
||||
current.map((item) =>
|
||||
item.id === rule.id ? { ...item, source: nextValue } : item,
|
||||
),
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
shape="circle"
|
||||
variant="plain"
|
||||
icon={<FaTrash />}
|
||||
onClick={() =>
|
||||
setToggleRules((current) => current.filter((item) => item.id !== rule.id))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-12 gap-2 mt-2">
|
||||
<select
|
||||
className={`${baseInputClass} col-span-4`}
|
||||
value={rule.operator}
|
||||
title="Kaynak field için koşul operatörü."
|
||||
onChange={(event) =>
|
||||
setToggleRules((current) =>
|
||||
current.map((item) =>
|
||||
item.id === rule.id
|
||||
? {
|
||||
...item,
|
||||
operator: event.target.value as ToggleRule['operator'],
|
||||
}
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="empty">empty</option>
|
||||
<option value="notEmpty">notEmpty</option>
|
||||
<option value="equals">equals</option>
|
||||
<option value="notEquals">notEquals</option>
|
||||
</select>
|
||||
<input
|
||||
className={`${baseInputClass} col-span-12 md:col-span-4`}
|
||||
value={rule.value}
|
||||
disabled={rule.operator === 'empty' || rule.operator === 'notEmpty'}
|
||||
onChange={(event) =>
|
||||
setToggleRules((current) =>
|
||||
current.map((item) =>
|
||||
item.id === rule.id ? { ...item, value: event.target.value } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
placeholder="Koşul değeri"
|
||||
title="Eşitse/Eşit değilse durumunda karşılaştırılacak değer."
|
||||
/>
|
||||
<select
|
||||
className={`${baseInputClass} col-span-12 md:col-span-4`}
|
||||
value={String(rule.whenTrue)}
|
||||
title="Koşul doğruysa hedef property hangi değeri alsın?"
|
||||
onChange={(event) =>
|
||||
setToggleRules((current) =>
|
||||
current.map((item) =>
|
||||
item.id === rule.id
|
||||
? { ...item, whenTrue: event.target.value === 'true' }
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="true">true</option>
|
||||
<option value="false">false</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
|
||||
<div
|
||||
className="mb-3 text-sm font-semibold"
|
||||
className="mb-2 text-sm font-semibold"
|
||||
title="Start/End tarih alanlarından gün farkını hesaplayıp hedef field'a yazar. Örnek: StartDate + EndDate -> TotalDays."
|
||||
>
|
||||
4. Tarih Farkı
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={fillDateDefaults}
|
||||
title="Eski kullanım: StartDate ve EndDate değişince TotalDays alanını gün farkı olarak hesapla."
|
||||
>
|
||||
StartDate / EndDate / TotalDays
|
||||
</Button>
|
||||
3. Tarih ve Saat Farkı
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-2">
|
||||
{renderLabeledFieldSelect('Başlangıç tarihi', daysStartField, setDaysStartField)}
|
||||
{renderLabeledFieldSelect('Bitiş tarihi', daysEndField, setDaysEndField)}
|
||||
{renderLabeledFieldSelect('Sonuç alanı', daysTargetField, setDaysTargetField)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
|
||||
<div className="flex items-center justify-between gap-2 mb-3">
|
||||
<div>
|
||||
<div
|
||||
className="text-sm font-semibold"
|
||||
title="Başlangıç ve bitiş saatinden saat farkını hesaplar. Bitiş başlangıçtan küçükse gece devri kabul edip 24 saat ekler."
|
||||
>
|
||||
5. Saat Farkı
|
||||
</div>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 rounded border border-gray-100 px-2 py-1.5 text-sm dark:border-gray-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={timeDiffEnabled}
|
||||
onChange={(event) => {
|
||||
setTimeDiffEnabled(event.target.checked)
|
||||
fillTimeDefaults()
|
||||
}}
|
||||
/>
|
||||
<span title="Eski kullanım: StartTime ve EndTime farkını hesapla; sonuç negatifse 24 saat ekleyip TotalHours alanına yaz.">
|
||||
Aktif
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-2">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 pt-2">
|
||||
{renderLabeledFieldSelect('Başlangıç saati', timeStartField, setTimeStartField)}
|
||||
{renderLabeledFieldSelect('Bitiş saati', timeEndField, setTimeEndField)}
|
||||
{renderLabeledFieldSelect('Sonuç alanı', timeTargetField, setTimeTargetField)}
|
||||
|
|
@ -1439,24 +1020,10 @@ function EditorScriptBuilderDialog({
|
|||
className="mb-3 text-sm font-semibold"
|
||||
title="Quantity ve UnitPrice ile TotalAmount hesaplar. Seçili kayıttan hesapla lookup kaydından değerleri alır; satır toplamını yenile mevcut form değerlerinden hesaplar."
|
||||
>
|
||||
6. Tutar Hesaplamaları
|
||||
5. Tutar Hesaplamaları
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-2 mb-3 md:grid-cols-2">
|
||||
<label className="flex items-start gap-2 rounded border border-gray-100 dark:border-gray-700 px-2 py-2 text-sm">
|
||||
<input
|
||||
className="mt-1"
|
||||
type="checkbox"
|
||||
checked={selectedItemAmountEnabled}
|
||||
onChange={(event) => {
|
||||
setSelectedItemAmountEnabled(event.target.checked)
|
||||
fillAmountDefaults()
|
||||
}}
|
||||
/>
|
||||
<span title="Seçilen lookup/grid kaydından miktar, birim fiyat ve varsa birim bilgisini alır; toplamı bu değerlerle hesaplar.">
|
||||
Seçili kayıttan miktar/fiyat al
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-start gap-2 rounded border border-gray-100 dark:border-gray-700 px-2 py-2 text-sm">
|
||||
<label className="flex items-start gap-2 text-sm">
|
||||
<input
|
||||
className="mt-1"
|
||||
type="checkbox"
|
||||
|
|
@ -1490,9 +1057,9 @@ function EditorScriptBuilderDialog({
|
|||
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
|
||||
<div
|
||||
className="mb-3 text-sm font-semibold"
|
||||
title="Hâlâ karşılanmayan özel bir durum varsa burayı kullan. Servis çağrısı tek satırlık global servis çağrıları için, özel script ileri seviye serbest kod içindir."
|
||||
title="Tek satırlık global servis çağrıları için kullan. Daha detaylı kodu sağdaki TypeScript script editor alanında düzenleyebilirsin."
|
||||
>
|
||||
7. Servis Çağrısı ve Özel Script
|
||||
6. Servis Çağrısı
|
||||
</div>
|
||||
<input
|
||||
className={baseInputClass}
|
||||
|
|
@ -1501,27 +1068,39 @@ function EditorScriptBuilderDialog({
|
|||
placeholder="UiEvalService.ApiGenerateBackgroundWorkers();"
|
||||
title="Global servis çağrısı. Örnek: UiEvalService.ApiGenerateBackgroundWorkers();"
|
||||
/>
|
||||
<textarea
|
||||
className="w-full min-h-[110px] mt-2 px-2 py-2 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm font-mono text-gray-700 dark:text-gray-100 focus:outline-none focus:border-indigo-400"
|
||||
value={customScript}
|
||||
onChange={(event) => setCustomScript(event.target.value)}
|
||||
placeholder="İleri seviye özel script"
|
||||
title="İleri seviye serbest JavaScript. Mümkünse önce Koşullu Aksiyon kullan."
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded border border-gray-200 dark:border-gray-700 p-3 flex flex-col min-h-[520px]">
|
||||
<div
|
||||
className="flex items-center gap-2 text-sm font-semibold mb-3"
|
||||
title="Boş editorScript ve seçim yoksa preview boş kalır. Seçim yaptıkça editorScript alanına yazılacak kod burada oluşur."
|
||||
className="flex items-center justify-between gap-2 text-sm font-semibold mb-3"
|
||||
title="Seçim yaptıkça editorScript alanına yazılacak kod burada oluşur. Oluşan TypeScript kodunu kaydetmeden önce düzenleyebilirsin."
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<FaCode />
|
||||
Script Önizleme
|
||||
</span>
|
||||
<span className="rounded bg-gray-100 px-2 py-1 text-[11px] font-medium text-gray-500 dark:bg-gray-800 dark:text-gray-300">
|
||||
TypeScript
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 min-h-[430px] overflow-hidden rounded border border-gray-200 dark:border-gray-700">
|
||||
<Editor
|
||||
height="100%"
|
||||
language="typescript"
|
||||
theme="vs-dark"
|
||||
value={scriptEditorValue}
|
||||
onChange={(nextValue) => setScriptEditorValue(nextValue || '')}
|
||||
options={{
|
||||
automaticLayout: true,
|
||||
fontSize: 12,
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
tabSize: 2,
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<pre className="flex-1 overflow-auto rounded bg-gray-50 dark:bg-gray-900 p-3 text-xs whitespace-pre-wrap">
|
||||
{preview || 'Seçim yaptıkça script burada oluşacak.'}
|
||||
</pre>
|
||||
</section>
|
||||
</div>
|
||||
</Dialog.Body>
|
||||
|
|
@ -1535,7 +1114,7 @@ function EditorScriptBuilderDialog({
|
|||
variant="solid"
|
||||
icon={<FaCheck />}
|
||||
onClick={() => {
|
||||
onApply(preview)
|
||||
onApply(scriptEditorValue)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -220,12 +220,7 @@ function JsonRowOpDialogEditForm({
|
|||
invalid={errors.itemType && touched.itemType}
|
||||
errorMessage={errors.itemType}
|
||||
>
|
||||
<Field
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
name="itemType"
|
||||
placeholder="Item Type"
|
||||
>
|
||||
<Field type="text" autoComplete="off" name="itemType" placeholder="Item Type">
|
||||
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||||
<Select
|
||||
field={field}
|
||||
|
|
@ -305,6 +300,16 @@ function JsonRowOpDialogEditForm({
|
|||
<pre>
|
||||
{`• (() => { const d=v=>!v?null:(v instanceof Date?v:new Date(v));const nf={...formData,[editor.dataField]:e?.value};const s=d(nf.StartDate),t=d(nf.EndDate);setFormData({...formData,TotalDays: s&&t?Math.max(0,Math.floor((Date.UTC(t.getFullYear(),t.getMonth(),t.getDate())-Date.UTC(s.getFullYear(),s.getMonth(),s.getDate()))/(24*60*60*1000))+1):null});})(); `}
|
||||
</pre>
|
||||
<pre>
|
||||
{
|
||||
"• (() => { const p=e.component.option('selectedItem')||{}; const q=Math.round((parseFloat(p.Quantity)||0)*100); const u=Math.round((parseFloat(p.UnitPrice)||0)*100); setFormData({ ...formData, Quantity:q/100, UnitPrice:u/100, UomId:p.UomId, TotalAmount:Math.round((q*u)/100)/100 }); })();"
|
||||
}
|
||||
</pre>
|
||||
<pre>
|
||||
{
|
||||
'• (() => { const n={...formData,[e.dataField]:e.value}; const q=Math.round((parseFloat(n.Quantity)||0)*100); const u=Math.round((parseFloat(n.UnitPrice)||0)*100); setFormData({ ...n, TotalAmount:Math.round((q*u)/100)/100 }); })();'
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -531,9 +536,7 @@ function JsonRowOpDialogEditForm({
|
|||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={translate(
|
||||
'::ListForms.ListFormEdit.TabColumns',
|
||||
)}
|
||||
label={translate('::ListForms.ListFormEdit.TabColumns')}
|
||||
>
|
||||
<Field
|
||||
type="text"
|
||||
|
|
@ -775,10 +778,7 @@ function JsonRowOpDialogEditForm({
|
|||
</div>
|
||||
</div>
|
||||
<div className="w-1/12 ml-2 align-middle text-center">
|
||||
<Field
|
||||
name={`items.${index}.isRequired`}
|
||||
component={Checkbox}
|
||||
/>
|
||||
<Field name={`items.${index}.isRequired`} component={Checkbox} />
|
||||
</div>
|
||||
<div className="w-1/12 ml-2">
|
||||
<Field
|
||||
|
|
|
|||
|
|
@ -1089,6 +1089,16 @@ const WizardStep3 = ({
|
|||
<pre>
|
||||
{`• (() => { const d=v=>!v?null:(v instanceof Date?v:new Date(v));const nf={...formData,[editor.dataField]:e?.value};const s=d(nf.StartDate),t=d(nf.EndDate);setFormData({...formData,TotalDays: s&&t?Math.max(0,Math.floor((Date.UTC(t.getFullYear(),t.getMonth(),t.getDate())-Date.UTC(s.getFullYear(),s.getMonth(),s.getDate()))/(24*60*60*1000))+1):null});})(); `}
|
||||
</pre>
|
||||
<pre>
|
||||
{
|
||||
"• (() => { const p=e.component.option('selectedItem')||{}; const q=Math.round((parseFloat(p.Quantity)||0)*100); const u=Math.round((parseFloat(p.UnitPrice)||0)*100); setFormData({ ...formData, Quantity:q/100, UnitPrice:u/100, UomId:p.UomId, TotalAmount:Math.round((q*u)/100)/100 }); })();"
|
||||
}
|
||||
</pre>
|
||||
<pre>
|
||||
{
|
||||
'• (() => { const n={...formData,[e.dataField]:e.value}; const q=Math.round((parseFloat(n.Quantity)||0)*100); const u=Math.round((parseFloat(n.UnitPrice)||0)*100); setFormData({ ...n, TotalAmount:Math.round((q*u)/100)/100 }); })();'
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ const Grid = (props: GridProps) => {
|
|||
const gridRef = useRef<DataGridRef>()
|
||||
const refListFormCode = useRef('')
|
||||
const widgetGroupRef = useRef<HTMLDivElement>(null)
|
||||
const editingFormDataRef = useRef<Record<string, any>>({})
|
||||
// Edit popup state kaydetmeyi engellemek için flag
|
||||
const isEditingRef = useRef(false)
|
||||
|
||||
|
|
@ -347,6 +348,7 @@ const Grid = (props: GridProps) => {
|
|||
const onInitNewRow = useCallback(
|
||||
(e: any) => {
|
||||
if (!gridDto?.columnFormats) {
|
||||
editingFormDataRef.current = { ...(e.data || {}) }
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -436,6 +438,8 @@ const Grid = (props: GridProps) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
editingFormDataRef.current = { ...(e.data || {}) }
|
||||
})()
|
||||
},
|
||||
[gridDto, searchParams, extraFilters, getNextSequenceValue],
|
||||
|
|
@ -486,6 +490,7 @@ const Grid = (props: GridProps) => {
|
|||
const onEditingStart = useCallback(
|
||||
(e: DataGridTypes.EditingStartEvent<any, any>) => {
|
||||
isEditingRef.current = true
|
||||
editingFormDataRef.current = { ...(e.data || {}) }
|
||||
setMode('edit')
|
||||
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
||||
const columns = e.component.option('columns') as GridColumnData[]
|
||||
|
|
@ -1371,11 +1376,31 @@ const Grid = (props: GridProps) => {
|
|||
colCount: 1,
|
||||
onFieldDataChanged: (e) => {
|
||||
if (e.dataField) {
|
||||
const previousValue = editingFormDataRef.current?.[e.dataField]
|
||||
editingFormDataRef.current = {
|
||||
...(e.component?.option?.('formData') || {}),
|
||||
}
|
||||
|
||||
const formItem = gridDto.gridOptions.editingFormDto
|
||||
.flatMap((group) => group.items || [])
|
||||
.find((i) => i.dataField === e.dataField)
|
||||
if (formItem?.editorScript) {
|
||||
try {
|
||||
const grid = gridRef.current?.instance()
|
||||
const rowKey = grid?.option('editing.editRowKey')
|
||||
const rowIndex =
|
||||
rowKey !== undefined ? grid?.getRowIndexByKey(rowKey) : -1
|
||||
const formData = e.component?.option?.('formData') || {}
|
||||
const setFormData = (newData: any) => {
|
||||
e.component?.option?.('formData', newData)
|
||||
editingFormDataRef.current = { ...newData }
|
||||
if (grid && rowIndex !== undefined && rowIndex >= 0) {
|
||||
Object.keys(newData).forEach((field) => {
|
||||
grid.cellValue(rowIndex, field, newData[field])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
eval(formItem.editorScript)
|
||||
} catch (err) {
|
||||
console.error('Script exec error', err)
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ const Tree = (props: TreeProps) => {
|
|||
const gridRef = useRef<TreeListRef>()
|
||||
const refListFormCode = useRef('')
|
||||
const widgetGroupRef = useRef<HTMLDivElement>(null)
|
||||
const editingFormDataRef = useRef<Record<string, any>>({})
|
||||
// Edit popup state kaydetmeyi engellemek için flag
|
||||
const isEditingRef = useRef(false)
|
||||
|
||||
|
|
@ -381,6 +382,7 @@ const Tree = (props: TreeProps) => {
|
|||
|
||||
function onInitNewRow(e: any) {
|
||||
if (!gridDto?.columnFormats) {
|
||||
editingFormDataRef.current = { ...(e.data || {}) }
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -444,6 +446,8 @@ const Tree = (props: TreeProps) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
editingFormDataRef.current = { ...(e.data || {}) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -483,6 +487,7 @@ const Tree = (props: TreeProps) => {
|
|||
const onEditingStart = useCallback(
|
||||
(e: TreeListTypes.EditingStartEvent) => {
|
||||
isEditingRef.current = true
|
||||
editingFormDataRef.current = { ...(e.data || {}) }
|
||||
setMode('edit')
|
||||
setIsPopupFullScreen(gridDto?.gridOptions.editingOptionDto?.popup?.fullScreen ?? false)
|
||||
const columns = e.component.option('columns') as GridColumnData[]
|
||||
|
|
@ -1029,11 +1034,31 @@ const Tree = (props: TreeProps) => {
|
|||
colCount: 1,
|
||||
onFieldDataChanged: (e) => {
|
||||
if (e.dataField) {
|
||||
const previousValue = editingFormDataRef.current?.[e.dataField]
|
||||
editingFormDataRef.current = {
|
||||
...(e.component?.option?.('formData') || {}),
|
||||
}
|
||||
|
||||
const formItem = gridDto.gridOptions.editingFormDto
|
||||
.flatMap((group) => group.items || [])
|
||||
.find((i) => i.dataField === e.dataField)
|
||||
if (formItem?.editorScript) {
|
||||
try {
|
||||
const grid = gridRef.current?.instance()
|
||||
const rowKey = grid?.option('editing.editRowKey')
|
||||
const rowIndex =
|
||||
rowKey !== undefined ? grid?.getRowIndexByKey(rowKey) : -1
|
||||
const formData = e.component?.option?.('formData') || {}
|
||||
const setFormData = (newData: any) => {
|
||||
e.component?.option?.('formData', newData)
|
||||
editingFormDataRef.current = { ...newData }
|
||||
if (grid && rowIndex !== undefined && rowIndex >= 0) {
|
||||
Object.keys(newData).forEach((field) => {
|
||||
grid.cellValue(rowIndex, field, newData[field])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
eval(formItem.editorScript)
|
||||
} catch (err) {
|
||||
console.error('Script exec error', err)
|
||||
|
|
|
|||
Loading…
Reference in a new issue