feat(m4): project & member management with tests and e2e

- ProjectRepository, ProjectMemberRepository, minimal NotificationRepository
- ProjectService: create/update/archive/delete project, add/remove member,
  permission checks (isMember/admin), member-added notification, getDashboard
  skeleton, getMyProjects, getMemberRole
- projectValidator, API services factory
- API routes: projects CRUD, members list/add/remove
- Screens: dashboard (project list + create form), project overview, members,
  settings; layout (Header/Sidebar/ProjectNav) and project components
- Unit tests (Project/ProjectMember repositories, ProjectService),
  integration project-member-permission, e2e project-management
This commit is contained in:
Ken Yasue
2026-06-25 01:02:10 +02:00
parent 8a67523c0e
commit 9eb391ea8d
30 changed files with 2230 additions and 14 deletions

View File

@ -0,0 +1,45 @@
const NAV_ITEMS = [
{ href: '', label: '概要' },
{ href: '/members', label: 'メンバー' },
{ href: '/settings', label: '設定' },
] as const;
/**
* プロジェクト内サブナビゲーション。
* href は `/projects/{projectId}` を起点とする相対パス。
*/
export function ProjectNav({
projectId,
active,
}: {
projectId: number;
active: 'overview' | 'members' | 'settings';
}) {
const activeMap: Record<string, boolean> = {
overview: active === 'overview',
members: active === 'members',
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>
);
}