mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 04:51:34 +00:00
250 lines
5.9 KiB
TypeScript
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"}`;
|
|
}
|