mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Improve UI
This commit is contained in:
parent
e737628cea
commit
e7016aab21
|
@ -17,7 +17,10 @@
|
||||||
"react", "simple-import-sort"
|
"react", "simple-import-sort"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"simple-import-sort/imports": "error",
|
"simple-import-sort/imports": "warn",
|
||||||
|
"@typescript-eslint/no-unused-vars": "warn",
|
||||||
|
"no-trailing-spaces": "warn",
|
||||||
|
"no-multiple-empty-lines": "warn",
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/semi": "off",
|
"@typescript-eslint/semi": "off",
|
||||||
"@typescript-eslint/strict-boolean-expressions": "off",
|
"@typescript-eslint/strict-boolean-expressions": "off",
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
import { type MouseEventHandler } from 'react';
|
interface ButtonProps
|
||||||
|
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children'> {
|
||||||
interface ButtonProps {
|
|
||||||
id?: string
|
|
||||||
text?: string
|
text?: string
|
||||||
icon?: React.ReactNode
|
icon?: React.ReactNode
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
disabled?: boolean
|
|
||||||
dense?: boolean
|
dense?: boolean
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
widthClass?: string
|
widthClass?: string
|
||||||
borderClass?: string
|
borderClass?: string
|
||||||
colorClass?: string
|
colorClass?: string
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement> | undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Button({
|
function Button({
|
||||||
|
@ -26,7 +22,7 @@ function Button({
|
||||||
return (
|
return (
|
||||||
<button id={id}
|
<button id={id}
|
||||||
type='button'
|
type='button'
|
||||||
disabled={disabled}
|
disabled={disabled ?? loading}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
className={`inline-flex items-center gap-2 align-middle justify-center ${padding} ${borderClass} ${colorClass} ${widthClass} ${cursor}`}
|
className={`inline-flex items-center gap-2 align-middle justify-center ${padding} ${borderClass} ${colorClass} ${widthClass} ${cursor}`}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
import useClickedOutside from '../../hooks/useClickedOutside';
|
import useClickedOutside from '../../hooks/useClickedOutside';
|
||||||
|
import useEscapeKey from '../../hooks/useEscapeKey';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
|
@ -8,27 +9,28 @@ interface ModalProps {
|
||||||
submitText?: string
|
submitText?: string
|
||||||
show: boolean
|
show: boolean
|
||||||
canSubmit: boolean
|
canSubmit: boolean
|
||||||
toggle: () => void
|
hideWindow: () => void
|
||||||
onSubmit: () => void
|
onSubmit: () => void
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
function Modal({ title, show, toggle, onSubmit, onCancel, canSubmit, children, submitText = 'Продолжить' }: ModalProps) {
|
function Modal({ title, show, hideWindow, onSubmit, onCancel, canSubmit, children, submitText = 'Продолжить' }: ModalProps) {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
useClickedOutside({ ref, callback: toggle })
|
useClickedOutside({ ref, callback: hideWindow });
|
||||||
|
useEscapeKey(hideWindow);
|
||||||
|
|
||||||
if (!show) {
|
if (!show) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
toggle();
|
hideWindow();
|
||||||
if (onCancel) onCancel();
|
if (onCancel) onCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
toggle();
|
hideWindow();
|
||||||
onSubmit();
|
onSubmit();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,6 +50,7 @@ function Modal({ title, show, toggle, onSubmit, onCancel, canSubmit, children, s
|
||||||
colorClass='clr-btn-primary'
|
colorClass='clr-btn-primary'
|
||||||
disabled={!canSubmit}
|
disabled={!canSubmit}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
text='Отмена'
|
text='Отмена'
|
||||||
|
|
|
@ -9,7 +9,7 @@ function SubmitButton({ text = 'ОК', icon, disabled, loading = false }: Submit
|
||||||
return (
|
return (
|
||||||
<button type='submit'
|
<button type='submit'
|
||||||
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-bold disabled:cursor-not-allowed rounded clr-btn-primary ${loading ? ' cursor-progress' : ''}`}
|
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-bold disabled:cursor-not-allowed rounded clr-btn-primary ${loading ? ' cursor-progress' : ''}`}
|
||||||
disabled={disabled}
|
disabled={disabled ?? loading}
|
||||||
>
|
>
|
||||||
{icon && <span>{icon}</span>}
|
{icon && <span>{icon}</span>}
|
||||||
{text && <span>{text}</span>}
|
{text && <span>{text}</span>}
|
||||||
|
|
|
@ -37,7 +37,7 @@ interface IRSFormContext {
|
||||||
claim: (callback?: BackendCallback) => void
|
claim: (callback?: BackendCallback) => void
|
||||||
download: (callback: BackendCallback) => void
|
download: (callback: BackendCallback) => void
|
||||||
|
|
||||||
cstUpdate: (data: any, callback?: BackendCallback) => void
|
cstUpdate: (cstdID: string, data: any, callback?: BackendCallback) => void
|
||||||
cstCreate: (data: any, callback?: BackendCallback) => void
|
cstCreate: (data: any, callback?: BackendCallback) => void
|
||||||
cstDelete: (data: any, callback?: BackendCallback) => void
|
cstDelete: (data: any, callback?: BackendCallback) => void
|
||||||
cstMoveTo: (data: any, callback?: BackendCallback) => void
|
cstMoveTo: (data: any, callback?: BackendCallback) => void
|
||||||
|
@ -102,7 +102,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSucccess: (response) => {
|
onSucccess: (response) => {
|
||||||
reload()
|
reload(setProcessing)
|
||||||
.then(() => { if (callback != null) callback(response); })
|
.then(() => { if (callback != null) callback(response); })
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
|
@ -131,10 +131,10 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSucccess: (response) => {
|
onSucccess: (response) => {
|
||||||
schema.owner = user.id
|
schema.owner = user.id;
|
||||||
schema.time_update = response.data.time_update
|
schema.time_update = response.data.time_update;
|
||||||
setSchema(schema)
|
setSchema(schema);
|
||||||
if (callback != null) callback(response)
|
if (callback != null) callback(response);
|
||||||
}
|
}
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
}, [schemaID, setError, schema, user, setSchema])
|
}, [schemaID, setError, schema, user, setSchema])
|
||||||
|
@ -151,16 +151,20 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
}, [schemaID, setError])
|
}, [schemaID, setError])
|
||||||
|
|
||||||
const cstUpdate = useCallback(
|
const cstUpdate = useCallback(
|
||||||
(data: any, callback?: BackendCallback) => {
|
(cstID: string, data: any, callback?: BackendCallback) => {
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
patchConstituenta(String(activeID), {
|
patchConstituenta(cstID, {
|
||||||
data,
|
data,
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSucccess: callback
|
onSucccess: (response) => {
|
||||||
|
reload(setProcessing)
|
||||||
|
.then(() => { if (callback != null) callback(response); })
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
}, [activeID, setError])
|
}, [setError])
|
||||||
|
|
||||||
const cstCreate = useCallback(
|
const cstCreate = useCallback(
|
||||||
(data: any, callback?: BackendCallback) => {
|
(data: any, callback?: BackendCallback) => {
|
||||||
|
@ -171,11 +175,11 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSucccess: (response) => {
|
onSucccess: (response) => {
|
||||||
setSchema(response.data.schema)
|
setSchema(response.data.schema);
|
||||||
if (callback != null) callback(response)
|
if (callback != null) callback(response);
|
||||||
}
|
}
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
}, [schemaID, setError, setSchema])
|
}, [schemaID, setError, setSchema]);
|
||||||
|
|
||||||
const cstDelete = useCallback(
|
const cstDelete = useCallback(
|
||||||
(data: any, callback?: BackendCallback) => {
|
(data: any, callback?: BackendCallback) => {
|
||||||
|
@ -190,7 +194,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
if (callback != null) callback(response)
|
if (callback != null) callback(response)
|
||||||
}
|
}
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
}, [schemaID, setError, setSchema])
|
}, [schemaID, setError, setSchema]);
|
||||||
|
|
||||||
const cstMoveTo = useCallback(
|
const cstMoveTo = useCallback(
|
||||||
(data: any, callback?: BackendCallback) => {
|
(data: any, callback?: BackendCallback) => {
|
||||||
|
@ -201,11 +205,11 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error) },
|
onError: error => { setError(error) },
|
||||||
onSucccess: (response) => {
|
onSucccess: (response) => {
|
||||||
setSchema(response.data)
|
setSchema(response.data);
|
||||||
if (callback != null) callback(response)
|
if (callback != null) callback(response);
|
||||||
}
|
}
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
}, [schemaID, setError, setSchema])
|
}, [schemaID, setError, setSchema]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RSFormContext.Provider value={{
|
<RSFormContext.Provider value={{
|
||||||
|
|
21
rsconcept/frontend/src/hooks/useEscapeKey.ts
Normal file
21
rsconcept/frontend/src/hooks/useEscapeKey.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
const KEY_NAME_ESC = 'Escape';
|
||||||
|
const KEY_EVENT_TYPE = 'keyup';
|
||||||
|
|
||||||
|
function useEscapeKey(handleClose: () => void) {
|
||||||
|
const handleEscKey = useCallback((event: KeyboardEvent) => {
|
||||||
|
if (event.key === KEY_NAME_ESC) {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
}, [handleClose]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener(KEY_EVENT_TYPE, handleEscKey, false);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener(KEY_EVENT_TYPE, handleEscKey, false);
|
||||||
|
};
|
||||||
|
}, [handleEscKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useEscapeKey;
|
|
@ -12,26 +12,25 @@ export function useRSFormDetails({ target }: { target?: string }) {
|
||||||
function setSchema(schema?: IRSForm) {
|
function setSchema(schema?: IRSForm) {
|
||||||
if (schema) CalculateStats(schema);
|
if (schema) CalculateStats(schema);
|
||||||
setInnerSchema(schema);
|
setInnerSchema(schema);
|
||||||
console.log(schema);
|
console.log('Loaded schema: ', schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchData = useCallback(
|
const fetchData = useCallback(
|
||||||
async () => {
|
async (setCustomLoading?: typeof setLoading) => {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setInnerSchema(undefined);
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await getRSFormDetails(target, {
|
await getRSFormDetails(target, {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading,
|
setLoading: setCustomLoading ?? setLoading,
|
||||||
onError: error => { setError(error); },
|
onError: error => { setInnerSchema(undefined); setError(error); },
|
||||||
onSucccess: (response) => { setSchema(response.data); }
|
onSucccess: (response) => { setSchema(response.data); }
|
||||||
});
|
});
|
||||||
}, [target]);
|
}, [target]);
|
||||||
|
|
||||||
async function reload() {
|
async function reload(setCustomLoading?: typeof setLoading) {
|
||||||
await fetchData();
|
await fetchData(setCustomLoading);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { type AxiosResponse } from 'axios';
|
import { type AxiosResponse } from 'axios';
|
||||||
import { useCallback, useLayoutEffect, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import SubmitButton from '../../components/Common/SubmitButton';
|
import SubmitButton from '../../components/Common/SubmitButton';
|
||||||
|
@ -19,6 +19,7 @@ function ConstituentEditor() {
|
||||||
} = useRSForm();
|
} = useRSForm();
|
||||||
|
|
||||||
const [showCstModal, setShowCstModal] = useState(false);
|
const [showCstModal, setShowCstModal] = useState(false);
|
||||||
|
const [isModified, setIsModified] = useState(false);
|
||||||
const [editMode, setEditMode] = useState(EditMode.TEXT);
|
const [editMode, setEditMode] = useState(EditMode.TEXT);
|
||||||
|
|
||||||
const [alias, setAlias] = useState('');
|
const [alias, setAlias] = useState('');
|
||||||
|
@ -29,13 +30,28 @@ function ConstituentEditor() {
|
||||||
const [convention, setConvention] = useState('');
|
const [convention, setConvention] = useState('');
|
||||||
const [typification, setTypification] = useState('N/A');
|
const [typification, setTypification] = useState('N/A');
|
||||||
|
|
||||||
|
const isEnabled = useMemo(() => activeCst && isEditable, [activeCst, isEditable]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (schema?.items && schema?.items.length > 0) {
|
if (schema?.items && schema?.items.length > 0) {
|
||||||
// TODO: figure out why schema.items could be undef?
|
// TODO: figure out why schema.items could be undef?
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
setActiveID((prev) => (prev ?? schema?.items![0].id ?? undefined));
|
setActiveID((prev) => (prev ?? schema?.items![0].id ?? undefined));
|
||||||
}
|
}
|
||||||
}, [schema, setActiveID])
|
}, [schema, setActiveID]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!activeCst) {
|
||||||
|
setIsModified(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsModified(
|
||||||
|
activeCst.term?.raw !== term ||
|
||||||
|
activeCst.definition?.text?.raw !== textDefinition ||
|
||||||
|
activeCst.convention !== convention ||
|
||||||
|
activeCst.definition?.formal !== expression
|
||||||
|
);
|
||||||
|
}, [activeCst, term, textDefinition, expression, convention]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (activeCst) {
|
if (activeCst) {
|
||||||
|
@ -57,17 +73,12 @@ function ConstituentEditor() {
|
||||||
alias: alias,
|
alias: alias,
|
||||||
convention: convention,
|
convention: convention,
|
||||||
definition_formal: expression,
|
definition_formal: expression,
|
||||||
definition_text: {
|
definition_raw: textDefinition,
|
||||||
raw: textDefinition,
|
term_raw: term
|
||||||
resolved: ''
|
|
||||||
},
|
|
||||||
term: {
|
|
||||||
raw: term,
|
|
||||||
resolved: '',
|
|
||||||
forms: activeCst?.term?.forms ?? []
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
cstUpdate(data, () => toast.success('Изменения сохранены'));
|
cstUpdate(String(activeID), data, () => {
|
||||||
|
toast.success('Изменения сохранены');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -119,7 +130,7 @@ function ConstituentEditor() {
|
||||||
<div className='flex items-start w-full gap-2'>
|
<div className='flex items-start w-full gap-2'>
|
||||||
<CreateCstModal
|
<CreateCstModal
|
||||||
show={showCstModal}
|
show={showCstModal}
|
||||||
toggle={() => { setShowCstModal(!showCstModal); }}
|
hideWindow={() => { setShowCstModal(false); }}
|
||||||
onCreate={handleAddNew}
|
onCreate={handleAddNew}
|
||||||
defaultType={activeCst?.cstType as CstType}
|
defaultType={activeCst?.cstType as CstType}
|
||||||
/>
|
/>
|
||||||
|
@ -128,7 +139,7 @@ function ConstituentEditor() {
|
||||||
<button type='submit'
|
<button type='submit'
|
||||||
title='Сохранить изменения'
|
title='Сохранить изменения'
|
||||||
className='px-1 py-1 font-bold rounded whitespace-nowrap disabled:cursor-not-allowed clr-btn-primary'
|
className='px-1 py-1 font-bold rounded whitespace-nowrap disabled:cursor-not-allowed clr-btn-primary'
|
||||||
disabled={!isEditable}
|
disabled={!isModified || !isEnabled}
|
||||||
>
|
>
|
||||||
<SaveIcon size={5} />
|
<SaveIcon size={5} />
|
||||||
</button>
|
</button>
|
||||||
|
@ -158,18 +169,18 @@ function ConstituentEditor() {
|
||||||
<button type='button'
|
<button type='button'
|
||||||
title='Создать конституенты после данной'
|
title='Создать конституенты после данной'
|
||||||
className='px-1 py-1 font-bold rounded-full whitespace-nowrap disabled:cursor-not-allowed clr-btn-clear'
|
className='px-1 py-1 font-bold rounded-full whitespace-nowrap disabled:cursor-not-allowed clr-btn-clear'
|
||||||
disabled={!isEditable}
|
disabled={!isEnabled}
|
||||||
onClick={() => { handleAddNew(); }}
|
onClick={() => { handleAddNew(); }}
|
||||||
>
|
>
|
||||||
<SmallPlusIcon size={5} color={isEditable ? 'text-green' : ''} />
|
<SmallPlusIcon size={5} color={isEnabled ? 'text-green' : ''} />
|
||||||
</button>
|
</button>
|
||||||
<button type='button'
|
<button type='button'
|
||||||
title='Удалить редактируемую конституенту'
|
title='Удалить редактируемую конституенту'
|
||||||
className='px-1 py-1 font-bold rounded-full whitespace-nowrap disabled:cursor-not-allowed clr-btn-clear'
|
className='px-1 py-1 font-bold rounded-full whitespace-nowrap disabled:cursor-not-allowed clr-btn-clear'
|
||||||
disabled={!isEditable}
|
disabled={!isEnabled}
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
>
|
>
|
||||||
<DumpBinIcon size={5} color={isEditable ? 'text-red' : ''} />
|
<DumpBinIcon size={5} color={isEnabled ? 'text-red' : ''} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -177,7 +188,7 @@ function ConstituentEditor() {
|
||||||
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
|
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
|
||||||
rows={2}
|
rows={2}
|
||||||
value={term}
|
value={term}
|
||||||
disabled={!isEditable}
|
disabled={!isEnabled}
|
||||||
spellCheck
|
spellCheck
|
||||||
onChange={event => { setTerm(event.target.value); }}
|
onChange={event => { setTerm(event.target.value); }}
|
||||||
onFocus={() => { setEditMode(EditMode.TEXT); }}
|
onFocus={() => { setEditMode(EditMode.TEXT); }}
|
||||||
|
@ -190,7 +201,7 @@ function ConstituentEditor() {
|
||||||
<ExpressionEditor id='expression' label='Формальное выражение'
|
<ExpressionEditor id='expression' label='Формальное выражение'
|
||||||
placeholder='Родоструктурное выражение, задающее формальное определение'
|
placeholder='Родоструктурное выражение, задающее формальное определение'
|
||||||
value={expression}
|
value={expression}
|
||||||
disabled={!isEditable}
|
disabled={!isEnabled}
|
||||||
isActive={editMode === 'rslang'}
|
isActive={editMode === 'rslang'}
|
||||||
toggleEditMode={() => { setEditMode(EditMode.RSLANG); }}
|
toggleEditMode={() => { setEditMode(EditMode.RSLANG); }}
|
||||||
onChange={event => { setExpression(event.target.value); }}
|
onChange={event => { setExpression(event.target.value); }}
|
||||||
|
@ -201,7 +212,7 @@ function ConstituentEditor() {
|
||||||
placeholder='Лингвистическая интерпретация формального выражения'
|
placeholder='Лингвистическая интерпретация формального выражения'
|
||||||
rows={4}
|
rows={4}
|
||||||
value={textDefinition}
|
value={textDefinition}
|
||||||
disabled={!isEditable}
|
disabled={!isEnabled}
|
||||||
spellCheck
|
spellCheck
|
||||||
onChange={event => { setTextDefinition(event.target.value); }}
|
onChange={event => { setTextDefinition(event.target.value); }}
|
||||||
onFocus={() => { setEditMode(EditMode.TEXT); }}
|
onFocus={() => { setEditMode(EditMode.TEXT); }}
|
||||||
|
@ -210,7 +221,7 @@ function ConstituentEditor() {
|
||||||
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
||||||
rows={4}
|
rows={4}
|
||||||
value={convention}
|
value={convention}
|
||||||
disabled={!isEditable}
|
disabled={!isEnabled}
|
||||||
spellCheck
|
spellCheck
|
||||||
onChange={event => { setConvention(event.target.value); }}
|
onChange={event => { setConvention(event.target.value); }}
|
||||||
onFocus={() => { setEditMode(EditMode.TEXT); }}
|
onFocus={() => { setEditMode(EditMode.TEXT); }}
|
||||||
|
@ -218,7 +229,7 @@ function ConstituentEditor() {
|
||||||
<div className='flex justify-center w-full mt-2'>
|
<div className='flex justify-center w-full mt-2'>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
text='Сохранить изменения'
|
text='Сохранить изменения'
|
||||||
disabled={!isEditable}
|
disabled={!isModified || !isEnabled}
|
||||||
icon={<SaveIcon size={6} />}
|
icon={<SaveIcon size={6} />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -257,7 +257,7 @@ function ConstituentsTable({ onOpenEdit }: ConstituentsTableProps) {
|
||||||
return (<>
|
return (<>
|
||||||
<CreateCstModal
|
<CreateCstModal
|
||||||
show={showCstModal}
|
show={showCstModal}
|
||||||
toggle={() => { setShowCstModal(!showCstModal); }}
|
hideWindow={() => { setShowCstModal(false); }}
|
||||||
onCreate={handleAddNew}
|
onCreate={handleAddNew}
|
||||||
/>
|
/>
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
|
|
|
@ -7,12 +7,12 @@ import { CstTypeSelector, getCstTypeLabel } from '../../utils/staticUI';
|
||||||
|
|
||||||
interface CreateCstModalProps {
|
interface CreateCstModalProps {
|
||||||
show: boolean
|
show: boolean
|
||||||
toggle: () => void
|
hideWindow: () => void
|
||||||
defaultType?: CstType
|
defaultType?: CstType
|
||||||
onCreate: (type: CstType) => void
|
onCreate: (type: CstType) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function CreateCstModal({ show, toggle, defaultType, onCreate }: CreateCstModalProps) {
|
function CreateCstModal({ show, hideWindow, defaultType, onCreate }: CreateCstModalProps) {
|
||||||
const [validated, setValidated] = useState(false);
|
const [validated, setValidated] = useState(false);
|
||||||
const [selectedType, setSelectedType] = useState<CstType | undefined>(undefined);
|
const [selectedType, setSelectedType] = useState<CstType | undefined>(undefined);
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ function CreateCstModal({ show, toggle, defaultType, onCreate }: CreateCstModalP
|
||||||
<Modal
|
<Modal
|
||||||
title='Создание конституенты'
|
title='Создание конституенты'
|
||||||
show={show}
|
show={show}
|
||||||
toggle={toggle}
|
hideWindow={hideWindow}
|
||||||
canSubmit={validated}
|
canSubmit={validated}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
@ -29,11 +29,28 @@ function RSFormCard() {
|
||||||
const [comment, setComment] = useState('');
|
const [comment, setComment] = useState('');
|
||||||
const [common, setCommon] = useState(false);
|
const [common, setCommon] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
const [isModified, setIsModified] = useState(true);
|
||||||
setTitle(schema?.title ?? '');
|
|
||||||
setAlias(schema?.alias ?? '');
|
useLayoutEffect(() => {
|
||||||
setComment(schema?.comment ?? '');
|
if (!schema) {
|
||||||
setCommon(schema?.is_common ?? false);
|
setIsModified(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsModified(
|
||||||
|
schema.title !== title ||
|
||||||
|
schema.alias !== alias ||
|
||||||
|
schema.comment !== comment ||
|
||||||
|
schema.is_common !== common
|
||||||
|
);
|
||||||
|
}, [schema, title, alias, comment, common]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (schema) {
|
||||||
|
setTitle(schema.title);
|
||||||
|
setAlias(schema.alias);
|
||||||
|
setComment(schema.comment);
|
||||||
|
setCommon(schema.is_common);
|
||||||
|
}
|
||||||
}, [schema]);
|
}, [schema]);
|
||||||
|
|
||||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
@ -87,7 +104,7 @@ function RSFormCard() {
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
text='Сохранить изменения'
|
text='Сохранить изменения'
|
||||||
loading={processing}
|
loading={processing}
|
||||||
disabled={!isEditable || processing}
|
disabled={!isModified || !isEditable}
|
||||||
icon={<SaveIcon size={6} />}
|
icon={<SaveIcon size={6} />}
|
||||||
/>
|
/>
|
||||||
<div className='flex justify-end gap-1'>
|
<div className='flex justify-end gap-1'>
|
||||||
|
@ -97,7 +114,6 @@ function RSFormCard() {
|
||||||
onClick={shareCurrentURLProc}
|
onClick={shareCurrentURLProc}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
disabled={processing}
|
|
||||||
tooltip='Скачать TRS файл'
|
tooltip='Скачать TRS файл'
|
||||||
icon={<DownloadIcon color='text-primary'/>}
|
icon={<DownloadIcon color='text-primary'/>}
|
||||||
loading={processing}
|
loading={processing}
|
||||||
|
@ -105,15 +121,16 @@ function RSFormCard() {
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
|
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
|
||||||
disabled={!isClaimable || processing || !user}
|
|
||||||
icon={<CrownIcon color={isOwned ? '' : 'text-green'}/>}
|
icon={<CrownIcon color={isOwned ? '' : 'text-green'}/>}
|
||||||
|
loading={processing}
|
||||||
|
disabled={!isClaimable || !user}
|
||||||
onClick={() => { claimOwnershipProc(claim); }}
|
onClick={() => { claimOwnershipProc(claim); }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'}
|
tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'}
|
||||||
disabled={!isEditable || processing}
|
|
||||||
icon={<DumpBinIcon color={isEditable ? 'text-red' : ''} />}
|
icon={<DumpBinIcon color={isEditable ? 'text-red' : ''} />}
|
||||||
loading={processing}
|
loading={processing}
|
||||||
|
disabled={!isEditable}
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user