mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Improve UI procedures
This commit is contained in:
parent
ff564704fe
commit
94961deb8a
|
@ -7,7 +7,7 @@ interface DropdownProps {
|
|||
function Dropdown({children, widthClass='w-fit', stretchLeft}: DropdownProps) {
|
||||
return (
|
||||
<div className='relative'>
|
||||
<div className={`absolute ${stretchLeft ? 'right-0': 'left-0'} z-10 flex flex-col items-stretch justify-start p-2 mt-2 text-sm origin-top-right bg-white border border-gray-100 divide-y rounded-md shadow-lg dark:border-gray-500 dark:bg-gray-900 ${widthClass}`}>
|
||||
<div className={`absolute ${stretchLeft ? 'right-0': 'left-0'} py-2 z-10 flex flex-col items-stretch justify-start px-2 mt-2 text-sm origin-top-right bg-white border border-gray-100 divide-y rounded-md shadow-lg dark:border-gray-500 dark:bg-gray-900 ${widthClass}`}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
|
22
rsconcept/frontend/src/components/Common/DropdownButton.tsx
Normal file
22
rsconcept/frontend/src/components/Common/DropdownButton.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
interface NavigationTextItemProps {
|
||||
description?: string | undefined
|
||||
onClick?: () => void
|
||||
disabled?: boolean
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
function DropdownButton({description='', onClick, disabled, children}: NavigationTextItemProps) {
|
||||
const behavior = (onClick ? 'cursor-pointer clr-hover': 'cursor-default') + ' disabled:cursor-not-allowed';
|
||||
return (
|
||||
<button
|
||||
disabled={disabled}
|
||||
title={description}
|
||||
onClick={onClick}
|
||||
className={`px-3 py-1 text-left overflow-ellipsis ${behavior} whitespace-nowrap`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropdownButton;
|
|
@ -7,7 +7,7 @@ interface LabeledTextProps {
|
|||
|
||||
function LabeledText({id, label, text, tooltip}: LabeledTextProps) {
|
||||
return (
|
||||
<div className='flex justify-between gap-2'>
|
||||
<div className='flex justify-between gap-4'>
|
||||
<label
|
||||
className='font-semibold'
|
||||
title={tooltip}
|
||||
|
|
|
@ -63,7 +63,7 @@ export function EyeOffIcon({size}: IconProps) {
|
|||
|
||||
export function PenIcon({size}: IconProps) {
|
||||
return (
|
||||
<IconSVG viewbox='0 0 16 16' size={size}>
|
||||
<IconSVG viewbox='-3 -3 21 21' size={size}>
|
||||
<path d='M15.502 1.94a.5.5 0 010 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 01.707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 00-.121.196l-.805 2.414a.25.25 0 00.316.316l2.414-.805a.5.5 0 00.196-.12l6.813-6.814z' />
|
||||
<path d='M1 13.5A1.5 1.5 0 002.5 15h11a1.5 1.5 0 001.5-1.5v-6a.5.5 0 00-1 0v6a.5.5 0 01-.5.5h-11a.5.5 0 01-.5-.5v-11a.5.5 0 01.5-.5H9a.5.5 0 000-1H2.5A1.5 1.5 0 001 2.5v11z' />
|
||||
</IconSVG>
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
interface NavigationTextItemProps {
|
||||
text?: string | undefined
|
||||
description?: string | undefined
|
||||
onClick: () => void
|
||||
bold?: boolean
|
||||
}
|
||||
|
||||
function NavigationTextItem({text='', description='', onClick, bold}: NavigationTextItemProps) {
|
||||
return (
|
||||
<button
|
||||
title={description}
|
||||
onClick={onClick}
|
||||
className={(bold ? 'font-bold ': '') + 'px-4 py-1 hover:bg-gray-50 hover:text-gray-700 dark:hover:text-white dark:hover:bg-gray-500 overflow-ellipsis text-left'}>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavigationTextItem;
|
|
@ -1,6 +1,6 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import NavigationTextItem from './NavigationTextItem';
|
||||
import DropdownButton from '../Common/DropdownButton';
|
||||
import { useTheme } from '../../context/ThemeContext';
|
||||
import Dropdown from '../Common/Dropdown';
|
||||
|
||||
|
@ -29,17 +29,19 @@ function UserDropdown({hideDropdown}: UserDropdownProps) {
|
|||
};
|
||||
|
||||
return (
|
||||
<Dropdown widthClass='w-36' stretchLeft >
|
||||
<NavigationTextItem description='Профиль пользователя'
|
||||
text={user?.username}
|
||||
onClick={navigateProfile}
|
||||
/>
|
||||
<NavigationTextItem description='Переключение темы оформления'
|
||||
text={darkMode ? 'Светлая тема' : 'Темная тема'}
|
||||
onClick={toggleDarkMode}
|
||||
/>
|
||||
<NavigationTextItem text={'Мои схемы'} onClick={navigateMyWork} />
|
||||
<NavigationTextItem text={'Выйти...'} bold onClick={logoutAndRedirect} />
|
||||
<Dropdown widthClass='w-36' stretchLeft>
|
||||
<DropdownButton description='Профиль пользователя' onClick={navigateProfile}>
|
||||
{user?.username}
|
||||
</DropdownButton>
|
||||
<DropdownButton description='Переключение темы оформления' onClick={toggleDarkMode}>
|
||||
{darkMode ? 'Светлая тема' : 'Темная тема'}
|
||||
</DropdownButton>
|
||||
<DropdownButton onClick={navigateMyWork}>
|
||||
Мои схемы
|
||||
</DropdownButton>
|
||||
<DropdownButton onClick={logoutAndRedirect}>
|
||||
<b>Выйти...</b>
|
||||
</DropdownButton>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,15 +12,16 @@ interface IRSFormContext {
|
|||
error: ErrorInfo
|
||||
loading: boolean
|
||||
processing: boolean
|
||||
isOwned: boolean
|
||||
isEditable: boolean
|
||||
isClaimable: boolean
|
||||
forceAdmin: boolean
|
||||
readonly: boolean
|
||||
isTracking: boolean
|
||||
|
||||
setActive: (cst: IConstituenta | undefined) => void
|
||||
setForceAdmin: (value: boolean) => void
|
||||
setReadonly: (value: boolean) => void
|
||||
setActive: React.Dispatch<React.SetStateAction<IConstituenta | undefined>>
|
||||
toggleForceAdmin: () => void
|
||||
toggleReadonly: () => void
|
||||
toggleTracking: () => void
|
||||
reload: () => void
|
||||
update: (data: any, callback?: BackendCallback) => void
|
||||
|
@ -37,6 +38,7 @@ export const RSFormContext = createContext<IRSFormContext>({
|
|||
error: undefined,
|
||||
loading: false,
|
||||
processing: false,
|
||||
isOwned: false,
|
||||
isEditable: false,
|
||||
isClaimable: false,
|
||||
forceAdmin: false,
|
||||
|
@ -44,8 +46,8 @@ export const RSFormContext = createContext<IRSFormContext>({
|
|||
isTracking: true,
|
||||
|
||||
setActive: () => {},
|
||||
setForceAdmin: () => {},
|
||||
setReadonly: () => {},
|
||||
toggleForceAdmin: () => {},
|
||||
toggleReadonly: () => {},
|
||||
toggleTracking: () => {},
|
||||
reload: () => {},
|
||||
update: () => {},
|
||||
|
@ -70,23 +72,23 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
|||
const [forceAdmin, setForceAdmin] = useState(false);
|
||||
const [readonly, setReadonly] = useState(false);
|
||||
|
||||
const isOwned = useMemo(() => user?.id === schema?.owner || false, [user, schema]);
|
||||
const isClaimable = useMemo(() => (user?.id !== schema?.owner || false), [user, schema]);
|
||||
const isEditable = useMemo(() => {
|
||||
return (
|
||||
!readonly &&
|
||||
(user?.id === schema?.owner || (forceAdmin && user?.is_staff) || false)
|
||||
(isOwned || (forceAdmin && user?.is_staff) || false)
|
||||
)
|
||||
}, [user, schema, readonly, forceAdmin]);
|
||||
}, [user, readonly, forceAdmin, isOwned]);
|
||||
|
||||
const isTracking = useMemo(() => {
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const toggleTracking = useCallback(() => {
|
||||
toast('not implemented yet');
|
||||
}, []);
|
||||
|
||||
|
||||
const isClaimable = useMemo(() => (user?.id !== schema?.owner || false), [user, schema]);
|
||||
|
||||
async function update(data: any, callback?: BackendCallback) {
|
||||
setError(undefined);
|
||||
patchRSForm(id, {
|
||||
|
@ -143,9 +145,10 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
|||
<RSFormContext.Provider value={{
|
||||
schema, error, loading, processing,
|
||||
active, setActive,
|
||||
forceAdmin, setForceAdmin,
|
||||
readonly, setReadonly,
|
||||
isEditable, isClaimable,
|
||||
forceAdmin, readonly,
|
||||
toggleForceAdmin: () => setForceAdmin(prev => !prev),
|
||||
toggleReadonly: () => setReadonly(prev => !prev),
|
||||
isOwned, isEditable, isClaimable,
|
||||
isTracking, toggleTracking,
|
||||
cstUpdate,
|
||||
reload, update, download, destroy, claim
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
@apply text-zinc-200 bg-gray-900
|
||||
}
|
||||
|
||||
.clr-hover {
|
||||
@apply hover:bg-gray-50 hover:text-gray-700 dark:hover:text-white dark:hover:bg-gray-500
|
||||
}
|
||||
|
||||
.text-red {
|
||||
@apply text-red-400 dark:text-red-600
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { EditMode } from '../../utils/models';
|
||||
import { EditMode, IConstituenta } from '../../utils/models';
|
||||
import { toast } from 'react-toastify';
|
||||
import TextArea from '../../components/Common/TextArea';
|
||||
import ExpressionEditor from './ExpressionEditor';
|
||||
|
@ -24,10 +24,10 @@ function ConstituentEditor() {
|
|||
const [typification, setTypification] = useState('N/A');
|
||||
|
||||
useEffect(() => {
|
||||
if (!active && schema?.items && schema?.items.length > 0) {
|
||||
setActive(schema?.items[0]);
|
||||
if (schema?.items && schema?.items.length > 0) {
|
||||
setActive((prev) => (prev || schema?.items![0]));
|
||||
}
|
||||
}, [schema, setActive, active])
|
||||
}, [schema, setActive])
|
||||
|
||||
useEffect(() => {
|
||||
if (active) {
|
||||
|
|
|
@ -32,13 +32,12 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
|||
const handleRowClicked = useCallback(
|
||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||
if (event.ctrlKey) {
|
||||
console.log('ctrl + click');
|
||||
setActive(cst);
|
||||
}
|
||||
}, []);
|
||||
}, [setActive]);
|
||||
|
||||
const handleDoubleClick = useCallback(
|
||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||
console.log('activating')
|
||||
setActive(cst);
|
||||
}, [setActive]);
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
|||
|
||||
const handleRowSelected = useCallback(
|
||||
({selectedRows} : SelectionInfo<IConstituenta>) => {
|
||||
console.log('on selection change')
|
||||
setSelectedRows(selectedRows);
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -12,12 +12,15 @@ import { useNavigate } from 'react-router-dom';
|
|||
import { toast } from 'react-toastify';
|
||||
import fileDownload from 'js-file-download';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { claimOwnershipProc, deleteRSFormProc, shareCurrentURLProc } from '../../utils/procedures';
|
||||
|
||||
function RSFormCard() {
|
||||
const navigate = useNavigate();
|
||||
const intl = useIntl();
|
||||
const { getUserLabel } = useUsers();
|
||||
const { schema, update, download, reload, isEditable, isClaimable, processing, destroy, claim } = useRSForm();
|
||||
const { user } = useAuth();
|
||||
|
||||
const [title, setTitle] = useState('');
|
||||
const [alias, setAlias] = useState('');
|
||||
|
@ -45,23 +48,8 @@ function RSFormCard() {
|
|||
});
|
||||
};
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
if (window.confirm('Вы уверены, что хотите удалить данную схему?')) {
|
||||
destroy(() => {
|
||||
toast.success('Схема удалена');
|
||||
navigate('/rsforms?filter=personal');
|
||||
});
|
||||
}
|
||||
}, [destroy, navigate]);
|
||||
|
||||
const handleClaimOwner = useCallback(() => {
|
||||
if (window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
||||
claim(() => {
|
||||
toast.success('Вы стали владельцем схемы');
|
||||
reload();
|
||||
});
|
||||
}
|
||||
}, [claim, reload]);
|
||||
const handleDelete =
|
||||
useCallback(() => deleteRSFormProc(destroy, navigate), [destroy, navigate]);
|
||||
|
||||
const handleDownload = useCallback(() => {
|
||||
download((response: AxiosResponse) => {
|
||||
|
@ -73,12 +61,6 @@ function RSFormCard() {
|
|||
}
|
||||
});
|
||||
}, [download, schema?.alias]);
|
||||
|
||||
const handleShare = useCallback(() => {
|
||||
const url = window.location.href + '&share';
|
||||
navigator.clipboard.writeText(url);
|
||||
toast.success(`Ссылка скопирована: ${url}`);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className='flex-grow max-w-xl px-4 py-2 border min-w-fit'>
|
||||
|
@ -112,21 +94,23 @@ function RSFormCard() {
|
|||
<Button
|
||||
tooltip='Поделиться схемой'
|
||||
icon={<ShareIcon />}
|
||||
onClick={handleShare}
|
||||
colorClass='text-primary'
|
||||
onClick={shareCurrentURLProc}
|
||||
/>
|
||||
<Button
|
||||
disabled={processing}
|
||||
tooltip='Скачать TRS файл'
|
||||
icon={<DownloadIcon />}
|
||||
colorClass='text-primary'
|
||||
loading={processing}
|
||||
onClick={handleDownload}
|
||||
/>
|
||||
<Button
|
||||
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
|
||||
disabled={!isClaimable || processing}
|
||||
disabled={!isClaimable || processing || !user}
|
||||
icon={<CrownIcon />}
|
||||
colorClass='text-green'
|
||||
onClick={handleClaimOwner}
|
||||
onClick={() => claimOwnershipProc(claim, reload)}
|
||||
/>
|
||||
<Button
|
||||
tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Tabs, TabList, TabPanel } from 'react-tabs';
|
|||
import ConstituentsTable from './ConstituentsTable';
|
||||
import { IConstituenta } from '../../utils/models';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import ConceptTab from '../../components/Common/ConceptTab';
|
||||
import RSFormCard from './RSFormCard';
|
||||
import { Loader } from '../../components/Common/Loader';
|
||||
|
@ -21,6 +21,7 @@ enum TabsList {
|
|||
function RSFormTabs() {
|
||||
const { setActive, active, error, schema, loading } = useRSForm();
|
||||
const [tabIndex, setTabIndex] = useLocalStorage('rsform_edit_tab', TabsList.CARD);
|
||||
const [init, setInit] = useState(false);
|
||||
|
||||
const onEditCst = (cst: IConstituenta) => {
|
||||
console.log(`Set active cst: ${cst.alias}`);
|
||||
|
@ -33,11 +34,14 @@ function RSFormTabs() {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
const url = new URL(window.location.href);
|
||||
const activeQuery = url.searchParams.get('active');
|
||||
const activeCst = schema?.items?.find((cst) => cst.entityUID === Number(activeQuery)) || undefined;
|
||||
setActive(activeCst);
|
||||
}, [setActive, schema?.items]);
|
||||
if (schema) {
|
||||
const url = new URL(window.location.href);
|
||||
const activeQuery = url.searchParams.get('active');
|
||||
const activeCst = schema?.items?.find((cst) => cst.entityUID === Number(activeQuery)) || undefined;
|
||||
setActive(activeCst);
|
||||
setInit(true);
|
||||
}
|
||||
}, [setActive, schema, setInit]);
|
||||
|
||||
useEffect(() => {
|
||||
const url = new URL(window.location.href);
|
||||
|
@ -46,15 +50,17 @@ function RSFormTabs() {
|
|||
}, [setTabIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
let url = new URL(window.location.href);
|
||||
url.searchParams.set('tab', String(tabIndex));
|
||||
if (active) {
|
||||
url.searchParams.set('active', String(active.entityUID));
|
||||
} else {
|
||||
url.searchParams.delete('active');
|
||||
if (init) {
|
||||
let url = new URL(window.location.href);
|
||||
url.searchParams.set('tab', String(tabIndex));
|
||||
if (active) {
|
||||
url.searchParams.set('active', String(active.entityUID));
|
||||
} else {
|
||||
url.searchParams.delete('active');
|
||||
}
|
||||
window.history.pushState(null, '', url.toString());
|
||||
}
|
||||
window.history.replaceState(null, '', url.toString());
|
||||
}, [tabIndex, active]);
|
||||
}, [tabIndex, active, init]);
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
|
|
|
@ -1,13 +1,32 @@
|
|||
import { useCallback } from 'react';
|
||||
import Button from '../../components/Common/Button';
|
||||
import Dropdown from '../../components/Common/Dropdown';
|
||||
import { EyeIcon, EyeOffIcon, MenuIcon, PenIcon } from '../../components/Icons';
|
||||
import { CrownIcon, DumpBinIcon, EyeIcon, EyeOffIcon, MenuIcon, PenIcon } from '../../components/Icons';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import useDropdown from '../../hooks/useDropdown';
|
||||
import DropdownButton from '../../components/Common/DropdownButton';
|
||||
import Checkbox from '../../components/Common/Checkbox';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { claimOwnershipProc, deleteRSFormProc } from '../../utils/procedures';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
function TablistTools() {
|
||||
const { isEditable, isTracking, toggleTracking } = useRSForm();
|
||||
const navigate = useNavigate();
|
||||
const {user} = useAuth();
|
||||
const {
|
||||
isOwned, isEditable, isTracking, readonly, forceAdmin,
|
||||
toggleTracking, toggleForceAdmin, toggleReadonly,
|
||||
claim, reload, destroy
|
||||
} = useRSForm();
|
||||
const schemaMenu = useDropdown();
|
||||
const editMenu = useDropdown();
|
||||
|
||||
const handleClaimOwner = useCallback(() => {
|
||||
claimOwnershipProc(claim, reload)
|
||||
}, [claim, reload]);
|
||||
|
||||
const handleDelete =
|
||||
useCallback(() => deleteRSFormProc(destroy, navigate), [destroy, navigate]);
|
||||
|
||||
return (
|
||||
<div className='flex items-center w-fit'>
|
||||
|
@ -21,10 +40,14 @@ function TablistTools() {
|
|||
/>
|
||||
{ schemaMenu.isActive &&
|
||||
<Dropdown>
|
||||
<p className='whitespace-nowrap'>стать владельцем</p>
|
||||
<p>клонировать</p>
|
||||
<p>поделиться</p>
|
||||
<p>удалить</p>
|
||||
<DropdownButton disabled={isEditable} onClick={handleDelete}>
|
||||
<div className='inline-flex items-center gap-1 justify-normal'>
|
||||
<span className={isOwned ? 'text-red' : ''}><DumpBinIcon size={4} /></span>
|
||||
<p>Удалить схему</p>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
</Dropdown>}
|
||||
</div>
|
||||
<div ref={editMenu.ref}>
|
||||
|
@ -38,9 +61,22 @@ function TablistTools() {
|
|||
/>
|
||||
{ editMenu.isActive &&
|
||||
<Dropdown>
|
||||
<p className='whitespace-nowrap'>стать владельцем / уже владелец</p>
|
||||
<p>ридонли</p>
|
||||
<p>админ оверрайд</p>
|
||||
<DropdownButton disabled={!user} onClick={!isOwned ? handleClaimOwner : undefined}>
|
||||
<div className='inline-flex items-center gap-1 justify-normal'>
|
||||
<span className={isOwned ? 'text-green' : ''}><CrownIcon size={4} /></span>
|
||||
<p>
|
||||
{ isOwned && <b>Владелец схемы</b> }
|
||||
{ !isOwned && <b>Стать владельцем</b> }
|
||||
</p>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
<DropdownButton onClick={toggleReadonly}>
|
||||
<Checkbox value={readonly} label='только чтение'/>
|
||||
</DropdownButton>
|
||||
{user?.is_staff &&
|
||||
<DropdownButton onClick={toggleForceAdmin}>
|
||||
<Checkbox value={forceAdmin} label='режим администратора'/>
|
||||
</DropdownButton>}
|
||||
</Dropdown>}
|
||||
</div>
|
||||
<div>
|
||||
|
|
34
rsconcept/frontend/src/utils/procedures.ts
Normal file
34
rsconcept/frontend/src/utils/procedures.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { toast } from 'react-toastify';
|
||||
import { BackendCallback } from './backendAPI';
|
||||
|
||||
export function shareCurrentURLProc() {
|
||||
const url = window.location.href + '&share';
|
||||
navigator.clipboard.writeText(url);
|
||||
toast.success(`Ссылка скопирована: ${url}`);
|
||||
}
|
||||
|
||||
export function claimOwnershipProc(
|
||||
claim: (callback: BackendCallback) => void,
|
||||
reload: Function
|
||||
) {
|
||||
if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
||||
return;
|
||||
}
|
||||
claim(() => {
|
||||
toast.success('Вы стали владельцем схемы');
|
||||
reload();
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteRSFormProc(
|
||||
destroy: (callback: BackendCallback) => void,
|
||||
navigate: Function
|
||||
) {
|
||||
if (!window.confirm('Вы уверены, что хотите удалить данную схему?')) {
|
||||
return;
|
||||
}
|
||||
destroy(() => {
|
||||
toast.success('Схема удалена');
|
||||
navigate('/rsforms?filter=personal');
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user