Files
BarangaySystem/resources/js/Pages/Barangay/DocumentRequestDetail.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

189 lines
8.4 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';
import { useAuth } from '../../composables/Core/useAuth.js';
usePageTitle('Document Request Detail');
const { isBarangayStaff } = useAuth();
const request = ref(null);
const loading = ref(false);
const urlParams = new URLSearchParams(window.location.search);
const target = urlParams.get('target');
const statusColors = {
PENDING: 'bg-yellow-100 text-yellow-700',
PAYMENT_PENDING: 'bg-orange-100 text-orange-700',
PAID: 'bg-blue-100 text-blue-700',
PROCESSING:'bg-indigo-100 text-indigo-700',
READY: 'bg-teal-100 text-teal-700',
CLAIMED: 'bg-green-100 text-green-700',
CANCELLED: 'bg-red-100 text-red-700',
};
const statusLabels = {
PENDING: 'Pending',
PAYMENT_PENDING: 'Awaiting Payment',
PAID: 'Paid',
PROCESSING:'Processing',
READY: 'Ready for Pickup',
CLAIMED: 'Claimed',
CANCELLED: 'Cancelled',
};
const getStatus = computed(() => request.value?.status?.value ?? request.value?.status ?? '');
const loadRequest = async () => {
loading.value = true;
const res = await executeRequest('/admin/documents/show', 'POST', { target });
if (res.success) request.value = res.data;
loading.value = false;
};
const confirmPayment = async () => {
if (!confirm('Confirm payment received?')) return;
const res = await executeRequest('/admin/documents/confirm-payment', 'POST', {
target,
amount_paid: request.value?.base_fee ?? 0,
payment_method: 'CASH',
});
if (res.success) loadRequest();
};
const markReady = async () => {
if (!confirm('Mark as ready for pickup?')) return;
const res = await executeRequest('/admin/documents/mark-ready', 'POST', { target });
if (res.success) loadRequest();
};
const markClaimed = async () => {
if (!confirm('Mark as claimed by resident?')) return;
const res = await executeRequest('/admin/documents/mark-claimed', 'POST', { target });
if (res.success) loadRequest();
};
const cancel = async () => {
if (!confirm('Cancel this request?')) return;
const res = await executeRequest('/documents/cancel', 'POST', { target });
if (res.success) loadRequest();
};
onMounted(loadRequest);
</script>
<template>
<div class="p-4 max-w-3xl mx-auto">
<button @click="navigate(isBarangayStaff ? '/barangay/managedocumentrequests' : '/barangay/requestdocument')"
class="text-sm text-blue-500 mb-4 inline-flex items-center gap-1">
Back
</button>
<div v-if="loading" class="text-center py-8 text-gray-400">Loading...</div>
<div v-else-if="request">
<!-- Header card -->
<div class="bg-white rounded-xl shadow p-5 mb-4">
<div class="flex justify-between items-start">
<div>
<div class="flex items-center gap-2 mb-1">
<span class="font-mono font-semibold text-blue-600 text-lg">{{ request.request_no }}</span>
<span :class="`text-xs px-2 py-0.5 rounded-full font-medium ${statusColors[getStatus] ?? 'bg-gray-100'}`">
{{ statusLabels[getStatus] ?? getStatus }}
</span>
</div>
<p class="text-xl font-bold text-gray-800">{{ request.document_type?.name ?? request.request_type }}</p>
<p class="text-sm text-gray-500 mt-1">Submitted: {{ request.created_at }}</p>
</div>
<div class="text-right">
<div class="text-2xl font-bold text-green-600">
{{ request.base_fee > 0 ? `${Number(request.base_fee).toFixed(2)}` : 'Free' }}
</div>
<div class="text-xs text-gray-400">Fee</div>
</div>
</div>
</div>
<!-- Requester info -->
<div class="bg-white rounded-xl shadow p-5 mb-4">
<h2 class="font-semibold text-gray-700 mb-3 border-b pb-2">Requester Information</h2>
<div class="grid grid-cols-2 gap-3 text-sm">
<div><span class="text-gray-400">Name:</span> {{ request.resident?.fullname ?? request.requester_name ?? '—' }}</div>
<div><span class="text-gray-400">Purok:</span> {{ request.resident?.purok ?? '—' }}</div>
<div class="col-span-2"><span class="text-gray-400">Purpose:</span> {{ request.purpose ?? '—' }}</div>
<div v-if="request.remarks" class="col-span-2">
<span class="text-gray-400">Remarks:</span> {{ request.remarks }}
</div>
</div>
</div>
<!-- Payment history -->
<div v-if="request.payments && request.payments.length" class="bg-white rounded-xl shadow p-5 mb-4">
<h2 class="font-semibold text-gray-700 mb-3 border-b pb-2">Payment Records</h2>
<div v-for="p in request.payments" :key="p.id" class="flex justify-between text-sm py-1 border-b last:border-0">
<span>{{ p.payment_method }} {{ p.created_at }}</span>
<span class="font-semibold text-green-600">{{ Number(p.amount_paid).toFixed(2) }}</span>
</div>
</div>
<!-- Staff actions -->
<div v-if="isBarangayStaff" class="bg-white rounded-xl shadow p-5">
<h2 class="font-semibold text-gray-700 mb-3">Actions</h2>
<div class="flex flex-wrap gap-2">
<button
v-if="['PENDING','PAYMENT_PENDING'].includes(getStatus)"
@click="confirmPayment"
class="btn-sm bg-green-500 text-white">
Confirm Payment
</button>
<button
v-if="['PAID','PROCESSING'].includes(getStatus)"
@click="markReady"
class="btn-sm bg-teal-500 text-white">
Mark Ready for Pickup
</button>
<button
v-if="getStatus === 'READY'"
@click="markClaimed"
class="btn-sm bg-blue-500 text-white">
Mark Claimed
</button>
<button
v-if="!['CLAIMED','CANCELLED'].includes(getStatus)"
@click="cancel"
class="btn-sm bg-red-100 text-red-600">
Cancel Request
</button>
<p v-if="['CLAIMED','CANCELLED'].includes(getStatus)" class="text-sm text-gray-400">
This request is {{ statusLabels[getStatus].toLowerCase() }}. No further actions available.
</p>
</div>
</div>
<!-- Resident view: status tracker -->
<div v-else class="bg-white rounded-xl shadow p-5">
<h2 class="font-semibold text-gray-700 mb-3">Request Progress</h2>
<div class="flex items-center gap-1 text-xs">
<template v-for="(step, i) in ['PENDING','PAID','PROCESSING','READY','CLAIMED']" :key="step">
<div :class="`px-2 py-1 rounded font-medium ${['PENDING','PAYMENT_PENDING','PAID','PROCESSING','READY','CLAIMED'].indexOf(getStatus) >= i ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-400'}`">
{{ statusLabels[step] ?? step }}
</div>
<div v-if="i < 4" class="flex-1 h-0.5 bg-gray-200"></div>
</template>
</div>
<p v-if="getStatus === 'READY'" class="mt-3 text-sm text-teal-600 font-medium">
Your document is ready for pickup at the barangay hall.
</p>
<p v-else-if="getStatus === 'CANCELLED'" class="mt-3 text-sm text-red-500">
This request has been cancelled.
</p>
</div>
</div>
<p v-else class="text-center text-gray-400 py-8">Request not found.</p>
</div>
</template>