Загрузка данных


Привет! Это отличная лабораторная для закрепления работы с состояниями и массивами в React. Так как ты будешь делать это в GitHub Codespaces, я составил для тебя пошаговый план, который проведет тебя от настройки окружения до готового пулл-реквеста.
Вот подробная инструкция, как выполнить эту работу.
Шаг 1. Подготовка репозитория и окружения
Согласно твоим требованиям к сдаче работы, сначала нужно правильно подготовить Git.
 * Создай форк оригинального репозитория в свою организацию (назови его, как требовалось: название-репозитория-твояфамилия).
 * Открой этот форк в GitHub Codespaces.
 * Создай новую ветку wip и переключись на нее. В терминале Codespaces введи:
   git checkout -b wip

Шаг 2. Создание проекта
Теперь нужно создать React-приложение с помощью Vite.
 * В терминале запусти команду создания проекта (точка в конце означает "создать в текущей папке", если у тебя пустой репозиторий. Если нужно создать подпапку, замени . на shelf-app):
   npm create vite@latest . -- --template react

 * Установи зависимости:
   npm install

 * Запусти сервер для разработки, чтобы видеть изменения в реальном времени:
   npm run dev

Шаг 3. Создание файловой структуры
Согласно условию, каждый компонент должен лежать в своей папке с .jsx и .css файлом. Создай в папке src следующую структуру:
src/
 ├─ components/
 │   ├─ BookForm/
 │   │   ├─ BookForm.jsx
 │   │   └─ BookForm.css
 │   ├─ Checkbox/
 │   ├─ Input/
 │   ├─ Button/
 │   ├─ FilterChip/
 │   ├─ BookList/
 │   │   ├─ BookList.jsx
 │   │   └─ BookList.css
 │   └─ BookItem/
 │       ├─ BookItem.jsx
 │       └─ BookItem.css
 ├─ pages/
 │   └─ ShelfScreen/
 │       ├─ ShelfScreen.jsx
 │       └─ ShelfScreen.css
 ├─ App.jsx
 └─ App.css

(Для простых компонентов вроде Input, Button, Checkbox, FilterChip также создай папки и файлы по аналогии).
Шаг 4. Написание кода (сверху вниз)
Давай напишем логику для основных файлов. Верстку и классы тебе нужно будет перенести из reference.html, здесь я показываю именно React-логику.
1. src/App.jsx (Главное хранилище состояния)
Здесь мы храним массив книг и фильтр, а также пишем функции для их изменения.
import { useState } from 'react'
import ShelfScreen from './pages/ShelfScreen/ShelfScreen'
import './App.css'

function App() {
  // Состояние для списка книг
  const [books, setBooks] = useState([])
  // Состояние для фильтра (показывать только непрочитанные)
  const [showOnlyUnread, setShowOnlyUnread] = useState(false)

  // Добавление книги (иммутабельно)
  const handleAddBook = (title) => {
    const newBook = {
      id: Date.now(),
      title,
      author: 'Неизвестный автор', // Можно расширить форму позже
      read: false
    }
    setBooks(prev => [...prev, newBook])
  }

  // Переключение статуса "Прочитано" (иммутабельно)
  const handleToggleRead = (id) => {
    setBooks(prev => prev.map(book => 
      book.id === id ? { ...book, read: !book.read } : book
    ))
  }

  // Удаление книги (иммутабельно)
  const handleDeleteBook = (id) => {
    setBooks(prev => prev.filter(book => book.id !== id))
  }

  return (
    <div className="app-container">
      <ShelfScreen 
        books={books}
        showOnlyUnread={showOnlyUnread}
        setShowOnlyUnread={setShowOnlyUnread}
        onAddBook={handleAddBook}
        onToggleRead={handleToggleRead}
        onDeleteBook={handleDeleteBook}
      />
    </div>
  )
}

export default App

2. src/pages/ShelfScreen/ShelfScreen.jsx (Фильтрация и компоновка)
Этот компонент берет данные из App, фильтрует их и передает нужным блокам.
import BookForm from '../../components/BookForm/BookForm'
import BookList from '../../components/BookList/BookList'
// Импортируй FilterChip, когда создашь его

