F: Add block stats to side panel

This commit is contained in:
Ivan 2025-07-07 15:02:45 +03:00
parent 442a18a795
commit dc5ff50175
8 changed files with 53 additions and 20 deletions

View File

@ -41,7 +41,7 @@ export function HelpOssGraph() {
<IconFitImage className='inline-icon' /> Вписать в экран
</li>
<li>
<IconLeftOpen className='inline-icon' /> Панель связанной КС
<IconLeftOpen className='inline-icon' /> Панель содержания
</li>
<li>
<IconSettings className='inline-icon' /> Диалог настроек

View File

@ -236,7 +236,6 @@ export function OssFlow() {
showPanel ? 'translate-x-0' : 'opacity-0 translate-x-full pointer-events-none'
)}
isMounted={showPanel}
selectedItems={selectedItems}
/>
</div>
);

View File

@ -0,0 +1,24 @@
import { OperationType } from '../../../../backend/types';
import { OssStats } from '../../../../components/oss-stats';
import { type IBlock, type IOperationSchema, NodeType } from '../../../../models/oss';
interface BlockStatsProps {
target: IBlock;
oss: IOperationSchema;
}
export function BlockStats({ target, oss }: BlockStatsProps) {
const contents = oss.hierarchy.expandAllOutputs([target.nodeID]);
const items = contents.map(item => oss.itemByNodeID.get(item)).filter(item => !!item);
const operations = items.filter(item => item.nodeType === NodeType.OPERATION);
const blockStats = {
count_all: contents.length,
count_inputs: operations.filter(item => item.operation_type === OperationType.INPUT).length,
count_synthesis: operations.filter(item => item.operation_type === OperationType.SYNTHESIS).length,
count_schemas: operations.filter(item => !!item.result).length,
count_owned: operations.filter(item => !!item.result && item.is_owned).length,
count_block: contents.length - operations.length
};
return <OssStats stats={blockStats} className='pr-3' />;
}

View File

