mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Refactor navigation and library search
This commit is contained in:
parent
743ddf298e
commit
fc7af76cfe
|
@ -7,9 +7,8 @@ function Footer() {
|
|||
<footer className='z-50 px-4 pt-2 pb-4 text-sm select-none whitespace-nowrap clr-footer'>
|
||||
<div className='flex items-stretch justify-center w-full gap-4 mx-auto'>
|
||||
<div className='underline'>
|
||||
<Link to='/library' tabIndex={-1}>Библиотека</Link> <br/>
|
||||
<Link to='/manuals' tabIndex={-1}>Справка</Link> <br/>
|
||||
<Link to='/library?filter=common' tabIndex={-1}>Библиотека</Link> <br/>
|
||||
|
||||
</div>
|
||||
<div className=''>
|
||||
<a href={urls.concept} tabIndex={-1} className='underline'>Центр Концепт</a>
|
||||
|
|
|
@ -1,12 +1,25 @@
|
|||
import { EducationIcon, EyeIcon, GroupIcon } from '../Icons';
|
||||
|
||||
function HelpLibrary() {
|
||||
return (
|
||||
<div className=''>
|
||||
<h1>Библиотека концептуальных схем</h1>
|
||||
<p>В библиотеки собраны различные концептуальные схемы. Группировка и классификации схем на данный момент не проводится.</p>
|
||||
<p>На текущем этапе развития Портала происходит первичное наполнение Библиотеки концептуальными схемами.</p>
|
||||
<p>Доступен поиск схемы в Библиотеке с помощью инструментов, расположенных в верхней части страницы.</p>
|
||||
<p>Аттрибут "общедоступная схема" делает схему видимой в разделе библиотека.</p>
|
||||
<p>Аттрибут "библиотечная схема" позволяет выделить неизменяемые схемы, используемые как примеры и основа последующих разработок.</p>
|
||||
<p>В библиотеки собраны различные концептуальные схемы.</p>
|
||||
<p>Группировка и классификации схем на данный момент не проводится.</p>
|
||||
<p>На текущем этапе происходит наполнение Библиотеки концептуальными схемами.</p>
|
||||
<p>Поиск осуществлеяется с помощью инструментов в верхней части страницы.</p>
|
||||
<div className='flex items-center gap-2'>
|
||||
<EyeIcon size={4}/>
|
||||
<p>Аттрибут <b>отслеживаемая</b> обозначает отслеживание схемы.</p>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<GroupIcon size={4}/>
|
||||
<p>Аттрибут <b>общедоступная</b> делает схему видимой в разделе библиотека.</p>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<EducationIcon size={4}/>
|
||||
<p>Аттрибут <b>библиотечная</b> выделяет неизменяемые стандартные схемы.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ interface LogoProps {
|
|||
function Logo({ title }: LogoProps) {
|
||||
return (
|
||||
<Link to='/' className='flex items-center mr-4' tabIndex={-1}>
|
||||
<img src='/favicon.svg' className='min-h-[2.5rem] mr-2 min-w-[2.5rem]' alt=''/>
|
||||
<span className='self-center hidden text-2xl font-semibold lg:block whitespace-nowrap dark:text-white'>{title}</span>
|
||||
<img src='/favicon.svg' className='min-h-[2rem] mr-2 min-w-[2rem]' alt=''/>
|
||||
<span className='self-center hidden text-xl font-semibold lg:block whitespace-nowrap dark:text-white'>{title}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,9 +4,7 @@ import { useConceptTheme } from '../../context/ThemeContext';
|
|||
import { EducationIcon, LibraryIcon } from '../Icons';
|
||||
import Logo from './Logo'
|
||||
import NavigationButton from './NavigationButton';
|
||||
import TopSearch from './TopSearch';
|
||||
import UserMenu from './UserMenu';
|
||||
import UserTools from './UserTools';
|
||||
|
||||
function Navigation () {
|
||||
const navigate = useNavigate();
|
||||
|
@ -16,11 +14,11 @@ function Navigation () {
|
|||
const navigateHelp = () => { navigate('/manuals') };
|
||||
|
||||
return (
|
||||
<nav className='sticky top-0 left-0 right-0 z-50 h-fit'>
|
||||
<nav className='sticky top-0 left-0 right-0 z-50 select-none h-fit'>
|
||||
{!noNavigation &&
|
||||
<button
|
||||
title='Скрыть навигацию'
|
||||
className='absolute top-0 right-0 z-[60] w-[1.2rem] h-[4rem] border-b-2 border-l-2 clr-nav rounded-none'
|
||||
className='absolute top-0 right-0 z-[60] w-[1.2rem] border-b-2 border-l-2 clr-nav rounded-none'
|
||||
onClick={toggleNoNavigation}
|
||||
>
|
||||
<p>{'>'}</p><p>{'>'}</p>
|
||||
|
@ -28,22 +26,30 @@ function Navigation () {
|
|||
{noNavigation &&
|
||||
<button
|
||||
title='Показать навигацию'
|
||||
className='absolute top-0 right-0 z-[60] w-[4rem] h-[1.6rem] border-b-2 border-l-2 clr-nav rounded-none'
|
||||
className='absolute top-0 right-0 z-[60] px-1 h-[1.6rem] border-b-2 border-l-2 clr-nav rounded-none'
|
||||
onClick={toggleNoNavigation}
|
||||
>
|
||||
{'∨∨∨'}
|
||||
</button>}
|
||||
{!noNavigation &&
|
||||
<div className='pr-6 pl-2 py-2.5 h-[4rem] flex items-center justify-between border-b-2 clr-nav rounded-none'>
|
||||
<div className='flex items-start justify-start select-none'>
|
||||
<div className='flex items-center justify-between py-1 pl-2 pr-6 border-b-2 rounded-none clr-nav'>
|
||||
<div className='flex items-center justify-start'>
|
||||
<Logo title='КонцептПортал' />
|
||||
<TopSearch />
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<UserTools/>
|
||||
<div className='flex items-center pl-2'>
|
||||
<NavigationButton icon={<LibraryIcon />} description='Общие схемы' onClick={navigateCommon} />
|
||||
<NavigationButton icon={<EducationIcon />} description='Справка' onClick={navigateHelp} />
|
||||
<NavigationButton
|
||||
text='Библиотека'
|
||||
description='Библиотека концептуальных схем'
|
||||
icon={<LibraryIcon />}
|
||||
onClick={navigateCommon}
|
||||
/>
|
||||
<NavigationButton
|
||||
text='Справка'
|
||||
description='Справочные материалы и обучение'
|
||||
icon={<EducationIcon />}
|
||||
onClick={navigateHelp}
|
||||
/>
|
||||
<UserMenu />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
interface NavigationButtonProps {
|
||||
id?: string
|
||||
text?: string
|
||||
icon: React.ReactNode
|
||||
description?: string
|
||||
colorClass?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const defaultColors = 'text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white'
|
||||
|
||||
function NavigationButton({ id, icon, description, colorClass = defaultColors, onClick }: NavigationButtonProps) {
|
||||
function NavigationButton({ id, icon, description, onClick, text }: NavigationButtonProps) {
|
||||
return (
|
||||
<button id={id}
|
||||
title={description}
|
||||
type='button'
|
||||
onClick={onClick}
|
||||
className={'min-w-fit p-2 mr-1 focus:ring-4 rounded-lg focus:ring-gray-300 dark:focus:ring-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 ' + colorClass}
|
||||
className='flex gap-1 p-2 mr-1 rounded-lg min-w-fit whitespace-nowrap clr-btn-nav'
|
||||
>
|
||||
{icon}
|
||||
{icon && <span>{icon}</span>}
|
||||
{text && <span className='font-semibold'>{text}</span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useNavSearch } from '../../context/NavSearchContext';
|
||||
import { MagnifyingGlassIcon } from '../Icons';
|
||||
|
||||
function TopSearch() {
|
||||
const navigate = useNavigate();
|
||||
const { query, setQuery } = useNavSearch();
|
||||
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
|
||||
if (event.key === 'Enter') {
|
||||
const url = new URL(window.location.href);
|
||||
if (!url.href.includes('/library')) {
|
||||
event.preventDefault();
|
||||
navigate('/library?filter=query');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='hidden md:block md:pl-2'>
|
||||
<div className='relative md:w-96'>
|
||||
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
|
||||
<MagnifyingGlassIcon />
|
||||
</div>
|
||||
<input
|
||||
type='text'
|
||||
name='email'
|
||||
id='topbar-search'
|
||||
value={query}
|
||||
className='text-sm block w-full pl-10 p-2.5 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-600 dark:border-gray-400 dark:placeholder-gray-200 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500'
|
||||
placeholder='Поиск схемы...'
|
||||
onChange={data => setQuery(data.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TopSearch;
|
|
@ -8,7 +8,7 @@ import UserDropdown from './UserDropdown';
|
|||
|
||||
function LoginRef() {
|
||||
return (
|
||||
<Link to='login' className='inline-block text-sm font-bold hover:underline'>
|
||||
<Link to='login' className='inline-block h-full px-1 py-2 font-semibold rounded-lg hover:underline clr-btn-nav text-primary'>
|
||||
Войти...
|
||||
</Link>
|
||||
);
|
||||
|
@ -19,6 +19,7 @@ function UserMenu() {
|
|||
const menu = useDropdown();
|
||||
return (
|
||||
<div ref={menu.ref}>
|
||||
<div className='w-[4.2rem] flex justify-end'>
|
||||
{ !user && <LoginRef />}
|
||||
{ user &&
|
||||
<NavigationButton
|
||||
|
@ -26,6 +27,7 @@ function UserMenu() {
|
|||
description={`Пользователь ${user?.username}`}
|
||||
onClick={menu.toggle}
|
||||
/>}
|
||||
</div>
|
||||
{ user && menu.isActive &&
|
||||
<UserDropdown
|
||||
hideDropdown={() => { menu.hide(); }}
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import ConceptTooltip from '../Common/ConceptTooltip';
|
||||
import TextURL from '../Common/TextURL';
|
||||
import { PlusIcon, SquaresIcon } from '../Icons';
|
||||
import NavigationButton from './NavigationButton';
|
||||
|
||||
function UserTools() {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
|
||||
const navigateCreateRSForm = () => navigate('/rsform-create');
|
||||
const navigateMyWork = () => navigate('/library?filter=personal');
|
||||
|
||||
return (
|
||||
<div className='flex items-center px-2 border-r-2 clr-border-nav'>
|
||||
<span>
|
||||
{ user &&
|
||||
<NavigationButton
|
||||
description='Новая схема'
|
||||
icon={<PlusIcon />}
|
||||
onClick={navigateCreateRSForm}
|
||||
colorClass='text-url'
|
||||
/>}
|
||||
{ !user &&
|
||||
<NavigationButton id='items-nav-help'
|
||||
icon={<PlusIcon />}
|
||||
/>}
|
||||
<ConceptTooltip anchorSelect='#items-nav-help' clickable>
|
||||
<div className='flex flex-col cursor-default'>
|
||||
<p>Создание и редактирование концептуальных схем</p>
|
||||
<p>доступно только <b>зарегистрированным пользователям</b></p>
|
||||
<div className='flex flex-col self-center'>
|
||||
<TextURL text='Войти в систему' href='/login'/>
|
||||
<TextURL text='Зарегистрироваться' href='/signup'/>
|
||||
</div>
|
||||
</div>
|
||||
</ConceptTooltip>
|
||||
</span>
|
||||
{ user && <NavigationButton icon={<SquaresIcon />} description='Мои схемы' onClick={navigateMyWork} /> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserTools;
|
|
@ -1,38 +0,0 @@
|
|||
import { createContext, useCallback, useContext, useState } from 'react';
|
||||
|
||||
interface INavSearchContext {
|
||||
query: string
|
||||
setQuery: (value: string) => void
|
||||
resetQuery: () => void
|
||||
}
|
||||
|
||||
const NavSearchContext = createContext<INavSearchContext | null>(null);
|
||||
export const useNavSearch = () => {
|
||||
const context = useContext(NavSearchContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useNavSearch has to be used within <NavSearchState.Provider>'
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
interface NavSearchStateProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const NavSearchState = ({ children }: NavSearchStateProps) => {
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
const resetQuery = useCallback(() => setQuery(''), []);
|
||||
|
||||
return (
|
||||
<NavSearchContext.Provider value={{
|
||||
query,
|
||||
setQuery,
|
||||
resetQuery: resetQuery
|
||||
}}>
|
||||
{children}
|
||||
</NavSearchContext.Provider>
|
||||
);
|
||||
}
|
|
@ -94,7 +94,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
|||
|
||||
const isTracking = useMemo(
|
||||
() => {
|
||||
if (!schema || !user) {
|
||||
if (!user || !schema || !user.id) {
|
||||
return false;
|
||||
}
|
||||
return schema.subscribers.includes(user.id);
|
||||
|
@ -165,7 +165,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: error => setError(error),
|
||||
onSuccess: () => {
|
||||
if (!schema.subscribers.includes(user.id)) {
|
||||
if (user.id && !schema.subscribers.includes(user.id)) {
|
||||
schema.subscribers.push(user.id);
|
||||
}
|
||||
if (!user.subscriptions.includes(schema.id)) {
|
||||
|
@ -188,7 +188,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: error => setError(error),
|
||||
onSuccess: () => {
|
||||
if (schema.subscribers.includes(user.id)) {
|
||||
if (user.id && schema.subscribers.includes(user.id)) {
|
||||
schema.subscribers.splice(schema.subscribers.indexOf(user.id), 1);
|
||||
}
|
||||
if (user.subscriptions.includes(schema.id)) {
|
||||
|
|
|
@ -47,14 +47,14 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
|
|||
const mainHeight = useMemo(
|
||||
() => {
|
||||
return !noNavigation ?
|
||||
'calc(100vh - 9.2rem)'
|
||||
'calc(100vh - 8.6rem)'
|
||||
: '100vh';
|
||||
}, [noNavigation]);
|
||||
|
||||
const viewportHeight = useMemo(
|
||||
() => {
|
||||
return !noNavigation ?
|
||||
'calc(100vh - 4.5rem)'
|
||||
'calc(100vh - 3.9rem)'
|
||||
: '100vh';
|
||||
}, [noNavigation]);
|
||||
|
||||
|
|
|
@ -86,6 +86,10 @@
|
|||
@apply hover:bg-gray-200 hover:text-gray-700 dark:hover:text-white dark:hover:bg-gray-500
|
||||
}
|
||||
|
||||
.clr-btn-nav {
|
||||
@apply text-gray-500 hover:text-gray-900 dark:text-gray-200 dark:hover:text-white focus:ring-gray-300 dark:focus:ring-gray-400 hover:bg-gray-100 dark:hover:bg-gray-600
|
||||
}
|
||||
|
||||
.clr-btn-primary {
|
||||
@apply text-white bg-blue-400 hover:bg-blue-600 dark:bg-orange-600 dark:hover:bg-orange-400 disabled:bg-gray-400 dark:disabled:bg-gray-600
|
||||
}
|
||||
|
@ -115,7 +119,7 @@
|
|||
}
|
||||
|
||||
.text-url {
|
||||
@apply hover:text-blue-600 text-blue-400 dark:text-orange-600 dark:hover:text-orange-400
|
||||
@apply hover:text-blue-600 text-blue-400 dark:text-orange-500 dark:hover:text-orange-300
|
||||
}
|
||||
|
||||
.text-red {
|
||||
|
|
|
@ -10,7 +10,6 @@ import App from './App.tsx'
|
|||
import ErrorFallback from './components/ErrorFallback.tsx';
|
||||
import { AuthState } from './context/AuthContext.tsx';
|
||||
import { LibraryState } from './context/LibraryContext.tsx';
|
||||
import { NavSearchState } from './context/NavSearchContext.tsx';
|
||||
import { ThemeState } from './context/ThemeContext.tsx';
|
||||
import { UsersState } from './context/UsersContext.tsx';
|
||||
import { initBackend } from './utils/backendAPI.ts';
|
||||
|
@ -36,7 +35,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|||
>
|
||||
<IntlProvider locale='ru' defaultLocale='ru'>
|
||||
<ThemeState>
|
||||
<NavSearchState>
|
||||
<AuthState>
|
||||
<UsersState>
|
||||
<LibraryState>
|
||||
|
@ -46,7 +44,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|||
</LibraryState>
|
||||
</UsersState>
|
||||
</AuthState>
|
||||
</NavSearchState>
|
||||
</ThemeState>
|
||||
</IntlProvider>
|
||||
</ErrorBoundary>
|
||||
|
|
70
rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx
Normal file
70
rsconcept/frontend/src/pages/LibraryPage/SearchPanel.tsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { useLayoutEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { MagnifyingGlassIcon } from '../../components/Icons';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { ILibraryFilter } from '../../utils/models';
|
||||
|
||||
interface SearchPanelProps {
|
||||
filter: ILibraryFilter
|
||||
setFilter: React.Dispatch<React.SetStateAction<ILibraryFilter>>
|
||||
}
|
||||
|
||||
function SearchPanel({ filter, setFilter }: SearchPanelProps) {
|
||||
const search = useLocation().search;
|
||||
const { user } = useAuth();
|
||||
|
||||
const [query, setQuery] = useState('')
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const filterType = new URLSearchParams(search).get('filter');
|
||||
if (filterType === 'common') {
|
||||
setQuery('');
|
||||
setFilter({
|
||||
is_common: true
|
||||
});
|
||||
} else if (filterType === 'personal' && user) {
|
||||
setQuery('');
|
||||
setFilter({
|
||||
ownedBy: user.id!
|
||||
});
|
||||
}
|
||||
}, [user, search, setQuery, setFilter]);
|
||||
|
||||
return (
|
||||
<div className='sticky top-0 left-0 right-0 z-10 flex justify-center w-full border-b clr-bg-pop'>
|
||||
<div className='relative w-96'>
|
||||
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
|
||||
<MagnifyingGlassIcon />
|
||||
</div>
|
||||
<input
|
||||
type='text'
|
||||
value={query}
|
||||
className='w-full p-2 pl-10 text-sm outline-none clr-bg-pop border-x clr-border'
|
||||
placeholder='Поиск схемы...'
|
||||
onChange={data => setQuery(data.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SearchPanel;
|
||||
|
||||
|
||||
{/* <div className='sticky top-0 left-0 right-0 z-10 flex items-start justify-between w-full gap-1 px-2 py-1 bg-white border-b rounded clr-bg-pop clr-border'>
|
||||
<MatchModePicker
|
||||
value={filterMatch}
|
||||
onChange={setFilterMatch}
|
||||
/>
|
||||
<input type='text'
|
||||
className='w-full px-2 bg-white outline-none select-none hover:text-clip clr-bg-pop'
|
||||
placeholder='наберите текст фильтра'
|
||||
value={filterText}
|
||||
onChange={event => setFilterText(event.target.value)}
|
||||
/>
|
||||
<DependencyModePicker
|
||||
value={filterSource}
|
||||
onChange={setFilterSource}
|
||||
/>
|
||||
</div> */}
|
|
@ -3,20 +3,22 @@ import { useIntl } from 'react-intl';
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import ConceptDataTable from '../../components/Common/ConceptDataTable';
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
import MiniButton from '../../components/Common/MiniButton';
|
||||
import TextURL from '../../components/Common/TextURL';
|
||||
import { EducationIcon, EyeIcon, GroupIcon } from '../../components/Icons';
|
||||
import HelpLibrary from '../../components/Help/HelpLibrary';
|
||||
import { EducationIcon, EyeIcon, GroupIcon, HelpIcon, PlusIcon } from '../../components/Icons';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useNavSearch } from '../../context/NavSearchContext';
|
||||
import { useUsers } from '../../context/UsersContext';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
import { ILibraryItem } from '../../utils/models'
|
||||
import { ILibraryItem } from '../../utils/models';
|
||||
|
||||
interface ViewLibraryProps {
|
||||
items: ILibraryItem[]
|
||||
cleanQuery: () => void
|
||||
}
|
||||
|
||||
function ViewLibrary({ items }: ViewLibraryProps) {
|
||||
const { resetQuery: cleanQuery } = useNavSearch();
|
||||
function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
|
||||
const { user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const intl = useIntl();
|
||||
|
@ -24,6 +26,10 @@ function ViewLibrary({ items }: ViewLibraryProps) {
|
|||
|
||||
const openRSForm = (item: ILibraryItem) => navigate(`/rsforms/${item.id}`);
|
||||
|
||||
function handleCreateNew() {
|
||||
navigate('/rsform-create');
|
||||
}
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
|
@ -83,6 +89,26 @@ function ViewLibrary({ items }: ViewLibraryProps) {
|
|||
], [intl, getUserLabel, user]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='relative w-full'>
|
||||
<div className='absolute top-0 left-0 z-20 flex gap-1 mt-2 ml-1'>
|
||||
<MiniButton
|
||||
onClick={handleCreateNew}
|
||||
tooltip='Создать схему'
|
||||
noHover
|
||||
disabled={!user || !user.id}
|
||||
icon={<PlusIcon color={!user || !user.id ? '' : 'text-primary'} size={5} />}
|
||||
/>
|
||||
<div id='library-help' className='py-2'>
|
||||
<HelpIcon color='text-primary' size={5} />
|
||||
</div>
|
||||
<ConceptTooltip anchorSelect='#library-help'>
|
||||
<div className='max-w-[35rem]'>
|
||||
<HelpLibrary />
|
||||
</div>
|
||||
</ConceptTooltip>
|
||||
</div>
|
||||
</div>
|
||||
<ConceptDataTable
|
||||
columns={columns}
|
||||
data={items}
|
||||
|
@ -111,6 +137,7 @@ function ViewLibrary({ items }: ViewLibraryProps) {
|
|||
paginationRowsPerPageOptions={[10, 20, 30, 50, 100]}
|
||||
onRowClicked={openRSForm}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,49 +1,39 @@
|
|||
import { useLayoutEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import BackendError from '../../components/BackendError'
|
||||
import { Loader } from '../../components/Common/Loader'
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { useNavSearch } from '../../context/NavSearchContext';
|
||||
import { ILibraryFilter, ILibraryItem } from '../../utils/models';
|
||||
import SearchPanel from './SearchPanel';
|
||||
import ViewLibrary from './ViewLibrary';
|
||||
|
||||
function LibraryPage() {
|
||||
const search = useLocation().search;
|
||||
const { query, resetQuery: cleanQuery } = useNavSearch();
|
||||
const { user } = useAuth();
|
||||
const library = useLibrary();
|
||||
|
||||
const [ filterParams, setFilterParams ] = useState<ILibraryFilter>({});
|
||||
const [ items, setItems ] = useState<ILibraryItem[]>([]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const filterType = new URLSearchParams(search).get('filter');
|
||||
if (filterType === 'common') {
|
||||
cleanQuery();
|
||||
setFilterParams({
|
||||
is_common: true
|
||||
});
|
||||
} else if (filterType === 'personal' && user) {
|
||||
cleanQuery();
|
||||
setFilterParams({
|
||||
ownedBy: user.id!
|
||||
});
|
||||
}
|
||||
}, [user, search, cleanQuery]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const filter = filterParams;
|
||||
filterParams.queryMeta = query ? query: undefined;
|
||||
setItems(library.filter(filter));
|
||||
}, [query, library, filterParams]);
|
||||
}, [library, filterParams]);
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
{ library.loading && <Loader /> }
|
||||
{ library.error && <BackendError error={library.error} />}
|
||||
{ !library.loading && library.items && <ViewLibrary items={items} /> }
|
||||
{ !library.loading && library.items &&
|
||||
<div className='flex flex-col w-full'>
|
||||
<SearchPanel
|
||||
filter={filterParams}
|
||||
setFilter={setFilterParams}
|
||||
/>
|
||||
<ViewLibrary
|
||||
cleanQuery={() => setFilterParams({})}
|
||||
items={items}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -337,8 +337,8 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
|||
const canvasHeight = useMemo(
|
||||
() => {
|
||||
return !noNavigation ?
|
||||
'calc(100vh - 10.5rem)'
|
||||
: 'calc(100vh - 2rem)';
|
||||
'calc(100vh - 9.8rem)'
|
||||
: 'calc(100vh - 1.8rem)';
|
||||
}, [noNavigation]);
|
||||
|
||||
const dismissedStyle = useCallback(
|
||||
|
|
Loading…
Reference in New Issue
Block a user