Files
BarangaySystem/resources/js/Pages/Barangay/ManageProjects.vue
Jonathan Sykes fbb7e3ff37
Some checks failed
tests / PHP 8.2 (swoole-5.1.6) (push) Has been cancelled
tests / PHP 8.3 (swoole-5.1.6) (push) Has been cancelled
tests / PHP 8.4 (swoole-6.0) (push) Has been cancelled
feat: implement barangay system phases 2-14
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
2026-06-07 03:09:09 +08:00

194 lines
9.3 KiB
Vue

<script setup>
import { ref, onMounted } from 'vue';
import { usePageTitle } from '../../composables/Core/usePageTitle';
import { executeRequest } from '../../utils/executeRequest.js';
usePageTitle('Barangay Projects');
const projects = ref([]);
const summary = ref(null);
const loading = ref(false);
const statusFilter = ref('');
const typeFilter = ref('');
const showModal = ref(false);
const editingItem = ref(null);
const blankForm = () => ({
project_name: '', description: '', type: 'INFRASTRUCTURE',
budget: '', fund_source: 'GENERAL_FUND',
start_date: '', end_date: '',
implementing_office: '', contractor: '', location: '', beneficiaries_count: '',
});
const form = ref(blankForm());
const projectTypes = ['INFRASTRUCTURE', 'LIVELIHOOD', 'HEALTH', 'EDUCATION', 'ENVIRONMENT', 'OTHERS'];
const fundSources = ['GENERAL_FUND', 'SK', 'PROVINCE', 'NATIONAL', 'OTHERS'];
const statusOptions = ['PLANNED', 'ONGOING', 'COMPLETED', 'SUSPENDED', 'CANCELLED'];
const statusColors = {
PLANNED: 'bg-blue-100 text-blue-700', ONGOING: 'bg-yellow-100 text-yellow-700',
COMPLETED: 'bg-green-100 text-green-700', SUSPENDED: 'bg-orange-100 text-orange-700',
CANCELLED: 'bg-red-100 text-red-700',
};
const loadData = async () => {
loading.value = true;
const params = new URLSearchParams();
if (statusFilter.value) params.append('status', statusFilter.value);
if (typeFilter.value) params.append('type', typeFilter.value);
const [projRes, sumRes] = await Promise.all([
executeRequest(`/projects?${params}`),
executeRequest('/projects/summary'),
]);
if (projRes.success) projects.value = projRes.data.data ?? projRes.data;
if (sumRes.success) summary.value = sumRes.data;
loading.value = false;
};
const openCreate = () => { editingItem.value = null; form.value = blankForm(); showModal.value = true; };
const openEdit = (item) => { editingItem.value = item; form.value = { ...item }; showModal.value = true; };
const saveProject = async () => {
const url = editingItem.value ? '/projects/update' : '/projects/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; loadData(); }
};
const updateStatus = async (item, status) => {
await executeRequest('/projects/status', 'POST', { target: item.hashkey, status });
loadData();
};
onMounted(loadData);
</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">Barangay Projects</h1>
<button @click="openCreate" class="btn-primary">+ Add Project</button>
</div>
<!-- Summary Cards -->
<div v-if="summary" class="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-5">
<div class="bg-white rounded-lg shadow p-3 text-center">
<div class="text-2xl font-bold">{{ summary.total }}</div>
<div class="text-xs text-gray-500">Total</div>
</div>
<div class="bg-yellow-50 rounded-lg shadow p-3 text-center">
<div class="text-2xl font-bold text-yellow-600">{{ summary.ongoing }}</div>
<div class="text-xs text-gray-500">Ongoing</div>
</div>
<div class="bg-green-50 rounded-lg shadow p-3 text-center">
<div class="text-2xl font-bold text-green-600">{{ summary.completed }}</div>
<div class="text-xs text-gray-500">Completed</div>
</div>
<div class="bg-blue-50 rounded-lg shadow p-3 text-center">
<div class="text-xl font-bold text-blue-600">{{ Number(summary.total_budget ?? 0).toLocaleString() }}</div>
<div class="text-xs text-gray-500">Total Budget</div>
</div>
</div>
<div class="flex gap-2 mb-4">
<select v-model="statusFilter" @change="loadData" class="input w-36">
<option value="">All Status</option>
<option v-for="s in statusOptions" :key="s" :value="s">{{ s }}</option>
</select>
<select v-model="typeFilter" @change="loadData" class="input w-40">
<option value="">All Types</option>
<option v-for="t in projectTypes" :key="t" :value="t">{{ t }}</option>
</select>
</div>
<div v-if="loading" class="text-center py-8 text-gray-400">Loading...</div>
<div v-else class="grid gap-3">
<div v-for="p in projects" :key="p.id" class="bg-white rounded-lg shadow p-4">
<div class="flex justify-between items-start">
<div>
<h3 class="font-semibold">{{ p.project_name }}</h3>
<div class="text-xs text-gray-500 mt-1">{{ p.type }} | {{ p.fund_source?.replace('_', ' ') }}</div>
<div class="text-sm text-gray-700 mt-1">Budget: <strong>{{ Number(p.budget).toLocaleString() }}</strong></div>
<div class="text-xs text-gray-400 mt-1">{{ p.start_date }} {{ p.end_date || 'TBD' }}</div>
</div>
<div class="flex flex-col items-end gap-2">
<span :class="`px-2 py-0.5 rounded-full text-xs font-medium ${statusColors[p.status] ?? 'bg-gray-100'}`">
{{ p.status }}
</span>
<div class="flex gap-1">
<button @click="openEdit(p)" class="btn-sm">Edit</button>
<select @change="e => updateStatus(p, e.target.value)" class="text-xs border rounded px-1">
<option value="">Change Status</option>
<option v-for="s in statusOptions" :key="s" :value="s">{{ s }}</option>
</select>
</div>
</div>
</div>
<p v-if="p.description" class="text-sm text-gray-600 mt-2 line-clamp-2">{{ p.description }}</p>
</div>
<p v-if="!projects.length" class="text-center text-gray-400 py-8">No projects found.</p>
</div>
<!-- Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="showModal = false">
<div class="modal-box max-w-2xl">
<h2 class="text-lg font-bold mb-4">{{ editingItem ? 'Edit Project' : 'New Project' }}</h2>
<div class="grid grid-cols-2 gap-3">
<div class="col-span-2">
<label class="label">Project Name *</label>
<input v-model="form.project_name" class="input" required />
</div>
<div>
<label class="label">Type *</label>
<select v-model="form.type" class="input">
<option v-for="t in projectTypes" :key="t" :value="t">{{ t }}</option>
</select>
</div>
<div>
<label class="label">Fund Source *</label>
<select v-model="form.fund_source" class="input">
<option v-for="f in fundSources" :key="f" :value="f">{{ f.replace('_', ' ') }}</option>
</select>
</div>
<div>
<label class="label">Budget () *</label>
<input v-model="form.budget" type="number" class="input" />
</div>
<div>
<label class="label">Beneficiaries Count</label>
<input v-model="form.beneficiaries_count" type="number" class="input" />
</div>
<div>
<label class="label">Start Date *</label>
<input v-model="form.start_date" type="date" class="input" />
</div>
<div>
<label class="label">Target End Date</label>
<input v-model="form.end_date" type="date" class="input" />
</div>
<div>
<label class="label">Implementing Office</label>
<input v-model="form.implementing_office" class="input" />
</div>
<div>
<label class="label">Contractor</label>
<input v-model="form.contractor" class="input" />
</div>
<div class="col-span-2">
<label class="label">Location</label>
<input v-model="form.location" class="input" />
</div>
<div class="col-span-2">
<label class="label">Description</label>
<textarea v-model="form.description" class="input h-20"></textarea>
</div>
</div>
<div class="flex justify-end gap-2 mt-4">
<button @click="showModal = false" class="btn-secondary">Cancel</button>
<button @click="saveProject" class="btn-primary">Save</button>
</div>
</div>
</div>
</div>
</template>