FE - csv file compile

IT쿠키·2024년 12월 29일
post-thumbnail

csv file 내용 compile 시키기

  1. csv 파일을 읽어서 오류가 있는 항목들을 걸러내고, 필요한 내용이 있는 항목에 대해서 계산 결과를 나오게 해주면 된다.
  2. 숫자에 해당하는 값만 계산 결과를 출력하고 숫자가 아닌 값이 하나라도 존재하면 해당 행에
    대해서는 계산 결과를 출력하지 않는다.
  3. 출력 후 숫자가 아닌 값들만 모아서 출력해본다.

해당 csv 파일에 대한 row 열이 front에서는 처리하기 힘들다는 걸 알게됨
1. nextjs Route Handlers 를 활용해서 처리하기로함
-> 해당 csv 파일 컴파일시 url query로 보내려고 했지만 해당 query열이 비정상적으로 길어진다는 걸 깨닫게됨
-> 그러므로 put이나 post로 request body로 보내야 활용이 좋다고 생각하게됨

// GET 메서드 사용 시
export async function GET(request: Request) {
  // URL에서 파일 데이터를 쿼리 파라미터로 받아야 함
  // 큰 파일의 경우 URL 길이 제한으로 문제 발생
}

// PUT 메서드 사용 시
export async function PUT(request: Request) {
  // 리소스를 완전히 대체할 때 사용
  // 파일 업로드에도 사용 가능하지만 POST가 더 일반적
}
// POST 메서드 사용시 
export async function POST(request: Request) {
  // 대용량 파일 및 업로드에는 POST가 일반적
}

csv 파일 형식이 길기 때문에 json 보다는 원본 그대로 읽을 수 있는
// CSV 파일의 원본 텍스트 내용을 그대로 받음
// 예시: "1,2,3\n4,5,6\n7,8,9"

import { NextResponse } from "next/server";
import Papa from "papaparse";

interface StatisticalResult {
  minVal: number;
  maxVal: number;
  total: number;
  mean: number;
  stdev: number;
  median: number;
}

interface ParseResult {
  data: string[][];
}

/**
 * CSV 파일 처리 API 엔드포인트
 * @param {Request} request CSV 파일 데이터
 * @returns {Promise<NextResponse>} 처리된 결과와 숫자가 아닌 문자들
 */
export async function POST(request: Request): Promise<NextResponse> {
  const text = await request.text();
  // request.text() 활용
  const results: StatisticalResult[] = [];
  // 결과값 노출
  const nonNumericChars: string[] = [];
  // 해당 문자열인 경우 문자열 저장

  //import Papa from "papaparse"; -> csv file 컴파일러 활용 라이  브러리
  Papa.parse(text, {
    complete: (result: ParseResult) => {
      result.data.forEach((row: string[]) => {
        if (row.length === 0) return;
		-> row 가 없는 경우 return
        // 각 행에서 숫자가 아닌 문자들만 필터링
        const nonNumericInRow = row.filter((value: string) => {
          const trimmed = value.trim();
          // 공백제거  문자열 양 끝의 공백을 제거하면서 원본 문자열을 수정하지 않고 새로운 문자열을 반환합니다.
          const num = Number(trimmed);
          return isNaN(num) || trimmed === "";
        });

        // 숫자가 아닌 문자가 있다면 저장
        if (nonNumericInRow.length > 0) {
          nonNumericChars.push(...nonNumericInRow);
        }

        // 모든 값이 숫자인 경우에만 통계 계산
        const isValidRow = row.every((value: string) => {
          const num = Number(value.trim());
          return !isNaN(num) && value.trim() !== "";
        });

        if (isValidRow) {
          const numbers = row.map(Number);
          // 최소값 계산
          const minVal = Math.min(...numbers);
          // 최댓갑 계산
          const maxVal = Math.max(...numbers);
          // total 종합값 계산
          const total = numbers.reduce((a, b) => a + b, 0);
          // 평균값 계산 total / numbers.length 
          const mean = total / numbers.length;
          // 표준편차 (standard deviation)
          const stdev = Math.sqrt(
            numbers
              .map(x => Math.pow(x - mean, 2))    // 각 값에서 평균을 빼고 제곱
              .reduce((a, b) => a + b)            // 제곱값들의 합계
              / numbers.length                     // 개수로 나누고
          );                                      // 제곱근 구하기
          // 중간값 구하기
          const median = numbers.sort((a, b) => a - b)[
            Math.floor(numbers.length / 2)
          ];

          results.push({ minVal, maxVal, total, mean, stdev, median });
        }
      });
    },
    header: false,
    skipEmptyLines: true,
  });

  return NextResponse.json({
    results,
    nonNumericChars: [...new Set(nonNumericChars)], // 중복 제거
  });
}

이게 router handler를 활용해서 작업

client 단

"use client";

import ResultsDisplay, { ITEMS_PER_PAGE } from "./ResultsDisplay";

import NonNumericRowsDisplay from "./NonNumericRowsDisplay";
import { Result } from "@/types/csv";
import { useState } from "react";

const Brick1 = () => {
  const [results, setResults] = useState<Result[]>([]);
  const [nonNumericChars, setNonNumericChars] = useState<string[]>([]);
  const [currentPage, setCurrentPage] = useState(0);

  const handleFileChange = async (
    event: React.ChangeEvent<HTMLInputElement>
  ) => {
    const file = event.target.files?.[0];
    if (!file) return;

    const reader = new FileReader();
    reader.onload = async (e) => {
      const text = e.target?.result;
      if (typeof text === "string") {
        const response = await fetch("/api/upload", {
          method: "POST",
          headers: {
            "Content-Type": "text/plain",
          },
          body: text,
        });

        const data = await response.json();
        setResults(data.results);
        setNonNumericChars(data.nonNumericChars);
        setCurrentPage(0);
      }
    };
    reader.readAsText(file);
  };

  const handleNextPage = () => {
    if ((currentPage + 1) * ITEMS_PER_PAGE < results.length) {
      setCurrentPage(currentPage + 1);
    }
  };

  const handlePrevPage = () => {
    if (currentPage > 0) {
      setCurrentPage(currentPage - 1);
    }
  };
  return (
    <div>
      <h1>CSV Processing</h1>
      <input type="file" accept=".csv" onChange={handleFileChange} />
      <ResultsDisplay results={results} currentPage={currentPage} />
      <button onClick={handlePrevPage} disabled={currentPage === 0}>
        Previous
      </button>
      <button
        onClick={handleNextPage}
        disabled={(currentPage + 1) * ITEMS_PER_PAGE >= results.length}>
        Next
      </button>
      <NonNumericRowsDisplay nonNumericChars={nonNumericChars} />
    </div>
  );
};

export default Brick1;
profile
IT 삶을 사는 쿠키

0개의 댓글