Add frontend for CstRename function

This commit is contained in:
IRBorisov 2023-08-23 01:36:17 +03:00
parent 7322a232d4
commit 24da53bf40
7 changed files with 133 additions and 54 deletions

View File

@ -6,6 +6,7 @@ import Button from './Button';
interface ModalProps { interface ModalProps {
title?: string title?: string
submitText?: string submitText?: string
submitInvalidTooltip?: string
readonly?: boolean readonly?: boolean
canSubmit?: boolean canSubmit?: boolean
hideWindow: () => void hideWindow: () => void
@ -14,7 +15,13 @@ interface ModalProps {
children: React.ReactNode children: React.ReactNode
} }
function Modal({ title, hideWindow, onSubmit, readonly, onCancel, canSubmit, children, submitText = 'Продолжить' }: ModalProps) { function Modal({
title, hideWindow, onSubmit,
readonly, onCancel, canSubmit,
submitInvalidTooltip,
children,
submitText = 'Продолжить'
}: ModalProps) {
const ref = useRef(null); const ref = useRef(null);
useEscapeKey(hideWindow); useEscapeKey(hideWindow);
@ -30,19 +37,20 @@ function Modal({ title, hideWindow, onSubmit, readonly, onCancel, canSubmit, chi
return ( return (
<> <>
<div className='fixed top-0 left-0 z-50 w-full h-full opacity-50 clr-modal'> <div className='fixed top-0 left-0 z-50 w-full h-full opacity-50 clr-modal' />
</div>
<div <div
ref={ref} ref={ref}
className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-6 py-4 flex flex-col w-fit max-w-[95vw] h-fit z-[60] clr-card border shadow-md mb-[5rem]' className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-6 py-4 flex flex-col w-fit max-w-[95vw] h-fit z-[60] clr-card border shadow-md mb-[5rem]'
> >
{ title && <h1 className='mb-2 text-xl font-bold text-center'>{title}</h1> } { title && <h1 className='mb-2 text-xl font-bold text-center'>{title}</h1> }
<div className='max-h-[calc(95vh-15rem)] overflow-auto'> <div className='max-h-[calc(95vh-15rem)]'>
{children} {children}
</div> </div>
<div className='flex justify-center w-full gap-4 pt-4 mt-2 border-t-4'> <div className='flex justify-center w-full gap-4 pt-4 mt-2 border-t-4'>
{!readonly && <Button {!readonly &&
<Button
text={submitText} text={submitText}
tooltip={!canSubmit ? submitInvalidTooltip: ''}
widthClass='min-w-[6rem] min-h-[2.6rem] w-fit h-fit' widthClass='min-w-[6rem] min-h-[2.6rem] w-fit h-fit'
colorClass='clr-btn-primary' colorClass='clr-btn-primary'
disabled={!canSubmit} disabled={!canSubmit}

View File

@ -18,14 +18,14 @@ function TextInput({
...props ...props
}: TextInputProps) { }: TextInputProps) {
return ( return (
<div className={`flex ${singleRow ? 'items-center gap-4' : 'flex-col items-start'} [&:not(:first-child)]:mt-3`}> <div className={`flex [&:not(:first-child)]:mt-3 ${singleRow ? 'items-center gap-4 ' + widthClass : 'flex-col items-start'}`}>
<Label <Label
text={label} text={label}
required={required} required={required}
htmlFor={id} htmlFor={id}
/> />
<input id={id} <input id={id}
className={`px-3 py-2 mt-2 leading-tight border shadow truncate hover:text-clip ${colorClass} ${widthClass}`} className={`px-3 py-2 mt-2 leading-tight border shadow truncate hover:text-clip ${colorClass} ${singleRow ? '' : widthClass}`}
required={required} required={required}
{...props} {...props}
/> />

View File

@ -6,12 +6,13 @@ import { useRSFormDetails } from '../hooks/useRSFormDetails'
import { import {
type DataCallback, getTRSFile, type DataCallback, getTRSFile,
patchConstituenta, patchDeleteConstituenta, patchConstituenta, patchDeleteConstituenta,
patchMoveConstituenta, patchResetAliases, patchRSForm, patchMoveConstituenta, patchRenameConstituenta,
patchResetAliases, patchRSForm,
patchUploadTRS, postClaimRSForm, postNewConstituenta patchUploadTRS, postClaimRSForm, postNewConstituenta
} from '../utils/backendAPI' } from '../utils/backendAPI'
import { import {
IConstituentaList, IConstituentaMeta, ICstCreateData, IConstituentaList, IConstituentaMeta, ICstCreateData,
ICstMovetoData, ICstUpdateData, IRSForm, ICstMovetoData, ICstRenameData, ICstUpdateData, IRSForm,
IRSFormMeta, IRSFormUpdateData, IRSFormUploadData IRSFormMeta, IRSFormUpdateData, IRSFormUploadData
} from '../utils/models' } from '../utils/models'
import { useAuth } from './AuthContext' import { useAuth } from './AuthContext'
@ -42,6 +43,7 @@ interface IRSFormContext {
resetAliases: (callback: () => void) => void resetAliases: (callback: () => void) => void
cstCreate: (data: ICstCreateData, callback?: DataCallback<IConstituentaMeta>) => void cstCreate: (data: ICstCreateData, callback?: DataCallback<IConstituentaMeta>) => void
cstRename: (data: ICstRenameData, callback?: DataCallback<IConstituentaMeta>) => void
cstUpdate: (data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => void cstUpdate: (data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => void
cstDelete: (data: IConstituentaList, callback?: () => void) => void cstDelete: (data: IConstituentaList, callback?: () => void) => void
cstMoveTo: (data: ICstMovetoData, callback?: () => void) => void cstMoveTo: (data: ICstMovetoData, callback?: () => void) => void
@ -153,7 +155,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
patchResetAliases(schemaID, { patchResetAliases(schemaID, {
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => { setError(error) }, onError: error => setError(error),
onSuccess: newData => { onSuccess: newData => {
setSchema(Object.assign(schema, newData)); setSchema(Object.assign(schema, newData));
if (callback) callback(); if (callback) callback();
@ -167,7 +169,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
getTRSFile(schemaID, { getTRSFile(schemaID, {
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => { setError(error) }, onError: error => setError(error),
onSuccess: callback onSuccess: callback
}); });
}, [schemaID, setError]); }, [schemaID, setError]);
@ -179,7 +181,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
data: data, data: data,
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => { setError(error) }, onError: error => setError(error),
onSuccess: newData => { onSuccess: newData => {
setSchema(newData.schema); setSchema(newData.schema);
if (callback) callback(newData.new_cst); if (callback) callback(newData.new_cst);
@ -194,7 +196,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
data: data, data: data,
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => { setError(error) }, onError: error => setError(error),
onSuccess: newData => { onSuccess: newData => {
setSchema(newData); setSchema(newData);
if (callback) callback(); if (callback) callback();
@ -209,13 +211,27 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
data: data, data: data,
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => { setError(error) }, onError: error => setError(error),
onSuccess: newData => { onSuccess: newData => {
reload(setProcessing, () => { if (callback != null) callback(newData); }) reload(setProcessing, () => { if (callback) callback(newData); })
} }
}); });
}, [setError, reload]); }, [setError, reload]);
const cstRename = useCallback(
(data: ICstRenameData, callback?: DataCallback<IConstituentaMeta>) => {
setError(undefined)
patchRenameConstituenta(schemaID, {
data: data,
showError: true,
setLoading: setProcessing,
onError: error => setError(error),
onSuccess: newData => {
reload(setProcessing, () => { if (callback) callback(newData); })
}
});
}, [setError, reload, schemaID]);
const cstMoveTo = useCallback( const cstMoveTo = useCallback(
(data: ICstMovetoData, callback?: () => void) => { (data: ICstMovetoData, callback?: () => void) => {
setError(undefined) setError(undefined)
@ -223,7 +239,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
data: data, data: data,
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => { setError(error) }, onError: error => setError(error),
onSuccess: newData => { onSuccess: newData => {
setSchema(newData); setSchema(newData);
if (callback) callback(); if (callback) callback();
@ -237,11 +253,11 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
error, loading, processing, error, loading, processing,
isForceAdmin, isReadonly, isOwned, isEditable, isForceAdmin, isReadonly, isOwned, isEditable,
isClaimable, isTracking, isClaimable, isTracking,
toggleForceAdmin: () => { setIsForceAdmin(prev => !prev) }, toggleForceAdmin: () => setIsForceAdmin(prev => !prev),
toggleReadonly: () => { setIsReadonly(prev => !prev) }, toggleReadonly: () => setIsReadonly(prev => !prev),
toggleTracking, toggleTracking,
update, download, upload, claim, resetAliases, update, download, upload, claim, resetAliases,
cstUpdate, cstCreate, cstDelete, cstMoveTo cstUpdate, cstCreate, cstRename, cstDelete, cstMoveTo
}}> }}>
{ children } { children }
</RSFormContext.Provider> </RSFormContext.Provider>

View File

@ -1,18 +1,20 @@
import { useEffect, useState } from 'react'; import { useLayoutEffect, useState } from 'react';
import ConceptSelect from '../../components/Common/ConceptSelect'; import ConceptSelect from '../../components/Common/ConceptSelect';
import Modal from '../../components/Common/Modal'; import Modal from '../../components/Common/Modal';
import TextInput from '../../components/Common/TextInput'; import TextInput from '../../components/Common/TextInput';
import { useRSForm } from '../../context/RSFormContext';
import { CstType, ICstRenameData } from '../../utils/models'; import { CstType, ICstRenameData } from '../../utils/models';
import { CstTypeSelector, getCstTypeLabel } from '../../utils/staticUI'; import { createAliasFor, CstTypeSelector, getCstTypeLabel, getCstTypePrefix } from '../../utils/staticUI';
interface DlgRenameCstProps { interface DlgRenameCstProps {
hideWindow: () => void hideWindow: () => void
initial: ICstRenameData initial?: ICstRenameData
onRename: (data: ICstRenameData) => void onRename: (data: ICstRenameData) => void
} }
function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) { function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
const { schema } = useRSForm();
const [validated, setValidated] = useState(false); const [validated, setValidated] = useState(false);
const [cstType, setCstType] = useState<CstType>(CstType.BASE); const [cstType, setCstType] = useState<CstType>(CstType.BASE);
const [cstID, setCstID] = useState(0) const [cstID, setCstID] = useState(0)
@ -26,11 +28,17 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
} }
} }
const handleSubmit = () => { const handleSubmit = () => onRename(getData());
onRename(getData());
};
useEffect(() => { useLayoutEffect(
() => {
if (schema && initial && cstType !== initial.cst_type) {
setAlias(createAliasFor(cstType, schema));
}
}, [initial, cstType, schema]);
useLayoutEffect(
() => {
if (initial) { if (initial) {
setCstType(initial.cst_type); setCstType(initial.cst_type);
setAlias(initial.alias); setAlias(initial.alias);
@ -38,35 +46,43 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
} }
}, [initial]); }, [initial]);
useEffect(() => { useLayoutEffect(
// setValidated(selectedType !== undefined); () => {
setValidated(true) if (!initial || !schema) {
}, [cstType, alias] setValidated(false);
); } else if(alias === initial.alias || alias.length < 2 || alias[0] !== getCstTypePrefix(cstType)) {
setValidated(false);
} else {
setValidated(!schema.items.find(cst => cst.alias === alias))
}
}, [cstType, alias, initial, schema]);
return ( return (
<Modal <Modal
title='Создание конституенты' title='Переименование конституенты'
hideWindow={hideWindow} hideWindow={hideWindow}
canSubmit={validated} canSubmit={validated}
onSubmit={handleSubmit} onSubmit={handleSubmit}
submitInvalidTooltip={'Введите имя, соответствующее типу и отсутствующее в схеме'}
submitText='Переименовать'
> >
<div className='h-fit w-[20rem] px-2 mb-2 flex flex-col justify-stretch'> <div className='flex items-center gap-4 px-2 mb-2 h-fit min-w-[25rem]'>
<div className='flex justify-center w-full'>
<ConceptSelect <ConceptSelect
className='my-2 min-w-[15rem] self-center' className='my-2 min-w-[14rem] self-center'
options={CstTypeSelector} options={CstTypeSelector}
placeholder='Выберите тип' placeholder='Выберите тип'
values={cstType ? [{ value: cstType, label: getCstTypeLabel(cstType) }] : []} values={cstType ? [{ value: cstType, label: getCstTypeLabel(cstType) }] : []}
onChange={data => { setCstType(data.length > 0 ? data[0].value : CstType.BASE); }} onChange={data => { setCstType(data.length > 0 ? data[0].value : CstType.BASE); }}
/> />
</div> <div>
<TextInput id='alias' <TextInput id='alias' label='Имя'
label='Имя конституенты' singleRow
widthClass='w-[7rem]'
value={alias} value={alias}
onChange={event => setAlias(event.target.value)} onChange={event => setAlias(event.target.value)}
/> />
</div> </div>
</div>
</Modal> </Modal>
); );
} }

View File

@ -9,7 +9,7 @@ import TextArea from '../../components/Common/TextArea';
import CstStatusInfo from '../../components/Help/InfoCstStatus'; import CstStatusInfo from '../../components/Help/InfoCstStatus';
import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons'; import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { CstType, EditMode, ICstCreateData, ICstUpdateData, SyntaxTree } from '../../utils/models'; import { CstType, EditMode, ICstCreateData, ICstRenameData, ICstUpdateData, SyntaxTree } from '../../utils/models';
import { getCstTypificationLabel } from '../../utils/staticUI'; import { getCstTypificationLabel } from '../../utils/staticUI';
import EditorRSExpression from './EditorRSExpression'; import EditorRSExpression from './EditorRSExpression';
import ViewSideConstituents from './elements/ViewSideConstituents'; import ViewSideConstituents from './elements/ViewSideConstituents';
@ -22,10 +22,11 @@ interface EditorConstituentaProps {
onOpenEdit: (cstID: number) => void onOpenEdit: (cstID: number) => void
onShowAST: (expression: string, ast: SyntaxTree) => void onShowAST: (expression: string, ast: SyntaxTree) => void
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
onRenameCst: (initial: ICstRenameData) => void
onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void
} }
function EditorConstituenta({ activeID, onShowAST, onCreateCst, onOpenEdit, onDeleteCst }: EditorConstituentaProps) { function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onOpenEdit, onDeleteCst }: EditorConstituentaProps) {
const { schema, processing, isEditable, cstUpdate } = useRSForm(); const { schema, processing, isEditable, cstUpdate } = useRSForm();
const activeCst = useMemo( const activeCst = useMemo(
() => { () => {
@ -112,7 +113,15 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onOpenEdit, onDe
} }
function handleRename() { function handleRename() {
toast.info('Переименование в разработке'); if (!activeID || !activeCst) {
return;
}
const data: ICstRenameData = {
id: activeID,
alias: activeCst?.alias,
cst_type: activeCst.cstType
};
onRenameCst(data);
} }
return ( return (

View File

@ -9,11 +9,12 @@ import { Loader } from '../../components/Common/Loader';
import { useLibrary } from '../../context/LibraryContext'; import { useLibrary } from '../../context/LibraryContext';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants'; import { prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
import { ICstCreateData, SyntaxTree } from '../../utils/models'; import { ICstCreateData, ICstRenameData, SyntaxTree } from '../../utils/models';
import { createAliasFor } from '../../utils/staticUI'; import { createAliasFor } from '../../utils/staticUI';
import DlgCloneRSForm from './DlgCloneRSForm'; import DlgCloneRSForm from './DlgCloneRSForm';
import DlgCreateCst from './DlgCreateCst'; import DlgCreateCst from './DlgCreateCst';
import DlgDeleteCst from './DlgDeleteCst'; import DlgDeleteCst from './DlgDeleteCst';
import DlgRenameCst from './DlgRenameCst';
import DlgShowAST from './DlgShowAST'; import DlgShowAST from './DlgShowAST';
import DlgUploadRSForm from './DlgUploadRSForm'; import DlgUploadRSForm from './DlgUploadRSForm';
import EditorConstituenta from './EditorConstituenta'; import EditorConstituenta from './EditorConstituenta';
@ -35,7 +36,7 @@ function RSTabs() {
const search = useLocation().search; const search = useLocation().search;
const { const {
error, schema, loading, error, schema, loading,
cstCreate, cstDelete cstCreate, cstDelete, cstRename
} = useRSForm(); } = useRSForm();
const { destroySchema } = useLibrary(); const { destroySchema } = useLibrary();
@ -56,6 +57,9 @@ function RSTabs() {
const [createInitialData, setCreateInitialData] = useState<ICstCreateData>(); const [createInitialData, setCreateInitialData] = useState<ICstCreateData>();
const [showCreateCst, setShowCreateCst] = useState(false); const [showCreateCst, setShowCreateCst] = useState(false);
const [renameInitialData, setRenameInitialData] = useState<ICstRenameData>();
const [showRenameCst, setShowRenameCst] = useState(false);
useLayoutEffect(() => { useLayoutEffect(() => {
if (schema) { if (schema) {
const oldTitle = document.title const oldTitle = document.title
@ -122,6 +126,17 @@ function RSTabs() {
} }
}, [handleCreateCst]); }, [handleCreateCst]);
const handleRenameCst = useCallback(
(data: ICstRenameData) => {
cstRename(data, () => toast.success(`Конституента переименована: ${renameInitialData!.alias} -> ${data.alias}`));
}, [cstRename, renameInitialData]);
const promptRenameCst = useCallback(
(initialData: ICstRenameData) => {
setRenameInitialData(initialData);
setShowRenameCst(true);
}, []);
const handleDeleteCst = useCallback( const handleDeleteCst = useCallback(
(deleted: number[]) => { (deleted: number[]) => {
if (!schema) { if (!schema) {
@ -205,6 +220,12 @@ function RSTabs() {
onCreate={handleCreateCst} onCreate={handleCreateCst}
initial={createInitialData} initial={createInitialData}
/>} />}
{showRenameCst &&
<DlgRenameCst
hideWindow={() => setShowRenameCst(false)}
onRename={handleRenameCst}
initial={renameInitialData}
/>}
{showDeleteCst && {showDeleteCst &&
<DlgDeleteCst <DlgDeleteCst
hideWindow={() => setShowDeleteCst(false)} hideWindow={() => setShowDeleteCst(false)}
@ -254,6 +275,7 @@ function RSTabs() {
onShowAST={onShowAST} onShowAST={onShowAST}
onCreateCst={promptCreateCst} onCreateCst={promptCreateCst}
onDeleteCst={promptDeleteCst} onDeleteCst={promptDeleteCst}
onRenameCst={promptRenameCst}
/> />
</TabPanel> </TabPanel>

View File

@ -5,7 +5,7 @@ import { type ErrorInfo } from '../components/BackendError'
import { config } from './constants' import { config } from './constants'
import { import {
IConstituentaList, IConstituentaMeta, IConstituentaList, IConstituentaMeta,
ICstCreateData, ICstCreatedResponse, ICstMovetoData, ICstUpdateData, ICstCreateData, ICstCreatedResponse, ICstMovetoData, ICstRenameData, ICstUpdateData,
ICurrentUser, IExpressionParse, IRSExpression, ICurrentUser, IExpressionParse, IRSExpression,
IRSFormCreateData, IRSFormData, IRSFormCreateData, IRSFormData,
IRSFormMeta, IRSFormUpdateData, IRSFormUploadData, IUserInfo, IRSFormMeta, IRSFormUpdateData, IRSFormUploadData, IUserInfo,
@ -210,6 +210,14 @@ export function patchConstituenta(target: string, request: FrontExchange<ICstUpd
}); });
} }
export function patchRenameConstituenta(schema: string, request: FrontExchange<ICstRenameData, IConstituentaMeta>) {
AxiosPatch({
title: `Renaming constituenta id=${request.data.id} for schema id=${schema}`,
endpoint: `/api/rsforms/${schema}/cst-rename/`,
request: request
});
}
export function patchMoveConstituenta(schema: string, request: FrontExchange<ICstMovetoData, IRSFormData>) { export function patchMoveConstituenta(schema: string, request: FrontExchange<ICstMovetoData, IRSFormData>) {
AxiosPatch({ AxiosPatch({
title: `Moving Constituents for RSForm id=${schema}: ${JSON.stringify(request.data.items)} to ${request.data.move_to}`, title: `Moving Constituents for RSForm id=${schema}: ${JSON.stringify(request.data.items)} to ${request.data.move_to}`,