Fix UI bugs and styles + refactor contexts

This commit is contained in:
IRBorisov 2023-07-25 00:20:37 +03:00
parent 920a7baff4
commit 747ce126ae
20 changed files with 128 additions and 107 deletions

View File

@ -106,7 +106,8 @@ class RSForm(models.Model):
) )
self._update_from_core() self._update_from_core()
self.save() self.save()
return Constituenta.objects.get(pk=result.pk) result.refresh_from_db()
return result
@transaction.atomic @transaction.atomic
def move_cst(self, listCst: list['Constituenta'], target: int): def move_cst(self, listCst: list['Constituenta'], target: int):

View File

@ -200,25 +200,26 @@ class TestRSFormViewset(APITestCase):
def test_delete_constituenta(self): def test_delete_constituenta(self):
schema = self.rsform_owned schema = self.rsform_owned
data = json.dumps({'items': [{'id': 1337}]}) data = json.dumps({'items': [{'id': 1337}]})
response = self.client.post(f'/api/rsforms/{schema.id}/cst-multidelete/', response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
data=data, content_type='application/json') data=data, content_type='application/json')
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
x1 = Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1) x1 = Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1)
x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2) x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2)
data = json.dumps({'items': [{'id': x1.id}]}) data = json.dumps({'items': [{'id': x1.id}]})
response = self.client.post(f'/api/rsforms/{schema.id}/cst-multidelete/', response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
data=data, content_type='application/json') data=data, content_type='application/json')
x2.refresh_from_db() x2.refresh_from_db()
schema.refresh_from_db() schema.refresh_from_db()
self.assertEqual(response.status_code, 202) self.assertEqual(response.status_code, 202)
self.assertEqual(len(response.data['items']), 1)
self.assertEqual(schema.constituents().count(), 1) self.assertEqual(schema.constituents().count(), 1)
self.assertEqual(x2.alias, 'X2') self.assertEqual(x2.alias, 'X2')
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 1)
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', csttype='basic', order=1) x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', csttype='basic', order=1)
data = json.dumps({'items': [{'id': x3.id}]}) data = json.dumps({'items': [{'id': x3.id}]})
response = self.client.post(f'/api/rsforms/{schema.id}/cst-multidelete/', response = self.client.patch(f'/api/rsforms/{schema.id}/cst-multidelete/',
data=data, content_type='application/json') data=data, content_type='application/json')
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)

View File

@ -69,14 +69,16 @@ class RSFormViewSet(viewsets.ModelViewSet):
response['Location'] = constituenta.get_absolute_url() response['Location'] = constituenta.get_absolute_url()
return response return response
@action(detail=True, methods=['post'], url_path='cst-multidelete') @action(detail=True, methods=['patch'], url_path='cst-multidelete')
def cst_multidelete(self, request, pk): def cst_multidelete(self, request, pk):
''' Delete multiple constituents ''' ''' Delete multiple constituents '''
schema: models.RSForm = self.get_object() schema: models.RSForm = self.get_object()
serializer = serializers.CstListSerlializer(data=request.data, context={'schema': schema}) serializer = serializers.CstListSerlializer(data=request.data, context={'schema': schema})
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
schema.delete_cst(serializer.validated_data['constituents']) schema.delete_cst(serializer.validated_data['constituents'])
return Response(status=202) schema.refresh_from_db()
outSerializer = serializers.RSFormDetailsSerlializer(schema)
return Response(status=202, data=outSerializer.data)
@action(detail=True, methods=['patch'], url_path='cst-moveto') @action(detail=True, methods=['patch'], url_path='cst-moveto')
def cst_moveto(self, request, pk): def cst_moveto(self, request, pk):

View File

