summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-10-03 13:54:38 +0900
committerjoonhoekim <26rote@gmail.com>2025-10-03 13:54:38 +0900
commit8945be5ea89365f8a686a0e65b5a7d5b61c2ca20 (patch)
treed7ee4acd93bcffacea3c095cb60d5a9c67998be9 /lib/vendor-document-list
parentdefda07c0bb4b0bd444ca8dc4fd3f89322bda0ce (diff)
(김준회) 부서별 권한관리, swp 코멘트 기능, 벤더 po, shi-api 동기화 로직 수정
Diffstat (limited to 'lib/vendor-document-list')
-rw-r--r--lib/vendor-document-list/plant/document-comment-dialog.tsx183
-rw-r--r--lib/vendor-document-list/plant/document-stage-toolbar.tsx2
-rw-r--r--lib/vendor-document-list/plant/document-stages-columns.tsx25
-rw-r--r--lib/vendor-document-list/plant/document-stages-service.ts53
-rw-r--r--lib/vendor-document-list/plant/document-stages-table.tsx17
-rw-r--r--lib/vendor-document-list/plant/shi-buyer-system-api.ts3
6 files changed, 277 insertions, 6 deletions
diff --git a/lib/vendor-document-list/plant/document-comment-dialog.tsx b/lib/vendor-document-list/plant/document-comment-dialog.tsx
new file mode 100644
index 00000000..3dc3d321
--- /dev/null
+++ b/lib/vendor-document-list/plant/document-comment-dialog.tsx
@@ -0,0 +1,183 @@
+"use client"
+
+import React from "react"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { Button } from "@/components/ui/button"
+import { Textarea } from "@/components/ui/textarea"
+import { Label } from "@/components/ui/label"
+import { ScrollArea } from "@/components/ui/scroll-area"
+import { Loader2, MessageSquare } from "lucide-react"
+import { toast } from "sonner"
+import { useSession } from "next-auth/react"
+import { addDocumentComment } from "./document-stages-service"
+import { useRouter } from "next/navigation"
+import { cn } from "@/lib/utils"
+
+interface DocumentCommentDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ documentId: number
+ docNumber: string
+ currentComment: string | null
+}
+
+export function DocumentCommentDialog({
+ open,
+ onOpenChange,
+ documentId,
+ docNumber,
+ currentComment,
+}: DocumentCommentDialogProps) {
+ const [newComment, setNewComment] = React.useState("")
+ const [isSubmitting, setIsSubmitting] = React.useState(false)
+ const { data: session } = useSession()
+ const router = useRouter()
+
+ // 기존 코멘트를 줄 단위로 파싱
+ const existingComments = React.useMemo(() => {
+ if (!currentComment) return []
+ return currentComment.split('\n').filter(line => line.trim() !== '')
+ }, [currentComment])
+
+ const handleSubmit = async () => {
+ if (!newComment.trim()) {
+ toast.error("코멘트 내용을 입력해주세요.")
+ return
+ }
+
+ if (!session?.user?.name || !session?.user?.email) {
+ toast.error("사용자 정보를 가져올 수 없습니다.")
+ return
+ }
+
+ setIsSubmitting(true)
+ try {
+ const result = await addDocumentComment({
+ documentId,
+ newComment: newComment.trim(),
+ userInfo: {
+ name: session.user.name,
+ email: session.user.email,
+ },
+ })
+
+ if (result.success) {
+ toast.success("코멘트가 추가되었습니다.")
+ setNewComment("")
+ onOpenChange(false)
+ router.refresh()
+ } else {
+ toast.error(result.error || "코멘트 추가 중 오류가 발생했습니다.")
+ }
+ } catch (error) {
+ console.error("Error adding comment:", error)
+ toast.error("코멘트 추가 중 오류가 발생했습니다.")
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ const handleClose = () => {
+ if (!isSubmitting) {
+ setNewComment("")
+ onOpenChange(false)
+ }
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={handleClose}>
+ <DialogContent className="sm:max-w-[600px] max-h-[80vh] flex flex-col">
+ <DialogHeader className="flex-shrink-0">
+ <DialogTitle className="flex items-center gap-2">
+ <MessageSquare className="h-5 w-5" />
+ Document Comment
+ </DialogTitle>
+ <DialogDescription>
+ 문서번호: <span className="font-mono font-medium">{docNumber}</span>
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="flex-1 flex flex-col gap-4 min-h-0">
+ {/* 기존 코멘트 표시 영역 (읽기 전용) */}
+ <div className="space-y-2">
+ <Label className="text-sm font-medium">기존 코멘트</Label>
+ <ScrollArea className="h-[200px] w-full rounded-md border p-4 bg-gray-50 dark:bg-gray-900">
+ {existingComments.length > 0 ? (
+ <div className="space-y-2">
+ {existingComments.map((comment, index) => (
+ <div
+ key={index}
+ className={cn(
+ "text-sm p-2 rounded",
+ index % 2 === 0
+ ? "bg-white dark:bg-gray-800"
+ : "bg-gray-100 dark:bg-gray-850"
+ )}
+ >
+ {comment}
+ </div>
+ ))}
+ </div>
+ ) : (
+ <div className="flex items-center justify-center h-full text-sm text-muted-foreground">
+ 기존 코멘트가 없습니다.
+ </div>
+ )}
+ </ScrollArea>
+ </div>
+
+ {/* 새 코멘트 입력 영역 */}
+ <div className="space-y-2">
+ <Label htmlFor="new-comment" className="text-sm font-medium">
+ 새 코멘트 추가
+ </Label>
+ <Textarea
+ id="new-comment"
+ value={newComment}
+ onChange={(e) => setNewComment(e.target.value)}
+ placeholder="새로운 코멘트를 입력하세요..."
+ rows={4}
+ className="resize-none"
+ disabled={isSubmitting}
+ />
+ <p className="text-xs text-muted-foreground">
+ 작성자 정보와 함께 코멘트가 추가됩니다: [{session?.user?.name}·{session?.user?.email}]
+ </p>
+ </div>
+ </div>
+
+ <DialogFooter className="flex-shrink-0">
+ <Button
+ variant="outline"
+ onClick={handleClose}
+ disabled={isSubmitting}
+ >
+ 닫기
+ </Button>
+ <Button
+ onClick={handleSubmit}
+ disabled={isSubmitting || !newComment.trim()}
+ >
+ {isSubmitting ? (
+ <>
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
+ 추가 중...
+ </>
+ ) : (
+ "코멘트 추가"
+ )}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+}
+
+
diff --git a/lib/vendor-document-list/plant/document-stage-toolbar.tsx b/lib/vendor-document-list/plant/document-stage-toolbar.tsx
index f676e1fc..51767528 100644
--- a/lib/vendor-document-list/plant/document-stage-toolbar.tsx
+++ b/lib/vendor-document-list/plant/document-stage-toolbar.tsx
@@ -103,7 +103,7 @@ export function DocumentsTableToolbarActions({
{(() => {
const selectedRows = table.getFilteredSelectedRowModel().rows;
const deletableDocuments = selectedRows
- .map((row) => row.original)s
+ .map((row) => row.original)
.filter((doc) => !doc.buyerSystemStatus); // buyerSystemStatus가 null인 것만 필터링
return deletableDocuments.length > 0 ? (
diff --git a/lib/vendor-document-list/plant/document-stages-columns.tsx b/lib/vendor-document-list/plant/document-stages-columns.tsx
index af68ddb2..d5dfc895 100644
--- a/lib/vendor-document-list/plant/document-stages-columns.tsx
+++ b/lib/vendor-document-list/plant/document-stages-columns.tsx
@@ -380,14 +380,31 @@ export function getDocumentStagesColumns({
),
cell: ({ row }) => {
const doc = row.original
+ const hasComment = doc.buyerSystemComment && doc.buyerSystemComment.trim() !== ''
return (
- <div className="flex items-center gap-2">
- {doc.buyerSystemComment}
- </div>
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-8 gap-2"
+ onClick={(e) => {
+ e.stopPropagation()
+ setRowAction({ row, type: "view_comment" })
+ }}
+ >
+ <MessageSquare className={cn(
+ "h-4 w-4",
+ hasComment ? "text-blue-600 dark:text-blue-400" : "text-gray-400"
+ )} />
+ {hasComment ? (
+ <span className="text-xs">View ({doc.buyerSystemComment.split('\n').length})</span>
+ ) : (
+ <span className="text-xs text-muted-foreground">Add</span>
+ )}
+ </Button>
)
},
- size: 180,
+ size: 150,
enableResizing: true,
meta: {
excelHeader: "Document Comment"
diff --git a/lib/vendor-document-list/plant/document-stages-service.ts b/lib/vendor-document-list/plant/document-stages-service.ts
index 5f803104..cff448d5 100644
--- a/lib/vendor-document-list/plant/document-stages-service.ts
+++ b/lib/vendor-document-list/plant/document-stages-service.ts
@@ -1201,6 +1201,59 @@ export async function pullDocumentStatusFromSHI(
}
}
+// 문서 코멘트 추가
+export async function addDocumentComment(input: {
+ documentId: number
+ newComment: string
+ userInfo: {
+ name: string
+ email: string
+ }
+}) {
+ try {
+ // 1. 현재 문서 정보 조회
+ const document = await db.query.stageDocuments.findFirst({
+ where: eq(stageDocuments.id, input.documentId),
+ })
+
+ if (!document) {
+ return { success: false, error: "문서를 찾을 수 없습니다." }
+ }
+
+ // 2. 새 코멘트 포맷팅: [이름·이메일] 코멘트내용
+ const timestamp = new Date().toISOString().split('T')[0] // YYYY-MM-DD
+ const formattedComment = `[${input.userInfo.name}·${input.userInfo.email}] ${timestamp}: ${input.newComment}`
+
+ // 3. 기존 코멘트와 결합
+ const updatedComment = document.buyerSystemComment
+ ? `${document.buyerSystemComment}\n${formattedComment}`
+ : formattedComment
+
+ // 4. 문서 업데이트
+ await db
+ .update(stageDocuments)
+ .set({
+ buyerSystemComment: updatedComment,
+ updatedAt: new Date(),
+ })
+ .where(eq(stageDocuments.id, input.documentId))
+
+ // 5. 캐시 무효화
+ revalidatePath(`/partners/document-list-only/${document.contractId}`)
+
+ return {
+ success: true,
+ data: { comment: updatedComment }
+ }
+ } catch (error) {
+ console.error("코멘트 추가 실패:", error)
+ return {
+ success: false,
+ error: "코멘트 추가 중 오류가 발생했습니다."
+ }
+ }
+}
+
interface FileValidation {
projectId: number
diff --git a/lib/vendor-document-list/plant/document-stages-table.tsx b/lib/vendor-document-list/plant/document-stages-table.tsx
index 6cc112e3..63f0eae6 100644
--- a/lib/vendor-document-list/plant/document-stages-table.tsx
+++ b/lib/vendor-document-list/plant/document-stages-table.tsx
@@ -34,6 +34,7 @@ import { EditDocumentDialog } from "./document-stage-dialogs"
import { EditStageDialog } from "./document-stage-dialogs"
import { DocumentsTableToolbarActions } from "./document-stage-toolbar"
import { useSession } from "next-auth/react"
+import { DocumentCommentDialog } from "./document-comment-dialog"
interface DocumentStagesTableProps {
promises: Promise<[Awaited<ReturnType<typeof getDocumentStagesOnly>>]>
@@ -68,6 +69,7 @@ export function DocumentStagesTable({
const [editDocumentOpen, setEditDocumentOpen] = React.useState(false)
const [editStageOpen, setEditStageOpen] = React.useState(false)
const [excelImportOpen, setExcelImportOpen] = React.useState(false)
+ const [commentDialogOpen, setCommentDialogOpen] = React.useState(false)
// 선택된 항목들
const [selectedDocument, setSelectedDocument] = React.useState<StageDocumentsView | null>(null)
@@ -101,6 +103,9 @@ export function DocumentStagesTable({
}
setExpandedRows(newExpanded)
break
+ case "view_comment":
+ setCommentDialogOpen(true)
+ break
}
}
},
@@ -164,6 +169,7 @@ export function DocumentStagesTable({
setEditDocumentOpen(false)
setEditStageOpen(false)
setExcelImportOpen(false)
+ setCommentDialogOpen(false)
setSelectedDocument(null)
setSelectedStageId(null)
setRowAction(null)
@@ -413,6 +419,17 @@ export function DocumentStagesTable({
documents={rowAction?.row.original ? [rowAction?.row.original] : []}
onSuccess={() => rowAction?.row.toggleSelected(false)}
/>
+
+ <DocumentCommentDialog
+ open={commentDialogOpen}
+ onOpenChange={(open) => {
+ if (!open) closeAllDialogs()
+ else setCommentDialogOpen(open)
+ }}
+ documentId={selectedDocument?.documentId || 0}
+ docNumber={selectedDocument?.docNumber || ''}
+ currentComment={selectedDocument?.buyerSystemComment || null}
+ />
</div>
)
} \ No newline at end of file
diff --git a/lib/vendor-document-list/plant/shi-buyer-system-api.ts b/lib/vendor-document-list/plant/shi-buyer-system-api.ts
index b0462af8..9256eaf4 100644
--- a/lib/vendor-document-list/plant/shi-buyer-system-api.ts
+++ b/lib/vendor-document-list/plant/shi-buyer-system-api.ts
@@ -246,6 +246,7 @@ export class ShiBuyerSystemAPI {
vendorDocNumber: stageDocuments.vendorDocNumber,
title: stageDocuments.title,
status: stageDocuments.status,
+ buyerSystemComment: stageDocuments.buyerSystemComment, // 코멘트 필드 추가
projectCode: sql<string>`(SELECT code FROM projects WHERE id = ${stageDocuments.projectId})`,
vendorCode: sql<string>`(SELECT vendor_code FROM vendors WHERE id = ${stageDocuments.vendorId})`,
vendorName: sql<string>`(SELECT vendor_name FROM vendors WHERE id = ${stageDocuments.vendorId})`,
@@ -300,7 +301,7 @@ export class ShiBuyerSystemAPI {
OWN_DOC_NO: doc.vendorDocNumber || doc.docNumber,
DSC: doc.title,
DOC_CLASS: 'B3',
- COMMENT: '',
+ COMMENT: doc.buyerSystemComment || '', // 실제 코멘트 전송
// 조민정 프로 요청으로 'ACTIVE' --> '생성요청' 값으로 변경 (251002,김준회)
STATUS: '생성요청',
CRTER: 'EVCP_SYSTEM',