167 lines
6.3 KiB
Vue
167 lines
6.3 KiB
Vue
<script setup>
|
|
import { ref, computed, watch, onMounted } from 'vue';
|
|
import { useChapters } from '../composables/useChapters.js';
|
|
import { useNavigate } from '../composables/Core/useNavigate.js';
|
|
import { usePageTitle } from '../composables/Core/usePageTitle';
|
|
|
|
usePageTitle('Create Chapter');
|
|
|
|
const { fetchOfficerScope, createChapter, loading } = useChapters();
|
|
const { navigate } = useNavigate();
|
|
|
|
const CHILD_LEVELS = {
|
|
national: ['region'],
|
|
region: ['province'],
|
|
province: ['city', 'municipal'],
|
|
city: ['barangay'],
|
|
municipal: ['barangay'],
|
|
barangay: [],
|
|
};
|
|
|
|
const ownChapter = ref(null);
|
|
const cooperative = ref(null);
|
|
|
|
const form = ref({ name: '', location_key: '', lat: '', lng: '' });
|
|
const userEditedKey = ref(false);
|
|
|
|
const errorMessage = ref('');
|
|
const submitting = ref(false);
|
|
const createdChapter = ref(null);
|
|
|
|
const childLevel = computed(() => {
|
|
if (!ownChapter.value) return null;
|
|
const levels = CHILD_LEVELS[ownChapter.value.level] || [];
|
|
return levels.length ? levels[0] : null;
|
|
});
|
|
|
|
const isBarangay = computed(() => ownChapter.value?.level === 'barangay');
|
|
|
|
watch(() => form.value.name, (val) => {
|
|
if (!userEditedKey.value) {
|
|
form.value.location_key = (val || '').toLowerCase().trim();
|
|
}
|
|
});
|
|
|
|
const canSubmit = computed(() => form.value.name && form.value.location_key && childLevel.value);
|
|
|
|
const submit = async () => {
|
|
if (submitting.value || !canSubmit.value) return;
|
|
errorMessage.value = '';
|
|
submitting.value = true;
|
|
try {
|
|
const res = await createChapter({
|
|
name: form.value.name,
|
|
locationKey: form.value.location_key,
|
|
lat: form.value.lat === '' ? null : Number(form.value.lat),
|
|
lng: form.value.lng === '' ? null : Number(form.value.lng),
|
|
});
|
|
if (res.success) {
|
|
createdChapter.value = res.chapter;
|
|
} else {
|
|
errorMessage.value = res.message || 'Failed to create chapter.';
|
|
}
|
|
} catch (err) {
|
|
errorMessage.value = err.response?.data?.message || err.response?.data?.error || 'An error occurred.';
|
|
} finally {
|
|
submitting.value = false;
|
|
}
|
|
};
|
|
|
|
onMounted(async () => {
|
|
const scope = await fetchOfficerScope();
|
|
ownChapter.value = scope?.own_chapter ?? null;
|
|
cooperative.value = scope?.cooperative ?? null;
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="container py-4" style="max-width: 560px;">
|
|
<h5 class="fw-bold mb-3"><i class="fas fa-map-marker-alt me-2"></i>Create Sub-Chapter</h5>
|
|
|
|
<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>
|
|
|
|
<div v-else-if="isBarangay" class="text-center py-5 text-muted">
|
|
<i class="fas fa-ban fa-2x text-danger mb-2"></i>
|
|
<p>Cannot create sub-chapters at barangay level.</p>
|
|
</div>
|
|
|
|
<div v-else-if="createdChapter" class="text-center py-5">
|
|
<i class="fas fa-check-circle fa-4x text-success mb-3"></i>
|
|
<h5 class="fw-bold">Chapter Created!</h5>
|
|
<p class="text-muted">
|
|
<strong>{{ createdChapter.name }}</strong>
|
|
<span class="badge rounded-pill level-badge ms-2">{{ (createdChapter.level || '').toUpperCase() }}</span>
|
|
</p>
|
|
<button class="btn btn-primary rounded-pill px-4" @click="navigate({ page: 'Home' })">Done</button>
|
|
</div>
|
|
|
|
<div v-else class="info-card rounded-4 p-4">
|
|
<div class="meta rounded-3 p-2 mb-3 small">
|
|
<div><i class="fas fa-layer-group me-1"></i> Level: <strong>{{ (childLevel || '').toUpperCase() }}</strong></div>
|
|
<div><i class="fas fa-sitemap me-1"></i> Parent: <strong>{{ ownChapter.name }}</strong></div>
|
|
<div v-if="cooperative"><i class="fas fa-handshake me-1"></i> Cooperative: <strong>{{ cooperative.name }}</strong></div>
|
|
</div>
|
|
|
|
<div v-if="errorMessage" class="alert alert-danger rounded-3 small py-2">{{ errorMessage }}</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-semibold">Chapter Name</label>
|
|
<input v-model="form.name" type="text" class="form-control rounded-pill" placeholder="e.g. Malaybalay City" />
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-semibold">Location Key</label>
|
|
<input v-model="form.location_key" type="text" class="form-control rounded-pill"
|
|
placeholder="auto-generated" @input="userEditedKey = true" />
|
|
<div class="form-text small text-muted">Lowercase identifier (auto-filled from name).</div>
|
|
</div>
|
|
|
|
<div class="row g-2 mb-4">
|
|
<div class="col-6">
|
|
<label class="form-label small fw-semibold">Latitude <span class="text-muted">(optional)</span></label>
|
|
<input v-model="form.lat" type="number" step="any" class="form-control rounded-pill" />
|
|
</div>
|
|
<div class="col-6">
|
|
<label class="form-label small fw-semibold">Longitude <span class="text-muted">(optional)</span></label>
|
|
<input v-model="form.lng" type="number" step="any" class="form-control rounded-pill" />
|
|
</div>
|
|
</div>
|
|
|
|
<button class="btn btn-primary rounded-pill w-100 py-2 fw-semibold" :disabled="submitting || !canSubmit" @click="submit">
|
|
<span v-if="submitting" class="spinner-border spinner-border-sm me-2"></span>
|
|
<i v-else class="fas fa-plus me-2"></i>
|
|
{{ submitting ? 'Creating...' : 'Create Chapter' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.info-card {
|
|
background: var(--bg-card);
|
|
color: var(--text-primary);
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
}
|
|
.meta {
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
line-height: 1.6;
|
|
}
|
|
.level-badge {
|
|
background: var(--accent-color);
|
|
color: #fff;
|
|
}
|
|
:global(.dark-mode) .info-card,
|
|
:global(.dark-mode) .meta {
|
|
border-color: rgba(255, 255, 255, 0.08);
|
|
}
|
|
</style>
|