diff options
| -rw-r--r-- | components/knox/approval/ApprovalCancel.tsx | 127 | ||||
| -rw-r--r-- | components/knox/approval/ApprovalDetail.tsx | 2 | ||||
| -rw-r--r-- | components/knox/approval/ApprovalList.tsx | 2 | ||||
| -rw-r--r-- | lib/knox-api/approval/approval.ts | 12 | ||||
| -rw-r--r-- | lib/material/table/material-detail-dialog.tsx | 112 |
5 files changed, 202 insertions, 53 deletions
diff --git a/components/knox/approval/ApprovalCancel.tsx b/components/knox/approval/ApprovalCancel.tsx index b40b43df..62afce94 100644 --- a/components/knox/approval/ApprovalCancel.tsx +++ b/components/knox/approval/ApprovalCancel.tsx @@ -7,6 +7,7 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Separator } from '@/components/ui/separator'; +import { Textarea } from '@/components/ui/textarea'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; import { toast } from 'sonner'; import { Loader2, XCircle, AlertTriangle, CheckCircle } from 'lucide-react'; @@ -47,6 +48,7 @@ export default function ApprovalCancel({ const [isCancelling, setIsCancelling] = useState(false); const [error, setError] = useState<string | null>(null); const [cancelResult, setCancelResult] = useState<{ apInfId: string } | null>(null); + const [cancelOpinion, setCancelOpinion] = useState(''); const fetchApprovalDetail = async () => { if (!apInfId.trim()) { @@ -58,11 +60,12 @@ export default function ApprovalCancel({ setError(null); setApprovalDetail(null); setCancelResult(null); + setCancelOpinion(''); try { const response = await getApprovalDetail(apInfId); - if (response.result === 'SUCCESS') { + if (response.result === 'success') { setApprovalDetail(response.data); } else { setError('결재 정보를 가져오는데 실패했습니다.'); @@ -80,12 +83,17 @@ export default function ApprovalCancel({ const handleCancelApproval = async () => { if (!approvalDetail) return; + if (!cancelOpinion.trim()) { + toast.error('상신취소 의견을 입력해주세요.'); + return; + } + setIsCancelling(true); try { - const response = await cancelApproval(approvalDetail.apInfId); + const response = await cancelApproval(approvalDetail.apInfId, cancelOpinion); - if (response.result === 'SUCCESS') { + if (response.result === 'success') { setCancelResult({ apInfId: response.data.apInfId }); toast.success('결재가 성공적으로 취소되었습니다.'); onCancelSuccess?.(response.data.apInfId); @@ -95,6 +103,9 @@ export default function ApprovalCancel({ ...approvalDetail, status: '4' // 상신취소 }); + + // 의견 초기화 + setCancelOpinion(''); } else { toast.error('결재 취소에 실패했습니다.'); } @@ -288,54 +299,78 @@ export default function ApprovalCancel({ </div> </div> - {/* 취소 버튼 */} + {/* 취소 의견 및 버튼 */} {canCancelApproval(approvalDetail.status) && ( <> <Separator /> - <div className="flex justify-end"> - <AlertDialog> - <AlertDialogTrigger asChild> - <Button variant="destructive" disabled={isCancelling}> - {isCancelling ? ( - <> - <Loader2 className="w-4 h-4 mr-2 animate-spin" /> - 취소 중... - </> - ) : ( - <> - <XCircle className="w-4 h-4 mr-2" /> - 결재 취소 - </> - )} - </Button> - </AlertDialogTrigger> - <AlertDialogContent> - <AlertDialogHeader> - <AlertDialogTitle>결재 취소 확인</AlertDialogTitle> - <AlertDialogDescription> - 정말로 이 결재를 취소하시겠습니까? - <br /> - <br /> - <strong>결재 ID:</strong> {approvalDetail.apInfId} - <br /> - <strong>제목:</strong> {approvalDetail.subject} - <br /> - <br /> - 이 작업은 되돌릴 수 없습니다. - </AlertDialogDescription> - </AlertDialogHeader> - <AlertDialogFooter> - <AlertDialogCancel>취소</AlertDialogCancel> - <AlertDialogAction - onClick={handleCancelApproval} - className="bg-red-600 hover:bg-red-700" + <div className="space-y-4"> + <div> + <Label htmlFor="cancelOpinion" className="text-sm font-medium"> + 상신취소 의견 <span className="text-red-500">*</span> + </Label> + <Textarea + id="cancelOpinion" + placeholder="상신취소 사유를 입력해주세요" + value={cancelOpinion} + onChange={(e) => setCancelOpinion(e.target.value)} + className="mt-1" + rows={3} + /> + <p className="text-xs text-gray-500 mt-1"> + 상신취소 의견은 필수 입력 항목입니다. + </p> + </div> + + <div className="flex justify-end"> + <AlertDialog> + <AlertDialogTrigger asChild> + <Button + variant="destructive" + disabled={isCancelling || !cancelOpinion.trim()} > - 결재 취소 - </AlertDialogAction> - </AlertDialogFooter> - </AlertDialogContent> - </AlertDialog> + {isCancelling ? ( + <> + <Loader2 className="w-4 h-4 mr-2 animate-spin" /> + 취소 중... + </> + ) : ( + <> + <XCircle className="w-4 h-4 mr-2" /> + 결재 취소 + </> + )} + </Button> + </AlertDialogTrigger> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle>결재 취소 확인</AlertDialogTitle> + <AlertDialogDescription> + 정말로 이 결재를 취소하시겠습니까? + <br /> + <br /> + <strong>결재 ID:</strong> {approvalDetail.apInfId} + <br /> + <strong>제목:</strong> {approvalDetail.subject} + <br /> + <strong>취소 의견:</strong> {cancelOpinion} + <br /> + <br /> + 이 작업은 되돌릴 수 없습니다. + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel>취소</AlertDialogCancel> + <AlertDialogAction + onClick={handleCancelApproval} + className="bg-red-600 hover:bg-red-700" + > + 결재 취소 + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + </div> </div> </> )} diff --git a/components/knox/approval/ApprovalDetail.tsx b/components/knox/approval/ApprovalDetail.tsx index d6c883cb..c36137b4 100644 --- a/components/knox/approval/ApprovalDetail.tsx +++ b/components/knox/approval/ApprovalDetail.tsx @@ -86,7 +86,7 @@ export default function ApprovalDetail({ getApprovalContent(id) ]); - if (detailResponse.result === 'SUCCESS' && contentResponse.result === 'SUCCESS') { + if (detailResponse.result.toLowerCase() === 'success' && contentResponse.result.toLowerCase() === 'success') { setApprovalData({ detail: detailResponse.data, content: contentResponse.data diff --git a/components/knox/approval/ApprovalList.tsx b/components/knox/approval/ApprovalList.tsx index 3a766901..13a13936 100644 --- a/components/knox/approval/ApprovalList.tsx +++ b/components/knox/approval/ApprovalList.tsx @@ -67,7 +67,7 @@ export default function ApprovalList({ response = await getApprovalHistory(); } - if (response.result === 'SUCCESS') { + if (response.result === 'success') { setListData(response.data as unknown as ListItem[]); } else { setError('목록을 가져오는데 실패했습니다.'); diff --git a/lib/knox-api/approval/approval.ts b/lib/knox-api/approval/approval.ts index 080867cd..fe78c8be 100644 --- a/lib/knox-api/approval/approval.ts +++ b/lib/knox-api/approval/approval.ts @@ -199,7 +199,7 @@ export async function submitApproval( } // Knox API 성공 시 데이터베이스에 저장 - if (result.result === 'SUCCESS') { + if (result.result === 'success') { try { await saveApprovalToDatabase( request.apInfId, @@ -473,14 +473,16 @@ export async function getApprovalHistory( /** * 상신 취소 - * POST /approval/api/v2.0/approvals/{apInfId}/cancel + * POST /approval/api/v2.0/approvals/{apInfId}/cancel?opinion={opinion} */ export async function cancelApproval( - apInfId: string + apInfId: string, + opinion: string ): Promise<CancelApprovalResponse> { try { const config = await getKnoxConfig(); - const response = await fetch(`${config.baseUrl}/approval/api/v2.0/approvals/${apInfId}/cancel`, { + const encodedOpinion = encodeURIComponent(opinion); + const response = await fetch(`${config.baseUrl}/approval/api/v2.0/approvals/${apInfId}/cancel?opinion=${encodedOpinion}`, { method: 'POST', headers: await createJsonHeaders(), }); @@ -492,7 +494,7 @@ export async function cancelApproval( const result = await response.json(); // Knox API 성공 시 데이터베이스에서 삭제 - if (result.result === 'SUCCESS') { + if (result.result === 'success') { try { await deleteApprovalFromDatabase(apInfId); } catch (dbError) { diff --git a/lib/material/table/material-detail-dialog.tsx b/lib/material/table/material-detail-dialog.tsx index aed0485c..80ec91f9 100644 --- a/lib/material/table/material-detail-dialog.tsx +++ b/lib/material/table/material-detail-dialog.tsx @@ -166,6 +166,118 @@ export function MaterialDetailDialog({ <TableCell>{data.material.GROES || "-"}</TableCell> </TableRow> <TableRow> + <TableCell className="font-medium">포장재료그룹 (MAGRV)</TableCell> + <TableCell>{data.material.MAGRV || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">치수단위 (MEABM)</TableCell> + <TableCell>{data.material.MEABM || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">자재상태일자 (MSTDE)</TableCell> + <TableCell>{data.material.MSTDE || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">제품계층 (PRDHA)</TableCell> + <TableCell>{data.material.PRDHA || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">포장재료유형 (VHART)</TableCell> + <TableCell>{data.material.VHART || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">부피단위 (VOLEH)</TableCell> + <TableCell>{data.material.VOLEH || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">활동코드 (ZZACT)</TableCell> + <TableCell>{data.material.ZZACT || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">승인일자 (ZZAPPDT)</TableCell> + <TableCell>{data.material.ZZAPPDT || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">승인시간 (ZZAPPTM)</TableCell> + <TableCell>{data.material.ZZAPPTM || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">승인사용자 (ZZAPPUS)</TableCell> + <TableCell>{data.material.ZZAPPUS || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">인증코드 (ZZCERT)</TableCell> + <TableCell>{data.material.ZZCERT || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">검사 (ZZINSP)</TableCell> + <TableCell>{data.material.ZZINSP || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">최종수정일자 (ZZLAMDT)</TableCell> + <TableCell>{data.material.ZZLAMDT || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">최종수정시간 (ZZLAMTM)</TableCell> + <TableCell>{data.material.ZZLAMTM || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">최종수정사용자 (ZZLAMUS)</TableCell> + <TableCell>{data.material.ZZLAMUS || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">자재마스터유형 (ZZMMTYP)</TableCell> + <TableCell>{data.material.ZZMMTYP || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">MRC (ZZMRC)</TableCell> + <TableCell>{data.material.ZZMRC || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">선행후행조정관 (ZZPRCD_SCV_CTLP)</TableCell> + <TableCell>{data.material.ZZPRCD_SCV_CTLP || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">CRUD 상태 (ZZPRFLG)</TableCell> + <TableCell>{data.material.ZZPRFLG || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">등록일자 (ZZREGDT)</TableCell> + <TableCell>{data.material.ZZREGDT || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">등록시간 (ZZREGTM)</TableCell> + <TableCell>{data.material.ZZREGTM || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">등록사용자 (ZZREGUS)</TableCell> + <TableCell>{data.material.ZZREGUS || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">REP 자재 (ZZREPMAT)</TableCell> + <TableCell>{data.material.ZZREPMAT || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">대표지름 (ZZREP_DIA)</TableCell> + <TableCell>{data.material.ZZREP_DIA || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">대표지름단위 (ZZREP_DIA_UOM)</TableCell> + <TableCell>{data.material.ZZREP_DIA_UOM || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">대표품명재질 (ZZREP_ITM_MATL)</TableCell> + <TableCell>{data.material.ZZREP_ITM_MATL || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">SM ID (ZZSMID)</TableCell> + <TableCell>{data.material.ZZSMID || "-"}</TableCell> + </TableRow> + <TableRow> + <TableCell className="font-medium">철강재료 (ZZSTL)</TableCell> + <TableCell>{data.material.ZZSTL || "-"}</TableCell> + </TableRow> + <TableRow> <TableCell className="font-medium">생성일시</TableCell> <TableCell>{formatDate(data.material.createdAt, "KR")}</TableCell> </TableRow> |
