From b54f6f03150dd78d86db62201b6386bf14b72394 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 15 Oct 2025 12:52:11 +0000 Subject: (대표님) 커버, 데이터룸, 파일매니저, 담당자할당 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[lng]/evcp/(evcp)/(eng)/cover/page.tsx | 77 ++ app/[lng]/evcp/(evcp)/(procurement)/po/page.tsx | 2 +- .../(evcp)/data-room/[projectId]/files/page.tsx | 17 - .../evcp/(evcp)/data-room/[projectId]/layout.tsx | 19 - .../(evcp)/data-room/[projectId]/members/page.tsx | 811 --------------------- .../evcp/(evcp)/data-room/[projectId]/page.tsx | 10 - .../(evcp)/data-room/[projectId]/settings/page.tsx | 488 ------------- .../(evcp)/data-room/[projectId]/stats/page.tsx | 373 ---------- app/[lng]/evcp/(evcp)/data-room/page.tsx | 26 - .../evcp/data-room/[projectId]/files/page.tsx | 17 + app/[lng]/evcp/data-room/[projectId]/layout.tsx | 19 + .../evcp/data-room/[projectId]/members/page.tsx | 811 +++++++++++++++++++++ app/[lng]/evcp/data-room/[projectId]/page.tsx | 10 + .../evcp/data-room/[projectId]/settings/page.tsx | 488 +++++++++++++ .../evcp/data-room/[projectId]/stats/page.tsx | 373 ++++++++++ app/[lng]/evcp/data-room/layout.tsx | 17 + .../evcp/data-room/owner-companies/[id]/page.tsx | 43 ++ .../owner-companies/[id]/users/new/page.tsx | 41 ++ .../data-room/owner-companies/[id]/users/page.tsx | 63 ++ .../evcp/data-room/owner-companies/new/page.tsx | 18 + app/[lng]/evcp/data-room/owner-companies/page.tsx | 32 + app/[lng]/evcp/data-room/page.tsx | 26 + .../data-room/[projectId]/files/page.tsx | 14 - .../(partners)/data-room/[projectId]/layout.tsx | 19 - .../data-room/[projectId]/members/page.tsx | 811 --------------------- .../(partners)/data-room/[projectId]/page.tsx | 10 - .../data-room/[projectId]/settings/page.tsx | 488 ------------- .../data-room/[projectId]/stats/page.tsx | 373 ---------- app/[lng]/partners/(partners)/data-room/page.tsx | 26 - app/[lng]/partners/(partners)/po/page.tsx | 2 +- .../partners/data-room/[projectId]/files/page.tsx | 10 + .../partners/data-room/[projectId]/layout.tsx | 19 + .../data-room/[projectId]/members/page.tsx | 811 +++++++++++++++++++++ app/[lng]/partners/data-room/[projectId]/page.tsx | 10 + .../data-room/[projectId]/settings/page.tsx | 488 +++++++++++++ .../partners/data-room/[projectId]/stats/page.tsx | 373 ++++++++++ app/[lng]/partners/data-room/layout.tsx | 17 + app/[lng]/partners/data-room/page.tsx | 26 + app/api/projects/[projectId]/cover/route.ts | 73 ++ app/api/projects/cover-template/save/route.ts | 125 ++++ app/api/projects/cover-template/upload/route.ts | 127 ++++ 41 files changed, 4116 insertions(+), 3487 deletions(-) create mode 100644 app/[lng]/evcp/(evcp)/(eng)/cover/page.tsx delete mode 100644 app/[lng]/evcp/(evcp)/data-room/[projectId]/files/page.tsx delete mode 100644 app/[lng]/evcp/(evcp)/data-room/[projectId]/layout.tsx delete mode 100644 app/[lng]/evcp/(evcp)/data-room/[projectId]/members/page.tsx delete mode 100644 app/[lng]/evcp/(evcp)/data-room/[projectId]/page.tsx delete mode 100644 app/[lng]/evcp/(evcp)/data-room/[projectId]/settings/page.tsx delete mode 100644 app/[lng]/evcp/(evcp)/data-room/[projectId]/stats/page.tsx delete mode 100644 app/[lng]/evcp/(evcp)/data-room/page.tsx create mode 100644 app/[lng]/evcp/data-room/[projectId]/files/page.tsx create mode 100644 app/[lng]/evcp/data-room/[projectId]/layout.tsx create mode 100644 app/[lng]/evcp/data-room/[projectId]/members/page.tsx create mode 100644 app/[lng]/evcp/data-room/[projectId]/page.tsx create mode 100644 app/[lng]/evcp/data-room/[projectId]/settings/page.tsx create mode 100644 app/[lng]/evcp/data-room/[projectId]/stats/page.tsx create mode 100644 app/[lng]/evcp/data-room/layout.tsx create mode 100644 app/[lng]/evcp/data-room/owner-companies/[id]/page.tsx create mode 100644 app/[lng]/evcp/data-room/owner-companies/[id]/users/new/page.tsx create mode 100644 app/[lng]/evcp/data-room/owner-companies/[id]/users/page.tsx create mode 100644 app/[lng]/evcp/data-room/owner-companies/new/page.tsx create mode 100644 app/[lng]/evcp/data-room/owner-companies/page.tsx create mode 100644 app/[lng]/evcp/data-room/page.tsx delete mode 100644 app/[lng]/partners/(partners)/data-room/[projectId]/files/page.tsx delete mode 100644 app/[lng]/partners/(partners)/data-room/[projectId]/layout.tsx delete mode 100644 app/[lng]/partners/(partners)/data-room/[projectId]/members/page.tsx delete mode 100644 app/[lng]/partners/(partners)/data-room/[projectId]/page.tsx delete mode 100644 app/[lng]/partners/(partners)/data-room/[projectId]/settings/page.tsx delete mode 100644 app/[lng]/partners/(partners)/data-room/[projectId]/stats/page.tsx delete mode 100644 app/[lng]/partners/(partners)/data-room/page.tsx create mode 100644 app/[lng]/partners/data-room/[projectId]/files/page.tsx create mode 100644 app/[lng]/partners/data-room/[projectId]/layout.tsx create mode 100644 app/[lng]/partners/data-room/[projectId]/members/page.tsx create mode 100644 app/[lng]/partners/data-room/[projectId]/page.tsx create mode 100644 app/[lng]/partners/data-room/[projectId]/settings/page.tsx create mode 100644 app/[lng]/partners/data-room/[projectId]/stats/page.tsx create mode 100644 app/[lng]/partners/data-room/layout.tsx create mode 100644 app/[lng]/partners/data-room/page.tsx create mode 100644 app/api/projects/[projectId]/cover/route.ts create mode 100644 app/api/projects/cover-template/save/route.ts create mode 100644 app/api/projects/cover-template/upload/route.ts (limited to 'app') diff --git a/app/[lng]/evcp/(evcp)/(eng)/cover/page.tsx b/app/[lng]/evcp/(evcp)/(eng)/cover/page.tsx new file mode 100644 index 00000000..9f2b2e61 --- /dev/null +++ b/app/[lng]/evcp/(evcp)/(eng)/cover/page.tsx @@ -0,0 +1,77 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { searchParamsProjectsCache } from "@/lib/projects/validation" +import { InformationButton } from "@/components/information/information-button" +import { getProjectListsForCover } from "@/lib/cover/service" +import { ProjectsTableForCover } from "@/lib/cover/table/projects-table" + +interface IndexPageProps { + searchParams: Promise +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsProjectsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getProjectListsForCover({ + ...search, + filters: validFilters, + }), + + ]) + + return ( + +
+
+
+
+

+ 프로젝트 리스트 +

+ +
+ {/*

+ S-EDP로부터 수신하는 프로젝트 리스트입니다. 향후 MDG로 전환됩니다.{" "} + + + 버튼 + + 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. +

*/} +
+
+
+ + }> + {/* */} + + + } + > + + +
+ ) +} diff --git a/app/[lng]/evcp/(evcp)/(procurement)/po/page.tsx b/app/[lng]/evcp/(evcp)/(procurement)/po/page.tsx index 7479df8c..292ef1cb 100644 --- a/app/[lng]/evcp/(evcp)/(procurement)/po/page.tsx +++ b/app/[lng]/evcp/(evcp)/(procurement)/po/page.tsx @@ -34,7 +34,7 @@ export default async function VendorPONew(props: VendorPOPageProps) {

- PO 관리 + PO/계약 관리

diff --git a/app/[lng]/evcp/(evcp)/data-room/[projectId]/files/page.tsx b/app/[lng]/evcp/(evcp)/data-room/[projectId]/files/page.tsx deleted file mode 100644 index baac96ad..00000000 --- a/app/[lng]/evcp/(evcp)/data-room/[projectId]/files/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -// app/projects/[projectId]/files/page.tsx -import { FileManager } from '@/components/file-manager/FileManager'; - -export default async function ProjectFilesPage({ - params, -}: { - params: { projectId: string }; -}) { - - const projectId = await params.projectId - - return ( -
- -
- ); -} \ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/data-room/[projectId]/layout.tsx b/app/[lng]/evcp/(evcp)/data-room/[projectId]/layout.tsx deleted file mode 100644 index d2e74f8e..00000000 --- a/app/[lng]/evcp/(evcp)/data-room/[projectId]/layout.tsx +++ /dev/null @@ -1,19 +0,0 @@ -// app/projects/[projectId]/layout.tsx -import { ProjectNav } from '@/components/project/ProjectNav'; - -export default function ProjectLayout({ - children, - params, -}: { - children: React.ReactNode; - params: { projectId: string }; -}) { - return ( -
- -
- {children} -
-
- ); -} diff --git a/app/[lng]/evcp/(evcp)/data-room/[projectId]/members/page.tsx b/app/[lng]/evcp/(evcp)/data-room/[projectId]/members/page.tsx deleted file mode 100644 index 18442c0e..00000000 --- a/app/[lng]/evcp/(evcp)/data-room/[projectId]/members/page.tsx +++ /dev/null @@ -1,811 +0,0 @@ -// app/projects/[projectId]/members/page.tsx -'use client'; - -import { use, useState, useEffect, useRef } from 'react'; -import { - Users, - UserPlus, - Crown, - Shield, - Eye, - Edit2, - Trash2, - Mail, - MoreVertical, - Search, - Filter, - Check, - ChevronsUpDown, - Loader2, - UserCog -} from 'lucide-react'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Badge } from '@/components/ui/badge'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from '@/components/ui/popover'; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from '@/components/ui/command'; -import { Label } from '@/components/ui/label'; -import { useToast } from '@/hooks/use-toast'; -import { cn } from '@/lib/utils'; -import { - Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow -} from '@/components/ui/table'; -import { Separator } from '@/components/ui/separator'; -import { getUsersForFilter } from '@/lib/gtc-contract/service'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" - -interface Member { - id: string; - userId: number; - user: { - name: string; - email: string; - imageUrl?: string; - domain: string; - }; - role: 'owner' | 'admin' | 'editor' | 'viewer'; - addedAt: string; - lastAccess?: string; -} - -interface User { - id: number; - name: string; - email: string; - domain?: string; // 'partners' | 'internal' 등 -} - -export default function ProjectMembersPage({ - params: promiseParams -}: { - params: Promise<{ projectId: string }> -}) { - // Next.js 15+ params Promise 처리 - const params = use(promiseParams); - const projectId = params.projectId; - - const [members, setMembers] = useState([]); - const [loading, setLoading] = useState(true); - const [searchQuery, setSearchQuery] = useState(''); - const [roleFilter, setRoleFilter] = useState('all'); - const [addMemberOpen, setAddMemberOpen] = useState(false); - const [editingMember, setEditingMember] = useState(null); - - // 사용자 선택 관련 상태 - const [availableUsers, setAvailableUsers] = useState([]); - const [selectedUser, setSelectedUser] = useState(null); - const [userSearchTerm, setUserSearchTerm] = useState(''); - const [userPopoverOpen, setUserPopoverOpen] = useState(false); - const [loadingUsers, setLoadingUsers] = useState(false); - const [isExternalUser, setIsExternalUser] = useState(false); // 외부 사용자 여부 - - const [newMemberRole, setNewMemberRole] = useState('viewer'); - const [currentUserRole, setCurrentUserRole] = useState('viewer'); - const [page, setPage] = useState(1); - const pageSize = 20; - - // Command component key management - const userOptionIdsRef = useRef>({}); - const popoverContentId = `popover-content-${Date.now()}`; - const commandId = `command-${Date.now()}`; - - const { toast } = useToast(); - - useEffect(() => { - setPage(1); - }, [searchQuery, roleFilter]); - - useEffect(() => { - fetchMembers(); - checkUserRole(); - }, [projectId]); - - // 다이얼로그가 열릴 때 사용자 목록 가져오기 - useEffect(() => { - if (addMemberOpen) { - fetchAvailableUsers(); - } else { - // 다이얼로그가 닫힐 때 초기화 - setSelectedUser(null); - setUserSearchTerm(''); - setNewMemberRole('viewer'); - setIsExternalUser(false); - } - }, [addMemberOpen]); - - const fetchAvailableUsers = async () => { - try { - setLoadingUsers(true); - const users = await getUsersForFilter(); - // 이미 프로젝트에 있는 멤버는 제외 - const memberUserIds = members.map(m => m.userId); - const filteredUsers = users.filter(u => !memberUserIds.includes(u.id)); - setAvailableUsers(filteredUsers); - } catch (error) { - console.error('사용자 목록 로드 실패:', error); - toast({ - title: '오류', - description: '사용자 목록을 불러올 수 없습니다.', - variant: 'destructive', - }); - } finally { - setLoadingUsers(false); - } - }; - - const fetchMembers = async () => { - try { - setLoading(true); - const response = await fetch(`/api/projects/${projectId}/members`); - const data = await response.json(); - setMembers(data.member); - } catch (error) { - toast({ - title: '오류', - description: '멤버 목록을 불러올 수 없습니다.', - variant: 'destructive', - }); - } finally { - setLoading(false); - } - }; - - const checkUserRole = async () => { - try { - const response = await fetch(`/api/projects/${projectId}/access`); - const data = await response.json(); - setCurrentUserRole(data.role); - } catch (error) { - console.error('권한 확인 실패:', error); - } - }; - - const addMember = async () => { - if (!selectedUser) { - toast({ - title: '오류', - description: '사용자를 선택해주세요.', - variant: 'destructive', - }); - return; - } - - try { - const response = await fetch(`/api/projects/${projectId}/members`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - userId: selectedUser.id, - role: newMemberRole, - }), - }); - - if (!response.ok) throw new Error('멤버 추가 실패'); - - toast({ - title: '성공', - description: '새 멤버가 추가되었습니다.', - }); - - setAddMemberOpen(false); - fetchMembers(); - } catch (error) { - toast({ - title: '오류', - description: '멤버 추가에 실패했습니다.', - variant: 'destructive', - }); - } - }; - - const updateMemberRole = async (memberId: string, newRole: string) => { - try { - const response = await fetch(`/api/projects/${projectId}/members/${memberId}`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ role: newRole }), - }); - - if (!response.ok) throw new Error('역할 변경 실패'); - - toast({ - title: '성공', - description: '멤버 역할이 변경되었습니다.', - }); - - fetchMembers(); - } catch (error) { - toast({ - title: '오류', - description: '역할 변경에 실패했습니다.', - variant: 'destructive', - }); - } - }; - - const removeMember = async (memberId: string) => { - try { - const response = await fetch(`/api/projects/${projectId}/members/${memberId}`, { - method: 'DELETE', - }); - - if (!response.ok) throw new Error('멤버 제거 실패'); - - toast({ - title: '성공', - description: '멤버가 제거되었습니다.', - }); - - fetchMembers(); - } catch (error) { - toast({ - title: '오류', - description: '멤버 제거에 실패했습니다.', - variant: 'destructive', - }); - } - }; - - const handleSelectUser = (user: User) => { - setSelectedUser(user); - setUserPopoverOpen(false); - - // 외부 사용자(partners)인 경우 역할을 viewer로 고정 - if (user.domain === 'partners') { - setIsExternalUser(true); - setNewMemberRole('viewer'); - } else { - setIsExternalUser(false); - // 내부 사용자는 기본값 viewer로 설정하되 변경 가능 - setNewMemberRole('viewer'); - } - }; - - const formatDateShort = (iso?: string) => - iso ? new Date(iso).toLocaleDateString() : '-'; - - const roleConfig = { - owner: { label: 'Owner', icon: Crown, color: 'text-yellow-500', bg: 'bg-yellow-50' }, - admin: { label: 'Admin', icon: Shield, color: 'text-blue-500', bg: 'bg-blue-50' }, - editor: { label: 'Editor', icon: Edit2, color: 'text-green-500', bg: 'bg-green-50' }, - viewer: { label: 'Viewer', icon: Eye, color: 'text-gray-500', bg: 'bg-gray-50' }, - }; - - const filteredMembers = members.filter(member => { - const matchesSearch = member.user.name.toLowerCase().includes(searchQuery.toLowerCase()) || - member.user.email.toLowerCase().includes(searchQuery.toLowerCase()); - const matchesRole = roleFilter === 'all' || member.role === roleFilter; - return matchesSearch && matchesRole; - }); - - // 사용자 검색 필터링 - const filteredUsers = availableUsers.filter(user => - user.name.toLowerCase().includes(userSearchTerm.toLowerCase()) || - user.email.toLowerCase().includes(userSearchTerm.toLowerCase()) - ); - - const canManageMembers = currentUserRole === 'owner' || currentUserRole === 'admin'; - - const totalPages = Math.max(1, Math.ceil(filteredMembers.length / pageSize)); - const paginatedMembers = filteredMembers.slice((page - 1) * pageSize, page * pageSize); - - if (loading) { - return ( -
-
- -

