<select/> -> <Select /> olarak değiştirildi.

This commit is contained in:
Sedat ÖZTÜRK 2026-08-20 17:54:11 +03:00
parent 7186cb0ab0
commit 4c09de9ad0
27 changed files with 1114 additions and 913 deletions

View file

@ -1,6 +1,6 @@
import { useMemo, useState } from 'react'
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 Input from '@/components/ui/Input'
@ -352,6 +352,11 @@ const StyleModal = ({
const [search, setSearch] = useState('')
const [customClass, setCustomClass] = useState('')
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 classes = useMemo(() => {
const source =
@ -409,18 +414,15 @@ const StyleModal = ({
onChange={(event) => setSearch(event.target.value)}
/>
</label>
<select
className="rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-950 dark:text-white"
value={category}
onChange={(event) => setCategory(event.target.value as typeof category)}
>
<option value="__all__">{translate('::App.StyleModal.AllCategories')}</option>
{Object.keys(STYLE_GROUPS).map((name) => (
<option key={name} value={name}>
{name}
</option>
))}
</select>
<Select
size="sm"
menuPortalTarget={document.body}
options={categoryOptions}
value={categoryOptions.filter((option) => option.value === category)}
onChange={(option) =>
setCategory((option?.value ?? '__all__') as typeof category)
}
/>
</div>
)}
{mode === 'class' && (

View file

@ -1,4 +1,4 @@
import { Button, Dialog } from '@/components/ui'
import { Button, Dialog, Select } from '@/components/ui'
import { useLocalization } from '@/utils/hooks/useLocalization'
import Editor, { type Monaco } from '@monaco-editor/react'
import type * as monacoApi from 'monaco-editor'
@ -276,18 +276,20 @@ function ScriptBuilderDialog({
)
}
return (
<select
className={controlClass}
value={options.includes(selectedValue) ? selectedValue : ''}
onChange={(event) => onChange(event.target.value)}
>
<option value="">{placeholder}</option>
{options.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<Select
size="sm"
className="w-full"
isClearable
placeholder={placeholder}
menuPortalTarget={document.body}
options={options.map((option) => ({ value: option, label: option }))}
value={
options.includes(selectedValue)
? { value: selectedValue, label: selectedValue }
: null
}
onChange={(option) => onChange(option?.value ?? '')}
/>
)
}
@ -318,24 +320,21 @@ function ScriptBuilderDialog({
</button>
</span>
))}
<select
className={`${controlClass} !h-8 !w-44`}
value=""
onChange={(event) => {
const next = event.target.value
<Select
size="sm"
className="w-44"
placeholder={translate('::App.ScriptBuilder.AddOption')}
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
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>
)
@ -357,17 +356,21 @@ function ScriptBuilderDialog({
param.placeholder || translate('::App.ScriptBuilder.Choose'),
)}
{param.type === 'select' && (
<select
className={controlClass}
value={currentValue}
onChange={(event) => updateParam(rule.id, param.key, event.target.value)}
>
{param.choices?.map((choice) => (
<option key={choice.value} value={choice.value}>
{choice.label}
</option>
))}
</select>
<Select
size="sm"
className="w-full"
menuPortalTarget={document.body}
options={(param.choices ?? []).map((choice) => ({
value: choice.value,
label: choice.label,
}))}
value={(param.choices ?? [])
.filter((choice) => choice.value === currentValue)
.map((choice) => ({ value: choice.value, label: choice.label }))}
onChange={(option) =>
option && updateParam(rule.id, param.key, option.value)
}
/>
)}
{(param.type === 'text' || param.type === 'number') && (
<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 operator = dialect.operators.find((item) => item.value === condition.operator)
const kind =
@ -401,16 +424,16 @@ function ScriptBuilderDialog({
<span className="mb-1 block text-xs text-gray-500">
{translate('::App.ScriptBuilder.Conjunction')}
</span>
<select
className={controlClass}
value={rule.join ?? 'and'}
onChange={(event) =>
updateRule(rule.id, { join: event.target.value as ScriptRule['join'] })
<Select
size="sm"
className="w-full"
menuPortalTarget={document.body}
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>
) : (
<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">
{translate('::App.ScriptBuilder.Comparison')}
</span>
<select
className={controlClass}
value={condition.operator}
onChange={(event) => updateCondition(rule, index, { operator: event.target.value })}
>
{dialect.operators.map((item) => (
<option key={item.value} value={item.value}>
{item.label.startsWith('App.') ? translate('::' + item.label) : item.label}
</option>
))}
</select>
<Select
size="sm"
className="w-full"
menuPortalTarget={document.body}
options={operatorOptions}
value={operatorOptions.filter((option) => option.value === condition.operator)}
onChange={(option) =>
option && updateCondition(rule, index, { operator: option.value })
}
/>
</label>
{operator?.needsSource && dialect.conditionKinds.length > 1 && (
@ -445,19 +467,19 @@ function ScriptBuilderDialog({
<span className="mb-1 block text-xs text-gray-500">
{translate('::App.Listform.ListformField.SourceId')}
</span>
<select
className={controlClass}
value={condition.kind ?? dialect.conditionKinds[0]?.value ?? ''}
onChange={(event) =>
updateCondition(rule, index, { kind: event.target.value, source: '' })
<Select
size="sm"
className="w-full"
menuPortalTarget={document.body}
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>
)}
@ -602,17 +624,18 @@ function ScriptBuilderDialog({
{dialect.triggers && (
<label className="w-full md:w-56">
<span className="mb-1 block text-xs text-gray-500">Ne zaman</span>
<select
className={controlClass}
value={trigger ?? dialect.triggers[0].value}
onChange={(event) => updateRule(rule.id, { trigger: event.target.value })}
>
{dialect.triggers.map((item) => (
<option key={item.value} value={item.value} title={item.help}>
{item.label}
</option>
))}
</select>
<Select
size="sm"
className="w-full"
menuPortalTarget={document.body}
options={triggerOptions}
value={triggerOptions.filter(
(option) => option.value === (trigger ?? dialect.triggers?.[0]?.value),
)}
onChange={(option) =>
option && updateRule(rule.id, { trigger: option.value })
}
/>
</label>
)}

View file

@ -248,6 +248,9 @@ function SelectBase<
placeholder: (provided) => ({ ...provided, margin: 0 }),
singleValue: (provided) => ({ ...provided, margin: 0 }),
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,
}}
theme={(theme) => ({

View file

@ -999,18 +999,19 @@ const SqlDataSourceView = ({
{rows.length > 1 && (
<label className="flex items-center gap-1">
{translate('::App.Platform.Row')}
<select
className="rounded border border-sky-300 bg-white px-1 py-0.5 text-[10px] dark:border-sky-800 dark:bg-slate-900"
value={rowIndex}
onChange={(event) => goToRow(Number(event.target.value) || 0)}
onClick={(event) => event.stopPropagation()}
>
{rows.map((_, index) => (
<option key={index} value={index}>
{index + 1}
</option>
))}
</select>
<UiKit.Select
size="xs"
className="min-w-[4.5rem]"
maxMenuHeight={200}
menuPosition="fixed"
menuPortalTarget={window.document.body}
options={rows.map((_, index) => ({
value: index,
label: String(index + 1),
}))}
value={{ value: rowIndex, label: String(rowIndex + 1) }}
onChange={(option) => goToRow(Number(option?.value) || 0)}
/>
</label>
)}
</div>

View file

@ -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 type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
@ -50,6 +50,10 @@ function TablePickerModal({
null,
)
const [pickerColumns, setPickerColumns] = useState<DatabaseColumnDto[]>([])
const pickerColumnOptions = pickerColumns.map((c) => ({
value: c.columnName ?? '',
label: c.columnName ?? '',
}))
const [isLoadingColumns, setIsLoadingColumns] = useState(false)
const [keyCol, setKeyCol] = useState('')
const [nameCol, setNameCol] = useState('')
@ -163,35 +167,31 @@ function TablePickerModal({
<label className="text-[11px] font-medium text-gray-500 dark:text-gray-400">
{translate('::App.ListFormFieldEdit.KeyColumn')}
</label>
<select
value={keyCol}
className="w-full text-xs h-8 px-2 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
onChange={(e) => setKeyCol(e.target.value)}
>
<option value="">{translate('::App.Platform.Select')}</option>
{pickerColumns.map((c) => (
<option key={c.columnName} value={c.columnName}>
{c.columnName}
</option>
))}
</select>
<Select
size="sm"
className="w-full text-xs"
isClearable
placeholder={translate('::App.Platform.Select')}
menuPortalTarget={document.body}
options={pickerColumnOptions}
value={pickerColumnOptions.filter((option) => option.value === keyCol)}
onChange={(option) => setKeyCol(option?.value ?? '')}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-[11px] font-medium text-gray-500 dark:text-gray-400">
{translate('::App.ListFormFieldEdit.NameColumn')}
</label>
<select
value={nameCol}
className="w-full text-xs h-8 px-2 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
onChange={(e) => setNameCol(e.target.value)}
>
<option value="">{translate('::App.Platform.Select')}</option>
{pickerColumns.map((c) => (
<option key={c.columnName} value={c.columnName}>
{c.columnName}
</option>
))}
</select>
<Select
size="sm"
className="w-full text-xs"
isClearable
placeholder={translate('::App.Platform.Select')}
menuPortalTarget={document.body}
options={pickerColumnOptions}
value={pickerColumnOptions.filter((option) => option.value === nameCol)}
onChange={(option) => setNameCol(option?.value ?? '')}
/>
</div>
{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">

View file

@ -1,4 +1,4 @@
import { Button, Dialog } from '@/components/ui'
import { Button, Dialog, Select } from '@/components/ui'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useEffect, useMemo, useState } from 'react'
import type { IconType } from 'react-icons'
@ -61,6 +61,7 @@ type EditorOptionsBuilderDialogProps = {
}
const leafTypes: LeafValueType[] = ['string', 'number', 'boolean', 'json']
const leafTypeOptions = leafTypes.map((item) => ({ value: item, label: item }))
const PRESETS_KEY = '__presets'
const UNMANAGED_KEY = '__unmanaged'
@ -545,25 +546,22 @@ function EditorOptionsBuilderDialog({
>
{path}
</span>
<select
className={`${controlClass} col-span-2`}
value={type}
onChange={(event) =>
<Select
size="sm"
className="col-span-2"
menuPortalTarget={document.body}
options={leafTypeOptions}
value={leafTypeOptions.filter((option) => option.value === type)}
onChange={(option) =>
setPath(
path,
textToLeaf(
leafToText(current),
event.target.value as LeafValueType,
(option?.value ?? 'string') as LeafValueType,
),
)
}
>
{leafTypes.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
/>
<Input
unstyle
className={`${controlClass} col-span-5`}
@ -596,17 +594,16 @@ function EditorOptionsBuilderDialog({
placeholder="path.to.option"
onChange={(event) => setNewPath(event.target.value)}
/>
<select
className={`${controlClass} col-span-2`}
value={newType}
onChange={(event) => setNewType(event.target.value as LeafValueType)}
>
{leafTypes.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
<Select
size="sm"
className="col-span-2"
menuPortalTarget={document.body}
options={leafTypeOptions}
value={leafTypeOptions.filter((option) => option.value === newType)}
onChange={(option) =>
setNewType((option?.value ?? 'string') as LeafValueType)
}
/>
<Input
unstyle
className={`${controlClass} col-span-5`}

View file

@ -4,6 +4,7 @@ import { coerceNumber, coerceSize, leafToText } from './jsonUtils'
import type { OptionSpec } from './optionSpecs'
import { useLocalization } from '@/utils/hooks/useLocalization'
import Input from '@/components/ui/Input'
import Select from '@/components/ui/Select'
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'
@ -64,19 +65,24 @@ const BooleanControl = ({
? 'false'
: ''
const booleanOptions = [
{ value: 'true', label: 'true' },
{ value: 'false', label: 'false' },
]
return (
<select
className={controlClass}
value={current}
onChange={(event) => {
if (!event.target.value) return onChange(undefined)
onChange(event.target.value === 'true')
<Select
size="sm"
isClearable
placeholder={translate('::App.ListFormEditorOptions.Undefined')}
menuPortalTarget={document.body}
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 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 (
<select
className={controlClass}
value={current}
onChange={(event) => {
const raw = event.target.value
<Select
size="sm"
isClearable
placeholder={translate('::App.ListFormEditorOptions.Undefined')}
menuPortalTarget={document.body}
options={choiceOptions}
value={choiceOptions.filter((option) => option.value === current)}
onChange={(option) => {
const raw = option?.value
if (!raw) return onChange(undefined)
const choice = spec.choices?.find((item) => String(item.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>
/>
)
}

View file

@ -44,7 +44,7 @@ export const triggerLabels: { value: RuleTrigger; label: string; help: string }[
label: 'App.ScriptBuilderOpen.OpenLabel',
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. */

View file

@ -1,4 +1,4 @@
import { Button, Dialog } from '@/components/ui'
import { Button, Dialog, Select } from '@/components/ui'
import type { SelectBoxOption } from '@/types/shared'
import { useLocalization } from '@/utils/hooks/useLocalization'
import {
@ -230,6 +230,10 @@ function SortableItem({
null,
)
const [pickerColumns, setPickerColumns] = useState<DatabaseColumnDto[]>([])
const pickerColumnOptions = pickerColumns.map((c) => ({
value: c.columnName ?? '',
label: c.columnName ?? '',
}))
const [isLoadingPickerColumns, setIsLoadingPickerColumns] = useState(false)
const [pickerKeyCol, setPickerKeyCol] = useState('')
const [pickerNameCol, setPickerNameCol] = useState('')
@ -321,17 +325,17 @@ function SortableItem({
<span className="text-[10px] text-gray-400 font-medium">
{translate('::App.WizardStep3.EditorType')}
</span>
<select
value={item.editorType}
onChange={(e) => onEditorTypeChange(e.target.value)}
className="w-full text-xs h-7 px-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
>
{columnEditorTypeListOptions.map((et) => (
<option key={et.value} value={et.value}>
{et.label}
</option>
))}
</select>
<Select
size="xs"
className="w-full text-xs"
maxMenuHeight={200}
menuPortalTarget={document.body}
options={columnEditorTypeListOptions}
value={columnEditorTypeListOptions.filter(
(option) => option.value === item.editorType,
)}
onChange={(option) => option && onEditorTypeChange(option.value)}
/>
</div>
<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">
{translate('::App.WizardStep3.LookupDataSourceType')}
</span>
<select
value={item.lookupDataSourceType}
onChange={(e) =>
onLookupDataSourceTypeChange(e.target.value as unknown as UiLookupDataSourceTypeEnum)
<Select
size="xs"
className="w-full text-xs"
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 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">
{translate('::App.ListFormFieldEdit.KeyColumn')}
</label>
<select
value={pickerKeyCol}
onChange={(e) => setPickerKeyCol(e.target.value)}
className="w-full text-xs h-7 px-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
>
<option value="">{translate('::App.Platform.Select')}</option>
{pickerColumns.map((c) => (
<option key={c.columnName} value={c.columnName}>
{c.columnName}
</option>
))}
</select>
<Select
size="xs"
className="w-full text-xs"
isClearable
placeholder={translate('::App.Platform.Select')}
menuPortalTarget={document.body}
options={pickerColumnOptions}
value={pickerColumnOptions.filter(
(option) => option.value === pickerKeyCol,
)}
onChange={(option) => setPickerKeyCol(option?.value ?? '')}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-[10px] font-medium text-gray-500 dark:text-gray-400">
{translate('::App.ListFormFieldEdit.NameColumn')}
</label>
<select
value={pickerNameCol}
onChange={(e) => setPickerNameCol(e.target.value)}
className="w-full text-xs h-7 px-1.5 rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 focus:outline-none focus:border-indigo-400"
>
<option value="">{translate('::App.Platform.Select')}</option>
{pickerColumns.map((c) => (
<option key={c.columnName} value={c.columnName}>
{c.columnName}
</option>
))}
</select>
<Select
size="xs"
className="w-full text-xs"
isClearable
placeholder={translate('::App.Platform.Select')}
menuPortalTarget={document.body}
options={pickerColumnOptions}
value={pickerColumnOptions.filter(
(option) => option.value === pickerNameCol,
)}
onChange={(option) => setPickerNameCol(option?.value ?? '')}
/>
</div>
{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">
@ -651,17 +657,17 @@ function SortableItem({
<div className="flex items-center gap-2">
<div className="flex items-center gap-1">
<span className="text-[10px] text-gray-400">{translate('::App.WizardStep3.Span')}</span>
<select
value={item.colSpan}
onChange={(e) => onColSpanChange(Number(e.target.value))}
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"
>
{Array.from({ length: groupColCount }, (_, i) => i + 1).map((n) => (
<option key={n} value={n}>
{n}
</option>
))}
</select>
<Select
size="xs"
className="w-20 text-xs"
menuPortalTarget={document.body}
options={Array.from({ length: groupColCount }, (_, i) => ({
value: i + 1,
label: String(i + 1),
}))}
value={{ value: item.colSpan, label: String(item.colSpan) }}
onChange={(option) => option && onColSpanChange(Number(option.value))}
/>
</div>
<label
className="flex items-center gap-1 cursor-pointer ml-auto"

View file

@ -1,4 +1,5 @@
import classNames from 'classnames'
import Select from '@/components/ui/Select'
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
import Container from '@/components/shared/Container'
import { Button, Checkbox, Dialog, Input, Menu, toast } from '@/components/ui'
@ -480,6 +481,10 @@ function RolesPermission({
const [copyDialogOpen, setCopyDialogOpen] = useState(false)
const [copyDialogRole, setCopyDialogRole] = useState('')
const copyRoleOptions = roleList
.filter((role) => role !== name)
.map((role) => ({ value: role, label: role }))
// Fetch all roles for select (except current)
useEffect(() => {
async function fetchRoles() {
@ -571,20 +576,16 @@ function RolesPermission({
>
<h5 className="mb-2">{translate('::AbpIdentity.Roles.CopyPermissions')}</h5>
<div className="mb-4">
<select
className="border rounded px-2 py-1 w-full"
value={copyDialogRole}
onChange={(e) => setCopyDialogRole(e.target.value)}
>
<option value="">{translate('::App.Platform.Select')}</option>
{roleList
.filter((role) => role !== name)
.map((role) => (
<option key={role} value={role}>
{role}
</option>
))}
</select>
<Select
size="sm"
className="w-full"
isClearable
placeholder={translate('::App.Platform.Select')}
menuPortalTarget={document.body}
options={copyRoleOptions}
value={copyRoleOptions.filter((option) => option.value === copyDialogRole)}
onChange={(option) => setCopyDialogRole(option?.value ?? '')}
/>
</div>
<div className="flex justify-end gap-2">
<Button variant="plain" onClick={() => setCopyDialogOpen(false)}>

View file

@ -9,6 +9,7 @@ import { FaTimes, FaUsers, FaUser, FaBullhorn, FaPaperPlane } from 'react-icons/
import { Button } from '@/components/ui'
import { useLocalization } from '@/utils/hooks/useLocalization'
import Input from '@/components/ui/Input'
import Select from '@/components/ui/Select'
interface ChatPanelProps {
user: { id: string; name: string; role: string }
@ -50,6 +51,11 @@ const ChatPanel = ({
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 (
<div className="h-full bg-white flex flex-col text-gray-900">
{/* Header */}
@ -125,21 +131,18 @@ const ChatPanel = ({
</div>
{messageMode === 'private' && (
<select
value={selectedRecipient?.id || ''}
onChange={(e) => {
const recipient = availableRecipients.find((p) => p.id === e.target.value)
<Select
size="xs"
className="w-full"
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)
}}
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>

View file

@ -29,7 +29,7 @@ import PageTitle from '@/components/shared/PageTitle'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { VideoroomDto } from '@/proxy/videoroom/models'
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'
export interface RoomProps {
@ -45,6 +45,21 @@ const RoomList = () => {
const { user } = useStoreState((state) => state.auth)
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 = {
id: crypto.randomUUID(),
name: '',
@ -733,80 +748,70 @@ const RoomList = () => {
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{translate('::App.VideoRoom.DefaultMicrophoneState')}
</label>
<select
value={videoroom.settingsDto?.defaultMicrophoneState}
onChange={(e) =>
<Select
size="sm"
menuPortalTarget={document.body}
options={microphoneStateOptions}
value={microphoneStateOptions.filter(
(option) =>
option.value === videoroom.settingsDto?.defaultMicrophoneState,
)}
onChange={(option) =>
setVideoroom({
...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 className="flex items-center justify-between">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{translate('::App.VideoRoom.DefaultCameraState')}
</label>
<select
value={videoroom.settingsDto?.defaultCameraState}
onChange={(e) =>
<Select
size="sm"
menuPortalTarget={document.body}
options={cameraStateOptions}
value={cameraStateOptions.filter(
(option) => option.value === videoroom.settingsDto?.defaultCameraState,
)}
onChange={(option) =>
setVideoroom({
...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 className="flex items-center justify-between">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{translate('::App.VideoRoom.DefaultLayout')}
</label>
<select
value={videoroom.settingsDto?.defaultLayout}
onChange={(e) =>
<Select
size="sm"
menuPortalTarget={document.body}
options={layoutOptions}
value={layoutOptions.filter(
(option) => option.value === videoroom.settingsDto?.defaultLayout,
)}
onChange={(option) =>
setVideoroom({
...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>
<label className="flex items-center space-x-3">

View file

@ -25,6 +25,7 @@ import { parseComponentDependencies } from '@/contexts/componentRuntime'
import { usePermission } from '@/utils/hooks/usePermission'
import { COMPONENT_PERMISSION } from '@/constants/permission.constant'
import Input from '@/components/ui/Input'
import Select from '@/components/ui/Select'
const ComponentManager: React.FC = () => {
const {
@ -45,6 +46,12 @@ const ComponentManager: React.FC = () => {
const activeComponents = components?.filter((c) => c.isActive).length || 0
const inactiveComponents = totalComponents - activeComponents
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 canCreate = checkPermission(COMPONENT_PERMISSION.CREATE)
const canUpdate = checkPermission(COMPONENT_PERMISSION.UPDATE)
@ -143,19 +150,13 @@ const ComponentManager: React.FC = () => {
</div>
<div className="flex items-center gap-2">
<FaFilter className="w-5 h-5 text-slate-500 dark:text-gray-400" />
<select
value={filterActive}
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"
onChange={(e) => setFilterActive(e.target.value as 'all' | 'active' | 'inactive')}
>
<option value="all">{translate('::App.ComponentFilter.All')}</option>
<option value="active">
{translate('::App.EntityFilter.Active')}
</option>
<option value="inactive">
{translate('::App.EntityFilter.Inactive')}
</option>
</select>
<Select
size="sm"
className="min-w-[9rem]"
options={filterActiveOptions}
value={filterActiveOptions.filter((o) => o.value === filterActive)}
onChange={(option) => setFilterActive(option?.value ?? 'all')}
/>
</div>
<Button
className="flex items-center gap-2"

View file

@ -18,9 +18,20 @@ import PageTitle from '@/components/shared/PageTitle'
import { ROUTES_ENUM } from '@/routes/route.constant'
import Button from '@/components/ui/Button'
import Input from '@/components/ui/Input'
import Select from '@/components/ui/Select'
const DynamicServiceManager: React.FC = () => {
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 [isLoading, setIsLoading] = useState(false)
const [searchTerm, setSearchTerm] = useState('')
@ -140,22 +151,12 @@ const DynamicServiceManager: React.FC = () => {
</div>
<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" />
<select
value={filterStatus}
onChange={(e) =>
setFilterStatus(e.target.value as 'all' | 'Success' | 'Failed' | 'Pending')
}
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>
<Select
className="w-full lg:w-48"
options={filterStatusOptions}
value={filterStatusOptions.filter((o) => o.value === filterStatus)}
onChange={(option) => setFilterStatus(option?.value ?? 'all')}
/>
</div>
<div className="w-full sm:w-auto">
<Button

View file

@ -7,6 +7,7 @@ import {
FaTable,
FaPlus,
FaEye,
FaProjectDiagram,
FaCog,
FaCode,
FaDatabase,
@ -748,7 +749,7 @@ const SqlObjectExplorer = ({
closeCtx()
}}
>
<FaEye className="text-purple-500" /> Design View
<FaProjectDiagram className="text-purple-500" /> Design View
</Button>
)}

View file

@ -1,5 +1,5 @@
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 { getDataSources } from '@/services/data-source.service'
import type { DataSourceDto } from '@/proxy/data-source'
@ -272,6 +272,11 @@ const SqlQueryManager = () => {
`[${escapeSqlIdentifier(schemaName)}].[${escapeSqlIdentifier(objectName)}]`
const getSafePgFullName = (schemaName: string, objectName: string) =>
`"${escapePgIdentifier(schemaName)}"."${escapePgIdentifier(objectName)}"`
const dataSourceOptions = state.dataSources.map((ds) => ({
value: ds.code ?? '',
label: ds.code ?? '',
}))
const selectedDataSourceType = state.dataSources.find(
(item) => item.code === state.selectedDataSource,
)?.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-wrap items-center gap-2 sm:gap-3">
<FaDatabase className="text-lg text-blue-500" />
<select
className="border border-gray-300 rounded px-2 py-1 max-w-full dark:bg-gray-700 dark:border-gray-600"
disabled={state.dataSources.length === 0}
value={state.selectedDataSource || ''}
onChange={(e) => {
const ds = state.dataSources.find((d) => d.code === e.target.value)
<Select
size="sm"
className="min-w-[12rem] max-w-full"
isDisabled={state.dataSources.length === 0}
menuPortalTarget={document.body}
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)
}}
>
{state.dataSources.map((ds) => (
<option key={ds.code} value={ds.code}>
{ds.code}
</option>
))}
</select>
/>
<DbMigrateButton />
{/* Seed dosyalari (configs/seeds) File Manager uzerinden yonetilir. */}
<Link to={ROUTES_ENUM.protected.admin.files} target="_blank">

View file

@ -1,6 +1,6 @@
import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react'
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 {
FaPlus,
FaTrash,
@ -1336,6 +1336,28 @@ const SqlTableDesignerDialog = ({
const [dbTables, setDbTables] = useState<{ schemaName: string; tableName: string }[]>([])
const [targetTableColumns, setTargetTableColumns] = 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 [indexes, setIndexes] = useState<TableIndex[]>([])
const [originalIndexes, setOriginalIndexes] = useState<TableIndex[]>([])
@ -2374,11 +2396,9 @@ const SqlTableDesignerDialog = ({
>
<div className="col-span-4">
<Input
unstyle
size="sm"
type="text"
className={`w-full px-2 py-1 text-sm border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white ${
isDuplicate ? 'border-red-400' : ''
}`}
className={isDuplicate ? 'w-full border-red-400' : 'w-full'}
placeholder={translate('::App.SqlQueryManager.ColumnNamePlaceholder')}
value={col.columnName}
onChange={(e) => updateColumn(col.id, 'columnName', e.target.value)}
@ -2391,28 +2411,27 @@ const SqlTableDesignerDialog = ({
/>
</div>
<div className="col-span-3">
<select
className="w-full px-2 py-1 text-sm border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white"
value={col.dataType}
onChange={(e) => {
const dt = e.target.value as SqlDataType
<Select
size="sm"
className="w-full"
maxMenuHeight={200}
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)
if (dt !== 'nvarchar') updateColumn(col.id, 'maxLength', '')
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 className="col-span-1">
<Input
unstyle
size="sm"
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="-"
value={col.maxLength}
disabled={col.dataType !== 'nvarchar'}
@ -2427,9 +2446,9 @@ const SqlTableDesignerDialog = ({
</div>
<div className="col-span-2">
<Input
unstyle
size="sm"
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')}
value={col.defaultValue}
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>
</label>
<Input
unstyle
size="sm"
type="text"
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}
onChange={(e) => onEntityNameChange(e.target.value)}
placeholder={translate('::App.SqlQueryManager.EntityNamePlaceholder')}
@ -2574,10 +2593,10 @@ const SqlTableDesignerDialog = ({
{translate('::App.Listform.ListformField.TableName')}
</label>
<Input
unstyle
size="sm"
type="text"
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}
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">
{translate('::App.SqlQueryManager.FkColumnInThisTable')}
</label>
<select
value={fkForm.fkColumnName}
onChange={(e) => setFkForm((f) => ({ ...f, fkColumnName: e.target.value }))}
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>
{columns
.filter((c) => c.columnName.trim())
.map((c) => (
<option key={c.id} value={c.columnName}>
{c.columnName}
</option>
))}
</select>
<Select
size="sm"
className="w-full"
isClearable
placeholder={translate('::App.Platform.Select')}
menuPortalTarget={document.body}
options={fkColumnOptions}
value={fkColumnOptions.filter(
(option) => option.value === fkForm.fkColumnName,
)}
onChange={(option) =>
setFkForm((f) => ({ ...f, fkColumnName: option?.value ?? '' }))
}
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
{translate('::App.SqlQueryManager.TargetTable')}
</label>
<select
value={fkForm.referencedTable}
onChange={(e) => {
const val = e.target.value
<Select
size="sm"
className="w-full"
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: '' }))
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>
@ -2805,29 +2824,21 @@ const SqlTableDesignerDialog = ({
? `${translate('::App.Platform.LoadingWithThreeDot')}`
: ''}
</label>
<select
value={fkForm.referencedColumn}
onChange={(e) => setFkForm((f) => ({ ...f, referencedColumn: e.target.value }))}
disabled={targetColsLoading || targetTableColumns.length === 0}
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"
>
<option value="">
{translate('::App.SqlQueryManager.SelectTargetTableFirst')}
</option>
{targetTableColumns.map((col) => (
<option
key={col}
value={col}
disabled={
!targetTableKeyColumns.some((k) => k.toLowerCase() === col.toLowerCase())
}
>
{targetTableKeyColumns.some((k) => k.toLowerCase() === col.toLowerCase())
? `${col} (PK/UNIQUE)`
: `${col} (Not Key)`}
</option>
))}
</select>
<Select
size="sm"
className="w-full"
isClearable
isDisabled={targetColsLoading || targetTableColumns.length === 0}
placeholder={translate('::App.SqlQueryManager.SelectTargetTableFirst')}
menuPortalTarget={document.body}
options={referencedColumnOptions}
value={referencedColumnOptions.filter(
(option) => option.value === fkForm.referencedColumn,
)}
onChange={(option) =>
setFkForm((f) => ({ ...f, referencedColumn: option?.value ?? '' }))
}
/>
{fkForm.referencedTable &&
!targetColsLoading &&
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">
{translate('::App.SqlQueryManager.CascadeUpdate')}
</label>
<select
value={fkForm.cascadeUpdate}
onChange={(e) =>
<Select
size="sm"
className="w-full"
menuPortalTarget={document.body}
options={cascadeOptions}
value={cascadeOptions.filter(
(option) => option.value === fkForm.cascadeUpdate,
)}
onChange={(option) =>
option &&
setFkForm((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>
<label className="block text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1.5">
{translate('::App.SqlQueryManager.CascadeDelete')}
</label>
<select
value={fkForm.cascadeDelete}
onChange={(e) =>
<Select
size="sm"
className="w-full"
menuPortalTarget={document.body}
options={cascadeOptions}
value={cascadeOptions.filter(
(option) => option.value === fkForm.cascadeDelete,
)}
onChange={(option) =>
option &&
setFkForm((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>
@ -2887,11 +2898,11 @@ const SqlTableDesignerDialog = ({
<div className="flex items-center gap-6">
<label className="flex items-center gap-2 cursor-pointer">
<Input
unstyle
size="sm"
type="checkbox"
checked={fkForm.isRequired}
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">
{translate('::App.Listform.ListformField.Required')}
@ -3135,14 +3146,14 @@ const SqlTableDesignerDialog = ({
{translate('::App.SqlQueryManager.IndexConstraintName')}
</label>
<Input
unstyle
size="sm"
type="text"
value={indexForm.indexName}
onChange={(e) => setIndexForm((f) => ({ ...f, indexName: e.target.value }))}
placeholder={
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>
@ -3150,13 +3161,13 @@ const SqlTableDesignerDialog = ({
<div className="flex items-center gap-3">
<label className="flex items-center gap-2 cursor-pointer">
<Input
unstyle
size="sm"
type="checkbox"
checked={indexForm.isClustered}
onChange={(e) =>
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>
</label>
@ -3191,7 +3202,7 @@ const SqlTableDesignerDialog = ({
>
<div className="col-span-1">
<Input
unstyle
size="sm"
type="checkbox"
checked={!!existing}
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 className="col-span-7 text-sm font-mono text-gray-800 dark:text-gray-200">
@ -3228,23 +3239,26 @@ const SqlTableDesignerDialog = ({
</div>
<div className="col-span-4">
{existing && (
<select
value={existing.order}
onChange={(e) =>
<Select
size="sm"
className="w-full"
menuPortalTarget={document.body}
options={indexOrderOptions}
value={indexOrderOptions.filter(
(option) => option.value === existing.order,
)}
onChange={(option) =>
option &&
setIndexForm((f) => ({
...f,
columns: f.columns.map((ic) =>
ic.columnName === col.columnName
? { ...ic, order: e.target.value as 'ASC' | 'DESC' }
? { ...ic, order: option.value }
: 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>

View file

@ -612,7 +612,7 @@ const SqlViewDesignerDialog = ({
</span>
<Input
unstyle
className={`${inputCls} w-[190px]`}
className={`${inputCls} w-[320px]`}
placeholder="Vw_OrderSummary"
value={settings.viewName}
disabled={isEditMode}

View file

@ -39,7 +39,7 @@ import { getList } from '@/services/form.service'
import type { GridDto } from '@/proxy/form/models'
import { getListForms } from '@/services/admin/list-form.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 VisualCanvas, { DESIGNER_DRAG_TYPE } from '@/components/visualDesigner/VisualCanvas'
import {
@ -1164,22 +1164,20 @@ const PropertyEditor = ({
if (type === 'select' && options) {
const currentValue = String(value ?? '')
return (
<select
className={inputClass}
value={currentValue}
onChange={(event) => onChange(event.target.value)}
>
{/* An empty value is not emitted, so the component keeps its own or its
container's default spell that out instead of showing a blank row. */}
{!options.includes(currentValue) && (
<option value="">{translate('::Abp.Mailing.Default')}</option>
)}
{options.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
/* An empty value is not emitted, so the component keeps its own or its
container's default spell that out instead of showing a blank row. */
<Select
size="xs"
className="w-full"
menuPortalTarget={window.document.body}
options={options.map((option) => ({ value: option, label: option }))}
value={
options.includes(currentValue)
? { value: currentValue, label: currentValue }
: { value: '', label: translate('::Abp.Mailing.Default') }
}
onChange={(option) => onChange(option?.value ?? '')}
/>
)
}
// 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">
Koleksiyon
</span>
<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"
value={selectOptionsBinding.path}
onChange={(event) =>
<Select
size="xs"
className="w-full"
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, {
path: event.target.value,
path: option?.value ?? '',
labelPath: '',
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>
{selectCollectionSample && (
<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">
{translate('::' + label)}
</span>
<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"
value={selectOptionsBinding[key] || ''}
onChange={(event) =>
<Select
size="xs"
className="w-full"
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, {
[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>
))}
</div>
@ -3609,28 +3617,25 @@ const VisualComponentDesigner = () => {
</button>
</span>
))}
<select
className="rounded-md border border-slate-300 bg-white px-2 py-1 text-[10px] dark:border-slate-700 dark:bg-slate-900"
value=""
onChange={(event) => {
const next = event.target.value
<Select
size="xs"
className="min-w-[8rem]"
placeholder={translate(
'::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
updateSelectedBindingDetails(optionDataProperty, {
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>
<p className="mt-1 text-[10px] leading-4 text-slate-500">
{translate('::App.DeveloperKitComponentDesigner.ExtraColumnsHint')}{' '}
@ -3764,42 +3769,54 @@ const VisualComponentDesigner = () => {
</label>
{lookup && (
<div className="mt-2 space-y-1.5">
<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"
value={lookup.sourceId}
onChange={(event) => selectColumnLookupSource(column, event.target.value)}
>
<option value="">
{translate('::App.DeveloperKitComponentDesigner.SelectEndpoint')}
</option>
{bindableDataSources.map((source) => (
<option key={source.id} value={source.id}>
{source.name}
</option>
))}
</select>
<Select
size="xs"
className="w-full"
isClearable
placeholder={translate(
'::App.DeveloperKitComponentDesigner.SelectEndpoint',
)}
menuPortalTarget={window.document.body}
options={bindableDataSources.map((source) => ({
value: source.id,
label: source.name,
}))}
value={bindableDataSources
.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 && (
<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"
value={lookup.path}
onChange={(event) =>
<Select
size="xs"
className="w-full"
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, {
path: event.target.value,
path: option?.value ?? '',
valueField: '',
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 && (
<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">
{translate(label)}
</span>
<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"
value={lookup[field]}
onChange={(event) =>
writeColumnLookup(column, { [field]: event.target.value })
}
>
<option value="">
{rowFields.length
<Select
size="xs"
className="w-full"
isClearable
maxMenuHeight={200}
placeholder={
rowFields.length
? translate('::App.DeveloperKitComponentDesigner.SelectField')
: translate(
'::App.DeveloperKitComponentDesigner.NoColumnInResponse',
)}
</option>
{rowFields.map((path) => (
<option key={path} value={path}>
{path}
</option>
))}
</select>
)
}
menuPortalTarget={window.document.body}
options={rowFields.map((path) => ({ value: path, label: path }))}
value={
lookup[field]
? { value: lookup[field], label: lookup[field] }
: null
}
onChange={(option) =>
writeColumnLookup(column, { [field]: option?.value ?? '' })
}
/>
</label>
))}
</div>
@ -3987,6 +4007,15 @@ const VisualComponentDesigner = () => {
const patchFilter = (id: string, updates: Partial<DesignerDataSourceFilter>) =>
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 (
<div className="rounded-lg border border-slate-200 p-3 dark:border-slate-800">
<div className="mb-1 flex items-center justify-between">
@ -4038,25 +4067,28 @@ const VisualComponentDesigner = () => {
>
<div className="flex items-center gap-1.5">
{strictColumns ? (
<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"
value={filter.field || ''}
onChange={(event) => patchFilter(filter.id, { field: event.target.value })}
>
<option value="">
{translate('::App.DeveloperKitComponentDesigner.FilterColumn')}
</option>
{/* A field configured before the list form changed is kept in
the list, so switching forms does not silently blank it. */}
{(filter.field && !columns.includes(filter.field)
<Select
size="xs"
className="min-w-0 flex-1"
isClearable
maxMenuHeight={200}
placeholder={translate(
'::App.DeveloperKitComponentDesigner.FilterColumn',
)}
menuPortalTarget={window.document.body}
options={(filter.field && !columns.includes(filter.field)
? [filter.field, ...columns]
: columns
).map((column) => (
<option key={column} value={column}>
{column}
</option>
))}
</select>
).map((column) => ({ value: column, label: column }))}
value={
filter.field
? { value: filter.field, label: filter.field }
: null
}
onChange={(option) =>
patchFilter(filter.id, { field: option?.value ?? '' })
}
/>
) : (
<Input
unstyle
@ -4067,21 +4099,26 @@ const VisualComponentDesigner = () => {
onChange={(event) => patchFilter(filter.id, { field: event.target.value })}
/>
)}
<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"
value={filter.operator}
onChange={(event) =>
<Select
size="xs"
className="w-24"
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, {
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
className="rounded p-1 text-slate-400 hover:text-red-600"
title={translate('::App.Platform.Delete')}
@ -4095,26 +4132,23 @@ const VisualComponentDesigner = () => {
</div>
{!valueless && (
<div className="flex items-center gap-1.5">
<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"
value={filter.source}
onChange={(event) =>
<Select
size="xs"
className="w-24"
maxMenuHeight={200}
menuPortalTarget={window.document.body}
options={filterSourceOptions}
value={filterSourceOptions.filter(
(option) => option.value === filter.source,
)}
onChange={(option) =>
option &&
patchFilter(filter.id, {
source: event.target.value as DesignerFilterSource,
source: option.value as DesignerFilterSource,
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' && (
<Input
unstyle
@ -4135,24 +4169,28 @@ const VisualComponentDesigner = () => {
next to the source selector left neither of them usable. */}
{!valueless && filter.source === 'record' && (
<div className="flex items-center gap-1.5">
<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"
value={masterRef}
onChange={(event) =>
<Select
size="xs"
className="min-w-0 flex-1"
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, {
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
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"
@ -4264,11 +4302,25 @@ const VisualComponentDesigner = () => {
<p className="mb-2 text-[10px] leading-4 text-slate-500">
{translate('::' + slot.description)}
</p>
<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"
value={currentValue}
onChange={(event) => {
const sourceId = event.target.value
<Select
size="xs"
className="w-full"
isClearable
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)
const source = document.dataSources.find((item) => item.id === sourceId)
if (
@ -4279,16 +4331,7 @@ const VisualComponentDesigner = () => {
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 && (
<p className="mt-1.5 text-[10px] leading-4 text-amber-600">
{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">
Koleksiyon
</span>
<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"
value={String(selectedNode.props.collectionPath ?? '')}
onChange={(event) => updateSelectedProp('collectionPath', event.target.value)}
>
{collectionPaths.map((path) => (
<option key={path || '__root__'} value={path}>
{path || translate('::App.DeveloperKitComponentDesigner.ResponseItself')}
</option>
))}
</select>
<Select
size="xs"
className="w-full"
maxMenuHeight={200}
menuPortalTarget={window.document.body}
options={collectionPaths.map((path) => ({
value: path,
label:
path || translate('::App.DeveloperKitComponentDesigner.ResponseItself'),
}))}
value={{
value: String(selectedNode.props.collectionPath ?? ''),
label:
String(selectedNode.props.collectionPath ?? '') ||
translate('::App.DeveloperKitComponentDesigner.ResponseItself'),
}}
onChange={(option) =>
updateSelectedProp('collectionPath', option?.value ?? '')
}
/>
</label>
</div>
<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">
{translate('::App.DeveloperKitComponentDesigner.KeyParamHint')}
</p>
<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"
value={getSqlDataSourceKeySource(selectedNode)}
onChange={(event) => updateSelectedProp('keySource', event.target.value)}
>
{SQL_DATA_SOURCE_KEY_SOURCES.map((item) => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</select>
<Select
size="xs"
className="mb-2 w-full"
maxMenuHeight={200}
menuPortalTarget={window.document.body}
options={SQL_DATA_SOURCE_KEY_SOURCES}
value={SQL_DATA_SOURCE_KEY_SOURCES.filter(
(item) => item.value === getSqlDataSourceKeySource(selectedNode),
)}
onChange={(option) => option && updateSelectedProp('keySource', option.value)}
/>
<Input
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"
@ -4499,6 +4551,10 @@ const VisualComponentDesigner = () => {
const column = binding?.sourceId === sqlScopeNode.id ? binding.path : ''
const rawDefault = selectedNode.props?.[SQL_DEFAULT_VALUE_PROP]
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}`
// 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.
@ -4552,18 +4608,24 @@ const VisualComponentDesigner = () => {
})}
</p>
{sqlRecordProperty === 'checked' ? (
<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"
disabled={!column}
value={currentDefault}
onChange={(event) => updateSelectedProp(SQL_DEFAULT_VALUE_PROP, event.target.value)}
>
<option value="">
{translate('::App.DeveloperKitComponentDesigner.NoDefaultValue')}
</option>
<option value="true">{translate('::App.Platform.Yes')}</option>
<option value="false">{translate('::App.Platform.No')}</option>
</select>
<Select
size="xs"
className="w-full"
isClearable
isDisabled={!column}
maxMenuHeight={200}
placeholder={translate(
'::App.DeveloperKitComponentDesigner.NoDefaultValue',
)}
menuPortalTarget={window.document.body}
options={sqlDefaultValueOptions}
value={sqlDefaultValueOptions.filter(
(option) => option.value === currentDefault,
)}
onChange={(option) =>
updateSelectedProp(SQL_DEFAULT_VALUE_PROP, option?.value ?? '')
}
/>
) : (
<Input
unstyle
@ -4665,11 +4727,24 @@ const VisualComponentDesigner = () => {
{sqlScopeSource?.name}
</div>
) : (
<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"
value={activeDataSource?.id || ''}
onChange={(event) => {
const sourceId = event.target.value
<Select
size="sm"
className="w-full"
isClearable
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)
if (
(isOptionDataComponent(selectedNode?.type) ||
@ -4703,20 +4778,7 @@ const VisualComponentDesigner = () => {
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>
</div>
@ -4733,24 +4795,30 @@ const VisualComponentDesigner = () => {
<span className="mb-1 block text-[10px] font-semibold uppercase tracking-wider text-slate-400">
Koleksiyon
</span>
<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"
value={tabularItemsBinding.path}
onChange={(event) =>
updateSelectedBindingDetails('items', { path: event.target.value })
}
>
{!tabularCollectionPaths.length && (
<option value="">
{translate('::App.DeveloperKitComponentDesigner.NoCollectionFound')}
</option>
<Select
size="xs"
className="w-full"
maxMenuHeight={200}
placeholder={translate(
'::App.DeveloperKitComponentDesigner.NoCollectionFound',
)}
{tabularCollectionPaths.map((path) => (
<option key={path || '__root__'} value={path}>
{path || translate('::App.DeveloperKitComponentDesigner.WholeResponseArray')}
</option>
))}
</select>
menuPortalTarget={window.document.body}
options={tabularCollectionPaths.map((path) => ({
value: path,
label:
path ||
translate('::App.DeveloperKitComponentDesigner.WholeResponseArray'),
}))}
value={{
value: tabularItemsBinding.path,
label:
tabularItemsBinding.path ||
translate('::App.DeveloperKitComponentDesigner.WholeResponseArray'),
}}
onChange={(option) =>
updateSelectedBindingDetails('items', { path: option?.value ?? '' })
}
/>
</label>
</div>
)}
@ -4871,6 +4939,29 @@ const VisualComponentDesigner = () => {
choice === '__root__' ||
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 (
<label
key={property.name}
@ -4882,11 +4973,21 @@ const VisualComponentDesigner = () => {
{property.tsType || property.type}
</span>
</span>
<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"
value={choice}
onChange={(event) => {
const nextChoice = event.target.value
<Select
size="xs"
className="w-full"
isClearable
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) {
updateSelectedBinding(property.name, '')
return
@ -4898,31 +4999,7 @@ const VisualComponentDesigner = () => {
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 && (
<div className="mt-1.5 truncate font-mono text-[9px] text-emerald-600">
{currentBindingSource?.name || currentBinding.sourceId}:{' '}
@ -6021,21 +6098,26 @@ const VisualComponentDesigner = () => {
<span className="mb-1.5 block text-[10px] font-semibold uppercase text-slate-500">
Metot
</span>
<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"
value={catalogSourceEditor.draft.method}
onChange={(event) =>
<Select
size="sm"
className="w-full"
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({
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>
</div>
<label className="block">

View file

@ -9,6 +9,7 @@ import { useState } from 'react'
import { FaCode, FaGripVertical, FaMagic, FaPlus, FaTrash } from 'react-icons/fa'
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
import Input from '@/components/ui/Input'
import Select from '@/components/ui/Select'
import {
GROUP_MODES,
GROUP_MODE_LABEL,
@ -86,6 +87,19 @@ export function CriteriaGrid({
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 =
'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">
<tr>
<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}
</th>
<th className="min-w-[110px] border border-gray-200 px-1 py-1 text-left dark:border-gray-700">
{labels.alias}
</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}
</th>
<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}
</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}
</th>
<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 })}
/>
) : (
<select
className={cellCls}
value={row.columnName}
onChange={(e) => onUpdateRow(row.id, { columnName: e.target.value })}
>
<option value=""></option>
{columnsOf(row.sourceId).map((c) => (
<option key={c.columnName} value={c.columnName}>
{c.columnName}
</option>
))}
</select>
<Select
size="xs"
className="w-full"
isClearable
placeholder="—"
menuPortalTarget={document.body}
options={columnsOf(row.sourceId).map((c) => ({
value: c.columnName ?? '',
label: c.columnName ?? '',
}))}
value={
row.columnName ? { value: row.columnName, label: row.columnName } : null
}
onChange={(option) =>
onUpdateRow(row.id, { columnName: option?.value ?? '' })
}
/>
)}
</td>
@ -230,19 +249,16 @@ export function CriteriaGrid({
{isExpression ? (
<span className="px-1 text-[10px] italic text-gray-400">SQL</span>
) : (
<select
className={cellCls}
value={row.sourceId}
onChange={(e) =>
onUpdateRow(row.id, { sourceId: e.target.value, columnName: '' })
<Select
size="xs"
className="w-full"
menuPortalTarget={document.body}
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>
@ -259,33 +275,36 @@ export function CriteriaGrid({
{grouped && (
<td className="border border-gray-200 px-0.5 dark:border-gray-700">
<select
className={cellCls}
value={row.groupMode}
disabled={isExpression}
onChange={(e) => {
const mode = e.target.value as GroupMode
<Select
size="xs"
className="w-full"
isDisabled={isExpression}
menuPortalTarget={document.body}
options={groupModeOptions}
value={groupModeOptions.filter((option) => option.value === row.groupMode)}
onChange={(option) => {
if (!option) return
const mode = option.value
onUpdateRow(row.id, {
groupMode: mode,
...(mode === 'Where' ? { output: false } : {}),
})
}}
>
{GROUP_MODES.map((m) => (
<option key={m} value={m}>
{GROUP_MODE_LABEL[m]}
</option>
))}
</select>
/>
</td>
)}
<td className="border border-gray-200 px-0.5 dark:border-gray-700">
<select
className={cellCls}
value={row.sortType}
onChange={(e) => {
const sortType = e.target.value as '' | 'ASC' | 'DESC'
<Select
size="xs"
className="w-full"
isClearable
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, {
sortType,
sortOrder: sortType
@ -293,11 +312,7 @@ export function CriteriaGrid({
: 0,
})
}}
>
<option value=""></option>
<option value="ASC">Ascending</option>
<option value="DESC">Descending</option>
</select>
/>
</td>
<td className="border border-gray-200 px-0.5 dark:border-gray-700">
@ -395,6 +410,11 @@ function FilterBuilder({
const [value2, setValue2] = useState('')
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 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"
onClick={(e) => e.stopPropagation()}
>
<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"
value={operator}
onChange={(e) => setOperator(e.target.value)}
>
{OPERATORS.map((op) => (
<option key={op} value={op}>
{op}
</option>
))}
</select>
<Select
size="xs"
className="mb-1 w-full"
menuPortalTarget={document.body}
options={OPERATORS.map((op) => ({ value: op, label: op }))}
value={{ value: operator, label: operator }}
onChange={(option) => option && setOperator(option.value)}
/>
{needsValue && (
<>
<Input
@ -447,14 +464,14 @@ function FilterBuilder({
onChange={(e) => setValue2(e.target.value)}
/>
)}
<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"
value={mode}
onChange={(e) => setMode(e.target.value as 'literal' | 'expression')}
>
<option value="literal">{labels.builderLiteral}</option>
<option value="expression">{labels.builderExpression}</option>
</select>
<Select
size="xs"
className="mb-1 w-full"
menuPortalTarget={document.body}
options={modeOptions}
value={modeOptions.filter((option) => option.value === mode)}
onChange={(option) => option && setMode(option.value)}
/>
</>
)}
<div className="flex items-center gap-1">

View file

@ -25,6 +25,7 @@ import {
} from 'react-icons/fa'
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
import Input from '@/components/ui/Input'
import Select from '@/components/ui/Select'
import {
BOX_FOOTER_HEIGHT,
BOX_HEADER_HEIGHT,
@ -343,6 +344,15 @@ export function DiagramPane({
? sources.find((s) => s.id === activeJoin.sourceId)
: 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 (
<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]"
@ -416,34 +426,33 @@ export function DiagramPane({
<div className="mb-1 text-[10px] font-semibold uppercase tracking-wide text-gray-400">
{labels.joinType}
</div>
<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"
value={activeJoinSource.joinKind}
onChange={(e) =>
onUpdateSource(activeJoinSource.id, { joinKind: e.target.value as JoinKind })
<Select
size="xs"
className="mb-1.5 w-full"
menuPortalTarget={document.body}
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) => (
<option key={k} value={k}>
{k === 'INNER' ? 'INNER JOIN' : `${k} OUTER JOIN`}
</option>
))}
</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"
value={activeJoin.operator}
onChange={(e) =>
/>
<Select
size="xs"
className="mb-1.5 w-full"
menuPortalTarget={document.body}
options={joinOperatorOptions}
value={joinOperatorOptions.filter(
(option) => option.value === activeJoin.operator,
)}
onChange={(option) =>
option &&
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
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"

View file

@ -388,7 +388,7 @@ function AnnouncementModalContent({ announcement, onClose, onLikeChange }: Annou
'::App.SocialWallPostItem.CommentPlaceholder',
)}
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
size="sm"

View file

@ -313,7 +313,7 @@ function EventModalContent({ event, onClose, onLikeChange }: EventModalProps) {
onKeyDown={handleKeyDown}
placeholder={translate('::App.Events.EventAttendance')}
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
type="button"

View file

@ -2,6 +2,7 @@ import React, { useState } from 'react'
import { GridExtraFilterState } from './Utils'
import { useLocalization } from '@/utils/hooks/useLocalization'
import Input from '@/components/ui/Input'
import Select from '@/components/ui/Select'
export function GridExtraFilterToolbar({
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 (
<div key={fs.fieldName} className="flex items-center gap-2">
<label>{fs.caption}</label>
{fs.controlType === 'Select' ? (
<select
className="border rounded px-1.5 py-1.5 dark:bg-gray-700 dark:text-white"
value={current?.value ?? ''}
onChange={(e) => handleSave(e.target.value)}
>
{!fs.defaultValue && <option value="">{translate('::App.Platform.Select')}</option>}
{fs.items?.map((item: any) => (
<option key={item.key} value={item.key}>
{item.value}
</option>
))}
</select>
<Select
size="xs"
className="min-w-[10rem]"
isClearable={!fs.defaultValue}
placeholder={translate('::App.Platform.Select')}
menuPortalTarget={document.body}
options={itemOptions}
value={itemOptions.filter(
(option) => option.value === String(current?.value ?? ''),
)}
onChange={(option) => handleSave(option?.value ?? '')}
/>
) : (
<Input
unstyle

View file

@ -38,6 +38,7 @@ import type { GridColumnData } from './GridColumnData'
import { flattenGridColumns } from './shared/columns'
import { useStoreState } from '@/store/store'
import Input from '@/components/ui/Input'
import Select from '@/components/ui/Select'
dayjs.extend(relativeTime)
@ -279,6 +280,11 @@ const TodoBoardContent = ({
[lookupText, options.statusExpr, translate],
)
const statusSelectOptions = statuses.map((status) => ({
value: status,
label: statusText(status),
}))
const clearDragState = useCallback(() => {
dragActiveRef.current = false
setDraggedKey(undefined)
@ -1000,23 +1006,21 @@ const TodoBoardContent = ({
{options.statusExpr && (
<div>
<label className={labelClass}>{translate('::App.Listform.ListformField.Status')}</label>
<select
className={inputClass}
disabled={!canUpdate}
value={String(draft[options.statusExpr] ?? '')}
onChange={(event) =>
<Select
size="sm"
isDisabled={!canUpdate}
menuPortalTarget={document.body}
options={statusSelectOptions}
value={statusSelectOptions.filter(
(option) => option.value === String(draft[options.statusExpr!] ?? ''),
)}
onChange={(option) =>
setDraft((current) => ({
...current,
[options.statusExpr!]: event.target.value,
[options.statusExpr!]: option?.value ?? '',
}))
}
>
{statuses.map((status) => (
<option key={status} value={status}>
{statusText(status)}
</option>
))}
</select>
/>
</div>
)}
{options.dueDateExpr && (

View file

@ -393,7 +393,7 @@ const MenuItemComponentBase: React.FC<MenuItemComponentProps> = ({
{isModalOpen && (
<Dialog
isOpen={isModalOpen}
width={640}
width={900}
onClose={() => setIsModalOpen(false)}
onRequestClose={() => setIsModalOpen(false)}
>
@ -497,6 +497,8 @@ const MenuItemComponentBase: React.FC<MenuItemComponentProps> = ({
{({ field, form }: FieldProps<SelectBoxOption>) => (
<Select
isClearable
maxMenuHeight={220}
menuPortalTarget={document.body}
field={field}
form={form}
options={permissions}

View file

@ -9,6 +9,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import { useConfig } from '@/components/ui/ConfigProvider'
import { useForm } from '@/components/ui/Form/context'
import Input from '@/components/ui/Input'
import Select from '@/components/ui/Select'
const menuService = new MenuService()
@ -253,6 +254,11 @@ export function MenuAddDialog({
.sort((a, b) => a.label.localeCompare(b.label))
}, [isEditMode, rawItems, editMenu])
const parentSelectOptions = parentOptions.map((option) => ({
value: option.code,
label: option.label,
}))
const handleSave = async () => {
if (!form.code.trim() || !form.menuTextEn.trim()) return
if (shortNameRequired && !form.shortName.trim()) return
@ -420,18 +426,19 @@ export function MenuAddDialog({
<div className="flex flex-col">
<label className={labelCls}>{translate('::App.Platform.MenuParent')}</label>
{isEditMode ? (
<select
value={form.parentCode}
onChange={(e) => setForm((p) => ({ ...p, parentCode: e.target.value }))}
className={fieldCls}
>
<option value="">{translate('::App.WizardStep1.RootMenu')}</option>
{parentOptions.map((option) => (
<option key={option.code} value={option.code}>
{option.label}
</option>
))}
</select>
<Select
size="sm"
isClearable
placeholder={translate('::App.WizardStep1.RootMenu')}
menuPortalTarget={document.body}
options={parentSelectOptions}
value={parentSelectOptions.filter(
(option) => option.value === form.parentCode,
)}
onChange={(option) =>
setForm((p) => ({ ...p, parentCode: option?.value ?? '' }))
}
/>
) : (
<Input unstyle disabled value={form.parentCode} className={disabledCls} />
)}