feat: add teams/groups with dashboard scoping

Schema:
- teams table (name unique, description)
- team_members table (team_id, user_id, unique constraint)
- team_id column on dashboards

API:
- GET/POST/PATCH/DELETE /teams
- POST /teams/:id/members (add user)
- DELETE /teams/:id/members/:userId (remove user)
- dashboards support team_id on create/update

Frontend:
- Teams tab in admin: CRUD + member management with add/remove
- Sidebar: dashboards filtered to user's teams
  (unassigned dashboards visible to all)
- Compact dashboard picker dropdown in sidebar

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Gjermund Høsøien Wiggen
2026-06-09 13:32:39 +02:00
parent c79cd183d4
commit 3616046b78
11 changed files with 1713 additions and 25 deletions

View File

@@ -4,6 +4,7 @@ import type {
Dashboard,
DashboardWidget,
WidgetData,
Team,
User,
Transaction,
SavedView,
@@ -335,3 +336,27 @@ export async function deleteWidget(dashboardId: string, widgetId: string): Promi
export async function getWidgetData(dashboardId: string, widgetId: string): Promise<{ data: WidgetData | null; error: string | null }> {
return request<WidgetData>(`/dashboards/${dashboardId}/widgets/${widgetId}/data`);
}
export async function getTeams(): Promise<{ data: Team[] | null; error: string | null }> {
return request<Team[]>("/teams");
}
export async function createTeam(data: { name: string; description?: string | null }): Promise<{ data: Team | null; error: string | null }> {
return request<Team>("/teams", { method: "POST", body: JSON.stringify(data) });
}
export async function updateTeam(id: string, data: { name?: string; description?: string | null }): Promise<{ data: Team | null; error: string | null }> {
return request<Team>(`/teams/${id}`, { method: "PATCH", body: JSON.stringify(data) });
}
export async function deleteTeam(id: string): Promise<{ data: { ok: boolean } | null; error: string | null }> {
return request<{ ok: boolean }>(`/teams/${id}`, { method: "DELETE" });
}
export async function addTeamMember(teamId: string, userId: string): Promise<{ data: unknown | null; error: string | null }> {
return request<unknown>(`/teams/${teamId}/members`, { method: "POST", body: JSON.stringify({ user_id: userId }) });
}
export async function removeTeamMember(teamId: string, userId: string): Promise<{ data: { ok: boolean } | null; error: string | null }> {
return request<{ ok: boolean }>(`/teams/${teamId}/members/${userId}`, { method: "DELETE" });
}