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
153 lines
7.8 KiB
Vue
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>
|