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) {
|
function Dropdown({children, widthClass='w-fit', stretchLeft}: DropdownProps) {
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<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}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</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) {
|
function LabeledText({id, label, text, tooltip}: LabeledTextProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-between gap-2'>
|
<div className='flex justify-between gap-4'>
|
||||||
<label
|
<label
|
||||||
className='font-semibold'
|
className='font-semibold'
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
|
|
|
@ -63,7 +63,7 @@ export function EyeOffIcon({size}: IconProps) {
|
||||||
|
|
||||||
export function PenIcon({size}: IconProps) {
|
export function PenIcon({size}: IconProps) {
|
||||||
return (
|
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='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' />
|
<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>
|
</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 { useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import NavigationTextItem from './NavigationTextItem';
|
import DropdownButton from '../Common/DropdownButton';
|
||||||
import { useTheme } from '../../context/ThemeContext';
|
import { useTheme } from '../../context/ThemeContext';
|
||||||
import Dropdown from '../Common/Dropdown';
|
import Dropdown from '../Common/Dropdown';
|
||||||
|
|
||||||
|
@ -29,17 +29,19 @@ function UserDropdown({hideDropdown}: UserDropdownProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown widthClass='w-36' stretchLeft >
|
<Dropdown widthClass='w-36' stretchLeft>
|
||||||
<NavigationTextItem description='Профиль пользователя'
|
<DropdownButton description='Профиль пользователя' onClick={navigateProfile}>
|
||||||
text={user?.username}
|
{user?.username}
|
||||||
onClick={navigateProfile}
|
</DropdownButton>
|
||||||
/>
|
<DropdownButton description='Переключение темы оформления' onClick={toggleDarkMode}>
|
||||||
<NavigationTextItem description='Переключение темы оформления'
|
{darkMode ? 'Светлая тема' : 'Темная тема'}
|
||||||
text={darkMode ? 'Светлая тема' : 'Темная тема'}
|
</DropdownButton>
|
||||||
onClick={toggleDarkMode}
|
<DropdownButton onClick={navigateMyWork}>
|
||||||
/>
|
Мои схемы
|
||||||
<NavigationTextItem text={'Мои схемы'} onClick={navigateMyWork} />
|
</DropdownButton>
|
||||||
<NavigationTextItem text={'Выйти...'} bold onClick={logoutAndRedirect} />
|
<DropdownButton onClick={logoutAndRedirect}>
|
||||||
|
<b>Выйти...</b>
|
||||||
|
</DropdownButton>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,15 +12,16 @@ interface IRSFormContext {
|
||||||
error: ErrorInfo
|
error: ErrorInfo
|
||||||
loading: boolean
|
loading: boolean
|
||||||
processing: boolean
|
processing: boolean
|
||||||
|
isOwned: boolean
|
||||||
isEditable: boolean
|
isEditable: boolean
|
||||||
isClaimable: boolean
|
isClaimable: boolean
|
||||||
forceAdmin: boolean
|
forceAdmin: boolean
|
||||||
readonly: boolean
|
readonly: boolean
|
||||||
isTracking: boolean
|
isTracking: boolean
|
||||||
|
|
||||||
setActive: (cst: IConstituenta | undefined) => void
|
setActive: React.Dispatch<React.SetStateAction<IConstituenta | undefined>>
|
||||||
setForceAdmin: (value: boolean) => void
|
toggleForceAdmin: () => void
|
||||||
setReadonly: (value: boolean) => void
|
toggleReadonly: () => void
|
||||||
toggleTracking: () => void
|
toggleTracking: () => void
|
||||||
reload: () => void
|
reload: () => void
|
||||||
update: (data: any, callback?: BackendCallback) => void
|
update: (data: any, callback?: BackendCallback) => void
|
||||||
|
@ -37,6 +38,7 @@ export const RSFormContext = createContext<IRSFormContext>({
|
||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
processing: false,
|
processing: false,
|
||||||
|
isOwned: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
isClaimable: false,
|
isClaimable: false,
|
||||||
forceAdmin: false,
|
forceAdmin: false,
|
||||||
|
@ -44,8 +46,8 @@ export const RSFormContext = createContext<IRSFormContext>({
|
||||||
isTracking: true,
|
isTracking: true,
|
||||||
|
|
||||||
setActive: () => {},
|
setActive: () => {},
|
||||||
setForceAdmin: () => {},
|
toggleForceAdmin: () => {},
|
||||||
setReadonly: () => {},
|
toggleReadonly: () => {},
|
||||||
toggleTracking: () => {},
|
toggleTracking: () => {},
|
||||||
reload: () => {},
|
reload: () => {},
|
||||||
update: () => {},
|
update: () => {},
|
||||||
|
@ -70,23 +72,23 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
||||||
const [forceAdmin, setForceAdmin] = useState(false);
|
const [forceAdmin, setForceAdmin] = useState(false);
|
||||||
const [readonly, setReadonly] = 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(() => {
|
const isEditable = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
!readonly &&
|
!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(() => {
|
const isTracking = useMemo(() => {
|
||||||
return true;
|
return true;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleTracking = useCallback(() => {
|
const toggleTracking = useCallback(() => {
|
||||||
toast('not implemented yet');
|
toast('not implemented yet');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const isClaimable = useMemo(() => (user?.id !== schema?.owner || false), [user, schema]);
|
|
||||||
|
|
||||||
async function update(data: any, callback?: BackendCallback) {
|
async function update(data: any, callback?: BackendCallback) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
patchRSForm(id, {
|
patchRSForm(id, {
|
||||||
|
@ -143,9 +145,10 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
||||||
<RSFormContext.Provider value={{
|
<RSFormContext.Provider value={{
|
||||||
schema, error, loading, processing,
|
schema, error, loading, processing,
|
||||||
active, setActive,
|
active, setActive,
|
||||||
forceAdmin, setForceAdmin,
|
forceAdmin, readonly,
|
||||||
readonly, setReadonly,
|
toggleForceAdmin: () => setForceAdmin(prev => !prev),
|
||||||
isEditable, isClaimable,
|
toggleReadonly: () => setReadonly(prev => !prev),
|
||||||
|
isOwned, isEditable, isClaimable,
|
||||||
isTracking, toggleTracking,
|
isTracking, toggleTracking,
|
||||||
cstUpdate,
|
cstUpdate,
|
||||||
reload, update, download, destroy, claim
|
reload, update, download, destroy, claim
|
||||||
|
|
|
@ -23,6 +23,10 @@
|
||||||
@apply text-zinc-200 bg-gray-900
|
@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 {
|
.text-red {
|
||||||
@apply text-red-400 dark:text-red-600
|
@apply text-red-400 dark:text-red-600
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { EditMode } from '../../utils/models';
|
import { EditMode, IConstituenta } from '../../utils/models';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
import TextArea from '../../components/Common/TextArea';
|
||||||
import ExpressionEditor from './ExpressionEditor';
|
import ExpressionEditor from './ExpressionEditor';
|
||||||
|
@ -24,10 +24,10 @@ function ConstituentEditor() {
|
||||||
const [typification, setTypification] = useState('N/A');
|
const [typification, setTypification] = useState('N/A');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!active && schema?.items && schema?.items.length > 0) {
|
if (schema?.items && schema?.items.length > 0) {
|
||||||
setActive(schema?.items[0]);
|
setActive((prev) => (prev || schema?.items![0]));
|
||||||
}
|
}
|
||||||
}, [schema, setActive, active])
|
}, [schema, setActive])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (active) {
|
if (active) {
|
||||||
|
|
|
@ -32,13 +32,12 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
const handleRowClicked = useCallback(
|
const handleRowClicked = useCallback(
|
||||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
console.log('ctrl + click');
|
setActive(cst);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [setActive]);
|
||||||
|
|
||||||
const handleDoubleClick = useCallback(
|
const handleDoubleClick = useCallback(
|
||||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
console.log('activating')
|
|
||||||
setActive(cst);
|
setActive(cst);
|
||||||
}, [setActive]);
|
}, [setActive]);
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
|
|
||||||
const handleRowSelected = useCallback(
|
const handleRowSelected = useCallback(
|
||||||
({selectedRows} : SelectionInfo<IConstituenta>) => {
|
({selectedRows} : SelectionInfo<IConstituenta>) => {
|
||||||
console.log('on selection change')
|
|
||||||
setSelectedRows(selectedRows);
|
setSelectedRows(selectedRows);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,15 @@ import { useNavigate } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import fileDownload from 'js-file-download';
|
import fileDownload from 'js-file-download';
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { useAuth } from '../../context/AuthContext';
|
||||||
|
import { claimOwnershipProc, deleteRSFormProc, shareCurrentURLProc } from '../../utils/procedures';
|
||||||
|
|
||||||
function RSFormCard() {
|
function RSFormCard() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { getUserLabel } = useUsers();
|
const { getUserLabel } = useUsers();
|
||||||
const { schema, update, download, reload, isEditable, isClaimable, processing, destroy, claim } = useRSForm();
|
const { schema, update, download, reload, isEditable, isClaimable, processing, destroy, claim } = useRSForm();
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [alias, setAlias] = useState('');
|
const [alias, setAlias] = useState('');
|
||||||
|
@ -45,23 +48,8 @@ function RSFormCard() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete =
|
||||||
if (window.confirm('Вы уверены, что хотите удалить данную схему?')) {
|
useCallback(() => deleteRSFormProc(destroy, navigate), [destroy, navigate]);
|
||||||
destroy(() => {
|
|
||||||
toast.success('Схема удалена');
|
|
||||||
navigate('/rsforms?filter=personal');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [destroy, navigate]);
|
|
||||||
|
|
||||||
const handleClaimOwner = useCallback(() => {
|
|
||||||
if (window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
|
||||||
claim(() => {
|
|
||||||
toast.success('Вы стали владельцем схемы');
|
|
||||||
reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [claim, reload]);
|
|
||||||
|
|
||||||
const handleDownload = useCallback(() => {
|
const handleDownload = useCallback(() => {
|
||||||
download((response: AxiosResponse) => {
|
download((response: AxiosResponse) => {
|
||||||
|
@ -73,12 +61,6 @@ function RSFormCard() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [download, schema?.alias]);
|
}, [download, schema?.alias]);
|
||||||
|
|
||||||
const handleShare = useCallback(() => {
|
|
||||||
const url = window.location.href + '&share';
|
|
||||||
navigator.clipboard.writeText(url);
|
|
||||||
toast.success(`Ссылка скопирована: ${url}`);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className='flex-grow max-w-xl px-4 py-2 border min-w-fit'>
|
<form onSubmit={handleSubmit} className='flex-grow max-w-xl px-4 py-2 border min-w-fit'>
|
||||||
|
@ -112,21 +94,23 @@ function RSFormCard() {
|
||||||
<Button
|
<Button
|
||||||
tooltip='Поделиться схемой'
|
tooltip='Поделиться схемой'
|
||||||
icon={<ShareIcon />}
|
icon={<ShareIcon />}
|
||||||
onClick={handleShare}
|
colorClass='text-primary'
|
||||||
|
onClick={shareCurrentURLProc}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
disabled={processing}
|
disabled={processing}
|
||||||
tooltip='Скачать TRS файл'
|
tooltip='Скачать TRS файл'
|
||||||
icon={<DownloadIcon />}
|
icon={<DownloadIcon />}
|
||||||
|
colorClass='text-primary'
|
||||||
loading={processing}
|
loading={processing}
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
|
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
|
||||||
disabled={!isClaimable || processing}
|
disabled={!isClaimable || processing || !user}
|
||||||
icon={<CrownIcon />}
|
icon={<CrownIcon />}
|
||||||
colorClass='text-green'
|
colorClass='text-green'
|
||||||
onClick={handleClaimOwner}
|
onClick={() => claimOwnershipProc(claim, reload)}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'}
|
tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Tabs, TabList, TabPanel } from 'react-tabs';
|
||||||
import ConstituentsTable from './ConstituentsTable';
|
import ConstituentsTable from './ConstituentsTable';
|
||||||
import { IConstituenta } from '../../utils/models';
|
import { IConstituenta } from '../../utils/models';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import ConceptTab from '../../components/Common/ConceptTab';
|
import ConceptTab from '../../components/Common/ConceptTab';
|
||||||
import RSFormCard from './RSFormCard';
|
import RSFormCard from './RSFormCard';
|
||||||
import { Loader } from '../../components/Common/Loader';
|
import { Loader } from '../../components/Common/Loader';
|
||||||
|
@ -21,6 +21,7 @@ enum TabsList {
|
||||||
function RSFormTabs() {
|
function RSFormTabs() {
|
||||||
const { setActive, active, error, schema, loading } = useRSForm();
|
const { setActive, active, error, schema, loading } = useRSForm();
|
||||||
const [tabIndex, setTabIndex] = useLocalStorage('rsform_edit_tab', TabsList.CARD);
|
const [tabIndex, setTabIndex] = useLocalStorage('rsform_edit_tab', TabsList.CARD);
|
||||||
|
const [init, setInit] = useState(false);
|
||||||
|
|
||||||
const onEditCst = (cst: IConstituenta) => {
|
const onEditCst = (cst: IConstituenta) => {
|
||||||
console.log(`Set active cst: ${cst.alias}`);
|
console.log(`Set active cst: ${cst.alias}`);
|
||||||
|
@ -33,11 +34,14 @@ function RSFormTabs() {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const url = new URL(window.location.href);
|
if (schema) {
|
||||||
const activeQuery = url.searchParams.get('active');
|
const url = new URL(window.location.href);
|
||||||
const activeCst = schema?.items?.find((cst) => cst.entityUID === Number(activeQuery)) || undefined;
|
const activeQuery = url.searchParams.get('active');
|
||||||
setActive(activeCst);
|
const activeCst = schema?.items?.find((cst) => cst.entityUID === Number(activeQuery)) || undefined;
|
||||||
}, [setActive, schema?.items]);
|
setActive(activeCst);
|
||||||
|
setInit(true);
|
||||||
|
}
|
||||||
|
}, [setActive, schema, setInit]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
|
@ -46,15 +50,17 @@ function RSFormTabs() {
|
||||||
}, [setTabIndex]);
|
}, [setTabIndex]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let url = new URL(window.location.href);
|
if (init) {
|
||||||
url.searchParams.set('tab', String(tabIndex));
|
let url = new URL(window.location.href);
|
||||||
if (active) {
|
url.searchParams.set('tab', String(tabIndex));
|
||||||
url.searchParams.set('active', String(active.entityUID));
|
if (active) {
|
||||||
} else {
|
url.searchParams.set('active', String(active.entityUID));
|
||||||
url.searchParams.delete('active');
|
} else {
|
||||||
|
url.searchParams.delete('active');
|
||||||
|
}
|
||||||
|
window.history.pushState(null, '', url.toString());
|
||||||
}
|
}
|
||||||
window.history.replaceState(null, '', url.toString());
|
}, [tabIndex, active, init]);
|
||||||
}, [tabIndex, active]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
|
|
|
@ -1,13 +1,32 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
import Button from '../../components/Common/Button';
|
import Button from '../../components/Common/Button';
|
||||||
import Dropdown from '../../components/Common/Dropdown';
|
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 { useRSForm } from '../../context/RSFormContext';
|
||||||
import useDropdown from '../../hooks/useDropdown';
|
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() {
|
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 schemaMenu = useDropdown();
|
||||||
const editMenu = useDropdown();
|
const editMenu = useDropdown();
|
||||||
|
|
||||||
|
const handleClaimOwner = useCallback(() => {
|
||||||
|
claimOwnershipProc(claim, reload)
|
||||||
|
}, [claim, reload]);
|
||||||
|
|
||||||
|
const handleDelete =
|
||||||
|
useCallback(() => deleteRSFormProc(destroy, navigate), [destroy, navigate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center w-fit'>
|
<div className='flex items-center w-fit'>
|
||||||
|
@ -21,10 +40,14 @@ function TablistTools() {
|
||||||
/>
|
/>
|
||||||
{ schemaMenu.isActive &&
|
{ schemaMenu.isActive &&
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<p className='whitespace-nowrap'>стать владельцем</p>
|
|
||||||
<p>клонировать</p>
|
<p>клонировать</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>}
|
</Dropdown>}
|
||||||
</div>
|
</div>
|
||||||
<div ref={editMenu.ref}>
|
<div ref={editMenu.ref}>
|
||||||
|
@ -38,9 +61,22 @@ function TablistTools() {
|
||||||
/>
|
/>
|
||||||
{ editMenu.isActive &&
|
{ editMenu.isActive &&
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<p className='whitespace-nowrap'>стать владельцем / уже владелец</p>
|
<DropdownButton disabled={!user} onClick={!isOwned ? handleClaimOwner : undefined}>
|
||||||
<p>ридонли</p>
|
<div className='inline-flex items-center gap-1 justify-normal'>
|
||||||
<p>админ оверрайд</p>
|
<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>}
|
</Dropdown>}
|
||||||
</div>
|
</div>
|
||||||
<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