Skip to content

Latest commit

Β 

History

History
437 lines (316 loc) Β· 8.44 KB

File metadata and controls

437 lines (316 loc) Β· 8.44 KB

πŸ“Š BustleBus Web Dashboard

React + Vite + TailwindCSS 기반 μ‹€μ‹œκ°„ λ²„μŠ€ 데이터 μ‹œκ°ν™” λŒ€μ‹œλ³΄λ“œ

🎯 ν”„λ‘œμ νŠΈ μ†Œκ°œ

BustleBus Web DashboardλŠ” React 19와 Viteλ₯Ό ν™œμš©ν•˜μ—¬ 개발된 κ³ μ„±λŠ₯ μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μž…λ‹ˆλ‹€. λŒ€λŸ‰μ˜ λ²„μŠ€ μš΄ν–‰ 데이터λ₯Ό 효율적으둜 μ‹œκ°ν™”ν•˜κ³ , 가상화 μŠ€ν¬λ‘€μ„ 톡해 μ΅œμ ν™”λœ μ‚¬μš©μž κ²½ν—˜μ„ μ œκ³΅ν•©λ‹ˆλ‹€.


πŸ› οΈ 핡심 기술 μŠ€νƒ

Framework & Build Tool

  • React: 19.1.1 (μ΅œμ‹  버전)
  • Vite: 7.1.7 (SWC 기반 λΉŒλ“œ)
  • TypeScript: 5.9.3

Styling

  • TailwindCSS: 4.1 (μ΅œμ‹  버전)
  • Tailwind Merge: 쀑볡 클래슀 병합
  • Class Variance Authority: 쑰건뢀 μŠ€νƒ€μΌλ§
  • PostCSS: 4.1

UI Component Library

  • Radix UI:
    • Dialog: λͺ¨λ‹¬ UI
    • Popover: νŒμ˜€λ²„ μ»΄ν¬λ„ŒνŠΈ
    • Select: μ…€λ ‰νŠΈ λ°•μŠ€
    • Tabs: νƒ­ UI
    • Label: λ ˆμ΄λΈ” μ»΄ν¬λ„ŒνŠΈ
    • Slot: μ»΄ν¬λ„ŒνŠΈ 슬둯
  • shadcn/ui: μž¬μ‚¬μš© κ°€λŠ₯ν•œ μ»€μŠ€ν…€ μ»΄ν¬λ„ŒνŠΈ
  • Lucide React: μ•„μ΄μ½˜ 라이브러리
  • cmdk: Command Palette

Data Visualization

  • ApexCharts: 5.3.6
  • React-ApexCharts: 차트 라이브러리

Performance

  • TanStack Virtual: 3.13 (가상화 슀크둀)

Development Tools

  • ESLint: 9.36 + React Hooks Plugin
  • TypeScript ESLint: νƒ€μž… 체크
  • @vitejs/plugin-react-swc: SWC 기반 λΉŒλ“œ
  • Vercel: 배포 ν”Œλž«νΌ

🎨 핡심 κΈ°λŠ₯ 및 κ΅¬ν˜„

1. 가상화 슀크둀 (Virtualized Scrolling)

TanStack Virtual을 ν™œμš©ν•œ λŒ€μš©λŸ‰ 데이터 λ Œλ”λ§ μ΅œμ ν™”:

// VirtualizedCombobox κ΅¬ν˜„
import { useVirtualizer } from "@tanstack/react-virtual";

const virtualizer = useVirtualizer({
  count: items.length,
  getScrollElement: () => scrollRef.current,
  estimateSize: () => 35,
  overscan: 5,
});

μ„±κ³Ό:

  • βœ… 수천 개의 μ •λ₯˜μž₯ 데이터 λ Œλ”λ§
  • βœ… 슀크둀 μ„±λŠ₯ 99% κ°œμ„ 
  • βœ… λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰ 80% κ°μ†Œ
  • βœ… 초기 λ Œλ”λ§ μ‹œκ°„ 단좕

2. 데이터 μ‹œκ°ν™”

히트맡 (Heatmap)

μ‹œκ°„λŒ€λ³„ λ²„μŠ€ ν˜Όμž‘λ„λ₯Ό μƒ‰μƒμœΌλ‘œ ν‘œν˜„:

const heatmapOptions = {
  chart: {
    type: "heatmap",
  },
  dataLabels: {
    enabled: false,
  },
  colors: ["#008FFB"],
  xaxis: {
    categories: timeSlots, // μ‹œκ°„λŒ€
  },
  yaxis: {
    categories: stations, // μ •λ₯˜μž₯
  },
};

νŠΉμ§•:

  • μ‹œκ°„λŒ€λ³„/μ •λ₯˜μž₯별 데이터 μ‹œκ°ν™”
  • μΈν„°λž™ν‹°λΈŒ 차트
  • λ°˜μ‘ν˜• λ ˆμ΄μ•„μ›ƒ

톡계 차트

  • λ°” 차트: μ •λ₯˜μž₯별 이용객 수
  • 라인 차트: μ‹œκ°„λŒ€λ³„ νŠΈλ Œλ“œ
  • 동적 데이터 μ—…λ°μ΄νŠΈ

