12 minute read

web-HRM-payroll #0.2 react-project

2023년 5월 16일

Human Resources Management web project

개발 툴 mac_vscode_chrome

글 작성 _ notion

1. 인사관리 시스템 구현

1-1. 폴더 구조

스크린샷 2023-05-16 오전 11.31.14.png

1-2. npm list

  • xlsx
  • reactstrap



2. Userlist.js

2-1. 최상단 import

import {
  Table,
  FormGroup,
  Label,
  Input,
  Row,
  Col,
  Button,
} from "reactstrap";
import { useState, useEffect } from "react";
// 엑셀 파일
import TableToExcel from "./TableToExcel";
import TotalExcel from "./TotalExcel";
// 당월 표시
import GetThisMonth from "./getThisMonth";

2-2. 변수 선언

function Userlist() {
  const [users, setUsers] = useState([]);
  const [sortDirection, setSortDirection] = useState("asc");
  const [searchTerm, setSearchTerm] = useState("");
  const [searchDays, setSearchDays] = useState("");
  const [checkedIndex, setCheckedIndex] = useState(-1);
  const [currentPage, setCurrentPage] = useState(1); // 현재 페이지
  const itemsPerPage = 10; // 한 페이지에 표시할 아이템 수
  const [currentSortKey, setCurrentSortKey] = useState("");
  const [tableData, setTableData] = useState([]);

  // --------------

  //엑셀 양식
  const tableColumns = [
    `당월 근태정산`,
    "사원번호",
    "이름",
    "나이",
    "유급휴가일수",
    "소정근로일수",
    "실제근로일수",
    "소정근로시간",
    "승인된 근로시간",
    "전화번호",
    "이메일",
    "입사일",
  ];
  //엑셀에 들어갈 나이를 출생년도로 바꿔주는 함수
  function calculateAge(birthYear) {
    const currentYear = new Date().getFullYear();
    return currentYear - birthYear;
  }

  //json 파일 불러오기
  useEffect(() => {
    fetch(
      "https://raw.githubusercontent.com/solfany/Json_Group/main/json/project/002/user-list.json"
    )
      .then((response) => response.json())
      .then((json) => {
        setUsers(json);
      });
  }, []);

  // ----------------
  //엑셀 양식
  useEffect(() => {
    setTableData(
      users.map((user) => [
        ``,
        `${user.rank} `,
        `${user.name} `,
        `${user.age} (${calculateAge(user.age)} 출생)`,
        `${user.data5} `,
        `${user.data1} `,
        `${user.data2} `,
        `${user.data3} 시간`,
        `${user.data4} 시간`,
        `${user.phone} `,
        `${user.email} `,
        `${user.date} `,
      ])
    );
  }, [users]);

  // 현재 테이블 해당하는 아이템들을 반환하는 함수
  const getCurrentItems = () => {
    const filterData = users.filter((user) => {
      return (
        user.name &&
        user.date &&
        user.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
        user.date.toLowerCase().includes(searchDays.toLowerCase())
      );
    });

    // ---------

    let sortedData;
    if (currentSortKey === "name") {
      sortedData = [...filterData].sort((a, b) =>
        sortDirection === "asc"
          ? a.name.localeCompare(b.name)
          : b.name.localeCompare(a.name)
      );
    } else if (currentSortKey === "vacationDays") {
      sortedData = [...filterData].sort((a, b) =>
        sortDirection === "asc"
          ? a.vacationDays - b.vacationDays
          : b.vacationDays - a.vacationDays
      );
    } else {
      sortedData = filterData;
    }

    // 페이지네이션 ----------
    const startIndex = (currentPage - 1) * itemsPerPage;
    const endIndex = startIndex + itemsPerPage;
    return sortedData.slice(startIndex, endIndex);
  };

  const totalPages = Math.ceil(users.length / itemsPerPage);

  // 현재 페이지에 해당하는 아이템들을 반환하는 함수
  const currentItems = getCurrentItems();

  // 이전 페이지로 이동하는 함수
  const goToPrevPage = () => {
    if (currentPage > 1) {
      setCurrentPage(currentPage - 1);
    }
  };

  // 다음 페이지로 이동하는 함수
  const goToNextPage = () => {
    if (currentPage < totalPages) {
      setCurrentPage(currentPage + 1);
    }
  };

  const handleSortBy = (key) => {
    if (sortDirection === "asc") {
      setUsers([...users].sort((a, b) => (a[key] < b[key] ? 1 : -1)));
      setSortDirection("desc");
    } else {
      setUsers([...users].sort((a, b) => (a[key] > b[key] ? 1 : -1)));
      setSortDirection("asc");
    }
  };

  //체크박스
  const handleCheckbox = (index) => {
    if (checkedIndex === index) {
      // 이미 선택된 체크박스를 클릭하면 선택을 해제합니다.
      setCheckedIndex(-1);
    } else {
      // 다른 체크박스를 클릭하면 해당 index로 설정합니다.
      setCheckedIndex(index);
    }
  };

  // 검색창 해당하는 db 찾기
  const handleSearchTermChange = (event) => {
    setSearchTerm(event.target.value);
    setCurrentPage(1);
  };

  // 달력 날짜 별로 해당하는 db 찾기
  const handleSearchDaysChange = (event) => {
    setSearchDays(event.target.value);
    setCurrentPage(1);
  };

해당 코드는 Userlist라는 함수형 컴포넌트이다.

코드의 주요 기능과 역할에 대해서 설명하겠다.

  1. 상태 관리: useState 훅을 사용해서 여러 상태 변수를 관리한다.
    • users는 사용자 데이터를 저장하는 배열이다.
    • sortDirection은 정렬 방향을 나타내는 문자열이다.
    • searchTerm은 검색어를 저장하는 문자열이다.
    • searchDays는 날짜 검색어를 저장하는 문자열이다.
    • checkedIndex는 체크된 체크박스의 인덱스를 저장하는 숫자이다.
    • currentPage는 현재 페이지 번호를 저장하는 숫자이다.
    • currentSortKey는 현재 정렬 기준을 나타내는 문자열이다.
    • tableData는 테이블에 표시될 데이터를 저장하는 배열이다.
  2. 데이터 로딩: useEffect 훅을 사용해서 초기에 JSON 데이터를 가져와서 users 상태 변수에 설정한다.
  3. 데이터 가공: calculateAge 함수를 사용해서 출생년도를 입력받아서 나이를 계산한다. 그리고 useEffect 훅을 사용해서 users 상태가 변경될 때마다 tableData를 업데이트한다.
  4. 데이터 필터링 및 정렬: getCurrentItems 함수를 사용해서 현재 페이지에 해당하는 아이템들을 필터링하고 정렬한다. users 상태에 저장된 데이터를 필터링해서 filterData를 생성한 후, 정렬 기준인 currentSortKey에 따라 정렬된 데이터인 sortedData를 얻는다.
  5. 페이지네이션: goToPrevPage 함수와 goToNextPage 함수를 사용해서 이전 페이지와 다음 페이지로 이동할 수 있다. 전체 페이지 수를 계산하기 위해 totalPages 변수를 사용한다.
  6. 정렬 기능: handleSortBy 함수를 사용해서 정렬 방식을 변경한다. 데이터를 정렬하기 위해 정렬 기준인 key에 따라 정렬을 수행하고, sortDirection 상태 변수에 따라 오름차순 또는 내림차순으로 정렬한다.
  7. 체크박스 기능: handleCheckbox 함수를 사용해서 체크박스를 선택하거나 선택을 해제할 수 있다. 선택한 체크박스의 인덱스를 checkedIndex 상태 변수에 저장한다.
  8. 검색 기능: handleSearchTermChange 함수와 handleSearchDaysChange 함수를 사용해서 검색어를 입력하고 관련된 데이터를 검색할 수 있다. 이름 검색어는 searchTerm 상태 변수에 저장되고, 날짜 검색어는 searchDays 상태 변수에 저장된다.

2-3. 반환 값

return (
    <div className="content">
      <GetThisMonth />
      <h3>근태정산</h3>

      <Row>
        <Col md="3">
          <FormGroup>
            <Input
              id="Date"
              name="date"
              placeholder="date placeholder"
              type="date"
              value={searchDays}
              onChange={handleSearchDaysChange}
              onClick={() => setCheckedIndex(-1)}
            />
          </FormGroup>
        </Col>
        <Col md="3">
          <Input
            type="text"
            value={searchTerm}
            onChange={handleSearchTermChange}
            placeholder=" 직원 이름으로 검색"
            onClick={() => setCheckedIndex(-1)}
          />
        </Col>
      </Row>
      <Table striped bordered hover>
        <thead>
          <tr>
            <th onClick={() => handleSortBy("name")}>
              직원{" "}
              {sortDirection === "asc" ? (
                <i className="fa fa-sort-alpha-asc"></i>
              ) : (
                <i className="fa fa-sort-alpha-desc"></i>
              )}
            </th>
            <th onClick={() => handleSortBy("data5")}>
              유급휴가 일수{" "}
              {sortDirection === "asc" ? (
                <i className="fa fa-sort-numeric-asc"></i>
              ) : (
                <i className="fa fa-sort-numeric-desc"></i>
              )}
            </th>
            <th>email</th>
            <th>소정근로일수</th>
            <th>실제 근로일수</th>
            <th>소정근로시간</th>
            <th>승인된 근로시간</th>
            <th>선택</th>
          </tr>
        </thead>
        <tbody>
          {currentItems.map((user, index) => (
            <tr key={user.id}>
              <td>{user.name}</td>
              <td>{user.data5}</td>
              <td>{user.email}</td>
              <td>{user.data1}</td>
              <td>{user.data2}</td>
              <td>{user.data3}</td>
              <td>{user.data4}</td>
              <td>
                <input
                  type="checkbox"
                  checked={index === checkedIndex}
                  onChange={() => handleCheckbox(index)}
                  onClick={(e) => e.stopPropagation()}
                />{" "}
                {index === checkedIndex}{" "}
              </td>
            </tr>
          ))}
        </tbody>
      </Table>
      <div
        style=
      >
        <Col>
          <Button size="sm" onClick={goToPrevPage}>
            이전
          </Button>

          {Array.from({ length: totalPages }, (_, i) => (
            <Button
              size="sm"
              key={i + 1}
              onClick={() => setCurrentPage(i + 1)}
              disabled={currentPage === i + 1}
            >
              {i + 1}
            </Button>
          ))}
          <Button size="sm" onClick={goToNextPage}>
            다음
          </Button>
        </Col>
      </div>
      <div
        style=
      >
        <TotalExcel
          tableData={tableData}
          tableColumns={tableColumns}
          fileName="myTable"
          sheetName="통합 근태 "
        />
      </div>
      {checkedIndex !== -1 && (
        <>
          <div
            style=
          >
            <h5>{currentItems[checkedIndex].name} 님의 근태정산 Excel</h5>
            <TableToExcel
              tableData={tableData}
              tableColumns={tableColumns}
              fileName={currentItems[checkedIndex].name}
              sheetName="mySheet"
              currentItems={currentItems}
              checkedIndex={checkedIndex}
            />
            <Button size="sm" onClick={() => setCheckedIndex(-1)}>
              취소
            </Button>
          </div>
        </>
      )}
    </div>
  );
}

