summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-10-13 08:56:27 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-10-13 08:56:27 +0000
commitb9a2081a76e669688d5884f20482b37cc8acca22 (patch)
tree385e78c05d193a54daaced836f1e1152696153a8 /components
parente84cf02a1cb4959a9d3bb5bbf37885c13a447f78 (diff)
(최겸, 임수민) 구매 입찰, 견적(그룹코드, tbe에러) 수정, data-room 수정
Diffstat (limited to 'components')
-rw-r--r--components/file-manager/FileManager.tsx100
-rw-r--r--components/layout/Footer.tsx13
-rw-r--r--components/project/ProjectDashboard.tsx103
-rw-r--r--components/project/ProjectList.tsx37
-rw-r--r--components/project/ProjectNav.tsx2
5 files changed, 212 insertions, 43 deletions
diff --git a/components/file-manager/FileManager.tsx b/components/file-manager/FileManager.tsx
index 587beb22..fa2d8c38 100644
--- a/components/file-manager/FileManager.tsx
+++ b/components/file-manager/FileManager.tsx
@@ -335,7 +335,7 @@ export function FileManager({ projectId }: FileManagerProps) {
const [searchQuery, setSearchQuery] = useState('');
const [loading, setLoading] = useState(false);
- console.log(items,"items")
+ console.log(items, "items")
// Upload states
const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
@@ -754,9 +754,9 @@ export function FileManager({ projectId }: FileManagerProps) {
// View file with PDFTron
const viewFile = async (file: FileItem) => {
try {
-
-
+
+
setViewerFileUrl(file.filePath);
setSelectedFile(file);
setViewerDialogOpen(true);
@@ -991,7 +991,16 @@ export function FileManager({ projectId }: FileManagerProps) {
<Button
size="sm"
variant="outline"
- onClick={() => setUploadDialogOpen(true)}
+ onClick={() => {
+ // 현재 폴더의 카테고리를 기본값으로 설정
+ if (currentParentId) {
+ const currentFolder = items.find(item => item.parentId === currentParentId);
+ if (currentFolder) {
+ setUploadCategory(currentFolder.category);
+ }
+ }
+ setUploadDialogOpen(true);
+ }}
>
<Upload className="h-4 w-4 mr-1" />
Upload
@@ -1005,7 +1014,7 @@ export function FileManager({ projectId }: FileManagerProps) {
{items.filter(item =>
selectedItems.has(item.id) &&
item.type === 'file' &&
- item.permissions?.canDownload ==='true'
+ item.permissions?.canDownload === 'true'
).length > 0 && (
<Button
size="sm"
@@ -1296,16 +1305,42 @@ export function FileManager({ projectId }: FileManagerProps) {
<SelectValue />
</SelectTrigger>
<SelectContent>
- {Object.entries(categoryConfig).map(([key, config]) => (
- <SelectItem key={key} value={key}>
- <div className="flex items-center">
- <config.icon className={cn("h-4 w-4 mr-2", config.color)} />
- <span>{config.label}</span>
- </div>
- </SelectItem>
- ))}
+ {Object.entries(categoryConfig)
+ .filter(([key]) => {
+ // 현재 폴더가 있는 경우
+ if (currentParentId) {
+ const currentFolder = items.find(item => item.parentId === currentParentId);
+ // 현재 폴더가 public이 아니면 public 옵션 제외
+ if (currentFolder && currentFolder.category !== 'public') {
+ return key !== 'public';
+ }
+ }
+ // 루트 폴더이거나 현재 폴더가 public인 경우 모든 옵션 표시
+ return true;
+ })
+ .map(([key, config]) => (
+ <SelectItem key={key} value={key}>
+ <div className="flex items-center">
+ <config.icon className={cn("h-4 w-4 mr-2", config.color)} />
+ <span>{config.label}</span>
+ </div>
+ </SelectItem>
+ ))}
</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 */}
@@ -1644,13 +1679,13 @@ export function FileManager({ projectId }: FileManagerProps) {
Changing category for {selectedFile?.name} folder.
</DialogDescription>
</DialogHeader>
-
+
<div className="space-y-4">
<div>
<Label>New Category</Label>
<div className="mt-2 space-y-2">
{Object.entries(categoryConfig).map(([key, config]) => (
- <div
+ <div
key={key}
className={cn(
"flex items-center p-3 rounded-lg border cursor-pointer transition-colors",
@@ -1672,24 +1707,35 @@ export function FileManager({ projectId }: FileManagerProps) {
))}
</div>
</div>
-
{selectedFile?.type === 'folder' && (
<div className="flex items-center space-x-2">
<Switch
id="apply-to-children"
- checked={applyToChildren}
- onCheckedChange={setApplyToChildren}
+ checked={newCategory !== 'public' ? true : applyToChildren}
+ onCheckedChange={(checked) => {
+ if (newCategory === 'public') {
+ setApplyToChildren(checked);
+ }
+ }}
+ disabled={newCategory !== 'public'}
/>
- <Label htmlFor="apply-to-children">
+ <Label htmlFor="apply-to-children" className={cn(
+ newCategory !== 'public' && "text-muted-foreground"
+ )}>
Apply to all files and subfolders
+ {newCategory !== 'public' && (
+ <span className="text-xs block mt-1">
+ (Required for security categories)
+ </span>
+ )}
</Label>
</div>
)}
</div>
-
+
<DialogFooter>
- <Button
- variant="outline"
+ <Button
+ variant="outline"
onClick={() => {
setCategoryDialogOpen(false);
setSelectedFile(null);
@@ -1698,7 +1744,7 @@ export function FileManager({ projectId }: FileManagerProps) {
>
Cancel
</Button>
- <Button
+ <Button
onClick={() => {
if (selectedFile) {
changeCategory(selectedFile.id, newCategory, applyToChildren);
@@ -1715,8 +1761,8 @@ export function FileManager({ projectId }: FileManagerProps) {
</Dialog>
{/* Secure Document Viewer Dialog */}
- <Dialog
- open={viewerDialogOpen}
+ <Dialog
+ open={viewerDialogOpen}
onOpenChange={(open) => {
if (!open) {
setViewerDialogOpen(false);
@@ -1747,7 +1793,7 @@ export function FileManager({ projectId }: FileManagerProps) {
</div>
</DialogDescription>
</DialogHeader>
-
+
<div className="relative flex-1 h-[calc(90vh-120px)]">
{viewerFileUrl && selectedFile && (
<SecurePDFViewer
@@ -1761,7 +1807,7 @@ export function FileManager({ projectId }: FileManagerProps) {
/>
)}
</div>
-
+
<div className="px-6 py-3 border-t bg-muted/50">
<div className="flex items-center justify-between text-xs text-muted-foreground">
<div className="flex items-center gap-4">
diff --git a/components/layout/Footer.tsx b/components/layout/Footer.tsx
index c994b844..bf533ae8 100644
--- a/components/layout/Footer.tsx
+++ b/components/layout/Footer.tsx
@@ -1,15 +1,24 @@
+'use client'
+
import { siteConfig } from "@/config/site"
+import { usePathname } from "next/navigation"
export function SiteFooter() {
+ const pathname = usePathname()
+ const isDataRoom = pathname?.includes('data-room')
+
return (
<footer className="border-grid border-t py-6 md:px-8 md:py-0">
<div className="container-wrapper">
<div className="container py-4">
<div className="text-balance text-center text-sm leading-loose text-muted-foreground md:text-left">
- enterprise Vendor Co-work Platform - 삼성중공업 전사벤더협업플랫폼
+ {isDataRoom
+ ? "Data Room - 삼성중공업 데이터룸"
+ : "enterprise Vendor Co-work Platform - 삼성중공업 전사벤더협업플랫폼"
+ }
</div>
</div>
</div>
</footer>
)
-}
+} \ No newline at end of file
diff --git a/components/project/ProjectDashboard.tsx b/components/project/ProjectDashboard.tsx
index 5f8afb75..581b7b95 100644
--- a/components/project/ProjectDashboard.tsx
+++ b/components/project/ProjectDashboard.tsx
@@ -103,7 +103,9 @@ export function ProjectDashboard({ projectId }: ProjectDashboardProps) {
// Dialog states
const [addMemberOpen, setAddMemberOpen] = useState(false);
const [transferOwnershipOpen, setTransferOwnershipOpen] = useState(false);
+ const [deleteProjectOpen, setDeleteProjectOpen] = useState(false);
const [newOwnerId, setNewOwnerId] = useState('');
+ const [deleteConfirmText, setDeleteConfirmText] = useState('');
// User selection related states
const [availableUsers, setAvailableUsers] = useState<User[]>([]);
@@ -256,6 +258,42 @@ export function ProjectDashboard({ projectId }: ProjectDashboardProps) {
}
};
+ // Delete project
+ const handleDeleteProject = async () => {
+ if (deleteConfirmText !== 'DELETE') {
+ toast({
+ title: 'Error',
+ description: 'Please type DELETE to confirm.',
+ variant: 'destructive',
+ });
+ return;
+ }
+
+ try {
+ const response = await fetch(`/api/projects/${projectId}`, {
+ method: 'DELETE',
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to delete project');
+ }
+
+ toast({
+ title: 'Success',
+ description: 'Project has been deleted.',
+ });
+
+ // 프로젝트 목록 페이지로 리다이렉트
+ window.location.href = '/evcp/data-room';
+ } catch (error) {
+ toast({
+ title: 'Error',
+ description: 'Failed to delete project.',
+ variant: 'destructive',
+ });
+ }
+ };
+
const formatBytes = (bytes: number) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
@@ -457,7 +495,10 @@ export function ProjectDashboard({ projectId }: ProjectDashboardProps) {
Permanently delete project and all files
</p>
</div>
- <Button variant="destructive">
+ <Button
+ variant="destructive"
+ onClick={() => setDeleteProjectOpen(true)}
+ >
<Trash2 className="h-4 w-4 mr-2" />
Delete Project
</Button>
@@ -744,6 +785,66 @@ export function ProjectDashboard({ projectId }: ProjectDashboardProps) {
</DialogFooter>
</DialogContent>
</Dialog>
+
+ {/* Delete Project Dialog */}
+ <Dialog open={deleteProjectOpen} onOpenChange={(open) => {
+ setDeleteProjectOpen(open);
+ if (!open) setDeleteConfirmText('');
+ }}>
+ <DialogContent>
+ <DialogHeader>
+ <DialogTitle className="text-red-600">Delete Project</DialogTitle>
+ <DialogDescription>
+ This action cannot be undone. This will permanently delete the project and all associated files.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="space-y-4">
+ <div className="rounded-lg bg-red-50 border border-red-200 p-4">
+ <h4 className="font-semibold text-red-800 mb-2">Warning</h4>
+ <ul className="text-sm text-red-700 space-y-1 list-disc list-inside">
+ <li>All files will be permanently deleted</li>
+ <li>All project members will lose access</li>
+ <li>All sharing links will be invalidated</li>
+ <li>This action cannot be reversed</li>
+ </ul>
+ </div>
+
+ <div className="space-y-2">
+ <Label htmlFor="delete-confirm">
+ Type <span className="font-mono font-bold">DELETE</span> to confirm
+ </Label>
+ <Input
+ id="delete-confirm"
+ placeholder="DELETE"
+ value={deleteConfirmText}
+ onChange={(e) => setDeleteConfirmText(e.target.value)}
+ className="font-mono"
+ />
+ </div>
+ </div>
+
+ <DialogFooter>
+ <Button
+ variant="outline"
+ onClick={() => {
+ setDeleteProjectOpen(false);
+ setDeleteConfirmText('');
+ }}
+ >
+ Cancel
+ </Button>
+ <Button
+ variant="destructive"
+ onClick={handleDeleteProject}
+ disabled={deleteConfirmText !== 'DELETE'}
+ >
+ <Trash2 className="h-4 w-4 mr-2" />
+ Delete Project
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
</div>
);
} \ No newline at end of file
diff --git a/components/project/ProjectList.tsx b/components/project/ProjectList.tsx
index 9dec7e77..e267b21c 100644
--- a/components/project/ProjectList.tsx
+++ b/components/project/ProjectList.tsx
@@ -98,20 +98,31 @@ export function ProjectList() {
fetchProjects();
}, []);
- const fetchProjects = async () => {
- try {
- const response = await fetch('/api/projects');
- const data = await response.json();
- setProjects(data);
- } catch (error) {
- toast({
- title: 'Error',
- description: 'Unable to load project list.',
- variant: 'destructive',
- });
+// components/project/ProjectList.tsx 의 fetchProjects 함수 수정
+
+const fetchProjects = async () => {
+ try {
+ const response = await fetch('/api/projects');
+ const data = await response.json();
+ setProjects(data);
+
+ // 멤버인 프로젝트가 정확히 1개일 때 자동 리다이렉트
+ const memberProjects = data.member || [];
+ const ownedProjects = data.owned || [];
+ const totalProjects = [...memberProjects, ...ownedProjects];
+
+ if (totalProjects.length === 1) {
+ const singleProject = totalProjects[0];
+ router.push(`/evcp/data-room/${singleProject.id}/files`);
}
- };
-
+ } catch (error) {
+ toast({
+ title: 'Error',
+ description: 'Unable to load project list.',
+ variant: 'destructive',
+ });
+ }
+};
const onSubmit = async (data: ProjectFormData) => {
setIsSubmitting(true);
try {
diff --git a/components/project/ProjectNav.tsx b/components/project/ProjectNav.tsx
index aac934ad..c62f760e 100644
--- a/components/project/ProjectNav.tsx
+++ b/components/project/ProjectNav.tsx
@@ -59,6 +59,7 @@ export function ProjectNav({ projectId }: ProjectNavProps) {
};
console.log(pathname, projectId)
+ console.log(projectRole, "projectRole")
const navItems = [
{
@@ -66,6 +67,7 @@ export function ProjectNav({ projectId }: ProjectNavProps) {
icon: Home,
href: `/evcp/data-room/${projectId}`,
active: pathname === `/${lng}/evcp/data-room/${projectId}`,
+ requireRole: ['owner', 'admin'],
},
{
label: 'Files',