summaryrefslogtreecommitdiff
path: root/components/project/ProjectList.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/project/ProjectList.tsx')
-rw-r--r--components/project/ProjectList.tsx221
1 files changed, 147 insertions, 74 deletions
diff --git a/components/project/ProjectList.tsx b/components/project/ProjectList.tsx
index 4a4f7962..9dec7e77 100644
--- a/components/project/ProjectList.tsx
+++ b/components/project/ProjectList.tsx
@@ -2,13 +2,12 @@
'use client';
import { useState, useEffect } from 'react';
-import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';
-import {
- Plus,
- Folder,
- Users,
- Globe,
+import {
+ Plus,
+ Folder,
+ Users,
+ Globe,
Lock,
Crown,
Calendar,
@@ -34,6 +33,7 @@ import { Switch } from '@/components/ui/switch';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useToast } from '@/hooks/use-toast';
import { cn } from '@/lib/utils';
+import { useRouter, usePathname } from "next/navigation"
interface Project {
id: string;
@@ -65,11 +65,16 @@ export function ProjectList() {
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
-
+ const pathname = usePathname()
+
+ const internal = pathname?.includes('evcp')
+
+ console.log(projects)
+
const router = useRouter();
const { toast } = useToast();
- // React Hook Form 설정
+ // React Hook Form setup
const {
register,
handleSubmit,
@@ -100,8 +105,8 @@ export function ProjectList() {
setProjects(data);
} catch (error) {
toast({
- title: '오류',
- description: '프로젝트 목록을 불러올 수 없습니다.',
+ title: 'Error',
+ description: 'Unable to load project list.',
variant: 'destructive',
});
}
@@ -116,25 +121,25 @@ export function ProjectList() {
body: JSON.stringify(data),
});
- if (!response.ok) throw new Error('프로젝트 생성 실패');
+ if (!response.ok) throw new Error('Failed to create project');
const project = await response.json();
-
+
toast({
- title: '성공',
- description: '프로젝트가 생성되었습니다.',
+ title: 'Success',
+ description: 'Project has been created.',
});
setCreateDialogOpen(false);
reset();
fetchProjects();
-
- // 생성된 프로젝트로 이동
+
+ // Navigate to created project
router.push(`/evcp/data-room/${project.id}`);
} catch (error) {
toast({
- title: '오류',
- description: '프로젝트 생성에 실패했습니다.',
+ title: 'Error',
+ description: 'Failed to create project.',
variant: 'destructive',
});
} finally {
@@ -150,19 +155,20 @@ export function ProjectList() {
};
const filteredProjects = {
- owned: projects.owned?.filter(p =>
+ owned: projects.owned?.filter(p =>
p.name.toLowerCase().includes(searchQuery.toLowerCase())
- ),
- member: projects.member?.filter(p =>
+ ) || [], // Return empty array instead of undefined
+ member: projects.member?.filter(p =>
p.name.toLowerCase().includes(searchQuery.toLowerCase())
- ),
- public: projects.public?.filter(p =>
+ ) || [],
+ public: projects.public?.filter(p =>
p.name.toLowerCase().includes(searchQuery.toLowerCase())
- ),
+ ) || [],
};
+
const ProjectCard = ({ project, role }: { project: Project; role?: string }) => (
- <Card
+ <Card
className="cursor-pointer hover:shadow-lg transition-shadow"
onClick={() => router.push(`/evcp/data-room/${project.id}/files`)}
>
@@ -182,7 +188,7 @@ export function ProjectList() {
)}
</div>
<CardDescription className="line-clamp-2">
- {project.description || '설명이 없습니다'}
+ {project.description || 'No description'}
</CardDescription>
</CardHeader>
<CardContent>
@@ -217,26 +223,30 @@ export function ProjectList() {
return (
<>
- {/* 헤더 */}
+ {/* Header */}
<div className="flex items-center justify-between mb-6">
<div>
- <h1 className="text-3xl font-bold">프로젝트</h1>
+ <h1 className="text-3xl font-bold">Projects</h1>
<p className="text-muted-foreground mt-1">
- 파일을 관리하고 팀과 협업하세요
+ Manage files and collaborate with your team
</p>
</div>
- {/* <Button onClick={() => setCreateDialogOpen(true)}>
- <Plus className="h-4 w-4 mr-2" />
- 새 프로젝트
- </Button> */}
+ {internal &&
+ <Button onClick={() => setCreateDialogOpen(true)}>
+ <Plus className="h-4 w-4 mr-2" />
+ New Project
+ </Button>
+
+ }
+
</div>
- {/* 검색 및 필터 */}
+ {/* Search and Filter */}
<div className="flex items-center gap-3 mb-6">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
- placeholder="프로젝트 검색..."
+ placeholder="Search projects..."
className="pl-9"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
@@ -251,22 +261,85 @@ export function ProjectList() {
</Button>
</div>
- {/* 프로젝트 목록 */}
+ {/* Project List */}
<Tabs defaultValue="owned" className="space-y-6">
<TabsList>
+ {internal &&
+ <TabsTrigger value="owned">
+ My Projects ({filteredProjects.owned?.length})
+ </TabsTrigger>
+ }
+
<TabsTrigger value="member">
- 참여 프로젝트 ({filteredProjects.member?.length})
+ Joined Projects ({filteredProjects.member?.length})
</TabsTrigger>
<TabsTrigger value="public">
- 공개 프로젝트 ({filteredProjects.public?.length})
+ Public Projects ({filteredProjects.public?.length})
</TabsTrigger>
</TabsList>
+ {/* My Projects Tab */}
+ {internal &&
+ <TabsContent value="owned">
+ {filteredProjects.owned?.length === 0 ? (
+ <div className="text-center py-12">
+ <Crown className="h-12 w-12 text-muted-foreground mx-auto mb-3" />
+ <p className="text-muted-foreground">You don't own any projects</p>
+ <Button
+ className="mt-4"
+ onClick={() => setCreateDialogOpen(true)}
+ >
+ <Plus className="h-4 w-4 mr-2" />
+ Create your first project
+ </Button>
+ </div>
+ ) : viewMode === 'grid' ? (
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
+ {filteredProjects.owned?.map(project => (
+ <ProjectCard key={project.id} project={project} role="owner" />
+ ))}
+ </div>
+ ) : (
+ <div className="space-y-2">
+ {filteredProjects.owned?.map(project => (
+ <Card
+ key={project.id}
+ className="cursor-pointer hover:shadow transition-shadow"
+ onClick={() => router.push(`/evcp/data-room/${project.id}/files`)}
+ >
+ <CardContent className="flex items-center justify-between p-4">
+ <div className="flex items-center gap-3">
+ <Folder className="h-5 w-5 text-blue-500" />
+ <div>
+ <p className="font-medium">{project.code} {project.name}</p>
+ <p className="text-sm text-muted-foreground">
+ {project.description || 'No description'}
+ </p>
+ </div>
+ </div>
+ <div className="flex items-center gap-2">
+ <Badge variant="secondary">Owner</Badge>
+ {project.isPublic ? (
+ <Globe className="h-4 w-4 text-green-500" />
+ ) : (
+ <Lock className="h-4 w-4 text-gray-500" />
+ )}
+ </div>
+ </CardContent>
+ </Card>
+ ))}
+ </div>
+ )}
+ </TabsContent>
+
+
+ }
+
<TabsContent value="member">
{filteredProjects.member?.length === 0 ? (
<div className="text-center py-12">
<Users className="h-12 w-12 text-muted-foreground mx-auto mb-3" />
- <p className="text-muted-foreground">참여 중인 프로젝트가 없습니다</p>
+ <p className="text-muted-foreground">You are not a member of any projects</p>
</div>
) : viewMode === 'grid' ? (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
@@ -277,7 +350,7 @@ export function ProjectList() {
) : (
<div className="space-y-2">
{filteredProjects.member?.map(project => (
- <Card
+ <Card
key={project.id}
className="cursor-pointer hover:shadow transition-shadow"
onClick={() => router.push(`/evcp/data-room/${project.id}/files`)}
@@ -286,9 +359,9 @@ export function ProjectList() {
<div className="flex items-center gap-3">
<Folder className="h-5 w-5 text-blue-500" />
<div>
- <p className="font-medium">{project.name}</p>
+ <p className="font-medium">{project.code} {project.name}</p>
<p className="text-sm text-muted-foreground">
- {project.description || '설명이 없습니다'}
+ {project.description || 'No description'}
</p>
</div>
</div>
@@ -311,7 +384,7 @@ export function ProjectList() {
{filteredProjects.public?.length === 0 ? (
<div className="text-center py-12">
<Globe className="h-12 w-12 text-muted-foreground mx-auto mb-3" />
- <p className="text-muted-foreground">공개 프로젝트가 없습니다</p>
+ <p className="text-muted-foreground">No public projects</p>
</div>
) : viewMode === 'grid' ? (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
@@ -322,7 +395,7 @@ export function ProjectList() {
) : (
<div className="space-y-2">
{filteredProjects.public?.map(project => (
- <Card
+ <Card
key={project.id}
className="cursor-pointer hover:shadow transition-shadow"
onClick={() => router.push(`/evcp/data-room/${project.id}/files`)}
@@ -331,13 +404,13 @@ export function ProjectList() {
<div className="flex items-center gap-3">
<Globe className="h-5 w-5 text-green-500" />
<div>
- <p className="font-medium">{project.name}</p>
+ <p className="font-medium">{project.code} {project.name}</p>
<p className="text-sm text-muted-foreground">
- {project.description || '설명이 없습니다'}
+ {project.description || 'No description'}
</p>
</div>
</div>
- <Badge variant="outline">공개</Badge>
+ <Badge variant="outline">Public</Badge>
</CardContent>
</Card>
))}
@@ -346,32 +419,32 @@ export function ProjectList() {
</TabsContent>
</Tabs>
- {/* 프로젝트 생성 다이얼로그 */}
+ {/* Create Project Dialog */}
<Dialog open={createDialogOpen} onOpenChange={handleDialogClose}>
<DialogContent>
<DialogHeader>
- <DialogTitle>새 프로젝트 만들기</DialogTitle>
+ <DialogTitle>Create New Project</DialogTitle>
<DialogDescription>
- 팀과 파일을 공유할 새 프로젝트를 생성합니다
+ Create a new project to share files with your team
</DialogDescription>
</DialogHeader>
-
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<Label htmlFor="code">
- 프로젝트 코드 <span className="text-red-500">*</span>
+ Project Code <span className="text-red-500">*</span>
</Label>
<Input
id="code"
{...register('code', {
- required: '프로젝트 코드는 필수입니다',
+ required: 'Project code is required',
minLength: {
value: 2,
- message: '프로젝트 코드는 최소 2자 이상이어야 합니다',
+ message: 'Project code must be at least 2 characters',
},
pattern: {
value: /^[A-Z0-9]+$/,
- message: '프로젝트 코드는 대문자와 숫자만 사용 가능합니다',
+ message: 'Project code can only contain uppercase letters and numbers',
},
})}
placeholder="SN1001"
@@ -384,52 +457,52 @@ export function ProjectList() {
<div>
<Label htmlFor="name">
- 프로젝트 이름 <span className="text-red-500">*</span>
+ Project Name <span className="text-red-500">*</span>
</Label>
<Input
id="name"
{...register('name', {
- required: '프로젝트 이름은 필수입니다',
+ required: 'Project name is required',
minLength: {
value: 2,
- message: '프로젝트 이름은 최소 2자 이상이어야 합니다',
+ message: 'Project name must be at least 2 characters',
},
maxLength: {
value: 50,
- message: '프로젝트 이름은 50자를 초과할 수 없습니다',
+ message: 'Project name cannot exceed 50 characters',
},
})}
- placeholder="예: FNLG"
+ placeholder="e.g. FNLG"
className={errors.name ? 'border-red-500' : ''}
/>
{errors.name && (
<p className="text-sm text-red-500 mt-1">{errors.name.message}</p>
)}
</div>
-
+
<div>
- <Label htmlFor="description">설명 (선택)</Label>
+ <Label htmlFor="description">Description (Optional)</Label>
<Input
id="description"
{...register('description', {
maxLength: {
value: 200,
- message: '설명은 200자를 초과할 수 없습니다',
+ message: 'Description cannot exceed 200 characters',
},
})}
- placeholder="프로젝트에 대한 간단한 설명"
+ placeholder="Brief description of the project"
className={errors.description ? 'border-red-500' : ''}
/>
{errors.description && (
<p className="text-sm text-red-500 mt-1">{errors.description.message}</p>
)}
</div>
-
+
<div className="flex items-center justify-between">
<div>
- <Label htmlFor="public">공개 프로젝트</Label>
+ <Label htmlFor="public">Public Project</Label>
<p className="text-sm text-muted-foreground">
- 모든 사용자가 이 프로젝트를 볼 수 있습니다
+ All users can view this project
</p>
</div>
<Switch
@@ -438,21 +511,21 @@ export function ProjectList() {
onCheckedChange={(checked) => setValue('isPublic', checked)}
/>
</div>
-
+
<DialogFooter>
- <Button
- type="button"
- variant="outline"
+ <Button
+ type="button"
+ variant="outline"
onClick={() => handleDialogClose(false)}
disabled={isSubmitting}
>
- 취소
+ Cancel
</Button>
- <Button
+ <Button
type="submit"
disabled={!isValid || isSubmitting}
>
- {isSubmitting ? '생성 중...' : '프로젝트 생성'}
+ {isSubmitting ? 'Creating...' : 'Create Project'}
</Button>
</DialogFooter>
</form>