From 4c09de9ad09155c6830d59212d9ef16e72d0e823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Thu, 20 Aug 2026 17:54:11 +0300 Subject: [PATCH] =?UTF-8?q?=20->=20=20olarak=20de?= =?UTF-8?q?=C4=9Fi=C5=9Ftirildi.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/components/codeLayout/StyleModal.tsx | 28 +- .../scriptBuilder/ScriptBuilderDialog.tsx | 187 ++--- ui/src/components/ui/Select/Select.tsx | 3 + .../visualDesigner/VisualCanvas.tsx | 25 +- .../edit/form-fields/FormFieldTabLookup.tsx | 50 +- .../EditorOptionsBuilderDialog.tsx | 45 +- .../editor-options/OptionField.tsx | 71 +- .../editor-script/scriptRecipes.ts | 2 +- .../listForm/wizard/WizardStepFields.tsx | 124 ++-- .../admin/role-management/RolesPermission.tsx | 29 +- ui/src/views/admin/videoroom/ChatPanel.tsx | 29 +- ui/src/views/admin/videoroom/RoomList.tsx | 89 +-- .../views/developerKit/ComponentManager.tsx | 27 +- .../developerKit/DynamicServiceManager.tsx | 33 +- .../views/developerKit/SqlObjectExplorer.tsx | 3 +- ui/src/views/developerKit/SqlQueryManager.tsx | 32 +- .../developerKit/SqlTableDesignerDialog.tsx | 246 ++++--- .../developerKit/SqlViewDesignerDialog.tsx | 2 +- .../developerKit/VisualComponentDesigner.tsx | 692 ++++++++++-------- .../sqlViewDesigner/CriteriaGrid.tsx | 155 ++-- .../sqlViewDesigner/DiagramPane.tsx | 57 +- .../intranet/widgets/AnnouncementModal.tsx | 2 +- ui/src/views/intranet/widgets/EventModal.tsx | 2 +- ui/src/views/list/GridExtraFilterToolbar.tsx | 29 +- ui/src/views/list/TodoBoard.tsx | 30 +- ui/src/views/menu/MenuItemComponent.tsx | 4 +- ui/src/views/shared/MenuAddDialog.tsx | 31 +- 27 files changed, 1114 insertions(+), 913 deletions(-) diff --git a/ui/src/components/codeLayout/StyleModal.tsx b/ui/src/components/codeLayout/StyleModal.tsx index cb1c8276..bedb823c 100644 --- a/ui/src/components/codeLayout/StyleModal.tsx +++ b/ui/src/components/codeLayout/StyleModal.tsx @@ -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('__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)} /> - setCategory(event.target.value as typeof category)} - > - {translate('::App.StyleModal.AllCategories')} - {Object.keys(STYLE_GROUPS).map((name) => ( - - {name} - - ))} - + option.value === category)} + onChange={(option) => + setCategory((option?.value ?? '__all__') as typeof category) + } + /> )} {mode === 'class' && ( diff --git a/ui/src/components/scriptBuilder/ScriptBuilderDialog.tsx b/ui/src/components/scriptBuilder/ScriptBuilderDialog.tsx index fa5630b8..a3017a6f 100644 --- a/ui/src/components/scriptBuilder/ScriptBuilderDialog.tsx +++ b/ui/src/components/scriptBuilder/ScriptBuilderDialog.tsx @@ -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 ( - onChange(event.target.value)} - > - {placeholder} - {options.map((option) => ( - - {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({ ))} - { - const next = event.target.value + !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] }) }} - > - {translate('::App.ScriptBuilder.AddOption')} - {options - .filter((fieldName) => !selectedFields.includes(fieldName)) - .map((fieldName) => ( - - {fieldName} - - ))} - + /> ) @@ -357,17 +356,21 @@ function ScriptBuilderDialog({ param.placeholder || translate('::App.ScriptBuilder.Choose'), )} {param.type === 'select' && ( - updateParam(rule.id, param.key, event.target.value)} - > - {param.choices?.map((choice) => ( - - {choice.label} - - ))} - + ({ + 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') && ( ({ + 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({ {translate('::App.ScriptBuilder.Conjunction')} - - updateRule(rule.id, { join: event.target.value as ScriptRule['join'] }) + option.value === (rule.join ?? 'and'))} + onChange={(option) => + option && updateRule(rule.id, { join: option.value as ScriptRule['join'] }) } - > - {translate('::App.Platform.And')} - {translate('::App.Platform.Or')} - + /> ) : ( @@ -427,17 +450,16 @@ function ScriptBuilderDialog({ {translate('::App.ScriptBuilder.Comparison')} - updateCondition(rule, index, { operator: event.target.value })} - > - {dialect.operators.map((item) => ( - - {item.label.startsWith('App.') ? translate('::' + item.label) : item.label} - - ))} - + option.value === condition.operator)} + onChange={(option) => + option && updateCondition(rule, index, { operator: option.value }) + } + /> {operator?.needsSource && dialect.conditionKinds.length > 1 && ( @@ -445,19 +467,19 @@ function ScriptBuilderDialog({ {translate('::App.Listform.ListformField.SourceId')} - - updateCondition(rule, index, { kind: event.target.value, source: '' }) + + option.value === (condition.kind ?? dialect.conditionKinds[0]?.value ?? ''), + )} + onChange={(option) => + option && updateCondition(rule, index, { kind: option.value, source: '' }) } - > - {dialect.conditionKinds.map((item) => ( - - {item.label} - - ))} - + /> )} @@ -602,17 +624,18 @@ function ScriptBuilderDialog({ {dialect.triggers && ( Ne zaman - updateRule(rule.id, { trigger: event.target.value })} - > - {dialect.triggers.map((item) => ( - - {item.label} - - ))} - + option.value === (trigger ?? dialect.triggers?.[0]?.value), + )} + onChange={(option) => + option && updateRule(rule.id, { trigger: option.value }) + } + /> )} diff --git a/ui/src/components/ui/Select/Select.tsx b/ui/src/components/ui/Select/Select.tsx index f81e75e4..f0dcf3a7 100644 --- a/ui/src/components/ui/Select/Select.tsx +++ b/ui/src/components/ui/Select/Select.tsx @@ -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) => ({ diff --git a/ui/src/components/visualDesigner/VisualCanvas.tsx b/ui/src/components/visualDesigner/VisualCanvas.tsx index b318313e..910ed95a 100644 --- a/ui/src/components/visualDesigner/VisualCanvas.tsx +++ b/ui/src/components/visualDesigner/VisualCanvas.tsx @@ -999,18 +999,19 @@ const SqlDataSourceView = ({ {rows.length > 1 && ( {translate('::App.Platform.Row')} - goToRow(Number(event.target.value) || 0)} - onClick={(event) => event.stopPropagation()} - > - {rows.map((_, index) => ( - - {index + 1} - - ))} - + ({ + value: index, + label: String(index + 1), + }))} + value={{ value: rowIndex, label: String(rowIndex + 1) }} + onChange={(option) => goToRow(Number(option?.value) || 0)} + /> )} diff --git a/ui/src/views/admin/listForm/edit/form-fields/FormFieldTabLookup.tsx b/ui/src/views/admin/listForm/edit/form-fields/FormFieldTabLookup.tsx index d98ec080..6f775523 100644 --- a/ui/src/views/admin/listForm/edit/form-fields/FormFieldTabLookup.tsx +++ b/ui/src/views/admin/listForm/edit/form-fields/FormFieldTabLookup.tsx @@ -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([]) + 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({ {translate('::App.ListFormFieldEdit.KeyColumn')} - setKeyCol(e.target.value)} - > - {translate('::App.Platform.Select')} - {pickerColumns.map((c) => ( - - {c.columnName} - - ))} - + option.value === keyCol)} + onChange={(option) => setKeyCol(option?.value ?? '')} + /> {translate('::App.ListFormFieldEdit.NameColumn')} - setNameCol(e.target.value)} - > - {translate('::App.Platform.Select')} - {pickerColumns.map((c) => ( - - {c.columnName} - - ))} - + option.value === nameCol)} + onChange={(option) => setNameCol(option?.value ?? '')} + /> {keyCol && nameCol && ( diff --git a/ui/src/views/admin/listForm/edit/json-row-operations/EditorOptionsBuilderDialog.tsx b/ui/src/views/admin/listForm/edit/json-row-operations/EditorOptionsBuilderDialog.tsx index 17bf64e2..246bc3fd 100644 --- a/ui/src/views/admin/listForm/edit/json-row-operations/EditorOptionsBuilderDialog.tsx +++ b/ui/src/views/admin/listForm/edit/json-row-operations/EditorOptionsBuilderDialog.tsx @@ -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} - + option.value === type)} + onChange={(option) => setPath( path, textToLeaf( leafToText(current), - event.target.value as LeafValueType, + (option?.value ?? 'string') as LeafValueType, ), ) } - > - {leafTypes.map((item) => ( - - {item} - - ))} - + /> setNewPath(event.target.value)} /> - setNewType(event.target.value as LeafValueType)} - > - {leafTypes.map((item) => ( - - {item} - - ))} - + option.value === newType)} + onChange={(option) => + setNewType((option?.value ?? 'string') as LeafValueType) + } + /> { - if (!event.target.value) return onChange(undefined) - onChange(event.target.value === 'true') + option.value === current)} + onChange={(option) => { + if (!option?.value) return onChange(undefined) + onChange(option.value === 'true') }} - > - {translate('::App.ListFormEditorOptions.Undefined')} - true - false - + /> ) } @@ -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 ( - { - const raw = event.target.value + 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) }} - > - {translate('::App.ListFormEditorOptions.Undefined')} - {spec.choices?.map((choice) => ( - - {choice.label} - - ))} - {current && !known && ( - - {current} ({translate('::App.ListFormEditorOptions.CurrentValue')}) - - )} - + /> ) } diff --git a/ui/src/views/admin/listForm/edit/json-row-operations/editor-script/scriptRecipes.ts b/ui/src/views/admin/listForm/edit/json-row-operations/editor-script/scriptRecipes.ts index a7ce4825..5ee2d241 100644 --- a/ui/src/views/admin/listForm/edit/json-row-operations/editor-script/scriptRecipes.ts +++ b/ui/src/views/admin/listForm/edit/json-row-operations/editor-script/scriptRecipes.ts @@ -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. */ diff --git a/ui/src/views/admin/listForm/wizard/WizardStepFields.tsx b/ui/src/views/admin/listForm/wizard/WizardStepFields.tsx index 484be74a..2043c8e2 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStepFields.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStepFields.tsx @@ -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([]) + 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({ {translate('::App.WizardStep3.EditorType')} - 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) => ( - - {et.label} - - ))} - + option.value === item.editorType, + )} + onChange={(option) => option && onEditorTypeChange(option.value)} + /> @@ -339,19 +343,21 @@ function SortableItem({ {translate('::App.WizardStep3.LookupDataSourceType')} - - onLookupDataSourceTypeChange(e.target.value as unknown as UiLookupDataSourceTypeEnum) + 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) => ( - - {opt.label} - - ))} - + /> @@ -514,35 +520,35 @@ function SortableItem({ {translate('::App.ListFormFieldEdit.KeyColumn')} - 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" - > - {translate('::App.Platform.Select')} - {pickerColumns.map((c) => ( - - {c.columnName} - - ))} - + option.value === pickerKeyCol, + )} + onChange={(option) => setPickerKeyCol(option?.value ?? '')} + /> {translate('::App.ListFormFieldEdit.NameColumn')} - 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" - > - {translate('::App.Platform.Select')} - {pickerColumns.map((c) => ( - - {c.columnName} - - ))} - + option.value === pickerNameCol, + )} + onChange={(option) => setPickerNameCol(option?.value ?? '')} + /> {pickerKeyCol && pickerNameCol && ( @@ -651,17 +657,17 @@ function SortableItem({ {translate('::App.WizardStep3.Span')} - 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) => ( - - {n} - - ))} - + ({ + value: i + 1, + label: String(i + 1), + }))} + value={{ value: item.colSpan, label: String(item.colSpan) }} + onChange={(option) => option && onColSpanChange(Number(option.value))} + /> 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({ > {translate('::AbpIdentity.Roles.CopyPermissions')} - setCopyDialogRole(e.target.value)} - > - {translate('::App.Platform.Select')} - {roleList - .filter((role) => role !== name) - .map((role) => ( - - {role} - - ))} - + option.value === copyDialogRole)} + onChange={(option) => setCopyDialogRole(option?.value ?? '')} + /> setCopyDialogOpen(false)}> diff --git a/ui/src/views/admin/videoroom/ChatPanel.tsx b/ui/src/views/admin/videoroom/ChatPanel.tsx index 048e8753..436950c7 100644 --- a/ui/src/views/admin/videoroom/ChatPanel.tsx +++ b/ui/src/views/admin/videoroom/ChatPanel.tsx @@ -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 ( {/* Header */} @@ -125,21 +131,18 @@ const ChatPanel = ({ {messageMode === 'private' && ( - { - const recipient = availableRecipients.find((p) => p.id === e.target.value) + 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" - > - {translate('::App.VideoRoom.SelectPerson')} - {availableRecipients.map((p) => ( - - {p.name} {p.isTeacher ? `(${translate('::App.VideoRoom.Teacher')})` : ''} - - ))} - + /> )} diff --git a/ui/src/views/admin/videoroom/RoomList.tsx b/ui/src/views/admin/videoroom/RoomList.tsx index 0ed4f833..cbbb8756 100644 --- a/ui/src/views/admin/videoroom/RoomList.tsx +++ b/ui/src/views/admin/videoroom/RoomList.tsx @@ -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 = () => { {translate('::App.VideoRoom.DefaultMicrophoneState')} - + + 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" - > - - {translate('::App.VideoRoom.MicrophoneMuted')} - - - {translate('::App.VideoRoom.MicrophoneUnmuted')} - - + /> {translate('::App.VideoRoom.DefaultCameraState')} - + 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" - > - {translate('::App.VideoRoom.CameraOn')} - {translate('::App.VideoRoom.CameraOff')} - + /> {translate('::App.VideoRoom.DefaultLayout')} - + 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" - > - - {translate('::App.VideoRoom.LayoutGridView')} - - - {translate('::App.VideoRoom.LayoutTeacherFocus')} - - - {translate('::App.VideoRoom.LayoutPresentation')} - - - {translate('::App.VideoRoom.LayoutSidebar')} - - + /> diff --git a/ui/src/views/developerKit/ComponentManager.tsx b/ui/src/views/developerKit/ComponentManager.tsx index 50c6883d..12130f6f 100644 --- a/ui/src/views/developerKit/ComponentManager.tsx +++ b/ui/src/views/developerKit/ComponentManager.tsx @@ -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 = () => { - setFilterActive(e.target.value as 'all' | 'active' | 'inactive')} - > - {translate('::App.ComponentFilter.All')} - - {translate('::App.EntityFilter.Active')} - - - {translate('::App.EntityFilter.Inactive')} - - + o.value === filterActive)} + onChange={(option) => setFilterActive(option?.value ?? 'all')} + /> { 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([]) const [isLoading, setIsLoading] = useState(false) const [searchTerm, setSearchTerm] = useState('') @@ -140,22 +151,12 @@ const DynamicServiceManager: React.FC = () => { - - 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" - > - {translate('::App.StaticLookup.All')} - - {translate('::App.Platform.Success')} - - {translate('::App.Platform.Failed')} - - {translate('::App.DeveloperKitDynamicServices.FilterPending')} - - + o.value === filterStatus)} + onChange={(option) => setFilterStatus(option?.value ?? 'all')} + /> - Design View + Design View )} diff --git a/ui/src/views/developerKit/SqlQueryManager.tsx b/ui/src/views/developerKit/SqlQueryManager.tsx index 0a6b75b0..582d7587 100644 --- a/ui/src/views/developerKit/SqlQueryManager.tsx +++ b/ui/src/views/developerKit/SqlQueryManager.tsx @@ -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`, - { - const ds = state.dataSources.find((d) => d.code === e.target.value) + option.value === (state.selectedDataSource || ''), + )} + onChange={(option) => { + const ds = state.dataSources.find((d) => d.code === option?.value) if (ds) handleDataSourceChange(ds) }} - > - {state.dataSources.map((ds) => ( - - {ds.code} - - ))} - + /> {/* Seed dosyalari (configs/seeds) File Manager uzerinden yonetilir. */} diff --git a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx index dcd4f317..024a7849 100644 --- a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx +++ b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx @@ -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([]) const [targetTableKeyColumns, setTargetTableKeyColumns] = useState([]) + + 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([]) const [originalIndexes, setOriginalIndexes] = useState([]) @@ -2374,11 +2396,9 @@ const SqlTableDesignerDialog = ({ > updateColumn(col.id, 'columnName', e.target.value)} @@ -2391,28 +2411,27 @@ const SqlTableDesignerDialog = ({ /> - { - const dt = e.target.value as SqlDataType + 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) => ( - - {t.label} - - ))} - + /> updateColumn(col.id, 'defaultValue', e.target.value)} @@ -2558,10 +2577,10 @@ const SqlTableDesignerDialog = ({ {translate('::App.SqlQueryManager.EntityName')} * onEntityNameChange(e.target.value)} placeholder={translate('::App.SqlQueryManager.EntityNamePlaceholder')} @@ -2574,10 +2593,10 @@ const SqlTableDesignerDialog = ({ {translate('::App.Listform.ListformField.TableName')} @@ -2760,41 +2779,41 @@ const SqlTableDesignerDialog = ({ {translate('::App.SqlQueryManager.FkColumnInThisTable')} - 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" - > - {translate('::App.Platform.Select')} - {columns - .filter((c) => c.columnName.trim()) - .map((c) => ( - - {c.columnName} - - ))} - + option.value === fkForm.fkColumnName, + )} + onChange={(option) => + setFkForm((f) => ({ ...f, fkColumnName: option?.value ?? '' })) + } + /> {translate('::App.SqlQueryManager.TargetTable')} - { - const val = e.target.value + 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" - > - {translate('::App.Platform.Select')} - {dbTables.map((t) => ( - - {t.tableName} - - ))} - + /> @@ -2805,29 +2824,21 @@ const SqlTableDesignerDialog = ({ ? ` — ${translate('::App.Platform.LoadingWithThreeDot')}` : ''} - 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" - > - - {translate('::App.SqlQueryManager.SelectTargetTableFirst')} - - {targetTableColumns.map((col) => ( - k.toLowerCase() === col.toLowerCase()) - } - > - {targetTableKeyColumns.some((k) => k.toLowerCase() === col.toLowerCase()) - ? `${col} (PK/UNIQUE)` - : `${col} (Not Key)`} - - ))} - + option.value === fkForm.referencedColumn, + )} + onChange={(option) => + setFkForm((f) => ({ ...f, referencedColumn: option?.value ?? '' })) + } + /> {fkForm.referencedTable && !targetColsLoading && targetTableKeyColumns.length === 0 && ( @@ -2843,43 +2854,43 @@ const SqlTableDesignerDialog = ({ {translate('::App.SqlQueryManager.CascadeUpdate')} - + 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) => ( - - {o.label} - - ))} - + /> {translate('::App.SqlQueryManager.CascadeDelete')} - + 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) => ( - - {o.label} - - ))} - + /> @@ -2887,11 +2898,11 @@ const SqlTableDesignerDialog = ({ setFkForm((f) => ({ ...f, isRequired: e.target.checked }))} - className="w-4 h-4 text-indigo-600 rounded" + className="w-4 text-indigo-600" /> {translate('::App.Listform.ListformField.Required')} @@ -3135,14 +3146,14 @@ const SqlTableDesignerDialog = ({ {translate('::App.SqlQueryManager.IndexConstraintName')} 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" /> @@ -3150,13 +3161,13 @@ const SqlTableDesignerDialog = ({ setIndexForm((f) => ({ ...f, isClustered: e.target.checked })) } - className="w-4 h-4 text-indigo-600 rounded" + className="w-4 text-indigo-600" /> Clustered @@ -3191,7 +3202,7 @@ const SqlTableDesignerDialog = ({ > { @@ -3220,7 +3231,7 @@ const SqlTableDesignerDialog = ({ }) } }} - className="w-4 h-4 text-indigo-600 rounded" + className="w-4 text-indigo-600" /> @@ -3228,23 +3239,26 @@ const SqlTableDesignerDialog = ({ {existing && ( - + 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" - > - ASC - DESC - + /> )} diff --git a/ui/src/views/developerKit/SqlViewDesignerDialog.tsx b/ui/src/views/developerKit/SqlViewDesignerDialog.tsx index 22e8c9dc..0a8596e6 100644 --- a/ui/src/views/developerKit/SqlViewDesignerDialog.tsx +++ b/ui/src/views/developerKit/SqlViewDesignerDialog.tsx @@ -612,7 +612,7 @@ const SqlViewDesignerDialog = ({ 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) && ( - {translate('::Abp.Mailing.Default')} - )} - {options.map((option) => ( - - {option} - - ))} - + /* 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. */ + ({ 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 = () => { Koleksiyon - + ({ + 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 && ( - - {translate('::App.DeveloperKitComponentDesigner.NoCollectionFound')} - - )} - {selectCollectionPaths.map((path) => ( - - {path || - translate('::App.DeveloperKitComponentDesigner.WholeResponseArray')} - - ))} - + /> {selectCollectionSample && ( @@ -3549,28 +3551,34 @@ const VisualComponentDesigner = () => { {translate('::' + label)} - + ({ + 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 ?? '', }) } - > - - {translate( - selectColumnFields.length - ? '::App.DeveloperKitComponentDesigner.SelectColumn' - : '::App.DeveloperKitComponentDesigner.ItemItself', - )} - - {selectColumnFields.map((field) => ( - - {field.path} - - ))} - + /> ))} @@ -3609,28 +3617,25 @@ const VisualComponentDesigner = () => { ))} - { - const next = event.target.value + !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], }) }} - > - - {translate('::App.DeveloperKitComponentDesigner.AddColumn')} - - {selectColumnFields - .filter((field) => !selectOptionColumns.includes(field.path)) - .map((field) => ( - - {field.path} - - ))} - + /> {translate('::App.DeveloperKitComponentDesigner.ExtraColumnsHint')}{' '} @@ -3764,42 +3769,54 @@ const VisualComponentDesigner = () => { {lookup && ( - selectColumnLookupSource(column, event.target.value)} - > - - {translate('::App.DeveloperKitComponentDesigner.SelectEndpoint')} - - {bindableDataSources.map((source) => ( - - {source.name} - - ))} - + ({ + 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 && ( - + ({ + 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: '', }) } - > - - {translate('::App.DeveloperKitComponentDesigner.SelectCollection')} - - {collectionPaths.map((path) => ( - - {path || - translate('::App.DeveloperKitComponentDesigner.WholeResponseArray')} - - ))} - + /> )} {lookup.sourceId && ( @@ -3813,26 +3830,29 @@ const VisualComponentDesigner = () => { {translate(label)} - - writeColumnLookup(column, { [field]: event.target.value }) - } - > - - {rowFields.length + - {rowFields.map((path) => ( - - {path} - - ))} - + ) + } + 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 ?? '' }) + } + /> ))} @@ -3987,6 +4007,15 @@ const VisualComponentDesigner = () => { const patchFilter = (id: string, updates: Partial) => 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 ( @@ -4038,25 +4067,28 @@ const VisualComponentDesigner = () => { > {strictColumns ? ( - patchFilter(filter.id, { field: event.target.value })} - > - - {translate('::App.DeveloperKitComponentDesigner.FilterColumn')} - - {/* 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) + ( - - {column} - - ))} - + ).map((column) => ({ value: column, label: column }))} + value={ + filter.field + ? { value: filter.field, label: filter.field } + : null + } + onChange={(option) => + patchFilter(filter.id, { field: option?.value ?? '' }) + } + /> ) : ( { onChange={(event) => patchFilter(filter.id, { field: event.target.value })} /> )} - + ({ + 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) => ( - - {FILTER_OPERATOR_LABELS[operator]} - - ))} - + /> { {!valueless && ( - + 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) => ( - - {translate( - `::App.DeveloperKitComponentDesigner.FilterSource${ - item.charAt(0).toUpperCase() + item.slice(1) - }`, - )} - - ))} - + /> {filter.source !== 'record' && ( { next to the source selector left neither of them usable. */} {!valueless && filter.source === 'record' && ( - + ({ + 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 ?? ''), }) } - > - - {translate('::App.DeveloperKitComponentDesigner.FilterMaster')} - - {sqlContainerRefs.map((item) => ( - - {item.ref} - - ))} - + /> { {translate('::' + slot.description)} - { - const sourceId = event.target.value + ({ + 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) } }} - > - - {translate('::App.DeveloperKitComponentDesigner.NotDefined')} - - {options.map((source) => ( - - {source.name} · {source.url} - - ))} - + /> {!options.length && ( {translate('::App.DeveloperKitComponentDesigner.NoEndpointForMethod', { @@ -4316,17 +4359,26 @@ const VisualComponentDesigner = () => { Koleksiyon - updateSelectedProp('collectionPath', event.target.value)} - > - {collectionPaths.map((path) => ( - - {path || translate('::App.DeveloperKitComponentDesigner.ResponseItself')} - - ))} - + ({ + 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 ?? '') + } + /> @@ -4336,17 +4388,17 @@ const VisualComponentDesigner = () => { {translate('::App.DeveloperKitComponentDesigner.KeyParamHint')} - updateSelectedProp('keySource', event.target.value)} - > - {SQL_DATA_SOURCE_KEY_SOURCES.map((item) => ( - - {item.label} - - ))} - + item.value === getSqlDataSourceKeySource(selectedNode), + )} + onChange={(option) => option && updateSelectedProp('keySource', option.value)} + /> { 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 = () => { })} {sqlRecordProperty === 'checked' ? ( - updateSelectedProp(SQL_DEFAULT_VALUE_PROP, event.target.value)} - > - - {translate('::App.DeveloperKitComponentDesigner.NoDefaultValue')} - - {translate('::App.Platform.Yes')} - {translate('::App.Platform.No')} - + option.value === currentDefault, + )} + onChange={(option) => + updateSelectedProp(SQL_DEFAULT_VALUE_PROP, option?.value ?? '') + } + /> ) : ( { {sqlScopeSource?.name} ) : ( - { - const sourceId = event.target.value + ({ + 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 && ( - - {translate('::App.DeveloperKitComponentDesigner.SelectEndpoint')} - - )} - {inspectorDataSources.map((source) => ( - - {source.name} - - ))} - + /> )} @@ -4733,24 +4795,30 @@ const VisualComponentDesigner = () => { Koleksiyon - - updateSelectedBindingDetails('items', { path: event.target.value }) - } - > - {!tabularCollectionPaths.length && ( - - {translate('::App.DeveloperKitComponentDesigner.NoCollectionFound')} - + ( - - {path || translate('::App.DeveloperKitComponentDesigner.WholeResponseArray')} - - ))} - + 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 ?? '' }) + } + /> )} @@ -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 ( { {property.tsType || property.type} - { - const nextChoice = event.target.value + option.value === choice, + )} + onChange={(option) => { + const nextChoice = option?.value if (!nextChoice) { updateSelectedBinding(property.name, '') return @@ -4898,31 +4999,7 @@ const VisualComponentDesigner = () => { getBindingPath(nextChoice, collection), ) }} - > - - {translate('::App.DeveloperKitComponentDesigner.StaticValueNoBinding')} - - {activeBindingSample !== undefined && - (collection || - !Array.isArray(activeBindingSample) || - isActiveRepeatedSource) && ( - - {translate( - isActiveRepeatedSource - ? '::App.DeveloperKitComponentDesigner.CurrentGridRow' - : collection - ? '::App.DeveloperKitComponentDesigner.WholeCollection' - : '::App.DeveloperKitComponentDesigner.WholeResponse', - )} - - )} - {!knownChoice && {choice} (mevcut path)} - {selectableFields.map((field) => ( - - {field.path} · {field.type} - - ))} - + /> {currentBinding?.sourceId && !foreignBinding && ( {currentBindingSource?.name || currentBinding.sourceId}:{' '} @@ -6021,21 +6098,26 @@ const VisualComponentDesigner = () => { Metot - + ({ + 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) => ( - - {method} - - ))} - + /> diff --git a/ui/src/views/developerKit/sqlViewDesigner/CriteriaGrid.tsx b/ui/src/views/developerKit/sqlViewDesigner/CriteriaGrid.tsx index 345ebc54..6e2673ea 100644 --- a/ui/src/views/developerKit/sqlViewDesigner/CriteriaGrid.tsx +++ b/ui/src/views/developerKit/sqlViewDesigner/CriteriaGrid.tsx @@ -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({ - + {labels.column} {labels.alias} - + {labels.table} @@ -132,7 +146,7 @@ export function CriteriaGrid({ {labels.groupBy} )} - + {labels.sortType} @@ -201,18 +215,23 @@ export function CriteriaGrid({ onChange={(e) => onUpdateRow(row.id, { expression: e.target.value })} /> ) : ( - onUpdateRow(row.id, { columnName: e.target.value })} - > - — - {columnsOf(row.sourceId).map((c) => ( - - {c.columnName} - - ))} - + ({ + value: c.columnName ?? '', + label: c.columnName ?? '', + }))} + value={ + row.columnName ? { value: row.columnName, label: row.columnName } : null + } + onChange={(option) => + onUpdateRow(row.id, { columnName: option?.value ?? '' }) + } + /> )} @@ -230,19 +249,16 @@ export function CriteriaGrid({ {isExpression ? ( SQL ) : ( - - onUpdateRow(row.id, { sourceId: e.target.value, columnName: '' }) + option.value === row.sourceId)} + onChange={(option) => + option && onUpdateRow(row.id, { sourceId: option.value, columnName: '' }) } - > - {sources.map((s) => ( - - {s.alias || s.objectName} - - ))} - + /> )} @@ -259,33 +275,36 @@ export function CriteriaGrid({ {grouped && ( - { - const mode = e.target.value as GroupMode + 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) => ( - - {GROUP_MODE_LABEL[m]} - - ))} - + /> )} - { - const sortType = e.target.value as '' | 'ASC' | 'DESC' + 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, }) }} - > - — - Ascending - Descending - + /> @@ -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()} > - setOperator(e.target.value)} - > - {OPERATORS.map((op) => ( - - {op} - - ))} - + ({ value: op, label: op }))} + value={{ value: operator, label: operator }} + onChange={(option) => option && setOperator(option.value)} + /> {needsValue && ( <> setValue2(e.target.value)} /> )} - setMode(e.target.value as 'literal' | 'expression')} - > - {labels.builderLiteral} - {labels.builderExpression} - + option.value === mode)} + onChange={(option) => option && setMode(option.value)} + /> > )} diff --git a/ui/src/views/developerKit/sqlViewDesigner/DiagramPane.tsx b/ui/src/views/developerKit/sqlViewDesigner/DiagramPane.tsx index 491ed51a..c48a93cf 100644 --- a/ui/src/views/developerKit/sqlViewDesigner/DiagramPane.tsx +++ b/ui/src/views/developerKit/sqlViewDesigner/DiagramPane.tsx @@ -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 ( {labels.joinType} - - onUpdateSource(activeJoinSource.id, { joinKind: e.target.value as JoinKind }) + option.value === activeJoinSource.joinKind, + )} + onChange={(option) => + option && onUpdateSource(activeJoinSource.id, { joinKind: option.value }) } - > - {JOIN_KINDS.filter((k) => k !== 'CROSS').map((k) => ( - - {k === 'INNER' ? 'INNER JOIN' : `${k} OUTER JOIN`} - - ))} - - + /> + 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) => ( - - {`${activeJoin.leftColumn} ${op} ${activeJoin.rightColumn}`} - - ))} - + /> ({ value: String(item.key), label: String(item.value) }), + ) + return ( {fs.caption} {fs.controlType === 'Select' ? ( - handleSave(e.target.value)} - > - {!fs.defaultValue && {translate('::App.Platform.Select')}} - {fs.items?.map((item: any) => ( - - {item.value} - - ))} - + option.value === String(current?.value ?? ''), + )} + onChange={(option) => handleSave(option?.value ?? '')} + /> ) : ( ({ + value: status, + label: statusText(status), + })) + const clearDragState = useCallback(() => { dragActiveRef.current = false setDraggedKey(undefined) @@ -1000,23 +1006,21 @@ const TodoBoardContent = ({ {options.statusExpr && ( {translate('::App.Listform.ListformField.Status')} - + option.value === String(draft[options.statusExpr!] ?? ''), + )} + onChange={(option) => setDraft((current) => ({ ...current, - [options.statusExpr!]: event.target.value, + [options.statusExpr!]: option?.value ?? '', })) } - > - {statuses.map((status) => ( - - {statusText(status)} - - ))} - + /> )} {options.dueDateExpr && ( diff --git a/ui/src/views/menu/MenuItemComponent.tsx b/ui/src/views/menu/MenuItemComponent.tsx index dc5b43ae..85af62ef 100644 --- a/ui/src/views/menu/MenuItemComponent.tsx +++ b/ui/src/views/menu/MenuItemComponent.tsx @@ -393,7 +393,7 @@ const MenuItemComponentBase: React.FC = ({ {isModalOpen && ( setIsModalOpen(false)} onRequestClose={() => setIsModalOpen(false)} > @@ -497,6 +497,8 @@ const MenuItemComponentBase: React.FC = ({ {({ field, form }: FieldProps) => ( 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({ {translate('::App.Platform.MenuParent')} {isEditMode ? ( - setForm((p) => ({ ...p, parentCode: e.target.value }))} - className={fieldCls} - > - {translate('::App.WizardStep1.RootMenu')} - {parentOptions.map((option) => ( - - {option.label} - - ))} - + option.value === form.parentCode, + )} + onChange={(option) => + setForm((p) => ({ ...p, parentCode: option?.value ?? '' })) + } + /> ) : ( )}
{translate('::App.DeveloperKitComponentDesigner.ExtraColumnsHint')}{' '} @@ -3764,42 +3769,54 @@ const VisualComponentDesigner = () => { {lookup && (
{translate('::' + slot.description)}
{translate('::App.DeveloperKitComponentDesigner.NoEndpointForMethod', { @@ -4316,17 +4359,26 @@ const VisualComponentDesigner = () => { Koleksiyon - updateSelectedProp('collectionPath', event.target.value)} - > - {collectionPaths.map((path) => ( - - {path || translate('::App.DeveloperKitComponentDesigner.ResponseItself')} - - ))} - + ({ + 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 ?? '') + } + />
{translate('::App.DeveloperKitComponentDesigner.KeyParamHint')}