feat: [게시판] 게시판 관리 UI 개선

- BoardDetail, BoardForm, DynamicBoard 폼 개선
- CommentItem, BoardDetailClientV2 UI 개선
- 게시판 페이지 라우팅 개선

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-25 22:33:06 +09:00
parent 81a016ada9
commit dc7e152311
9 changed files with 130 additions and 97 deletions

View File

@@ -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>
{/* 게시글 삭제 확인 다이얼로그 */} {/* 게시글 삭제 확인 다이얼로그 */}

View File

@@ -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]
); );

View File

@@ -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>

View File

@@ -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>

View File

@@ -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]);

View File

@@ -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>
); );
} }

View File

@@ -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

View File

@@ -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>
); );
} }

View File

@@ -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>
); );
} }