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

146 lines
5.6 KiB
Vue

<script setup>
import { ref, reactive, onMounted } from 'vue';
import { useChapters } from '../composables/useChapters.js';
import { useAuth } from '../composables/Core/useAuth.js';
import { usePageTitle } from '../composables/Core/usePageTitle';
usePageTitle('Chapter Org Chart');
const { fetchOrgChart, loading } = useChapters();
const { isCoopMember } = useAuth();
const ownChapter = ref(null);
const children = ref([]);
const showOfficers = reactive({});
const loadingChild = reactive({});
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 || 'Member';
};
const levelLabel = (level) => (level || '').toUpperCase();
const toggleChild = async (child) => {
showOfficers[child.id] = !showOfficers[child.id];
if (showOfficers[child.id] && (!child.officers || child.officers.length === 0) && !child._loaded) {
loadingChild[child.id] = true;
try {
const res = await fetchOrgChart({ chapterId: child.id });
child.officers = res?.officers ?? [];
child._loaded = true;
} finally {
loadingChild[child.id] = false;
}
}
};
onMounted(async () => {
const res = await fetchOrgChart({});
ownChapter.value = res?.own_chapter ?? null;
children.value = (res?.children ?? []).map((c) => ({ ...c, officers: c.officers ?? [], _loaded: false }));
});
</script>
<template>
<div class="container py-4" style="max-width: 720px;">
<div v-if="loading && !ownChapter" class="text-center py-5">
<div class="spinner-border text-primary" role="status"></div>
</div>
<template v-else-if="ownChapter">
<!-- Header card -->
<div class="header-card rounded-4 p-4 mb-3">
<span class="badge rounded-pill mb-2 level-badge">{{ levelLabel(ownChapter.level) }}</span>
<h4 class="fw-bold mb-0">{{ ownChapter.name }}</h4>
</div>
<!-- Own chapter officers -->
<div class="panel rounded-4 p-3 mb-3">
<h6 class="fw-semibold mb-3"><i class="fas fa-user-tie me-2"></i>Chapter Officers</h6>
<div v-if="!ownChapter.officers?.length" class="text-muted small">No officers assigned yet.</div>
<div v-else class="d-flex flex-wrap gap-2">
<span v-for="(o, i) in ownChapter.officers" :key="i" class="officer-pill rounded-pill px-3 py-2 small">
<strong>{{ o.name }}</strong>
<span class="opacity-75"> · {{ roleLabel(o.role) }}</span>
</span>
</div>
</div>
<!-- Child chapters (officers only, no members) -->
<div v-if="!isCoopMember" class="panel rounded-4 p-3">
<h6 class="fw-semibold mb-3"><i class="fas fa-sitemap me-2"></i>Sub-Chapters</h6>
<div v-if="!children.length" class="text-muted small">No sub-chapters yet.</div>
<div v-for="child in children" :key="child.id" class="child-row rounded-3 mb-2 p-3">
<div class="d-flex align-items-center gap-2" role="button" @click="toggleChild(child)">
<span class="badge rounded-pill level-badge-sm">{{ levelLabel(child.level) }}</span>
<span class="fw-semibold flex-grow-1 text-truncate">{{ child.name }}</span>
<span class="small text-muted">{{ child.member_count }} member{{ child.member_count !== 1 ? 's' : '' }}</span>
<i class="fas" :class="showOfficers[child.id] ? 'fa-chevron-down' : 'fa-chevron-right'"></i>
</div>
<div v-if="showOfficers[child.id]" class="mt-2 pt-2 border-top">
<div v-if="loadingChild[child.id]" class="small text-muted">Loading officers...</div>
<div v-else-if="!child.officers?.length" class="small text-muted">No officers assigned yet.</div>
<div v-else class="d-flex flex-wrap gap-2">
<span v-for="(o, i) in child.officers" :key="i" class="officer-pill rounded-pill px-3 py-1 small">
<strong>{{ o.name }}</strong>
<span class="opacity-75"> · {{ roleLabel(o.role) }}</span>
</span>
</div>
</div>
</div>
</div>
</template>
<div v-else class="text-center py-5 text-muted">
<i class="fas fa-sitemap fa-2x opacity-25 mb-3"></i>
<p>You are not assigned to a chapter yet.</p>
</div>
</div>
</template>
<style scoped>
.header-card {
background: var(--accent-color);
color: #fff;
}
.level-badge {
background: rgba(255, 255, 255, 0.25);
color: #fff;
}
.level-badge-sm {
background: var(--accent-color);
color: #fff;
font-size: 0.65rem;
}
.panel {
background: var(--bg-card);
color: var(--text-primary);
border: 1px solid rgba(0, 0, 0, 0.06);
}
.child-row {
background: var(--bg-primary);
color: var(--text-primary);
border: 1px solid rgba(0, 0, 0, 0.06);
}
.officer-pill {
background: var(--bg-primary);
color: var(--text-primary);
border: 1px solid rgba(0, 0, 0, 0.08);
}
:global(.dark-mode) .panel,
:global(.dark-mode) .child-row,
:global(.dark-mode) .officer-pill {
border-color: rgba(255, 255, 255, 0.08);
}
</style>