refactor(WEB): 회계/견적/설정/생산 등 전반적 코드 개선 및 공통화 2차
- 회계 모듈 전면 개선: 청구/입금/출금/매입/매출/세금계산서/일반전표/거래처원장 등 - 견적 모듈 금액 포맷/할인/수식/미리보기 등 코드 정리 - 설정 모듈: 계정관리/직급/직책/권한 상세 간소화 - 생산 모듈: 작업지시서/작업자화면/검수 문서 코드 정리 - UniversalListPage 엑셀 다운로드 및 필터 기능 확장 - 대시보드/게시판/수주 등 날짜 유틸 공통화 적용 - claudedocs 문서 인덱스 업데이트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,7 @@ import {
|
||||
CardHeader,
|
||||
} from '@/components/ui/card';
|
||||
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
import { toast } from 'sonner';
|
||||
import { useDeleteDialog } from '@/hooks/useDeleteDialog';
|
||||
import { CommentSection } from '../CommentSection';
|
||||
import { deletePost } from '../actions';
|
||||
import type { Post, Comment } from '../types';
|
||||
@@ -46,8 +46,6 @@ interface BoardDetailProps {
|
||||
export function BoardDetail({ post, comments: initialComments, currentUserId }: BoardDetailProps) {
|
||||
const router = useRouter();
|
||||
const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [comments, setComments] = useState<Comment[]>(initialComments);
|
||||
|
||||
const isMyPost = post.authorId === currentUserId;
|
||||
@@ -61,25 +59,11 @@ export function BoardDetail({ post, comments: initialComments, currentUserId }:
|
||||
router.push(`/ko/board/${post.boardCode}/${post.id}?mode=edit`);
|
||||
}, [router, post.boardCode, post.id]);
|
||||
|
||||
const handleConfirmDelete = useCallback(async () => {
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
const result = await deletePost(post.boardCode, post.id);
|
||||
if (result.success) {
|
||||
toast.success('게시글이 삭제되었습니다.');
|
||||
router.push('/ko/board');
|
||||
} else {
|
||||
toast.error(result.error || '게시글 삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('게시글 삭제 오류:', error);
|
||||
toast.error('게시글 삭제에 실패했습니다.');
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setShowDeleteDialog(false);
|
||||
}
|
||||
}, [post.boardCode, post.id, router]);
|
||||
const deleteDialog = useDeleteDialog({
|
||||
onDelete: async () => deletePost(post.boardCode, post.id),
|
||||
onSuccess: () => router.push('/ko/board'),
|
||||
entityName: '게시글',
|
||||
});
|
||||
|
||||
// ===== 댓글 핸들러 =====
|
||||
// TODO: 댓글 API 연동 (별도 작업)
|
||||
@@ -211,7 +195,7 @@ export function BoardDetail({ post, comments: initialComments, currentUserId }:
|
||||
<div className="flex items-center gap-1 md:gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
onClick={() => deleteDialog.single.open(post.id)}
|
||||
size="sm"
|
||||
className="text-destructive hover:bg-destructive hover:text-destructive-foreground md:size-default"
|
||||
>
|
||||
@@ -228,12 +212,12 @@ export function BoardDetail({ post, comments: initialComments, currentUserId }:
|
||||
|
||||
{/* 삭제 확인 다이얼로그 */}
|
||||
<DeleteConfirmDialog
|
||||
open={showDeleteDialog}
|
||||
onOpenChange={setShowDeleteDialog}
|
||||
onConfirm={handleConfirmDelete}
|
||||
open={deleteDialog.single.isOpen}
|
||||
onOpenChange={deleteDialog.single.onOpenChange}
|
||||
onConfirm={deleteDialog.single.confirm}
|
||||
title="게시글 삭제"
|
||||
description="정말 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다."
|
||||
loading={isDeleting}
|
||||
loading={deleteDialog.isPending}
|
||||
/>
|
||||
</PageLayout>
|
||||
);
|
||||
|
||||
@@ -224,10 +224,10 @@ export function BoardList() {
|
||||
|
||||
columns: [
|
||||
{ key: 'no', label: 'No.', className: 'w-[60px] text-center' },
|
||||
{ key: 'title', label: '제목', className: 'min-w-[300px]' },
|
||||
{ key: 'author', label: '작성자', className: 'w-[120px]' },
|
||||
{ key: 'createdAt', label: '등록일', className: 'w-[120px]' },
|
||||
{ key: 'viewCount', label: '조회수', className: 'w-[80px] text-center' },
|
||||
{ key: 'title', label: '제목', className: 'min-w-[300px]', sortable: true },
|
||||
{ key: 'author', label: '작성자', className: 'w-[120px]', sortable: true },
|
||||
{ key: 'createdAt', label: '등록일', className: 'w-[120px]', sortable: true },
|
||||
{ key: 'viewCount', label: '조회수', className: 'w-[80px] text-center', sortable: true },
|
||||
{ key: 'actions', label: '작업', className: 'w-[100px] text-center' },
|
||||
],
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { DetailPageSkeleton } from '@/components/ui/skeleton';
|
||||
import { ErrorCard } from '@/components/ui/error-card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
import { useDeleteDialog } from '@/hooks/useDeleteDialog';
|
||||
import { toast } from 'sonner';
|
||||
import { usePermission } from '@/hooks/usePermission';
|
||||
|
||||
@@ -58,8 +59,6 @@ export function BoardDetailClientV2({ boardId, initialMode }: BoardDetailClientV
|
||||
const [isLoading, setIsLoading] = useState(!isNewMode);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
// 데이터 로드
|
||||
useEffect(() => {
|
||||
@@ -161,36 +160,17 @@ export function BoardDetailClientV2({ boardId, initialMode }: BoardDetailClientV
|
||||
};
|
||||
|
||||
// 삭제 핸들러
|
||||
const handleDelete = () => {
|
||||
setDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (!boardData) return;
|
||||
|
||||
setIsDeleting(true);
|
||||
|
||||
try {
|
||||
const result = await deleteBoard(boardData.id);
|
||||
|
||||
const deleteDialog = useDeleteDialog({
|
||||
onDelete: async (id) => {
|
||||
const result = await deleteBoard(id);
|
||||
if (result.success) {
|
||||
await forceRefreshMenus();
|
||||
toast.success('게시판이 삭제되었습니다.');
|
||||
router.push(BASE_PATH);
|
||||
} else {
|
||||
setError(result.error || '삭제에 실패했습니다.');
|
||||
toast.error(result.error || '삭제에 실패했습니다.');
|
||||
setDeleteDialogOpen(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('게시판 삭제 실패:', err);
|
||||
setError('게시판 삭제 중 오류가 발생했습니다.');
|
||||
toast.error('게시판 삭제 중 오류가 발생했습니다.');
|
||||
setDeleteDialogOpen(false);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
},
|
||||
onSuccess: () => router.push(BASE_PATH),
|
||||
entityName: '게시판',
|
||||
});
|
||||
|
||||
// 수정 모드 전환
|
||||
const handleEdit = () => {
|
||||
@@ -271,13 +251,13 @@ export function BoardDetailClientV2({ boardId, initialMode }: BoardDetailClientV
|
||||
<BoardDetail
|
||||
board={boardData}
|
||||
onEdit={canUpdate ? handleEdit : undefined}
|
||||
onDelete={canDelete ? handleDelete : undefined}
|
||||
onDelete={canDelete ? () => deleteDialog.single.open(boardData!.id) : undefined}
|
||||
/>
|
||||
|
||||
<DeleteConfirmDialog
|
||||
open={deleteDialogOpen}
|
||||
onOpenChange={setDeleteDialogOpen}
|
||||
onConfirm={confirmDelete}
|
||||
open={deleteDialog.single.isOpen}
|
||||
onOpenChange={deleteDialog.single.onOpenChange}
|
||||
onConfirm={deleteDialog.single.confirm}
|
||||
title="게시판 삭제"
|
||||
description={
|
||||
<>
|
||||
@@ -288,7 +268,7 @@ export function BoardDetailClientV2({ boardId, initialMode }: BoardDetailClientV
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
loading={isDeleting}
|
||||
loading={deleteDialog.isPending}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user