import React, { useState,  useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
import Pagination from './Pagination';

const BoardList = () => {
  const [posts, setPosts] = useState([
    {"rnum":1,"hitCnt":18,"idx":5756,"title":"BatchTitle2","creaNm":"관리자","creaDtm":"2025-09-16"},
  ]);

  const [searchTerm, setSearchTerm] = useState('');//검색어 (검색바)
  const [submittedSearchTerm, setSubmittedSearchTerm] = useState(''); //검색어 (페이징 리스트)
  const [searchType, setSearchType] = useState('TITLE'); //검색 조건
  const [submittedSearchType, setSubmittedSearchType] = useState('TITLE');
  const [currentPage, setCurrentPage] = useState(1);
  const [totalPages, setTotalPages] = useState(100);

  //핸들링 이벤트의 실행주체
  const handleSearch = () => {
    console.log(searchTerm);
    console.log(searchType);
    setSubmittedSearchTerm(searchTerm);
    setCurrentPage(1); // Reset to first page on new search
  };

  //이벤트 감지기 (키이벤트)
  const handleKeyPress = (e) => {
    if (e.key === 'Enter') {//엔터를 입력 했을때
      handleSearch();
    }
  };

  //서버 주소 처리
  const retriveBoard = (type, keyword) => {
    console.log(type, keyword);
    axios.get("<http://localhost:8080/RestBoard/board/api/list>"
      , {params: {
        type: type,
        keyword: keyword,
        page: currentPage,
      }})
      .then(response => {
        setPosts(response.data.list);
        setTotalPages(response.data.totalpage);  
        console.log(response);
      }).catch(error=>{
        console.log(error);
        // [추가] API 오류 시 대체 데이터 설정
         setPosts([{ "rnum": 1, "hitCnt": 0, "idx": 9999, "title": "[오류] 목록을 불러올 수 없습니다. 백엔드 확인 필요.", "creaNm": "시스템", "creaDtm": "2025-11-04" }]);
          setTotalPages(1);
      })
  };

  useEffect(()=>{
    retriveBoard(submittedSearchType, submittedSearchTerm);
  
  }, [submittedSearchTerm, currentPage])

  // Change page
  const handlePageChange = (page) => setCurrentPage(page);

  return (
    <>
      <h2>Board</h2>

      <div className="row justify-content-between mb-3">
        <div className="col-md-auto">
            <Link to="/new" className="btn btn-primary">New Post</Link>
        </div>
        <div className="col-md-7">
          <div className="input-group">
            <select className="form-select" style={{ flex: '0 0 120px' }} value={searchType} onChange={e => setSearchType(e.target.value)}>
              <option value="TITLE">Title</option>
              <option value="CONTENTS">Content</option>
              <option value="ID">Author</option>  
            </select>
            <input
              type="text"
              className="form-control"
              placeholder={`Search by ${searchType}...`}
              value={searchTerm}
              onChange={e => setSearchTerm(e.target.value)}
              onKeyDown={e => handleKeyPress(e)}
            />
            <button className="btn btn-outline-secondary" type="button" onClick={e => handleSearch()}>Search</button>
          </div>
        </div>
      </div>

      {/* Post List */}
      <ul className="list-group mb-4">
        <li className="list-group-item d-flex justify-content-between align-items-center">
          <span>제목</span> <span>글쓴이</span>
        </li>
        {posts.map(post => (
          <li key={post.id} className="list-group-item d-flex justify-content-between align-items-center">
            <Link to={`/post/${post.idx}`}>{post.title}</Link>
            <span>{post.creaNm}</span>
          </li>
        ))}
      </ul>

      {/* Pagination 
      */}
      
      <Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={handlePageChange}  />
    </>
  );
};

export default BoardList;
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Header from './components/Header';
import BoardList from './components/BoardList';
import PostView from './components/PostView';
import PostForm from './components/PostForm';
import './App.css';
//spring 서블릿
function App() {
  return (
    <Router>
      <Header />
      <main>
        <Routes>
          <Route path="/" element={<BoardList />} />
          <Route path="/list" element={<BoardList />} />
          <Route path="/post/:id" element={<PostView />} />
          <Route path="/new" element={<PostForm />} />
        </Routes>
      </main>
    </Router>
  );
}

export default App;
import React from 'react';
import { Link } from 'react-router-dom';

const Header = () => {
  return (
    <header>
      <nav className="navbar navbar-expand-lg navbar-dark bg-dark">
        <div className="container">
          <Link className="navbar-brand" to="/">React Board</Link>
          <div className="collapse navbar-collapse">
            <ul className="navbar-nav ms-auto mb-2 mb-lg-0">
              <li className="nav-item">
                <Link className="nav-link" to="/">Home</Link>
              </li>
              <li className="nav-item">
                <Link className="nav-link" to="/new">New Post</Link>
              </li>
            </ul>
          </div>
        </div>
      </nav>
    </header>
  );
};

export default Header;

// Pagination.js

import React from 'react';
import '../Pagination.css';

// 2. 부모로부터 필요한 값과 함수를 props로 받습니다.
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
  // 1. 자식은 더 이상 페이지 상태를 직접 관리하지 않습니다.
  // const [currentPage, setCurrentPage] = useState(1); // 이 줄을 삭제!

  const pagesPerGroup = 10;
  const startPage = Math.floor((currentPage - 1) / pagesPerGroup) * pagesPerGroup + 1;
  const endPage = Math.min(startPage + pagesPerGroup - 1, totalPages);

  const pageNumbers = [];
  for (let i = startPage; i <= endPage; i++) {
    pageNumbers.push(i);
  }

  // 3. 모든 클릭 이벤트 핸들러에서 onPageChange를 호출합니다.
  const handleClick = (pageNumber) => {
    onPageChange(pageNumber);
  };

  const handlePrevClick = () => {
    onPageChange(Math.max(currentPage - 1, 1));
  };

  const handleNextClick = () => {
    onPageChange(Math.min(currentPage + 1, totalPages));
  };

  const handlePrevGroupClick = () => {
    onPageChange(Math.max(startPage - pagesPerGroup, 1));
  };

  const handleNextGroupClick = () => {
    onPageChange(Math.min(startPage + pagesPerGroup, totalPages));
  };

  return (
    <div className="pagination">
      <button onClick={handlePrevGroupClick} disabled={startPage === 1}>
        &lt;&lt;
      </button>
      <button onClick={handlePrevClick} disabled={currentPage === 1}>
        &lt;
      </button>
      {pageNumbers.map((number) => (
        <button
          key={number}
          onClick={() => handleClick(number)}
          className={currentPage === number ? 'active' : ''}
          disabled={currentPage === number}
        >
          {number}
        </button>
      ))}
      <button onClick={handleNextClick} disabled={currentPage === totalPages}>
        &gt;
      </button>
      <button onClick={handleNextGroupClick} disabled={endPage === totalPages}>
        &gt;&gt;
      </button>
    </div>
  );
};

