F: Add hide button for stats

This commit is contained in:
Ivan 2025-06-24 22:13:26 +03:00
parent dae47716ea
commit 2d2c0290e3
16 changed files with 162 additions and 56 deletions

View File

@ -25,6 +25,10 @@ export { LuQrCode as IconQR } from 'react-icons/lu';
export { LuFilterX as IconFilterReset } from 'react-icons/lu';
export {BiDownArrowCircle as IconOpenList } from 'react-icons/bi';
export { LuTriangleAlert as IconAlert } from 'react-icons/lu';
export { LuPanelLeftOpen as IconLeftOpen } from 'react-icons/lu';
export { LuPanelLeftClose as IconLeftClose } from 'react-icons/lu';
export { LuPanelBottomOpen as IconBottomOpen } from 'react-icons/lu';
export { LuPanelBottomClose as IconBottomClose } from 'react-icons/lu';
// ===== UI elements =======
export { BiX as IconClose } from 'react-icons/bi';
@ -97,9 +101,7 @@ export { LuDatabase as IconDatabase } from 'react-icons/lu';
export { LuView as IconDBStructure } from 'react-icons/lu';
export { LuPlaneTakeoff as IconRESTapi } from 'react-icons/lu';
export { LuImage as IconImage } from 'react-icons/lu';
export { TbColumns as IconList } from 'react-icons/tb';
export { GoVersions as IconVersions } from 'react-icons/go';
export { TbColumnsOff as IconListOff } from 'react-icons/tb';
export { LuAtSign as IconTerm } from 'react-icons/lu';
export { LuSubscript as IconAlias } from 'react-icons/lu';
export { TbMathFunction as IconFormula } from 'react-icons/tb';

View File

