Eksik LanguageText keyler tanımlandı
This commit is contained in:
parent
b5cba69e28
commit
f372c17249
166 changed files with 17380 additions and 8509 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,13 +1,13 @@
|
|||
import React, { useState, useRef, useCallback } from "react";
|
||||
import React, { useState, useRef, useCallback } from 'react'
|
||||
|
||||
interface SplitterProps {
|
||||
direction: "horizontal" | "vertical";
|
||||
initialSize: number;
|
||||
minSize?: number;
|
||||
maxSize?: number;
|
||||
children: [React.ReactNode, React.ReactNode];
|
||||
className?: string;
|
||||
reverse?: boolean; // İkinci panel için boyut kontrolü
|
||||
direction: 'horizontal' | 'vertical'
|
||||
initialSize: number
|
||||
minSize?: number
|
||||
maxSize?: number
|
||||
children: [React.ReactNode, React.ReactNode]
|
||||
className?: string
|
||||
reverse?: boolean // İkinci panel için boyut kontrolü
|
||||
}
|
||||
|
||||
export const Splitter: React.FC<SplitterProps> = ({
|
||||
|
|
@ -16,66 +16,66 @@ export const Splitter: React.FC<SplitterProps> = ({
|
|||
minSize = 200,
|
||||
maxSize = 800,
|
||||
children,
|
||||
className = "",
|
||||
className = '',
|
||||
reverse = false,
|
||||
}) => {
|
||||
const [size, setSize] = useState(initialSize);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const splitterRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [size, setSize] = useState(initialSize)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const splitterRef = useRef<HTMLDivElement>(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handleMouseDown = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
e.preventDefault()
|
||||
setIsDragging(true)
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!splitterRef.current || !containerRef.current) return;
|
||||
if (!splitterRef.current || !containerRef.current) return
|
||||
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
let newSize: number;
|
||||
const rect = containerRef.current.getBoundingClientRect()
|
||||
let newSize: number
|
||||
|
||||
if (direction === "horizontal") {
|
||||
if (direction === 'horizontal') {
|
||||
if (reverse) {
|
||||
// İkinci panel için boyut kontrolü - sağdan ölçüm
|
||||
newSize = rect.right - e.clientX;
|
||||
newSize = rect.right - e.clientX
|
||||
} else {
|
||||
// İlk panel için boyut kontrolü - soldan ölçüm
|
||||
newSize = e.clientX - rect.left;
|
||||
newSize = e.clientX - rect.left
|
||||
}
|
||||
} else {
|
||||
if (reverse) {
|
||||
// İkinci panel için boyut kontrolü - alttan ölçüm
|
||||
newSize = rect.bottom - e.clientY;
|
||||
newSize = rect.bottom - e.clientY
|
||||
} else {
|
||||
// İlk panel için boyut kontrolü - üstten ölçüm
|
||||
newSize = e.clientY - rect.top;
|
||||
newSize = e.clientY - rect.top
|
||||
}
|
||||
}
|
||||
|
||||
newSize = Math.max(minSize, Math.min(maxSize, newSize));
|
||||
setSize(newSize);
|
||||
};
|
||||
newSize = Math.max(minSize, Math.min(maxSize, newSize))
|
||||
setSize(newSize)
|
||||
}
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsDragging(false);
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
setIsDragging(false)
|
||||
document.removeEventListener('mousemove', handleMouseMove)
|
||||
document.removeEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
document.addEventListener('mousemove', handleMouseMove)
|
||||
document.addEventListener('mouseup', handleMouseUp)
|
||||
},
|
||||
[direction, minSize, maxSize, reverse]
|
||||
);
|
||||
[direction, minSize, maxSize, reverse],
|
||||
)
|
||||
|
||||
const isHorizontal = direction === "horizontal";
|
||||
const isHorizontal = direction === 'horizontal'
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`flex flex-1 min-h-0 ${
|
||||
isHorizontal ? "flex-row" : "flex-col"
|
||||
isHorizontal ? 'flex-row' : 'flex-col'
|
||||
} h-full w-full ${className}`}
|
||||
>
|
||||
{reverse ? (
|
||||
|
|
@ -85,20 +85,18 @@ export const Splitter: React.FC<SplitterProps> = ({
|
|||
<div
|
||||
ref={splitterRef}
|
||||
className={`
|
||||
${
|
||||
isHorizontal ? "w-1 cursor-col-resize" : "h-1 cursor-row-resize"
|
||||
}
|
||||
${isHorizontal ? 'w-1 cursor-col-resize' : 'h-1 cursor-row-resize'}
|
||||
bg-gray-300 dark:bg-gray-700 hover:bg-blue-500 dark:hover:bg-blue-600 transition-colors duration-200 flex-shrink-0
|
||||
${isDragging ? "bg-blue-500 dark:bg-blue-600" : ""}
|
||||
${isDragging ? 'bg-blue-500 dark:bg-blue-600' : ''}
|
||||
`}
|
||||
onMouseDown={handleMouseDown}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
[isHorizontal ? "width" : "height"]: `${size}px`,
|
||||
[isHorizontal ? "minWidth" : "minHeight"]: `${size}px`,
|
||||
[isHorizontal ? "maxWidth" : "maxHeight"]: `${size}px`,
|
||||
[isHorizontal ? 'width' : 'height']: `${size}px`,
|
||||
[isHorizontal ? 'minWidth' : 'minHeight']: `${size}px`,
|
||||
[isHorizontal ? 'maxWidth' : 'maxHeight']: `${size}px`,
|
||||
}}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
|
|
@ -109,9 +107,9 @@ export const Splitter: React.FC<SplitterProps> = ({
|
|||
<>
|
||||
<div
|
||||
style={{
|
||||
[isHorizontal ? "width" : "height"]: `${size}px`,
|
||||
[isHorizontal ? "minWidth" : "minHeight"]: `${size}px`,
|
||||
[isHorizontal ? "maxWidth" : "maxHeight"]: `${size}px`,
|
||||
[isHorizontal ? 'width' : 'height']: `${size}px`,
|
||||
[isHorizontal ? 'minWidth' : 'minHeight']: `${size}px`,
|
||||
[isHorizontal ? 'maxWidth' : 'maxHeight']: `${size}px`,
|
||||
}}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
|
|
@ -121,11 +119,9 @@ export const Splitter: React.FC<SplitterProps> = ({
|
|||
<div
|
||||
ref={splitterRef}
|
||||
className={`
|
||||
${
|
||||
isHorizontal ? "w-1 cursor-col-resize" : "h-1 cursor-row-resize"
|
||||
}
|
||||
${isHorizontal ? 'w-1 cursor-col-resize' : 'h-1 cursor-row-resize'}
|
||||
bg-gray-300 dark:bg-gray-700 hover:bg-blue-500 dark:hover:bg-blue-600 transition-colors duration-200 flex-shrink-0
|
||||
${isDragging ? "bg-blue-500 dark:bg-blue-600" : ""}
|
||||
${isDragging ? 'bg-blue-500 dark:bg-blue-600' : ''}
|
||||
`}
|
||||
onMouseDown={handleMouseDown}
|
||||
/>
|
||||
|
|
@ -134,5 +130,5 @@ export const Splitter: React.FC<SplitterProps> = ({
|
|||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useMemo, useState } from 'react'
|
||||
import { FaPlus, FaSearch, FaTimes } from 'react-icons/fa'
|
||||
import { Button } from '@/components/ui'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
interface StyleModalProps {
|
||||
isOpen: boolean
|
||||
|
|
@ -12,30 +13,348 @@ interface StyleModalProps {
|
|||
}
|
||||
|
||||
const STYLE_GROUPS = {
|
||||
Display: ['block', 'inline-block', 'inline', 'flex', 'inline-flex', 'grid', 'inline-grid', 'contents', 'hidden'],
|
||||
Layout: ['container', 'box-border', 'box-content', 'overflow-auto', 'overflow-hidden', 'overflow-visible', 'overflow-scroll', 'overflow-x-auto', 'overflow-y-auto'],
|
||||
Position: ['static', 'fixed', 'absolute', 'relative', 'sticky', 'inset-0', 'top-0', 'right-0', 'bottom-0', 'left-0', 'z-0', 'z-10', 'z-20', 'z-30', 'z-40', 'z-50'],
|
||||
Flexbox: ['flex-row', 'flex-row-reverse', 'flex-col', 'flex-col-reverse', 'flex-wrap', 'flex-nowrap', 'grow', 'grow-0', 'shrink', 'shrink-0', 'items-start', 'items-center', 'items-end', 'items-stretch', 'justify-start', 'justify-center', 'justify-end', 'justify-between', 'justify-around', 'justify-evenly', 'self-auto', 'self-start', 'self-center', 'self-end', 'self-stretch'],
|
||||
Grid: ['grid-cols-1', 'grid-cols-2', 'grid-cols-3', 'grid-cols-4', 'grid-cols-5', 'grid-cols-6', 'grid-cols-12', 'col-auto', 'col-span-1', 'col-span-2', 'col-span-3', 'col-span-4', 'col-span-6', 'col-span-12', 'grid-rows-1', 'grid-rows-2', 'grid-rows-3', 'row-span-1', 'row-span-2', 'row-span-3'],
|
||||
Spacing: ['m-0', 'm-1', 'm-2', 'm-3', 'm-4', 'm-5', 'm-6', 'm-8', 'm-auto', 'mx-auto', 'my-auto', 'mt-1', 'mt-2', 'mt-3', 'mt-4', 'mr-1', 'mr-2', 'mr-4', 'mb-1', 'mb-2', 'mb-4', 'ml-1', 'ml-2', 'ml-4', 'p-0', 'p-1', 'p-2', 'p-3', 'p-4', 'p-5', 'p-6', 'p-8', 'px-1', 'px-2', 'px-3', 'px-4', 'px-6', 'py-1', 'py-2', 'py-3', 'py-4', 'py-6', 'gap-0', 'gap-1', 'gap-2', 'gap-3', 'gap-4', 'gap-6', 'gap-8'],
|
||||
Sizing: ['w-0', 'w-auto', 'w-full', 'w-screen', 'w-1/2', 'w-1/3', 'w-2/3', 'w-1/4', 'w-3/4', 'h-0', 'h-auto', 'h-full', 'h-screen', 'min-w-0', 'min-w-full', 'min-h-0', 'min-h-full', 'max-w-xs', 'max-w-sm', 'max-w-md', 'max-w-lg', 'max-w-xl', 'max-w-2xl', 'max-w-full', 'max-h-full', 'aspect-square', 'aspect-video'],
|
||||
Typography: ['text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl', 'text-3xl', 'font-thin', 'font-light', 'font-normal', 'font-medium', 'font-semibold', 'font-bold', 'italic', 'not-italic', 'underline', 'line-through', 'no-underline', 'text-left', 'text-center', 'text-right', 'uppercase', 'lowercase', 'capitalize', 'truncate', 'whitespace-normal', 'whitespace-nowrap', 'break-words', 'leading-none', 'leading-tight', 'leading-normal', 'tracking-tight', 'tracking-normal', 'tracking-wide'],
|
||||
Colors: ['text-transparent', 'text-white', 'text-black', 'text-slate-400', 'text-slate-500', 'text-slate-600', 'text-slate-700', 'text-slate-800', 'text-slate-900', 'text-sky-500', 'text-blue-600', 'text-red-500', 'text-amber-500', 'text-emerald-500', 'bg-transparent', 'bg-white', 'bg-black', 'bg-slate-50', 'bg-slate-100', 'bg-slate-200', 'bg-slate-700', 'bg-slate-800', 'bg-slate-900', 'bg-sky-500', 'bg-blue-600', 'bg-red-500', 'bg-amber-500', 'bg-emerald-500'],
|
||||
Border: ['border', 'border-0', 'border-2', 'border-t', 'border-r', 'border-b', 'border-l', 'border-transparent', 'border-slate-200', 'border-slate-300', 'border-slate-700', 'border-sky-500', 'rounded-none', 'rounded-sm', 'rounded', 'rounded-md', 'rounded-lg', 'rounded-xl', 'rounded-2xl', 'rounded-full', 'divide-x', 'divide-y'],
|
||||
Effects: ['shadow-none', 'shadow-sm', 'shadow', 'shadow-md', 'shadow-lg', 'shadow-xl', 'opacity-0', 'opacity-50', 'opacity-75', 'opacity-100', 'blur-sm', 'blur', 'backdrop-blur-sm', 'backdrop-blur', 'transition', 'transition-all', 'duration-150', 'duration-300', 'ease-in', 'ease-out', 'ease-in-out'],
|
||||
Interaction: ['cursor-auto', 'cursor-default', 'cursor-pointer', 'cursor-not-allowed', 'select-none', 'select-text', 'pointer-events-none', 'pointer-events-auto', 'resize', 'resize-none', 'outline-none', 'focus:outline-none', 'hover:opacity-80', 'hover:bg-slate-100', 'focus:ring-2', 'focus:ring-sky-500'],
|
||||
Responsive: ['sm:block', 'sm:flex', 'sm:grid', 'sm:hidden', 'md:block', 'md:flex', 'md:grid', 'md:hidden', 'lg:block', 'lg:flex', 'lg:grid', 'lg:hidden', 'xl:block', 'xl:flex', 'xl:grid', 'dark:bg-slate-900', 'dark:bg-slate-950', 'dark:text-white', 'dark:text-slate-300', 'dark:border-slate-700'],
|
||||
Display: [
|
||||
'block',
|
||||
'inline-block',
|
||||
'inline',
|
||||
'flex',
|
||||
'inline-flex',
|
||||
'grid',
|
||||
'inline-grid',
|
||||
'contents',
|
||||
'hidden',
|
||||
],
|
||||
Layout: [
|
||||
'container',
|
||||
'box-border',
|
||||
'box-content',
|
||||
'overflow-auto',
|
||||
'overflow-hidden',
|
||||
'overflow-visible',
|
||||
'overflow-scroll',
|
||||
'overflow-x-auto',
|
||||
'overflow-y-auto',
|
||||
],
|
||||
Position: [
|
||||
'static',
|
||||
'fixed',
|
||||
'absolute',
|
||||
'relative',
|
||||
'sticky',
|
||||
'inset-0',
|
||||
'top-0',
|
||||
'right-0',
|
||||
'bottom-0',
|
||||
'left-0',
|
||||
'z-0',
|
||||
'z-10',
|
||||
'z-20',
|
||||
'z-30',
|
||||
'z-40',
|
||||
'z-50',
|
||||
],
|
||||
Flexbox: [
|
||||
'flex-row',
|
||||
'flex-row-reverse',
|
||||
'flex-col',
|
||||
'flex-col-reverse',
|
||||
'flex-wrap',
|
||||
'flex-nowrap',
|
||||
'grow',
|
||||
'grow-0',
|
||||
'shrink',
|
||||
'shrink-0',
|
||||
'items-start',
|
||||
'items-center',
|
||||
'items-end',
|
||||
'items-stretch',
|
||||
'justify-start',
|
||||
'justify-center',
|
||||
'justify-end',
|
||||
'justify-between',
|
||||
'justify-around',
|
||||
'justify-evenly',
|
||||
'self-auto',
|
||||
'self-start',
|
||||
'self-center',
|
||||
'self-end',
|
||||
'self-stretch',
|
||||
],
|
||||
Grid: [
|
||||
'grid-cols-1',
|
||||
'grid-cols-2',
|
||||
'grid-cols-3',
|
||||
'grid-cols-4',
|
||||
'grid-cols-5',
|
||||
'grid-cols-6',
|
||||
'grid-cols-12',
|
||||
'col-auto',
|
||||
'col-span-1',
|
||||
'col-span-2',
|
||||
'col-span-3',
|
||||
'col-span-4',
|
||||
'col-span-6',
|
||||
'col-span-12',
|
||||
'grid-rows-1',
|
||||
'grid-rows-2',
|
||||
'grid-rows-3',
|
||||
'row-span-1',
|
||||
'row-span-2',
|
||||
'row-span-3',
|
||||
],
|
||||
Spacing: [
|
||||
'm-0',
|
||||
'm-1',
|
||||
'm-2',
|
||||
'm-3',
|
||||
'm-4',
|
||||
'm-5',
|
||||
'm-6',
|
||||
'm-8',
|
||||
'm-auto',
|
||||
'mx-auto',
|
||||
'my-auto',
|
||||
'mt-1',
|
||||
'mt-2',
|
||||
'mt-3',
|
||||
'mt-4',
|
||||
'mr-1',
|
||||
'mr-2',
|
||||
'mr-4',
|
||||
'mb-1',
|
||||
'mb-2',
|
||||
'mb-4',
|
||||
'ml-1',
|
||||
'ml-2',
|
||||
'ml-4',
|
||||
'p-0',
|
||||
'p-1',
|
||||
'p-2',
|
||||
'p-3',
|
||||
'p-4',
|
||||
'p-5',
|
||||
'p-6',
|
||||
'p-8',
|
||||
'px-1',
|
||||
'px-2',
|
||||
'px-3',
|
||||
'px-4',
|
||||
'px-6',
|
||||
'py-1',
|
||||
'py-2',
|
||||
'py-3',
|
||||
'py-4',
|
||||
'py-6',
|
||||
'gap-0',
|
||||
'gap-1',
|
||||
'gap-2',
|
||||
'gap-3',
|
||||
'gap-4',
|
||||
'gap-6',
|
||||
'gap-8',
|
||||
],
|
||||
Sizing: [
|
||||
'w-0',
|
||||
'w-auto',
|
||||
'w-full',
|
||||
'w-screen',
|
||||
'w-1/2',
|
||||
'w-1/3',
|
||||
'w-2/3',
|
||||
'w-1/4',
|
||||
'w-3/4',
|
||||
'h-0',
|
||||
'h-auto',
|
||||
'h-full',
|
||||
'h-screen',
|
||||
'min-w-0',
|
||||
'min-w-full',
|
||||
'min-h-0',
|
||||
'min-h-full',
|
||||
'max-w-xs',
|
||||
'max-w-sm',
|
||||
'max-w-md',
|
||||
'max-w-lg',
|
||||
'max-w-xl',
|
||||
'max-w-2xl',
|
||||
'max-w-full',
|
||||
'max-h-full',
|
||||
'aspect-square',
|
||||
'aspect-video',
|
||||
],
|
||||
Typography: [
|
||||
'text-xs',
|
||||
'text-sm',
|
||||
'text-base',
|
||||
'text-lg',
|
||||
'text-xl',
|
||||
'text-2xl',
|
||||
'text-3xl',
|
||||
'font-thin',
|
||||
'font-light',
|
||||
'font-normal',
|
||||
'font-medium',
|
||||
'font-semibold',
|
||||
'font-bold',
|
||||
'italic',
|
||||
'not-italic',
|
||||
'underline',
|
||||
'line-through',
|
||||
'no-underline',
|
||||
'text-left',
|
||||
'text-center',
|
||||
'text-right',
|
||||
'uppercase',
|
||||
'lowercase',
|
||||
'capitalize',
|
||||
'truncate',
|
||||
'whitespace-normal',
|
||||
'whitespace-nowrap',
|
||||
'break-words',
|
||||
'leading-none',
|
||||
'leading-tight',
|
||||
'leading-normal',
|
||||
'tracking-tight',
|
||||
'tracking-normal',
|
||||
'tracking-wide',
|
||||
],
|
||||
Colors: [
|
||||
'text-transparent',
|
||||
'text-white',
|
||||
'text-black',
|
||||
'text-slate-400',
|
||||
'text-slate-500',
|
||||
'text-slate-600',
|
||||
'text-slate-700',
|
||||
'text-slate-800',
|
||||
'text-slate-900',
|
||||
'text-sky-500',
|
||||
'text-blue-600',
|
||||
'text-red-500',
|
||||
'text-amber-500',
|
||||
'text-emerald-500',
|
||||
'bg-transparent',
|
||||
'bg-white',
|
||||
'bg-black',
|
||||
'bg-slate-50',
|
||||
'bg-slate-100',
|
||||
'bg-slate-200',
|
||||
'bg-slate-700',
|
||||
'bg-slate-800',
|
||||
'bg-slate-900',
|
||||
'bg-sky-500',
|
||||
'bg-blue-600',
|
||||
'bg-red-500',
|
||||
'bg-amber-500',
|
||||
'bg-emerald-500',
|
||||
],
|
||||
Border: [
|
||||
'border',
|
||||
'border-0',
|
||||
'border-2',
|
||||
'border-t',
|
||||
'border-r',
|
||||
'border-b',
|
||||
'border-l',
|
||||
'border-transparent',
|
||||
'border-slate-200',
|
||||
'border-slate-300',
|
||||
'border-slate-700',
|
||||
'border-sky-500',
|
||||
'rounded-none',
|
||||
'rounded-sm',
|
||||
'rounded',
|
||||
'rounded-md',
|
||||
'rounded-lg',
|
||||
'rounded-xl',
|
||||
'rounded-2xl',
|
||||
'rounded-full',
|
||||
'divide-x',
|
||||
'divide-y',
|
||||
],
|
||||
Effects: [
|
||||
'shadow-none',
|
||||
'shadow-sm',
|
||||
'shadow',
|
||||
'shadow-md',
|
||||
'shadow-lg',
|
||||
'shadow-xl',
|
||||
'opacity-0',
|
||||
'opacity-50',
|
||||
'opacity-75',
|
||||
'opacity-100',
|
||||
'blur-sm',
|
||||
'blur',
|
||||
'backdrop-blur-sm',
|
||||
'backdrop-blur',
|
||||
'transition',
|
||||
'transition-all',
|
||||
'duration-150',
|
||||
'duration-300',
|
||||
'ease-in',
|
||||
'ease-out',
|
||||
'ease-in-out',
|
||||
],
|
||||
Interaction: [
|
||||
'cursor-auto',
|
||||
'cursor-default',
|
||||
'cursor-pointer',
|
||||
'cursor-not-allowed',
|
||||
'select-none',
|
||||
'select-text',
|
||||
'pointer-events-none',
|
||||
'pointer-events-auto',
|
||||
'resize',
|
||||
'resize-none',
|
||||
'outline-none',
|
||||
'focus:outline-none',
|
||||
'hover:opacity-80',
|
||||
'hover:bg-slate-100',
|
||||
'focus:ring-2',
|
||||
'focus:ring-sky-500',
|
||||
],
|
||||
Responsive: [
|
||||
'sm:block',
|
||||
'sm:flex',
|
||||
'sm:grid',
|
||||
'sm:hidden',
|
||||
'md:block',
|
||||
'md:flex',
|
||||
'md:grid',
|
||||
'md:hidden',
|
||||
'lg:block',
|
||||
'lg:flex',
|
||||
'lg:grid',
|
||||
'lg:hidden',
|
||||
'xl:block',
|
||||
'xl:flex',
|
||||
'xl:grid',
|
||||
'dark:bg-slate-900',
|
||||
'dark:bg-slate-950',
|
||||
'dark:text-white',
|
||||
'dark:text-slate-300',
|
||||
'dark:border-slate-700',
|
||||
],
|
||||
} as const
|
||||
|
||||
const splitClasses = (value: string) => value.split(/\s+/).filter(Boolean)
|
||||
|
||||
const StyleModal = ({ isOpen, mode, propertyName, value = '', onChange, onClose }: StyleModalProps) => {
|
||||
const StyleModal = ({
|
||||
isOpen,
|
||||
mode,
|
||||
propertyName,
|
||||
value = '',
|
||||
onChange,
|
||||
onClose,
|
||||
}: StyleModalProps) => {
|
||||
const { translate } = useLocalization()
|
||||
const [search, setSearch] = useState('')
|
||||
const [customClass, setCustomClass] = useState('')
|
||||
const [category, setCategory] = useState<keyof typeof STYLE_GROUPS | 'Tümü'>('Tümü')
|
||||
const [category, setCategory] = useState<keyof typeof STYLE_GROUPS | '__all__'>('__all__')
|
||||
const selected = useMemo(() => new Set(splitClasses(value)), [value])
|
||||
const classes = useMemo(() => {
|
||||
const source = category === 'Tümü' ? Object.values(STYLE_GROUPS).flat() : STYLE_GROUPS[category]
|
||||
const source =
|
||||
category === '__all__' ? Object.values(STYLE_GROUPS).flat() : STYLE_GROUPS[category]
|
||||
const query = search.trim().toLowerCase()
|
||||
return [...new Set(source)].filter((name) => !query || name.toLowerCase().includes(query))
|
||||
}, [category, search])
|
||||
|
|
@ -56,41 +375,112 @@ const StyleModal = ({ isOpen, mode, propertyName, value = '', onChange, onClose
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 p-4" onMouseDown={onClose}>
|
||||
<div className="flex max-h-[90vh] w-full max-w-5xl flex-col overflow-hidden rounded-xl bg-white shadow-2xl dark:bg-slate-900" onMouseDown={(event) => event.stopPropagation()}>
|
||||
<div
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 p-4"
|
||||
onMouseDown={onClose}
|
||||
>
|
||||
<div
|
||||
className="flex max-h-[90vh] w-full max-w-5xl flex-col overflow-hidden rounded-xl bg-white shadow-2xl dark:bg-slate-900"
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-slate-200 p-4 dark:border-slate-700">
|
||||
<div>
|
||||
<h2 className="font-semibold text-slate-900 dark:text-white">StyleModal · {propertyName}</h2>
|
||||
<p className="mt-1 text-xs text-slate-500">{mode === 'css' ? 'CSS kodunu doğrudan düzenleyin.' : 'Hazır sınıfları seçin veya CSS/Tailwind class adlarını elle düzenleyin.'}</p>
|
||||
<h2 className="font-semibold text-slate-900 dark:text-white">
|
||||
StyleModal · {propertyName}
|
||||
</h2>
|
||||
<p className="mt-1 text-xs text-slate-500">
|
||||
{translate(
|
||||
mode === 'css' ? '::App.StyleModal.CssModeHint' : '::App.StyleModal.ClassModeHint',
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Button icon={<FaTimes />} size="xs" variant="plain" onClick={onClose} />
|
||||
</div>
|
||||
{mode === 'class' && <div className="grid gap-3 border-b border-slate-200 p-4 md:grid-cols-[1fr_190px] dark:border-slate-700">
|
||||
{mode === 'class' && (
|
||||
<div className="grid gap-3 border-b border-slate-200 p-4 md:grid-cols-[1fr_190px] dark:border-slate-700">
|
||||
<label className="relative">
|
||||
<FaSearch className="absolute left-3 top-3 text-slate-400" />
|
||||
<input className="w-full rounded-md border border-slate-300 bg-white py-2 pl-9 pr-3 text-sm dark:border-slate-700 dark:bg-slate-950 dark:text-white" placeholder="Hazır sınıflarda ara…" value={search} onChange={(event) => setSearch(event.target.value)} />
|
||||
<input
|
||||
className="w-full rounded-md border border-slate-300 bg-white py-2 pl-9 pr-3 text-sm dark:border-slate-700 dark:bg-slate-950 dark:text-white"
|
||||
placeholder={translate('::App.StyleModal.SearchClasses')}
|
||||
value={search}
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<select className="rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950 dark:text-white" value={category} onChange={(event) => setCategory(event.target.value as typeof category)}>
|
||||
<option value="Tümü">Tüm kategoriler</option>
|
||||
{Object.keys(STYLE_GROUPS).map((name) => <option key={name} value={name}>{name}</option>)}
|
||||
</select>
|
||||
</div>}
|
||||
{mode === 'class' && <div className="flex gap-2 border-b border-slate-200 p-4 dark:border-slate-700">
|
||||
<input className="min-w-0 flex-1 rounded-md border border-slate-300 bg-white px-3 py-2 font-mono text-xs dark:border-slate-700 dark:bg-slate-950 dark:text-white" placeholder="Örn. hover:scale-105 w-[320px] [&>span]:font-bold" value={customClass} onChange={(event) => setCustomClass(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') addCustomClass() }} />
|
||||
<Button icon={<FaPlus />} size="sm" variant="default" onClick={addCustomClass}>Ekle</Button>
|
||||
</div>}
|
||||
<div className="border-b border-slate-200 bg-slate-50 p-4 dark:border-slate-700 dark:bg-slate-950">
|
||||
<label className="mb-1.5 block text-xs font-semibold text-slate-600 dark:text-slate-300">{propertyName} değerini elle düzenle</label>
|
||||
<textarea className={`w-full rounded-md border border-slate-300 bg-white p-3 font-mono text-xs text-slate-800 outline-none focus:border-sky-500 dark:border-slate-700 dark:bg-slate-900 dark:text-white ${mode === 'css' ? 'min-h-48' : 'min-h-20'}`} placeholder={mode === 'css' ? '.my-class { color: red; }' : 'my-card selected-state md:wide-card'} value={value} onChange={(event) => onChange(event.target.value)} />
|
||||
</div>
|
||||
{mode === 'class' && <div className="grid flex-1 grid-cols-2 gap-2 overflow-y-auto p-4 sm:grid-cols-3 lg:grid-cols-5">
|
||||
{classes.map((className) => (
|
||||
<button key={className} className={`rounded-md border px-3 py-2 text-left font-mono text-xs transition ${selected.has(className) ? 'border-sky-500 bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-300' : 'border-slate-200 text-slate-600 hover:border-sky-300 dark:border-slate-700 dark:text-slate-300'}`} type="button" onClick={() => toggleClass(className)}>{className}</button>
|
||||
<select
|
||||
className="rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950 dark:text-white"
|
||||
value={category}
|
||||
onChange={(event) => setCategory(event.target.value as typeof category)}
|
||||
>
|
||||
<option value="__all__">{translate('::App.StyleModal.AllCategories')}</option>
|
||||
{Object.keys(STYLE_GROUPS).map((name) => (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</div>}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
{mode === 'class' && (
|
||||
<div className="flex gap-2 border-b border-slate-200 p-4 dark:border-slate-700">
|
||||
<input
|
||||
className="min-w-0 flex-1 rounded-md border border-slate-300 bg-white px-3 py-2 font-mono text-xs dark:border-slate-700 dark:bg-slate-950 dark:text-white"
|
||||
placeholder={translate('::App.StyleModal.CustomClassPlaceholder')}
|
||||
value={customClass}
|
||||
onChange={(event) => setCustomClass(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') addCustomClass()
|
||||
}}
|
||||
/>
|
||||
<Button icon={<FaPlus />} size="sm" variant="default" onClick={addCustomClass}>
|
||||
{translate('::ListForms.Wizard.Add')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="border-b border-slate-200 bg-slate-50 p-4 dark:border-slate-700 dark:bg-slate-950">
|
||||
<label className="mb-1.5 block text-xs font-semibold text-slate-600 dark:text-slate-300">
|
||||
{translate('::App.StyleModal.EditValueManually', { property: propertyName })}
|
||||
</label>
|
||||
<textarea
|
||||
className={`w-full rounded-md border border-slate-300 bg-white p-3 font-mono text-xs text-slate-800 outline-none focus:border-sky-500 dark:border-slate-700 dark:bg-slate-900 dark:text-white ${mode === 'css' ? 'min-h-48' : 'min-h-20'}`}
|
||||
placeholder={
|
||||
mode === 'css' ? '.my-class { color: red; }' : 'my-card selected-state md:wide-card'
|
||||
}
|
||||
value={value}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{mode === 'class' && (
|
||||
<div className="grid flex-1 grid-cols-2 gap-2 overflow-y-auto p-4 sm:grid-cols-3 lg:grid-cols-5">
|
||||
{classes.map((className) => (
|
||||
<button
|
||||
key={className}
|
||||
className={`rounded-md border px-3 py-2 text-left font-mono text-xs transition ${selected.has(className) ? 'border-sky-500 bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-300' : 'border-slate-200 text-slate-600 hover:border-sky-300 dark:border-slate-700 dark:text-slate-300'}`}
|
||||
type="button"
|
||||
onClick={() => toggleClass(className)}
|
||||
>
|
||||
{className}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between border-t border-slate-200 p-4 dark:border-slate-700">
|
||||
<span className="text-xs text-slate-500">{mode === 'css' ? 'Ham CSS düzenleme modu' : `${selected.size} seçili · ${classes.length} görüntüleniyor`}</span>
|
||||
<div className="flex gap-2"><Button size="sm" variant="default" onClick={() => onChange('')}>Temizle</Button><Button size="sm" variant="solid" onClick={onClose}>Tamam</Button></div>
|
||||
<span className="text-xs text-slate-500">
|
||||
{mode === 'css'
|
||||
? translate('::App.StyleModal.RawCssMode')
|
||||
: translate('::App.StyleModal.SelectedShownCount', {
|
||||
selected: selected.size,
|
||||
shown: classes.length,
|
||||
})}
|
||||
</span>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="default" onClick={() => onChange('')}>
|
||||
{translate('::App.DeveloperKit.CrudEndpoints.Clear')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" onClick={onClose}>
|
||||
{translate('::OK')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,89 +1,80 @@
|
|||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import {
|
||||
FaCheckCircle,
|
||||
FaExclamationTriangle,
|
||||
FaClock,
|
||||
FaTimes,
|
||||
} from "react-icons/fa";
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { FaCheckCircle, FaExclamationTriangle, FaClock, FaTimes } from 'react-icons/fa'
|
||||
|
||||
interface StatusBadgeProps {
|
||||
status: string;
|
||||
size?: "sm" | "md";
|
||||
showIcon?: boolean;
|
||||
status: string
|
||||
size?: 'sm' | 'md'
|
||||
showIcon?: boolean
|
||||
}
|
||||
|
||||
const StatusBadge: React.FC<StatusBadgeProps> = ({
|
||||
status,
|
||||
size = "md",
|
||||
showIcon = true,
|
||||
}) => {
|
||||
const StatusBadge: React.FC<StatusBadgeProps> = ({ status, size = 'md', showIcon = true }) => {
|
||||
const sizeClasses = {
|
||||
sm: "px-1.5 py-0.5 text-xs",
|
||||
md: "px-2 py-0.5 text-xs",
|
||||
};
|
||||
sm: 'px-1.5 py-0.5 text-xs',
|
||||
md: 'px-2 py-0.5 text-xs',
|
||||
}
|
||||
|
||||
const getStatusConfig = (status: string) => {
|
||||
switch (status) {
|
||||
case "active":
|
||||
case 'active':
|
||||
return {
|
||||
color: "bg-green-100 text-green-800",
|
||||
color: 'bg-green-100 text-green-800',
|
||||
icon: <FaCheckCircle size={14} />,
|
||||
text: "Aktif",
|
||||
};
|
||||
case "inactive":
|
||||
text: 'Aktif',
|
||||
}
|
||||
case 'inactive':
|
||||
return {
|
||||
color: "bg-red-100 text-red-800",
|
||||
color: 'bg-red-100 text-red-800',
|
||||
icon: <FaTimes size={14} />,
|
||||
text: "Pasif",
|
||||
};
|
||||
case "pending":
|
||||
text: 'Pasif',
|
||||
}
|
||||
case 'pending':
|
||||
return {
|
||||
color: "bg-yellow-100 text-yellow-800",
|
||||
color: 'bg-yellow-100 text-yellow-800',
|
||||
icon: <FaClock size={14} />,
|
||||
text: "Beklemede",
|
||||
};
|
||||
case "critical":
|
||||
text: 'Beklemede',
|
||||
}
|
||||
case 'critical':
|
||||
return {
|
||||
color: "bg-red-100 text-red-800",
|
||||
color: 'bg-red-100 text-red-800',
|
||||
icon: <FaExclamationTriangle size={14} />,
|
||||
text: "Kritik",
|
||||
};
|
||||
case "low":
|
||||
text: 'Kritik',
|
||||
}
|
||||
case 'low':
|
||||
return {
|
||||
color: "bg-yellow-100 text-yellow-800",
|
||||
color: 'bg-yellow-100 text-yellow-800',
|
||||
icon: <FaExclamationTriangle size={14} />,
|
||||
text: "Düşük",
|
||||
};
|
||||
case "normal":
|
||||
text: 'App.Platform.Priority.Low',
|
||||
}
|
||||
case 'normal':
|
||||
return {
|
||||
color: "bg-green-100 text-green-800",
|
||||
color: 'bg-green-100 text-green-800',
|
||||
icon: <FaCheckCircle size={14} />,
|
||||
text: "Normal",
|
||||
};
|
||||
text: 'Normal',
|
||||
}
|
||||
default:
|
||||
return {
|
||||
color: "bg-gray-100 text-gray-800",
|
||||
color: 'bg-gray-100 text-gray-800',
|
||||
icon: <FaCheckCircle size={14} />,
|
||||
text: "Bilinmiyor",
|
||||
};
|
||||
text: 'Bilinmiyor',
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const config = getStatusConfig(status);
|
||||
const config = getStatusConfig(status)
|
||||
|
||||
return (
|
||||
<span
|
||||
className={classNames(
|
||||
"inline-flex items-center rounded-full font-medium",
|
||||
'inline-flex items-center rounded-full font-medium',
|
||||
config.color,
|
||||
sizeClasses[size]
|
||||
sizeClasses[size],
|
||||
)}
|
||||
>
|
||||
{showIcon && <span className="mr-1">{config.icon}</span>}
|
||||
{config.text}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default StatusBadge;
|
||||
export default StatusBadge
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import DynamicRenderer from './DynamicRenderer'
|
|||
import { useComponents } from '@/contexts/ComponentContext'
|
||||
import { parseComponentDependencies } from '@/contexts/componentRuntime'
|
||||
import { Loading } from '../shared'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
export interface ComponentPreviewProps {
|
||||
componentName?: string
|
||||
|
|
@ -10,6 +11,7 @@ export interface ComponentPreviewProps {
|
|||
}
|
||||
|
||||
const ComponentPreview: React.FC<ComponentPreviewProps> = ({ componentName, className = '' }) => {
|
||||
const { translate } = useLocalization()
|
||||
const { components, loading } = useComponents()
|
||||
|
||||
// Referans olarak sabit tutulmalı: DynamicRenderer bunu effect bağımlılığı olarak kullanıyor.
|
||||
|
|
@ -19,7 +21,11 @@ const ComponentPreview: React.FC<ComponentPreviewProps> = ({ componentName, clas
|
|||
}, [components, componentName])
|
||||
|
||||
if (!componentName) {
|
||||
return <div className="text-sm text-gray-500 dark:text-gray-400">Bileşen ismi yok.</div>
|
||||
return (
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{translate('::App.DeveloperKit.ComponentPreview.NoComponentName')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (loading || !Array.isArray(components)) {
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
import React from "react";
|
||||
import type { ErrorInfo } from "react";
|
||||
import React from 'react'
|
||||
import type { ErrorInfo } from 'react'
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
hasError: boolean
|
||||
error: Error | null
|
||||
}
|
||||
|
||||
class ErrorBoundary extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
super(props)
|
||||
this.state = { hasError: false, error: null }
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { hasError: true, error };
|
||||
return { hasError: true, error }
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
// Eğer component değişmişse, hata state'ini sıfırla
|
||||
if (prevProps.children !== this.props.children && this.state.hasError) {
|
||||
this.setState({ hasError: false, error: null });
|
||||
this.setState({ hasError: false, error: null })
|
||||
}
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: ErrorInfo) {
|
||||
console.error("Render hatası:", error, info);
|
||||
console.error('Render hatası:', error, info)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -36,27 +36,27 @@ class ErrorBoundary extends React.Component<Props, State> {
|
|||
return (
|
||||
<div
|
||||
style={{
|
||||
color: "red",
|
||||
background: "#fff0f0",
|
||||
border: "1px solid #f44336",
|
||||
padding: "1rem",
|
||||
borderRadius: "8px",
|
||||
fontFamily: "monospace",
|
||||
whiteSpace: "pre-wrap",
|
||||
color: 'red',
|
||||
background: '#fff0f0',
|
||||
border: '1px solid #f44336',
|
||||
padding: '1rem',
|
||||
borderRadius: '8px',
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
}}
|
||||
>
|
||||
⚠️ Render Hatası:
|
||||
⚠️ Render error:
|
||||
<br />
|
||||
<details open style={{ marginTop: "0.5rem" }}>
|
||||
<summary>Detayları Göster</summary>
|
||||
<details open style={{ marginTop: '0.5rem' }}>
|
||||
<summary>Show details</summary>
|
||||
{this.state.error?.message || String(this.state.error)}
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
export default ErrorBoundary
|
||||
|
|
|
|||
|
|
@ -41,9 +41,7 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
const importService = useMemo(() => new ImportService(), [])
|
||||
const [generating, setGenerating] = useState(false)
|
||||
const [expandedSessions, setExpandedSessions] = useState<Set<string>>(new Set())
|
||||
const [sessionExecutes, setSessionExecutes] = useState<
|
||||
Record<string, ListFormImportLogDto[]>
|
||||
>({})
|
||||
const [sessionExecutes, setSessionExecutes] = useState<Record<string, ListFormImportLogDto[]>>({})
|
||||
const [loadingExecutes, setLoadingExecutes] = useState<Set<string>>(new Set())
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -414,7 +412,10 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
</thead>
|
||||
<tbody className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
{editableColumns.map((column: any) => (
|
||||
<tr key={column.fieldName} className="hover:bg-gray-50 dark:hover:bg-gray-800/70">
|
||||
<tr
|
||||
key={column.fieldName}
|
||||
className="hover:bg-gray-50 dark:hover:bg-gray-800/70"
|
||||
>
|
||||
<td className="px-2 py-2 font-medium text-gray-800 dark:text-gray-100">
|
||||
{column.fieldName}
|
||||
</td>
|
||||
|
|
@ -740,8 +741,14 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
size="xs"
|
||||
>
|
||||
<span>
|
||||
{execute.errorRows} hata detayı
|
||||
{expandedErrors.has(execute.id) ? ' gizle' : ' göster'}
|
||||
{translate('::App.Listforms.ImportManager.ErrorDetails', {
|
||||
count: execute.errorRows,
|
||||
})}{' '}
|
||||
{translate(
|
||||
expandedErrors.has(execute.id)
|
||||
? '::App.Platform.Hide'
|
||||
: '::App.Platform.Show',
|
||||
)}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
|
|
@ -752,10 +759,10 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
<thead className="bg-orange-100 dark:bg-gray-800 sticky top-0">
|
||||
<tr>
|
||||
<th className="px-3 py-1 text-left font-medium text-orange-700 dark:text-orange-300 w-16">
|
||||
Satır
|
||||
{translate('::App.Platform.Row')}
|
||||
</th>
|
||||
<th className="px-3 py-1 text-left font-medium text-orange-700 dark:text-orange-300">
|
||||
Hata Mesajı
|
||||
{translate('::App.Platform.ErrorMessage')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -777,7 +784,9 @@ export const ImportDashboard: React.FC<ImportDashboardProps> = ({ gridDto }) =>
|
|||
</table>
|
||||
) : (
|
||||
<p className="px-3 py-2 text-orange-600 dark:text-orange-300">
|
||||
Hata detayı mevcut değil.
|
||||
{translate(
|
||||
'::App.Listforms.ImportManager.NoErrorDetail',
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -70,8 +70,7 @@ export const ImportProgress: React.FC<ImportProgressProps> = ({ session }) => {
|
|||
{getStatusMessage()}
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
{translate('::App.Listforms.Status.Processing')}{' '}
|
||||
{session.blobName}
|
||||
{translate('::App.Listforms.Status.Processing')} {session.blobName}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -38,10 +38,7 @@ const layouts = {
|
|||
const AuthLayout = lazy(() => import('./AuthLayout'))
|
||||
const PublicLayout = lazy(() => import('./PublicLayout'))
|
||||
|
||||
class LayoutChunkErrorBoundary extends Component<
|
||||
{ children: ReactNode },
|
||||
{ failed: boolean }
|
||||
> {
|
||||
class LayoutChunkErrorBoundary extends Component<{ children: ReactNode }, { failed: boolean }> {
|
||||
state = { failed: false }
|
||||
|
||||
static getDerivedStateFromError() {
|
||||
|
|
@ -57,16 +54,21 @@ class LayoutChunkErrorBoundary extends Component<
|
|||
return (
|
||||
<div className="flex min-h-screen items-center justify-center p-6">
|
||||
<div className="max-w-md text-center">
|
||||
<h1 className="mb-2 text-lg font-semibold">Uygulama kabuğu yüklenemedi</h1>
|
||||
{/*
|
||||
Bu sınır, uygulama konfigürasyonu (ve dolayısıyla lokalizasyon metinleri)
|
||||
yüklenemeden devreye girer; `translate` burada anahtarın kendisini döndürürdü.
|
||||
Bu yüzden açılış öncesi ekranların tamamı gibi sabit İngilizce tutulur.
|
||||
*/}
|
||||
<h1 className="mb-2 text-lg font-semibold">Application shell could not be loaded</h1>
|
||||
<p className="mb-4 text-sm text-gray-500">
|
||||
Bağlantınızı kontrol edip uygulamayı yeniden yükleyin.
|
||||
Check your connection and reload the application.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded bg-indigo-600 px-4 py-2 text-sm font-medium text-white"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Yeniden yükle
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ const PublicLayout = () => {
|
|||
type="button"
|
||||
onClick={() => setThemeMode(THEME_ENUM.MODE_LIGHT)}
|
||||
aria-pressed={!isDarkMode}
|
||||
title="Light mode"
|
||||
title={translate('::App.Theme.LightMode')}
|
||||
icon={<LuSun size={15} strokeWidth={1.8} />}
|
||||
variant="plain"
|
||||
size="xs"
|
||||
|
|
@ -151,7 +151,7 @@ const PublicLayout = () => {
|
|||
type="button"
|
||||
onClick={() => setThemeMode(THEME_ENUM.MODE_DARK)}
|
||||
aria-pressed={isDarkMode}
|
||||
title="Dark mode"
|
||||
title={translate('::App.Theme.DarkMode')}
|
||||
icon={<LuMoon size={15} strokeWidth={1.8} />}
|
||||
variant="plain"
|
||||
size="xs"
|
||||
|
|
@ -199,7 +199,11 @@ const PublicLayout = () => {
|
|||
'relative flex items-center gap-1.5 px-3 py-2 text-sm font-medium rounded-md transition-all duration-200 group'
|
||||
const activeClass = active ? 'text-white' : 'text-gray-300 hover:text-white'
|
||||
return link.path ? (
|
||||
<Link key={link.path} to={link.path} className={`${baseClass} ${activeClass}`}>
|
||||
<Link
|
||||
key={link.path}
|
||||
to={link.path}
|
||||
className={`${baseClass} ${activeClass}`}
|
||||
>
|
||||
{link.icon && (
|
||||
<link.icon
|
||||
size={22}
|
||||
|
|
@ -270,14 +274,10 @@ const PublicLayout = () => {
|
|||
<Button
|
||||
className="lg:hidden col-start-3 justify-self-end flex items-center justify-center w-9 h-9 text-white rounded-md hover:bg-white/10 transition-colors"
|
||||
onClick={toggleMenu}
|
||||
aria-label="Toggle menu"
|
||||
aria-label={translate('::App.Platform.ToggleMenu')}
|
||||
aria-expanded={isOpen}
|
||||
icon={
|
||||
isOpen ? (
|
||||
<LuX size={20} strokeWidth={2} />
|
||||
) : (
|
||||
<LuMenu size={20} strokeWidth={2} />
|
||||
)
|
||||
isOpen ? <LuX size={20} strokeWidth={2} /> : <LuMenu size={20} strokeWidth={2} />
|
||||
}
|
||||
variant="plain"
|
||||
size="xs"
|
||||
|
|
@ -545,7 +545,9 @@ const PublicLayout = () => {
|
|||
|
||||
{/* Contact Info */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold mb-4 text-white">{translate('::App.Contact')}</h3>
|
||||
<h3 className="text-lg font-bold mb-4 text-white">
|
||||
{translate('::App.Contact')}
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
<li className="flex items-start space-x-3">
|
||||
<LuMapPin
|
||||
|
|
@ -556,7 +558,11 @@ const PublicLayout = () => {
|
|||
<span className="text-gray-400">{translate('::Public.footer.address')}</span>
|
||||
</li>
|
||||
<li className="flex items-center space-x-3">
|
||||
<LuPhone size={18} strokeWidth={1.75} className="text-gray-400 flex-shrink-0" />
|
||||
<LuPhone
|
||||
size={18}
|
||||
strokeWidth={1.75}
|
||||
className="text-gray-400 flex-shrink-0"
|
||||
/>
|
||||
<a
|
||||
href="tel:+905447697638"
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
|
|
@ -565,7 +571,11 @@ const PublicLayout = () => {
|
|||
</a>
|
||||
</li>
|
||||
<li className="flex items-center space-x-3">
|
||||
<LuMail size={18} strokeWidth={1.75} className="text-gray-400 flex-shrink-0" />
|
||||
<LuMail
|
||||
size={18}
|
||||
strokeWidth={1.75}
|
||||
className="text-gray-400 flex-shrink-0"
|
||||
/>
|
||||
<a
|
||||
href="mailto:destek@sozsoft.com"
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export const Basket: React.FC<BasketProps> = ({
|
|||
const { translate } = useLocalization()
|
||||
|
||||
const handleClearBasket = () => {
|
||||
if (window.confirm('Sepetteki tüm ürünleri silmek istediğinizden emin misiniz?')) {
|
||||
if (window.confirm(translate('::Public.basket.clearConfirm'))) {
|
||||
clearBasket()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,9 +53,7 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
|
|||
variant={globalBillingCycle === 'monthly' ? 'solid' : 'default'}
|
||||
size="xs"
|
||||
className={globalBillingCycle === 'monthly' ? 'shadow-md' : ''}
|
||||
title={
|
||||
hasBasketItems ? 'Sepette ürün varken faturalama döngüsü değiştirilemez' : undefined
|
||||
}
|
||||
title={hasBasketItems ? translate('::Public.basket.billingCycleLocked') : undefined}
|
||||
>
|
||||
{translate('::Public.products.billingcycle.monthly')}
|
||||
</Button>
|
||||
|
|
@ -65,9 +63,7 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
|
|||
variant={globalBillingCycle === 'yearly' ? 'solid' : 'default'}
|
||||
size="xs"
|
||||
className={globalBillingCycle === 'yearly' ? 'shadow-md' : ''}
|
||||
title={
|
||||
hasBasketItems ? 'Sepette ürün varken faturalama döngüsü değiştirilemez' : undefined
|
||||
}
|
||||
title={hasBasketItems ? translate('::Public.basket.billingCycleLocked') : undefined}
|
||||
>
|
||||
{translate('::Public.products.billingcycle.yearly')}
|
||||
</Button>
|
||||
|
|
@ -90,11 +86,13 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
|
|||
icon={<FaMinus className="w-4 h-4" />}
|
||||
variant="default"
|
||||
size="xs"
|
||||
title={hasBasketItems ? 'Sepette ürün varken periyod değiştirilemez' : undefined}
|
||||
title={hasBasketItems ? translate('::Public.basket.periodLocked') : undefined}
|
||||
/>
|
||||
|
||||
<div className="flex items-center space-x-2 bg-gray-50 px-2 py-1 rounded-lg dark:bg-gray-800">
|
||||
<span className="font-bold text-lg text-gray-600 dark:text-white">{globalPeriod}</span>
|
||||
<span className="font-bold text-lg text-gray-600 dark:text-white">
|
||||
{globalPeriod}
|
||||
</span>
|
||||
<span className="text-xs text-gray-600 dark:text-white">
|
||||
{globalBillingCycle === 'monthly'
|
||||
? translate('::Public.products.billingcycle.month')
|
||||
|
|
@ -108,7 +106,7 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
|
|||
icon={<FaPlus className="w-4 h-4" />}
|
||||
variant="default"
|
||||
size="xs"
|
||||
title={hasBasketItems ? 'Sepette ürün varken periyod değiştirilemez' : undefined}
|
||||
title={hasBasketItems ? translate('::Public.basket.periodLocked') : undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
import React from 'react'
|
||||
import { FaArrowLeft, FaBuilding, FaCalendarAlt, FaCheckCircle, FaEnvelope, FaPhone } from 'react-icons/fa'
|
||||
import {
|
||||
FaArrowLeft,
|
||||
FaBuilding,
|
||||
FaCalendarAlt,
|
||||
FaCheckCircle,
|
||||
FaEnvelope,
|
||||
FaPhone,
|
||||
} from 'react-icons/fa'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
|
@ -42,8 +49,8 @@ export const OrderSuccess: React.FC<OrderSuccessProps> = ({ orderId, order }) =>
|
|||
|
||||
const getItemPrice = (item: BasketItem) =>
|
||||
item.billingCycle === 'monthly'
|
||||
? item.product.monthlyPrice ?? 0
|
||||
: item.product.yearlyPrice ?? 0
|
||||
? (item.product.monthlyPrice ?? 0)
|
||||
: (item.product.yearlyPrice ?? 0)
|
||||
|
||||
const vatRateLabel = (() => {
|
||||
if (!order) return ''
|
||||
|
|
@ -117,7 +124,9 @@ export const OrderSuccess: React.FC<OrderSuccessProps> = ({ orderId, order }) =>
|
|||
</h3>
|
||||
<div className="grid grid-cols-1 gap-3 text-sm text-gray-700 dark:text-gray-300 sm:grid-cols-2">
|
||||
<div className="rounded-lg bg-gray-50 p-3 dark:bg-gray-800/70">
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">License Start Time</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
License Start Time
|
||||
</div>
|
||||
<div className="font-semibold">{formatDate(order.licenseStartTime)}</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-gray-50 p-3 dark:bg-gray-800/70">
|
||||
|
|
@ -158,7 +167,11 @@ export const OrderSuccess: React.FC<OrderSuccessProps> = ({ orderId, order }) =>
|
|||
<div className="col-span-1 text-right">{order.period}</div>
|
||||
<div className="col-span-1 text-right">{item.quantity}</div>
|
||||
<div className="col-span-1 text-right">
|
||||
{item.billingCycle === 'monthly' ? 'Aylık' : 'Yıllık'}
|
||||
{translate(
|
||||
item.billingCycle === 'monthly'
|
||||
? '::Public.order.monthly'
|
||||
: '::Public.order.yearly',
|
||||
)}
|
||||
</div>
|
||||
<div className="col-span-2 text-right">
|
||||
%{((item.vatRate ?? 0) * 100).toFixed(0)} / {formatPrice(item.vatAmount ?? 0)}
|
||||
|
|
@ -177,7 +190,10 @@ export const OrderSuccess: React.FC<OrderSuccessProps> = ({ orderId, order }) =>
|
|||
<span>{formatPrice(order.subtotal)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{translate('::Public.products.kdv')}{vatRateLabel}</span>
|
||||
<span>
|
||||
{translate('::Public.products.kdv')}
|
||||
{vatRateLabel}
|
||||
</span>
|
||||
<span>{formatPrice(order.vatTotal)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ interface PaymentFormProps {
|
|||
}
|
||||
|
||||
export const PaymentForm: React.FC<PaymentFormProps> = ({ onBack, onComplete, basketData }) => {
|
||||
const defaultPaymentMethod = 'Kredi Kartı'
|
||||
const defaultPaymentMethod = 'CreditCard'
|
||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string>(defaultPaymentMethod)
|
||||
const [selectedInstallment, setSelectedInstallment] = useState<InstallmentOptionDto>()
|
||||
const [paymentData, setPaymentData] = useState({
|
||||
|
|
@ -390,7 +390,10 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({ onBack, onComplete, ba
|
|||
<span>{formatPrice(basketData.subtotal)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>{translate('::Public.products.kdv')}{vatRateLabel}</span>
|
||||
<span>
|
||||
{translate('::Public.products.kdv')}
|
||||
{vatRateLabel}
|
||||
</span>
|
||||
<span>{formatPrice(basketData.vatTotal)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
|
|
|
|||
|
|
@ -114,9 +114,13 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2 line-clamp-2 dark:text-gray-100">{translate('::' + product.name)}</h3>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2 line-clamp-2 dark:text-gray-100">
|
||||
{translate('::' + product.name)}
|
||||
</h3>
|
||||
|
||||
<p className="text-gray-600 text-sm mb-4 line-clamp-3 dark:text-gray-300">{translate('::' + product.description)}</p>
|
||||
<p className="text-gray-600 text-sm mb-4 line-clamp-3 dark:text-gray-300">
|
||||
{translate('::' + product.description)}
|
||||
</p>
|
||||
|
||||
{/* Quantity and Yearly Savings above price */}
|
||||
<div className="mb-4 space-y-3">
|
||||
|
|
@ -157,15 +161,21 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|||
<div className="mb-4">
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||||
{formatPrice(getCurrentPrice())}
|
||||
<span className="text-sm font-normal text-gray-500 ml-1 dark:text-gray-400">{getUnitText()}</span>
|
||||
<span className="text-sm font-normal text-gray-500 ml-1 dark:text-gray-400">
|
||||
{getUnitText()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{translate('::Public.products.kdv')} (%{(getVatRate() * 100).toFixed(0)}): {formatPrice(getVatAmount())}
|
||||
{translate('::Public.products.kdv')} (%{(getVatRate() * 100).toFixed(0)}):{' '}
|
||||
{formatPrice(getVatAmount())}
|
||||
</div>
|
||||
{globalPeriod > 1 && (
|
||||
<div className="text-lg font-semibold text-blue-600 mt-1">
|
||||
{translate('::App.Listform.ListformField.Total')} {formatPrice(getTotalPriceWithVat())}
|
||||
<span className="text-sm font-normal text-gray-500 ml-1 dark:text-gray-400">{getPeriodText()}</span>
|
||||
{translate('::App.Listform.ListformField.Total')}{' '}
|
||||
{formatPrice(getTotalPriceWithVat())}
|
||||
<span className="text-sm font-normal text-gray-500 ml-1 dark:text-gray-400">
|
||||
{getPeriodText()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{globalPeriod <= 1 && (
|
||||
|
|
@ -188,7 +198,9 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|||
size="md"
|
||||
className={!isDisabled ? 'hover:scale-[1.02] active:scale-[0.98]' : ''}
|
||||
>
|
||||
{isDisabled ? translate('::Public.products.inBasket') : translate('::Public.products.addToBasket')}
|
||||
{isDisabled
|
||||
? translate('::Public.products.inBasket')
|
||||
: translate('::Public.products.addToBasket')}
|
||||
</Button>
|
||||
)
|
||||
})()}
|
||||
|
|
|
|||
|
|
@ -91,8 +91,12 @@ const getNationalPhoneDigits = (value: string, country?: CountryDto) => {
|
|||
return digits
|
||||
}
|
||||
|
||||
const getPhoneValidationError = (value: string, country?: CountryDto) => {
|
||||
if (!country) return 'Lütfen önce ülke seçin.'
|
||||
const getPhoneValidationError = (
|
||||
value: string,
|
||||
country: CountryDto | undefined,
|
||||
translate: (key: string, params?: Record<string, string | number>) => string,
|
||||
) => {
|
||||
if (!country) return translate('::App.Tenant.SelectCountryFirst')
|
||||
|
||||
const length = getNationalPhoneDigits(value, country).length
|
||||
const minLength = getPhoneMinLength(country)
|
||||
|
|
@ -100,8 +104,8 @@ const getPhoneValidationError = (value: string, country?: CountryDto) => {
|
|||
if (length >= minLength && length <= maxLength) return ''
|
||||
|
||||
return minLength === maxLength
|
||||
? `Telefon numarası ${minLength} haneli olmalıdır.`
|
||||
: `Telefon numarası ${minLength}-${maxLength} hane arasında olmalıdır.`
|
||||
? translate('::App.Tenant.PhoneExactLength', { length: minLength })
|
||||
: translate('::App.Tenant.PhoneRangeLength', { min: minLength, max: maxLength })
|
||||
}
|
||||
|
||||
const formatPhoneNumber = (value: string, country?: CountryDto) => {
|
||||
|
|
@ -309,7 +313,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
|
||||
if (isExisting) {
|
||||
if (!formData.name?.trim()) {
|
||||
setExistingTenantError('Lütfen önce organization code girin.')
|
||||
setExistingTenantError(translate('::App.Tenant.EnterOrgCodeFirst'))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -318,12 +322,12 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
normalizeCode(foundTenantName) !== normalizeCode(formData.name) ||
|
||||
!formData.organizationName
|
||||
) {
|
||||
setExistingTenantError('Devam etmek için önce organization code ile kurum bilgisini bulun.')
|
||||
setExistingTenantError(translate('::App.Tenant.FindOrgBeforeContinue'))
|
||||
return
|
||||
}
|
||||
|
||||
if (!hasRequiredFields(formData, REQUIRED_EXISTING_CUSTOMER_FIELDS)) {
|
||||
setExistingTenantError('Kurum bilgileri eksik geldi. Lütfen kurum kodunu tekrar aratın.')
|
||||
setExistingTenantError(translate('::App.Tenant.OrgDataIncomplete'))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -338,23 +342,27 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
}
|
||||
|
||||
if (!hasRequiredFields(formData, REQUIRED_NEW_CUSTOMER_FIELDS)) {
|
||||
setFormError('Lütfen zorunlu alanları eksiksiz doldurun.')
|
||||
setFormError(translate('::App.Tenant.FillRequiredFields'))
|
||||
hasValidationError = true
|
||||
}
|
||||
|
||||
const nextPhoneError = getPhoneValidationError(formData.phoneNumber || '', selectedCountry)
|
||||
const nextPhoneError = getPhoneValidationError(
|
||||
formData.phoneNumber || '',
|
||||
selectedCountry,
|
||||
translate,
|
||||
)
|
||||
setPhoneError(nextPhoneError)
|
||||
if (nextPhoneError) {
|
||||
hasValidationError = true
|
||||
}
|
||||
|
||||
if (!isExisting && !EMAIL_PATTERN.test(formData.email || '')) {
|
||||
setEmailError('Lütfen geçerli bir e-posta adresi girin.')
|
||||
setEmailError(translate('::App.Platform.InvalidEmail'))
|
||||
hasValidationError = true
|
||||
}
|
||||
|
||||
if (!isExisting && !WEBSITE_PATTERN.test(formData.website || '')) {
|
||||
setWebsiteError('Lütfen geçerli bir web sitesi adresi girin.')
|
||||
setWebsiteError(translate('::App.Tenant.InvalidWebsite'))
|
||||
hasValidationError = true
|
||||
}
|
||||
|
||||
|
|
@ -499,7 +507,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
const getTenantInfo = async () => {
|
||||
const tenantName = formData.name?.trim()
|
||||
if (!tenantName) {
|
||||
setExistingTenantError('Lütfen önce organization code girin.')
|
||||
setExistingTenantError(translate('::App.Tenant.EnterOrgCodeFirst'))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -535,7 +543,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
if (tenant.data.name) setFoundTenantName(tenant.data.name)
|
||||
} catch (error) {
|
||||
setFoundTenantName('')
|
||||
setExistingTenantError('Kurum bulunamadı. Lütfen organization code bilgisini kontrol edin.')
|
||||
setExistingTenantError(translate('::App.Tenant.OrgNotFound'))
|
||||
console.error('Kurum bilgisi alınırken hata:', error)
|
||||
} finally {
|
||||
setIsLoadingTenant(false)
|
||||
|
|
@ -634,7 +642,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
className="!h-[40px] min-w-[180px] sm:mr-2 sm:rounded-r-lg sm:rounded-l-none [&>span]:gap-2"
|
||||
>
|
||||
{isLoadingTenant
|
||||
? 'Aranıyor...'
|
||||
? translate('::App.Tenant.Searching')
|
||||
: translate('::Public.products.tenantForm.searchOrg')}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -647,7 +655,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
!canSubmitExistingTenant &&
|
||||
!isLoadingTenant && (
|
||||
<p className="mt-2 text-sm text-amber-600">
|
||||
Devam etmek için önce Find Organization ile kurum bilgilerini getirin.
|
||||
{translate('::App.Tenant.FindOrgHint')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
|
@ -807,7 +815,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
countryOptions.find((option) => option.value === formData.country) || null
|
||||
}
|
||||
onChange={handleCountryChange}
|
||||
placeholder="Ülke seçin"
|
||||
placeholder={translate('::App.Tenant.SelectCountry')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -827,7 +835,11 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
options={cityOptions}
|
||||
value={cityOptions.find((option) => option.value === formData.city) || null}
|
||||
onChange={handleCityChange}
|
||||
placeholder={formData.country ? 'Şehir seçin' : 'Önce ülke seçin'}
|
||||
placeholder={translate(
|
||||
formData.country
|
||||
? '::App.Tenant.SelectCity'
|
||||
: '::App.Tenant.SelectCountryFirstShort',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -847,7 +859,11 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
null
|
||||
}
|
||||
onChange={handleDistrictChange}
|
||||
placeholder={formData.city ? 'İlçe seçin' : 'Önce şehir seçin'}
|
||||
placeholder={translate(
|
||||
formData.city
|
||||
? '::App.Tenant.SelectDistrict'
|
||||
: '::App.Tenant.SelectCityFirst',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -870,7 +886,11 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
) || null
|
||||
}
|
||||
onChange={handleTownshipChange}
|
||||
placeholder={formData.district ? 'Township seçin' : 'Önce ilçe seçin'}
|
||||
placeholder={translate(
|
||||
formData.district
|
||||
? '::App.Tenant.SelectTownship'
|
||||
: '::App.Tenant.SelectDistrictFirst',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -922,7 +942,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
required
|
||||
inputMode="numeric"
|
||||
placeholder={phoneMask.replace(/0/g, '_')}
|
||||
aria-label={`Telefon numarası, format: ${phoneMask}`}
|
||||
aria-label={translate('::App.Tenant.PhoneAria', { mask: phoneMask })}
|
||||
value={formData.phoneNumber || ''}
|
||||
onChange={(e) => {
|
||||
handleInputChange(
|
||||
|
|
@ -933,7 +953,11 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
|||
}}
|
||||
onBlur={() =>
|
||||
setPhoneError(
|
||||
getPhoneValidationError(formData.phoneNumber || '', selectedCountry),
|
||||
getPhoneValidationError(
|
||||
formData.phoneNumber || '',
|
||||
selectedCountry,
|
||||
translate,
|
||||
),
|
||||
)
|
||||
}
|
||||
aria-invalid={Boolean(phoneError)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Button, Dialog } from '@/components/ui'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import Editor, { type Monaco } from '@monaco-editor/react'
|
||||
import type * as monacoApi from 'monaco-editor'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
|
@ -69,9 +70,12 @@ function ScriptBuilderDialog({
|
|||
dialect,
|
||||
onClose,
|
||||
onApply,
|
||||
saveLabel = 'Kaydet',
|
||||
cancelLabel = 'Vazgeç',
|
||||
saveLabel,
|
||||
cancelLabel,
|
||||
}: ScriptBuilderDialogProps) {
|
||||
const { translate } = useLocalization()
|
||||
const saveText = saveLabel ?? translate('::Save')
|
||||
const cancelText = cancelLabel ?? translate('::Cancel')
|
||||
const [rules, setRules] = useState<ScriptRule[]>([])
|
||||
const [code, setCode] = useState('')
|
||||
// Sihirbaz bir butona basılarak açılır; varsayılan her zaman kod editörüdür.
|
||||
|
|
@ -169,7 +173,11 @@ function ScriptBuilderDialog({
|
|||
* Ek koşul kalmadığında `conditions` alanı tamamen kaldırılır — eski tek
|
||||
* koşullu kurallarla aynı JSON'a serileşmesi buna bağlı.
|
||||
*/
|
||||
const updateCondition = (rule: ScriptRule, index: number, patch: Partial<ScriptRuleCondition>) => {
|
||||
const updateCondition = (
|
||||
rule: ScriptRule,
|
||||
index: number,
|
||||
patch: Partial<ScriptRuleCondition>,
|
||||
) => {
|
||||
if (index === 0) {
|
||||
updateRule(rule.id, { condition: { ...rule.condition, ...patch } })
|
||||
return
|
||||
|
|
@ -191,7 +199,10 @@ function ScriptBuilderDialog({
|
|||
|
||||
const removeCondition = (rule: ScriptRule, index: number) => {
|
||||
const conditions = (rule.conditions ?? []).filter((_, item) => item !== index - 1)
|
||||
updateRule(rule.id, conditions.length ? { conditions } : { conditions: undefined, join: undefined })
|
||||
updateRule(
|
||||
rule.id,
|
||||
conditions.length ? { conditions } : { conditions: undefined, join: undefined },
|
||||
)
|
||||
}
|
||||
|
||||
const moveRule = (index: number, delta: number) =>
|
||||
|
|
@ -314,7 +325,7 @@ function ScriptBuilderDialog({
|
|||
updateRule(rule.id, { fields: [...selectedFields, next] })
|
||||
}}
|
||||
>
|
||||
<option value="">+ ekle</option>
|
||||
<option value="">{translate('::App.ScriptBuilder.AddOption')}</option>
|
||||
{options
|
||||
.filter((fieldName) => !selectedFields.includes(fieldName))
|
||||
.map((fieldName) => (
|
||||
|
|
@ -341,7 +352,7 @@ function ScriptBuilderDialog({
|
|||
param.optionSource,
|
||||
currentValue,
|
||||
(next) => updateParam(rule.id, param.key, next),
|
||||
param.placeholder || 'Seç',
|
||||
param.placeholder || translate('::App.ScriptBuilder.Choose'),
|
||||
)}
|
||||
{param.type === 'select' && (
|
||||
<select
|
||||
|
|
@ -384,7 +395,9 @@ function ScriptBuilderDialog({
|
|||
<div key={index} className="grid grid-cols-12 items-end gap-2">
|
||||
{index > 0 ? (
|
||||
<label className="col-span-12 min-w-0 md:col-span-2">
|
||||
<span className="mb-1 block text-xs text-gray-500">Bağlaç</span>
|
||||
<span className="mb-1 block text-xs text-gray-500">
|
||||
{translate('::App.ScriptBuilder.Conjunction')}
|
||||
</span>
|
||||
<select
|
||||
className={controlClass}
|
||||
value={rule.join ?? 'and'}
|
||||
|
|
@ -392,19 +405,25 @@ function ScriptBuilderDialog({
|
|||
updateRule(rule.id, { join: event.target.value as ScriptRule['join'] })
|
||||
}
|
||||
>
|
||||
<option value="and">VE</option>
|
||||
<option value="or">VEYA</option>
|
||||
<option value="and">{translate('::App.ScriptBuilder.And')}</option>
|
||||
<option value="or">{translate('::App.ScriptBuilder.Or')}</option>
|
||||
</select>
|
||||
</label>
|
||||
) : (
|
||||
<div className="col-span-12 hidden md:col-span-2 md:block">
|
||||
<span className="mb-1 block text-xs text-gray-500">Koşul</span>
|
||||
<div className="flex h-9 items-center text-xs font-semibold text-gray-400">EĞER</div>
|
||||
<span className="mb-1 block text-xs text-gray-500">
|
||||
{translate('::App.ScriptBuilder.Condition')}
|
||||
</span>
|
||||
<div className="flex h-9 items-center text-xs font-semibold text-gray-400">
|
||||
{translate('::App.ScriptBuilder.If')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label className="col-span-12 min-w-0 md:col-span-3">
|
||||
<span className="mb-1 block text-xs text-gray-500">Karşılaştırma</span>
|
||||
<span className="mb-1 block text-xs text-gray-500">
|
||||
{translate('::App.ScriptBuilder.Comparison')}
|
||||
</span>
|
||||
<select
|
||||
className={controlClass}
|
||||
value={condition.operator}
|
||||
|
|
@ -412,7 +431,7 @@ function ScriptBuilderDialog({
|
|||
>
|
||||
{dialect.operators.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
{item.label.startsWith('App.') ? translate('::' + item.label) : item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
|
@ -420,7 +439,9 @@ function ScriptBuilderDialog({
|
|||
|
||||
{operator?.needsSource && dialect.conditionKinds.length > 1 && (
|
||||
<label className="col-span-12 min-w-0 md:col-span-2">
|
||||
<span className="mb-1 block text-xs text-gray-500">Kaynak</span>
|
||||
<span className="mb-1 block text-xs text-gray-500">
|
||||
{translate('::App.ScriptBuilder.Source')}
|
||||
</span>
|
||||
<select
|
||||
className={controlClass}
|
||||
value={condition.kind ?? dialect.conditionKinds[0]?.value ?? ''}
|
||||
|
|
@ -439,19 +460,23 @@ function ScriptBuilderDialog({
|
|||
|
||||
{operator?.needsSource && (
|
||||
<label className="col-span-12 min-w-0 md:col-span-3">
|
||||
<span className="mb-1 block text-xs text-gray-500">Alan</span>
|
||||
<span className="mb-1 block text-xs text-gray-500">
|
||||
{translate('::App.ScriptBuilder.Field')}
|
||||
</span>
|
||||
{renderOptionInput(
|
||||
kind?.optionSource,
|
||||
condition.source,
|
||||
(next) => updateCondition(rule, index, { source: next }),
|
||||
kind?.placeholder || 'Seç',
|
||||
kind?.placeholder || translate('::App.ScriptBuilder.Choose'),
|
||||
)}
|
||||
</label>
|
||||
)}
|
||||
|
||||
{operator?.needsValue && (
|
||||
<label className="col-span-12 min-w-0 md:col-span-2">
|
||||
<span className="mb-1 block text-xs text-gray-500">Değer</span>
|
||||
<span className="mb-1 block text-xs text-gray-500">
|
||||
{translate('::App.ScriptBuilder.Value')}
|
||||
</span>
|
||||
<input
|
||||
className={controlClass}
|
||||
value={condition.value}
|
||||
|
|
@ -468,7 +493,7 @@ function ScriptBuilderDialog({
|
|||
size="xs"
|
||||
type="button"
|
||||
variant="plain"
|
||||
title="Koşulu kaldır"
|
||||
title={translate('::App.ScriptBuilder.RemoveCondition')}
|
||||
icon={<FaTimes />}
|
||||
onClick={() => removeCondition(rule, index)}
|
||||
/>
|
||||
|
|
@ -511,7 +536,8 @@ function ScriptBuilderDialog({
|
|||
</div>
|
||||
{conditions.length > 1 && (
|
||||
<span className="shrink-0 rounded-full bg-violet-100 px-2 py-[1px] text-[11px] text-violet-700 dark:bg-violet-900 dark:text-violet-200">
|
||||
{conditions.length} koşul · {rule.join === 'or' ? 'VEYA' : 'VE'}
|
||||
{translate('::App.ScriptBuilder.ConditionCount', { count: conditions.length })} ·{' '}
|
||||
{translate(rule.join === 'or' ? '::App.ScriptBuilder.Or' : '::App.ScriptBuilder.And')}
|
||||
</span>
|
||||
)}
|
||||
{triggerInfo && trigger !== dialect.triggers?.[0]?.value && (
|
||||
|
|
@ -527,10 +553,10 @@ function ScriptBuilderDialog({
|
|||
className="flex shrink-0 items-center gap-1 rounded-full bg-amber-100 px-2 py-[1px] text-[11px] text-amber-700 dark:bg-amber-900 dark:text-amber-200"
|
||||
title={
|
||||
dialect.getRuleWarning?.(rule) ||
|
||||
'Zorunlu alanlar dolmadığı için bu kural script’e eklenmez.'
|
||||
translate('::App.ScriptBuilder.IncompleteRuleWarning')
|
||||
}
|
||||
>
|
||||
<FaExclamationTriangle size={9} /> eksik
|
||||
<FaExclamationTriangle size={9} /> {translate('::App.ScriptBuilder.Missing')}
|
||||
</span>
|
||||
)}
|
||||
<div className="flex shrink-0 items-center">
|
||||
|
|
@ -539,7 +565,7 @@ function ScriptBuilderDialog({
|
|||
size="xs"
|
||||
type="button"
|
||||
variant="plain"
|
||||
title="Yukarı taşı"
|
||||
title={translate('::App.Platform.MoveUp')}
|
||||
icon={<FaArrowUp />}
|
||||
onClick={() => moveRule(index, -1)}
|
||||
/>
|
||||
|
|
@ -548,7 +574,7 @@ function ScriptBuilderDialog({
|
|||
size="xs"
|
||||
type="button"
|
||||
variant="plain"
|
||||
title="Aşağı taşı"
|
||||
title={translate('::App.Platform.MoveDown')}
|
||||
icon={<FaArrowDown />}
|
||||
onClick={() => moveRule(index, 1)}
|
||||
/>
|
||||
|
|
@ -557,7 +583,7 @@ function ScriptBuilderDialog({
|
|||
size="xs"
|
||||
type="button"
|
||||
variant="plain"
|
||||
title="Kuralı sil"
|
||||
title={translate('::App.ScriptBuilder.DeleteRule')}
|
||||
icon={<FaTrash />}
|
||||
onClick={() => touchRules((current) => current.filter((item) => item.id !== rule.id))}
|
||||
/>
|
||||
|
|
@ -595,10 +621,10 @@ function ScriptBuilderDialog({
|
|||
size="xs"
|
||||
type="button"
|
||||
icon={<FaPlus />}
|
||||
title="Aynı kurala ikinci bir koşul ekler; VE/VEYA ile birleşir."
|
||||
title={translate('::App.ScriptBuilder.AddConditionHint')}
|
||||
onClick={() => addCondition(rule)}
|
||||
>
|
||||
Koşul ekle
|
||||
{translate('::App.ScriptBuilder.AddCondition')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -622,7 +648,7 @@ function ScriptBuilderDialog({
|
|||
<FaSearch className="text-gray-400" size={11} />
|
||||
<input
|
||||
className="w-full min-w-0 bg-transparent text-xs text-gray-700 outline-none dark:text-gray-100"
|
||||
placeholder="Kısayol veya snippet ara"
|
||||
placeholder={translate('::App.ScriptBuilder.SearchHelp')}
|
||||
value={helpSearch}
|
||||
onChange={(event) => setHelpSearch(event.target.value)}
|
||||
/>
|
||||
|
|
@ -631,7 +657,7 @@ function ScriptBuilderDialog({
|
|||
size="xs"
|
||||
type="button"
|
||||
variant="plain"
|
||||
title="Paneli kapat"
|
||||
title={translate('::App.Platform.ClosePanel')}
|
||||
icon={<FaTimes />}
|
||||
onClick={() => setHelpOpen(false)}
|
||||
/>
|
||||
|
|
@ -656,7 +682,9 @@ function ScriptBuilderDialog({
|
|||
</button>
|
||||
))}
|
||||
{!filteredSnippets.length && (
|
||||
<span className="text-[11px] text-gray-400">Eşleşen snippet yok.</span>
|
||||
<span className="text-[11px] text-gray-400">
|
||||
{translate('::App.ScriptBuilder.NoMatchingSnippet')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-gray-400">
|
||||
|
|
@ -678,7 +706,9 @@ function ScriptBuilderDialog({
|
|||
</button>
|
||||
))}
|
||||
{!filteredApi.length && (
|
||||
<span className="text-[11px] text-gray-400">Eşleşen kısayol yok.</span>
|
||||
<span className="text-[11px] text-gray-400">
|
||||
{translate('::App.ScriptBuilder.NoMatchingShortcut')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -728,15 +758,15 @@ function ScriptBuilderDialog({
|
|||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
title="Kısayol ve snippet panelini aç"
|
||||
title={translate('::App.ScriptBuilder.OpenHelpPanel')}
|
||||
icon={<FaBook />}
|
||||
onClick={() => setHelpOpen(true)}
|
||||
>
|
||||
Yardım
|
||||
{translate('::App.ScriptBuilder.Help')}
|
||||
</Button>
|
||||
)}
|
||||
<Button size="sm" type="button" icon={<FaCopy />} onClick={copyScript}>
|
||||
{copied ? 'Kopyalandı' : 'Kopyala'}
|
||||
{translate(copied ? '::App.Platform.Copied' : '::App.SqlQueryManager.Copy')}
|
||||
</Button>
|
||||
<div className="flex overflow-hidden rounded-md border border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
|
|
@ -757,10 +787,10 @@ function ScriptBuilderDialog({
|
|||
? 'bg-indigo-500 text-white'
|
||||
: 'text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||
}`}
|
||||
title="Kod yazmadan kural kurmak için sihirbazı aç"
|
||||
title={translate('::App.ScriptBuilder.OpenWizardHint')}
|
||||
onClick={openWizard}
|
||||
>
|
||||
<FaMagic /> Sihirbaz
|
||||
<FaMagic /> {translate('::App.ScriptBuilder.Wizard')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -769,12 +799,9 @@ function ScriptBuilderDialog({
|
|||
{view === 'wizard' && codeDiverged && (
|
||||
<div className="flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-700 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-200">
|
||||
<FaExclamationTriangle className="shrink-0" />
|
||||
<span className="flex-1">
|
||||
Koddaki script kurallardan farklı. Burada bir kuralı değiştirdiğin anda kod
|
||||
kurallardan yeniden üretilir.
|
||||
</span>
|
||||
<span className="flex-1">{translate('::App.ScriptBuilder.CodeDivergedWarning')}</span>
|
||||
<Button size="xs" type="button" onClick={() => setView('code')}>
|
||||
Kodu gör
|
||||
{translate('::App.ScriptBuilder.ShowCode')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -789,11 +816,11 @@ function ScriptBuilderDialog({
|
|||
disabled={!generatedScript}
|
||||
size="xs"
|
||||
type="button"
|
||||
title="Sihirbazdaki kurallardan script'i yeniden üretir."
|
||||
title={translate('::App.ScriptBuilder.RegenerateHint')}
|
||||
icon={<FaMagic />}
|
||||
onClick={() => setCode(generatedScript)}
|
||||
>
|
||||
Kurallardan üret
|
||||
{translate('::App.ScriptBuilder.Regenerate')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -824,7 +851,9 @@ function ScriptBuilderDialog({
|
|||
{paletteOpen ? <FaChevronDown size={11} /> : <FaChevronRight size={11} />}
|
||||
</span>
|
||||
<FaPlus className="text-indigo-500" size={12} />
|
||||
<span className="flex-1 text-sm font-semibold">Kural Ekle</span>
|
||||
<span className="flex-1 text-sm font-semibold">
|
||||
{translate('::App.ScriptBuilder.AddRule')}
|
||||
</span>
|
||||
</button>
|
||||
{paletteOpen && (
|
||||
<div className="flex flex-col gap-3 border-t border-gray-100 px-3 py-3 dark:border-gray-800">
|
||||
|
|
@ -848,7 +877,7 @@ function ScriptBuilderDialog({
|
|||
key={recipe.id}
|
||||
size="sm"
|
||||
type="button"
|
||||
title={`${recipe.summary}\n\nÖrnek: ${recipe.example}`}
|
||||
title={`${recipe.summary}\n\n${translate('::App.ScriptBuilder.Example')}: ${recipe.example}`}
|
||||
onClick={() => addRule(recipe.id)}
|
||||
>
|
||||
{recipe.label}
|
||||
|
|
@ -866,8 +895,7 @@ function ScriptBuilderDialog({
|
|||
|
||||
{!rules.length && (
|
||||
<div className="rounded-lg border border-dashed border-gray-300 p-8 text-center text-xs text-gray-400 dark:border-gray-700">
|
||||
Henüz kural yok. Yukarıdan bir kural ekle; her kural tek satırlık script üretir ve
|
||||
kod sekmesine anında yansır.
|
||||
{translate('::App.ScriptBuilder.NoRulesYet')}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
|
@ -876,10 +904,14 @@ function ScriptBuilderDialog({
|
|||
<div className="mb-3 flex items-center justify-between gap-2">
|
||||
<span className="flex items-center gap-2 text-sm font-semibold">
|
||||
<FaCode className="text-gray-400" />
|
||||
Script Önizleme
|
||||
{translate('::App.ScriptBuilder.ScriptPreview')}
|
||||
</span>
|
||||
<span className="rounded-full bg-gray-100 px-2 py-[2px] text-[11px] text-gray-500 dark:bg-gray-800 dark:text-gray-300">
|
||||
{generatedScript ? `${generatedScript.split('\n').length - 1} satır` : 'boş'}
|
||||
{generatedScript
|
||||
? translate('::App.ScriptBuilder.LineCount', {
|
||||
count: generatedScript.split('\n').length - 1,
|
||||
})
|
||||
: translate('::ListForms.EditorOptions.Empty')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
|
|
@ -887,13 +919,16 @@ function ScriptBuilderDialog({
|
|||
height="100%"
|
||||
language="javascript"
|
||||
theme="vs-dark"
|
||||
value={generatedScript || '// Kural ekledikçe script burada oluşur'}
|
||||
value={
|
||||
generatedScript || `// ${translate('::App.ScriptBuilder.EmptyScriptHint')}`
|
||||
}
|
||||
options={{ ...editorOptions, readOnly: true }}
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-2 text-[11px] text-gray-400">
|
||||
İlk satırdaki <code>{'// @builder'}</code> yorumu kuralları saklar; dialog yeniden
|
||||
açıldığında sihirbaz kuralları buradan geri yükler.
|
||||
{translate('::App.ScriptBuilder.BuilderCommentHintPrefix')}{' '}
|
||||
<code>{'// @builder'}</code>{' '}
|
||||
{translate('::App.ScriptBuilder.BuilderCommentHintSuffix')}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
|
@ -902,7 +937,7 @@ function ScriptBuilderDialog({
|
|||
|
||||
<Dialog.Footer className="mt-1 flex justify-end gap-2 border-t border-gray-200 pt-3 dark:border-gray-700">
|
||||
<Button size="sm" type="button" variant="plain" icon={<FaTimes />} onClick={onClose}>
|
||||
{cancelLabel}
|
||||
{cancelText}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
|
|
@ -914,7 +949,7 @@ function ScriptBuilderDialog({
|
|||
onClose()
|
||||
}}
|
||||
>
|
||||
{saveLabel}
|
||||
{saveText}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -161,13 +161,38 @@ export const scriptRuleConditions = (rule: ScriptRule): ScriptRuleCondition[] =>
|
|||
|
||||
export const DEFAULT_SCRIPT_OPERATORS: ScriptOperator[] = [
|
||||
{ value: 'always', label: 'Her zaman' },
|
||||
{ value: 'equals', label: 'Eşitse', needsValue: true, needsSource: true },
|
||||
{ value: 'notEquals', label: 'Eşit değilse', needsValue: true, needsSource: true },
|
||||
{ value: 'contains', label: 'İçeriyorsa', needsValue: true, needsSource: true },
|
||||
{ value: 'empty', label: 'Boşsa', needsSource: true },
|
||||
{
|
||||
value: 'equals',
|
||||
label: 'App.ScriptBuilder.Operator.Equals',
|
||||
needsValue: true,
|
||||
needsSource: true,
|
||||
},
|
||||
{
|
||||
value: 'notEquals',
|
||||
label: 'App.ScriptBuilder.Operator.NotEquals',
|
||||
needsValue: true,
|
||||
needsSource: true,
|
||||
},
|
||||
{
|
||||
value: 'contains',
|
||||
label: 'App.ScriptBuilder.Operator.Contains',
|
||||
needsValue: true,
|
||||
needsSource: true,
|
||||
},
|
||||
{ value: 'empty', label: 'App.ScriptBuilder.Operator.Empty', needsSource: true },
|
||||
{ value: 'notEmpty', label: 'Doluysa', needsSource: true },
|
||||
{ value: 'greaterThan', label: 'Büyükse', needsValue: true, needsSource: true },
|
||||
{ value: 'lessThan', label: 'Küçükse', needsValue: true, needsSource: true },
|
||||
{ value: 'isTrue', label: 'İşaretliyse', needsSource: true },
|
||||
{ value: 'isFalse', label: 'İşaretli değilse', needsSource: true },
|
||||
{
|
||||
value: 'greaterThan',
|
||||
label: 'App.ScriptBuilder.Operator.GreaterThan',
|
||||
needsValue: true,
|
||||
needsSource: true,
|
||||
},
|
||||
{
|
||||
value: 'lessThan',
|
||||
label: 'App.ScriptBuilder.Operator.LessThan',
|
||||
needsValue: true,
|
||||
needsSource: true,
|
||||
},
|
||||
{ value: 'isTrue', label: 'App.ScriptBuilder.Operator.IsTrue', needsSource: true },
|
||||
{ value: 'isFalse', label: 'App.ScriptBuilder.Operator.IsFalse', needsSource: true },
|
||||
]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const DbMigrateButton = ({ hideLabelOnMobile = false, className }: DbMigrateButt
|
|||
return null
|
||||
}
|
||||
|
||||
const label = translate('::ListForms.ListForm.DbMigrate') || 'DB Migrate'
|
||||
const label = translate('::ListForms.ListForm.DbMigrate')
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -39,7 +39,7 @@ const DbMigrateButton = ({ hideLabelOnMobile = false, className }: DbMigrateButt
|
|||
icon={<FcAcceptDatabase />}
|
||||
className={className}
|
||||
onClick={() => setIsConfirmOpen(true)}
|
||||
title={translate('::App.DbMigrate.StartMessage') || 'Run DB Migration'}
|
||||
title={translate('::App.DbMigrate.StartMessage')}
|
||||
>
|
||||
{hideLabelOnMobile ? <span className="hidden sm:inline">{label}</span> : label}
|
||||
</Button>
|
||||
|
|
@ -49,7 +49,7 @@ const DbMigrateButton = ({ hideLabelOnMobile = false, className }: DbMigrateButt
|
|||
type="info"
|
||||
title={label}
|
||||
cancelText={translate('::Cancel')}
|
||||
confirmText={translate('::App.Platform.Execute') || 'Çalıştır'}
|
||||
confirmText={translate('::App.Platform.Execute')}
|
||||
onCancel={() => setIsConfirmOpen(false)}
|
||||
onClose={() => setIsConfirmOpen(false)}
|
||||
onConfirm={() => {
|
||||
|
|
@ -58,8 +58,7 @@ const DbMigrateButton = ({ hideLabelOnMobile = false, className }: DbMigrateButt
|
|||
}}
|
||||
>
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
{translate('::App.DbMigrate.ConfirmMessage') ||
|
||||
'Are you sure you want to start the database migration process?'}
|
||||
{translate('::App.DbMigrate.ConfirmMessage')}
|
||||
</p>
|
||||
</ConfirmDialog>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -44,9 +44,7 @@ const TenantSelector = () => {
|
|||
setTenantName(undefined)
|
||||
const parts = window.location.hostname.split('.')
|
||||
const mainDomain = parts.length >= 3 ? parts.slice(1).join('.') : window.location.hostname
|
||||
setWarningTimeout(
|
||||
`"${name}" kurumuna ait kayıt bulunamadı.\nAna sayfaya yönlendiriliyorsunuz...`,
|
||||
)
|
||||
setWarningTimeout(translate('::App.Tenant.NotFoundRedirect', { name }))
|
||||
setTimeout(() => {
|
||||
window.location.href = `${window.location.protocol}//${mainDomain}`
|
||||
}, 3000)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { useInputGroup } from '../InputGroup/context'
|
|||
import { CONTROL_SIZES } from '../utils/constants'
|
||||
import { Spinner } from '../Spinner'
|
||||
import type { CommonProps, TypeAttributes } from '../@types/common'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
// ── Tipler ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -73,15 +74,10 @@ export interface AutoCompleteProps extends CommonProps {
|
|||
|
||||
function defaultFilter(options: AutoCompleteOption[], query: string) {
|
||||
const lower = query.toLowerCase()
|
||||
return options.filter(
|
||||
(o) => !o.disabled && o.label.toLowerCase().includes(lower),
|
||||
)
|
||||
return options.filter((o) => !o.disabled && o.label.toLowerCase().includes(lower))
|
||||
}
|
||||
|
||||
function useDebounce<T extends (...args: Parameters<T>) => void>(
|
||||
fn: T,
|
||||
delay: number,
|
||||
) {
|
||||
function useDebounce<T extends (...args: Parameters<T>) => void>(fn: T, delay: number) {
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
return useCallback(
|
||||
(...args: Parameters<T>) => {
|
||||
|
|
@ -94,8 +90,8 @@ function useDebounce<T extends (...args: Parameters<T>) => void>(
|
|||
|
||||
// ── Komponent ────────────────────────────────────────────────────────────────
|
||||
|
||||
const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>(
|
||||
(props, ref) => {
|
||||
const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>((props, ref) => {
|
||||
const { translate } = useLocalization()
|
||||
const {
|
||||
className,
|
||||
style,
|
||||
|
|
@ -109,8 +105,8 @@ const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>(
|
|||
fetchOptions,
|
||||
debounce: debounceMs = 300,
|
||||
renderOption,
|
||||
noOptionsText = 'Seçenek bulunamadı',
|
||||
loadingText = 'Yükleniyor...',
|
||||
noOptionsText,
|
||||
loadingText,
|
||||
onInputChange,
|
||||
onSelect,
|
||||
clearable = true,
|
||||
|
|
@ -127,9 +123,7 @@ const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>(
|
|||
)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [activeIndex, setActiveIndex] = useState(-1)
|
||||
const [filteredOptions, setFilteredOptions] = useState<
|
||||
AutoCompleteOption[]
|
||||
>([])
|
||||
const [filteredOptions, setFilteredOptions] = useState<AutoCompleteOption[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
|
@ -139,8 +133,7 @@ const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>(
|
|||
const { themeColor, primaryColorLevel, controlSize } = useConfig()
|
||||
const formControlSize = useForm()?.size
|
||||
const inputGroupSize = useInputGroup()?.size
|
||||
const resolvedSize =
|
||||
size || inputGroupSize || formControlSize || controlSize
|
||||
const resolvedSize = size || inputGroupSize || formControlSize || controlSize
|
||||
const sizeClass = `h-${CONTROL_SIZES[resolvedSize]}`
|
||||
|
||||
// Sync controlled value
|
||||
|
|
@ -156,10 +149,7 @@ const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>(
|
|||
// Dışarı tıkla → kapat
|
||||
useEffect(() => {
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(e.target as Node)
|
||||
) {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
||||
setOpen(false)
|
||||
setActiveIndex(-1)
|
||||
}
|
||||
|
|
@ -237,9 +227,7 @@ const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>(
|
|||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault()
|
||||
setActiveIndex((i) =>
|
||||
Math.min(i + 1, filteredOptions.length - 1),
|
||||
)
|
||||
setActiveIndex((i) => Math.min(i + 1, filteredOptions.length - 1))
|
||||
break
|
||||
case 'ArrowUp':
|
||||
e.preventDefault()
|
||||
|
|
@ -280,11 +268,7 @@ const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>(
|
|||
const invalidClass = 'border-red-500 focus:ring-red-500'
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={classNames('autocomplete', className)}
|
||||
style={style}
|
||||
>
|
||||
<div ref={containerRef} className={classNames('autocomplete', className)} style={style}>
|
||||
<div className="autocomplete-input-wrapper">
|
||||
<input
|
||||
ref={ref}
|
||||
|
|
@ -298,38 +282,27 @@ const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>(
|
|||
sizeClass,
|
||||
invalid ? invalidClass : ringClass,
|
||||
disabled && 'autocomplete-input-disabled',
|
||||
clearable &&
|
||||
inputValue &&
|
||||
'autocomplete-input-clearable',
|
||||
clearable && inputValue && 'autocomplete-input-clearable',
|
||||
)}
|
||||
role="combobox"
|
||||
aria-autocomplete="list"
|
||||
aria-expanded={open}
|
||||
aria-controls={listboxId}
|
||||
aria-activedescendant={
|
||||
activeIndex >= 0
|
||||
? `autocomplete-opt-${activeIndex}`
|
||||
: undefined
|
||||
}
|
||||
aria-activedescendant={activeIndex >= 0 ? `autocomplete-opt-${activeIndex}` : undefined}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
onKeyDown={handleKeyDown}
|
||||
{...rest}
|
||||
/>
|
||||
<div className="autocomplete-suffix">
|
||||
{loading && (
|
||||
<Spinner
|
||||
size={14}
|
||||
className="autocomplete-spinner"
|
||||
/>
|
||||
)}
|
||||
{loading && <Spinner size={14} className="autocomplete-spinner" />}
|
||||
{clearable && inputValue && !loading && (
|
||||
<button
|
||||
type="button"
|
||||
className="autocomplete-clear"
|
||||
onClick={handleClear}
|
||||
tabIndex={-1}
|
||||
aria-label="Temizle"
|
||||
aria-label={translate('::App.DeveloperKit.CrudEndpoints.Clear')}
|
||||
>
|
||||
<svg
|
||||
width={12}
|
||||
|
|
@ -348,19 +321,14 @@ const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>(
|
|||
</div>
|
||||
|
||||
{open && (
|
||||
<ul
|
||||
ref={listRef}
|
||||
id={listboxId}
|
||||
role="listbox"
|
||||
className="autocomplete-dropdown"
|
||||
>
|
||||
<ul ref={listRef} id={listboxId} role="listbox" className="autocomplete-dropdown">
|
||||
{loading ? (
|
||||
<li className="autocomplete-option autocomplete-option-info">
|
||||
{loadingText}
|
||||
{loadingText ?? translate('::App.Loading')}
|
||||
</li>
|
||||
) : filteredOptions.length === 0 ? (
|
||||
<li className="autocomplete-option autocomplete-option-info">
|
||||
{noOptionsText}
|
||||
{noOptionsText ?? translate('::App.AutoComplete.NoOptions')}
|
||||
</li>
|
||||
) : (
|
||||
filteredOptions.map((option, index) => (
|
||||
|
|
@ -372,21 +340,16 @@ const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>(
|
|||
aria-disabled={option.disabled}
|
||||
className={classNames(
|
||||
'autocomplete-option',
|
||||
activeIndex === index &&
|
||||
'autocomplete-option-active',
|
||||
option.disabled &&
|
||||
'autocomplete-option-disabled',
|
||||
activeIndex === index && 'autocomplete-option-active',
|
||||
option.disabled && 'autocomplete-option-disabled',
|
||||
)}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => {
|
||||
if (!option.disabled)
|
||||
commitSelection(option)
|
||||
if (!option.disabled) commitSelection(option)
|
||||
}}
|
||||
onMouseEnter={() => setActiveIndex(index)}
|
||||
>
|
||||
{renderOption
|
||||
? renderOption(option, activeIndex === index)
|
||||
: option.label}
|
||||
{renderOption ? renderOption(option, activeIndex === index) : option.label}
|
||||
</li>
|
||||
))
|
||||
)}
|
||||
|
|
@ -394,8 +357,7 @@ const AutoComplete = forwardRef<HTMLInputElement, AutoCompleteProps>(
|
|||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
AutoComplete.displayName = 'AutoComplete'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
import React, {
|
||||
Children,
|
||||
cloneElement,
|
||||
forwardRef,
|
||||
isValidElement,
|
||||
type ReactNode,
|
||||
} from 'react'
|
||||
import React, { Children, cloneElement, forwardRef, isValidElement, type ReactNode } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
export interface BreadcrumbItemProps extends CommonProps {
|
||||
/** Tıklanabilir link href */
|
||||
|
|
@ -32,8 +27,7 @@ export interface BreadcrumbProps extends CommonProps {
|
|||
size?: 'sm' | 'md' | 'lg'
|
||||
}
|
||||
|
||||
export const BreadcrumbItem = forwardRef<HTMLLIElement, BreadcrumbItemProps>(
|
||||
(props, ref) => {
|
||||
export const BreadcrumbItem = forwardRef<HTMLLIElement, BreadcrumbItemProps>((props, ref) => {
|
||||
const {
|
||||
className,
|
||||
children,
|
||||
|
|
@ -51,11 +45,7 @@ export const BreadcrumbItem = forwardRef<HTMLLIElement, BreadcrumbItemProps>(
|
|||
return (
|
||||
<li
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
'breadcrumb-item',
|
||||
active && 'breadcrumb-item-active',
|
||||
className,
|
||||
)}
|
||||
className={classNames('breadcrumb-item', active && 'breadcrumb-item-active', className)}
|
||||
style={style}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
{...rest}
|
||||
|
|
@ -64,26 +54,22 @@ export const BreadcrumbItem = forwardRef<HTMLLIElement, BreadcrumbItemProps>(
|
|||
href={href}
|
||||
className={classNames(
|
||||
'breadcrumb-link',
|
||||
active
|
||||
? 'breadcrumb-link-active'
|
||||
: 'breadcrumb-link-default',
|
||||
active ? 'breadcrumb-link-active' : 'breadcrumb-link-default',
|
||||
onClick && !active && 'cursor-pointer',
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{icon && (
|
||||
<span className="breadcrumb-icon">{icon}</span>
|
||||
)}
|
||||
{icon && <span className="breadcrumb-icon">{icon}</span>}
|
||||
{children}
|
||||
</Tag>
|
||||
</li>
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
BreadcrumbItem.displayName = 'BreadcrumbItem'
|
||||
|
||||
const Breadcrumb = forwardRef<HTMLElement, BreadcrumbProps>((props, ref) => {
|
||||
const { translate } = useLocalization()
|
||||
const {
|
||||
className,
|
||||
children,
|
||||
|
|
@ -103,17 +89,12 @@ const Breadcrumb = forwardRef<HTMLElement, BreadcrumbProps>((props, ref) => {
|
|||
|
||||
if (maxItems && total > maxItems) {
|
||||
collapsedCount = total - maxItems
|
||||
visibleItems = [
|
||||
items[0],
|
||||
...items.slice(total - (maxItems - 1)),
|
||||
]
|
||||
visibleItems = [items[0], ...items.slice(total - (maxItems - 1))]
|
||||
}
|
||||
|
||||
const renderedItems = visibleItems.map((item, index) => {
|
||||
const isLast =
|
||||
maxItems && collapsedCount > 0
|
||||
? index === visibleItems.length - 1
|
||||
: index === total - 1
|
||||
maxItems && collapsedCount > 0 ? index === visibleItems.length - 1 : index === total - 1
|
||||
|
||||
const cloned = cloneElement(item as React.ReactElement<BreadcrumbItemProps>, {
|
||||
active: isLast,
|
||||
|
|
@ -123,27 +104,19 @@ const Breadcrumb = forwardRef<HTMLElement, BreadcrumbProps>((props, ref) => {
|
|||
<span key={index} className="breadcrumb-entry" style={{ gap }}>
|
||||
{index === 1 && collapsedCount > 0 && (
|
||||
<>
|
||||
<span
|
||||
className="breadcrumb-separator"
|
||||
aria-hidden
|
||||
style={{ marginInline: gap }}
|
||||
>
|
||||
<span className="breadcrumb-separator" aria-hidden style={{ marginInline: gap }}>
|
||||
{separator}
|
||||
</span>
|
||||
<span
|
||||
className="breadcrumb-collapsed"
|
||||
title={`${collapsedCount} öğe gizlendi`}
|
||||
title={translate('::App.Breadcrumb.CollapsedCount', { count: collapsedCount })}
|
||||
>
|
||||
...
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{index > 0 && (
|
||||
<span
|
||||
className="breadcrumb-separator"
|
||||
aria-hidden
|
||||
style={{ marginInline: gap }}
|
||||
>
|
||||
<span className="breadcrumb-separator" aria-hidden style={{ marginInline: gap }}>
|
||||
{separator}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -156,11 +129,7 @@ const Breadcrumb = forwardRef<HTMLElement, BreadcrumbProps>((props, ref) => {
|
|||
<nav
|
||||
ref={ref}
|
||||
aria-label="breadcrumb"
|
||||
className={classNames(
|
||||
'breadcrumb-nav',
|
||||
`breadcrumb-${size}`,
|
||||
className,
|
||||
)}
|
||||
className={classNames('breadcrumb-nav', `breadcrumb-${size}`, className)}
|
||||
style={style}
|
||||
{...rest}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { useForm } from '../Form/context'
|
|||
import { useInputGroup } from '../InputGroup/context'
|
||||
import { CONTROL_SIZES } from '../utils/constants'
|
||||
import type { CommonProps, TypeAttributes } from '../@types/common'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
export interface ChipsProps extends CommonProps {
|
||||
/** Kontrollü değer */
|
||||
|
|
@ -50,6 +51,7 @@ export interface ChipsProps extends CommonProps {
|
|||
}
|
||||
|
||||
const Chips = forwardRef<HTMLDivElement, ChipsProps>((props, ref) => {
|
||||
const { translate } = useLocalization()
|
||||
const {
|
||||
className,
|
||||
style,
|
||||
|
|
@ -208,18 +210,26 @@ const Chips = forwardRef<HTMLDivElement, ChipsProps>((props, ref) => {
|
|||
onKeyDown={(e) => handleChipKeyDown(e, i)}
|
||||
data-focused={focusedChip === i}
|
||||
>
|
||||
<span className="chips-item-label">
|
||||
{itemTemplate ? itemTemplate(chip) : chip}
|
||||
</span>
|
||||
<span className="chips-item-label">{itemTemplate ? itemTemplate(chip) : chip}</span>
|
||||
{!disabled && (
|
||||
<button
|
||||
type="button"
|
||||
className="chips-item-remove"
|
||||
onClick={(e) => { e.stopPropagation(); removeChip(i) }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
removeChip(i)
|
||||
}}
|
||||
tabIndex={-1}
|
||||
aria-label={`${chip} kaldır`}
|
||||
aria-label={translate('::App.Chips.RemoveChip', { chip })}
|
||||
>
|
||||
<svg
|
||||
width={10}
|
||||
height={10}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2.5}
|
||||
>
|
||||
<svg width={10} height={10} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5}>
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
|
|
@ -237,7 +247,7 @@ const Chips = forwardRef<HTMLDivElement, ChipsProps>((props, ref) => {
|
|||
placeholder={chips.length === 0 ? placeholder : undefined}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
aria-label="Chip ekle"
|
||||
aria-label={translate('::App.Chips.AddChip')}
|
||||
onChange={(e) => setInputVal(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={() => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import React, {
|
|||
} from 'react'
|
||||
import classNames from 'classnames'
|
||||
import type { CommonProps, TypeAttributes } from '../@types/common'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
// ── Hex/RGB yardımcı fonksiyonlar ───────────────────────────────────────────
|
||||
|
||||
|
|
@ -31,10 +32,7 @@ const hexToRgb = (hex: string): { r: number; g: number; b: number } | null => {
|
|||
|
||||
const rgbToHex = (r: number, g: number, b: number): string => {
|
||||
return (
|
||||
'#' +
|
||||
[r, g, b]
|
||||
.map((v) => Math.min(255, Math.max(0, v)).toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
'#' + [r, g, b].map((v) => Math.min(255, Math.max(0, v)).toString(16).padStart(2, '0')).join('')
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +70,7 @@ export interface ColorPickerProps extends CommonProps {
|
|||
// ── Bileşen ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const ColorPicker = forwardRef<HTMLDivElement, ColorPickerProps>((props, ref) => {
|
||||
const { translate } = useLocalization()
|
||||
const {
|
||||
className,
|
||||
style,
|
||||
|
|
@ -106,7 +105,7 @@ const ColorPicker = forwardRef<HTMLDivElement, ColorPickerProps>((props, ref) =>
|
|||
const nativeRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const currentHex = (() => {
|
||||
const src = isControlled ? valueProp! : fieldValue ?? internalHex
|
||||
const src = isControlled ? valueProp! : (fieldValue ?? internalHex)
|
||||
return isValidHex(src) ? src : internalHex
|
||||
})()
|
||||
|
||||
|
|
@ -119,10 +118,7 @@ const ColorPicker = forwardRef<HTMLDivElement, ColorPickerProps>((props, ref) =>
|
|||
useEffect(() => {
|
||||
if (!open) return
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(e.target as Node)
|
||||
) {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
||||
setOpen(false)
|
||||
}
|
||||
}
|
||||
|
|
@ -187,9 +183,7 @@ const ColorPicker = forwardRef<HTMLDivElement, ColorPickerProps>((props, ref) =>
|
|||
return (
|
||||
<div
|
||||
ref={(node) => {
|
||||
;(
|
||||
containerRef as React.MutableRefObject<HTMLDivElement | null>
|
||||
).current = node
|
||||
;(containerRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
||||
if (typeof ref === 'function') ref(node)
|
||||
else if (ref) ref.current = node
|
||||
}}
|
||||
|
|
@ -213,13 +207,10 @@ const ColorPicker = forwardRef<HTMLDivElement, ColorPickerProps>((props, ref) =>
|
|||
{/* Tetikleyici swatch */}
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
'color-picker-swatch',
|
||||
disabled && 'color-picker-disabled',
|
||||
)}
|
||||
className={classNames('color-picker-swatch', disabled && 'color-picker-disabled')}
|
||||
style={{ backgroundColor: currentHex }}
|
||||
disabled={disabled}
|
||||
aria-label="Renk seç"
|
||||
aria-label={translate('::App.ColorPicker.PickColor')}
|
||||
onClick={() => {
|
||||
if (disabled) return
|
||||
setOpen((o) => !o)
|
||||
|
|
@ -248,8 +239,7 @@ const ColorPicker = forwardRef<HTMLDivElement, ColorPickerProps>((props, ref) =>
|
|||
type="button"
|
||||
className={classNames(
|
||||
'color-picker-preset-dot',
|
||||
currentHex.toLowerCase() ===
|
||||
color.toLowerCase() &&
|
||||
currentHex.toLowerCase() === color.toLowerCase() &&
|
||||
'color-picker-preset-dot-active',
|
||||
)}
|
||||
style={{ backgroundColor: color }}
|
||||
|
|
@ -287,13 +277,9 @@ const ColorPicker = forwardRef<HTMLDivElement, ColorPickerProps>((props, ref) =>
|
|||
min={0}
|
||||
max={255}
|
||||
value={rgb[ch]}
|
||||
onChange={(e) =>
|
||||
handleRgbChange(ch, e.target.value)
|
||||
}
|
||||
onChange={(e) => handleRgbChange(ch, e.target.value)}
|
||||
/>
|
||||
<span className="color-picker-input-label">
|
||||
{ch.toUpperCase()}
|
||||
</span>
|
||||
<span className="color-picker-input-label">{ch.toUpperCase()}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
import { createPortal } from 'react-dom'
|
||||
import classNames from 'classnames'
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
// ── Tipler ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -138,6 +139,7 @@ const ImageViewerOverlay = ({
|
|||
onClose: () => void
|
||||
onIndexChange: (i: number) => void
|
||||
}) => {
|
||||
const { translate } = useLocalization()
|
||||
const [zoom, setZoom] = useState(1)
|
||||
const [rotation, setRotation] = useState(0)
|
||||
const [dragging, setDragging] = useState(false)
|
||||
|
|
@ -156,7 +158,9 @@ const ImageViewerOverlay = ({
|
|||
}, [])
|
||||
|
||||
// Reset on image change
|
||||
useEffect(() => { resetTransform() }, [index, resetTransform])
|
||||
useEffect(() => {
|
||||
resetTransform()
|
||||
}, [index, resetTransform])
|
||||
|
||||
const goNext = useCallback(() => {
|
||||
if (!hasNext) return
|
||||
|
|
@ -168,10 +172,14 @@ const ImageViewerOverlay = ({
|
|||
onIndexChange(loop ? (index - 1 + images.length) % images.length : index - 1)
|
||||
}, [hasPrev, loop, index, images.length, onIndexChange])
|
||||
|
||||
const zoomIn = useCallback(() =>
|
||||
setZoom((z) => Math.min(z + zoomStep, maxZoom)), [zoomStep, maxZoom])
|
||||
const zoomOut = useCallback(() =>
|
||||
setZoom((z) => Math.max(z - zoomStep, minZoom)), [zoomStep, minZoom])
|
||||
const zoomIn = useCallback(
|
||||
() => setZoom((z) => Math.min(z + zoomStep, maxZoom)),
|
||||
[zoomStep, maxZoom],
|
||||
)
|
||||
const zoomOut = useCallback(
|
||||
() => setZoom((z) => Math.max(z - zoomStep, minZoom)),
|
||||
[zoomStep, minZoom],
|
||||
)
|
||||
const rotateCW = useCallback(() => setRotation((r) => r + 90), [])
|
||||
const rotateCCW = useCallback(() => setRotation((r) => r - 90), [])
|
||||
|
||||
|
|
@ -184,9 +192,7 @@ const ImageViewerOverlay = ({
|
|||
const mimeMatch = src.match(/^data:([^;,]+)/)
|
||||
const mime = mimeMatch?.[1] ?? 'image/png'
|
||||
const ext = mime.split('/')[1]?.replace('jpeg', 'jpg') ?? 'png'
|
||||
const baseName = current.alt
|
||||
? current.alt.replace(/\.[^.]+$/, '')
|
||||
: 'image'
|
||||
const baseName = current.alt ? current.alt.replace(/\.[^.]+$/, '') : 'image'
|
||||
const a = document.createElement('a')
|
||||
a.href = src
|
||||
a.download = `${baseName}.${ext}`
|
||||
|
|
@ -208,8 +214,7 @@ const ImageViewerOverlay = ({
|
|||
a.download = fileName
|
||||
// Aynı origin değilse fetch + blob ile zorla indir
|
||||
try {
|
||||
const isSameOrigin =
|
||||
new URL(src, window.location.href).origin === window.location.origin
|
||||
const isSameOrigin = new URL(src, window.location.href).origin === window.location.origin
|
||||
if (!isSameOrigin) {
|
||||
fetch(src)
|
||||
.then((r) => r.blob())
|
||||
|
|
@ -221,7 +226,9 @@ const ImageViewerOverlay = ({
|
|||
})
|
||||
return
|
||||
}
|
||||
} catch { /* origin parse hatası — direkt dene */ }
|
||||
} catch {
|
||||
/* origin parse hatası — direkt dene */
|
||||
}
|
||||
a.click()
|
||||
}
|
||||
}, [current])
|
||||
|
|
@ -230,13 +237,28 @@ const ImageViewerOverlay = ({
|
|||
useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft': goPrev(); break
|
||||
case 'ArrowRight': goNext(); break
|
||||
case 'Escape': onClose(); break
|
||||
case '+': case '=': zoomIn(); break
|
||||
case '-': zoomOut(); break
|
||||
case 'r': rotateCW(); break
|
||||
case 'R': rotateCCW(); break
|
||||
case 'ArrowLeft':
|
||||
goPrev()
|
||||
break
|
||||
case 'ArrowRight':
|
||||
goNext()
|
||||
break
|
||||
case 'Escape':
|
||||
onClose()
|
||||
break
|
||||
case '+':
|
||||
case '=':
|
||||
zoomIn()
|
||||
break
|
||||
case '-':
|
||||
zoomOut()
|
||||
break
|
||||
case 'r':
|
||||
rotateCW()
|
||||
break
|
||||
case 'R':
|
||||
rotateCCW()
|
||||
break
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', handler)
|
||||
|
|
@ -264,7 +286,10 @@ const ImageViewerOverlay = ({
|
|||
y: dragStart.current.oy + (e.clientY - dragStart.current.y),
|
||||
})
|
||||
}
|
||||
const handleMouseUp = () => { setDragging(false); dragStart.current = null }
|
||||
const handleMouseUp = () => {
|
||||
setDragging(false)
|
||||
dragStart.current = null
|
||||
}
|
||||
|
||||
const overlayClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||
if (e.target === e.currentTarget) onClose()
|
||||
|
|
@ -275,7 +300,7 @@ const ImageViewerOverlay = ({
|
|||
className="image-viewer-overlay"
|
||||
role="dialog"
|
||||
aria-modal
|
||||
aria-label={current.alt ?? 'Resim görüntüleyici'}
|
||||
aria-label={current.alt ?? translate('::App.ImageViewer.Title')}
|
||||
>
|
||||
{/* Toolbar */}
|
||||
{showToolbar && (
|
||||
|
|
@ -283,33 +308,42 @@ const ImageViewerOverlay = ({
|
|||
<span className="image-viewer-counter">
|
||||
{index + 1} / {images.length}
|
||||
</span>
|
||||
{current.caption && (
|
||||
<span className="image-viewer-caption">{current.caption}</span>
|
||||
)}
|
||||
{current.caption && <span className="image-viewer-caption">{current.caption}</span>}
|
||||
<div className="image-viewer-toolbar-actions">
|
||||
{toolbarExtra}
|
||||
<button type="button" onClick={zoomOut} title="Uzaklaştır (-)">
|
||||
<button type="button" onClick={zoomOut} title={translate('::App.Platform.ZoomOut')}>
|
||||
<IconZoomOut />
|
||||
</button>
|
||||
<span className="image-viewer-zoom-label">
|
||||
{Math.round(zoom * 100)}%
|
||||
</span>
|
||||
<button type="button" onClick={zoomIn} title="Yaklaştır (+)">
|
||||
<span className="image-viewer-zoom-label">{Math.round(zoom * 100)}%</span>
|
||||
<button type="button" onClick={zoomIn} title={translate('::App.Platform.ZoomIn')}>
|
||||
<IconZoomIn />
|
||||
</button>
|
||||
<button type="button" onClick={resetTransform} title="Sıfırla">
|
||||
<button type="button" onClick={resetTransform} title={translate('::Reset')}>
|
||||
<IconReset />
|
||||
</button>
|
||||
<button type="button" onClick={rotateCCW} title="Sola döndür (R)">
|
||||
<button
|
||||
type="button"
|
||||
onClick={rotateCCW}
|
||||
title={translate('::App.ImageViewer.RotateLeft')}
|
||||
>
|
||||
<IconRotateCCW />
|
||||
</button>
|
||||
<button type="button" onClick={rotateCW} title="Sağa döndür (r)">
|
||||
<button
|
||||
type="button"
|
||||
onClick={rotateCW}
|
||||
title={translate('::App.ImageViewer.RotateRight')}
|
||||
>
|
||||
<IconRotateCW />
|
||||
</button>
|
||||
<button type="button" onClick={handleDownload} title="İndir">
|
||||
<button type="button" onClick={handleDownload} title={translate('::Download')}>
|
||||
<IconDownload />
|
||||
</button>
|
||||
<button type="button" onClick={onClose} title="Kapat (Esc)" className="image-viewer-close">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
title={translate('::App.Platform.CloseWithEsc')}
|
||||
className="image-viewer-close"
|
||||
>
|
||||
<IconClose />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -318,10 +352,7 @@ const ImageViewerOverlay = ({
|
|||
|
||||
{/* Stage */}
|
||||
<div
|
||||
className={classNames(
|
||||
'image-viewer-stage',
|
||||
dragging && 'image-viewer-dragging',
|
||||
)}
|
||||
className={classNames('image-viewer-stage', dragging && 'image-viewer-dragging')}
|
||||
onClick={overlayClick}
|
||||
onWheel={handleWheel}
|
||||
onMouseDown={handleMouseDown}
|
||||
|
|
@ -348,7 +379,7 @@ const ImageViewerOverlay = ({
|
|||
type="button"
|
||||
className="image-viewer-nav image-viewer-nav-prev"
|
||||
onClick={goPrev}
|
||||
aria-label="Önceki"
|
||||
aria-label={translate('::App.VisualDesigner.Canvas.Previous')}
|
||||
>
|
||||
<IconPrev />
|
||||
</button>
|
||||
|
|
@ -358,7 +389,7 @@ const ImageViewerOverlay = ({
|
|||
type="button"
|
||||
className="image-viewer-nav image-viewer-nav-next"
|
||||
onClick={goNext}
|
||||
aria-label="Sonraki"
|
||||
aria-label={translate('::App.VisualDesigner.Canvas.Next')}
|
||||
>
|
||||
<IconNext />
|
||||
</button>
|
||||
|
|
@ -376,14 +407,10 @@ const ImageViewerOverlay = ({
|
|||
'image-viewer-thumb',
|
||||
i === index && 'image-viewer-thumb-active',
|
||||
)}
|
||||
aria-label={img.alt ?? `Resim ${i + 1}`}
|
||||
aria-label={img.alt ?? translate('::App.ImageViewer.ImageN', { index: i + 1 })}
|
||||
aria-pressed={i === index}
|
||||
>
|
||||
<img
|
||||
src={img.thumbnail ?? img.src}
|
||||
alt={img.alt ?? ''}
|
||||
draggable={false}
|
||||
/>
|
||||
<img src={img.thumbnail ?? img.src} alt={img.alt ?? ''} draggable={false} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -395,6 +422,7 @@ const ImageViewerOverlay = ({
|
|||
// ── Ana Komponent ────────────────────────────────────────────────────────────
|
||||
|
||||
const ImageViewer = forwardRef<HTMLDivElement, ImageViewerProps>((props, ref) => {
|
||||
const { translate } = useLocalization()
|
||||
const {
|
||||
className,
|
||||
style,
|
||||
|
|
@ -421,8 +449,7 @@ const ImageViewer = forwardRef<HTMLDivElement, ImageViewerProps>((props, ref) =>
|
|||
const [internalIndex, setInternalIndex] = useState(defaultIndex)
|
||||
|
||||
const isOpen = isControlled ? openProp! : internalOpen
|
||||
const currentIndex =
|
||||
activeIndexProp !== undefined ? activeIndexProp : internalIndex
|
||||
const currentIndex = activeIndexProp !== undefined ? activeIndexProp : internalIndex
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
if (!isControlled) setInternalOpen(false)
|
||||
|
|
@ -463,13 +490,9 @@ const ImageViewer = forwardRef<HTMLDivElement, ImageViewerProps>((props, ref) =>
|
|||
type="button"
|
||||
className="image-viewer-trigger-item"
|
||||
onClick={() => openAt(i)}
|
||||
aria-label={img.alt ?? `Resim ${i + 1}`}
|
||||
aria-label={img.alt ?? translate('::App.ImageViewer.ImageN', { index: i + 1 })}
|
||||
>
|
||||
<img
|
||||
src={img.thumbnail ?? img.src}
|
||||
alt={img.alt ?? ''}
|
||||
draggable={false}
|
||||
/>
|
||||
<img src={img.thumbnail ?? img.src} alt={img.alt ?? ''} draggable={false} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -52,11 +52,9 @@ const KNOB_START_ANGLE = -220 // derece (saat 7 hizası)
|
|||
const KNOB_END_ANGLE = 40 // derece (saat 5 hizası)
|
||||
const KNOB_RANGE = KNOB_END_ANGLE - KNOB_START_ANGLE // 260 derece toplam yay
|
||||
|
||||
const clamp = (v: number, min: number, max: number) =>
|
||||
Math.min(max, Math.max(min, v))
|
||||
const clamp = (v: number, min: number, max: number) => Math.min(max, Math.max(min, v))
|
||||
|
||||
const snap = (v: number, step: number, min: number) =>
|
||||
Math.round((v - min) / step) * step + min
|
||||
const snap = (v: number, step: number, min: number) => Math.round((v - min) / step) * step + min
|
||||
|
||||
const Knob = forwardRef<SVGSVGElement, KnobProps>((props, ref) => {
|
||||
const {
|
||||
|
|
@ -93,13 +91,10 @@ const Knob = forwardRef<SVGSVGElement, KnobProps>((props, ref) => {
|
|||
}, [isControlled, valueProp, min, max])
|
||||
|
||||
useEffect(() => {
|
||||
if (field?.value !== undefined)
|
||||
setInternalValue(clamp(field.value, min, max))
|
||||
if (field?.value !== undefined) setInternalValue(clamp(field.value, min, max))
|
||||
}, [field?.value, min, max])
|
||||
|
||||
const currentValue = isControlled
|
||||
? clamp(valueProp!, min, max)
|
||||
: internalValue
|
||||
const currentValue = isControlled ? clamp(valueProp!, min, max) : internalValue
|
||||
|
||||
const commit = useCallback(
|
||||
(raw: number) => {
|
||||
|
|
@ -119,7 +114,7 @@ const Knob = forwardRef<SVGSVGElement, KnobProps>((props, ref) => {
|
|||
|
||||
const valueRatio = (currentValue - min) / (max - min)
|
||||
const valueAngleDeg = KNOB_START_ANGLE + valueRatio * KNOB_RANGE
|
||||
const valueArcLength = (valueRatio * KNOB_RANGE / 360) * circumference
|
||||
const valueArcLength = ((valueRatio * KNOB_RANGE) / 360) * circumference
|
||||
const fullArcLength = (KNOB_RANGE / 360) * circumference
|
||||
|
||||
// Çember yayını SVG stroke-dashoffset ile çiziyoruz
|
||||
|
|
@ -129,8 +124,7 @@ const Knob = forwardRef<SVGSVGElement, KnobProps>((props, ref) => {
|
|||
|
||||
const resolveValueColor = () =>
|
||||
valueColor ?? `var(--color-${themeColor}-${primaryColorLevel}, #6366f1)`
|
||||
const resolveRangeColor = () =>
|
||||
rangeColor ?? `currentColor`
|
||||
const resolveRangeColor = () => rangeColor ?? `currentColor`
|
||||
const resolveTextColor = () => textColor ?? undefined
|
||||
|
||||
const label = valueTemplate.replace('{value}', String(currentValue))
|
||||
|
|
@ -150,8 +144,7 @@ const Knob = forwardRef<SVGSVGElement, KnobProps>((props, ref) => {
|
|||
[min, max],
|
||||
)
|
||||
|
||||
const getAngleFromPointer = useCallback(
|
||||
(clientX: number, clientY: number) => {
|
||||
const getAngleFromPointer = useCallback((clientX: number, clientY: number) => {
|
||||
const el = svgRef.current
|
||||
if (!el) return 0
|
||||
const rect = el.getBoundingClientRect()
|
||||
|
|
@ -166,9 +159,7 @@ const Knob = forwardRef<SVGSVGElement, KnobProps>((props, ref) => {
|
|||
// Döngü düzeltmesi
|
||||
}
|
||||
return angle
|
||||
},
|
||||
[],
|
||||
)
|
||||
}, [])
|
||||
|
||||
const handlePointerDown = (e: ReactPointerEvent<SVGSVGElement>) => {
|
||||
if (disabled || readOnly) return
|
||||
|
|
@ -230,9 +221,7 @@ const Knob = forwardRef<SVGSVGElement, KnobProps>((props, ref) => {
|
|||
|
||||
return (
|
||||
<span className={classNames('knob-wrapper', className)} style={style}>
|
||||
{name && (
|
||||
<input type="hidden" name={name ?? field?.name} value={currentValue} />
|
||||
)}
|
||||
{name && <input type="hidden" name={name ?? field?.name} value={currentValue} />}
|
||||
<svg
|
||||
ref={(node) => {
|
||||
;(svgRef as React.MutableRefObject<SVGSVGElement | null>).current = node
|
||||
|
|
|
|||
|
|
@ -1,11 +1,4 @@
|
|||
import React, {
|
||||
forwardRef,
|
||||
useRef,
|
||||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
Fragment,
|
||||
} from 'react'
|
||||
import React, { forwardRef, useRef, useState, useEffect, useCallback, Fragment } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import type { CSSProperties } from 'react'
|
||||
|
|
@ -75,12 +68,8 @@ const Marquee = forwardRef<HTMLDivElement, MarqueeProps>((props, ref) => {
|
|||
|
||||
const calculateWidth = useCallback(() => {
|
||||
if (trackRef.current && containerRef.current) {
|
||||
const tw = isVertical
|
||||
? trackRef.current.offsetHeight
|
||||
: trackRef.current.offsetWidth
|
||||
const cw = isVertical
|
||||
? containerRef.current.offsetHeight
|
||||
: containerRef.current.offsetWidth
|
||||
const tw = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth
|
||||
const cw = isVertical ? containerRef.current.offsetHeight : containerRef.current.offsetWidth
|
||||
|
||||
if (autoFill && tw > 0) {
|
||||
setMultiplier(Math.max(Math.ceil((cw * 2) / tw), 2))
|
||||
|
|
@ -123,9 +112,7 @@ const Marquee = forwardRef<HTMLDivElement, MarqueeProps>((props, ref) => {
|
|||
...gradientStyle,
|
||||
...style,
|
||||
['--marquee-gradient-width' as string]:
|
||||
typeof gradientWidth === 'number'
|
||||
? `${gradientWidth}px`
|
||||
: gradientWidth,
|
||||
typeof gradientWidth === 'number' ? `${gradientWidth}px` : gradientWidth,
|
||||
}
|
||||
|
||||
const animationStyle: CSSProperties = {
|
||||
|
|
@ -159,8 +146,7 @@ const Marquee = forwardRef<HTMLDivElement, MarqueeProps>((props, ref) => {
|
|||
return (
|
||||
<div
|
||||
ref={(node) => {
|
||||
;(containerRef as React.MutableRefObject<HTMLDivElement | null>).current =
|
||||
node
|
||||
;(containerRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
||||
if (typeof ref === 'function') {
|
||||
ref(node)
|
||||
} else if (ref) {
|
||||
|
|
@ -183,11 +169,8 @@ const Marquee = forwardRef<HTMLDivElement, MarqueeProps>((props, ref) => {
|
|||
className={classNames(
|
||||
'marquee-track',
|
||||
animationClass,
|
||||
(pauseOnHover || isPaused) &&
|
||||
'marquee-pause-on-hover',
|
||||
(pauseOnClick || isPaused) &&
|
||||
isPaused &&
|
||||
'marquee-paused',
|
||||
(pauseOnHover || isPaused) && 'marquee-pause-on-hover',
|
||||
(pauseOnClick || isPaused) && isPaused && 'marquee-paused',
|
||||
)}
|
||||
style={animationStyle}
|
||||
onAnimationIteration={handleAnimationIteration}
|
||||
|
|
@ -201,8 +184,7 @@ const Marquee = forwardRef<HTMLDivElement, MarqueeProps>((props, ref) => {
|
|||
className={classNames(
|
||||
'marquee-track',
|
||||
animationClass,
|
||||
(pauseOnHover || isPaused) &&
|
||||
'marquee-pause-on-hover',
|
||||
(pauseOnHover || isPaused) && 'marquee-pause-on-hover',
|
||||
isPaused && 'marquee-paused',
|
||||
)}
|
||||
style={animationStyle}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ export interface NotificationProps extends CommonProps {
|
|||
width?: number | string
|
||||
}
|
||||
|
||||
const Notification = forwardRef<HTMLDivElement, NotificationProps>(
|
||||
(props, ref) => {
|
||||
const Notification = forwardRef<HTMLDivElement, NotificationProps>((props, ref) => {
|
||||
const {
|
||||
className,
|
||||
children,
|
||||
|
|
@ -38,11 +37,7 @@ const Notification = forwardRef<HTMLDivElement, NotificationProps>(
|
|||
// tamamlandıktan sonra kendilerini DOM'dan kaldırır.
|
||||
const [hidden, setHidden] = useState(false)
|
||||
|
||||
const { clear } = useTimeout(
|
||||
onClose as () => void,
|
||||
duration,
|
||||
duration > 0
|
||||
)
|
||||
const { clear } = useTimeout(onClose as () => void, duration, duration > 0)
|
||||
|
||||
const handleClose = useCallback(
|
||||
(e: MouseEvent<HTMLSpanElement>) => {
|
||||
|
|
@ -54,7 +49,7 @@ const Notification = forwardRef<HTMLDivElement, NotificationProps>(
|
|||
}, 400)
|
||||
}
|
||||
},
|
||||
[onClose, clear, triggerByToast]
|
||||
[onClose, clear, triggerByToast],
|
||||
)
|
||||
|
||||
const notificationClass = classNames('notification', className)
|
||||
|
|
@ -64,18 +59,8 @@ const Notification = forwardRef<HTMLDivElement, NotificationProps>(
|
|||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
{...rest}
|
||||
className={notificationClass}
|
||||
style={{ width: width, ...style }}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'notification-content',
|
||||
!children && 'no-child'
|
||||
)}
|
||||
>
|
||||
<div ref={ref} {...rest} className={notificationClass} style={{ width: width, ...style }}>
|
||||
<div className={classNames('notification-content', !children && 'no-child')}>
|
||||
{type && !customIcon ? (
|
||||
<div className="mr-3">
|
||||
<StatusIcon type={type} />
|
||||
|
|
@ -84,18 +69,9 @@ const Notification = forwardRef<HTMLDivElement, NotificationProps>(
|
|||
{customIcon && <div className="mr-3">{customIcon}</div>}
|
||||
<div className="mr-4">
|
||||
{title && (
|
||||
<div
|
||||
className={classNames(
|
||||
'notification-title',
|
||||
!!children && 'mb-1'
|
||||
<div className={classNames('notification-title', !!children && 'mb-1')}>{title}</div>
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div className="notification-description">
|
||||
{children}
|
||||
</div>
|
||||
<div className="notification-description">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
{closable && (
|
||||
|
|
@ -108,8 +84,7 @@ const Notification = forwardRef<HTMLDivElement, NotificationProps>(
|
|||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
Notification.displayName = 'Notification'
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import React, {
|
|||
import classNames from 'classnames'
|
||||
import { useConfig } from '../ConfigProvider'
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
export interface RateProps extends CommonProps {
|
||||
/** Toplam yıldız sayısı. Varsayılan: 5 */
|
||||
|
|
@ -59,13 +60,7 @@ const StarIcon = ({ filled, half, size }: { filled: boolean; half: boolean; size
|
|||
)}
|
||||
<polygon
|
||||
points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26"
|
||||
fill={
|
||||
half
|
||||
? 'url(#rate-half)'
|
||||
: filled
|
||||
? 'currentColor'
|
||||
: 'none'
|
||||
}
|
||||
fill={half ? 'url(#rate-half)' : filled ? 'currentColor' : 'none'}
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
|
|
@ -75,6 +70,7 @@ const StarIcon = ({ filled, half, size }: { filled: boolean; half: boolean; size
|
|||
)
|
||||
|
||||
const Rate = forwardRef<HTMLDivElement, RateProps>((props, ref) => {
|
||||
const { translate } = useLocalization()
|
||||
const {
|
||||
className,
|
||||
style,
|
||||
|
|
@ -183,9 +179,7 @@ const Rate = forwardRef<HTMLDivElement, RateProps>((props, ref) => {
|
|||
const starNode =
|
||||
typeof character === 'function'
|
||||
? character(index)
|
||||
: character ?? (
|
||||
<StarIcon filled={filled} half={half} size={size} />
|
||||
)
|
||||
: (character ?? <StarIcon filled={filled} half={half} size={size} />)
|
||||
|
||||
return (
|
||||
<span
|
||||
|
|
@ -202,7 +196,7 @@ const Rate = forwardRef<HTMLDivElement, RateProps>((props, ref) => {
|
|||
onClick={(e) => handleClick(index, e)}
|
||||
role={!disabled && !readOnly ? 'radio' : undefined}
|
||||
aria-checked={displayValue >= index + 1}
|
||||
aria-label={tooltip ?? `${index + 1} yıldız`}
|
||||
aria-label={tooltip ?? translate('::App.Rate.StarCount', { count: index + 1 })}
|
||||
>
|
||||
{starNode}
|
||||
</span>
|
||||
|
|
@ -218,7 +212,7 @@ const Rate = forwardRef<HTMLDivElement, RateProps>((props, ref) => {
|
|||
onKeyDown={handleKeyDown}
|
||||
tabIndex={disabled || readOnly ? undefined : 0}
|
||||
role="radiogroup"
|
||||
aria-label="Değerlendirme"
|
||||
aria-label={translate('::App.Rate.Label')}
|
||||
{...rest}
|
||||
>
|
||||
{Array.from({ length: count }, (_, i) => renderStar(i))}
|
||||
|
|
|
|||
|
|
@ -51,8 +51,7 @@ export interface SliderProps extends CommonProps {
|
|||
}
|
||||
|
||||
const clamp = (v: number, min: number, max: number) => Math.min(max, Math.max(min, v))
|
||||
const snap = (v: number, step: number, min: number) =>
|
||||
Math.round((v - min) / step) * step + min
|
||||
const snap = (v: number, step: number, min: number) => Math.round((v - min) / step) * step + min
|
||||
|
||||
const Slider = forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
|
||||
const {
|
||||
|
|
@ -106,8 +105,7 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
|
|||
|
||||
const currentValue = isControlled ? valueProp! : internalValue
|
||||
|
||||
const toArray = (v: SliderValue): [number, number] =>
|
||||
Array.isArray(v) ? v : [v, v]
|
||||
const toArray = (v: SliderValue): [number, number] => (Array.isArray(v) ? v : [v, v])
|
||||
|
||||
const commit = useCallback(
|
||||
(next: SliderValue, final = false) => {
|
||||
|
|
@ -138,10 +136,7 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
|
|||
[isVertical, min, max, step],
|
||||
)
|
||||
|
||||
const handlePointerDown = (
|
||||
e: ReactPointerEvent<HTMLDivElement>,
|
||||
handle: 0 | 1,
|
||||
) => {
|
||||
const handlePointerDown = (e: ReactPointerEvent<HTMLDivElement>, handle: 0 | 1) => {
|
||||
if (disabled || readOnly) return
|
||||
e.currentTarget.setPointerCapture(e.pointerId)
|
||||
activeHandle.current = handle
|
||||
|
|
@ -160,9 +155,7 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
|
|||
if (range) {
|
||||
const [a, b] = toArray(currentValue)
|
||||
const next: [number, number] =
|
||||
activeHandle.current === 0
|
||||
? [Math.min(newVal, b), b]
|
||||
: [a, Math.max(newVal, a)]
|
||||
activeHandle.current === 0 ? [Math.min(newVal, b), b] : [a, Math.max(newVal, a)]
|
||||
commit(next)
|
||||
} else {
|
||||
commit(newVal)
|
||||
|
|
@ -219,19 +212,32 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
|
|||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
case 'ArrowUp':
|
||||
e.preventDefault(); next = clamp(snap(cur + step, step, min), min, max); break
|
||||
e.preventDefault()
|
||||
next = clamp(snap(cur + step, step, min), min, max)
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
case 'ArrowDown':
|
||||
e.preventDefault(); next = clamp(snap(cur - step, step, min), min, max); break
|
||||
e.preventDefault()
|
||||
next = clamp(snap(cur - step, step, min), min, max)
|
||||
break
|
||||
case 'Home':
|
||||
e.preventDefault(); next = min; break
|
||||
e.preventDefault()
|
||||
next = min
|
||||
break
|
||||
case 'End':
|
||||
e.preventDefault(); next = max; break
|
||||
e.preventDefault()
|
||||
next = max
|
||||
break
|
||||
case 'PageUp':
|
||||
e.preventDefault(); next = clamp(snap(cur + step * 10, step, min), min, max); break
|
||||
e.preventDefault()
|
||||
next = clamp(snap(cur + step * 10, step, min), min, max)
|
||||
break
|
||||
case 'PageDown':
|
||||
e.preventDefault(); next = clamp(snap(cur - step * 10, step, min), min, max); break
|
||||
default: return
|
||||
e.preventDefault()
|
||||
next = clamp(snap(cur - step * 10, step, min), min, max)
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
if (range) {
|
||||
const result: [number, number] =
|
||||
|
|
@ -263,7 +269,8 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
|
|||
: { left: `calc(${p1}% - ${handleSize / 2}px)` }
|
||||
|
||||
// Marks hesapla
|
||||
const resolvedMarks = marks === true
|
||||
const resolvedMarks =
|
||||
marks === true
|
||||
? Array.from({ length: Math.floor((max - min) / step) + 1 }, (_, i) => ({
|
||||
value: min + i * step,
|
||||
label: `${min + i * step}`,
|
||||
|
|
@ -281,11 +288,7 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
|
|||
return (
|
||||
<div
|
||||
key={handle}
|
||||
className={classNames(
|
||||
'slider-handle',
|
||||
ringClass,
|
||||
disabled && 'slider-handle-disabled',
|
||||
)}
|
||||
className={classNames('slider-handle', ringClass, disabled && 'slider-handle-disabled')}
|
||||
style={{ ...hStyle, width: handleSize, height: handleSize }}
|
||||
role="slider"
|
||||
tabIndex={disabled || readOnly ? -1 : 0}
|
||||
|
|
@ -299,15 +302,22 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
|
|||
onPointerUp={handlePointerUp}
|
||||
onKeyDown={(e) => handleKeyDown(e, handle)}
|
||||
onFocus={() =>
|
||||
tooltip && setShowTooltip((t) => { const n = [...t]; n[handle] = true; return n })
|
||||
tooltip &&
|
||||
setShowTooltip((t) => {
|
||||
const n = [...t]
|
||||
n[handle] = true
|
||||
return n
|
||||
})
|
||||
}
|
||||
onBlur={() =>
|
||||
setShowTooltip((t) => { const n = [...t]; n[handle] = false; return n })
|
||||
setShowTooltip((t) => {
|
||||
const n = [...t]
|
||||
n[handle] = false
|
||||
return n
|
||||
})
|
||||
}
|
||||
>
|
||||
{(tooltip || show) && show && (
|
||||
<div className="slider-tooltip">{val}</div>
|
||||
)}
|
||||
{(tooltip || show) && show && <div className="slider-tooltip">{val}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -334,11 +344,7 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
|
|||
<div
|
||||
ref={trackRef}
|
||||
className="slider-track"
|
||||
style={
|
||||
isVertical
|
||||
? { width: trackSize }
|
||||
: { height: trackSize }
|
||||
}
|
||||
style={isVertical ? { width: trackSize } : { height: trackSize }}
|
||||
onClick={handleTrackClick}
|
||||
>
|
||||
<div className="slider-fill" style={trackFillStyle} />
|
||||
|
|
@ -352,21 +358,14 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
|
|||
<div className={classNames('slider-marks', isVertical && 'slider-marks-vertical')}>
|
||||
{resolvedMarks.map((m) => {
|
||||
const pct = percentOf(m.value)
|
||||
const markStyle = isVertical
|
||||
? { bottom: `${pct}%` }
|
||||
: { left: `${pct}%` }
|
||||
const markStyle = isVertical ? { bottom: `${pct}%` } : { left: `${pct}%` }
|
||||
const active = range ? m.value >= v0 && m.value <= v1 : m.value <= v0
|
||||
return (
|
||||
<span key={m.value} className="slider-mark-wrapper" style={markStyle}>
|
||||
<span
|
||||
className={classNames(
|
||||
'slider-mark-dot',
|
||||
active && 'slider-mark-dot-active',
|
||||
)}
|
||||
className={classNames('slider-mark-dot', active && 'slider-mark-dot-active')}
|
||||
/>
|
||||
{m.label && (
|
||||
<span className="slider-mark-label">{m.label}</span>
|
||||
)}
|
||||
{m.label && <span className="slider-mark-label">{m.label}</span>}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ const fadeMotionProps = {
|
|||
|
||||
const scaleTransition = (
|
||||
offsetX: number | string,
|
||||
offsetY: number | string
|
||||
offsetY: number | string,
|
||||
): Record<NotificationPlacement, MotionTransition> => {
|
||||
return {
|
||||
'top-end': {
|
||||
|
|
@ -147,7 +147,7 @@ const scaleTransition = (
|
|||
|
||||
const fadeTransition = (
|
||||
offsetX: number | string,
|
||||
offsetY: number | string
|
||||
offsetY: number | string,
|
||||
): Record<NotificationPlacement, MotionTransition> => {
|
||||
return {
|
||||
'top-end': {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import ScriptBuilderDialog from '@/components/scriptBuilder/ScriptBuilderDialog'
|
||||
import { useMemo } from 'react'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { createDesignerScriptDialect } from './designerScriptDialect'
|
||||
import type { SqlDataSourceEvent } from './types'
|
||||
|
||||
|
|
@ -37,6 +38,8 @@ function DesignerScriptBuilderDialog({
|
|||
onClose,
|
||||
onApply,
|
||||
}: DesignerScriptBuilderDialogProps) {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
// Çağıran her render'da yeni diziler üretiyor; lehçe içeriklerine göre
|
||||
// sabitlenmezse editör yazarken sıfırlanırdı.
|
||||
const recordFieldKey = recordFields.join('|')
|
||||
|
|
@ -50,8 +53,9 @@ function DesignerScriptBuilderDialog({
|
|||
recordFields: recordFieldKey ? recordFieldKey.split('|') : [],
|
||||
refNames: refNameKey ? refNameKey.split('|') : [],
|
||||
sqlRef,
|
||||
translate,
|
||||
}),
|
||||
[componentLabel, eventInfo, eventName, recordFieldKey, refNameKey, sqlRef],
|
||||
[componentLabel, eventInfo, eventName, recordFieldKey, refNameKey, sqlRef, translate],
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -307,7 +307,8 @@ const buildDesignerRefs = (store: DesignerRefStore | null) => {
|
|||
return Array.isArray(options) ? options : []
|
||||
},
|
||||
getSelectedOption: () => currentOption(),
|
||||
getColumn: (path: string) => getDesignerValueByPath(currentOption(), String(path || '')),
|
||||
getColumn: (path: string) =>
|
||||
getDesignerValueByPath(currentOption(), String(path || '')),
|
||||
getLabel: () => String(currentOption()?.label ?? ''),
|
||||
getColumnNames: () =>
|
||||
getDesignerBindingColumns(
|
||||
|
|
@ -362,7 +363,7 @@ const designTimeApi = {
|
|||
error?.response?.data?.error?.message ||
|
||||
error?.response?.data?.message ||
|
||||
error?.message ||
|
||||
'İşlem tamamlanamadı.',
|
||||
'App.Platform.OperationCouldNotBeCompleted',
|
||||
}
|
||||
|
||||
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor as FunctionConstructor
|
||||
|
|
@ -432,7 +433,7 @@ const toSelectOptions = (
|
|||
: record[primitiveKeys[1] || primitiveKeys[0]]
|
||||
return {
|
||||
...record,
|
||||
label: String(labelValue ?? `Seçenek ${index + 1}`),
|
||||
label: String(labelValue ?? `#${index + 1}`),
|
||||
value: optionValue ?? index,
|
||||
}
|
||||
})
|
||||
|
|
@ -656,7 +657,10 @@ const getPreviewProps = (
|
|||
return props
|
||||
}
|
||||
|
||||
const PlatformPlaceholder = ({ node }: { node: DesignerNode }) => (
|
||||
const PlatformPlaceholder = ({ node }: { node: DesignerNode }) => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex min-h-40 items-center justify-center rounded-lg border border-dashed border-sky-300 bg-gradient-to-br from-sky-50 to-indigo-50 p-6 text-center dark:border-sky-800 dark:from-slate-900 dark:to-sky-950"
|
||||
style={{ minHeight: String(node.props.height || '320px') }}
|
||||
|
|
@ -669,14 +673,17 @@ const PlatformPlaceholder = ({ node }: { node: DesignerNode }) => (
|
|||
{node.type.replace(/View$/, '')}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-slate-500">
|
||||
{String(node.props.listFormCode || 'Property panelinden List Form Code seçin')}
|
||||
{String(
|
||||
node.props.listFormCode || translate('::App.VisualDesigner.Canvas.SelectListFormCode'),
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-3 text-[11px] uppercase tracking-wider text-sky-700 dark:text-sky-300">
|
||||
Platform görünümü
|
||||
{translate('::App.VisualDesigner.Canvas.PlatformView')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const PLATFORM_VIEW_NAMES: Record<string, PlatformViewName> = {
|
||||
ListView: 'List',
|
||||
|
|
@ -690,7 +697,10 @@ const PLATFORM_VIEW_NAMES: Record<string, PlatformViewName> = {
|
|||
ChartView: 'Chart',
|
||||
}
|
||||
|
||||
const GridColumnHeaders = ({ columns }: { columns: string[] }) => (
|
||||
const GridColumnHeaders = ({ columns }: { columns: string[] }) => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
return (
|
||||
<div className="min-w-0 overflow-auto rounded-md border border-slate-200 bg-slate-100 [grid-column:1/-1] dark:border-slate-700 dark:bg-slate-800">
|
||||
{columns.length ? (
|
||||
<div className="flex min-w-max divide-x divide-slate-200 dark:divide-slate-700">
|
||||
|
|
@ -705,11 +715,12 @@ const GridColumnHeaders = ({ columns }: { columns: string[] }) => (
|
|||
</div>
|
||||
) : (
|
||||
<div className="px-3 py-2 text-center text-xs text-slate-400">
|
||||
Data panelinden gösterilecek sütunları seçin.
|
||||
{translate('::App.VisualDesigner.Canvas.SelectColumnsFromData')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Dates and decimals follow the selected language, like the DevExtreme grids do.
|
||||
const getGridCellText = (value: unknown) =>
|
||||
|
|
@ -732,6 +743,7 @@ const GridDataTablePreview = ({
|
|||
overflow?: boolean
|
||||
selectedColumns?: string[]
|
||||
}) => {
|
||||
const { translate } = useLocalization()
|
||||
const firstObject = items.find(
|
||||
(item): item is Record<string, unknown> =>
|
||||
Boolean(item) && typeof item === 'object' && !Array.isArray(item),
|
||||
|
|
@ -741,7 +753,7 @@ const GridDataTablePreview = ({
|
|||
if (!columns.length) {
|
||||
return (
|
||||
<div className="rounded-lg border border-dashed border-slate-300 bg-slate-50 p-4 text-center text-xs text-slate-500 [grid-column:1/-1] dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
|
||||
Preview için en az bir sütun seçin.
|
||||
{translate('::App.VisualDesigner.Canvas.SelectAtLeastOneColumn')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -824,6 +836,7 @@ const SqlDataSourceView = ({
|
|||
formScope: DesignerFormScope,
|
||||
) => React.ReactNode
|
||||
}) => {
|
||||
const { translate } = useLocalization()
|
||||
const selectId = getSqlDataSourceEndpointId(node, 'selectEndpoint')
|
||||
const keyField = getSqlDataSourceKeyField(node)
|
||||
const collectionPath = String(node.props.collectionPath ?? '')
|
||||
|
|
@ -872,7 +885,7 @@ const SqlDataSourceView = ({
|
|||
const canDelete = Boolean(getSqlDataSourceEndpointId(node, 'deleteEndpoint'))
|
||||
// Save follows the explicit mode, exactly like the generated runtime does.
|
||||
const canSave = mode === 'new' ? canInsert : canUpdate
|
||||
const designTimeTitle = 'Tasarım modunda endpoint çağrısı yapılmaz.'
|
||||
const designTimeTitle = translate('::App.VisualDesigner.Canvas.DesignTimeNoCall')
|
||||
|
||||
const toolbarButton = (
|
||||
label: string,
|
||||
|
|
@ -913,13 +926,21 @@ const SqlDataSourceView = ({
|
|||
<span>
|
||||
key: <code>{keyField}</code>
|
||||
</span>
|
||||
<span>{selectId ? `${rows.length} kayıt` : 'Select endpointi seçilmedi'}</span>
|
||||
<span>
|
||||
{selectId
|
||||
? translate('::App.VisualDesigner.Canvas.RecordCount', { count: rows.length })
|
||||
: translate('::App.VisualDesigner.Canvas.NoSelectEndpoint')}
|
||||
</span>
|
||||
<span className="rounded bg-sky-600 px-1.5 py-0.5 font-semibold text-white">
|
||||
{mode === 'new' ? 'Yeni kayıt' : 'Düzenleme'}
|
||||
{translate(
|
||||
mode === 'new'
|
||||
? '::App.VisualDesigner.Canvas.NewRecord'
|
||||
: '::App.VisualDesigner.Canvas.Editing',
|
||||
)}
|
||||
</span>
|
||||
{rows.length > 1 && (
|
||||
<label className="flex items-center gap-1">
|
||||
Satır
|
||||
{translate('::App.VisualDesigner.Canvas.Row')}
|
||||
<select
|
||||
className="rounded border border-sky-300 bg-white px-1 py-0.5 text-[10px] dark:border-sky-800 dark:bg-slate-900"
|
||||
value={rowIndex}
|
||||
|
|
@ -940,7 +961,7 @@ const SqlDataSourceView = ({
|
|||
{/* The drop zone belongs with the content, above the command toolbar. */}
|
||||
{interactive && !node.children.length && (
|
||||
<div className="rounded border border-dashed border-slate-300 px-3 py-4 text-center text-xs text-slate-400 dark:border-slate-700">
|
||||
Bileşeni buraya bırakın; Data sekmesinden sütununa bağlayın.
|
||||
{translate('::App.VisualDesigner.Canvas.DropAndBind')}
|
||||
</div>
|
||||
)}
|
||||
{node.props.showToolbar !== false && (
|
||||
|
|
@ -948,46 +969,60 @@ const SqlDataSourceView = ({
|
|||
{/* Navigation appears on its own once there is more than one record. */}
|
||||
{rows.length > 1 && (
|
||||
<>
|
||||
{toolbarButton('Önceki', rowIndex > 0, 'plain', 'İlk kayıttasınız.', () =>
|
||||
goToRow(Math.max(0, rowIndex - 1)),
|
||||
{toolbarButton(
|
||||
translate('::App.VisualDesigner.Canvas.Previous'),
|
||||
rowIndex > 0,
|
||||
'plain',
|
||||
translate('::App.VisualDesigner.Canvas.AtFirstRecord'),
|
||||
() => goToRow(Math.max(0, rowIndex - 1)),
|
||||
)}
|
||||
<span className="text-xs text-slate-500">
|
||||
{rows.length ? `${rowIndex + 1} / ${rows.length}` : '0 / 0'}
|
||||
</span>
|
||||
{toolbarButton(
|
||||
'Sonraki',
|
||||
translate('::App.VisualDesigner.Canvas.Next'),
|
||||
rowIndex < rows.length - 1,
|
||||
'plain',
|
||||
'Son kayıttasınız.',
|
||||
translate('::App.VisualDesigner.Canvas.AtLastRecord'),
|
||||
() => goToRow(Math.min(rows.length - 1, rowIndex + 1)),
|
||||
)}
|
||||
<span className="mx-1 h-5 w-px bg-slate-300 dark:bg-slate-700" />
|
||||
</>
|
||||
)}
|
||||
{/* New and Reload only touch local state, so they work at design time. */}
|
||||
{toolbarButton('Yeni', canInsert, 'plain', 'Insert için POST endpointi seçin.', () => {
|
||||
{toolbarButton(
|
||||
translate('::App.VisualDesigner.Canvas.New'),
|
||||
canInsert,
|
||||
'plain',
|
||||
translate('::App.VisualDesigner.Canvas.SelectInsertEndpoint'),
|
||||
() => {
|
||||
setDraft({})
|
||||
setMode('new')
|
||||
})}
|
||||
},
|
||||
)}
|
||||
{toolbarButton(
|
||||
'Kaydet',
|
||||
translate('::Save'),
|
||||
canSave,
|
||||
'primary',
|
||||
translate(
|
||||
mode === 'new'
|
||||
? 'Insert için POST endpointi seçin.'
|
||||
: 'Update için PUT endpointi seçin.',
|
||||
? '::App.VisualDesigner.Canvas.SelectInsertEndpoint'
|
||||
: '::App.VisualDesigner.Canvas.SelectUpdateEndpoint',
|
||||
),
|
||||
)}
|
||||
{toolbarButton(
|
||||
'Sil',
|
||||
translate('::App.Platform.Delete'),
|
||||
canDelete && hasKey && mode === 'edit',
|
||||
'danger',
|
||||
canDelete ? `Silmek için ${keyField} alanı dolu olmalıdır.` : 'DELETE endpointi seçin.',
|
||||
canDelete
|
||||
? translate('::App.VisualDesigner.Canvas.KeyFieldRequired', { field: keyField })
|
||||
: translate('::App.VisualDesigner.Canvas.SelectDeleteEndpoint'),
|
||||
)}
|
||||
{toolbarButton(
|
||||
'Yenile',
|
||||
translate('::App.Platform.Refresh'),
|
||||
Boolean(selectId),
|
||||
'plain',
|
||||
'Select için GET endpointi seçin.',
|
||||
translate('::App.VisualDesigner.Canvas.SelectSelectEndpoint'),
|
||||
() => goToRow(rowIndex),
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -1014,7 +1049,9 @@ const resolveNodeContent = (
|
|||
? getBindingValue(childrenBinding, dataValues, currentItem)
|
||||
: undefined
|
||||
if (boundChildren === null || boundChildren === undefined) {
|
||||
return (resolveStaticLanguageKeys(node.props.children, translate) as React.ReactNode) || undefined
|
||||
return (
|
||||
(resolveStaticLanguageKeys(node.props.children, translate) as React.ReactNode) || undefined
|
||||
)
|
||||
}
|
||||
if (React.isValidElement(boundChildren)) return boundChildren
|
||||
return typeof boundChildren === 'object' ? JSON.stringify(boundChildren) : String(boundChildren)
|
||||
|
|
@ -1134,14 +1171,18 @@ const renderElement = (
|
|||
const ownContent = resolveNodeContent(node, dataValues, currentItem, translate, refOverrides)
|
||||
const hasOwnContent = ownContent !== undefined && ownContent !== null && ownContent !== ''
|
||||
const content: React.ReactNode =
|
||||
React.Children.count(children) > 0
|
||||
? hasOwnContent
|
||||
? <>
|
||||
React.Children.count(children) > 0 ? (
|
||||
hasOwnContent ? (
|
||||
<>
|
||||
{ownContent}
|
||||
{children}
|
||||
</>
|
||||
: children
|
||||
: ownContent
|
||||
) : (
|
||||
children
|
||||
)
|
||||
) : (
|
||||
ownContent
|
||||
)
|
||||
const props = getPreviewProps(
|
||||
node,
|
||||
dataValues,
|
||||
|
|
@ -1172,7 +1213,7 @@ const renderElement = (
|
|||
key={String(option.value ?? index)}
|
||||
eventKey={String(option.value ?? index)}
|
||||
>
|
||||
{String(option.label ?? option.value ?? `Menü ${index + 1}`)}
|
||||
{String(option.label ?? option.value ?? `#${index + 1}`)}
|
||||
</UiKit.Menu.MenuItem>
|
||||
))}
|
||||
{content}
|
||||
|
|
@ -1200,7 +1241,7 @@ const renderElement = (
|
|||
disabled={Boolean(option.disabled)}
|
||||
eventKey={String(option.value ?? index)}
|
||||
>
|
||||
{String(option.label ?? option.value ?? `Öğe ${index + 1}`)}
|
||||
{String(option.label ?? option.value ?? `#${index + 1}`)}
|
||||
</UiKit.Dropdown.Item>
|
||||
))}
|
||||
{content}
|
||||
|
|
@ -1226,7 +1267,7 @@ const renderElement = (
|
|||
disabled={Boolean(option.disabled)}
|
||||
value={option.value ?? index}
|
||||
>
|
||||
{String(option.label ?? option.value ?? `Seçenek ${index + 1}`)}
|
||||
{String(option.label ?? option.value ?? `#${index + 1}`)}
|
||||
</UiKit.Radio>
|
||||
))}
|
||||
{content}
|
||||
|
|
@ -1348,7 +1389,7 @@ const NodeView = ({
|
|||
const insideDraggedSubtree = React.useContext(DesignerDraggedSubtreeContext) || isDragSource
|
||||
const canDropInside = acceptsDroppedChildren && !insideDraggedSubtree
|
||||
const canDropBeside = !insideDraggedSubtree
|
||||
const dragLabel = drag?.label || drag?.name || 'Bileşen'
|
||||
const dragLabel = drag?.label || drag?.name || translate('::App.VisualDesigner.Canvas.Component')
|
||||
/**
|
||||
* Taşımayı başlatan ortak kod. Hem düğüm gövdesi hem de tutamak bunu kullanır:
|
||||
* Input/Select/Textarea gibi canlı önizleme kontrolleri Chrome'da üst öğenin
|
||||
|
|
@ -1500,7 +1541,7 @@ const NodeView = ({
|
|||
disabled={Boolean(option.disabled)}
|
||||
value={String(option.value ?? index)}
|
||||
>
|
||||
{String(option.label ?? option.value ?? `Sekme ${index + 1}`)}
|
||||
{String(option.label ?? option.value ?? `#${index + 1}`)}
|
||||
</UiKit.Tabs.TabNav>
|
||||
))}
|
||||
</UiKit.Tabs.TabList>
|
||||
|
|
@ -1560,7 +1601,7 @@ const NodeView = ({
|
|||
))}
|
||||
{interactive && !tabNodes.length && !option.content && (
|
||||
<div className="flex min-h-12 items-center justify-center rounded border border-dashed border-slate-300 px-2 text-center text-[10px] text-slate-400 dark:border-slate-700">
|
||||
Bu sekme için komponent bırakın
|
||||
{translate('::App.VisualDesigner.Canvas.DropComponentInTab')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -1655,7 +1696,7 @@ const NodeView = ({
|
|||
))}
|
||||
{interactive && !cellNodes.length && (
|
||||
<div className="flex min-h-12 items-center justify-center rounded border border-dashed border-slate-300 px-2 text-center text-[10px] text-slate-400 dark:border-slate-700">
|
||||
Komponent bırakın
|
||||
{translate('::App.VisualDesigner.Canvas.DropComponent')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -1714,7 +1755,7 @@ const NodeView = ({
|
|||
key={`grid_loading_${node.id}`}
|
||||
className="rounded-lg border border-slate-200 bg-slate-50 p-4 text-center text-xs text-slate-500 [grid-column:1/-1] dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400"
|
||||
>
|
||||
Veriler yükleniyor…
|
||||
{translate('::App.VisualDesigner.Canvas.LoadingData')}
|
||||
</div>,
|
||||
]
|
||||
: node.type === 'Grid' &&
|
||||
|
|
@ -1775,7 +1816,9 @@ const NodeView = ({
|
|||
} ${hasStaticChildren || hasChildrenBinding ? 'mt-3' : ''}`}
|
||||
{...containerDropZoneProps}
|
||||
>
|
||||
{containerDropZone.over ? `${dragLabel} buraya bırakılacak` : 'Bileşeni buraya bırakın'}
|
||||
{containerDropZone.over
|
||||
? translate('::App.VisualDesigner.Canvas.WillDropHere', { label: dragLabel })
|
||||
: translate('::App.VisualDesigner.Canvas.DropComponentHere')}
|
||||
</div>,
|
||||
]
|
||||
: renderedChildren
|
||||
|
|
@ -1880,7 +1923,7 @@ const NodeView = ({
|
|||
{/* Sürükleme sürerken bırakılamayan hedef açıkça söylenir. */}
|
||||
{dragging && insideDraggedSubtree && !isDragSource && (
|
||||
<span className="pointer-events-none absolute right-1 top-1 z-30 rounded bg-slate-500 px-1.5 py-[1px] text-[10px] font-semibold text-white shadow">
|
||||
buraya taşınamaz
|
||||
{translate('::App.VisualDesigner.Canvas.CannotMoveHere')}
|
||||
</span>
|
||||
)}
|
||||
{interactive && (
|
||||
|
|
@ -1895,7 +1938,9 @@ const NodeView = ({
|
|||
<span
|
||||
draggable
|
||||
className="flex cursor-grab items-center gap-1 px-2 text-[10px] font-semibold active:cursor-grabbing"
|
||||
title={`${node.ref || node.type} — sürükleyerek taşı`}
|
||||
title={translate('::App.VisualDesigner.Canvas.DragToMove', {
|
||||
name: node.ref || node.type,
|
||||
})}
|
||||
onDragStart={startNodeDrag}
|
||||
onDragEnd={() => endDesignerDrag()}
|
||||
>
|
||||
|
|
@ -1904,7 +1949,9 @@ const NodeView = ({
|
|||
<button
|
||||
className="p-1.5 enabled:hover:bg-sky-700 disabled:cursor-not-allowed disabled:opacity-40"
|
||||
disabled={!canMoveUp}
|
||||
title={canMoveUp ? 'Yukarı taşı' : 'Zaten en üstte'}
|
||||
title={translate(
|
||||
canMoveUp ? '::App.Platform.MoveUp' : '::App.VisualDesigner.Canvas.AlreadyAtTop',
|
||||
)}
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
|
|
@ -1916,7 +1963,11 @@ const NodeView = ({
|
|||
<button
|
||||
className="p-1.5 enabled:hover:bg-sky-700 disabled:cursor-not-allowed disabled:opacity-40"
|
||||
disabled={!canMoveDown}
|
||||
title={canMoveDown ? 'Aşağı taşı' : 'Zaten en altta'}
|
||||
title={translate(
|
||||
canMoveDown
|
||||
? '::App.Platform.MoveDown'
|
||||
: '::App.VisualDesigner.Canvas.AlreadyAtBottom',
|
||||
)}
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
|
|
@ -1927,7 +1978,7 @@ const NodeView = ({
|
|||
</button>
|
||||
<button
|
||||
className="p-1.5 hover:bg-sky-700"
|
||||
title="Çoğalt"
|
||||
title={translate('::App.VisualDesigner.Canvas.Duplicate')}
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
|
|
@ -1938,7 +1989,7 @@ const NodeView = ({
|
|||
</button>
|
||||
<button
|
||||
className="p-1.5 hover:bg-red-600"
|
||||
title="Sil"
|
||||
title={translate('::App.Platform.Delete')}
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
|
|
@ -1984,7 +2035,7 @@ const NodeView = ({
|
|||
className="m-2 rounded border border-dashed border-slate-300 px-3 py-4 text-center text-xs text-slate-400"
|
||||
{...containerDropZoneProps}
|
||||
>
|
||||
Bileşeni buraya bırakın
|
||||
{translate('::App.VisualDesigner.Canvas.DropComponentHere')}
|
||||
</div>
|
||||
)}
|
||||
<span className="hidden">{index}</span>
|
||||
|
|
@ -2009,6 +2060,7 @@ const VisualCanvas = ({
|
|||
renderCustomComponent,
|
||||
dataValues = {},
|
||||
}: VisualCanvasProps) => {
|
||||
const { translate } = useLocalization()
|
||||
const previewDataValues = dataValues
|
||||
const [refState, setRefState] = React.useState<Record<string, DesignerRefOverride>>({})
|
||||
const nodesByRef = React.useMemo(() => {
|
||||
|
|
@ -2080,7 +2132,8 @@ const VisualCanvas = ({
|
|||
event.preventDefault()
|
||||
endDesignerDrag()
|
||||
const raw =
|
||||
event.dataTransfer.getData(DESIGNER_DRAG_TYPE) || event.dataTransfer.getData('text/plain')
|
||||
event.dataTransfer.getData(DESIGNER_DRAG_TYPE) ||
|
||||
event.dataTransfer.getData('text/plain')
|
||||
if (!raw) return
|
||||
const payload = JSON.parse(raw)
|
||||
if (payload.source === 'library') {
|
||||
|
|
@ -2094,8 +2147,8 @@ const VisualCanvas = ({
|
|||
{drag && interactive && (
|
||||
<div className="pointer-events-none sticky top-0 z-40 mb-3 flex items-center justify-center">
|
||||
<span className="rounded-full bg-slate-900/90 px-3 py-1 text-[11px] font-medium text-white shadow-lg">
|
||||
<b>{drag.label || drag.name}</b> sürükleniyor · kesikli çerçeveli alanlar içine alır,
|
||||
yeşil çizgi araya yerleştirir
|
||||
<b>{drag.label || drag.name}</b>{' '}
|
||||
{translate('::App.VisualDesigner.Canvas.DraggingHint')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -2134,7 +2187,7 @@ const VisualCanvas = ({
|
|||
}`}
|
||||
{...rootDropZone.props}
|
||||
>
|
||||
Sayfanın en altına bırak
|
||||
{translate('::App.VisualDesigner.Canvas.DropAtPageEnd')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -2154,19 +2207,22 @@ const VisualCanvas = ({
|
|||
+
|
||||
</div>
|
||||
<h3 className="font-semibold text-slate-700 dark:text-slate-200">
|
||||
Sayfa layout’unu seçin
|
||||
{translate('::App.VisualDesigner.Canvas.ChoosePageLayout')}
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
Hazır bir yerleşimle başlayın veya Toolbox’taki My Components grubundan kendi layout
|
||||
komponentinizi kullanın.
|
||||
{translate('::App.VisualDesigner.Canvas.ChoosePageLayoutHint')}
|
||||
</p>
|
||||
{interactive && (
|
||||
<div className="mt-5 grid grid-cols-2 gap-2 sm:grid-cols-4">
|
||||
{[
|
||||
['PageContainer', 'Sayfa alanı', '□'],
|
||||
['TwoColumns', 'İki kolon', '▥'],
|
||||
['SidebarContent', 'Sidebar + içerik', '◧'],
|
||||
['HeaderContent', 'Üst alan + içerik', '⊟'],
|
||||
['PageContainer', translate('::App.VisualDesigner.Layout.PageContainer'), '□'],
|
||||
['TwoColumns', translate('::App.VisualDesigner.Layout.TwoColumns'), '▥'],
|
||||
[
|
||||
'SidebarContent',
|
||||
translate('::App.VisualDesigner.Layout.SidebarContent'),
|
||||
'◧',
|
||||
],
|
||||
['HeaderContent', translate('::App.VisualDesigner.Layout.HeaderContent'), '⊟'],
|
||||
].map(([name, label, icon]) => (
|
||||
<button
|
||||
key={name}
|
||||
|
|
|
|||
|
|
@ -187,93 +187,93 @@ export interface DesignerScriptRecipe {
|
|||
}
|
||||
|
||||
export const DESIGNER_SCRIPT_RECIPE_GROUPS = [
|
||||
'Komponent erişimi',
|
||||
'API çağrıları',
|
||||
'SqlDataSource',
|
||||
'Event ve sayfa',
|
||||
'App.VisualDesigner.Catalog.Group.ComponentAccess',
|
||||
'App.VisualDesigner.Catalog.Group.ApiCalls',
|
||||
'App.VisualDesigner.Catalog.Group.SqlDataSource',
|
||||
'App.VisualDesigner.Catalog.Group.EventAndPage',
|
||||
] as const
|
||||
|
||||
export const DESIGNER_SCRIPT_RECIPES: DesignerScriptRecipe[] = [
|
||||
{
|
||||
group: 'Komponent erişimi',
|
||||
title: 'Değer atama ve okuma',
|
||||
description: 'Her komponent kendi ref adıyla adreslenir.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ComponentAccess',
|
||||
title: 'App.VisualDesigner.Catalog.ComponentAccess.Title',
|
||||
description: 'App.VisualDesigner.Catalog.ComponentAccess.Description',
|
||||
code: `refs.{{ref}}.setValue('Merhaba')
|
||||
const value = refs.{{ref}}.getValue()
|
||||
console.log(value)`,
|
||||
},
|
||||
{
|
||||
group: 'Komponent erişimi',
|
||||
title: 'Aktif / pasif yapma',
|
||||
description: 'Kaydet butonunu koşula göre kilitlemek için.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ComponentAccess',
|
||||
title: 'App.VisualDesigner.Catalog.ComponentAccess.Title2',
|
||||
description: 'App.VisualDesigner.Catalog.ComponentAccess.Description2',
|
||||
code: `refs.{{ref}}.setEnabled(false)
|
||||
console.log(refs.{{ref}}.isEnabled())`,
|
||||
},
|
||||
{
|
||||
group: 'Komponent erişimi',
|
||||
title: 'Gösterme / gizleme',
|
||||
description: 'Gizlenen komponent sayfada hiç render edilmez.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ComponentAccess',
|
||||
title: 'App.VisualDesigner.Catalog.ComponentAccess.Title3',
|
||||
description: 'App.VisualDesigner.Catalog.ComponentAccess.Description3',
|
||||
code: `refs.{{ref}}.setVisible(false)
|
||||
// veya: refs.{{ref}}.hide() / refs.{{ref}}.show()`,
|
||||
},
|
||||
{
|
||||
group: 'Komponent erişimi',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ComponentAccess',
|
||||
title: 'Salt okunur yapma',
|
||||
description: 'Değeri görünür kalsın ama düzenlenemesin.',
|
||||
description: 'App.VisualDesigner.Catalog.ComponentAccess.Description4',
|
||||
code: `refs.{{ref}}.setReadOnly(true)`,
|
||||
},
|
||||
{
|
||||
group: 'Komponent erişimi',
|
||||
title: 'Etiket / metin değiştirme',
|
||||
description: 'Buton ve etiket yazısını çalışma anında değiştirir.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ComponentAccess',
|
||||
title: 'App.VisualDesigner.Catalog.ComponentAccess.Title4',
|
||||
description: 'App.VisualDesigner.Catalog.ComponentAccess.Description5',
|
||||
code: `refs.{{ref}}.setText('Kaydediliyor…')`,
|
||||
},
|
||||
{
|
||||
group: 'Komponent erişimi',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ComponentAccess',
|
||||
title: 'Serbest property atama',
|
||||
description: 'Property panelindeki her property script ile de yazılabilir.',
|
||||
description: 'App.VisualDesigner.Catalog.ComponentAccess.Description6',
|
||||
code: `refs.{{ref}}.setProps({ placeholder: 'Ara…', className: 'border-red-500' })
|
||||
refs.{{ref}}.setProp('size', 'sm')`,
|
||||
},
|
||||
{
|
||||
group: 'Komponent erişimi',
|
||||
title: 'Script ile yapılanları geri alma',
|
||||
description: 'Komponenti tasarımdaki haline döndürür.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ComponentAccess',
|
||||
title: 'App.VisualDesigner.Catalog.ComponentAccess.Title5',
|
||||
description: 'App.VisualDesigner.Catalog.ComponentAccess.Description7',
|
||||
code: `refs.{{ref}}.reset()`,
|
||||
},
|
||||
{
|
||||
group: 'API çağrıları',
|
||||
title: 'GET — kayıt okuma',
|
||||
description: 'Sonuç doğrudan response body’sidir.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ApiCalls',
|
||||
title: 'App.VisualDesigner.Catalog.ApiCalls.Title',
|
||||
description: 'App.VisualDesigner.Catalog.ApiCalls.Description',
|
||||
code: `const rows = await api.get('{{url}}')
|
||||
console.log(rows)`,
|
||||
},
|
||||
{
|
||||
group: 'API çağrıları',
|
||||
title: 'GET — parametre ile',
|
||||
description: 'İkinci argüman query string olarak gönderilir.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ApiCalls',
|
||||
title: 'App.VisualDesigner.Catalog.ApiCalls.Title2',
|
||||
description: 'App.VisualDesigner.Catalog.ApiCalls.Description2',
|
||||
code: `const rows = await api.get('{{url}}', { maxResultCount: 10 })
|
||||
console.log(rows)`,
|
||||
},
|
||||
{
|
||||
group: 'API çağrıları',
|
||||
title: 'POST — yeni kayıt',
|
||||
description: 'Gövde ikinci argüman olarak verilir.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ApiCalls',
|
||||
title: 'App.VisualDesigner.Catalog.ApiCalls.Title3',
|
||||
description: 'App.VisualDesigner.Catalog.ApiCalls.Description3',
|
||||
code: `const created = await api.post('{{url}}', {
|
||||
name: refs.{{ref}}.getValue(),
|
||||
})
|
||||
console.log(created)`,
|
||||
},
|
||||
{
|
||||
group: 'API çağrıları',
|
||||
title: 'PUT ve DELETE',
|
||||
description: 'Güncelleme ve silme aynı imzayı kullanır.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ApiCalls',
|
||||
title: 'App.VisualDesigner.Catalog.ApiCalls.Title4',
|
||||
description: 'App.VisualDesigner.Catalog.ApiCalls.Description4',
|
||||
code: `await api.put('{{url}}/5', { name: 'Yeni ad' })
|
||||
await api.delete('{{url}}/5')`,
|
||||
},
|
||||
{
|
||||
group: 'API çağrıları',
|
||||
title: 'Hata yönetimi ve bildirim',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ApiCalls',
|
||||
title: 'App.VisualDesigner.Catalog.ApiCalls.Title5',
|
||||
description: 'notify tipleri: success, info, warning, danger.',
|
||||
code: `try {
|
||||
await api.post('{{url}}', { name: refs.{{ref}}.getValue() })
|
||||
|
|
@ -283,9 +283,9 @@ await api.delete('{{url}}/5')`,
|
|||
}`,
|
||||
},
|
||||
{
|
||||
group: 'API çağrıları',
|
||||
title: 'Çağrı boyunca butonu kilitleme',
|
||||
description: 'Çift tıklamayı ve mükerrer isteği önler.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ApiCalls',
|
||||
title: 'App.VisualDesigner.Catalog.ApiCalls.Title6',
|
||||
description: 'App.VisualDesigner.Catalog.ApiCalls.Description5',
|
||||
code: `refs.{{ref}}.setEnabled(false)
|
||||
refs.{{ref}}.setText('Gönderiliyor…')
|
||||
try {
|
||||
|
|
@ -299,9 +299,9 @@ try {
|
|||
}`,
|
||||
},
|
||||
{
|
||||
group: 'API çağrıları',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ApiCalls',
|
||||
title: 'Serbest istek',
|
||||
description: 'Header, timeout gibi ayarlar gerektiğinde.',
|
||||
description: 'App.VisualDesigner.Catalog.ApiCalls.Description6',
|
||||
code: `const result = await api.request({
|
||||
url: '{{url}}',
|
||||
method: 'GET',
|
||||
|
|
@ -310,26 +310,25 @@ try {
|
|||
console.log(result)`,
|
||||
},
|
||||
{
|
||||
group: 'Komponent erişimi',
|
||||
title: 'Select’in görünmeyen sütunlarını okuma',
|
||||
description:
|
||||
'Select yalnızca Label gösterir; Data sekmesinde eklenen sütunlar seçenekle birlikte taşınır.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ComponentAccess',
|
||||
title: 'App.VisualDesigner.Catalog.ComponentAccess.Title6',
|
||||
description: 'App.VisualDesigner.Catalog.SelectHiddenColumns.Description',
|
||||
code: `// onChange içinde: seçilen kaydın diğer sütunları
|
||||
const fiyat = refs.{{ref}}.getColumn('unitPrice')
|
||||
const stok = refs.{{ref}}.getColumn('stockQuantity')
|
||||
refs.{{sql}}.setFields({ UnitPrice: fiyat, Stock: stok })`,
|
||||
},
|
||||
{
|
||||
group: 'Komponent erişimi',
|
||||
title: 'Seçilen kaydın tamamı',
|
||||
description: 'Çoklu seçimde dizi döner.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.ComponentAccess',
|
||||
title: 'App.VisualDesigner.Catalog.ComponentAccess.Title7',
|
||||
description: 'App.VisualDesigner.Catalog.ComponentAccess.Description8',
|
||||
code: `const secilen = refs.{{ref}}.getSelectedOption()
|
||||
console.log(secilen, refs.{{ref}}.getLabel())`,
|
||||
},
|
||||
{
|
||||
group: 'SqlDataSource',
|
||||
title: 'Kaydet / sil / yenile',
|
||||
description: 'Toolbar’ı gizleyip kendi butonlarınızı kullanabilirsiniz.',
|
||||
title: 'App.VisualDesigner.Catalog.SqlDataSource.Title',
|
||||
description: 'App.VisualDesigner.Catalog.SqlDataSource.Description',
|
||||
code: `await refs.{{sql}}.save()
|
||||
// await refs.{{sql}}.remove()
|
||||
// await refs.{{sql}}.reload()
|
||||
|
|
@ -337,23 +336,23 @@ console.log(secilen, refs.{{ref}}.getLabel())`,
|
|||
},
|
||||
{
|
||||
group: 'SqlDataSource',
|
||||
title: 'Aktif kaydı okuma ve yazma',
|
||||
description: 'Alan adı Select sonucundaki sütun adıdır.',
|
||||
title: 'App.VisualDesigner.Catalog.SqlDataSource.Title2',
|
||||
description: 'App.VisualDesigner.Catalog.SqlDataSource.Description2',
|
||||
code: `const record = refs.{{sql}}.getRecord()
|
||||
console.log(record)
|
||||
refs.{{sql}}.setField('name', 'Yeni ad')`,
|
||||
},
|
||||
{
|
||||
group: 'SqlDataSource',
|
||||
title: 'Kayıtlar arasında gezinme',
|
||||
description: 'Select birden fazla satır döndürdüğünde.',
|
||||
title: 'App.VisualDesigner.Catalog.SqlDataSource.Title3',
|
||||
description: 'App.VisualDesigner.Catalog.SqlDataSource.Description3',
|
||||
code: `const rows = refs.{{sql}}.getRows()
|
||||
refs.{{sql}}.goToRow(rows.length - 1)`,
|
||||
},
|
||||
{
|
||||
group: 'SqlDataSource',
|
||||
title: 'Kaydetmeden önce doğrulama',
|
||||
description: 'Zorunlu alan boşsa işlemi durdurur.',
|
||||
title: 'App.VisualDesigner.Catalog.SqlDataSource.Title4',
|
||||
description: 'App.VisualDesigner.Catalog.SqlDataSource.Description4',
|
||||
code: `const record = refs.{{sql}}.getRecord()
|
||||
if (!record.name) {
|
||||
notify('Ad alanı zorunludur', 'warning')
|
||||
|
|
@ -363,35 +362,35 @@ await refs.{{sql}}.save()`,
|
|||
},
|
||||
{
|
||||
group: 'SqlDataSource',
|
||||
title: 'Durum bilgisi',
|
||||
description: 'Yeni kayıt / düzenleme, meşgul ve hata durumu.',
|
||||
title: 'App.VisualDesigner.Catalog.SqlDataSource.Title5',
|
||||
description: 'App.VisualDesigner.Catalog.SqlDataSource.Description5',
|
||||
code: `console.log(refs.{{sql}}.getMode(), refs.{{sql}}.isBusy(), refs.{{sql}}.getError())`,
|
||||
},
|
||||
{
|
||||
group: 'Event ve sayfa',
|
||||
group: 'App.VisualDesigner.Catalog.Group.EventAndPage',
|
||||
title: 'Event verisi',
|
||||
description: 'event içeriği komponent tipine göre değişir.',
|
||||
description: 'App.VisualDesigner.Catalog.EventAndPage.Description',
|
||||
code: `// Input: event.target.value · Checkbox: event.checked
|
||||
// Select/Dropdown/Menu: seçilen değer · Button: tıklama eventi
|
||||
console.log(event)`,
|
||||
},
|
||||
{
|
||||
group: 'Event ve sayfa',
|
||||
group: 'App.VisualDesigner.Catalog.Group.EventAndPage',
|
||||
title: 'Komponentin kendi bilgileri',
|
||||
description: 'Scriptin bağlı olduğu komponent ve tasarım property’leri.',
|
||||
description: 'App.VisualDesigner.Catalog.EventAndPage.Description2',
|
||||
code: `console.log(component.type, component.ref)
|
||||
console.log(props)`,
|
||||
},
|
||||
{
|
||||
group: 'Event ve sayfa',
|
||||
title: 'Sayfa yönlendirme',
|
||||
description: 'Kayıt sonrası liste sayfasına dönmek için.',
|
||||
group: 'App.VisualDesigner.Catalog.Group.EventAndPage',
|
||||
title: 'App.VisualDesigner.Catalog.EventAndPage.Title',
|
||||
description: 'App.VisualDesigner.Catalog.EventAndPage.Description3',
|
||||
code: `window.location.href = '/app/orders'`,
|
||||
},
|
||||
{
|
||||
group: 'Event ve sayfa',
|
||||
group: 'App.VisualDesigner.Catalog.Group.EventAndPage',
|
||||
title: 'URL parametresi okuma',
|
||||
description: 'Detay sayfasında anahtar değerini almak için.',
|
||||
description: 'App.VisualDesigner.Catalog.EventAndPage.Description4',
|
||||
code: `const id = new URLSearchParams(window.location.search).get('id')
|
||||
console.log(id)`,
|
||||
},
|
||||
|
|
@ -405,7 +404,7 @@ const COLLECTION_ITEMS_PROPERTY = {
|
|||
type: 'array' as const,
|
||||
value: [],
|
||||
category: 'properties' as const,
|
||||
description: 'Statik veya endpoint kaynaklı veri koleksiyonu',
|
||||
description: 'App.VisualDesigner.Catalog.Items.Description',
|
||||
tsType: 'unknown[]',
|
||||
options: undefined,
|
||||
required: false,
|
||||
|
|
@ -415,7 +414,7 @@ const GRID_COMPACT_PROPERTY = {
|
|||
type: 'boolean' as const,
|
||||
value: false,
|
||||
category: 'properties' as const,
|
||||
description: 'Satır ve hücre boşluklarını azaltır',
|
||||
description: 'App.VisualDesigner.Catalog.Compact.Description',
|
||||
tsType: 'boolean',
|
||||
options: undefined,
|
||||
required: false,
|
||||
|
|
@ -426,7 +425,7 @@ const GRID_TABLE_BEHAVIOR_PROPERTIES = [
|
|||
type: 'boolean' as const,
|
||||
value: true,
|
||||
category: 'properties' as const,
|
||||
description: 'Satırların üzerine gelindiğinde vurgu rengi gösterir',
|
||||
description: 'App.VisualDesigner.Catalog.Hoverable.Description',
|
||||
tsType: 'boolean',
|
||||
options: undefined,
|
||||
required: false,
|
||||
|
|
@ -436,7 +435,7 @@ const GRID_TABLE_BEHAVIOR_PROPERTIES = [
|
|||
type: 'boolean' as const,
|
||||
value: true,
|
||||
category: 'properties' as const,
|
||||
description: 'Geniş tablolarda yatay kaydırmayı etkinleştirir',
|
||||
description: 'App.VisualDesigner.Catalog.Overflow.Description',
|
||||
tsType: 'boolean',
|
||||
options: undefined,
|
||||
required: false,
|
||||
|
|
@ -446,7 +445,7 @@ const GRID_TABLE_BEHAVIOR_PROPERTIES = [
|
|||
type: 'boolean' as const,
|
||||
value: false,
|
||||
category: 'properties' as const,
|
||||
description: 'Veri satırları arasındaki kenarlıkları kaldırır',
|
||||
description: 'App.VisualDesigner.Catalog.BorderlessRow.Description',
|
||||
tsType: 'boolean',
|
||||
options: undefined,
|
||||
required: false,
|
||||
|
|
@ -458,7 +457,7 @@ const TABLE_DIMENSION_PROPERTIES = [
|
|||
type: 'number' as const,
|
||||
value: 2,
|
||||
category: 'properties' as const,
|
||||
description: 'Table içindeki statik satır sayısı',
|
||||
description: 'App.VisualDesigner.Catalog.RowCount.Description',
|
||||
tsType: 'number',
|
||||
options: undefined,
|
||||
required: false,
|
||||
|
|
@ -468,7 +467,7 @@ const TABLE_DIMENSION_PROPERTIES = [
|
|||
type: 'number' as const,
|
||||
value: 3,
|
||||
category: 'properties' as const,
|
||||
description: 'Her satırdaki statik hücre sayısı',
|
||||
description: 'App.VisualDesigner.Catalog.ColumnCount.Description',
|
||||
tsType: 'number',
|
||||
options: undefined,
|
||||
required: false,
|
||||
|
|
@ -478,11 +477,11 @@ const SELECT_OPTIONS_PROPERTY = {
|
|||
name: 'options',
|
||||
type: 'array' as const,
|
||||
value: [
|
||||
{ label: 'Seçenek 1', value: '1' },
|
||||
{ label: 'Seçenek 2', value: '2' },
|
||||
{ label: 'App.VisualDesigner.Catalog.Options.Label', value: '1' },
|
||||
{ label: 'App.VisualDesigner.Catalog.Options.Label2', value: '2' },
|
||||
],
|
||||
category: 'properties' as const,
|
||||
description: 'Select items; statik olarak veya endpoint koleksiyonundan beslenebilir',
|
||||
description: 'App.VisualDesigner.Catalog.Options.Description',
|
||||
tsType: 'Array<{ label: string; value: unknown }>',
|
||||
options: undefined,
|
||||
required: false,
|
||||
|
|
@ -490,7 +489,7 @@ const SELECT_OPTIONS_PROPERTY = {
|
|||
const MENU_ITEMS_PROPERTY = {
|
||||
...SELECT_OPTIONS_PROPERTY,
|
||||
name: 'items',
|
||||
description: 'Menu items; statik olarak veya endpoint koleksiyonundan beslenebilir',
|
||||
description: 'App.VisualDesigner.Catalog.Items.Description2',
|
||||
}
|
||||
const TABS_ITEMS_PROPERTY = {
|
||||
...SELECT_OPTIONS_PROPERTY,
|
||||
|
|
@ -499,8 +498,7 @@ const TABS_ITEMS_PROPERTY = {
|
|||
{ label: 'Sekme 1', value: '1', content: 'Sekme 1 içeriği' },
|
||||
{ label: 'Sekme 2', value: '2', content: 'Sekme 2 içeriği' },
|
||||
],
|
||||
description:
|
||||
'Sekmeler; statik olarak veya endpoint koleksiyonundan beslenebilir. content alanı sekme içeriğini yazar.',
|
||||
description: 'App.VisualDesigner.Catalog.Tabs.Description',
|
||||
tsType: 'Array<{ label: string; value: unknown; content?: string }>',
|
||||
}
|
||||
|
||||
|
|
@ -723,19 +721,24 @@ const platformDefinition = (
|
|||
})
|
||||
|
||||
export const PLATFORM_COMPONENTS: DesignerComponentDefinition[] = [
|
||||
platformDefinition('ListView', 'List', 'App.VisualDesigner.Catalog.ListView.Description'),
|
||||
platformDefinition('DataGridView', 'Grid', 'App.VisualDesigner.Catalog.DataGridView.Description'),
|
||||
platformDefinition('TreeView', 'Tree', 'App.VisualDesigner.Catalog.TreeView.Description'),
|
||||
platformDefinition('GanttView', 'GanttView', 'App.VisualDesigner.Catalog.GanttView.Description'),
|
||||
platformDefinition('TodoBoard', 'TodoBoard', 'App.VisualDesigner.Catalog.TodoBoard.Description'),
|
||||
platformDefinition('CardView', 'CardView', 'App.VisualDesigner.Catalog.CardView.Description'),
|
||||
platformDefinition(
|
||||
'ListView',
|
||||
'List',
|
||||
'Tüm etkin platform görünümlerini /admin/list/:listFormCode ile aynı şekilde yönetir',
|
||||
'SchedulerView',
|
||||
'SchedulerView',
|
||||
'App.VisualDesigner.Catalog.SchedulerView.Description',
|
||||
),
|
||||
platformDefinition('PivotView', 'Pivot', 'App.VisualDesigner.Catalog.PivotView.Description'),
|
||||
platformDefinition(
|
||||
'ChartView',
|
||||
'Chart',
|
||||
'App.VisualDesigner.Catalog.ChartView.Description',
|
||||
'420px',
|
||||
),
|
||||
platformDefinition('DataGridView', 'Grid', 'Sozsoft dinamik veri grid görünümü'),
|
||||
platformDefinition('TreeView', 'Tree', 'Sozsoft dinamik ağaç görünümü'),
|
||||
platformDefinition('GanttView', 'GanttView', 'Sozsoft dinamik Gantt görünümü'),
|
||||
platformDefinition('TodoBoard', 'TodoBoard', 'Sozsoft dinamik iş panosu'),
|
||||
platformDefinition('CardView', 'CardView', 'Sozsoft dinamik kart görünümü'),
|
||||
platformDefinition('SchedulerView', 'SchedulerView', 'Sozsoft dinamik planlayıcı görünümü'),
|
||||
platformDefinition('PivotView', 'Pivot', 'Sozsoft dinamik pivot görünümü'),
|
||||
platformDefinition('ChartView', 'Chart', 'Sozsoft dinamik grafik görünümü', '420px'),
|
||||
]
|
||||
|
||||
/**
|
||||
|
|
@ -750,8 +753,7 @@ export const SQL_DATA_SOURCE_DEFINITION: DesignerComponentDefinition = {
|
|||
category: 'data',
|
||||
kind: 'layout',
|
||||
toolboxGroup: 'data',
|
||||
description:
|
||||
'GET/POST/PUT/DELETE endpointlerini tek kayıt üzerinde yöneten veri kabı; içine bırakılan komponentler sütunlara bağlanır',
|
||||
description: 'App.VisualDesigner.Catalog.SqlDataSource.Description',
|
||||
acceptsChildren: true,
|
||||
properties: [
|
||||
{
|
||||
|
|
@ -759,42 +761,42 @@ export const SQL_DATA_SOURCE_DEFINITION: DesignerComponentDefinition = {
|
|||
type: 'string',
|
||||
value: '',
|
||||
category: 'properties',
|
||||
description: 'Kaydı okuyan GET endpointinin data source id’si (Data sekmesinden seçilir)',
|
||||
description: 'App.VisualDesigner.Catalog.String.Description',
|
||||
},
|
||||
{
|
||||
name: 'insertEndpoint',
|
||||
type: 'string',
|
||||
value: '',
|
||||
category: 'properties',
|
||||
description: 'Yeni kayıt için POST endpointi',
|
||||
description: 'App.VisualDesigner.Catalog.String.Description2',
|
||||
},
|
||||
{
|
||||
name: 'updateEndpoint',
|
||||
type: 'string',
|
||||
value: '',
|
||||
category: 'properties',
|
||||
description: 'Mevcut kaydı güncelleyen PUT endpointi',
|
||||
description: 'App.VisualDesigner.Catalog.String.Description3',
|
||||
},
|
||||
{
|
||||
name: 'deleteEndpoint',
|
||||
type: 'string',
|
||||
value: '',
|
||||
category: 'properties',
|
||||
description: 'Kaydı silen DELETE endpointi',
|
||||
description: 'App.VisualDesigner.Catalog.String.Description4',
|
||||
},
|
||||
{
|
||||
name: 'keyFieldName',
|
||||
type: 'string',
|
||||
value: 'id',
|
||||
category: 'properties',
|
||||
description: 'Insert/Update ayrımı ve endpoint parametreleri bu sütundan doldurulur',
|
||||
description: 'App.VisualDesigner.Catalog.String.Description5',
|
||||
},
|
||||
{
|
||||
name: 'collectionPath',
|
||||
type: 'string',
|
||||
value: '',
|
||||
category: 'properties',
|
||||
description: 'GET cevabı içinde satırların bulunduğu path (boşsa cevabın kendisi kullanılır)',
|
||||
description: 'App.VisualDesigner.Catalog.String.Description6',
|
||||
},
|
||||
{
|
||||
name: 'keySource',
|
||||
|
|
@ -802,36 +804,35 @@ export const SQL_DATA_SOURCE_DEFINITION: DesignerComponentDefinition = {
|
|||
value: 'query',
|
||||
options: ['query', 'route'],
|
||||
category: 'properties',
|
||||
description: 'Key değerinin sayfa URL’sinden okunma şekli',
|
||||
description: 'App.VisualDesigner.Catalog.Select.Description',
|
||||
},
|
||||
{
|
||||
name: 'keyParamName',
|
||||
type: 'string',
|
||||
value: '',
|
||||
category: 'properties',
|
||||
description: 'URL’den okunacak parametre adı (boşsa key field kullanılır)',
|
||||
description: 'App.VisualDesigner.Catalog.String.Description7',
|
||||
},
|
||||
{
|
||||
name: 'previewKeyValue',
|
||||
type: 'string',
|
||||
value: '',
|
||||
category: 'properties',
|
||||
description:
|
||||
'Sadece tasarım ekranı: GetById endpointinin sütunlarını okuyabilmek için örnek key değeri',
|
||||
description: 'App.VisualDesigner.Catalog.PreviewKeyValue.Description',
|
||||
},
|
||||
{
|
||||
name: 'autoLoad',
|
||||
type: 'boolean',
|
||||
value: true,
|
||||
category: 'properties',
|
||||
description: 'Sayfa açıldığında Select endpointini otomatik çağırır',
|
||||
description: 'App.VisualDesigner.Catalog.Boolean.Description',
|
||||
},
|
||||
{
|
||||
name: 'showToolbar',
|
||||
type: 'boolean',
|
||||
value: true,
|
||||
category: 'properties',
|
||||
description: 'New / Save / Delete / Reload butonlarını gösterir',
|
||||
description: 'App.VisualDesigner.Catalog.Boolean.Description2',
|
||||
},
|
||||
{
|
||||
name: 'gap',
|
||||
|
|
@ -861,7 +862,7 @@ export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [
|
|||
category: 'layout',
|
||||
kind: 'layout',
|
||||
toolboxGroup: 'layout',
|
||||
description: 'Intranet içeriği için ortalanmış sayfa alanı',
|
||||
description: 'App.VisualDesigner.Catalog.PageContainer.Description',
|
||||
acceptsChildren: true,
|
||||
properties: [
|
||||
{ name: 'maxWidth', type: 'string', value: '1280px', category: 'styling' },
|
||||
|
|
@ -877,7 +878,7 @@ export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [
|
|||
category: 'layout',
|
||||
kind: 'layout',
|
||||
toolboxGroup: 'layout',
|
||||
description: 'Sayfayı iki eşit kolona böler',
|
||||
description: 'App.VisualDesigner.Catalog.TwoColumns.Description',
|
||||
acceptsChildren: true,
|
||||
properties: [
|
||||
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
|
||||
|
|
@ -891,7 +892,7 @@ export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [
|
|||
category: 'layout',
|
||||
kind: 'layout',
|
||||
toolboxGroup: 'layout',
|
||||
description: 'Dar sidebar ve geniş içerik alanı',
|
||||
description: 'App.VisualDesigner.Catalog.SidebarContent.Description',
|
||||
acceptsChildren: true,
|
||||
properties: [
|
||||
{ name: 'sidebarWidth', type: 'string', value: '280px', category: 'styling' },
|
||||
|
|
@ -906,7 +907,7 @@ export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [
|
|||
category: 'layout',
|
||||
kind: 'layout',
|
||||
toolboxGroup: 'layout',
|
||||
description: 'Üst alan ve alt içerik için dikey yerleşim',
|
||||
description: 'App.VisualDesigner.Catalog.HeaderContent.Description',
|
||||
acceptsChildren: true,
|
||||
properties: [
|
||||
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
|
||||
|
|
@ -920,7 +921,7 @@ export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [
|
|||
category: 'layout',
|
||||
kind: 'layout',
|
||||
toolboxGroup: 'layout',
|
||||
description: 'Yatay ve responsive yerleşim',
|
||||
description: 'App.VisualDesigner.Catalog.FlexRow.Description',
|
||||
acceptsChildren: true,
|
||||
properties: [
|
||||
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
|
||||
|
|
@ -942,7 +943,7 @@ export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [
|
|||
category: 'layout',
|
||||
kind: 'layout',
|
||||
toolboxGroup: 'ui',
|
||||
description: 'Bileşenler arasında boşluk',
|
||||
description: 'App.VisualDesigner.Catalog.Spacer.Description',
|
||||
properties: [{ name: 'height', type: 'number', value: 24, category: 'styling' }],
|
||||
hooks: [],
|
||||
},
|
||||
|
|
@ -987,7 +988,7 @@ export const getDesignerCatalog = (customNames: string[] = []): DesignerComponen
|
|||
category: 'custom',
|
||||
kind: 'custom',
|
||||
toolboxGroup: 'custom',
|
||||
description: 'Veritabanındaki yeniden kullanılabilir bileşen',
|
||||
description: 'App.VisualDesigner.Catalog.Spacer.Description2',
|
||||
acceptsChildren: true,
|
||||
properties: [{ name: 'className', type: 'string', value: '', category: 'styling' }],
|
||||
hooks: [],
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import {
|
|||
type DesignerRuleCondition,
|
||||
type DesignerScriptContext,
|
||||
type DesignerScriptRule,
|
||||
type TranslateFn,
|
||||
} from './designerScriptRecipes'
|
||||
import type { SqlDataSourceEvent } from './types'
|
||||
|
||||
|
|
@ -75,57 +76,57 @@ const snippets = (sqlRef: string): ScriptSnippet[] => {
|
|||
const sql = sqlRef || 'sqlDataSource1'
|
||||
return [
|
||||
{
|
||||
label: 'Alan değişimine göre hesapla',
|
||||
group: 'Kayıt',
|
||||
description: 'Sadece ilgili sütunlar değişince hesaplar.',
|
||||
label: 'App.VisualDesigner.Snippet.CalcOnFieldChange.Label',
|
||||
group: 'App.VisualDesigner.Snippet.Group.Record',
|
||||
description: 'App.VisualDesigner.Snippet.CalcOnFieldChange.Description',
|
||||
code: `if (['\${1:Quantity}', '\${2:UnitPrice}'].includes(event.field)) {\n const total = Number(event.record.\${1:Quantity} || 0) * Number(event.record.\${2:UnitPrice} || 0)\n refs.${sql}.setField('\${3:TotalAmount}', Math.round(total * 100) / 100)\n}`,
|
||||
},
|
||||
{
|
||||
label: 'Kaydetmeden önce doğrula',
|
||||
group: 'Akış',
|
||||
description: 'Eksik alan varsa işlemi iptal eder.',
|
||||
label: 'App.VisualDesigner.Snippet.ValidateBeforeSave.Label',
|
||||
group: 'App.VisualDesigner.Snippet.Group.Flow',
|
||||
description: 'App.VisualDesigner.Snippet.ValidateBeforeSave.Description',
|
||||
code: `const eksik = ['\${1:Name}', '\${2:Code}'].filter((column) => !event.record[column])\nif (eksik.length) {\n notify('Zorunlu alanlar: ' + eksik.join(', '), 'warning')\n return false\n}`,
|
||||
},
|
||||
{
|
||||
label: 'Çok koşullu blok',
|
||||
group: 'Akış',
|
||||
description: 'VE / VEYA ile birleşen koşullar.',
|
||||
label: 'App.VisualDesigner.Snippet.MultiCondition.Label',
|
||||
group: 'App.VisualDesigner.Snippet.Group.Flow',
|
||||
description: 'App.VisualDesigner.Snippet.MultiCondition.Description',
|
||||
code: `if (refs.${sql}.getMode() === 'new' || Number(refs.${sql}.getField('\${1:Total}') || 0) > \${2:1000}) {\n \${3:notify('Kontrol edin')}\n}`,
|
||||
},
|
||||
{
|
||||
label: 'Toplu alan yazma',
|
||||
group: 'Kayıt',
|
||||
description: 'Birden fazla sütunu tek çağrıda doldurur.',
|
||||
label: 'App.VisualDesigner.Snippet.BulkSet.Label',
|
||||
group: 'App.VisualDesigner.Snippet.Group.Record',
|
||||
description: 'App.VisualDesigner.Snippet.BulkSet.Description',
|
||||
code: `refs.${sql}.setFields({ \${1:Status}: '\${2:Taslak}', \${3:Currency}: '\${4:TRY}' })`,
|
||||
},
|
||||
{
|
||||
label: 'Komponent görünürlüğü',
|
||||
group: 'Komponent',
|
||||
description: 'Koşula göre gösterir veya gizler.',
|
||||
label: 'App.VisualDesigner.Snippet.Visibility.Label',
|
||||
group: 'App.VisualDesigner.Snippet.Group.Component',
|
||||
description: 'App.VisualDesigner.Snippet.Visibility.Description',
|
||||
code: `refs.\${1:card1}.setVisible(refs.${sql}.getMode() === '\${2:edit}')`,
|
||||
},
|
||||
{
|
||||
label: 'API çağrısı ve hata yakalama',
|
||||
group: 'Entegrasyon',
|
||||
description: 'Çağrı başarısızsa bildirim gösterir.',
|
||||
label: 'App.VisualDesigner.Snippet.ApiCall.Label',
|
||||
group: 'App.VisualDesigner.Snippet.Group.Integration',
|
||||
description: 'App.VisualDesigner.Snippet.ApiCall.Description',
|
||||
code: `try {\n const sonuc = await api.get('\${1:/api/app/customer/1}')\n refs.${sql}.setField('\${2:Title}', sonuc?.\${3:name})\n} catch (error) {\n notify(api.errorMessage(error), 'danger')\n}`,
|
||||
},
|
||||
{
|
||||
label: 'Satırlar arasında dolaş',
|
||||
group: 'Kayıt',
|
||||
description: 'Select sonucundaki tüm satırları gezer.',
|
||||
label: 'App.VisualDesigner.Snippet.IterateRows.Label',
|
||||
group: 'App.VisualDesigner.Snippet.Group.Record',
|
||||
description: 'App.VisualDesigner.Snippet.IterateRows.Description',
|
||||
code: `refs.${sql}.getRows().forEach((row, index) => {\n \${1:console.log(index, row)}\n})`,
|
||||
},
|
||||
{
|
||||
label: 'Değişiklik varsa kaydet',
|
||||
group: 'Kayıt',
|
||||
description: 'Boşuna istek atmaz.',
|
||||
label: 'App.VisualDesigner.Snippet.SaveIfChanged.Label',
|
||||
group: 'App.VisualDesigner.Snippet.Group.Record',
|
||||
description: 'App.VisualDesigner.Snippet.SaveIfChanged.Description',
|
||||
code: `if (refs.${sql}.hasChanges()) await refs.${sql}.save()`,
|
||||
},
|
||||
{
|
||||
label: 'Event içeriğini incele',
|
||||
group: 'Akış',
|
||||
description: 'Geliştirirken event nesnesinin ne taşıdığını görmek için.',
|
||||
label: 'App.VisualDesigner.Snippet.InspectEvent.Label',
|
||||
group: 'App.VisualDesigner.Snippet.Group.Flow',
|
||||
description: 'App.VisualDesigner.Snippet.InspectEvent.Description',
|
||||
code: `console.log('\${1:event}', JSON.parse(JSON.stringify(event ?? null)))`,
|
||||
},
|
||||
]
|
||||
|
|
@ -143,11 +144,14 @@ export type DesignerScriptDialectContext = {
|
|||
eventInfo?: SqlDataSourceEvent
|
||||
/** Event'in sahibi komponentin adı; başlıkta gösterilir. */
|
||||
componentLabel?: string
|
||||
/** `useLocalization` hook'undan gelen çeviri fonksiyonu. */
|
||||
translate: TranslateFn
|
||||
}
|
||||
|
||||
export const createDesignerScriptDialect = (
|
||||
context: DesignerScriptDialectContext,
|
||||
): ScriptDialect => {
|
||||
const t = context.translate
|
||||
const sqlContext: DesignerScriptContext = {
|
||||
sqlRef: context.sqlRef,
|
||||
cancelable: Boolean(context.eventInfo?.cancelable),
|
||||
|
|
@ -166,12 +170,23 @@ export const createDesignerScriptDialect = (
|
|||
|
||||
const recipeViews: ScriptRecipeView[] = usableRecipes.map((recipe) => ({
|
||||
id: recipe.id,
|
||||
label: recipe.label,
|
||||
label: t('::' + recipe.label),
|
||||
group: recipe.group,
|
||||
summary: recipe.summary,
|
||||
summary: t('::' + recipe.summary),
|
||||
example: recipe.example,
|
||||
conditionIsArgument: recipe.conditionIsArgument,
|
||||
params: recipe.params.map(toParam),
|
||||
params: recipe.params.map(toParam).map((param) => ({
|
||||
...param,
|
||||
label: t('::' + param.label),
|
||||
help: param.help ? t('::' + param.help) : undefined,
|
||||
placeholder: param.placeholder?.startsWith('App.')
|
||||
? t('::' + param.placeholder)
|
||||
: param.placeholder,
|
||||
choices: param.choices?.map((choice) => ({
|
||||
...choice,
|
||||
label: choice.label?.startsWith('App.') ? t('::' + choice.label) : choice.label,
|
||||
})),
|
||||
})),
|
||||
}))
|
||||
|
||||
const asDesignerRule = (rule: ScriptRule) => rule as unknown as DesignerScriptRule
|
||||
|
|
@ -181,25 +196,51 @@ export const createDesignerScriptDialect = (
|
|||
title: 'Event Script',
|
||||
subject: `${context.componentLabel ? `${context.componentLabel}.` : ''}${context.eventName}`,
|
||||
subjectHint: context.eventInfo?.description,
|
||||
codeHint: context.eventInfo?.cancelable
|
||||
? 'event / refs / api / notify hazır. false döndürürsen işlem iptal edilir.'
|
||||
: 'event / refs / api / notify hazır. Ctrl+Space tamamlama listesini açar.',
|
||||
codeHint: t(
|
||||
context.eventInfo?.cancelable
|
||||
? '::App.VisualDesigner.CodeHintCancelable'
|
||||
: '::App.VisualDesigner.CodeHint',
|
||||
),
|
||||
groups: designerRecipeGroups.map((group) => ({
|
||||
key: group.key,
|
||||
title: group.title,
|
||||
description: group.description,
|
||||
title: t('::' + group.title),
|
||||
description: t('::' + group.description),
|
||||
icon: groupIcons[group.key],
|
||||
})),
|
||||
recipes: recipeViews,
|
||||
operators: DEFAULT_SCRIPT_OPERATORS,
|
||||
conditionKinds: [
|
||||
{ value: 'record', label: 'Kayıt sütunu', optionSource: RECORD_SOURCE },
|
||||
{ value: 'ref', label: 'Komponent değeri', optionSource: REF_SOURCE },
|
||||
{ value: 'event', label: 'Event verisi', optionSource: EVENT_SOURCE },
|
||||
{ value: 'expression', label: 'Serbest ifade', placeholder: `refs.${context.sqlRef}.getMode()` },
|
||||
{
|
||||
value: 'record',
|
||||
label: t('::App.VisualDesigner.ConditionKind.RecordColumn'),
|
||||
optionSource: RECORD_SOURCE,
|
||||
},
|
||||
{
|
||||
value: 'ref',
|
||||
label: t('::App.VisualDesigner.ConditionKind.ComponentValue'),
|
||||
optionSource: REF_SOURCE,
|
||||
},
|
||||
{
|
||||
value: 'event',
|
||||
label: t('::App.VisualDesigner.ConditionKind.EventData'),
|
||||
optionSource: EVENT_SOURCE,
|
||||
},
|
||||
{
|
||||
value: 'expression',
|
||||
label: t('::App.VisualDesigner.ConditionKind.FreeExpression'),
|
||||
placeholder: `refs.${context.sqlRef}.getMode()`,
|
||||
},
|
||||
],
|
||||
apiReference: designerRuntimeApiReference,
|
||||
snippets: snippets(context.sqlRef),
|
||||
apiReference: designerRuntimeApiReference.map((entry) => ({
|
||||
...entry,
|
||||
description: t('::' + entry.description),
|
||||
})),
|
||||
snippets: snippets(context.sqlRef).map((snippet) => ({
|
||||
...snippet,
|
||||
label: t('::' + snippet.label),
|
||||
group: t('::' + snippet.group),
|
||||
description: t('::' + snippet.description),
|
||||
})),
|
||||
getOptions: (optionSource) =>
|
||||
optionSource === RECORD_SOURCE
|
||||
? recordFields
|
||||
|
|
@ -219,27 +260,28 @@ export const createDesignerScriptDialect = (
|
|||
return { ...parsed, rules: parsed.rules as unknown as ScriptRule[] }
|
||||
},
|
||||
describeCondition: (condition) =>
|
||||
describeDesignerCondition(condition as unknown as DesignerRuleCondition),
|
||||
describeDesignerCondition(condition as unknown as DesignerRuleCondition, t),
|
||||
describeRule: (rule) => {
|
||||
const recipe = designerRecipesById.get(rule.recipe as DesignerRecipeId)
|
||||
if (!recipe) return ''
|
||||
const action = recipe.describe(asDesignerRule(rule))
|
||||
const action = recipe.describe(asDesignerRule(rule), t)
|
||||
if (recipe.conditionIsArgument) return action
|
||||
const always = t('::App.VisualDesigner.Describe.Always')
|
||||
const conditions = [rule.condition, ...(rule.conditions ?? [])]
|
||||
.map((condition) =>
|
||||
describeDesignerCondition(condition as unknown as DesignerRuleCondition),
|
||||
describeDesignerCondition(condition as unknown as DesignerRuleCondition, t),
|
||||
)
|
||||
.filter((text) => text !== 'Her zaman')
|
||||
const prefix = conditions.length
|
||||
? conditions.join(rule.join === 'or' ? ' veya ' : ' ve ')
|
||||
: 'Her zaman'
|
||||
.filter((text) => text !== always)
|
||||
const joiner =
|
||||
rule.join === 'or'
|
||||
? ` ${t('::App.VisualDesigner.Word.Or')} `
|
||||
: ` ${t('::App.VisualDesigner.Word.And')} `
|
||||
const prefix = conditions.length ? conditions.join(joiner) : always
|
||||
return `${prefix} → ${action}`
|
||||
},
|
||||
getRuleWarning: (rule) => {
|
||||
const recipe = designerRecipesById.get(rule.recipe as DesignerRecipeId)
|
||||
return recipe?.needsSqlRef && !context.sqlRef
|
||||
? 'Bu kural bir SqlDataSource ref adı gerektiriyor.'
|
||||
: ''
|
||||
return recipe?.needsSqlRef && !context.sqlRef ? t('::App.VisualDesigner.SqlRefRequired') : ''
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -444,25 +444,25 @@ export const SQL_DATA_SOURCE_SLOTS: readonly SqlDataSourceSlot[] = [
|
|||
property: 'selectEndpoint',
|
||||
method: 'GET',
|
||||
label: 'Select (GET)',
|
||||
description: 'Kaydı okuyan endpoint. İçerideki komponentler bu sütunlara bağlanır.',
|
||||
description: 'App.VisualDesigner.Event.X.Description',
|
||||
},
|
||||
{
|
||||
property: 'insertEndpoint',
|
||||
method: 'POST',
|
||||
label: 'Insert (POST)',
|
||||
description: 'Key alanı boşken Save butonu bu endpointi çağırır.',
|
||||
description: 'App.VisualDesigner.Event.X.Description2',
|
||||
},
|
||||
{
|
||||
property: 'updateEndpoint',
|
||||
method: 'PUT',
|
||||
label: 'Update (PUT)',
|
||||
description: 'Key alanı doluyken Save butonu bu endpointi çağırır.',
|
||||
description: 'App.VisualDesigner.Event.X.Description3',
|
||||
},
|
||||
{
|
||||
property: 'deleteEndpoint',
|
||||
method: 'DELETE',
|
||||
label: 'Delete (DELETE)',
|
||||
description: 'Delete butonunu aktifleştirir.',
|
||||
description: 'App.VisualDesigner.Event.X.Description4',
|
||||
},
|
||||
] as const
|
||||
|
||||
|
|
@ -489,101 +489,101 @@ export interface SqlDataSourceEvent {
|
|||
export const SQL_DATA_SOURCE_EVENTS: readonly SqlDataSourceEvent[] = [
|
||||
{
|
||||
name: 'onLoad',
|
||||
label: 'Select tamamlandı',
|
||||
description: 'Select endpointi cevap verdiğinde, satırlar forma yerleştikten sonra çalışır.',
|
||||
label: 'App.VisualDesigner.Event.OnLoad.Label',
|
||||
description: 'App.VisualDesigner.Event.OnLoad.Description',
|
||||
payload: [
|
||||
{ key: 'rows', description: 'Select sonucundaki tüm satırlar' },
|
||||
{ key: 'count', description: 'Satır sayısı' },
|
||||
{ key: 'record', description: 'Aktif satır' },
|
||||
{ key: 'index', description: 'Aktif satırın sırası' },
|
||||
{ key: 'rows', description: 'App.VisualDesigner.Event.Rows.Description' },
|
||||
{ key: 'count', description: 'App.VisualDesigner.Event.Count.Description' },
|
||||
{ key: 'record', description: 'App.VisualDesigner.Event.Record.Description' },
|
||||
{ key: 'index', description: 'App.VisualDesigner.Event.Index.Description' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'onRecordChange',
|
||||
label: 'Aktif kayıt değişti',
|
||||
description: 'Satırlar arasında gezinildiğinde veya yeni kayıt yüklendiğinde çalışır.',
|
||||
label: 'App.VisualDesigner.Event.OnRecordChange.Label',
|
||||
description: 'App.VisualDesigner.Event.OnRecordChange.Description',
|
||||
payload: [
|
||||
{ key: 'record', description: 'Yeni aktif kayıt' },
|
||||
{ key: 'index', description: 'Satır sırası' },
|
||||
{ key: 'rows', description: 'Tüm satırlar' },
|
||||
{ key: 'count', description: 'Satır sayısı' },
|
||||
{ key: 'record', description: 'App.VisualDesigner.Event.Record.Description2' },
|
||||
{ key: 'index', description: 'App.VisualDesigner.Event.Index.Description2' },
|
||||
{ key: 'rows', description: 'App.VisualDesigner.Event.Rows.Description2' },
|
||||
{ key: 'count', description: 'App.VisualDesigner.Event.Count.Description' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'onFieldChange',
|
||||
label: 'Alan değişti',
|
||||
description: 'İçerideki bir komponent kayda değer yazdığında çalışır; hesaplama için idealdir.',
|
||||
label: 'App.VisualDesigner.Event.OnFieldChange.Label',
|
||||
description: 'App.VisualDesigner.Event.OnFieldChange.Description',
|
||||
payload: [
|
||||
{ key: 'field', description: 'Değişen sütun adı' },
|
||||
{ key: 'value', description: 'Yeni değer' },
|
||||
{ key: 'previous', description: 'Önceki değer' },
|
||||
{ key: 'record', description: 'Değişiklik uygulanmış kayıt' },
|
||||
{ key: 'field', description: 'App.VisualDesigner.Event.Field.Description' },
|
||||
{ key: 'value', description: 'App.VisualDesigner.Event.Value.Description' },
|
||||
{ key: 'previous', description: 'App.VisualDesigner.Event.Previous.Description' },
|
||||
{ key: 'record', description: 'App.VisualDesigner.Event.Record.Description3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'onNewRecord',
|
||||
label: 'Yeni kayıt',
|
||||
description: 'Yeni butonuyla boş kayda geçildiğinde çalışır; varsayılan değer yazmak için.',
|
||||
payload: [{ key: 'record', description: 'Boş kayıt' }],
|
||||
label: 'App.VisualDesigner.Event.OnNewRecord.Label',
|
||||
description: 'App.VisualDesigner.Event.OnNewRecord.Description',
|
||||
payload: [{ key: 'record', description: 'App.VisualDesigner.Event.Record.Description4' }],
|
||||
},
|
||||
{
|
||||
name: 'onModeChange',
|
||||
label: 'Mod değişti',
|
||||
description: 'Kayıt new/edit modları arasında geçtiğinde çalışır.',
|
||||
label: 'App.VisualDesigner.Event.OnModeChange.Label',
|
||||
description: 'App.VisualDesigner.Event.OnModeChange.Description',
|
||||
payload: [
|
||||
{ key: 'mode', description: '"new" veya "edit"' },
|
||||
{ key: 'record', description: 'Güncel kayıt' },
|
||||
{ key: 'record', description: 'App.VisualDesigner.Event.Record.Description5' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'onBeforeSave',
|
||||
label: 'Kaydetmeden önce',
|
||||
description: 'Save çağrılmadan önce çalışır. Script false döndürürse kayıt iptal edilir.',
|
||||
label: 'App.VisualDesigner.Event.OnBeforeSave.Label',
|
||||
description: 'App.VisualDesigner.Event.OnBeforeSave.Description',
|
||||
cancelable: true,
|
||||
payload: [
|
||||
{ key: 'record', description: 'Kaydedilecek kayıt' },
|
||||
{ key: 'original', description: 'Yüklendiği andaki hali' },
|
||||
{ key: 'payload', description: 'Endpointe gidecek gövde (Update ise sadece değişenler)' },
|
||||
{ key: 'record', description: 'App.VisualDesigner.Event.Record.Description6' },
|
||||
{ key: 'original', description: 'App.VisualDesigner.Event.Original.Description' },
|
||||
{ key: 'payload', description: 'App.VisualDesigner.Event.Payload.Description' },
|
||||
{ key: 'mode', description: '"new" veya "edit"' },
|
||||
{ key: 'isNew', description: 'Insert mi çalışacak?' },
|
||||
{ key: 'isNew', description: 'App.VisualDesigner.Event.IsNew.Description' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'onAfterSave',
|
||||
label: 'Kaydettikten sonra',
|
||||
description: 'Insert/Update başarıyla tamamlandığında çalışır.',
|
||||
label: 'App.VisualDesigner.Event.OnAfterSave.Label',
|
||||
description: 'App.VisualDesigner.Event.OnAfterSave.Description',
|
||||
payload: [
|
||||
{ key: 'record', description: 'Gönderilen kayıt' },
|
||||
{ key: 'response', description: 'Endpoint cevabı' },
|
||||
{ key: 'mode', description: 'Kaydetme anındaki mod' },
|
||||
{ key: 'isNew', description: 'Insert mi çalıştı?' },
|
||||
{ key: 'record', description: 'App.VisualDesigner.Event.Record.Description7' },
|
||||
{ key: 'response', description: 'App.VisualDesigner.Event.Response.Description' },
|
||||
{ key: 'mode', description: 'App.VisualDesigner.Event.Mode.Description' },
|
||||
{ key: 'isNew', description: 'App.VisualDesigner.Event.IsNew.Description2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'onBeforeDelete',
|
||||
label: 'Silmeden önce',
|
||||
description: 'Onay kutusundan önce çalışır. Script false döndürürse silme iptal edilir.',
|
||||
label: 'App.VisualDesigner.Event.OnBeforeDelete.Label',
|
||||
description: 'App.VisualDesigner.Event.OnBeforeDelete.Description',
|
||||
cancelable: true,
|
||||
payload: [
|
||||
{ key: 'record', description: 'Silinecek kayıt' },
|
||||
{ key: 'key', description: 'Key sütunundaki değer' },
|
||||
{ key: 'record', description: 'App.VisualDesigner.Event.Record.Description8' },
|
||||
{ key: 'key', description: 'App.VisualDesigner.Event.Key.Description' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'onAfterDelete',
|
||||
label: 'Sildikten sonra',
|
||||
description: 'Delete endpointi başarıyla tamamlandığında çalışır.',
|
||||
payload: [{ key: 'record', description: 'Silinen kayıt' }],
|
||||
label: 'App.VisualDesigner.Event.OnAfterDelete.Label',
|
||||
description: 'App.VisualDesigner.Event.OnAfterDelete.Description',
|
||||
payload: [{ key: 'record', description: 'App.VisualDesigner.Event.Record.Description9' }],
|
||||
},
|
||||
{
|
||||
name: 'onError',
|
||||
label: 'Hata oluştu',
|
||||
description: 'Select/Insert/Update/Delete sırasında hata alındığında çalışır.',
|
||||
label: 'App.VisualDesigner.Event.OnError.Label',
|
||||
description: 'App.VisualDesigner.Event.OnError.Description',
|
||||
payload: [
|
||||
{ key: 'message', description: 'Kullanıcıya gösterilen hata metni' },
|
||||
{ key: 'message', description: 'App.VisualDesigner.Event.Message.Description' },
|
||||
{ key: 'action', description: '"reload" | "save" | "delete"' },
|
||||
{ key: 'error', description: 'Ham hata nesnesi' },
|
||||
{ key: 'record', description: 'Hata anındaki kayıt' },
|
||||
{ key: 'error', description: 'App.VisualDesigner.Event.Error.Description' },
|
||||
{ key: 'record', description: 'App.VisualDesigner.Event.Record.Description10' },
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
|
@ -613,7 +613,7 @@ export const getSqlDataSourceKeyField = (node: DesignerNode) =>
|
|||
*/
|
||||
export const SQL_DATA_SOURCE_KEY_SOURCES = [
|
||||
{ value: 'query', label: 'URL query string (?id=…)' },
|
||||
{ value: 'route', label: 'URL son segmenti (/…/5)' },
|
||||
{ value: 'route', label: 'App.VisualDesigner.Event.OnError.Label2' },
|
||||
] as const
|
||||
|
||||
export type SqlDataSourceKeySource = (typeof SQL_DATA_SOURCE_KEY_SOURCES)[number]['value']
|
||||
|
|
|
|||
|
|
@ -67,13 +67,13 @@ export class OrderService {
|
|||
} catch (error) {
|
||||
console.error('Order creation error:', error)
|
||||
|
||||
let errorMessage = 'Sipariş oluşturulurken hata oluştu'
|
||||
let errorMessage = 'Public.payment.createOrderError'
|
||||
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (error.response) {
|
||||
errorMessage = error.response.data?.message || errorMessage
|
||||
} else if (error.request) {
|
||||
errorMessage = 'Sunucuya bağlanılamadı'
|
||||
errorMessage = 'App.Platform.ServerUnreachable'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ import { toast } from '@/components/ui'
|
|||
import Notification from '@/components/ui/Notification'
|
||||
import { VideoroomAttendanceDto, VideoroomChatDto } from '@/proxy/videoroom/models'
|
||||
|
||||
type TranslateFn = (key: string, params?: Record<string, string | number>) => string
|
||||
|
||||
export class SignalRService {
|
||||
/** `useLocalization` hook'undan gelen çeviri fonksiyonu; bildirimlerde kullanılır. */
|
||||
private translate: TranslateFn = (key) => key
|
||||
private connection!: signalR.HubConnection
|
||||
private isConnected: boolean = false
|
||||
private currentSessionId?: string
|
||||
|
|
@ -32,6 +36,10 @@ export class SignalRService {
|
|||
private onIceCandidateReceived?: (fromUserId: string, candidate: RTCIceCandidateInit) => void
|
||||
private onForceCleanup?: () => void
|
||||
|
||||
setTranslate(translate: TranslateFn) {
|
||||
this.translate = translate
|
||||
}
|
||||
|
||||
constructor() {
|
||||
const { auth } = store.getState()
|
||||
|
||||
|
|
@ -99,9 +107,15 @@ export class SignalRService {
|
|||
|
||||
this.connection.onreconnected(async () => {
|
||||
this.isConnected = true
|
||||
toast.push(<Notification title="🔄 Bağlantı tekrar kuruldu" type="success" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Signal.Reconnected')}
|
||||
type="success"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
if (this.currentSessionId && store.getState().auth.user) {
|
||||
const u = store.getState().auth.user
|
||||
|
|
@ -112,7 +126,10 @@ export class SignalRService {
|
|||
this.connection.onclose(async () => {
|
||||
if (this.isKicked) {
|
||||
toast.push(
|
||||
<Notification title="⚠️ Bağlantı koptu, yeniden bağlanılıyor..." type="warning" />,
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Signal.Reconnecting')}
|
||||
type="warning"
|
||||
/>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
this.isConnected = false
|
||||
|
|
@ -131,15 +148,27 @@ export class SignalRService {
|
|||
})
|
||||
|
||||
this.connection.on('Error', (message: string) => {
|
||||
toast.push(<Notification title={`❌ Hata: ${message}`} type="danger" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Signal.Error', { message })}
|
||||
type="danger"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
this.connection.on('Warning', (message: string) => {
|
||||
toast.push(<Notification title={`⚠️ Uyarı: ${message}`} type="warning" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Signal.Warning', { message })}
|
||||
type="warning"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
this.connection.on('Info', (message: string) => {
|
||||
|
|
@ -152,7 +181,7 @@ export class SignalRService {
|
|||
if (this.isKicked) {
|
||||
toast.push(
|
||||
<Notification
|
||||
title="❌ Sınıftan çıkarıldığınız için yeniden bağlanma engellendi"
|
||||
title={this.translate('::App.Videoroom.Signal.KickedNoReconnect')}
|
||||
type="danger"
|
||||
/>,
|
||||
)
|
||||
|
|
@ -163,9 +192,15 @@ export class SignalRService {
|
|||
|
||||
this.connection.on('ForceDisconnect', async (message: string) => {
|
||||
this.isKicked = true
|
||||
toast.push(<Notification title={`❌ Sınıftan çıkarıldınız: ${message}`} type="danger" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Signal.Kicked', { message })}
|
||||
type="danger"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
if (this.onForceCleanup) {
|
||||
this.onForceCleanup()
|
||||
|
|
@ -194,18 +229,24 @@ export class SignalRService {
|
|||
try {
|
||||
const startPromise = this.connection.start()
|
||||
const timeout = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Bağlantı zaman aşımına uğradı')), 10000),
|
||||
setTimeout(
|
||||
() => reject(new Error(this.translate('::App.Videoroom.Signal.Timeout'))),
|
||||
10000,
|
||||
),
|
||||
)
|
||||
|
||||
await Promise.race([startPromise, timeout])
|
||||
this.isConnected = true
|
||||
toast.push(<Notification title="✅ Bağlantı kuruldu" type="success" />, {
|
||||
toast.push(
|
||||
<Notification title={this.translate('::App.Videoroom.Signal.Connected')} type="success" />,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
} catch {
|
||||
toast.push(
|
||||
<Notification
|
||||
title="⚠️ Sunucuya bağlanılamadı. Lütfen sayfayı yenileyin veya internet bağlantınızı kontrol edin."
|
||||
title={this.translate('::App.Videoroom.Signal.ServerUnreachable')}
|
||||
type="danger"
|
||||
/>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -224,7 +265,7 @@ export class SignalRService {
|
|||
if (!this.isConnected) {
|
||||
toast.push(
|
||||
<Notification
|
||||
title="⚠️ Bağlantı yok. Sınıfa katılmadan önce bağlantıyı kontrol edin."
|
||||
title={this.translate('::App.Videoroom.Signal.NoConnection')}
|
||||
type="warning"
|
||||
/>,
|
||||
{
|
||||
|
|
@ -238,9 +279,12 @@ export class SignalRService {
|
|||
try {
|
||||
await this.connection.invoke('JoinClass', sessionId, userId, userName, isTeacher, isActive)
|
||||
} catch {
|
||||
toast.push(<Notification title="❌ Sınıfa katılamadı" type="danger" />, {
|
||||
toast.push(
|
||||
<Notification title={this.translate('::App.Videoroom.Signal.JoinFailed')} type="danger" />,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -256,9 +300,15 @@ export class SignalRService {
|
|||
await this.connection.invoke('LeaveClass', sessionId)
|
||||
this.currentSessionId = undefined
|
||||
} catch {
|
||||
toast.push(<Notification title="⚠️ Çıkış başarısız" type="warning" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Signal.LeaveFailed')}
|
||||
type="warning"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -297,9 +347,15 @@ export class SignalRService {
|
|||
'public',
|
||||
)
|
||||
} catch {
|
||||
toast.push(<Notification title="❌ Mesaj gönderilemedi" type="danger" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.PublicMessageFailed')}
|
||||
type="danger"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -344,9 +400,15 @@ export class SignalRService {
|
|||
'private',
|
||||
)
|
||||
} catch {
|
||||
toast.push(<Notification title="❌ Özel mesaj gönderilemedi" type="danger" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.PrivateMessageFailed')}
|
||||
type="danger"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -384,9 +446,12 @@ export class SignalRService {
|
|||
isTeacher,
|
||||
)
|
||||
} catch {
|
||||
toast.push(<Notification title="❌ Duyuru gönderilemedi" type="danger" />, {
|
||||
toast.push(
|
||||
<Notification title={this.translate('::App.Videoroom.AnnouncementFailed')} type="danger" />,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -406,9 +471,12 @@ export class SignalRService {
|
|||
try {
|
||||
await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted, isTeacher)
|
||||
} catch {
|
||||
toast.push(<Notification title="⚠️ Katılımcı susturulamadı" type="warning" />, {
|
||||
toast.push(
|
||||
<Notification title={this.translate('::App.Videoroom.MuteFailed')} type="warning" />,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -423,9 +491,15 @@ export class SignalRService {
|
|||
try {
|
||||
await this.connection.invoke('RaiseHand', sessionId, studentId, studentName)
|
||||
} catch {
|
||||
toast.push(<Notification title="❌ El kaldırma başarısız" type="danger" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Signal.RaiseHandFailed')}
|
||||
type="danger"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -440,9 +514,12 @@ export class SignalRService {
|
|||
try {
|
||||
await this.connection.invoke('KickParticipant', sessionId, participantId)
|
||||
} catch {
|
||||
toast.push(<Notification title="❌ Katılımcı atılamadı" type="danger" />, {
|
||||
toast.push(
|
||||
<Notification title={this.translate('::App.Videoroom.KickFailed')} type="danger" />,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -457,9 +534,15 @@ export class SignalRService {
|
|||
try {
|
||||
await this.connection.invoke('ApproveHandRaise', sessionId, studentId)
|
||||
} catch {
|
||||
toast.push(<Notification title="⚠️ El kaldırma onayı başarısız" type="warning" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Signal.HandRaiseApproveFailed')}
|
||||
type="warning"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -474,9 +557,15 @@ export class SignalRService {
|
|||
try {
|
||||
await this.connection.invoke('DismissHandRaise', sessionId, studentId)
|
||||
} catch {
|
||||
toast.push(<Notification title="⚠️ El indirme başarısız" type="warning" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Signal.LowerHandFailed')}
|
||||
type="warning"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -554,9 +643,15 @@ export class SignalRService {
|
|||
try {
|
||||
await this.connection.invoke('LeaveClass', this.currentSessionId)
|
||||
} catch {
|
||||
toast.push(<Notification title="⚠️ Bağlantı koparılırken hata" type="warning" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Signal.DisconnectError')}
|
||||
type="warning"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if (this.connection) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
import { toast } from '@/components/ui'
|
||||
import Notification from '@/components/ui/Notification'
|
||||
|
||||
type TranslateFn = (key: string, params?: Record<string, string | number>) => string
|
||||
|
||||
export class WebRTCService {
|
||||
/** `useLocalization` hook'undan gelen çeviri fonksiyonu; bildirimlerde kullanılır. */
|
||||
private translate: TranslateFn = (key) => key
|
||||
private peerConnections: Map<string, RTCPeerConnection> = new Map()
|
||||
private retryCounts: Map<string, number> = new Map()
|
||||
private maxRetries = 3
|
||||
private signalRService: any
|
||||
private sessionId: string = ''
|
||||
|
||||
setTranslate(translate: TranslateFn) {
|
||||
this.translate = translate
|
||||
}
|
||||
|
||||
private localStream: MediaStream | null = null
|
||||
private onRemoteStream?: (userId: string, stream: MediaStream) => void
|
||||
private onIceCandidate?: (userId: string, candidate: RTCIceCandidateInit) => void
|
||||
|
|
@ -50,7 +58,7 @@ export class WebRTCService {
|
|||
} catch {
|
||||
toast.push(
|
||||
<Notification
|
||||
title="❌ Kamera/Mikrofon erişilemedi. Tarayıcı ayarlarınızı veya izinleri kontrol edin."
|
||||
title={this.translate('::App.Videoroom.Rtc.MediaAccessFailed')}
|
||||
type="danger"
|
||||
/>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -93,7 +101,10 @@ export class WebRTCService {
|
|||
if (retries < this.maxRetries) {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={`⚠️ Bağlantı başarısız, yeniden deneniyor (${retries + 1}/${this.maxRetries})`}
|
||||
title={this.translate('::App.Videoroom.Rtc.RetryingConnection', {
|
||||
current: retries + 1,
|
||||
total: this.maxRetries,
|
||||
})}
|
||||
type="warning"
|
||||
/>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -103,7 +114,9 @@ export class WebRTCService {
|
|||
} else {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={`❌ Bağlantı kurulamadı (${this.maxRetries} deneme başarısız).`}
|
||||
title={this.translate('::App.Videoroom.Rtc.ConnectionFailed', {
|
||||
total: this.maxRetries,
|
||||
})}
|
||||
type="danger"
|
||||
/>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -120,7 +133,7 @@ export class WebRTCService {
|
|||
} catch {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={`⚠️ ICE candidate eklenemedi. Kullanıcı: ${userId}`}
|
||||
title={this.translate('::App.Videoroom.Rtc.IceCandidateFailed', { userId })}
|
||||
type="warning"
|
||||
/>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -151,9 +164,12 @@ export class WebRTCService {
|
|||
await pc.setLocalDescription(offer)
|
||||
return offer
|
||||
} catch {
|
||||
toast.push(<Notification title="❌ Offer oluşturulamadı" type="danger" />, {
|
||||
toast.push(
|
||||
<Notification title={this.translate('::App.Videoroom.Rtc.OfferFailed')} type="danger" />,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
throw new Error('Offer creation failed')
|
||||
}
|
||||
}
|
||||
|
|
@ -171,9 +187,12 @@ export class WebRTCService {
|
|||
await pc.setLocalDescription(answer)
|
||||
return answer
|
||||
} catch {
|
||||
toast.push(<Notification title="❌ Answer oluşturulamadı" type="danger" />, {
|
||||
toast.push(
|
||||
<Notification title={this.translate('::App.Videoroom.Rtc.AnswerFailed')} type="danger" />,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
throw new Error('Answer creation failed')
|
||||
}
|
||||
}
|
||||
|
|
@ -200,7 +219,7 @@ export class WebRTCService {
|
|||
} catch {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={`⚠️ ICE candidate eklenemedi. Kullanıcı: ${userId}`}
|
||||
title={this.translate('::App.Videoroom.Rtc.IceCandidateFailed', { userId })}
|
||||
type="warning"
|
||||
/>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -240,9 +259,12 @@ export class WebRTCService {
|
|||
})
|
||||
}
|
||||
} catch {
|
||||
toast.push(<Notification title="❌ Kamera açılamadı" type="danger" />, {
|
||||
toast.push(
|
||||
<Notification title={this.translate('::App.Videoroom.Rtc.CameraFailed')} type="danger" />,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -269,9 +291,15 @@ export class WebRTCService {
|
|||
})
|
||||
}
|
||||
} catch {
|
||||
toast.push(<Notification title="❌ Mikrofon açılamadı" type="danger" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Rtc.MicrophoneFailed')}
|
||||
type="danger"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -288,14 +316,26 @@ export class WebRTCService {
|
|||
if (this.signalRService) {
|
||||
await this.signalRService.sendOffer(this.sessionId, userId, offer)
|
||||
} else {
|
||||
toast.push(<Notification title="⚠️ Tekrar bağlanma başarısız" type="warning" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Rtc.ReconnectFailed')}
|
||||
type="warning"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
toast.push(<Notification title="❌ ICE restart başarısız" type="danger" />, {
|
||||
toast.push(
|
||||
<Notification
|
||||
title={this.translate('::App.Videoroom.Rtc.IceRestartFailed')}
|
||||
type="danger"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export const useOrders = () => {
|
|||
setError(null)
|
||||
} catch (err) {
|
||||
console.error('Products fetch error:', err)
|
||||
setError('Ürünler yüklenirken hata oluştu')
|
||||
setError('App.Orders.LoadProductsError')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export function usePWA(): boolean {
|
||||
const [isPWA, setIsPWA] = useState(false);
|
||||
const [isPWA, setIsPWA] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const checkPWA = () => {
|
||||
const isStandalone = window.matchMedia('(display-mode: standalone)').matches || (window.navigator as any).standalone;
|
||||
setIsPWA(isStandalone);
|
||||
};
|
||||
const isStandalone =
|
||||
window.matchMedia('(display-mode: standalone)').matches ||
|
||||
(window.navigator as any).standalone
|
||||
setIsPWA(isStandalone)
|
||||
}
|
||||
|
||||
checkPWA(); // İlk kontrolü yap
|
||||
checkPWA() // İlk kontrolü yap
|
||||
|
||||
// display-mode değiştiğinde güncellenmesi için event listener ekle
|
||||
window.matchMedia('(display-mode: standalone)').addEventListener('change', checkPWA);
|
||||
window.matchMedia('(display-mode: standalone)').addEventListener('change', checkPWA)
|
||||
|
||||
return () => {
|
||||
// Event listener'ı temizle
|
||||
window.matchMedia('(display-mode: standalone)').removeEventListener('change', checkPWA);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return isPWA;
|
||||
window.matchMedia('(display-mode: standalone)').removeEventListener('change', checkPWA)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return isPWA
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
import { FiBell, FiCheck, FiGitBranch, FiPlay, FiSlash } from "react-icons/fi";
|
||||
import { FiBell, FiCheck, FiGitBranch, FiPlay, FiSlash } from 'react-icons/fi'
|
||||
|
||||
export const kindOptions = [
|
||||
{ value: "Start", label: "Başlat" },
|
||||
{ value: "Compare", label: "Karşılaştırma" },
|
||||
{ value: "Approval", label: "Onaylanacak kişi" },
|
||||
{ value: "Inform", label: "Bilgilendirme" },
|
||||
{ value: "End", label: "Akışı bitir" },
|
||||
];
|
||||
{ value: 'Start', label: 'ListForms.Workflow.Kind.Start' },
|
||||
{ value: 'Compare', label: 'ListForms.Workflow.Kind.Compare' },
|
||||
{ value: 'Approval', label: 'ListForms.Workflow.Kind.Approval' },
|
||||
{ value: 'Inform', label: 'ListForms.Workflow.Kind.Inform' },
|
||||
{ value: 'End', label: 'ListForms.Workflow.Kind.End' },
|
||||
]
|
||||
|
||||
export const operatorOptions = [">", ">=", "<", "<=", "=", "!="].map(
|
||||
(value) => ({
|
||||
export const operatorOptions = ['>', '>=', '<', '<=', '=', '!='].map((value) => ({
|
||||
value,
|
||||
label: value,
|
||||
}),
|
||||
);
|
||||
}))
|
||||
|
||||
export const kindIcon: Record<string, any> = {
|
||||
Start: FiPlay as any,
|
||||
|
|
@ -21,13 +19,13 @@ export const kindIcon: Record<string, any> = {
|
|||
Approval: FiCheck as any,
|
||||
Inform: FiBell as any,
|
||||
End: FiSlash as any,
|
||||
};
|
||||
}
|
||||
|
||||
export const nodeSize = {
|
||||
width: 176,
|
||||
height: 128,
|
||||
};
|
||||
}
|
||||
|
||||
export function getNodeHeight(item: { kind: string }) {
|
||||
return item?.kind === "Compare" ? 158 : nodeSize.height;
|
||||
return item?.kind === 'Compare' ? 158 : nodeSize.height
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,27 +153,27 @@ export function collectLinks(criteria: WorkflowCriteriaDto[]) {
|
|||
return
|
||||
}
|
||||
|
||||
addLink(links, criteria, source, source.nextOnStart, 'Sonraki', 'next', {
|
||||
addLink(links, criteria, source, source.nextOnStart, OUTCOME_KEYS.nextOnStart, 'next', {
|
||||
index: 0,
|
||||
count: 1,
|
||||
field: 'nextOnStart',
|
||||
})
|
||||
addLink(links, criteria, source, source.nextOnTrue, 'Doğru', 'true', {
|
||||
addLink(links, criteria, source, source.nextOnTrue, OUTCOME_KEYS.nextOnTrue, 'true', {
|
||||
index: 0,
|
||||
count: 2,
|
||||
field: 'nextOnTrue',
|
||||
})
|
||||
addLink(links, criteria, source, source.nextOnFalse, 'Yanlış', 'false', {
|
||||
addLink(links, criteria, source, source.nextOnFalse, OUTCOME_KEYS.nextOnFalse, 'false', {
|
||||
index: 1,
|
||||
count: 2,
|
||||
field: 'nextOnFalse',
|
||||
})
|
||||
addLink(links, criteria, source, source.nextOnApprove, 'Onay', 'approve', {
|
||||
addLink(links, criteria, source, source.nextOnApprove, OUTCOME_KEYS.nextOnApprove, 'approve', {
|
||||
index: 0,
|
||||
count: 2,
|
||||
field: 'nextOnApprove',
|
||||
})
|
||||
addLink(links, criteria, source, source.nextOnReject, 'Red', 'reject', {
|
||||
addLink(links, criteria, source, source.nextOnReject, OUTCOME_KEYS.nextOnReject, 'reject', {
|
||||
index: 1,
|
||||
count: 2,
|
||||
field: 'nextOnReject',
|
||||
|
|
@ -242,43 +242,53 @@ function sideToward(from: WorkflowCriteriaDto, to: WorkflowCriteriaDto) {
|
|||
return dy >= 0 ? 'bottom' : 'top'
|
||||
}
|
||||
|
||||
/** Çıkış etiketleri lokalizasyon anahtarı olarak taşınır; ekranda `translate` ile çözülür. */
|
||||
export const OUTCOME_KEYS = {
|
||||
nextOnStart: 'ListForms.Workflow.Outcome.Next',
|
||||
nextOnTrue: 'ListForms.Workflow.Outcome.True',
|
||||
nextOnFalse: 'ListForms.Workflow.Outcome.False',
|
||||
nextOnApprove: 'ListForms.Workflow.Outcome.Approve',
|
||||
nextOnReject: 'ListForms.Workflow.Outcome.Reject',
|
||||
compare: 'ListForms.Workflow.Outcome.CompareState',
|
||||
} as const
|
||||
|
||||
export function getNodeOutcomes(item: WorkflowCriteriaDto): WorkflowOutcome[] {
|
||||
if (item.kind === 'Compare') {
|
||||
const outcomes = item.compareOutcomes?.length
|
||||
? item.compareOutcomes
|
||||
: [
|
||||
{ label: 'Doğru', targetId: item.nextOnTrue },
|
||||
{ label: 'Yanlış', targetId: item.nextOnFalse },
|
||||
{ label: OUTCOME_KEYS.nextOnTrue, targetId: item.nextOnTrue },
|
||||
{ label: OUTCOME_KEYS.nextOnFalse, targetId: item.nextOnFalse },
|
||||
]
|
||||
|
||||
return outcomes.slice(0, 4).map((outcome, index) => ({
|
||||
field: `compareOutcomes:${index}`,
|
||||
label: outcome.label || `Durum ${index + 1}`,
|
||||
label: outcome.label || `#${index + 1}`,
|
||||
targetId: outcome.targetId,
|
||||
}))
|
||||
}
|
||||
|
||||
if (item.kind === 'Approval') {
|
||||
return [
|
||||
{ field: 'nextOnApprove', label: 'Onay', targetId: item.nextOnApprove },
|
||||
{ field: 'nextOnReject', label: 'Red', targetId: item.nextOnReject },
|
||||
{ field: 'nextOnApprove', label: OUTCOME_KEYS.nextOnApprove, targetId: item.nextOnApprove },
|
||||
{ field: 'nextOnReject', label: OUTCOME_KEYS.nextOnReject, targetId: item.nextOnReject },
|
||||
]
|
||||
}
|
||||
|
||||
if (item.kind === 'End') return []
|
||||
|
||||
return [{ field: 'nextOnStart', label: 'Sonraki', targetId: item.nextOnStart }]
|
||||
return [{ field: 'nextOnStart', label: OUTCOME_KEYS.nextOnStart, targetId: item.nextOnStart }]
|
||||
}
|
||||
|
||||
export function outcomeLabel(field?: string) {
|
||||
if (field?.startsWith('compareOutcomes:')) return 'Karşılaştırma durumu'
|
||||
if (field?.startsWith('compareOutcomes:')) return OUTCOME_KEYS.compare
|
||||
|
||||
const labels: Record<string, string> = {
|
||||
nextOnStart: 'Sonraki',
|
||||
nextOnTrue: 'Doğru',
|
||||
nextOnFalse: 'Yanlış',
|
||||
nextOnApprove: 'Onay',
|
||||
nextOnReject: 'Red',
|
||||
nextOnStart: OUTCOME_KEYS.nextOnStart,
|
||||
nextOnTrue: OUTCOME_KEYS.nextOnTrue,
|
||||
nextOnFalse: OUTCOME_KEYS.nextOnFalse,
|
||||
nextOnApprove: OUTCOME_KEYS.nextOnApprove,
|
||||
nextOnReject: OUTCOME_KEYS.nextOnReject,
|
||||
}
|
||||
|
||||
return field ? labels[field] : undefined
|
||||
|
|
@ -406,7 +416,9 @@ function formatCriteriaId(number: number) {
|
|||
|
||||
function isDefaultTitleVariant(title: string | null | undefined, baseTitle: string) {
|
||||
const normalized = (title || '').trim()
|
||||
return normalized === baseTitle || new RegExp(`^${escapeRegExp(baseTitle)}\\d+$`, 'u').test(normalized)
|
||||
return (
|
||||
normalized === baseTitle || new RegExp(`^${escapeRegExp(baseTitle)}\\d+$`, 'u').test(normalized)
|
||||
)
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string) {
|
||||
|
|
@ -432,8 +444,7 @@ export function toCriteriaForm(item: WorkflowCriteriaDto): WorkflowCriteriaForm
|
|||
}
|
||||
|
||||
export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput {
|
||||
const sharedPerson =
|
||||
item.kind === 'Approval' || item.kind === 'Inform' ? item.approver || '' : ''
|
||||
const sharedPerson = item.kind === 'Approval' || item.kind === 'Inform' ? item.approver || '' : ''
|
||||
const compareOutcomes = (item.compareOutcomes || [])
|
||||
.slice(0, 4)
|
||||
.filter((outcome) => outcome.label?.trim())
|
||||
|
|
@ -465,12 +476,12 @@ export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput
|
|||
export function defaultTitle(kind: string) {
|
||||
return (
|
||||
{
|
||||
Start: 'İş Akışı Başlat',
|
||||
Compare: 'Karşılaştırma',
|
||||
Approval: 'Onay',
|
||||
Inform: 'Bilgilendirme',
|
||||
End: 'İş Akışı Bitir',
|
||||
}[kind] ?? 'İş Akışı Adımı'
|
||||
Start: 'ListForms.Workflow.Kind.Start',
|
||||
Compare: 'ListForms.Workflow.Kind.Compare',
|
||||
Approval: 'ListForms.Workflow.Kind.Approval',
|
||||
Inform: 'ListForms.Workflow.Kind.Inform',
|
||||
End: 'ListForms.Workflow.Kind.End',
|
||||
}[kind] ?? 'ListForms.Workflow.Kind.Step'
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -570,7 +581,7 @@ export function compareOutcomeRuleText(
|
|||
`${condition.compareColumn} ${condition.compareOperator} ${formatCompactValue(condition.compareValue)}`,
|
||||
)
|
||||
.join(' ve ')
|
||||
: 'Kural yok'
|
||||
: 'ListForms.Workflow.NoRule'
|
||||
}
|
||||
|
||||
export function formatCompactValue(value: number | string | null | undefined) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ import { useStoreState } from '@/store'
|
|||
* Kullanıcı bilinçli olarak kurulum sihirbazına yönlendirilmez — çalışan bir sistemde
|
||||
* geçici bir arıza kimseyi kurulum ekranına düşürmemeli. Bunun yerine durum açıkça
|
||||
* belirtilir, sunucu arka planda izlenir ve toparlandığı anda sayfa kendini yeniler.
|
||||
*
|
||||
* Metinler bilinçli olarak `translate` ile lokalize EDİLMEZ: bu ekran yalnızca
|
||||
* konfigürasyon (ve onunla gelen lokalizasyon sözlüğü) yüklenemediğinde render edilir,
|
||||
* dolayısıyla `translate` anahtarın kendisini döndürürdü. Açılış öncesi tüm ekranlar
|
||||
* bu nedenle sabit İngilizce tutulur.
|
||||
*/
|
||||
const ServiceUnavailable = () => {
|
||||
const isDark = useStoreState((state) => state.theme.mode) === 'dark'
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { ErrorBoundary } from 'react-error-boundary'
|
|||
import type { FallbackProps } from 'react-error-boundary'
|
||||
import { FaArrowLeft } from 'react-icons/fa'
|
||||
import { Navigate, useLocation } from 'react-router-dom'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import DialogProvider from './shared/DialogContext'
|
||||
import DialogShowComponent from './shared/DialogContext/DialogShowComponent'
|
||||
import UiDialog from './shared/UiDialog'
|
||||
|
|
@ -20,13 +21,16 @@ interface ViewsProps {
|
|||
|
||||
// react-error-boundary v6'dan itibaren `error` `unknown` tipinde geliyor;
|
||||
// throw edilen her şey Error olmayabilir, o yüzden daraltıyoruz.
|
||||
function fallbackRender({ error, resetErrorBoundary }: FallbackProps) {
|
||||
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||
const { translate } = useLocalization()
|
||||
const { name, message } =
|
||||
error instanceof Error ? error : { name: 'Hata!', message: String(error) }
|
||||
error instanceof Error
|
||||
? error
|
||||
: { name: translate('::App.Platform.Error'), message: String(error) }
|
||||
|
||||
return (
|
||||
<Alert showIcon className="mb-4" type="danger">
|
||||
<h5>{name ?? 'Hata!'}</h5>
|
||||
<h5>{name ?? translate('::App.Platform.Error')}</h5>
|
||||
<div>{message}</div>
|
||||
<Button
|
||||
icon={<FaArrowLeft />}
|
||||
|
|
@ -49,7 +53,7 @@ const Views = (props: ViewsProps) => {
|
|||
}, [location, setWarning])
|
||||
|
||||
return (
|
||||
<ErrorBoundary fallbackRender={fallbackRender}>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<Suspense fallback={<Loading loading={true} />}>
|
||||
{!!warning?.length && (
|
||||
<Alert showIcon className="mb-4 text-sm text-left" type="warning">
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { NotificationChannelColors } from '@/constants/notification-channel.enum
|
|||
import { NotificationDto } from '@/proxy/notification/models'
|
||||
import classNames from 'classnames'
|
||||
import dayjs from 'dayjs'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
type EventProps = {
|
||||
data: NotificationDto
|
||||
|
|
@ -19,6 +20,8 @@ const HighlightedText = ({ children, className }: CommonProps) => {
|
|||
}
|
||||
|
||||
const Event = ({ data, compact }: EventProps) => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
return (
|
||||
<>
|
||||
{compact ? (
|
||||
|
|
@ -28,7 +31,11 @@ const Event = ({ data, compact }: EventProps) => {
|
|||
<span className="text-xs">{dayjs(data.creationTime).format('hh:mm A')}</span>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<span className="mx-1">bildirim gönderdi {data.notificationType} </span>
|
||||
<span className="mx-1">
|
||||
{translate('::Abp.Identity.ActivityLogs.SentNotificationOfType', {
|
||||
type: data.notificationType,
|
||||
})}{' '}
|
||||
</span>
|
||||
|
||||
<Tag
|
||||
prefix
|
||||
|
|
@ -42,7 +49,8 @@ const Event = ({ data, compact }: EventProps) => {
|
|||
) : (
|
||||
<div className="my-1 flex items-center">
|
||||
<div>
|
||||
<HighlightedText>{data.notificationType}</HighlightedText> bildirimi gönderdi.
|
||||
<HighlightedText>{data.notificationType}</HighlightedText>{' '}
|
||||
{translate('::Abp.Identity.ActivityLogs.SentNotification')}
|
||||
</div>
|
||||
|
||||
<Tag
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ const Log = ({
|
|||
</div>
|
||||
<Timeline className="dark:bg-gray-800">
|
||||
{isEmpty(notifications[group]) ? (
|
||||
<Timeline.Item className="dark:text-gray-400">Bildirim yok</Timeline.Item>
|
||||
<Timeline.Item className="dark:text-gray-400">
|
||||
{translate('::Abp.Identity.ActivityLogs.NoNotification')}
|
||||
</Timeline.Item>
|
||||
) : (
|
||||
notifications[group].map((notification) => (
|
||||
<Timeline.Item
|
||||
|
|
@ -56,11 +58,13 @@ const Log = ({
|
|||
))}
|
||||
<div className="text-center">
|
||||
{loadable ? (
|
||||
<Button size="sm" loading={isLoading} variant='solid' onClick={onLoadMore}>
|
||||
<Button size="sm" loading={isLoading} variant="solid" onClick={onLoadMore}>
|
||||
{translate('::Abp.Identity.ActivityLogs.LoadMore')}
|
||||
</Button>
|
||||
) : (
|
||||
<span className="dark:text-gray-400">{translate('::Abp.Identity.ActivityLogs.ReceivedAllNotifications')}</span>
|
||||
<span className="dark:text-gray-400">
|
||||
{translate('::Abp.Identity.ActivityLogs.ReceivedAllNotifications')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -44,8 +44,15 @@ const LogFilter = ({
|
|||
const { translate } = useLocalization()
|
||||
|
||||
const content = (
|
||||
<div className={classNames('rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4', className)}>
|
||||
<h5 className="mb-4 text-base font-semibold text-gray-900 dark:text-white">{translate('::Abp.Identity.ActivityLogs.Filters')}</h5>
|
||||
<div
|
||||
className={classNames(
|
||||
'rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<h5 className="mb-4 text-base font-semibold text-gray-900 dark:text-white">
|
||||
{translate('::Abp.Identity.ActivityLogs.Filters')}
|
||||
</h5>
|
||||
<Checkbox.Group
|
||||
vertical
|
||||
value={filter}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,15 @@ const getFileTypeLabel = (item: FileItemType, translate: (key: string) => string
|
|||
|
||||
// Defined at module level: nesting it inside FileItem would remount the <img>
|
||||
// (and restart the download) on every parent render.
|
||||
const ImagePreview = ({ src, alt, fallback }: { src: string; alt: string; fallback: ReactNode }) => {
|
||||
const ImagePreview = ({
|
||||
src,
|
||||
alt,
|
||||
fallback,
|
||||
}: {
|
||||
src: string
|
||||
alt: string
|
||||
fallback: ReactNode
|
||||
}) => {
|
||||
const [imageError, setImageError] = useState(false)
|
||||
|
||||
return (
|
||||
|
|
@ -250,16 +258,16 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
|||
navigator.clipboard.writeText(fileUrl).then(
|
||||
() => {
|
||||
toast.push(
|
||||
<Notification type="success" title="Başarılı">
|
||||
Dosya URL'si panoya kopyalandı
|
||||
<Notification type="success" title={translate('::App.Platform.Success')}>
|
||||
{translate('::FileManager.UrlCopied')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
},
|
||||
() => {
|
||||
toast.push(
|
||||
<Notification type="danger" title="Hata">
|
||||
URL kopyalanamadı
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{translate('::FileManager.UrlCopyFailed')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -438,7 +446,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
|||
</span>
|
||||
) : item.type === 'folder' && typeof item.childCount === 'number' ? (
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{item.childCount} öğe
|
||||
{translate('::FileManager.ItemCount', { count: item.childCount })}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">-</span>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,11 @@ export const CreateFolderModal = forwardRef<HTMLDivElement, CreateFolderModalPro
|
|||
<Dialog isOpen={isOpen} onClose={handleClose}>
|
||||
<div ref={ref}>
|
||||
<form className="py-6" onSubmit={handleSubmit}>
|
||||
<FormItem label={translate('::FileManager.FolderName')} invalid={!!error} errorMessage={error}>
|
||||
<FormItem
|
||||
label={translate('::FileManager.FolderName')}
|
||||
invalid={!!error}
|
||||
errorMessage={error}
|
||||
>
|
||||
<Input
|
||||
autoFocus
|
||||
value={folderName}
|
||||
|
|
@ -58,11 +62,11 @@ export const CreateFolderModal = forwardRef<HTMLDivElement, CreateFolderModalPro
|
|||
</form>
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-4 border-t">
|
||||
<Button size='sm' variant="default" disabled={loading} onClick={handleClose}>
|
||||
<Button size="sm" variant="default" disabled={loading} onClick={handleClose}>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
size="sm"
|
||||
variant="solid"
|
||||
loading={loading}
|
||||
disabled={!folderName.trim()}
|
||||
|
|
@ -156,11 +160,11 @@ export const RenameItemModal = forwardRef<HTMLDivElement, RenameItemModalProps>(
|
|||
</form>
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-4 border-t">
|
||||
<Button size='sm' variant="default" disabled={loading} onClick={handleClose}>
|
||||
<Button size="sm" variant="default" disabled={loading} onClick={handleClose}>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
size="sm"
|
||||
variant="solid"
|
||||
loading={loading}
|
||||
disabled={!newName.trim()}
|
||||
|
|
@ -239,15 +243,10 @@ export const DeleteConfirmModal = forwardRef<HTMLDivElement, DeleteConfirmModalP
|
|||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-4 border-t">
|
||||
<Button size='sm' variant="default" disabled={loading} onClick={onClose}>
|
||||
<Button size="sm" variant="default" disabled={loading} onClick={onClose}>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
variant="solid"
|
||||
loading={loading}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<Button size="sm" variant="solid" loading={loading} onClick={handleDelete}>
|
||||
{translate('::Delete')}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -651,6 +651,7 @@ function OrgChartTree({
|
|||
setPositions: Dispatch<SetStateAction<PositionMap>>
|
||||
edges: EdgeLink[]
|
||||
}) {
|
||||
const { translate } = useLocalization()
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const nodeRefs = useRef<NodeRefMap>({})
|
||||
const cardRefs = useRef<CardRefMap>({})
|
||||
|
|
@ -765,7 +766,7 @@ function OrgChartTree({
|
|||
return (
|
||||
<div className="flex flex-col items-center justify-center py-24 text-slate-400">
|
||||
<FaSitemap className="w-12 h-12 mb-4 opacity-30" />
|
||||
<p className="text-sm">Veri bulunamadı</p>
|
||||
<p className="text-sm">{translate('::App.NoDataFound')}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -874,7 +875,7 @@ const OrgChart = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="Organizasyon Şeması" />
|
||||
<PageTitle title={translate('::App.Definitions.OrgChart')} />
|
||||
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Toolbar */}
|
||||
|
|
@ -882,7 +883,7 @@ const OrgChart = () => {
|
|||
<div className="flex items-center gap-3 min-w-0">
|
||||
{MenuIcon}
|
||||
<h4 className="text-sm font-medium truncate text-gray-900 dark:text-white">
|
||||
{translate('::App.Definitions.OrgChart') || 'Organizasyon Şeması'}
|
||||
{translate('::App.Definitions.OrgChart')}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
|
|
@ -900,9 +901,7 @@ const OrgChart = () => {
|
|||
}`}
|
||||
onClick={() => setMode('department')}
|
||||
>
|
||||
<span className="hidden sm:inline">
|
||||
{translate('::App.Hr.Department') || 'Departman'}
|
||||
</span>
|
||||
<span className="hidden sm:inline">{translate('::App.Hr.Department')}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="plain"
|
||||
|
|
@ -916,9 +915,7 @@ const OrgChart = () => {
|
|||
}`}
|
||||
onClick={() => setMode('jobPosition')}
|
||||
>
|
||||
<span className="hidden sm:inline">
|
||||
{translate('::App.Hr.JobPosition') || 'Pozisyon'}
|
||||
</span>
|
||||
<span className="hidden sm:inline">{translate('::App.Hr.JobPosition')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
@ -931,7 +928,7 @@ const OrgChart = () => {
|
|||
onChange={(e) => setShowUsers(e.target.checked)}
|
||||
/>
|
||||
<span className="hidden sm:inline">
|
||||
{translate('::App.Definitions.OrgChart.ShowUsers') || 'Kullanıcılar'}
|
||||
{translate('::App.Definitions.OrgChart.ShowUsers')}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
|
@ -972,10 +969,12 @@ const OrgChart = () => {
|
|||
shape="none"
|
||||
icon={<FaFileImage className="h-3.5 w-3.5 flex-shrink-0" />}
|
||||
className="!inline-flex !h-auto !items-center !justify-center gap-1.5 !rounded-lg bg-slate-100 !p-1.5 text-xs font-medium text-slate-600 transition-colors hover:!bg-slate-200 disabled:opacity-50 dark:bg-gray-800 dark:text-gray-300 dark:hover:!bg-gray-700 sm:!p-2 sm:text-sm"
|
||||
title="JPG olarak indir"
|
||||
title={translate('::App.Definitions.OrgChart.ExportJpg')}
|
||||
onClick={handleExportJpg}
|
||||
>
|
||||
<span className="hidden sm:inline">{exporting ? 'İşleniyor…' : 'Export'}</span>
|
||||
<span className="hidden sm:inline">
|
||||
{exporting ? translate('::App.Platform.Processing') : 'Export'}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ function ChartTabSeries(props: FormEditProps & { listFormCode: string }) {
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Alanlar getirilemedi
|
||||
{translate('::App.Platform.FieldsFetchFailed')}
|
||||
{error.toString()}
|
||||
</Notification>,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -143,8 +143,8 @@ function FormCustomization({
|
|||
validate={(values) => {
|
||||
if (values.userId && values.roleId) {
|
||||
return {
|
||||
userId: 'Hem rol hem user seçilemez',
|
||||
roleId: 'Hem rol hem user seçilemez',
|
||||
userId: translate('::ListForms.RoleOrUserOnly'),
|
||||
roleId: translate('::ListForms.RoleOrUserOnly'),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ function FormCustomization({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -312,7 +312,7 @@ function FormCustomization({
|
|||
>
|
||||
<h5 className="mb-4">Delete</h5>
|
||||
|
||||
<p>Silmek istediğinize emin misiniz?</p>
|
||||
<p>{translate('::DeleteConfirmation')}</p>
|
||||
|
||||
<div className="text-right mt-6">
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ const FormEdit = () => {
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -240,8 +240,7 @@ const FormEdit = () => {
|
|||
{listFormValues.isTenant && (
|
||||
<Badge
|
||||
className="font-semibold dark:bg-red-500 dark:text-white"
|
||||
content="Bu bir MULTI TENANT form'dur, veri kaybı olmaması için, sorgularda TENANTID
|
||||
parametresini kullanmayı unutmayınız."
|
||||
content={translate('::ListForms.ListFormEdit.MultiTenantWarning')}
|
||||
innerClass="p-1 bg-red-50 text-red-500"
|
||||
></Badge>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -103,13 +103,25 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
|||
if (selectCommandColumns.length > 0) return
|
||||
|
||||
const table = dbObjects.tables.find((t) => t.tableName === cmd)
|
||||
if (table) { loadColumns(dsCode, table.schemaName, table.tableName); return }
|
||||
if (table) {
|
||||
loadColumns(dsCode, table.schemaName, table.tableName)
|
||||
return
|
||||
}
|
||||
const view = dbObjects.views.find((v) => v.objectName === cmd)
|
||||
if (view) { loadColumns(dsCode, view.schemaName, view.objectName); return }
|
||||
if (view) {
|
||||
loadColumns(dsCode, view.schemaName, view.objectName)
|
||||
return
|
||||
}
|
||||
const fn = dbObjects.functions.find((f) => f.objectName === cmd)
|
||||
if (fn) { loadColumns(dsCode, fn.schemaName, fn.objectName); return }
|
||||
if (fn) {
|
||||
loadColumns(dsCode, fn.schemaName, fn.objectName)
|
||||
return
|
||||
}
|
||||
const sp = dbObjects.storedProcedures.find((p) => p.objectName === cmd)
|
||||
if (sp) { loadColumns(dsCode, sp.schemaName, sp.objectName); return }
|
||||
if (sp) {
|
||||
loadColumns(dsCode, sp.schemaName, sp.objectName)
|
||||
return
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dbObjects])
|
||||
|
||||
|
|
@ -295,7 +307,11 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
|||
form.setFieldValue('keyFieldName', '')
|
||||
form.setFieldValue('keyFieldDbSourceType', undefined)
|
||||
if (!option.__isNew__ && option.__schema != null && option.__rawName) {
|
||||
loadColumns(values.dataSourceCode ?? '', option.__schema, option.__rawName)
|
||||
loadColumns(
|
||||
values.dataSourceCode ?? '',
|
||||
option.__schema,
|
||||
option.__rawName,
|
||||
)
|
||||
} else {
|
||||
setSelectCommandColumns([])
|
||||
}
|
||||
|
|
@ -338,8 +354,11 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
|||
</span>
|
||||
) : selectCommandColumns.length === 0 && !isLoadingColumns ? (
|
||||
<span className="text-xs ml-2 text-gray-400">
|
||||
{translate('::ListForms.ListFormEdit.DatabaseDataSourceSelectCommand') +
|
||||
' seçince sütunlar yüklenir'}
|
||||
{translate('::ListForms.ListFormEdit.ColumnsLoadAfterSelect', {
|
||||
command: translate(
|
||||
'::ListForms.ListFormEdit.DatabaseDataSourceSelectCommand',
|
||||
),
|
||||
})}
|
||||
</span>
|
||||
) : null
|
||||
}
|
||||
|
|
@ -355,9 +374,7 @@ function FormTabDatabaseDataSource(props: FormEditProps) {
|
|||
placeholder={
|
||||
isLoadingColumns
|
||||
? translate('::Loading')
|
||||
: translate(
|
||||
'::App.Listform.ListformField.KeyFieldName',
|
||||
)
|
||||
: translate('::App.Listform.ListformField.KeyFieldName')
|
||||
}
|
||||
options={selectCommandColumns.map((c) => ({
|
||||
label: `${c.columnName} (${c.dataType})`,
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ function FormTabGantt(props: FormEditProps) {
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Alanlar getirilemedi
|
||||
{translate('::App.Platform.FieldsFetchFailed')}
|
||||
{error.toString()}
|
||||
</Notification>,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ function FormTabScheduler(props: FormEditProps) {
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Alanlar getirilemedi
|
||||
{translate('::App.Platform.FieldsFetchFailed')}
|
||||
{error.toString()}
|
||||
</Notification>,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ function FormTabSubForm() {
|
|||
<Td>{row.tabTitle}</Td>
|
||||
<Td>{row.tabType}</Td>
|
||||
<Td>{row.code}</Td>
|
||||
<Td>{row.isRefresh ? 'Evet' : 'Hayır'}</Td>
|
||||
<Td>{translate(row.isRefresh ? '::App.Platform.Yes' : '::App.Platform.No')}</Td>
|
||||
<Td>
|
||||
<ul className="list-disc">
|
||||
{row.relation?.map((i) => (
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ function FormTabTree(props: FormEditProps) {
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Alanlar getirilemedi
|
||||
{translate('::App.Platform.FieldsFetchFailed')}
|
||||
{error.toString()}
|
||||
</Notification>,
|
||||
{
|
||||
|
|
@ -184,7 +184,14 @@ function FormTabTree(props: FormEditProps) {
|
|||
component={Checkbox}
|
||||
/>
|
||||
</FormItem>
|
||||
<Button block size="sm" variant="solid" loading={isSubmitting} type="submit" className="my-2">
|
||||
<Button
|
||||
block
|
||||
size="sm"
|
||||
variant="solid"
|
||||
loading={isSubmitting}
|
||||
type="submit"
|
||||
className="my-2"
|
||||
>
|
||||
{isSubmitting ? translate('::Saving') : translate('::Save')}
|
||||
</Button>
|
||||
</FormContainer>
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ function FormFieldTabConditionalFormatting({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -206,7 +206,7 @@ function FormFieldTabConditionalFormatting({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const schema = object().shape({
|
|||
.nullable()
|
||||
.test(
|
||||
'is-json-object',
|
||||
'editorOptions geçerli bir JSON nesnesi olmalıdır.',
|
||||
'ListForms.ListFormFieldEdit.EditorOptionsMustBeJsonObject',
|
||||
(value) => !value?.trim() || isValidJsonText(value),
|
||||
),
|
||||
})
|
||||
|
|
@ -126,21 +126,15 @@ function FormFieldTabDetails({
|
|||
/>
|
||||
</FormItem>
|
||||
<FormItem label={translate('::ListForms.ListFormFieldEdit.DetailsAlignment')}>
|
||||
<SelectField
|
||||
name="alignment"
|
||||
options={listFormAlignmentOptions}
|
||||
/>
|
||||
<SelectField name="alignment" options={listFormAlignmentOptions} />
|
||||
</FormItem>
|
||||
<FormItem label={translate('::ListForms.ListFormFieldEdit.DetailsFormat')}>
|
||||
<SelectField
|
||||
name="format"
|
||||
options={tooltipFormatListOptions}
|
||||
/>
|
||||
<SelectField name="format" options={tooltipFormatListOptions} />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={translate('::ListForms.ListFormFieldEdit.DetailsEditorOptions')}
|
||||
invalid={errors.editorOptions && touched.editorOptions}
|
||||
errorMessage={errors.editorOptions}
|
||||
errorMessage={errors.editorOptions && translate('::' + errors.editorOptions)}
|
||||
>
|
||||
<div className="flex gap-2 items-start">
|
||||
<Field
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ function TablePickerModal({
|
|||
)}
|
||||
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200">
|
||||
{step === 'table'
|
||||
? translate('::ListForms.Wizard.Step3.SelectTable') || 'Tablo Seç'
|
||||
? translate('::ListForms.Wizard.Step3.SelectTable')
|
||||
: (pickerTable?.tableName ?? '')}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -99,7 +99,7 @@ function TablePickerModal({
|
|||
<input
|
||||
autoFocus
|
||||
value={tableSearch}
|
||||
placeholder={translate('::Search') || 'Ara...'}
|
||||
placeholder={translate('::Search')}
|
||||
className="w-full text-xs px-2 py-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||
onChange={(e) => setTableSearch(e.target.value)}
|
||||
/>
|
||||
|
|
@ -107,7 +107,7 @@ function TablePickerModal({
|
|||
<div className="overflow-y-auto flex-1 p-2">
|
||||
{!dbObjects ? (
|
||||
<div className="text-xs text-gray-400 text-center py-6">
|
||||
{translate('::ListForms.Wizard.Step3.NoTablesAvailable') || 'Tablo bulunamadı'}
|
||||
{translate('::ListForms.Wizard.Step3.NoTablesAvailable')}
|
||||
</div>
|
||||
) : (
|
||||
dbObjects.tables
|
||||
|
|
@ -160,14 +160,14 @@ function TablePickerModal({
|
|||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[11px] font-medium text-gray-500 dark:text-gray-400">
|
||||
Key Sütunu
|
||||
{translate('::ListForms.ListFormFieldEdit.KeyColumn')}
|
||||
</label>
|
||||
<select
|
||||
value={keyCol}
|
||||
className="w-full text-xs h-8 px-2 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||
onChange={(e) => setKeyCol(e.target.value)}
|
||||
>
|
||||
<option value="">-- Seçiniz --</option>
|
||||
<option value="">{translate('::App.Select')}</option>
|
||||
{pickerColumns.map((c) => (
|
||||
<option key={c.columnName} value={c.columnName}>
|
||||
{c.columnName}
|
||||
|
|
@ -177,14 +177,14 @@ function TablePickerModal({
|
|||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[11px] font-medium text-gray-500 dark:text-gray-400">
|
||||
Name Sütunu
|
||||
{translate('::ListForms.ListFormFieldEdit.NameColumn')}
|
||||
</label>
|
||||
<select
|
||||
value={nameCol}
|
||||
className="w-full text-xs h-8 px-2 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||
onChange={(e) => setNameCol(e.target.value)}
|
||||
>
|
||||
<option value="">-- Seçiniz --</option>
|
||||
<option value="">{translate('::App.Select')}</option>
|
||||
{pickerColumns.map((c) => (
|
||||
<option key={c.columnName} value={c.columnName}>
|
||||
{c.columnName}
|
||||
|
|
@ -305,7 +305,7 @@ function FormFieldTabLookup({
|
|||
className="ml-2 !h-auto !items-center gap-1 !rounded border border-indigo-200 bg-indigo-50 !px-1.5 !py-0.5 text-[10px] font-medium text-indigo-600 transition-colors hover:!bg-indigo-100 dark:border-indigo-700 dark:bg-indigo-900/20 dark:text-indigo-400 dark:hover:!bg-indigo-800/40"
|
||||
onClick={openTablePicker}
|
||||
>
|
||||
{translate('::ListForms.Wizard.Step3.GenerateFromTable') || 'Tablodan Oluştur'}
|
||||
{translate('::ListForms.Wizard.Step3.GenerateFromTable')}
|
||||
</Button>
|
||||
}
|
||||
invalid={errors.lookupDto?.lookupQuery && touched.lookupDto?.lookupQuery}
|
||||
|
|
@ -430,7 +430,14 @@ function FormFieldTabLookup({
|
|||
</FormItem>
|
||||
</Card>
|
||||
</div>
|
||||
<Button block size="sm" className="mt-1" variant="solid" loading={isSubmitting} type="submit">
|
||||
<Button
|
||||
block
|
||||
size="sm"
|
||||
className="mt-1"
|
||||
variant="solid"
|
||||
loading={isSubmitting}
|
||||
type="submit"
|
||||
>
|
||||
{isSubmitting ? translate('::Saving') : translate('::Save')}
|
||||
</Button>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ function FormFieldTabValidationRules({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -216,7 +216,7 @@ function FormFieldTabValidationRules({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -235,10 +235,7 @@ function FormFieldTabValidationRules({
|
|||
invalid={errors.type && touched.type}
|
||||
errorMessage={errors.type}
|
||||
>
|
||||
<SelectField
|
||||
name="type"
|
||||
options={columnValidationTypeListOptions}
|
||||
/>
|
||||
<SelectField name="type" options={columnValidationTypeListOptions} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
|
|
@ -328,7 +325,14 @@ function FormFieldTabValidationRules({
|
|||
<Field name="trim" placeholder="Trim" component={Checkbox} />
|
||||
</FormItem>
|
||||
|
||||
<Button block size="sm" className="mt-1" variant="solid" loading={isLoading} type="submit">
|
||||
<Button
|
||||
block
|
||||
size="sm"
|
||||
className="mt-1"
|
||||
variant="solid"
|
||||
loading={isLoading}
|
||||
type="submit"
|
||||
>
|
||||
{isLoading ? translate('::Saving') : translate('::Save')}
|
||||
</Button>
|
||||
</FormContainer>
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ function FormFields({
|
|||
if (newCols.length === 0) {
|
||||
toast.push(
|
||||
<Notification type="info" duration={2000}>
|
||||
Tüm sütunlar zaten eklenmiş.
|
||||
{translate('::ListForms.ListFormField.AllColumnsAlreadyAdded')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -182,7 +182,7 @@ function FormFields({
|
|||
}
|
||||
toast.push(
|
||||
<Notification type="success" duration={2000}>
|
||||
{newCols.length} sütun eklendi.
|
||||
{translate('::ListForms.ListFormField.ColumnsAdded', { count: newCols.length })}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -250,7 +250,7 @@ function FormFields({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Alanlar getirilemedi
|
||||
{translate('::App.Platform.FieldsFetchFailed')}
|
||||
{error.toString()}
|
||||
</Notification>,
|
||||
{
|
||||
|
|
@ -298,7 +298,7 @@ function FormFields({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -324,8 +324,8 @@ function FormFields({
|
|||
validate={(values) => {
|
||||
if (values.userId && values.roleId) {
|
||||
return {
|
||||
userId: 'Hem rol hem user seçilemez',
|
||||
roleId: 'Hem rol hem user seçilemez',
|
||||
userId: translate('::ListForms.RoleOrUserOnly'),
|
||||
roleId: translate('::ListForms.RoleOrUserOnly'),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -336,7 +336,7 @@ function FormFields({
|
|||
getFields(cultureName, roleId, userId).then(() => {
|
||||
toast.push(
|
||||
<Notification type="success" duration={100}>
|
||||
{'Alanlar geldi'}
|
||||
{translate('::ListForms.ListFormField.FieldsLoaded')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -419,7 +419,7 @@ function FormFields({
|
|||
variant="plain"
|
||||
type="button"
|
||||
size="sm"
|
||||
title="Tüm Sütunları Ekle"
|
||||
title={translate('::ListForms.ListFormField.AddAllColumns')}
|
||||
icon={<FaTable />}
|
||||
loading={isAddingAllColumns}
|
||||
onClick={addAllColumns}
|
||||
|
|
@ -517,8 +517,8 @@ function FormFields({
|
|||
validate={(values) => {
|
||||
if (values.userId && values.roleId) {
|
||||
return {
|
||||
userId: 'Hem rol hem user seçilemez',
|
||||
roleId: 'Hem rol hem user seçilemez',
|
||||
userId: translate('::ListForms.RoleOrUserOnly'),
|
||||
roleId: translate('::ListForms.RoleOrUserOnly'),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -547,7 +547,7 @@ function FormFields({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -582,10 +582,7 @@ function FormFields({
|
|||
</FormItem>
|
||||
|
||||
<FormItem label={translate('::ListForms.ListFormEdit.CustomValueType')}>
|
||||
<SelectField
|
||||
name="sourceDbType"
|
||||
options={dbSourceTypeOptions}
|
||||
/>
|
||||
<SelectField name="sourceDbType" options={dbSourceTypeOptions} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
|
|
@ -594,10 +591,7 @@ function FormFields({
|
|||
errorMessage={errors.cultureName}
|
||||
asterisk={true}
|
||||
>
|
||||
<SelectField
|
||||
name="cultureName"
|
||||
options={langOptions}
|
||||
/>
|
||||
<SelectField name="cultureName" options={langOptions} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
|
|
@ -605,10 +599,7 @@ function FormFields({
|
|||
invalid={errors.roleId && touched.roleId}
|
||||
errorMessage={errors.roleId}
|
||||
>
|
||||
<SelectField
|
||||
name="roleId"
|
||||
options={roleList}
|
||||
/>
|
||||
<SelectField name="roleId" options={roleList} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
|
|
@ -616,10 +607,7 @@ function FormFields({
|
|||
invalid={errors.userId && touched.userId}
|
||||
errorMessage={errors.userId}
|
||||
>
|
||||
<SelectField
|
||||
name="userId"
|
||||
options={userList}
|
||||
/>
|
||||
<SelectField name="userId" options={userList} />
|
||||
</FormItem>
|
||||
|
||||
<Button block size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
|
|
@ -637,8 +625,8 @@ function FormFields({
|
|||
onClose={() => setDeleteRowId(null)}
|
||||
onRequestClose={() => setDeleteRowId(null)}
|
||||
>
|
||||
<h5 className="mb-4">Sil</h5>
|
||||
<p>Silmek istediğinize emin misiniz?</p>
|
||||
<h5 className="mb-4">{translate('::App.Platform.Delete')}</h5>
|
||||
<p>{translate('::DeleteConfirmation')}</p>
|
||||
<div className="text-right mt-6">
|
||||
<Button
|
||||
className="ltr:mr-2 rtl:ml-2"
|
||||
|
|
@ -714,14 +702,19 @@ function FormFields({
|
|||
try {
|
||||
await postListFormFieldCopy({ ...values })
|
||||
|
||||
toast.push(<Notification type="success">{'Field Kopyalandı.'}</Notification>, {
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::ListForms.ListFormField.FieldCopied')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
handleCopyFieldFormClose()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -739,10 +732,7 @@ function FormFields({
|
|||
invalid={errors.sourceFieldName && touched.sourceFieldName}
|
||||
errorMessage={errors.sourceFieldName}
|
||||
>
|
||||
<SelectField
|
||||
name="sourceFieldName"
|
||||
options={fieldList}
|
||||
/>
|
||||
<SelectField name="sourceFieldName" options={fieldList} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
|
|
@ -764,10 +754,7 @@ function FormFields({
|
|||
invalid={errors.sourceCultureName && touched.sourceCultureName}
|
||||
errorMessage={errors.sourceCultureName}
|
||||
>
|
||||
<SelectField
|
||||
name="sourceCultureName"
|
||||
options={langOptions}
|
||||
/>
|
||||
<SelectField name="sourceCultureName" options={langOptions} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
|
|
@ -775,10 +762,7 @@ function FormFields({
|
|||
invalid={errors.targetCultureName && touched.targetCultureName}
|
||||
errorMessage={errors.targetCultureName}
|
||||
>
|
||||
<SelectField
|
||||
name="targetCultureName"
|
||||
options={langOptions}
|
||||
/>
|
||||
<SelectField name="targetCultureName" options={langOptions} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
|
|
@ -786,10 +770,7 @@ function FormFields({
|
|||
invalid={errors.roleId && touched.roleId}
|
||||
errorMessage={errors.roleId}
|
||||
>
|
||||
<SelectField
|
||||
name="roleId"
|
||||
options={roleList}
|
||||
/>
|
||||
<SelectField name="roleId" options={roleList} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
|
|
@ -797,10 +778,7 @@ function FormFields({
|
|||
invalid={errors.userId && touched.userId}
|
||||
errorMessage={errors.userId}
|
||||
>
|
||||
<SelectField
|
||||
name="userId"
|
||||
options={userList}
|
||||
/>
|
||||
<SelectField name="userId" options={userList} />
|
||||
</FormItem>
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<FormItem label={translate('::App.Reports.Search')} labelClass="justify-center">
|
||||
|
|
|
|||
|
|
@ -248,7 +248,8 @@ function EditorOptionsBuilderDialog({
|
|||
const visiblePresets = useMemo(
|
||||
() =>
|
||||
editorOptionsPresets.filter(
|
||||
(preset) => showAll || !preset.editors || !editorType || preset.editors.includes(editorType),
|
||||
(preset) =>
|
||||
showAll || !preset.editors || !editorType || preset.editors.includes(editorType),
|
||||
),
|
||||
[editorType, showAll],
|
||||
)
|
||||
|
|
@ -326,9 +327,9 @@ function EditorOptionsBuilderDialog({
|
|||
<h5 className="mr-1">Editor Options</h5>
|
||||
<span
|
||||
className="rounded-full bg-indigo-100 px-2.5 py-[2px] text-[11px] font-medium text-indigo-700 dark:bg-indigo-900 dark:text-indigo-200"
|
||||
title="Alanlar bu editör tipine göre süzülüyor. Editör tipi Seçenekler sekmesinden değiştirilir."
|
||||
title={translate('::ListForms.EditorOptions.EditorTypeFilterHint')}
|
||||
>
|
||||
{editorType || 'editör tipi seçilmemiş'}
|
||||
{editorType || translate('::ListForms.EditorOptions.NoEditorType')}
|
||||
</span>
|
||||
<span
|
||||
className={`rounded-full px-2.5 py-[2px] text-[11px] font-medium ${
|
||||
|
|
@ -336,9 +337,9 @@ function EditorOptionsBuilderDialog({
|
|||
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-200'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400'
|
||||
}`}
|
||||
title="JSON'a yazılacak ayar sayısı"
|
||||
title={translate('::ListForms.EditorOptions.SetCountHint')}
|
||||
>
|
||||
{setCount} ayar
|
||||
{translate('::ListForms.EditorOptions.SettingCount', { count: setCount })}
|
||||
</span>
|
||||
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
|
|
@ -346,11 +347,11 @@ function EditorOptionsBuilderDialog({
|
|||
<button
|
||||
disabled={!!rawError}
|
||||
type="button"
|
||||
title={rawError ? 'Önce JSON hatasını düzelt.' : undefined}
|
||||
title={rawError ? translate('::ListForms.EditorOptions.FixJsonFirst') : undefined}
|
||||
className={tabButtonClass(tab === 'builder')}
|
||||
onClick={() => goToTab('builder')}
|
||||
>
|
||||
<FaSlidersH /> Ayarlar
|
||||
<FaSlidersH /> {translate('::ListForms.EditorOptions.SettingsTab')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -365,19 +366,18 @@ function EditorOptionsBuilderDialog({
|
|||
|
||||
{rawError && (
|
||||
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-900 dark:bg-red-950 dark:text-red-200">
|
||||
JSON geçersiz: {rawError} — düzeltilene kadar kaydedilemez. Mevcut değer korunuyor.
|
||||
{translate('::ListForms.EditorOptions.InvalidJsonBanner', {
|
||||
error: translate('::' + rawError),
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === 'json' ? (
|
||||
<div className="flex min-h-0 flex-1 flex-col gap-2">
|
||||
<div className="flex items-center justify-between gap-2 text-xs text-gray-500">
|
||||
<span>
|
||||
Buradaki JSON doğrudan editorOptions alanına yazılır. Geçerli olduğu anda Ayarlar
|
||||
sekmesi de güncellenir.
|
||||
</span>
|
||||
<span>{translate('::ListForms.EditorOptions.JsonTabHint')}</span>
|
||||
<Button size="sm" type="button" icon={<FaCopy />} onClick={copyPreview}>
|
||||
{copied ? 'Kopyalandı' : 'Kopyala'}
|
||||
{translate(copied ? '::App.Platform.Copied' : '::App.SqlQueryManager.Copy')}
|
||||
</Button>
|
||||
</div>
|
||||
<textarea
|
||||
|
|
@ -397,47 +397,47 @@ function EditorOptionsBuilderDialog({
|
|||
<input
|
||||
className={`${controlClass} !w-64`}
|
||||
value={search}
|
||||
placeholder="Ayar ara (mask, format, search…)"
|
||||
placeholder={translate('::ListForms.EditorOptions.SearchSetting')}
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
/>
|
||||
<label
|
||||
className="flex cursor-pointer items-center gap-1.5 rounded-md border border-gray-200 px-2.5 py-1.5 text-xs text-gray-600 dark:border-gray-700 dark:text-gray-300"
|
||||
title="Kapalıyken yalnızca seçili editöre uyan ve halihazırda dolu olan ayarlar listelenir."
|
||||
title={translate('::ListForms.EditorOptions.ShowAllHint')}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showAll}
|
||||
onChange={(event) => setShowAll(event.target.checked)}
|
||||
/>
|
||||
Tüm ayarlar
|
||||
{translate('::ListForms.EditorOptions.AllSettings')}
|
||||
</label>
|
||||
<label
|
||||
className="flex cursor-pointer items-center gap-1.5 rounded-md border border-gray-200 px-2.5 py-1.5 text-xs text-gray-600 dark:border-gray-700 dark:text-gray-300"
|
||||
title="Yalnızca JSON'da değeri olan ayarları göster."
|
||||
title={translate('::ListForms.EditorOptions.OnlySetHint')}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={onlySet}
|
||||
onChange={(event) => setOnlySet(event.target.checked)}
|
||||
/>
|
||||
Sadece dolu
|
||||
{translate('::ListForms.EditorOptions.OnlySet')}
|
||||
</label>
|
||||
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<Button size="sm" type="button" onClick={() => setAllSections(true)}>
|
||||
Tümünü aç
|
||||
{translate('::ListForms.EditorOptions.ExpandAll')}
|
||||
</Button>
|
||||
<Button size="sm" type="button" onClick={() => setAllSections(false)}>
|
||||
Tümünü kapat
|
||||
{translate('::ListForms.EditorOptions.CollapseAll')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
title="Tüm ayarları temizler. Kaydedilirse editorOptions boşalır."
|
||||
title={translate('::ListForms.EditorOptions.ClearAllHint')}
|
||||
icon={<FaEraser />}
|
||||
onClick={() => setOptions({})}
|
||||
>
|
||||
Temizle
|
||||
{translate('::App.DeveloperKit.CrudEndpoints.Clear')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -450,8 +450,8 @@ function EditorOptionsBuilderDialog({
|
|||
<div className={cardClass}>
|
||||
<SectionHeader
|
||||
icon={FaMagic}
|
||||
title="Hazır Ayarlar"
|
||||
description="Seçilen kalıp mevcut JSON ile birleştirilir; diğer ayarlarını silmez."
|
||||
title={translate('::ListForms.EditorOptions.Presets')}
|
||||
description={translate('::ListForms.EditorOptions.PresetsHint')}
|
||||
badge={`${visiblePresets.length}`}
|
||||
open={isSectionOpen(PRESETS_KEY)}
|
||||
onToggle={() => toggleSection(PRESETS_KEY)}
|
||||
|
|
@ -463,10 +463,10 @@ function EditorOptionsBuilderDialog({
|
|||
key={preset.key}
|
||||
size="sm"
|
||||
type="button"
|
||||
title={preset.description}
|
||||
title={translate('::' + preset.description)}
|
||||
onClick={() => setOptions((current) => ({ ...current, ...preset.value }))}
|
||||
>
|
||||
{preset.label}
|
||||
{translate('::' + preset.label)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -480,15 +480,17 @@ function EditorOptionsBuilderDialog({
|
|||
<SectionHeader
|
||||
accent={group.setCount > 0}
|
||||
icon={groupIcons[group.key]}
|
||||
title={group.title}
|
||||
description={group.description}
|
||||
title={translate('::' + group.title)}
|
||||
description={translate('::' + group.description)}
|
||||
badge={`${group.setCount}/${group.specs.length}`}
|
||||
open={open}
|
||||
onToggle={() => toggleSection(group.key)}
|
||||
/>
|
||||
{open && (
|
||||
<div className="border-t border-gray-100 px-3 py-3 dark:border-gray-800">
|
||||
<p className="mb-3 text-[11px] text-gray-400">{group.description}</p>
|
||||
<p className="mb-3 text-[11px] text-gray-400">
|
||||
{translate('::' + group.description)}
|
||||
</p>
|
||||
<div className="grid grid-cols-1 gap-x-4 gap-y-3 md:grid-cols-3">
|
||||
{group.specs.map((spec) => (
|
||||
<OptionField
|
||||
|
|
@ -507,8 +509,7 @@ function EditorOptionsBuilderDialog({
|
|||
|
||||
{!groupedSpecs.length && (
|
||||
<div className="rounded-lg border border-dashed border-gray-300 p-8 text-center text-xs text-gray-400 dark:border-gray-700">
|
||||
Filtreye uyan ayar yok. "Tüm ayarlar" kutusunu işaretleyerek tüm
|
||||
sözlüğü görebilirsin.
|
||||
{translate('::ListForms.EditorOptions.NoMatchingSetting')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -517,8 +518,8 @@ function EditorOptionsBuilderDialog({
|
|||
<SectionHeader
|
||||
accent={unmanagedPaths.length > 0}
|
||||
icon={FaPuzzlePiece}
|
||||
title="Sözlükte Olmayan Ayarlar"
|
||||
description="JSON'da bulunan ama listede karşılığı olmayan her şey burada görünür."
|
||||
title={translate('::ListForms.EditorOptions.UnmanagedSettings')}
|
||||
description={translate('::ListForms.EditorOptions.UnmanagedSettingsHint')}
|
||||
badge={`${unmanagedPaths.length}`}
|
||||
open={isSectionOpen(UNMANAGED_KEY)}
|
||||
onToggle={() => toggleSection(UNMANAGED_KEY)}
|
||||
|
|
@ -526,8 +527,7 @@ function EditorOptionsBuilderDialog({
|
|||
{isSectionOpen(UNMANAGED_KEY) && (
|
||||
<div className="border-t border-gray-100 px-3 py-3 dark:border-gray-800">
|
||||
<p className="mb-3 text-[11px] text-gray-400">
|
||||
Hiçbir ayar gizli kalmaz. DevExtreme dokümanındaki herhangi bir yolu buradan
|
||||
ekleyebilirsin (örn: toolbar.multiline).
|
||||
{translate('::ListForms.EditorOptions.UnmanagedHelp')}
|
||||
</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
{unmanagedPaths.map((path) => {
|
||||
|
|
@ -573,7 +573,7 @@ function EditorOptionsBuilderDialog({
|
|||
size="sm"
|
||||
type="button"
|
||||
variant="plain"
|
||||
title="Bu ayarı sil"
|
||||
title={translate('::ListForms.EditorOptions.DeleteSetting')}
|
||||
icon={<FaTrash />}
|
||||
onClick={() =>
|
||||
setOptions((previous) => unsetByPath(previous, path))
|
||||
|
|
@ -604,7 +604,11 @@ function EditorOptionsBuilderDialog({
|
|||
<input
|
||||
className={`${controlClass} col-span-5`}
|
||||
value={newValue}
|
||||
placeholder={newType === 'boolean' ? 'true / false' : 'değer'}
|
||||
placeholder={
|
||||
newType === 'boolean'
|
||||
? 'true / false'
|
||||
: translate('::ListForms.EditorOptions.Value')
|
||||
}
|
||||
onChange={(event) => setNewValue(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
|
|
@ -619,7 +623,7 @@ function EditorOptionsBuilderDialog({
|
|||
size="sm"
|
||||
type="button"
|
||||
variant="plain"
|
||||
title="Ayarı ekle"
|
||||
title={translate('::ListForms.EditorOptions.AddSetting')}
|
||||
icon={<FaPlus />}
|
||||
onClick={addCustomOption}
|
||||
/>
|
||||
|
|
@ -635,18 +639,22 @@ function EditorOptionsBuilderDialog({
|
|||
<div className="mb-3 flex items-center justify-between gap-2">
|
||||
<span className="flex items-center gap-2 text-sm font-semibold">
|
||||
<FaCode className="text-gray-400" />
|
||||
JSON Önizleme
|
||||
{translate('::ListForms.EditorOptions.JsonPreview')}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded-full bg-gray-100 px-2 py-[2px] text-[11px] font-normal text-gray-500 dark:bg-gray-800 dark:text-gray-300">
|
||||
{preview ? `${stringifyOptions(options).length} karakter` : 'boş'}
|
||||
{preview
|
||||
? translate('::ListForms.EditorOptions.CharCount', {
|
||||
count: stringifyOptions(options).length,
|
||||
})
|
||||
: translate('::ListForms.EditorOptions.Empty')}
|
||||
</span>
|
||||
<Button
|
||||
shape="circle"
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="plain"
|
||||
title="JSON'u panoya kopyala"
|
||||
title={translate('::ListForms.EditorOptions.CopyJson')}
|
||||
icon={copied ? <FaCheck /> : <FaCopy />}
|
||||
onClick={copyPreview}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ function EditorScriptBuilderDialog({
|
|||
// sabitlenmezse editör yazarken sıfırlanırdı.
|
||||
const fieldKey = fields.map((field) => String(field.value || field.label || '')).join('|')
|
||||
const dialect = useMemo(
|
||||
() => createFormScriptDialect({ currentField, fields: fieldKey.split('|') }),
|
||||
[currentField, fieldKey],
|
||||
() => createFormScriptDialect({ currentField, fields: fieldKey.split('|'), translate }),
|
||||
[currentField, fieldKey, translate],
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -147,7 +147,11 @@ function JsonRowOpDialogAnnotation({
|
|||
}
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{data.index === -1 ? 'Kayıt eklendi' : 'Kayıt güncellendi'}
|
||||
{translate(
|
||||
data.index === -1
|
||||
? '::App.Platform.RecordAdded'
|
||||
: '::App.Platform.RecordUpdated',
|
||||
)}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -155,7 +159,7 @@ function JsonRowOpDialogAnnotation({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -449,14 +453,19 @@ function JsonRowOpDialogAnnotation({
|
|||
setSubmitting(true)
|
||||
try {
|
||||
await deleteListFormJsonRow(data.id, data.tabName, values.index)
|
||||
toast.push(<Notification type="success">Kayıt silindi </Notification>, {
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::App.Platform.RecordDeleted')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -470,7 +479,7 @@ function JsonRowOpDialogAnnotation({
|
|||
<Form className="flex flex-col h-full">
|
||||
<Dialog.Body className="flex flex-col gap-2">
|
||||
<h5>Delete</h5>
|
||||
<p>Silmek istediğinize emin misiniz?</p>
|
||||
<p>{translate('::DeleteConfirmation')}</p>
|
||||
</Dialog.Body>
|
||||
<Dialog.Footer className="flex justify-end gap-2 border-t pt-3 mt-1">
|
||||
<Button size="sm" variant="plain" onClick={handleClose}>
|
||||
|
|
|
|||
|
|
@ -162,7 +162,11 @@ function JsonRowOpDialogAxis({
|
|||
}
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{data.index === -1 ? 'Kayıt eklendi' : 'Kayıt güncellendi'}
|
||||
{translate(
|
||||
data.index === -1
|
||||
? '::App.Platform.RecordAdded'
|
||||
: '::App.Platform.RecordUpdated',
|
||||
)}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -170,7 +174,7 @@ function JsonRowOpDialogAxis({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -477,21 +481,26 @@ function JsonRowOpDialogAxis({
|
|||
{data.operation === 'delete' && (
|
||||
<>
|
||||
<h5 className="mb-4">Delete</h5>
|
||||
<p>Silmek istediğinize emin misiniz?</p>
|
||||
<p>{translate('::DeleteConfirmation')}</p>
|
||||
<Formik
|
||||
initialValues={data}
|
||||
onSubmit={async (values, { setSubmitting }) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await deleteListFormJsonRow(data.id, data.tabName, values.index)
|
||||
toast.push(<Notification type="success">Kayıt silindi </Notification>, {
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::App.Platform.RecordDeleted')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -505,7 +514,12 @@ function JsonRowOpDialogAxis({
|
|||
<Form>
|
||||
<FormContainer size="sm">
|
||||
<div className="text-right mt-4">
|
||||
<Button size="sm" className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={handleClose}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="ltr:mr-2 rtl:ml-2"
|
||||
variant="plain"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
|
|
|
|||
|
|
@ -139,7 +139,11 @@ function JsonRowOpDialogCommand({
|
|||
}
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{data.index === -1 ? 'Kayıt eklendi' : 'Kayıt güncellendi'}
|
||||
{translate(
|
||||
data.index === -1
|
||||
? '::App.Platform.RecordAdded'
|
||||
: '::App.Platform.RecordUpdated',
|
||||
)}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -147,7 +151,7 @@ function JsonRowOpDialogCommand({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -366,7 +370,12 @@ function JsonRowOpDialogCommand({
|
|||
</Tabs>
|
||||
</div>
|
||||
<div className="text-right mt-4">
|
||||
<Button size="sm" className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={handleClose}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="ltr:mr-2 rtl:ml-2"
|
||||
variant="plain"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
|
|
@ -382,21 +391,26 @@ function JsonRowOpDialogCommand({
|
|||
{data.operation === 'delete' && (
|
||||
<>
|
||||
<h5 className="mb-4">Delete</h5>
|
||||
<p>Silmek istediğinize emin misiniz?</p>
|
||||
<p>{translate('::DeleteConfirmation')}</p>
|
||||
<Formik
|
||||
initialValues={data}
|
||||
onSubmit={async (values, { setSubmitting }) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await deleteListFormJsonRow(data.id, data.tabName, values.index)
|
||||
toast.push(<Notification type="success">Kayıt silindi </Notification>, {
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::App.Platform.RecordDeleted')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -411,7 +425,12 @@ function JsonRowOpDialogCommand({
|
|||
<Form>
|
||||
<FormContainer size="sm">
|
||||
<div className="text-right mt-4">
|
||||
<Button size="sm" className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={handleClose}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="ltr:mr-2 rtl:ml-2"
|
||||
variant="plain"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
|
|
|
|||
|
|
@ -121,7 +121,11 @@ function JsonRowOpDialogDatabase({
|
|||
}
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{data.index === -1 ? 'Kayıt eklendi' : 'Kayıt güncellendi'}
|
||||
{translate(
|
||||
data.index === -1
|
||||
? '::App.Platform.RecordAdded'
|
||||
: '::App.Platform.RecordUpdated',
|
||||
)}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -129,7 +133,7 @@ function JsonRowOpDialogDatabase({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -239,7 +243,12 @@ function JsonRowOpDialogDatabase({
|
|||
)}
|
||||
</div>
|
||||
<div className="text-right mt-4">
|
||||
<Button size="sm" className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={handleClose}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="ltr:mr-2 rtl:ml-2"
|
||||
variant="plain"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
|
|
@ -255,21 +264,26 @@ function JsonRowOpDialogDatabase({
|
|||
{data.operation === 'delete' && (
|
||||
<>
|
||||
<h5 className="mb-4">Delete</h5>
|
||||
<p>Silmek istediğinize emin misiniz?</p>
|
||||
<p>{translate('::DeleteConfirmation')}</p>
|
||||
<Formik
|
||||
initialValues={data}
|
||||
onSubmit={async (values, { setSubmitting }) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await deleteListFormJsonRow(data.id, data.tabName, values.index)
|
||||
toast.push(<Notification type="success">Kayıt silindi </Notification>, {
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::App.Platform.RecordDeleted')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -284,7 +298,12 @@ function JsonRowOpDialogDatabase({
|
|||
<Form>
|
||||
<FormContainer size="sm">
|
||||
<div className="text-right mt-4">
|
||||
<Button size="sm" className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={handleClose}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="ltr:mr-2 rtl:ml-2"
|
||||
variant="plain"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
|
|
|
|||
|
|
@ -56,7 +56,11 @@ function JsonRowOpDialogEditForm({
|
|||
onSubmit={async (values, { setSubmitting }) => {
|
||||
try {
|
||||
await deleteListFormJsonRow(data.id, data.tabName, values.index)
|
||||
toast.push(<Notification type="success">Kayıt silindi</Notification>)
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::App.Platform.RecordDeleted')}
|
||||
</Notification>,
|
||||
)
|
||||
await handleClose()
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
|
|
@ -65,24 +69,46 @@ function JsonRowOpDialogEditForm({
|
|||
>
|
||||
{({ isSubmitting }) => (
|
||||
<Form>
|
||||
<Dialog.Body><p>{translate('::DeleteConfirmation')}</p></Dialog.Body>
|
||||
<Dialog.Body>
|
||||
<p>{translate('::DeleteConfirmation')}</p>
|
||||
</Dialog.Body>
|
||||
<Dialog.Footer className="flex justify-end gap-2">
|
||||
<Button type="button" variant="plain" onClick={handleClose}>{translate('::Cancel')}</Button>
|
||||
<Button type="submit" variant="solid" loading={isSubmitting}>{translate('::Delete')}</Button>
|
||||
<Button type="button" variant="plain" onClick={handleClose}>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button type="submit" variant="solid" loading={isSubmitting}>
|
||||
{translate('::Delete')}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
) : (
|
||||
<Formik
|
||||
initialValues={data.editFormValues ?? { order: 1, itemType: 'group', caption: '', colCount: 1, colSpan: 1 }}
|
||||
initialValues={
|
||||
data.editFormValues ?? {
|
||||
order: 1,
|
||||
itemType: 'group',
|
||||
caption: '',
|
||||
colCount: 1,
|
||||
colSpan: 1,
|
||||
}
|
||||
}
|
||||
validationSchema={schema}
|
||||
onSubmit={async (values, { setSubmitting }) => {
|
||||
try {
|
||||
const input: ListFormJsonRowDto = { index: data.index, fieldName: data.tabName, itemEditingForm: values }
|
||||
const input: ListFormJsonRowDto = {
|
||||
index: data.index,
|
||||
fieldName: data.tabName,
|
||||
itemEditingForm: values,
|
||||
}
|
||||
if (data.index === -1) await postListFormJsonRow(data.id, input)
|
||||
else await putListFormJsonRow(data.id, input)
|
||||
toast.push(<Notification type="success">Kayıt kaydedildi</Notification>)
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::App.Platform.RecordSaved')}
|
||||
</Notification>,
|
||||
)
|
||||
await handleClose()
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
|
|
@ -92,19 +118,44 @@ function JsonRowOpDialogEditForm({
|
|||
{({ errors, touched, isSubmitting }) => (
|
||||
<Form>
|
||||
<Dialog.Body className="grid grid-cols-2 gap-3">
|
||||
<FormItem label={translate('::App.Listform.ListformField.Order')} invalid={!!(errors.order && touched.order)} errorMessage={errors.order}>
|
||||
<FormItem
|
||||
label={translate('::App.Listform.ListformField.Order')}
|
||||
invalid={!!(errors.order && touched.order)}
|
||||
errorMessage={errors.order}
|
||||
>
|
||||
<Field name="order" type="number" component={Input} />
|
||||
</FormItem>
|
||||
<FormItem label={translate('::ListForms.ListFormEdit.EditingFormItemType')}>
|
||||
<Field name="itemType">{({ field, form }: FieldProps) => <Select options={itemTypeOptions} value={itemTypeOptions.find((option) => option.value === field.value)} onChange={(option) => form.setFieldValue(field.name, option?.value)} />}</Field>
|
||||
<Field name="itemType">
|
||||
{({ field, form }: FieldProps) => (
|
||||
<Select
|
||||
options={itemTypeOptions}
|
||||
value={itemTypeOptions.find((option) => option.value === field.value)}
|
||||
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
className="col-span-2"
|
||||
label={translate('::ListForms.ListFormEdit.DetailsTitle')}
|
||||
>
|
||||
<Field name="caption" component={Input} />
|
||||
</FormItem>
|
||||
<FormItem label={translate('::ListForms.ListFormEdit.EditingFormColumnCount')}>
|
||||
<Field name="colCount" type="number" component={Input} />
|
||||
</FormItem>
|
||||
<FormItem label={translate('::ListForms.ListFormEdit.EditingFormColumnSpan')}>
|
||||
<Field name="colSpan" type="number" component={Input} />
|
||||
</FormItem>
|
||||
<FormItem className="col-span-2" label={translate('::ListForms.ListFormEdit.DetailsTitle')}><Field name="caption" component={Input} /></FormItem>
|
||||
<FormItem label={translate('::ListForms.ListFormEdit.EditingFormColumnCount')}><Field name="colCount" type="number" component={Input} /></FormItem>
|
||||
<FormItem label={translate('::ListForms.ListFormEdit.EditingFormColumnSpan')}><Field name="colSpan" type="number" component={Input} /></FormItem>
|
||||
</Dialog.Body>
|
||||
<Dialog.Footer className="flex justify-end gap-2">
|
||||
<Button type="button" variant="plain" onClick={handleClose}>{translate('::Cancel')}</Button>
|
||||
<Button type="submit" variant="solid" loading={isSubmitting}>{translate('::Save')}</Button>
|
||||
<Button type="button" variant="plain" onClick={handleClose}>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button type="submit" variant="solid" loading={isSubmitting}>
|
||||
{translate('::Save')}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Form>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ function JsonRowOpDialogExtraFilter({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Alanlar getirilemedi
|
||||
{translate('::App.Platform.FieldsFetchFailed')}
|
||||
{error.toString()}
|
||||
</Notification>,
|
||||
{
|
||||
|
|
@ -156,7 +156,11 @@ function JsonRowOpDialogExtraFilter({
|
|||
}
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{data.index === -1 ? 'Kayıt eklendi' : 'Kayıt güncellendi'}
|
||||
{translate(
|
||||
data.index === -1
|
||||
? '::App.Platform.RecordAdded'
|
||||
: '::App.Platform.RecordUpdated',
|
||||
)}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -164,7 +168,7 @@ function JsonRowOpDialogExtraFilter({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -286,7 +290,12 @@ function JsonRowOpDialogExtraFilter({
|
|||
</FormItem>
|
||||
</div>
|
||||
<div className="text-right mt-4">
|
||||
<Button size="sm" className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={handleClose}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="ltr:mr-2 rtl:ml-2"
|
||||
variant="plain"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
|
|
@ -302,21 +311,26 @@ function JsonRowOpDialogExtraFilter({
|
|||
{data.operation === 'delete' && (
|
||||
<>
|
||||
<h5 className="mb-4">Delete</h5>
|
||||
<p>Silmek istediğinize emin misiniz?</p>
|
||||
<p>{translate('::DeleteConfirmation')}</p>
|
||||
<Formik
|
||||
initialValues={data}
|
||||
onSubmit={async (values, { setSubmitting }) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await deleteListFormJsonRow(data.id, data.tabName, values.index)
|
||||
toast.push(<Notification type="success">Kayıt silindi </Notification>, {
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::App.Platform.RecordDeleted')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -331,7 +345,12 @@ function JsonRowOpDialogExtraFilter({
|
|||
<Form>
|
||||
<FormContainer size="sm">
|
||||
<div className="text-right mt-4">
|
||||
<Button size="sm" className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={handleClose}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="ltr:mr-2 rtl:ml-2"
|
||||
variant="plain"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
|
|
|
|||
|
|
@ -94,7 +94,11 @@ function JsonRowOpDialogPane({
|
|||
}
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{data.index === -1 ? 'Kayıt eklendi' : 'Kayıt güncellendi'}
|
||||
{translate(
|
||||
data.index === -1
|
||||
? '::App.Platform.RecordAdded'
|
||||
: '::App.Platform.RecordUpdated',
|
||||
)}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -102,7 +106,7 @@ function JsonRowOpDialogPane({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -134,7 +138,7 @@ function JsonRowOpDialogPane({
|
|||
<Input
|
||||
{...field}
|
||||
type="color"
|
||||
className='!h-10 !p-0 !m-0 !border-0'
|
||||
className="!h-10 !p-0 !m-0 !border-0"
|
||||
onChange={(e) => form.setFieldValue(field.name, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -150,7 +154,12 @@ function JsonRowOpDialogPane({
|
|||
</FormItem>
|
||||
</div>
|
||||
<div className="text-right mt-4">
|
||||
<Button size="sm" className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={handleClose}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="ltr:mr-2 rtl:ml-2"
|
||||
variant="plain"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
|
|
@ -166,21 +175,26 @@ function JsonRowOpDialogPane({
|
|||
{data.operation === 'delete' && (
|
||||
<>
|
||||
<h5 className="mb-4">Delete</h5>
|
||||
<p>Silmek istediğinize emin misiniz?</p>
|
||||
<p>{translate('::DeleteConfirmation')}</p>
|
||||
<Formik
|
||||
initialValues={data}
|
||||
onSubmit={async (values, { setSubmitting }) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await deleteListFormJsonRow(data.id, data.tabName, values.index)
|
||||
toast.push(<Notification type="success">Kayıt silindi </Notification>, {
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::App.Platform.RecordDeleted')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -194,7 +208,12 @@ function JsonRowOpDialogPane({
|
|||
<Form>
|
||||
<FormContainer size="sm">
|
||||
<div className="text-right mt-4">
|
||||
<Button size="sm" className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={handleClose}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="ltr:mr-2 rtl:ml-2"
|
||||
variant="plain"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
|
|
|
|||
|
|
@ -187,7 +187,11 @@ function JsonRowOpDialogSeries({
|
|||
}
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{data.index === -1 ? 'Kayıt eklendi' : 'Kayıt güncellendi'}
|
||||
{translate(
|
||||
data.index === -1
|
||||
? '::App.Platform.RecordAdded'
|
||||
: '::App.Platform.RecordUpdated',
|
||||
)}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -195,7 +199,7 @@ function JsonRowOpDialogSeries({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -301,7 +305,9 @@ function JsonRowOpDialogSeries({
|
|||
form={form}
|
||||
isClearable={true}
|
||||
options={fieldList}
|
||||
value={fieldList?.find((option) => option.value === values.valueField)}
|
||||
value={fieldList?.find(
|
||||
(option) => option.value === values.valueField,
|
||||
)}
|
||||
menuPlacement="auto"
|
||||
maxMenuHeight={150}
|
||||
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||
|
|
@ -348,7 +354,9 @@ function JsonRowOpDialogSeries({
|
|||
form={form}
|
||||
isClearable={true}
|
||||
options={valueAxisList()}
|
||||
value={valueAxisList()?.find((option) => option.value === values.axis)}
|
||||
value={valueAxisList()?.find(
|
||||
(option) => option.value === values.axis,
|
||||
)}
|
||||
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -367,7 +375,9 @@ function JsonRowOpDialogSeries({
|
|||
form={form}
|
||||
isClearable={true}
|
||||
options={valuePaneList()}
|
||||
value={valuePaneList()?.find((option) => option.value === values.pane)}
|
||||
value={valuePaneList()?.find(
|
||||
(option) => option.value === values.pane,
|
||||
)}
|
||||
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -405,7 +415,7 @@ function JsonRowOpDialogSeries({
|
|||
<Input
|
||||
{...field}
|
||||
type="color"
|
||||
className='!h-10 !p-0 !m-0 !border-0'
|
||||
className="!h-10 !p-0 !m-0 !border-0"
|
||||
onChange={(e) => form.setFieldValue(field.name, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -466,7 +476,7 @@ function JsonRowOpDialogSeries({
|
|||
<Input
|
||||
{...field}
|
||||
type="color"
|
||||
className='!h-10 !p-0 !m-0 !border-0'
|
||||
className="!h-10 !p-0 !m-0 !border-0"
|
||||
onChange={(e) => form.setFieldValue(field.name, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -509,7 +519,7 @@ function JsonRowOpDialogSeries({
|
|||
<Input
|
||||
{...field}
|
||||
type="color"
|
||||
className='!h-10 !p-0 !m-0 !border-0'
|
||||
className="!h-10 !p-0 !m-0 !border-0"
|
||||
onChange={(e) => form.setFieldValue(field.name, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -552,14 +562,19 @@ function JsonRowOpDialogSeries({
|
|||
setSubmitting(true)
|
||||
try {
|
||||
await deleteListFormJsonRow(data.id, data.tabName, values.index)
|
||||
toast.push(<Notification type="success">Kayıt silindi </Notification>, {
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::App.Platform.RecordDeleted')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
|
|||
|
|
@ -186,7 +186,11 @@ function JsonRowOpDialogSubForm({
|
|||
}
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{data.index === -1 ? 'Kayıt eklendi' : 'Kayıt güncellendi'}
|
||||
{translate(
|
||||
data.index === -1
|
||||
? '::App.Platform.RecordAdded'
|
||||
: '::App.Platform.RecordUpdated',
|
||||
)}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -194,7 +198,7 @@ function JsonRowOpDialogSubForm({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -451,12 +455,16 @@ function JsonRowOpDialogSubForm({
|
|||
setSubmitting(true)
|
||||
try {
|
||||
await deleteListFormJsonRow(data.id, data.tabName, values.index)
|
||||
toast.push(<Notification type="success">Kayıt silindi </Notification>)
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::App.Platform.RecordDeleted')}
|
||||
</Notification>,
|
||||
)
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
|
|||
|
|
@ -117,7 +117,11 @@ function JsonRowOpDialogWidget({
|
|||
}
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{data.index === -1 ? 'Kayıt eklendi' : 'Kayıt güncellendi'}
|
||||
{translate(
|
||||
data.index === -1
|
||||
? '::App.Platform.RecordAdded'
|
||||
: '::App.Platform.RecordUpdated',
|
||||
)}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -125,7 +129,7 @@ function JsonRowOpDialogWidget({
|
|||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
@ -259,10 +263,7 @@ function JsonRowOpDialogWidget({
|
|||
invalid={errors.sqlQuery && touched.sqlQuery}
|
||||
errorMessage={errors.sqlQuery}
|
||||
>
|
||||
<div
|
||||
className="border rounded-lg overflow-hidden"
|
||||
style={{ height: '35vh' }}
|
||||
>
|
||||
<div className="border rounded-lg overflow-hidden" style={{ height: '35vh' }}>
|
||||
<SqlEditor
|
||||
value={values.sqlQuery || ''}
|
||||
height="35vh"
|
||||
|
|
@ -355,8 +356,7 @@ function JsonRowOpDialogWidget({
|
|||
FaTruck
|
||||
</div>
|
||||
<div>
|
||||
📈 <strong>Analytics:</strong> FaArrowUp, FaArrowDown,
|
||||
FaArrowTrendUp
|
||||
📈 <strong>Analytics:</strong> FaArrowUp, FaArrowDown, FaArrowTrendUp
|
||||
</div>
|
||||
<div>
|
||||
⚙️ <strong>Settings:</strong> FaCog, FaTools, FaWrench
|
||||
|
|
@ -441,14 +441,19 @@ function JsonRowOpDialogWidget({
|
|||
setSubmitting(true)
|
||||
try {
|
||||
await deleteListFormJsonRow(data.id, data.tabName, values.index)
|
||||
toast.push(<Notification type="success">Kayıt silindi </Notification>, {
|
||||
toast.push(
|
||||
<Notification type="success">
|
||||
{translate('::App.Platform.RecordDeleted')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
},
|
||||
)
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
<code>{error}</code>
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
|
|||
import { FaExclamationTriangle, FaUndo } from 'react-icons/fa'
|
||||
import { coerceNumber, coerceSize, leafToText } from './jsonUtils'
|
||||
import type { OptionSpec } from './optionSpecs'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
export const controlClass =
|
||||
'w-full min-w-0 h-9 px-2 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-100 focus:outline-none focus:border-indigo-400 disabled:opacity-60'
|
||||
|
|
@ -15,29 +16,52 @@ type OptionFieldProps = {
|
|||
}
|
||||
|
||||
/** Değerin spec ile uyuşup uyuşmadığını söyler; uyuşmuyorsa alan kilitlenir. */
|
||||
const detectMismatch = (spec: OptionSpec, value: unknown): string | undefined => {
|
||||
const detectMismatch = (
|
||||
spec: OptionSpec,
|
||||
value: unknown,
|
||||
): { key: string; params?: Record<string, string> } | undefined => {
|
||||
if (value === undefined || value === null) return undefined
|
||||
|
||||
const isObjectLike = typeof value === 'object'
|
||||
|
||||
if (spec.type === 'json') return undefined
|
||||
if (spec.type === 'stringList') {
|
||||
return Array.isArray(value) ? undefined : 'Bu alan dizi bekliyor, mevcut değer dizi değil.'
|
||||
return Array.isArray(value)
|
||||
? undefined
|
||||
: { key: '::ListForms.EditorOptions.Mismatch.ExpectedArray' }
|
||||
}
|
||||
if (isObjectLike) {
|
||||
return 'Bu yol şu anda nesne/dizi değeri tutuyor. Ham JSON sekmesinden düzenleyin.'
|
||||
return { key: '::ListForms.EditorOptions.Mismatch.ObjectValue' }
|
||||
}
|
||||
if (spec.type === 'number' && typeof value !== 'number') {
|
||||
return `Sayı bekleniyor, mevcut değer ${typeof value}. Yeni değer girildiğinde sayıya çevrilir.`
|
||||
return {
|
||||
key: '::ListForms.EditorOptions.Mismatch.ExpectedNumber',
|
||||
params: { type: typeof value },
|
||||
}
|
||||
}
|
||||
if (spec.type === 'boolean' && typeof value !== 'boolean') {
|
||||
return `Boolean bekleniyor, mevcut değer ${typeof value}. Listeden seçim yaparsan düzelir.`
|
||||
return {
|
||||
key: '::ListForms.EditorOptions.Mismatch.ExpectedBoolean',
|
||||
params: { type: typeof value },
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const BooleanControl = ({ value, onChange }: { value: unknown; onChange: (v: unknown) => void }) => {
|
||||
const current = value === true || value === 'true' ? 'true' : value === false || value === 'false' ? 'false' : ''
|
||||
const BooleanControl = ({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: unknown
|
||||
onChange: (v: unknown) => void
|
||||
}) => {
|
||||
const { translate } = useLocalization()
|
||||
const current =
|
||||
value === true || value === 'true'
|
||||
? 'true'
|
||||
: value === false || value === 'false'
|
||||
? 'false'
|
||||
: ''
|
||||
|
||||
return (
|
||||
<select
|
||||
|
|
@ -48,7 +72,7 @@ const BooleanControl = ({ value, onChange }: { value: unknown; onChange: (v: unk
|
|||
onChange(event.target.value === 'true')
|
||||
}}
|
||||
>
|
||||
<option value="">— tanımsız —</option>
|
||||
<option value="">{translate('::ListForms.EditorOptions.Undefined')}</option>
|
||||
<option value="true">true</option>
|
||||
<option value="false">false</option>
|
||||
</select>
|
||||
|
|
@ -64,6 +88,7 @@ const SelectControl = ({
|
|||
value: unknown
|
||||
onChange: (v: unknown) => void
|
||||
}) => {
|
||||
const { translate } = useLocalization()
|
||||
const current = value === undefined || value === null ? '' : String(value)
|
||||
const known = spec.choices?.some((choice) => String(choice.value) === current)
|
||||
|
||||
|
|
@ -78,18 +103,23 @@ const SelectControl = ({
|
|||
onChange(choice ? choice.value : raw)
|
||||
}}
|
||||
>
|
||||
<option value="">— tanımsız —</option>
|
||||
<option value="">{translate('::ListForms.EditorOptions.Undefined')}</option>
|
||||
{spec.choices?.map((choice) => (
|
||||
<option key={String(choice.value)} value={String(choice.value)}>
|
||||
{choice.label}
|
||||
</option>
|
||||
))}
|
||||
{current && !known && <option value={current}>{current} (mevcut değer)</option>}
|
||||
{current && !known && (
|
||||
<option value={current}>
|
||||
{current} ({translate('::ListForms.EditorOptions.CurrentValue')})
|
||||
</option>
|
||||
)}
|
||||
</select>
|
||||
)
|
||||
}
|
||||
|
||||
const JsonControl = ({ value, onChange }: { value: unknown; onChange: (v: unknown) => void }) => {
|
||||
const { translate } = useLocalization()
|
||||
const serialized = value === undefined ? '' : JSON.stringify(value, null, 2)
|
||||
const [draft, setDraft] = useState(serialized)
|
||||
const [invalid, setInvalid] = useState(false)
|
||||
|
|
@ -122,7 +152,11 @@ const JsonControl = ({ value, onChange }: { value: unknown; onChange: (v: unknow
|
|||
}
|
||||
}}
|
||||
/>
|
||||
{invalid && <p className="mt-1 text-[11px] text-red-500">Geçersiz JSON, değer kaydedilmedi.</p>}
|
||||
{invalid && (
|
||||
<p className="mt-1 text-[11px] text-red-500">
|
||||
{translate('::ListForms.EditorOptions.InvalidJsonNotSaved')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -135,11 +169,14 @@ const StringListControl = ({
|
|||
spec: OptionSpec
|
||||
value: unknown
|
||||
onChange: (v: unknown) => void
|
||||
}) => (
|
||||
}) => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
return (
|
||||
<input
|
||||
className={controlClass}
|
||||
value={Array.isArray(value) ? value.join(', ') : leafToText(value)}
|
||||
placeholder={spec.placeholder}
|
||||
placeholder={spec.placeholder ? translate('::' + spec.placeholder) : undefined}
|
||||
onChange={(event) => {
|
||||
const items = event.target.value
|
||||
.split(',')
|
||||
|
|
@ -149,9 +186,13 @@ const StringListControl = ({
|
|||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const OptionField = ({ spec, value, onChange }: OptionFieldProps) => {
|
||||
const { translate } = useLocalization()
|
||||
const mismatch = detectMismatch(spec, value)
|
||||
const help = spec.help ? translate('::' + spec.help) : undefined
|
||||
const placeholder = spec.placeholder ? translate('::' + spec.placeholder) : undefined
|
||||
const isSet = value !== undefined
|
||||
// Nesne/dizi tutan bir yol basit girdiyle düzenlenemez; ham JSON'a yönlendiriyoruz.
|
||||
// Basit tip uyuşmazlıkları (metin yazılmış sayı gibi) düzenlenebilir kalır.
|
||||
|
|
@ -177,7 +218,7 @@ const OptionField = ({ spec, value, onChange }: OptionFieldProps) => {
|
|||
className={controlClass}
|
||||
type="number"
|
||||
value={typeof value === 'number' ? value : leafToText(value)}
|
||||
placeholder={spec.placeholder}
|
||||
placeholder={placeholder}
|
||||
onChange={(event) => onChange(coerceNumber(event.target.value))}
|
||||
/>
|
||||
)
|
||||
|
|
@ -186,7 +227,7 @@ const OptionField = ({ spec, value, onChange }: OptionFieldProps) => {
|
|||
<input
|
||||
className={controlClass}
|
||||
value={leafToText(value)}
|
||||
placeholder={spec.placeholder}
|
||||
placeholder={placeholder}
|
||||
onChange={(event) => onChange(coerceSize(event.target.value))}
|
||||
/>
|
||||
)
|
||||
|
|
@ -195,8 +236,10 @@ const OptionField = ({ spec, value, onChange }: OptionFieldProps) => {
|
|||
<input
|
||||
className={controlClass}
|
||||
value={leafToText(value)}
|
||||
placeholder={spec.placeholder}
|
||||
onChange={(event) => onChange(event.target.value === '' ? undefined : event.target.value)}
|
||||
placeholder={placeholder}
|
||||
onChange={(event) =>
|
||||
onChange(event.target.value === '' ? undefined : event.target.value)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -213,14 +256,14 @@ const OptionField = ({ spec, value, onChange }: OptionFieldProps) => {
|
|||
<div className="mb-1 flex items-center gap-1">
|
||||
<span
|
||||
className={`min-w-0 truncate text-xs ${isSet ? 'font-semibold text-indigo-600 dark:text-indigo-300' : 'text-gray-500'}`}
|
||||
title={spec.help ? `${spec.path} — ${spec.help}` : spec.path}
|
||||
title={help ? `${spec.path} — ${help}` : spec.path}
|
||||
>
|
||||
{spec.label}
|
||||
</span>
|
||||
{spec.platform && (
|
||||
<span
|
||||
className="rounded bg-emerald-100 px-1 text-[10px] text-emerald-700 dark:bg-emerald-900 dark:text-emerald-200"
|
||||
title="Bu alan backend tarafından tipli olarak okunur; yanlış tipte yazılırsa yok sayılır."
|
||||
title={translate('::ListForms.EditorOptions.PlatformFieldHint')}
|
||||
>
|
||||
platform
|
||||
</span>
|
||||
|
|
@ -229,7 +272,7 @@ const OptionField = ({ spec, value, onChange }: OptionFieldProps) => {
|
|||
<button
|
||||
type="button"
|
||||
className="ml-auto shrink-0 text-[10px] text-gray-400 hover:text-red-500"
|
||||
title="Bu ayarı JSON'dan kaldır"
|
||||
title={translate('::ListForms.EditorOptions.RemoveFromJson')}
|
||||
onClick={() => onChange(undefined)}
|
||||
>
|
||||
<FaUndo />
|
||||
|
|
@ -240,13 +283,13 @@ const OptionField = ({ spec, value, onChange }: OptionFieldProps) => {
|
|||
{mismatch && (
|
||||
<p className="mt-1 flex items-start gap-1 text-[11px] text-amber-600">
|
||||
<FaExclamationTriangle className="mt-[2px] shrink-0" />
|
||||
<span>{mismatch}</span>
|
||||
<span>{translate(mismatch.key, mismatch.params)}</span>
|
||||
</p>
|
||||
)}
|
||||
{/* Yardım metni tek satıra kısaltılır; tamamı tooltip'te durur. */}
|
||||
{!mismatch && spec.help && (
|
||||
<p className="mt-1 truncate text-[11px] text-gray-400" title={spec.help}>
|
||||
{spec.help}
|
||||
{!mismatch && help && (
|
||||
<p className="mt-1 truncate text-[11px] text-gray-400" title={help}>
|
||||
{help}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export const parseOptionsJson = (value?: string): ParseResult => {
|
|||
try {
|
||||
const parsed = JSON.parse(trimmed)
|
||||
if (!isPlainObject(parsed)) {
|
||||
return { data: {}, error: 'editorOptions bir JSON nesnesi ({ ... }) olmalıdır.' }
|
||||
return { data: {}, error: 'ListForms.EditorOptions.MustBeJsonObject' }
|
||||
}
|
||||
return { data: parsed }
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -57,41 +57,68 @@ export type OptionGroupKey =
|
|||
export const optionGroups: { key: OptionGroupKey; title: string; description: string }[] = [
|
||||
{
|
||||
key: 'common',
|
||||
title: 'Genel Davranış',
|
||||
description: 'Tüm editörlerde geçerli olan durum, yetki ve doğrulama ayarları.',
|
||||
title: 'ListForms.EditorOptions.Spec.Common.Title',
|
||||
description: 'ListForms.EditorOptions.Spec.Common.Description',
|
||||
},
|
||||
{
|
||||
key: 'appearance',
|
||||
title: 'Görünüm ve Boyut',
|
||||
description: 'Etiket, boyut ve stil ayarları. Boyutlar sayı veya 100% gibi CSS değeri olabilir.',
|
||||
title: 'ListForms.EditorOptions.Spec.Appearance.Title',
|
||||
description: 'ListForms.EditorOptions.Spec.Appearance.Description',
|
||||
},
|
||||
{
|
||||
key: 'text',
|
||||
title: 'Metin / Maske',
|
||||
description: 'ListForms.EditorOptions.Spec.Text.Description',
|
||||
},
|
||||
{
|
||||
key: 'number',
|
||||
title: 'ListForms.EditorOptions.Spec.Number.Title',
|
||||
description: 'ListForms.EditorOptions.Spec.Number.Description',
|
||||
},
|
||||
{
|
||||
key: 'date',
|
||||
title: 'ListForms.EditorOptions.Spec.Date.Title',
|
||||
description: 'ListForms.EditorOptions.Spec.Date.Description',
|
||||
},
|
||||
{ key: 'text', title: 'Metin / Maske', description: 'TextBox, TextArea ve Autocomplete ayarları.' },
|
||||
{ key: 'number', title: 'Sayı', description: 'NumberBox için sınır, adım ve format ayarları.' },
|
||||
{ key: 'date', title: 'Tarih / Saat', description: 'DateBox, Calendar ve DateRangeBox ayarları.' },
|
||||
{
|
||||
key: 'dropdown',
|
||||
title: 'Açılır Liste / Arama',
|
||||
description: 'SelectBox, Lookup, TagBox ve GridBox için ortak liste ve arama ayarları.',
|
||||
title: 'ListForms.EditorOptions.Spec.Dropdown.Title',
|
||||
description: 'ListForms.EditorOptions.Spec.Dropdown.Description',
|
||||
},
|
||||
{
|
||||
key: 'tagBox',
|
||||
title: 'TagBox',
|
||||
description: 'ListForms.EditorOptions.Spec.TagBox.Description',
|
||||
},
|
||||
{ key: 'tagBox', title: 'TagBox', description: 'Çoklu seçim editörüne özel ayarlar.' },
|
||||
{
|
||||
key: 'gridBox',
|
||||
title: 'GridBox',
|
||||
description: 'Tablo görünümlü açılır seçici. Bu alanlar backend tarafından GridBoxOptions olarak okunur.',
|
||||
description: 'ListForms.EditorOptions.Spec.GridBox.Description',
|
||||
},
|
||||
{
|
||||
key: 'image',
|
||||
title: 'Görsel Yükleme / Önizleme',
|
||||
description: 'ImageUpload ve ImageViewer editörleri için yükleme ve küçük resim ayarları.',
|
||||
title: 'ListForms.EditorOptions.Spec.Image.Title',
|
||||
description: 'ListForms.EditorOptions.Spec.Image.Description',
|
||||
},
|
||||
{
|
||||
key: 'choice',
|
||||
title: 'ListForms.EditorOptions.Spec.Choice.Title',
|
||||
description: 'ListForms.EditorOptions.Spec.Choice.Description',
|
||||
},
|
||||
{
|
||||
key: 'slider',
|
||||
title: 'ListForms.EditorOptions.Spec.Slider.Title',
|
||||
description: 'ListForms.EditorOptions.Spec.Slider.Description',
|
||||
},
|
||||
{
|
||||
key: 'html',
|
||||
title: 'ListForms.EditorOptions.Spec.Html.Title',
|
||||
description: 'ListForms.EditorOptions.Spec.Html.Description',
|
||||
},
|
||||
{ key: 'choice', title: 'Onay / Seçim', description: 'CheckBox, Switch, RadioGroup ve ColorBox ayarları.' },
|
||||
{ key: 'slider', title: 'Kaydırıcı', description: 'Slider ve RangeSlider ayarları.' },
|
||||
{ key: 'html', title: 'HTML Editör', description: 'dxHtmlEditor araç çubuğu ve görsel ayarları.' },
|
||||
{
|
||||
key: 'grid',
|
||||
title: 'Liste Sütunu Etkisi',
|
||||
description:
|
||||
'Bu alanlar düzenleme editörünün yanı sıra listedeki sütun gösterimini de etkiler.',
|
||||
title: 'ListForms.EditorOptions.Spec.Grid.Title',
|
||||
description: 'ListForms.EditorOptions.Spec.Grid.Description',
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -115,8 +142,6 @@ const SLIDER_EDITORS = ['dxSlider', 'dxRangeSlider']
|
|||
/** Metin girişi olmayan editörlerde placeholder/mask alanlarını gizlemek için. */
|
||||
const NON_INPUT_EDITORS = ['dxCheckBox', 'dxSwitch', 'dxRadioGroup', ...SLIDER_EDITORS]
|
||||
|
||||
const booleanHelp = 'Boş bırakılırsa JSON\'a yazılmaz ve DevExtreme varsayılanı geçerli olur.'
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Ayar sözlüğü
|
||||
* ------------------------------------------------------------------ */
|
||||
|
|
@ -128,31 +153,43 @@ export const optionSpecs: OptionSpec[] = [
|
|||
label: 'readOnly',
|
||||
type: 'boolean',
|
||||
group: 'common',
|
||||
help: 'Alan görünür ama değiştirilemez. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.ReadOnly.Help',
|
||||
},
|
||||
{
|
||||
path: 'disabled',
|
||||
label: 'disabled',
|
||||
type: 'boolean',
|
||||
group: 'common',
|
||||
help: 'Alan pasifleşir ve forma dahil edilmez. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.Disabled.Help',
|
||||
},
|
||||
{
|
||||
path: 'visible',
|
||||
label: 'visible',
|
||||
type: 'boolean',
|
||||
group: 'common',
|
||||
help: 'Editörü tamamen gizler. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.Visible.Help',
|
||||
},
|
||||
{
|
||||
path: 'hint',
|
||||
label: 'hint',
|
||||
type: 'text',
|
||||
group: 'common',
|
||||
help: 'ListForms.EditorOptions.Spec.Hint.Help',
|
||||
},
|
||||
{
|
||||
path: 'tabIndex',
|
||||
label: 'tabIndex',
|
||||
type: 'number',
|
||||
group: 'common',
|
||||
help: 'ListForms.EditorOptions.Spec.TabIndex.Help',
|
||||
},
|
||||
{ path: 'hint', label: 'hint', type: 'text', group: 'common', help: 'Fare ile üzerine gelince çıkan ipucu.' },
|
||||
{ path: 'tabIndex', label: 'tabIndex', type: 'number', group: 'common', help: 'Tab ile gezinme sırası.' },
|
||||
{
|
||||
path: 'valueChangeEvent',
|
||||
label: 'valueChangeEvent',
|
||||
type: 'text',
|
||||
group: 'common',
|
||||
placeholder: 'change / input / keyup',
|
||||
help: 'Değerin hangi DOM olayında işleneceği. Anlık kaydetme için input.',
|
||||
help: 'ListForms.EditorOptions.Spec.ValueChangeEvent.Help',
|
||||
},
|
||||
{
|
||||
path: 'validationMessageMode',
|
||||
|
|
@ -183,14 +220,14 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'text',
|
||||
group: 'common',
|
||||
placeholder: 'my-editor-class',
|
||||
help: 'Editör kök elemanına eklenecek CSS sınıfı.',
|
||||
help: 'ListForms.EditorOptions.Spec.ElementAttrClass.Help',
|
||||
},
|
||||
{
|
||||
path: 'inputAttr.aria-label',
|
||||
label: 'inputAttr.aria-label',
|
||||
type: 'text',
|
||||
group: 'common',
|
||||
help: 'Erişilebilirlik etiketi.',
|
||||
help: 'ListForms.EditorOptions.Spec.InputAttrAriaLabel.Help',
|
||||
},
|
||||
{
|
||||
path: 'inputAttr.style',
|
||||
|
|
@ -198,7 +235,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'text',
|
||||
group: 'common',
|
||||
placeholder: 'text-align: right',
|
||||
help: 'Girdi alanına doğrudan inline CSS uygular. Sayısal alanları sağa yaslamak için kullanışlıdır.',
|
||||
help: 'ListForms.EditorOptions.Spec.InputAttrStyle.Help',
|
||||
},
|
||||
|
||||
// ── Görünüm ──────────────────────────────────────────────────────
|
||||
|
|
@ -208,7 +245,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'size',
|
||||
group: 'appearance',
|
||||
placeholder: '100% veya 240',
|
||||
help: 'Sadece rakam yazarsan sayı olarak kaydedilir (240). Yüzde için 100% yaz.',
|
||||
help: 'ListForms.EditorOptions.Spec.Width.Help',
|
||||
},
|
||||
{ path: 'height', label: 'height', type: 'size', group: 'appearance', placeholder: '200' },
|
||||
{
|
||||
|
|
@ -216,9 +253,15 @@ export const optionSpecs: OptionSpec[] = [
|
|||
label: 'placeholder',
|
||||
type: 'text',
|
||||
group: 'appearance',
|
||||
help: 'Alan boşken görünen metin. Sütunun PlaceHolder alanını ezer.',
|
||||
help: 'ListForms.EditorOptions.Spec.Placeholder.Help',
|
||||
},
|
||||
{
|
||||
path: 'label',
|
||||
label: 'label',
|
||||
type: 'text',
|
||||
group: 'appearance',
|
||||
help: 'ListForms.EditorOptions.Spec.Label.Help',
|
||||
},
|
||||
{ path: 'label', label: 'label', type: 'text', group: 'appearance', help: 'Editör içi etiket metni.' },
|
||||
{
|
||||
path: 'labelMode',
|
||||
label: 'labelMode',
|
||||
|
|
@ -247,7 +290,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
label: 'showClearButton',
|
||||
type: 'boolean',
|
||||
group: 'appearance',
|
||||
help: 'Temizleme (x) butonu. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.ShowClearButton.Help',
|
||||
},
|
||||
|
||||
// ── Metin / Maske ────────────────────────────────────────────────
|
||||
|
|
@ -275,7 +318,14 @@ export const optionSpecs: OptionSpec[] = [
|
|||
editors: TEXT_EDITORS,
|
||||
placeholder: '(000) 000-0000',
|
||||
},
|
||||
{ path: 'maskChar', label: 'maskChar', type: 'text', group: 'text', editors: TEXT_EDITORS, placeholder: '_' },
|
||||
{
|
||||
path: 'maskChar',
|
||||
label: 'maskChar',
|
||||
type: 'text',
|
||||
group: 'text',
|
||||
editors: TEXT_EDITORS,
|
||||
placeholder: '_',
|
||||
},
|
||||
{
|
||||
path: 'maskInvalidMessage',
|
||||
label: 'maskInvalidMessage',
|
||||
|
|
@ -300,7 +350,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'boolean',
|
||||
group: 'text',
|
||||
editors: TEXT_EDITORS,
|
||||
help: 'true ise maske karakterleri de veritabanına yazılır. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.UseMaskedValue.Help',
|
||||
},
|
||||
{
|
||||
path: 'maskRules.X',
|
||||
|
|
@ -309,16 +359,22 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'text',
|
||||
editors: TEXT_EDITORS,
|
||||
placeholder: '[0-9]',
|
||||
help: 'Maskede X karakterinin karşılığı olan düzenli ifade.',
|
||||
help: 'ListForms.EditorOptions.Spec.MaskRulesX.Help',
|
||||
},
|
||||
{
|
||||
path: 'spellcheck',
|
||||
label: 'spellcheck',
|
||||
type: 'boolean',
|
||||
group: 'text',
|
||||
editors: TEXT_EDITORS,
|
||||
},
|
||||
{ path: 'spellcheck', label: 'spellcheck', type: 'boolean', group: 'text', editors: TEXT_EDITORS },
|
||||
{
|
||||
path: 'autoResizeEnabled',
|
||||
label: 'autoResizeEnabled',
|
||||
type: 'boolean',
|
||||
group: 'text',
|
||||
editors: ['dxTextArea'],
|
||||
help: 'İçerik büyüdükçe alan uzar. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.AutoResizeEnabled.Help',
|
||||
},
|
||||
{ path: 'minHeight', label: 'minHeight', type: 'size', group: 'text', editors: ['dxTextArea'] },
|
||||
{ path: 'maxHeight', label: 'maxHeight', type: 'size', group: 'text', editors: ['dxTextArea'] },
|
||||
|
|
@ -347,7 +403,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'select',
|
||||
group: 'number',
|
||||
editors: ['dxNumberBox'],
|
||||
help: 'Sayısal format tipi. Seçilirse format alanı nesne olur.',
|
||||
help: 'ListForms.EditorOptions.Spec.FormatType.Help',
|
||||
choices: [
|
||||
{ value: 'fixedPoint', label: 'fixedPoint' },
|
||||
{ value: 'decimal', label: 'decimal' },
|
||||
|
|
@ -366,7 +422,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'number',
|
||||
group: 'number',
|
||||
editors: ['dxNumberBox'],
|
||||
help: 'Ondalık basamak sayısı.',
|
||||
help: 'ListForms.EditorOptions.Spec.FormatPrecision.Help',
|
||||
},
|
||||
{
|
||||
path: 'format.currency',
|
||||
|
|
@ -389,7 +445,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'boolean',
|
||||
group: 'number',
|
||||
editors: ['dxNumberBox', 'dxDateBox'],
|
||||
help: 'Girdiyi formatlı maske olarak yönetir. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.UseMaskBehavior.Help',
|
||||
},
|
||||
|
||||
// ── Tarih ────────────────────────────────────────────────────────
|
||||
|
|
@ -425,7 +481,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'date',
|
||||
editors: DATE_EDITORS,
|
||||
placeholder: 'dd/MM/yyyy',
|
||||
help: 'Ekranda gösterilecek biçim. Listedeki sütun biçimini de ezer.',
|
||||
help: 'ListForms.EditorOptions.Spec.DisplayFormat.Help',
|
||||
},
|
||||
{
|
||||
path: 'dateSerializationFormat',
|
||||
|
|
@ -434,7 +490,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'date',
|
||||
editors: DATE_EDITORS,
|
||||
placeholder: 'yyyy-MM-ddTHH:mm:ss',
|
||||
help: 'Veritabanına yazılacak biçim. Tarih kaymalarının çoğu bu alandan kaynaklanır.',
|
||||
help: 'ListForms.EditorOptions.Spec.DateSerializationFormat.Help',
|
||||
},
|
||||
{
|
||||
path: 'interval',
|
||||
|
|
@ -442,11 +498,11 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'number',
|
||||
group: 'date',
|
||||
editors: DATE_EDITORS,
|
||||
help: 'Saat listesinde dakika aralığı.',
|
||||
help: 'ListForms.EditorOptions.Spec.Interval.Help',
|
||||
},
|
||||
{
|
||||
path: 'min',
|
||||
label: 'min (tarih)',
|
||||
label: 'min',
|
||||
type: 'text',
|
||||
group: 'date',
|
||||
editors: DATE_EDITORS,
|
||||
|
|
@ -454,7 +510,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
},
|
||||
{
|
||||
path: 'max',
|
||||
label: 'max (tarih)',
|
||||
label: 'max',
|
||||
type: 'text',
|
||||
group: 'date',
|
||||
editors: DATE_EDITORS,
|
||||
|
|
@ -515,7 +571,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'dropdown',
|
||||
editors: DROPDOWN_EDITORS,
|
||||
platform: true,
|
||||
help: 'Listede arama kutusu. TagBox için backend TagBoxOptions olarak da okur. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.SearchEnabled.Help',
|
||||
},
|
||||
{
|
||||
path: 'searchMode',
|
||||
|
|
@ -535,7 +591,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'dropdown',
|
||||
editors: DROPDOWN_EDITORS,
|
||||
placeholder: 'name',
|
||||
help: 'Aramanın hangi alanda yapılacağı.',
|
||||
help: 'ListForms.EditorOptions.Spec.SearchExpr.Help',
|
||||
},
|
||||
{
|
||||
path: 'searchTimeout',
|
||||
|
|
@ -565,7 +621,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'dropdown',
|
||||
editors: DROPDOWN_EDITORS,
|
||||
platform: true,
|
||||
help: 'Listede olmayan değerin yazılmasına izin verir. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.AcceptCustomValue.Help',
|
||||
},
|
||||
{
|
||||
path: 'noDataText',
|
||||
|
|
@ -573,7 +629,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'text',
|
||||
group: 'dropdown',
|
||||
editors: DROPDOWN_EDITORS,
|
||||
placeholder: 'Kayıt bulunamadı',
|
||||
placeholder: 'ListForms.EditorOptions.Spec.NoDataText.Placeholder',
|
||||
},
|
||||
{
|
||||
path: 'deferRendering',
|
||||
|
|
@ -581,7 +637,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'boolean',
|
||||
group: 'dropdown',
|
||||
editors: DROPDOWN_EDITORS,
|
||||
help: 'Liste yalnızca açıldığında render edilir. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.DeferRendering.Help',
|
||||
},
|
||||
{
|
||||
path: 'wrapItemText',
|
||||
|
|
@ -620,7 +676,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'tagBox',
|
||||
editors: ['dxTagBox'],
|
||||
platform: true,
|
||||
help: 'Liste öğelerinde onay kutusu gösterir. Boş bırakılırsa platform varsayılanı true. ',
|
||||
help: 'ListForms.EditorOptions.Spec.ShowSelectionControls.Help',
|
||||
},
|
||||
{
|
||||
path: 'maxDisplayedTags',
|
||||
|
|
@ -645,13 +701,19 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'tagBox',
|
||||
editors: ['dxTagBox'],
|
||||
platform: true,
|
||||
help: 'Boş bırakılırsa platform varsayılanı useButtons.',
|
||||
help: 'ListForms.EditorOptions.Spec.ApplyValueMode.Help',
|
||||
choices: [
|
||||
{ value: 'instantly', label: 'instantly' },
|
||||
{ value: 'useButtons', label: 'useButtons' },
|
||||
],
|
||||
},
|
||||
{ path: 'multiline', label: 'multiline', type: 'boolean', group: 'tagBox', editors: ['dxTagBox'] },
|
||||
{
|
||||
path: 'multiline',
|
||||
label: 'multiline',
|
||||
type: 'boolean',
|
||||
group: 'tagBox',
|
||||
editors: ['dxTagBox'],
|
||||
},
|
||||
{
|
||||
path: 'hideSelectedItems',
|
||||
label: 'hideSelectedItems',
|
||||
|
|
@ -669,7 +731,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
editors: ['dxGridBox'],
|
||||
platform: true,
|
||||
placeholder: 'key, name, group',
|
||||
help: 'Açılır tabloda gösterilecek sütunlar. Virgülle ayır; JSON\'a dizi olarak yazılır.',
|
||||
help: 'ListForms.EditorOptions.Spec.Columns.Help',
|
||||
},
|
||||
{
|
||||
path: 'selectionMode',
|
||||
|
|
@ -678,7 +740,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'gridBox',
|
||||
editors: ['dxGridBox'],
|
||||
platform: true,
|
||||
help: 'multiple seçilirse liste sütunu da çoklu değer olarak çalışır.',
|
||||
help: 'ListForms.EditorOptions.Spec.SelectionMode.Help',
|
||||
choices: [
|
||||
{ value: 'single', label: 'single' },
|
||||
{ value: 'multiple', label: 'multiple' },
|
||||
|
|
@ -692,7 +754,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'gridBox',
|
||||
editors: ['dxGridBox'],
|
||||
platform: true,
|
||||
help: 'Açılır tabloda filtre satırı. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.FilterRowVisible.Help',
|
||||
},
|
||||
|
||||
// ── Görsel ───────────────────────────────────────────────────────
|
||||
|
|
@ -703,7 +765,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'image',
|
||||
editors: IMAGE_EDITORS,
|
||||
platform: true,
|
||||
help: 'Boş bırakılırsa görsel base64 olarak saklanır.',
|
||||
help: 'ListForms.EditorOptions.Spec.UploadUrl.Help',
|
||||
},
|
||||
{
|
||||
path: 'fileFieldName',
|
||||
|
|
@ -712,7 +774,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'image',
|
||||
editors: IMAGE_EDITORS,
|
||||
placeholder: 'file',
|
||||
help: 'Yükleme isteğindeki form alan adı.',
|
||||
help: 'ListForms.EditorOptions.Spec.FileFieldName.Help',
|
||||
},
|
||||
{
|
||||
path: 'accept',
|
||||
|
|
@ -730,7 +792,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
group: 'image',
|
||||
editors: IMAGE_EDITORS,
|
||||
platform: true,
|
||||
help: 'Çoklu görsel. Metin olarak "true" yazılırsa da çalışır ama boolean tercih edilmeli.',
|
||||
help: 'ListForms.EditorOptions.Spec.Multiple.Help',
|
||||
},
|
||||
{
|
||||
path: 'maxFileSize',
|
||||
|
|
@ -748,10 +810,22 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'text',
|
||||
group: 'choice',
|
||||
editors: ['dxCheckBox'],
|
||||
help: 'Onay kutusunun yanında görünen metin.',
|
||||
help: 'ListForms.EditorOptions.Spec.Text.Help',
|
||||
},
|
||||
{
|
||||
path: 'switchedOnText',
|
||||
label: 'switchedOnText',
|
||||
type: 'text',
|
||||
group: 'choice',
|
||||
editors: ['dxSwitch'],
|
||||
},
|
||||
{
|
||||
path: 'switchedOffText',
|
||||
label: 'switchedOffText',
|
||||
type: 'text',
|
||||
group: 'choice',
|
||||
editors: ['dxSwitch'],
|
||||
},
|
||||
{ path: 'switchedOnText', label: 'switchedOnText', type: 'text', group: 'choice', editors: ['dxSwitch'] },
|
||||
{ path: 'switchedOffText', label: 'switchedOffText', type: 'text', group: 'choice', editors: ['dxSwitch'] },
|
||||
{
|
||||
path: 'layout',
|
||||
label: 'layout',
|
||||
|
|
@ -794,7 +868,13 @@ export const optionSpecs: OptionSpec[] = [
|
|||
{ value: 'always', label: 'always' },
|
||||
],
|
||||
},
|
||||
{ path: 'showRange', label: 'showRange', type: 'boolean', group: 'slider', editors: SLIDER_EDITORS },
|
||||
{
|
||||
path: 'showRange',
|
||||
label: 'showRange',
|
||||
type: 'boolean',
|
||||
group: 'slider',
|
||||
editors: SLIDER_EDITORS,
|
||||
},
|
||||
|
||||
// ── HTML editör ──────────────────────────────────────────────────
|
||||
{
|
||||
|
|
@ -821,7 +901,7 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'json',
|
||||
group: 'html',
|
||||
editors: ['dxHtmlEditor'],
|
||||
help: 'Araç çubuğu tanımı. Hazır ayarlardaki "htmlEditor toolbar" bunu doldurur.',
|
||||
help: 'ListForms.EditorOptions.Spec.ToolbarItems.Help',
|
||||
},
|
||||
{
|
||||
path: 'mediaResizing.enabled',
|
||||
|
|
@ -857,21 +937,21 @@ export const optionSpecs: OptionSpec[] = [
|
|||
type: 'text',
|
||||
group: 'grid',
|
||||
placeholder: 'dd/MM/yyyy veya currency',
|
||||
help: 'Metin olarak yazılırsa listedeki sütun biçimini de ezer. Sayı formatı için Sayı bölümündeki format.type alanını kullan.',
|
||||
help: 'ListForms.EditorOptions.Spec.Format.Help',
|
||||
},
|
||||
{
|
||||
path: 'encodeHtml',
|
||||
label: 'encodeHtml',
|
||||
type: 'boolean',
|
||||
group: 'grid',
|
||||
help: 'false yapılırsa hücre içeriği HTML olarak render edilir. ' + booleanHelp,
|
||||
help: 'ListForms.EditorOptions.Spec.EncodeHtml.Help',
|
||||
},
|
||||
{
|
||||
path: 'buttons',
|
||||
label: 'buttons',
|
||||
type: 'json',
|
||||
group: 'grid',
|
||||
help: 'Editör içi butonlar. options.onClick metin olarak yazılırsa çalışma anında fonksiyona çevrilir.',
|
||||
help: 'ListForms.EditorOptions.Spec.Buttons.Help',
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -96,32 +96,32 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
|
|||
{
|
||||
key: 'readOnly',
|
||||
label: 'Salt okunur',
|
||||
description: 'readOnly: true — alan görünür ama değiştirilemez.',
|
||||
description: 'ListForms.EditorOptions.Preset.ReadOnly.Description',
|
||||
value: { readOnly: true },
|
||||
},
|
||||
{
|
||||
key: 'disabled',
|
||||
label: 'Pasif',
|
||||
description: 'disabled: true — alan tamamen pasifleşir.',
|
||||
description: 'ListForms.EditorOptions.Preset.Disabled.Description',
|
||||
value: { disabled: true },
|
||||
},
|
||||
{
|
||||
key: 'rightAligned',
|
||||
label: 'Sağa yaslı',
|
||||
description: 'Girdi metnini sağa yaslar. Tutar/miktar alanları için.',
|
||||
label: 'ListForms.EditorOptions.Preset.RightAligned.Label',
|
||||
description: 'ListForms.EditorOptions.Preset.RightAligned.Description',
|
||||
value: { inputAttr: { style: 'text-align: right' } },
|
||||
},
|
||||
{
|
||||
key: 'fixedPoint2',
|
||||
label: 'Ondalık (2 hane)',
|
||||
label: 'ListForms.EditorOptions.Preset.FixedPoint2.Label',
|
||||
description: 'format.type fixedPoint, precision 2.',
|
||||
editors: ['dxNumberBox'],
|
||||
value: { format: { type: 'fixedPoint', precision: 2 } },
|
||||
},
|
||||
{
|
||||
key: 'numberSpin2',
|
||||
label: 'Ondalık + spin',
|
||||
description: 'precision 2, mask davranışı ve artır/azalt butonları.',
|
||||
label: 'ListForms.EditorOptions.Preset.NumberSpin2.Label',
|
||||
description: 'ListForms.EditorOptions.Preset.NumberSpin2.Description',
|
||||
editors: ['dxNumberBox'],
|
||||
value: {
|
||||
format: { type: 'fixedPoint', precision: 2 },
|
||||
|
|
@ -132,14 +132,14 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
|
|||
{
|
||||
key: 'currency',
|
||||
label: 'Para birimi (TRY)',
|
||||
description: 'currency formatı, 2 ondalık.',
|
||||
description: 'ListForms.EditorOptions.Preset.Currency.Description',
|
||||
editors: ['dxNumberBox'],
|
||||
value: { format: { type: 'currency', precision: 2, currency: 'TRY' } },
|
||||
},
|
||||
{
|
||||
key: 'date',
|
||||
label: 'Tarih dd/MM/yyyy',
|
||||
description: 'Görünüm ve veritabanı biçimini birlikte ayarlar.',
|
||||
label: 'ListForms.EditorOptions.Preset.DateFormat.Label',
|
||||
description: 'ListForms.EditorOptions.Preset.Date.Description',
|
||||
editors: ['dxDateBox', 'dxCalendar', 'dxDateRangeBox'],
|
||||
value: {
|
||||
type: 'date',
|
||||
|
|
@ -149,8 +149,8 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
|
|||
},
|
||||
{
|
||||
key: 'dateTime',
|
||||
label: 'Tarih + saat',
|
||||
description: 'dd/MM/yyyy HH:mm görünümü, ISO serileştirme.',
|
||||
label: 'ListForms.EditorOptions.Preset.DateTime.Label',
|
||||
description: 'ListForms.EditorOptions.Preset.DateTime.Description',
|
||||
editors: ['dxDateBox', 'dxCalendar', 'dxDateRangeBox'],
|
||||
value: {
|
||||
type: 'datetime',
|
||||
|
|
@ -160,8 +160,8 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
|
|||
},
|
||||
{
|
||||
key: 'timePicker',
|
||||
label: 'Saat seçici',
|
||||
description: '5 dakika aralıklı liste tipi saat seçimi.',
|
||||
label: 'ListForms.EditorOptions.Preset.TimePicker.Label',
|
||||
description: 'ListForms.EditorOptions.Preset.TimePicker.Description',
|
||||
editors: ['dxDateBox'],
|
||||
value: {
|
||||
type: 'time',
|
||||
|
|
@ -175,7 +175,7 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
|
|||
{
|
||||
key: 'phoneMask',
|
||||
label: 'Telefon maskesi',
|
||||
description: 'Maske, yer tutucu ve hata mesajı.',
|
||||
description: 'ListForms.EditorOptions.Preset.PhoneMask.Description',
|
||||
editors: ['dxTextBox', 'dxAutocomplete'],
|
||||
value: {
|
||||
mask: '(000) 000-0000',
|
||||
|
|
@ -187,22 +187,34 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
|
|||
},
|
||||
{
|
||||
key: 'textAreaAuto',
|
||||
label: 'Otomatik büyüyen alan',
|
||||
description: 'autoResizeEnabled ve minimum yükseklik.',
|
||||
label: 'ListForms.EditorOptions.Preset.TextAreaAuto.Label',
|
||||
description: 'ListForms.EditorOptions.Preset.TextAreaAuto.Description',
|
||||
editors: ['dxTextArea'],
|
||||
value: { autoResizeEnabled: true, minHeight: 80, maxHeight: 320 },
|
||||
},
|
||||
{
|
||||
key: 'searchableList',
|
||||
label: 'Aranabilir liste',
|
||||
description: 'İçinde geçen kayıtları arar, 300 ms gecikme.',
|
||||
editors: ['dxSelectBox', 'dxLookup', 'dxTagBox', 'dxDropDownBox', 'dxGridBox', 'dxAutocomplete'],
|
||||
value: { searchEnabled: true, searchMode: 'contains', searchTimeout: 300, showClearButton: true },
|
||||
description: 'ListForms.EditorOptions.Preset.SearchableList.Description',
|
||||
editors: [
|
||||
'dxSelectBox',
|
||||
'dxLookup',
|
||||
'dxTagBox',
|
||||
'dxDropDownBox',
|
||||
'dxGridBox',
|
||||
'dxAutocomplete',
|
||||
],
|
||||
value: {
|
||||
searchEnabled: true,
|
||||
searchMode: 'contains',
|
||||
searchTimeout: 300,
|
||||
showClearButton: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'tagBoxInstant',
|
||||
label: 'TagBox anlık seçim',
|
||||
description: 'Onay kutulu, butonsuz, anında uygulanan çoklu seçim.',
|
||||
label: 'ListForms.EditorOptions.Preset.TagBoxInstant.Label',
|
||||
description: 'ListForms.EditorOptions.Preset.TagBoxInstant.Description',
|
||||
editors: ['dxTagBox'],
|
||||
value: {
|
||||
showSelectionControls: true,
|
||||
|
|
@ -213,8 +225,8 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
|
|||
},
|
||||
{
|
||||
key: 'gridBoxSingle',
|
||||
label: 'GridBox tekli seçim',
|
||||
description: 'key/name sütunları, filtre satırı açık, tek seçim.',
|
||||
label: 'ListForms.EditorOptions.Preset.GridBoxSingle.Label',
|
||||
description: 'ListForms.EditorOptions.Preset.GridBoxSingle.Description',
|
||||
editors: ['dxGridBox'],
|
||||
value: {
|
||||
columns: ['key', 'name'],
|
||||
|
|
@ -227,8 +239,8 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
|
|||
},
|
||||
{
|
||||
key: 'gridBoxMulti',
|
||||
label: 'GridBox çoklu seçim',
|
||||
description: 'Çoklu seçim; liste sütunu da çoklu değer olarak çalışır.',
|
||||
label: 'ListForms.EditorOptions.Preset.GridBoxMulti.Label',
|
||||
description: 'ListForms.EditorOptions.Preset.GridBoxMulti.Description',
|
||||
editors: ['dxGridBox'],
|
||||
value: {
|
||||
columns: ['key', 'name'],
|
||||
|
|
@ -240,27 +252,27 @@ export const editorOptionsPresets: EditorOptionsPreset[] = [
|
|||
},
|
||||
{
|
||||
key: 'imageMulti',
|
||||
label: 'Çoklu görsel 80x80',
|
||||
description: 'Çoklu yükleme ve 80x80 küçük resim.',
|
||||
label: 'ListForms.EditorOptions.Preset.ImageMulti.Label',
|
||||
description: 'ListForms.EditorOptions.Preset.ImageMulti.Description',
|
||||
editors: ['dxImageUpload', 'dxImageViewer'],
|
||||
value: { width: 80, height: 80, multiple: true, accept: 'image/*' },
|
||||
},
|
||||
{
|
||||
key: 'htmlToolbar',
|
||||
label: 'HtmlEditor araç çubuğu',
|
||||
description: 'Tam araç çubuğu, görsel yükleme ve boyutlandırma.',
|
||||
label: 'ListForms.EditorOptions.Preset.HtmlToolbar.Label',
|
||||
description: 'ListForms.EditorOptions.Preset.HtmlToolbar.Description',
|
||||
editors: ['dxHtmlEditor'],
|
||||
value: buildHtmlEditorOptions(),
|
||||
},
|
||||
{
|
||||
key: 'height100',
|
||||
label: 'Yükseklik 100',
|
||||
label: 'ListForms.EditorOptions.Preset.Height100.Label',
|
||||
description: 'height: 100',
|
||||
value: { height: 100 },
|
||||
},
|
||||
{
|
||||
key: 'height200',
|
||||
label: 'Yükseklik 200',
|
||||
label: 'ListForms.EditorOptions.Preset.Height200.Label',
|
||||
description: 'height: 200',
|
||||
value: { height: 200 },
|
||||
},
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import {
|
|||
type RecipeId,
|
||||
type RecipeParam,
|
||||
type ScriptRule as FormScriptRule,
|
||||
type TranslateFn,
|
||||
} from './scriptRecipes'
|
||||
|
||||
const groupIcons: Record<RecipeGroupKey, ScriptRecipeGroup['icon']> = {
|
||||
|
|
@ -52,8 +53,7 @@ const FIELD_SOURCE = 'field'
|
|||
const toParam = (param: RecipeParam): ScriptRecipeParam => ({
|
||||
key: param.key,
|
||||
label: param.label,
|
||||
type:
|
||||
param.type === 'field' ? 'option' : param.type === 'fieldList' ? 'fieldList' : param.type,
|
||||
type: param.type === 'field' ? 'option' : param.type === 'fieldList' ? 'fieldList' : param.type,
|
||||
optionSource: param.type === 'field' || param.type === 'fieldList' ? FIELD_SOURCE : undefined,
|
||||
placeholder: param.placeholder,
|
||||
help: param.help,
|
||||
|
|
@ -79,57 +79,57 @@ const apiReference: ScriptApiEntry[] = runtimeApiReference
|
|||
*/
|
||||
const snippets: ScriptSnippet[] = [
|
||||
{
|
||||
label: 'Koşullu değer yaz',
|
||||
group: 'Akış',
|
||||
description: 'Bir alan doluysa hedefe yaz.',
|
||||
label: 'ListForms.ScriptBuilder.Snippet.ConditionalSet.Label',
|
||||
group: 'ListForms.ScriptBuilder.Snippet.Group.Flow',
|
||||
description: 'ListForms.ScriptBuilder.Snippet.ConditionalSet.Description',
|
||||
code: "if (get('${1:Kaynak}')) set('${2:Hedef}', get('${1:Kaynak}'))",
|
||||
},
|
||||
{
|
||||
label: 'Çok koşullu blok',
|
||||
group: 'Akış',
|
||||
description: 'VE / VEYA ile birleşen iki koşul.',
|
||||
label: 'ListForms.ScriptBuilder.Snippet.MultiCondition.Label',
|
||||
group: 'ListForms.ScriptBuilder.Snippet.Group.Flow',
|
||||
description: 'ListForms.ScriptBuilder.Snippet.MultiCondition.Description',
|
||||
code: "if (str('${1:Durum}') === '${2:Aktif}' && num('${3:Tutar}') > ${4:1000}) {\n ${5:notify('Limit aşıldı')}\n}",
|
||||
},
|
||||
{
|
||||
label: 'Açılışta / değişimde ayrımı',
|
||||
group: 'Akış',
|
||||
description: 'isReady form açılışında true gelir.',
|
||||
code: "if (isReady) {\n ${1:// form açılırken}\n} else {\n ${2:// değer değişince}\n}",
|
||||
label: 'ListForms.ScriptBuilder.Snippet.OpenVsChange.Label',
|
||||
group: 'ListForms.ScriptBuilder.Snippet.Group.Flow',
|
||||
description: 'ListForms.ScriptBuilder.Snippet.OpenVsChange.Description',
|
||||
code: 'if (isReady) {\n ${1:// form açılırken}\n} else {\n ${2:// değer değişince}\n}',
|
||||
},
|
||||
{
|
||||
label: 'Toplu alan yazma',
|
||||
group: 'Veri',
|
||||
description: 'Tek çağrıda birden fazla alan.',
|
||||
code: "set({ ${1:Alan1}: ${2:null}, ${3:Alan2}: ${4:null} })",
|
||||
label: 'ListForms.ScriptBuilder.Snippet.BulkSet.Label',
|
||||
group: 'ListForms.ScriptBuilder.Snippet.Group.Data',
|
||||
description: 'ListForms.ScriptBuilder.Snippet.BulkSet.Description',
|
||||
code: 'set({ ${1:Alan1}: ${2:null}, ${3:Alan2}: ${4:null} })',
|
||||
},
|
||||
{
|
||||
label: 'Seçili kayıttan doldur',
|
||||
group: 'Veri',
|
||||
description: 'Lookup seçiminden birkaç alanı birden taşı.',
|
||||
label: 'ListForms.ScriptBuilder.Snippet.FillFromSelected.Label',
|
||||
group: 'ListForms.ScriptBuilder.Snippet.Group.Data',
|
||||
description: 'ListForms.ScriptBuilder.Snippet.FillFromSelected.Description',
|
||||
code: "copy('${1:kolon}', '${2:Hedef}')\ncopy('${3:kolon2}', '${4:Hedef2}')",
|
||||
},
|
||||
{
|
||||
label: 'Onay iste, vazgeçilirse geri al',
|
||||
group: 'Etkileşim',
|
||||
description: 'ask false dönerse alan eski değerine döner.',
|
||||
label: 'ListForms.ScriptBuilder.Snippet.AskConfirm.Label',
|
||||
group: 'ListForms.ScriptBuilder.Snippet.Group.Interaction',
|
||||
description: 'ListForms.ScriptBuilder.Snippet.AskConfirm.Description',
|
||||
code: "if (!ask('${1:Emin misiniz?}')) return",
|
||||
},
|
||||
{
|
||||
label: 'API çağrısı ve hata yakalama',
|
||||
group: 'Entegrasyon',
|
||||
description: 'Çağrı başarısızsa uyarı gösterir.',
|
||||
label: 'ListForms.ScriptBuilder.Snippet.ApiCall.Label',
|
||||
group: 'ListForms.ScriptBuilder.Snippet.Group.Integration',
|
||||
description: 'ListForms.ScriptBuilder.Snippet.ApiCall.Description',
|
||||
code: "try {\n set('${1:Hedef}', await api('${2:/api/app/musteri/{value}}', '${3:data.name}'))\n} catch (error) {\n notify('${4:Servise ulaşılamadı}')\n}",
|
||||
},
|
||||
{
|
||||
label: 'Hesaplama ve yuvarlama',
|
||||
group: 'Hesaplama',
|
||||
description: 'İki alandan türetilmiş değer.',
|
||||
label: 'ListForms.ScriptBuilder.Snippet.Calc.Label',
|
||||
group: 'ListForms.ScriptBuilder.Snippet.Group.Calc',
|
||||
description: 'ListForms.ScriptBuilder.Snippet.Calc.Description',
|
||||
code: "set('${1:Toplam}', round(num('${2:Miktar}') * num('${3:Fiyat}'), ${4:2}))",
|
||||
},
|
||||
{
|
||||
label: 'Alanı koşula göre kilitle',
|
||||
group: 'Görünüm',
|
||||
description: 'Koşul doğruyken salt okunur olur.',
|
||||
label: 'ListForms.ScriptBuilder.Snippet.LockField.Label',
|
||||
group: 'ListForms.ScriptBuilder.Snippet.Group.View',
|
||||
description: 'ListForms.ScriptBuilder.Snippet.LockField.Description',
|
||||
code: "readOnly('${1:Alan}', str('${2:Durum}') === '${3:Kapali}')",
|
||||
},
|
||||
]
|
||||
|
|
@ -139,9 +139,12 @@ export type FormScriptDialectContext = {
|
|||
fields: string[]
|
||||
/** Script'in bağlı olduğu alan; başlıkta gösterilir. */
|
||||
currentField?: string
|
||||
/** `useLocalization` hook'undan gelen çeviri fonksiyonu. */
|
||||
translate: TranslateFn
|
||||
}
|
||||
|
||||
export const createFormScriptDialect = (context: FormScriptDialectContext): ScriptDialect => {
|
||||
const t = context.translate
|
||||
const fields = Array.from(new Set(context.fields.filter(Boolean))).sort((left, right) =>
|
||||
left.localeCompare(right),
|
||||
)
|
||||
|
|
@ -151,22 +154,51 @@ export const createFormScriptDialect = (context: FormScriptDialectContext): Scri
|
|||
return {
|
||||
id: 'form',
|
||||
title: 'Editor Script',
|
||||
subject: context.currentField || 'alan seçilmemiş',
|
||||
subjectHint: 'Bu alan değiştiğinde çalışacak script.',
|
||||
codeHint:
|
||||
'Tek satırlık yardımcılarla yaz; get/set/notify hazır. Ctrl+Space tamamlama listesini açar.',
|
||||
subject: context.currentField || t('::ListForms.ScriptBuilder.NoFieldSelected'),
|
||||
subjectHint: t('::ListForms.ScriptBuilder.SubjectHint'),
|
||||
codeHint: t('::ListForms.ScriptBuilder.CodeHint'),
|
||||
groups: recipeGroups.map((group) => ({
|
||||
key: group.key,
|
||||
title: group.title,
|
||||
description: group.description,
|
||||
title: t('::' + group.title),
|
||||
description: t('::' + group.description),
|
||||
icon: groupIcons[group.key],
|
||||
})),
|
||||
recipes: recipeViews,
|
||||
recipes: recipeViews.map((recipe) => ({
|
||||
...recipe,
|
||||
label: t('::' + recipe.label),
|
||||
summary: t('::' + recipe.summary),
|
||||
params: recipe.params.map((param) => ({
|
||||
...param,
|
||||
label: t('::' + param.label),
|
||||
help: param.help ? t('::' + param.help) : undefined,
|
||||
placeholder: param.placeholder?.startsWith('ListForms.')
|
||||
? t('::' + param.placeholder)
|
||||
: param.placeholder,
|
||||
})),
|
||||
})),
|
||||
operators: DEFAULT_SCRIPT_OPERATORS,
|
||||
conditionKinds: [{ value: FIELD_SOURCE, label: 'Form alanı', optionSource: FIELD_SOURCE }],
|
||||
triggers: triggerLabels,
|
||||
apiReference,
|
||||
snippets,
|
||||
conditionKinds: [
|
||||
{
|
||||
value: FIELD_SOURCE,
|
||||
label: t('::ListForms.ScriptBuilder.FormField'),
|
||||
optionSource: FIELD_SOURCE,
|
||||
},
|
||||
],
|
||||
triggers: triggerLabels.map((trigger) => ({
|
||||
...trigger,
|
||||
label: t('::' + trigger.label),
|
||||
help: t('::' + trigger.help),
|
||||
})),
|
||||
apiReference: apiReference.map((entry) => ({
|
||||
...entry,
|
||||
description: t('::' + entry.description),
|
||||
})),
|
||||
snippets: snippets.map((snippet) => ({
|
||||
...snippet,
|
||||
label: t('::' + snippet.label),
|
||||
group: t('::' + snippet.group),
|
||||
description: t('::' + snippet.description),
|
||||
})),
|
||||
getOptions: (optionSource) => (optionSource === FIELD_SOURCE ? fields : []),
|
||||
getRecipe: (recipeId) => recipeViews.find((recipe) => recipe.id === recipeId),
|
||||
createRule: (recipeId) => createRule(recipeId as RecipeId) as unknown as ScriptRule,
|
||||
|
|
@ -184,18 +216,23 @@ export const createFormScriptDialect = (context: FormScriptDialectContext): Scri
|
|||
return { ...parsed, rules: parsed.rules as unknown as ScriptRule[] }
|
||||
},
|
||||
describeCondition: (condition) =>
|
||||
describeCondition(condition as unknown as FormScriptRule['condition']),
|
||||
describeCondition(condition as unknown as FormScriptRule['condition'], t),
|
||||
describeRule: (rule) => {
|
||||
const recipe = recipesById.get(rule.recipe as RecipeId)
|
||||
if (!recipe) return ''
|
||||
const action = recipe.describe(asFormRule(rule))
|
||||
const action = recipe.describe(asFormRule(rule), t)
|
||||
if (recipe.conditionIsArgument) return action
|
||||
const always = t('::ListForms.ScriptBuilder.Describe.Always')
|
||||
const conditions = [rule.condition, ...(rule.conditions ?? [])]
|
||||
.map((condition) => describeCondition(condition as unknown as FormScriptRule['condition']))
|
||||
.filter((text) => text !== 'Her zaman')
|
||||
const prefix = conditions.length
|
||||
? conditions.join(rule.join === 'or' ? ' veya ' : ' ve ')
|
||||
: 'Her zaman'
|
||||
.map((condition) =>
|
||||
describeCondition(condition as unknown as FormScriptRule['condition'], t),
|
||||
)
|
||||
.filter((text) => text !== always)
|
||||
const joiner =
|
||||
rule.join === 'or'
|
||||
? ` ${t('::ListForms.ScriptBuilder.Word.Or')} `
|
||||
: ` ${t('::ListForms.ScriptBuilder.Word.And')} `
|
||||
const prefix = conditions.length ? conditions.join(joiner) : always
|
||||
return `${prefix} → ${action}`
|
||||
},
|
||||
getTrigger: (rule) => effectiveTrigger(asFormRule(rule)),
|
||||
|
|
|
|||
|
|
@ -34,13 +34,17 @@ export type RuleCondition = {
|
|||
export type RuleTrigger = 'change' | 'open' | 'both'
|
||||
|
||||
export const triggerLabels: { value: RuleTrigger; label: string; help: string }[] = [
|
||||
{ value: 'change', label: 'Değiştiğinde', help: 'Yalnızca bu alanın değeri değişince çalışır.' },
|
||||
{
|
||||
value: 'change',
|
||||
label: 'ListForms.ScriptBuilder.Change.Label',
|
||||
help: 'ListForms.ScriptBuilder.Change.Help',
|
||||
},
|
||||
{
|
||||
value: 'open',
|
||||
label: 'Form açılırken',
|
||||
help: 'Popup edit form açıldığında bir kez çalışır. Varsayılan değer üretmek için.',
|
||||
label: 'ListForms.ScriptBuilder.Open.Label',
|
||||
help: 'ListForms.ScriptBuilder.Open.Help',
|
||||
},
|
||||
{ value: 'both', label: 'Her ikisi', help: 'Hem açılışta hem değer değişiminde çalışır.' },
|
||||
{ value: 'both', label: 'Her ikisi', help: 'ListForms.ScriptBuilder.Both.Help' },
|
||||
]
|
||||
|
||||
/** Ek koşullar nasıl birleşecek. */
|
||||
|
|
@ -93,7 +97,7 @@ export type Recipe = {
|
|||
conditionIsArgument?: boolean
|
||||
params: RecipeParam[]
|
||||
build: (rule: ScriptRule) => string
|
||||
describe: (rule: ScriptRule) => string
|
||||
describe: (rule: ScriptRule, t: TranslateFn) => string
|
||||
example: string
|
||||
}
|
||||
|
||||
|
|
@ -117,17 +121,31 @@ export type RecipeId =
|
|||
| 'custom'
|
||||
|
||||
export const recipeGroups: { key: RecipeGroupKey; title: string; description: string }[] = [
|
||||
{ key: 'calc', title: 'Hesaplama', description: 'Alanlardan yeni bir değer üretir.' },
|
||||
{ key: 'data', title: 'Veri', description: 'Alanlara değer yazar, kopyalar veya temizler.' },
|
||||
{ key: 'view', title: 'Görünüm', description: 'Alanların düzenlenebilirliğini yönetir.' },
|
||||
{ key: 'interaction', title: 'Etkileşim', description: 'Kullanıcıya mesaj gösterir, sayfa açar.' },
|
||||
{ key: 'integration', title: 'Entegrasyon', description: 'API ve servis çağrıları, serbest kod.' },
|
||||
{ key: 'calc', title: 'Hesaplama', description: 'ListForms.ScriptBuilder.Calc.Description' },
|
||||
{ key: 'data', title: 'Veri', description: 'ListForms.ScriptBuilder.Data.Description' },
|
||||
{
|
||||
key: 'view',
|
||||
title: 'ListForms.ScriptBuilder.View.Title',
|
||||
description: 'ListForms.ScriptBuilder.View.Description',
|
||||
},
|
||||
{
|
||||
key: 'interaction',
|
||||
title: 'ListForms.ScriptBuilder.Interaction.Title',
|
||||
description: 'ListForms.ScriptBuilder.Interaction.Description',
|
||||
},
|
||||
{
|
||||
key: 'integration',
|
||||
title: 'Entegrasyon',
|
||||
description: 'ListForms.ScriptBuilder.Integration.Description',
|
||||
},
|
||||
]
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Yardımcılar
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
export type TranslateFn = (key: string, params?: Record<string, string | number>) => string
|
||||
|
||||
const q = (input: string) => JSON.stringify(input ?? '')
|
||||
|
||||
const label = (input: string, fallback: string) => input || fallback
|
||||
|
|
@ -172,28 +190,31 @@ export const buildRuleConditionExpression = (rule: ScriptRule): string => {
|
|||
return parts.map((part) => `(${part})`).join(rule.join === 'or' ? ' || ' : ' && ')
|
||||
}
|
||||
|
||||
export const describeCondition = (condition: RuleCondition): string => {
|
||||
if (condition.operator === 'always' || !condition.source) return 'Her zaman'
|
||||
const source = label(condition.source, 'alan')
|
||||
export const describeCondition = (condition: RuleCondition, t: TranslateFn): string => {
|
||||
if (condition.operator === 'always' || !condition.source) {
|
||||
return t('::ListForms.ScriptBuilder.Describe.Always')
|
||||
}
|
||||
const source = label(condition.source, t('::ListForms.ScriptBuilder.Word.Field'))
|
||||
const value = label(condition.value, t('::ListForms.ScriptBuilder.Word.Value'))
|
||||
switch (condition.operator) {
|
||||
case 'empty':
|
||||
return `${source} boşsa`
|
||||
return t('::ListForms.ScriptBuilder.Describe.Empty', { source })
|
||||
case 'notEmpty':
|
||||
return `${source} doluysa`
|
||||
return t('::ListForms.ScriptBuilder.Describe.NotEmpty', { source })
|
||||
case 'isTrue':
|
||||
return `${source} işaretliyse`
|
||||
return t('::ListForms.ScriptBuilder.Describe.IsTrue', { source })
|
||||
case 'isFalse':
|
||||
return `${source} işaretli değilse`
|
||||
return t('::ListForms.ScriptBuilder.Describe.IsFalse', { source })
|
||||
case 'contains':
|
||||
return `${source} içinde "${label(condition.value, 'değer')}" geçiyorsa`
|
||||
return t('::ListForms.ScriptBuilder.Describe.Contains', { source, value })
|
||||
case 'greaterThan':
|
||||
return `${source} > ${label(condition.value, 'değer')} ise`
|
||||
return t('::ListForms.ScriptBuilder.Describe.GreaterThan', { source, value })
|
||||
case 'lessThan':
|
||||
return `${source} < ${label(condition.value, 'değer')} ise`
|
||||
return t('::ListForms.ScriptBuilder.Describe.LessThan', { source, value })
|
||||
case 'notEquals':
|
||||
return `${source} ≠ "${label(condition.value, 'değer')}" ise`
|
||||
return t('::ListForms.ScriptBuilder.Describe.NotEquals', { source, value })
|
||||
default:
|
||||
return `${source} = "${label(condition.value, 'değer')}" ise`
|
||||
return t('::ListForms.ScriptBuilder.Describe.Equals', { source, value })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -206,81 +227,95 @@ const numberOrField = (raw: string) => (isNumericLiteral(raw) ? raw : `num(${q(r
|
|||
export const recipes: Recipe[] = [
|
||||
{
|
||||
id: 'multiply',
|
||||
label: 'Çarpım (miktar × fiyat)',
|
||||
label: 'ListForms.ScriptBuilder.Multiply.Label',
|
||||
group: 'calc',
|
||||
summary: 'İki alanı çarpar, yuvarlar ve hedefe yazar.',
|
||||
summary: 'ListForms.ScriptBuilder.Multiply.Summary',
|
||||
example: "set('TotalAmount', round(num('Quantity') * num('UnitPrice'), 2))",
|
||||
params: [
|
||||
{ key: 'left', label: 'Birinci alan', type: 'field' },
|
||||
{ key: 'right', label: 'İkinci alan', type: 'field' },
|
||||
{ key: 'target', label: 'Sonuç alanı', type: 'field' },
|
||||
{ key: 'right', label: 'ListForms.ScriptBuilder.Right.Label', type: 'field' },
|
||||
{ key: 'target', label: 'ListForms.ScriptBuilder.Target.Label', type: 'field' },
|
||||
{
|
||||
key: 'digits',
|
||||
label: 'Ondalık',
|
||||
label: 'ListForms.ScriptBuilder.Digits.Label',
|
||||
type: 'number',
|
||||
optional: true,
|
||||
placeholder: '2',
|
||||
help: 'Boş bırakılırsa 2 kullanılır.',
|
||||
help: 'ListForms.ScriptBuilder.Digits.Help',
|
||||
},
|
||||
],
|
||||
build: ({ params }) =>
|
||||
`set(${q(params.target)}, round(${numberOrField(params.left)} * ${numberOrField(
|
||||
params.right,
|
||||
)}, ${params.digits || 2}))`,
|
||||
describe: ({ params }) =>
|
||||
`${label(params.left, 'alan')} × ${label(params.right, 'alan')} sonucunu ${label(
|
||||
params.target,
|
||||
'hedef',
|
||||
)} alanına yaz.`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Multiply', {
|
||||
left: label(params.left, t('::ListForms.ScriptBuilder.Word.Field')),
|
||||
right: label(params.right, t('::ListForms.ScriptBuilder.Word.Field')),
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Target')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'subtract',
|
||||
label: 'Fark (brüt − indirim)',
|
||||
label: 'ListForms.ScriptBuilder.Subtract.Label',
|
||||
group: 'calc',
|
||||
summary: 'Birinci alandan ikinciyi çıkarır, yuvarlar ve hedefe yazar.',
|
||||
summary: 'ListForms.ScriptBuilder.Subtract.Summary',
|
||||
example: "set('Net', round(num('Gross') - num('Discount'), 2))",
|
||||
params: [
|
||||
{ key: 'left', label: 'Çıkarılan alan', type: 'field' },
|
||||
{ key: 'right', label: 'Çıkan alan', type: 'field' },
|
||||
{ key: 'target', label: 'Sonuç alanı', type: 'field' },
|
||||
{ key: 'digits', label: 'Ondalık', type: 'number', optional: true, placeholder: '2' },
|
||||
{ key: 'left', label: 'ListForms.ScriptBuilder.Left.Label', type: 'field' },
|
||||
{ key: 'right', label: 'ListForms.ScriptBuilder.Right.Label2', type: 'field' },
|
||||
{ key: 'target', label: 'ListForms.ScriptBuilder.Target.Label2', type: 'field' },
|
||||
{
|
||||
key: 'digits',
|
||||
label: 'ListForms.ScriptBuilder.Digits.Label',
|
||||
type: 'number',
|
||||
optional: true,
|
||||
placeholder: '2',
|
||||
},
|
||||
],
|
||||
build: ({ params }) =>
|
||||
`set(${q(params.target)}, round(${numberOrField(params.left)} - ${numberOrField(
|
||||
params.right,
|
||||
)}, ${params.digits || 2}))`,
|
||||
describe: ({ params }) =>
|
||||
`${label(params.left, 'alan')} − ${label(params.right, 'alan')} sonucunu ${label(
|
||||
params.target,
|
||||
'hedef',
|
||||
)} alanına yaz.`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Subtract', {
|
||||
left: label(params.left, t('::ListForms.ScriptBuilder.Word.Field')),
|
||||
right: label(params.right, t('::ListForms.ScriptBuilder.Word.Field')),
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Target')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'percent',
|
||||
label: 'Yüzde (KDV, iskonto)',
|
||||
label: 'ListForms.ScriptBuilder.Percent.Label',
|
||||
group: 'calc',
|
||||
summary: 'Tutarın belirtilen yüzdesini hesaplar; oran alan ya da sabit olabilir.',
|
||||
summary: 'ListForms.ScriptBuilder.Percent.Summary',
|
||||
example: "set('Kdv', round(num('Tutar') * num('KdvOrani') / 100, 2))",
|
||||
params: [
|
||||
{ key: 'amount', label: 'Tutar alanı', type: 'field' },
|
||||
{ key: 'amount', label: 'ListForms.ScriptBuilder.Amount.Label', type: 'field' },
|
||||
{
|
||||
key: 'rate',
|
||||
label: 'Oran',
|
||||
type: 'text',
|
||||
placeholder: '20 veya KdvOrani',
|
||||
help: 'Sayı yazarsan sabit oran, alan adı yazarsan o alandan okunur.',
|
||||
help: 'ListForms.ScriptBuilder.Rate.Help',
|
||||
},
|
||||
{ key: 'target', label: 'ListForms.ScriptBuilder.Target.Label3', type: 'field' },
|
||||
{
|
||||
key: 'digits',
|
||||
label: 'ListForms.ScriptBuilder.Digits.Label2',
|
||||
type: 'number',
|
||||
optional: true,
|
||||
placeholder: '2',
|
||||
},
|
||||
{ key: 'target', label: 'Sonuç alanı', type: 'field' },
|
||||
{ key: 'digits', label: 'Ondalık', type: 'number', optional: true, placeholder: '2' },
|
||||
{
|
||||
key: 'mode',
|
||||
label: 'Sonuç',
|
||||
label: 'ListForms.ScriptBuilder.Mode.Label',
|
||||
type: 'select',
|
||||
optional: true,
|
||||
choices: [
|
||||
{ value: '', label: 'Yalnızca yüzde tutarı' },
|
||||
{ value: 'add', label: 'Tutar + yüzde' },
|
||||
{ value: 'subtract', label: 'Tutar − yüzde' },
|
||||
{ value: '', label: 'ListForms.ScriptBuilder.X.Label' },
|
||||
{ value: 'add', label: 'ListForms.ScriptBuilder.Add.Label' },
|
||||
{ value: 'subtract', label: 'ListForms.ScriptBuilder.Subtract.Label' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
@ -295,191 +330,215 @@ export const recipes: Recipe[] = [
|
|||
: portion
|
||||
return `set(${q(params.target)}, round(${expression}, ${params.digits || 2}))`
|
||||
},
|
||||
describe: ({ params }) =>
|
||||
`${label(params.amount, 'tutar')} üzerinden %${label(params.rate, 'oran')} hesabını ${label(
|
||||
params.target,
|
||||
'hedef',
|
||||
)} alanına yaz.`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Percent', {
|
||||
amount: label(params.amount, t('::ListForms.ScriptBuilder.Word.Amount')),
|
||||
rate: label(params.rate, t('::ListForms.ScriptBuilder.Word.Rate')),
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Target')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'today',
|
||||
label: 'Bugünün tarihi',
|
||||
label: 'ListForms.ScriptBuilder.Today.Label',
|
||||
group: 'calc',
|
||||
summary: 'Bugünü (veya kaç gün sonrasını) tarih alanına yazar.',
|
||||
summary: 'ListForms.ScriptBuilder.Today.Summary',
|
||||
example: "set('Tarih', new Date().toISOString().slice(0, 10))",
|
||||
params: [
|
||||
{ key: 'target', label: 'Hedef alan', type: 'field' },
|
||||
{ key: 'target', label: 'ListForms.ScriptBuilder.Target.Label2', type: 'field' },
|
||||
{
|
||||
key: 'offset',
|
||||
label: 'Gün ekle',
|
||||
label: 'ListForms.ScriptBuilder.Offset.Label',
|
||||
type: 'number',
|
||||
optional: true,
|
||||
placeholder: '0',
|
||||
help: 'Negatif yazarsan geçmiş bir tarih üretir.',
|
||||
help: 'ListForms.ScriptBuilder.Offset.Help',
|
||||
},
|
||||
],
|
||||
build: ({ params }) => {
|
||||
const offset = Number(params.offset) || 0
|
||||
const base = offset
|
||||
? `new Date(Date.now() + ${offset} * 86400000)`
|
||||
: 'new Date()'
|
||||
const base = offset ? `new Date(Date.now() + ${offset} * 86400000)` : 'new Date()'
|
||||
return `set(${q(params.target)}, ${base}.toISOString().slice(0, 10))`
|
||||
},
|
||||
describe: ({ params }) => {
|
||||
describe: ({ params }, t) => {
|
||||
const offset = Number(params.offset) || 0
|
||||
const when = offset > 0 ? `${offset} gün sonrasını` : offset < 0 ? `${-offset} gün öncesini` : 'bugünü'
|
||||
return `${when} ${label(params.target, 'hedef')} alanına yaz.`
|
||||
const when =
|
||||
offset > 0
|
||||
? t('::ListForms.ScriptBuilder.Word.DaysLater', { count: offset })
|
||||
: offset < 0
|
||||
? t('::ListForms.ScriptBuilder.Word.DaysEarlier', { count: -offset })
|
||||
: t('::ListForms.ScriptBuilder.Word.Today')
|
||||
return t('::ListForms.ScriptBuilder.Describe.Today', {
|
||||
when,
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Target')),
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'sum',
|
||||
label: 'Toplama',
|
||||
group: 'calc',
|
||||
summary: 'Seçilen alanları toplar ve hedefe yazar.',
|
||||
summary: 'ListForms.ScriptBuilder.Sum.Summary',
|
||||
example: "set('Total', round(sum('Net', 'Tax'), 2))",
|
||||
params: [
|
||||
{ key: 'target', label: 'Sonuç alanı', type: 'field' },
|
||||
{ key: 'sources', label: 'Toplanacak alanlar', type: 'fieldList' },
|
||||
{ key: 'digits', label: 'Ondalık', type: 'number', optional: true, placeholder: '2' },
|
||||
{ key: 'target', label: 'ListForms.ScriptBuilder.Target.Label4', type: 'field' },
|
||||
{ key: 'sources', label: 'ListForms.ScriptBuilder.Sources.Label3', type: 'fieldList' },
|
||||
{
|
||||
key: 'digits',
|
||||
label: 'ListForms.ScriptBuilder.Digits.Label3',
|
||||
type: 'number',
|
||||
optional: true,
|
||||
placeholder: '2',
|
||||
},
|
||||
],
|
||||
build: ({ params, fields }) =>
|
||||
`set(${q(params.target)}, round(sum(${(fields ?? []).map(q).join(', ')}), ${
|
||||
params.digits || 2
|
||||
}))`,
|
||||
describe: ({ params, fields }) =>
|
||||
`${(fields ?? []).join(' + ') || 'alanlar'} toplamını ${label(
|
||||
params.target,
|
||||
'hedef',
|
||||
)} alanına yaz.`,
|
||||
describe: ({ params, fields }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Sum', {
|
||||
sources: (fields ?? []).join(' + ') || t('::ListForms.ScriptBuilder.Word.Fields'),
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Target')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'formula',
|
||||
label: 'Serbest formül',
|
||||
label: 'ListForms.ScriptBuilder.Formula.Label',
|
||||
group: 'calc',
|
||||
summary: 'JavaScript ifadesi yazarsın; num/str/get/selected kullanabilirsin.',
|
||||
summary: 'ListForms.ScriptBuilder.Formula.Summary',
|
||||
example: "set('Net', num('Gross') - num('Discount'))",
|
||||
params: [
|
||||
{ key: 'target', label: 'Sonuç alanı', type: 'field' },
|
||||
{ key: 'target', label: 'ListForms.ScriptBuilder.Target.Label5', type: 'field' },
|
||||
{
|
||||
key: 'expression',
|
||||
label: 'İfade',
|
||||
label: 'ListForms.ScriptBuilder.Expression.Label',
|
||||
type: 'text',
|
||||
placeholder: "num('Gross') * 0.18",
|
||||
help: "Kullanılabilir: num('X'), str('X'), get('X'), value, selected('Yol'), round(x, 2)",
|
||||
help: 'ListForms.ScriptBuilder.Expression.Help',
|
||||
},
|
||||
],
|
||||
build: ({ params }) => `set(${q(params.target)}, ${params.expression || 'null'})`,
|
||||
describe: ({ params }) =>
|
||||
`${label(params.expression, 'ifade')} sonucunu ${label(params.target, 'hedef')} alanına yaz.`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Formula', {
|
||||
expression: label(params.expression, t('::ListForms.ScriptBuilder.Word.Expression')),
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Target')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'days',
|
||||
label: 'Gün farkı',
|
||||
label: 'ListForms.ScriptBuilder.Days.Label',
|
||||
group: 'calc',
|
||||
summary: 'İki tarih arasındaki gün sayısını (bitiş dahil) yazar.',
|
||||
summary: 'ListForms.ScriptBuilder.Days.Summary',
|
||||
example: "set('TotalDays', days('StartDate', 'EndDate'))",
|
||||
params: [
|
||||
{ key: 'start', label: 'Başlangıç tarihi', type: 'field' },
|
||||
{ key: 'end', label: 'Bitiş tarihi', type: 'field' },
|
||||
{ key: 'target', label: 'Sonuç alanı', type: 'field' },
|
||||
{ key: 'start', label: 'ListForms.ScriptBuilder.Start.Label', type: 'field' },
|
||||
{ key: 'end', label: 'ListForms.ScriptBuilder.End.Label', type: 'field' },
|
||||
{ key: 'target', label: 'ListForms.ScriptBuilder.Target.Label6', type: 'field' },
|
||||
],
|
||||
build: ({ params }) =>
|
||||
`set(${q(params.target)}, days(${q(params.start)}, ${q(params.end)}))`,
|
||||
describe: ({ params }) =>
|
||||
`${label(params.start, 'başlangıç')} → ${label(params.end, 'bitiş')} gün farkını ${label(
|
||||
params.target,
|
||||
'hedef',
|
||||
)} alanına yaz.`,
|
||||
build: ({ params }) => `set(${q(params.target)}, days(${q(params.start)}, ${q(params.end)}))`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Days', {
|
||||
start: label(params.start, t('::ListForms.ScriptBuilder.Word.Start')),
|
||||
end: label(params.end, t('::ListForms.ScriptBuilder.Word.End')),
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Target')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'hours',
|
||||
label: 'Saat farkı',
|
||||
label: 'ListForms.ScriptBuilder.Hours.Label',
|
||||
group: 'calc',
|
||||
summary: 'İki saat arasındaki farkı yazar; bitiş küçükse ertesi güne taşar.',
|
||||
summary: 'ListForms.ScriptBuilder.Hours.Summary',
|
||||
example: "set('TotalHours', hours('StartTime', 'EndTime'))",
|
||||
params: [
|
||||
{ key: 'start', label: 'Başlangıç saati', type: 'field' },
|
||||
{ key: 'end', label: 'Bitiş saati', type: 'field' },
|
||||
{ key: 'target', label: 'Sonuç alanı', type: 'field' },
|
||||
{ key: 'start', label: 'ListForms.ScriptBuilder.Start.Label2', type: 'field' },
|
||||
{ key: 'end', label: 'ListForms.ScriptBuilder.End.Label2', type: 'field' },
|
||||
{ key: 'target', label: 'ListForms.ScriptBuilder.Target.Label7', type: 'field' },
|
||||
],
|
||||
build: ({ params }) =>
|
||||
`set(${q(params.target)}, hours(${q(params.start)}, ${q(params.end)}))`,
|
||||
describe: ({ params }) =>
|
||||
`${label(params.start, 'başlangıç')} → ${label(params.end, 'bitiş')} saat farkını ${label(
|
||||
params.target,
|
||||
'hedef',
|
||||
)} alanına yaz.`,
|
||||
build: ({ params }) => `set(${q(params.target)}, hours(${q(params.start)}, ${q(params.end)}))`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Hours', {
|
||||
start: label(params.start, t('::ListForms.ScriptBuilder.Word.Start')),
|
||||
end: label(params.end, t('::ListForms.ScriptBuilder.Word.End')),
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Target')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'copy',
|
||||
label: 'Seçili kayıttan kopyala',
|
||||
label: 'ListForms.ScriptBuilder.Copy.Label',
|
||||
group: 'data',
|
||||
summary: 'Lookup/GridBox ile seçilen kaydın bir kolonunu form alanına taşır.',
|
||||
summary: 'ListForms.ScriptBuilder.Copy.Summary',
|
||||
example: "copy('unitPrice', 'UnitPrice')",
|
||||
params: [
|
||||
{
|
||||
key: 'source',
|
||||
label: 'Seçili kayıttaki kolon/yol',
|
||||
label: 'ListForms.ScriptBuilder.Source.Label',
|
||||
type: 'text',
|
||||
placeholder: 'unitPrice veya Customer.Name',
|
||||
},
|
||||
{ key: 'target', label: 'Hedef alan', type: 'field' },
|
||||
{ key: 'target', label: 'ListForms.ScriptBuilder.Target.Label2', type: 'field' },
|
||||
],
|
||||
build: ({ params }) => `copy(${q(params.source)}, ${q(params.target)})`,
|
||||
describe: ({ params }) =>
|
||||
`Seçili kayıttaki ${label(params.source, 'kolon')} değerini ${label(
|
||||
params.target,
|
||||
'hedef',
|
||||
)} alanına kopyala.`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Copy', {
|
||||
source: label(params.source, t('::ListForms.ScriptBuilder.Word.Column')),
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Target')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'setValue',
|
||||
label: 'Değer yaz',
|
||||
label: 'ListForms.ScriptBuilder.SetValue.Label',
|
||||
group: 'data',
|
||||
summary: 'Sabit metin veya {Alan} / {value} / {selected.Yol} token’ları yazar.',
|
||||
summary: 'ListForms.ScriptBuilder.SetValue.Summary',
|
||||
example: "set('Aciklama', tpl('{Musteri} - {value}'))",
|
||||
params: [
|
||||
{ key: 'target', label: 'Hedef alan', type: 'field' },
|
||||
{ key: 'target', label: 'ListForms.ScriptBuilder.Target.Label2', type: 'field' },
|
||||
{
|
||||
key: 'text',
|
||||
label: 'Değer',
|
||||
label: 'ListForms.ScriptBuilder.Text.Label',
|
||||
type: 'text',
|
||||
placeholder: '{Musteri} - {value}',
|
||||
help: 'Token kullanabilirsin: {AlanAdi}, {value}, {selected.Name}',
|
||||
help: 'ListForms.ScriptBuilder.Text.Help',
|
||||
},
|
||||
],
|
||||
build: ({ params }) => `set(${q(params.target)}, tpl(${q(params.text)}))`,
|
||||
describe: ({ params }) =>
|
||||
`${label(params.target, 'hedef')} alanına "${label(params.text, 'değer')}" yaz.`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.SetValue', {
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Target')),
|
||||
text: label(params.text, t('::ListForms.ScriptBuilder.Word.Value')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'clear',
|
||||
label: 'Alanları temizle',
|
||||
label: 'ListForms.ScriptBuilder.Clear.Label',
|
||||
group: 'data',
|
||||
summary: 'Seçilen alanları null yapar.',
|
||||
summary: 'ListForms.ScriptBuilder.Clear.Summary',
|
||||
example: "clear('Il', 'Ilce')",
|
||||
params: [{ key: 'sources', label: 'Temizlenecek alanlar', type: 'fieldList' }],
|
||||
params: [
|
||||
{ key: 'sources', label: 'ListForms.ScriptBuilder.Sources.Label2', type: 'fieldList' },
|
||||
],
|
||||
build: ({ fields }) => `clear(${(fields ?? []).map(q).join(', ')})`,
|
||||
describe: ({ fields }) => `${(fields ?? []).join(', ') || 'alanları'} temizle.`,
|
||||
describe: ({ fields }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Clear', {
|
||||
sources: (fields ?? []).join(', ') || t('::ListForms.ScriptBuilder.Word.Fields'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'readOnly',
|
||||
label: 'Salt okunur yap',
|
||||
group: 'view',
|
||||
summary: 'Koşul sağlandığında alanı kilitler, sağlanmadığında açar.',
|
||||
summary: 'ListForms.ScriptBuilder.ReadOnly.Summary',
|
||||
conditionIsArgument: true,
|
||||
example: "readOnly('SatisSebebi', str('Durum') === 'Aktif')",
|
||||
params: [
|
||||
{ key: 'target', label: 'Kilitlenecek alan', type: 'field' },
|
||||
{
|
||||
key: 'invert',
|
||||
label: 'Tersine çevir',
|
||||
label: 'ListForms.ScriptBuilder.Invert.Label',
|
||||
type: 'select',
|
||||
optional: true,
|
||||
help: 'Evet seçilirse koşul sağlandığında alan açılır.',
|
||||
help: 'ListForms.ScriptBuilder.Invert.Help',
|
||||
choices: [
|
||||
{ value: '', label: 'Hayır' },
|
||||
{ value: 'true', label: 'Evet' },
|
||||
{ value: '', label: 'ListForms.ScriptBuilder.X.Label2' },
|
||||
{ value: 'true', label: 'ListForms.ScriptBuilder.Yes' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
@ -488,39 +547,62 @@ export const recipes: Recipe[] = [
|
|||
const flag = rule.params.invert === 'true' ? `!(${expression})` : expression
|
||||
return `readOnly(${q(rule.params.target)}, ${flag})`
|
||||
},
|
||||
describe: ({ params, condition }) =>
|
||||
`${describeCondition(condition)} ${label(params.target, 'alan')} alanını ${
|
||||
params.invert === 'true' ? 'düzenlenebilir' : 'salt okunur'
|
||||
} yap.`,
|
||||
describe: ({ params, condition }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.ReadOnly', {
|
||||
condition: describeCondition(condition, t),
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Field')),
|
||||
mode: t(
|
||||
params.invert === 'true'
|
||||
? '::ListForms.ScriptBuilder.Word.Editable'
|
||||
: '::ListForms.ScriptBuilder.Word.ReadOnly',
|
||||
),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'notify',
|
||||
label: 'Uyarı göster',
|
||||
label: 'ListForms.ScriptBuilder.Notify.Label',
|
||||
group: 'interaction',
|
||||
summary: 'Kullanıcıya bilgi mesajı gösterir.',
|
||||
summary: 'ListForms.ScriptBuilder.Notify.Summary',
|
||||
example: "notify('Limit aşıldı')",
|
||||
params: [
|
||||
{ key: 'message', label: 'Mesaj', type: 'text', placeholder: '{Musteri} limiti aşıldı' },
|
||||
{
|
||||
key: 'message',
|
||||
label: 'ListForms.ScriptBuilder.Message.Label',
|
||||
type: 'text',
|
||||
placeholder: 'ListForms.ScriptBuilder.Notify.MessagePlaceholder',
|
||||
},
|
||||
],
|
||||
build: ({ params }) => `notify(${q(params.message)})`,
|
||||
describe: ({ params }) => `"${label(params.message, 'mesaj')}" uyarısını göster.`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Notify', {
|
||||
message: label(params.message, t('::ListForms.ScriptBuilder.Word.Message')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'ask',
|
||||
label: 'Onay iste',
|
||||
label: 'ListForms.ScriptBuilder.Ask.Label',
|
||||
group: 'interaction',
|
||||
summary: 'Onay ister; kullanıcı vazgeçerse alan eski değerine döner.',
|
||||
summary: 'ListForms.ScriptBuilder.Ask.Summary',
|
||||
example: "if (!ask('Emin misiniz?')) return",
|
||||
params: [{ key: 'message', label: 'Mesaj', type: 'text', placeholder: 'Emin misiniz?' }],
|
||||
params: [
|
||||
{
|
||||
key: 'message',
|
||||
label: 'ListForms.ScriptBuilder.Message.Label',
|
||||
type: 'text',
|
||||
placeholder: 'ListForms.ScriptBuilder.Ask.MessagePlaceholder',
|
||||
},
|
||||
],
|
||||
build: ({ params }) => `if (!ask(${q(params.message)})) return`,
|
||||
describe: ({ params }) =>
|
||||
`"${label(params.message, 'mesaj')}" onayını iste; vazgeçilirse değeri geri al.`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Ask', {
|
||||
message: label(params.message, t('::ListForms.ScriptBuilder.Word.Message')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'openUrl',
|
||||
label: 'URL aç',
|
||||
label: 'ListForms.ScriptBuilder.OpenUrl.Label',
|
||||
group: 'interaction',
|
||||
summary: 'Yeni sekmede veya aynı sekmede adres açar.',
|
||||
summary: 'ListForms.ScriptBuilder.OpenUrl.Summary',
|
||||
example: "openUrl('/report?id={Id}')",
|
||||
params: [
|
||||
{ key: 'url', label: 'URL', type: 'text', placeholder: '/report?id={Id}' },
|
||||
|
|
@ -539,16 +621,19 @@ export const recipes: Recipe[] = [
|
|||
params.target && params.target !== '_blank'
|
||||
? `openUrl(${q(params.url)}, ${q(params.target)})`
|
||||
: `openUrl(${q(params.url)})`,
|
||||
describe: ({ params }) => `${label(params.url, 'adres')} adresini aç.`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.OpenUrl', {
|
||||
url: label(params.url, t('::ListForms.ScriptBuilder.Word.Address')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'apiToField',
|
||||
label: 'API sonucunu alana yaz',
|
||||
group: 'integration',
|
||||
summary: 'Adresi çağırır, JSON içinden okuduğu değeri alana yazar.',
|
||||
summary: 'ListForms.ScriptBuilder.ApiToField.Summary',
|
||||
example: "set('Unvan', await api('/api/musteri/{value}', 'data.name'))",
|
||||
params: [
|
||||
{ key: 'target', label: 'Hedef alan', type: 'field' },
|
||||
{ key: 'target', label: 'ListForms.ScriptBuilder.Target.Label2', type: 'field' },
|
||||
{ key: 'url', label: 'API adresi', type: 'text', placeholder: '/api/musteri/{value}' },
|
||||
{
|
||||
key: 'path',
|
||||
|
|
@ -556,24 +641,24 @@ export const recipes: Recipe[] = [
|
|||
type: 'text',
|
||||
optional: true,
|
||||
placeholder: 'data.name',
|
||||
help: 'Boş bırakılırsa tüm JSON yazılır.',
|
||||
help: 'ListForms.ScriptBuilder.Path.Help',
|
||||
},
|
||||
],
|
||||
build: ({ params }) =>
|
||||
`set(${q(params.target)}, await api(${q(params.url)}${
|
||||
params.path ? `, ${q(params.path)}` : ''
|
||||
}))`,
|
||||
describe: ({ params }) =>
|
||||
`${label(params.url, 'api')} çağrısının sonucunu ${label(
|
||||
params.target,
|
||||
'hedef',
|
||||
)} alanına yaz.`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.ApiToField', {
|
||||
url: label(params.url, 'api'),
|
||||
target: label(params.target, t('::ListForms.ScriptBuilder.Word.Target')),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'custom',
|
||||
label: 'Serbest tek satır',
|
||||
label: 'ListForms.ScriptBuilder.Custom.Label',
|
||||
group: 'integration',
|
||||
summary: 'Servis çağrısı veya kendi yazdığın tek satırlık kod.',
|
||||
summary: 'ListForms.ScriptBuilder.Custom.Summary',
|
||||
example: 'UiEvalService.ApiGenerateBackgroundWorkers()',
|
||||
params: [
|
||||
{
|
||||
|
|
@ -584,7 +669,10 @@ export const recipes: Recipe[] = [
|
|||
},
|
||||
],
|
||||
build: ({ params }) => (params.code || '').trim().replace(/;$/, ''),
|
||||
describe: ({ params }) => label(params.code, 'kod') + ' çalıştır.',
|
||||
describe: ({ params }, t) =>
|
||||
t('::ListForms.ScriptBuilder.Describe.Custom', {
|
||||
code: label(params.code, t('::ListForms.ScriptBuilder.Word.Code')),
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -741,25 +829,36 @@ export const createRule = (recipe: RecipeId): ScriptRule => ({
|
|||
|
||||
/** Cheatsheet olarak gösterilen runtime API özeti. */
|
||||
export const runtimeApiReference: { signature: string; description: string }[] = [
|
||||
{ signature: 'value / field', description: 'Değişen alanın değeri ve adı.' },
|
||||
{ signature: "get('Alan')", description: 'Form değerini okur (büyük/küçük harf duyarsız).' },
|
||||
{ signature: "num('Alan') / str('Alan') / bool('Alan')", description: 'Tip dönüşümlü okuma.' },
|
||||
{ signature: "dateOf('Alan')", description: 'Date nesnesi ya da null döner.' },
|
||||
{ signature: "set('Alan', deger)", description: 'Alana yazar. Nesne de verebilirsin.' },
|
||||
{ signature: "set({ A: 1, B: 2 })", description: 'Birden fazla alanı tek çağrıda yazar.' },
|
||||
{ signature: "clear('A', 'B')", description: 'Alanları null yapar.' },
|
||||
{ signature: "copy('kolon', 'Alan')", description: 'Seçili lookup kaydından kopyalar.' },
|
||||
{ signature: "selected('Yol') / snum() / sstr()", description: 'Seçili kaydı okur.' },
|
||||
{ signature: "readOnly('Alan', kosul)", description: 'Alanı kilitler veya açar.' },
|
||||
{ signature: "round(x, 2) / sum('A','B')", description: 'Yuvarlama ve toplama.' },
|
||||
{ signature: "days('Bas','Bit') / hours('Bas','Bit')", description: 'Tarih ve saat farkı.' },
|
||||
{ signature: "tpl('{Alan} - {value}')", description: 'Token doldurur.' },
|
||||
{ signature: "notify('mesaj') / ask('mesaj')", description: 'Uyarı ve onay. ask false ise geri alır.' },
|
||||
{ signature: "openUrl('/adres')", description: 'Yeni sekmede açar.' },
|
||||
{ signature: "await api('/api/x', 'data.name')", description: 'JSON çağrısı yapar.' },
|
||||
{ signature: 'value / field', description: 'ListForms.ScriptBuilder.Api.ValueField' },
|
||||
{ signature: "get('Alan')", description: 'ListForms.ScriptBuilder.Api.Get' },
|
||||
{
|
||||
signature: "num('Alan') / str('Alan') / bool('Alan')",
|
||||
description: 'ListForms.ScriptBuilder.Api.Cast',
|
||||
},
|
||||
{ signature: "dateOf('Alan')", description: 'ListForms.ScriptBuilder.Api.DateOf' },
|
||||
{ signature: "set('Alan', deger)", description: 'ListForms.ScriptBuilder.Api.Set' },
|
||||
{ signature: 'set({ A: 1, B: 2 })', description: 'ListForms.ScriptBuilder.Api.SetMany' },
|
||||
{ signature: "clear('A', 'B')", description: 'ListForms.ScriptBuilder.Api.Clear' },
|
||||
{ signature: "copy('kolon', 'Alan')", description: 'ListForms.ScriptBuilder.Api.Copy' },
|
||||
{
|
||||
signature: "selected('Yol') / snum() / sstr()",
|
||||
description: 'ListForms.ScriptBuilder.Api.Selected',
|
||||
},
|
||||
{ signature: "readOnly('Alan', kosul)", description: 'ListForms.ScriptBuilder.Api.ReadOnly' },
|
||||
{ signature: "round(x, 2) / sum('A','B')", description: 'ListForms.ScriptBuilder.Api.Math' },
|
||||
{
|
||||
signature: "days('Bas','Bit') / hours('Bas','Bit')",
|
||||
description: 'ListForms.ScriptBuilder.Api.DateDiff',
|
||||
},
|
||||
{ signature: "tpl('{Alan} - {value}')", description: 'ListForms.ScriptBuilder.Api.Tpl' },
|
||||
{
|
||||
signature: "notify('mesaj') / ask('mesaj')",
|
||||
description: 'ListForms.ScriptBuilder.Api.NotifyAsk',
|
||||
},
|
||||
{ signature: "openUrl('/adres')", description: 'ListForms.ScriptBuilder.Api.OpenUrl' },
|
||||
{ signature: "await api('/api/x', 'data.name')", description: 'ListForms.ScriptBuilder.Api.Api' },
|
||||
{
|
||||
signature: 'isReady',
|
||||
description:
|
||||
'Form açılışında true, değer değişiminde false. Script açılışta da çalışsın istiyorsan bu ifadeyi kullan.',
|
||||
description: 'ListForms.ScriptBuilder.Api.IsReady',
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -618,7 +618,7 @@ const Wizard = () => {
|
|||
} catch {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
{translate('::App.Listforms.WizardFileLoadError') || 'Failed to load wizard file.'}
|
||||
{translate('::App.Listforms.WizardFileLoadError')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -789,7 +789,7 @@ const Wizard = () => {
|
|||
|
||||
const handleDeploy = async () => {
|
||||
const formik = formikRef.current
|
||||
if (!formik) throw new Error(translate('::App.Platform.Unknown') || 'Form bulunamadı')
|
||||
if (!formik) throw new Error(translate('::App.Platform.Unknown'))
|
||||
|
||||
const values = formik.values
|
||||
|
||||
|
|
@ -861,11 +861,13 @@ const Wizard = () => {
|
|||
|
||||
return (
|
||||
<Container>
|
||||
<PageTitle title={translate('::' + (isEditMode ? 'App.Listforms.WizardEdit' : 'App.Listforms.Wizard'))} />
|
||||
<PageTitle
|
||||
title={translate('::' + (isEditMode ? 'App.Listforms.WizardEdit' : 'App.Listforms.Wizard'))}
|
||||
/>
|
||||
|
||||
{isLoadingEditData && (
|
||||
<p className="text-xs text-gray-400 text-center py-4 animate-pulse">
|
||||
{translate('::App.Loading') || 'Loading...'}
|
||||
{translate('::App.Loading')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
|
@ -879,32 +881,32 @@ const Wizard = () => {
|
|||
>
|
||||
{({ touched, errors, values }) => {
|
||||
const activeSteps = [
|
||||
{ id: 0, title: translate('::ListForms.Wizard.MenuInfo') || 'Menu Info' },
|
||||
{ id: 0, title: translate('::ListForms.Wizard.MenuInfo') },
|
||||
{
|
||||
id: 1,
|
||||
title: translate('::ListForms.Wizard.ListFormSettings') || 'List Form Settings',
|
||||
title: translate('::ListForms.Wizard.ListFormSettings'),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: translate('::ListForms.Wizard.ListFormFields') || 'List Form Fields',
|
||||
title: translate('::ListForms.Wizard.ListFormFields'),
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms',
|
||||
title: translate('::ListForms.ListFormEdit.SubForms'),
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets',
|
||||
title: translate('::ListForms.ListFormEdit.TabWidgets'),
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow',
|
||||
title: translate('::ListForms.ListFormEdit.TabWorkflow'),
|
||||
},
|
||||
...OPTIONAL_STEPS.filter((step) => values[step.flag]).map((step) => ({
|
||||
id: step.id,
|
||||
title: translate(step.translationKey) || step.fallbackTitle,
|
||||
})),
|
||||
{ id: 10, title: translate('::App.Platform.Deploy') || 'Deploy' },
|
||||
{ id: 10, title: translate('::App.Platform.Deploy') },
|
||||
]
|
||||
|
||||
// Opsiyonel bir adım (todo/tree/gantt/scheduler) sonradan kapatılırsa
|
||||
|
|
@ -931,7 +933,7 @@ const Wizard = () => {
|
|||
<Steps.Item
|
||||
customIcon={<FaFolderOpen />}
|
||||
status="pending"
|
||||
title={translate('::App.Listforms.WizardManager') || 'Wizard Manager'}
|
||||
title={translate('::App.Listforms.WizardManager')}
|
||||
onStepChange={() =>
|
||||
navigate(ROUTES_ENUM.protected.saas.listFormManagement.wizardManager)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const WizardFileManager = () => {
|
|||
} catch {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
{translate('::App.Listforms.WizardFileLoadError') || 'Failed to load wizard files.'}
|
||||
{translate('::App.Listforms.WizardFileLoadError')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -79,8 +79,7 @@ const WizardFileManager = () => {
|
|||
toast.push(
|
||||
<Notification type="success" duration={3000}>
|
||||
<strong>{confirm.wizardName}</strong>{' '}
|
||||
{translate('::App.Listforms.WizardFileDeleteSuccess') ||
|
||||
'wizard file deleted successfully.'}
|
||||
{translate('::App.Listforms.WizardFileDeleteSuccess')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -88,8 +87,7 @@ const WizardFileManager = () => {
|
|||
} catch (err: any) {
|
||||
toast.push(
|
||||
<Notification type="danger">
|
||||
{translate('::App.Listforms.WizardFileDeleteError') || 'Failed to delete wizard file.'}:{' '}
|
||||
{err?.message ?? 'Unknown error'}
|
||||
{translate('::App.Listforms.WizardFileDeleteError')}: {err?.message ?? 'Unknown error'}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -110,7 +108,7 @@ const WizardFileManager = () => {
|
|||
<div className="flex items-center gap-2 min-w-0">
|
||||
{MenuIcon}
|
||||
<h4 className="text-sm font-medium truncate">
|
||||
{translate('::App.Listforms.WizardManager') || 'Wizard Seed Dosyaları'}
|
||||
{translate('::App.Listforms.WizardManager')}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
|
|
@ -120,7 +118,7 @@ const WizardFileManager = () => {
|
|||
<Input
|
||||
size="sm"
|
||||
className="pl-6 w-36 sm:w-44"
|
||||
placeholder={translate('::App.Platform.Search') || 'Search...'}
|
||||
placeholder={translate('::App.Platform.Search')}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
|
|
@ -128,7 +126,7 @@ const WizardFileManager = () => {
|
|||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
title={translate('::App.Platform.Refresh') || 'Yenile'}
|
||||
title={translate('::App.Platform.Refresh')}
|
||||
loading={loading}
|
||||
onClick={loadFiles}
|
||||
>
|
||||
|
|
@ -142,7 +140,7 @@ const WizardFileManager = () => {
|
|||
className="flex items-center"
|
||||
>
|
||||
<FaPlus className="mr-1" />
|
||||
<span className="hidden sm:inline">{translate('::ListForms.Wizard.AddNewRecord') || 'Add New Record'}</span>
|
||||
<span className="hidden sm:inline">{translate('::ListForms.Wizard.AddNewRecord')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -152,7 +150,7 @@ const WizardFileManager = () => {
|
|||
<div className="text-center py-12">
|
||||
<FaCalendarAlt size={48} className="mx-auto text-gray-400 mb-4" />
|
||||
<p className="text-sm text-gray-400 text-center py-4">
|
||||
{translate('::App.Listforms.WizardNoFiles') || 'No wizard files found.'}
|
||||
{translate('::App.Listforms.WizardNoFiles')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -188,10 +186,7 @@ const WizardFileManager = () => {
|
|||
<div className="flex items-center gap-2 shrink-0 sm:ml-3">
|
||||
{!f.hasInsertedRecords && (
|
||||
<span
|
||||
title={
|
||||
translate('::App.Listforms.WizardFileNoTrackedRecords') ||
|
||||
'Bu dosyada izlenen kayıt bilgisi yok. Eski format olabilir.'
|
||||
}
|
||||
title={translate('::App.Listforms.WizardFileNoTrackedRecords')}
|
||||
className="text-yellow-500 text-xs flex items-center gap-1"
|
||||
>
|
||||
<FaExclamationTriangle />
|
||||
|
|
@ -202,7 +197,7 @@ const WizardFileManager = () => {
|
|||
variant="plain"
|
||||
className="text-indigo-500 hover:bg-indigo-50 dark:hover:bg-indigo-900/20"
|
||||
type="button"
|
||||
title={translate('::App.Platform.Edit') || 'Edit'}
|
||||
title={translate('::App.Platform.Edit')}
|
||||
onClick={() =>
|
||||
navigate(ROUTES_ENUM.protected.saas.listFormManagement.wizard, {
|
||||
state: { editFileName: f.fileName },
|
||||
|
|
@ -216,7 +211,7 @@ const WizardFileManager = () => {
|
|||
variant="plain"
|
||||
className="text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||
type="button"
|
||||
title={translate('::Delete') || 'Delete'}
|
||||
title={translate('::Delete')}
|
||||
loading={deletingFile === f.fileName}
|
||||
onClick={() =>
|
||||
setConfirm({ fileName: f.fileName, wizardName: f.wizardName || f.fileName })
|
||||
|
|
@ -256,7 +251,7 @@ const WizardFileManager = () => {
|
|||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" type="button" onClick={handleDeleteConfirm}>
|
||||
{translate('::Delete') || 'Yes, Delete'}
|
||||
{translate('::Delete')}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ function MenuTreeInline({
|
|||
if (!node.id) return
|
||||
if (
|
||||
!window.confirm(
|
||||
`"${node.displayName}" ${translate('::ListForms.Wizard.Step1.DeleteMenuConfirm') || 'menüsünü silmek istediğinize emin misiniz?'}`,
|
||||
`"${node.displayName}" ${translate('::ListForms.Wizard.Step1.DeleteMenuConfirm')}`,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
|
@ -422,7 +422,7 @@ function MenuTreeInline({
|
|||
<div className="px-4 py-3 text-sm text-gray-400">Loading…</div>
|
||||
) : enrichedNodes.length === 0 ? (
|
||||
<div className="px-4 py-3 text-sm text-gray-400">
|
||||
{translate('::ListForms.Wizard.Step1.NoMenusAvailable') || 'No menus available'}
|
||||
{translate('::ListForms.Wizard.Step1.NoMenusAvailable')}
|
||||
</div>
|
||||
) : (
|
||||
enrichedNodes.map((node) => (
|
||||
|
|
@ -504,8 +504,7 @@ const WizardStep1 = ({
|
|||
!values.menuCode && translate('::ListForms.Wizard.Step1.MenuCode'),
|
||||
// Yup şeması menuOrder >= 1 istiyor; başlangıç değeri 0 olduğu için
|
||||
// burada kontrol edilmezse "Next" aktif görünüp sessizce çalışmıyordu.
|
||||
!(Number(values.menuOrder) >= 1) &&
|
||||
(translate('::ListForms.Wizard.Step1.MenuOrder') || 'Menu Order'),
|
||||
!(Number(values.menuOrder) >= 1) && translate('::ListForms.Wizard.Step1.MenuOrder'),
|
||||
!values.permissionGroupName && translate('::ListForms.Wizard.Step1.PermissionGroupName'),
|
||||
!values.languageTextMenuEn && translate('::ListForms.Wizard.Step4.MenuEn'),
|
||||
!values.languageTextMenuTr && translate('::ListForms.Wizard.Step4.MenuTr'),
|
||||
|
|
@ -521,8 +520,7 @@ const WizardStep1 = ({
|
|||
asterisk={true}
|
||||
extra={
|
||||
<span className="text-xs ml-2 text-gray-400">
|
||||
{translate('::ListForms.Wizard.Step1.WizardNameHint') ||
|
||||
'Used to generate ListForm Code and Menu Code'}
|
||||
{translate('::ListForms.Wizard.Step1.WizardNameHint')}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
|
|
@ -561,9 +559,7 @@ const WizardStep1 = ({
|
|||
variant="solid"
|
||||
icon={<FaPlus className="text-xs" />}
|
||||
>
|
||||
<span className="whitespace-nowrap">
|
||||
{translate('::ListForms.Wizard.Add') || 'Ekle'}
|
||||
</span>
|
||||
<span className="whitespace-nowrap">{translate('::ListForms.Wizard.Add')}</span>
|
||||
</Button>
|
||||
{values.menuParentCode && (
|
||||
<Button
|
||||
|
|
@ -578,7 +574,7 @@ const WizardStep1 = ({
|
|||
icon={<FaTimes className="text-xs" />}
|
||||
className="!inline-flex !h-auto !items-center !justify-center gap-1 !rounded !px-2 !py-0.5 text-xs text-gray-500 whitespace-nowrap hover:border-red-400 hover:text-red-500 dark:border-gray-600"
|
||||
>
|
||||
{translate('::ListForms.Wizard.ClearSelection') || 'Seçimi Kaldır'}
|
||||
{translate('::ListForms.Wizard.ClearSelection')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -624,7 +620,7 @@ const WizardStep1 = ({
|
|||
asterisk={true}
|
||||
extra={
|
||||
<span className="text-xs ml-2 text-gray-400">
|
||||
{translate('::ListForms.Wizard.Step1.MenuCodeHint') || 'Auto-derived, editable'}
|
||||
{translate('::ListForms.Wizard.Step1.MenuCodeHint')}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
|
|
@ -640,14 +636,13 @@ const WizardStep1 = ({
|
|||
|
||||
{/* Menu Order */}
|
||||
<FormItem
|
||||
label={translate('::ListForms.Wizard.Step1.MenuOrder') || 'Menu Order'}
|
||||
label={translate('::ListForms.Wizard.Step1.MenuOrder')}
|
||||
asterisk={true}
|
||||
invalid={!!(errors.menuOrder && touched.menuOrder)}
|
||||
errorMessage={errors.menuOrder}
|
||||
extra={
|
||||
<span className="text-xs ml-2 text-gray-400">
|
||||
{translate('::ListForms.Wizard.Step1.MenuOrderHint') ||
|
||||
'Automatically assigned, editable'}
|
||||
{translate('::ListForms.Wizard.Step1.MenuOrderHint')}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
|
|
@ -690,9 +685,7 @@ const WizardStep1 = ({
|
|||
type="text"
|
||||
autoComplete="off"
|
||||
name="languageTextMenuEn"
|
||||
placeholder={
|
||||
translate('::ListForms.Wizard.Step1.DisplayNameEnglish') || 'English Menu Text'
|
||||
}
|
||||
placeholder={translate('::ListForms.Wizard.Step1.DisplayNameEnglish')}
|
||||
component={Input}
|
||||
/>
|
||||
</FormItem>
|
||||
|
|
@ -708,9 +701,7 @@ const WizardStep1 = ({
|
|||
type="text"
|
||||
autoComplete="off"
|
||||
name="languageTextMenuTr"
|
||||
placeholder={
|
||||
translate('::ListForms.Wizard.Step1.DisplayNameTurkish') || 'Turkish Menu Text'
|
||||
}
|
||||
placeholder={translate('::ListForms.Wizard.Step1.DisplayNameTurkish')}
|
||||
component={Input}
|
||||
/>
|
||||
</FormItem>
|
||||
|
|
@ -756,7 +747,7 @@ const WizardStep1 = ({
|
|||
<div className="flex-1 flex items-center gap-2">
|
||||
{!step1CanGo && (
|
||||
<span className="text-xs text-amber-600 dark:text-amber-400 font-medium">
|
||||
⚠ {translate('::ListForms.Wizard.Required') || 'Zorunlu'}: {step1Missing.join(', ')}
|
||||
⚠ {translate('::ListForms.Wizard.Required')}: {step1Missing.join(', ')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -767,7 +758,7 @@ const WizardStep1 = ({
|
|||
disabled={!step1CanGo}
|
||||
onClick={onNext}
|
||||
>
|
||||
{translate('::Next') || 'Next'}
|
||||
{translate('::Next')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -188,10 +188,7 @@ const WizardStep11 = ({
|
|||
const [isDone, setIsDone] = useState(false)
|
||||
const [hasError, setHasError] = useState(false)
|
||||
|
||||
const steps = useMemo(
|
||||
() => buildLogSteps(values, groups, translate),
|
||||
[values, groups, translate],
|
||||
)
|
||||
const steps = useMemo(() => buildLogSteps(values, groups, translate), [values, groups, translate])
|
||||
|
||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
||||
|
||||
|
|
@ -344,10 +341,8 @@ const WizardStep11 = ({
|
|||
</div>
|
||||
|
||||
<Section
|
||||
title={
|
||||
translate('::ListForms.Wizard.Step4.ColumnsAndFormLayout') || 'Columns & Form Layout'
|
||||
}
|
||||
badge={`${selectedColumns.size} ${translate('::App.Listform.ListformField.Column')} / ${editingFormFields.length} ${translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}`}
|
||||
title={translate('::ListForms.Wizard.Step4.ColumnsAndFormLayout')}
|
||||
badge={`${selectedColumns.size} ${translate('::App.Listform.ListformField.Column')} / ${editingFormFields.length} ${translate('::ListForms.Wizard.Step4.EditingForm')}`}
|
||||
>
|
||||
<div className="mb-3 grid grid-cols-3 gap-2">
|
||||
{[
|
||||
|
|
@ -357,7 +352,7 @@ const WizardStep11 = ({
|
|||
className: 'text-indigo-600 dark:text-indigo-400',
|
||||
},
|
||||
{
|
||||
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
|
||||
label: translate('::ListForms.Wizard.Step4.EditingForm'),
|
||||
value: editingFormFields.length,
|
||||
className: 'text-emerald-600 dark:text-emerald-400',
|
||||
},
|
||||
|
|
@ -401,7 +396,7 @@ const WizardStep11 = ({
|
|||
<div className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
{g.items.length === 0 ? (
|
||||
<span className="block px-3 py-2 text-xs text-gray-300 italic">
|
||||
{translate('::ListForms.Wizard.Step4.NoFields') || 'Alan yok'}
|
||||
{translate('::ListForms.Wizard.Step4.NoFields')}
|
||||
</span>
|
||||
) : (
|
||||
g.items.map((item) => {
|
||||
|
|
@ -425,11 +420,11 @@ const WizardStep11 = ({
|
|||
</div>
|
||||
<div className="flex flex-wrap justify-end gap-1">
|
||||
<span className="rounded bg-indigo-50 dark:bg-indigo-900/30 px-1.5 py-0.5 text-[10px] text-indigo-600 dark:text-indigo-300">
|
||||
{translate('::ListForms.Wizard.Step4.SelectedColumns') || 'List'}
|
||||
{translate('::ListForms.Wizard.Step4.SelectedColumns')}
|
||||
</span>
|
||||
{isPopupField && (
|
||||
<span className="rounded bg-emerald-50 dark:bg-emerald-900/30 px-1.5 py-0.5 text-[10px] text-emerald-600 dark:text-emerald-300">
|
||||
{translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}
|
||||
{translate('::ListForms.Wizard.Step4.EditingForm')}
|
||||
</span>
|
||||
)}
|
||||
{isKeyField && (
|
||||
|
|
@ -455,7 +450,7 @@ const WizardStep11 = ({
|
|||
{ungroupedSelectedColumns.length > 0 && (
|
||||
<div className="rounded-lg border border-amber-100 dark:border-amber-900/40 bg-amber-50/50 dark:bg-amber-900/10 px-3 py-2">
|
||||
<div className="mb-1.5 text-xs font-semibold text-amber-700 dark:text-amber-300">
|
||||
{translate('::ListForms.Wizard.Step4.UngroupedColumns') || 'Ungrouped columns'}
|
||||
{translate('::ListForms.Wizard.Step4.UngroupedColumns')}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{ungroupedSelectedColumns.map((col) => {
|
||||
|
|
@ -481,10 +476,7 @@ const WizardStep11 = ({
|
|||
</Section>
|
||||
|
||||
{subForms.length > 0 && (
|
||||
<Section
|
||||
title={translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms'}
|
||||
badge={subForms.length}
|
||||
>
|
||||
<Section title={translate('::ListForms.ListFormEdit.SubForms')} badge={subForms.length}>
|
||||
<div className="flex flex-col gap-3">
|
||||
{subForms.map((subForm, index) => (
|
||||
<div
|
||||
|
|
@ -517,10 +509,7 @@ const WizardStep11 = ({
|
|||
)}
|
||||
|
||||
{widgets.length > 0 && (
|
||||
<Section
|
||||
title={translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets'}
|
||||
badge={widgets.length}
|
||||
>
|
||||
<Section title={translate('::ListForms.ListFormEdit.TabWidgets')} badge={widgets.length}>
|
||||
<div className="flex flex-col gap-3">
|
||||
{widgets.map((widget, index) => (
|
||||
<div
|
||||
|
|
@ -545,7 +534,7 @@ const WizardStep11 = ({
|
|||
|
||||
{(hasWorkflowFields || workflowItems.length > 0) && (
|
||||
<Section
|
||||
title={translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow'}
|
||||
title={translate('::ListForms.ListFormEdit.TabWorkflow')}
|
||||
badge={workflowItems.length}
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
|
|
@ -594,7 +583,7 @@ const WizardStep11 = ({
|
|||
)}
|
||||
|
||||
{values.todo && (
|
||||
<Section title={translate('::ListForms.ListFormEdit.TabTodo') || 'Todo / Kanban'}>
|
||||
<Section title={translate('::ListForms.ListFormEdit.TabTodo')}>
|
||||
<div className="grid grid-cols-2 gap-x-4">
|
||||
<Row
|
||||
label={translate('::ListForms.ListFormFieldEdit.TodoTitleExpr')}
|
||||
|
|
@ -657,7 +646,7 @@ const WizardStep11 = ({
|
|||
{ label: translate('::ListForms.Wizard.Step4.StatGroup'), value: groups.length },
|
||||
{ label: translate('::ListForms.Wizard.Step4.StatField'), value: totalFields },
|
||||
{
|
||||
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
|
||||
label: translate('::ListForms.Wizard.Step4.EditingForm'),
|
||||
value: editingFormFields.length,
|
||||
},
|
||||
{
|
||||
|
|
@ -665,19 +654,19 @@ const WizardStep11 = ({
|
|||
value: selectedColumns.size,
|
||||
},
|
||||
{
|
||||
label: translate('::ListForms.ListFormEdit.SubForms') || 'Sub Forms',
|
||||
label: translate('::ListForms.ListFormEdit.SubForms'),
|
||||
value: subForms.length,
|
||||
},
|
||||
{
|
||||
label: translate('::ListForms.ListFormEdit.TabWidgets') || 'Widgets',
|
||||
label: translate('::ListForms.ListFormEdit.TabWidgets'),
|
||||
value: widgets.length,
|
||||
},
|
||||
{
|
||||
label: translate('::ListForms.ListFormEdit.TabWorkflow') || 'Workflow',
|
||||
label: translate('::ListForms.ListFormEdit.TabWorkflow'),
|
||||
value: workflowItems.length,
|
||||
},
|
||||
{
|
||||
label: translate('::ListForms.ListFormEdit.TabTodo') || 'Todo',
|
||||
label: translate('::ListForms.ListFormEdit.TabTodo'),
|
||||
value: values.todo ? 1 : 0,
|
||||
},
|
||||
].map((s) => (
|
||||
|
|
@ -698,7 +687,7 @@ const WizardStep11 = ({
|
|||
<div className="px-4 py-2.5 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between shrink-0">
|
||||
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200 flex items-center gap-2">
|
||||
<FaRocket className="text-indigo-400 text-xs" />
|
||||
{translate('::ListForms.Wizard.Step4.DeployLog') || 'Deploy Log'}
|
||||
{translate('::ListForms.Wizard.Step4.DeployLog')}
|
||||
</span>
|
||||
{isDone && (
|
||||
<span className="text-xs text-emerald-500 font-semibold flex items-center gap-1">
|
||||
|
|
@ -771,7 +760,7 @@ const WizardStep11 = ({
|
|||
disabled={isDeploying}
|
||||
onClick={onBack}
|
||||
>
|
||||
{translate('::Back') || 'Back'}
|
||||
{translate('::Back')}
|
||||
</Button>
|
||||
<div className="flex-1 flex items-center justify-end">
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -124,8 +124,7 @@ const WizardStep2 = ({
|
|||
!values.languageTextDescTr && translate('::ListForms.Wizard.Step2.DescriptionTextTurkish'),
|
||||
!values.languageTextTitleEn && translate('::ListForms.Wizard.Step2.TitleTextEnglish'),
|
||||
!values.languageTextTitleTr && translate('::ListForms.Wizard.Step2.TitleTextTurkish'),
|
||||
selectedColumns.size === 0 &&
|
||||
(translate('::ListForms.Wizard.Step2.ColumnSelection') || 'Sütun seçimi'),
|
||||
selectedColumns.size === 0 && translate('::ListForms.Wizard.Step2.ColumnSelection'),
|
||||
].filter(Boolean) as string[]
|
||||
const step2CanGo = step2Missing.length === 0
|
||||
|
||||
|
|
@ -140,8 +139,7 @@ const WizardStep2 = ({
|
|||
asterisk={true}
|
||||
extra={
|
||||
<span className="text-xs ml-2 text-gray-400">
|
||||
{translate('::ListForms.Wizard.Step2.ListFormCodeHint') ||
|
||||
'Auto-derived from Wizard Name, editable'}
|
||||
{translate('::ListForms.Wizard.Step2.ListFormCodeHint')}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
|
|
@ -230,7 +228,7 @@ const WizardStep2 = ({
|
|||
const grouped = dbObjects
|
||||
? [
|
||||
{
|
||||
label: translate('::ListForms.Wizard.Step2.Tables') || 'Tables',
|
||||
label: translate('::ListForms.Wizard.Step2.Tables'),
|
||||
options: dbObjects.tables.map((t) => ({
|
||||
label: t.tableName,
|
||||
value: t.tableName,
|
||||
|
|
@ -240,9 +238,7 @@ const WizardStep2 = ({
|
|||
})),
|
||||
},
|
||||
{
|
||||
label:
|
||||
translate('::ListForms.Wizard.Step2.StoredProcedures') ||
|
||||
'Stored Procedures',
|
||||
label: translate('::ListForms.Wizard.Step2.StoredProcedures'),
|
||||
options: dbObjects.storedProcedures.map((p) => ({
|
||||
label: p.objectName,
|
||||
value: p.objectName,
|
||||
|
|
@ -252,7 +248,7 @@ const WizardStep2 = ({
|
|||
})),
|
||||
},
|
||||
{
|
||||
label: translate('::App.Platform.Views') || 'Views',
|
||||
label: translate('::App.Platform.Views'),
|
||||
options: dbObjects.views.map((v) => ({
|
||||
label: v.objectName,
|
||||
value: v.objectName,
|
||||
|
|
@ -262,7 +258,7 @@ const WizardStep2 = ({
|
|||
})),
|
||||
},
|
||||
{
|
||||
label: translate('::ListForms.Wizard.Step2.Functions') || 'Functions',
|
||||
label: translate('::ListForms.Wizard.Step2.Functions'),
|
||||
options: dbObjects.functions.map((f) => ({
|
||||
label: f.objectName,
|
||||
value: f.objectName,
|
||||
|
|
@ -285,8 +281,7 @@ const WizardStep2 = ({
|
|||
placeholder={
|
||||
isLoadingDbObjects
|
||||
? translate('::App.Loading')
|
||||
: translate('::ListForms.Wizard.Step2.SelectCommand') ||
|
||||
'Tablo/View/SP seç veya SQL yaz…'
|
||||
: translate('::ListForms.Wizard.Step2.SelectCommand')
|
||||
}
|
||||
value={field.value ? { label: field.value, value: field.value } : null}
|
||||
onChange={(option: any) => {
|
||||
|
|
@ -369,8 +364,8 @@ const WizardStep2 = ({
|
|||
isLoading={isLoadingColumns}
|
||||
placeholder={
|
||||
isLoadingColumns
|
||||
? translate('::ListForms.Wizard.Step2.ColumnsLoading') || 'Sütunlar yükleniyor…'
|
||||
: translate('::ListForms.Wizard.Step2.SelectKeyColumn') || 'Key sütunu seç…'
|
||||
? translate('::ListForms.Wizard.Step2.ColumnsLoading')
|
||||
: translate('::ListForms.Wizard.Step2.SelectKeyColumn')
|
||||
}
|
||||
options={selectCommandColumns.map((c) => ({
|
||||
label: `${c.columnName} (${c.dataType})`,
|
||||
|
|
@ -639,9 +634,7 @@ const WizardStep2 = ({
|
|||
type="text"
|
||||
autoComplete="off"
|
||||
name="languageTextTitleEn"
|
||||
placeholder={
|
||||
translate('::ListForms.Wizard.Step2.TitleTextEnglish') || 'English Title Text'
|
||||
}
|
||||
placeholder={translate('::ListForms.Wizard.Step2.TitleTextEnglish')}
|
||||
component={Input}
|
||||
/>
|
||||
</FormItem>
|
||||
|
|
@ -656,9 +649,7 @@ const WizardStep2 = ({
|
|||
type="text"
|
||||
autoComplete="off"
|
||||
name="languageTextTitleTr"
|
||||
placeholder={
|
||||
translate('::ListForms.Wizard.Step2.TitleTextTurkish') || 'Turkish Title Text'
|
||||
}
|
||||
placeholder={translate('::ListForms.Wizard.Step2.TitleTextTurkish')}
|
||||
component={Input}
|
||||
/>
|
||||
</FormItem>
|
||||
|
|
@ -673,10 +664,7 @@ const WizardStep2 = ({
|
|||
type="text"
|
||||
autoComplete="off"
|
||||
name="languageTextDescEn"
|
||||
placeholder={
|
||||
translate('::ListForms.Wizard.Step2.DescriptionTextEnglish') ||
|
||||
'English Description Text'
|
||||
}
|
||||
placeholder={translate('::ListForms.Wizard.Step2.DescriptionTextEnglish')}
|
||||
component={Input}
|
||||
/>
|
||||
</FormItem>
|
||||
|
|
@ -691,10 +679,7 @@ const WizardStep2 = ({
|
|||
type="text"
|
||||
autoComplete="off"
|
||||
name="languageTextDescTr"
|
||||
placeholder={
|
||||
translate('::ListForms.Wizard.Step2.DescriptionTextTurkish') ||
|
||||
'Turkish Description Text'
|
||||
}
|
||||
placeholder={translate('::ListForms.Wizard.Step2.DescriptionTextTurkish')}
|
||||
component={Input}
|
||||
/>
|
||||
</FormItem>
|
||||
|
|
@ -702,7 +687,7 @@ const WizardStep2 = ({
|
|||
|
||||
{/* Column Selection Panel */}
|
||||
<FormItem
|
||||
label={translate('::ListForms.Wizard.Step2.Columns') || 'Sütunlar'}
|
||||
label={translate('::ListForms.Wizard.Step2.Columns')}
|
||||
extra={
|
||||
selectCommandColumns.length > 0 ? (
|
||||
<div className="flex items-center gap-2 ml-3">
|
||||
|
|
@ -712,7 +697,7 @@ const WizardStep2 = ({
|
|||
onClick={() => onToggleAllColumns(true)}
|
||||
className="text-xs px-2 py-0.5 rounded bg-indigo-500 text-white hover:bg-indigo-600"
|
||||
>
|
||||
{translate('::ListForms.Wizard.Step2.SelectAll') || 'Tümünü Seç'}
|
||||
{translate('::ListForms.Wizard.Step2.SelectAll')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
|
|
@ -720,7 +705,7 @@ const WizardStep2 = ({
|
|||
onClick={() => onToggleAllColumns(false)}
|
||||
className="text-xs px-2 py-0.5 rounded border border-gray-300 dark:border-gray-600 text-gray-500 hover:text-red-500 hover:border-red-400"
|
||||
>
|
||||
{translate('::ListForms.Wizard.Step2.ClearAll') || 'Tümünü Kaldır'}
|
||||
{translate('::ListForms.Wizard.Step2.ClearAll')}
|
||||
</Button>
|
||||
<span className="text-xs text-gray-400">
|
||||
{selectedColumns.size}/{selectCommandColumns.length}{' '}
|
||||
|
|
@ -733,12 +718,11 @@ const WizardStep2 = ({
|
|||
<div className="rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 overflow-hidden">
|
||||
{isLoadingColumns ? (
|
||||
<div className="px-4 py-3 text-sm text-gray-400">
|
||||
{translate('::ListForms.Wizard.Step2.ColumnsLoading') || 'Sütunlar yükleniyor…'}
|
||||
{translate('::ListForms.Wizard.Step2.ColumnsLoading')}
|
||||
</div>
|
||||
) : selectCommandColumns.length === 0 ? (
|
||||
<div className="px-4 py-3 text-sm text-gray-400">
|
||||
{translate('::ListForms.Wizard.Step2.ColumnsWillAppear') ||
|
||||
'Select Command seçilince sütunlar burada görünecek'}
|
||||
{translate('::ListForms.Wizard.Step2.ColumnsWillAppear')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-40 overflow-y-auto py-1">
|
||||
|
|
@ -775,7 +759,7 @@ const WizardStep2 = ({
|
|||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-6 py-0 h-16 flex items-center">
|
||||
<div className="flex items-center gap-3 w-full">
|
||||
<Button variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
|
||||
{translate('::Back') || 'Back'}
|
||||
{translate('::Back')}
|
||||
</Button>
|
||||
<div className="flex-1 flex items-center justify-end gap-3">
|
||||
{!step2CanGo && (
|
||||
|
|
@ -790,7 +774,7 @@ const WizardStep2 = ({
|
|||
disabled={!step2CanGo}
|
||||
onClick={onNext}
|
||||
>
|
||||
{translate('::Next') || 'Next'}
|
||||
{translate('::Next')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -387,7 +387,7 @@ function SortableItem({
|
|||
icon={<FaPlus className="text-[8px]" />}
|
||||
className="!h-auto !items-center gap-1 !rounded border border-indigo-200 bg-indigo-50 !px-1.5 !py-0.5 text-[10px] font-medium text-indigo-600 transition-colors hover:!bg-indigo-100 dark:border-indigo-700 dark:bg-indigo-900/20 dark:text-indigo-400 dark:hover:!bg-indigo-800/40 shrink-0"
|
||||
>
|
||||
{translate('::ListForms.Wizard.Step3.GenerateFromTable') || 'Tablodan Oluştur'}
|
||||
{translate('::ListForms.Wizard.Step3.GenerateFromTable')}
|
||||
</Button>
|
||||
</div>
|
||||
<textarea
|
||||
|
|
@ -423,7 +423,7 @@ function SortableItem({
|
|||
)}
|
||||
<span className="text-sm font-semibold text-gray-700 dark:text-gray-200">
|
||||
{pickerStep === 'table'
|
||||
? translate('::ListForms.Wizard.Step3.SelectTable') || 'Tablo Seç'
|
||||
? translate('::ListForms.Wizard.Step3.SelectTable')
|
||||
: (pickerTable?.tableName ?? '')}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -445,15 +445,14 @@ function SortableItem({
|
|||
autoFocus
|
||||
value={tableSearch}
|
||||
onChange={(e) => setTableSearch(e.target.value)}
|
||||
placeholder={translate('::Search') || 'Ara...'}
|
||||
placeholder={translate('::Search')}
|
||||
className="w-full text-xs px-2 py-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||
/>
|
||||
</div>
|
||||
<div className="overflow-y-auto flex-1 p-2">
|
||||
{!dbObjects ? (
|
||||
<div className="text-xs text-gray-400 text-center py-6">
|
||||
{translate('::ListForms.Wizard.Step3.NoTablesAvailable') ||
|
||||
'Tablo bulunamadı'}
|
||||
{translate('::ListForms.Wizard.Step3.NoTablesAvailable')}
|
||||
</div>
|
||||
) : (
|
||||
dbObjects.tables
|
||||
|
|
@ -506,14 +505,14 @@ function SortableItem({
|
|||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[10px] font-medium text-gray-500 dark:text-gray-400">
|
||||
Key Sütunu
|
||||
{translate('::ListForms.ListFormFieldEdit.KeyColumn')}
|
||||
</label>
|
||||
<select
|
||||
value={pickerKeyCol}
|
||||
onChange={(e) => setPickerKeyCol(e.target.value)}
|
||||
className="w-full text-xs h-7 px-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||
>
|
||||
<option value="">-- Seçiniz --</option>
|
||||
<option value="">{translate('::App.Select')}</option>
|
||||
{pickerColumns.map((c) => (
|
||||
<option key={c.columnName} value={c.columnName}>
|
||||
{c.columnName}
|
||||
|
|
@ -523,14 +522,14 @@ function SortableItem({
|
|||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[10px] font-medium text-gray-500 dark:text-gray-400">
|
||||
Name Sütunu
|
||||
{translate('::ListForms.ListFormFieldEdit.NameColumn')}
|
||||
</label>
|
||||
<select
|
||||
value={pickerNameCol}
|
||||
onChange={(e) => setPickerNameCol(e.target.value)}
|
||||
className="w-full text-xs h-7 px-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
|
||||
>
|
||||
<option value="">-- Seçiniz --</option>
|
||||
<option value="">{translate('::App.Select')}</option>
|
||||
{pickerColumns.map((c) => (
|
||||
<option key={c.columnName} value={c.columnName}>
|
||||
{c.columnName}
|
||||
|
|
@ -565,7 +564,7 @@ function SortableItem({
|
|||
variant="solid"
|
||||
className="mt-1 !h-auto !rounded !py-1.5 text-xs font-semibold text-white transition-colors hover:!bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-40"
|
||||
>
|
||||
Tamam
|
||||
{translate('::OK')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -659,7 +658,7 @@ function SortableItem({
|
|||
</div>
|
||||
<label
|
||||
className="flex items-center gap-1 cursor-pointer ml-auto"
|
||||
title={translate('::ListForms.Wizard.Step3.IncludeInEditingForm') || 'Editing form'}
|
||||
title={translate('::ListForms.Wizard.Step3.IncludeInEditingForm')}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
|
@ -668,7 +667,7 @@ function SortableItem({
|
|||
className="w-3 h-3 accent-indigo-500"
|
||||
/>
|
||||
<span className="text-[10px] text-gray-400">
|
||||
{translate('::ListForms.Wizard.Step3.IncludeInEditingForm') || 'Editing form'}
|
||||
{translate('::ListForms.Wizard.Step3.IncludeInEditingForm')}
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-1 cursor-pointer ml-auto" title="Required">
|
||||
|
|
@ -679,7 +678,7 @@ function SortableItem({
|
|||
className="w-3 h-3 accent-red-500"
|
||||
/>
|
||||
<span className="text-[10px] text-gray-400">
|
||||
{translate('::ListForms.Wizard.Step3.Required') || 'Required'}
|
||||
{translate('::ListForms.Wizard.Step3.Required')}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
|
@ -741,7 +740,7 @@ function GroupCard({
|
|||
{/* ColCount */}
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<span className="text-xs text-gray-400">
|
||||
{translate('::ListForms.Wizard.Step3.Cols') || 'Cols:'}
|
||||
{translate('::ListForms.Wizard.Step3.Cols')}
|
||||
</span>
|
||||
{[1, 2, 3].map((n) => (
|
||||
<Button
|
||||
|
|
@ -769,12 +768,9 @@ function GroupCard({
|
|||
shape="none"
|
||||
icon={<FaArrowRight className="text-[9px]" />}
|
||||
className="!h-6 !items-center gap-1 !rounded border border-indigo-200 bg-indigo-50 !px-2 text-[11px] font-medium text-indigo-600 transition-colors hover:!bg-indigo-100 dark:border-indigo-700 dark:bg-indigo-900/20 dark:text-indigo-400 dark:hover:!bg-indigo-900/40 shrink-0"
|
||||
title={
|
||||
translate('::ListForms.Wizard.Step3.AddAllToGroupTitle') ||
|
||||
'Tüm mevcut sütunları bu gruba ekle'
|
||||
}
|
||||
title={translate('::ListForms.Wizard.Step3.AddAllToGroupTitle')}
|
||||
>
|
||||
{translate('::ListForms.Wizard.Step3.AddAll') || 'Tümünü Ekle'}
|
||||
{translate('::ListForms.Wizard.Step3.AddAll')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
|
|
@ -784,7 +780,7 @@ function GroupCard({
|
|||
shape="circle"
|
||||
icon={<FaTrash className="text-xs" />}
|
||||
className="!h-7 !w-7 !px-0 text-gray-300 transition-colors hover:!bg-transparent hover:text-red-500"
|
||||
title={translate('::ListForms.Wizard.Step3.DeleteGroup') || 'Delete group'}
|
||||
title={translate('::ListForms.Wizard.Step3.DeleteGroup')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -800,7 +796,7 @@ function GroupCard({
|
|||
>
|
||||
{group.items.length === 0 && !isOver && (
|
||||
<div className="flex items-center justify-center h-12 text-xs text-gray-300 dark:text-gray-600 select-none">
|
||||
{translate('::ListForms.Wizard.Step3.DragColumnsHere') || 'Sütunları buraya sürükleyin'}
|
||||
{translate('::ListForms.Wizard.Step3.DragColumnsHere')}
|
||||
</div>
|
||||
)}
|
||||
<SortableContext items={itemIds} strategy={rectSortingStrategy}>
|
||||
|
|
@ -1085,10 +1081,9 @@ const WizardStep3 = ({
|
|||
const hasEmptyGroup = groups.some((g) => g.items.length === 0)
|
||||
const canProceed = !hasNoGroups && !hasEmptyGroup
|
||||
const validationMsg = hasNoGroups
|
||||
? translate('::ListForms.Wizard.Step3.AtLeastOneGroup') || 'En az bir grup eklemelisiniz.'
|
||||
? translate('::ListForms.Wizard.Step3.AtLeastOneGroup')
|
||||
: hasEmptyGroup
|
||||
? translate('::ListForms.Wizard.Step3.AtLeastOneColumn') ||
|
||||
'Her gruba en az bir sütun eklemelisiniz.'
|
||||
? translate('::ListForms.Wizard.Step3.AtLeastOneColumn')
|
||||
: ''
|
||||
|
||||
return (
|
||||
|
|
@ -1114,8 +1109,7 @@ const WizardStep3 = ({
|
|||
<div className="flex flex-row flex-wrap lg:flex-col gap-1.5 max-h-40 lg:max-h-[calc(100vh-280px)] overflow-y-auto pr-1">
|
||||
{availableColumns.length === 0 ? (
|
||||
<div className="text-xs text-gray-300 dark:text-gray-600 py-4 text-center select-none w-full">
|
||||
{translate('::ListForms.Wizard.Step3.AllColumnsAdded') ||
|
||||
'Tüm sütunlar gruplara eklendi'}
|
||||
{translate('::ListForms.Wizard.Step3.AllColumnsAdded')}
|
||||
</div>
|
||||
) : (
|
||||
availableColumns.map((col) => <AvailableColumnChip key={col} colName={col} />)
|
||||
|
|
@ -1128,8 +1122,7 @@ const WizardStep3 = ({
|
|||
<div className="flex-1 flex flex-col gap-3">
|
||||
{groups.length === 0 && (
|
||||
<div className="rounded-xl border-2 border-dashed border-gray-200 dark:border-gray-700 flex items-center justify-center h-36 text-sm text-gray-300 dark:text-gray-600 select-none">
|
||||
{translate('::ListForms.Wizard.Step3.NoGroupsYet') ||
|
||||
'Henüz grup yok — aşağıdan grup ekleyin'}
|
||||
{translate('::ListForms.Wizard.Step3.NoGroupsYet')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -1160,7 +1153,7 @@ const WizardStep3 = ({
|
|||
icon={<FaPlus className="text-xs" />}
|
||||
className="w-full !h-auto !items-center !justify-center gap-2 !rounded-xl border-2 border-dashed border-gray-200 !py-2.5 text-sm text-gray-400 transition-colors hover:!bg-transparent hover:border-indigo-400 hover:text-indigo-500 dark:border-gray-700 dark:text-gray-500 dark:hover:border-indigo-600 dark:hover:text-indigo-400"
|
||||
>
|
||||
{translate('::ListForms.Wizard.Step3.AddGroup') || 'Grup Ekle'}
|
||||
{translate('::ListForms.Wizard.Step3.AddGroup')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1239,7 +1232,7 @@ const WizardStep3 = ({
|
|||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-4 py-2 min-h-16 flex items-center">
|
||||
<div className="flex flex-wrap items-center gap-2 w-full">
|
||||
<Button size="sm" variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
|
||||
{translate('::Back') || 'Back'}
|
||||
{translate('::Back')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
|
|
@ -1248,7 +1241,7 @@ const WizardStep3 = ({
|
|||
icon={<FaCode />}
|
||||
onClick={() => setIsHelperOpen(true)}
|
||||
>
|
||||
{translate('::Helper Codes') || 'Helper Codes'}
|
||||
{translate('::Helper Codes')}
|
||||
</Button>
|
||||
<div className="flex-1 flex items-center justify-end gap-3">
|
||||
{!canProceed && (
|
||||
|
|
@ -1264,7 +1257,7 @@ const WizardStep3 = ({
|
|||
disabled={!canProceed}
|
||||
onClick={onNext}
|
||||
>
|
||||
{translate('::Next') || 'Next'}
|
||||
{translate('::Next')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ function WizardStep4({
|
|||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-4 py-2 min-h-16 flex items-center">
|
||||
<div className="flex flex-wrap items-center gap-2 w-full">
|
||||
<Button size="sm" variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
|
||||
{translate('::Back') || 'Back'}
|
||||
{translate('::Back')}
|
||||
</Button>
|
||||
<div className="flex-1 flex items-center justify-end gap-3">
|
||||
<Button
|
||||
|
|
@ -229,7 +229,7 @@ function WizardStep4({
|
|||
icon={<FaArrowRight />}
|
||||
onClick={onNext}
|
||||
>
|
||||
{translate('::Next') || 'Next'}
|
||||
{translate('::Next')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ function WizardStep5({ widgets, translate, onChange, onBack, onNext }: Props) {
|
|||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-4 py-2 min-h-16 flex items-center">
|
||||
<div className="flex flex-wrap items-center gap-2 w-full">
|
||||
<Button size="sm" variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
|
||||
{translate('::Back') || 'Back'}
|
||||
{translate('::Back')}
|
||||
</Button>
|
||||
<div className="flex-1 flex items-center justify-end gap-3">
|
||||
<Button
|
||||
|
|
@ -153,7 +153,7 @@ function WizardStep5({ widgets, translate, onChange, onBack, onNext }: Props) {
|
|||
icon={<FaArrowRight />}
|
||||
onClick={onNext}
|
||||
>
|
||||
{translate('::Next') || 'Next'}
|
||||
{translate('::Next')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ function WizardStep6({
|
|||
...normalizeCriteria(emptyCriteria('Start', listFormCode)),
|
||||
id: startId,
|
||||
nodeId: startId,
|
||||
title: 'İş Akışı Başlat1',
|
||||
title: translate('::ListForms.Workflow.Demo.Start'),
|
||||
nextOnStart: approval1Id,
|
||||
positionX: 34,
|
||||
positionY: 28,
|
||||
|
|
@ -232,7 +232,7 @@ function WizardStep6({
|
|||
...normalizeCriteria(emptyCriteria('Approval', listFormCode)),
|
||||
id: approval1Id,
|
||||
nodeId: approval1Id,
|
||||
title: 'Onay1',
|
||||
title: translate('::ListForms.Workflow.Demo.Approval1'),
|
||||
nextOnApprove: approval2Id,
|
||||
nextOnReject: informId,
|
||||
positionX: 323,
|
||||
|
|
@ -242,7 +242,7 @@ function WizardStep6({
|
|||
...normalizeCriteria(emptyCriteria('Approval', listFormCode)),
|
||||
id: approval2Id,
|
||||
nodeId: approval2Id,
|
||||
title: 'Onay2',
|
||||
title: translate('::ListForms.Workflow.Demo.Approval2'),
|
||||
nextOnApprove: informId,
|
||||
nextOnReject: informId,
|
||||
positionX: 586,
|
||||
|
|
@ -252,7 +252,7 @@ function WizardStep6({
|
|||
...normalizeCriteria(emptyCriteria('Inform', listFormCode)),
|
||||
id: informId,
|
||||
nodeId: informId,
|
||||
title: 'Bilgilendirme1',
|
||||
title: translate('::ListForms.Workflow.Demo.Inform'),
|
||||
nextOnStart: endId,
|
||||
positionX: 458,
|
||||
positionY: 336,
|
||||
|
|
@ -261,7 +261,7 @@ function WizardStep6({
|
|||
...normalizeCriteria(emptyCriteria('End', listFormCode)),
|
||||
id: endId,
|
||||
nodeId: endId,
|
||||
title: 'İş Akışı Bitir1',
|
||||
title: translate('::ListForms.Workflow.Demo.End'),
|
||||
positionX: 792,
|
||||
positionY: 336,
|
||||
} as WorkflowCriteriaDto,
|
||||
|
|
@ -470,7 +470,7 @@ function WizardStep6({
|
|||
<div className="fixed bottom-0 left-0 right-0 z-10 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 px-4 py-2 min-h-16 flex items-center">
|
||||
<div className="flex flex-wrap items-center gap-2 w-full">
|
||||
<Button size="sm" variant="default" type="button" icon={<FaArrowLeft />} onClick={onBack}>
|
||||
{translate('::Back') || 'Back'}
|
||||
{translate('::Back')}
|
||||
</Button>
|
||||
<div className="flex-1 flex items-center justify-end gap-3">
|
||||
<Button
|
||||
|
|
@ -480,7 +480,7 @@ function WizardStep6({
|
|||
icon={<FaArrowRight />}
|
||||
onClick={continueToTodo}
|
||||
>
|
||||
{translate('::Next') || 'Next'}
|
||||
{translate('::Next')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
collectLinks,
|
||||
getNodeOutcomes,
|
||||
outcomeLabel,
|
||||
OUTCOME_KEYS,
|
||||
type WorkflowLink,
|
||||
type WorkflowOutcome,
|
||||
} from '@/utils/workflow/workflowHelpers'
|
||||
|
|
@ -93,6 +94,8 @@ export function WorkflowCanvas({
|
|||
)
|
||||
const links = useMemo(() => collectLinks(arrowCriteria) as WorkflowLink[], [arrowCriteria])
|
||||
|
||||
const { translate } = useLocalization()
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key !== 'Delete') return
|
||||
event.preventDefault()
|
||||
|
|
@ -124,14 +127,14 @@ export function WorkflowCanvas({
|
|||
>
|
||||
{pendingLink && (
|
||||
<div className="sticky left-2.5 top-2.5 z-50 m-2.5 inline-flex min-h-[34px] items-center rounded-md border border-[#8bb3f1] bg-[#eff6ff] px-3 text-[13px] text-blue-800 shadow-lg dark:border-blue-500/60 dark:bg-blue-900/40 dark:text-blue-100">
|
||||
{outcomeLabel(pendingLink.outcome)} çıkışı seçildi. Hedef iş akışı adımına tıklayın.
|
||||
{translate('::ListForms.Workflow.PendingLinkHint', {
|
||||
outcome: translate('::' + (outcomeLabel(pendingLink.outcome) ?? '')),
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{currentCriteria.length === 0 && (
|
||||
<div className="sticky left-[18px] top-[18px] z-30 inline-grid max-w-[360px] gap-1 rounded-lg border border-[#cfd6e2] bg-white/95 p-3.5 text-[#475467] shadow-lg dark:border-gray-700 dark:bg-gray-900/95 dark:text-gray-300">
|
||||
<span>
|
||||
Üstteki butonlardan adım ekleyin, sonra çıkış etiketleriyle bağlantıları kurun.
|
||||
</span>
|
||||
<span>{translate('::ListForms.Workflow.EmptyCanvasHint')}</span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
|
|
@ -252,11 +255,7 @@ export function WorkflowCanvas({
|
|||
aria-hidden="true"
|
||||
>
|
||||
{links.map((link) => (
|
||||
<ArrowLabel
|
||||
key={`${link.key}-label`}
|
||||
link={link}
|
||||
pendingLink={pendingLink}
|
||||
/>
|
||||
<ArrowLabel key={`${link.key}-label`} link={link} pendingLink={pendingLink} />
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
|
|
@ -387,7 +386,9 @@ function FlowNode({
|
|||
onBeginLink(item.id, outcome.field)
|
||||
}}
|
||||
>
|
||||
{outcome.label}
|
||||
{outcome.label.startsWith('ListForms.')
|
||||
? translate('::' + outcome.label)
|
||||
: outcome.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -503,13 +504,7 @@ function Arrow({
|
|||
)
|
||||
}
|
||||
|
||||
function ArrowLabel({
|
||||
link,
|
||||
pendingLink,
|
||||
}: {
|
||||
link: WorkflowLink
|
||||
pendingLink: PendingLink
|
||||
}) {
|
||||
function ArrowLabel({ link, pendingLink }: { link: WorkflowLink; pendingLink: PendingLink }) {
|
||||
if (!link.label) return null
|
||||
|
||||
const route = buildArrowRoute(link.source, link.target, link.sourcePort)
|
||||
|
|
@ -550,8 +545,8 @@ function ArrowLabel({
|
|||
function linkTone(link: WorkflowLink) {
|
||||
const field = link.sourcePort?.field || ''
|
||||
const label = link.label || ''
|
||||
if (field === 'nextOnReject' || label === 'Red') return 'reject'
|
||||
if (field === 'nextOnApprove' || label === 'Onay') return 'approve'
|
||||
if (field === 'nextOnReject' || label === OUTCOME_KEYS.nextOnReject) return 'reject'
|
||||
if (field === 'nextOnApprove' || label === OUTCOME_KEYS.nextOnApprove) return 'approve'
|
||||
if (field.startsWith('compareOutcomes:') || link.source.kind === 'Compare') return 'compare'
|
||||
if (field === 'nextOnStart') return 'next'
|
||||
return 'neutral'
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import {
|
|||
import type { CompareOutcomeDto, WorkflowCriteriaDto } from '@/services/workflow.service'
|
||||
import { SelectBoxOption } from '@/types/shared'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
type TranslateFn = (key: string, params?: Record<string, string | number>) => string
|
||||
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||
import { MULTIVALUE_DELIMITER } from '@/constants/app.constant'
|
||||
|
||||
|
|
@ -48,9 +50,10 @@ export function WorkflowCriteria({
|
|||
onSubmit,
|
||||
onDelete,
|
||||
}: WorkflowCriteriaProps) {
|
||||
const { translate } = useLocalization()
|
||||
const setField = (name: string, value: unknown) => onChange({ ...formValues, [name]: value })
|
||||
const targetOptions = [
|
||||
{ value: '', label: 'Bağlantı yok' },
|
||||
{ value: '', label: translate('::ListForms.Workflow.NoConnection') },
|
||||
...criteria
|
||||
.filter((item) => item.id !== formValues.id)
|
||||
.map((item) => ({ value: item.id, label: `${item.id} - ${item.title}` })),
|
||||
|
|
@ -164,7 +167,6 @@ export function WorkflowCriteria({
|
|||
/>
|
||||
)
|
||||
const closeDialog = () => onSelect('')
|
||||
const { translate } = useLocalization()
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -189,7 +191,7 @@ export function WorkflowCriteria({
|
|||
|
||||
{criteria.map((item) => {
|
||||
const isSelected = item.id === selectedId
|
||||
const connectionSummary = criteriaConnectionSummary(item, criteria)
|
||||
const connectionSummary = criteriaConnectionSummary(item, criteria, translate)
|
||||
|
||||
return (
|
||||
<Tr
|
||||
|
|
@ -233,7 +235,9 @@ export function WorkflowCriteria({
|
|||
'::' + kindOptions.find((option) => option.value === item.kind)?.value,
|
||||
)}
|
||||
</Td>
|
||||
<Td className="min-w-[220px] break-words">{criteriaSummaryContent(item)}</Td>
|
||||
<Td className="min-w-[220px] break-words">
|
||||
{criteriaSummaryContent(item, translate)}
|
||||
</Td>
|
||||
<Td className="min-w-[220px] break-words">{connectionSummary}</Td>
|
||||
</Tr>
|
||||
)
|
||||
|
|
@ -250,7 +254,10 @@ export function WorkflowCriteria({
|
|||
<FormItem asterisk label={translate('::App.Platform.Type')}>
|
||||
<SelectField
|
||||
required
|
||||
options={kindOptions}
|
||||
options={kindOptions.map((option) => ({
|
||||
...option,
|
||||
label: translate('::' + option.label),
|
||||
}))}
|
||||
value={formValues.kind}
|
||||
onChange={(value) => setField('kind', value)}
|
||||
/>
|
||||
|
|
@ -263,10 +270,7 @@ export function WorkflowCriteria({
|
|||
/>
|
||||
</FormItem>
|
||||
{(formValues.kind === 'Approval' || formValues.kind === 'Inform') && (
|
||||
<FormItem
|
||||
asterisk
|
||||
label={translate('::App.Listform.ListformField.Approver')}
|
||||
>
|
||||
<FormItem asterisk label={translate('::App.Listform.ListformField.Approver')}>
|
||||
<MultiSelectField
|
||||
required
|
||||
options={userList}
|
||||
|
|
@ -334,7 +338,9 @@ export function WorkflowCriteria({
|
|||
setField('compareOutcomes', [
|
||||
...(formValues.compareOutcomes || []),
|
||||
emptyCompareOutcome1(
|
||||
`Durum ${(formValues.compareOutcomes || []).length + 1}`,
|
||||
translate('::ListForms.Workflow.OutcomeN', {
|
||||
index: (formValues.compareOutcomes || []).length + 1,
|
||||
}),
|
||||
defaultCompareColumn,
|
||||
),
|
||||
])
|
||||
|
|
@ -364,7 +370,7 @@ export function WorkflowCriteria({
|
|||
<Input
|
||||
required
|
||||
value={outcome.label}
|
||||
aria-label="Durum adı zorunlu"
|
||||
aria-label={translate('::ListForms.Workflow.OutcomeNameRequired')}
|
||||
className="flex-[5]"
|
||||
onChange={(event) =>
|
||||
updateCompareOutcome(index, {
|
||||
|
|
@ -439,7 +445,7 @@ export function WorkflowCriteria({
|
|||
className="flex-[1]"
|
||||
onClick={() => removeCompareCondition(index, conditionIndex)}
|
||||
>
|
||||
Sil
|
||||
{translate('::App.Platform.Delete')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
|
@ -571,7 +577,7 @@ function MultiSelectField({
|
|||
)
|
||||
}
|
||||
|
||||
function criteriaSummaryContent(item: WorkflowCriteriaDto) {
|
||||
function criteriaSummaryContent(item: WorkflowCriteriaDto, translate: TranslateFn) {
|
||||
if (item.kind === 'Compare') {
|
||||
const outcomes = item.compareOutcomes || []
|
||||
if (!outcomes.length) return '-'
|
||||
|
|
@ -580,7 +586,9 @@ function criteriaSummaryContent(item: WorkflowCriteriaDto) {
|
|||
<ul className="m-0 grid gap-1">
|
||||
{outcomes.map((outcome, index: number) => (
|
||||
<li key={`${outcome.label || 'outcome'}-${index}`}>
|
||||
<strong>{outcome.label || `Durum ${index + 1}`}:</strong>{' '}
|
||||
<strong>
|
||||
{outcome.label || translate('::ListForms.Workflow.OutcomeN', { index: index + 1 })}:
|
||||
</strong>{' '}
|
||||
{compareOutcomeRuleText(outcome)}
|
||||
</li>
|
||||
))}
|
||||
|
|
@ -591,7 +599,11 @@ function criteriaSummaryContent(item: WorkflowCriteriaDto) {
|
|||
return criteriaSummary(item)
|
||||
}
|
||||
|
||||
function criteriaConnectionSummary(item: WorkflowCriteriaDto, criteria: WorkflowCriteriaDto[]) {
|
||||
function criteriaConnectionSummary(
|
||||
item: WorkflowCriteriaDto,
|
||||
criteria: WorkflowCriteriaDto[],
|
||||
translate: TranslateFn,
|
||||
) {
|
||||
if (item.kind === 'Compare') {
|
||||
const outcomes = item.compareOutcomes || []
|
||||
if (!outcomes.length) return '-'
|
||||
|
|
@ -600,7 +612,9 @@ function criteriaConnectionSummary(item: WorkflowCriteriaDto, criteria: Workflow
|
|||
<ul className="m-0 grid gap-1">
|
||||
{outcomes.map((outcome, index: number) => (
|
||||
<li key={`${outcome.label || 'target'}-${index}`}>
|
||||
<strong>{outcome.label || `Durum ${index + 1}`}:</strong>{' '}
|
||||
<strong>
|
||||
{outcome.label || translate('::ListForms.Workflow.OutcomeN', { index: index + 1 })}:
|
||||
</strong>{' '}
|
||||
{targetTitle(criteria, outcome.targetId)}
|
||||
</li>
|
||||
))}
|
||||
|
|
@ -612,10 +626,12 @@ function criteriaConnectionSummary(item: WorkflowCriteriaDto, criteria: Workflow
|
|||
return (
|
||||
<ul className="m-0 grid gap-1">
|
||||
<li>
|
||||
<strong>Onay:</strong> {targetTitle(criteria, item.nextOnApprove)}
|
||||
<strong>{translate('::ListForms.Workflow.Approve')}:</strong>{' '}
|
||||
{targetTitle(criteria, item.nextOnApprove)}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Red:</strong> {targetTitle(criteria, item.nextOnReject)}
|
||||
<strong>{translate('::ListForms.Workflow.Reject')}:</strong>{' '}
|
||||
{targetTitle(criteria, item.nextOnReject)}
|
||||
</li>
|
||||
</ul>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ function DesignerToolbar({
|
|||
shape="round"
|
||||
className={classNames(designerButtonClass, designerNeutralColorClass)}
|
||||
disabled={busy}
|
||||
title="Demo akışı yükle"
|
||||
title={translate('::ListForms.Workflow.LoadDemoFlow')}
|
||||
onClick={onResetDemo}
|
||||
>
|
||||
<FiRefreshCw />
|
||||
|
|
@ -209,7 +209,7 @@ function DesignerToolbar({
|
|||
shape="round"
|
||||
className={classNames(designerButtonClass, designerAccentColorClass)}
|
||||
disabled={busy || currentCriteria.length === 0}
|
||||
title="Düğümleri okunabilir şekilde yerleştir"
|
||||
title={translate('::ListForms.Workflow.FitLayout')}
|
||||
onClick={onFitLayout}
|
||||
>
|
||||
<FiMaximize2 />
|
||||
|
|
@ -220,7 +220,7 @@ function DesignerToolbar({
|
|||
variant="solid"
|
||||
shape="round"
|
||||
className={classNames(designerIconButtonClass, designerAccentColorClass)}
|
||||
title="Yakınlaştır"
|
||||
title={translate('::App.Platform.ZoomIn')}
|
||||
onClick={onZoomIn}
|
||||
>
|
||||
<FiZoomIn />
|
||||
|
|
@ -230,7 +230,7 @@ function DesignerToolbar({
|
|||
variant="solid"
|
||||
shape="round"
|
||||
className={classNames(designerIconButtonClass, designerAccentColorClass)}
|
||||
title="Uzaklaştır"
|
||||
title={translate('::App.Platform.ZoomOut')}
|
||||
onClick={onZoomOut}
|
||||
>
|
||||
<FiZoomOut />
|
||||
|
|
@ -269,7 +269,11 @@ function DesignerTabs({
|
|||
const { translate } = useLocalization()
|
||||
|
||||
return (
|
||||
<div className="inline-flex gap-1 rounded-lg" role="tablist" aria-label="Akış tasarımı">
|
||||
<div
|
||||
className="inline-flex gap-1 rounded-lg"
|
||||
role="tablist"
|
||||
aria-label={translate('::ListForms.Workflow.FlowDesign')}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant="plain"
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ const OrganizationUnits = () => {
|
|||
}
|
||||
toast.push(
|
||||
<Notification type="success" duration={2000}>
|
||||
{translate('::Kaydet')}
|
||||
{translate('::Save')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -234,7 +234,7 @@ const OrganizationUnits = () => {
|
|||
} catch (error) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -291,7 +291,7 @@ const OrganizationUnits = () => {
|
|||
} catch (error) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -327,7 +327,7 @@ const OrganizationUnits = () => {
|
|||
await ouPut({ id, displayName: name })
|
||||
toast.push(
|
||||
<Notification type="success" duration={2000}>
|
||||
{translate('::Kaydet')}
|
||||
{translate('::Save')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -338,7 +338,7 @@ const OrganizationUnits = () => {
|
|||
} catch (error) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -360,7 +360,7 @@ const OrganizationUnits = () => {
|
|||
await ouPutMove(id, newParentId)
|
||||
toast.push(
|
||||
<Notification type="success" duration={2000}>
|
||||
{translate('::Kaydet')}
|
||||
{translate('::Save')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -371,7 +371,7 @@ const OrganizationUnits = () => {
|
|||
} catch (error) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -428,8 +428,8 @@ const OrganizationUnits = () => {
|
|||
<div className="file-actions">
|
||||
<div className="flex gap-1 folderFileActions">
|
||||
<Button
|
||||
size='xs'
|
||||
variant='plain'
|
||||
size="xs"
|
||||
variant="plain"
|
||||
shape="circle"
|
||||
className="!h-6 !w-6 !bg-blue-50 !p-0 text-blue-600 hover:!bg-blue-100 hover:text-blue-700 dark:!bg-blue-900/30 dark:text-blue-300 dark:hover:!bg-blue-800/60 dark:hover:text-blue-100"
|
||||
icon={<FaUserPlus className="text-xs" />}
|
||||
|
|
@ -437,8 +437,8 @@ const OrganizationUnits = () => {
|
|||
title={translate('::Abp.Identity.OrganizationUnit.MoveAllUsers')}
|
||||
></Button>
|
||||
<Button
|
||||
size='xs'
|
||||
variant='plain'
|
||||
size="xs"
|
||||
variant="plain"
|
||||
shape="circle"
|
||||
className="!h-6 !w-6 !bg-teal-50 !p-0 text-teal-600 hover:!bg-teal-100 hover:text-teal-700 dark:!bg-teal-900/30 dark:text-teal-300 dark:hover:!bg-teal-800/60 dark:hover:text-teal-100"
|
||||
icon={<FaEdit className="text-xs" />}
|
||||
|
|
@ -446,8 +446,8 @@ const OrganizationUnits = () => {
|
|||
title={translate('::Abp.Identity.OrganizationUnit.Rename')}
|
||||
></Button>
|
||||
<Button
|
||||
size='xs'
|
||||
variant='plain'
|
||||
size="xs"
|
||||
variant="plain"
|
||||
shape="circle"
|
||||
className="!h-6 !w-6 !bg-red-50 !p-0 text-red-600 hover:!bg-red-100 hover:text-red-700 dark:!bg-red-900/30 dark:text-red-300 dark:hover:!bg-red-800/60 dark:hover:text-red-100"
|
||||
icon={<FaTrashAlt className="text-xs" />}
|
||||
|
|
@ -562,9 +562,9 @@ const OrganizationUnits = () => {
|
|||
<THead>
|
||||
<Tr>
|
||||
<Th></Th>
|
||||
<Th>Adı Soyadı</Th>
|
||||
<Th>E-Posta</Th>
|
||||
<Th>Durum</Th>
|
||||
<Th>{translate('::App.Platform.FullName')}</Th>
|
||||
<Th>{translate('::Abp.Account.EmailAddress')}</Th>
|
||||
<Th>{translate('::App.Platform.Status')}</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
)}
|
||||
|
|
@ -633,7 +633,7 @@ const OrganizationUnits = () => {
|
|||
<THead>
|
||||
<Tr>
|
||||
<Th></Th>
|
||||
<Th>Rol</Th>
|
||||
<Th>{translate('::App.Platform.Role')}</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
)}
|
||||
|
|
@ -786,7 +786,7 @@ const OrganizationUnits = () => {
|
|||
} catch (error) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -857,7 +857,7 @@ const OrganizationUnits = () => {
|
|||
} catch (error) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -935,7 +935,7 @@ const OrganizationUnits = () => {
|
|||
} catch (error) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -984,7 +984,7 @@ const OrganizationUnits = () => {
|
|||
} catch (error) {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
Hata
|
||||
{translate('::App.Platform.Error')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
|
|||
|
|
@ -89,15 +89,15 @@ const General = () => {
|
|||
const maxFileSize = 2000000
|
||||
|
||||
if (fileList.length >= 1) {
|
||||
return `Sadece bir dosya seçebilirsiniz`
|
||||
return translate('::App.Profile.OnlyOneFile')
|
||||
}
|
||||
|
||||
if (files) {
|
||||
for (const f of files) {
|
||||
if (!allowedFileType.includes(f.type)) {
|
||||
valid = '.jpg, .jpeg, .gif veya .png yükleyebilirsiniz'
|
||||
valid = translate('::App.Profile.AllowedImageTypes')
|
||||
} else if (f.size >= maxFileSize) {
|
||||
valid = 'En fazla 2mb dosya yükleyebilirsiniz'
|
||||
valid = translate('::App.Profile.MaxFileSize')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ const General = () => {
|
|||
avatar: AVATAR_URL(auth.user.id, auth.tenant.tenantId) + `?${dayjs().unix()}`,
|
||||
})
|
||||
|
||||
toast.push(<Notification title={'Profil güncellendi'} type="success" />, {
|
||||
toast.push(<Notification title={translate('::App.Profile.Updated')} type="success" />, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
} else {
|
||||
|
|
@ -178,31 +178,31 @@ const General = () => {
|
|||
const educationOptions: SelectBoxOption[] = [
|
||||
{
|
||||
value: 'İlkokul',
|
||||
label: translate('::App.EducationLevel.Primary') || 'İlkokul',
|
||||
label: translate('::App.EducationLevel.Primary'),
|
||||
},
|
||||
{
|
||||
value: 'Ortaokul',
|
||||
label: translate('::App.EducationLevel.MiddleSchool') || 'Ortaokul',
|
||||
label: translate('::App.EducationLevel.MiddleSchool'),
|
||||
},
|
||||
{
|
||||
value: 'Lise',
|
||||
label: translate('::App.EducationLevel.HighSchool') || 'Lise',
|
||||
label: translate('::App.EducationLevel.HighSchool'),
|
||||
},
|
||||
{
|
||||
value: 'Ön Lisans',
|
||||
label: translate('::App.EducationLevel.Associate') || 'Ön Lisans',
|
||||
label: translate('::App.EducationLevel.Associate'),
|
||||
},
|
||||
{
|
||||
value: 'Lisans',
|
||||
label: translate('::App.EducationLevel.Bachelor') || 'Lisans',
|
||||
label: translate('::App.EducationLevel.Bachelor'),
|
||||
},
|
||||
{
|
||||
value: 'Yüksek Lisans',
|
||||
label: translate('::App.EducationLevel.Master') || 'Yüksek Lisans',
|
||||
label: translate('::App.EducationLevel.Master'),
|
||||
},
|
||||
{
|
||||
value: 'Doktora',
|
||||
label: translate('::App.EducationLevel.PhD') || 'Doktora',
|
||||
label: translate('::App.EducationLevel.PhD'),
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ const CreateTenantFromOrderDialog = ({
|
|||
|
||||
toast.push(
|
||||
<Notification type="success" duration={2500}>
|
||||
{translate('::App.Platform.Success') || 'Tenant kaydedildi'}
|
||||
{translate('::App.Platform.Success')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
|
@ -95,7 +95,7 @@ const CreateTenantFromOrderDialog = ({
|
|||
(error as any)?.response?.data?.error?.message ??
|
||||
(error as any)?.response?.data?.message ??
|
||||
(error as any)?.message ??
|
||||
'Islem basarisiz'
|
||||
translate('::App.Platform.OperationFailed')
|
||||
|
||||
toast.push(
|
||||
<Notification type="danger" duration={5000}>
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ function TenantConnectionString({
|
|||
} catch {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
{'Hata'}
|
||||
{translate('::App.Platform.Error')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -136,7 +136,7 @@ function TenantConnectionString({
|
|||
} catch {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={2000}>
|
||||
{'Hata'}
|
||||
{translate('::App.Platform.Error')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
|
|
@ -187,7 +187,7 @@ function TenantConnectionString({
|
|||
(error as any)?.response?.data?.error?.message ??
|
||||
(error as any)?.response?.data?.message ??
|
||||
(error as any)?.message ??
|
||||
'İşlem başarısız'
|
||||
translate('::App.Platform.OperationFailed')
|
||||
|
||||
toast.push(
|
||||
<Notification type="warning" duration={10000}>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue