Files
BarangaySystem/resources/js/Pages/AssignChapterOfficer.vue
2026-06-06 18:43:00 +08:00

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>