@ -1,9 +1,8 @@
import {
IconClone,
IconDestroy,
IconDownload,
IconEditor,
IconImmutable,
IconLeftOpen,
IconOSS,
IconOwner,
IconPublic,
@ -49,15 +48,12 @@ export function HelpRSCard() {
<li>
<IconImmutable className='inline-icon' /> Неизменные схемы
</li>
<li>
<IconClone className='inline-icon icon-green' /> Клонировать создать копию схемы
</li>
<li>
<IconDownload className='inline-icon' /> Загрузить/Выгрузить взаимодействие с Экстеор
</li>
<li>
<IconDestroy className='inline-icon icon-red' /> Удалить полностью удаляет схему из базы Портала
</li>
<li>
<IconLeftOpen className='inline-icon' /> Отображение статистики
</li>
</ul>
</div>
);

View File

@ -5,7 +5,7 @@ import {
IconEdit,
IconFilter,
IconKeyboard,
IconList,
IconLeftOpen,
IconMoveDown,
IconMoveUp,
IconNewItem,
@ -37,7 +37,7 @@ export function HelpRSEditor() {
<IconPredecessor className='inline-icon' /> переход к исходной
</li>
<li>
<IconList className='inline-icon' /> список конституент
<IconLeftOpen className='inline-icon' /> список конституент
</li>
<li>
<IconSave className='inline-icon' /> сохранить: <kbd>Ctrl + S</kbd>

View File

@ -0,0 +1,23 @@
import { type DomIconProps, IconBottomClose, IconBottomOpen, IconLeftClose, IconLeftOpen } from '@/components/icons';
/** Icon for sidebar visibility. */
export function IconShowSidebar({
value,
size = '1.25rem',
className,
isBottom
}: DomIconProps<boolean> & { isBottom: boolean }) {
if (isBottom) {
if (value) {
return <IconBottomClose size={size} className={className ?? 'icon-primary'} />;
} else {
return <IconBottomOpen size={size} className={className ?? 'icon-primary'} />;
}
} else {
if (value) {
return <IconLeftClose size={size} className={className ?? 'icon-primary'} />;
} else {
return <IconLeftOpen size={size} className={className ?? 'icon-primary'} />;
}
}
}

View File

@ -10,29 +10,46 @@ import { MiniButton } from '@/components/control';
import { IconDestroy, IconSave, IconShare } from '@/components/icons';
import { cn } from '@/components/utils';
import { useModificationStore } from '@/stores/modification';
import { usePreferencesStore } from '@/stores/preferences';
import { tooltipText } from '@/utils/labels';
import { prepareTooltip, sharePage } from '@/utils/utils';
import { AccessPolicy, type ILibraryItem, LibraryItemType } from '../backend/types';
import { useMutatingLibrary } from '../backend/use-mutating-library';
import { IconShowSidebar } from './icon-show-sidebar';
import { MiniSelectorOSS } from './mini-selector-oss';
interface ToolbarItemCardProps {
className?: string;
isNarrow: boolean;
onSubmit: () => void;
isMutable: boolean;
schema: ILibraryItem;
deleteSchema: () => void;
}
export function ToolbarItemCard({ className, schema, onSubmit, isMutable, deleteSchema }: ToolbarItemCardProps) {
export function ToolbarItemCard({
className,
isNarrow,
schema,
onSubmit,
isMutable,
deleteSchema
}: ToolbarItemCardProps) {
const role = useRoleStore(state => state.role);
const router = useConceptNavigation();
const { isModified } = useModificationStore();
const isProcessing = useMutatingLibrary();
const canSave = isModified && !isProcessing;
const showRSFormStats = usePreferencesStore(state => state.showRSFormStats);
const toggleShowRSFormStats = usePreferencesStore(state => state.toggleShowRSFormStats);
const showOSSStats = usePreferencesStore(state => state.showOSSStats);
const toggleShowOSSStats = usePreferencesStore(state => state.toggleShowOSSStats);
const isRSForm = schema.item_type === LibraryItemType.RSFORM;
const isOSS = schema.item_type === LibraryItemType.OSS;
const ossSelector = (() => {
if (schema.item_type !== LibraryItemType.RSFORM) {
return null;
@ -76,6 +93,15 @@ export function ToolbarItemCard({ className, schema, onSubmit, isMutable, delete
disabled={!isMutable || isProcessing || role < UserRole.OWNER}
/>
) : null}
{(isRSForm || isOSS) && (
<MiniButton
title='Отображение статистики'
icon={
<IconShowSidebar value={isRSForm ? showRSFormStats : showOSSStats} isBottom={isNarrow} size='1.25rem' />
}
onClick={isRSForm ? toggleShowRSFormStats : toggleShowOSSStats}
/>
)}
<BadgeHelp topic={HelpTopic.UI_RS_CARD} offset={4} />
</div>
);

View File

@ -4,7 +4,9 @@ import clsx from 'clsx';
import { EditorLibraryItem, ToolbarItemCard } from '@/features/library/components';
import { useWindowSize } from '@/hooks/use-window-size';
import { useModificationStore } from '@/stores/modification';
import { usePreferencesStore } from '@/stores/preferences';
import { globalIDs } from '@/utils/constants';
import { useOssEdit } from '../oss-edit-context';
@ -12,9 +14,14 @@ import { useOssEdit } from '../oss-edit-context';
import { FormOSS } from './form-oss';
import { OssStats } from './oss-stats';
const SIDELIST_LAYOUT_THRESHOLD = 768; // px
export function EditorOssCard() {
const { schema, isMutable, deleteSchema } = useOssEdit();
const { isModified } = useModificationStore();
const showOSSStats = usePreferencesStore(state => state.showOSSStats);
const windowSize = useWindowSize();
const isNarrow = !!windowSize.width && windowSize.width <= SIDELIST_LAYOUT_THRESHOLD;
function initiateSubmit() {
const element = document.getElementById(globalIDs.library_item_editor) as HTMLFormElement;
@ -33,25 +40,29 @@ export function EditorOssCard() {
}
return (
<>
<div
onKeyDown={handleInput}
className={clsx(
'relative md:w-fit md:max-w-fit max-w-128',
'flex px-6 pt-8',
isNarrow && 'flex-col md:items-center'
)}
>
<ToolbarItemCard
className='cc-tab-tools'
onSubmit={initiateSubmit}
schema={schema}
isMutable={isMutable}
deleteSchema={deleteSchema}
isNarrow={isNarrow}
/>
<div
onKeyDown={handleInput}
className={clsx('md:max-w-fit max-w-128 min-w-fit', 'flex flex-row flex-wrap pt-8 px-6 justify-center')}
>
<div className='cc-column px-3'>
<FormOSS key={schema.id} />
<EditorLibraryItem schema={schema} isAttachedToOSS={false} />
</div>
<OssStats className='mt-3 md:mt-8 md:ml-5 w-80 md:w-56 mx-auto h-min' stats={schema.stats} />
<div className='cc-column px-3 mx-0 md:mx-auto'>
<FormOSS key={schema.id} />
<EditorLibraryItem schema={schema} isAttachedToOSS={false} />
</div>
</>
<OssStats className='w-80 md:w-56 mt-3 md:mt-8 md:ml-5 mx-auto' stats={schema.stats} isMounted={showOSSStats} />
</div>
);
}

View File

@ -13,12 +13,20 @@ import { type IOperationSchemaStats } from '../../../models/oss';
interface OssStatsProps {
className?: string;
isMounted: boolean;
stats: IOperationSchemaStats;
}
export function OssStats({ className, stats }: OssStatsProps) {
export function OssStats({ className, isMounted, stats }: OssStatsProps) {
return (
<div className={cn('grid grid-cols-4 gap-1 justify-items-end', className)}>
<aside
className={cn(
'grid grid-cols-4 gap-1 justify-items-end h-min',
'cc-animate-sidebar',
isMounted ? 'max-w-full' : 'opacity-0 max-w-0',
className
)}
>
<div id='count_operations' className='w-fit flex gap-3 hover:cursor-default '>
<span>Всего</span>
<span>{stats.count_all}</span>
@ -55,6 +63,6 @@ export function OssStats({ className, stats }: OssStatsProps) {
icon={<IconRSFormImported size='1.25rem' />}
value={stats.count_schemas - stats.count_owned}
/>
</div>
</aside>
);
}

View File

@ -96,6 +96,7 @@ export function EditorConstituenta() {
onSubmit={initiateSubmit}
onReset={() => setToggleReset(prev => !prev)}
disabled={disabled}
isNarrow={isNarrow}
/>
<div className='mx-0 md:mx-auto pt-8 md:w-195 shrink-0 xs:pt-0'>
@ -112,7 +113,7 @@ export function EditorConstituenta() {
) : null}
</div>
<ViewConstituents
className={isNarrow ? 'mt-3 mx-6 overflow-hidden' : 'mt-9 h-fit overflow-visible'}
className={isNarrow ? 'mt-3 mx-6 overflow-hidden' : 'mt-9 overflow-visible'}
isMounted={showList}
isBottom={isNarrow}
/>

View File

@ -1,19 +1,16 @@
'use client';
import clsx from 'clsx';
import { urls, useConceptNavigation } from '@/app';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components';
import { MiniSelectorOSS } from '@/features/library/components';
import { IconShowSidebar } from '@/features/library/components/icon-show-sidebar';
import { useFindPredecessor } from '@/features/oss/backend/use-find-predecessor';
import { MiniButton } from '@/components/control';
import {
IconClone,
IconDestroy,
IconList,
IconListOff,
IconMoveDown,
IconMoveUp,
IconNewItem,
@ -21,6 +18,7 @@ import {
IconReset,
IconSave
} from '@/components/icons';
import { cn } from '@/components/utils';
import { useModificationStore } from '@/stores/modification';
import { usePreferencesStore } from '@/stores/preferences';
import { tooltipText } from '@/utils/labels';
@ -34,6 +32,7 @@ interface ToolbarConstituentaProps {
className?: string;
activeCst: IConstituenta | null;
disabled: boolean;
isNarrow: boolean;
onSubmit: () => void;
onReset: () => void;
@ -43,6 +42,7 @@ export function ToolbarConstituenta({
className,
activeCst,
disabled,
isNarrow,
onSubmit,
onReset
@ -80,7 +80,7 @@ export function ToolbarConstituenta({
}
return (
<div className={clsx('px-1 rounded-b-2xl backdrop-blur-xs cc-icons cc-animate-position outline-hidden', className)}>
<div className={cn('px-1 rounded-b-2xl backdrop-blur-xs cc-icons outline-hidden', className)}>
{schema.oss.length > 0 ? (
<MiniSelectorOSS
items={schema.oss}
@ -152,13 +152,7 @@ export function ToolbarConstituenta({
<MiniButton
title='Отображение списка конституент'
icon={
showList ? (
<IconList size='1.25rem' className='icon-primary' />
) : (
<IconListOff size='1.25rem' className='icon-primary' />
)
}
icon={<IconShowSidebar size='1.25rem' value={showList} isBottom={isNarrow} />}
onClick={toggleList}
/>
<BadgeHelp topic={HelpTopic.UI_RS_EDITOR} offset={4} contentClass='sm:max-w-160' />

View File

@ -1,8 +1,12 @@
'use client';
import clsx from 'clsx';
import { EditorLibraryItem, ToolbarItemCard } from '@/features/library/components';
import { useWindowSize } from '@/hooks/use-window-size';
import { useModificationStore } from '@/stores/modification';
import { usePreferencesStore } from '@/stores/preferences';
import { globalIDs } from '@/utils/constants';
import { useRSEdit } from '../rsedit-context';
@ -10,9 +14,14 @@ import { useRSEdit } from '../rsedit-context';
import { FormRSForm } from './form-rsform';
import { RSFormStats } from './rsform-stats';
const SIDELIST_LAYOUT_THRESHOLD = 768; // px
export function EditorRSFormCard() {
const { schema, isArchive, isMutable, deleteSchema, isAttachedToOSS } = useRSEdit();
const { isModified } = useModificationStore();
const showRSFormStats = usePreferencesStore(state => state.showRSFormStats);
const windowSize = useWindowSize();
const isNarrow = !!windowSize.width && windowSize.width <= SIDELIST_LAYOUT_THRESHOLD;
function initiateSubmit() {
const element = document.getElementById(globalIDs.library_item_editor) as HTMLFormElement;
@ -31,24 +40,33 @@ export function EditorRSFormCard() {
}
return (
<div onKeyDown={handleInput} className='relative md:w-fit md:max-w-fit max-w-128 flex flex-row flex-wrap px-6 pt-8'>
<div
onKeyDown={handleInput}
className={clsx(
'relative md:w-fit md:max-w-fit max-w-128',
'flex px-6 pt-8',
isNarrow && 'flex-col md:items-center'
)}
>
<ToolbarItemCard
className='cc-tab-tools'
onSubmit={initiateSubmit}
schema={schema}
isMutable={isMutable}
deleteSchema={deleteSchema}
isNarrow={isNarrow}
/>
<div className='cc-column shrink'>
<div className='cc-column mx-0 md:mx-auto'>
<FormRSForm key={schema.id} />
<EditorLibraryItem schema={schema} isAttachedToOSS={isAttachedToOSS} />
</div>
<RSFormStats
className='mt-3 md:mt-8 md:ml-5 w-80 md:w-56 mx-auto h-min'
className='w-80 md:w-56 mt-3 md:mt-8 md:ml-5 mx-auto'
stats={schema.stats}
isArchive={isArchive}
isMounted={showRSFormStats}
/>
</div>
);

View File

@ -1,5 +1,3 @@
import clsx from 'clsx';
import {
IconChild,
IconConvention,
@ -19,6 +17,7 @@ import {
IconStatusProperty,
IconTerminology
} from '@/components/icons';
import { cn } from '@/components/utils';
import { ValueStats } from '@/components/view';
import { type IRSFormStats } from '../../../models/rsform';
@ -26,13 +25,22 @@ import { type IRSFormStats } from '../../../models/rsform';
interface RSFormStatsProps {
className?: string;
isArchive: boolean;
isMounted: boolean;
stats: IRSFormStats;
}
export function RSFormStats({ className, stats, isArchive }: RSFormStatsProps) {
export function RSFormStats({ className, stats, isArchive, isMounted }: RSFormStatsProps) {
return (
<div className={clsx('grid grid-cols-4 gap-1 justify-items-end', className)}>
<div id='count_all' className='col-span-2 w-fit flex gap-3 hover:cursor-default '>
<aside
className={cn(
'cc-animate-sidebar',
'h-min',
'grid grid-cols-4 gap-1 justify-items-end ',
isMounted ? 'max-w-full' : 'opacity-0 max-w-0',
className
)}
>
<div id='count_all' className='col-span-2 w-fit flex gap-3 hover:cursor-default'>
<span>Всего</span>
<span>{stats.count_all}</span>
</div>
@ -140,6 +148,6 @@ export function RSFormStats({ className, stats, isArchive }: RSFormStatsProps) {
}
value={stats.count_convention}
/>
</div>
</aside>
);
}

View File

@ -1,4 +1,4 @@
import clsx from 'clsx';
'use client';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components';
@ -15,6 +15,7 @@ import {
IconOpenList,
IconReset
} from '@/components/icons';
import { cn } from '@/components/utils';
import { prefixes } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
@ -46,7 +47,7 @@ export function ToolbarRSList({ className }: ToolbarRSListProps) {
} = useRSEdit();
return (
<div className={clsx('cc-icons items-start outline-hidden', className)}>
<div className={cn('cc-icons items-start outline-hidden', className)}>
{schema.oss.length > 0 ? (
<MiniSelectorOSS
items={schema.oss}

View File

@ -1,9 +1,8 @@
'use client';
import clsx from 'clsx';
import { useRoleStore, UserRole } from '@/features/users';
import { cn } from '@/components/utils';
import { useWindowSize } from '@/hooks/use-window-size';
import { useFitHeight } from '@/stores/app-layout';
@ -26,11 +25,11 @@ export function ViewConstituents({ className, isBottom, isMounted }: ViewConstit
return (
<aside
className={clsx(
className={cn(
'cc-animate-sidebar',
'border',
isBottom ? 'rounded-md' : 'rounded-l-md rounded-r-none',
isBottom ? 'rounded-md' : 'rounded-l-md rounded-r-none h-fit',
isMounted ? 'max-w-full' : 'opacity-0 max-w-0',
'ease-in-out duration-1000 transition-[opacity,max-width]',
className
)}
>

View File

@ -91,6 +91,7 @@
--duration-modal: 300ms;
--duration-fade: 300ms;
--duration-move: 500ms;
--duration-transform: 1000ms;
--duration-cycle: 1000ms;
/* Custom animations */

View File

@ -20,6 +20,12 @@ interface PreferencesStore {
showCstSideList: boolean;
toggleShowCstSideList: () => void;
showRSFormStats: boolean;
toggleShowRSFormStats: () => void;
showOSSStats: boolean;
toggleShowOSSStats: () => void;
showExpressionControls: boolean;
toggleShowExpressionControls: () => void;
}
@ -63,6 +69,12 @@ export const usePreferencesStore = create<PreferencesStore>()(
showCstSideList: true,
toggleShowCstSideList: () => set(state => ({ showCstSideList: !state.showCstSideList })),
showRSFormStats: true,
toggleShowRSFormStats: () => set(state => ({ showRSFormStats: !state.showRSFormStats })),
showOSSStats: true,
toggleShowOSSStats: () => set(state => ({ showOSSStats: !state.showOSSStats })),
showExpressionControls: true,
toggleShowExpressionControls: () => set(state => ({ showExpressionControls: !state.showExpressionControls }))
}),

View File

@ -142,6 +142,12 @@
}
}
@utility cc-animate-sidebar {
transition-property: max-width, opacity;
transition-timing-function: var(--ease-in-out);
transition-duration: var(--duration-transform);
}
@utility cc-animate-position {
transition-property: transform top left bottom right margin padding;
transition-timing-function: var(--ease-bezier);