feat: [게시판] 게시판 관리 UI 개선
- BoardDetail, BoardForm, DynamicBoard 폼 개선 - CommentItem, BoardDetailClientV2 UI 개선 - 게시판 페이지 라우팅 개선 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,8 @@ import { DynamicBoardEditForm } from '@/components/board/DynamicBoard/DynamicBoa
|
|||||||
import { useState, useEffect, useCallback, Suspense } from 'react';
|
import { useState, useEffect, useCallback, Suspense } from 'react';
|
||||||
import { DetailPageSkeleton } from '@/components/ui/skeleton';
|
import { DetailPageSkeleton } from '@/components/ui/skeleton';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { ArrowLeft, Pencil, Trash2, MessageSquare, Eye } from 'lucide-react';
|
import { ArrowLeft, Edit, Trash2, MessageSquare, Eye } from 'lucide-react';
|
||||||
|
import { useMenuStore } from '@/stores/menuStore';
|
||||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||||
import { PageHeader } from '@/components/organisms/PageHeader';
|
import { PageHeader } from '@/components/organisms/PageHeader';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@@ -100,6 +101,7 @@ function DetailModeRouter() {
|
|||||||
// 실제 상세 컴포넌트 (자체 hooks 사용)
|
// 실제 상세 컴포넌트 (자체 hooks 사용)
|
||||||
function DynamicBoardDetailContent({ boardCode, postId }: { boardCode: string; postId: string }) {
|
function DynamicBoardDetailContent({ boardCode, postId }: { boardCode: string; postId: string }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
|
||||||
|
|
||||||
// 게시판 정보
|
// 게시판 정보
|
||||||
const [boardName, setBoardName] = useState<string>('게시판');
|
const [boardName, setBoardName] = useState<string>('게시판');
|
||||||
@@ -261,6 +263,7 @@ function DynamicBoardDetailContent({ boardCode, postId }: { boardCode: string; p
|
|||||||
icon={MessageSquare}
|
icon={MessageSquare}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="pb-24">
|
||||||
{/* 게시글 상세 */}
|
{/* 게시글 상세 */}
|
||||||
<Card className="mb-6">
|
<Card className="mb-6">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -282,9 +285,9 @@ function DynamicBoardDetailContent({ boardCode, postId }: { boardCode: string; p
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isAuthor && (
|
{isAuthor && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="hidden md:flex items-center gap-2">
|
||||||
<Button variant="outline" size="sm" onClick={handleEdit}>
|
<Button variant="outline" size="sm" onClick={handleEdit}>
|
||||||
<Pencil className="h-4 w-4 mr-1" />
|
<Edit className="h-4 w-4 mr-1" />
|
||||||
수정
|
수정
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="destructive" size="sm" onClick={() => setShowDeleteDialog(true)}>
|
<Button variant="destructive" size="sm" onClick={() => setShowDeleteDialog(true)}>
|
||||||
@@ -400,13 +403,33 @@ function DynamicBoardDetailContent({ boardCode, postId }: { boardCode: string; p
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 하단 버튼 */}
|
{/* 하단 액션 버튼 (sticky) */}
|
||||||
<div className="mt-6 flex justify-start">
|
<div
|
||||||
<Button variant="outline" onClick={handleBack}>
|
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`}
|
||||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
>
|
||||||
목록으로
|
<Button variant="outline" onClick={handleBack} size="sm" className="md:size-default">
|
||||||
|
<ArrowLeft className="w-4 h-4 md:mr-2" />
|
||||||
|
<span className="hidden md:inline">목록으로</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
{isAuthor && (
|
||||||
|
<div className="flex items-center gap-1 md:gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setShowDeleteDialog(true)}
|
||||||
|
size="sm"
|
||||||
|
className="text-destructive hover:bg-destructive hover:text-destructive-foreground md:size-default"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4 md:mr-2" />
|
||||||
|
<span className="hidden md:inline">삭제</span>
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleEdit} size="sm" className="md:size-default">
|
||||||
|
<Edit className="w-4 h-4 md:mr-2" />
|
||||||
|
<span className="hidden md:inline">수정</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 게시글 삭제 확인 다이얼로그 */}
|
{/* 게시글 삭제 확인 다이얼로그 */}
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ function DynamicBoardListContent({ boardCode }: { boardCode: string }) {
|
|||||||
|
|
||||||
const handleRowClick = useCallback(
|
const handleRowClick = useCallback(
|
||||||
(item: BoardPost) => {
|
(item: BoardPost) => {
|
||||||
router.push(`/ko/boards/${boardCode}/${item.id}`);
|
router.push(`/ko/boards/${boardCode}/${item.id}?mode=view`);
|
||||||
},
|
},
|
||||||
[router, boardCode]
|
[router, boardCode]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -125,17 +125,11 @@ export function BoardDetail({ post, comments: initialComments, currentUserId }:
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* 메타 정보: 작성자 | 날짜 | 조회수 */}
|
{/* 메타 정보: 작성자 | 날짜 | 조회수 */}
|
||||||
<div className="flex items-center gap-2 text-sm text-gray-500 mt-2">
|
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2 text-sm text-gray-500 mt-2">
|
||||||
<span>{post.authorName}</span>
|
<span>{post.authorName}{post.authorDepartment ? ` / ${post.authorDepartment}` : ''}</span>
|
||||||
{post.authorDepartment && (
|
<span className="hidden sm:inline text-gray-300">|</span>
|
||||||
<>
|
|
||||||
<span className="text-gray-300">|</span>
|
|
||||||
<span>{post.authorDepartment}</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<span className="text-gray-300">|</span>
|
|
||||||
<span>{format(new Date(post.createdAt), 'yyyy-MM-dd HH:mm')}</span>
|
<span>{format(new Date(post.createdAt), 'yyyy-MM-dd HH:mm')}</span>
|
||||||
<span className="text-gray-300">|</span>
|
<span className="hidden sm:inline text-gray-300">|</span>
|
||||||
<span>조회수 {post.viewCount}</span>
|
<span>조회수 {post.viewCount}</span>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { ClipboardList, ArrowLeft, Edit, Trash2 } from 'lucide-react';
|
import { ClipboardList, ArrowLeft, Edit, Trash2 } from 'lucide-react';
|
||||||
|
import { useMenuStore } from '@/stores/menuStore';
|
||||||
import type { Board } from './types';
|
import type { Board } from './types';
|
||||||
import {
|
import {
|
||||||
BOARD_STATUS_LABELS,
|
BOARD_STATUS_LABELS,
|
||||||
@@ -33,6 +34,7 @@ const formatDateTime = (dateString: string): string => {
|
|||||||
|
|
||||||
export function BoardDetail({ board, onEdit, onDelete }: BoardDetailProps) {
|
export function BoardDetail({ board, onEdit, onDelete }: BoardDetailProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
router.push('/ko/board/board-management');
|
router.push('/ko/board/board-management');
|
||||||
@@ -54,7 +56,7 @@ export function BoardDetail({ board, onEdit, onDelete }: BoardDetailProps) {
|
|||||||
icon={ClipboardList}
|
icon={ClipboardList}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6 pb-24">
|
||||||
{/* 게시판 정보 */}
|
{/* 게시판 정보 */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
@@ -93,26 +95,34 @@ export function BoardDetail({ board, onEdit, onDelete }: BoardDetailProps) {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 버튼 영역 */}
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Button variant="outline" onClick={handleBack}>
|
{/* 하단 액션 버튼 (sticky) */}
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<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>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<Button variant="outline" onClick={handleBack} size="sm" className="md:size-default">
|
||||||
{onDelete && (
|
<ArrowLeft className="w-4 h-4 md:mr-2" />
|
||||||
<Button variant="outline" onClick={onDelete} className="text-destructive hover:bg-destructive hover:text-destructive-foreground">
|
<span className="hidden md:inline">목록으로</span>
|
||||||
<Trash2 className="w-4 h-4 mr-2" />
|
</Button>
|
||||||
삭제
|
<div className="flex items-center gap-1 md:gap-2">
|
||||||
</Button>
|
{onDelete && (
|
||||||
)}
|
<Button
|
||||||
{onEdit && (
|
variant="outline"
|
||||||
<Button onClick={onEdit}>
|
onClick={onDelete}
|
||||||
<Edit className="w-4 h-4 mr-2" />
|
size="sm"
|
||||||
수정
|
className="text-destructive hover:bg-destructive hover:text-destructive-foreground md:size-default"
|
||||||
</Button>
|
>
|
||||||
)}
|
<Trash2 className="w-4 h-4 md:mr-2" />
|
||||||
</div>
|
<span className="hidden md:inline">삭제</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{onEdit && (
|
||||||
|
<Button onClick={onEdit} size="sm" className="md:size-default">
|
||||||
|
<Edit className="w-4 h-4 md:mr-2" />
|
||||||
|
<span className="hidden md:inline">수정</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
|||||||
@@ -93,9 +93,10 @@ export function BoardDetailClientV2({ boardId, initialMode }: BoardDetailClientV
|
|||||||
|
|
||||||
// URL 쿼리 변경 감지
|
// URL 쿼리 변경 감지
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isNewMode && modeFromQuery === 'edit') {
|
if (isNewMode) return;
|
||||||
|
if (modeFromQuery === 'edit') {
|
||||||
setMode('edit');
|
setMode('edit');
|
||||||
} else if (!isNewMode && !modeFromQuery) {
|
} else {
|
||||||
setMode('view');
|
setMode('view');
|
||||||
}
|
}
|
||||||
}, [modeFromQuery, isNewMode]);
|
}, [modeFromQuery, isNewMode]);
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { ClipboardList, ArrowLeft, Save } from 'lucide-react';
|
import { ClipboardList, ArrowLeft, Save, X } from 'lucide-react';
|
||||||
|
import { useMenuStore } from '@/stores/menuStore';
|
||||||
import type { Board, BoardFormData, BoardTarget, BoardStatus } from './types';
|
import type { Board, BoardFormData, BoardTarget, BoardStatus } from './types';
|
||||||
import { BOARD_TARGETS, BOARD_STATUS_LABELS } from './types';
|
import { BOARD_TARGETS, BOARD_STATUS_LABELS } from './types';
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ const getCurrentDateTime = (): string => {
|
|||||||
|
|
||||||
export function BoardForm({ mode, board, onSubmit }: BoardFormProps) {
|
export function BoardForm({ mode, board, onSubmit }: BoardFormProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
|
||||||
const [formData, setFormData] = useState<BoardFormData>({
|
const [formData, setFormData] = useState<BoardFormData>({
|
||||||
target: 'all',
|
target: 'all',
|
||||||
targetName: '',
|
targetName: '',
|
||||||
@@ -129,7 +131,7 @@ export function BoardForm({ mode, board, onSubmit }: BoardFormProps) {
|
|||||||
icon={ClipboardList}
|
icon={ClipboardList}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
<form onSubmit={handleSubmit} className="space-y-6 pb-24">
|
||||||
{/* 게시판 정보 */}
|
{/* 게시판 정보 */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -254,18 +256,21 @@ export function BoardForm({ mode, board, onSubmit }: BoardFormProps) {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 버튼 영역 */}
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Button type="button" variant="outline" onClick={handleBack}>
|
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
||||||
취소
|
|
||||||
</Button>
|
|
||||||
<Button type="submit">
|
|
||||||
<Save className="w-4 h-4 mr-2" />
|
|
||||||
{mode === 'create' ? '등록' : '저장'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{/* 하단 액션 버튼 (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={handleBack} size="sm" className="md:size-default">
|
||||||
|
<X className="w-4 h-4 md:mr-2" />
|
||||||
|
<span className="hidden md:inline">취소</span>
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => onSubmit(formData)} size="sm" className="md:size-default">
|
||||||
|
<Save className="w-4 h-4 md:mr-2" />
|
||||||
|
<span className="hidden md:inline">{mode === 'create' ? '등록' : '저장'}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
import { useState, useCallback, memo } from 'react';
|
import { useState, useCallback, memo } from 'react';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { User, Pencil, Trash2 } from 'lucide-react';
|
import { User, Edit, Trash2 } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||||
@@ -90,7 +90,7 @@ export const CommentItem = memo(function CommentItem({
|
|||||||
{/* 댓글 내용 */}
|
{/* 댓글 내용 */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
{/* 작성자 정보 + 날짜 + 버튼 */}
|
{/* 작성자 정보 + 날짜 + 버튼 */}
|
||||||
<div className="flex items-center justify-between gap-2 mb-1">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1 sm:gap-2 mb-1">
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<span className="font-medium text-gray-900">{authorInfo}</span>
|
<span className="font-medium text-gray-900">{authorInfo}</span>
|
||||||
<span className="text-gray-400">
|
<span className="text-gray-400">
|
||||||
@@ -107,7 +107,7 @@ export const CommentItem = memo(function CommentItem({
|
|||||||
className="h-7 px-2 text-gray-500 hover:text-gray-700"
|
className="h-7 px-2 text-gray-500 hover:text-gray-700"
|
||||||
onClick={handleEditClick}
|
onClick={handleEditClick}
|
||||||
>
|
>
|
||||||
<Pencil className="h-3 w-3 mr-1" />
|
<Edit className="h-3 w-3 mr-1" />
|
||||||
수정
|
수정
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { ArrowLeft, Save, MessageSquare } from 'lucide-react';
|
import { X, Save, MessageSquare } from 'lucide-react';
|
||||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||||
import { PageHeader } from '@/components/organisms/PageHeader';
|
import { PageHeader } from '@/components/organisms/PageHeader';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@@ -16,6 +16,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { useMenuStore } from '@/stores/menuStore';
|
||||||
import { createDynamicBoardPost } from '@/components/board/DynamicBoard/actions';
|
import { createDynamicBoardPost } from '@/components/board/DynamicBoard/actions';
|
||||||
import { getBoardByCode } from '@/components/board/BoardManagement/actions';
|
import { getBoardByCode } from '@/components/board/BoardManagement/actions';
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ interface DynamicBoardCreateFormProps {
|
|||||||
|
|
||||||
export function DynamicBoardCreateForm({ boardCode }: DynamicBoardCreateFormProps) {
|
export function DynamicBoardCreateForm({ boardCode }: DynamicBoardCreateFormProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
|
||||||
|
|
||||||
// 게시판 정보
|
// 게시판 정보
|
||||||
const [boardName, setBoardName] = useState<string>('게시판');
|
const [boardName, setBoardName] = useState<string>('게시판');
|
||||||
@@ -71,7 +73,7 @@ export function DynamicBoardCreateForm({ boardCode }: DynamicBoardCreateFormProp
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
router.push(`/ko/boards/${boardCode}/${result.data.id}`);
|
router.push(`/ko/boards/${boardCode}/${result.data.id}?mode=view`);
|
||||||
} else {
|
} else {
|
||||||
setError(result.error || '게시글 등록에 실패했습니다.');
|
setError(result.error || '게시글 등록에 실패했습니다.');
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
@@ -91,7 +93,7 @@ export function DynamicBoardCreateForm({ boardCode }: DynamicBoardCreateFormProp
|
|||||||
icon={MessageSquare}
|
icon={MessageSquare}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit} className="pb-24">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">게시글 작성</CardTitle>
|
<CardTitle className="text-base">게시글 작성</CardTitle>
|
||||||
@@ -143,23 +145,21 @@ export function DynamicBoardCreateForm({ boardCode }: DynamicBoardCreateFormProp
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 버튼 영역 */}
|
|
||||||
<div className="mt-6 flex items-center justify-between">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleCancel}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
>
|
|
||||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
|
||||||
취소
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" disabled={isSubmitting}>
|
|
||||||
<Save className="h-4 w-4 mr-2" />
|
|
||||||
{isSubmitting ? '등록 중...' : '등록'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{/* 하단 액션 버튼 (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>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { ArrowLeft, Save, MessageSquare } from 'lucide-react';
|
import { ArrowLeft, X, Save, MessageSquare } from 'lucide-react';
|
||||||
import { DetailPageSkeleton } from '@/components/ui/skeleton';
|
import { DetailPageSkeleton } from '@/components/ui/skeleton';
|
||||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||||
import { PageHeader } from '@/components/organisms/PageHeader';
|
import { PageHeader } from '@/components/organisms/PageHeader';
|
||||||
@@ -17,6 +17,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { useMenuStore } from '@/stores/menuStore';
|
||||||
import { getDynamicBoardPost, updateDynamicBoardPost } from '@/components/board/DynamicBoard/actions';
|
import { getDynamicBoardPost, updateDynamicBoardPost } from '@/components/board/DynamicBoard/actions';
|
||||||
import { getBoardByCode } from '@/components/board/BoardManagement/actions';
|
import { getBoardByCode } from '@/components/board/BoardManagement/actions';
|
||||||
import type { PostApiData } from '@/components/customer-center/shared/types';
|
import type { PostApiData } from '@/components/customer-center/shared/types';
|
||||||
@@ -59,6 +60,7 @@ interface DynamicBoardEditFormProps {
|
|||||||
|
|
||||||
export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditFormProps) {
|
export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditFormProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
|
||||||
|
|
||||||
// 게시판 정보
|
// 게시판 정보
|
||||||
const [boardName, setBoardName] = useState<string>('게시판');
|
const [boardName, setBoardName] = useState<string>('게시판');
|
||||||
@@ -134,7 +136,7 @@ export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditForm
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
router.push(`/ko/boards/${boardCode}/${postId}`);
|
router.push(`/ko/boards/${boardCode}/${postId}?mode=view`);
|
||||||
} else {
|
} else {
|
||||||
setError(result.error || '게시글 수정에 실패했습니다.');
|
setError(result.error || '게시글 수정에 실패했습니다.');
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
@@ -143,7 +145,7 @@ export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditForm
|
|||||||
|
|
||||||
// 취소
|
// 취소
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
router.push(`/ko/boards/${boardCode}/${postId}`);
|
router.push(`/ko/boards/${boardCode}/${postId}?mode=view`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 로딩 상태
|
// 로딩 상태
|
||||||
@@ -178,7 +180,7 @@ export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditForm
|
|||||||
icon={MessageSquare}
|
icon={MessageSquare}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit} className="pb-24">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">게시글 수정</CardTitle>
|
<CardTitle className="text-base">게시글 수정</CardTitle>
|
||||||
@@ -230,23 +232,21 @@ export function DynamicBoardEditForm({ boardCode, postId }: DynamicBoardEditForm
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 버튼 영역 */}
|
|
||||||
<div className="mt-6 flex items-center justify-between">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleCancel}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
>
|
|
||||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
|
||||||
취소
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" disabled={isSubmitting}>
|
|
||||||
<Save className="h-4 w-4 mr-2" />
|
|
||||||
{isSubmitting ? '저장 중...' : '저장'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{/* 하단 액션 버튼 (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>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user