Final Project 신세계아이앤씨

재고관리 페이지 ( 리액트, 스프링부트 )

pjh8838 2024. 7. 24. 09:12

달력에서 일자 클릭하면

클릭한 일자에 해당하는 유통기한 임박 상품들 재고테이블에서 가져와서 보여준다

 

근데 유통기한 임박 상품 리스트를 재고관리에서 가져와야해서

재고관리 페이지를 먼저 만들어야함

 

 

 

 

프론트 ( 리액트 )

 

1. 데이터를 받아오고 사용할 틀을 만들어준다

import { useState, useEffect } from 'react';
import axios from 'axios';
import stockk from './StockList.module.css';
import ReactPaginate from 'react-paginate';

const Stock = ({ columns }) => {
    // 선택된 행의 인덱스를 저장하는 상태
    const [selectedRows, setSelectedRows] = useState([]);

    // 서버에서 가져온 재고 데이터를 저장하는 상태
    const [stock, setStock] = useState([]);

    // 현재 정렬 옵션을 저장하는 상태
    const [sortOption, setSortOption] = useState('');

    // 카테고리 필터 값을 저장하는 상태
    const [categoryFilter, setCategoryFilter] = useState('');

    // 위치 필터 값을 저장하는 상태
    const [locationFilter, setLocationFilter] = useState('');

    // 각 정렬 체크박스의 상태를 저장하는 상태
    const [sortStates, setSortStates] = useState({
        quantity: false,
        expiry: false,
        stock: false
    });

    // 현재 페이지 번호를 저장하는 상태
    const [currentPage, setCurrentPage] = useState(0);

    // 페이지당 항목 수
    const itemsPerPage = 10;


    //thead는 컬럼명 header
    //tbody는 데이터 accessor

    // 서버에서 재고 데이터 가져오기
    useEffect(() => {
        axios.get('http://localhost:8090/traders/stock')
            .then(response => {
                setStock(response.data);
                // console.log(response.data);
            })
            .catch(error => {
                console.error('There was an error fetching the goods!', error);
            });
    }, []);

    // 모든 행을 선택하거나 선택을 해제하는 함수
    const handleSelectAll = (event) => {
        if (event.target.checked) {
            setSelectedRows(stock.map((_, index) => index));
        } else {
            setSelectedRows([]);
        }
    };

    // 특정 행을 선택하거나 선택을 해제하는 함수
    const handleSelectRow = (rowIndex) => {
        if (selectedRows.includes(rowIndex)) {
            setSelectedRows(selectedRows.filter(index => index !== rowIndex));
        } else {
            setSelectedRows([...selectedRows, rowIndex]);
        }
    };

    // 정렬 옵션을 변경하는 함수
    const handleSortChange = (sortBy) => {
        setSortStates(prevState => {
            const newSortStates = { ...prevState, [sortBy]: !prevState[sortBy] };

            // 체크박스가 해제되면 정렬 옵션을 기본 상태로 설정
            if (!newSortStates[sortBy]) {
                setSortOption('');
            } else {
                setSortOption(sortBy);
            }

            // 한 번에 하나의 정렬만 활성화
            for (const key in newSortStates) {
                if (key !== sortBy) {
                    newSortStates[key] = false;
                }
            }
            return newSortStates;
        });
    };

    // 필터링 및 정렬된 재고 데이터를 반환하는 함수
    const sortedAndFilteredStock = stock
        .filter(item => {
            return (categoryFilter ? item.goods.gcategory === categoryFilter : true) &&
                (locationFilter ? item.loc2 === locationFilter : true);
        })
        .sort((a, b) => {
            switch (sortOption) {
                case 'quantity':
                    return b.stockquantity - a.stockquantity;
                case 'expiry':
                    return new Date(a.expdate) - new Date(b.expdate);
                case 'stock':
                    return b.stockquantity - a.stockquantity;
                default:
                    return 0;
            }
        });

    // 카테고리 필터 값을 변경하는 함수
    const handleCategoryFilterChange = (event) => {
        setCategoryFilter(event.target.value);
    };

    // 위치 필터 값을 변경하는 함수
    const handleLocationFilterChange = (event) => {
        setLocationFilter(event.target.value);
    };

    //페이지네이션
    // 현재 페이지에 표시할 항목을 반환하는 함수
    const displayStock = sortedAndFilteredStock.slice(currentPage * itemsPerPage, (currentPage + 1) * itemsPerPage);

    // 총 페이지 수를 계산하는 함수
    const pageCount = Math.ceil(sortedAndFilteredStock.length / itemsPerPage);

    // 페이지를 변경하는 함수
    const handlePageChange = ({ selected }) => {
        setCurrentPage(selected);
    };

    return (
        <div className={stockk.tableTop}>
            <div className={stockk.tableCon}>
                <input type='checkbox' checked={sortStates.quantity} onChange={() => handleSortChange('quantity')} />
                <span>판매량</span>
                <input type='checkbox' checked={sortStates.expiry} onChange={() => handleSortChange('expiry')} />
                <span>유통기한 순</span>
                <input type='checkbox' checked={sortStates.stock} onChange={() => handleSortChange('stock')} />
                <span>재고량 순</span>

                <form className={stockk.cate_Form}>
                    <select name='category' onChange={handleCategoryFilterChange}>
                        <option disabled selected>카테고리</option>
                        <option>간식류</option>
                        <option>곡류</option>
                        <option>소스류</option>
                    </select>
                </form>

                <form className={stockk.cate_Form}>
                    <select name='category' onChange={handleLocationFilterChange}>
                        <option disabled selected>위치</option>
                        <option>A</option>
                        <option>B</option>
                        <option>C</option>
                    </select>
                </form>
            </div>
            <table className={stockk.stockT}>
                <thead>
                    <tr>
                        <th>
                            <input
                                type="checkbox"
                                onChange={handleSelectAll}
                                checked={selectedRows.length === stock.length}
                            />
                        </th>

                        {columns.map((column, index) => (
                            <th key={index}>{column.header}</th>
                        ))}
                    </tr>
                </thead>
                <tbody>
                    {displayStock.map((row, rowIndex) => (
                        <tr key={rowIndex}>
                            <td>
                                <input
                                    type="checkbox"
                                    onChange={() => handleSelectRow(rowIndex)}
                                    checked={selectedRows.includes(rowIndex)}
                                />
                            </td>
                            {columns.map((column, colIndex) => (
                                <td key={colIndex}>
                                    {column.render ? column.render(row) : row[column.accessor]}
                                    {/* render는 이미지나 입력필드 같은 특수한 데이터
                 accessor는 데이터 키값 */}
                                </td>
                            ))}
                        </tr>
                    ))}
                    {displayStock.length < itemsPerPage &&
                        Array.from({ length: itemsPerPage - displayStock.length }).map((_, index) => (
                            <tr key={`empty-${index}`}>
                                <td colSpan={columns.length + 1}>&nbsp;</td>
                            </tr>
                        ))}
                </tbody>
            </table>
            <hr className={stockk.pageLine} />
            <ReactPaginate
                previousLabel={"Previous"}
                nextLabel={"Next"}
                pageCount={pageCount}
                onPageChange={handlePageChange}
                containerClassName={"paginationBttns"}
                previousLinkClassName={"previousBttn"}
                nextLinkClassName={"nextBttn"}
                disabledClassName={"paginationDisabled"}
                activeClassName={"paginationActive"}
                className={stockk.page}
            />
            <div className={stockk.stockBtn}>
                <button>발주하기</button>
                <button>삭제하기</button>
            </div>
        </div>
    );
};

