Complete adaptation from BukidBountyApp to Philippine barangay governance: - Barangay models: Resident, Household, HouseholdMember, Blotter, BlotterHearing, DocumentRequest, RequestPayment, RequestType, BarangayProject, BarangayBudget - Controllers: ResidentController, HouseholdController, BlotterController, BlotterHearingController, DocumentRequestController, RequestTypeController, ProjectController, BudgetController, QRPHController, AdminConsoleController, UserController, FileController, ChapterController, LoginController - Vue pages: Home, ManageResidents, ResidentProfile, ManageHouseholds, ManageBlotters, BlotterDetail, RequestDocument, ManageDocumentRequests, DocumentRequestDetail, ManageRequestTypes, ManageProjects, BudgetLedger, AdminConsole - Barangay roles: PunongBarangay, Kagawad, Secretary, Treasurer, SK, Tanod, BHW, Staff, Resident - UserPermissions matrix rewritten with barangay-specific permission mappings - VueRouteMap replaced with barangay SPA routes - UserActions enum references corrected across all controllers - Removed all market/cooperative/POS/subscription code and models
214 lines
9.2 KiB
Vue
214 lines
9.2 KiB
Vue
<script setup>
|
|
import { ref, onMounted, computed } from 'vue';
|
|
import { usePageTitle } from '../../composables/Core/usePageTitle';
|
|
import { executeRequest } from '../../utils/executeRequest.js';
|
|
import { navigate } from '../../utils/navigate.js';
|
|
|
|
usePageTitle('Residents');
|
|
|
|
const residents = ref([]);
|
|
const loading = ref(false);
|
|
const search = ref('');
|
|
const purokFilter = ref('');
|
|
const puroks = ref([]);
|
|
const page = ref(1);
|
|
const total = ref(0);
|
|
const perPage = 20;
|
|
|
|
const showModal = ref(false);
|
|
const editingItem = ref(null);
|
|
|
|
const blankForm = () => ({
|
|
firstname: '', middlename: '', lastname: '', suffix: '',
|
|
dob: '', birthplace: '', gender: 'MALE', civil_status: 'SINGLE',
|
|
citizenship: 'Filipino', religion: '', occupation: '',
|
|
monthly_income: '', blood_type: '',
|
|
voter_status: false, head_of_household: false,
|
|
purok: '', street: '', barangay: '', city: '', province: '', region: '',
|
|
philhealth_id: '', sss_id: '', gsis_id: '', tin: '',
|
|
emergency_contact_name: '', emergency_contact_phone: '', emergency_contact_address: '',
|
|
});
|
|
const form = ref(blankForm());
|
|
|
|
const loadResidents = async () => {
|
|
loading.value = true;
|
|
const params = new URLSearchParams({ page: page.value, per_page: perPage });
|
|
if (search.value) params.append('search', search.value);
|
|
if (purokFilter.value) params.append('purok', purokFilter.value);
|
|
const res = await executeRequest(`/residents?${params}`);
|
|
if (res.success) {
|
|
residents.value = res.data.data ?? res.data;
|
|
total.value = res.data.total ?? residents.value.length;
|
|
}
|
|
loading.value = false;
|
|
};
|
|
|
|
const loadPuroks = async () => {
|
|
const res = await executeRequest('/residents/puroks');
|
|
if (res.success) puroks.value = res.data;
|
|
};
|
|
|
|
const openCreate = () => {
|
|
editingItem.value = null;
|
|
form.value = blankForm();
|
|
showModal.value = true;
|
|
};
|
|
|
|
const openEdit = (item) => {
|
|
editingItem.value = item;
|
|
form.value = { ...item };
|
|
showModal.value = true;
|
|
};
|
|
|
|
const saveResident = async () => {
|
|
const url = editingItem.value ? '/residents/update' : '/residents/create';
|
|
const payload = editingItem.value
|
|
? { target: editingItem.value.hashkey, ...form.value }
|
|
: form.value;
|
|
const res = await executeRequest(url, 'POST', payload);
|
|
if (res.success) {
|
|
showModal.value = false;
|
|
loadResidents();
|
|
}
|
|
};
|
|
|
|
const viewProfile = (item) => navigate(`/Barangay/ResidentProfile?target=${item.hashkey}`);
|
|
|
|
onMounted(() => { loadResidents(); loadPuroks(); });
|
|
</script>
|
|
|
|
<template>
|
|
<div class="p-4 max-w-6xl mx-auto">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h1 class="text-2xl font-bold">Resident Records</h1>
|
|
<button @click="openCreate" class="btn-primary">+ Add Resident</button>
|
|
</div>
|
|
|
|
<div class="flex gap-2 mb-4">
|
|
<input v-model="search" @input="loadResidents" placeholder="Search by name..." class="input flex-1" />
|
|
<select v-model="purokFilter" @change="loadResidents" class="input w-40">
|
|
<option value="">All Puroks</option>
|
|
<option v-for="p in puroks" :key="p" :value="p">{{ p }}</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div v-if="loading" class="text-center py-8 text-gray-400">Loading...</div>
|
|
<div v-else>
|
|
<table class="w-full text-sm">
|
|
<thead class="bg-gray-100">
|
|
<tr>
|
|
<th class="text-left p-2">Name</th>
|
|
<th class="text-left p-2">DOB</th>
|
|
<th class="text-left p-2">Gender</th>
|
|
<th class="text-left p-2">Purok</th>
|
|
<th class="text-left p-2">Status</th>
|
|
<th class="p-2"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="r in residents" :key="r.id" class="border-b hover:bg-gray-50">
|
|
<td class="p-2 font-medium">{{ r.lastname }}, {{ r.firstname }} {{ r.middlename }}</td>
|
|
<td class="p-2">{{ r.dob }}</td>
|
|
<td class="p-2">{{ r.gender }}</td>
|
|
<td class="p-2">{{ r.purok || '—' }}</td>
|
|
<td class="p-2">
|
|
<span :class="r.is_active ? 'badge-green' : 'badge-red'">
|
|
{{ r.is_active ? 'Active' : 'Inactive' }}
|
|
</span>
|
|
</td>
|
|
<td class="p-2 flex gap-2">
|
|
<button @click="viewProfile(r)" class="btn-sm">View</button>
|
|
<button @click="openEdit(r)" class="btn-sm">Edit</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<p v-if="!residents.length" class="text-center text-gray-400 py-8">No residents found.</p>
|
|
</div>
|
|
|
|
<!-- Modal -->
|
|
<div v-if="showModal" class="modal-overlay" @click.self="showModal = false">
|
|
<div class="modal-box">
|
|
<h2 class="text-lg font-bold mb-4">{{ editingItem ? 'Edit Resident' : 'New Resident' }}</h2>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="label">First Name *</label>
|
|
<input v-model="form.firstname" class="input" required />
|
|
</div>
|
|
<div>
|
|
<label class="label">Middle Name</label>
|
|
<input v-model="form.middlename" class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="label">Last Name *</label>
|
|
<input v-model="form.lastname" class="input" required />
|
|
</div>
|
|
<div>
|
|
<label class="label">Suffix</label>
|
|
<input v-model="form.suffix" class="input" placeholder="Jr., Sr., III" />
|
|
</div>
|
|
<div>
|
|
<label class="label">Date of Birth *</label>
|
|
<input v-model="form.dob" type="date" class="input" required />
|
|
</div>
|
|
<div>
|
|
<label class="label">Gender *</label>
|
|
<select v-model="form.gender" class="input">
|
|
<option value="MALE">Male</option>
|
|
<option value="FEMALE">Female</option>
|
|
<option value="OTHER">Other</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="label">Civil Status</label>
|
|
<select v-model="form.civil_status" class="input">
|
|
<option value="SINGLE">Single</option>
|
|
<option value="MARRIED">Married</option>
|
|
<option value="WIDOWED">Widowed</option>
|
|
<option value="SEPARATED">Separated</option>
|
|
<option value="ANNULLED">Annulled</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="label">Blood Type</label>
|
|
<select v-model="form.blood_type" class="input">
|
|
<option value="">—</option>
|
|
<option v-for="bt in ['A+','A-','B+','B-','AB+','AB-','O+','O-']" :key="bt" :value="bt">{{ bt }}</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="label">Occupation</label>
|
|
<input v-model="form.occupation" class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="label">Monthly Income</label>
|
|
<input v-model="form.monthly_income" type="number" class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="label">Purok</label>
|
|
<input v-model="form.purok" class="input" />
|
|
</div>
|
|
<div>
|
|
<label class="label">Street / Address</label>
|
|
<input v-model="form.street" class="input" />
|
|
</div>
|
|
<div class="col-span-2">
|
|
<label class="label">Emergency Contact</label>
|
|
<input v-model="form.emergency_contact_name" class="input" placeholder="Name" />
|
|
</div>
|
|
<div>
|
|
<input v-model="form.emergency_contact_phone" class="input" placeholder="Phone number" />
|
|
</div>
|
|
<div>
|
|
<input v-model="form.emergency_contact_address" class="input" placeholder="Address" />
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-end gap-2 mt-4">
|
|
<button @click="showModal = false" class="btn-secondary">Cancel</button>
|
|
<button @click="saveResident" class="btn-primary">Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|