summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/plant
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-10-15 12:52:11 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-10-15 12:52:11 +0000
commitb54f6f03150dd78d86db62201b6386bf14b72394 (patch)
treeb3092bb34805fdc65eee5282e86a9fb90ba20d6e /lib/vendor-document-list/plant
parentc1bd1a2f499ee2f0742170021b37dab410983ab7 (diff)
(대표님) 커버, 데이터룸, 파일매니저, 담당자할당 등
Diffstat (limited to 'lib/vendor-document-list/plant')
-rw-r--r--lib/vendor-document-list/plant/upload/columns.tsx198
-rw-r--r--lib/vendor-document-list/plant/upload/table.tsx37
2 files changed, 131 insertions, 104 deletions
diff --git a/lib/vendor-document-list/plant/upload/columns.tsx b/lib/vendor-document-list/plant/upload/columns.tsx
index 01fc61df..9c2fe228 100644
--- a/lib/vendor-document-list/plant/upload/columns.tsx
+++ b/lib/vendor-document-list/plant/upload/columns.tsx
@@ -17,16 +17,16 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
-import {
- Ellipsis,
- Upload,
- Eye,
- RefreshCw,
- CheckCircle2,
- XCircle,
+import {
+ Ellipsis,
+ Upload,
+ Eye,
+ RefreshCw,
+ CheckCircle2,
+ XCircle,
AlertCircle,
Clock,
- Download
+ Download
} from "lucide-react"
interface GetColumnsProps {
@@ -109,7 +109,7 @@ export function getColumns({
const stageName = row.getValue("stageName") as string
const stageStatus = row.original.stageStatus
const stageOrder = row.original.stageOrder
-
+
return (
<div className="space-y-1">
<div className="flex items-center gap-2">
@@ -119,12 +119,12 @@ export function getColumns({
<span className="text-sm">{stageName}</span>
</div>
{stageStatus && (
- <Badge
+ <Badge
variant={
stageStatus === "COMPLETED" ? "success" :
- stageStatus === "IN_PROGRESS" ? "default" :
- stageStatus === "REJECTED" ? "destructive" :
- "secondary"
+ stageStatus === "IN_PROGRESS" ? "default" :
+ stageStatus === "REJECTED" ? "destructive" :
+ "secondary"
}
className="text-xs"
>
@@ -145,9 +145,9 @@ export function getColumns({
const planDate = row.getValue("stagePlanDate") as Date | null
const isOverdue = row.original.isOverdue
const daysUntilDue = row.original.daysUntilDue
-
+
if (!planDate) return <span className="text-muted-foreground">-</span>
-
+
return (
<div className="space-y-1">
<div className={isOverdue ? "text-destructive font-medium" : ""}>
@@ -187,7 +187,7 @@ export function getColumns({
const reviewStatus = row.original.latestReviewStatus
const revisionNumber = row.original.latestRevisionNumber
const revisionCode = row.original.latestRevisionCode
-
+
if (!status) {
return (
<Badge variant="outline" className="gap-1">
@@ -196,20 +196,20 @@ export function getColumns({
</Badge>
)
}
-
+
return (
<div className="space-y-1">
- <Badge
+ <Badge
variant={
reviewStatus === "APPROVED" ? "success" :
- reviewStatus === "REJECTED" ? "destructive" :
- status === "SUBMITTED" ? "default" :
- "secondary"
+ reviewStatus === "REJECTED" ? "destructive" :
+ status === "SUBMITTED" ? "default" :
+ "secondary"
}
>
{reviewStatus || status}
</Badge>
- {revisionCode !== null &&(
+ {revisionCode !== null && (
<div className="text-xs text-muted-foreground">
{revisionCode}
</div>
@@ -229,7 +229,7 @@ export function getColumns({
const syncStatus = row.getValue("latestSyncStatus") as string | null
const syncProgress = row.original.syncProgress
const requiresSync = row.original.requiresSync
-
+
if (!syncStatus || syncStatus === "pending") {
if (requiresSync) {
return (
@@ -241,15 +241,15 @@ export function getColumns({
}
return <span className="text-muted-foreground">-</span>
}
-
+
return (
<div className="space-y-2">
- <Badge
+ <Badge
variant={
syncStatus === "synced" ? "success" :
- syncStatus === "failed" ? "destructive" :
- syncStatus === "syncing" ? "default" :
- "secondary"
+ syncStatus === "failed" ? "destructive" :
+ syncStatus === "syncing" ? "default" :
+ "secondary"
}
className="gap-1"
>
@@ -274,9 +274,9 @@ export function getColumns({
cell: ({ row }) => {
const totalFiles = row.getValue("totalFiles") as number
const syncedFiles = row.original.syncedFilesCount
-
+
if (!totalFiles) return <span className="text-muted-foreground">0</span>
-
+
return (
<div className="text-sm">
{syncedFiles !== null && syncedFiles !== undefined ? (
@@ -297,7 +297,7 @@ export function getColumns({
// cell: ({ row }) => {
// const vendorName = row.getValue("vendorName") as string
// const vendorCode = row.original.vendorCode
-
+
// return (
// <div className="space-y-1">
// <div className="text-sm">{vendorName}</div>
@@ -309,82 +309,88 @@ export function getColumns({
// },
// size: 150,
// },
- {
- id: "actions",
- enableHiding: false,
- cell: function Cell({ row }) {
- const requiresSubmission = row.original.requiresSubmission
- const requiresSync = row.original.requiresSync
- const latestSubmissionId = row.original.latestSubmissionId
-
- return (
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button
- aria-label="Open menu"
- variant="ghost"
- className="flex size-7 p-0"
+// columns.tsx
+{
+ id: "actions",
+ enableHiding: false,
+ cell: function Cell({ row }) {
+ const requiresSubmission = row.original.requiresSubmission
+ const requiresSync = row.original.requiresSync
+ const latestSubmissionId = row.original.latestSubmissionId
+ const projectCode = row.original.projectCode // 프로젝트 코드 가져오기
+
+ return (
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ aria-label="Open menu"
+ variant="ghost"
+ className="flex size-7 p-0"
+ >
+ <Ellipsis className="size-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end" className="w-48">
+ {requiresSubmission && (
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "upload" })}
+ className="gap-2"
+ >
+ <Upload className="h-4 w-4" />
+ Upload Documents
+ </DropdownMenuItem>
+ )}
+
+ {latestSubmissionId && (
+ <>
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "view" })}
+ className="gap-2"
>
- <Ellipsis className="size-4" />
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="end" className="w-48">
- {requiresSubmission && (
+ <Eye className="h-4 w-4" />
+ View Submission
+ </DropdownMenuItem>
+
+ {requiresSync && (
<DropdownMenuItem
- onSelect={() => setRowAction({ row, type: "upload" })}
+ onSelect={() => setRowAction({ row, type: "sync" })}
className="gap-2"
>
- <Upload className="h-4 w-4" />
- Upload Documents
+ <RefreshCw className="h-4 w-4" />
+ Retry Sync
</DropdownMenuItem>
)}
-
- {latestSubmissionId && (
- <>
- <DropdownMenuItem
- onSelect={() => setRowAction({ row, type: "view" })}
- className="gap-2"
- >
- <Eye className="h-4 w-4" />
- View Submission
- </DropdownMenuItem>
-
- {requiresSync && (
- <DropdownMenuItem
- onSelect={() => setRowAction({ row, type: "sync" })}
- className="gap-2"
- >
- <RefreshCw className="h-4 w-4" />
- Retry Sync
- </DropdownMenuItem>
- )}
- </>
- )}
-
-
- {/* ✅ 커버 페이지 다운로드 */}
- <DropdownMenuItem
- onSelect={() => setRowAction({ row, type: "downloadCover" })}
- className="gap-2"
- >
- <Download className="h-4 w-4" />
- Download Cover Page
- </DropdownMenuItem>
+ </>
+ )}
+ {/* ✅ 커버 페이지 다운로드 - projectCode가 있을 때만 표시 */}
+ {projectCode && (
+ <>
<DropdownMenuSeparator />
-
<DropdownMenuItem
- onSelect={() => setRowAction({ row, type: "history" })}
+ onSelect={() => setRowAction({ row, type: "downloadCover" })}
className="gap-2"
>
- <Clock className="h-4 w-4" />
- View History
+ <Download className="h-4 w-4" />
+ Download Cover Page
</DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
- )
- },
- size: 40,
- }
+ </>
+ )}
+
+ <DropdownMenuSeparator />
+
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "history" })}
+ className="gap-2"
+ >
+ <Clock className="h-4 w-4" />
+ View History
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ )
+ },
+ size: 40,
+}
]
} \ No newline at end of file
diff --git a/lib/vendor-document-list/plant/upload/table.tsx b/lib/vendor-document-list/plant/upload/table.tsx
index 84b04092..2247fc57 100644
--- a/lib/vendor-document-list/plant/upload/table.tsx
+++ b/lib/vendor-document-list/plant/upload/table.tsx
@@ -21,6 +21,7 @@ import { SingleUploadDialog } from "./components/single-upload-dialog"
import { HistoryDialog } from "./components/history-dialog"
import { ViewSubmissionDialog } from "./components/view-submission-dialog"
import { toast } from "sonner"
+import { quickDownload } from "@/lib/file-download"
interface StageSubmissionsTableProps {
promises: Promise<[
@@ -167,23 +168,43 @@ export function StageSubmissionsTable({ promises, selectedProjectId }: StageSubm
const { type, row } = rowAction;
if (type === "downloadCover") {
- // 2) 서버에서 생성 후 다운로드 (예: API 호출)
+ const projectCode = row.original.projectCode;
+ const project = projects.find(p => p.code === projectCode);
+
+ if (!project) {
+ toast.error("프로젝트 정보를 찾을 수 없습니다.");
+ setRowAction(null);
+ return;
+ }
+
(async () => {
try {
- const res = await fetch(`/api/stages/${row.original.stageId}/cover`, { method: "POST" });
- if (!res.ok) throw new Error("failed");
- const { fileUrl } = await res.json(); // 서버 응답: { fileUrl: string }
- window.open(fileUrl, "_blank", "noopener,noreferrer");
+ const res = await fetch(`/api/projects/${project.id}/cover`, {
+ method: "GET"
+ });
+
+ if (!res.ok) {
+ const error = await res.json();
+ throw new Error(error.message || "커버 페이지를 가져올 수 없습니다");
+ }
+
+ const { fileUrl, fileName } = await res.json();
+
+ // quickDownload 사용
+ quickDownload(fileUrl, fileName || `${projectCode}_cover.docx`);
+
+ toast.success("커버 페이지 다운로드를 시작했습니다.");
+
} catch (e) {
- toast.error("커버 페이지 생성에 실패했습니다.");
+ toast.error(e instanceof Error ? e.message : "커버 페이지 다운로드에 실패했습니다.");
console.error(e);
} finally {
setRowAction(null);
}
})();
}
- }, [rowAction, setRowAction]);
-
+ }, [rowAction, setRowAction, projects]);
+
return (
<>
<DataTable table={table}>