Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/app/workspaces/[workspaceId]/files/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 워크스페이스 자료실 페이지의 라우트 진입점입니다.
import { ResourcesView } from '@/views/store-operation/resources';

interface FilesPageProps {
params: Promise<{
workspaceId: string;
}>;
}

export default async function FilesPage({ params }: FilesPageProps) {
const { workspaceId } = await params;

return <ResourcesView workspaceId={workspaceId} />;
}
8 changes: 8 additions & 0 deletions src/entities/resource/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// 자료실 도메인의 타입과 목업 데이터 공개 API입니다.
export type {
ResourceFormValues,
ResourceItem,
ResourceLinkProvider,
ResourceType,
} from './model/resource.types';
export { mockResources } from './model/mock-resources';
140 changes: 140 additions & 0 deletions src/entities/resource/model/mock-resources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Supabase Storage와 resources 테이블 연결 전 자료실 화면에서 사용하는 목업 데이터입니다.
import type { ResourceItem } from './resource.types';

export const mockResources: ResourceItem[] = [
{
id: 'resource-1',
workspaceId: 'store-workspace',
title: '7월 신메뉴 레시피.pdf',
description: '망고라떼, 피치에이드 레시피',
resourceType: 'file',
fileName: '7월 신메뉴 레시피.pdf',
uploadedBy: '김민서',
createdAt: '2025-06-28',
},
{
id: 'resource-2',
workspaceId: 'store-workspace',
title: '6월 매출 정산.xls',
description: '일별 매출 집계표',
resourceType: 'file',
fileName: '6월 매출 정산.xlsx',
uploadedBy: '이준혁',
createdAt: '2025-06-30',
},
{
id: 'resource-3',
workspaceId: 'store-workspace',
title: '재고 관리 시트',
description: 'Google 스프레드시트',
resourceType: 'link',
linkProvider: 'link',
url: 'https://docs.google.com/spreadsheets',
uploadedBy: '김민서',
createdAt: '2025-06-01',
},
{
id: 'resource-4',
workspaceId: 'store-workspace',
title: '카드단말기 사용설명서.pdf',
description: '신형 단말기 매뉴얼',
resourceType: 'file',
fileName: '카드단말기 사용설명서.pdf',
uploadedBy: '이준혁',
createdAt: '2025-06-22',
},
{
id: 'resource-5',
workspaceId: 'team-workspace',
title: '캡스톤 기획서 v2.pdf',
description: '최종 기획 문서',
resourceType: 'file',
fileName: '캡스톤 기획서 v2.pdf',
uploadedBy: '김지은',
createdAt: '2025-06-28',
},
{
id: 'resource-6',
workspaceId: 'team-workspace',
title: 'Figma 디자인 파일',
description: 'UI 컴포넌트 모음',
resourceType: 'link',
linkProvider: 'figma',
url: 'https://figma.com',
uploadedBy: '박서준',
createdAt: '2025-06-26',
},
{
id: 'resource-7',
workspaceId: 'team-workspace',
title: '사용자 인터뷰 결과.xlsx',
description: '인터뷰 응답 원본 데이터',
resourceType: 'file',
fileName: '사용자 인터뷰 결과.xlsx',
uploadedBy: '이하은',
createdAt: '2025-06-24',
},
{
id: 'resource-8',
workspaceId: 'team-workspace',
title: 'API 명세서 (Notion)',
description: 'REST API 문서',
resourceType: 'link',
linkProvider: 'notion',
url: 'https://notion.so',
uploadedBy: '최민준',
createdAt: '2025-06-22',
},
{
id: 'resource-9',
workspaceId: 'team-workspace',
title: '로고 최종본.zip',
description: 'PNG, SVG, AI 파일 포함',
resourceType: 'file',
fileName: '로고 최종본.zip',
uploadedBy: '박서준',
createdAt: '2025-06-20',
},
{
id: 'resource-10',
workspaceId: 'side-workspace',
title: 'Fitto 피그마 파일',
description: '전체 UI/X 디자인',
resourceType: 'link',
linkProvider: 'figma',
url: 'http://figma.com',
uploadedBy: '박서준',
createdAt: '2025-06-30',
},
{
id: 'resource-11',
workspaceId: 'side-workspace',
title: 'Github Repository',
description: '메인 코드 저장소',
resourceType: 'link',
linkProvider: 'github',
url: 'https://github.com',
uploadedBy: '이하은',
createdAt: '2025-06-31',
},
Comment on lines +110 to +119

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

존재하지 않는 날짜 값 (2025-06-31).

resource-11createdAt'2025-06-31'인데, 6월은 30일까지만 존재합니다. sortResourceslocaleCompare로 문자열 비교만 하므로 크래시는 나지 않지만, 실제로는 유효하지 않은 날짜라 향후 Date 파싱이나 표시 로직에서 문제가 될 수 있습니다.

🛠️ 제안 수정
     resourceType: 'link',
     linkProvider: 'github',
     url: 'https://github.com',
     uploadedBy: '이하은',
-    createdAt: '2025-06-31',
+    createdAt: '2025-06-30',
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
id: 'resource-11',
workspaceId: 'side-workspace',
title: 'Github Repository',
description: '매인 코드 저장소',
resourceType: 'link',
linkProvider: 'github',
url: 'https://github.com',
uploadedBy: '이하은',
createdAt: '2025-06-31',
},
id: 'resource-11',
workspaceId: 'side-workspace',
title: 'Github Repository',
description: '매인 코드 저장소',
resourceType: 'link',
linkProvider: 'github',
url: 'https://github.com',
uploadedBy: '이하은',
createdAt: '2025-06-30',
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/entities/resource/model/mock-resources.ts` around lines 110 - 119, The
mock resource in mock-resources.ts uses an invalid createdAt date value for
resource-11, so update that fixture to a real calendar date. Keep the fix
localized to the resource-11 object in the mock data and ensure the createdAt
string remains in the same format as the other resource entries so functions
like sortResources and any future Date parsing continue to work with valid
input.

{
id: 'resource-12',
workspaceId: 'side-workspace',
title: 'API 명세서 v3.pdf',
description: 'REST API 전체 문서',
resourceType: 'file',
fileName: 'API 명세서 v3.pdf',
uploadedBy: '이하은',
createdAt: '2025-06-29',
},
{
id: 'resource-13',
workspaceId: 'side-workspace',
title: '기획서_Fitto_v2.pdf',
description: '서비스 기획 문서',
resourceType: 'file',
fileName: '기획서_Fitto_v2.pdf',
uploadedBy: '김지은',
createdAt: '2025-06-15',
},
];
26 changes: 26 additions & 0 deletions src/entities/resource/model/resource.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 자료실에서 파일과 외부 링크를 같은 목록으로 다루기 위한 타입입니다.
export type ResourceType = 'file' | 'link';

export type ResourceLinkProvider = 'link' | 'notion' | 'figma' | 'github';

export interface ResourceItem {
id: string;
workspaceId: string;
title: string;
description: string;
resourceType: ResourceType;
linkProvider?: ResourceLinkProvider;
url?: string;
fileName?: string;
uploadedBy: string;
createdAt: string;
}

export interface ResourceFormValues {
resourceType: ResourceType;
title: string;
description: string;
url: string;
fileName: string;
linkProvider: ResourceLinkProvider;
}
21 changes: 13 additions & 8 deletions src/entities/workspace/model/mock-workspace.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
// 워크스페이스 API 연결 전 사이드바에 표시하는 현재 워크스페이스 목업입니다.
// 워크스페이스 API 연결 전 사이드바와 purpose별 navigation 확인에 사용하는 목업입니다.
import { cache } from 'react';
import type { Workspace } from './workspace.types';

const mockWorkspacesById: Record<string, Workspace> = {
test: {
id: 'test',
'store-workspace': {
id: 'store-workspace',
name: '카페 그레이 운영',
purpose: 'store-operation',
},
'team-workspace': {
id: 'team-workspace',
name: '캡스톤 디자인 팀',
purpose: 'team-project',
},
'store-test': {
id: 'store-test',
name: '카페 그레이 운영',
purpose: 'store-operation',
'side-workspace': {
id: 'side-workspace',
name: 'Fitto 앱 개발팀',
purpose: 'side-project',
},
};

export const mockWorkspace: Workspace = mockWorkspacesById['store-test'];
export const mockWorkspace: Workspace = mockWorkspacesById['store-workspace'];

export const getMockWorkspaceById = cache(
(workspaceId: string): Workspace | null => mockWorkspacesById[workspaceId] ?? null,
Expand Down
4 changes: 4 additions & 0 deletions src/features/manage-resources/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// 자료실 관리 기능의 상태 훅과 UI 컴포넌트 공개 API입니다.
export { useResourceLibraryState } from './model/use-resource-library-state';
export { ResourceAddDialog } from './ui/ResourceAddDialog';
export { ResourceList } from './ui/ResourceList';
91 changes: 91 additions & 0 deletions src/features/manage-resources/model/use-resource-library-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use client';

// 자료실 목록과 파일/링크 추가 모달의 클라이언트 목업 상태를 관리합니다.
import { useMemo, useState } from 'react';
import type { ResourceFormValues, ResourceItem, ResourceType } from '@/entities/resource';

interface UseResourceLibraryStateParams {
initialResources: ResourceItem[];
workspaceId: string;
uploaderName: string;
}

function sortResources(resources: ResourceItem[]) {
return [...resources].sort((first, second) => second.createdAt.localeCompare(first.createdAt));
}

function createResourceId() {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return `resource-${crypto.randomUUID()}`;
}

return `resource-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
}

