## 역할
인증된 사용자 전용 업무 페이지 영역. 영업, 생산, 구매, 재고, 품질, 비용 등 70개 이상의 업무 페이지를 포함. 공통 레이아웃(Header + Sidebar + Content)으로 감싸진 대시보드 시스템.

## 공통 패턴

### 페이지 구조
모든 페이지는 `"use client"` 클라이언트 컴포넌트. 구성:
1. 제목: `<h2 className="text-lg font-bold text-gray-800 mb-4">{제목}</h2>`
2. 검색 폼: `SearchForm` + `SearchField` + `CodeSelect`/`Input`
3. 버튼 영역: 조회, 등록, 삭제 등
4. 데이터 그리드: `DataGrid` + `Pagination`

### 데이터 조회
```tsx
const fetchData = useCallback(async () => {
  const res = await fetch("/api/{domain}/{resource}", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ year, category_cd, ... }),
  });
  if (res.ok) {
    const json = await res.json();
    setData(json.RESULTLIST || []);
  }
}, [dependencies]);
```

### 팝업 처리
```tsx
window.open(url, "formPopup", "width=1200,height=550,left=...,top=...");
```

### 행 삭제
```tsx
if (selectedRows.length === 0) {
  Swal.fire({ icon: "warning", title: "항목을 선택하세요." });
  return;
}
```

## 연결 고리
- API: `POST /api/{domain}/{resource}` (RESULTLIST 응답)
- Store: `useAuthStore()` (user, logout), `useMenuStore()` (메뉴 상태)
- 컴포넌트: DataGrid, SearchForm, SearchField, CodeSelect, Input, Button, Pagination
- 유틸: `numberWithCommas()`, `cn()`

## 숨겨진 스펙
- 데이터 타입: `Record<string, unknown>[]`
- DataGrid 높이: `calc(100vh - 350px)` 패턴 (페이지별 350~400px 차감)
- 년도 필터: `Array.from({ length: 5 }, (_, i) => new Date().getFullYear() - i)`
- 셀 클릭: `col.cellClick` 콜백으로 상세 폼 팝업
- formatter `"money"` → 통화 형식
- JSP 원본 주석: `// salesMgmt/salesMgmtContractList.jsp 대응`
- 에러처리 미비: `if (res.ok)` 체크만, try-catch 없는 경우 많음

@MISTAKES.md
