hooked up add and remove member

This commit is contained in:
2025-12-18 16:55:05 -05:00
parent 1db75ee773
commit e01e742c07
3 changed files with 126 additions and 25 deletions

View File

@@ -0,0 +1,103 @@
<script setup lang="ts">
import { addMemberToRole, removeMemberFromRole } from '@/api/roles';
import { MemberLight } from '@shared/types/member';
import { Role } from '@shared/types/roles';
import { computed, ref } from 'vue';
import Dialog from '../ui/dialog/Dialog.vue'
import DialogContent from '../ui/dialog/DialogContent.vue'
import DialogHeader from '../ui/dialog/DialogHeader.vue'
import DialogTitle from '../ui/dialog/DialogTitle.vue'
import DialogDescription from '../ui/dialog/DialogDescription.vue'
import DialogFooter from '../ui/dialog/DialogFooter.vue'
import Button from '../ui/button/Button.vue';
import InputGroup from '../ui/input-group/InputGroup.vue';
import InputGroupAddon from '../ui/input-group/InputGroupAddon.vue';
import { SearchIcon } from 'lucide-vue-next';
const props = defineProps<{
allMembers: MemberLight[],
role: Role
}>()
const emit = defineEmits(['submit'])
const showAddMemberDialog = ref(false)
const memberToAdd = ref<MemberLight | null>(null)
const searchQuery = ref('')
const filteredMembers = computed(() => {
const q = searchQuery.value.trim().toLowerCase()
if (!q) return props.allMembers
return props.allMembers.filter(m =>
m.displayName?.toLowerCase().includes(q) ||
m.username?.toLowerCase().includes(q)
)
})
defineExpose({
openDialog,
})
function openDialog() {
showAddMemberDialog.value = true;
}
async function handleAddMember() {
//guard
if (memberToAdd.value == null)
return;
await addMemberToRole(memberToAdd.value.id, props.role.id);
emit('submit');
showAddMemberDialog.value = false;
}
</script>
<template>
<Dialog v-model:open="showAddMemberDialog">
<DialogContent class="sm:max-w-md">
<DialogHeader>
<DialogTitle>Add member to {{ props.role?.name }}</DialogTitle>
<DialogDescription>
Search for a member to add to this group.
</DialogDescription>
</DialogHeader>
<!-- Search -->
<InputGroup>
<InputGroupAddon>
<SearchIcon class="h-4 w-4 text-muted-foreground" />
</InputGroupAddon>
<input v-model="searchQuery" type="text" placeholder="Search members…"
class="flex-1 bg-transparent outline-none text-sm" />
</InputGroup>
<!-- Results -->
<div class="mt-3 h-64 overflow-y-auto scrollbar-themed rounded-md border">
<ul class="divide-y">
<li v-for="member in filteredMembers" :key="member.id" @click="memberToAdd = member"
class="flex items-center justify-between px-3 py-2 cursor-pointer hover:bg-muted"
:class="memberToAdd?.id === member.id && 'bg-muted'">
<span>{{ member.displayName || member.username }}</span>
<span v-if="memberToAdd?.id === member.id" class="text-xs text-muted-foreground">
Selected
</span>
</li>
</ul>
</div>
<DialogFooter>
<Button variant="secondary" @click="showAddMemberDialog = false">
Cancel
</Button>
<Button :disabled="!memberToAdd" @click="handleAddMember">
Add
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { getRoleDetails, getRoleMembers } from '@/api/roles'
import { addMemberToRole, getRoleDetails, getRoleMembers, removeMemberFromRole } from '@/api/roles'
import type { MemberLight } from '@shared/types/member'
import type { Role } from '@shared/types/roles'
import { computed, onMounted, ref, watch } from 'vue'
@@ -11,6 +11,8 @@ import MemberCard from '../members/MemberCard.vue'
import InputGroup from '../ui/input-group/InputGroup.vue'
import InputGroupAddon from '../ui/input-group/InputGroupAddon.vue'
import AddMember from './addMember.vue'
const route = useRoute()
const roleData = ref<Role | null>(null)
@@ -18,9 +20,7 @@ const roleMembers = ref<MemberLight[]>([])
const loading = ref(true)
async function loadRole() {
loading.value = true
const id = Number(route.params.id)
console.log(id);
roleData.value = await getRoleDetails(id)
roleMembers.value = await getRoleMembers(id)
@@ -37,21 +37,30 @@ const roleMembersFiltered = computed(() => {
)
})
// const availableMembers = computed(() => {
// if (!activeRole.value) return [];
// return allMembers.value.filter(
// member => !activeRole.value!.members.some(
// roleMember => roleMember.member_id === member.member_id
// )
// );
// })
const props = defineProps<{
allMembers: MemberLight[]
}>()
const availableMembers = computed(() =>
props.allMembers.filter(
m => !roleMembers.value.some(rm => rm.id === m.id)
)
)
async function handleRemoveMember(memberId: number) {
await removeMemberFromRole(memberId, Number(route.params.id));
await loadRole();
}
const addMemberRef = ref<InstanceType<typeof AddMember> | null>(null)
onMounted(loadRole)
watch(() => route.params.id, loadRole)
</script>
<template>
<AddMember ref="addMemberRef" :all-members="availableMembers" :role="roleData" @submit="loadRole"></AddMember>
<div class="h-full px-6 py-2">
<!-- Loading -->
<div v-if="loading" class="text-muted-foreground">
@@ -102,7 +111,7 @@ watch(() => route.params.id, loadRole)
<input v-model="searchQuery" type="text" placeholder="Search members…"
class="flex-1 bg-transparent outline-none text-sm" />
</InputGroup>
<Button variant="secondary">
<Button variant="secondary" @click="addMemberRef.openDialog()">
<Plus class="mr-2 h-4 w-4" />
Add Member
</Button>
@@ -120,7 +129,8 @@ watch(() => route.params.id, loadRole)
class="flex items-center justify-between rounded-md px-3 py-2 hover:bg-muted/50">
<MemberCard :member-id="member.id" />
<Button variant="ghost" size="icon" class="text-muted-foreground">
<Button variant="ghost" size="icon" class="text-muted-foreground"
@click="handleRemoveMember(member.id)">
<X class="h-4 w-4" />
</Button>
</li>

View File

@@ -113,19 +113,7 @@ 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();
}
onMounted(async () => {
roles.value = await getRoles();