generated from muhtadeetaron/nextjs-template
281 lines
8.0 KiB
TypeScript
281 lines
8.0 KiB
TypeScript
import {
|
|
Pagination,
|
|
PaginationContent,
|
|
PaginationItem,
|
|
PaginationLink,
|
|
PaginationNext,
|
|
PaginationPrevious,
|
|
} from "@/components/ui/pagination";
|
|
import { useDateContext } from "@/context/DateContext";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
|
|
import {
|
|
autoPlaceArticles,
|
|
createArticlesFromFilenames,
|
|
isSameDay,
|
|
} from "../../utils/helpers";
|
|
import { usePageContext } from "@/context/PageContext";
|
|
|
|
// Type Definitions
|
|
interface ArticleImage {
|
|
page: number;
|
|
date: string;
|
|
articles: string[];
|
|
}
|
|
|
|
interface Origin {
|
|
x: number;
|
|
y: number;
|
|
}
|
|
|
|
const NewspaperViewer = () => {
|
|
const { currentPage, setCurrentPage } = usePageContext();
|
|
const [selectedArticle, setSelectedArticle] = useState<string | null>(null);
|
|
const [images, setImages] = useState<ArticleImage[]>([]);
|
|
const [colCount, setColCount] = useState(8);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const { date } = useDateContext();
|
|
const [origin, setOrigin] = useState<Origin>({ x: 0, y: 0 });
|
|
|
|
// Update column count based on page
|
|
useEffect(() => {
|
|
setColCount(currentPage === 1 ? 6 : 8);
|
|
}, [currentPage]);
|
|
|
|
// Simulated async fetch (replace with actual fetch if needed)
|
|
useEffect(() => {
|
|
const fetchImages = async () => {
|
|
try {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
// Simulate delay
|
|
await new Promise((res) => setTimeout(res, 500));
|
|
|
|
setImages([
|
|
{
|
|
page: 1,
|
|
date: "Mon May 13 2025 22:00:46 GMT+0600 (Bangladesh Standard Time)",
|
|
articles: [
|
|
"1_r2_c6.jpg",
|
|
"1_r3_c3.jpg",
|
|
"1_r3_c1.jpg",
|
|
"1_r3_c2.jpg",
|
|
"1_r4_c2.jpg",
|
|
"(1_r4_c1).jpg",
|
|
"(1_r4_c2).jpg",
|
|
"1_r2_c1.jpg",
|
|
"1_r2_c1_g6.jpg",
|
|
],
|
|
},
|
|
{
|
|
page: 2,
|
|
date: "Mon May 13 2025 22:00:00 GMT+0600 (Bangladesh Standard Time)",
|
|
articles: [
|
|
"2_r1_c8.jpg",
|
|
"2_r2_c2_g1).jpg",
|
|
"2_r1_c2_g3.jpg",
|
|
"2_r1_c4_g5.jpg",
|
|
"(2_r1_c4_g5.jpg",
|
|
"2_r1_c1_g8).jpg",
|
|
"2_r1_c1_g8.jpg",
|
|
"2_r1_c2_g3).jpg",
|
|
"2_r2_c2_g3.jpg",
|
|
"2_r2_c3_g5.jpg",
|
|
"2_r3_c3_g3.jpg",
|
|
"(2_r3_c3)_g5.jpg",
|
|
"(2_r1_c2)_g1.jpg",
|
|
"((2_r1_c2)_g1.jpg",
|
|
"2_r2_c2_g1.jpg",
|
|
],
|
|
},
|
|
]);
|
|
} catch (err) {
|
|
setError("Failed to load newspaper data.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchImages();
|
|
}, []);
|
|
|
|
const zoomIn = (articleUrl: string, e: React.MouseEvent) => {
|
|
const { clientX, clientY } = e;
|
|
setOrigin({ x: clientX, y: clientY });
|
|
setSelectedArticle(articleUrl);
|
|
};
|
|
|
|
const zoomOut = () => {
|
|
setSelectedArticle(null);
|
|
};
|
|
|
|
const dateFilteredContent = useMemo(
|
|
() => images.filter((item) => isSameDay(item.date, date)),
|
|
[images, date]
|
|
);
|
|
|
|
const hasContentForDate = dateFilteredContent.length > 0;
|
|
|
|
const dateArticles = useMemo(
|
|
() =>
|
|
hasContentForDate
|
|
? dateFilteredContent.flatMap((pageGroup) =>
|
|
createArticlesFromFilenames(pageGroup.articles, pageGroup.page)
|
|
)
|
|
: [],
|
|
[dateFilteredContent, hasContentForDate]
|
|
);
|
|
|
|
const currentPageArticles = dateArticles.filter(
|
|
(a) => a.page === currentPage
|
|
);
|
|
const placedArticles = useMemo(
|
|
() => autoPlaceArticles(currentPageArticles, colCount),
|
|
[currentPageArticles, colCount]
|
|
);
|
|
|
|
const totalPages = hasContentForDate
|
|
? Math.max(...dateFilteredContent.map((item) => item.page))
|
|
: 0;
|
|
|
|
return (
|
|
<div className="w-3/4 mx-auto">
|
|
{isLoading ? (
|
|
<div className="flex justify-center items-center h-64">Loading...</div>
|
|
) : error ? (
|
|
<div className="text-red-600 text-center h-64 flex items-center justify-center">
|
|
{error}
|
|
</div>
|
|
) : hasContentForDate ? (
|
|
<>
|
|
{/* Pagination */}
|
|
<section className="my-4">
|
|
<Pagination>
|
|
<PaginationContent>
|
|
<PaginationItem>
|
|
<PaginationPrevious
|
|
href="#"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
setCurrentPage((prev: number) => Math.max(1, prev - 1));
|
|
}}
|
|
/>
|
|
</PaginationItem>
|
|
|
|
{Array.from({ length: totalPages }, (_, i) => i + 1).map(
|
|
(n) => (
|
|
<PaginationItem key={n}>
|
|
<PaginationLink
|
|
href="#"
|
|
isActive={n === currentPage}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
setCurrentPage(n);
|
|
}}
|
|
>
|
|
{n}
|
|
</PaginationLink>
|
|
</PaginationItem>
|
|
)
|
|
)}
|
|
|
|
<PaginationItem>
|
|
<PaginationNext
|
|
href="#"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
setCurrentPage((prev) => Math.min(totalPages, prev + 1));
|
|
}}
|
|
/>
|
|
</PaginationItem>
|
|
</PaginationContent>
|
|
</Pagination>
|
|
</section>
|
|
|
|
{/* Articles */}
|
|
<section
|
|
className="gap-1"
|
|
style={{
|
|
display: "grid",
|
|
gridTemplateColumns: `repeat(${colCount}, 1fr)`,
|
|
}}
|
|
>
|
|
{placedArticles.map((article) => (
|
|
<div
|
|
key={article.id}
|
|
style={{
|
|
gridColumn: `${article.gridColumnStart} / span ${article.colSpan}`,
|
|
gridRow: `${article.gridRowStart} / span ${article.rowSpan}`,
|
|
overflow: "hidden",
|
|
}}
|
|
className="hover:opacity-60 hover:cursor-pointer transition-opacity"
|
|
>
|
|
<img
|
|
onClick={(e) => zoomIn(`/${article.image}`, e)}
|
|
src={`/${article.image}`}
|
|
alt={`Article ${article.id}`}
|
|
style={{
|
|
width: "100%",
|
|
height: "100%",
|
|
objectFit: "cover",
|
|
display: "block",
|
|
}}
|
|
/>
|
|
</div>
|
|
))}
|
|
</section>
|
|
</>
|
|
) : (
|
|
<div className="flex flex-col items-center justify-center h-64 text-center">
|
|
<h2 className="text-2xl font-semibold mb-2">
|
|
No newspaper available
|
|
</h2>
|
|
<p className="text-gray-600">
|
|
There is no newspaper edition for{" "}
|
|
{date?.toLocaleDateString("en-US", {
|
|
weekday: "long",
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
})}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Zoomed View */}
|
|
{selectedArticle && (
|
|
<div
|
|
className="fixed inset-0 bg-black/70 flex justify-center items-center z-50"
|
|
onClick={zoomOut}
|
|
>
|
|
<div
|
|
className="relative bg-white rounded-lg overflow-hidden shadow-lg"
|
|
style={{
|
|
animation: "zoomIn 0.3s ease-out",
|
|
transformOrigin: `${origin.x}px ${origin.y}px`,
|
|
}}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<img
|
|
src={selectedArticle}
|
|
alt="Zoomed Article"
|
|
className="rounded max-h-[90vh] max-w-[90vw]"
|
|
/>
|
|
<button
|
|
onClick={zoomOut}
|
|
className="absolute top-2 right-2 text-black text-2xl"
|
|
aria-label="Close"
|
|
>
|
|
×
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default NewspaperViewer;
|