184 lines
7.3 KiB
Vue
184 lines
7.3 KiB
Vue
<script setup>
|
|
import { ref, computed, onMounted } from 'vue';
|
|
import { useChapters } from '../composables/useChapters.js';
|
|
import { useNavigate } from '../composables/Core/useNavigate.js';
|
|
import { useModal } from '../composables/Core/useModal.js';
|
|
import { usePageTitle } from '../composables/Core/usePageTitle';
|
|
|
|
usePageTitle('Assign Officer');
|
|
|
|
const { fetchOfficerScope, assignOfficer, loading } = useChapters();
|
|
const { navigate } = useNavigate();
|
|
const modal = useModal();
|
|
|
|
const ROLES = ['PRESIDENT', 'VICE_PRESIDENT', 'SECRETARY', 'TREASURER', 'AUDITOR', 'BOARD_MEMBER'];
|
|
const roleLabel = (r) => ({
|
|
PRESIDENT: 'President', VICE_PRESIDENT: 'Vice President', SECRETARY: 'Secretary',
|
|
TREASURER: 'Treasurer', AUDITOR: 'Auditor', BOARD_MEMBER: 'Board Member',
|
|
}[r] || r);
|
|
|
|
const ownChapter = ref(null);
|
|
const eligibleMembers = ref([]);
|
|
const childChapters = ref([]);
|
|
|
|
const memberFilter = ref('');
|
|
const selectedMember = ref(null);
|
|
const selectedChapter = ref(null);
|
|
const selectedRole = ref('');
|
|
const submitting = ref(false);
|
|
|
|
const step = computed(() => {
|
|
if (!selectedMember.value) return 1;
|
|
if (!selectedChapter.value) return 2;
|
|
if (!selectedRole.value) return 3;
|
|
return 4;
|
|
});
|
|
|
|
const filteredMembers = computed(() => {
|
|
const q = memberFilter.value.trim().toLowerCase();
|
|
if (!q) return eligibleMembers.value;
|
|
return eligibleMembers.value.filter((m) => (m.name || '').toLowerCase().includes(q));
|
|
});
|
|
|
|
const selectMember = (m) => { selectedMember.value = m; };
|
|
const selectChapter = (c) => { selectedChapter.value = c; };
|
|
|
|
const back = () => {
|
|
if (selectedRole.value) { selectedRole.value = ''; return; }
|
|
if (selectedChapter.value) { selectedChapter.value = null; return; }
|
|
if (selectedMember.value) { selectedMember.value = null; return; }
|
|
};
|
|
|
|
const confirmAssign = async () => {
|
|
if (submitting.value) return;
|
|
submitting.value = true;
|
|
try {
|
|
const res = await assignOfficer({
|
|
memberUserHashkey: selectedMember.value.user_hashkey,
|
|
childChapterId: selectedChapter.value.id,
|
|
role: selectedRole.value,
|
|
});
|
|
if (res.success) {
|
|
modal.quickDismiss({
|
|
title: 'Officer Assigned',
|
|
body: res.message || 'Member assigned successfully.',
|
|
});
|
|
navigate({ page: 'Home' });
|
|
}
|
|
} catch (err) {
|
|
modal.quickDismiss({
|
|
title: 'Error',
|
|
body: err.response?.data?.message || err.response?.data?.error || 'Failed to assign officer.',
|
|
});
|
|
} finally {
|
|
submitting.value = false;
|
|
}
|
|
};
|
|
|
|
onMounted(async () => {
|
|
const scope = await fetchOfficerScope();
|
|
ownChapter.value = scope?.own_chapter ?? null;
|
|
eligibleMembers.value = scope?.eligible_members ?? [];
|
|
childChapters.value = scope?.child_chapters ?? [];
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="container py-4" style="max-width: 620px;">
|
|
<div class="d-flex align-items-center gap-2 mb-3">
|
|
<button v-if="step > 1" class="btn btn-sm btn-outline-secondary rounded-circle" @click="back">
|
|
<i class="fas fa-arrow-left"></i>
|
|
</button>
|
|
<h5 class="fw-bold mb-0"><i class="fas fa-user-tie me-2"></i>Assign Officer</h5>
|
|
</div>
|
|
|
|
<div v-if="loading && !ownChapter" class="text-center py-5">
|
|
<div class="spinner-border text-primary" role="status"></div>
|
|
</div>
|
|
|
|
<div v-else-if="!ownChapter" class="text-center py-5 text-muted">
|
|
<i class="fas fa-exclamation-triangle fa-2x text-warning mb-2"></i>
|
|
<p>You are not assigned to a chapter.</p>
|
|
</div>
|
|
|
|
<template v-else>
|
|
<!-- Step 1: pick member -->
|
|
<div v-if="step === 1" class="panel rounded-4 p-3">
|
|
<h6 class="fw-semibold mb-2">1. Select a member</h6>
|
|
<input v-model="memberFilter" type="text" class="form-control rounded-pill mb-3" placeholder="Search members..." />
|
|
<div v-if="!filteredMembers.length" class="text-muted small py-3 text-center">
|
|
No eligible members in {{ ownChapter.name }}.
|
|
</div>
|
|
<div v-for="m in filteredMembers" :key="m.user_hashkey" class="row-item rounded-3 p-3 mb-2" role="button" @click="selectMember(m)">
|
|
<i class="fas fa-user me-2 text-muted"></i><span class="fw-semibold">{{ m.name }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 2: pick child chapter -->
|
|
<div v-else-if="step === 2" class="panel rounded-4 p-3">
|
|
<h6 class="fw-semibold mb-2">2. Select a sub-chapter</h6>
|
|
<p class="small text-muted">Assigning <strong>{{ selectedMember.name }}</strong></p>
|
|
<div v-if="!childChapters.length" class="text-muted small py-3 text-center">
|
|
No sub-chapters available. Create one first.
|
|
</div>
|
|
<div v-for="c in childChapters" :key="c.id" class="row-item rounded-3 p-3 mb-2" role="button" @click="selectChapter(c)">
|
|
<span class="badge rounded-pill level-badge me-2">{{ (c.level || '').toUpperCase() }}</span>
|
|
<span class="fw-semibold">{{ c.name }}</span>
|
|
<span class="small text-muted ms-2">{{ c.active_members_count }} members</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3: pick role -->
|
|
<div v-else-if="step === 3" class="panel rounded-4 p-3">
|
|
<h6 class="fw-semibold mb-3">3. Select a role</h6>
|
|
<div class="d-grid gap-2">
|
|
<button v-for="r in ROLES" :key="r" class="row-item rounded-3 p-3 text-start" @click="selectedRole = r">
|
|
<i class="fas fa-id-badge me-2 text-muted"></i>{{ roleLabel(r) }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 4: confirm -->
|
|
<div v-else class="panel rounded-4 p-4">
|
|
<h6 class="fw-semibold mb-3">4. Confirm</h6>
|
|
<p>
|
|
Assign <strong>{{ selectedMember.name }}</strong> as
|
|
<strong>{{ roleLabel(selectedRole) }}</strong> to
|
|
<strong>{{ selectedChapter.name }}</strong>?
|
|
</p>
|
|
<div class="alert alert-warning rounded-3 small py-2">
|
|
This will MOVE them from {{ ownChapter.name }} to {{ selectedChapter.name }}.
|
|
</div>
|
|
<button class="btn btn-primary rounded-pill w-100 py-2 fw-semibold" :disabled="submitting" @click="confirmAssign">
|
|
<span v-if="submitting" class="spinner-border spinner-border-sm me-2"></span>
|
|
<i v-else class="fas fa-check me-2"></i>
|
|
{{ submitting ? 'Assigning...' : 'Confirm Assignment' }}
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.panel {
|
|
background: var(--bg-card);
|
|
color: var(--text-primary);
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
}
|
|
.row-item {
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
cursor: pointer;
|
|
}
|
|
.level-badge {
|
|
background: var(--accent-color);
|
|
color: #fff;
|
|
font-size: 0.65rem;
|
|
}
|
|
:global(.dark-mode) .panel,
|
|
:global(.dark-mode) .row-item {
|
|
border-color: rgba(255, 255, 255, 0.08);
|
|
}
|
|
</style>
|