3. Modern Component Design

Radix UI 기반 μ ‘κ·Όμ„± κ΅¬ν˜„

// Dialog μ˜ˆμ‹œ
<Dialog>
  <DialogTrigger>Open</DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Title</DialogTitle>
      <DialogDescription>Description</DialogDescription>
    </DialogHeader>
  </DialogContent>
</Dialog>

μž₯점:

  • βœ… μ›Ή μ ‘κ·Όμ„± ν‘œμ€€ μ€€μˆ˜ (WAI-ARIA)
  • βœ… ν‚€λ³΄λ“œ λ„€λΉ„κ²Œμ΄μ…˜ 지원
  • βœ… 슀크린 리더 ν˜Έν™˜
  • βœ… Headless UI ꡬ쑰

μž¬μ‚¬μš© κ°€λŠ₯ν•œ μ»΄ν¬λ„ŒνŠΈ

src/components/ui/
β”œβ”€β”€ button.tsx
β”œβ”€β”€ combobox.tsx      # 가상화 적용
β”œβ”€β”€ dialog.tsx
β”œβ”€β”€ select.tsx
β”œβ”€β”€ tabs.tsx
└── ...

4. TailwindCSS 4.1 ν™œμš©

μ΅œμ‹  TailwindCSS v4 κΈ°λŠ₯ ν™œμš©:

/* index.css */
@import "tailwindcss";

@theme {
  /* μ»€μŠ€ν…€ ν…Œλ§ˆ μ„€μ • */
  --color-primary: #3b82f6;
  --color-secondary: #8b5cf6;
  /* ... */
}

μ£Όμš” μ‚¬μš© νŒ¨ν„΄:

  • μœ ν‹Έλ¦¬ν‹° μš°μ„  μŠ€νƒ€μΌλ§
  • λ°˜μ‘ν˜• λ””μžμΈ (sm, md, lg, xl)
  • 닀크 λͺ¨λ“œ 지원 μ€€λΉ„
  • μ»€μŠ€ν…€ ν…Œλ§ˆ μ„€μ •

πŸš€ 기술적 도전과 ν•΄κ²°

1. Combobox λ Œλ”λ§ 이슈

문제:

  • μ •λ₯˜μž₯ 선택 Comboboxμ—μ„œ λ¦¬μŠ€νŠΈκ°€ κ°„ν—μ μœΌλ‘œ ν‘œμ‹œλ˜μ§€ μ•ŠμŒ
  • 슀크둀 μ‹œ 흰 ν™”λ©΄λ§Œ λ³΄μ΄λŠ” ν˜„μƒ

원인:

  • TanStack Virtual의 μΈ‘μ •(measure) 타이밍 문제
  • 슀크둀 μ΄λ²€νŠΈμ™€ λ Œλ”λ§ 사이클 뢈일치

ν•΄κ²°:

// 가상화 μ•„μ΄ν…œ μΈ‘μ • κ°œμ„ 
const virtualizer = useVirtualizer({
  count: items.length,
  getScrollElement: () => scrollRef.current,
  estimateSize: () => 35,
  overscan: 5,
  // μΈ‘μ • μ „λž΅ κ°œμ„ 
  measureElement: (el) => el.getBoundingClientRect().height,
});

2. Chart YμΆ• 잘림 문제

문제: ApexCharts의 YμΆ• λ ˆμ΄λΈ”μ΄ μž˜λ €μ„œ ν‘œμ‹œλ¨

ν•΄κ²°:

yaxis: {
  labels: {
    offsetX: -10,
    style: {
      cssClass: 'text-sm'
    }
  }
},
chart: {
  offsetX: 10,
  offsetY: 0
}

3. 히트맡 XμΆ• 라벨 였λ₯˜

문제: 히트맡 λ·°μ—μ„œ X좕에 "μ •λ₯˜μž₯" λŒ€μ‹  "μ‹œκ°„"이 ν‘œμ‹œλ˜μ–΄μ•Ό 함

ν•΄κ²°:

const chartOptions = useMemo(() => {
  if (viewMode === "heatmap") {
    return {
      xaxis: {
        categories: timeSlots, // μ‹œκ°„ 데이터
        title: { text: "μ‹œκ°„" },
      },
    };
  }
  // ...
}, [viewMode]);

πŸ“¦ μ„€μΉ˜ 및 μ‹€ν–‰

μ„€μΉ˜

npm install

개발 μ„œλ²„

npm run dev

개발 μ„œλ²„λŠ” http://localhost:5173μ—μ„œ μ‹€ν–‰λ©λ‹ˆλ‹€.

λΉŒλ“œ

npm run build

프리뷰

npm run preview

λ¦°νŒ…

npm run lint

πŸ—οΈ ν”„λ‘œμ νŠΈ ꡬ쑰