멤버 목록을 불러오는 중...

-
-
- ); - } - - return ( -
- {/* 헤더 */} -
-
-

프로젝트 멤버

-

- 프로젝트에 참여 중인 멤버를 관리합니다 -

-
- - {canManageMembers && ( - - )} -
- - {/* 필터 */} -
-
- - setSearchQuery(e.target.value)} - /> -
- - -
- - {/* 멤버 목록 (Table) */} -
- - - - - 이름 - 이메일 - 구분 - 역할 - 추가일 - 마지막 접속 - 액션 - - - - - {paginatedMembers.length > 0 ? ( - paginatedMembers.map((member) => { - const config = roleConfig[member.role]; - const Icon = config.icon; - const isInternal = member.user.domain !== 'partners'; - - return ( - - {/* Avatar */} - - - - - {member.user.name?.charAt(0).toUpperCase()} - - - - - {/* Name */} - - {member.user.name} - - - {/* Email */} - - {member.user.email} - - - {/* Domain */} - - - {isInternal ? 'Internal' : 'Partner'} - - - - {/* Role */} - - {canManageMembers && member.role !== 'owner' && member.user.domain !== 'partners' ? ( - - ) : ( -
-
- - - {config.label} - -
- {member.user.domain === 'partners' && canManageMembers && member.role !== 'owner' && ( - (고정) - )} -
- )} -
- - {/* AddedAt */} - - {formatDateShort(member.addedAt)} - - - {/* LastAccess */} - - {formatDateShort(member.lastAccess)} - - - {/* Actions */} - -
- {canManageMembers && member.role !== 'owner' ? ( - - - - - - - - 메일 보내기 - - - removeMember(member.id)} - > - - 제거 - - - - ) : ( - - )} -
-
-
- ); - }) - ) : ( - - -
- - 검색 결과가 없습니다 -
-
-
- )} -
-
-
- - {/* Pagination */} -
-
- 총 {filteredMembers.length}명 · {pageSize}명/페이지 -
-
- - - {page} / {totalPages} - - -
-
- - {/* 멤버 추가 다이얼로그 */} - - - - 멤버 추가 - - 프로젝트에 멤버를 추가합니다 - - - - - - 내부 사용자 - - 외부 사용자 - Viewer 전용 - - - - -
- - - {loadingUsers ? ( -
- - 사용자 목록 불러오는 중... -
- ) : ( - <> - - - - - - - - { - e.stopPropagation(); - const target = e.currentTarget; - target.scrollTop += e.deltaY; - }} - > - 사용자를 찾을 수 없습니다. - - {filteredUsers - .filter(u => u.domain !== 'partners') - .map((user) => ( - { - setSelectedUser(user); - setUserPopoverOpen(false); - setIsExternalUser(false); - setNewMemberRole('viewer'); - }} - value={`${user.name} ${user.email}`} - className="truncate" - > - -
-
{user.name}
-
{user.email}
-
- -
- ))} -
-
-
-
-
- -

