# 🚀 前端开发快速启动指南 - Day 18

**日期**: 2025-11-05
**状态**: ✅ 后端 API 就绪，前端可以立即开始开发
**预计工作量**: 16-22 小时（2-3 天）

---

## 📋 前提条件检查清单

在开始开发前，确保以下条件已满足：

- [ ] **后端 API 正在运行**
  ```bash
  # 如果未运行，执行：
  cd colaflow-api/src/ColaFlow.Api
  dotnet run
  ```

- [ ] **可以访问 Scalar UI**
  打开浏览器：http://localhost:5167/scalar/v1

- [ ] **已阅读 API 文档**
  位置：`docs/api/FRONTEND_HANDOFF_DAY16.md`

- [ ] **前端项目可以运行**
  ```bash
  cd colaflow-web
  npm install
  npm run dev
  ```

---

## 🎯 Day 18 开发目标

**核心目标**: 完成 ProjectManagement API 集成，替换旧的 Issue Management API

**必须完成的功能** (P0):
1. ✅ Projects 列表和详情页面
2. ✅ Epics 列表和详情页面
3. ✅ Stories 列表和详情页面
4. ✅ Tasks 列表和详情页面
5. ✅ 更新 Kanban Board 使用新 API

**可选功能** (P1):
- Sprint 管理基础功能
- User 管理界面
- SignalR 实时更新

---

## 🚀 快速开始（5分钟）

### Step 1: 验证后端 API

打开浏览器访问：http://localhost:5167/scalar/v1

你应该看到 Scalar API 文档界面，包含以下模块：
- 🔐 Authentication
- 📦 ProjectManagement
- 👤 Identity & Tenants
- 📡 Real-time (SignalR)

### Step 2: 测试 API（使用 Scalar UI）

1. 点击 **"Authorize"** 按钮
2. 获取 JWT token（从登录接口或使用测试 token）
3. 输入：`Bearer <your-token>`
4. 测试几个端点：
   - `GET /api/v1/projects` - 获取项目列表
   - `GET /api/v1/epics` - 获取 Epic 列表

### Step 3: 生成 TypeScript 类型（推荐）

```bash
cd colaflow-web

# 安装类型生成工具
npm install --save-dev openapi-typescript

# 生成类型
npx openapi-typescript http://localhost:5167/openapi/v1.json --output ./src/types/api.ts

# 查看生成的类型
cat src/types/api.ts | head -50
```

---

## 📚 关键文档位置

| 文档 | 位置 | 用途 |
|------|------|------|
| **API 完整参考** | `docs/api/ProjectManagement-API-Reference.md` | 所有端点详细说明 |
| **API 端点清单** | `docs/api/API-Endpoints-Summary.md` | 快速查找端点 |
| **前端集成指南** | `docs/api/FRONTEND_HANDOFF_DAY16.md` | 代码示例和最佳实践 |
| **OpenAPI Spec** | `docs/api/openapi.json` | 标准 OpenAPI 3.0 规范 |
| **Scalar UI** | http://localhost:5167/scalar/v1 | 交互式 API 文档 |

---

## 🔧 开发工作流

### Phase 1: API Client 设置（1-2小时）

#### 1.1 创建 API Client 基础配置

**文件**: `colaflow-web/lib/api/client.ts`

```typescript
import axios, { AxiosInstance } from 'axios';

const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5167';

class ApiClient {
  private client: AxiosInstance;

  constructor() {
    this.client = axios.create({
      baseURL: API_BASE_URL,
      headers: {
        'Content-Type': 'application/json',
      },
    });

    // 请求拦截器 - 添加 JWT token
    this.client.interceptors.request.use((config) => {
      const token = localStorage.getItem('jwt_token');
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    });

    // 响应拦截器 - 处理错误
    this.client.interceptors.response.use(
      (response) => response,
      (error) => {
        if (error.response?.status === 401) {
          // Token 过期，跳转到登录页
          localStorage.removeItem('jwt_token');
          window.location.href = '/login';
        }
        return Promise.reject(error);
      }
    );
  }

  public get<T>(url: string, params?: any) {
    return this.client.get<T>(url, { params });
  }

  public post<T>(url: string, data?: any) {
    return this.client.post<T>(url, data);
  }

  public put<T>(url: string, data?: any) {
    return this.client.put<T>(url, data);
  }

  public delete<T>(url: string) {
    return this.client.delete<T>(url);
  }
}

export const apiClient = new ApiClient();
```

#### 1.2 创建 ProjectManagement API 模块

**文件**: `colaflow-web/lib/api/pm.ts`

```typescript
import { apiClient } from './client';

// Types (可以从 openapi-typescript 生成的文件导入)
export interface Project {
  id: string;
  name: string;
  key: string;
  description?: string;
  tenantId: string;
  createdAt: string;
  updatedAt: string;
}

export interface Epic {
  id: string;
  title: string;
  description?: string;
  projectId: string;
  status: 'Backlog' | 'Todo' | 'InProgress' | 'Done';
  priority: 'Low' | 'Medium' | 'High' | 'Critical';
  estimatedHours?: number;
  actualHours?: number;
  assigneeId?: string;
  tenantId: string;
  createdAt: string;
  updatedAt: string;
}

export interface Story {
  id: string;
  title: string;
  description?: string;
  epicId: string;
  projectId: string;
  status: 'Backlog' | 'Todo' | 'InProgress' | 'Done';
  priority: 'Low' | 'Medium' | 'High' | 'Critical';
  estimatedHours?: number;
  actualHours?: number;
  assigneeId?: string;
  tenantId: string;
  createdAt: string;
  updatedAt: string;
}

export interface Task {
  id: string;
  title: string;
  description?: string;
  storyId: string;
  projectId: string;
  status: 'Backlog' | 'Todo' | 'InProgress' | 'Done';
  priority: 'Low' | 'Medium' | 'High' | 'Critical';
  estimatedHours?: number;
  actualHours?: number;
  assigneeId?: string;
  tenantId: string;
  createdAt: string;
  updatedAt: string;
}

// API 方法
export const projectsApi = {
  list: () => apiClient.get<Project[]>('/api/v1/projects'),
  get: (id: string) => apiClient.get<Project>(`/api/v1/projects/${id}`),
  create: (data: { name: string; key: string; description?: string }) =>
    apiClient.post<Project>('/api/v1/projects', data),
  update: (id: string, data: { name: string; key: string; description?: string }) =>
    apiClient.put<Project>(`/api/v1/projects/${id}`, data),
  delete: (id: string) => apiClient.delete(`/api/v1/projects/${id}`),
};

export const epicsApi = {
  list: (projectId?: string) =>
    apiClient.get<Epic[]>('/api/v1/epics', { projectId }),
  get: (id: string) => apiClient.get<Epic>(`/api/v1/epics/${id}`),
  create: (data: {
    projectId: string;
    title: string;
    description?: string;
    priority: Epic['priority'];
    estimatedHours?: number;
  }) => apiClient.post<Epic>('/api/v1/epics', data),
  update: (id: string, data: Partial<Epic>) =>
    apiClient.put<Epic>(`/api/v1/epics/${id}`, data),
  changeStatus: (id: string, status: Epic['status']) =>
    apiClient.put<Epic>(`/api/v1/epics/${id}/status`, { status }),
  assign: (id: string, assigneeId: string) =>
    apiClient.put<Epic>(`/api/v1/epics/${id}/assign`, { assigneeId }),
};

export const storiesApi = {
  list: (epicId?: string) =>
    apiClient.get<Story[]>('/api/v1/stories', { epicId }),
  get: (id: string) => apiClient.get<Story>(`/api/v1/stories/${id}`),
  create: (data: {
    epicId: string;
    title: string;
    description?: string;
    priority: Story['priority'];
    estimatedHours?: number;
  }) => apiClient.post<Story>('/api/v1/stories', data),
  update: (id: string, data: Partial<Story>) =>
    apiClient.put<Story>(`/api/v1/stories/${id}`, data),
  assign: (id: string, assigneeId: string) =>
    apiClient.put<Story>(`/api/v1/stories/${id}/assign`, { assigneeId }),
};

export const tasksApi = {
  list: (storyId?: string) =>
    apiClient.get<Task[]>('/api/v1/tasks', { storyId }),
  get: (id: string) => apiClient.get<Task>(`/api/v1/tasks/${id}`),
  create: (data: {
    storyId: string;
    title: string;
    description?: string;
    priority: Task['priority'];
    estimatedHours?: number;
  }) => apiClient.post<Task>('/api/v1/tasks', data),
  update: (id: string, data: Partial<Task>) =>
    apiClient.put<Task>(`/api/v1/tasks/${id}`, data),
  changeStatus: (id: string, status: Task['status']) =>
    apiClient.put<Task>(`/api/v1/tasks/${id}/status`, { status }),
  assign: (id: string, assigneeId: string) =>
    apiClient.put<Task>(`/api/v1/tasks/${id}/assign`, { assigneeId }),
};
```

#### 1.3 创建 React Query Hooks

**文件**: `colaflow-web/lib/hooks/use-projects.ts`

```typescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { projectsApi, Project } from '@/lib/api/pm';
import { toast } from 'sonner';

export function useProjects() {
  return useQuery({
    queryKey: ['projects'],
    queryFn: async () => {
      const response = await projectsApi.list();
      return response.data;
    },
  });
}

export function useProject(id: string) {
  return useQuery({
    queryKey: ['projects', id],
    queryFn: async () => {
      const response = await projectsApi.get(id);
      return response.data;
    },
    enabled: !!id,
  });
}

export function useCreateProject() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: { name: string; key: string; description?: string }) =>
      projectsApi.create(data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] });
      toast.success('Project created successfully!');
    },
    onError: (error: any) => {
      toast.error(error.response?.data?.detail || 'Failed to create project');
    },
  });
}

export function useUpdateProject() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({ id, data }: { id: string; data: Partial<Project> }) =>
      projectsApi.update(id, data),
    onSuccess: (_, variables) => {
      queryClient.invalidateQueries({ queryKey: ['projects'] });
      queryClient.invalidateQueries({ queryKey: ['projects', variables.id] });
      toast.success('Project updated successfully!');
    },
    onError: (error: any) => {
      toast.error(error.response?.data?.detail || 'Failed to update project');
    },
  });
}

export function useDeleteProject() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (id: string) => projectsApi.delete(id),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] });
      toast.success('Project deleted successfully!');
    },
    onError: (error: any) => {
      toast.error(error.response?.data?.detail || 'Failed to delete project');
    },
  });
}
```

**类似地创建**:
- `use-epics.ts`
- `use-stories.ts`
- `use-tasks.ts`

---

### Phase 2: Projects UI（3-4小时）

#### 2.1 Projects 列表页面

**文件**: `colaflow-web/app/(dashboard)/projects/page.tsx`

```typescript
'use client';

import { useProjects, useDeleteProject } from '@/lib/hooks/use-projects';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import Link from 'next/link';
import { PlusIcon, TrashIcon } from 'lucide-react';

export default function ProjectsPage() {
  const { data: projects, isLoading, error } = useProjects();
  const deleteProject = useDeleteProject();

  if (isLoading) {
    return (
      <div className="space-y-4">
        <Skeleton className="h-24 w-full" />
        <Skeleton className="h-24 w-full" />
        <Skeleton className="h-24 w-full" />
      </div>
    );
  }

  if (error) {
    return (
      <div className="text-center text-red-500">
        Error loading projects: {error.message}
      </div>
    );
  }

  return (
    <div className="container py-6">
      <div className="flex justify-between items-center mb-6">
        <h1 className="text-3xl font-bold">Projects</h1>
        <Link href="/projects/new">
          <Button>
            <PlusIcon className="mr-2 h-4 w-4" />
            New Project
          </Button>
        </Link>
      </div>

      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
        {projects?.map((project) => (
          <Card key={project.id} className="p-6 hover:shadow-lg transition">
            <Link href={`/projects/${project.id}`}>
              <h3 className="text-xl font-semibold mb-2">{project.name}</h3>
              <p className="text-sm text-muted-foreground mb-2">{project.key}</p>
              {project.description && (
                <p className="text-sm text-gray-600 mb-4">
                  {project.description}
                </p>
              )}
            </Link>
            <div className="flex justify-end">
              <Button
                variant="destructive"
                size="sm"
                onClick={() => {
                  if (confirm('Are you sure you want to delete this project?')) {
                    deleteProject.mutate(project.id);
                  }
                }}
              >
                <TrashIcon className="h-4 w-4" />
              </Button>
            </div>
          </Card>
        ))}
      </div>

      {projects?.length === 0 && (
        <div className="text-center py-12">
          <p className="text-muted-foreground">No projects yet. Create your first project!</p>
        </div>
      )}
    </div>
  );
}
```

#### 2.2 Project 详情页面

**文件**: `colaflow-web/app/(dashboard)/projects/[id]/page.tsx`

```typescript
'use client';

import { useProject } from '@/lib/hooks/use-projects';
import { useEpics } from '@/lib/hooks/use-epics';
import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import Link from 'next/link';

export default function ProjectDetailPage({ params }: { params: { id: string } }) {
  const { data: project, isLoading: projectLoading } = useProject(params.id);
  const { data: epics, isLoading: epicsLoading } = useEpics(params.id);

  if (projectLoading) return <div>Loading project...</div>;
  if (!project) return <div>Project not found</div>;

  return (
    <div className="container py-6">
      <div className="flex justify-between items-center mb-6">
        <div>
          <h1 className="text-3xl font-bold">{project.name}</h1>
          <p className="text-muted-foreground">{project.key}</p>
        </div>
        <Button asChild>
          <Link href={`/projects/${params.id}/epics/new`}>
            New Epic
          </Link>
        </Button>
      </div>

      {project.description && (
        <p className="mb-6 text-gray-600">{project.description}</p>
      )}

      <Tabs defaultValue="epics">
        <TabsList>
          <TabsTrigger value="epics">Epics</TabsTrigger>
          <TabsTrigger value="kanban">Kanban Board</TabsTrigger>
          <TabsTrigger value="settings">Settings</TabsTrigger>
        </TabsList>

        <TabsContent value="epics" className="mt-6">
          {epicsLoading ? (
            <div>Loading epics...</div>
          ) : (
            <div className="space-y-4">
              {epics?.map((epic) => (
                <Card key={epic.id} className="p-4">
                  <Link href={`/projects/${params.id}/epics/${epic.id}`}>
                    <h3 className="font-semibold">{epic.title}</h3>
                    <p className="text-sm text-muted-foreground">{epic.description}</p>
                    <div className="flex gap-2 mt-2">
                      <Badge>{epic.status}</Badge>
                      <Badge variant="outline">{epic.priority}</Badge>
                    </div>
                  </Link>
                </Card>
              ))}
            </div>
          )}
        </TabsContent>

        <TabsContent value="kanban">
          <Link href={`/projects/${params.id}/kanban`}>
            <Button>Open Kanban Board</Button>
          </Link>
        </TabsContent>

        <TabsContent value="settings">
          <div>Project settings coming soon...</div>
        </TabsContent>
      </Tabs>
    </div>
  );
}
```

---

### Phase 3: Epics/Stories/Tasks UI（4-5小时）

按照类似的模式实现：
- Epic 列表和详情页
- Story 列表和详情页
- Task 列表和详情页

**参考 Phase 2 的代码结构**。

---

### Phase 4: 更新 Kanban Board（5-6小时）

#### 4.1 更新 Kanban Board 使用新 API

**文件**: `colaflow-web/app/(dashboard)/projects/[id]/kanban/page.tsx`

```typescript
'use client';

import { useEpics } from '@/lib/hooks/use-epics';
import { useStories } from '@/lib/hooks/use-stories';
import { useTasks } from '@/lib/hooks/use-tasks';
import { KanbanBoard } from '@/components/kanban/KanbanBoard';

export default function KanbanPage({ params }: { params: { id: string } }) {
  // 获取项目的所有 epics, stories, tasks
  const { data: epics } = useEpics(params.id);
  const { data: stories } = useStories(); // 可能需要按 project 过滤
  const { data: tasks } = useTasks();

  // 将数据转换为 Kanban Board 需要的格式
  const kanbanData = useMemo(() => {
    // 合并 epics, stories, tasks 到统一的工作项列表
    const workItems = [
      ...(epics || []).map(epic => ({ ...epic, type: 'epic' as const })),
      ...(stories || []).map(story => ({ ...story, type: 'story' as const })),
      ...(tasks || []).map(task => ({ ...task, type: 'task' as const })),
    ];

    // 按状态分组
    return {
      Backlog: workItems.filter(item => item.status === 'Backlog'),
      Todo: workItems.filter(item => item.status === 'Todo'),
      InProgress: workItems.filter(item => item.status === 'InProgress'),
      Done: workItems.filter(item => item.status === 'Done'),
    };
  }, [epics, stories, tasks]);

  return (
    <div className="container py-6">
      <h1 className="text-3xl font-bold mb-6">Kanban Board</h1>
      <KanbanBoard data={kanbanData} projectId={params.id} />
    </div>
  );
}
```