@ -10,19 +10,23 @@ import { useMainHeight } from '@/stores/app-layout';
import { usePreferencesStore } from '@/stores/preferences';
import { PARAMETER } from '@/utils/constants';
import { type IOssItem, NodeType } from '../../../../models/oss';
import { NodeType } from '../../../../models/oss';
import { useOssEdit } from '../../oss-edit-context';
import { BlockStats } from './block-stats';
import { ViewSchema } from './view-schema';
interface SidePanelProps {
selectedItems: IOssItem[];
className?: string;
isMounted: boolean;
}
export function SidePanel({ selectedItems, isMounted, className }: SidePanelProps) {
export function SidePanel({ isMounted, className }: SidePanelProps) {
const { schema, isMutable, selectedItems } = useOssEdit();
const selectedOperation =
selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.OPERATION ? selectedItems[0] : null;
const selectedBlock =
selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.BLOCK ? selectedItems[0] : null;
const selectedSchema = selectedOperation?.result ?? null;
const debouncedMounted = useDebounce(isMounted, PARAMETER.moveDuration);
@ -52,21 +56,23 @@ export function SidePanel({ selectedItems, isMounted, className }: SidePanelProp
'mt-0 mb-1',
'font-medium text-sm select-none self-center',
'transition-transform',
selectedSchema && 'translate-x-16'
selectedSchema && 'translate-x-20'
)}
>
Содержание КС
Содержание
</div>
{!selectedOperation ? (
<div className='text-center text-sm cc-fade-in'>Выделите операцию для просмотра</div>
) : !selectedSchema ? (
{!selectedOperation && !selectedBlock ? (
<div className='text-center text-sm cc-fade-in'>Выделите операцию или блок для просмотра</div>
) : null}
{selectedOperation && !selectedSchema ? (
<div className='text-center text-sm cc-fade-in'>Отсутствует концептуальная схема для выбранной операции</div>
) : debouncedMounted ? (
) : selectedOperation && selectedSchema && debouncedMounted ? (
<Suspense fallback={<Loader />}>
<ViewSchema schemaID={selectedSchema} />
<ViewSchema schemaID={selectedSchema} isMutable={isMutable && selectedOperation.is_owned} />
</Suspense>
) : null}
{selectedBlock ? <BlockStats target={selectedBlock} oss={schema} /> : null}
</aside>
);
}

View File

@ -16,6 +16,7 @@ import { type RO } from '@/utils/meta';
interface ToolbarConstituentsProps {
schema: IRSForm;
isMutable: boolean;
activeCst: IConstituenta | null;
setActive: (cstID: number) => void;
resetActive: () => void;
@ -27,6 +28,7 @@ export function ToolbarConstituents({
activeCst,
setActive,
resetActive,
isMutable,
className
}: ToolbarConstituentsProps) {
const router = useConceptNavigation();
@ -162,33 +164,33 @@ export function ToolbarConstituents({
title='Создать конституенту'
icon={<IconNewItem size='1rem' className='icon-green' />}
onClick={createCst}
disabled={isProcessing}
disabled={!isMutable || isProcessing}
/>
<MiniButton
title='Клонировать конституенту'
icon={<IconClone size='1rem' className='icon-green' />}
onClick={cloneCst}
disabled={!activeCst || isProcessing}
disabled={!isMutable || !activeCst || isProcessing}
/>
<MiniButton
title='Удалить выделенную конституенту'
onClick={promptDeleteCst}
icon={<IconDestroy size='1rem' className='icon-red' />}
disabled={!activeCst || isProcessing || activeCst?.is_inherited}
disabled={!isMutable || !activeCst || isProcessing || activeCst?.is_inherited}
/>
<MiniButton
title='Переместить вверх'
icon={<IconMoveUp size='1rem' className='icon-primary' />}
onClick={moveUp}
disabled={!activeCst || isProcessing || schema.items.length < 2 || hasSearch}
disabled={!isMutable || !activeCst || isProcessing || schema.items.length < 2 || hasSearch}
/>
<MiniButton
title='Переместить вниз'
icon={<IconMoveDown size='1rem' className='icon-primary' />}
onClick={moveDown}
disabled={!activeCst || isProcessing || schema.items.length < 2 || hasSearch}
disabled={!isMutable || !activeCst || isProcessing || schema.items.length < 2 || hasSearch}
/>
</div>
);

View File

@ -10,9 +10,10 @@ import { ToolbarConstituents } from './toolbar-constituents';
interface ViewSchemaProps {
schemaID: number;
isMutable: boolean;
}
export function ViewSchema({ schemaID }: ViewSchemaProps) {
export function ViewSchema({ schemaID, isMutable }: ViewSchemaProps) {
const { schema } = useRSFormSuspense({ itemID: schemaID });
const [activeID, setActiveID] = useState<number | null>(null);
const activeCst = activeID ? schema.cstByID.get(activeID) ?? null : null;
@ -25,6 +26,7 @@ export function ViewSchema({ schemaID }: ViewSchemaProps) {
className='absolute -top-7 left-1'
schema={schema}
activeCst={activeCst}
isMutable={isMutable}
setActive={setActiveID}
resetActive={() => setActiveID(null)}
/>

View File

@ -115,7 +115,7 @@ export function ToolbarOssGraph({
onClick={resetView}
/>
<MiniButton
title='Панель содержания КС'
title='Панель содержания'
icon={<IconShowSidebar value={showSidePanel} isBottom={false} size='1.25rem' />}
onClick={toggleShowSidePanel}
/>

View File

@ -98,7 +98,7 @@ export function TableSideConstituents({
noDataComponent={
<NoData className='min-h-20'>
<p>Список конституент пуст</p>
<p>Измените параметры фильтра</p>
<p>Измените параметры фильтра или создайте конституенту</p>
</NoData>
}
onRowClicked={onActivate ? cst => onActivate(cst) : undefined}