- 내부 사용자는 모든 역할을 부여할 수 있습니다. -

- - )} -
- -
- - -
-
- - -
-

- 보안 정책 안내
- 외부 사용자(파트너)는 보안 정책상 Viewer 권한만 부여 가능합니다. -

-
- -
- - - {loadingUsers ? ( -
- - 사용자 목록 불러오는 중... -
- ) : ( - - - - - - - - { - e.stopPropagation(); - const target = e.currentTarget; - target.scrollTop += e.deltaY; - }} - > - 파트너를 찾을 수 없습니다. - - {filteredUsers - .filter(u => u.domain === 'partners') - .map((user) => ( - { - setSelectedUser(user); - setUserPopoverOpen(false); - setIsExternalUser(true); - setNewMemberRole('viewer'); - }} - value={user.name} - className="truncate" - > - - {user.name} - 파트너 - - - ))} - - - - - - )} -
- -
- - -
-
-
- - - - - -
-
-
- ); -} \ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/data-room/[projectId]/page.tsx b/app/[lng]/evcp/(evcp)/data-room/[projectId]/page.tsx deleted file mode 100644 index d54a8cab..00000000 --- a/app/[lng]/evcp/(evcp)/data-room/[projectId]/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -// app/projects/[projectId]/page.tsx -import { ProjectDashboard } from '@/components/project/ProjectDashboard'; - -export default function ProjectPage({ - params, -}: { - params: { projectId: string }; -}) { - return ; -} diff --git a/app/[lng]/evcp/(evcp)/data-room/[projectId]/settings/page.tsx b/app/[lng]/evcp/(evcp)/data-room/[projectId]/settings/page.tsx deleted file mode 100644 index aa0f3b52..00000000 --- a/app/[lng]/evcp/(evcp)/data-room/[projectId]/settings/page.tsx +++ /dev/null @@ -1,488 +0,0 @@ - -// app/projects/[projectId]/settings/page.tsx -'use client'; - -import { useState, useEffect } from 'react'; -import { - Settings, - Shield, - Globe, - Trash2, - AlertCircle, - Save, - Lock, - Unlock, - Archive, - Users, - HardDrive -} from 'lucide-react'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Switch } from '@/components/ui/switch'; -import { Textarea } from '@/components/ui/textarea'; -import { Alert, AlertDescription } from '@/components/ui/alert'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { useToast } from '@/hooks/use-toast'; -import { useRouter } from 'next/navigation'; - -interface ProjectSettings { - id: string; - name: string; - description: string; - isPublic: boolean; - externalAccessEnabled: boolean; - storageLimit: number; - maxFileSize: number; - allowedFileTypes: string[]; - autoArchiveDays: number; - requireApproval: boolean; - defaultCategory: string; -} - -export default function ProjectSettingsPage({ - params -}: { - params: { projectId: string } -}) { - const [settings, setSettings] = useState(null); - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [archiveDialogOpen, setArchiveDialogOpen] = useState(false); - const [currentUserRole, setCurrentUserRole] = useState('viewer'); - - const { toast } = useToast(); - const router = useRouter(); - - useEffect(() => { - fetchSettings(); - checkUserRole(); - }, [params.projectId]); - - const fetchSettings = async () => { - try { - setLoading(true); - const response = await fetch(`/api/projects/${params.projectId}/settings`); - - if (!response.ok) { - throw new Error('설정을 불러올 수 없습니다'); - } - - const data = await response.json(); - setSettings(data); - } catch (error) { - toast({ - title: '오류', - description: '프로젝트 설정을 불러올 수 없습니다.', - variant: 'destructive', - }); - } finally { - setLoading(false); - } - }; - - const checkUserRole = async () => { - try { - const response = await fetch(`/api/projects/${params.projectId}/access`); - const data = await response.json(); - setCurrentUserRole(data.role); - } catch (error) { - console.error('권한 확인 실패:', error); - } - }; - - const saveSettings = async () => { - if (!settings) return; - - try { - setSaving(true); - const response = await fetch(`/api/projects/${params.projectId}/settings`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(settings), - }); - - if (!response.ok) throw new Error('설정 저장 실패'); - - toast({ - title: '성공', - description: '프로젝트 설정이 저장되었습니다.', - }); - } catch (error) { - toast({ - title: '오류', - description: '설정 저장에 실패했습니다.', - variant: 'destructive', - }); - } finally { - setSaving(false); - } - }; - - const deleteProject = async () => { - try { - const response = await fetch(`/api/projects/${params.projectId}`, { - method: 'DELETE', - }); - - if (!response.ok) throw new Error('프로젝트 삭제 실패'); - - toast({ - title: '성공', - description: '프로젝트가 삭제되었습니다.', - }); - - router.push('/projects'); - } catch (error) { - toast({ - title: '오류', - description: '프로젝트 삭제에 실패했습니다.', - variant: 'destructive', - }); - } - }; - - const archiveProject = async () => { - try { - const response = await fetch(`/api/projects/${params.projectId}/archive`, { - method: 'POST', - }); - - if (!response.ok) throw new Error('프로젝트 보관 실패'); - - toast({ - title: '성공', - description: '프로젝트가 보관되었습니다.', - }); - - router.push('/projects'); - } catch (error) { - toast({ - title: '오류', - description: '프로젝트 보관에 실패했습니다.', - variant: 'destructive', - }); - } - }; - - const canEdit = currentUserRole === 'owner' || currentUserRole === 'admin'; - - if (loading || !settings) { - return ( -
-
- {[...Array(5)].map((_, i) => ( -
- ))} -
-
- ); - } - - return ( -
- {/* 헤더 */} -
-
-

프로젝트 설정

-

- 프로젝트 설정을 관리합니다 -

-
- - {canEdit && ( - - )} -
- - {!canEdit && ( - - - - 프로젝트 설정을 변경하려면 Owner 또는 Admin 권한이 필요합니다. - - - )} - - - - 일반 - 접근 관리 - 스토리지 - {currentUserRole === 'owner' && ( - 위험 영역 - )} - - - - - - 기본 정보 - - -
- - setSettings({ ...settings, name: e.target.value })} - disabled={!canEdit} - /> -
- -
- -