2026-01-27 14:47:28 +09:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 동적 게시판 수정 폼 컴포넌트
|
|
|
|
|
* - mode=edit 또는 /edit 페이지에서 사용
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
|
import { useRouter } from 'next/navigation';
|
feat: 다중 도메인 UI 개선 및 컴포넌트 리팩토링
- 게시판, HR, 설정, 차량관리, 건설, 견적 등 전반적 UI 개선
- FormField, TabChip, Select 등 공통 컴포넌트 개선
- 가격배분 edit 페이지 제거 및 상세 페이지 통합
- 체크리스트, 근태, 급여, 권한 관리 등 폼 개선
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:30:06 +09:00
|
|
|
import { ArrowLeft, X, Save, MessageSquare } from 'lucide-react';
|
2026-01-27 14:47:28 +09:00
|
|
|
import { DetailPageSkeleton } from '@/components/ui/skeleton';
|
|
|
|
|
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';
|
feat: 다중 도메인 UI 개선 및 컴포넌트 리팩토링
- 게시판, HR, 설정, 차량관리, 건설, 견적 등 전반적 UI 개선
- FormField, TabChip, Select 등 공통 컴포넌트 개선
- 가격배분 edit 페이지 제거 및 상세 페이지 통합
- 체크리스트, 근태, 급여, 권한 관리 등 폼 개선
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:30:06 +09:00
|
|
|
import { useMenuStore } from '@/stores/menuStore';
|
2026-01-27 14:47:28 +09:00
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface DynamicBoardEditFormProps {
|
|
|
|
|
boardCode: string;
|
|
|
|
|
postId: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditFormProps) {
|
|
|
|
|
const router = useRouter();
|
feat: 다중 도메인 UI 개선 및 컴포넌트 리팩토링
- 게시판, HR, 설정, 차량관리, 건설, 견적 등 전반적 UI 개선
- FormField, TabChip, Select 등 공통 컴포넌트 개선
- 가격배분 edit 페이지 제거 및 상세 페이지 통합
- 체크리스트, 근태, 급여, 권한 관리 등 폼 개선
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:30:06 +09:00
|
|
|
const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
|
2026-01-27 14:47:28 +09:00
|
|
|
|
|
|
|
|
// 게시판 정보
|
|
|
|
|
const [boardName, setBoardName] = useState<string>('게시판');
|
|
|
|
|
|
|
|
|
|
// 원본 게시글
|
|
|
|
|
const [post, setPost] = useState<BoardPost | null>(null);
|
|
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
|
|
|
|
|
|
|
|
// 폼 상태
|
|
|
|
|
const [title, setTitle] = useState('');
|
|
|
|
|
const [content, setContent] = useState('');
|
|
|
|
|
const [isSecret, setIsSecret] = useState(false);
|
|
|
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
|
const [error, setError] = useState<string | null>(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) {
|
feat: 다중 도메인 UI 개선 및 컴포넌트 리팩토링
- 게시판, HR, 설정, 차량관리, 건설, 견적 등 전반적 UI 개선
- FormField, TabChip, Select 등 공통 컴포넌트 개선
- 가격배분 edit 페이지 제거 및 상세 페이지 통합
- 체크리스트, 근태, 급여, 권한 관리 등 폼 개선
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:30:06 +09:00
|
|
|
router.push(`/ko/boards/${boardCode}/${postId}?mode=view`);
|
2026-01-27 14:47:28 +09:00
|
|
|
} else {
|
|
|
|
|
setError(result.error || '게시글 수정에 실패했습니다.');
|
|
|
|
|
setIsSubmitting(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 취소
|
|
|
|
|
const handleCancel = () => {
|
feat: 다중 도메인 UI 개선 및 컴포넌트 리팩토링
- 게시판, HR, 설정, 차량관리, 건설, 견적 등 전반적 UI 개선
- FormField, TabChip, Select 등 공통 컴포넌트 개선
- 가격배분 edit 페이지 제거 및 상세 페이지 통합
- 체크리스트, 근태, 급여, 권한 관리 등 폼 개선
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:30:06 +09:00
|
|
|
router.push(`/ko/boards/${boardCode}/${postId}?mode=view`);
|
2026-01-27 14:47:28 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 로딩 상태
|
|
|
|
|
if (isLoading) {
|
|
|
|
|
return (
|
|
|
|
|
<PageLayout>
|
|
|
|
|
<DetailPageSkeleton />
|
|
|
|
|
</PageLayout>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 로드 에러
|
|
|
|
|
if (loadError || !post) {
|
|
|
|
|
return (
|
|
|
|
|
<PageLayout>
|
|
|
|
|
<div className="flex flex-col items-center justify-center min-h-[400px] gap-4">
|
|
|
|
|
<p className="text-muted-foreground">{loadError || '게시글을 찾을 수 없습니다.'}</p>
|
|
|
|
|
<Button variant="outline" onClick={() => router.push(`/ko/boards/${boardCode}`)}>
|
|
|
|
|
<ArrowLeft className="h-4 w-4 mr-2" />
|
|
|
|
|
목록으로
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</PageLayout>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<PageLayout>
|
|
|
|
|
<PageHeader
|
|
|
|
|
title={boardName}
|
|
|
|
|
description="게시글 수정"
|
|
|
|
|
icon={MessageSquare}
|
|
|
|
|
/>
|
|
|
|
|
|
feat: 다중 도메인 UI 개선 및 컴포넌트 리팩토링
- 게시판, HR, 설정, 차량관리, 건설, 견적 등 전반적 UI 개선
- FormField, TabChip, Select 등 공통 컴포넌트 개선
- 가격배분 edit 페이지 제거 및 상세 페이지 통합
- 체크리스트, 근태, 급여, 권한 관리 등 폼 개선
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:30:06 +09:00
|
|
|
<form onSubmit={handleSubmit} className="pb-24">
|
2026-01-27 14:47:28 +09:00
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="text-base">게시글 수정</CardTitle>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
{error && (
|
|
|
|
|
<div className="p-3 bg-destructive/10 border border-destructive/20 rounded-md">
|
|
|
|
|
<p className="text-sm text-destructive">{error}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 제목 */}
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="title">제목 *</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="title"
|
|
|
|
|
value={title}
|
|
|
|
|
onChange={(e) => setTitle(e.target.value)}
|
|
|
|
|
placeholder="제목을 입력하세요"
|
|
|
|
|
disabled={isSubmitting}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 내용 */}
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="content">내용 *</Label>
|
|
|
|
|
<Textarea
|
|
|
|
|
id="content"
|
|
|
|
|
value={content}
|
|
|
|
|
onChange={(e) => setContent(e.target.value)}
|
|
|
|
|
placeholder="내용을 입력하세요"
|
|
|
|
|
rows={15}
|
|
|
|
|
disabled={isSubmitting}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 비밀글 설정 */}
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
<Checkbox
|
|
|
|
|
id="isSecret"
|
|
|
|
|
checked={isSecret}
|
|
|
|
|
onCheckedChange={(checked) => setIsSecret(checked as boolean)}
|
|
|
|
|
disabled={isSubmitting}
|
|
|
|
|
/>
|
|
|
|
|
<Label htmlFor="isSecret" className="font-normal cursor-pointer">
|
|
|
|
|
비밀글로 등록
|
|
|
|
|
</Label>
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
</form>
|
feat: 다중 도메인 UI 개선 및 컴포넌트 리팩토링
- 게시판, HR, 설정, 차량관리, 건설, 견적 등 전반적 UI 개선
- FormField, TabChip, Select 등 공통 컴포넌트 개선
- 가격배분 edit 페이지 제거 및 상세 페이지 통합
- 체크리스트, 근태, 급여, 권한 관리 등 폼 개선
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:30:06 +09:00
|
|
|
|
|
|
|
|
{/* 하단 액션 버튼 (sticky) */}
|
|
|
|
|
<div
|
|
|
|
|
className={`fixed bottom-4 left-4 right-4 px-4 py-3 bg-background/95 backdrop-blur rounded-xl border shadow-lg z-50 transition-all duration-300 md:bottom-6 md:px-6 md:right-[48px] ${sidebarCollapsed ? 'md:left-[156px]' : 'md:left-[316px]'} flex items-center justify-between`}
|
|
|
|
|
>
|
|
|
|
|
<Button type="button" variant="outline" onClick={handleCancel} disabled={isSubmitting} size="sm" className="md:size-default">
|
|
|
|
|
<X className="w-4 h-4 md:mr-2" />
|
|
|
|
|
<span className="hidden md:inline">취소</span>
|
|
|
|
|
</Button>
|
|
|
|
|
<Button onClick={(e) => { e.preventDefault(); const form = document.querySelector('form'); form?.requestSubmit(); }} disabled={isSubmitting} size="sm" className="md:size-default">
|
|
|
|
|
<Save className="w-4 h-4 md:mr-2" />
|
|
|
|
|
<span className="hidden md:inline">{isSubmitting ? '저장 중...' : '저장'}</span>
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2026-01-27 14:47:28 +09:00
|
|
|
</PageLayout>
|
|
|
|
|
);
|
|
|
|
|
}
|