diff --git a/ui/src/views/admin/listForm/edit/json-row-operations/EditorOptionsBuilderDialog.tsx b/ui/src/views/admin/listForm/edit/json-row-operations/EditorOptionsBuilderDialog.tsx index af36552..4717c0b 100644 --- a/ui/src/views/admin/listForm/edit/json-row-operations/EditorOptionsBuilderDialog.tsx +++ b/ui/src/views/admin/listForm/edit/json-row-operations/EditorOptionsBuilderDialog.tsx @@ -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 - {parseError && ( @@ -314,8 +307,8 @@ function EditorOptionsBuilderDialog({ )} -
-
+
+
(
-
+
{['min', 'max', 'step'].map((key) => ( ))}
+
+
+
+
+ 7. Özel Ayarlar +
+
+ +
+
+ {customOptions.map((option) => ( +
+ + 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" + /> + + + 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." + /> +
+ ))} +
+
+
+ +
- 7. Hazır Ayarlar + Hazır Ayarlar
-
-
-
-
- 8. Özel Ayarlar -
-
- -
-
- {customOptions.map((option) => ( -
- - 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" - /> - - - 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." - /> -
- ))} -
-
-
- -
JSON Önizleme
-
+            
               {preview || '{}'}
             
diff --git a/ui/src/views/admin/listForm/edit/json-row-operations/EditorScriptBuilderDialog.tsx b/ui/src/views/admin/listForm/edit/json-row-operations/EditorScriptBuilderDialog.tsx index 87c403f..06f659a 100644 --- a/ui/src/views/admin/listForm/edit/json-row-operations/EditorScriptBuilderDialog.tsx +++ b/ui/src/views/admin/listForm/edit/json-row-operations/EditorScriptBuilderDialog.tsx @@ -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([]) - const [toggleRules, setToggleRules] = useState([]) 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([]) 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) => ( {[ 'setField', - 'copySelected', 'apiToField', 'calculate', - 'setFieldState', - 'setFieldStyle', ].includes(action.actionType) && (
{renderLabeledFieldSelect('Hedef field', action.targetField, (value) => @@ -1043,80 +901,6 @@ function EditorScriptBuilderDialog({ /> )} - {action.actionType === 'copySelected' && ( - - 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' && ( - <> - - - - )} - - {action.actionType === 'setFieldStyle' && ( - <> - - - 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' && ( <> - setToggleRules((current) => - current.map((item) => - item.id === rule.id - ? { - ...item, - property: event.target.value as ToggleRule['property'], - } - : item, - ), - ) - } - > - - - - -
- {renderLabeledFieldSelect('Eğer: kaynak field', rule.source, (nextValue) => - setToggleRules((current) => - current.map((item) => - item.id === rule.id ? { ...item, source: nextValue } : item, - ), - ), - )} -
-
-
- - - 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." - /> - -
-
- ))} -
- -
- 4. Tarih Farkı -
-
- + 3. Tarih ve Saat Farkı
{renderLabeledFieldSelect('Başlangıç tarihi', daysStartField, setDaysStartField)} {renderLabeledFieldSelect('Bitiş tarihi', daysEndField, setDaysEndField)} {renderLabeledFieldSelect('Sonuç alanı', daysTargetField, setDaysTargetField)}
-
-
-
-
-
- 5. Saat Farkı -
-
- -
-
+
{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ı
- -