119 lines
3.2 KiB
TypeScript
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 : [];
|
|
}
|