feat: 기안함/결재함 상세 모달 버튼 분기 및 수정 기능 추가

- 기안함 임시저장 상세: 복제, 상신, 인쇄 버튼 표시
- 기안함 결재대기 이후 상세: 인쇄만 표시
- 결재함 상세: 수정, 반려, 승인, 인쇄 버튼 표시
- 결재함 리스트 작업컬럼 수정 버튼 → 기안함 수정 페이지 이동
- DocumentDetailModal mode/documentStatus 기반 조건부 렌더링

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-29 21:14:08 +09:00
parent 388b113b58
commit 0e5307f7a3
5 changed files with 163 additions and 47 deletions

View File

@@ -13,6 +13,7 @@ import { toast } from 'sonner';
import {
getDrafts,
getDraftsSummary,
getDraftById,
deleteDraft,
deleteDrafts,
submitDraft,
@@ -238,14 +239,21 @@ export function DraftBox() {
// ===== 문서 클릭/수정 핸들러 (조건부 로직) =====
// 임시저장 → 문서 작성 페이지 (수정 모드)
// 그 외 → 문서 상세 모달
const handleDocumentClick = useCallback((item: DraftRecord) => {
// 그 외 → 문서 상세 모달 (상세 API 호출하여 content 포함된 데이터 가져옴)
const handleDocumentClick = useCallback(async (item: DraftRecord) => {
if (item.status === 'draft') {
// 임시저장 상태 → 문서 작성 페이지로 이동 (수정 모드)
router.push(`/ko/approval/draft/new?id=${item.id}&mode=edit`);
} else {
// 그 외 상태 → 문서 상세 모달 열기
setSelectedDocument(item);
// 그 외 상태 → 문서 상세 API 호출 후 모달 열기
// 목록 API에서는 content가 포함되지 않을 수 있으므로 상세 조회 필요
const detailData = await getDraftById(item.id);
if (detailData) {
setSelectedDocument(detailData);
} else {
// 상세 조회 실패 시 기존 데이터 사용
setSelectedDocument(item);
}
setIsModalOpen(true);
}
}, [router]);
@@ -258,9 +266,14 @@ export function DraftBox() {
}, [selectedDocument, router]);
const handleModalCopy = useCallback(() => {
console.log('[DraftBox] handleModalCopy 호출됨, selectedDocument:', selectedDocument);
if (selectedDocument) {
router.push(`/ko/approval/draft/new?copyFrom=${selectedDocument.id}`);
const copyUrl = `/ko/approval/draft/new?copyFrom=${selectedDocument.id}`;
console.log('[DraftBox] 복제 URL로 이동:', copyUrl);
router.push(copyUrl);
setIsModalOpen(false);
} else {
console.log('[DraftBox] selectedDocument가 없음');
}
}, [selectedDocument, router]);
@@ -276,6 +289,29 @@ export function DraftBox() {
setIsModalOpen(false);
}, []);
// ===== 모달에서 상신 핸들러 =====
const handleModalSubmit = useCallback(async () => {
if (!selectedDocument) return;
startTransition(async () => {
try {
const result = await submitDraft(selectedDocument.id);
if (result.success) {
toast.success('문서를 상신했습니다.');
setIsModalOpen(false);
setSelectedDocument(null);
loadData();
loadSummary();
} else {
toast.error(result.error || '상신에 실패했습니다.');
}
} catch (error) {
console.error('Submit error:', error);
toast.error('상신 중 오류가 발생했습니다.');
}
});
}, [selectedDocument, loadData, loadSummary]);
// ===== DraftRecord → 모달용 데이터 변환 =====
const getDocumentType = (item: DraftRecord): DocumentType => {
// documentTypeCode 우선 사용, 없으면 documentType(양식명)으로 추론
@@ -371,7 +407,11 @@ export function DraftBox() {
}
default: {
// proposal (품의서)
// API content 구조: { vendor, vendorPaymentDate, title, description, reason, estimatedCost }
// API content 구조: { vendor, vendorPaymentDate, title, description, reason, estimatedCost, files }
const files = (content.files as Array<{ id: number; name: string; url?: string }>) || [];
// Next.js 프록시 URL 사용 (인증된 요청 프록시)
const attachmentUrls = files.map(f => `/api/files/${f.id}/download`);
return {
documentNo: item.documentNo,
createdAt: item.draftDate,
@@ -381,7 +421,7 @@ export function DraftBox() {
description: (content.description as string) || '',
reason: (content.reason as string) || '',
estimatedCost: (content.estimatedCost as number) || 0,
attachments: [],
attachments: attachmentUrls,
approvers,
drafter,
};
@@ -451,7 +491,8 @@ export function DraftBox() {
</Badge>
</TableCell>
<TableCell className="text-center" onClick={(e) => e.stopPropagation()}>
{isSelected && (
{/* 임시저장 상태일 때만 수정/삭제 버튼 표시 */}
{isSelected && item.status === 'draft' && (
<div className="flex items-center justify-center gap-1">
<Button
variant="ghost"
@@ -510,7 +551,8 @@ export function DraftBox() {
</div>
}
actions={
isSelected ? (
/* 임시저장 상태일 때만 수정/삭제 버튼 표시 */
isSelected && item.status === 'draft' ? (
<div className="flex gap-2">
<Button variant="outline" className="flex-1" onClick={() => handleDocumentClick(item)}>
<Pencil className="w-4 h-4 mr-2" />
@@ -628,10 +670,11 @@ export function DraftBox() {
onOpenChange={setIsModalOpen}
documentType={getDocumentType(selectedDocument)}
data={convertToModalData(selectedDocument)}
mode="draft"
documentStatus={selectedDocument.status}
onEdit={handleModalEdit}
onCopy={handleModalCopy}
onApprove={handleModalApprove}
onReject={handleModalReject}
onSubmit={handleModalSubmit}
/>
)}
</>