- Deleted redundant components (`AddActionButton`, `AlertBox`, `AlertStack`, `BackButton`, `CancelButton`, and `CollapsableArea`) and related files. - Removed unused models (`Book`, `BookSerie`, `BookTables`, `Character`, and `Chapter`) to reduce codebase clutter. - Updated project structure and references to reflect these removals.
137 lines
6.4 KiB
TypeScript
137 lines
6.4 KiB
TypeScript
'use client';
|
|
import React, {useState} from 'react';
|
|
import {SpellListItem, SpellTagProps} from '@/lib/types/spell';
|
|
import InputField from '@/components/form/InputField';
|
|
import TextInput from '@/components/form/TextInput';
|
|
import SpellTagChip from '@/components/book/settings/spells/SpellTagChip';
|
|
import {Plus, Settings, Wand2} from 'lucide-react';
|
|
import EntityListItem from '@/components/ui/EntityListItem';
|
|
import AvatarIcon from '@/components/ui/AvatarIcon';
|
|
import {useTranslations} from '@/lib/i18n';
|
|
import EmptyState from '@/components/ui/EmptyState';
|
|
import Button from '@/components/ui/Button';
|
|
import Badge from '@/components/ui/Badge';
|
|
|
|
interface SpellSettingsListProps {
|
|
spells: SpellListItem[];
|
|
tags: SpellTagProps[];
|
|
onSpellClick: (spell: SpellListItem) => void;
|
|
onAddSpell: () => void;
|
|
onManageTags: () => void;
|
|
}
|
|
|
|
/**
|
|
* SpellSettingsList - Liste des sorts pour BookSetting/SerieSetting
|
|
* Inclut recherche, filtre par tag, et gestion des tags
|
|
* PAS de scroll interne (géré par parent)
|
|
*/
|
|
export default function SpellSettingsList({
|
|
spells,
|
|
tags,
|
|
onSpellClick,
|
|
onAddSpell,
|
|
onManageTags,
|
|
}: SpellSettingsListProps): React.JSX.Element {
|
|
const t = useTranslations();
|
|
const [searchQuery, setSearchQuery] = useState<string>('');
|
|
const [filterTag, setFilterTag] = useState<string>('all');
|
|
|
|
function getFilteredSpells(): SpellListItem[] {
|
|
return spells.filter(function (spell: SpellListItem): boolean {
|
|
const matchesSearch: boolean = spell.name.toLowerCase().includes(searchQuery.toLowerCase());
|
|
const matchesTag: boolean = filterTag === 'all' || spell.tags.some(function (tag: SpellTagProps): boolean {
|
|
return tag.id === filterTag;
|
|
});
|
|
return matchesSearch && matchesTag;
|
|
});
|
|
}
|
|
|
|
const filteredSpells: SpellListItem[] = getFilteredSpells();
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="px-4 space-y-3">
|
|
<InputField
|
|
input={
|
|
<TextInput
|
|
value={searchQuery}
|
|
setValue={function (e: React.ChangeEvent<HTMLInputElement>): void {
|
|
setSearchQuery(e.target.value);
|
|
}}
|
|
placeholder={t('spellList.search')}
|
|
/>
|
|
}
|
|
actionIcon={Plus}
|
|
actionLabel={t('spellList.add')}
|
|
addButtonCallBack={async function (): Promise<void> {
|
|
onAddSpell();
|
|
}}
|
|
/>
|
|
|
|
<div className="flex flex-wrap gap-3 items-center">
|
|
<div className="flex-1 min-w-[150px]">
|
|
<select
|
|
value={filterTag}
|
|
onChange={function (e: React.ChangeEvent<HTMLSelectElement>): void {
|
|
setFilterTag(e.target.value);
|
|
}}
|
|
className="input-base cursor-pointer"
|
|
>
|
|
<option value="all" className="bg-tertiary text-text-primary">
|
|
{t('spellList.allTags')}
|
|
</option>
|
|
{tags.map(function (tag: SpellTagProps): React.JSX.Element {
|
|
return (
|
|
<option key={tag.id} value={tag.id} className="bg-tertiary text-text-primary">
|
|
{tag.name}
|
|
</option>
|
|
);
|
|
})}
|
|
</select>
|
|
</div>
|
|
<Button variant="secondary" size="sm" icon={Settings} onClick={onManageTags}>
|
|
{t('spellList.manageTags')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="px-2">
|
|
{filteredSpells.length === 0 ? (
|
|
<EmptyState icon={Wand2} title={t('spellList.noSpells')}
|
|
description={t('spellList.noSpellsDescription')}/>
|
|
) : (
|
|
<div className="space-y-2 p-2">
|
|
{filteredSpells.map(function (spell: SpellListItem): React.JSX.Element {
|
|
return (
|
|
<EntityListItem
|
|
key={spell.id}
|
|
onClick={function (): void {
|
|
onSpellClick(spell);
|
|
}}
|
|
avatar={<AvatarIcon icon={Wand2}/>}
|
|
title={spell.name}
|
|
subtitle={spell.description}
|
|
extra={
|
|
spell.tags.length > 0 ? (
|
|
<div className="flex flex-wrap gap-1.5">
|
|
{spell.tags.slice(0, 3).map(function (tag: SpellTagProps): React.JSX.Element {
|
|
return <SpellTagChip key={tag.id} tag={tag} size="sm"/>;
|
|
})}
|
|
{spell.tags.length > 3 && (
|
|
<Badge variant="muted" size="sm">
|
|
+{spell.tags.length - 3}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
) : undefined
|
|
}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|