Merge pull request 'Role-Management-Overhaul' (#122) from Role-Management-Overhaul into main
All checks were successful
Continuous Integration / Update Development (push) Successful in 1m52s

Reviewed-on: #122
This commit was merged in pull request #122.
This commit is contained in:
2025-12-30 20:54:50 -06:00
15 changed files with 473 additions and 216 deletions

View File

@@ -9,7 +9,7 @@ import {
CardTitle,
} from '@/components/ui/card'
import { onMounted, ref, computed, reactive, watch } from 'vue';
import { addMemberToRole, createRole, deleteRole, getRoles, removeMemberFromRole, Role } from '@/api/roles';
import { addMemberToRole, createRole, deleteRole, getRoles, removeMemberFromRole } from '@/api/roles';
import Badge from '@/components/ui/badge/Badge.vue';
import {
Dialog,
@@ -34,8 +34,11 @@ import { Plus, X } from 'lucide-vue-next';
import Separator from '@/components/ui/separator/Separator.vue';
import Input from '@/components/ui/input/Input.vue';
import Label from '@/components/ui/label/Label.vue';
import { getMembers } from '@/api/member';
import { Member } from '@shared/types/member';
import { getAllLightMembers, getMembers } from '@/api/member';
import { Member, MemberLight } from '@shared/types/member';
import { Role } from '@shared/types/roles';
import RoleView from '@/components/roles/roleView.vue';
import { useRoute } from 'vue-router';
const roles = ref<Role[]>([])
const activeRole = ref<Role | null>(null)
@@ -43,16 +46,9 @@ const showDialog = ref(false);
const showCreateGroupDialog = ref(false);
const addingMember = ref(false);
const memberToAdd = ref<Member | null>(null);
const route = useRoute();
const allMembers = ref<Member[]>([])
const availableMembers = computed(() => {
if (!activeRole.value) return [];
return allMembers.value.filter(
member => !activeRole.value!.members.some(
roleMember => roleMember.member_id === member.member_id
)
);
})
const allMembers = ref<MemberLight[]>([])
type RoleDraft = {
name: string
@@ -117,141 +113,40 @@ async function handleCreateGroup() {
}
}
async function handleAddMember() {
//guard
if (memberToAdd.value == null)
return;
await addMemberToRole(memberToAdd.value.member_id, activeRole.value.id);
roles.value = await getRoles();
}
async function handleRemoveMember(memberId: number) {
removeMemberFromRole(memberId, activeRole.value.id);
roles.value = await getRoles();
}
async function handleDeleteRole() {
await deleteRole(activeRole.value.id);
}
onMounted(async () => {
roles.value = await getRoles();
allMembers.value = await getMembers();
allMembers.value = await getAllLightMembers();
})
</script>
<template>
<div>
<Dialog v-model:open="showDialog"
v-on:update:open="() => { showDialog = false; addingMember = false; memberToAdd = null; }">
<DialogContent>
<DialogHeader>
<DialogTitle class="flex justify-between items-center">{{ activeRole.name }}
<Badge class="mr-5">
{{ activeRole.color }}
</Badge>
</DialogTitle>
<DialogDescription>
{{ activeRole.description }}
</DialogDescription>
</DialogHeader>
<div class="mt-5">
<div class="flex justify-between items-center">
<p>Members with this role</p>
</div>
<Separator class="my-2"></Separator>
<ul class="flex flex-col gap-2">
<li v-for="member in activeRole.members" class="flex justify-between items-center">
<p>{{ member.member_name }}</p>
<X class="text-muted-foreground" @click="handleRemoveMember(member.member_id)"></X>
</li>
<div v-if="!addingMember" @click="addingMember = true"
class="flex gap-2 text-muted-foreground hover:text-primary hover:cursor-pointer">
<Plus :size="20"></Plus>Add Member
</div>
<div v-else class="flex gap-2">
<Combobox v-model="memberToAdd" by="value">
<ComboboxAnchor>
<div class="relative w-full max-w-sm items-center">
<ComboboxInput class="pl-9"
:display-value="(member: Member) => member?.member_name"
placeholder="Search Members" />
</div>
</ComboboxAnchor>
<ComboboxList>
<ComboboxEmpty>
No Members.
</ComboboxEmpty>
<ComboboxGroup>
<ComboboxItem v-for="member in availableMembers" :key="member.member_id"
:value="member">
{{ member.member_name }}
</ComboboxItem>
</ComboboxGroup>
</ComboboxList>
</Combobox>
<Button variant="secondary" @click="addingMember = false">Cancel</Button>
<Button @click="handleAddMember">Save</Button>
</div>
</ul>
<div class="max-w-7xl mx-auto w-full my-[0.2dvh] h-[0.8dvh]">
<div class="flex items-center justify-between">
<p class="scroll-m-20 text-2xl font-semibold tracking-tight my-5">Role Management</p>
</div>
<div class="grid grid-cols-[minmax(400px,280px)_1fr] gap-5">
<div class="w-full pr-5 border-r">
<div class="space-y-1">
<p class="px-3 py-2 text-xs font-medium text-muted-foreground uppercase">
Roles
</p>
<button v-for="r in roles" :key="r.id" @click="$router.push(`/administration/roles/${r.id}`)"
:class="[
'flex w-full items-center gap-3 rounded-md px-3 py-2 transition-colors cursor-pointer',
Number(route.params.id) == r.id
? 'bg-muted font-medium'
: 'hover:bg-muted/50'
]">
<span class="h-2 w-2 rounded-full shrink-0" :style="{ backgroundColor: r.color }" />
<span class="truncate">{{ r.name }}</span>
</button>
</div>
<DialogFooter>
<!-- <Button @click="handleDeleteRole">Delete Group</Button> -->
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog v-model:open="showCreateGroupDialog" v-on:update:open="() => { showCreateGroupDialog = false; }">
<DialogContent>
<DialogHeader>
<DialogTitle class="flex justify-between items-center">Create Group</DialogTitle>
<DialogDescription>Create a new group of members</DialogDescription>
</DialogHeader>
<form class="mt-5 space-y-5" @submit.prevent="handleCreateGroup">
<div>
<Label class="mb-2 block" for="group-name">Group Name</Label>
<Input id="group-name" v-model="roleDraft.name" :aria-invalid="!!draftErrors.name" />
<p v-if="draftErrors.name" class="text-destructive text-sm mt-1">{{ draftErrors.name }}</p>
</div>
<div>
<Label class="mb-2 block" for="group-desc">Description</Label>
<Input id="group-desc" v-model="roleDraft.description" />
</div>
<div>
<Label class="mb-2 block" for="group-color">Color</Label>
<Input id="group-color" type="color" v-model="roleDraft.color" />
</div>
<DialogFooter>
<Button type="button" variant="secondary" @click="showCreateGroupDialog = false"
:disabled="creating">
Cancel
</Button>
<Button type="submit" :disabled="creating">
{{ creating ? "Saving..." : "Save" }}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
<div class="max-w-5xl mx-auto">
<div class="flex items-center justify-between my-4">
<p>Groups</p>
<!-- <Button @click="showCreateGroupDialog = true">+ Add New Group</Button> -->
</div>
<div class="grid grid-cols-3 gap-5">
<Card v-for="value in roles" :key="value.id" @click="activeRole = value; showDialog = true"
class="cursor-pointer">
<CardHeader>
<CardTitle class="flex justify-between items-center">{{ value.name }}
<Badge>
{{ value.color }}
</Badge>
</CardTitle>
<CardDescription>{{ value.description }}</CardDescription>
</CardHeader>
<CardFooter>
<p class="text-muted-foreground">{{ value.members.length }} members</p>
</CardFooter>
</Card>
<div>
<RoleView :all-members="allMembers"></RoleView>
</div>
</div>
</div>