---
globs: src/app/(main)/**/*.tsx, src/app/(auth)/**/*.tsx, src/app/admin-panel/**/*.tsx
---

# 페이지 코딩 규칙

## 기본 구조
모든 페이지는 `"use client"` 클라이언트 컴포넌트:
```tsx
"use client";

import { useState, useCallback } from "react";
import { DataGrid, GridColumn } from "@/components/grid/data-grid";
import { SearchForm, SearchField } from "@/components/layout/search-form";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { CodeSelect } from "@/components/ui/code-select";
import Swal from "sweetalert2";
```

## 페이지 레이아웃 순서
1. 제목: `<h2 className="text-lg font-bold text-gray-800 mb-4">{제목}</h2>`
2. 검색 폼: `<SearchForm>` + `<SearchField>`
3. 버튼 영역: 조회/등록/삭제
4. 데이터 그리드: `<DataGrid>` + `<Pagination>`

## 데이터 조회
```tsx
const [data, setData] = useState<Record<string, unknown>[]>([]);

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 || []);
  }
}, [year, category_cd]);
```

## 팝업
```tsx
const left = (window.screen.width - 1200) / 2;
const top = (window.screen.height - 550) / 2;
window.open(url, "formPopup", `width=1200,height=550,left=${left},top=${top}`);
```

## 삭제 확인
```tsx
if (selectedRows.length === 0) {
  Swal.fire("알림", "삭제할 항목을 선택하세요.", "warning");
  return;
}
```

## DataGrid 높이
페이지별 `calc(100vh - 350px)` ~ `calc(100vh - 400px)` 사용.

## 년도 필터 (자주 사용)
```tsx
Array.from({ length: 5 }, (_, i) => new Date().getFullYear() - i)
```
