массовое удаление

This commit is contained in:
2025-05-26 00:24:15 +04:00
parent 3293c8b1b9
commit e9d0a1d549
7 changed files with 143 additions and 5731 deletions

File diff suppressed because one or more lines are too long

View File

@@ -48,6 +48,20 @@ function App() {
});
};
// Массовое удаление
const deleteSelectedBooks = (ids) => {
Promise.all(
ids.map((id) =>
fetch(`http://localhost:5174/books/${id}`, { method: "DELETE" })
)
).then(() => {
fetch("http://localhost:5174/books")
.then((res) => res.json())
.then((data) => setBooks(data));
});
};
// Обновить книгу
const updateBook = (book) => {
fetch(`http://localhost:5174/books/${book.id}`, {
@@ -90,6 +104,7 @@ function App() {
setSearchQuery={setSearchQuery}
statusFilter={statusFilter}
setStatusFilter={setStatusFilter}
onDeleteSelected={deleteSelectedBooks}
/>
}
/>

View File

@@ -2,26 +2,63 @@ import React from "react";
import StatusLabel from "./StatusLabel";
import { Link } from "react-router-dom";
function BookCard({ book, authorName, statusName, onEdit, onDelete }) {
function BookCard({
book,
authorName,
statusName,
onEdit,
onDelete,
selected,
onSelect
}) {
return (
<div className="card card-hover mb-3 w-100" style={{ maxWidth: "450px", margin: "0 auto" }}>
<Link to={`/manga/${book.id}`}>
<div
className="card card-hover mb-3 w-100"
style={{ maxWidth: "450px", margin: "0 auto", position: "relative" }}
>
{/* Чекбокс для выделения */}
<input
type="checkbox"
checked={selected}
onChange={e => onSelect(book.id, e.target.checked)}
style={{
position: "absolute",
top: 10,
left: 10,
zIndex: 10,
width: "20px",
height: "20px"
}}
title="Выделить для массового удаления"
/>
<Link to={`/manga/${book.id}`}>
<img
src={book.cover || "https://placehold.co/200x300"}
className="card-img-top shadow mt-2 mb-3"
alt="Обложка книги"
style={{ maxHeight: "300px", objectFit: "contain" }}
src={book.cover || "https://placehold.co/200x300"}
className="card-img-top shadow mt-2 mb-3"
alt="Обложка книги"
style={{ maxHeight: "300px", objectFit: "contain" }}
/>
</Link>
<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)}>
<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
className="btn btn-danger"
onClick={() => onDelete(book.id)}
>
Удалить
</button>
</div>

View File

@@ -1,13 +1,23 @@
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 || "Нет статуса";
function BookList({
books,
authors,
statuses,
onEdit,
onDelete,
selectedIds,
onSelect
}) {
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 => (
{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}
@@ -18,6 +28,8 @@ function BookList({ books, authors, statuses, onEdit, onDelete }) {
statusName={getStatusName(book.statusId)}
onEdit={onEdit}
onDelete={onDelete}
selected={selectedIds.includes(book.id)}
onSelect={onSelect}
/>
</div>
))}

View File

@@ -1,5 +1,4 @@
import React from "react";
import React, { useState } from "react";
import BookForm from "./BookForm";
import BookList from "./BookList";
@@ -16,8 +15,30 @@ export default function Home({
searchQuery,
setSearchQuery,
statusFilter,
setStatusFilter
setStatusFilter,
onDeleteSelected // новый пропс!
}) {
// Состояние для выделенных книг
const [selectedIds, setSelectedIds] = useState([]);
// Обработчик выделения карточки
const handleSelect = (id, checked) => {
setSelectedIds(prev =>
checked ? [...prev, id] : prev.filter(selectedId => selectedId !== id)
);
};
// Кнопка массового удаления
const handleDeleteSelected = () => {
if (
selectedIds.length &&
window.confirm(`Удалить ${selectedIds.length} выделенных книг?`)
) {
onDeleteSelected(selectedIds);
setSelectedIds([]); // очистка выделения
}
};
return (
<>
<h1 className="mb-4">Книги</h1>
@@ -40,6 +61,18 @@ export default function Home({
))}
</select>
</div>
{/* Кнопка массового удаления */}
<div className="mb-2">
<button
className="btn btn-danger"
disabled={selectedIds.length === 0}
onClick={handleDeleteSelected}
>
Удалить выделенные ({selectedIds.length})
</button>
</div>
<BookForm
authors={authors}
statuses={statuses}
@@ -53,6 +86,8 @@ export default function Home({
statuses={statuses}
onEdit={setEditingBook}
onDelete={onDelete}
selectedIds={selectedIds}
onSelect={handleSelect}
/>
</>
);

5672
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +0,0 @@
{
"name": "int-prog",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "npm-run-all --parallel backend vite",
"vite": "vite",
"build": "vite build",
"serve": "http-server -p 3000 ./html/",
"backend": "json-server ./html/database/data.json -p 5174",
"prod": "npm-run-all build serve --parallel backend serve",
"lint": "eslint . --ext js --report-unused-disable-directives --max-warnings 0"
},
"dependencies": {
"bootstrap": "5.3.3",
"bootstrap-icons": "^1.11.3",
"inputmask": "^5.0.9"
},
"devDependencies": {
"eslint": "8.56.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "10.0.2",
"eslint-plugin-html": "8.1.2",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "5.2.3",
"http-server": "14.1.1",
"json-server": "^1.0.0-beta.3",
"npm-run-all": "4.1.5",
"vite": "6.2.0"
}
}