<select/> -> <Select /> olarak değiştirildi.
This commit is contained in:
parent
7186cb0ab0
commit
4c09de9ad0
27 changed files with 1114 additions and 913 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { FaPlus, FaSearch, FaTimes } from 'react-icons/fa'
|
import { FaPlus, FaSearch, FaTimes } from 'react-icons/fa'
|
||||||
import { Button } from '@/components/ui'
|
import { Button, Select } from '@/components/ui'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
|
||||||
|
|
@ -352,6 +352,11 @@ const StyleModal = ({
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [customClass, setCustomClass] = useState('')
|
const [customClass, setCustomClass] = useState('')
|
||||||
const [category, setCategory] = useState<keyof typeof STYLE_GROUPS | '__all__'>('__all__')
|
const [category, setCategory] = useState<keyof typeof STYLE_GROUPS | '__all__'>('__all__')
|
||||||
|
|
||||||
|
const categoryOptions = [
|
||||||
|
{ value: '__all__', label: translate('::App.StyleModal.AllCategories') },
|
||||||
|
...Object.keys(STYLE_GROUPS).map((name) => ({ value: name, label: name })),
|
||||||
|
]
|
||||||
const selected = useMemo(() => new Set(splitClasses(value)), [value])
|
const selected = useMemo(() => new Set(splitClasses(value)), [value])
|
||||||
const classes = useMemo(() => {
|
const classes = useMemo(() => {
|
||||||
const source =
|
const source =
|
||||||
|
|
@ -409,18 +414,15 @@ const StyleModal = ({
|
||||||
onChange={(event) => setSearch(event.target.value)}
|
onChange={(event) => setSearch(event.target.value)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<select
|
<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"
|
size="sm"
|
||||||
value={category}
|
menuPortalTarget={document.body}
|
||||||
onChange={(event) => setCategory(event.target.value as typeof category)}
|
options={categoryOptions}
|
||||||
>
|
value={categoryOptions.filter((option) => option.value === category)}
|
||||||
<option value="__all__">{translate('::App.StyleModal.AllCategories')}</option>
|
onChange={(option) =>
|
||||||
{Object.keys(STYLE_GROUPS).map((name) => (
|
setCategory((option?.value ?? '__all__') as typeof category)
|
||||||
<option key={name} value={name}>
|
}
|
||||||
{name}
|
/>
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{mode === 'class' && (
|
{mode === 'class' && (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button, Dialog } from '@/components/ui'
|
import { Button, Dialog, Select } from '@/components/ui'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import Editor, { type Monaco } from '@monaco-editor/react'
|
import Editor, { type Monaco } from '@monaco-editor/react'
|
||||||
import type * as monacoApi from 'monaco-editor'
|
import type * as monacoApi from 'monaco-editor'
|
||||||
|
|
@ -276,18 +276,20 @@ function ScriptBuilderDialog({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<select
|
<Select
|
||||||
className={controlClass}
|
size="sm"
|
||||||
value={options.includes(selectedValue) ? selectedValue : ''}
|
className="w-full"
|
||||||
onChange={(event) => onChange(event.target.value)}
|
isClearable
|
||||||
>
|
placeholder={placeholder}
|
||||||
<option value="">{placeholder}</option>
|
menuPortalTarget={document.body}
|
||||||
{options.map((option) => (
|
options={options.map((option) => ({ value: option, label: option }))}
|
||||||
<option key={option} value={option}>
|
value={
|
||||||
{option}
|
options.includes(selectedValue)
|
||||||
</option>
|
? { value: selectedValue, label: selectedValue }
|
||||||
))}
|
: null
|
||||||
</select>
|
}
|
||||||
|
onChange={(option) => onChange(option?.value ?? '')}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,24 +320,21 @@ function ScriptBuilderDialog({
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
<select
|
<Select
|
||||||
className={`${controlClass} !h-8 !w-44`}
|
size="sm"
|
||||||
value=""
|
className="w-44"
|
||||||
onChange={(event) => {
|
placeholder={translate('::App.ScriptBuilder.AddOption')}
|
||||||
const next = event.target.value
|
menuPortalTarget={document.body}
|
||||||
|
options={options
|
||||||
|
.filter((fieldName) => !selectedFields.includes(fieldName))
|
||||||
|
.map((fieldName) => ({ value: fieldName, label: fieldName }))}
|
||||||
|
value={null}
|
||||||
|
onChange={(option) => {
|
||||||
|
const next = option?.value
|
||||||
if (!next || selectedFields.includes(next)) return
|
if (!next || selectedFields.includes(next)) return
|
||||||
updateRule(rule.id, { fields: [...selectedFields, next] })
|
updateRule(rule.id, { fields: [...selectedFields, next] })
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<option value="">{translate('::App.ScriptBuilder.AddOption')}</option>
|
|
||||||
{options
|
|
||||||
.filter((fieldName) => !selectedFields.includes(fieldName))
|
|
||||||
.map((fieldName) => (
|
|
||||||
<option key={fieldName} value={fieldName}>
|
|
||||||
{fieldName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -357,17 +356,21 @@ function ScriptBuilderDialog({
|
||||||
param.placeholder || translate('::App.ScriptBuilder.Choose'),
|
param.placeholder || translate('::App.ScriptBuilder.Choose'),
|
||||||
)}
|
)}
|
||||||
{param.type === 'select' && (
|
{param.type === 'select' && (
|
||||||
<select
|
<Select
|
||||||
className={controlClass}
|
size="sm"
|
||||||
value={currentValue}
|
className="w-full"
|
||||||
onChange={(event) => updateParam(rule.id, param.key, event.target.value)}
|
menuPortalTarget={document.body}
|
||||||
>
|
options={(param.choices ?? []).map((choice) => ({
|
||||||
{param.choices?.map((choice) => (
|
value: choice.value,
|
||||||
<option key={choice.value} value={choice.value}>
|
label: choice.label,
|
||||||
{choice.label}
|
}))}
|
||||||
</option>
|
value={(param.choices ?? [])
|
||||||
))}
|
.filter((choice) => choice.value === currentValue)
|
||||||
</select>
|
.map((choice) => ({ value: choice.value, label: choice.label }))}
|
||||||
|
onChange={(option) =>
|
||||||
|
option && updateParam(rule.id, param.key, option.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{(param.type === 'text' || param.type === 'number') && (
|
{(param.type === 'text' || param.type === 'number') && (
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -388,6 +391,26 @@ function ScriptBuilderDialog({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const joinOptions = [
|
||||||
|
{ value: 'and', label: translate('::App.Platform.And') },
|
||||||
|
{ value: 'or', label: translate('::App.Platform.Or') },
|
||||||
|
]
|
||||||
|
|
||||||
|
const operatorOptions = dialect.operators.map((item) => ({
|
||||||
|
value: item.value,
|
||||||
|
label: item.label.startsWith('App.') ? translate('::' + item.label) : item.label,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const conditionKindOptions = dialect.conditionKinds.map((item) => ({
|
||||||
|
value: item.value,
|
||||||
|
label: item.label,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const triggerOptions = (dialect.triggers ?? []).map((item) => ({
|
||||||
|
value: item.value,
|
||||||
|
label: item.label,
|
||||||
|
}))
|
||||||
|
|
||||||
const renderCondition = (rule: ScriptRule, condition: ScriptRuleCondition, index: number) => {
|
const renderCondition = (rule: ScriptRule, condition: ScriptRuleCondition, index: number) => {
|
||||||
const operator = dialect.operators.find((item) => item.value === condition.operator)
|
const operator = dialect.operators.find((item) => item.value === condition.operator)
|
||||||
const kind =
|
const kind =
|
||||||
|
|
@ -401,16 +424,16 @@ function ScriptBuilderDialog({
|
||||||
<span className="mb-1 block text-xs text-gray-500">
|
<span className="mb-1 block text-xs text-gray-500">
|
||||||
{translate('::App.ScriptBuilder.Conjunction')}
|
{translate('::App.ScriptBuilder.Conjunction')}
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
className={controlClass}
|
size="sm"
|
||||||
value={rule.join ?? 'and'}
|
className="w-full"
|
||||||
onChange={(event) =>
|
menuPortalTarget={document.body}
|
||||||
updateRule(rule.id, { join: event.target.value as ScriptRule['join'] })
|
options={joinOptions}
|
||||||
|
value={joinOptions.filter((option) => option.value === (rule.join ?? 'and'))}
|
||||||
|
onChange={(option) =>
|
||||||
|
option && updateRule(rule.id, { join: option.value as ScriptRule['join'] })
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
<option value="and">{translate('::App.Platform.And')}</option>
|
|
||||||
<option value="or">{translate('::App.Platform.Or')}</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
</label>
|
||||||
) : (
|
) : (
|
||||||
<div className="col-span-12 hidden md:col-span-2 md:block">
|
<div className="col-span-12 hidden md:col-span-2 md:block">
|
||||||
|
|
@ -427,17 +450,16 @@ function ScriptBuilderDialog({
|
||||||
<span className="mb-1 block text-xs text-gray-500">
|
<span className="mb-1 block text-xs text-gray-500">
|
||||||
{translate('::App.ScriptBuilder.Comparison')}
|
{translate('::App.ScriptBuilder.Comparison')}
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
className={controlClass}
|
size="sm"
|
||||||
value={condition.operator}
|
className="w-full"
|
||||||
onChange={(event) => updateCondition(rule, index, { operator: event.target.value })}
|
menuPortalTarget={document.body}
|
||||||
>
|
options={operatorOptions}
|
||||||
{dialect.operators.map((item) => (
|
value={operatorOptions.filter((option) => option.value === condition.operator)}
|
||||||
<option key={item.value} value={item.value}>
|
onChange={(option) =>
|
||||||
{item.label.startsWith('App.') ? translate('::' + item.label) : item.label}
|
option && updateCondition(rule, index, { operator: option.value })
|
||||||
</option>
|
}
|
||||||
))}
|
/>
|
||||||
</select>
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{operator?.needsSource && dialect.conditionKinds.length > 1 && (
|
{operator?.needsSource && dialect.conditionKinds.length > 1 && (
|
||||||
|
|
@ -445,19 +467,19 @@ function ScriptBuilderDialog({
|
||||||
<span className="mb-1 block text-xs text-gray-500">
|
<span className="mb-1 block text-xs text-gray-500">
|
||||||
{translate('::App.Listform.ListformField.SourceId')}
|
{translate('::App.Listform.ListformField.SourceId')}
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
className={controlClass}
|
size="sm"
|
||||||
value={condition.kind ?? dialect.conditionKinds[0]?.value ?? ''}
|
className="w-full"
|
||||||
onChange={(event) =>
|
menuPortalTarget={document.body}
|
||||||
updateCondition(rule, index, { kind: event.target.value, source: '' })
|
options={conditionKindOptions}
|
||||||
|
value={conditionKindOptions.filter(
|
||||||
|
(option) =>
|
||||||
|
option.value === (condition.kind ?? dialect.conditionKinds[0]?.value ?? ''),
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
|
option && updateCondition(rule, index, { kind: option.value, source: '' })
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
{dialect.conditionKinds.map((item) => (
|
|
||||||
<option key={item.value} value={item.value}>
|
|
||||||
{item.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -602,17 +624,18 @@ function ScriptBuilderDialog({
|
||||||
{dialect.triggers && (
|
{dialect.triggers && (
|
||||||
<label className="w-full md:w-56">
|
<label className="w-full md:w-56">
|
||||||
<span className="mb-1 block text-xs text-gray-500">Ne zaman</span>
|
<span className="mb-1 block text-xs text-gray-500">Ne zaman</span>
|
||||||
<select
|
<Select
|
||||||
className={controlClass}
|
size="sm"
|
||||||
value={trigger ?? dialect.triggers[0].value}
|
className="w-full"
|
||||||
onChange={(event) => updateRule(rule.id, { trigger: event.target.value })}
|
menuPortalTarget={document.body}
|
||||||
>
|
options={triggerOptions}
|
||||||
{dialect.triggers.map((item) => (
|
value={triggerOptions.filter(
|
||||||
<option key={item.value} value={item.value} title={item.help}>
|
(option) => option.value === (trigger ?? dialect.triggers?.[0]?.value),
|
||||||
{item.label}
|
)}
|
||||||
</option>
|
onChange={(option) =>
|
||||||
))}
|
option && updateRule(rule.id, { trigger: option.value })
|
||||||
</select>
|
}
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,9 @@ function SelectBase<
|
||||||
placeholder: (provided) => ({ ...provided, margin: 0 }),
|
placeholder: (provided) => ({ ...provided, margin: 0 }),
|
||||||
singleValue: (provided) => ({ ...provided, margin: 0 }),
|
singleValue: (provided) => ({ ...provided, margin: 0 }),
|
||||||
menu: (provided) => ({ ...provided, zIndex: 50 }),
|
menu: (provided) => ({ ...provided, zIndex: 50 }),
|
||||||
|
// Portalled menus must clear dialogs, drawers and sticky toolbars;
|
||||||
|
// react-select's own inline zIndex of 1 would leave them behind.
|
||||||
|
menuPortal: (provided) => ({ ...provided, zIndex: 9999 }),
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
theme={(theme) => ({
|
theme={(theme) => ({
|
||||||
|
|
|
||||||
|
|
@ -999,18 +999,19 @@ const SqlDataSourceView = ({
|
||||||
{rows.length > 1 && (
|
{rows.length > 1 && (
|
||||||
<label className="flex items-center gap-1">
|
<label className="flex items-center gap-1">
|
||||||
{translate('::App.Platform.Row')}
|
{translate('::App.Platform.Row')}
|
||||||
<select
|
<UiKit.Select
|
||||||
className="rounded border border-sky-300 bg-white px-1 py-0.5 text-[10px] dark:border-sky-800 dark:bg-slate-900"
|
size="xs"
|
||||||
value={rowIndex}
|
className="min-w-[4.5rem]"
|
||||||
onChange={(event) => goToRow(Number(event.target.value) || 0)}
|
maxMenuHeight={200}
|
||||||
onClick={(event) => event.stopPropagation()}
|
menuPosition="fixed"
|
||||||
>
|
menuPortalTarget={window.document.body}
|
||||||
{rows.map((_, index) => (
|
options={rows.map((_, index) => ({
|
||||||
<option key={index} value={index}>
|
value: index,
|
||||||
{index + 1}
|
label: String(index + 1),
|
||||||
</option>
|
}))}
|
||||||
))}
|
value={{ value: rowIndex, label: String(rowIndex + 1) }}
|
||||||
</select>
|
onChange={(option) => goToRow(Number(option?.value) || 0)}
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button, Card, FormItem, Input } from '@/components/ui'
|
import { Button, Card, FormItem, Input, Select } from '@/components/ui'
|
||||||
import { ColumnFormatEditDto, ListFormFieldEditTabs } from '@/proxy/admin/list-form-field/models'
|
import { ColumnFormatEditDto, ListFormFieldEditTabs } from '@/proxy/admin/list-form-field/models'
|
||||||
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
|
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
|
||||||
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
||||||
|
|
@ -50,6 +50,10 @@ function TablePickerModal({
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
const [pickerColumns, setPickerColumns] = useState<DatabaseColumnDto[]>([])
|
const [pickerColumns, setPickerColumns] = useState<DatabaseColumnDto[]>([])
|
||||||
|
const pickerColumnOptions = pickerColumns.map((c) => ({
|
||||||
|
value: c.columnName ?? '',
|
||||||
|
label: c.columnName ?? '',
|
||||||
|
}))
|
||||||
const [isLoadingColumns, setIsLoadingColumns] = useState(false)
|
const [isLoadingColumns, setIsLoadingColumns] = useState(false)
|
||||||
const [keyCol, setKeyCol] = useState('')
|
const [keyCol, setKeyCol] = useState('')
|
||||||
const [nameCol, setNameCol] = useState('')
|
const [nameCol, setNameCol] = useState('')
|
||||||
|
|
@ -163,35 +167,31 @@ function TablePickerModal({
|
||||||
<label className="text-[11px] font-medium text-gray-500 dark:text-gray-400">
|
<label className="text-[11px] font-medium text-gray-500 dark:text-gray-400">
|
||||||
{translate('::App.ListFormFieldEdit.KeyColumn')}
|
{translate('::App.ListFormFieldEdit.KeyColumn')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={keyCol}
|
size="sm"
|
||||||
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"
|
className="w-full text-xs"
|
||||||
onChange={(e) => setKeyCol(e.target.value)}
|
isClearable
|
||||||
>
|
placeholder={translate('::App.Platform.Select')}
|
||||||
<option value="">{translate('::App.Platform.Select')}</option>
|
menuPortalTarget={document.body}
|
||||||
{pickerColumns.map((c) => (
|
options={pickerColumnOptions}
|
||||||
<option key={c.columnName} value={c.columnName}>
|
value={pickerColumnOptions.filter((option) => option.value === keyCol)}
|
||||||
{c.columnName}
|
onChange={(option) => setKeyCol(option?.value ?? '')}
|
||||||
</option>
|
/>
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<label className="text-[11px] font-medium text-gray-500 dark:text-gray-400">
|
<label className="text-[11px] font-medium text-gray-500 dark:text-gray-400">
|
||||||
{translate('::App.ListFormFieldEdit.NameColumn')}
|
{translate('::App.ListFormFieldEdit.NameColumn')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={nameCol}
|
size="sm"
|
||||||
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"
|
className="w-full text-xs"
|
||||||
onChange={(e) => setNameCol(e.target.value)}
|
isClearable
|
||||||
>
|
placeholder={translate('::App.Platform.Select')}
|
||||||
<option value="">{translate('::App.Platform.Select')}</option>
|
menuPortalTarget={document.body}
|
||||||
{pickerColumns.map((c) => (
|
options={pickerColumnOptions}
|
||||||
<option key={c.columnName} value={c.columnName}>
|
value={pickerColumnOptions.filter((option) => option.value === nameCol)}
|
||||||
{c.columnName}
|
onChange={(option) => setNameCol(option?.value ?? '')}
|
||||||
</option>
|
/>
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
{keyCol && nameCol && (
|
{keyCol && nameCol && (
|
||||||
<div className="rounded bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-[10px] font-mono text-gray-500 dark:text-gray-400 break-all">
|
<div className="rounded bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-[10px] font-mono text-gray-500 dark:text-gray-400 break-all">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button, Dialog } from '@/components/ui'
|
import { Button, Dialog, Select } from '@/components/ui'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import type { IconType } from 'react-icons'
|
import type { IconType } from 'react-icons'
|
||||||
|
|
@ -61,6 +61,7 @@ type EditorOptionsBuilderDialogProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const leafTypes: LeafValueType[] = ['string', 'number', 'boolean', 'json']
|
const leafTypes: LeafValueType[] = ['string', 'number', 'boolean', 'json']
|
||||||
|
const leafTypeOptions = leafTypes.map((item) => ({ value: item, label: item }))
|
||||||
|
|
||||||
const PRESETS_KEY = '__presets'
|
const PRESETS_KEY = '__presets'
|
||||||
const UNMANAGED_KEY = '__unmanaged'
|
const UNMANAGED_KEY = '__unmanaged'
|
||||||
|
|
@ -545,25 +546,22 @@ function EditorOptionsBuilderDialog({
|
||||||
>
|
>
|
||||||
{path}
|
{path}
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
className={`${controlClass} col-span-2`}
|
size="sm"
|
||||||
value={type}
|
className="col-span-2"
|
||||||
onChange={(event) =>
|
menuPortalTarget={document.body}
|
||||||
|
options={leafTypeOptions}
|
||||||
|
value={leafTypeOptions.filter((option) => option.value === type)}
|
||||||
|
onChange={(option) =>
|
||||||
setPath(
|
setPath(
|
||||||
path,
|
path,
|
||||||
textToLeaf(
|
textToLeaf(
|
||||||
leafToText(current),
|
leafToText(current),
|
||||||
event.target.value as LeafValueType,
|
(option?.value ?? 'string') as LeafValueType,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
{leafTypes.map((item) => (
|
|
||||||
<option key={item} value={item}>
|
|
||||||
{item}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
unstyle
|
||||||
className={`${controlClass} col-span-5`}
|
className={`${controlClass} col-span-5`}
|
||||||
|
|
@ -596,17 +594,16 @@ function EditorOptionsBuilderDialog({
|
||||||
placeholder="path.to.option"
|
placeholder="path.to.option"
|
||||||
onChange={(event) => setNewPath(event.target.value)}
|
onChange={(event) => setNewPath(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<select
|
<Select
|
||||||
className={`${controlClass} col-span-2`}
|
size="sm"
|
||||||
value={newType}
|
className="col-span-2"
|
||||||
onChange={(event) => setNewType(event.target.value as LeafValueType)}
|
menuPortalTarget={document.body}
|
||||||
>
|
options={leafTypeOptions}
|
||||||
{leafTypes.map((item) => (
|
value={leafTypeOptions.filter((option) => option.value === newType)}
|
||||||
<option key={item} value={item}>
|
onChange={(option) =>
|
||||||
{item}
|
setNewType((option?.value ?? 'string') as LeafValueType)
|
||||||
</option>
|
}
|
||||||
))}
|
/>
|
||||||
</select>
|
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
unstyle
|
||||||
className={`${controlClass} col-span-5`}
|
className={`${controlClass} col-span-5`}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { coerceNumber, coerceSize, leafToText } from './jsonUtils'
|
||||||
import type { OptionSpec } from './optionSpecs'
|
import type { OptionSpec } from './optionSpecs'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
import Select from '@/components/ui/Select'
|
||||||
|
|
||||||
export const controlClass =
|
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'
|
'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'
|
||||||
|
|
@ -64,19 +65,24 @@ const BooleanControl = ({
|
||||||
? 'false'
|
? 'false'
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
|
const booleanOptions = [
|
||||||
|
{ value: 'true', label: 'true' },
|
||||||
|
{ value: 'false', label: 'false' },
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<select
|
<Select
|
||||||
className={controlClass}
|
size="sm"
|
||||||
value={current}
|
isClearable
|
||||||
onChange={(event) => {
|
placeholder={translate('::App.ListFormEditorOptions.Undefined')}
|
||||||
if (!event.target.value) return onChange(undefined)
|
menuPortalTarget={document.body}
|
||||||
onChange(event.target.value === 'true')
|
options={booleanOptions}
|
||||||
|
value={booleanOptions.filter((option) => option.value === current)}
|
||||||
|
onChange={(option) => {
|
||||||
|
if (!option?.value) return onChange(undefined)
|
||||||
|
onChange(option.value === 'true')
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<option value="">{translate('::App.ListFormEditorOptions.Undefined')}</option>
|
|
||||||
<option value="true">true</option>
|
|
||||||
<option value="false">false</option>
|
|
||||||
</select>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,29 +99,36 @@ const SelectControl = ({
|
||||||
const current = value === undefined || value === null ? '' : String(value)
|
const current = value === undefined || value === null ? '' : String(value)
|
||||||
const known = spec.choices?.some((choice) => String(choice.value) === current)
|
const known = spec.choices?.some((choice) => String(choice.value) === current)
|
||||||
|
|
||||||
|
const choiceOptions = [
|
||||||
|
...(spec.choices ?? []).map((choice) => ({
|
||||||
|
value: String(choice.value),
|
||||||
|
label: String(choice.label),
|
||||||
|
})),
|
||||||
|
...(current && !known
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
value: current,
|
||||||
|
label: `${current} (${translate('::App.ListFormEditorOptions.CurrentValue')})`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<select
|
<Select
|
||||||
className={controlClass}
|
size="sm"
|
||||||
value={current}
|
isClearable
|
||||||
onChange={(event) => {
|
placeholder={translate('::App.ListFormEditorOptions.Undefined')}
|
||||||
const raw = event.target.value
|
menuPortalTarget={document.body}
|
||||||
|
options={choiceOptions}
|
||||||
|
value={choiceOptions.filter((option) => option.value === current)}
|
||||||
|
onChange={(option) => {
|
||||||
|
const raw = option?.value
|
||||||
if (!raw) return onChange(undefined)
|
if (!raw) return onChange(undefined)
|
||||||
const choice = spec.choices?.find((item) => String(item.value) === raw)
|
const choice = spec.choices?.find((item) => String(item.value) === raw)
|
||||||
onChange(choice ? choice.value : raw)
|
onChange(choice ? choice.value : raw)
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<option value="">{translate('::App.ListFormEditorOptions.Undefined')}</option>
|
|
||||||
{spec.choices?.map((choice) => (
|
|
||||||
<option key={String(choice.value)} value={String(choice.value)}>
|
|
||||||
{choice.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
{current && !known && (
|
|
||||||
<option value={current}>
|
|
||||||
{current} ({translate('::App.ListFormEditorOptions.CurrentValue')})
|
|
||||||
</option>
|
|
||||||
)}
|
|
||||||
</select>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export const triggerLabels: { value: RuleTrigger; label: string; help: string }[
|
||||||
label: 'App.ScriptBuilderOpen.OpenLabel',
|
label: 'App.ScriptBuilderOpen.OpenLabel',
|
||||||
help: 'App.ScriptBuilderOpen.Help',
|
help: 'App.ScriptBuilderOpen.Help',
|
||||||
},
|
},
|
||||||
{ value: 'both', label: 'Her ikisi', help: 'App.ScriptBuilderBoth.Help' },
|
{ value: 'both', label: 'App.StaticLookup.Both', help: 'App.ScriptBuilderBoth.Help' },
|
||||||
]
|
]
|
||||||
|
|
||||||
/** Ek koşullar nasıl birleşecek. */
|
/** Ek koşullar nasıl birleşecek. */
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button, Dialog } from '@/components/ui'
|
import { Button, Dialog, Select } from '@/components/ui'
|
||||||
import type { SelectBoxOption } from '@/types/shared'
|
import type { SelectBoxOption } from '@/types/shared'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import {
|
import {
|
||||||
|
|
@ -230,6 +230,10 @@ function SortableItem({
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
const [pickerColumns, setPickerColumns] = useState<DatabaseColumnDto[]>([])
|
const [pickerColumns, setPickerColumns] = useState<DatabaseColumnDto[]>([])
|
||||||
|
const pickerColumnOptions = pickerColumns.map((c) => ({
|
||||||
|
value: c.columnName ?? '',
|
||||||
|
label: c.columnName ?? '',
|
||||||
|
}))
|
||||||
const [isLoadingPickerColumns, setIsLoadingPickerColumns] = useState(false)
|
const [isLoadingPickerColumns, setIsLoadingPickerColumns] = useState(false)
|
||||||
const [pickerKeyCol, setPickerKeyCol] = useState('')
|
const [pickerKeyCol, setPickerKeyCol] = useState('')
|
||||||
const [pickerNameCol, setPickerNameCol] = useState('')
|
const [pickerNameCol, setPickerNameCol] = useState('')
|
||||||
|
|
@ -321,17 +325,17 @@ function SortableItem({
|
||||||
<span className="text-[10px] text-gray-400 font-medium">
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
{translate('::App.WizardStep3.EditorType')}
|
{translate('::App.WizardStep3.EditorType')}
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
value={item.editorType}
|
size="xs"
|
||||||
onChange={(e) => onEditorTypeChange(e.target.value)}
|
className="w-full text-xs"
|
||||||
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"
|
maxMenuHeight={200}
|
||||||
>
|
menuPortalTarget={document.body}
|
||||||
{columnEditorTypeListOptions.map((et) => (
|
options={columnEditorTypeListOptions}
|
||||||
<option key={et.value} value={et.value}>
|
value={columnEditorTypeListOptions.filter(
|
||||||
{et.label}
|
(option) => option.value === item.editorType,
|
||||||
</option>
|
)}
|
||||||
))}
|
onChange={(option) => option && onEditorTypeChange(option.value)}
|
||||||
</select>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row flex-wrap gap-1.5">
|
<div className="flex flex-row flex-wrap gap-1.5">
|
||||||
|
|
@ -339,19 +343,21 @@ function SortableItem({
|
||||||
<span className="text-[10px] text-gray-400 font-medium">
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
{translate('::App.WizardStep3.LookupDataSourceType')}
|
{translate('::App.WizardStep3.LookupDataSourceType')}
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
value={item.lookupDataSourceType}
|
size="xs"
|
||||||
onChange={(e) =>
|
className="w-full text-xs"
|
||||||
onLookupDataSourceTypeChange(e.target.value as unknown as UiLookupDataSourceTypeEnum)
|
menuPortalTarget={document.body}
|
||||||
|
options={columnLookupDataSourceTypeListOptions}
|
||||||
|
value={columnLookupDataSourceTypeListOptions.filter(
|
||||||
|
(option) => option.value === item.lookupDataSourceType,
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
|
option &&
|
||||||
|
onLookupDataSourceTypeChange(
|
||||||
|
option.value as unknown as UiLookupDataSourceTypeEnum,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
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"
|
/>
|
||||||
>
|
|
||||||
{columnLookupDataSourceTypeListOptions.map((opt) => (
|
|
||||||
<option key={opt.value} value={opt.value}>
|
|
||||||
{opt.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-0.5 min-w-[100px] flex-1">
|
<div className="flex flex-col gap-0.5 min-w-[100px] flex-1">
|
||||||
|
|
@ -514,35 +520,35 @@ function SortableItem({
|
||||||
<label className="text-[10px] font-medium text-gray-500 dark:text-gray-400">
|
<label className="text-[10px] font-medium text-gray-500 dark:text-gray-400">
|
||||||
{translate('::App.ListFormFieldEdit.KeyColumn')}
|
{translate('::App.ListFormFieldEdit.KeyColumn')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={pickerKeyCol}
|
size="xs"
|
||||||
onChange={(e) => setPickerKeyCol(e.target.value)}
|
className="w-full text-xs"
|
||||||
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"
|
isClearable
|
||||||
>
|
placeholder={translate('::App.Platform.Select')}
|
||||||
<option value="">{translate('::App.Platform.Select')}</option>
|
menuPortalTarget={document.body}
|
||||||
{pickerColumns.map((c) => (
|
options={pickerColumnOptions}
|
||||||
<option key={c.columnName} value={c.columnName}>
|
value={pickerColumnOptions.filter(
|
||||||
{c.columnName}
|
(option) => option.value === pickerKeyCol,
|
||||||
</option>
|
)}
|
||||||
))}
|
onChange={(option) => setPickerKeyCol(option?.value ?? '')}
|
||||||
</select>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<label className="text-[10px] font-medium text-gray-500 dark:text-gray-400">
|
<label className="text-[10px] font-medium text-gray-500 dark:text-gray-400">
|
||||||
{translate('::App.ListFormFieldEdit.NameColumn')}
|
{translate('::App.ListFormFieldEdit.NameColumn')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={pickerNameCol}
|
size="xs"
|
||||||
onChange={(e) => setPickerNameCol(e.target.value)}
|
className="w-full text-xs"
|
||||||
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"
|
isClearable
|
||||||
>
|
placeholder={translate('::App.Platform.Select')}
|
||||||
<option value="">{translate('::App.Platform.Select')}</option>
|
menuPortalTarget={document.body}
|
||||||
{pickerColumns.map((c) => (
|
options={pickerColumnOptions}
|
||||||
<option key={c.columnName} value={c.columnName}>
|
value={pickerColumnOptions.filter(
|
||||||
{c.columnName}
|
(option) => option.value === pickerNameCol,
|
||||||
</option>
|
)}
|
||||||
))}
|
onChange={(option) => setPickerNameCol(option?.value ?? '')}
|
||||||
</select>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{pickerKeyCol && pickerNameCol && (
|
{pickerKeyCol && pickerNameCol && (
|
||||||
<div className="rounded bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-[10px] font-mono text-gray-500 dark:text-gray-400 break-all">
|
<div className="rounded bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-[10px] font-mono text-gray-500 dark:text-gray-400 break-all">
|
||||||
|
|
@ -651,17 +657,17 @@ function SortableItem({
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span className="text-[10px] text-gray-400">{translate('::App.WizardStep3.Span')}</span>
|
<span className="text-[10px] text-gray-400">{translate('::App.WizardStep3.Span')}</span>
|
||||||
<select
|
<Select
|
||||||
value={item.colSpan}
|
size="xs"
|
||||||
onChange={(e) => onColSpanChange(Number(e.target.value))}
|
className="w-20 text-xs"
|
||||||
className="text-xs h-5 w-9 px-0.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"
|
menuPortalTarget={document.body}
|
||||||
>
|
options={Array.from({ length: groupColCount }, (_, i) => ({
|
||||||
{Array.from({ length: groupColCount }, (_, i) => i + 1).map((n) => (
|
value: i + 1,
|
||||||
<option key={n} value={n}>
|
label: String(i + 1),
|
||||||
{n}
|
}))}
|
||||||
</option>
|
value={{ value: item.colSpan, label: String(item.colSpan) }}
|
||||||
))}
|
onChange={(option) => option && onColSpanChange(Number(option.value))}
|
||||||
</select>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<label
|
<label
|
||||||
className="flex items-center gap-1 cursor-pointer ml-auto"
|
className="flex items-center gap-1 cursor-pointer ml-auto"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
import Select from '@/components/ui/Select'
|
||||||
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
|
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
|
||||||
import Container from '@/components/shared/Container'
|
import Container from '@/components/shared/Container'
|
||||||
import { Button, Checkbox, Dialog, Input, Menu, toast } from '@/components/ui'
|
import { Button, Checkbox, Dialog, Input, Menu, toast } from '@/components/ui'
|
||||||
|
|
@ -480,6 +481,10 @@ function RolesPermission({
|
||||||
const [copyDialogOpen, setCopyDialogOpen] = useState(false)
|
const [copyDialogOpen, setCopyDialogOpen] = useState(false)
|
||||||
const [copyDialogRole, setCopyDialogRole] = useState('')
|
const [copyDialogRole, setCopyDialogRole] = useState('')
|
||||||
|
|
||||||
|
const copyRoleOptions = roleList
|
||||||
|
.filter((role) => role !== name)
|
||||||
|
.map((role) => ({ value: role, label: role }))
|
||||||
|
|
||||||
// Fetch all roles for select (except current)
|
// Fetch all roles for select (except current)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchRoles() {
|
async function fetchRoles() {
|
||||||
|
|
@ -571,20 +576,16 @@ function RolesPermission({
|
||||||
>
|
>
|
||||||
<h5 className="mb-2">{translate('::AbpIdentity.Roles.CopyPermissions')}</h5>
|
<h5 className="mb-2">{translate('::AbpIdentity.Roles.CopyPermissions')}</h5>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<select
|
<Select
|
||||||
className="border rounded px-2 py-1 w-full"
|
size="sm"
|
||||||
value={copyDialogRole}
|
className="w-full"
|
||||||
onChange={(e) => setCopyDialogRole(e.target.value)}
|
isClearable
|
||||||
>
|
placeholder={translate('::App.Platform.Select')}
|
||||||
<option value="">{translate('::App.Platform.Select')}</option>
|
menuPortalTarget={document.body}
|
||||||
{roleList
|
options={copyRoleOptions}
|
||||||
.filter((role) => role !== name)
|
value={copyRoleOptions.filter((option) => option.value === copyDialogRole)}
|
||||||
.map((role) => (
|
onChange={(option) => setCopyDialogRole(option?.value ?? '')}
|
||||||
<option key={role} value={role}>
|
/>
|
||||||
{role}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
<Button variant="plain" onClick={() => setCopyDialogOpen(false)}>
|
<Button variant="plain" onClick={() => setCopyDialogOpen(false)}>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { FaTimes, FaUsers, FaUser, FaBullhorn, FaPaperPlane } from 'react-icons/
|
||||||
import { Button } from '@/components/ui'
|
import { Button } from '@/components/ui'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
import Select from '@/components/ui/Select'
|
||||||
|
|
||||||
interface ChatPanelProps {
|
interface ChatPanelProps {
|
||||||
user: { id: string; name: string; role: string }
|
user: { id: string; name: string; role: string }
|
||||||
|
|
@ -50,6 +51,11 @@ const ChatPanel = ({
|
||||||
|
|
||||||
const availableRecipients = participants.filter((p) => p.id !== user.id)
|
const availableRecipients = participants.filter((p) => p.id !== user.id)
|
||||||
|
|
||||||
|
const recipientOptions = availableRecipients.map((p) => ({
|
||||||
|
value: p.id,
|
||||||
|
label: `${p.name}${p.isTeacher ? ` (${translate('::App.VideoRoom.Teacher')})` : ''}`,
|
||||||
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full bg-white flex flex-col text-gray-900">
|
<div className="h-full bg-white flex flex-col text-gray-900">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|
@ -125,21 +131,18 @@ const ChatPanel = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{messageMode === 'private' && (
|
{messageMode === 'private' && (
|
||||||
<select
|
<Select
|
||||||
value={selectedRecipient?.id || ''}
|
size="xs"
|
||||||
onChange={(e) => {
|
className="w-full"
|
||||||
const recipient = availableRecipients.find((p) => p.id === e.target.value)
|
isClearable
|
||||||
|
placeholder={translate('::App.VideoRoom.SelectPerson')}
|
||||||
|
options={recipientOptions}
|
||||||
|
value={recipientOptions.filter((o) => o.value === (selectedRecipient?.id ?? ''))}
|
||||||
|
onChange={(option) => {
|
||||||
|
const recipient = availableRecipients.find((p) => p.id === option?.value)
|
||||||
setSelectedRecipient(recipient ? { id: recipient.id, name: recipient.name } : null)
|
setSelectedRecipient(recipient ? { id: recipient.id, name: recipient.name } : null)
|
||||||
}}
|
}}
|
||||||
className="w-full px-2 py-1 text-xs border border-gray-300 rounded"
|
/>
|
||||||
>
|
|
||||||
<option value="">{translate('::App.VideoRoom.SelectPerson')}</option>
|
|
||||||
{availableRecipients.map((p) => (
|
|
||||||
<option key={p.id} value={p.id}>
|
|
||||||
{p.name} {p.isTeacher ? `(${translate('::App.VideoRoom.Teacher')})` : ''}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import PageTitle from '@/components/shared/PageTitle'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { VideoroomDto } from '@/proxy/videoroom/models'
|
import { VideoroomDto } from '@/proxy/videoroom/models'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { Button, Dialog, Input } from '@/components/ui'
|
import { Button, Dialog, Input, Select } from '@/components/ui'
|
||||||
import { FcVideoCall } from 'react-icons/fc'
|
import { FcVideoCall } from 'react-icons/fc'
|
||||||
|
|
||||||
export interface RoomProps {
|
export interface RoomProps {
|
||||||
|
|
@ -45,6 +45,21 @@ const RoomList = () => {
|
||||||
const { user } = useStoreState((state) => state.auth)
|
const { user } = useStoreState((state) => state.auth)
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
|
const microphoneStateOptions = [
|
||||||
|
{ value: 'muted' as const, label: translate('::App.VideoRoom.MicrophoneMuted') },
|
||||||
|
{ value: 'unmuted' as const, label: translate('::App.VideoRoom.MicrophoneUnmuted') },
|
||||||
|
]
|
||||||
|
const cameraStateOptions = [
|
||||||
|
{ value: 'on' as const, label: translate('::App.VideoRoom.CameraOn') },
|
||||||
|
{ value: 'off' as const, label: translate('::App.VideoRoom.CameraOff') },
|
||||||
|
]
|
||||||
|
const layoutOptions = [
|
||||||
|
{ value: 'grid', label: translate('::App.VideoRoom.LayoutGridView') },
|
||||||
|
{ value: 'teacher-focus', label: translate('::App.VideoRoom.LayoutTeacherFocus') },
|
||||||
|
{ value: 'presentation', label: translate('::App.VideoRoom.LayoutPresentation') },
|
||||||
|
{ value: 'sidebar', label: translate('::App.VideoRoom.LayoutSidebar') },
|
||||||
|
]
|
||||||
|
|
||||||
const newClassEntity: VideoroomDto = {
|
const newClassEntity: VideoroomDto = {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -733,80 +748,70 @@ const RoomList = () => {
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||||
{translate('::App.VideoRoom.DefaultMicrophoneState')}
|
{translate('::App.VideoRoom.DefaultMicrophoneState')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={videoroom.settingsDto?.defaultMicrophoneState}
|
size="sm"
|
||||||
onChange={(e) =>
|
menuPortalTarget={document.body}
|
||||||
|
options={microphoneStateOptions}
|
||||||
|
value={microphoneStateOptions.filter(
|
||||||
|
(option) =>
|
||||||
|
option.value === videoroom.settingsDto?.defaultMicrophoneState,
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
setVideoroom({
|
setVideoroom({
|
||||||
...videoroom,
|
...videoroom,
|
||||||
settingsDto: {
|
settingsDto: {
|
||||||
...videoroom.settingsDto!,
|
...videoroom.settingsDto!,
|
||||||
defaultMicrophoneState: e.target.value as 'muted' | 'unmuted',
|
defaultMicrophoneState: option?.value ?? 'muted',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
/>
|
||||||
>
|
|
||||||
<option value="muted">
|
|
||||||
{translate('::App.VideoRoom.MicrophoneMuted')}
|
|
||||||
</option>
|
|
||||||
<option value="unmuted">
|
|
||||||
{translate('::App.VideoRoom.MicrophoneUnmuted')}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||||
{translate('::App.VideoRoom.DefaultCameraState')}
|
{translate('::App.VideoRoom.DefaultCameraState')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={videoroom.settingsDto?.defaultCameraState}
|
size="sm"
|
||||||
onChange={(e) =>
|
menuPortalTarget={document.body}
|
||||||
|
options={cameraStateOptions}
|
||||||
|
value={cameraStateOptions.filter(
|
||||||
|
(option) => option.value === videoroom.settingsDto?.defaultCameraState,
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
setVideoroom({
|
setVideoroom({
|
||||||
...videoroom,
|
...videoroom,
|
||||||
settingsDto: {
|
settingsDto: {
|
||||||
...videoroom.settingsDto!,
|
...videoroom.settingsDto!,
|
||||||
defaultCameraState: e.target.value as 'on' | 'off',
|
defaultCameraState: option?.value ?? 'on',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
/>
|
||||||
>
|
|
||||||
<option value="on">{translate('::App.VideoRoom.CameraOn')}</option>
|
|
||||||
<option value="off">{translate('::App.VideoRoom.CameraOff')}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||||
{translate('::App.VideoRoom.DefaultLayout')}
|
{translate('::App.VideoRoom.DefaultLayout')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={videoroom.settingsDto?.defaultLayout}
|
size="sm"
|
||||||
onChange={(e) =>
|
menuPortalTarget={document.body}
|
||||||
|
options={layoutOptions}
|
||||||
|
value={layoutOptions.filter(
|
||||||
|
(option) => option.value === videoroom.settingsDto?.defaultLayout,
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
setVideoroom({
|
setVideoroom({
|
||||||
...videoroom,
|
...videoroom,
|
||||||
settingsDto: {
|
settingsDto: {
|
||||||
...videoroom.settingsDto!,
|
...videoroom.settingsDto!,
|
||||||
defaultLayout: e.target.value,
|
defaultLayout: option?.value ?? 'grid',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
/>
|
||||||
>
|
|
||||||
<option value="grid">
|
|
||||||
{translate('::App.VideoRoom.LayoutGridView')}
|
|
||||||
</option>
|
|
||||||
<option value="teacher-focus">
|
|
||||||
{translate('::App.VideoRoom.LayoutTeacherFocus')}
|
|
||||||
</option>
|
|
||||||
<option value="presentation">
|
|
||||||
{translate('::App.VideoRoom.LayoutPresentation')}
|
|
||||||
</option>
|
|
||||||
<option value="sidebar">
|
|
||||||
{translate('::App.VideoRoom.LayoutSidebar')}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import { parseComponentDependencies } from '@/contexts/componentRuntime'
|
||||||
import { usePermission } from '@/utils/hooks/usePermission'
|
import { usePermission } from '@/utils/hooks/usePermission'
|
||||||
import { COMPONENT_PERMISSION } from '@/constants/permission.constant'
|
import { COMPONENT_PERMISSION } from '@/constants/permission.constant'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
import Select from '@/components/ui/Select'
|
||||||
|
|
||||||
const ComponentManager: React.FC = () => {
|
const ComponentManager: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -45,6 +46,12 @@ const ComponentManager: React.FC = () => {
|
||||||
const activeComponents = components?.filter((c) => c.isActive).length || 0
|
const activeComponents = components?.filter((c) => c.isActive).length || 0
|
||||||
const inactiveComponents = totalComponents - activeComponents
|
const inactiveComponents = totalComponents - activeComponents
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
|
const filterActiveOptions = [
|
||||||
|
{ value: 'all' as const, label: translate('::App.ComponentFilter.All') },
|
||||||
|
{ value: 'active' as const, label: translate('::App.EntityFilter.Active') },
|
||||||
|
{ value: 'inactive' as const, label: translate('::App.EntityFilter.Inactive') },
|
||||||
|
]
|
||||||
const { checkPermission } = usePermission()
|
const { checkPermission } = usePermission()
|
||||||
const canCreate = checkPermission(COMPONENT_PERMISSION.CREATE)
|
const canCreate = checkPermission(COMPONENT_PERMISSION.CREATE)
|
||||||
const canUpdate = checkPermission(COMPONENT_PERMISSION.UPDATE)
|
const canUpdate = checkPermission(COMPONENT_PERMISSION.UPDATE)
|
||||||
|
|
@ -143,19 +150,13 @@ const ComponentManager: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FaFilter className="w-5 h-5 text-slate-500 dark:text-gray-400" />
|
<FaFilter className="w-5 h-5 text-slate-500 dark:text-gray-400" />
|
||||||
<select
|
<Select
|
||||||
value={filterActive}
|
size="sm"
|
||||||
className="px-2 py-1 border border-slate-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="min-w-[9rem]"
|
||||||
onChange={(e) => setFilterActive(e.target.value as 'all' | 'active' | 'inactive')}
|
options={filterActiveOptions}
|
||||||
>
|
value={filterActiveOptions.filter((o) => o.value === filterActive)}
|
||||||
<option value="all">{translate('::App.ComponentFilter.All')}</option>
|
onChange={(option) => setFilterActive(option?.value ?? 'all')}
|
||||||
<option value="active">
|
/>
|
||||||
{translate('::App.EntityFilter.Active')}
|
|
||||||
</option>
|
|
||||||
<option value="inactive">
|
|
||||||
{translate('::App.EntityFilter.Inactive')}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,20 @@ import PageTitle from '@/components/shared/PageTitle'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import Button from '@/components/ui/Button'
|
import Button from '@/components/ui/Button'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
import Select from '@/components/ui/Select'
|
||||||
|
|
||||||
const DynamicServiceManager: React.FC = () => {
|
const DynamicServiceManager: React.FC = () => {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
|
const filterStatusOptions = [
|
||||||
|
{ value: 'all' as const, label: translate('::App.StaticLookup.All') },
|
||||||
|
{ value: 'Success' as const, label: translate('::App.Platform.Success') },
|
||||||
|
{ value: 'Failed' as const, label: translate('::App.Platform.Failed') },
|
||||||
|
{
|
||||||
|
value: 'Pending' as const,
|
||||||
|
label: translate('::App.DeveloperKitDynamicServices.FilterPending'),
|
||||||
|
},
|
||||||
|
]
|
||||||
const [services, setServices] = useState<DynamicServiceDto[]>([])
|
const [services, setServices] = useState<DynamicServiceDto[]>([])
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
|
|
@ -140,22 +151,12 @@ const DynamicServiceManager: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 w-full lg:w-auto">
|
<div className="flex items-center gap-2 w-full lg:w-auto">
|
||||||
<FaFilter className="w-4 h-4 text-slate-500 dark:text-gray-400" />
|
<FaFilter className="w-4 h-4 text-slate-500 dark:text-gray-400" />
|
||||||
<select
|
<Select
|
||||||
value={filterStatus}
|
className="w-full lg:w-48"
|
||||||
onChange={(e) =>
|
options={filterStatusOptions}
|
||||||
setFilterStatus(e.target.value as 'all' | 'Success' | 'Failed' | 'Pending')
|
value={filterStatusOptions.filter((o) => o.value === filterStatus)}
|
||||||
}
|
onChange={(option) => setFilterStatus(option?.value ?? 'all')}
|
||||||
className="w-full lg:w-auto px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
/>
|
||||||
>
|
|
||||||
<option value="all">{translate('::App.StaticLookup.All')}</option>
|
|
||||||
<option value="Success">
|
|
||||||
{translate('::App.Platform.Success')}
|
|
||||||
</option>
|
|
||||||
<option value="Failed">{translate('::App.Platform.Failed')}</option>
|
|
||||||
<option value="Pending">
|
|
||||||
{translate('::App.DeveloperKitDynamicServices.FilterPending')}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full sm:w-auto">
|
<div className="w-full sm:w-auto">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
FaTable,
|
FaTable,
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaEye,
|
FaEye,
|
||||||
|
FaProjectDiagram,
|
||||||
FaCog,
|
FaCog,
|
||||||
FaCode,
|
FaCode,
|
||||||
FaDatabase,
|
FaDatabase,
|
||||||
|
|
@ -748,7 +749,7 @@ const SqlObjectExplorer = ({
|
||||||
closeCtx()
|
closeCtx()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FaEye className="text-purple-500" /> Design View
|
<FaProjectDiagram className="text-purple-500" /> Design View
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { lazy, Suspense, useState, useCallback, useEffect, useMemo, useRef } from 'react'
|
import { lazy, Suspense, useState, useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
import { Button, Dialog, Notification, toast } from '@/components/ui'
|
import { Button, Dialog, Notification, Select, toast } from '@/components/ui'
|
||||||
import Container from '@/components/shared/Container'
|
import Container from '@/components/shared/Container'
|
||||||
import { getDataSources } from '@/services/data-source.service'
|
import { getDataSources } from '@/services/data-source.service'
|
||||||
import type { DataSourceDto } from '@/proxy/data-source'
|
import type { DataSourceDto } from '@/proxy/data-source'
|
||||||
|
|
@ -272,6 +272,11 @@ const SqlQueryManager = () => {
|
||||||
`[${escapeSqlIdentifier(schemaName)}].[${escapeSqlIdentifier(objectName)}]`
|
`[${escapeSqlIdentifier(schemaName)}].[${escapeSqlIdentifier(objectName)}]`
|
||||||
const getSafePgFullName = (schemaName: string, objectName: string) =>
|
const getSafePgFullName = (schemaName: string, objectName: string) =>
|
||||||
`"${escapePgIdentifier(schemaName)}"."${escapePgIdentifier(objectName)}"`
|
`"${escapePgIdentifier(schemaName)}"."${escapePgIdentifier(objectName)}"`
|
||||||
|
const dataSourceOptions = state.dataSources.map((ds) => ({
|
||||||
|
value: ds.code ?? '',
|
||||||
|
label: ds.code ?? '',
|
||||||
|
}))
|
||||||
|
|
||||||
const selectedDataSourceType = state.dataSources.find(
|
const selectedDataSourceType = state.dataSources.find(
|
||||||
(item) => item.code === state.selectedDataSource,
|
(item) => item.code === state.selectedDataSource,
|
||||||
)?.dataSourceType
|
)?.dataSourceType
|
||||||
|
|
@ -1086,21 +1091,20 @@ GO`,
|
||||||
<div className="flex flex-col gap-2 px-1 py-1 lg:flex-row lg:items-center lg:justify-between">
|
<div className="flex flex-col gap-2 px-1 py-1 lg:flex-row lg:items-center lg:justify-between">
|
||||||
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
||||||
<FaDatabase className="text-lg text-blue-500" />
|
<FaDatabase className="text-lg text-blue-500" />
|
||||||
<select
|
<Select
|
||||||
className="border border-gray-300 rounded px-2 py-1 max-w-full dark:bg-gray-700 dark:border-gray-600"
|
size="sm"
|
||||||
disabled={state.dataSources.length === 0}
|
className="min-w-[12rem] max-w-full"
|
||||||
value={state.selectedDataSource || ''}
|
isDisabled={state.dataSources.length === 0}
|
||||||
onChange={(e) => {
|
menuPortalTarget={document.body}
|
||||||
const ds = state.dataSources.find((d) => d.code === e.target.value)
|
options={dataSourceOptions}
|
||||||
|
value={dataSourceOptions.filter(
|
||||||
|
(option) => option.value === (state.selectedDataSource || ''),
|
||||||
|
)}
|
||||||
|
onChange={(option) => {
|
||||||
|
const ds = state.dataSources.find((d) => d.code === option?.value)
|
||||||
if (ds) handleDataSourceChange(ds)
|
if (ds) handleDataSourceChange(ds)
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
{state.dataSources.map((ds) => (
|
|
||||||
<option key={ds.code} value={ds.code}>
|
|
||||||
{ds.code}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<DbMigrateButton />
|
<DbMigrateButton />
|
||||||
{/* Seed dosyalari (configs/seeds) File Manager uzerinden yonetilir. */}
|
{/* Seed dosyalari (configs/seeds) File Manager uzerinden yonetilir. */}
|
||||||
<Link to={ROUTES_ENUM.protected.admin.files} target="_blank">
|
<Link to={ROUTES_ENUM.protected.admin.files} target="_blank">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react'
|
import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { Button, Dialog, Notification, toast, Checkbox } from '@/components/ui'
|
import { Button, Checkbox, Dialog, Notification, Select, toast } from '@/components/ui'
|
||||||
import {
|
import {
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaTrash,
|
FaTrash,
|
||||||
|
|
@ -1336,6 +1336,28 @@ const SqlTableDesignerDialog = ({
|
||||||
const [dbTables, setDbTables] = useState<{ schemaName: string; tableName: string }[]>([])
|
const [dbTables, setDbTables] = useState<{ schemaName: string; tableName: string }[]>([])
|
||||||
const [targetTableColumns, setTargetTableColumns] = useState<string[]>([])
|
const [targetTableColumns, setTargetTableColumns] = useState<string[]>([])
|
||||||
const [targetTableKeyColumns, setTargetTableKeyColumns] = useState<string[]>([])
|
const [targetTableKeyColumns, setTargetTableKeyColumns] = useState<string[]>([])
|
||||||
|
|
||||||
|
const dataTypeOptions = DATA_TYPES
|
||||||
|
const cascadeOptions = CASCADE_OPTIONS
|
||||||
|
const indexOrderOptions: { value: 'ASC' | 'DESC'; label: string }[] = [
|
||||||
|
{ value: 'ASC', label: 'ASC' },
|
||||||
|
{ value: 'DESC', label: 'DESC' },
|
||||||
|
]
|
||||||
|
const fkColumnOptions = columns
|
||||||
|
.filter((c) => c.columnName.trim())
|
||||||
|
.map((c) => ({ value: c.columnName, label: c.columnName }))
|
||||||
|
const referencedTableOptions = dbTables.map((t) => ({
|
||||||
|
value: t.tableName,
|
||||||
|
label: t.tableName,
|
||||||
|
}))
|
||||||
|
const referencedColumnOptions = targetTableColumns.map((col) => {
|
||||||
|
const isKey = targetTableKeyColumns.some((k) => k.toLowerCase() === col.toLowerCase())
|
||||||
|
return {
|
||||||
|
value: col,
|
||||||
|
label: isKey ? `${col} (PK/UNIQUE)` : `${col} (Not Key)`,
|
||||||
|
isDisabled: !isKey,
|
||||||
|
}
|
||||||
|
})
|
||||||
const [targetColsLoading, setTargetColsLoading] = useState(false)
|
const [targetColsLoading, setTargetColsLoading] = useState(false)
|
||||||
const [indexes, setIndexes] = useState<TableIndex[]>([])
|
const [indexes, setIndexes] = useState<TableIndex[]>([])
|
||||||
const [originalIndexes, setOriginalIndexes] = useState<TableIndex[]>([])
|
const [originalIndexes, setOriginalIndexes] = useState<TableIndex[]>([])
|
||||||
|
|
@ -2374,11 +2396,9 @@ const SqlTableDesignerDialog = ({
|
||||||
>
|
>
|
||||||
<div className="col-span-4">
|
<div className="col-span-4">
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
size="sm"
|
||||||
type="text"
|
type="text"
|
||||||
className={`w-full px-2 py-1 text-sm border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white ${
|
className={isDuplicate ? 'w-full border-red-400' : 'w-full'}
|
||||||
isDuplicate ? 'border-red-400' : ''
|
|
||||||
}`}
|
|
||||||
placeholder={translate('::App.SqlQueryManager.ColumnNamePlaceholder')}
|
placeholder={translate('::App.SqlQueryManager.ColumnNamePlaceholder')}
|
||||||
value={col.columnName}
|
value={col.columnName}
|
||||||
onChange={(e) => updateColumn(col.id, 'columnName', e.target.value)}
|
onChange={(e) => updateColumn(col.id, 'columnName', e.target.value)}
|
||||||
|
|
@ -2391,28 +2411,27 @@ const SqlTableDesignerDialog = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-3">
|
<div className="col-span-3">
|
||||||
<select
|
<Select
|
||||||
className="w-full px-2 py-1 text-sm border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
size="sm"
|
||||||
value={col.dataType}
|
className="w-full"
|
||||||
onChange={(e) => {
|
maxMenuHeight={200}
|
||||||
const dt = e.target.value as SqlDataType
|
menuPortalTarget={document.body}
|
||||||
|
options={dataTypeOptions}
|
||||||
|
value={dataTypeOptions.filter((option) => option.value === col.dataType)}
|
||||||
|
onChange={(option) => {
|
||||||
|
if (!option) return
|
||||||
|
const dt = option.value as SqlDataType
|
||||||
updateColumn(col.id, 'dataType', dt)
|
updateColumn(col.id, 'dataType', dt)
|
||||||
if (dt !== 'nvarchar') updateColumn(col.id, 'maxLength', '')
|
if (dt !== 'nvarchar') updateColumn(col.id, 'maxLength', '')
|
||||||
else if (!col.maxLength) updateColumn(col.id, 'maxLength', '100')
|
else if (!col.maxLength) updateColumn(col.id, 'maxLength', '100')
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
{DATA_TYPES.map((t) => (
|
|
||||||
<option key={t.value} value={t.value}>
|
|
||||||
{t.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
size="sm"
|
||||||
type="number"
|
type="number"
|
||||||
className="w-full px-2 py-1 text-sm border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white text-center"
|
className="w-full text-center"
|
||||||
placeholder="-"
|
placeholder="-"
|
||||||
value={col.maxLength}
|
value={col.maxLength}
|
||||||
disabled={col.dataType !== 'nvarchar'}
|
disabled={col.dataType !== 'nvarchar'}
|
||||||
|
|
@ -2427,9 +2446,9 @@ const SqlTableDesignerDialog = ({
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
size="sm"
|
||||||
type="text"
|
type="text"
|
||||||
className="w-full px-2 py-1 text-sm border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
className="w-full"
|
||||||
placeholder={translate('::Abp.Mailing.Default')}
|
placeholder={translate('::Abp.Mailing.Default')}
|
||||||
value={col.defaultValue}
|
value={col.defaultValue}
|
||||||
onChange={(e) => updateColumn(col.id, 'defaultValue', e.target.value)}
|
onChange={(e) => updateColumn(col.id, 'defaultValue', e.target.value)}
|
||||||
|
|
@ -2558,10 +2577,10 @@ const SqlTableDesignerDialog = ({
|
||||||
{translate('::App.SqlQueryManager.EntityName')} <span className="text-red-500">*</span>
|
{translate('::App.SqlQueryManager.EntityName')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
size="sm"
|
||||||
type="text"
|
type="text"
|
||||||
disabled={isEditMode} // Entity name (and thus table name) cannot be changed in edit mode
|
disabled={isEditMode} // Entity name (and thus table name) cannot be changed in edit mode
|
||||||
className="w-full px-3 py-2 text-sm border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
className="w-full"
|
||||||
value={settings.entityName}
|
value={settings.entityName}
|
||||||
onChange={(e) => onEntityNameChange(e.target.value)}
|
onChange={(e) => onEntityNameChange(e.target.value)}
|
||||||
placeholder={translate('::App.SqlQueryManager.EntityNamePlaceholder')}
|
placeholder={translate('::App.SqlQueryManager.EntityNamePlaceholder')}
|
||||||
|
|
@ -2574,10 +2593,10 @@ const SqlTableDesignerDialog = ({
|
||||||
{translate('::App.Listform.ListformField.TableName')}
|
{translate('::App.Listform.ListformField.TableName')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
size="sm"
|
||||||
type="text"
|
type="text"
|
||||||
readOnly
|
readOnly
|
||||||
className="w-full px-3 py-2 text-sm border rounded bg-gray-50 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 cursor-not-allowed"
|
className="w-full bg-gray-50 cursor-not-allowed"
|
||||||
value={isEditMode ? (initialTableData?.tableName ?? '') : settings.tableName}
|
value={isEditMode ? (initialTableData?.tableName ?? '') : settings.tableName}
|
||||||
placeholder={translate('::App.SqlQueryManager.TableNameAutoGenerated')}
|
placeholder={translate('::App.SqlQueryManager.TableNameAutoGenerated')}
|
||||||
/>
|
/>
|
||||||
|
|
@ -2760,41 +2779,41 @@ const SqlTableDesignerDialog = ({
|
||||||
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
|
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
|
||||||
{translate('::App.SqlQueryManager.FkColumnInThisTable')}
|
{translate('::App.SqlQueryManager.FkColumnInThisTable')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={fkForm.fkColumnName}
|
size="sm"
|
||||||
onChange={(e) => setFkForm((f) => ({ ...f, fkColumnName: e.target.value }))}
|
className="w-full"
|
||||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm dark:bg-gray-700 dark:text-white focus:ring-2 focus:ring-indigo-500"
|
isClearable
|
||||||
>
|
placeholder={translate('::App.Platform.Select')}
|
||||||
<option value="">{translate('::App.Platform.Select')}</option>
|
menuPortalTarget={document.body}
|
||||||
{columns
|
options={fkColumnOptions}
|
||||||
.filter((c) => c.columnName.trim())
|
value={fkColumnOptions.filter(
|
||||||
.map((c) => (
|
(option) => option.value === fkForm.fkColumnName,
|
||||||
<option key={c.id} value={c.columnName}>
|
)}
|
||||||
{c.columnName}
|
onChange={(option) =>
|
||||||
</option>
|
setFkForm((f) => ({ ...f, fkColumnName: option?.value ?? '' }))
|
||||||
))}
|
}
|
||||||
</select>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
|
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
|
||||||
{translate('::App.SqlQueryManager.TargetTable')}
|
{translate('::App.SqlQueryManager.TargetTable')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={fkForm.referencedTable}
|
size="sm"
|
||||||
onChange={(e) => {
|
className="w-full"
|
||||||
const val = e.target.value
|
isClearable
|
||||||
|
placeholder={translate('::App.Platform.Select')}
|
||||||
|
menuPortalTarget={document.body}
|
||||||
|
options={referencedTableOptions}
|
||||||
|
value={referencedTableOptions.filter(
|
||||||
|
(option) => option.value === fkForm.referencedTable,
|
||||||
|
)}
|
||||||
|
onChange={(option) => {
|
||||||
|
const val = option?.value ?? ''
|
||||||
setFkForm((f) => ({ ...f, referencedTable: val, referencedColumn: '' }))
|
setFkForm((f) => ({ ...f, referencedTable: val, referencedColumn: '' }))
|
||||||
loadTargetColumns(val)
|
loadTargetColumns(val)
|
||||||
}}
|
}}
|
||||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm dark:bg-gray-700 dark:text-white focus:ring-2 focus:ring-indigo-500"
|
/>
|
||||||
>
|
|
||||||
<option value="">{translate('::App.Platform.Select')}</option>
|
|
||||||
{dbTables.map((t) => (
|
|
||||||
<option key={`${t.schemaName}.${t.tableName}`} value={t.tableName}>
|
|
||||||
{t.tableName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -2805,29 +2824,21 @@ const SqlTableDesignerDialog = ({
|
||||||
? ` — ${translate('::App.Platform.LoadingWithThreeDot')}`
|
? ` — ${translate('::App.Platform.LoadingWithThreeDot')}`
|
||||||
: ''}
|
: ''}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={fkForm.referencedColumn}
|
size="sm"
|
||||||
onChange={(e) => setFkForm((f) => ({ ...f, referencedColumn: e.target.value }))}
|
className="w-full"
|
||||||
disabled={targetColsLoading || targetTableColumns.length === 0}
|
isClearable
|
||||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm dark:bg-gray-700 dark:text-white focus:ring-2 focus:ring-indigo-500 disabled:opacity-60"
|
isDisabled={targetColsLoading || targetTableColumns.length === 0}
|
||||||
>
|
placeholder={translate('::App.SqlQueryManager.SelectTargetTableFirst')}
|
||||||
<option value="">
|
menuPortalTarget={document.body}
|
||||||
{translate('::App.SqlQueryManager.SelectTargetTableFirst')}
|
options={referencedColumnOptions}
|
||||||
</option>
|
value={referencedColumnOptions.filter(
|
||||||
{targetTableColumns.map((col) => (
|
(option) => option.value === fkForm.referencedColumn,
|
||||||
<option
|
)}
|
||||||
key={col}
|
onChange={(option) =>
|
||||||
value={col}
|
setFkForm((f) => ({ ...f, referencedColumn: option?.value ?? '' }))
|
||||||
disabled={
|
}
|
||||||
!targetTableKeyColumns.some((k) => k.toLowerCase() === col.toLowerCase())
|
/>
|
||||||
}
|
|
||||||
>
|
|
||||||
{targetTableKeyColumns.some((k) => k.toLowerCase() === col.toLowerCase())
|
|
||||||
? `${col} (PK/UNIQUE)`
|
|
||||||
: `${col} (Not Key)`}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{fkForm.referencedTable &&
|
{fkForm.referencedTable &&
|
||||||
!targetColsLoading &&
|
!targetColsLoading &&
|
||||||
targetTableKeyColumns.length === 0 && (
|
targetTableKeyColumns.length === 0 && (
|
||||||
|
|
@ -2843,43 +2854,43 @@ const SqlTableDesignerDialog = ({
|
||||||
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
|
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
|
||||||
{translate('::App.SqlQueryManager.CascadeUpdate')}
|
{translate('::App.SqlQueryManager.CascadeUpdate')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={fkForm.cascadeUpdate}
|
size="sm"
|
||||||
onChange={(e) =>
|
className="w-full"
|
||||||
|
menuPortalTarget={document.body}
|
||||||
|
options={cascadeOptions}
|
||||||
|
value={cascadeOptions.filter(
|
||||||
|
(option) => option.value === fkForm.cascadeUpdate,
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
|
option &&
|
||||||
setFkForm((f) => ({
|
setFkForm((f) => ({
|
||||||
...f,
|
...f,
|
||||||
cascadeUpdate: e.target.value as CascadeBehavior,
|
cascadeUpdate: option.value as CascadeBehavior,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm dark:bg-gray-700 dark:text-white focus:ring-2 focus:ring-indigo-500"
|
/>
|
||||||
>
|
|
||||||
{CASCADE_OPTIONS.map((o) => (
|
|
||||||
<option key={o.value} value={o.value}>
|
|
||||||
{o.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
|
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
|
||||||
{translate('::App.SqlQueryManager.CascadeDelete')}
|
{translate('::App.SqlQueryManager.CascadeDelete')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<Select
|
||||||
value={fkForm.cascadeDelete}
|
size="sm"
|
||||||
onChange={(e) =>
|
className="w-full"
|
||||||
|
menuPortalTarget={document.body}
|
||||||
|
options={cascadeOptions}
|
||||||
|
value={cascadeOptions.filter(
|
||||||
|
(option) => option.value === fkForm.cascadeDelete,
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
|
option &&
|
||||||
setFkForm((f) => ({
|
setFkForm((f) => ({
|
||||||
...f,
|
...f,
|
||||||
cascadeDelete: e.target.value as CascadeBehavior,
|
cascadeDelete: option.value as CascadeBehavior,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm dark:bg-gray-700 dark:text-white focus:ring-2 focus:ring-indigo-500"
|
/>
|
||||||
>
|
|
||||||
{CASCADE_OPTIONS.map((o) => (
|
|
||||||
<option key={o.value} value={o.value}>
|
|
||||||
{o.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -2887,11 +2898,11 @@ const SqlTableDesignerDialog = ({
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<label className="flex items-center gap-2 cursor-pointer">
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
size="sm"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={fkForm.isRequired}
|
checked={fkForm.isRequired}
|
||||||
onChange={(e) => setFkForm((f) => ({ ...f, isRequired: e.target.checked }))}
|
onChange={(e) => setFkForm((f) => ({ ...f, isRequired: e.target.checked }))}
|
||||||
className="w-4 h-4 text-indigo-600 rounded"
|
className="w-4 text-indigo-600"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-700 dark:text-gray-300">
|
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
{translate('::App.Listform.ListformField.Required')}
|
{translate('::App.Listform.ListformField.Required')}
|
||||||
|
|
@ -3135,14 +3146,14 @@ const SqlTableDesignerDialog = ({
|
||||||
{translate('::App.SqlQueryManager.IndexConstraintName')}
|
{translate('::App.SqlQueryManager.IndexConstraintName')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
size="sm"
|
||||||
type="text"
|
type="text"
|
||||||
value={indexForm.indexName}
|
value={indexForm.indexName}
|
||||||
onChange={(e) => setIndexForm((f) => ({ ...f, indexName: e.target.value }))}
|
onChange={(e) => setIndexForm((f) => ({ ...f, indexName: e.target.value }))}
|
||||||
placeholder={
|
placeholder={
|
||||||
buildIndexName(indexForm.indexType, indexForm.columns) || `PK_EntityName_Id`
|
buildIndexName(indexForm.indexType, indexForm.columns) || `PK_EntityName_Id`
|
||||||
}
|
}
|
||||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm dark:bg-gray-700 dark:text-white focus:ring-2 focus:ring-indigo-500"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -3150,13 +3161,13 @@ const SqlTableDesignerDialog = ({
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<label className="flex items-center gap-2 cursor-pointer">
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
size="sm"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={indexForm.isClustered}
|
checked={indexForm.isClustered}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setIndexForm((f) => ({ ...f, isClustered: e.target.checked }))
|
setIndexForm((f) => ({ ...f, isClustered: e.target.checked }))
|
||||||
}
|
}
|
||||||
className="w-4 h-4 text-indigo-600 rounded"
|
className="w-4 text-indigo-600"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-700 dark:text-gray-300">Clustered</span>
|
<span className="text-sm text-gray-700 dark:text-gray-300">Clustered</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -3191,7 +3202,7 @@ const SqlTableDesignerDialog = ({
|
||||||
>
|
>
|
||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
size="sm"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={!!existing}
|
checked={!!existing}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|
@ -3220,7 +3231,7 @@ const SqlTableDesignerDialog = ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="w-4 h-4 text-indigo-600 rounded"
|
className="w-4 text-indigo-600"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-7 text-sm font-mono text-gray-800 dark:text-gray-200">
|
<div className="col-span-7 text-sm font-mono text-gray-800 dark:text-gray-200">
|
||||||
|
|
@ -3228,23 +3239,26 @@ const SqlTableDesignerDialog = ({
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4">
|
<div className="col-span-4">
|
||||||
{existing && (
|
{existing && (
|
||||||
<select
|
<Select
|
||||||
value={existing.order}
|
size="sm"
|
||||||
onChange={(e) =>
|
className="w-full"
|
||||||
|
menuPortalTarget={document.body}
|
||||||
|
options={indexOrderOptions}
|
||||||
|
value={indexOrderOptions.filter(
|
||||||
|
(option) => option.value === existing.order,
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
|
option &&
|
||||||
setIndexForm((f) => ({
|
setIndexForm((f) => ({
|
||||||
...f,
|
...f,
|
||||||
columns: f.columns.map((ic) =>
|
columns: f.columns.map((ic) =>
|
||||||
ic.columnName === col.columnName
|
ic.columnName === col.columnName
|
||||||
? { ...ic, order: e.target.value as 'ASC' | 'DESC' }
|
? { ...ic, order: option.value }
|
||||||
: ic,
|
: ic,
|
||||||
),
|
),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
className="w-full px-2 py-0.5 text-xs border border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700 dark:text-white"
|
/>
|
||||||
>
|
|
||||||
<option value="ASC">ASC</option>
|
|
||||||
<option value="DESC">DESC</option>
|
|
||||||
</select>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -612,7 +612,7 @@ const SqlViewDesignerDialog = ({
|
||||||
</span>
|
</span>
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
unstyle
|
||||||
className={`${inputCls} w-[190px]`}
|
className={`${inputCls} w-[320px]`}
|
||||||
placeholder="Vw_OrderSummary"
|
placeholder="Vw_OrderSummary"
|
||||||
value={settings.viewName}
|
value={settings.viewName}
|
||||||
disabled={isEditMode}
|
disabled={isEditMode}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ import { getList } from '@/services/form.service'
|
||||||
import type { GridDto } from '@/proxy/form/models'
|
import type { GridDto } from '@/proxy/form/models'
|
||||||
import { getListForms } from '@/services/admin/list-form.service'
|
import { getListForms } from '@/services/admin/list-form.service'
|
||||||
import { developerKitService } from '@/services/developerKit.service'
|
import { developerKitService } from '@/services/developerKit.service'
|
||||||
import { Button, Notification, toast } from '@/components/ui'
|
import { Button, Notification, Select, toast } from '@/components/ui'
|
||||||
import StyleModal from '@/components/codeLayout/StyleModal'
|
import StyleModal from '@/components/codeLayout/StyleModal'
|
||||||
import VisualCanvas, { DESIGNER_DRAG_TYPE } from '@/components/visualDesigner/VisualCanvas'
|
import VisualCanvas, { DESIGNER_DRAG_TYPE } from '@/components/visualDesigner/VisualCanvas'
|
||||||
import {
|
import {
|
||||||
|
|
@ -1164,22 +1164,20 @@ const PropertyEditor = ({
|
||||||
if (type === 'select' && options) {
|
if (type === 'select' && options) {
|
||||||
const currentValue = String(value ?? '')
|
const currentValue = String(value ?? '')
|
||||||
return (
|
return (
|
||||||
<select
|
/* An empty value is not emitted, so the component keeps its own or its
|
||||||
className={inputClass}
|
container's default — spell that out instead of showing a blank row. */
|
||||||
value={currentValue}
|
<Select
|
||||||
onChange={(event) => onChange(event.target.value)}
|
size="xs"
|
||||||
>
|
className="w-full"
|
||||||
{/* An empty value is not emitted, so the component keeps its own or its
|
menuPortalTarget={window.document.body}
|
||||||
container's default — spell that out instead of showing a blank row. */}
|
options={options.map((option) => ({ value: option, label: option }))}
|
||||||
{!options.includes(currentValue) && (
|
value={
|
||||||
<option value="">{translate('::Abp.Mailing.Default')}</option>
|
options.includes(currentValue)
|
||||||
)}
|
? { value: currentValue, label: currentValue }
|
||||||
{options.map((option) => (
|
: { value: '', label: translate('::Abp.Mailing.Default') }
|
||||||
<option key={option} value={option}>
|
}
|
||||||
{option}
|
onChange={(option) => onChange(option?.value ?? '')}
|
||||||
</option>
|
/>
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// An icon prop holds a name; the picker is what makes that name findable and
|
// An icon prop holds a name; the picker is what makes that name findable and
|
||||||
|
|
@ -3513,29 +3511,33 @@ const VisualComponentDesigner = () => {
|
||||||
<span className="mb-1 block text-[10px] font-semibold text-slate-500">
|
<span className="mb-1 block text-[10px] font-semibold text-slate-500">
|
||||||
Koleksiyon
|
Koleksiyon
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-2 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={selectOptionsBinding.path}
|
className="w-full"
|
||||||
onChange={(event) =>
|
placeholder={translate(
|
||||||
|
'::App.DeveloperKitComponentDesigner.NoCollectionFound',
|
||||||
|
)}
|
||||||
|
menuPortalTarget={window.document.body}
|
||||||
|
options={selectCollectionPaths.map((path) => ({
|
||||||
|
value: path,
|
||||||
|
label:
|
||||||
|
path ||
|
||||||
|
translate('::App.DeveloperKitComponentDesigner.WholeResponseArray'),
|
||||||
|
}))}
|
||||||
|
value={{
|
||||||
|
value: selectOptionsBinding.path,
|
||||||
|
label:
|
||||||
|
selectOptionsBinding.path ||
|
||||||
|
translate('::App.DeveloperKitComponentDesigner.WholeResponseArray'),
|
||||||
|
}}
|
||||||
|
onChange={(option) =>
|
||||||
updateSelectedBindingDetails(optionDataProperty, {
|
updateSelectedBindingDetails(optionDataProperty, {
|
||||||
path: event.target.value,
|
path: option?.value ?? '',
|
||||||
labelPath: '',
|
labelPath: '',
|
||||||
valuePath: '',
|
valuePath: '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
{!selectCollectionPaths.length && (
|
|
||||||
<option value="">
|
|
||||||
{translate('::App.DeveloperKitComponentDesigner.NoCollectionFound')}
|
|
||||||
</option>
|
|
||||||
)}
|
|
||||||
{selectCollectionPaths.map((path) => (
|
|
||||||
<option key={path || '__root__'} value={path}>
|
|
||||||
{path ||
|
|
||||||
translate('::App.DeveloperKitComponentDesigner.WholeResponseArray')}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
</label>
|
||||||
{selectCollectionSample && (
|
{selectCollectionSample && (
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
|
@ -3549,28 +3551,34 @@ const VisualComponentDesigner = () => {
|
||||||
<span className="mb-1 block text-[10px] font-semibold text-slate-500">
|
<span className="mb-1 block text-[10px] font-semibold text-slate-500">
|
||||||
{translate('::' + label)}
|
{translate('::' + label)}
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-2 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={selectOptionsBinding[key] || ''}
|
className="w-full"
|
||||||
onChange={(event) =>
|
isClearable
|
||||||
|
placeholder={translate(
|
||||||
|
selectColumnFields.length
|
||||||
|
? '::App.DeveloperKitComponentDesigner.SelectColumn'
|
||||||
|
: '::App.DeveloperKitComponentDesigner.ItemItself',
|
||||||
|
)}
|
||||||
|
menuPortalTarget={window.document.body}
|
||||||
|
options={selectColumnFields.map((field) => ({
|
||||||
|
value: field.path,
|
||||||
|
label: field.path,
|
||||||
|
}))}
|
||||||
|
value={
|
||||||
|
selectOptionsBinding[key]
|
||||||
|
? {
|
||||||
|
value: selectOptionsBinding[key] as string,
|
||||||
|
label: selectOptionsBinding[key] as string,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
onChange={(option) =>
|
||||||
updateSelectedBindingDetails(optionDataProperty, {
|
updateSelectedBindingDetails(optionDataProperty, {
|
||||||
[key]: event.target.value,
|
[key]: option?.value ?? '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
<option value="">
|
|
||||||
{translate(
|
|
||||||
selectColumnFields.length
|
|
||||||
? '::App.DeveloperKitComponentDesigner.SelectColumn'
|
|
||||||
: '::App.DeveloperKitComponentDesigner.ItemItself',
|
|
||||||
)}
|
|
||||||
</option>
|
|
||||||
{selectColumnFields.map((field) => (
|
|
||||||
<option key={field.path} value={field.path}>
|
|
||||||
{field.path}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -3609,28 +3617,25 @@ const VisualComponentDesigner = () => {
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
<select
|
<Select
|
||||||
className="rounded-md border border-slate-300 bg-white px-2 py-1 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value=""
|
className="min-w-[8rem]"
|
||||||
onChange={(event) => {
|
placeholder={translate(
|
||||||
const next = event.target.value
|
'::App.DeveloperKitComponentDesigner.AddColumn',
|
||||||
|
)}
|
||||||
|
menuPortalTarget={window.document.body}
|
||||||
|
options={selectColumnFields
|
||||||
|
.filter((field) => !selectOptionColumns.includes(field.path))
|
||||||
|
.map((field) => ({ value: field.path, label: field.path }))}
|
||||||
|
value={null}
|
||||||
|
onChange={(option) => {
|
||||||
|
const next = option?.value
|
||||||
if (!next || selectOptionColumns.includes(next)) return
|
if (!next || selectOptionColumns.includes(next)) return
|
||||||
updateSelectedBindingDetails(optionDataProperty, {
|
updateSelectedBindingDetails(optionDataProperty, {
|
||||||
columns: [...selectOptionColumns, next],
|
columns: [...selectOptionColumns, next],
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<option value="">
|
|
||||||
{translate('::App.DeveloperKitComponentDesigner.AddColumn')}
|
|
||||||
</option>
|
|
||||||
{selectColumnFields
|
|
||||||
.filter((field) => !selectOptionColumns.includes(field.path))
|
|
||||||
.map((field) => (
|
|
||||||
<option key={field.path} value={field.path}>
|
|
||||||
{field.path}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-[10px] leading-4 text-slate-500">
|
<p className="mt-1 text-[10px] leading-4 text-slate-500">
|
||||||
{translate('::App.DeveloperKitComponentDesigner.ExtraColumnsHint')}{' '}
|
{translate('::App.DeveloperKitComponentDesigner.ExtraColumnsHint')}{' '}
|
||||||
|
|
@ -3764,42 +3769,54 @@ const VisualComponentDesigner = () => {
|
||||||
</label>
|
</label>
|
||||||
{lookup && (
|
{lookup && (
|
||||||
<div className="mt-2 space-y-1.5">
|
<div className="mt-2 space-y-1.5">
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={lookup.sourceId}
|
className="w-full"
|
||||||
onChange={(event) => selectColumnLookupSource(column, event.target.value)}
|
isClearable
|
||||||
>
|
placeholder={translate(
|
||||||
<option value="">
|
'::App.DeveloperKitComponentDesigner.SelectEndpoint',
|
||||||
{translate('::App.DeveloperKitComponentDesigner.SelectEndpoint')}
|
)}
|
||||||
</option>
|
menuPortalTarget={window.document.body}
|
||||||
{bindableDataSources.map((source) => (
|
options={bindableDataSources.map((source) => ({
|
||||||
<option key={source.id} value={source.id}>
|
value: source.id,
|
||||||
{source.name}
|
label: source.name,
|
||||||
</option>
|
}))}
|
||||||
))}
|
value={bindableDataSources
|
||||||
</select>
|
.filter((source) => source.id === lookup.sourceId)
|
||||||
|
.map((source) => ({ value: source.id, label: source.name }))}
|
||||||
|
onChange={(option) =>
|
||||||
|
selectColumnLookupSource(column, option?.value ?? '')
|
||||||
|
}
|
||||||
|
/>
|
||||||
{lookup.sourceId && collectionPaths.length > 1 && (
|
{lookup.sourceId && collectionPaths.length > 1 && (
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={lookup.path}
|
className="w-full"
|
||||||
onChange={(event) =>
|
isClearable
|
||||||
|
maxMenuHeight={200}
|
||||||
|
placeholder={translate(
|
||||||
|
'::App.DeveloperKitComponentDesigner.SelectCollection',
|
||||||
|
)}
|
||||||
|
menuPortalTarget={window.document.body}
|
||||||
|
options={collectionPaths.map((path) => ({
|
||||||
|
value: path,
|
||||||
|
label:
|
||||||
|
path ||
|
||||||
|
translate('::App.DeveloperKitComponentDesigner.WholeResponseArray'),
|
||||||
|
}))}
|
||||||
|
value={
|
||||||
|
lookup.path
|
||||||
|
? { value: lookup.path, label: lookup.path }
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
onChange={(option) =>
|
||||||
writeColumnLookup(column, {
|
writeColumnLookup(column, {
|
||||||
path: event.target.value,
|
path: option?.value ?? '',
|
||||||
valueField: '',
|
valueField: '',
|
||||||
textField: '',
|
textField: '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
<option value="">
|
|
||||||
{translate('::App.DeveloperKitComponentDesigner.SelectCollection')}
|
|
||||||
</option>
|
|
||||||
{collectionPaths.map((path) => (
|
|
||||||
<option key={path || '__root__'} value={path}>
|
|
||||||
{path ||
|
|
||||||
translate('::App.DeveloperKitComponentDesigner.WholeResponseArray')}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
)}
|
)}
|
||||||
{lookup.sourceId && (
|
{lookup.sourceId && (
|
||||||
<div className="grid grid-cols-2 gap-1.5">
|
<div className="grid grid-cols-2 gap-1.5">
|
||||||
|
|
@ -3813,26 +3830,29 @@ const VisualComponentDesigner = () => {
|
||||||
<span className="mb-0.5 block text-[9px] text-slate-400">
|
<span className="mb-0.5 block text-[9px] text-slate-400">
|
||||||
{translate(label)}
|
{translate(label)}
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={lookup[field]}
|
className="w-full"
|
||||||
onChange={(event) =>
|
isClearable
|
||||||
writeColumnLookup(column, { [field]: event.target.value })
|
maxMenuHeight={200}
|
||||||
}
|
placeholder={
|
||||||
>
|
rowFields.length
|
||||||
<option value="">
|
|
||||||
{rowFields.length
|
|
||||||
? translate('::App.DeveloperKitComponentDesigner.SelectField')
|
? translate('::App.DeveloperKitComponentDesigner.SelectField')
|
||||||
: translate(
|
: translate(
|
||||||
'::App.DeveloperKitComponentDesigner.NoColumnInResponse',
|
'::App.DeveloperKitComponentDesigner.NoColumnInResponse',
|
||||||
)}
|
)
|
||||||
</option>
|
}
|
||||||
{rowFields.map((path) => (
|
menuPortalTarget={window.document.body}
|
||||||
<option key={path} value={path}>
|
options={rowFields.map((path) => ({ value: path, label: path }))}
|
||||||
{path}
|
value={
|
||||||
</option>
|
lookup[field]
|
||||||
))}
|
? { value: lookup[field], label: lookup[field] }
|
||||||
</select>
|
: null
|
||||||
|
}
|
||||||
|
onChange={(option) =>
|
||||||
|
writeColumnLookup(column, { [field]: option?.value ?? '' })
|
||||||
|
}
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -3987,6 +4007,15 @@ const VisualComponentDesigner = () => {
|
||||||
const patchFilter = (id: string, updates: Partial<DesignerDataSourceFilter>) =>
|
const patchFilter = (id: string, updates: Partial<DesignerDataSourceFilter>) =>
|
||||||
writeFilters(filters.map((filter) => (filter.id === id ? { ...filter, ...updates } : filter)))
|
writeFilters(filters.map((filter) => (filter.id === id ? { ...filter, ...updates } : filter)))
|
||||||
|
|
||||||
|
const filterSourceOptions = DESIGNER_FILTER_SOURCES.map((item) => ({
|
||||||
|
value: item,
|
||||||
|
label: translate(
|
||||||
|
`::App.DeveloperKitComponentDesigner.FilterSource${
|
||||||
|
item.charAt(0).toUpperCase() + item.slice(1)
|
||||||
|
}`,
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-slate-200 p-3 dark:border-slate-800">
|
<div className="rounded-lg border border-slate-200 p-3 dark:border-slate-800">
|
||||||
<div className="mb-1 flex items-center justify-between">
|
<div className="mb-1 flex items-center justify-between">
|
||||||
|
|
@ -4038,25 +4067,28 @@ const VisualComponentDesigner = () => {
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
{strictColumns ? (
|
{strictColumns ? (
|
||||||
<select
|
<Select
|
||||||
className="min-w-0 flex-1 rounded border border-slate-300 bg-white px-1 py-1 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={filter.field || ''}
|
className="min-w-0 flex-1"
|
||||||
onChange={(event) => patchFilter(filter.id, { field: event.target.value })}
|
isClearable
|
||||||
>
|
maxMenuHeight={200}
|
||||||
<option value="">
|
placeholder={translate(
|
||||||
{translate('::App.DeveloperKitComponentDesigner.FilterColumn')}
|
'::App.DeveloperKitComponentDesigner.FilterColumn',
|
||||||
</option>
|
)}
|
||||||
{/* A field configured before the list form changed is kept in
|
menuPortalTarget={window.document.body}
|
||||||
the list, so switching forms does not silently blank it. */}
|
options={(filter.field && !columns.includes(filter.field)
|
||||||
{(filter.field && !columns.includes(filter.field)
|
|
||||||
? [filter.field, ...columns]
|
? [filter.field, ...columns]
|
||||||
: columns
|
: columns
|
||||||
).map((column) => (
|
).map((column) => ({ value: column, label: column }))}
|
||||||
<option key={column} value={column}>
|
value={
|
||||||
{column}
|
filter.field
|
||||||
</option>
|
? { value: filter.field, label: filter.field }
|
||||||
))}
|
: null
|
||||||
</select>
|
}
|
||||||
|
onChange={(option) =>
|
||||||
|
patchFilter(filter.id, { field: option?.value ?? '' })
|
||||||
|
}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
unstyle
|
||||||
|
|
@ -4067,21 +4099,26 @@ const VisualComponentDesigner = () => {
|
||||||
onChange={(event) => patchFilter(filter.id, { field: event.target.value })}
|
onChange={(event) => patchFilter(filter.id, { field: event.target.value })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<select
|
<Select
|
||||||
className="w-24 rounded border border-slate-300 bg-white px-1 py-1 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={filter.operator}
|
className="w-24"
|
||||||
onChange={(event) =>
|
maxMenuHeight={200}
|
||||||
|
menuPortalTarget={window.document.body}
|
||||||
|
options={DESIGNER_FILTER_OPERATORS.map((operator) => ({
|
||||||
|
value: operator,
|
||||||
|
label: FILTER_OPERATOR_LABELS[operator],
|
||||||
|
}))}
|
||||||
|
value={{
|
||||||
|
value: filter.operator,
|
||||||
|
label: FILTER_OPERATOR_LABELS[filter.operator],
|
||||||
|
}}
|
||||||
|
onChange={(option) =>
|
||||||
|
option &&
|
||||||
patchFilter(filter.id, {
|
patchFilter(filter.id, {
|
||||||
operator: event.target.value as DesignerFilterOperator,
|
operator: option.value as DesignerFilterOperator,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
{DESIGNER_FILTER_OPERATORS.map((operator) => (
|
|
||||||
<option key={operator} value={operator}>
|
|
||||||
{FILTER_OPERATOR_LABELS[operator]}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
<button
|
||||||
className="rounded p-1 text-slate-400 hover:text-red-600"
|
className="rounded p-1 text-slate-400 hover:text-red-600"
|
||||||
title={translate('::App.Platform.Delete')}
|
title={translate('::App.Platform.Delete')}
|
||||||
|
|
@ -4095,26 +4132,23 @@ const VisualComponentDesigner = () => {
|
||||||
</div>
|
</div>
|
||||||
{!valueless && (
|
{!valueless && (
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<select
|
<Select
|
||||||
className="w-24 rounded border border-slate-300 bg-white px-1 py-1 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={filter.source}
|
className="w-24"
|
||||||
onChange={(event) =>
|
maxMenuHeight={200}
|
||||||
|
menuPortalTarget={window.document.body}
|
||||||
|
options={filterSourceOptions}
|
||||||
|
value={filterSourceOptions.filter(
|
||||||
|
(option) => option.value === filter.source,
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
|
option &&
|
||||||
patchFilter(filter.id, {
|
patchFilter(filter.id, {
|
||||||
source: event.target.value as DesignerFilterSource,
|
source: option.value as DesignerFilterSource,
|
||||||
value: '',
|
value: '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
{DESIGNER_FILTER_SOURCES.map((item) => (
|
|
||||||
<option key={item} value={item}>
|
|
||||||
{translate(
|
|
||||||
`::App.DeveloperKitComponentDesigner.FilterSource${
|
|
||||||
item.charAt(0).toUpperCase() + item.slice(1)
|
|
||||||
}`,
|
|
||||||
)}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{filter.source !== 'record' && (
|
{filter.source !== 'record' && (
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
unstyle
|
||||||
|
|
@ -4135,24 +4169,28 @@ const VisualComponentDesigner = () => {
|
||||||
next to the source selector left neither of them usable. */}
|
next to the source selector left neither of them usable. */}
|
||||||
{!valueless && filter.source === 'record' && (
|
{!valueless && filter.source === 'record' && (
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<select
|
<Select
|
||||||
className="min-w-0 flex-1 rounded border border-slate-300 bg-white px-1 py-1 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={masterRef}
|
className="min-w-0 flex-1"
|
||||||
onChange={(event) =>
|
isClearable
|
||||||
|
maxMenuHeight={200}
|
||||||
|
placeholder={translate(
|
||||||
|
'::App.DeveloperKitComponentDesigner.FilterMaster',
|
||||||
|
)}
|
||||||
|
menuPortalTarget={window.document.body}
|
||||||
|
options={sqlContainerRefs.map((item) => ({
|
||||||
|
value: item.ref,
|
||||||
|
label: item.ref,
|
||||||
|
}))}
|
||||||
|
value={masterRef ? { value: masterRef, label: masterRef } : null}
|
||||||
|
onChange={(option) =>
|
||||||
patchFilter(filter.id, {
|
patchFilter(filter.id, {
|
||||||
value: masterColumn ? `${event.target.value}.${masterColumn}` : event.target.value,
|
value: masterColumn
|
||||||
|
? `${option?.value ?? ''}.${masterColumn}`
|
||||||
|
: (option?.value ?? ''),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
<option value="">
|
|
||||||
{translate('::App.DeveloperKitComponentDesigner.FilterMaster')}
|
|
||||||
</option>
|
|
||||||
{sqlContainerRefs.map((item) => (
|
|
||||||
<option key={item.ref} value={item.ref}>
|
|
||||||
{item.ref}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
unstyle
|
||||||
className="min-w-0 flex-1 rounded border border-slate-300 bg-white px-2 py-1 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
className="min-w-0 flex-1 rounded border border-slate-300 bg-white px-2 py-1 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
||||||
|
|
@ -4264,11 +4302,25 @@ const VisualComponentDesigner = () => {
|
||||||
<p className="mb-2 text-[10px] leading-4 text-slate-500">
|
<p className="mb-2 text-[10px] leading-4 text-slate-500">
|
||||||
{translate('::' + slot.description)}
|
{translate('::' + slot.description)}
|
||||||
</p>
|
</p>
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-2 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={currentValue}
|
className="w-full"
|
||||||
onChange={(event) => {
|
isClearable
|
||||||
const sourceId = event.target.value
|
maxMenuHeight={200}
|
||||||
|
placeholder={translate('::App.DeveloperKitComponentDesigner.NotDefined')}
|
||||||
|
menuPortalTarget={window.document.body}
|
||||||
|
options={options.map((source) => ({
|
||||||
|
value: source.id,
|
||||||
|
label: `${source.name} · ${source.url}`,
|
||||||
|
}))}
|
||||||
|
value={options
|
||||||
|
.filter((source) => source.id === currentValue)
|
||||||
|
.map((source) => ({
|
||||||
|
value: source.id,
|
||||||
|
label: `${source.name} · ${source.url}`,
|
||||||
|
}))}
|
||||||
|
onChange={(option) => {
|
||||||
|
const sourceId = option?.value ?? ''
|
||||||
updateSelectedProp(slot.property, sourceId)
|
updateSelectedProp(slot.property, sourceId)
|
||||||
const source = document.dataSources.find((item) => item.id === sourceId)
|
const source = document.dataSources.find((item) => item.id === sourceId)
|
||||||
if (
|
if (
|
||||||
|
|
@ -4279,16 +4331,7 @@ const VisualComponentDesigner = () => {
|
||||||
void testDataSource(source)
|
void testDataSource(source)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<option value="">
|
|
||||||
{translate('::App.DeveloperKitComponentDesigner.NotDefined')}
|
|
||||||
</option>
|
|
||||||
{options.map((source) => (
|
|
||||||
<option key={source.id} value={source.id}>
|
|
||||||
{source.name} · {source.url}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{!options.length && (
|
{!options.length && (
|
||||||
<p className="mt-1.5 text-[10px] leading-4 text-amber-600">
|
<p className="mt-1.5 text-[10px] leading-4 text-amber-600">
|
||||||
{translate('::App.DeveloperKitComponentDesigner.NoEndpointForMethod', {
|
{translate('::App.DeveloperKitComponentDesigner.NoEndpointForMethod', {
|
||||||
|
|
@ -4316,17 +4359,26 @@ const VisualComponentDesigner = () => {
|
||||||
<span className="mb-1 block text-[10px] font-semibold uppercase tracking-wider text-slate-400">
|
<span className="mb-1 block text-[10px] font-semibold uppercase tracking-wider text-slate-400">
|
||||||
Koleksiyon
|
Koleksiyon
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-2 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={String(selectedNode.props.collectionPath ?? '')}
|
className="w-full"
|
||||||
onChange={(event) => updateSelectedProp('collectionPath', event.target.value)}
|
maxMenuHeight={200}
|
||||||
>
|
menuPortalTarget={window.document.body}
|
||||||
{collectionPaths.map((path) => (
|
options={collectionPaths.map((path) => ({
|
||||||
<option key={path || '__root__'} value={path}>
|
value: path,
|
||||||
{path || translate('::App.DeveloperKitComponentDesigner.ResponseItself')}
|
label:
|
||||||
</option>
|
path || translate('::App.DeveloperKitComponentDesigner.ResponseItself'),
|
||||||
))}
|
}))}
|
||||||
</select>
|
value={{
|
||||||
|
value: String(selectedNode.props.collectionPath ?? ''),
|
||||||
|
label:
|
||||||
|
String(selectedNode.props.collectionPath ?? '') ||
|
||||||
|
translate('::App.DeveloperKitComponentDesigner.ResponseItself'),
|
||||||
|
}}
|
||||||
|
onChange={(option) =>
|
||||||
|
updateSelectedProp('collectionPath', option?.value ?? '')
|
||||||
|
}
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg border border-slate-200 p-3 dark:border-slate-800">
|
<div className="rounded-lg border border-slate-200 p-3 dark:border-slate-800">
|
||||||
|
|
@ -4336,17 +4388,17 @@ const VisualComponentDesigner = () => {
|
||||||
<p className="mb-2 text-[10px] leading-4 text-slate-500">
|
<p className="mb-2 text-[10px] leading-4 text-slate-500">
|
||||||
{translate('::App.DeveloperKitComponentDesigner.KeyParamHint')}
|
{translate('::App.DeveloperKitComponentDesigner.KeyParamHint')}
|
||||||
</p>
|
</p>
|
||||||
<select
|
<Select
|
||||||
className="mb-2 w-full rounded-md border border-slate-300 bg-white px-2 py-2 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={getSqlDataSourceKeySource(selectedNode)}
|
className="mb-2 w-full"
|
||||||
onChange={(event) => updateSelectedProp('keySource', event.target.value)}
|
maxMenuHeight={200}
|
||||||
>
|
menuPortalTarget={window.document.body}
|
||||||
{SQL_DATA_SOURCE_KEY_SOURCES.map((item) => (
|
options={SQL_DATA_SOURCE_KEY_SOURCES}
|
||||||
<option key={item.value} value={item.value}>
|
value={SQL_DATA_SOURCE_KEY_SOURCES.filter(
|
||||||
{item.label}
|
(item) => item.value === getSqlDataSourceKeySource(selectedNode),
|
||||||
</option>
|
)}
|
||||||
))}
|
onChange={(option) => option && updateSelectedProp('keySource', option.value)}
|
||||||
</select>
|
/>
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
unstyle
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-2 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
className="w-full rounded-md border border-slate-300 bg-white px-2 py-2 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
||||||
|
|
@ -4499,6 +4551,10 @@ const VisualComponentDesigner = () => {
|
||||||
const column = binding?.sourceId === sqlScopeNode.id ? binding.path : ''
|
const column = binding?.sourceId === sqlScopeNode.id ? binding.path : ''
|
||||||
const rawDefault = selectedNode.props?.[SQL_DEFAULT_VALUE_PROP]
|
const rawDefault = selectedNode.props?.[SQL_DEFAULT_VALUE_PROP]
|
||||||
const currentDefault = rawDefault === undefined || rawDefault === null ? '' : String(rawDefault)
|
const currentDefault = rawDefault === undefined || rawDefault === null ? '' : String(rawDefault)
|
||||||
|
const sqlDefaultValueOptions = [
|
||||||
|
{ value: 'true', label: translate('::App.Platform.Yes') },
|
||||||
|
{ value: 'false', label: translate('::App.Platform.No') },
|
||||||
|
]
|
||||||
const columnListId = `sql-columns-${sqlScopeNode.id}`
|
const columnListId = `sql-columns-${sqlScopeNode.id}`
|
||||||
// A picker gets a real date editor, unless it holds a token — `@today` is not
|
// A picker gets a real date editor, unless it holds a token — `@today` is not
|
||||||
// a date the browser can render, and typing it needs a plain text field.
|
// a date the browser can render, and typing it needs a plain text field.
|
||||||
|
|
@ -4552,18 +4608,24 @@ const VisualComponentDesigner = () => {
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
{sqlRecordProperty === 'checked' ? (
|
{sqlRecordProperty === 'checked' ? (
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-2 text-[10px] disabled:opacity-50 dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
disabled={!column}
|
className="w-full"
|
||||||
value={currentDefault}
|
isClearable
|
||||||
onChange={(event) => updateSelectedProp(SQL_DEFAULT_VALUE_PROP, event.target.value)}
|
isDisabled={!column}
|
||||||
>
|
maxMenuHeight={200}
|
||||||
<option value="">
|
placeholder={translate(
|
||||||
{translate('::App.DeveloperKitComponentDesigner.NoDefaultValue')}
|
'::App.DeveloperKitComponentDesigner.NoDefaultValue',
|
||||||
</option>
|
)}
|
||||||
<option value="true">{translate('::App.Platform.Yes')}</option>
|
menuPortalTarget={window.document.body}
|
||||||
<option value="false">{translate('::App.Platform.No')}</option>
|
options={sqlDefaultValueOptions}
|
||||||
</select>
|
value={sqlDefaultValueOptions.filter(
|
||||||
|
(option) => option.value === currentDefault,
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
|
updateSelectedProp(SQL_DEFAULT_VALUE_PROP, option?.value ?? '')
|
||||||
|
}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
unstyle
|
||||||
|
|
@ -4665,11 +4727,24 @@ const VisualComponentDesigner = () => {
|
||||||
{sqlScopeSource?.name}
|
{sqlScopeSource?.name}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2.5 py-2 text-xs dark:border-slate-700 dark:bg-slate-900"
|
size="sm"
|
||||||
value={activeDataSource?.id || ''}
|
className="w-full"
|
||||||
onChange={(event) => {
|
isClearable
|
||||||
const sourceId = event.target.value
|
maxMenuHeight={200}
|
||||||
|
placeholder={translate(
|
||||||
|
'::App.DeveloperKitComponentDesigner.SelectEndpoint',
|
||||||
|
)}
|
||||||
|
menuPortalTarget={window.document.body}
|
||||||
|
options={inspectorDataSources.map((source) => ({
|
||||||
|
value: source.id,
|
||||||
|
label: source.name,
|
||||||
|
}))}
|
||||||
|
value={inspectorDataSources
|
||||||
|
.filter((source) => source.id === (activeDataSource?.id || ''))
|
||||||
|
.map((source) => ({ value: source.id, label: source.name }))}
|
||||||
|
onChange={(option) => {
|
||||||
|
const sourceId = option?.value ?? ''
|
||||||
setDataPanelSourceId(sourceId)
|
setDataPanelSourceId(sourceId)
|
||||||
if (
|
if (
|
||||||
(isOptionDataComponent(selectedNode?.type) ||
|
(isOptionDataComponent(selectedNode?.type) ||
|
||||||
|
|
@ -4703,20 +4778,7 @@ const VisualComponentDesigner = () => {
|
||||||
void testDataSource(source)
|
void testDataSource(source)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
{/* Without this a browser paints the first option while the
|
|
||||||
value is still empty, which reads as a made choice. */}
|
|
||||||
{!activeDataSource && (
|
|
||||||
<option value="">
|
|
||||||
{translate('::App.DeveloperKitComponentDesigner.SelectEndpoint')}
|
|
||||||
</option>
|
|
||||||
)}
|
|
||||||
{inspectorDataSources.map((source) => (
|
|
||||||
<option key={source.id} value={source.id}>
|
|
||||||
{source.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -4733,24 +4795,30 @@ const VisualComponentDesigner = () => {
|
||||||
<span className="mb-1 block text-[10px] font-semibold uppercase tracking-wider text-slate-400">
|
<span className="mb-1 block text-[10px] font-semibold uppercase tracking-wider text-slate-400">
|
||||||
Koleksiyon
|
Koleksiyon
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-2 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={tabularItemsBinding.path}
|
className="w-full"
|
||||||
onChange={(event) =>
|
maxMenuHeight={200}
|
||||||
updateSelectedBindingDetails('items', { path: event.target.value })
|
placeholder={translate(
|
||||||
}
|
'::App.DeveloperKitComponentDesigner.NoCollectionFound',
|
||||||
>
|
|
||||||
{!tabularCollectionPaths.length && (
|
|
||||||
<option value="">
|
|
||||||
{translate('::App.DeveloperKitComponentDesigner.NoCollectionFound')}
|
|
||||||
</option>
|
|
||||||
)}
|
)}
|
||||||
{tabularCollectionPaths.map((path) => (
|
menuPortalTarget={window.document.body}
|
||||||
<option key={path || '__root__'} value={path}>
|
options={tabularCollectionPaths.map((path) => ({
|
||||||
{path || translate('::App.DeveloperKitComponentDesigner.WholeResponseArray')}
|
value: path,
|
||||||
</option>
|
label:
|
||||||
))}
|
path ||
|
||||||
</select>
|
translate('::App.DeveloperKitComponentDesigner.WholeResponseArray'),
|
||||||
|
}))}
|
||||||
|
value={{
|
||||||
|
value: tabularItemsBinding.path,
|
||||||
|
label:
|
||||||
|
tabularItemsBinding.path ||
|
||||||
|
translate('::App.DeveloperKitComponentDesigner.WholeResponseArray'),
|
||||||
|
}}
|
||||||
|
onChange={(option) =>
|
||||||
|
updateSelectedBindingDetails('items', { path: option?.value ?? '' })
|
||||||
|
}
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -4871,6 +4939,29 @@ const VisualComponentDesigner = () => {
|
||||||
choice === '__root__' ||
|
choice === '__root__' ||
|
||||||
selectableFields.some((field) => field.path === choice)
|
selectableFields.some((field) => field.path === choice)
|
||||||
|
|
||||||
|
const bindingChoiceOptions = [
|
||||||
|
...(activeBindingSample !== undefined &&
|
||||||
|
(collection || !Array.isArray(activeBindingSample) || isActiveRepeatedSource)
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
value: '__root__',
|
||||||
|
label: translate(
|
||||||
|
isActiveRepeatedSource
|
||||||
|
? '::App.DeveloperKitComponentDesigner.CurrentGridRow'
|
||||||
|
: collection
|
||||||
|
? '::App.DeveloperKitComponentDesigner.WholeCollection'
|
||||||
|
: '::App.DeveloperKitComponentDesigner.WholeResponse',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(!knownChoice ? [{ value: choice, label: `${choice} (mevcut path)` }] : []),
|
||||||
|
...selectableFields.map((field) => ({
|
||||||
|
value: field.path,
|
||||||
|
label: `${field.path} · ${field.type}`,
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
key={property.name}
|
key={property.name}
|
||||||
|
|
@ -4882,11 +4973,21 @@ const VisualComponentDesigner = () => {
|
||||||
{property.tsType || property.type}
|
{property.tsType || property.type}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-2 text-[10px] dark:border-slate-700 dark:bg-slate-900"
|
size="xs"
|
||||||
value={choice}
|
className="w-full"
|
||||||
onChange={(event) => {
|
isClearable
|
||||||
const nextChoice = event.target.value
|
maxMenuHeight={200}
|
||||||
|
placeholder={translate(
|
||||||
|
'::App.DeveloperKitComponentDesigner.StaticValueNoBinding',
|
||||||
|
)}
|
||||||
|
menuPortalTarget={window.document.body}
|
||||||
|
options={bindingChoiceOptions}
|
||||||
|
value={bindingChoiceOptions.filter(
|
||||||
|
(option) => option.value === choice,
|
||||||
|
)}
|
||||||
|
onChange={(option) => {
|
||||||
|
const nextChoice = option?.value
|
||||||
if (!nextChoice) {
|
if (!nextChoice) {
|
||||||
updateSelectedBinding(property.name, '')
|
updateSelectedBinding(property.name, '')
|
||||||
return
|
return
|
||||||
|
|
@ -4898,31 +4999,7 @@ const VisualComponentDesigner = () => {
|
||||||
getBindingPath(nextChoice, collection),
|
getBindingPath(nextChoice, collection),
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<option value="">
|
|
||||||
{translate('::App.DeveloperKitComponentDesigner.StaticValueNoBinding')}
|
|
||||||
</option>
|
|
||||||
{activeBindingSample !== undefined &&
|
|
||||||
(collection ||
|
|
||||||
!Array.isArray(activeBindingSample) ||
|
|
||||||
isActiveRepeatedSource) && (
|
|
||||||
<option value="__root__">
|
|
||||||
{translate(
|
|
||||||
isActiveRepeatedSource
|
|
||||||
? '::App.DeveloperKitComponentDesigner.CurrentGridRow'
|
|
||||||
: collection
|
|
||||||
? '::App.DeveloperKitComponentDesigner.WholeCollection'
|
|
||||||
: '::App.DeveloperKitComponentDesigner.WholeResponse',
|
|
||||||
)}
|
|
||||||
</option>
|
|
||||||
)}
|
|
||||||
{!knownChoice && <option value={choice}>{choice} (mevcut path)</option>}
|
|
||||||
{selectableFields.map((field) => (
|
|
||||||
<option key={field.path} value={field.path}>
|
|
||||||
{field.path} · {field.type}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{currentBinding?.sourceId && !foreignBinding && (
|
{currentBinding?.sourceId && !foreignBinding && (
|
||||||
<div className="mt-1.5 truncate font-mono text-[9px] text-emerald-600">
|
<div className="mt-1.5 truncate font-mono text-[9px] text-emerald-600">
|
||||||
{currentBindingSource?.name || currentBinding.sourceId}:{' '}
|
{currentBindingSource?.name || currentBinding.sourceId}:{' '}
|
||||||
|
|
@ -6021,21 +6098,26 @@ const VisualComponentDesigner = () => {
|
||||||
<span className="mb-1.5 block text-[10px] font-semibold uppercase text-slate-500">
|
<span className="mb-1.5 block text-[10px] font-semibold uppercase text-slate-500">
|
||||||
Metot
|
Metot
|
||||||
</span>
|
</span>
|
||||||
<select
|
<Select
|
||||||
className="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-xs text-slate-800 outline-none focus:border-sky-500 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100"
|
size="sm"
|
||||||
value={catalogSourceEditor.draft.method}
|
className="w-full"
|
||||||
onChange={(event) =>
|
maxMenuHeight={200}
|
||||||
|
menuPortalTarget={window.document.body}
|
||||||
|
options={DESIGNER_HTTP_METHODS.map((method) => ({
|
||||||
|
value: method,
|
||||||
|
label: method,
|
||||||
|
}))}
|
||||||
|
value={{
|
||||||
|
value: catalogSourceEditor.draft.method,
|
||||||
|
label: catalogSourceEditor.draft.method,
|
||||||
|
}}
|
||||||
|
onChange={(option) =>
|
||||||
|
option &&
|
||||||
updateCatalogSourceDraft({
|
updateCatalogSourceDraft({
|
||||||
method: event.target.value as DesignerHttpMethod,
|
method: option.value as DesignerHttpMethod,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
{DESIGNER_HTTP_METHODS.map((method) => (
|
|
||||||
<option key={method} value={method}>
|
|
||||||
{method}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<label className="block">
|
<label className="block">
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { useState } from 'react'
|
||||||
import { FaCode, FaGripVertical, FaMagic, FaPlus, FaTrash } from 'react-icons/fa'
|
import { FaCode, FaGripVertical, FaMagic, FaPlus, FaTrash } from 'react-icons/fa'
|
||||||
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
import Select from '@/components/ui/Select'
|
||||||
import {
|
import {
|
||||||
GROUP_MODES,
|
GROUP_MODES,
|
||||||
GROUP_MODE_LABEL,
|
GROUP_MODE_LABEL,
|
||||||
|
|
@ -86,6 +87,19 @@ export function CriteriaGrid({
|
||||||
|
|
||||||
const orCount = orColumnCount(rows)
|
const orCount = orColumnCount(rows)
|
||||||
|
|
||||||
|
const sourceOptions = sources.map((s) => ({
|
||||||
|
value: s.id,
|
||||||
|
label: s.alias || s.objectName,
|
||||||
|
}))
|
||||||
|
const groupModeOptions = GROUP_MODES.map((m) => ({
|
||||||
|
value: m,
|
||||||
|
label: GROUP_MODE_LABEL[m],
|
||||||
|
}))
|
||||||
|
const sortTypeOptions: { value: '' | 'ASC' | 'DESC'; label: string }[] = [
|
||||||
|
{ value: 'ASC', label: 'Asc' },
|
||||||
|
{ value: 'DESC', label: 'Desc' },
|
||||||
|
]
|
||||||
|
|
||||||
const cellCls =
|
const cellCls =
|
||||||
'w-full rounded border border-transparent bg-transparent px-1 py-0.5 text-xs hover:border-gray-300 focus:border-blue-500 focus:bg-white dark:text-gray-100 dark:hover:border-gray-600 dark:focus:bg-gray-700'
|
'w-full rounded border border-transparent bg-transparent px-1 py-0.5 text-xs hover:border-gray-300 focus:border-blue-500 focus:bg-white dark:text-gray-100 dark:hover:border-gray-600 dark:focus:bg-gray-700'
|
||||||
|
|
||||||
|
|
@ -115,13 +129,13 @@ export function CriteriaGrid({
|
||||||
<thead className="sticky top-0 z-10 bg-gray-100 text-[10px] font-semibold uppercase tracking-wide text-gray-500 dark:bg-gray-800 dark:text-gray-300">
|
<thead className="sticky top-0 z-10 bg-gray-100 text-[10px] font-semibold uppercase tracking-wide text-gray-500 dark:bg-gray-800 dark:text-gray-300">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="w-6 border border-gray-200 px-1 py-1 dark:border-gray-700" />
|
<th className="w-6 border border-gray-200 px-1 py-1 dark:border-gray-700" />
|
||||||
<th className="min-w-[150px] border border-gray-200 px-1 py-1 text-left dark:border-gray-700">
|
<th className="min-w-[260px] border border-gray-200 px-1 py-1 text-left dark:border-gray-700">
|
||||||
{labels.column}
|
{labels.column}
|
||||||
</th>
|
</th>
|
||||||
<th className="min-w-[110px] border border-gray-200 px-1 py-1 text-left dark:border-gray-700">
|
<th className="min-w-[110px] border border-gray-200 px-1 py-1 text-left dark:border-gray-700">
|
||||||
{labels.alias}
|
{labels.alias}
|
||||||
</th>
|
</th>
|
||||||
<th className="min-w-[90px] border border-gray-200 px-1 py-1 text-left dark:border-gray-700">
|
<th className="min-w-[180px] border border-gray-200 px-1 py-1 text-left dark:border-gray-700">
|
||||||
{labels.table}
|
{labels.table}
|
||||||
</th>
|
</th>
|
||||||
<th className="w-14 border border-gray-200 px-1 py-1 dark:border-gray-700">
|
<th className="w-14 border border-gray-200 px-1 py-1 dark:border-gray-700">
|
||||||
|
|
@ -132,7 +146,7 @@ export function CriteriaGrid({
|
||||||
{labels.groupBy}
|
{labels.groupBy}
|
||||||
</th>
|
</th>
|
||||||
)}
|
)}
|
||||||
<th className="w-[86px] border border-gray-200 px-1 py-1 text-left dark:border-gray-700">
|
<th className="w-[110px] border border-gray-200 px-1 py-1 text-left dark:border-gray-700">
|
||||||
{labels.sortType}
|
{labels.sortType}
|
||||||
</th>
|
</th>
|
||||||
<th className="w-[70px] border border-gray-200 px-1 py-1 text-left dark:border-gray-700">
|
<th className="w-[70px] border border-gray-200 px-1 py-1 text-left dark:border-gray-700">
|
||||||
|
|
@ -201,18 +215,23 @@ export function CriteriaGrid({
|
||||||
onChange={(e) => onUpdateRow(row.id, { expression: e.target.value })}
|
onChange={(e) => onUpdateRow(row.id, { expression: e.target.value })}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<select
|
<Select
|
||||||
className={cellCls}
|
size="xs"
|
||||||
value={row.columnName}
|
className="w-full"
|
||||||
onChange={(e) => onUpdateRow(row.id, { columnName: e.target.value })}
|
isClearable
|
||||||
>
|
placeholder="—"
|
||||||
<option value="">—</option>
|
menuPortalTarget={document.body}
|
||||||
{columnsOf(row.sourceId).map((c) => (
|
options={columnsOf(row.sourceId).map((c) => ({
|
||||||
<option key={c.columnName} value={c.columnName}>
|
value: c.columnName ?? '',
|
||||||
{c.columnName}
|
label: c.columnName ?? '',
|
||||||
</option>
|
}))}
|
||||||
))}
|
value={
|
||||||
</select>
|
row.columnName ? { value: row.columnName, label: row.columnName } : null
|
||||||
|
}
|
||||||
|
onChange={(option) =>
|
||||||
|
onUpdateRow(row.id, { columnName: option?.value ?? '' })
|
||||||
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
@ -230,19 +249,16 @@ export function CriteriaGrid({
|
||||||
{isExpression ? (
|
{isExpression ? (
|
||||||
<span className="px-1 text-[10px] italic text-gray-400">SQL</span>
|
<span className="px-1 text-[10px] italic text-gray-400">SQL</span>
|
||||||
) : (
|
) : (
|
||||||
<select
|
<Select
|
||||||
className={cellCls}
|
size="xs"
|
||||||
value={row.sourceId}
|
className="w-full"
|
||||||
onChange={(e) =>
|
menuPortalTarget={document.body}
|
||||||
onUpdateRow(row.id, { sourceId: e.target.value, columnName: '' })
|
options={sourceOptions}
|
||||||
|
value={sourceOptions.filter((option) => option.value === row.sourceId)}
|
||||||
|
onChange={(option) =>
|
||||||
|
option && onUpdateRow(row.id, { sourceId: option.value, columnName: '' })
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
{sources.map((s) => (
|
|
||||||
<option key={s.id} value={s.id}>
|
|
||||||
{s.alias || s.objectName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
@ -259,33 +275,36 @@ export function CriteriaGrid({
|
||||||
|
|
||||||
{grouped && (
|
{grouped && (
|
||||||
<td className="border border-gray-200 px-0.5 dark:border-gray-700">
|
<td className="border border-gray-200 px-0.5 dark:border-gray-700">
|
||||||
<select
|
<Select
|
||||||
className={cellCls}
|
size="xs"
|
||||||
value={row.groupMode}
|
className="w-full"
|
||||||
disabled={isExpression}
|
isDisabled={isExpression}
|
||||||
onChange={(e) => {
|
menuPortalTarget={document.body}
|
||||||
const mode = e.target.value as GroupMode
|
options={groupModeOptions}
|
||||||
|
value={groupModeOptions.filter((option) => option.value === row.groupMode)}
|
||||||
|
onChange={(option) => {
|
||||||
|
if (!option) return
|
||||||
|
const mode = option.value
|
||||||
onUpdateRow(row.id, {
|
onUpdateRow(row.id, {
|
||||||
groupMode: mode,
|
groupMode: mode,
|
||||||
...(mode === 'Where' ? { output: false } : {}),
|
...(mode === 'Where' ? { output: false } : {}),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
{GROUP_MODES.map((m) => (
|
|
||||||
<option key={m} value={m}>
|
|
||||||
{GROUP_MODE_LABEL[m]}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<td className="border border-gray-200 px-0.5 dark:border-gray-700">
|
<td className="border border-gray-200 px-0.5 dark:border-gray-700">
|
||||||
<select
|
<Select
|
||||||
className={cellCls}
|
size="xs"
|
||||||
value={row.sortType}
|
className="w-full"
|
||||||
onChange={(e) => {
|
isClearable
|
||||||
const sortType = e.target.value as '' | 'ASC' | 'DESC'
|
placeholder="—"
|
||||||
|
menuPortalTarget={document.body}
|
||||||
|
options={sortTypeOptions}
|
||||||
|
value={sortTypeOptions.filter((option) => option.value === row.sortType)}
|
||||||
|
onChange={(option) => {
|
||||||
|
const sortType = (option?.value ?? '') as '' | 'ASC' | 'DESC'
|
||||||
onUpdateRow(row.id, {
|
onUpdateRow(row.id, {
|
||||||
sortType,
|
sortType,
|
||||||
sortOrder: sortType
|
sortOrder: sortType
|
||||||
|
|
@ -293,11 +312,7 @@ export function CriteriaGrid({
|
||||||
: 0,
|
: 0,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<option value="">—</option>
|
|
||||||
<option value="ASC">Ascending</option>
|
|
||||||
<option value="DESC">Descending</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="border border-gray-200 px-0.5 dark:border-gray-700">
|
<td className="border border-gray-200 px-0.5 dark:border-gray-700">
|
||||||
|
|
@ -395,6 +410,11 @@ function FilterBuilder({
|
||||||
const [value2, setValue2] = useState('')
|
const [value2, setValue2] = useState('')
|
||||||
const [mode, setMode] = useState<'literal' | 'expression'>('literal')
|
const [mode, setMode] = useState<'literal' | 'expression'>('literal')
|
||||||
|
|
||||||
|
const modeOptions: { value: 'literal' | 'expression'; label: string }[] = [
|
||||||
|
{ value: 'literal', label: labels.builderLiteral },
|
||||||
|
{ value: 'expression', label: labels.builderExpression },
|
||||||
|
]
|
||||||
|
|
||||||
const needsValue = operator !== 'IS NULL' && operator !== 'IS NOT NULL'
|
const needsValue = operator !== 'IS NULL' && operator !== 'IS NOT NULL'
|
||||||
|
|
||||||
const build = () => {
|
const build = () => {
|
||||||
|
|
@ -418,17 +438,14 @@ function FilterBuilder({
|
||||||
className="absolute left-0 top-full z-50 mt-1 w-[230px] rounded-lg border border-gray-200 bg-white p-2 shadow-xl dark:border-gray-700 dark:bg-gray-800"
|
className="absolute left-0 top-full z-50 mt-1 w-[230px] rounded-lg border border-gray-200 bg-white p-2 shadow-xl dark:border-gray-700 dark:bg-gray-800"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<select
|
<Select
|
||||||
className="mb-1 w-full rounded border px-1 py-1 text-xs dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
size="xs"
|
||||||
value={operator}
|
className="mb-1 w-full"
|
||||||
onChange={(e) => setOperator(e.target.value)}
|
menuPortalTarget={document.body}
|
||||||
>
|
options={OPERATORS.map((op) => ({ value: op, label: op }))}
|
||||||
{OPERATORS.map((op) => (
|
value={{ value: operator, label: operator }}
|
||||||
<option key={op} value={op}>
|
onChange={(option) => option && setOperator(option.value)}
|
||||||
{op}
|
/>
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{needsValue && (
|
{needsValue && (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -447,14 +464,14 @@ function FilterBuilder({
|
||||||
onChange={(e) => setValue2(e.target.value)}
|
onChange={(e) => setValue2(e.target.value)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<select
|
<Select
|
||||||
className="mb-1 w-full rounded border px-1 py-1 text-[11px] dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
size="xs"
|
||||||
value={mode}
|
className="mb-1 w-full"
|
||||||
onChange={(e) => setMode(e.target.value as 'literal' | 'expression')}
|
menuPortalTarget={document.body}
|
||||||
>
|
options={modeOptions}
|
||||||
<option value="literal">{labels.builderLiteral}</option>
|
value={modeOptions.filter((option) => option.value === mode)}
|
||||||
<option value="expression">{labels.builderExpression}</option>
|
onChange={(option) => option && setMode(option.value)}
|
||||||
</select>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import {
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
import Select from '@/components/ui/Select'
|
||||||
import {
|
import {
|
||||||
BOX_FOOTER_HEIGHT,
|
BOX_FOOTER_HEIGHT,
|
||||||
BOX_HEADER_HEIGHT,
|
BOX_HEADER_HEIGHT,
|
||||||
|
|
@ -343,6 +344,15 @@ export function DiagramPane({
|
||||||
? sources.find((s) => s.id === activeJoin.sourceId)
|
? sources.find((s) => s.id === activeJoin.sourceId)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
const joinKindOptions = JOIN_KINDS.filter((k) => k !== 'CROSS').map((k) => ({
|
||||||
|
value: k,
|
||||||
|
label: k === 'INNER' ? 'INNER JOIN' : `${k} OUTER JOIN`,
|
||||||
|
}))
|
||||||
|
const joinOperatorOptions = JOIN_OPERATORS.map((op) => ({
|
||||||
|
value: op,
|
||||||
|
label: activeJoin ? `${activeJoin.leftColumn} ${op} ${activeJoin.rightColumn}` : op,
|
||||||
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative h-full overflow-auto rounded-lg border border-gray-200 bg-white [--grid-color:#edf1f6] dark:border-gray-700 dark:bg-gray-950 dark:[--grid-color:#243244]"
|
className="relative h-full overflow-auto rounded-lg border border-gray-200 bg-white [--grid-color:#edf1f6] dark:border-gray-700 dark:bg-gray-950 dark:[--grid-color:#243244]"
|
||||||
|
|
@ -416,34 +426,33 @@ export function DiagramPane({
|
||||||
<div className="mb-1 text-[10px] font-semibold uppercase tracking-wide text-gray-400">
|
<div className="mb-1 text-[10px] font-semibold uppercase tracking-wide text-gray-400">
|
||||||
{labels.joinType}
|
{labels.joinType}
|
||||||
</div>
|
</div>
|
||||||
<select
|
<Select
|
||||||
className="mb-1.5 w-full rounded border px-1 py-1 text-xs dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
size="xs"
|
||||||
value={activeJoinSource.joinKind}
|
className="mb-1.5 w-full"
|
||||||
onChange={(e) =>
|
menuPortalTarget={document.body}
|
||||||
onUpdateSource(activeJoinSource.id, { joinKind: e.target.value as JoinKind })
|
options={joinKindOptions}
|
||||||
|
value={joinKindOptions.filter(
|
||||||
|
(option) => option.value === activeJoinSource.joinKind,
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
|
option && onUpdateSource(activeJoinSource.id, { joinKind: option.value })
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
{JOIN_KINDS.filter((k) => k !== 'CROSS').map((k) => (
|
<Select
|
||||||
<option key={k} value={k}>
|
size="xs"
|
||||||
{k === 'INNER' ? 'INNER JOIN' : `${k} OUTER JOIN`}
|
className="mb-1.5 w-full"
|
||||||
</option>
|
menuPortalTarget={document.body}
|
||||||
))}
|
options={joinOperatorOptions}
|
||||||
</select>
|
value={joinOperatorOptions.filter(
|
||||||
<select
|
(option) => option.value === activeJoin.operator,
|
||||||
className="mb-1.5 w-full rounded border px-1 py-1 text-xs dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
)}
|
||||||
value={activeJoin.operator}
|
onChange={(option) =>
|
||||||
onChange={(e) =>
|
option &&
|
||||||
onUpdateJoin(activeJoin.sourceId, activeJoin.conditionId, {
|
onUpdateJoin(activeJoin.sourceId, activeJoin.conditionId, {
|
||||||
operator: e.target.value as JoinOperator,
|
operator: option.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
{JOIN_OPERATORS.map((op) => (
|
|
||||||
<option key={op} value={op}>
|
|
||||||
{`${activeJoin.leftColumn} ${op} ${activeJoin.rightColumn}`}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="w-full rounded px-1 py-1 text-left text-[11px] text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20"
|
className="w-full rounded px-1 py-1 text-left text-[11px] text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||||
|
|
|
||||||
|
|
@ -388,7 +388,7 @@ function AnnouncementModalContent({ announcement, onClose, onLikeChange }: Annou
|
||||||
'::App.SocialWallPostItem.CommentPlaceholder',
|
'::App.SocialWallPostItem.CommentPlaceholder',
|
||||||
)}
|
)}
|
||||||
rows={1}
|
rows={1}
|
||||||
className="flex-1 resize-none px-2 py-1 text-sm rounded-xl bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 transition-colors"
|
className="flex-1 resize-none px-2 py-1 text-sm rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 transition-colors"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
|
||||||
|
|
@ -313,7 +313,7 @@ function EventModalContent({ event, onClose, onLikeChange }: EventModalProps) {
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder={translate('::App.Events.EventAttendance')}
|
placeholder={translate('::App.Events.EventAttendance')}
|
||||||
rows={1}
|
rows={1}
|
||||||
className="flex-1 resize-none px-2 py-1 text-sm rounded-xl bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 transition-colors"
|
className="flex-1 resize-none px-2 py-1 text-sm rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 transition-colors"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import React, { useState } from 'react'
|
||||||
import { GridExtraFilterState } from './Utils'
|
import { GridExtraFilterState } from './Utils'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
import Select from '@/components/ui/Select'
|
||||||
|
|
||||||
export function GridExtraFilterToolbar({
|
export function GridExtraFilterToolbar({
|
||||||
filters,
|
filters,
|
||||||
|
|
@ -42,23 +43,27 @@ export function GridExtraFilterToolbar({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const itemOptions: { value: string; label: string }[] = (fs.items ?? []).map(
|
||||||
|
(item: any) => ({ value: String(item.key), label: String(item.value) }),
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={fs.fieldName} className="flex items-center gap-2">
|
<div key={fs.fieldName} className="flex items-center gap-2">
|
||||||
<label>{fs.caption}</label>
|
<label>{fs.caption}</label>
|
||||||
|
|
||||||
{fs.controlType === 'Select' ? (
|
{fs.controlType === 'Select' ? (
|
||||||
<select
|
<Select
|
||||||
className="border rounded px-1.5 py-1.5 dark:bg-gray-700 dark:text-white"
|
size="xs"
|
||||||
value={current?.value ?? ''}
|
className="min-w-[10rem]"
|
||||||
onChange={(e) => handleSave(e.target.value)}
|
isClearable={!fs.defaultValue}
|
||||||
>
|
placeholder={translate('::App.Platform.Select')}
|
||||||
{!fs.defaultValue && <option value="">{translate('::App.Platform.Select')}</option>}
|
menuPortalTarget={document.body}
|
||||||
{fs.items?.map((item: any) => (
|
options={itemOptions}
|
||||||
<option key={item.key} value={item.key}>
|
value={itemOptions.filter(
|
||||||
{item.value}
|
(option) => option.value === String(current?.value ?? ''),
|
||||||
</option>
|
)}
|
||||||
))}
|
onChange={(option) => handleSave(option?.value ?? '')}
|
||||||
</select>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Input
|
<Input
|
||||||
unstyle
|
unstyle
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import type { GridColumnData } from './GridColumnData'
|
||||||
import { flattenGridColumns } from './shared/columns'
|
import { flattenGridColumns } from './shared/columns'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
import Select from '@/components/ui/Select'
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
|
|
@ -279,6 +280,11 @@ const TodoBoardContent = ({
|
||||||
[lookupText, options.statusExpr, translate],
|
[lookupText, options.statusExpr, translate],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const statusSelectOptions = statuses.map((status) => ({
|
||||||
|
value: status,
|
||||||
|
label: statusText(status),
|
||||||
|
}))
|
||||||
|
|
||||||
const clearDragState = useCallback(() => {
|
const clearDragState = useCallback(() => {
|
||||||
dragActiveRef.current = false
|
dragActiveRef.current = false
|
||||||
setDraggedKey(undefined)
|
setDraggedKey(undefined)
|
||||||
|
|
@ -1000,23 +1006,21 @@ const TodoBoardContent = ({
|
||||||
{options.statusExpr && (
|
{options.statusExpr && (
|
||||||
<div>
|
<div>
|
||||||
<label className={labelClass}>{translate('::App.Listform.ListformField.Status')}</label>
|
<label className={labelClass}>{translate('::App.Listform.ListformField.Status')}</label>
|
||||||
<select
|
<Select
|
||||||
className={inputClass}
|
size="sm"
|
||||||
disabled={!canUpdate}
|
isDisabled={!canUpdate}
|
||||||
value={String(draft[options.statusExpr] ?? '')}
|
menuPortalTarget={document.body}
|
||||||
onChange={(event) =>
|
options={statusSelectOptions}
|
||||||
|
value={statusSelectOptions.filter(
|
||||||
|
(option) => option.value === String(draft[options.statusExpr!] ?? ''),
|
||||||
|
)}
|
||||||
|
onChange={(option) =>
|
||||||
setDraft((current) => ({
|
setDraft((current) => ({
|
||||||
...current,
|
...current,
|
||||||
[options.statusExpr!]: event.target.value,
|
[options.statusExpr!]: option?.value ?? '',
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
{statuses.map((status) => (
|
|
||||||
<option key={status} value={status}>
|
|
||||||
{statusText(status)}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{options.dueDateExpr && (
|
{options.dueDateExpr && (
|
||||||
|
|
|
||||||
|
|
@ -393,7 +393,7 @@ const MenuItemComponentBase: React.FC<MenuItemComponentProps> = ({
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
<Dialog
|
<Dialog
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
width={640}
|
width={900}
|
||||||
onClose={() => setIsModalOpen(false)}
|
onClose={() => setIsModalOpen(false)}
|
||||||
onRequestClose={() => setIsModalOpen(false)}
|
onRequestClose={() => setIsModalOpen(false)}
|
||||||
>
|
>
|
||||||
|
|
@ -497,6 +497,8 @@ const MenuItemComponentBase: React.FC<MenuItemComponentProps> = ({
|
||||||
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||||||
<Select
|
<Select
|
||||||
isClearable
|
isClearable
|
||||||
|
maxMenuHeight={220}
|
||||||
|
menuPortalTarget={document.body}
|
||||||
field={field}
|
field={field}
|
||||||
form={form}
|
form={form}
|
||||||
options={permissions}
|
options={permissions}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { useConfig } from '@/components/ui/ConfigProvider'
|
import { useConfig } from '@/components/ui/ConfigProvider'
|
||||||
import { useForm } from '@/components/ui/Form/context'
|
import { useForm } from '@/components/ui/Form/context'
|
||||||
import Input from '@/components/ui/Input'
|
import Input from '@/components/ui/Input'
|
||||||
|
import Select from '@/components/ui/Select'
|
||||||
|
|
||||||
const menuService = new MenuService()
|
const menuService = new MenuService()
|
||||||
|
|
||||||
|
|
@ -253,6 +254,11 @@ export function MenuAddDialog({
|
||||||
.sort((a, b) => a.label.localeCompare(b.label))
|
.sort((a, b) => a.label.localeCompare(b.label))
|
||||||
}, [isEditMode, rawItems, editMenu])
|
}, [isEditMode, rawItems, editMenu])
|
||||||
|
|
||||||
|
const parentSelectOptions = parentOptions.map((option) => ({
|
||||||
|
value: option.code,
|
||||||
|
label: option.label,
|
||||||
|
}))
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!form.code.trim() || !form.menuTextEn.trim()) return
|
if (!form.code.trim() || !form.menuTextEn.trim()) return
|
||||||
if (shortNameRequired && !form.shortName.trim()) return
|
if (shortNameRequired && !form.shortName.trim()) return
|
||||||
|
|
@ -420,18 +426,19 @@ export function MenuAddDialog({
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<label className={labelCls}>{translate('::App.Platform.MenuParent')}</label>
|
<label className={labelCls}>{translate('::App.Platform.MenuParent')}</label>
|
||||||
{isEditMode ? (
|
{isEditMode ? (
|
||||||
<select
|
<Select
|
||||||
value={form.parentCode}
|
size="sm"
|
||||||
onChange={(e) => setForm((p) => ({ ...p, parentCode: e.target.value }))}
|
isClearable
|
||||||
className={fieldCls}
|
placeholder={translate('::App.WizardStep1.RootMenu')}
|
||||||
>
|
menuPortalTarget={document.body}
|
||||||
<option value="">{translate('::App.WizardStep1.RootMenu')}</option>
|
options={parentSelectOptions}
|
||||||
{parentOptions.map((option) => (
|
value={parentSelectOptions.filter(
|
||||||
<option key={option.code} value={option.code}>
|
(option) => option.value === form.parentCode,
|
||||||
{option.label}
|
)}
|
||||||
</option>
|
onChange={(option) =>
|
||||||
))}
|
setForm((p) => ({ ...p, parentCode: option?.value ?? '' }))
|
||||||
</select>
|
}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Input unstyle disabled value={form.parentCode} className={disabledCls} />
|
<Input unstyle disabled value={form.parentCode} className={disabledCls} />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue