- SearchService (cross-resource keyword search across board/chat/todo/file/ event/meeting/milestone/note with type filter, project isolation) - DashboardService (project dashboard: in-progress todos, near-due(7d), latest chat/board/notes/files(5), next meeting, milestones w/ progress, recent activity(10); personal dashboard: my projects, incomplete todos, upcoming meetings, unread notifications, overdue tasks, recent activity) - CalendarRepository.findByProject - APIs: GET /api/projects/:projectId/search, GET /api/dashboard - Screens: complete personal dashboard, complete project overview, search page + SearchForm, ProjectNav 検索 link - Unit tests (SearchService, DashboardService) + e2e dashboard
79 lines
2.1 KiB
TypeScript
79 lines
2.1 KiB
TypeScript
const NAV_ITEMS = [
|
|
{ href: '', label: '概要' },
|
|
{ href: '/board', label: '掲示板' },
|
|
{ href: '/notes', label: 'メモ' },
|
|
{ href: '/chat', label: 'チャット' },
|
|
{ href: '/todos', label: 'ToDo' },
|
|
{ href: '/files', label: 'ファイル' },
|
|
{ href: '/calendar', label: 'カレンダー' },
|
|
{ href: '/milestones', label: 'マイルストーン' },
|
|
{ href: '/meetings', label: 'ミーティング' },
|
|
{ href: '/search', label: '検索' },
|
|
{ href: '/members', label: 'メンバー' },
|
|
{ href: '/activity', label: 'アクティビティ' },
|
|
{ href: '/settings', label: '設定' },
|
|
] as const;
|
|
|
|
/**
|
|
* プロジェクト内サブナビゲーション。
|
|
* href は `/projects/{projectId}` を起点とする相対パス。
|
|
*/
|
|
export function ProjectNav({
|
|
projectId,
|
|
active,
|
|
}: {
|
|
projectId: number;
|
|
active:
|
|
| 'overview'
|
|
| 'board'
|
|
| 'notes'
|
|
| 'chat'
|
|
| 'todos'
|
|
| 'files'
|
|
| 'calendar'
|
|
| 'milestones'
|
|
| 'meetings'
|
|
| 'search'
|
|
| 'members'
|
|
| 'activity'
|
|
| 'settings';
|
|
}) {
|
|
const activeMap: Record<string, boolean> = {
|
|
overview: active === 'overview',
|
|
board: active === 'board',
|
|
notes: active === 'notes',
|
|
chat: active === 'chat',
|
|
todos: active === 'todos',
|
|
files: active === 'files',
|
|
calendar: active === 'calendar',
|
|
milestones: active === 'milestones',
|
|
meetings: active === 'meetings',
|
|
search: active === 'search',
|
|
members: active === 'members',
|
|
activity: active === 'activity',
|
|
settings: active === 'settings',
|
|
};
|
|
|
|
return (
|
|
<nav className="flex gap-4 border-b bg-white px-6 py-2 text-sm">
|
|
{NAV_ITEMS.map((item) => {
|
|
const key = item.href === '' ? 'overview' : item.href.slice(1);
|
|
const href = `/projects/${projectId}${item.href}`;
|
|
return (
|
|
<a
|
|
key={item.href}
|
|
href={href}
|
|
className={
|
|
activeMap[key]
|
|
? 'font-medium text-blue-600'
|
|
: 'text-gray-600 hover:underline'
|
|
}
|
|
>
|
|
{item.label}
|
|
</a>
|
|
);
|
|
})}
|
|
</nav>
|
|
);
|
|
}
|