Archived
177 lines
4.8 KiB
TypeScript
177 lines
4.8 KiB
TypeScript
import { useTranslation } from "react-i18n-lite";
|
|
import { readingStatusKey } from "@/locales";
|
|
import styles from "../../pages/Library.module.css";
|
|
|
|
const LIBRARY_STATUS_COLORS: Record<string, { bg: string; text: string }> = {
|
|
want_to_read: { bg: "#dbeafe", text: "#1d4ed8" },
|
|
reading: { bg: "#dcfce7", text: "#16a34a" },
|
|
finished: { bg: "#f3e8ff", text: "#9333ea" },
|
|
dnf: { bg: "#fef3c7", text: "#b45309" },
|
|
};
|
|
|
|
interface LibraryBookCardData {
|
|
id: number;
|
|
title: string;
|
|
author: string;
|
|
genre: string;
|
|
format: string;
|
|
cover_image: string | null;
|
|
reading_status: string;
|
|
progressPercent: number | null;
|
|
}
|
|
|
|
function badgeLabel(
|
|
book: LibraryBookCardData,
|
|
t: (key: string, params?: Record<string, string>) => string,
|
|
): string {
|
|
if (book.reading_status === "reading" && book.progressPercent != null && book.progressPercent > 0) {
|
|
return t("library.readingStatus.readingWithProgress", {
|
|
percent: String(book.progressPercent),
|
|
});
|
|
}
|
|
return t(readingStatusKey(book.reading_status));
|
|
}
|
|
|
|
interface LibraryBookCardProps {
|
|
book: LibraryBookCardData;
|
|
isMobile: boolean;
|
|
onOpen: () => void;
|
|
onContextMenu: (e: React.MouseEvent) => void;
|
|
}
|
|
|
|
export function LibraryBookCard({
|
|
book,
|
|
isMobile,
|
|
onOpen,
|
|
onContextMenu,
|
|
}: LibraryBookCardProps) {
|
|
const { t } = useTranslation();
|
|
const sc = LIBRARY_STATUS_COLORS[book.reading_status] ?? { bg: "#f3f4f6", text: "#6b7280" };
|
|
const showProgressBar =
|
|
book.reading_status === "reading" &&
|
|
book.progressPercent != null &&
|
|
book.progressPercent > 0;
|
|
|
|
return (
|
|
<div
|
|
onClick={onOpen}
|
|
onContextMenu={onContextMenu}
|
|
className={styles.bookCard}
|
|
style={{ display: isMobile ? "flex" : "block" }}
|
|
>
|
|
<div
|
|
style={{
|
|
width: isMobile ? 80 : "100%",
|
|
height: isMobile ? 120 : 180,
|
|
background: "#f0f0f0",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
position: "relative",
|
|
flexShrink: 0,
|
|
}}
|
|
>
|
|
{book.cover_image ? (
|
|
<img
|
|
src={book.cover_image}
|
|
alt={book.title}
|
|
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
/>
|
|
) : (
|
|
<span style={{ fontSize: isMobile ? 32 : 48 }}>📖</span>
|
|
)}
|
|
{showProgressBar && (
|
|
<div className={styles.coverProgressBar}>
|
|
<div
|
|
className={styles.coverProgressFill}
|
|
style={{ width: `${book.progressPercent}%` }}
|
|
/>
|
|
</div>
|
|
)}
|
|
{!isMobile && (
|
|
<span
|
|
style={{
|
|
position: "absolute",
|
|
top: 8,
|
|
right: 8,
|
|
background: sc.bg,
|
|
color: sc.text,
|
|
fontSize: 11,
|
|
fontWeight: 600,
|
|
padding: "2px 8px",
|
|
borderRadius: 999,
|
|
lineHeight: "18px",
|
|
maxWidth: "calc(100% - 16px)",
|
|
overflow: "hidden",
|
|
textOverflow: "ellipsis",
|
|
whiteSpace: "nowrap",
|
|
}}
|
|
>
|
|
{badgeLabel(book, t)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div style={{ padding: isMobile ? "8px 12px" : 12, flex: 1 }}>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "flex-start",
|
|
justifyContent: "space-between",
|
|
gap: 8,
|
|
}}
|
|
>
|
|
<div style={{ minWidth: 0, flex: 1 }}>
|
|
<h3
|
|
style={{
|
|
fontSize: 14,
|
|
fontWeight: 600,
|
|
color: "#1f2937",
|
|
margin: 0,
|
|
overflow: "hidden",
|
|
textOverflow: "ellipsis",
|
|
whiteSpace: "nowrap",
|
|
}}
|
|
>
|
|
{book.title}
|
|
</h3>
|
|
<p style={{ fontSize: 12, color: "#6b7280", margin: "2px 0" }}>
|
|
{book.author || t("common.unknownAuthor")}
|
|
</p>
|
|
</div>
|
|
{isMobile && (
|
|
<span
|
|
style={{
|
|
background: sc.bg,
|
|
color: sc.text,
|
|
fontSize: 10,
|
|
fontWeight: 600,
|
|
padding: "2px 6px",
|
|
borderRadius: 999,
|
|
whiteSpace: "nowrap",
|
|
flexShrink: 0,
|
|
}}
|
|
>
|
|
{badgeLabel(book, t)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
{book.genre && (
|
|
<span
|
|
style={{
|
|
fontSize: 11,
|
|
color: "#4f46e5",
|
|
background: "#eef2ff",
|
|
padding: "1px 6px",
|
|
borderRadius: 4,
|
|
display: "inline-block",
|
|
marginTop: 4,
|
|
}}
|
|
>
|
|
{book.genre}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|