위의 코드는 Userlist 컴포넌트의 렌더링 부분이다.

이 부분은 JSX로 작성되어 사용자 리스트와 관련된 UI를 생성한다.

아래는 코드의 주요 내용에 대한 설명이다.

  • <div className="content">: 컨텐츠를 감싸는 부모 요소이다.
  • <GetThisMonth />: GetThisMonth 컴포넌트를 렌더링한다. 이 컴포넌트는 현재 달을 표시하는 역할을 수행한다..
  • <h3>근태정산</h3>: 제목을 표시하는 <h3> 요소이다.
  • 검색 기능:
    • 날짜 입력 필드: 사용자가 날짜를 선택할 수 있는 입력 필드이다. searchDays 상태 변수와 handleSearchDaysChange 함수를 사용하여 날짜 값을 관리한다.
    • 이름 검색 필드: 사용자가 직원의 이름으로 검색할 수 있는 입력 필드이다. searchTerm 상태 변수와 handleSearchTermChange 함수를 사용하여 검색어 값을 관리한다.
  • 테이블:
    • <Table> 컴포넌트: Bootstrap의 테이블 컴포넌트를 사용하여 데이터를 표시한다.
    • <thead>: 테이블의 헤더 부분이다. 각 열의 제목을 표시하고, 클릭 이벤트를 통해 해당 열을 기준으로 정렬할 수 있다.
    • <tbody>: 테이블의 본문 부분이다. currentItems 배열에 저장된 사용자 데이터를 매핑하여 행을 생성한다. 각 행은 사용자의 정보와 체크박스를 포함한다.
  • 페이지네이션:
    • 이전 페이지 및 다음 페이지로 이동하는 버튼을 제공한다. goToPrevPage 함수와 goToNextPage 함수를 사용하여 페이지를 변경할 수 있다. 페이지 버튼은 currentPage 상태 변수를 기준으로 활성화 여부를 결정한다.
  • 엑셀 다운로드:
    • 통합 근태 엑셀 다운로드 버튼: TotalExcel 컴포넌트를 사용하여 전체 테이블 데이터를 엑셀 파일로 다운로드할 수 있다.
    • 개별 사용자의 근태 엑셀 다운로드: TableToExcel 컴포넌트를 사용하여 선택된 사용자의 근태 정보를 엑셀 파일로 다운로드할 수 있다. 선택된 사용자의 이름을 표시하고, 엑셀 다운로드 및 취소 버튼이 제공된다.

