diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-13 08:56:27 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-13 08:56:27 +0000 |
| commit | b9a2081a76e669688d5884f20482b37cc8acca22 (patch) | |
| tree | 385e78c05d193a54daaced836f1e1152696153a8 /components | |
| parent | e84cf02a1cb4959a9d3bb5bbf37885c13a447f78 (diff) | |
(최겸, 임수민) 구매 입찰, 견적(그룹코드, tbe에러) 수정, data-room 수정
Diffstat (limited to 'components')
| -rw-r--r-- | components/file-manager/FileManager.tsx | 100 | ||||
| -rw-r--r-- | components/layout/Footer.tsx | 13 | ||||
| -rw-r--r-- | components/project/ProjectDashboard.tsx | 103 | ||||
| -rw-r--r-- | components/project/ProjectList.tsx | 37 | ||||
| -rw-r--r-- | components/project/ProjectNav.tsx | 2 |
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', |