export default Stock;

 

2. 게시판 형태의 페이지가 많아서 재사용하기 위해 컬럼, 키값을 다른 페이지에 적어준다

 
import Stock from './Stock';

const StockList = () => {
  const columns = [
    { header: '재고코드', accessor: 'stockid' },
    { header: '상품코드', accessor: 'goods.gcode', render: (row) => row.goods.gcode },
    //stock 테이블의 값은 그냥 키값으로 들고와지는데
    //외래키나 조인으로 연결된 goods 테이블의 정보는
    //render를 써서 가져와야한다
    { header: "이미지", accessor: 'goods.gimage', render: (row) => <img src={`http://localhost:8090/traders/images/items/${row.goods.gimage}.png`} alt={row.goods.gname} style={{width: '50px', height: '50px'}} /> },
    { header: '카테고리', accessor: 'goods.gcategory', render: (row) => row.goods.gcategory},
    {
      header: '재고수량',
      accessor: 'stockquantity'
    },
    { header: '유통기한', accessor: 'expdate' },
    { header: '상품판매가', accessor: 'gprice' },
    { header: '대분류', accessor: 'loc1' },
    { header: '중분류', accessor: 'loc2' },
    { header: '소분류', accessor: 'loc3' },
  ];

  return <Stock columns={columns} />;
};

export default StockList;

 

백엔드 ( 스프링부트, JPA, MySql )

StockDTO
Stock.java entity

 

StockRepository

 

StockService

 

StockServiceImpl

 

StockController

 

결과물

 

기능 정리

1. 정렬기능 

handleSortChange로 정렬 옵션기능 만듬,    새로운 변수 만들고 stock에 필터줘서 정렬기능 만듬

 

  // 필터링 및 정렬된 재고 데이터를 반환하는 함수
    const sortedAndFilteredStock = stock
        .filter(item => {
            return (categoryFilter ? item.goods.gcategory === categoryFilter : true) &&
                (locationFilter ? item.loc2 === locationFilter : true);
        })
        .sort((a, b) => {
            switch (sortOption) {
                case 'quantity':
                    return b.stockquantity - a.stockquantity;
                case 'expiry':
                    return new Date(a.expdate) - new Date(b.expdate);
                case 'stock':
                    return b.stockquantity - a.stockquantity;
                default:
                    return 0;
            }
        });
 
 
// 정렬 옵션을 변경하는 함수
    const handleSortChange = (sortBy) => {
        setSortStates(prevState => {
            const newSortStates = { ...prevState, [sortBy]: !prevState[sortBy] };

            // 체크박스가 해제되면 정렬 옵션을 기본 상태로 설정
            if (!newSortStates[sortBy]) {
                setSortOption('');
            } else {
                setSortOption(sortBy);
            }

            // 한 번에 하나의 정렬만 활성화
            for (const key in newSortStates) {
                if (key !== sortBy) {
                    newSortStates[key] = false;
                }
            }
            return newSortStates;
        });
    };



728x90