Compare commits

..

No commits in common. "b3c8b217c08e26be3325bcff2aeb567e7e630708" and "25029a212bb63e1cc656cd08b1a69a43655656aa" have entirely different histories.

8 changed files with 54 additions and 36 deletions

View File

@ -361,11 +361,6 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
model = self._get_item() model = self._get_item()
load_metadata = input_serializer.validated_data['load_metadata'] load_metadata = input_serializer.validated_data['load_metadata']
data = utility.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME) data = utility.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
if data is None:
return Response(
status=c.HTTP_400_BAD_REQUEST,
data={'file': msg.exteorFileCorrupted()}
)
data['id'] = model.pk data['id'] = model.pk
serializer = s.RSFormTRSSerializer( serializer = s.RSFormTRSSerializer(
@ -491,17 +486,11 @@ class TrsImportView(views.APIView):
request=s.FileSerializer, request=s.FileSerializer,
responses={ responses={
c.HTTP_201_CREATED: LibraryItemSerializer, c.HTTP_201_CREATED: LibraryItemSerializer,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None c.HTTP_403_FORBIDDEN: None
} }
) )
def post(self, request: Request) -> HttpResponse: def post(self, request: Request) -> HttpResponse:
data = utility.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME) data = utility.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
if data is None:
return Response(
status=c.HTTP_400_BAD_REQUEST,
data={'file': msg.exteorFileCorrupted()}
)
owner = cast(User, self.request.user) owner = cast(User, self.request.user)
_prepare_rsform_data(data, request, owner) _prepare_rsform_data(data, request, owner)
serializer = s.RSFormTRSSerializer( serializer = s.RSFormTRSSerializer(
@ -537,11 +526,6 @@ def create_rsform(request: Request) -> HttpResponse:
) )
data = utility.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME) data = utility.read_zipped_json(request.FILES['file'].file, utils.EXTEOR_INNER_FILENAME)
if data is None:
return Response(
status=c.HTTP_400_BAD_REQUEST,
data={'file': msg.exteorFileCorrupted()}
)
_prepare_rsform_data(data, request, owner) _prepare_rsform_data(data, request, owner)
serializer_rsform = s.RSFormTRSSerializer(data=data, context={'load_meta': True}) serializer_rsform = s.RSFormTRSSerializer(data=data, context={'load_meta': True})
serializer_rsform.is_valid(raise_exception=True) serializer_rsform.is_valid(raise_exception=True)

View File

@ -14,10 +14,6 @@ def operationNotInOSS(title: str):
return f'Операция не принадлежит ОСС: {title}' return f'Операция не принадлежит ОСС: {title}'
def exteorFileCorrupted():
return 'Файл Экстеор не соответствует ожидаемому формату. Попробуйте сохранить файл в новой версии'
def previousResultMissing(): def previousResultMissing():
return 'Отсутствует результат предыдущей операции' return 'Отсутствует результат предыдущей операции'

View File

@ -1,17 +1,13 @@
''' Utility functions. ''' ''' Utility functions. '''
import json import json
from io import BytesIO from io import BytesIO
from typing import Optional from zipfile import ZipFile
from zipfile import BadZipFile, ZipFile
def read_zipped_json(data, json_filename: str) -> Optional[dict]: def read_zipped_json(data, json_filename: str) -> dict:
''' Read JSON from zipped data. ''' ''' Read JSON from zipped data '''
try:
with ZipFile(data, 'r') as archive: with ZipFile(data, 'r') as archive:
json_data = archive.read(json_filename) json_data = archive.read(json_filename)
except BadZipFile:
return None
result: dict = json.loads(json_data) result: dict = json.loads(json_data)
return result return result

View File