결론적으로 위의 코드는 사용자 리스트를 표시하고, 검색, 정렬, 페이지네이션, 엑셀 다운로드 등의 기능을 제공하는 UI를 생성한다.




3. TableRoExcel.js

// 개인 엑셀 시트
import React from "react";
import * as XLSX from "xlsx";
import { Button } from "reactstrap";

const TableToExcel = ({
  tableData,
  tableColumns,
  fileName,
  sheetName,
  checkedIndex,
}) => {
  const exportToExcel = () => {
    // 체크된 행 데이터 추출
    const checkedData = tableData.filter(
      (rowData, rowIndex) => rowIndex === checkedIndex
    );

    // 테이블 데이터를 시트 데이터로 변환
    const sheetData = [tableColumns, ...checkedData];
    const sheet = XLSX.utils.aoa_to_sheet(sheetData);

    // 엑셀 워크북 생성
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, sheet, sheetName);

    // 엑셀 파일 다운로드
    XLSX.writeFile(workbook, `${fileName}_근태정산.xlsx`);
  };

  return (
    <Button size="sm" onClick={exportToExcel}>
      내려받기
    </Button>
  );
};

export default TableToExcel;

위의 코드는 개별 사용자의 근태 정보를 엑셀 파일로 내려받을 수 있는 컴포넌트인 TableToExcel이다. 아래는 코드의 주요 내용에 대한 설명이다.

  • tableData: 전체 테이블 데이터를 나타내는 배열이다.
  • tableColumns: 테이블의 열 제목을 나타내는 배열이다.
  • fileName: 다운로드되는 엑셀 파일의 이름이다.
  • sheetName: 생성되는 시트의 이름이다.
  • checkedIndex: 선택된 사용자의 인덱스이다.
  • exportToExcel 함수: 엑셀 파일을 내려받는 함수이다. 다음 단계를 수행한다.
    • 선택된 사용자의 행 데이터를 checkedData에 필터링하여 추출한다.
    • 테이블 데이터와 열 제목을 시트 데이터로 변환한다.
    • XLSX.utils.aoa_to_sheet 함수를 사용하여 시트 데이터를 생성한다.
    • 새로운 엑셀 워크북을 생성한다.
    • XLSX.utils.book_append_sheet 함수를 사용하여 시트를 워크북에 추가한다.
    • XLSX.writeFile 함수를 사용하여 엑셀 파일을 다운로드다.
  • 렌더링: 다운로드 버튼을 렌더링합니다. 버튼을 클릭하면 exportToExcel 함수가 실행된다.

