From f8dbc6b2aee89feb1fc5c27c669e1ba0d1fdbcf1 Mon Sep 17 00:00:00 2001 From: byeongcheolryu Date: Tue, 30 Dec 2025 21:56:01 +0900 Subject: [PATCH] =?UTF-8?q?feat(WEB):=20=EB=8F=99=EC=A0=81=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=ED=8C=90,=20=ED=8C=8C=ED=8A=B8=EB=84=88=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC,=20=EA=B3=B5=EC=A7=80=20=ED=8C=9D=EC=97=85=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 동적 게시판 시스템 구현 (/boards/[boardCode]) - 파트너 관리 페이지 및 폼 추가 - 공지 팝업 모달 컴포넌트 (NoticePopupModal) - localStorage 기반 1일간 숨김 기능 - 테스트 페이지 (/test/popup) - IntegratedListTemplateV2 개선 - 기타 버그 수정 및 타입 개선 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- claudedocs/[REF] juil-pages-test-urls.md | 2 + claudedocs/_index.md | 3 +- ...IMPL-2025-12-30] dynamic-board-creation.md | 120 +++ ...-30] partner-management-session-context.md | 101 +++ .../board/board-management/[id]/edit/page.tsx | 3 + .../board/board-management/new/page.tsx | 3 + .../boards/[boardCode]/[postId]/edit/page.tsx | 250 ++++++ .../boards/[boardCode]/[postId]/page.tsx | 411 +++++++++ .../boards/[boardCode]/create/page.tsx | 162 ++++ .../(protected)/boards/[boardCode]/page.tsx | 398 +++++++++ .../juil/project/bidding/estimates/page.tsx | 5 + .../bidding/partners/[id]/edit/page.tsx | 19 + .../project/bidding/partners/[id]/page.tsx | 19 + .../project/bidding/partners/new/page.tsx | 5 + .../bidding/site-briefings/[id]/edit/page.tsx | 18 + .../bidding/site-briefings/[id]/page.tsx | 18 + .../bidding/site-briefings/new/page.tsx | 5 + .../project/bidding/site-briefings/page.tsx | 5 + .../[locale]/(protected)/test/popup/page.tsx | 134 +++ .../board/BoardManagement/BoardForm.tsx | 43 +- .../board/BoardManagement/actions.ts | 24 +- src/components/board/BoardManagement/types.ts | 8 +- src/components/board/DynamicBoard/actions.ts | 371 ++++++++ .../juil/estimates/EstimateListClient.tsx | 569 ++++++++++++ .../business/juil/estimates/actions.ts | 288 ++++++ .../business/juil/estimates/index.ts | 3 + .../business/juil/estimates/types.ts | 107 +++ .../business/juil/partners/PartnerForm.tsx | 835 ++++++++++++++++++ .../juil/partners/PartnerListClient.tsx | 34 +- .../business/juil/partners/actions.ts | 310 +++++-- .../business/juil/partners/index.ts | 1 + .../business/juil/partners/types.ts | 219 ++++- .../juil/site-briefings/SiteBriefingForm.tsx | 687 ++++++++++++++ .../site-briefings/SiteBriefingListClient.tsx | 616 +++++++++++++ .../business/juil/site-briefings/actions.ts | 257 ++++++ .../business/juil/site-briefings/index.ts | 4 + .../business/juil/site-briefings/types.ts | 258 ++++++ .../NoticePopupModal/NoticePopupModal.tsx | 169 ++++ .../common/NoticePopupModal/index.ts | 2 + .../customer-center/shared/types.ts | 4 +- src/components/organisms/StatCards.tsx | 14 +- .../templates/IntegratedListTemplateV2.tsx | 2 + tsconfig.tsbuildinfo | 2 +- 43 files changed, 6395 insertions(+), 113 deletions(-) create mode 100644 claudedocs/board/[IMPL-2025-12-30] dynamic-board-creation.md create mode 100644 claudedocs/juil/[NEXT-2025-12-30] partner-management-session-context.md create mode 100644 src/app/[locale]/(protected)/boards/[boardCode]/[postId]/edit/page.tsx create mode 100644 src/app/[locale]/(protected)/boards/[boardCode]/[postId]/page.tsx create mode 100644 src/app/[locale]/(protected)/boards/[boardCode]/create/page.tsx create mode 100644 src/app/[locale]/(protected)/boards/[boardCode]/page.tsx create mode 100644 src/app/[locale]/(protected)/juil/project/bidding/estimates/page.tsx create mode 100644 src/app/[locale]/(protected)/juil/project/bidding/partners/[id]/edit/page.tsx create mode 100644 src/app/[locale]/(protected)/juil/project/bidding/partners/[id]/page.tsx create mode 100644 src/app/[locale]/(protected)/juil/project/bidding/partners/new/page.tsx create mode 100644 src/app/[locale]/(protected)/juil/project/bidding/site-briefings/[id]/edit/page.tsx create mode 100644 src/app/[locale]/(protected)/juil/project/bidding/site-briefings/[id]/page.tsx create mode 100644 src/app/[locale]/(protected)/juil/project/bidding/site-briefings/new/page.tsx create mode 100644 src/app/[locale]/(protected)/juil/project/bidding/site-briefings/page.tsx create mode 100644 src/app/[locale]/(protected)/test/popup/page.tsx create mode 100644 src/components/board/DynamicBoard/actions.ts create mode 100644 src/components/business/juil/estimates/EstimateListClient.tsx create mode 100644 src/components/business/juil/estimates/actions.ts create mode 100644 src/components/business/juil/estimates/index.ts create mode 100644 src/components/business/juil/estimates/types.ts create mode 100644 src/components/business/juil/partners/PartnerForm.tsx create mode 100644 src/components/business/juil/site-briefings/SiteBriefingForm.tsx create mode 100644 src/components/business/juil/site-briefings/SiteBriefingListClient.tsx create mode 100644 src/components/business/juil/site-briefings/actions.ts create mode 100644 src/components/business/juil/site-briefings/index.ts create mode 100644 src/components/business/juil/site-briefings/types.ts create mode 100644 src/components/common/NoticePopupModal/NoticePopupModal.tsx create mode 100644 src/components/common/NoticePopupModal/index.ts diff --git a/claudedocs/[REF] juil-pages-test-urls.md b/claudedocs/[REF] juil-pages-test-urls.md index f76a8820..703c1606 100644 --- a/claudedocs/[REF] juil-pages-test-urls.md +++ b/claudedocs/[REF] juil-pages-test-urls.md @@ -16,6 +16,8 @@ Last Updated: 2025-12-30 | 페이지 | URL | 상태 | |---|---|---| | **거래처 관리** | `/ko/juil/project/bidding/partners` | ✅ 완료 | +| **현장설명회관리** | `/ko/juil/project/bidding/site-briefings` | ✅ 완료 | +| **견적관리** | `/ko/juil/project/bidding/estimates` | 🆕 NEW | ## 공사 관리 (Construction) ### 인수인계 / 실측 / 발주 / 시공 diff --git a/claudedocs/_index.md b/claudedocs/_index.md index 15bf9e85..3f8fda2c 100644 --- a/claudedocs/_index.md +++ b/claudedocs/_index.md @@ -214,7 +214,8 @@ claudedocs/ | 파일 | 설명 | |------|------| -| `[REF] juil-project-structure.md` | 🔴 **NEW** - 주일 프로젝트 구조 가이드 (경로, 컴포넌트, 테스트 URL) | +| `[NEXT-2025-12-30] partner-management-session-context.md` | ⭐ **세션 체크포인트** - 거래처 관리 리스트 완료, 등록/상세/수정 예정 | +| `[REF] juil-project-structure.md` | 주일 프로젝트 구조 가이드 (경로, 컴포넌트, 테스트 URL) | **프로젝트 정보**: - 업체: 주일 (공사/건설) diff --git a/claudedocs/board/[IMPL-2025-12-30] dynamic-board-creation.md b/claudedocs/board/[IMPL-2025-12-30] dynamic-board-creation.md new file mode 100644 index 00000000..97230f95 --- /dev/null +++ b/claudedocs/board/[IMPL-2025-12-30] dynamic-board-creation.md @@ -0,0 +1,120 @@ +# 게시판 동적 생성 구현 + +> 작성일: 2025-12-30 +> 상태: 완료 + +## 개요 + +게시판 관리에서 게시판을 등록하면 고객센터 메뉴에 자동으로 추가되고, +해당 게시판 페이지가 동적으로 렌더링되도록 구현합니다. + +--- + +## 작업 목록 + +### Phase 1: 게시판 관리 폼 수정 + +- [x] 1.1 대상 옵션에 "권한" 추가 + - 현재: 전사, 부서 + - 변경: 전사, 부서, **권한** + - 파일: `src/components/board/BoardManagement/types.ts` +- [x] 1.2 권한 선택 시 다중 선택 체크박스 표시 + - 파일: `src/components/board/BoardManagement/BoardForm.tsx` + - MOCK_PERMISSIONS: 관리자, 매니저, 직원, 게스트 +- [x] 1.3 API 요청 데이터에 권한 정보 포함 + - 파일: `src/components/board/BoardManagement/actions.ts` + - transformFrontendToApi: permissions → extra_settings.permissions + +### Phase 2: 메뉴 즉시 갱신 + +- [x] 2.1 게시판 등록 성공 후 `forceRefreshMenus()` 호출 + - 파일: `src/app/[locale]/(protected)/board/board-management/new/page.tsx` +- [x] 2.2 게시판 수정 성공 후 `forceRefreshMenus()` 호출 + - 파일: `src/app/[locale]/(protected)/board/board-management/[id]/edit/page.tsx` + +### Phase 3: 동적 게시판 라우트 생성 + +- [x] 3.1 `/customer-center/[boardCode]/page.tsx` - 리스트 +- [x] 3.2 `/customer-center/[boardCode]/[postId]/page.tsx` - 상세 +- [x] 3.3 `/customer-center/[boardCode]/create/page.tsx` - 등록 +- [x] 3.4 `/customer-center/[boardCode]/[postId]/edit/page.tsx` - 수정 + +### Phase 4: 테스트 및 검증 + +- [ ] 4.1 게시판 등록 → 메뉴 자동 추가 확인 +- [ ] 4.2 동적 게시판 리스트/상세/등록/수정 동작 확인 +- [ ] 4.3 권한별 접근 제어 확인 + +--- + +## 기술 명세 + +### 대상 타입 + +| 대상 | 옆 셀렉트박스 | API 필드 | +|------|---------------|----------| +| 전사 | 없음 | `target: 'all'` | +| 부서 | 부서 단일 선택 | `target: 'department', target_id: number` | +| 권한 | 권한 다중 선택 (체크박스) | `target: 'permission', permissions: string[]` | + +### 게시판 타입 + +- **기본 타입**: 1:1문의 형태 (댓글 사용 가능) +- **참고 페이지**: `/customer-center/qna` + +### 메뉴 갱신 플로우 + +``` +게시판 등록 API 호출 (POST /api/v1/boards) + ↓ +백엔드: 게시판 생성 + 메뉴 테이블에 추가 + ↓ +프론트: 등록 성공 응답 받음 + ↓ +프론트: forceRefreshMenus() 호출 + ↓ +사이드바 메뉴 즉시 업데이트 +``` + +### 동적 게시판 URL 구조 + +``` +/boards/[boardCode] → 목록 +/boards/[boardCode]/create → 등록 +/boards/[boardCode]/[postId] → 상세 +/boards/[boardCode]/[postId]/edit → 수정 +``` + +> **URL 변경 이력 (2025-12-30)** +> - 변경 전: `/customer-center/[boardCode]` +> - 변경 후: `/boards/[boardCode]` +> - 사유: 백엔드 메뉴 API path 규칙에 맞춤 (`/boards/free`, `/boards/board_xxx`) + +--- + +## 관련 파일 + +### 수정된 파일 +- `src/components/board/BoardManagement/types.ts` - BoardTarget에 'permission' 추가 +- `src/components/board/BoardManagement/BoardForm.tsx` - 권한 다중 선택 UI 추가 +- `src/components/board/BoardManagement/actions.ts` - permissions 변환 로직 +- `src/components/customer-center/shared/types.ts` - SystemBoardCode 확장 +- `src/app/[locale]/(protected)/board/board-management/new/page.tsx` - forceRefreshMenus 호출 +- `src/app/[locale]/(protected)/board/board-management/[id]/edit/page.tsx` - forceRefreshMenus 호출 + +### 새로 생성된 파일 +- `src/app/[locale]/(protected)/boards/[boardCode]/page.tsx` - 동적 게시판 목록 +- `src/app/[locale]/(protected)/boards/[boardCode]/[postId]/page.tsx` - 동적 게시판 상세 +- `src/app/[locale]/(protected)/boards/[boardCode]/create/page.tsx` - 동적 게시판 등록 +- `src/app/[locale]/(protected)/boards/[boardCode]/[postId]/edit/page.tsx` - 동적 게시판 수정 + +--- + +## 진행 로그 + +| 날짜 | 작업 내용 | +|------|----------| +| 2025-12-30 | 요구사항 정리 및 체크리스트 생성 | +| 2025-12-30 | Phase 1~3 구현 완료 | +| 2025-12-30 | URL 경로 변경: `/customer-center/[boardCode]` → `/boards/[boardCode]` | +| 2025-12-30 | API URL 불일치 해결: `system-boards` → `boards` (DynamicBoard/actions.ts 생성) | \ No newline at end of file diff --git a/claudedocs/juil/[NEXT-2025-12-30] partner-management-session-context.md b/claudedocs/juil/[NEXT-2025-12-30] partner-management-session-context.md new file mode 100644 index 00000000..896d69cf --- /dev/null +++ b/claudedocs/juil/[NEXT-2025-12-30] partner-management-session-context.md @@ -0,0 +1,101 @@ +# 주일 거래처 관리 세션 컨텍스트 + +Last Updated: 2025-12-30 + +## 세션 요약 (2025-12-30) + +### 완료된 작업 +- [x] 거래처 리스트 필터 위치 수정 (테이블 위로 이동) +- [x] 거래처 폼 컴포넌트 생성 (PartnerForm.tsx) +- [x] 등록 페이지 생성 (/new/page.tsx) +- [x] 상세 페이지 생성 (/[id]/page.tsx) +- [x] 수정 페이지 생성 (/[id]/edit/page.tsx) +- [x] types.ts 확장 (전체 필드 추가) +- [x] actions.ts CRUD 함수 추가 + +### 다음 세션 TODO +- [ ] **회사 정보 + 신용/거래 정보 섹션 합치기** (스크린샷 기준으로 하나의 섹션) +- [ ] 실제 API 연동 + +### 참고 사항 +- 스크린샷에서 "회사 정보"와 "신용/거래 정보"가 하나의 Card 섹션으로 되어 있음 +- 현재 코드는 별도 섹션으로 분리됨 → 합쳐야 함 + +--- + +## 완료된 작업 (전체) + +### 1. 프로젝트 구조 설정 +- [x] `claudedocs/juil/` 문서 폴더 생성 +- [x] `[REF] juil-project-structure.md` 프로젝트 구조 가이드 작성 +- [x] `_index.md` 문서 맵에 juil 섹션 추가 + +### 2. 거래처 관리 리스트 페이지 +- [x] 페이지: `src/app/[locale]/(protected)/juil/project/bidding/partners/page.tsx` +- [x] 컴포넌트: `src/components/business/juil/partners/PartnerListClient.tsx` +- [x] 타입: `src/components/business/juil/partners/types.ts` +- [x] 액션: `src/components/business/juil/partners/actions.ts` (목업 데이터) +- [x] 인덱스: `src/components/business/juil/partners/index.ts` +- [x] 레이아웃 수정: 필터를 테이블 위로 이동, 등록 버튼 상단 배치 + +### 3. 거래처 등록/상세/수정 페이지 +- [x] 폼 컴포넌트: `src/components/business/juil/partners/PartnerForm.tsx` +- [x] 등록 페이지: `src/app/[locale]/(protected)/juil/project/bidding/partners/new/page.tsx` +- [x] 상세 페이지: `src/app/[locale]/(protected)/juil/project/bidding/partners/[id]/page.tsx` +- [x] 수정 페이지: `src/app/[locale]/(protected)/juil/project/bidding/partners/[id]/edit/page.tsx` + +### 4. 구현된 기능 + +#### 리스트 페이지 +- 통계 카드 (전체 거래처 / 미등록) +- 검색 (거래처명, 번호, 대표자, 담당자) +- 탭 필터 (전체 / 신규) +- 테이블 위 필터: `총 N건 | 전체 ▾ | 최신순 ▾` +- 테이블 컬럼: 체크박스, 번호, 거래처번호, 구분, 거래처명, 대표자, 담당자, 전화번호, 매출 결제일, 악성채권, 작업 +- 행 선택 시 수정/삭제 버튼 표시 +- 일괄 삭제 다이얼로그 +- 페이지네이션 +- 모바일 카드 뷰 + +#### 폼 페이지 (등록/상세/수정 공통) +- **기본 정보**: 사업자등록번호, 거래처코드, 거래처명, 대표자명, 거래처유형, 업태, 업종 +- **연락처 정보**: 주소 (우편번호 찾기 DAUM), 전화번호, 모바일, 팩스, 이메일 +- **담당자 정보**: 담당자명, 담당자 전화, 시스템 관리자 +- **회사 정보**: 회사 로고 (BLOB 업로드), 매출 결제일, 신용등급, 거래등급, 세금계산서 이메일 +- **추가 정보**: 미수금, 연체 (토글), 악성채권 (토글) +- **메모**: 추가/삭제 기능 +- **필요 서류**: 파일 업로드 (드래그 앤 드롭) + +#### 모드별 버튼 분기 +- **등록**: 취소 | 저장 +- **수정**: 삭제 | 수정 +- **상세**: 목록가기 | 수정 + +## 테스트 URL + +| 페이지 | URL | 상태 | +|--------|-----|------| +| 거래처 관리 (리스트) | `/ko/juil/project/bidding/partners` | ✅ 완료 | +| 거래처 등록 | `/ko/juil/project/bidding/partners/new` | ✅ 완료 | +| 거래처 상세 | `/ko/juil/project/bidding/partners/1` | ✅ 완료 | +| 거래처 수정 | `/ko/juil/project/bidding/partners/1/edit` | ✅ 완료 | + +## 디렉토리 구조 + +``` +src/ +├── app/[locale]/(protected)/juil/ +│ └── project/bidding/partners/ +│ ├── page.tsx ✅ +│ ├── new/page.tsx ✅ +│ └── [id]/ +│ ├── page.tsx ✅ +│ └── edit/page.tsx ✅ +│ +└── components/business/juil/partners/ + ├── index.ts ✅ + ├── types.ts ✅ + ├── actions.ts ✅ (목업) + ├── PartnerListClient.tsx ✅ + └── PartnerForm.tsx ✅ (섹션 수정 필요) +``` \ No newline at end of file diff --git a/src/app/[locale]/(protected)/board/board-management/[id]/edit/page.tsx b/src/app/[locale]/(protected)/board/board-management/[id]/edit/page.tsx index a9eac557..18a6c9bc 100644 --- a/src/app/[locale]/(protected)/board/board-management/[id]/edit/page.tsx +++ b/src/app/[locale]/(protected)/board/board-management/[id]/edit/page.tsx @@ -5,6 +5,7 @@ import { useState, useEffect, useCallback } from 'react'; import { Loader2 } from 'lucide-react'; import { BoardForm } from '@/components/board/BoardManagement/BoardForm'; import { getBoardById, updateBoard } from '@/components/board/BoardManagement/actions'; +import { forceRefreshMenus } from '@/lib/utils/menuRefresh'; import { Button } from '@/components/ui/button'; import type { Board, BoardFormData } from '@/components/board/BoardManagement/types'; @@ -51,6 +52,8 @@ export default function BoardEditPage() { }); if (result.success) { + // 게시판 수정 성공 시 메뉴 즉시 갱신 + await forceRefreshMenus(); router.push(`/ko/board/board-management/${board.id}`); } else { setError(result.error || '수정에 실패했습니다.'); diff --git a/src/app/[locale]/(protected)/board/board-management/new/page.tsx b/src/app/[locale]/(protected)/board/board-management/new/page.tsx index 25af722d..a5e30375 100644 --- a/src/app/[locale]/(protected)/board/board-management/new/page.tsx +++ b/src/app/[locale]/(protected)/board/board-management/new/page.tsx @@ -5,6 +5,7 @@ import { useState } from 'react'; import { Loader2 } from 'lucide-react'; import { BoardForm } from '@/components/board/BoardManagement/BoardForm'; import { createBoard } from '@/components/board/BoardManagement/actions'; +import { forceRefreshMenus } from '@/lib/utils/menuRefresh'; import type { BoardFormData } from '@/components/board/BoardManagement/types'; // 게시판 코드 생성 (임시: 타임스탬프 기반) @@ -29,6 +30,8 @@ export default function BoardNewPage() { }); if (result.success && result.data) { + // 게시판 생성 성공 시 메뉴 즉시 갱신 + await forceRefreshMenus(); router.push('/ko/board/board-management'); } else { setError(result.error || '게시판 생성에 실패했습니다.'); diff --git a/src/app/[locale]/(protected)/boards/[boardCode]/[postId]/edit/page.tsx b/src/app/[locale]/(protected)/boards/[boardCode]/[postId]/edit/page.tsx new file mode 100644 index 00000000..c8ebdab4 --- /dev/null +++ b/src/app/[locale]/(protected)/boards/[boardCode]/[postId]/edit/page.tsx @@ -0,0 +1,250 @@ +'use client'; + +/** + * 동적 게시판 수정 페이지 + */ + +import { useState, useEffect } from 'react'; +import { useRouter, useParams } from 'next/navigation'; +import { ArrowLeft, Save, MessageSquare, Loader2 } from 'lucide-react'; +import { PageLayout } from '@/components/organisms/PageLayout'; +import { PageHeader } from '@/components/organisms/PageHeader'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Checkbox } from '@/components/ui/checkbox'; +import { getDynamicBoardPost, updateDynamicBoardPost } from '@/components/board/DynamicBoard/actions'; +import { getBoardByCode } from '@/components/board/BoardManagement/actions'; +import type { PostApiData } from '@/components/customer-center/shared/types'; + +interface BoardPost { + id: string; + title: string; + content: string; + authorId: string; + authorName: string; + status: string; + views: number; + isNotice: boolean; + isSecret: boolean; + createdAt: string; + updatedAt: string; +} + +// API 데이터 → 프론트엔드 타입 변환 +function transformApiToPost(apiData: PostApiData): BoardPost { + return { + id: String(apiData.id), + title: apiData.title, + content: apiData.content, + authorId: String(apiData.user_id), + authorName: apiData.author?.name || '회원', + status: apiData.status, + views: apiData.views, + isNotice: apiData.is_notice, + isSecret: apiData.is_secret, + createdAt: apiData.created_at, + updatedAt: apiData.updated_at, + }; +} + +export default function DynamicBoardEditPage() { + const router = useRouter(); + const params = useParams(); + const boardCode = params.boardCode as string; + const postId = params.postId as string; + + // 게시판 정보 + const [boardName, setBoardName] = useState('게시판'); + + // 원본 게시글 + const [post, setPost] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [loadError, setLoadError] = useState(null); + + // 폼 상태 + const [title, setTitle] = useState(''); + const [content, setContent] = useState(''); + const [isSecret, setIsSecret] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + + // 게시판 정보 로드 + useEffect(() => { + async function fetchBoardInfo() { + const result = await getBoardByCode(boardCode); + if (result.success && result.data) { + setBoardName(result.data.boardName); + } + } + fetchBoardInfo(); + }, [boardCode]); + + // 게시글 로드 + useEffect(() => { + async function fetchPost() { + setIsLoading(true); + setLoadError(null); + + const result = await getDynamicBoardPost(boardCode, postId); + + if (result.success && result.data) { + const postData = transformApiToPost(result.data); + setPost(postData); + setTitle(postData.title); + setContent(postData.content); + setIsSecret(postData.isSecret); + } else { + setLoadError(result.error || '게시글을 찾을 수 없습니다.'); + } + + setIsLoading(false); + } + + fetchPost(); + }, [boardCode, postId]); + + // 폼 제출 + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!title.trim()) { + setError('제목을 입력해주세요.'); + return; + } + + if (!content.trim()) { + setError('내용을 입력해주세요.'); + return; + } + + setIsSubmitting(true); + setError(null); + + const result = await updateDynamicBoardPost(boardCode, postId, { + title: title.trim(), + content: content.trim(), + is_secret: isSecret, + }); + + if (result.success) { + router.push(`/ko/boards/${boardCode}/${postId}`); + } else { + setError(result.error || '게시글 수정에 실패했습니다.'); + setIsSubmitting(false); + } + }; + + // 취소 + const handleCancel = () => { + router.push(`/ko/boards/${boardCode}/${postId}`); + }; + + // 로딩 상태 + if (isLoading) { + return ( + +
+ +
+
+ ); + } + + // 로드 에러 + if (loadError || !post) { + return ( + +
+

{loadError || '게시글을 찾을 수 없습니다.'}

+ +
+
+ ); + } + + return ( + + + +
+ + + 게시글 수정 + + + {error && ( +
+

{error}

+
+ )} + + {/* 제목 */} +
+ + setTitle(e.target.value)} + placeholder="제목을 입력하세요" + disabled={isSubmitting} + /> +
+ + {/* 내용 */} +
+ +