implemented group member search

This commit is contained in:
2025-12-18 16:21:34 -05:00
parent f0624a64bd
commit 1db75ee773
4 changed files with 36 additions and 19 deletions

View File

@@ -2,12 +2,14 @@
import { getRoleDetails, getRoleMembers } from '@/api/roles'
import type { MemberLight } from '@shared/types/member'
import type { Role } from '@shared/types/roles'
import { onMounted, ref, watch } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import Button from '@/components/ui/button/Button.vue'
import Separator from '@/components/ui/separator/Separator.vue'
import { Plus, X } from 'lucide-vue-next'
import { Plus, SearchIcon, X } from 'lucide-vue-next'
import MemberCard from '../members/MemberCard.vue'
import InputGroup from '../ui/input-group/InputGroup.vue'
import InputGroupAddon from '../ui/input-group/InputGroupAddon.vue'
const route = useRoute()
@@ -25,6 +27,16 @@ async function loadRole() {
loading.value = false
}
const searchQuery = ref('')
const roleMembersFiltered = computed(() => {
if (!searchQuery.value) return roleMembers.value
const query = searchQuery.value.toLowerCase()
return roleMembers.value.filter(member =>
member.displayName?.toLowerCase().includes(query) ||
member.username?.toLowerCase().includes(query)
)
})
// const availableMembers = computed(() => {
// if (!activeRole.value) return [];
// return allMembers.value.filter(
@@ -81,11 +93,20 @@ watch(() => route.params.id, loadRole)
<h3 class="text-sm font-medium">
Members ({{ roleMembers.length }})
</h3>
<div class="flex items-center gap-5">
<InputGroup class="w-64">
<InputGroupAddon>
<SearchIcon class="h-4 w-4 text-muted-foreground" />
</InputGroupAddon>
<Button size="sm" variant="secondary">
<Plus class="mr-2 h-4 w-4" />
Add Member
</Button>
<input v-model="searchQuery" type="text" placeholder="Search members…"
class="flex-1 bg-transparent outline-none text-sm" />
</InputGroup>
<Button variant="secondary">
<Plus class="mr-2 h-4 w-4" />
Add Member
</Button>
</div>
</div>
<!-- Empty state -->
@@ -95,11 +116,11 @@ watch(() => route.params.id, loadRole)
<div class="overflow-y-auto pr-1">
<ul class="space-y-1">
<li v-for="member in roleMembers" :key="member.id"
class="flex items-center justify-between rounded-md px-3 py-2 hover:bg-muted">
<li v-for="member in roleMembersFiltered" :key="member.id"
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 hover:text-destructive">
<Button variant="ghost" size="icon" class="text-muted-foreground">
<X class="h-4 w-4" />
</Button>
</li>

View File

@@ -2,10 +2,10 @@ import { cva } from "class-variance-authority";
export { default as InputGroup } from "./InputGroup.vue";
export { default as InputGroupAddon } from "./InputGroupAddon.vue";
export { default as InputGroupButton } from "./InputGroupButton.vue";
export { default as InputGroupInput } from "./InputGroupInput.vue";
export { default as InputGroupText } from "./InputGroupText.vue";
export { default as InputGroupTextarea } from "./InputGroupTextarea.vue";
// export { default as InputGroupButton } from "./InputGroupButton.vue";
// export { default as InputGroupInput } from "./InputGroupInput.vue";
// export { default as InputGroupText } from "./InputGroupText.vue";
// export { default as InputGroupTextarea } from "./InputGroupTextarea.vue";
export const inputGroupAddonVariants = cva(
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",

View File

@@ -22,7 +22,7 @@ const modelValue = useVModel(props, "modelValue", emits, {
data-slot="input"
:class="
cn(
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
props.class,

View File

@@ -127,10 +127,6 @@ async function handleRemoveMember(memberId: number) {
roles.value = await getRoles();
}
async function handleDeleteRole() {
await deleteRole(activeRole.value.id);
}
onMounted(async () => {
roles.value = await getRoles();
allMembers.value = await getAllLightMembers();
@@ -150,7 +146,7 @@ onMounted(async () => {
<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 text-sm transition-colors cursor-pointer',
'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'