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

153 lines
7.8 KiB
Vue

<script setup>
import { ref, onMounted } from 'vue';
import { usePageTitle } from '../../composables/Core/usePageTitle';
import { executeRequest } from '../../utils/executeRequest.js';
usePageTitle('Blotter Detail');
const blotter = ref(null);
const hearings = ref([]);
const loading = ref(false);
const showHearingModal = ref(false);
const hearingForm = ref({ hearing_date: '', officer_id: '', notes: '' });
const urlParams = new URLSearchParams(window.location.search);
const target = urlParams.get('target');
const statusLabels = {
FILED: 'Filed', FOR_HEARING: 'For Hearing', SETTLED: 'Settled',
RESOLVED: 'Resolved', DISMISSED: 'Dismissed', ENDORSED: 'Endorsed',
};
const statusColors = {
FILED: 'bg-blue-100 text-blue-700', FOR_HEARING: 'bg-yellow-100 text-yellow-700',
SETTLED: 'bg-green-100 text-green-700', RESOLVED: 'bg-teal-100 text-teal-700',
DISMISSED: 'bg-gray-100 text-gray-700', ENDORSED: 'bg-purple-100 text-purple-700',
};
const loadData = async () => {
loading.value = true;
const [bRes, hRes] = await Promise.all([
executeRequest('/blotters/show', 'POST', { target }),
executeRequest('/blotters/hearings', 'POST', { blotter: target }),
]);
if (bRes.success) blotter.value = bRes.data;
if (hRes.success) hearings.value = hRes.data;
loading.value = false;
};
const updateStatus = async (status) => {
if (!confirm(`Change status to ${statusLabels[status]}?`)) return;
await executeRequest('/blotters/status', 'POST', { target, status });
loadData();
};
const scheduleHearing = async () => {
await executeRequest('/blotters/hearings/schedule', 'POST', { blotter: target, ...hearingForm.value });
showHearingModal.value = false;
hearingForm.value = { hearing_date: '', officer_id: '', notes: '' };
loadData();
};
const getStatus = () => blotter.value?.status?.value ?? blotter.value?.status;
onMounted(loadData);
</script>
<template>
<div class="p-4 max-w-4xl mx-auto">
<div v-if="loading" class="text-center py-8 text-gray-400">Loading...</div>
<div v-else-if="blotter">
<div class="flex items-start justify-between mb-4">
<div>
<h1 class="text-2xl font-bold">Blotter {{ blotter.blotter_no }}</h1>
<p class="text-gray-500 text-sm">Filed: {{ blotter.complaint_date }}</p>
</div>
<span :class="`px-3 py-1 rounded-full text-sm font-medium ${statusColors[getStatus()] ?? 'bg-gray-100'}`">
{{ statusLabels[getStatus()] ?? getStatus() }}
</span>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-5">
<div class="bg-white rounded-lg shadow p-4">
<h3 class="font-semibold text-gray-700 mb-2 border-b pb-1">Complainant</h3>
<p class="font-medium">{{ blotter.complainant_name }}</p>
<p class="text-sm text-gray-500">{{ blotter.complainant_contact }}</p>
<p class="text-sm text-gray-500">{{ blotter.complainant_address }}</p>
</div>
<div class="bg-white rounded-lg shadow p-4">
<h3 class="font-semibold text-gray-700 mb-2 border-b pb-1">Respondent</h3>
<p class="font-medium">{{ blotter.respondent_name }}</p>
<p class="text-sm text-gray-500">{{ blotter.respondent_contact }}</p>
<p class="text-sm text-gray-500">{{ blotter.respondent_address }}</p>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4 mb-4">
<h3 class="font-semibold text-gray-700 mb-2">Incident Details</h3>
<div class="grid grid-cols-2 gap-2 text-sm mb-3">
<div><span class="text-gray-500">Type:</span> {{ blotter.incident_type }}</div>
<div><span class="text-gray-500">Date:</span> {{ blotter.incident_date }}</div>
<div class="col-span-2"><span class="text-gray-500">Location:</span> {{ blotter.incident_location }}</div>
</div>
<div class="bg-gray-50 rounded p-3 text-sm text-gray-700">{{ blotter.narrative }}</div>
</div>
<div v-if="blotter.resolution" class="bg-green-50 rounded-lg p-4 mb-4">
<h3 class="font-semibold text-green-700 mb-1">Resolution</h3>
<p class="text-sm">{{ blotter.resolution }}</p>
</div>
<!-- Hearings -->
<div class="bg-white rounded-lg shadow p-4 mb-4">
<div class="flex items-center justify-between mb-3">
<h3 class="font-semibold text-gray-700">Hearings ({{ hearings.length }})</h3>
<button @click="showHearingModal = true" class="btn-sm btn-primary">Schedule Hearing</button>
</div>
<div v-for="h in hearings" :key="h.id" class="border-b py-2 text-sm">
<div class="flex justify-between">
<span class="font-medium">{{ h.hearing_date }}</span>
<span :class="`text-xs px-2 py-0.5 rounded ${h.status === 'HELD' ? 'bg-green-100 text-green-700' : h.status === 'POSTPONED' ? 'bg-orange-100 text-orange-700' : 'bg-blue-100 text-blue-700'}`">{{ h.status }}</span>
</div>
<p v-if="h.notes" class="text-gray-500 mt-1">{{ h.notes }}</p>
</div>
<p v-if="!hearings.length" class="text-gray-400 text-sm">No hearings scheduled.</p>
</div>
<!-- Actions -->
<div class="bg-white rounded-lg shadow p-4">
<h3 class="font-semibold text-gray-700 mb-3">Update Status</h3>
<div class="flex flex-wrap gap-2">
<button v-if="getStatus() === 'FILED'" @click="updateStatus('FOR_HEARING')" class="btn-sm bg-yellow-500 text-white">Set For Hearing</button>
<button v-if="['FILED','FOR_HEARING'].includes(getStatus())" @click="updateStatus('SETTLED')" class="btn-sm bg-green-500 text-white">Mark Settled</button>
<button v-if="['FILED','FOR_HEARING','SETTLED'].includes(getStatus())" @click="updateStatus('RESOLVED')" class="btn-sm bg-teal-500 text-white">Mark Resolved</button>
<button v-if="['FILED','FOR_HEARING'].includes(getStatus())" @click="updateStatus('DISMISSED')" class="btn-sm bg-gray-500 text-white">Dismiss</button>
<button v-if="['FILED','FOR_HEARING'].includes(getStatus())" @click="updateStatus('ENDORSED')" class="btn-sm bg-purple-500 text-white">Endorse</button>
</div>
</div>
<!-- Schedule Hearing Modal -->
<div v-if="showHearingModal" class="modal-overlay" @click.self="showHearingModal = false">
<div class="modal-box">
<h2 class="text-lg font-bold mb-4">Schedule a Hearing</h2>
<div class="space-y-3">
<div>
<label class="label">Hearing Date & Time *</label>
<input v-model="hearingForm.hearing_date" type="datetime-local" class="input" />
</div>
<div>
<label class="label">Notes</label>
<textarea v-model="hearingForm.notes" class="input h-20"></textarea>
</div>
</div>
<div class="flex justify-end gap-2 mt-4">
<button @click="showHearingModal = false" class="btn-secondary">Cancel</button>
<button @click="scheduleHearing" class="btn-primary">Schedule</button>
</div>
</div>
</div>
</div>
<p v-else class="text-center text-gray-400 py-8">Blotter not found.</p>
</div>
</template>