Merge branch 'fe/develop' of https://lab.ssafy.com/s11-s-project/S11P21S002 into fe/refactor/upload
This commit is contained in:
commit
bc22f31a55
@ -31,7 +31,7 @@ export default function ProjectCreateModal({ onSubmit, buttonClass = '' }: Proje
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader title="새 프로젝트" />
|
||||
<DialogHeader title="새 프로젝트 추가" />
|
||||
<ProjectCreateForm
|
||||
onSubmit={(data: ProjectCreateFormValues) => {
|
||||
const formattedData: ProjectRequest = {
|
||||
|
@ -34,7 +34,7 @@ export default function WorkSpaceCreateModal({ onSubmit }: WorkSpaceCreateModalP
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader title="새 워크스페이스" />
|
||||
<DialogHeader title="새 워크스페이스 추가" />
|
||||
<WorkSpaceCreateForm onSubmit={handleFormSubmit} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
@ -1,17 +1,29 @@
|
||||
import { useEffect, Suspense } from 'react';
|
||||
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
|
||||
import { useEffect, Suspense, useState } from 'react';
|
||||
import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
import Header from '../Header';
|
||||
import useAuthStore from '@/stores/useAuthStore';
|
||||
import WorkSpaceCreateModal from '../WorkSpaceCreateModal';
|
||||
import { WorkspaceRequest, WorkspaceResponse } from '@/types';
|
||||
import { ProjectRequest, WorkspaceRequest, WorkspaceResponse } from '@/types';
|
||||
import useWorkspaceListQuery from '@/queries/workspaces/useWorkspaceListQuery';
|
||||
import useCreateWorkspaceQuery from '@/queries/workspaces/useCreateWorkspaceQuery';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Ellipsis } from 'lucide-react';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui/dropdown-menu';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
|
||||
import WorkSpaceCreateForm, { WorkSpaceCreateFormValues } from '../WorkSpaceCreateModal/WorkSpaceCreateForm';
|
||||
import ProjectCreateForm, { ProjectCreateFormValues } from '../ProjectCreateModal/ProjectCreateForm';
|
||||
import useCreateProjectQuery from '@/queries/projects/useCreateProjectQuery';
|
||||
import WorkspaceUpdateForm, { WorkspaceUpdateFormValues } from '../WorkspaceUpdateModal/WorkspaceUpdateForm';
|
||||
import useUpdateWorkspaceQuery from '@/queries/workspaces/useUpdateWorkspaceQuery';
|
||||
|
||||
export default function WorkspaceBrowseLayout() {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { profile } = useAuthStore();
|
||||
const memberId = profile?.id ?? 0;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [isOpenWorkspaceCreate, setIsOpenWorkspaceCreate] = useState<boolean>(false);
|
||||
const [isOpenWorkspaceUpdate, setIsOpenWorkspaceUpdate] = useState<boolean>(false);
|
||||
const [isOpenProjectCreate, setIsOpenProjectCreate] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (memberId == 0) {
|
||||
@ -19,17 +31,64 @@ export default function WorkspaceBrowseLayout() {
|
||||
}
|
||||
}, [memberId, navigate]);
|
||||
|
||||
const { data: workspacesResponse } = useWorkspaceListQuery(memberId ?? 0);
|
||||
const createWorkspace = useCreateWorkspaceQuery();
|
||||
const updateWorkspace = useUpdateWorkspaceQuery();
|
||||
const createProject = useCreateProjectQuery();
|
||||
|
||||
const { data: workspacesResponse, refetch } = useWorkspaceListQuery(memberId ?? 0);
|
||||
const workspaces = workspacesResponse?.workspaceResponses ?? [];
|
||||
const activeWorkspaceId: number = Number(location.pathname.split('/')[2] || '-1');
|
||||
const activeWorkspace = workspaces.filter((workspace) => workspace.id === activeWorkspaceId)[0] ?? null;
|
||||
|
||||
const handleCreateWorkspace = (values: WorkSpaceCreateFormValues) => {
|
||||
const data: WorkspaceRequest = {
|
||||
title: values.workspaceName,
|
||||
content: values.workspaceDescription || '',
|
||||
};
|
||||
|
||||
const handleCreateWorkspace = (data: WorkspaceRequest) => {
|
||||
createWorkspace.mutate({
|
||||
memberId,
|
||||
data,
|
||||
});
|
||||
|
||||
setIsOpenWorkspaceCreate(false);
|
||||
};
|
||||
|
||||
const workspaces = workspacesResponse?.workspaceResponses ?? [];
|
||||
const handleUpdateWorkspace = (values: WorkspaceUpdateFormValues) => {
|
||||
const data: WorkspaceRequest = {
|
||||
title: values.workspaceName,
|
||||
content: values.workspaceDescription || '',
|
||||
};
|
||||
|
||||
updateWorkspace.mutate({
|
||||
workspaceId: activeWorkspaceId,
|
||||
memberId,
|
||||
data,
|
||||
});
|
||||
|
||||
setIsOpenWorkspaceUpdate(false);
|
||||
refetch();
|
||||
};
|
||||
|
||||
const handleCreateProject = (values: ProjectCreateFormValues) => {
|
||||
const data: ProjectRequest = {
|
||||
title: values.projectName,
|
||||
projectType: values.labelType.toLowerCase() as ProjectRequest['projectType'],
|
||||
categories: values.categories,
|
||||
};
|
||||
|
||||
createProject.mutate({
|
||||
workspaceId: activeWorkspaceId,
|
||||
memberId,
|
||||
data,
|
||||
});
|
||||
|
||||
setIsOpenProjectCreate(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refetch();
|
||||
}, [isOpenWorkspaceUpdate, refetch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -42,7 +101,18 @@ export default function WorkspaceBrowseLayout() {
|
||||
<h1 className="subheading mr-2.5 w-full overflow-hidden text-ellipsis whitespace-nowrap p-2">
|
||||
내 워크스페이스
|
||||
</h1>
|
||||
<WorkSpaceCreateModal onSubmit={handleCreateWorkspace} />
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<div className="rounded-full p-2 duration-200 hover:bg-gray-200">
|
||||
<Ellipsis size={16} />
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={() => setIsOpenWorkspaceCreate(true)}>
|
||||
새 워크스페이스 추가
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{workspaces.length > 0 ? (
|
||||
@ -51,13 +121,29 @@ export default function WorkspaceBrowseLayout() {
|
||||
key={workspace.id}
|
||||
to={`/browse/${workspace.id}`}
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
'cursor-pointer rounded-lg p-3 hover:bg-gray-200',
|
||||
isActive ? 'body-strong bg-gray-300' : 'body'
|
||||
)
|
||||
cn('cursor-pointer rounded-lg hover:bg-gray-200', isActive ? 'body-strong bg-gray-200' : 'body')
|
||||
}
|
||||
>
|
||||
{workspace.title}
|
||||
<div className="flex items-center justify-center">
|
||||
<p className="w-full overflow-hidden text-ellipsis whitespace-nowrap p-3">{workspace.title}</p>
|
||||
{workspace.id === activeWorkspaceId && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<div className="mr-1 rounded-full p-2 duration-200 hover:bg-gray-300">
|
||||
<Ellipsis size={16} />
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={() => setIsOpenWorkspaceUpdate(true)}>
|
||||
워크스페이스 이름 변경
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setIsOpenProjectCreate(true)}>
|
||||
워크스페이스에 새 프로젝트 추가
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
</NavLink>
|
||||
))
|
||||
) : (
|
||||
@ -74,6 +160,42 @@ export default function WorkspaceBrowseLayout() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
open={isOpenWorkspaceCreate}
|
||||
onOpenChange={setIsOpenWorkspaceCreate}
|
||||
>
|
||||
<DialogTrigger asChild></DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader title="새 워크스페이스 추가" />
|
||||
<WorkSpaceCreateForm onSubmit={handleCreateWorkspace} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={isOpenWorkspaceUpdate}
|
||||
onOpenChange={setIsOpenWorkspaceUpdate}
|
||||
>
|
||||
<DialogTrigger asChild></DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader title="워크스페이스 이름 변경" />
|
||||
<WorkspaceUpdateForm
|
||||
workspace={activeWorkspace}
|
||||
onSubmit={handleUpdateWorkspace}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={isOpenProjectCreate}
|
||||
onOpenChange={setIsOpenProjectCreate}
|
||||
>
|
||||
<DialogTrigger asChild></DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader title="새 프로젝트 추가" />
|
||||
<ProjectCreateForm onSubmit={handleCreateProject} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -60,19 +60,22 @@ export default function WorkspaceDropdownMenu({
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Menu
|
||||
size={16}
|
||||
className="stroke-gray-900"
|
||||
/>
|
||||
<Menu size={16} />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuItem onClick={() => console.log('프로젝트 이름 수정')}>프로젝트 이름 수정</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={handleOpenUploadFile}>파일 업로드</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleOpenUploadPresigned}>파일 업로드 (PresignedUrl 이용)</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleOpenUploadFolderFile}>폴더 업로드 (파일 업로드 API 이용)</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleOpenUploadFolder}>폴더 업로드 (백엔드 구현 필요)</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleOpenUploadZip}>폴더 압축파일 업로드</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setIsOpenUploadFile(true)}>파일 업로드</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setIsOpenUploadPresigned(true)}>
|
||||
파일 업로드 (PresignedUrl 이용)
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setIsOpenUploadFolderFile(true)}>
|
||||
폴더 업로드 (파일 업로드 API 이용)
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setIsOpenUploadFolder(true)}>
|
||||
폴더 업로드 (백엔드 구현 필요)
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setIsOpenUploadZip(true)}>폴더 압축파일 업로드</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@ -86,7 +89,7 @@ export default function WorkspaceDropdownMenu({
|
||||
<ImageUploadForm
|
||||
onClose={handleCloseUploadFile}
|
||||
onRefetch={onRefetch}
|
||||
onFileCount={handleFileCount}
|
||||
onFileCount={(fileCount: number) => setFileCount(fileCount)}
|
||||
projectId={projectId}
|
||||
folderId={folderId}
|
||||
uploadImageZipMutation={uploadImageZipMutation}
|
||||
@ -107,9 +110,9 @@ export default function WorkspaceDropdownMenu({
|
||||
title={presignedCount > 0 ? `파일 업로드 PreSigned (${presignedCount})` : '파일 업로드 PreSigned'}
|
||||
/>
|
||||
<ImageUploadPresignedForm
|
||||
onClose={handleCloseUploadPresigned}
|
||||
onClose={() => setIsOpenUploadPresigned(false)}
|
||||
onRefetch={onRefetch}
|
||||
onFileCount={handlePresignedCount}
|
||||
onFileCount={(fileCount: number) => setPresignedCount(fileCount)}
|
||||
projectId={projectId}
|
||||
folderId={folderId}
|
||||
/>
|
||||
|
@ -0,0 +1,88 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../ui/form';
|
||||
import { Input } from '../ui/input';
|
||||
import { Button } from '../ui/button';
|
||||
import { WorkspaceResponse } from '@/types';
|
||||
|
||||
const formSchema = z.object({
|
||||
workspaceName: z
|
||||
.string()
|
||||
.min(1, { message: '이름을 입력해주세요.' })
|
||||
.max(50, { message: '이름은 50자 이내여야 합니다.' }),
|
||||
workspaceDescription: z
|
||||
.string()
|
||||
.min(1, { message: '설명을 입력해주세요.' })
|
||||
.max(200, { message: '설명은 200자 이내여야 합니다.' }),
|
||||
});
|
||||
|
||||
export type WorkspaceUpdateFormValues = z.infer<typeof formSchema>;
|
||||
|
||||
interface WorkspaceUpdateFormProps {
|
||||
workspace: WorkspaceResponse;
|
||||
onSubmit: (data: WorkspaceUpdateFormValues) => void;
|
||||
}
|
||||
|
||||
export default function WorkspaceUpdateForm({ workspace, onSubmit }: WorkspaceUpdateFormProps) {
|
||||
const defaultValues: Partial<WorkspaceUpdateFormValues> = {
|
||||
workspaceName: workspace.title,
|
||||
workspaceDescription: workspace.content,
|
||||
};
|
||||
|
||||
const form = useForm<WorkspaceUpdateFormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="flex flex-col gap-5"
|
||||
>
|
||||
<FormField
|
||||
name="workspaceName"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="body-strong">워크스페이스 이름</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="이름을 입력해주세요."
|
||||
maxLength={50}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
name="workspaceDescription"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="body-strong">워크스페이스 설명</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="워크스페이스 설명을 입력해주세요."
|
||||
maxLength={200}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="blue"
|
||||
disabled={!form.formState.isValid}
|
||||
>
|
||||
워크스페이스 이름 변경하기
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
46
frontend/src/components/WorkspaceUpdateModal/index.tsx
Normal file
46
frontend/src/components/WorkspaceUpdateModal/index.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { WorkspaceRequest, WorkspaceResponse } from '@/types';
|
||||
import { useState } from 'react';
|
||||
import WorkspaceUpdateForm, { WorkspaceUpdateFormValues } from './WorkspaceUpdateForm';
|
||||
|
||||
interface WorkspaceUpdateModalProps {
|
||||
workspace: WorkspaceResponse;
|
||||
onSubmit: (data: WorkspaceRequest) => void;
|
||||
}
|
||||
|
||||
export default function WorkspaceUpdateModal({ workspace, onSubmit }: WorkspaceUpdateModalProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const handleFormSubmit = (data: WorkspaceUpdateFormValues) => {
|
||||
const formattedData: WorkspaceRequest = {
|
||||
title: data.workspaceName,
|
||||
content: data.workspaceDescription || '',
|
||||
};
|
||||
onSubmit(formattedData);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<button
|
||||
className="flex items-center justify-center p-2"
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<Plus size={20} />
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader title="워크스페이스 이름 변경" />
|
||||
<WorkspaceUpdateForm
|
||||
workspace={workspace}
|
||||
onSubmit={handleFormSubmit}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user