From fcbcaad9acbf20cd6996ec0253196d6a1ada5858 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 28 Mar 2026 19:35:39 +0100 Subject: [PATCH 1/2] Allow shows to be searched by tags --- api/src/controllers/shows/logic.ts | 14 ++++++++++++-- front/src/primitives/icons.tsx | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/api/src/controllers/shows/logic.ts b/api/src/controllers/shows/logic.ts index 449b429f..44e2d7ef 100644 --- a/api/src/controllers/shows/logic.ts +++ b/api/src/controllers/shows/logic.ts @@ -1,4 +1,4 @@ -import { and, eq, exists, ne, type SQL, sql } from "drizzle-orm"; +import { and, eq, exists, ne, type SQL, sql, or } from "drizzle-orm"; import { alias } from "drizzle-orm/pg-core"; import { db } from "~/db"; import { @@ -364,7 +364,17 @@ export async function getShows({ .where( and( filter, - query ? sql`${transQ.name} %> ${query}::text` : undefined, + query + ? or( + sql`${transQ.name} %> ${query}::text`, + exists( + db + .select() + .from(sql`unnest(${transQ.tags}) as tag`) + .where(sql`tag %> ${query}::text`), + ), + ) + : undefined, keysetPaginate({ after, sort }), ), ) diff --git a/front/src/primitives/icons.tsx b/front/src/primitives/icons.tsx index f86be613..68a119a7 100644 --- a/front/src/primitives/icons.tsx +++ b/front/src/primitives/icons.tsx @@ -90,6 +90,7 @@ export const IconButton = ({ "outline-0 hover:bg-gray-400/50 focus-visible:bg-gray-400/50", className, )} + disabled={disabled} {...(asProps as AsProps)} > Date: Sat, 28 Mar 2026 19:41:42 +0100 Subject: [PATCH 2/2] Add staff page --- api/src/controllers/shows/logic.ts | 2 +- front/public/translations/en.json | 11 +++- front/src/app/(app)/staff/[slug].tsx | 5 ++ front/src/models/staff.ts | 14 ++++- front/src/ui/details/staff.tsx | 63 +++++++++++++++------- front/src/ui/staff/index.tsx | 81 ++++++++++++++++++++++++++++ 6 files changed, 153 insertions(+), 23 deletions(-) create mode 100644 front/src/app/(app)/staff/[slug].tsx create mode 100644 front/src/ui/staff/index.tsx diff --git a/api/src/controllers/shows/logic.ts b/api/src/controllers/shows/logic.ts index 44e2d7ef..40627702 100644 --- a/api/src/controllers/shows/logic.ts +++ b/api/src/controllers/shows/logic.ts @@ -1,4 +1,4 @@ -import { and, eq, exists, ne, type SQL, sql, or } from "drizzle-orm"; +import { and, eq, exists, ne, or, type SQL, sql } from "drizzle-orm"; import { alias } from "drizzle-orm/pg-core"; import { db } from "~/db"; import { diff --git a/front/public/translations/en.json b/front/public/translations/en.json index bece652d..d9a2ed6d 100644 --- a/front/public/translations/en.json +++ b/front/public/translations/en.json @@ -48,7 +48,16 @@ "version": "Version {{number}}", "part": "Part {{number}}", "videos-map": "Edit video mappings", - "staff-as":"as {{character}}" + "staff-as":"as {{character}}", + "staff-kind": { + "actor": "Actor", + "director": "Director", + "writter": "Writer", + "producer": "Producer", + "music": "Music", + "crew": "Crew", + "other": "Other" + } }, "videos-map": { "none": "NONE", diff --git a/front/src/app/(app)/staff/[slug].tsx b/front/src/app/(app)/staff/[slug].tsx new file mode 100644 index 00000000..404b349a --- /dev/null +++ b/front/src/app/(app)/staff/[slug].tsx @@ -0,0 +1,5 @@ +import { StaffPage } from "~/ui/staff"; + +export { ErrorBoundary } from "~/ui/error-boundary"; + +export default StaffPage; diff --git a/front/src/models/staff.ts b/front/src/models/staff.ts index 22be5dac..e2dc4257 100644 --- a/front/src/models/staff.ts +++ b/front/src/models/staff.ts @@ -1,4 +1,5 @@ import { z } from "zod/v4"; +import { Show } from "./show"; import { KImage } from "./utils/images"; import { Metadata } from "./utils/metadata"; import { zdate } from "./utils/utils"; @@ -22,7 +23,7 @@ export const Staff = z.object({ }); export type Staff = z.infer; -export const Role = z.object({ +const BaseRole = z.object({ kind: z.enum([ "actor", "director", @@ -33,6 +34,17 @@ export const Role = z.object({ "other", ]), character: Character.nullable(), +}); + +export const Role = BaseRole.extend({ staff: Staff, }); export type Role = z.infer; + +export const RoleWithShow = BaseRole.extend({ + show: Show, +}).transform((x) => ({ + ...x, + id: `${x.show.id}-${x.kind}-${x.character?.name ?? "none"}`, +})); +export type RoleWithShow = z.infer; diff --git a/front/src/ui/details/staff.tsx b/front/src/ui/details/staff.tsx index e7109d33..79b0b4c4 100644 --- a/front/src/ui/details/staff.tsx +++ b/front/src/ui/details/staff.tsx @@ -1,43 +1,52 @@ import { useTranslation } from "react-i18next"; import { View } from "react-native"; -import { Role } from "~/models"; -import { Container, H2, P, Poster, Skeleton, SubP } from "~/primitives"; +import { type KImage, Role } from "~/models"; +import { Container, H2, Link, P, Poster, Skeleton, SubP } from "~/primitives"; import { InfiniteGrid, type QueryIdentifier } from "~/query"; import { EmptyView } from "../empty-view"; -const CharacterCard = ({ item }: { item: Role }) => { - const { t } = useTranslation(); +export const CharacterCard = ({ + href, + name, + subtitle, + image, + characterImage, +}: { + href: string; + name: string; + subtitle: string; + image: KImage | null; + characterImage?: KImage | null; +}) => { return ( - - + +

- {item.staff.name} + {name}

- {item.character - ? t("show.staff-as", { - character: item.character.name, - }) - : item.kind} + {subtitle}
- {item.character && ( - + {characterImage && ( + )} -
+ ); }; CharacterCard.Loader = () => ( - - + + - - + ); @@ -66,7 +75,21 @@ export const Staff = ({ )} Empty={} - Render={({ item }) => } + Render={({ item }) => ( + + )} Loader={() => } getItemKey={(item) => `${item.staff.id}-${item.kind}-${item.character?.name ?? "none"}` diff --git a/front/src/ui/staff/index.tsx b/front/src/ui/staff/index.tsx new file mode 100644 index 00000000..ad8be4ad --- /dev/null +++ b/front/src/ui/staff/index.tsx @@ -0,0 +1,81 @@ +import { useTranslation } from "react-i18next"; +import { View } from "react-native"; +import { RoleWithShow, Staff as StaffModel } from "~/models"; +import { Container, H1, Head, Poster, Skeleton, SubP } from "~/primitives"; +import { Fetch, InfiniteFetch, type QueryIdentifier } from "~/query"; +import { useQueryState } from "~/utils"; +import { CharacterCard } from "../details/staff"; +import { EmptyView } from "../empty-view"; + +const StaffHeader = ({ slug }: { slug: string }) => { + return ( + ( + + + + +

{staff.name}

+ {staff.latinName && {staff.latinName}} +
+
+ )} + Loader={() => ( + + + + + + + + )} + /> + ); +}; + +export const StaffPage = () => { + const { t } = useTranslation(); + const [slug] = useQueryState("slug", undefined!); + + return ( + } + Render={({ item }) => ( + + )} + Loader={() => } + Empty={} + /> + ); +}; + +StaffPage.staffQuery = (slug: string): QueryIdentifier => ({ + path: ["api", "staff", slug], + parser: StaffModel, +}); + +StaffPage.rolesQuery = (slug: string): QueryIdentifier => ({ + path: ["api", "staff", slug, "roles"], + parser: RoleWithShow, + infinite: true, +});