엑셀·CSV 업로드 운영 툴이 필요할 때

엑셀·CSV 데이터를 API로 업로드하고 미리보기까지 구현하는 방법. 서버 언어와 상관없이, UI는 셀렉트 어드민이 자동으로 구성합니다.

엑셀·CSV 업로드 운영 툴이 필요할 때

엑셀이나 CSV 파일로 받은 데이터를 빠르게 업로드하고 저장하는 기능.
운영툴을 만들다 보면 한 번은 꼭 필요해집니다.

이번 글에서는 셀렉트 어드민(Select Admin)으로
엑셀·CSV 업로드 → 변환 → 저장 → 미리보기까지 구현하는 방법을 소개합니다.

서버 언어는 상관없습니다.
Node.js, Python, Java 등 어떤 환경이든 API만 제공하면,
셀렉트 어드민이 UI를 자동으로 구성합니다.


왜 필요한 기능인가요?

운영팀은 종종 엑셀이나 CSV 데이터를 받아 임시로 시스템에 반입해야 합니다.
예를 들어 다음과 같은 경우입니다.

  • 재고 데이터를 엑셀로 받아 업로드해야 할 때
  • 파트너사에서 전달한 주문 데이터를 검증해야 할 때
  • CSV를 JSON 형태로 가공해 내부 API로 전달해야 할 때

이 예제는 그런 상황을 위한 “업로드 → 변환 → 저장 → 미리보기” 흐름 템플릿입니다.


작동 방식 한눈에 보기

  1. 엑셀 또는 CSV 파일을 업로드
  2. 지정된 컬럼(예: 상품명, 수량)으로 데이터 변환
  3. 지정한 API(/local/import/save-json)로 전송 및 저장
  4. 업로드된 파일 목록을 테이블로 표시
  5. 파일명을 클릭하면 상세 데이터를 모달로 미리보기

예제 코드 (셀렉트 어드민 YAML)

아래 YAML을 그대로 붙여넣으면,
파일 업로드 → 저장 → 목록 조회 → 상세 보기까지 자동으로 구성됩니다.

blocks:
  # 1) 파일 업로드 & JSON 저장
  - type: http
    name: 엑셀 업로드 → JSON 저장
    method: POST
    display: form
    params:
      - key: sheet
        label: 엑셀/CSV 업로드
        width: 400px
        format: sheet
        accept: .csv,.xlsx
        preview: true
        sheetOptions:
          multiple: true
          # ✅ 엑셀 시리얼날짜 자동 변환 (YYYY-MM-DD 형태)
          convertDate:
            - 시작일
            - 종료일
    fetchFn: |
      try {
        if (!Array.isArray(sheet) || sheet.length === 0) {
          throw new Error('업로드된 시트에 데이터가 없습니다.')
        }

        const rows = sheet.map(r => ({
          name: String(r['상품명'] ?? ''),
          unit: Number(r['수량'] ?? 0),
          ...(r['시작일'] ? { startDate: r['시작일'] } : {}),
          ...(r['종료일'] ? { endDate: r['종료일'] } : {})
        }))

        const base = API || 'http://localhost:9500'
        const res = await fetch(`${base}/local/import/save-json`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ payload: { rows } })
        })

        if (!res.ok) throw new Error(`HTTP ${res.status}`)
        await res.json().catch(() => ({}))

        $toast(`저장 완료 (${rows.length}행)`)        
        return true
      } catch (err) {
        $toast(String(err?.message || err), { type: 'error' })
        return false
      }

  # 1-1) 업로드 양식 다운로드
  - type: http
    method: GET
    fetchFn: |
      return [
        {
          "id": "",
          "상품명": "",
          "수량": "",
          "시작일": "",
          "종료일": ""
        }
      ]
    showDownload: false
    tableOptions:
      hidden: true
    actions:
      - label: 업로드 양식 다운로드
        button:
          icon: mdi-download
        showDownload: csv xlsx
        single: true

  # 2) 업로드된 파일 목록
  - type: http
    name: 업로드 파일 목록
    method: GET
    display: table
    showDownload: false
    fetchFn: |
      try {
        const base = API || 'http://localhost:9500'
        const r = await fetch(`${base}/local/import/list`)
        if (!r.ok) throw new Error(`HTTP ${r.status}`)
        const data = await r.json().catch(() => ({}))

        return (data.files || []).map(f => ({
          file: f.file,
          size: f.size,
          mtime: new Date(f.mtime).toISOString()
        }))
      } catch (err) {
        $toast(`파일 목록을 불러오지 못했습니다: ${String(err?.message || err)}`, { type: 'error' })
        return []
      }
    columns:
      file:
        width: 520px
        openModal: file-detail-:file
      size:
        formatFn: |
          (value > 1024*1024)
            ? (value/1024/1024).toFixed(1) + ' MB'
            : (value/1024).toFixed(1) + ' KB'
      mtime:
        width: 220px

    # 3) 파일 상세 보기 모달
    modals:
      - path: file-detail-:file
        width: 800px
        title: 파일 내용 보기
        blocks:
          - type: http
            name: 파일 데이터
            method: GET
            fetchFn: |
              try {
                const base = API || 'http://localhost:9500'
                const url = new URL(`${base}/local/import/read`)
                url.searchParams.set('file', file)

                const r = await fetch(url)
                if (!r.ok) throw new Error(`HTTP ${r.status}`)
                const data = await r.json().catch(() => ({}))

                return data.rows || []
              } catch (err) {
                $toast(`파일을 불러오지 못했습니다: ${String(err?.message || err)}`, { type: 'error' })
                return []
              }
            columns:
              name: { width: 300px }
              unit: { width: 120px }
              startDate:
                width: 140px
                formatFn: date
              endDate:
                width: 140px
                formatFn: date
            params:
              - key: file
                valueFromRow: file
            showDownload: csv xlsx

이런 상황에 유용합니다

  • 파트너사 데이터를 업로드 후 바로 검증해야 할 때
  • 사내에서 CSV → JSON 변환 플로우를 빠르게 구성해야 할 때
  • 로컬 API만 있는 환경에서 UI를 즉시 띄워보고 싶을 때

서버 코드도 빠르게 만들고 싶다면

위 YAML을 기반으로 테스트용 서버 코드까지 빠르게 만들고 싶다면,
LLM에게 아래처럼 요청해보세요.

환경:
- Node.js 20
- "type": "module" (ESM)
- Express

요청:
아래 YAML 스펙에 맞는 API 서버 코드를 작성해주세요.
(프론트엔드가 이 YAML을 기반으로 API를 호출합니다)

<여기에 YAML 코드 붙여넣기>

마무리하며

이 예제는 엑셀·CSV 데이터를 업로드 → 저장 → 조회 → 미리보기까지
한 번에 연결하는 완결된 흐름을 보여줍니다.

서버는 단지 API만 제공하면 됩니다.
UI 구성은 셀렉트 어드민이 자동으로 처리합니다.

복잡한 설정 없이 바로 복사해 실행해 보세요.
운영툴의 첫 페이지를, 오늘 안에 완성할 수 있습니다.