Files
neo/frontend/pages/setup/users/[id].vue
2026-01-05 07:48:22 +01:00

228 lines
7.5 KiB
Vue

<template>
<div class="min-h-screen bg-background">
<NuxtLayout name="default">
<main class="container mx-auto px-4 py-8">
<div class="flex items-center justify-between">
<div>
<Button variant="ghost" size="sm" @click="navigateTo('/setup/users')" class="mb-2">
Back to Users
</Button>
<h1 class="text-3xl font-bold">{{ getUserName(user) }}</h1>
<p class="text-muted-foreground">{{ user?.email }}</p>
</div>
</div>
<div v-if="loading" class="flex items-center justify-center py-12">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
<Tabs v-else default-value="details" class="w-full">
<TabsList>
<TabsTrigger value="details">Details</TabsTrigger>
<TabsTrigger value="roles">Roles</TabsTrigger>
</TabsList>
<TabsContent value="details" class="mt-6">
<Card>
<CardHeader>
<CardTitle>User Information</CardTitle>
</CardHeader>
<CardContent class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<Label class="text-muted-foreground">Email</Label>
<p class="font-medium">{{ user?.email }}</p>
</div>
<div>
<Label class="text-muted-foreground">First Name</Label>
<p class="font-medium">{{ user?.firstName || 'N/A' }}</p>
</div>
<div>
<Label class="text-muted-foreground">Last Name</Label>
<p class="font-medium">{{ user?.lastName || 'N/A' }}</p>
</div>
<div>
<Label class="text-muted-foreground">Created At</Label>
<p class="font-medium">{{ formatDate(user?.createdAt) }}</p>
</div>
<div>
<Label class="text-muted-foreground">Updated At</Label>
<p class="font-medium">{{ formatDate(user?.updatedAt) }}</p>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="roles" class="mt-6">
<Card>
<CardHeader>
<div class="flex items-center justify-between">
<div>
<CardTitle>Assigned Roles</CardTitle>
<CardDescription>Manage role assignments for this user</CardDescription>
</div>
<Button @click="showAddRoleDialog = true" size="sm">
<Plus class="mr-2 h-4 w-4" />
Add Role
</Button>
</div>
</CardHeader>
<CardContent>
<div v-if="userRoles.length === 0" class="text-center py-8 text-muted-foreground">
No roles assigned. Add roles to grant permissions.
</div>
<div v-else class="space-y-2">
<div
v-for="role in userRoles"
:key="role.id"
class="flex items-center justify-between p-3 border rounded-lg"
>
<div>
<p class="font-medium">{{ role.name }}</p>
<p class="text-sm text-muted-foreground">{{ role.description || 'No description' }}</p>
</div>
<Button variant="ghost" size="sm" @click="removeRole(role.id)">
<X class="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
<!-- Add Role Dialog -->
<Dialog v-model:open="showAddRoleDialog">
<DialogContent>
<DialogHeader>
<DialogTitle>Add Role</DialogTitle>
<DialogDescription>
Select a role to assign to this user
</DialogDescription>
</DialogHeader>
<div class="space-y-4">
<div class="space-y-2">
<Label>Available Roles</Label>
<Select v-model="selectedRoleId" @update:model-value="(value) => selectedRoleId = value">
<SelectTrigger>
<SelectValue placeholder="Choose a role" />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="role in availableRoles" :key="role.id" :value="role.id">
{{ role.name }}
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button variant="outline" @click="showAddRoleDialog = false">Cancel</Button>
<Button @click="addRole" :disabled="!selectedRoleId">
Add Role
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</main>
</NuxtLayout>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '~/components/ui/card';
import { Button } from '~/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '~/components/ui/tabs';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '~/components/ui/dialog';
import { Label } from '~/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '~/components/ui/select';
import { Plus, X } from 'lucide-vue-next';
const route = useRoute();
const { api } = useApi();
const { toast } = useToast();
const loading = ref(true);
const user = ref<any>(null);
const userRoles = ref<any[]>([]);
const allRoles = ref<any[]>([]);
const showAddRoleDialog = ref(false);
const selectedRoleId = ref('');
const availableRoles = computed(() => {
const assignedIds = new Set(userRoles.value.map(r => r.id));
return allRoles.value.filter(r => !assignedIds.has(r.id));
});
const loadUser = async () => {
try {
loading.value = true;
const userId = route.params.id;
const response = await api.get(`/setup/users/${userId}`);
user.value = response;
userRoles.value = response.roles || [];
} catch (error: any) {
console.error('Failed to load user:', error);
toast.error('Failed to load user');
} finally {
loading.value = false;
}
};
const loadAllRoles = async () => {
try {
const response = await api.get('/setup/roles');
allRoles.value = response || [];
} catch (error: any) {
console.error('Failed to load roles:', error);
}
};
const addRole = async () => {
if (!selectedRoleId.value) return;
try {
await api.post(`/setup/users/${route.params.id}/roles`, {
roleId: selectedRoleId.value,
});
toast.success('Role added successfully');
showAddRoleDialog.value = false;
selectedRoleId.value = '';
await loadUser();
} catch (error: any) {
console.error('Failed to add role:', error);
toast.error(error.message || 'Failed to add role');
}
};
const removeRole = async (roleId: string) => {
try {
await api.delete(`/setup/users/${route.params.id}/roles/${roleId}`);
toast.success('Role removed successfully');
await loadUser();
} catch (error: any) {
console.error('Failed to remove role:', error);
toast.error(error.message || 'Failed to remove role');
}
};
const getUserName = (user: any) => {
if (!user) return 'User';
if (user.firstName || user.lastName) {
return [user.firstName, user.lastName].filter(Boolean).join(' ');
}
return user.email || 'User';
};
const formatDate = (date: string) => {
if (!date) return 'N/A';
return new Date(date).toLocaleDateString();
};
onMounted(async () => {
await Promise.all([loadUser(), loadAllRoles()]);
});
</script>