summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/[lng]/evcp/data-room/[projectId]/members/page.tsx41
-rw-r--r--app/[lng]/evcp/data-room/[projectId]/settings/page.tsx20
-rw-r--r--components/file-manager/FileManager.tsx874
-rw-r--r--components/layout/HeaderDataroom.tsx24
-rw-r--r--components/layout/HeaderSimple.tsx4
-rw-r--r--components/project/ProjectDashboard.tsx4
-rw-r--r--components/project/ProjectNav.tsx14
-rw-r--r--i18n/locales/en/menu.json4
-rw-r--r--i18n/locales/ko/menu.json4
-rw-r--r--lib/forms/services.ts7
-rw-r--r--lib/gtc-contract/service.ts1
-rw-r--r--lib/owner-companies/service.ts4
12 files changed, 695 insertions, 306 deletions
diff --git a/app/[lng]/evcp/data-room/[projectId]/members/page.tsx b/app/[lng]/evcp/data-room/[projectId]/members/page.tsx
index 18442c0e..dbd5e37d 100644
--- a/app/[lng]/evcp/data-room/[projectId]/members/page.tsx
+++ b/app/[lng]/evcp/data-room/[projectId]/members/page.tsx
@@ -105,7 +105,7 @@ export default function ProjectMembersPage({
const [roleFilter, setRoleFilter] = useState<string>('all');
const [addMemberOpen, setAddMemberOpen] = useState(false);
const [editingMember, setEditingMember] = useState<Member | null>(null);
-
+
// 사용자 선택 관련 상태
const [availableUsers, setAvailableUsers] = useState<User[]>([]);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
@@ -113,12 +113,12 @@ export default function ProjectMembersPage({
const [userPopoverOpen, setUserPopoverOpen] = useState(false);
const [loadingUsers, setLoadingUsers] = useState(false);
const [isExternalUser, setIsExternalUser] = useState(false); // 외부 사용자 여부
-
+
const [newMemberRole, setNewMemberRole] = useState<string>('viewer');
const [currentUserRole, setCurrentUserRole] = useState<string>('viewer');
const [page, setPage] = useState(1);
const pageSize = 20;
-
+
// Command component key management
const userOptionIdsRef = useRef<Record<number, string>>({});
const popoverContentId = `popover-content-${Date.now()}`;
@@ -284,7 +284,7 @@ export default function ProjectMembersPage({
const handleSelectUser = (user: User) => {
setSelectedUser(user);
setUserPopoverOpen(false);
-
+
// 외부 사용자(partners)인 경우 역할을 viewer로 고정
if (user.domain === 'partners') {
setIsExternalUser(true);
@@ -319,6 +319,7 @@ export default function ProjectMembersPage({
user.email.toLowerCase().includes(userSearchTerm.toLowerCase())
);
+
const canManageMembers = currentUserRole === 'owner' || currentUserRole === 'admin';
const totalPages = Math.max(1, Math.ceil(filteredMembers.length / pageSize));
@@ -577,7 +578,7 @@ export default function ProjectMembersPage({
<TabsContent value="internal" className="space-y-4 mt-4">
<div className="space-y-2">
<Label htmlFor="internal-user">사용자 선택</Label>
-
+
{loadingUsers ? (
<div className="flex items-center justify-center py-4">
<Loader2 className="h-4 w-4 animate-spin" />
@@ -665,8 +666,8 @@ export default function ProjectMembersPage({
<div className="space-y-2">
<Label htmlFor="internal-role">역할</Label>
- <Select
- value={newMemberRole}
+ <Select
+ value={newMemberRole}
onValueChange={setNewMemberRole}
disabled={!selectedUser || isExternalUser}
>
@@ -685,14 +686,14 @@ export default function ProjectMembersPage({
<TabsContent value="external" className="space-y-4 mt-4">
<div className="rounded-lg bg-amber-50 border border-amber-200 p-3 mb-4">
<p className="text-sm text-amber-800">
- <strong>보안 정책 안내</strong><br/>
+ <strong>보안 정책 안내</strong><br />
외부 사용자(파트너)는 보안 정책상 Viewer 권한만 부여 가능합니다.
</p>
</div>
<div className="space-y-2">
<Label htmlFor="external-user">파트너 선택</Label>
-
+
{loadingUsers ? (
<div className="flex items-center justify-center py-4">
<Loader2 className="h-4 w-4 animate-spin" />
@@ -723,7 +724,7 @@ export default function ProjectMembersPage({
<PopoverContent className="w-[460px] p-0">
<Command>
<CommandInput
- placeholder="이름으로 검색..."
+ placeholder="이름 또는 이메일로 검색..."
value={userSearchTerm}
onValueChange={setUserSearchTerm}
/>
@@ -738,7 +739,7 @@ export default function ProjectMembersPage({
<CommandEmpty>파트너를 찾을 수 없습니다.</CommandEmpty>
<CommandGroup heading="파트너 목록">
{filteredUsers
- .filter(u => u.domain === 'partners')
+ .filter(u => u.ownerCompanyId !== null)
.map((user) => (
<CommandItem
key={user.id}
@@ -748,12 +749,14 @@ export default function ProjectMembersPage({
setIsExternalUser(true);
setNewMemberRole('viewer');
}}
- value={user.name}
+ value={`${user.name} ${user.email}`}
className="truncate"
>
- <Users className="mr-2 h-4 w-4 text-amber-600" />
- <span className="truncate flex-1">{user.name}</span>
- <Badge variant="outline" className="text-xs mx-2">파트너</Badge>
+ <Users className="mr-2 h-4 w-4 text-amber-600 flex-shrink-0" />
+ <div className="flex-1 truncate">
+ <div className="font-medium truncate">{user.name}</div>
+ <div className="text-xs text-muted-foreground truncate">{user.email}</div>
+ </div>
<Check
className={cn(
"ml-auto h-4 w-4 flex-shrink-0",
@@ -785,8 +788,8 @@ export default function ProjectMembersPage({
</Tabs>
<DialogFooter>
- <Button
- variant="outline"
+ <Button
+ variant="outline"
onClick={() => {
setAddMemberOpen(false);
setSelectedUser(null);
@@ -797,8 +800,8 @@ export default function ProjectMembersPage({
>
취소
</Button>
- <Button
- onClick={addMember}
+ <Button
+ onClick={addMember}
disabled={!selectedUser}
>
추가하기
diff --git a/app/[lng]/evcp/data-room/[projectId]/settings/page.tsx b/app/[lng]/evcp/data-room/[projectId]/settings/page.tsx
index aa0f3b52..fc132e65 100644
--- a/app/[lng]/evcp/data-room/[projectId]/settings/page.tsx
+++ b/app/[lng]/evcp/data-room/[projectId]/settings/page.tsx
@@ -2,7 +2,7 @@
// app/projects/[projectId]/settings/page.tsx
'use client';
-import { useState, useEffect } from 'react';
+import { useState, useEffect ,use} from 'react';
import {
Settings,
Shield,
@@ -59,8 +59,12 @@ interface ProjectSettings {
export default function ProjectSettingsPage({
params
}: {
- params: { projectId: string }
+ params: Promise<{ projectId: string }>
}) {
+
+ const { projectId } = use(params);
+
+
const [settings, setSettings] = useState<ProjectSettings | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
@@ -74,12 +78,12 @@ export default function ProjectSettingsPage({
useEffect(() => {
fetchSettings();
checkUserRole();
- }, [params.projectId]);
+ }, [projectId]);
const fetchSettings = async () => {
try {
setLoading(true);
- const response = await fetch(`/api/projects/${params.projectId}/settings`);
+ const response = await fetch(`/api/projects/${projectId}/settings`);
if (!response.ok) {
throw new Error('설정을 불러올 수 없습니다');
@@ -100,7 +104,7 @@ export default function ProjectSettingsPage({
const checkUserRole = async () => {
try {
- const response = await fetch(`/api/projects/${params.projectId}/access`);
+ const response = await fetch(`/api/projects/${projectId}/access`);
const data = await response.json();
setCurrentUserRole(data.role);
} catch (error) {
@@ -113,7 +117,7 @@ export default function ProjectSettingsPage({
try {
setSaving(true);
- const response = await fetch(`/api/projects/${params.projectId}/settings`, {
+ const response = await fetch(`/api/projects/${projectId}/settings`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settings),
@@ -138,7 +142,7 @@ export default function ProjectSettingsPage({
const deleteProject = async () => {
try {
- const response = await fetch(`/api/projects/${params.projectId}`, {
+ const response = await fetch(`/api/projects/${projectId}`, {
method: 'DELETE',
});
@@ -161,7 +165,7 @@ export default function ProjectSettingsPage({
const archiveProject = async () => {
try {
- const response = await fetch(`/api/projects/${params.projectId}/archive`, {
+ const response = await fetch(`/api/projects/${projectId}/archive`, {
method: 'POST',
});
diff --git a/components/file-manager/FileManager.tsx b/components/file-manager/FileManager.tsx
index f92f6b04..1af29e74 100644
--- a/components/file-manager/FileManager.tsx
+++ b/components/file-manager/FileManager.tsx
@@ -115,6 +115,7 @@ interface FileItem {
viewCount?: number;
parentId?: string | null;
children?: FileItem[];
+ filePath?: string;
}
interface UploadingFile {
@@ -151,6 +152,7 @@ const TreeItem: React.FC<{
onDelete: (ids: string[]) => void;
onShare: (item: FileItem) => void;
onRename: (item: FileItem) => void;
+ onCategoryChange: (item: FileItem) => void;
isInternalUser: boolean;
}> = ({
item,
@@ -166,6 +168,7 @@ const TreeItem: React.FC<{
onDelete,
onShare,
onRename,
+ onCategoryChange,
isInternalUser
}) => {
const hasChildren = item.type === 'folder' && item.children && item.children.length > 0;
@@ -184,113 +187,180 @@ const TreeItem: React.FC<{
return (
<>
- <div
- className={cn(
- "flex items-center p-2 rounded-lg cursor-pointer transition-colors",
- "hover:bg-accent",
- isSelected && "bg-accent"
- )}
- style={{ paddingLeft: `${level * 24 + 8}px` }}
- onClick={() => onSelectItem(item.id)}
- onDoubleClick={() => onDoubleClick(item)}
- >
- <div className="flex items-center mr-2">
- {item.type === 'folder' && (
- <button
- onClick={(e) => {
- e.stopPropagation();
- onToggleExpand(item.id);
- }}
- className="p-0.5 hover:bg-gray-200 rounded"
- >
- {isExpanded ? (
- <ChevronDown className="h-4 w-4" />
- ) : (
- <ChevronRight className="h-4 w-4" />
+ <ContextMenu>
+ <ContextMenuTrigger>
+ <div
+ className={cn(
+ "flex items-center p-2 rounded-lg cursor-pointer transition-colors",
+ "hover:bg-accent",
+ isSelected && "bg-accent"
+ )}
+ style={{ paddingLeft: `${level * 24 + 8}px` }}
+ onClick={() => onSelectItem(item.id)}
+ onDoubleClick={() => onDoubleClick(item)}
+ >
+ <div className="flex items-center mr-2">
+ {item.type === 'folder' && (
+ <button
+ onClick={(e) => {
+ e.stopPropagation();
+ onToggleExpand(item.id);
+ }}
+ className="p-0.5 hover:bg-gray-200 rounded"
+ >
+ {isExpanded ? (
+ <ChevronDown className="h-4 w-4" />
+ ) : (
+ <ChevronRight className="h-4 w-4" />
+ )}
+ </button>
)}
- </button>
- )}
- {item.type === 'file' && (
- <div className="w-5" />
- )}
- </div>
+ {item.type === 'file' && (
+ <div className="w-5" />
+ )}
+ </div>
- {item.type === 'folder' ? (
- <Folder className="h-5 w-5 text-blue-500 mr-2" />
- ) : (
- <File className="h-5 w-5 text-gray-500 mr-2" />
- )}
-
- <span className="flex-1">{item.name}</span>
-
- <Badge variant="outline" className="mr-2">
- <CategoryIcon className={cn("h-3 w-3 mr-1", categoryColor)} />
- {categoryLabel}
- </Badge>
-
- <span className="text-sm text-muted-foreground mr-4">
- {formatFileSize(item.size)}
- </span>
- <span className="text-sm text-muted-foreground mr-2">
- {new Date(item.updatedAt).toLocaleDateString()}
- </span>
-
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button variant="ghost" size="sm">
- <MoreVertical className="h-4 w-4" />
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent>
- {item.type === 'file' && (
- <>
- <DropdownMenuItem onClick={() => onView(item)}>
- <Eye className="h-4 w-4 mr-2" />
- View
- </DropdownMenuItem>
- {item.permissions?.canDownload && (
- <DropdownMenuItem onClick={() => onDownload(item)}>
+ {item.type === 'folder' ? (
+ <Folder className="h-5 w-5 text-blue-500 mr-2" />
+ ) : (
+ <File className="h-5 w-5 text-gray-500 mr-2" />
+ )}
+
+ <span className="flex-1">{item.name}</span>
+
+ <Badge variant="outline" className="mr-2">
+ <CategoryIcon className={cn("h-3 w-3 mr-1", categoryColor)} />
+ {categoryLabel}
+ </Badge>
+
+ <span className="text-sm text-muted-foreground mr-4">
+ {formatFileSize(item.size)}
+ </span>
+ <span className="text-sm text-muted-foreground mr-2">
+ {new Date(item.updatedAt).toLocaleDateString()}
+ </span>
+
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="ghost" size="sm">
+ <MoreVertical className="h-4 w-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent>
+ {item.type === 'file' && (
+ <>
+ <DropdownMenuItem onClick={() => onView(item)}>
+ <Eye className="h-4 w-4 mr-2" />
+ View
+ </DropdownMenuItem>
+ {item.permissions?.canDownload && (
+ <DropdownMenuItem onClick={() => onDownload(item)}>
+ <Download className="h-4 w-4 mr-2" />
+ Download
+ </DropdownMenuItem>
+ )}
+ </>
+ )}
+
+ {item.type === 'folder' && (
+ <DropdownMenuItem onClick={() => onDownloadFolder(item)}>
<Download className="h-4 w-4 mr-2" />
- Download
+ Download Folder
</DropdownMenuItem>
)}
- </>
- )}
- {item.type === 'folder' && (
- <DropdownMenuItem onClick={() => onDownloadFolder(item)}>
+ {isInternalUser && (
+ <>
+ <DropdownMenuSeparator />
+ {item.permissions?.canEdit && (
+ <DropdownMenuItem onClick={() => onRename(item)}>
+ <Edit2 className="h-4 w-4 mr-2" />
+ Rename
+ </DropdownMenuItem>
+ )}
+ <DropdownMenuItem onClick={() => onCategoryChange(item)}>
+ <Shield className="h-4 w-4 mr-2" />
+ Change Category
+ </DropdownMenuItem>
+ </>
+ )}
+
+ {item.permissions?.canDelete && (
+ <>
+ <DropdownMenuSeparator />
+ <DropdownMenuItem
+ className="text-destructive"
+ onClick={() => onDelete([item.id])}
+ >
+ <Trash2 className="h-4 w-4 mr-2" />
+ Delete
+ </DropdownMenuItem>
+ </>
+ )}
+ </DropdownMenuContent>
+ </DropdownMenu>
+ </div>
+ </ContextMenuTrigger>
+
+ <ContextMenuContent>
+ {item.type === 'file' && (
+ <>
+ <ContextMenuItem onClick={() => onView(item)}>
+ <Eye className="h-4 w-4 mr-2" />
+ View
+ </ContextMenuItem>
+ {item.permissions?.canDownload && (
+ <ContextMenuItem onClick={() => onDownload(item)}>
+ <Download className="h-4 w-4 mr-2" />
+ Download
+ </ContextMenuItem>
+ )}
+ </>
+ )}
+
+ {item.type === 'folder' && (
+ <>
+ <ContextMenuItem onClick={() => onDoubleClick(item)}>
+ <Folder className="h-4 w-4 mr-2" />
+ {isExpanded ? 'Collapse' : 'Expand'}
+ </ContextMenuItem>
+ <ContextMenuItem onClick={() => onDownloadFolder(item)}>
<Download className="h-4 w-4 mr-2" />
Download Folder
- </DropdownMenuItem>
- )}
-
- {isInternalUser && (
- <>
+ </ContextMenuItem>
+ </>
+ )}
- {item.permissions?.canEdit && (
- <DropdownMenuItem onClick={() => onRename(item)}>
- <Edit2 className="h-4 w-4 mr-2" />
- Rename
- </DropdownMenuItem>
- )}
- </>
- )}
+ {isInternalUser && (
+ <>
+ <ContextMenuSeparator />
+ {item.permissions?.canEdit && (
+ <ContextMenuItem onClick={() => onRename(item)}>
+ <Edit2 className="h-4 w-4 mr-2" />
+ Rename
+ </ContextMenuItem>
+ )}
+ <ContextMenuItem onClick={() => onCategoryChange(item)}>
+ <Shield className="h-4 w-4 mr-2" />
+ Change Category
+ </ContextMenuItem>
+ </>
+ )}
- {item.permissions?.canDelete && (
- <>
- <DropdownMenuSeparator />
- <DropdownMenuItem
- className="text-destructive"
- onClick={() => onDelete([item.id])}
- >
- <Trash2 className="h-4 w-4 mr-2" />
- Delete
- </DropdownMenuItem>
- </>
- )}
- </DropdownMenuContent>
- </DropdownMenu>
- </div>
+ {item.permissions?.canDelete && (
+ <>
+ <ContextMenuSeparator />
+ <ContextMenuItem
+ className="text-destructive"
+ onClick={() => onDelete([item.id])}
+ >
+ <Trash2 className="h-4 w-4 mr-2" />
+ Delete
+ </ContextMenuItem>
+ </>
+ )}
+ </ContextMenuContent>
+ </ContextMenu>
{item.type === 'folder' && isExpanded && item.children && (
<div>
@@ -310,6 +380,7 @@ const TreeItem: React.FC<{
onDelete={onDelete}
onShare={onShare}
onRename={onRename}
+ onCategoryChange={onCategoryChange}
isInternalUser={isInternalUser}
/>
))}
@@ -331,12 +402,12 @@ export function FileManager({ projectId }: FileManagerProps) {
const [searchQuery, setSearchQuery] = useState('');
const [loading, setLoading] = useState(false);
- console.log(items, "items")
-
// Upload states
const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]);
+ const [selectedFilesForUpload, setSelectedFilesForUpload] = useState<File[]>([]);
const [uploadCategory, setUploadCategory] = useState<string>('confidential');
+ const [preserveFolderStructure, setPreserveFolderStructure] = useState(false);
// Dialog states
const [folderDialogOpen, setFolderDialogOpen] = useState(false);
@@ -345,6 +416,9 @@ export function FileManager({ projectId }: FileManagerProps) {
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
const [viewerDialogOpen, setViewerDialogOpen] = useState(false);
const [viewerFileUrl, setViewerFileUrl] = useState<string | null>(null);
+ const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
+ const [applyToChildren, setApplyToChildren] = useState(false);
+ const [newCategory, setNewCategory] = useState('confidential');
// Dialog data
const [dialogValue, setDialogValue] = useState('');
@@ -473,8 +547,8 @@ export function FileManager({ projectId }: FileManagerProps) {
}
};
- // Handle file upload
- const handleFileUpload = async (files: FileList | File[]) => {
+ // Handle file upload - 실제 업로드 처리
+ const handleFileUpload = async (files: File[]) => {
const fileArray = Array.from(files);
// Initialize uploading file list
@@ -486,63 +560,64 @@ export function FileManager({ projectId }: FileManagerProps) {
setUploadingFiles(newUploadingFiles);
- // Process each file upload
- for (let i = 0; i < fileArray.length; i++) {
- const file = fileArray[i];
-
- try {
- // Update status: uploading
- setUploadingFiles(prev => prev.map((f, idx) =>
- idx === i ? { ...f, status: 'uploading', progress: 20 } : f
- ));
-
- // DRM decryption
- setUploadingFiles(prev => prev.map((f, idx) =>
- idx === i ? { ...f, status: 'processing', progress: 40 } : f
- ));
-
- const decryptedData = await decryptWithServerAction(file);
-
- // Create FormData
- const formData = new FormData();
- const blob = new Blob([decryptedData], { type: file.type });
- formData.append('file', blob, file.name);
- formData.append('category', uploadCategory);
- formData.append('fileSize', file.size.toString()); // Pass file size
- if (currentParentId) {
- formData.append('parentId', currentParentId);
- }
-
- // Update upload progress
- setUploadingFiles(prev => prev.map((f, idx) =>
- idx === i ? { ...f, progress: 60 } : f
- ));
-
- // API call
- const response = await fetch(`/api/data-room/${projectId}/upload`, {
- method: 'POST',
- body: formData,
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error || 'Upload failed');
+ // 폴더 구조 보존 옵션이 켜져있고 상대 경로가 있는 경우
+ if (preserveFolderStructure && fileArray.some((file: any) => file.webkitRelativePath)) {
+ // 폴더 구조를 먼저 생성
+ const folderMap = new Map<string, string>(); // path -> folderId
+
+ for (let i = 0; i < fileArray.length; i++) {
+ const file = fileArray[i];
+ const relativePath = (file as any).webkitRelativePath;
+
+ if (relativePath) {
+ const pathParts = relativePath.split('/');
+ const folders = pathParts.slice(0, -1); // 파일명 제외
+
+ let currentFolderPath = '';
+ let parentId = currentParentId;
+
+ // 각 폴더를 순차적으로 생성
+ for (const folderName of folders) {
+ currentFolderPath = currentFolderPath ? `${currentFolderPath}/${folderName}` : folderName;
+
+ if (!folderMap.has(currentFolderPath)) {
+ // 폴더 생성 API 호출
+ try {
+ const response = await fetch(`/api/data-room/${projectId}`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ name: folderName,
+ type: 'folder',
+ category: uploadCategory,
+ parentId: parentId,
+ }),
+ });
+
+ if (response.ok) {
+ const newFolder = await response.json();
+ folderMap.set(currentFolderPath, newFolder.id);
+ parentId = newFolder.id;
+ }
+ } catch (error) {
+ console.error('Failed to create folder:', folderName);
+ }
+ } else {
+ parentId = folderMap.get(currentFolderPath) || null;
+ }
+ }
+
+ // 폴더가 생성되었으면 해당 폴더에 파일 업로드
+ await uploadSingleFile(file, i, parentId);
+ } else {
+ // 상대 경로가 없으면 일반 업로드
+ await uploadSingleFile(file, i, currentParentId);
}
-
- // Success
- setUploadingFiles(prev => prev.map((f, idx) =>
- idx === i ? { ...f, status: 'completed', progress: 100 } : f
- ));
-
- } catch (error: any) {
- // Failure
- setUploadingFiles(prev => prev.map((f, idx) =>
- idx === i ? {
- ...f,
- status: 'error',
- error: error.message || 'Upload failed'
- } : f
- ));
+ }
+ } else {
+ // 일반 업로드 (폴더 구조 보존 없음)
+ for (let i = 0; i < fileArray.length; i++) {
+ await uploadSingleFile(fileArray[i], i, currentParentId);
}
}
@@ -550,7 +625,7 @@ export function FileManager({ projectId }: FileManagerProps) {
await fetchItems();
// Show toast if any files succeeded
- const successCount = newUploadingFiles.filter(f => f.status === 'completed').length;
+ const successCount = uploadingFiles.filter(f => f.status === 'completed').length;
if (successCount > 0) {
toast({
title: 'Upload Complete',
@@ -559,6 +634,72 @@ export function FileManager({ projectId }: FileManagerProps) {
}
};
+ // 단일 파일 업로드 함수
+ const uploadSingleFile = async (file: File, index: number, parentId: string | null) => {
+ try {
+ // Update status: uploading
+ setUploadingFiles(prev => prev.map((f, idx) =>
+ idx === index ? { ...f, status: 'uploading', progress: 20 } : f
+ ));
+
+ // DRM decryption
+ setUploadingFiles(prev => prev.map((f, idx) =>
+ idx === index ? { ...f, status: 'processing', progress: 40 } : f
+ ));
+
+ const decryptedData = await decryptWithServerAction(file);
+
+ // Create FormData
+ const formData = new FormData();
+ const blob = new Blob([decryptedData], { type: file.type });
+ formData.append('file', blob, file.name);
+ formData.append('category', uploadCategory);
+ formData.append('fileSize', file.size.toString());
+ if (parentId) {
+ formData.append('parentId', parentId);
+ }
+
+ // Update upload progress
+ setUploadingFiles(prev => prev.map((f, idx) =>
+ idx === index ? { ...f, progress: 60 } : f
+ ));
+
+ // API call
+ const response = await fetch(`/api/data-room/${projectId}/upload`, {
+ method: 'POST',
+ body: formData,
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || 'Upload failed');
+ }
+
+ // Success
+ setUploadingFiles(prev => prev.map((f, idx) =>
+ idx === index ? { ...f, status: 'completed', progress: 100 } : f
+ ));
+
+ } catch (error: any) {
+ // Failure
+ setUploadingFiles(prev => prev.map((f, idx) =>
+ idx === index ? {
+ ...f,
+ status: 'error',
+ error: error.message || 'Upload failed'
+ } : f
+ ));
+ }
+ };
+
+ // 업로드 시작 함수
+ const startUpload = async () => {
+ if (selectedFilesForUpload.length === 0) return;
+ await handleFileUpload(selectedFilesForUpload);
+ // 업로드 완료 후 선택된 파일 초기화
+ setSelectedFilesForUpload([]);
+ };
+
// Download folder
const downloadFolder = async (folder: FileItem) => {
if (folder.type !== 'folder') return;
@@ -620,14 +761,13 @@ export function FileManager({ projectId }: FileManagerProps) {
}
};
-
// Download multiple files
const downloadMultipleFiles = async (itemIds: string[]) => {
// Filter only actual files (exclude folders) that can be downloaded
const filesToDownload = items.filter(item =>
itemIds.includes(item.id) &&
item.type === 'file' &&
- item.permissions?.canDownload === 'true'
+ item.permissions?.canDownload
);
if (filesToDownload.length === 0) {
@@ -712,15 +852,9 @@ export function FileManager({ projectId }: FileManagerProps) {
// View file with PDFTron
const viewFile = async (file: FileItem) => {
try {
-
-
-
- setViewerFileUrl(file.filePath);
+ setViewerFileUrl(file.filePath || '');
setSelectedFile(file);
setViewerDialogOpen(true);
-
-
-
} catch (error) {
toast({
title: 'Error',
@@ -858,11 +992,6 @@ export function FileManager({ projectId }: FileManagerProps) {
}
};
- // Category change dialog states
- const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
- const [applyToChildren, setApplyToChildren] = useState(false);
- const [newCategory, setNewCategory] = useState('confidential');
-
// Handle folder double click
const handleFolderOpen = (folder: FileItem) => {
if (viewMode === 'grid') {
@@ -952,7 +1081,7 @@ export function FileManager({ projectId }: FileManagerProps) {
onClick={() => {
// 현재 폴더의 카테고리를 기본값으로 설정
if (currentParentId) {
- const currentFolder = items.find(item => item.parentId === currentParentId);
+ const currentFolder = items.find(item => item.id === currentParentId);
if (currentFolder) {
setUploadCategory(currentFolder.category);
}
@@ -972,7 +1101,7 @@ export function FileManager({ projectId }: FileManagerProps) {
{items.filter(item =>
selectedItems.has(item.id) &&
item.type === 'file' &&
- item.permissions?.canDownload === 'true'
+ item.permissions?.canDownload
).length > 0 && (
<Button
size="sm"
@@ -1057,7 +1186,7 @@ export function FileManager({ projectId }: FileManagerProps) {
<div className="flex justify-center items-center h-64">
<div className="text-muted-foreground">Loading...</div>
</div>
- ) : filteredItems.length === 0 ? (
+ ) : filteredItems.length === 0 && filteredTreeItems.length === 0 ? (
<div className="flex flex-col items-center justify-center h-64">
<Folder className="h-12 w-12 text-muted-foreground mb-2" />
<p className="text-muted-foreground">Empty</p>
@@ -1081,6 +1210,8 @@ export function FileManager({ projectId }: FileManagerProps) {
onDoubleClick={() => {
if (item.type === 'folder') {
handleFolderOpen(item);
+ } else {
+ viewFile(item);
}
}}
>
@@ -1114,7 +1245,72 @@ export function FileManager({ projectId }: FileManagerProps) {
</div>
</ContextMenuTrigger>
- {/* ... ContextMenuContent는 동일 ... */}
+ <ContextMenuContent>
+ {item.type === 'file' && (
+ <>
+ <ContextMenuItem onClick={() => viewFile(item)}>
+ <Eye className="h-4 w-4 mr-2" />
+ View
+ </ContextMenuItem>
+ {item.permissions?.canDownload && (
+ <ContextMenuItem onClick={() => downloadFile(item)}>
+ <Download className="h-4 w-4 mr-2" />
+ Download
+ </ContextMenuItem>
+ )}
+ </>
+ )}
+
+ {item.type === 'folder' && (
+ <>
+ <ContextMenuItem onClick={() => handleFolderOpen(item)}>
+ <Folder className="h-4 w-4 mr-2" />
+ Open
+ </ContextMenuItem>
+ <ContextMenuItem onClick={() => downloadFolder(item)}>
+ <Download className="h-4 w-4 mr-2" />
+ Download Folder
+ </ContextMenuItem>
+ </>
+ )}
+
+ {isInternalUser && (
+ <>
+ <ContextMenuSeparator />
+ {item.permissions?.canEdit && (
+ <ContextMenuItem onClick={() => {
+ setSelectedFile(item);
+ setDialogValue(item.name);
+ setRenameDialogOpen(true);
+ }}>
+ <Edit2 className="h-4 w-4 mr-2" />
+ Rename
+ </ContextMenuItem>
+ )}
+ <ContextMenuItem onClick={() => {
+ setSelectedFile(item);
+ setNewCategory(item.category);
+ setCategoryDialogOpen(true);
+ }}>
+ <Shield className="h-4 w-4 mr-2" />
+ Change Category
+ </ContextMenuItem>
+ </>
+ )}
+
+ {item.permissions?.canDelete && (
+ <>
+ <ContextMenuSeparator />
+ <ContextMenuItem
+ className="text-destructive"
+ onClick={() => deleteItems([item.id])}
+ >
+ <Trash2 className="h-4 w-4 mr-2" />
+ Delete
+ </ContextMenuItem>
+ </>
+ )}
+ </ContextMenuContent>
</ContextMenu>
);
})}
@@ -1145,6 +1341,11 @@ export function FileManager({ projectId }: FileManagerProps) {
setDialogValue(item.name);
setRenameDialogOpen(true);
}}
+ onCategoryChange={(item) => {
+ setSelectedFile(item);
+ setNewCategory(item.category);
+ setCategoryDialogOpen(true);
+ }}
isInternalUser={isInternalUser}
/>
))}
@@ -1154,13 +1355,20 @@ export function FileManager({ projectId }: FileManagerProps) {
</ScrollArea>
</div>
- {/* Upload Dialog */}
- <Dialog open={uploadDialogOpen} onOpenChange={setUploadDialogOpen}>
+ {/* Upload Dialog - 수정된 부분 */}
+ <Dialog open={uploadDialogOpen} onOpenChange={(open) => {
+ setUploadDialogOpen(open);
+ if (!open) {
+ setSelectedFilesForUpload([]);
+ setUploadingFiles([]);
+ setPreserveFolderStructure(false);
+ }
+ }}>
<DialogContent className="max-w-2xl max-h-[90vh] flex flex-col">
<DialogHeader>
<DialogTitle>Upload Files</DialogTitle>
<DialogDescription>
- Drag and drop files or click to select.
+ Drag and drop files or click to select. You can also select entire folders.
</DialogDescription>
</DialogHeader>
@@ -1176,15 +1384,12 @@ export function FileManager({ projectId }: FileManagerProps) {
<SelectContent>
{Object.entries(categoryConfig)
.filter(([key]) => {
- // 현재 폴더가 있는 경우
if (currentParentId) {
- const currentFolder = items.find(item => item.parentId === currentParentId);
- // 현재 폴더가 public이 아니면 public 옵션 제외
+ const currentFolder = items.find(item => item.id === currentParentId);
if (currentFolder && currentFolder.category !== 'public') {
return key !== 'public';
}
}
- // 루트 폴더이거나 현재 폴더가 public인 경우 모든 옵션 표시
return true;
})
.map(([key, config]) => (
@@ -1197,57 +1402,223 @@ export function FileManager({ projectId }: FileManagerProps) {
))}
</SelectContent>
</Select>
- {/* 현재 폴더 정보 표시 (선택사항) */}
- {currentParentId && (() => {
- const currentFolder = items.find(item => item.parentId === currentParentId);
- if (currentFolder && currentFolder.category !== 'public') {
- return (
- <p className="text-xs text-muted-foreground mt-1 flex items-center">
- <AlertCircle className="h-3 w-3 mr-1" />
- Current folder is {categoryConfig[currentFolder.category].label}.
- Public uploads are not allowed.
- </p>
- );
- }
- })()}
</div>
- {/* Dropzone */}
- <Dropzone
- onDrop={(acceptedFiles: File[]) => {
- handleFileUpload(acceptedFiles);
- }}
- accept={{
- 'application/pdf': ['.pdf'],
- 'application/msword': ['.doc'],
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
- 'application/vnd.ms-excel': ['.xls'],
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'],
- 'application/vnd.ms-powerpoint': ['.ppt'],
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation': ['.pptx'],
- 'text/plain': ['.txt'],
- 'text/csv': ['.csv'],
- 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'],
- 'application/zip': ['.zip'],
- 'application/x-rar-compressed': ['.rar'],
- 'application/x-7z-compressed': ['.7z'],
- 'application/x-dwg': ['.dwg'],
- 'application/x-dxf': ['.dxf'],
- }}
- multiple={true}
- disabled={false}
- >
- <DropzoneZone className="h-48 border-2 border-dashed border-gray-300 rounded-lg">
- <DropzoneInput />
- <div className="flex flex-col items-center justify-center h-full">
- <DropzoneUploadIcon className="h-12 w-12 text-muted-foreground mb-4" />
- <DropzoneTitle>Drag files or click to upload</DropzoneTitle>
- <DropzoneDescription>Multiple files can be uploaded simultaneously</DropzoneDescription>
+ {/* Preserve Folder Structure Option - 폴더가 선택된 경우만 표시 */}
+ {selectedFilesForUpload.some((file: any) => file.webkitRelativePath) && (
+ <div className="flex items-center space-x-2 p-3 bg-muted rounded-lg">
+ <Switch
+ id="preserve-folder"
+ checked={preserveFolderStructure}
+ onCheckedChange={setPreserveFolderStructure}
+ />
+ <Label htmlFor="preserve-folder" className="cursor-pointer">
+ <div className="font-medium">Preserve folder structure</div>
+ <div className="text-xs text-muted-foreground">
+ Create folders and maintain the original directory structure
+ </div>
+ </Label>
+ </div>
+ )}
+
+ {/* Dropzone - 파일이 선택되지 않았을 때만 표시 */}
+ {selectedFilesForUpload.length === 0 && uploadingFiles.length === 0 && (
+ <div
+ className="h-48 border-2 border-dashed border-gray-300 rounded-lg flex flex-col items-center justify-center relative"
+ onDragOver={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ e.currentTarget.classList.add('border-primary', 'bg-accent');
+ }}
+ onDragLeave={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ e.currentTarget.classList.remove('border-primary', 'bg-accent');
+ }}
+ onDrop={async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ e.currentTarget.classList.remove('border-primary', 'bg-accent');
+
+ const items = e.dataTransfer.items;
+ const files: File[] = [];
+ const filePromises: Promise<void>[] = [];
+
+ // 폴더 구조 감지를 위한 플래그
+ let hasFolderStructure = false;
+
+ // DataTransferItem을 통한 폴더 처리
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+
+ if (item.kind === 'file') {
+ const entry = item.webkitGetAsEntry();
+
+ if (entry) {
+ filePromises.push(
+ new Promise<void>(async (resolve) => {
+ const traverseFileTree = async (item: any, path: string = '') => {
+ if (item.isFile) {
+ await new Promise<void>((fileResolve) => {
+ item.file((file: File) => {
+ // 파일에 상대 경로 추가
+ Object.defineProperty(file, 'webkitRelativePath', {
+ value: path + file.name,
+ writable: false
+ });
+ files.push(file);
+ fileResolve();
+ });
+ });
+ } else if (item.isDirectory) {
+ hasFolderStructure = true;
+ const dirReader = item.createReader();
+ await new Promise<void>((dirResolve) => {
+ dirReader.readEntries(async (entries: any[]) => {
+ for (const entry of entries) {
+ await traverseFileTree(entry, path + item.name + '/');
+ }
+ dirResolve();
+ });
+ });
+ }
+ };
+
+ await traverseFileTree(entry);
+ resolve();
+ })
+ );
+ }
+ }
+ }
+
+ // 모든 파일 처리 완료 대기
+ await Promise.all(filePromises);
+
+ // 파일이 없으면 일반 파일 처리 (폴더가 아닌 경우)
+ if (files.length === 0) {
+ const droppedFiles = Array.from(e.dataTransfer.files);
+ if (droppedFiles.length > 0) {
+ setSelectedFilesForUpload(droppedFiles);
+ }
+ } else {
+ // 폴더 구조가 있으면 자동으로 옵션 활성화
+ if (hasFolderStructure) {
+ setPreserveFolderStructure(true);
+ }
+ setSelectedFilesForUpload(files);
+ }
+ }}
+ >
+ <input
+ type="file"
+ multiple
+ className="hidden"
+ id="file-input"
+ onChange={(e) => {
+ const files = Array.from(e.target.files || []);
+ setSelectedFilesForUpload(files);
+ }}
+ />
+ <DropzoneUploadIcon className="h-12 w-12 text-muted-foreground mb-4" />
+ <div className="text-center">
+ <p className="font-medium text-lg">Drag folders or files here</p>
+ <p className="text-sm text-muted-foreground mt-1">
+ Folders will maintain their structure
+ </p>
</div>
- </DropzoneZone>
- </Dropzone>
+ {/* <div className="mt-4 flex gap-2">
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => document.getElementById('file-input')?.click()}
+ >
+ <File className="h-4 w-4 mr-2" />
+ Browse Files
+ </Button>
+ <label htmlFor="folder-upload-btn" className="cursor-pointer">
+ <input
+ id="folder-upload-btn"
+ type="file"
+ // @ts-ignore
+ webkitdirectory=""
+ directory=""
+ multiple
+ className="hidden"
+ onChange={(e) => {
+ const files = Array.from(e.target.files || []);
+ setSelectedFilesForUpload(files);
+ if (files.length > 0 && (files[0] as any).webkitRelativePath) {
+ setPreserveFolderStructure(true);
+ }
+ }}
+ />
+ <Button
+ variant="outline"
+ size="sm"
+ type="button"
+ as="div"
+ >
+ <Folder className="h-4 w-4 mr-2" />
+ Browse Folder
+ </Button>
+ </label>
+ </div> */}
+ </div>
+ )}
- {/* Uploading File List */}
+ {/* Selected Files List - 업로드 전 */}
+ {selectedFilesForUpload.length > 0 && uploadingFiles.length === 0 && (
+ <div className="border rounded-lg p-4">
+ <div className="flex items-center justify-between mb-3">
+ <h4 className="font-medium text-sm">
+ Selected Files ({selectedFilesForUpload.length})
+ </h4>
+ <Button
+ size="sm"
+ variant="ghost"
+ onClick={() => {
+ setSelectedFilesForUpload([]);
+ setPreserveFolderStructure(false);
+ }}
+ >
+ <X className="h-4 w-4 mr-1" />
+ Clear All
+ </Button>
+ </div>
+ <div className="space-y-2 max-h-[300px] overflow-y-auto">
+ {selectedFilesForUpload.map((file, index) => {
+ const relativePath = (file as any).webkitRelativePath || file.name;
+ return (
+ <div key={index} className="flex items-center gap-3 p-2 bg-muted/50 rounded-md">
+ <File className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
+ <div className="flex-1 min-w-0">
+ <p className="text-sm truncate" title={relativePath}>
+ {relativePath}
+ </p>
+ <span className="text-xs text-muted-foreground">
+ {formatFileSize(file.size)}
+ </span>
+ </div>
+ <Button
+ size="sm"
+ variant="ghost"
+ onClick={() => {
+ setSelectedFilesForUpload(prev =>
+ prev.filter((_, i) => i !== index)
+ );
+ }}
+ >
+ <X className="h-3 w-3" />
+ </Button>
+ </div>
+ );
+ })}
+ </div>
+ </div>
+ )}
+
+ {/* Uploading File List - 업로드 중 */}
{uploadingFiles.length > 0 && (
<div className="border rounded-lg p-4 bg-muted/50">
<div className="flex items-center justify-between mb-3">
@@ -1290,19 +1661,6 @@ export function FileManager({ projectId }: FileManagerProps) {
<Progress value={uploadFile.progress} className="h-1.5 mt-2" />
)}
</div>
- {uploadFile.status === 'error' && (
- <Button
- size="sm"
- variant="ghost"
- onClick={() => {
- setUploadingFiles(prev =>
- prev.filter((_, i) => i !== index)
- );
- }}
- >
- <X className="h-4 w-4" />
- </Button>
- )}
</div>
))}
</div>
@@ -1316,11 +1674,30 @@ export function FileManager({ projectId }: FileManagerProps) {
variant="outline"
onClick={() => {
setUploadDialogOpen(false);
+ setSelectedFilesForUpload([]);
setUploadingFiles([]);
+ setPreserveFolderStructure(false);
}}
>
- Close
+ Cancel
</Button>
+ {/* 파일이 선택되었고 업로드 중이 아닐 때만 Upload 버튼 표시 */}
+ {selectedFilesForUpload.length > 0 && uploadingFiles.length === 0 && (
+ <Button onClick={startUpload}>
+ <Upload className="h-4 w-4 mr-2" />
+ Upload {selectedFilesForUpload.length} File{selectedFilesForUpload.length > 1 ? 's' : ''}
+ </Button>
+ )}
+ {/* 업로드 완료 후 Done 버튼 */}
+ {uploadingFiles.length > 0 && uploadingFiles.every(f => f.status === 'completed' || f.status === 'error') && (
+ <Button onClick={() => {
+ setUploadDialogOpen(false);
+ setUploadingFiles([]);
+ setSelectedFilesForUpload([]);
+ }}>
+ Done
+ </Button>
+ )}
</DialogFooter>
</DialogContent>
</Dialog>
@@ -1375,7 +1752,6 @@ export function FileManager({ projectId }: FileManagerProps) {
</DialogContent>
</Dialog>
-
{/* Rename Dialog */}
<Dialog open={renameDialogOpen} onOpenChange={setRenameDialogOpen}>
<DialogContent>
@@ -1429,13 +1805,13 @@ export function FileManager({ projectId }: FileManagerProps) {
</DialogContent>
</Dialog>
- {/* Category Change Dialog (for folders) */}
+ {/* Category Change Dialog */}
<Dialog open={categoryDialogOpen} onOpenChange={setCategoryDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Change Category</DialogTitle>
<DialogDescription>
- Changing category for {selectedFile?.name} folder.
+ Changing category for {selectedFile?.name}.
</DialogDescription>
</DialogHeader>
@@ -1506,7 +1882,9 @@ export function FileManager({ projectId }: FileManagerProps) {
<Button
onClick={() => {
if (selectedFile) {
- changeCategory(selectedFile.id, newCategory, applyToChildren);
+ changeCategory(selectedFile.id, newCategory,
+ selectedFile.type === 'folder' && newCategory !== 'public' ? true : applyToChildren
+ );
setCategoryDialogOpen(false);
setSelectedFile(null);
setApplyToChildren(false);
diff --git a/components/layout/HeaderDataroom.tsx b/components/layout/HeaderDataroom.tsx
index 333e3768..fa9b89cf 100644
--- a/components/layout/HeaderDataroom.tsx
+++ b/components/layout/HeaderDataroom.tsx
@@ -101,18 +101,18 @@ export function HeaderDataRoom() {
{/* 네비게이션 메뉴 - 간단한 배열 */}
<div className="hidden md:block flex-1 min-w-0">
- <nav className="flex items-center space-x-6">
- {simpleMenus.map((menu) => (
- <Link
- key={menu.href}
- href={`/${lng}${menu.href}`}
- className="text-sm font-medium transition-colors hover:text-primary"
- >
- {menu.title}
- </Link>
- ))}
- </nav>
-</div>
+ <nav className="flex items-center space-x-6">
+ {simpleMenus.map((menu) => (
+ <Link
+ key={menu.href}
+ href={`/${lng}${menu.href}`}
+ className="text-sm font-medium transition-colors hover:text-primary"
+ >
+ {menu.title}
+ </Link>
+ ))}
+ </nav>
+ </div>
{/* 우측 영역 */}
<div className="ml-auto flex flex-shrink-0 items-center space-x-2">
diff --git a/components/layout/HeaderSimple.tsx b/components/layout/HeaderSimple.tsx
index 989929ae..f6f60342 100644
--- a/components/layout/HeaderSimple.tsx
+++ b/components/layout/HeaderSimple.tsx
@@ -89,11 +89,11 @@ export function HeaderSimple() {
</Button>
{/* 로고 영역 - 항상 표시 */}
- <div className="mr-4 flex-shrink-0 flex items-center gap-2 lg:mr-6">
+ <div className="mr-8 flex-shrink-0 flex items-center gap-2 lg:mr-8">
<Link href={`/`} className="flex items-center gap-2">
<Image
className="dark:invert"
- src="/images/SHI_logo.svg"
+ src="/images/dataRoomLogo.png"
alt="EVCP Logo"
width={140}
height={20}
diff --git a/components/project/ProjectDashboard.tsx b/components/project/ProjectDashboard.tsx
index 581b7b95..12515903 100644
--- a/components/project/ProjectDashboard.tsx
+++ b/components/project/ProjectDashboard.tsx
@@ -348,10 +348,10 @@ export function ProjectDashboard({ projectId }: ProjectDashboardProps) {
<UserPlus className="h-4 w-4 mr-2" />
Add Member
</Button>
- <Button variant="outline">
+ {/* <Button variant="outline">
<Settings className="h-4 w-4 mr-2" />
Settings
- </Button>
+ </Button> */}
</div>
)}
</div>
diff --git a/components/project/ProjectNav.tsx b/components/project/ProjectNav.tsx
index c62f760e..1654c30d 100644
--- a/components/project/ProjectNav.tsx
+++ b/components/project/ProjectNav.tsx
@@ -89,13 +89,13 @@ export function ProjectNav({ projectId }: ProjectNavProps) {
active: pathname?.includes('stats') ,
requireRole: ['owner'],
},
- {
- label: 'Settings',
- icon: Settings,
- href: `/evcp/data-room/${projectId}/settings`,
- active: pathname?.includes('settiings') ,
- requireRole: ['owner', 'admin'],
- },
+ // {
+ // label: 'Settings',
+ // icon: Settings,
+ // href: `/evcp/data-room/${projectId}/settings`,
+ // active: pathname?.includes('settiings') ,
+ // requireRole: ['owner', 'admin'],
+ // },
];
const visibleNavItems = navItems.filter(item =>
diff --git a/i18n/locales/en/menu.json b/i18n/locales/en/menu.json
index 93cc538f..5fabc5c4 100644
--- a/i18n/locales/en/menu.json
+++ b/i18n/locales/en/menu.json
@@ -261,7 +261,9 @@
"document_submission": "Document/Drawing Submission",
"document_submission_desc": "Submit vendor documents/drawings",
"vendor_progress": "Vendor Progress",
- "vendor_progress_desc": "View vendor EDP input progress"
+ "vendor_progress_desc": "View vendor EDP input progress",
+ "cover": "Vendor Document Cover",
+ "cover_desc": "Covenr Page Generator"
}
},
"additional": {
diff --git a/i18n/locales/ko/menu.json b/i18n/locales/ko/menu.json
index fb3c4e7a..28a97373 100644
--- a/i18n/locales/ko/menu.json
+++ b/i18n/locales/ko/menu.json
@@ -95,7 +95,9 @@
"tbe": "TBE",
"tbe_desc": "Technical Bid Evaluation",
"itb": "RFQ 생성",
- "itb_desc": "PR 이슈 전 RFQ 생성"
+ "itb_desc": "PR 이슈 전 RFQ 생성",
+ "cover": "Vendor Document Cover",
+ "cover_desc": "Covenr Page Generator"
},
"vendor_management": {
"title": "협력업체 관리",
diff --git a/lib/forms/services.ts b/lib/forms/services.ts
index 6310b693..57b7f000 100644
--- a/lib/forms/services.ts
+++ b/lib/forms/services.ts
@@ -229,7 +229,6 @@ export async function getEditableFieldsByTag(
* 그리고 이 로직 전체를 unstable_cache로 감싸 캐싱.
*/
export async function getFormData(formCode: string, contractItemId: number) {
-
try {
// 기존 로직으로 projectId, columns, data 가져오기
@@ -1534,9 +1533,9 @@ async function transformDataToSEDPFormat(
}
// Apply the factor if we got one
- if (factor !== undefined && typeof value === 'number') {
- value = value * factor;
- }
+ // if (factor !== undefined && typeof value === 'number') {
+ // value = value * factor;
+ // }
}
const attribute: SEDPAttribute = {
diff --git a/lib/gtc-contract/service.ts b/lib/gtc-contract/service.ts
index 61545d95..e1bca3c5 100644
--- a/lib/gtc-contract/service.ts
+++ b/lib/gtc-contract/service.ts
@@ -320,6 +320,7 @@ export async function getUsersForFilter(): Promise<UserForFilter[]> {
name: users.name,
email: users.email,
domain: users.domain,
+ ownerCompanyId: users.ownerCompanyId,
})
.from(users)
.where(eq(users.isActive, true)) // 활성 사용자만
diff --git a/lib/owner-companies/service.ts b/lib/owner-companies/service.ts
index 3692abd4..2f3e914f 100644
--- a/lib/owner-companies/service.ts
+++ b/lib/owner-companies/service.ts
@@ -60,12 +60,12 @@ export async function createOwnerCompanyUser(
.values({
...data,
ownerCompanyId: companyId,
- domain: "owner", // 발주처 도메인
+ domain: "partners", // 발주처 도메인
isActive: true,
})
.returning();
- revalidatePath(`/owner-companies/${companyId}/users`);
+ revalidatePath(`/evcp/data-room/owner-companies/${companyId}/users`);
return { success: true, data: user };
}