---

## 🧪 测试清单

在提交代码前，请确保以下测试通过：

### 基础功能测试
- [ ] Projects 列表加载成功
- [ ] 创建新项目
- [ ] 编辑项目
- [ ] 删除项目
- [ ] 查看项目详情

### Epics/Stories/Tasks 测试
- [ ] 创建 Epic
- [ ] 创建 Story（在 Epic 下）
- [ ] 创建 Task（在 Story 下）
- [ ] 更新状态（Backlog → Todo → InProgress → Done）
- [ ] 分配任务给用户

### Kanban Board 测试
- [ ] 加载 Kanban Board
- [ ] 拖拽卡片更改状态
- [ ] 显示 Epic/Story/Task 层级关系
- [ ] 显示工时信息

### 错误处理测试
- [ ] 401 Unauthorized - 跳转到登录页
- [ ] 404 Not Found - 显示友好错误消息
- [ ] 网络错误 - 显示错误提示

---

## 🐛 常见问题与解决方案

### 问题 1: CORS 错误

**症状**: `Access-Control-Allow-Origin` 错误

**解决方案**:
```typescript
// 确保 API 已配置 CORS（后端已配置）
// 前端无需额外处理
```

### 问题 2: 401 Unauthorized

**症状**: 所有请求返回 401

**解决方案**:
```typescript
// 检查 JWT token 是否正确设置
const token = localStorage.getItem('jwt_token');
console.log('Token:', token);

// 检查 token 格式
// 应该是: Bearer <token>
```

### 问题 3: 404 Not Found（但资源存在）

**症状**: 可以看到资源，但 API 返回 404

**原因**: 多租户隔离 - 资源属于其他租户

**解决方案**:
```typescript
// 确保 JWT token 包含正确的 tenant_id
// 检查 JWT payload:
const payload = JSON.parse(atob(token.split('.')[1]));
console.log('Tenant ID:', payload.tenant_id);
```

### 问题 4: TypeScript 类型错误

**症状**: `Property 'xxx' does not exist on type`

**解决方案**:
```bash
# 重新生成类型
npx openapi-typescript http://localhost:5167/openapi/v1.json --output ./src/types/api.ts

# 或者手动定义类型
# 参考 docs/api/ProjectManagement-API-Reference.md 中的 Data Models
```

---

## 📞 获取帮助

### 文档资源
- **API 文档**: `docs/api/ProjectManagement-API-Reference.md`
- **Scalar UI**: http://localhost:5167/scalar/v1
- **交接指南**: `docs/api/FRONTEND_HANDOFF_DAY16.md`

### 后端团队联系
- 如果遇到 API 问题，请查看后端日志
- 如果需要新的 API 端点，请联系后端团队

### 测试 Token
```
# 使用 Scalar UI 的 "Try It" 功能测试 API
# 或使用 curl:
curl -H "Authorization: Bearer <token>" http://localhost:5167/api/v1/projects
```

---

## ✅ 完成标准

Day 18 结束时，应该完成：

1. ✅ **API 集成**
   - Projects CRUD 完成
   - Epics CRUD 完成
   - Stories CRUD 完成
   - Tasks CRUD 完成

2. ✅ **UI 实现**
   - 项目列表页
   - 项目详情页
   - Epic/Story/Task 列表页
   - Kanban Board 更新

3. ✅ **测试验证**
   - 所有基础功能测试通过
   - 错误处理正确
   - 多租户隔离验证

4. ✅ **代码质量**
   - TypeScript 类型安全
   - React Query 缓存优化
   - 用户体验流畅

---

## 🎉 开始开发吧！

**记住**:
- 🚀 后端 API 已就绪（95% production ready）
- 📚 完整文档可用（6,000+ 行）
- 🛡️ 多租户安全已验证（100%）
- ✅ 所有测试通过（39/39）

**你已经拥有了所有需要的资源，开始编码吧！** 💪

---

**Last Updated**: 2025-11-05 (Day 16)
**Status**: ✅ Frontend Development Ready
**Estimated Time**: 16-22 hours (2-3 days)