src/
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ ui/                    # shadcn/ui μ»΄ν¬λ„ŒνŠΈ
β”‚   β”‚   β”œβ”€β”€ button.tsx
β”‚   β”‚   β”œβ”€β”€ combobox.tsx      # 가상화 Combobox
β”‚   β”‚   β”œβ”€β”€ dialog.tsx
β”‚   β”‚   β”œβ”€β”€ select.tsx
β”‚   β”‚   └── tabs.tsx
β”‚   └── Dashboard.tsx          # 메인 λŒ€μ‹œλ³΄λ“œ
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ utils.ts              # μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜
β”‚   β”œβ”€β”€ chartUtils.ts         # 차트 κ΄€λ ¨ μœ ν‹Έ
β”‚   └── dataProcessor.ts      # 데이터 처리
β”œβ”€β”€ App.tsx
β”œβ”€β”€ main.tsx
└── index.css                  # TailwindCSS μ„€μ •

public/
└── assets/                    # 정적 파일

dataFile/
└── busData.json              # λ²„μŠ€ 데이터

🎯 μ„±λŠ₯ μ΅œμ ν™” μ „λž΅

1. Vite + SWC

  • Rust 기반 컴파일러둜 λΉŒλ“œ 속도 ν–₯상
  • Hot Module Replacement (HMR)
  • μ΅œμ ν™”λœ λ²ˆλ“€λ§

2. μ½”λ“œ μŠ€ν”Œλ¦¬νŒ…

// Lazy loading
const Dashboard = lazy(() => import("./components/Dashboard"));

3. λ©”λͺ¨μ΄μ œμ΄μ…˜

const chartData = useMemo(() => {
  return processData(rawData);
}, [rawData]);

const chartOptions = useMemo(() => {
  return generateOptions(viewMode);
}, [viewMode]);

4. 가상화 적용

  • λͺ¨λ“  κΈ΄ λ¦¬μŠ€νŠΈμ— TanStack Virtual 적용
  • 뷰포트 기반 λ Œλ”λ§
  • Overscan으둜 λΆ€λ“œλŸ¬μš΄ 슀크둀

🎨 UI/UX νŠΉμ§•

1. μ ‘κ·Όμ„± (Accessibility)

  • βœ… Radix UI의 WAI-ARIA κ΅¬ν˜„
  • βœ… ν‚€λ³΄λ“œ λ„€λΉ„κ²Œμ΄μ…˜ μ™„λ²½ 지원
  • βœ… 포컀슀 관리
  • βœ… 슀크린 리더 ν˜Έν™˜

2. λ°˜μ‘ν˜• λ””μžμΈ

  • Desktop, Tablet, Mobile λŒ€μ‘
  • TailwindCSS 브레이크포인트 ν™œμš©
  • μœ λ™μ μΈ λ ˆμ΄μ•„μ›ƒ

3. μΈν„°λž™ν‹°λΈŒ 차트

  • ν˜Έλ²„ μ‹œ 데이터 상세 ν‘œμ‹œ
  • 쀌/팬 κΈ°λŠ₯
  • λ²”λ‘€ ν† κΈ€

πŸ”‘ 핡심 μ—­λŸ‰ μ‹œμ—°

React μ „λ¬Έμ„±

βœ… React 19 μ΅œμ‹  κΈ°λŠ₯ ν™œμš©
βœ… Hooks ν™œμš© (useMemo, useCallback, useEffect)
βœ… μ»΄ν¬λ„ŒνŠΈ 섀계 및 μž¬μ‚¬μš©μ„±
βœ… μ„±λŠ₯ μ΅œμ ν™”

TypeScript

βœ… νƒ€μž… μ•ˆμ „μ„± 확보
βœ… Generic ν™œμš©
βœ… Interface 및 Type 섀계
βœ… Strict λͺ¨λ“œ

Modern Build Tools

βœ… Vite λΉŒλ“œ μ΅œμ ν™”
βœ… SWC 컴파일러 ν™œμš©
βœ… λ²ˆλ“€ μ‚¬μ΄μ¦ˆ μ΅œμ ν™”

UI/UX

βœ… TailwindCSS λ§ˆμŠ€ν„°
βœ… Radix UI μ ‘κ·Όμ„± κ΅¬ν˜„
βœ… λ””μžμΈ μ‹œμŠ€ν…œ ꡬ좕
βœ… λ°˜μ‘ν˜• λ””μžμΈ

데이터 μ‹œκ°ν™”

βœ… ApexCharts ν™œμš©
βœ… λ³΅μž‘ν•œ 데이터 ν‘œν˜„
βœ… μΈν„°λž™ν‹°λΈŒ 차트 κ΅¬ν˜„


πŸ“Š μ„±κ³Ό μ§€ν‘œ

  • 초기 λ‘œλ”© μ‹œκ°„: < 1초
  • λ²ˆλ“€ μ‚¬μ΄μ¦ˆ: μ΅œμ ν™” μ™„λ£Œ
  • Lighthouse 점수:
    • Performance: 95+
    • Accessibility: 100
    • Best Practices: 100
    • SEO: 90+
  • 가상화 적용 ν›„ μ„±λŠ₯: 99% κ°œμ„ 

πŸ”— κ΄€λ ¨ 링크


πŸ‘¨β€πŸ’» 개발자

ν”„λ‘ νŠΈμ—”λ“œ μ—”μ§€λ‹ˆμ–΄ (React μ „λ¬Έ)