TableToExcel 컴포넌트는 선택된 사용자의 근태 정보를 엑셀 파일로 내려받는 기능을 제공한다. 버튼을 클릭하면 해당 사용자의 데이터가 엑셀 파일로 저장된다.



4. TotalExcel.js

// 전체 엑셀
import React from "react";
import * as XLSX from "xlsx";
import { Button } from "reactstrap";

const TotalExcel = ({ tableData, tableColumns, fileName, sheetName }) => {
  const exportToExcel = () => {
    // 테이블 데이터를 시트 데이터로 변환
    const sheetData = [tableColumns, ...tableData];
    const sheet = XLSX.utils.aoa_to_sheet(sheetData);

    // 엑셀 워크북 생성
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, sheet, sheetName);

    // 엑셀 파일 다운로드
    XLSX.writeFile(workbook, `${fileName}.xlsx`);
  };

  return (
    <Button size="sm" onClick={exportToExcel}>
      당월 Excel 내려받기
    </Button>
  );
};

export default TotalExcel;

위의 코드는 전체 근태 정보를 엑셀 파일로 내려받을 수 있는 컴포넌트인 TotalExcel이다. 아래는 코드의 주요 내용에 대한 설명이다.

  • tableData: 전체 테이블 데이터를 나타내는 배열이다.
  • tableColumns: 테이블의 열 제목을 나타내는 배열이다.
  • fileName: 다운로드되는 엑셀 파일의 이름이다.
  • sheetName: 생성되는 시트의 이름이다.
  • exportToExcel 함수: 엑셀 파일을 내려받는 함수이다. 다음 단계를 수행한다.
    • 테이블 데이터와 열 제목을 시트 데이터로 변환한다.
    • XLSX.utils.aoa_to_sheet 함수를 사용하여 시트 데이터를 생성한다.
    • 새로운 엑셀 워크북을 생성한다.
    • XLSX.utils.book_append_sheet 함수를 사용하여 시트를 워크북에 추가한다.
    • XLSX.writeFile 함수를 사용하여 엑셀 파일을 다운로드한다.
  • 렌더링: 당월 Excel 내려받기 버튼을 렌더링한다. 버튼을 클릭하면 exportToExcel 함수가 실행된다.

