Files
ERitors-Scribe-Desktop/components/book/settings/characters/CharacterList.tsx

124 lines
6.3 KiB
TypeScript

import {characterCategories, CharacterProps} from "@/lib/models/Character";
import InputField from "@/components/form/InputField";
import TextInput from "@/components/form/TextInput";
import {faChevronRight, faPlus, faUser} from "@fortawesome/free-solid-svg-icons";
import {SelectBoxProps} from "@/shared/interface";
import CollapsableArea from "@/components/CollapsableArea";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {useState} from "react";
import {useTranslations} from "next-intl";
interface CharacterListProps {
characters: CharacterProps[];
handleCharacterClick: (character: CharacterProps) => void;
handleAddCharacter: () => void;
}
export default function CharacterList(
{
characters,
handleCharacterClick,
handleAddCharacter,
}: CharacterListProps) {
const t = useTranslations();
const [searchQuery, setSearchQuery] = useState<string>('');
function getFilteredCharacters(
characters: CharacterProps[],
searchQuery: string,
): CharacterProps[] {
return characters.filter(
(char: CharacterProps) =>
char.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
(char.lastName &&
char.lastName.toLowerCase().includes(searchQuery.toLowerCase())),
);
}
const filteredCharacters: CharacterProps[] = getFilteredCharacters(
characters,
searchQuery,
);
return (
<div className="space-y-4">
<div className="px-4 mb-4">
<InputField
input={
<TextInput
value={searchQuery}
setValue={(e) => setSearchQuery(e.target.value)}
placeholder={t("characterList.search")}
/>
}
actionIcon={faPlus}
actionLabel={t("characterList.add")}
addButtonCallBack={async () => handleAddCharacter()}
/>
</div>
<div className="overflow-y-auto max-h-[calc(100vh-350px)] px-2">
{characterCategories.map((category: SelectBoxProps) => {
const categoryCharacters = filteredCharacters.filter(
(char: CharacterProps) => char.category === category.value
);
if (categoryCharacters.length === 0) {
return null;
}
return (
<CollapsableArea
key={category.value}
title={category.label}
icon={faUser}
children={<div className="space-y-2 p-2">
{categoryCharacters.map(char => (
<div
key={char.id}
onClick={() => handleCharacterClick(char)}
className="group flex items-center p-4 bg-secondary/30 rounded-xl border-l-4 border-primary border border-secondary/50 cursor-pointer hover:bg-secondary hover:shadow-md hover:scale-102 transition-all duration-200 hover:border-primary/50"
>
<div
className="w-14 h-14 rounded-full border-2 border-primary overflow-hidden bg-secondary shadow-md group-hover:scale-110 transition-transform">
{char.image ? (
<img
src={char.image}
alt={char.name}
className="w-full h-full object-cover"
/>
) : (
<div
className="w-full h-full flex items-center justify-center bg-primary/10 text-primary font-bold text-lg">
{char.name?.charAt(0)?.toUpperCase() || '?'}
</div>
)}
</div>
<div className="ml-4 flex-1">
<div
className="text-text-primary font-bold text-base group-hover:text-primary transition-colors">{char.name || t("characterList.unknown")}</div>
<div
className="text-text-secondary text-sm mt-0.5">{char.lastName || t("characterList.noLastName")}</div>
</div>
<div className="w-28 px-3">
<div
className="text-primary text-sm font-semibold truncate">{char.title || t("characterList.noTitle")}</div>
<div
className="text-muted text-xs truncate mt-0.5">{char.role || t("characterList.noRole")}</div>
</div>
<div className="w-8 flex justify-center">
<FontAwesomeIcon icon={faChevronRight}
className="text-muted group-hover:text-primary group-hover:translate-x-1 transition-all w-4 h-4"/>
</div>
</div>
))}
</div>}
/>
);
})}
</div>
</div>
)
}