M: Fix minor issues
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run

This commit is contained in:
Ivan 2025-02-17 15:12:15 +03:00
parent 869de34fc5
commit 6deedb3afd
17 changed files with 76 additions and 90 deletions

View File

@ -5,13 +5,14 @@ import { useDebounce } from 'use-debounce';
import { Loader } from '@/components/Loader'; import { Loader } from '@/components/Loader';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
// TODO: add animation
export function GlobalLoader() { export function GlobalLoader() {
const navigation = useNavigation(); const navigation = useNavigation();
const isLoading = navigation.state === 'loading'; const isLoading = navigation.state === 'loading';
const [loadingDebounced] = useDebounce(isLoading, PARAMETER.navigationPopupDelay); const [loadingDebounced] = useDebounce(isLoading, PARAMETER.navigationPopupDelay);
if (!loadingDebounced || !isLoading) { if (!loadingDebounced) {
return null; return null;
} }

View File

@ -31,9 +31,9 @@ export function SelectLibraryItem({
label: `${cst.alias}: ${cst.title}` label: `${cst.alias}: ${cst.title}`
})) ?? []; })) ?? [];
function filter(option: { value: number | undefined; label: string }, inputValue: string) { function filter(option: { value: string | undefined; label: string }, query: string) {
const item = items?.find(item => item.id === option.value); const item = items?.find(item => item.id === Number(option.value));
return !item ? false : matchLibraryItem(item, inputValue); return !item ? false : matchLibraryItem(item, query);
} }
return ( return (
@ -42,7 +42,6 @@ export function SelectLibraryItem({
options={options} options={options}
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null} value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null}
onChange={data => onChange(items?.find(cst => cst.id === data?.value))} onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter} filterOption={filter}
placeholder={placeholder} placeholder={placeholder}
{...restProps} {...restProps}

View File

@ -9,13 +9,13 @@ import { IconMoveDown, IconMoveUp, IconRemove } from '@/components/Icons';
import { CProps } from '@/components/props'; import { CProps } from '@/components/props';
import { NoData } from '@/components/View'; import { NoData } from '@/components/View';
import { IOperation, OperationID } from '../models/oss'; import { IOperation } from '../models/oss';
import SelectOperation from './SelectOperation'; import SelectOperation from './SelectOperation';
interface PickMultiOperationProps extends CProps.Styling { interface PickMultiOperationProps extends CProps.Styling {
value: OperationID[]; value: number[];
onChange: (newValue: OperationID[]) => void; onChange: (newValue: number[]) => void;
items: IOperation[]; items: IOperation[];
rows?: number; rows?: number;
} }
@ -27,7 +27,7 @@ export function PickMultiOperation({ rows, items, value, onChange, className, ..
const nonSelectedItems = items.filter(item => !value.includes(item.id)); const nonSelectedItems = items.filter(item => !value.includes(item.id));
const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined); const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined);
function handleDelete(operation: OperationID) { function handleDelete(operation: number) {
onChange(value.filter(item => item !== operation)); onChange(value.filter(item => item !== operation));
} }
@ -39,7 +39,7 @@ export function PickMultiOperation({ rows, items, value, onChange, className, ..
} }
} }
function handleMoveUp(operation: OperationID) { function handleMoveUp(operation: number) {
const index = value.indexOf(operation); const index = value.indexOf(operation);
if (index > 0) { if (index > 0) {
const newSelected = [...value]; const newSelected = [...value];
@ -49,7 +49,7 @@ export function PickMultiOperation({ rows, items, value, onChange, className, ..
} }
} }
function handleMoveDown(operation: OperationID) { function handleMoveDown(operation: number) {
const index = value.indexOf(operation); const index = value.indexOf(operation);
if (index < value.length - 1) { if (index < value.length - 1) {
const newSelected = [...value]; const newSelected = [...value];

View File

@ -5,7 +5,7 @@ import clsx from 'clsx';
import { SelectSingle } from '@/components/Input'; import { SelectSingle } from '@/components/Input';
import { CProps } from '@/components/props'; import { CProps } from '@/components/props';
import { IOperation, OperationID } from '../models/oss'; import { IOperation } from '../models/oss';
import { matchOperation } from '../models/ossAPI'; import { matchOperation } from '../models/ossAPI';
interface SelectOperationProps extends CProps.Styling { interface SelectOperationProps extends CProps.Styling {
@ -31,9 +31,9 @@ function SelectOperation({
label: `${cst.alias}: ${cst.title}` label: `${cst.alias}: ${cst.title}`
})) ?? []; })) ?? [];
function filter(option: { value: OperationID | undefined; label: string }, inputValue: string) { function filter(option: { value: string | undefined; label: string }, query: string) {
const operation = items?.find(item => item.id === option.value); const operation = items?.find(item => item.id === Number(option.value));
return !operation ? false : matchOperation(operation, inputValue); return !operation ? false : matchOperation(operation, query);
} }
return ( return (
@ -42,7 +42,6 @@ function SelectOperation({
options={options} options={options}
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null} value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null}
onChange={data => onChange(items?.find(cst => cst.id === data?.value))} onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter} filterOption={filter}
placeholder={placeholder} placeholder={placeholder}
{...restProps} {...restProps}

View File

@ -14,7 +14,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { IOperationCreateDTO, IOperationPosition, schemaOperationCreate } from '../../backend/types'; import { IOperationCreateDTO, IOperationPosition, schemaOperationCreate } from '../../backend/types';
import { useOperationCreate } from '../../backend/useOperationCreate'; import { useOperationCreate } from '../../backend/useOperationCreate';
import { describeOperationType, labelOperationType } from '../../labels'; import { describeOperationType, labelOperationType } from '../../labels';
import { IOperationSchema, OperationID, OperationType } from '../../models/oss'; import { IOperationSchema, OperationType } from '../../models/oss';
import { calculateInsertPosition } from '../../models/ossAPI'; import { calculateInsertPosition } from '../../models/ossAPI';
import TabInputOperation from './TabInputOperation'; import TabInputOperation from './TabInputOperation';
@ -23,10 +23,10 @@ import TabSynthesisOperation from './TabSynthesisOperation';
export interface DlgCreateOperationProps { export interface DlgCreateOperationProps {
oss: IOperationSchema; oss: IOperationSchema;
positions: IOperationPosition[]; positions: IOperationPosition[];
initialInputs: OperationID[]; initialInputs: number[];
defaultX: number; defaultX: number;
defaultY: number; defaultY: number;
onCreate?: (newID: OperationID) => void; onCreate?: (newID: number) => void;
} }
export enum TabID { export enum TabID {

View File

@ -6,11 +6,6 @@ import { ICstSubstitute } from '@/features/rsform';
import { Graph } from '@/models/Graph'; import { Graph } from '@/models/Graph';
/**
* Represents {@link IOperation} identifier type.
*/
export type OperationID = number;
/** /**
* Represents {@link IOperation} type. * Represents {@link IOperation} type.
*/ */
@ -23,7 +18,7 @@ export enum OperationType {
* Represents Operation. * Represents Operation.
*/ */
export interface IOperation { export interface IOperation {
id: OperationID; id: number;
operation_type: OperationType; operation_type: OperationType;
oss: number; oss: number;
@ -39,22 +34,22 @@ export interface IOperation {
is_owned: boolean; is_owned: boolean;
is_consolidation: boolean; // aka 'diamond synthesis' is_consolidation: boolean; // aka 'diamond synthesis'
substitutions: ICstSubstituteEx[]; substitutions: ICstSubstituteEx[];
arguments: OperationID[]; arguments: number[];
} }
/** /**
* Represents {@link IOperation} Argument. * Represents {@link IOperation} Argument.
*/ */
export interface IArgument { export interface IArgument {
operation: OperationID; operation: number;
argument: OperationID; argument: number;
} }
/** /**
* Represents {@link ICstSubstitute} extended data. * Represents {@link ICstSubstitute} extended data.
*/ */
export interface ICstSubstituteEx extends ICstSubstitute { export interface ICstSubstituteEx extends ICstSubstitute {
operation: OperationID; operation: number;
original_alias: string; original_alias: string;
original_term: string; original_term: string;
substitution_alias: string; substitution_alias: string;
@ -83,7 +78,7 @@ export interface IOperationSchema extends ILibraryItemData {
graph: Graph; graph: Graph;
schemas: number[]; schemas: number[];
stats: IOperationSchemaStats; stats: IOperationSchemaStats;
operationByID: Map<OperationID, IOperation>; operationByID: Map<number, IOperation>;
} }
/** /**

View File

@ -21,7 +21,7 @@ import { Graph } from '../../../models/Graph';
import { IOperationPosition } from '../backend/types'; import { IOperationPosition } from '../backend/types';
import { describeSubstitutionError } from '../labels'; import { describeSubstitutionError } from '../labels';
import { IOperation, IOperationSchema, OperationID, OperationType, SubstitutionErrorType } from './oss'; import { IOperation, IOperationSchema, OperationType, SubstitutionErrorType } from './oss';
import { Position2D } from './ossLayout'; import { Position2D } from './ossLayout';
/** /**
@ -439,8 +439,8 @@ export class SubstitutionValidator {
* Filter relocate candidates from gives schema. * Filter relocate candidates from gives schema.
*/ */
export function getRelocateCandidates( export function getRelocateCandidates(
source: OperationID, source: number,
destination: OperationID, destination: number,
schema: IRSForm, schema: IRSForm,
oss: IOperationSchema oss: IOperationSchema
): IConstituenta[] { ): IConstituenta[] {
@ -482,7 +482,7 @@ export function getRelocateCandidates(
export function calculateInsertPosition( export function calculateInsertPosition(
oss: IOperationSchema, oss: IOperationSchema,
operationType: OperationType, operationType: OperationType,
argumentsOps: OperationID[], argumentsOps: number[],
positions: IOperationPosition[], positions: IOperationPosition[],
defaultPosition: Position2D defaultPosition: Position2D
): Position2D { ): Position2D {

View File

@ -31,7 +31,6 @@ import { useInputCreate } from '../../../backend/useInputCreate';
import { useMutatingOss } from '../../../backend/useMutatingOss'; import { useMutatingOss } from '../../../backend/useMutatingOss';
import { useOperationExecute } from '../../../backend/useOperationExecute'; import { useOperationExecute } from '../../../backend/useOperationExecute';
import { useUpdatePositions } from '../../../backend/useUpdatePositions'; import { useUpdatePositions } from '../../../backend/useUpdatePositions';
import { OperationID } from '../../../models/oss';
import { OssNode } from '../../../models/ossLayout'; import { OssNode } from '../../../models/ossLayout';
import { useOSSGraphStore } from '../../../stores/ossGraph'; import { useOSSGraphStore } from '../../../stores/ossGraph';
import { useOssEdit } from '../OssEditContext'; import { useOssEdit } from '../OssEditContext';
@ -137,7 +136,7 @@ function OssFlow() {
}); });
} }
function handleCreateOperation(inputs: OperationID[]) { function handleCreateOperation(inputs: number[]) {
const positions = getPositions(); const positions = getPositions();
const target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); const target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
controller.promptCreateOperation({ controller.promptCreateOperation({
@ -149,7 +148,7 @@ function OssFlow() {
}); });
} }
function handleDeleteOperation(target: OperationID) { function handleDeleteOperation(target: number) {
if (!controller.canDelete(target)) { if (!controller.canDelete(target)) {
return; return;
} }
@ -163,7 +162,7 @@ function OssFlow() {
handleDeleteOperation(controller.selected[0]); handleDeleteOperation(controller.selected[0]);
} }
function handleInputCreate(target: OperationID) { function handleInputCreate(target: number) {
const operation = controller.schema.operationByID.get(target); const operation = controller.schema.operationByID.get(target);
if (!operation) { if (!operation) {
return; return;
@ -178,15 +177,15 @@ function OssFlow() {
}).then(new_schema => router.push(urls.schema(new_schema.id))); }).then(new_schema => router.push(urls.schema(new_schema.id)));
} }
function handleEditSchema(target: OperationID) { function handleEditSchema(target: number) {
controller.promptEditInput(target, getPositions()); controller.promptEditInput(target, getPositions());
} }
function handleEditOperation(target: OperationID) { function handleEditOperation(target: number) {
controller.promptEditOperation(target, getPositions()); controller.promptEditOperation(target, getPositions());
} }
function handleOperationExecute(target: OperationID) { function handleOperationExecute(target: number) {
void operationExecute({ void operationExecute({
itemID: controller.schema.id, // itemID: controller.schema.id, //
data: { target: target, positions: getPositions() } data: { target: target, positions: getPositions() }
@ -200,7 +199,7 @@ function OssFlow() {
handleOperationExecute(controller.selected[0]); handleOperationExecute(controller.selected[0]);
} }
function handleRelocateConstituents(target: OperationID) { function handleRelocateConstituents(target: number) {
controller.promptRelocateConstituents(target, getPositions()); controller.promptRelocateConstituents(target, getPositions());
} }

View File

@ -14,7 +14,7 @@ import { promptText } from '@/utils/labels';
import { IOperationPosition } from '../../backend/types'; import { IOperationPosition } from '../../backend/types';
import { useOssSuspense } from '../../backend/useOSS'; import { useOssSuspense } from '../../backend/useOSS';
import { IOperationSchema, OperationID, OperationType } from '../../models/oss'; import { IOperationSchema, OperationType } from '../../models/oss';
export enum OssTabID { export enum OssTabID {
CARD = 0, CARD = 0,
@ -24,14 +24,14 @@ export enum OssTabID {
export interface ICreateOperationPrompt { export interface ICreateOperationPrompt {
defaultX: number; defaultX: number;
defaultY: number; defaultY: number;
inputs: OperationID[]; inputs: number[];
positions: IOperationPosition[]; positions: IOperationPosition[];
callback: (newID: OperationID) => void; callback: (newID: number) => void;
} }
export interface IOssEditContext extends ILibraryItemEditor { export interface IOssEditContext extends ILibraryItemEditor {
schema: IOperationSchema; schema: IOperationSchema;
selected: OperationID[]; selected: number[];
isOwned: boolean; isOwned: boolean;
isMutable: boolean; isMutable: boolean;
@ -41,17 +41,17 @@ export interface IOssEditContext extends ILibraryItemEditor {
setShowTooltip: (newValue: boolean) => void; setShowTooltip: (newValue: boolean) => void;
navigateTab: (tab: OssTabID) => void; navigateTab: (tab: OssTabID) => void;
navigateOperationSchema: (target: OperationID) => void; navigateOperationSchema: (target: number) => void;
deleteSchema: () => void; deleteSchema: () => void;
setSelected: React.Dispatch<React.SetStateAction<OperationID[]>>; setSelected: React.Dispatch<React.SetStateAction<number[]>>;
canDelete: (target: OperationID) => boolean; canDelete: (target: number) => boolean;
promptCreateOperation: (props: ICreateOperationPrompt) => void; promptCreateOperation: (props: ICreateOperationPrompt) => void;
promptDeleteOperation: (target: OperationID, positions: IOperationPosition[]) => void; promptDeleteOperation: (target: number, positions: IOperationPosition[]) => void;
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void; promptEditInput: (target: number, positions: IOperationPosition[]) => void;
promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void; promptEditOperation: (target: number, positions: IOperationPosition[]) => void;
promptRelocateConstituents: (target: OperationID | undefined, positions: IOperationPosition[]) => void; promptRelocateConstituents: (target: number | undefined, positions: IOperationPosition[]) => void;
} }
const OssEditContext = createContext<IOssEditContext | null>(null); const OssEditContext = createContext<IOssEditContext | null>(null);
@ -83,7 +83,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
const isMutable = role > UserRole.READER && !schema.read_only; const isMutable = role > UserRole.READER && !schema.read_only;
const [showTooltip, setShowTooltip] = useState(true); const [showTooltip, setShowTooltip] = useState(true);
const [selected, setSelected] = useState<OperationID[]>([]); const [selected, setSelected] = useState<number[]>([]);
const showEditInput = useDialogsStore(state => state.showChangeInputSchema); const showEditInput = useDialogsStore(state => state.showChangeInputSchema);
const showEditOperation = useDialogsStore(state => state.showEditOperation); const showEditOperation = useDialogsStore(state => state.showEditOperation);
@ -112,7 +112,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
router.push(url); router.push(url);
} }
function navigateOperationSchema(target: OperationID) { function navigateOperationSchema(target: number) {
const node = schema.operationByID.get(target); const node = schema.operationByID.get(target);
if (!node?.result) { if (!node?.result) {
return; return;
@ -143,7 +143,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
}); });
} }
function canDelete(target: OperationID) { function canDelete(target: number) {
const operation = schema.operationByID.get(target); const operation = schema.operationByID.get(target);
if (!operation) { if (!operation) {
return false; return false;
@ -154,7 +154,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
return schema.graph.expandOutputs([target]).length === 0; return schema.graph.expandOutputs([target]).length === 0;
} }
function promptEditOperation(target: OperationID, positions: IOperationPosition[]) { function promptEditOperation(target: number, positions: IOperationPosition[]) {
const operation = schema.operationByID.get(target); const operation = schema.operationByID.get(target);
if (!operation) { if (!operation) {
return; return;
@ -166,7 +166,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
}); });
} }
function promptDeleteOperation(target: OperationID, positions: IOperationPosition[]) { function promptDeleteOperation(target: number, positions: IOperationPosition[]) {
const operation = schema.operationByID.get(target); const operation = schema.operationByID.get(target);
if (!operation) { if (!operation) {
return; return;
@ -178,7 +178,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
}); });
} }
function promptEditInput(target: OperationID, positions: IOperationPosition[]) { function promptEditInput(target: number, positions: IOperationPosition[]) {
const operation = schema.operationByID.get(target); const operation = schema.operationByID.get(target);
if (!operation) { if (!operation) {
return; return;
@ -190,7 +190,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
}); });
} }
function promptRelocateConstituents(target: OperationID | undefined, positions: IOperationPosition[]) { function promptRelocateConstituents(target: number | undefined, positions: IOperationPosition[]) {
const operation = target ? schema.operationByID.get(target) : undefined; const operation = target ? schema.operationByID.get(target) : undefined;
showRelocateConstituents({ showRelocateConstituents({
oss: schema, oss: schema,

View File

@ -33,9 +33,9 @@ function SelectConstituenta({
label: `${cst.alias}${cst.is_inherited ? '*' : ''}: ${describeConstituenta(cst)}` label: `${cst.alias}${cst.is_inherited ? '*' : ''}: ${describeConstituenta(cst)}`
})) ?? []; })) ?? [];
function filter(option: { value: number | undefined; label: string }, inputValue: string) { function filter(option: { value: string | undefined; label: string }, query: string) {
const cst = items?.find(item => item.id === option.value); const cst = items?.find(item => item.id === Number(option.value));
return !cst ? false : matchConstituenta(cst, inputValue, CstMatchMode.ALL); return !cst ? false : matchConstituenta(cst, query, CstMatchMode.ALL);
} }
return ( return (
@ -44,7 +44,6 @@ function SelectConstituenta({
options={options} options={options}
value={value ? { value: value.id, label: `${value.alias}: ${describeConstituentaTerm(value)}` } : null} value={value ? { value: value.id, label: `${value.alias}: ${describeConstituentaTerm(value)}` } : null}
onChange={data => onChange(items?.find(cst => cst.id === data?.value))} onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter} filterOption={filter}
placeholder={placeholder} placeholder={placeholder}
{...restProps} {...restProps}

View File

@ -212,6 +212,13 @@ export function isFunctional(type: CstType): boolean {
} }
} }
/**
* Evaluate if {@link IConstituenta} can be used produce structure.
*/
export function canProduceStructure(cst: IConstituenta): boolean {
return !!cst.parse.typification && cst.cst_type !== CstType.BASE && cst.cst_type !== CstType.CONSTANT;
}
/** /**
* Validate new alias against {@link CstType} and {@link IRSForm}. * Validate new alias against {@link CstType} and {@link IRSForm}.
*/ */

View File

@ -46,7 +46,7 @@ import { useMutatingRSForm } from '../../backend/useMutatingRSForm';
import { useProduceStructure } from '../../backend/useProduceStructure'; import { useProduceStructure } from '../../backend/useProduceStructure';
import { useResetAliases } from '../../backend/useResetAliases'; import { useResetAliases } from '../../backend/useResetAliases';
import { useRestoreOrder } from '../../backend/useRestoreOrder'; import { useRestoreOrder } from '../../backend/useRestoreOrder';
import { CstType } from '../../models/rsform'; import { canProduceStructure } from '../../models/rsformAPI';
import { useRSEdit } from './RSEditContext'; import { useRSEdit } from './RSEditContext';
@ -75,12 +75,7 @@ function MenuRSTabs() {
const editMenu = useDropdown(); const editMenu = useDropdown();
const accessMenu = useDropdown(); const accessMenu = useDropdown();
// TODO: move into separate function const structureEnabled = !!controller.activeCst && canProduceStructure(controller.activeCst);
const canProduceStructure =
!!controller.activeCst &&
!!controller.activeCst.parse.typification &&
controller.activeCst.cst_type !== CstType.BASE &&
controller.activeCst.cst_type !== CstType.CONSTANT;
function calculateCloneLocation() { function calculateCloneLocation() {
const location = controller.schema.location; const location = controller.schema.location;
@ -344,7 +339,7 @@ function MenuRSTabs() {
text='Порождение структуры' text='Порождение структуры'
titleHtml='Раскрыть структуру типизации <br/>выделенной конституенты' titleHtml='Раскрыть структуру типизации <br/>выделенной конституенты'
icon={<IconGenerateStructure size='1rem' className='icon-primary' />} icon={<IconGenerateStructure size='1rem' className='icon-primary' />}
disabled={!controller.isContentEditable || !canProduceStructure || isProcessing} disabled={!controller.isContentEditable || !structureEnabled || isProcessing}
onClick={handleProduceStructure} onClick={handleProduceStructure}
/> />
<DropdownButton <DropdownButton

View File

@ -3,10 +3,9 @@ import clsx from 'clsx';
import { CProps } from '@/components/props'; import { CProps } from '@/components/props';
import { useLabelUser } from '../backend/useLabelUser'; import { useLabelUser } from '../backend/useLabelUser';
import { UserID } from '../models/user';
interface InfoUsersProps extends CProps.Styling { interface InfoUsersProps extends CProps.Styling {
items: UserID[]; items: number[];
prefix: string; prefix: string;
header?: string; header?: string;
} }

View File

@ -7,13 +7,12 @@ import { CProps } from '@/components/props';
import { useLabelUser } from '../backend/useLabelUser'; import { useLabelUser } from '../backend/useLabelUser';
import { useUsers } from '../backend/useUsers'; import { useUsers } from '../backend/useUsers';
import { UserID } from '../models/user';
import { matchUser } from '../models/userAPI'; import { matchUser } from '../models/userAPI';
interface SelectUserProps extends CProps.Styling { interface SelectUserProps extends CProps.Styling {
value?: UserID; value?: number;
onChange: (newValue: UserID) => void; onChange: (newValue: number) => void;
filter?: (userID: UserID) => boolean; filter?: (userID: number) => boolean;
placeholder?: string; placeholder?: string;
noBorder?: boolean; noBorder?: boolean;
@ -36,9 +35,9 @@ export function SelectUser({
label: getUserLabel(user.id) label: getUserLabel(user.id)
})); }));
function filterLabel(option: { value: UserID | undefined; label: string }, inputValue: string) { function filterLabel(option: { value: string | undefined; label: string }, query: string) {
const user = items.find(item => item.id === option.value); const user = items.find(item => item.id === Number(option.value));
return !user ? false : matchUser(user, inputValue); return !user ? false : matchUser(user, query);
} }
return ( return (
@ -49,7 +48,6 @@ export function SelectUser({
onChange={data => { onChange={data => {
if (data?.value !== undefined) onChange(data.value); if (data?.value !== undefined) onChange(data.value);
}} }}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filterLabel} filterOption={filterLabel}
placeholder={placeholder} placeholder={placeholder}
{...restProps} {...restProps}

View File

@ -2,17 +2,12 @@
* Module: Models for Users. * Module: Models for Users.
*/ */
/**
* Represents {@link User} identifier type.
*/
export type UserID = number;
/** /**
* Represents user detailed information. * Represents user detailed information.
* Some information should only be accessible to authorized users * Some information should only be accessible to authorized users
*/ */
export interface IUser { export interface IUser {
id: UserID; id: number;
username: string; username: string;
is_staff: boolean; is_staff: boolean;
email: string; email: string;

View File

@ -12,7 +12,7 @@ export const PARAMETER = {
minimalTimeout: 10, // milliseconds delay for fast updates minimalTimeout: 10, // milliseconds delay for fast updates
zoomDuration: 500, // milliseconds animation duration zoomDuration: 500, // milliseconds animation duration
navigationDuration: 300, // milliseconds navigation duration navigationDuration: 300, // milliseconds navigation duration
navigationPopupDelay: 200, // milliseconds delay for navigation popup navigationPopupDelay: 300, // milliseconds delay for navigation popup
ossImageWidth: 1280, // pixels - size of OSS image ossImageWidth: 1280, // pixels - size of OSS image
ossImageHeight: 960, // pixels - size of OSS image ossImageHeight: 960, // pixels - size of OSS image

View File

@ -6,8 +6,8 @@ test('should load the homepage and display login button', async ({ page }) => {
await authAnonymous(page); await authAnonymous(page);
await page.goto('/'); await page.goto('/');
await page.waitForSelector('.h-full > .mr-1');
await expect(page).toHaveTitle('Концепт Портал'); await expect(page).toHaveTitle('Концепт Портал');
await expect(page.getByRole('heading', { name: 'Портал' })).toBeVisible();
await page.click('.h-full > .mr-1'); await page.click('.h-full > .mr-1');
await expect(page.getByText('Логин или email')).toBeVisible(); await expect(page.getByText('Логин или email')).toBeVisible();