function createTodayLabel() {
return new Date().toISOString().slice(0, 10);
}

export function useResourceLibraryState({
initialResources,
workspaceId,
uploaderName,
}: UseResourceLibraryStateParams) {
const workspaceResources = useMemo(
() => sortResources(initialResources.filter((resource) => resource.workspaceId === workspaceId)),
[initialResources, workspaceId],
);
const [resourcesByWorkspaceId, setResourcesByWorkspaceId] = useState<
Record<string, ResourceItem[]>
>({});
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [dialogResourceType, setDialogResourceType] = useState<ResourceType>('file');
const resources = resourcesByWorkspaceId[workspaceId] ?? workspaceResources;

const addResource = (values: ResourceFormValues) => {
const title = values.title.trim() || values.fileName.trim() || values.url.trim();
const description = values.description.trim();

if (!title) {
return;
}

if (values.resourceType === 'link' && !values.url.trim()) {
return;
}

const nextResource: ResourceItem = {
id: createResourceId(),
workspaceId,
title,
description,
resourceType: values.resourceType,
linkProvider: values.resourceType === 'link' ? values.linkProvider : undefined,
url: values.resourceType === 'link' ? values.url.trim() : undefined,
fileName: values.resourceType === 'file' ? values.fileName.trim() : undefined,
uploadedBy: uploaderName,
createdAt: createTodayLabel(),
};

setResourcesByWorkspaceId((currentResourcesByWorkspaceId) => ({
...currentResourcesByWorkspaceId,
[workspaceId]: sortResources([
nextResource,
...(currentResourcesByWorkspaceId[workspaceId] ?? workspaceResources),
]),
}));
setIsDialogOpen(false);
};

return {
resources,
isDialogOpen,
dialogResourceType,
openDialog: (nextResourceType: ResourceType) => {
setDialogResourceType(nextResourceType);
setIsDialogOpen(true);
},
closeDialog: () => setIsDialogOpen(false),
addResource,
};
}
Loading