39948-vm/frontend/src/lib/tourFlowHelpers.ts

119 lines
3.2 KiB
TypeScript

/**
* Tour Flow Helpers
*
* Utilities for tour page routing and project handling
*/
/**
* Normalize a value to a valid route path
*
* @param value - Raw path value
* @returns Normalized path starting with /
*
* @example
* toRoutePath('my-page') // '/my-page'
* toRoutePath('//double//slash') // '/double/slash'
* toRoutePath('http://example.com/page') // '/page'
* toRoutePath('') // '/'
*/
export function toRoutePath(value?: string): string {
const raw = String(value || '').trim();
if (!raw || raw === '/') return '/';
const normalized = raw
.replace(/^[^/]+:\/\//, '') // Remove protocol
.replace(/^\/*/, '') // Remove leading slashes
.replace(/\/{2,}/g, '/'); // Collapse multiple slashes
const withSlash = `/${normalized}`;
return withSlash.length > 1 ? withSlash.replace(/\/$/, '') : withSlash;
}
/**
* Split a route path into segments
*
* @param value - Route path
* @returns Array of path segments
*
* @example
* routeParts('/my/page/path') // ['my', 'page', 'path']
* routeParts('/') // []
*/
export function routeParts(value?: string): string[] {
return toRoutePath(value).split('/').filter(Boolean);
}
/**
* Compare two route paths for sorting
*
* @param a - First route path
* @param b - Second route path
* @returns Negative if a < b, positive if a > b, 0 if equal
*
* @example
* compareRoutes('/a', '/b') // negative
* compareRoutes('/z', '/a') // positive
* compareRoutes('/page', '/page') // 0
*/
export function compareRoutes(a?: string, b?: string): number {
const aPath = toRoutePath(a);
const bPath = toRoutePath(b);
if (aPath === bPath) return 0;
const aParts = routeParts(aPath);
const bParts = routeParts(bPath);
const maxLen = Math.max(aParts.length, bParts.length);
for (let index = 0; index < maxLen; index += 1) {
const aPart = aParts[index];
const bPart = bParts[index];
if (aPart === undefined) return -1;
if (bPart === undefined) return 1;
const compare = aPart.localeCompare(bPart);
if (compare !== 0) return compare;
}
return aParts.length - bParts.length;
}
/**
* Item that may contain a projectId
*/
interface ProjectIdHolder {
projectId?: string;
project?: { id?: string } | string;
}
/**
* Extract project ID from an item that may have it in different formats
*
* @param item - Object that may contain projectId in various formats
* @returns The project ID string or empty string if not found
*
* @example
* getProjectId({ projectId: '123' }) // '123'
* getProjectId({ project: '456' }) // '456'
* getProjectId({ project: { id: '789' } }) // '789'
* getProjectId({}) // ''
*/
export function getProjectId(item: ProjectIdHolder): string {
if (item.projectId) return item.projectId;
if (typeof item.project === 'string') return item.project;
if (item.project?.id) return item.project.id;
return '';
}
/**
* Get rows from API response with safety check
*
* @param response - API response object
* @returns Array of rows or empty array
*/
export function getRows<T = unknown>(
response: { data?: { rows?: T[] } } | null | undefined,
): T[] {
return Array.isArray(response?.data?.rows) ? response.data.rows : [];
}