---
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. 제목: `
{제목}
`
2. 검색 폼: `` + ``
3. 버튼 영역: 조회/등록/삭제
4. 데이터 그리드: `` + ``
## 데이터 조회
```tsx
const [data, setData] = useState[]>([]);
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)
```