TotalExcel 컴포넌트는 전체 근태 정보를 엑셀 파일로 내려받는 기능을 제공한다. 버튼을 클릭하면 전체 근태 정보가 엑셀 파일로 저장된다.




5. GetThisMonth.js

import React from "react";
import listStyle from "./listStyle.css";

function GetThisMonth() {
  const today = new Date();
  const year = today.getFullYear();
  const month = today.getMonth() + 1;

  const options = [];
  for (let i = 1; i < 4; i++) {
    options.push(
      <option key={month - i} value={month - i}>
        {month - i}
      </option>
    );
  }

  return (
    <div className="d-flex month">
      <h1>{year}</h1>
      <select>{options}</select>
      <h1></h1>
    </div>
  );
}

export default GetThisMonth;

위의 코드는 현재 연도와 월을 표시하고, 이전 4개월을 선택할 수 있는 컴포넌트인 GetThisMonth를 정의한다. 아래는 코드의 주요 내용에 대한 설명이다.

  • today: 현재 날짜를 나타내는 Date 객체이다.
  • year: getFullYear() 메서드를 사용하여 현재 연도를 가져온다.
  • month: getMonth() 메서드를 사용하여 현재 월을 가져온다. (월은 0부터 시작하므로 1을 더해준다.)
  • options 배열: 이전 4개월을 선택할 수 있는 <option> 요소를 생성한다. 반복문을 사용하여 현재 월로부터 1씩 감소하면서 이전 월을 생성한다. 각 월에 대한 <option> 요소는 해당 월을 값을 가지고 있다.
  • 렌더링: 연도와 월을 표시하고 이전 4개월을 선택할 수 있는 <select> 요소를 렌더링한다. 연도와 월은 <h1> 요소로 표시되고, <select> 요소는 이전 월을 선택할 수 있는 드롭다운 목록이다.

