5 лаба

This commit is contained in:
2025-05-24 01:08:07 +04:00
parent f47194f1fc
commit 6988cf7ef6
49 changed files with 6632 additions and 5767 deletions

View File

@@ -0,0 +1,29 @@
import React from "react";
import StatusLabel from "./StatusLabel";
function BookCard({ book, authorName, statusName, onEdit, onDelete }) {
return (
<div className="card mb-3 w-100" style={{ maxWidth: "450px", margin: "0 auto" }}>
<img
src={book.cover || "https://placehold.co/200x300"}
className="card-img-top shadow mt-2 mb-3"
alt="Обложка книги"
style={{ maxHeight: "300px", objectFit: "contain" }}
/>
<div className="card-body">
<h5 className="card-title">{book.title}</h5>
<p className="card-text">{book.description}</p>
<p className="card-text"><small>Автор: {authorName}</small></p>
<p className="card-text"><StatusLabel statusName={statusName} /></p>
<button className="btn btn-warning me-2" onClick={() => onEdit(book)}>
Редактировать
</button>
<button className="btn btn-danger" onClick={() => onDelete(book.id)}>
Удалить
</button>
</div>
</div>
);
}
export default BookCard;

View File

@@ -0,0 +1,121 @@
import React, { useEffect, useState } from "react";
const defaultForm = {
title: "",
description: "",
authorId: "",
statusId: "",
cover: ""
};
function BookForm({ authors, statuses, onSubmit, editingBook, onCancel }) {
const [form, setForm] = useState(defaultForm);
useEffect(() => {
if (editingBook) setForm(editingBook);
else setForm(defaultForm);
}, [editingBook]);
const handleChange = (e) => {
setForm({ ...form, [e.target.name]: e.target.value });
};
const handleFileChange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setForm((prev) => ({ ...prev, cover: reader.result }));
};
reader.readAsDataURL(file);
}
};
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(form);
setForm(defaultForm);
if (onCancel) onCancel();
};
const handleCancel = (e) => {
e.preventDefault();
setForm(defaultForm);
if (onCancel) onCancel();
};
return (
<form onSubmit={handleSubmit} className="bg-white p-4 rounded mb-4 shadow-sm">
<div className="row g-3">
<div className="col-md-6">
<input
className="form-control"
name="title"
placeholder="Название книги"
value={form.title}
onChange={handleChange}
required
/>
</div>
<div className="col-md-6">
<select
className="form-select"
name="authorId"
value={form.authorId}
onChange={handleChange}
required
>
<option value="">Выберите автора</option>
{authors.map(a => (
<option key={a.id} value={a.id}>{a.name}</option>
))}
</select>
</div>
<div className="col-md-12">
<textarea
className="form-control"
name="description"
placeholder="Описание"
value={form.description}
onChange={handleChange}
rows={3}
required
/>
</div>
<div className="col-md-6">
<select
className="form-select"
name="statusId"
value={form.statusId}
onChange={handleChange}
required
>
<option value="">Выберите статус</option>
{statuses.map(s => (
<option key={s.id} value={s.id}>{s.name}</option>
))}
</select>
</div>
<div className="col-md-6">
<input
className="form-control"
type="file"
name="coverFile"
accept="image/*"
onChange={handleFileChange}
/>
</div>
</div>
<div className="mt-3 d-flex gap-2">
<button className="btn btn-success" type="submit">
{editingBook ? "Сохранить изменения" : "Добавить книгу"}
</button>
<button className="btn btn-secondary" type="button" onClick={handleCancel}>
Отмена
</button>
</div>
</form>
);
}
export default BookForm;

View File

@@ -0,0 +1,28 @@
import React from "react";
import BookCard from "./BookCard";
function BookList({ books, authors, statuses, onEdit, onDelete }) {
const getAuthorName = (id) => authors.find(a => String(a.id) === String(id))?.name || "Неизвестен";
const getStatusName = (id) => statuses.find(s => String(s.id) === String(id))?.name || "Нет статуса";
return (
<div className="row justify-content-center ">
{books.map(book => (
<div
className="col-12 col-sm-10 col-md-6 col-lg-4 d-flex align-items-stretch mb-4"
key={book.id}
>
<BookCard
book={book}
authorName={getAuthorName(book.authorId)}
statusName={getStatusName(book.statusId)}
onEdit={onEdit}
onDelete={onDelete}
/>
</div>
))}
</div>
);
}
export default BookList;

View File

@@ -0,0 +1,24 @@
import React from "react";
export default function Footer() {
return (
<footer className="bg-custom-dark text-light text-center py-4 mt-5">
<p>
Спасибо, что посетили наш сайт, если возникли вопросы, обращайтесь к нам на почту{" "}
<a href="mailto:manga@manga.scom" className="text-warning">
manga@manga.scom
</a>
</p>
<p>Если вас интересуют наши соц.сети, то вот они:</p>
<div className="d-flex justify-content-center">
<a href="https://vk.com/ded_moroz1509" className="me-3">
<img src="/img/VK0.png" alt="VK" height="30" />
</a>
{/* Добавь другие соцсети по аналогии: */}
{/* <a href="https://t.me/yourchannel" className="me-3">
<img src="/img/telegram.png" alt="Telegram" height="30" />
</a> */}
</div>
</footer>
);
}

View File

@@ -0,0 +1,58 @@
import React from "react";
import { Link } from "react-router-dom";
export default function Navbar() {
return (
<nav className="navbar navbar-expand-lg navbar-dark bg-custom-dark px-3">
<Link className="navbar-brand" to="/">
<img src="/img/manga.png" alt="ЛОГО" height="50" />
</Link>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNavDropdown"
aria-controls="navbarNavDropdown"
aria-expanded="false"
aria-label="Переключить навигацию"
>
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse ms-3" id="navbarNavDropdown">
<ul className="navbar-nav me-auto">
<li className="nav-item dropdown">
<a className="nav-link dropdown-toggle" href="#" id="catalogDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Каталог
</a>
<ul className="dropdown-menu dropdown-menu-dark" aria-labelledby="catalogDropdown">
<li>
<a className="dropdown-item" href="#">
Жанр 1
</a>
</li>
<li>
<a className="dropdown-item" href="#">
Жанр 2
</a>
</li>
<li>
<a className="dropdown-item" href="#">
Жанр 3
</a>
</li>
<li>
<a className="dropdown-item" href="#">
Жанр 4
</a>
</li>
</ul>
</li>
</ul>
<Link to="/account" className="btn btn-outline-warning">
Вход
</Link>
</div>
</nav>
);
}

View File

@@ -0,0 +1,13 @@
function StatusLabel({ statusName }) {
let className = "fw-bold ";
switch ((statusName || "").toLowerCase()) {
case "вышла": className += "text-success"; break;
case "не вышла": className += "text-danger"; break;
case "не переведена": className += "text-warning"; break;
case "прочитана": className += "text-primary"; break;
default: className += "text-muted";
}
return <span className={className}>Статус: {statusName || "Неизвестен"}</span>
}
export default StatusLabel;