@ -41,7 +41,7 @@ function TextInput({
<input <input
id={id} id={id}
className={clsx( className={clsx(
'min-w-0 py-2', 'py-2',
'leading-tight truncate hover:text-clip', 'leading-tight truncate hover:text-clip',
{ {
'px-3': !noBorder || !disabled, 'px-3': !noBorder || !disabled,

View File

@ -62,7 +62,7 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
id='dlg_cst_alias' id='dlg_cst_alias'
dense dense
label='Имя' label='Имя'
className='w-[7rem]' className='w-[7rem] mr-8'
value={state.alias} value={state.alias}
onChange={event => partialUpdate({ alias: event.target.value })} onChange={event => partialUpdate({ alias: event.target.value })}
/> />

View File

@ -46,7 +46,7 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
hideWindow={hideWindow} hideWindow={hideWindow}
canSubmit={validated} canSubmit={validated}
onSubmit={() => onRename(cstData)} onSubmit={() => onRename(cstData)}
className={clsx('w-[30rem]', 'py-6 pr-3 pl-6 flex gap-3 justify-center items-center')} className={clsx('w-[30rem]', 'py-6 pr-3 pl-6 flex justify-center items-center')}
> >
<SelectSingle <SelectSingle
id='dlg_cst_type' id='dlg_cst_type'
@ -64,7 +64,7 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
id='dlg_cst_alias' id='dlg_cst_alias'
dense dense
label='Имя' label='Имя'
className='w-[7rem]' className='w-[7rem] ml-3'
value={cstData.alias} value={cstData.alias}
onChange={event => updateData({ alias: event.target.value })} onChange={event => updateData({ alias: event.target.value })}
/> />

View File

@ -1,11 +1,12 @@
'use client'; 'use client';
import { useCallback, useLayoutEffect, useMemo } from 'react'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import BadgeConstituenta from '@/components/info/BadgeConstituenta'; import BadgeConstituenta from '@/components/info/BadgeConstituenta';
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable'; import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '@/components/ui/DataTable';
import NoData from '@/components/ui/NoData'; import NoData from '@/components/ui/NoData';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptOptions } from '@/context/ConceptOptionsContext';
import useWindowSize from '@/hooks/useWindowSize';
import { ConstituentaID, IConstituenta } from '@/models/rsform'; import { ConstituentaID, IConstituenta } from '@/models/rsform';
import { isMockCst } from '@/models/rsformAPI'; import { isMockCst } from '@/models/rsformAPI';
import { PARAMETER, prefixes } from '@/utils/constants'; import { PARAMETER, prefixes } from '@/utils/constants';
@ -15,6 +16,7 @@ interface TableSideConstituentsProps {
items: IConstituenta[]; items: IConstituenta[];
activeCst?: IConstituenta; activeCst?: IConstituenta;
onOpenEdit: (cstID: ConstituentaID) => void; onOpenEdit: (cstID: ConstituentaID) => void;
denseThreshold?: number;
autoScroll?: boolean; autoScroll?: boolean;
maxHeight: string; maxHeight: string;
} }
@ -26,9 +28,13 @@ function TableSideConstituents({
activeCst, activeCst,
autoScroll = true, autoScroll = true,
onOpenEdit, onOpenEdit,
maxHeight maxHeight,
denseThreshold = 9999
}: TableSideConstituentsProps) { }: TableSideConstituentsProps) {
const { colors } = useConceptOptions(); const { colors } = useConceptOptions();
const windowSize = useWindowSize();
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({ expression: true });
useLayoutEffect(() => { useLayoutEffect(() => {
if (!activeCst) { if (!activeCst) {
@ -48,6 +54,17 @@ function TableSideConstituents({
} }
}, [activeCst, autoScroll]); }, [activeCst, autoScroll]);
useLayoutEffect(() => {
setColumnVisibility(prev => {
const newValue = (windowSize.width ?? 0) >= denseThreshold;
if (newValue === prev.expression) {
return prev;
} else {
return { expression: newValue };
}
});
}, [windowSize, denseThreshold]);
const handleRowClicked = useCallback( const handleRowClicked = useCallback(
(cst: IConstituenta) => { (cst: IConstituenta) => {
if (!isMockCst(cst)) { if (!isMockCst(cst)) {
@ -86,6 +103,25 @@ function TableSideConstituents({
{props.getValue()} {props.getValue()}
</div> </div>
) )
}),
columnHelper.accessor('definition_formal', {
id: 'expression',
header: 'Выражение',
size: 2000,
minSize: 0,
maxSize: 2000,
enableHiding: true,
cell: props => (
<div
style={{
textWrap: 'pretty',
fontSize: 12,
color: isMockCst(props.row.original) ? colors.fgWarning : undefined
}}
>
{props.getValue()}
</div>
)
}) })
], ],
[colors] [colors]
@ -126,6 +162,8 @@ function TableSideConstituents({
conditionalRowStyles={conditionalRowStyles} conditionalRowStyles={conditionalRowStyles}
headPosition='0' headPosition='0'
enableHiding enableHiding
columnVisibility={columnVisibility}
onColumnVisibilityChange={setColumnVisibility}
noDataComponent={ noDataComponent={
<NoData className='min-h-[5rem]'> <NoData className='min-h-[5rem]'>
<p>Список конституент пуст</p> <p>Список конституент пуст</p>

View File

@ -14,6 +14,9 @@ import { animateSideView } from '@/styling/animations';
import ConstituentsSearch from './ConstituentsSearch'; import ConstituentsSearch from './ConstituentsSearch';
import TableSideConstituents from './TableSideConstituents'; import TableSideConstituents from './TableSideConstituents';
// Window width cutoff for expression show
const COLUMN_EXPRESSION_HIDE_THRESHOLD = 1500;
// Window width cutoff for dense search bar // Window width cutoff for dense search bar
const COLUMN_DENSE_SEARCH_THRESHOLD = 1100; const COLUMN_DENSE_SEARCH_THRESHOLD = 1100;
@ -44,6 +47,7 @@ function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit
activeCst={activeCst} activeCst={activeCst}
onOpenEdit={onOpenEdit} onOpenEdit={onOpenEdit}
autoScroll={!isBottom} autoScroll={!isBottom}
denseThreshold={COLUMN_EXPRESSION_HIDE_THRESHOLD}
/> />
), ),
[isBottom, filteredData, activeCst, onOpenEdit, calculateHeight, accessLevel] [isBottom, filteredData, activeCst, onOpenEdit, calculateHeight, accessLevel]