feat: complete Epic 1 — team management & permission system
- Story 1.1: Permission enum, config, AuthorizesPermissions & HasWorkspaceScope traits, member→worker migration - Story 1.2: Team page with member list, invitation system with queued email - Story 1.3: Role assignment (Manager/Worker) and member removal with activity logging - Story 1.4: Owner-only permission toggle matrix for Managers (manage team, view logs, configure portal) - Story 1.5: Role-based access enforcement — Workers see only assigned declarations/clients, sidebar scoping - Story 1.6: Workspace switcher dropdown for multi-workspace users with session-based switching - 83 new/modified files, 182 tests passing with zero regressions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,7 @@ const emit = defineEmits<{
|
||||
submit: [];
|
||||
}>();
|
||||
|
||||
const defaultRole = 'member';
|
||||
const defaultRole = 'worker';
|
||||
|
||||
function onUserToggle(userId: number, checked: boolean) {
|
||||
if (checked) {
|
||||
@@ -43,9 +43,14 @@ function onUserToggle(userId: number, checked: boolean) {
|
||||
props.form.user_ids = [...ids, userId];
|
||||
}
|
||||
const roles = props.form.user_roles ?? {};
|
||||
props.form.user_roles = { ...roles, [userId]: roles[userId] ?? defaultRole };
|
||||
props.form.user_roles = {
|
||||
...roles,
|
||||
[userId]: roles[userId] ?? defaultRole,
|
||||
};
|
||||
} else {
|
||||
props.form.user_ids = (props.form.user_ids ?? []).filter((id) => id !== userId);
|
||||
props.form.user_ids = (props.form.user_ids ?? []).filter(
|
||||
(id) => id !== userId,
|
||||
);
|
||||
const roles = { ...(props.form.user_roles ?? {}) };
|
||||
delete roles[userId];
|
||||
props.form.user_roles = roles;
|
||||
@@ -113,10 +118,15 @@ function getUserRole(userId: number): string {
|
||||
type="checkbox"
|
||||
:value="user.id"
|
||||
:checked="isUserSelected(user.id)"
|
||||
class="border-input size-4 shrink-0 rounded-[4px] border focus-visible:ring-2 focus-visible:ring-ring"
|
||||
@change="onUserToggle(user.id, ($event.target as HTMLInputElement).checked)"
|
||||
class="size-4 shrink-0 rounded-[4px] border border-input focus-visible:ring-2 focus-visible:ring-ring"
|
||||
@change="
|
||||
onUserToggle(
|
||||
user.id,
|
||||
($event.target as HTMLInputElement).checked,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<div class="min-w-0 flex-1 flex flex-col">
|
||||
<div class="flex min-w-0 flex-1 flex-col">
|
||||
<span class="text-sm font-medium">{{ user.name }}</span>
|
||||
<span class="text-xs text-muted-foreground">{{
|
||||
user.email
|
||||
@@ -125,7 +135,7 @@ function getUserRole(userId: number): string {
|
||||
<select
|
||||
:value="getUserRole(user.id)"
|
||||
:disabled="!isUserSelected(user.id)"
|
||||
class="border-input bg-background h-8 shrink-0 rounded-md border px-2 text-sm disabled:opacity-50"
|
||||
class="h-8 shrink-0 rounded-md border border-input bg-background px-2 text-sm disabled:opacity-50"
|
||||
@change="
|
||||
onRoleChange(
|
||||
user.id,
|
||||
|
||||
Reference in New Issue
Block a user