GetThisMonth 컴포넌트는 현재 연도와 월을 표시하고 이전 4개월을 선택할 수 있는 UI를 제공한다. 이전 월을 선택하면 해당 월의 값을 사용하여 다른 작업을 수행할 수 있다.




6. json

프로젝트 내에서는 JSON 파일을 생성하지 않고, 임시로 GitHub에 업로드된 링크를 사용하여 사용자 목록을 가져오는 방식으로 데이터베이스를 구성했다.

팀원들은 Firebase를 통해 서버에 데이터베이스를 업로드하고 다운로드할 수 있는 기능을 구축했고,

나 같은 경우는 남은 기능 구현, 문서 작성, 개인 공부 등으로 인해 시간이 부족하다고 판단하여, 간편한 방법으로 GitHub에 JSON 파일을 업로드한 후 사용하기로 결정했다.

GitHub에 JSON 파일을 업로드하고 사용하는 방법은 처음 시도하는 것이어서 조금 애를 먹기도 했지만, 흥미로운 경험이였다.

스크린샷 2023-05-16 오후 12.49.30.png

위의 사진은 내 깃허브에 있는 json 파일만 만들어둔 레파지토리이다.

아직은 하나밖에 없지만 앞으로 공부를 위해서 json 파일을 많이 만들 것 같아서 레파지토리를 하나 새로 만들었다 .




7. bestcode

내가 보았을 때, 제일 인상 깊은 코드는 TotalExcel 컴포넌트라고 생각한다.

해당 컴포넌트는 전체 엑셀 파일을 생성하고 다운로드하는 기능을 구현한다.

코드에서는 exportToExcel 함수를 정의하여 엑셀 파일을 생성하고 다운로드하는 로직을 처리한다.

테이블 데이터와 컬럼 데이터를 시트 데이터로 변환하고, XLSX 라이브러리를 사용하여 엑셀 워크북을 생성하고 시트를 추가한다.

마지막으로 XLSX.writeFile 함수를 호출하여 엑셀 파일을 다운로드한다.

컴포넌트는 Button 컴포넌트를 렌더링하여 사용자가 클릭할 수 있는 버튼을 생성한다. 이 버튼을 클릭하면 exportToExcel 함수가 호출되어 엑셀 파일이 생성되고 다운로드된다.

TotalExcel 컴포넌트는 재사용 가능하고 명확한 역할을 수행하며, 코드가 간결하게 작성되어 있다. 엑셀 파일 생성과 다운로드에 필요한 로직이 효율적으로 구현되어 있어서 인상깊고 잘 짠 코드라고 할 수 있습니다.

7. 이슈사항

개발 과정에서 useEffectfetch를 사용하여 JSON 파일의 데이터 목록을 먼저 불러와 테이블 형식으로 나타내는 작업을 수행했습니다. 그 다음으로는 reactstrap을 활용하여 날짜 검색 창과 직원 이름으로 검색할 수 있는 기능을 구현했습니다.

이러한 로직은 날짜와 이름이라는 명확한 기준점을 갖고 있어 해당 기준점을 중심으로 개발을 진행했습니다. 처음에는 두 기능이 유사하여 구현에 어려움을 겪었지만, 두 번째로 구현할 때는 원활하게 처리할 수 있었습니다.

또한 직원의 “이름”과 “유급 휴가 일수”를 오름차순과 내림차순으로 정렬하는 기능을 추가하여 당월에 유급 휴가를 사용한 직원들을 모아 볼 수 있도록 했습니다. 또한 사용자 목록을 선택하는 체크박스 UI를 구현하였고, 사용자를 선택하면 하단에 해당 사용자의 정보를 표시하거나 근태 정보를 엑셀 파일로 다운로드할 수 있는 버튼을 표시하고자 했습니다.

