Add routing, cast API conversion, and filters
Introduce client-side routing and cast API support. Key changes: - Add react-router-dom dependency and wire BrowserRouter in App. - Convert App to route-based structure (/, /media/:id, /cast, /cast/:id, /add, /import) with MediaDetailRoute and CastDetailRoute helpers. - Extend API types for cast items and add convertApiCastToStaff; fetchAllCast now returns Staff[] (mapped via converter). - Update components to use react-router hooks (useNavigate, useParams, useLocation, Link/NavLink): Header links, DetailView, CastDetailView, AddMediaView, ImporterView and others now navigate via routes. - Enhance CastView: fetch cast list, loading state, persistent search/sort/filter controls, filtering by occupations/media types and enabled categories, improved pagination and UI controls. - Update stashapp importer: add configurable blacklist check to skip scenes, increase per_page for queries. These changes consolidate navigation, improve cast data handling from the API, and add richer filtering and importer controls.
This commit is contained in:
71
src/api.ts
71
src/api.ts
@@ -111,6 +111,7 @@ export interface CreateStaffInput {
|
||||
export interface ApiCastItem {
|
||||
id: number;
|
||||
name: string;
|
||||
cleanname?: string;
|
||||
photo: string | null;
|
||||
bio: string | null;
|
||||
birthDate: string | null;
|
||||
@@ -119,6 +120,33 @@ export interface ApiCastItem {
|
||||
updatedAt: string;
|
||||
occupations?: string[];
|
||||
filmography?: ApiCastMediaItem[];
|
||||
media_types?: string[];
|
||||
bust_size?: number | null;
|
||||
cup_size?: string | null;
|
||||
waist_size?: number | null;
|
||||
hip_size?: number | null;
|
||||
height?: number | null;
|
||||
weight?: number | null;
|
||||
hair_color?: string | null;
|
||||
eye_color?: string | null;
|
||||
ethnicity?: string | null;
|
||||
adult_specifics?: {
|
||||
id: number;
|
||||
cast_id: number;
|
||||
bust_size?: number | null;
|
||||
cup_size?: string | null;
|
||||
waist_size?: number | null;
|
||||
hip_size?: number | null;
|
||||
height?: number | null;
|
||||
weight?: number | null;
|
||||
hair_color?: string | null;
|
||||
eye_color?: string | null;
|
||||
ethnicity?: string | null;
|
||||
tattoos?: string | null;
|
||||
piercings?: string | null;
|
||||
measurements?: string | null;
|
||||
shoe_size?: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ApiCastMediaItem {
|
||||
@@ -129,7 +157,7 @@ export interface ApiCastMediaItem {
|
||||
category: string | null;
|
||||
type: string;
|
||||
role: string;
|
||||
characterName: string | null;
|
||||
characterName?: string | null;
|
||||
}
|
||||
|
||||
export interface CreateCastInput {
|
||||
@@ -144,6 +172,43 @@ export interface CreateCastInput {
|
||||
export interface UpdateCastInput extends Partial<CreateCastInput> {}
|
||||
|
||||
|
||||
export function convertApiCastToStaff(apiItem: ApiCastItem): Staff {
|
||||
return {
|
||||
id: apiItem.id.toString(),
|
||||
name: apiItem.name,
|
||||
cleanname: apiItem.cleanname,
|
||||
role: apiItem.occupations?.[0] || 'Actor',
|
||||
photo: normalizeUrl(apiItem.photo) || `https://picsum.photos/seed/cast-${apiItem.id}/200/200`,
|
||||
bio: apiItem.bio || undefined,
|
||||
birthDate: apiItem.birthDate || undefined,
|
||||
birthPlace: apiItem.birthPlace || undefined,
|
||||
occupations: apiItem.occupations || ['Actor'],
|
||||
createdAt: apiItem.createdAt,
|
||||
updatedAt: apiItem.updatedAt,
|
||||
bust_size: apiItem.bust_size,
|
||||
cup_size: apiItem.cup_size,
|
||||
waist_size: apiItem.waist_size,
|
||||
hip_size: apiItem.hip_size,
|
||||
height: apiItem.height,
|
||||
weight: apiItem.weight,
|
||||
hair_color: apiItem.hair_color,
|
||||
eye_color: apiItem.eye_color,
|
||||
ethnicity: apiItem.ethnicity,
|
||||
filmography: apiItem.filmography?.map(item => ({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
year: item.year,
|
||||
poster: normalizeUrl(item.poster) || `https://picsum.photos/seed/${item.id}/400/600`,
|
||||
category: item.category,
|
||||
type: item.type,
|
||||
role: item.role,
|
||||
characterName: item.characterName
|
||||
})),
|
||||
media_types: apiItem.media_types,
|
||||
adult_specifics: apiItem.adult_specifics
|
||||
};
|
||||
}
|
||||
|
||||
export function convertApiToMedia(apiItem: ApiMediaItem): Media {
|
||||
// Convert staff from API to Media staff format
|
||||
const staff: Staff[] = (apiItem.staff || []).map((staffMember) => ({
|
||||
@@ -360,7 +425,7 @@ export async function deleteMedia(id: number | string): Promise<boolean> {
|
||||
}
|
||||
|
||||
// Cast API Functions
|
||||
export async function fetchAllCast(page: number = 1, limit: number = 100000): Promise<ApiCastItem[]> {
|
||||
export async function fetchAllCast(page: number = 1, limit: number = 100000): Promise<Staff[]> {
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/api/cast?page=${page}&limit=${limit}`);
|
||||
if (!response.ok) {
|
||||
@@ -369,7 +434,7 @@ export async function fetchAllCast(page: number = 1, limit: number = 100000): Pr
|
||||
const data: ApiResponse<PaginatedResponse<ApiCastItem>> = await response.json();
|
||||
|
||||
if (data.success && data.data.items) {
|
||||
return data.data.items;
|
||||
return data.data.items.map(convertApiCastToStaff);
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user