initial: bootstrap from BukidBountyApp base
This commit is contained in:
112
resources/js/Pages/CoopMemberSearch.vue
Normal file
112
resources/js/Pages/CoopMemberSearch.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { useChapters } from '../composables/useChapters.js';
|
||||
import { usePageTitle } from '../composables/Core/usePageTitle';
|
||||
|
||||
usePageTitle('Search Members');
|
||||
|
||||
const { searchMembers, loading } = useChapters();
|
||||
|
||||
const query = ref('');
|
||||
const results = ref([]);
|
||||
const searched = ref(false);
|
||||
let debounceTimer = null;
|
||||
|
||||
const roleLabel = (role) => {
|
||||
const map = {
|
||||
PRESIDENT: 'President',
|
||||
VICE_PRESIDENT: 'Vice President',
|
||||
SECRETARY: 'Secretary',
|
||||
TREASURER: 'Treasurer',
|
||||
AUDITOR: 'Auditor',
|
||||
BOARD_MEMBER: 'Board Member',
|
||||
};
|
||||
return map[role] || role;
|
||||
};
|
||||
|
||||
const isOfficer = (role) => role && role !== 'MEMBER';
|
||||
|
||||
const runSearch = async () => {
|
||||
const q = query.value.trim();
|
||||
if (q.length < 2) {
|
||||
results.value = [];
|
||||
searched.value = false;
|
||||
return;
|
||||
}
|
||||
searched.value = true;
|
||||
results.value = await searchMembers(q);
|
||||
};
|
||||
|
||||
watch(query, () => {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(runSearch, 400);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container py-4" style="max-width: 640px;">
|
||||
<h5 class="fw-bold mb-3"><i class="fas fa-search me-2"></i>Search Members</h5>
|
||||
|
||||
<div class="search-bar rounded-pill p-1 mb-3 d-flex align-items-center">
|
||||
<i class="fas fa-search mx-3 text-muted"></i>
|
||||
<input
|
||||
v-model="query"
|
||||
type="text"
|
||||
class="form-control border-0 bg-transparent"
|
||||
placeholder="Type a member name..."
|
||||
style="box-shadow: none;"
|
||||
/>
|
||||
<span v-if="loading" class="spinner-border spinner-border-sm me-3 text-muted"></span>
|
||||
</div>
|
||||
|
||||
<div v-if="query.trim().length < 2" class="text-center py-5 text-muted">
|
||||
<i class="fas fa-keyboard fa-2x opacity-25 mb-2"></i>
|
||||
<p class="small mb-0">Type at least 2 characters to search.</p>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div v-if="!results.length && searched && !loading" class="text-center py-5 text-muted">
|
||||
<i class="fas fa-user-slash fa-2x opacity-25 mb-2"></i>
|
||||
<p class="small mb-0">No members found matching "{{ query }}".</p>
|
||||
</div>
|
||||
|
||||
<div v-for="(m, i) in results" :key="i" class="result-card rounded-4 p-3 mb-2 d-flex align-items-center gap-3">
|
||||
<div class="avatar rounded-circle d-flex align-items-center justify-content-center fw-bold">
|
||||
{{ (m.name || '?').charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<div class="fw-semibold text-truncate">{{ m.name }}</div>
|
||||
<div class="small text-muted text-truncate">{{ m.chapter_name }}</div>
|
||||
</div>
|
||||
<span v-if="isOfficer(m.role)" class="badge rounded-pill role-badge">{{ roleLabel(m.role) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.search-bar {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.result-card {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: var(--accent-color);
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.role-badge {
|
||||
background: var(--accent-color);
|
||||
color: #fff;
|
||||
}
|
||||
:global(.dark-mode) .search-bar,
|
||||
:global(.dark-mode) .result-card {
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user