tercul-frontend/client/src/components/authors/utils.ts
2025-11-30 15:21:07 +01:00

250 lines
5.9 KiB
TypeScript

/**
* Author component utilities
* Provides utility functions for author components
*/
import type { Author } from "@shared/schema";
import type { AuthorDisplayUtils, AuthorWithStats } from "./types";
/**
* Utility functions for author display and formatting
*/
export const authorUtils: AuthorDisplayUtils = {
/**
* Get formatted lifespan display
*/
getLifespan: (author: Author): string => {
if (!author.birthYear) return "";
return `${author.birthYear}${author.deathYear ? ` - ${author.deathYear}` : " - present"}`;
},
/**
* Get author initials for avatar fallback
*/
getInitials: (name: string): string => {
return name
.split(" ")
.map((part) => part.charAt(0))
.join("")
.toUpperCase()
.slice(0, 2);
},
/**
* Format numbers for display
*/
formatNumber: (num: number | undefined, format: 'numbers' | 'abbreviated' | 'full' = 'numbers'): string => {
if (num === undefined) return '';
if (format === 'abbreviated') {
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`;
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`;
return num.toString();
}
if (format === 'numbers') {
return num.toLocaleString();
}
return num.toString();
},
/**
* Format rating for display
*/
formatRating: (rating: number | undefined): string => {
if (rating === undefined) return '';
return rating.toFixed(1);
},
/**
* Get display location from country/city
*/
getLocation: (author: Author): string | undefined => {
// TODO: This would need to be enhanced to resolve country/city IDs to names
// For now, using the legacy country field if available
return author.country;
},
/**
* Format bio with truncation
*/
formatBio: (
bio: string | undefined,
maxLength: number,
expanded: boolean = false
): { text: string; isTruncated: boolean } => {
if (!bio) return { text: "", isTruncated: false };
const isTruncated = bio.length > maxLength && !expanded;
const text = isTruncated ? `${bio.substring(0, maxLength)}...` : bio;
return { text, isTruncated };
},
};
/**
* Convert shared schema Author to AuthorWithStats for UI components
*/
export function enrichAuthorForUI(
author: Author,
stats?: {
worksCount?: number;
followersCount?: number;
isFollowed?: boolean;
isFeatured?: boolean;
genres?: string[];
influences?: string[];
stats?: {
views?: number;
likes?: number;
comments?: number;
};
}
): AuthorWithStats {
return {
...author,
worksCount: stats?.worksCount,
followersCount: stats?.followersCount,
isFollowed: stats?.isFollowed || false,
isFeatured: stats?.isFeatured || false,
genres: stats?.genres,
influences: stats?.influences,
location: authorUtils.getLocation(author),
nationality: author.country, // TODO: Resolve from countryId
era: undefined, // TODO: Compute from birth/death years and literary movements
stats: stats?.stats,
};
}
/**
* Generate author URL from slug
*/
export function getAuthorUrl(author: Author): string {
return `/authors/${author.slug}`;
}
/**
* Check if author has complete profile information
*/
export function hasCompleteProfile(author: Author): boolean {
return !!(
author.name &&
author.biography &&
author.birthYear &&
author.portrait
);
}
/**
* Calculate author age or lifespan
*/
export function getAuthorAge(author: Author): number | null {
if (!author.birthYear) return null;
const endYear = author.deathYear || new Date().getFullYear();
return endYear - author.birthYear;
}
/**
* Sort authors by different criteria
*/
export function sortAuthors(
authors: Author[],
sortBy: "name" | "birthYear" | "alphabetical" = "name",
order: "asc" | "desc" = "asc"
): Author[] {
const sorted = [...authors].sort((a, b) => {
let comparison = 0;
switch (sortBy) {
case "name":
case "alphabetical":
comparison = a.name.localeCompare(b.name);
break;
case "birthYear": {
const aYear = a.birthYear || 0;
const bYear = b.birthYear || 0;
comparison = aYear - bYear;
break;
}
default:
comparison = 0;
}
return order === "desc" ? -comparison : comparison;
});
return sorted;
}
/**
* Filter authors by criteria
*/
export function filterAuthors(
authors: Author[],
filters: {
country?: string;
birthYearStart?: number;
birthYearEnd?: number;
hasPortrait?: boolean;
hasBiography?: boolean;
}
): Author[] {
return authors.filter((author) => {
// Filter by country
if (filters.country && author.country !== filters.country) {
return false;
}
// Filter by birth year range
if (
filters.birthYearStart &&
(!author.birthYear || author.birthYear < filters.birthYearStart)
) {
return false;
}
if (
filters.birthYearEnd &&
(!author.birthYear || author.birthYear > filters.birthYearEnd)
) {
return false;
}
// Filter by portrait availability
if (filters.hasPortrait && !author.portrait) {
return false;
}
// Filter by biography availability
if (filters.hasBiography && !author.biography) {
return false;
}
return true;
});
}
/**
* Search authors by query
*/
export function searchAuthors(authors: Author[], query: string): Author[] {
if (!query.trim()) return authors;
const searchTerm = query.toLowerCase().trim();
return authors.filter((author) => {
return (
author.name.toLowerCase().includes(searchTerm) ||
author.biography?.toLowerCase().includes(searchTerm) ||
author.country?.toLowerCase().includes(searchTerm)
);
});
}
/**
* Format lifespan from birth and death years
*/
export function formatLifespan(birthYear?: number, deathYear?: number): string {
if (!birthYear) return "";
return `${birthYear}${deathYear ? ` - ${deathYear}` : " - present"}`;
}