또한 하단에 페이지네이션 기능을 구현했는데, 외부 라이브러리를 사용하지 않고 버튼에 필요한 기능을 구현하여 적용했습니다.

그러나 이후 문제가 발생했습니다. 페이지네이션 기능을 구현한 후 검색 기능과 엑셀 다운로드 기능이 현재 페이지에 표시되는 사용자 정보를 올바르게 불러오지 못하는 문제가 발생했습니다. JSON 파일은 1부터 20까지의 사용자 목록을 순차적으로 나열한 형태이며, 페이지네이션 기능으로 인해 한 페이지에 10개의 목록만 볼 수 있도록 설정되어 있습니다.

예를 들어, 첫 번째 페이지에서 1번 사용자인 A를 선택했을 때는 정상적으로 정보가 나타났지만, 두 번째 페이지에서 1번 사용자인 B를 선택했을 때도 첫 번째 페이지의 1번 사용자인 A의 정보가 출력되는 문제가 발생했습니다.

기존의 checkbox안의 함수를 구현시에 페이지네이션 기능을 고려하지 않고 코드를 작성한 결과였다.

7-2 해결방법

const handleSortBy = (key) => {
    if (currentSortKey === key) {
      setSortDirection((prevSortDirection) =>
        prevSortDirection === "asc" ? "desc" : "asc"
      );

수정 전 코드

const handleSortBy = (key) => {
  let sortedUsers;
  if (sortDirection === "asc") {
    sortedUsers = [...currentPageData].sort((a, b) => (a[key] < b[key] ? 1 : -1));
    setSortDirection("desc");
  } else {
    sortedUsers = [...currentPageData].sort((a, b) => (a[key] > b[key] ? 1 : -1));
    setSortDirection("asc");
  }
  setCurrentPageData(sortedUsers);
};

수정 후 코드

해당 코드를 수정한 이유는 기존의 코드에서 setSortDirection 함수를 호출한 후에 바로 setUsers 함수를 호출하여 users 배열을 정렬하는 방식에서 문제가 있었기 때문이다.

기존 코드에서는 setSortDirection 함수를 호출한 이후에 users 배열을 정렬하는데 사용되는 sort 함수가 실행되기 때문에, 정렬된 배열이 반영되기 전에 setUsers 함수가 호출되었다.

따라서 수정된 코드에서는 sortedUsers라는 새로운 변수를 도입하여 현재 페이지에 해당하는 데이터인 currentPageData를 기준으로 정렬을

수행하고, 정렬된 결과를 setCurrentPageData 함수를 사용하여 현재 페이지에 반영한다.

이를 통해 정렬된 결과가 바로 현재 페이지에 반영되어 예기치 않은 동작을 방지할 수 있다.



8. 오류 사항

지금까지 개발하면서 오류 중에서 가장 많이 나왔던 오류는

“TypeError: Cannot read properties of undefined” 오류이다.

이 오류는 객체나 배열의 속성에 접근할 때 해당 객체나 배열이 정의되지 않았거나 null 또는 undefined일 경우 발생한다.

해당 오류는 주로 변수 또는 속성의 이름 오타, 변수 초기화 누락, 비동기 작업의 결과를 기다리지 않고 접근하는 등의 이유로 발생할 수 있는데

이 오류를 해결하기 위해서는 해당 변수나 속성이 정의되었는지, 초기화되었는지 확인하고 필요한 경우 조건문이나 에러 처리를 통해 안전하게 접근해야 해야할 것을 잊지 말자



9. 개선 사항

3차 프로젝트에서는 데이터베이스를 활용하여 실제 사용자들의 리스트를 추가 및 조회할 수 있는 기능을 구현하고자 한다.

또한, 기능에 따라 컴포넌트를 분리하여 재사용성을 높일 필요가 있을 것으로 판단된다.

Leave a comment