hooked up add and remove member
This commit is contained in:
103
ui/src/components/roles/addMember.vue
Normal file
103
ui/src/components/roles/addMember.vue
Normal 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>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<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 { MemberLight } from '@shared/types/member'
|
||||||
import type { Role } from '@shared/types/roles'
|
import type { Role } from '@shared/types/roles'
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
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 InputGroup from '../ui/input-group/InputGroup.vue'
|
||||||
import InputGroupAddon from '../ui/input-group/InputGroupAddon.vue'
|
import InputGroupAddon from '../ui/input-group/InputGroupAddon.vue'
|
||||||
|
|
||||||
|
import AddMember from './addMember.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const roleData = ref<Role | null>(null)
|
const roleData = ref<Role | null>(null)
|
||||||
@@ -18,9 +20,7 @@ const roleMembers = ref<MemberLight[]>([])
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
|
|
||||||
async function loadRole() {
|
async function loadRole() {
|
||||||
loading.value = true
|
|
||||||
const id = Number(route.params.id)
|
const id = Number(route.params.id)
|
||||||
console.log(id);
|
|
||||||
roleData.value = await getRoleDetails(id)
|
roleData.value = await getRoleDetails(id)
|
||||||
roleMembers.value = await getRoleMembers(id)
|
roleMembers.value = await getRoleMembers(id)
|
||||||
|
|
||||||
@@ -37,21 +37,30 @@ const roleMembersFiltered = computed(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// const availableMembers = computed(() => {
|
const props = defineProps<{
|
||||||
// if (!activeRole.value) return [];
|
allMembers: MemberLight[]
|
||||||
// return allMembers.value.filter(
|
}>()
|
||||||
// member => !activeRole.value!.members.some(
|
|
||||||
// roleMember => roleMember.member_id === member.member_id
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
onMounted(loadRole)
|
||||||
watch(() => route.params.id, loadRole)
|
watch(() => route.params.id, loadRole)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<AddMember ref="addMemberRef" :all-members="availableMembers" :role="roleData" @submit="loadRole"></AddMember>
|
||||||
<div class="h-full px-6 py-2">
|
<div class="h-full px-6 py-2">
|
||||||
<!-- Loading -->
|
<!-- Loading -->
|
||||||
<div v-if="loading" class="text-muted-foreground">
|
<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…"
|
<input v-model="searchQuery" type="text" placeholder="Search members…"
|
||||||
class="flex-1 bg-transparent outline-none text-sm" />
|
class="flex-1 bg-transparent outline-none text-sm" />
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
<Button variant="secondary">
|
<Button variant="secondary" @click="addMemberRef.openDialog()">
|
||||||
<Plus class="mr-2 h-4 w-4" />
|
<Plus class="mr-2 h-4 w-4" />
|
||||||
Add Member
|
Add Member
|
||||||
</Button>
|
</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">
|
class="flex items-center justify-between rounded-md px-3 py-2 hover:bg-muted/50">
|
||||||
<MemberCard :member-id="member.id" />
|
<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" />
|
<X class="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -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 () => {
|
onMounted(async () => {
|
||||||
roles.value = await getRoles();
|
roles.value = await getRoles();
|
||||||
|
|||||||
Reference in New Issue
Block a user