diff --git a/rsconcept/frontend/src/app/application-layout.tsx b/rsconcept/frontend/src/app/application-layout.tsx index ec9398a3..277095f2 100644 --- a/rsconcept/frontend/src/app/application-layout.tsx +++ b/rsconcept/frontend/src/app/application-layout.tsx @@ -27,7 +27,7 @@ export function ApplicationLayout() {
default: module.DlgOssSettings })) ); +const DlgEditCst = React.lazy(() => + import('@/features/rsform/dialogs/dlg-edit-cst').then(module => ({ default: module.DlgEditCst })) +); export const GlobalDialogs = () => { const active = useDialogsStore(state => state.active); @@ -188,5 +191,7 @@ export const GlobalDialogs = () => { return ; case DialogType.UPLOAD_RSFORM: return ; + case DialogType.EDIT_CONSTITUENTA: + return ; } }; diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/side-panel.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/side-panel.tsx index a33daece..3daae3e5 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/side-panel.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/side-panel.tsx @@ -44,8 +44,7 @@ export function SidePanel({ isMounted, className }: SidePanelProps) { style={{ height: sidePanelHeight }} > } className={clsx( diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/view-schema.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/view-schema.tsx index 9cdb392f..7664f2da 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/view-schema.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/side-panel/view-schema.tsx @@ -1,11 +1,12 @@ import { useState } from 'react'; +import { type IConstituenta } from '@/features/rsform'; import { useRSFormSuspense } from '@/features/rsform/backend/use-rsform'; import { RSFormStats } from '@/features/rsform/components/rsform-stats'; import { ViewConstituents } from '@/features/rsform/components/view-constituents'; import { useFitHeight } from '@/stores/app-layout'; -import { notImplemented } from '@/utils/utils'; +import { useDialogsStore } from '@/stores/dialogs'; import { ToolbarConstituents } from './toolbar-constituents'; @@ -18,9 +19,14 @@ export function ViewSchema({ schemaID, isMutable }: ViewSchemaProps) { const { schema } = useRSFormSuspense({ itemID: schemaID }); const [activeID, setActiveID] = useState(null); const activeCst = activeID ? schema.cstByID.get(activeID) ?? null : null; + const showEditCst = useDialogsStore(state => state.showEditCst); const listHeight = useFitHeight('14.5rem', '10rem'); + function handleEditCst(cst: IConstituenta) { + showEditCst({ schema: schema, target: cst }); + } + return (
handleEditCst(activeCst!)} setActive={setActiveID} resetActive={() => setActiveID(null)} /> @@ -41,6 +47,7 @@ export function ViewSchema({ schemaID, isMutable }: ViewSchemaProps) { activeCst={activeCst} onActivate={cst => setActiveID(cst.id)} maxListHeight={listHeight} + onDoubleClick={isMutable ? handleEditCst : undefined} /> diff --git a/rsconcept/frontend/src/features/rsform/components/view-constituents/table-side-constituents.tsx b/rsconcept/frontend/src/features/rsform/components/view-constituents/table-side-constituents.tsx index 7a1c0a7d..336a39a7 100644 --- a/rsconcept/frontend/src/features/rsform/components/view-constituents/table-side-constituents.tsx +++ b/rsconcept/frontend/src/features/rsform/components/view-constituents/table-side-constituents.tsx @@ -18,6 +18,7 @@ interface TableSideConstituentsProps { schema: IRSForm; activeCst?: IConstituenta | null; onActivate?: (cst: IConstituenta) => void; + onDoubleClick?: (cst: IConstituenta) => void; maxHeight?: string; autoScroll?: boolean; @@ -29,6 +30,7 @@ export function TableSideConstituents({ schema, activeCst, onActivate, + onDoubleClick, maxHeight, autoScroll = true }: TableSideConstituentsProps) { @@ -102,6 +104,7 @@ export function TableSideConstituents({ } onRowClicked={onActivate ? cst => onActivate(cst) : undefined} + onRowDoubleClicked={onDoubleClick} /> ); } diff --git a/rsconcept/frontend/src/features/rsform/components/view-constituents/view-constituents.tsx b/rsconcept/frontend/src/features/rsform/components/view-constituents/view-constituents.tsx index 7ac450e1..d35a77c2 100644 --- a/rsconcept/frontend/src/features/rsform/components/view-constituents/view-constituents.tsx +++ b/rsconcept/frontend/src/features/rsform/components/view-constituents/view-constituents.tsx @@ -11,6 +11,7 @@ interface ViewConstituentsProps { schema: IRSForm; activeCst?: IConstituenta | null; onActivate?: (cst: IConstituenta) => void; + onDoubleClick?: (cst: IConstituenta) => void; className?: string; maxListHeight?: string; @@ -23,6 +24,7 @@ export function ViewConstituents({ schema, activeCst, onActivate, + onDoubleClick, className, maxListHeight, @@ -43,6 +45,7 @@ export function ViewConstituents({ onActivate={onActivate} maxHeight={maxListHeight} autoScroll={autoScroll} + onDoubleClick={onDoubleClick} /> ); diff --git a/rsconcept/frontend/src/features/rsform/dialogs/dlg-create-cst/form-create-cst.tsx b/rsconcept/frontend/src/features/rsform/dialogs/dlg-create-cst/form-create-cst.tsx index 74969cf6..2073c015 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/dlg-create-cst/form-create-cst.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/dlg-create-cst/form-create-cst.tsx @@ -11,6 +11,7 @@ import { TextArea, TextInput } from '@/components/input'; import { CstType, type ICreateConstituentaDTO } from '../../backend/types'; import { RSInput } from '../../components/rs-input'; import { SelectCstType } from '../../components/select-cst-type'; +import { getRSDefinitionPlaceholder } from '../../labels'; import { type IRSForm } from '../../models/rsform'; import { generateAlias, isBaseSet, isBasicConcept, isFunctional } from '../../models/rsform-api'; @@ -85,9 +86,7 @@ export function FormCreateCst({ schema }: FormCreateCstProps) { ? 'Определение функции' : 'Формальное определение' } - placeholder={ - cst_type !== CstType.STRUCTURED ? 'Родоструктурное выражение' : 'Типизация родовой структуры' - } + placeholder={getRSDefinitionPlaceholder(cst_type)} value={field.value} onChange={field.onChange} schema={schema} diff --git a/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/dlg-edit-cst.tsx b/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/dlg-edit-cst.tsx new file mode 100644 index 00000000..f65d0318 --- /dev/null +++ b/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/dlg-edit-cst.tsx @@ -0,0 +1,112 @@ +import { FormProvider, useForm, useWatch } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; + +import { urls, useConceptNavigation } from '@/app'; +import { useFindPredecessor } from '@/features/oss/backend/use-find-predecessor'; + +import { MiniButton } from '@/components/control'; +import { IconChild, IconRSForm } from '@/components/icons'; +import { ModalForm } from '@/components/modal'; +import { useDialogsStore } from '@/stores/dialogs'; +import { errorMsg } from '@/utils/labels'; + +import { type IUpdateConstituentaDTO, schemaUpdateConstituenta } from '../../backend/types'; +import { useUpdateConstituenta } from '../../backend/use-update-constituenta'; +import { type IConstituenta, type IRSForm } from '../../models/rsform'; +import { validateNewAlias } from '../../models/rsform-api'; +import { RSTabID } from '../../pages/rsform-page/rsedit-context'; + +import { FormEditCst } from './form-edit-cst'; + +export interface DlgEditCstProps { + schema: IRSForm; + target: IConstituenta; +} + +export function DlgEditCst() { + const { schema, target } = useDialogsStore(state => state.props as DlgEditCstProps); + const hideDialog = useDialogsStore(state => state.hideDialog); + const { updateConstituenta } = useUpdateConstituenta(); + const router = useConceptNavigation(); + const { findPredecessor } = useFindPredecessor(); + + const methods = useForm({ + resolver: zodResolver(schemaUpdateConstituenta), + defaultValues: { + target: target.id, + item_data: { + alias: target.alias, + cst_type: target.cst_type, + convention: target.convention, + definition_formal: target.definition_formal, + definition_raw: target.definition_raw, + term_raw: target.term_raw, + term_forms: target.term_forms + } + } + }); + + const alias = useWatch({ control: methods.control, name: 'item_data.alias' })!; + const cst_type = useWatch({ control: methods.control, name: 'item_data.cst_type' })!; + const isValid = (alias === target.alias && cst_type == target.cst_type) || validateNewAlias(alias, cst_type, schema); + + function onSubmit(data: IUpdateConstituentaDTO) { + return updateConstituenta({ itemID: schema.id, data }); + } + + function navigateToTarget() { + hideDialog(); + router.push({ + path: urls.schema_props({ + id: schema.id, + tab: RSTabID.CST_EDIT, + active: target.id + }) + }); + } + + function editSource() { + hideDialog(); + void findPredecessor(target.id).then(reference => + router.push({ + path: urls.schema_props({ + id: reference.schema, + active: reference.id, + tab: RSTabID.CST_EDIT + }) + }) + ); + } + + return ( + void methods.handleSubmit(onSubmit)(event)} + submitInvalidTooltip={errorMsg.aliasInvalid} + submitText='Сохранить' + className='cc-column w-140 max-h-120 py-2 px-6' + > +
+ } + className='' + onClick={navigateToTarget} + /> + } + disabled={!target.is_inherited} + onClick={editSource} + /> +
+ + + + +
+ ); +} diff --git a/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/form-edit-cst.tsx b/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/form-edit-cst.tsx new file mode 100644 index 00000000..bc2a0d3f --- /dev/null +++ b/rsconcept/frontend/src/features/rsform/dialogs/dlg-edit-cst/form-edit-cst.tsx @@ -0,0 +1,156 @@ +import { useState } from 'react'; +import { Controller, useFormContext, useWatch } from 'react-hook-form'; + +import { TextArea, TextInput } from '@/components/input'; + +import { CstType, type IUpdateConstituentaDTO } from '../../backend/types'; +import { SelectCstType } from '../../components/select-cst-type'; +import { getRSDefinitionPlaceholder, labelCstTypification } from '../../labels'; +import { type IConstituenta, type IRSForm } from '../../models/rsform'; +import { generateAlias, isBaseSet, isBasicConcept, isFunctional } from '../../models/rsform-api'; + +interface FormEditCstProps { + schema: IRSForm; + target: IConstituenta; +} + +export function FormEditCst({ target, schema }: FormEditCstProps) { + const { + setValue, + control, + register, + formState: { errors } + } = useFormContext(); + + const [forceComment, setForceComment] = useState(false); + + const cst_type = useWatch({ control, name: 'item_data.cst_type' }) ?? CstType.BASE; + const convention = useWatch({ control, name: 'item_data.convention' }); + const isBasic = isBasicConcept(cst_type); + const isElementary = isBaseSet(cst_type); + const isFunction = isFunctional(cst_type); + const showConvention = !!convention || forceComment || isBasic; + + function handleTypeChange(newValue: CstType) { + setValue('item_data.cst_type', newValue); + setValue('item_data.alias', generateAlias(newValue, schema), { shouldValidate: true }); + setForceComment(false); + } + + return ( + <> +
+ + +
+ +