export default function ShelfScreen({ 
  books, 
  showOnlyUnread, 
  setShowOnlyUnread, 
  onAddBook, 
  onToggleRead, 
  onDeleteBook 
}) {
  
  // Логика фильтрации
  const filteredBooks = showOnlyUnread 
    ? books.filter(book => !book.read)
    : books;

  return (
    <main>
      <h1>Моя полка</h1>
      
      <BookForm onAdd={onAddBook} />
      
      {/* Здесь можно использовать твой компонент FilterChip */}
      <label>
        <input 
          type="checkbox" 
          checked={showOnlyUnread}
          onChange={() => setShowOnlyUnread(prev => !prev)}
        />
        Показывать только непрочитанные
      </label>

      <BookList 
        books={filteredBooks} 
        onToggleRead={onToggleRead}
        onDeleteBook={onDeleteBook}
      />
    </main>
  )
}

3. src/components/BookForm/BookForm.jsx (Локальный state)
Форма хранит в себе то, что вводит пользователь, и очищается после отправки.
import { useState } from 'react'
// Импортируй свои Input и Button, если вынес их в отдельные компоненты

export default function BookForm({ onAdd }) {
  const [title, setTitle] = useState('')

  const handleSubmit = (e) => {
    e.preventDefault() // предотвращаем перезагрузку страницы
    const trimmedTitle = title.trim()
    
    if (trimmedTitle) {
      onAdd(trimmedTitle)
      setTitle('') // Очищаем поле после добавления
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="text" 
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="Название новой книги..."
      />
      <button type="submit">Добавить на полку</button>
    </form>
  )
}

4. src/components/BookList/BookList.jsx (Рендеринг списка)
Просто принимает отфильтрованный массив и рендерит карточки.
import BookItem from '../BookItem/BookItem'

export default function BookList({ books, onToggleRead, onDeleteBook }) {
  if (books.length === 0) {
    return <p>Здесь пока пусто...</p>
  }

  return (
    <div className="book-list">
      {books.map(book => (
        <BookItem 
          key={book.id} 
          book={book} 
          onToggle={onToggleRead}
          onDelete={onDeleteBook}
        />
      ))}
    </div>
  )
}

5. src/components/BookItem/BookItem.jsx (Карточка книги)
Глупый (dumb) компонент, который просто красиво выводит переданные пропсы.
export default function BookItem({ book, onToggle, onDelete }) {
  // Цветовая палитра для обложек-заглушек
  const colors = ['#f87171', '#60a5fa', '#34d399', '#fbbf24', '#a78bfa']
  const coverColor = colors[book.id % colors.length]

  return (
    <div className="book-item">
      <div className="book-cover" style={{ backgroundColor: coverColor }}>
        {/* Обложка */}
      </div>
      
      <div className="book-info">
        <h3>{book.title}</h3>
        <p>{book.author}</p>
        
        <label>
          <input 
            type="checkbox" 
            checked={book.read}
            onChange={() => onToggle(book.id)}
          />
          Прочитано
        </label>
        
        <button onClick={() => onDelete(book.id)}>Удалить</button>
      </div>
    </div>
  )
}

Шаг 5. Как сдать работу
Когда перенесешь все стили из reference.html в соответствующие .css файлы и проверишь, что приложение работает:
 * Зафиксируй изменения (Commit):
   В терминале (или через вкладку Source Control слева) выполни:
   git add .
git commit -m "feat: реализована логика приложения Shelf на React"

 * Отправь ветку в репозиторий (Push):
   git push origin wip

 * Захости работу (Deploy):
   * Если вы используете GitHub Pages, настрой его в настройках репозитория (Settings -> Pages), либо используй Vercel / Netlify (по инструкции вашего преподавателя).
 * Создай Pull Request:
   * Зайди на страницу своего форка на GitHub.
   * Появится зеленая кнопка "Compare & pull request" для твоей ветки wip.
   * Важно: убедись, что ты создаешь PR в ветку main своего же форка, а не в оригинальный репозиторий преподавателя (выбери base: main и compare: wip).
   * В поле "Reviewers" (справа) добавь пользователя ktkv419.
   * Оставь PR открытым — преподаватель проверит и смержит его сам.