@ -7,7 +7,7 @@ interface CardProps {
function Card({title, widthClass='min-w-fit', children}: CardProps) { function Card({title, widthClass='min-w-fit', children}: CardProps) {
return ( return (
<div className={`border shadow-md py-2 clr-card px-6 ${widthClass}`}> <div className={`border shadow-md py-2 clr-card px-6 ${widthClass}`}>
{ title && <h1 className='mb-2 text-xl font-bold'>{title}</h1> } { title && <h1 className='mb-2 text-xl font-bold whitespace-nowrap'>{title}</h1> }
{children} {children}
</div> </div>
); );

View File

@ -1,5 +1,5 @@
import DataTable, { createTheme, TableProps } from 'react-data-table-component'; import DataTable, { createTheme, TableProps } from 'react-data-table-component';
import { useTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
export interface SelectionInfo<T> { export interface SelectionInfo<T> {
allSelected: boolean; allSelected: boolean;
@ -38,7 +38,7 @@ createTheme('customDark', {
}, 'dark'); }, 'dark');
function DataTableThemed<T>({theme, ...props}: TableProps<T>) { function DataTableThemed<T>({theme, ...props}: TableProps<T>) {
const { darkMode } = useTheme(); const { darkMode } = useConceptTheme();
return ( return (
<DataTable<T> <DataTable<T>

View File

@ -6,35 +6,35 @@ import UserMenu from './UserMenu';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import UserTools from './UserTools'; import UserTools from './UserTools';
import Logo from './Logo'; import Logo from './Logo';
import { useState } from 'react'; import { useConceptTheme } from '../../context/ThemeContext';
function Navigation() { function Navigation() {
const {user} = useAuth(); const {user} = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
const [isActive, setActive] = useState(true); const { noNavigation, toggleNoNavigation } = useConceptTheme();
const navigateCommon = () => navigate('/rsforms?filter=common'); const navigateCommon = () => navigate('/rsforms?filter=common');
const navigateHelp = () => navigate('/manuals'); const navigateHelp = () => navigate('/manuals');
return ( return (
<nav className='sticky top-0 left-0 right-0 z-50'> <nav className='sticky top-0 left-0 right-0 z-50'>
{isActive && {!noNavigation &&
<button <button
title='Скрыть навигацию' 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] h-[4rem] border-b-2 border-l-2 clr-nav rounded-none'
onClick={() => setActive(!isActive)} onClick={toggleNoNavigation}
> >
<p>{'>'}</p><p>{'>'}</p> <p>{'>'}</p><p>{'>'}</p>
</button>} </button>}
{!isActive && {noNavigation &&
<button <button
title='Показать навигацию' 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] w-[4rem] h-[1.6rem] border-b-2 border-l-2 clr-nav rounded-none'
onClick={() => setActive(!isActive)} onClick={toggleNoNavigation}
> >
{''} {''}
</button>} </button>}
{isActive && {!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='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 '> <div className='flex items-start justify-start '>
<Logo title='КонцептПортал' /> <Logo title='КонцептПортал' />

View File

@ -1,9 +1,9 @@
import { useTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import { DarkThemeIcon, LightThemeIcon } from '../Icons'; import { DarkThemeIcon, LightThemeIcon } from '../Icons';
import NavigationButton from './NavigationButton'; import NavigationButton from './NavigationButton';
function ThemeSwitcher() { function ThemeSwitcher() {
const {darkMode, toggleDarkMode} = useTheme(); const {darkMode, toggleDarkMode} = useConceptTheme();
return ( return (
<> <>
{darkMode && <NavigationButton icon={<LightThemeIcon />} description='Светлая тема' onClick={toggleDarkMode} />} {darkMode && <NavigationButton icon={<LightThemeIcon />} description='Светлая тема' onClick={toggleDarkMode} />}

View File

@ -1,7 +1,7 @@
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import DropdownButton from '../Common/DropdownButton'; import DropdownButton from '../Common/DropdownButton';
import { useTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import Dropdown from '../Common/Dropdown'; import Dropdown from '../Common/Dropdown';
interface UserDropdownProps { interface UserDropdownProps {
@ -9,7 +9,7 @@ interface UserDropdownProps {
} }
function UserDropdown({hideDropdown}: UserDropdownProps) { function UserDropdown({hideDropdown}: UserDropdownProps) {
const {darkMode, toggleDarkMode} = useTheme(); const {darkMode, toggleDarkMode} = useConceptTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const {user, logout} = useAuth(); const {user, logout} = useAuth();

View File

@ -1,8 +1,8 @@
import { ToastContainer, ToastContainerProps } from 'react-toastify'; import { ToastContainer, ToastContainerProps } from 'react-toastify';
import { useTheme } from '../context/ThemeContext'; import { useConceptTheme } from '../context/ThemeContext';
function ToasterThemed({theme, ...props}: ToastContainerProps) { function ToasterThemed({theme, ...props}: ToastContainerProps) {
const { darkMode } = useTheme(); const { darkMode } = useConceptTheme();
return ( return (
<ToastContainer <ToastContainer

View File

@ -1,29 +1,30 @@
import { createContext, useCallback, useContext, useEffect, useState } from 'react'; import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useState } from 'react';
import { ICurrentUser, IUserSignupData } from '../utils/models'; import { ICurrentUser, IUserSignupData } from '../utils/models';
import { ErrorInfo } from '../components/BackendError'; import { ErrorInfo } from '../components/BackendError';
import useLocalStorage from '../hooks/useLocalStorage'; import useLocalStorage from '../hooks/useLocalStorage';
import { getAuth, postLogin, postLogout, postSignup } from '../utils/backendAPI'; import { BackendCallback, getAuth, postLogin, postLogout, postSignup } from '../utils/backendAPI';
interface IAuthContext { interface IAuthContext {
user: ICurrentUser | undefined user: ICurrentUser | undefined
login: (username: string, password: string) => Promise<void> login: (username: string, password: string, callback?: BackendCallback) => Promise<void>
logout: (onSuccess?: () => void) => Promise<void> logout: (callback?: BackendCallback) => Promise<void>
signup: (data: IUserSignupData) => Promise<void> signup: (data: IUserSignupData, callback?: BackendCallback) => Promise<void>
loading: boolean loading: boolean
error: ErrorInfo error: ErrorInfo
setError: (error: ErrorInfo) => void setError: (error: ErrorInfo) => void
} }
export const AuthContext = createContext<IAuthContext>({ const AuthContext = createContext<IAuthContext | null>(null);
user: undefined, export const useAuth = () => {
login: async () => {}, const context = useContext(AuthContext);
logout: async () => {}, if (!context) {
signup: async () => {}, throw new Error(
loading: false, 'useAuth has to be used within <AuthState.Provider>'
error: '', );
setError: () => {} }
}); return context;
}
interface AuthStateProps { interface AuthStateProps {
children: React.ReactNode children: React.ReactNode
@ -49,44 +50,49 @@ export const AuthState = ({ children }: AuthStateProps) => {
}, [setUser] }, [setUser]
); );
async function login(uname: string, pw: string, onSuccess?: () => void) { async function login(uname: string, pw: string, callback?: BackendCallback) {
setError(undefined); setError(undefined);
postLogin({ postLogin({
data: {username: uname, password: pw}, data: {username: uname, password: pw},
showError: true, showError: true,
setLoading: setLoading, setLoading: setLoading,
onError: error => setError(error), onError: error => setError(error),
onSucccess: response => { onSucccess:
loadCurrentUser(); async (response) => {
if(onSuccess) onSuccess(); await loadCurrentUser();
if(callback) callback(response);
} }
}); });
} }
async function logout() { async function logout(callback?: BackendCallback) {
setError(undefined); setError(undefined);
postLogout({ postLogout({
showError: true, showError: true,
onSucccess: response => { onSucccess:
loadCurrentUser(); async (response) => {
await loadCurrentUser();
if (callback) callback(response);
} }
}); });
} }
async function signup(data: IUserSignupData) { async function signup(data: IUserSignupData, callback?: BackendCallback) {
setError(undefined); setError(undefined);
postSignup({ postSignup({
data: data, data: data,
showError: true, showError: true,
setLoading: setLoading, setLoading: setLoading,
onError: error => setError(error), onError: error => setError(error),
onSucccess: response => { onSucccess:
loadCurrentUser(); async (response) => {
await loadCurrentUser();
if (callback) callback(response);
} }
}); });
} }
useEffect(() => { useLayoutEffect(() => {
loadCurrentUser(); loadCurrentUser();
}, [loadCurrentUser]) }, [loadCurrentUser])
@ -98,5 +104,3 @@ export const AuthState = ({ children }: AuthStateProps) => {
</AuthContext.Provider> </AuthContext.Provider>
); );
}; };
export const useAuth = () => useContext(AuthContext);

View File

@ -6,7 +6,7 @@ import { useAuth } from './AuthContext';
import { import {
BackendCallback, deleteRSForm, getTRSFile, BackendCallback, deleteRSForm, getTRSFile,
patchConstituenta, patchMoveConstituenta, patchRSForm, patchConstituenta, patchMoveConstituenta, patchRSForm,
postClaimRSForm, postDeleteConstituenta, postNewConstituenta postClaimRSForm, patchDeleteConstituenta, postNewConstituenta
} from '../utils/backendAPI'; } from '../utils/backendAPI';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@ -175,17 +175,17 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
const cstDelete = useCallback( const cstDelete = useCallback(
async (data: any, callback?: BackendCallback) => { async (data: any, callback?: BackendCallback) => {
setError(undefined); setError(undefined);
postDeleteConstituenta(schemaID, { patchDeleteConstituenta(schemaID, {
data: data, data: data,
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => setError(error), onError: error => setError(error),
onSucccess: async (response) => { onSucccess: async (response) => {
await reload(); setSchema(response.data);
if (callback) callback(response); if (callback) callback(response);
} }
}); });
}, [schemaID, setError, reload]); }, [schemaID, setError, setSchema]);
const cstMoveTo = useCallback( const cstMoveTo = useCallback(
async (data: any, callback?: BackendCallback) => { async (data: any, callback?: BackendCallback) => {

View File

@ -1,16 +1,24 @@
import { createContext, useContext, useEffect } from 'react'; import { createContext, useContext, useEffect, useState } from 'react';
import useLocalStorage from '../hooks/useLocalStorage'; import useLocalStorage from '../hooks/useLocalStorage';
interface IThemeContext { interface IThemeContext {
darkMode: boolean darkMode: boolean
noNavigation: boolean
toggleDarkMode: () => void toggleDarkMode: () => void
toggleNoNavigation: () => void
} }
export const ThemeContext = createContext<IThemeContext>({ const ThemeContext = createContext<IThemeContext | null>(null);
darkMode: true, export const useConceptTheme = () => {
toggleDarkMode: () => {} const context = useContext(ThemeContext);
}) if (!context) {
throw new Error(
'useConceptTheme has to be used within <ThemeState.Provider>'
);
}
return context;
}
interface ThemeStateProps { interface ThemeStateProps {
children: React.ReactNode children: React.ReactNode
@ -18,6 +26,7 @@ interface ThemeStateProps {
export const ThemeState = ({ children }: ThemeStateProps) => { export const ThemeState = ({ children }: ThemeStateProps) => {
const [darkMode, setDarkMode] = useLocalStorage('darkMode', false); const [darkMode, setDarkMode] = useLocalStorage('darkMode', false);
const [noNavigation, setNoNavigation] = useState(false);
const setDarkClass = (isDark: boolean) => { const setDarkClass = (isDark: boolean) => {
const root = window.document.documentElement; const root = window.document.documentElement;
@ -29,21 +38,16 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
root.setAttribute('data-color-scheme', !isDark ? 'light' : 'dark'); root.setAttribute('data-color-scheme', !isDark ? 'light' : 'dark');
}; };
const toggleDarkMode = () => {
setDarkMode(!darkMode)
};
useEffect(() => { useEffect(() => {
setDarkClass(darkMode) setDarkClass(darkMode)
}, [darkMode]); }, [darkMode]);
return ( return (
<ThemeContext.Provider value={{ <ThemeContext.Provider value={{
darkMode, toggleDarkMode darkMode, toggleDarkMode: () => setDarkMode(prev => !prev),
noNavigation, toggleNoNavigation: () => setNoNavigation(prev => !prev),
}}> }}>
{children} {children}
</ThemeContext.Provider> </ThemeContext.Provider>
); );
} }
export const useTheme = () => useContext(ThemeContext);

View File

@ -9,11 +9,16 @@ interface IUsersContext {
getUserLabel: (userID?: number) => string getUserLabel: (userID?: number) => string
} }
export const UsersContext = createContext<IUsersContext>({ const UsersContext = createContext<IUsersContext | null>(null);
users: [], export const useUsers = () => {
reload: async () => {}, const context = useContext(UsersContext);
getUserLabel: () => '' if (!context) {
}) throw new Error(
'useUsers has to be used within <UsersState.Provider>'
);
}
return context;
}
interface UsersStateProps { interface UsersStateProps {
children: React.ReactNode children: React.ReactNode
@ -64,5 +69,3 @@ export const UsersState = ({ children }: UsersStateProps) => {
</UsersContext.Provider> </UsersContext.Provider>
); );
} }
export const useUsers = () => useContext(UsersContext);

View File

@ -30,8 +30,7 @@ function LoginPage() {
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
if (!loading) { if (!loading) {
login(username, password) login(username, password, () => navigate('/rsforms?filter=personal'));
.then(() => navigate('/rsforms?filter=personal'));
} }
}; };

View File

@ -64,7 +64,7 @@ function ConstituentEditor() {
'forms': activeCst?.term?.forms || [], 'forms': activeCst?.term?.forms || [],
} }
}; };
cstUpdate(data).then(() => toast.success('Изменения сохранены')); cstUpdate(data, () => toast.success('Изменения сохранены'));
} }
}; };
@ -74,13 +74,13 @@ function ConstituentEditor() {
return; return;
} }
const data = { const data = {
'items': [activeID] 'items': [{'id': activeID}]
} }
const index = schema.items.findIndex((cst) => cst.id === activeID); const index = schema.items.findIndex((cst) => cst.id === activeID);
if (index !== -1 && index + 1 < schema.items.length) { if (index !== -1 && index + 1 < schema.items.length) {
setActiveID(schema.items[index + 1].id); setActiveID(schema.items[index + 1].id);
} }
cstDelete(data).then(() => toast.success('Конституента удалена')); cstDelete(data, () => toast.success('Конституента удалена'));
}, [activeID, schema, setActiveID, cstDelete]); }, [activeID, schema, setActiveID, cstDelete]);
const handleAddNew = useCallback( const handleAddNew = useCallback(

View File

@ -9,6 +9,7 @@ import Divider from '../../components/Common/Divider';
import { createAliasFor, getCstTypeLabel, getCstTypePrefix, getStatusInfo, getTypeLabel } from '../../utils/staticUI'; import { createAliasFor, getCstTypeLabel, getCstTypePrefix, getStatusInfo, getTypeLabel } from '../../utils/staticUI';
import CreateCstModal from './CreateCstModal'; import CreateCstModal from './CreateCstModal';
import { AxiosResponse } from 'axios'; import { AxiosResponse } from 'axios';
import { useConceptTheme } from '../../context/ThemeContext';
interface ConstituentsTableProps { interface ConstituentsTableProps {
onOpenEdit: (cst: IConstituenta) => void onOpenEdit: (cst: IConstituenta) => void
@ -19,6 +20,7 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
schema, isEditable, schema, isEditable,
cstCreate, cstDelete, cstMoveTo cstCreate, cstDelete, cstMoveTo
} = useRSForm(); } = useRSForm();
const { noNavigation } = useConceptTheme();
const [selected, setSelected] = useState<number[]>([]); const [selected, setSelected] = useState<number[]>([]);
const nothingSelected = useMemo(() => selected.length === 0, [selected]); const nothingSelected = useMemo(() => selected.length === 0, [selected]);
@ -71,7 +73,7 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
'items': selected.map(id => { return {'id': id }; }), 'items': selected.map(id => { return {'id': id }; }),
'move_to': insertIndex 'move_to': insertIndex
} }
cstMoveTo(data).then(() => toast.info('Перемещение вверх ' + insertIndex)); cstMoveTo(data);
}, [selected, schema?.items, cstMoveTo]); }, [selected, schema?.items, cstMoveTo]);
@ -81,20 +83,24 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
if (!schema?.items || selected.length === 0) { if (!schema?.items || selected.length === 0) {
return; return;
} }
let count = 0;
const currentIndex = schema.items.reduce((prev, cst, index) => { const currentIndex = schema.items.reduce((prev, cst, index) => {
if (selected.indexOf(cst.id) < 0) { if (selected.indexOf(cst.id) < 0) {
return prev; return prev;
} else if (prev === -1) { } else {
count += 1;
if (prev === -1) {
return index; return index;
} }
return Math.max(prev, index); return Math.max(prev, index);
}
}, -1); }, -1);
const insertIndex = Math.min(schema.items.length - 1, currentIndex + 1) + 1 const insertIndex = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
const data = { const data = {
'items': selected.map(id => { return {'id': id }; }), 'items': selected.map(id => { return {'id': id }; }),
'move_to': insertIndex 'move_to': insertIndex
} }
cstMoveTo(data).then(() => toast.info('Перемещение вниз ' + insertIndex)); cstMoveTo(data);
}, [selected, schema?.items, cstMoveTo]); }, [selected, schema?.items, cstMoveTo]);
// Generate new names for all constituents // Generate new names for all constituents
@ -131,7 +137,6 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
case 'ArrowUp': handleMoveUp(); return; case 'ArrowUp': handleMoveUp(); return;
case 'ArrowDown': handleMoveDown(); return; case 'ArrowDown': handleMoveDown(); return;
} }
console.log(event);
}, [isEditable, selected, handleMoveUp, handleMoveDown]); }, [isEditable, selected, handleMoveUp, handleMoveDown]);
const columns = useMemo(() => const columns = useMemo(() =>
@ -253,7 +258,10 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
onCreate={handleAddNew} onCreate={handleAddNew}
/> />
<div className='w-full'> <div className='w-full'>
<div className='sticky top-[4rem] z-10 flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem] clr-app'> <div
className={'flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem] clr-app'
+ (!noNavigation ? ' sticky z-10 top-[4rem]' : ' sticky z-10 top-[0rem]')}
>
<div className='mr-3 whitespace-nowrap'>Выбраны <span className='ml-2'><b>{selected.length}</b> из {schema?.stats?.count_all || 0}</span></div> <div className='mr-3 whitespace-nowrap'>Выбраны <span className='ml-2'><b>{selected.length}</b> из {schema?.stats?.count_all || 0}</span></div>
{isEditable && <div className='flex justify-start w-full gap-1'> {isEditable && <div className='flex justify-start w-full gap-1'>
<Button <Button

View File

@ -43,7 +43,7 @@ function RSFormCard() {
'comment': comment, 'comment': comment,
'is_common': common, 'is_common': common,
}; };
update(data).then(() => toast.success('Изменения сохранены')); update(data, () => toast.success('Изменения сохранены'));
}; };
const handleDelete = const handleDelete =

View File

@ -35,7 +35,7 @@ function RegisterPage() {
'first_name': firstName, 'first_name': firstName,
'last_name': lastName, 'last_name': lastName,
}; };
signup(data).then(() => setSuccess(true)); signup(data, () => setSuccess(true));
} }
}; };

