@gv-sh/specgen-user
Version:
[](https://github.com/gv-sh/specgen-user)
161 lines (144 loc) • 4.97 kB
JSX
// src/components/stories/StoryLibrary.jsx
import React, { useState, useEffect, useMemo } from 'react';
import { Button } from '../ui/button';
import { Alert, AlertDescription } from '../ui/alert';
import { PlusCircle, AlertTriangle, RefreshCw } from 'lucide-react';
import StoryCard from './StoryCard';
import StoryFilters from './StoryFilters';
import { EmptyLibrary, NoSearchResults } from './EmptyStates';
const StoryLibrary = ({
stories,
onStorySelect,
onCreateNew,
onDeleteStory,
highlightedStoryId,
loading,
error,
onReload // New prop for manual reload
}) => {
// State for filtering and search
const [searchQuery, setSearchQuery] = useState('');
const [yearFilter, setYearFilter] = useState('');
const [sortOrder, setSortOrder] = useState('newest');
const [allYears, setAllYears] = useState([]);
// Handle search filter changes
const handleSearchChange = (e) => {
setSearchQuery(e.target.value);
};
// Handle clearing all filters
const clearFilters = () => {
setSearchQuery('');
setYearFilter('');
};
// Handle retry loading
const handleRetry = () => {
if (onReload && typeof onReload === 'function') {
onReload();
}
};
// Extract years from stories
useEffect(() => {
const years = new Set();
stories?.forEach(story => {
if (story.year) {
years.add(parseInt(story.year, 10));
}
});
setAllYears([...years].sort((a, b) => a - b));
}, [stories]);
// Filtered stories based on search and year filter
const filteredStories = useMemo(() => {
let filtered = [...(stories || [])];
if (yearFilter) {
filtered = filtered.filter(story =>
story.year === parseInt(yearFilter, 10) ||
story.year?.toString() === yearFilter
);
}
if (searchQuery) {
const query = searchQuery.toLowerCase();
filtered = filtered.filter(story =>
story.title?.toLowerCase().includes(query) ||
story.content?.toLowerCase().includes(query)
);
}
if (sortOrder === 'newest') {
filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
} else {
filtered.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
}
return filtered;
}, [stories, yearFilter, searchQuery, sortOrder]);
return (
<div className="container max-w-6xl mx-auto p-6">
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-3xl font-bold tracking-tight">Your Stories</h1>
<p className="text-muted-foreground">Collection of your generated speculative fiction</p>
</div>
<Button
onClick={onCreateNew}
className="flex items-center"
>
<PlusCircle className="h-4 w-4 mr-2" />
Create New Story
</Button>
</div>
{/* Loading indicator for stories */}
{loading && !stories?.length && (
<div className="my-8">
<p className="text-center text-sm text-muted-foreground mb-2">Loading your stories...</p>
<div className="w-full bg-muted rounded-full h-2 mb-4 mx-auto max-w-md">
<div className="bg-primary h-2 rounded-full animate-pulse" style={{ width: '60%' }}></div>
</div>
</div>
)}
{error && (
<Alert className="mb-6" variant="destructive">
<AlertTriangle className="h-4 w-4 mr-2" />
<AlertDescription className="flex items-center justify-between w-full">
<span>{error}</span>
<Button
variant="outline"
size="sm"
onClick={handleRetry}
className="ml-4"
>
<RefreshCw className="h-3.5 w-3.5 mr-1.5" />
Retry
</Button>
</AlertDescription>
</Alert>
)}
{stories && stories.length > 0 && (
<StoryFilters
searchQuery={searchQuery}
yearFilter={yearFilter}
sortOrder={sortOrder}
allYears={allYears}
onSearchChange={handleSearchChange}
onYearFilterChange={setYearFilter}
onSortOrderChange={setSortOrder}
onClearFilters={clearFilters}
/>
)}
{!loading && (!stories || stories.length === 0) ? (
<EmptyLibrary onCreateNew={onCreateNew} />
) : !loading && filteredStories.length === 0 ? (
<NoSearchResults onClearFilters={clearFilters} />
) : !loading && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredStories.map((story) => (
<StoryCard
key={story.id}
story={story}
isHighlighted={highlightedStoryId === story.id}
onClick={() => onStorySelect(story)}
/>
))}
</div>
)}
</div>
);
};
export default StoryLibrary;