export default Pagination;
import React from 'react';
import { useParams, Link } from 'react-router-dom';
import axios from 'axios'; // 추가 axios 임포트

const PostView = () => {
  const { id } = useParams();
  // [추가] 게시글 상태 및 로딩 상태 추가
  const [post, setPost] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

// [추가] useEffect를 사용하여 API에서 게시글 데이터 조회
  useEffect(() => {
    setIsLoading(true);
    axios.get(`http://localhost:8080/RestBoard/board/api/view`, {
      params: { idx: id }
    })
    .then(response => {
      // API 응답의 데이터 구조에 따라 post 객체를 설정합니다.
      setPost(response.data); 
    })
    .catch(error => {
      console.error("Error fetching post:", error);
      // 에러 발생 시 더미 데이터로 대체
      setPost({ 
        idx: id, 
        title: `[ERROR] Post ${id} Load Failed`, 
        creaNm: `Error User`, 
        content: `Could not fetch content. Check API endpoint: <http://localhost:8080/RestBoard/board/api/view?idx=${id}`> 
      });
    })
    .finally(() => {
      setIsLoading(false);
    });
  }, [id]);

// [추가] 로딩 상태 처리
  if (isLoading) {
    return <h2>Loading post...</h2>;
  }
  
  // [추가] 데이터가 없을 경우 처리
  if (!post) {
    return <h2>Post not found or failed to load.</h2>;
  }

  // In a real app, you would fetch the post data based on the id
  // const post = { id: id, title: `Post ${id}`, author: `User${id}`, content: `This is the full content of post ${id}.` };

  return (
    <>
      <h2>{post.title}</h2>
      <p><strong>Author:</strong> {post.creaNm}</p>
      <p>{post.content}</p>
      <Link to="/list" className="btn btn-secondary">Back to List</Link>
    </>
  );
};

export default PostView;

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';

const PostForm = () => {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const navigate = useNavigate();

  const handleSubmit = (e) => {
    e.preventDefault();
    // In a real app, you would handle the form submission (e.g., API call)
    console.log({ title, content });
    // [수정] alert() 대신 console.log로 대체했습니다.
    console.log('New post created!');
    navigate('/list');
  };

  return (
    <>
      <h2>New Post</h2>
      <form onSubmit={handleSubmit}>
        <div className="mb-3">
          <label htmlFor="title" className="form-label">Title</label>
          <input 
            type="text" 
            className="form-control" 
            id="title" 
            value={title} 
            onChange={(e) => setTitle(e.target.value)} 
            required 
          />
        </div>
        <div className="mb-3">
          <label htmlFor="content" className="form-label">Content</label>
          <textarea 
            className="form-control" 
            id="content" 
            rows="5" 
            value={content} 
            onChange={(e) => setContent(e.target.value)} 
            required 
          />
        </div>
        <button type="submit" className="btn btn-primary">Submit</button>
      </form>
    </>
  );
};

export default PostForm;

{
  "name": "react_board",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.12.2",
    "bootstrap": "^5.3.8",
    "react": "^19.1.1",
    "react-dom": "^19.1.1",
    "react-router-dom": "^7.9.1"
  },
  "devDependencies": {
    "@eslint/js": "^9.35.0",
    "@types/react": "^19.1.13",
    "@types/react-dom": "^19.1.9",
    "@vitejs/plugin-react": "^5.0.2",
    "eslint": "^9.35.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.20",
    "globals": "^16.4.0",
    "vite": "^7.1.6"
  }
}