View File

@ -158,8 +158,8 @@ export async function postNewConstituenta(schema: string, request?: IFrontReques
}); });
} }
export async function postDeleteConstituenta(schema: string, request?: IFrontRequest) { export async function patchDeleteConstituenta(schema: string, request?: IFrontRequest) {
AxiosPost({ AxiosPatch<IRSForm>({
title: `Delete Constituents for RSForm id=${schema}: ${request?.data['items'].toString()}`, title: `Delete Constituents for RSForm id=${schema}: ${request?.data['items'].toString()}`,
endpoint: `${config.url.BASE}rsforms/${schema}/cst-multidelete/`, endpoint: `${config.url.BASE}rsforms/${schema}/cst-multidelete/`,
request: request request: request

View File

@ -207,7 +207,7 @@ export function getCstTypePrefix(type: CstType) {
case CstType.CONSTANT: return 'C'; case CstType.CONSTANT: return 'C';
case CstType.STRUCTURED: return 'S'; case CstType.STRUCTURED: return 'S';
case CstType.AXIOM: return 'A'; case CstType.AXIOM: return 'A';
case CstType.TERM: return 'T'; case CstType.TERM: return 'D';
case CstType.FUNCTION: return 'F'; case CstType.FUNCTION: return 'F';
case CstType.PREDICATE: return 'P'; case CstType.PREDICATE: return 'P';
case CstType.THEOREM: return 'T'; case CstType.THEOREM: return 'T';
@ -259,17 +259,16 @@ export function extractGlobals(expression: string): Set<string> {
} }
export function createAliasFor(type: CstType, schema: IRSForm): string { export function createAliasFor(type: CstType, schema: IRSForm): string {
let index = 1;
let prefix = getCstTypePrefix(type); let prefix = getCstTypePrefix(type);
let name = prefix + index; if (!schema.items || schema.items.length <= 0) {
if (schema.items && schema.items.length > 0) { return `${prefix}1`;
for (let i = 0; i < schema.items.length; ++i) {
if (schema.items[i].alias === name) {
++index;
name = prefix + index;
i = 0;
} }
const index = schema.items.reduce((prev, cst, index) => {
if (cst.cstType !== type) {
return prev;
} }
} index = Number(cst.alias.slice(1 - cst.alias.length)) + 1;
return name; return Math.max(prev, index);
}, 1);
return `${prefix}${index}`;
} }