Files
Verde-Web/resources/views/admin/trucks.blade.php

210 lines
9.1 KiB
PHP

@extends('admin.layouts.app', ['pageTitle' => 'Trucks'])
@section('page')
<div class="mx-auto max-w-7xl">
<header class="mb-6 flex items-end justify-between">
<div>
<h2 class="text-2xl font-semibold tracking-tight text-neutral-900">Trucks</h2>
<p class="mt-1 text-sm text-neutral-500">Vehicles assigned to collection teams.</p>
</div>
<button id="new-btn" class="btn-primary">+ New Truck</button>
</header>
<div class="card mb-4 flex flex-wrap items-center gap-3 p-4">
<select id="filter-status" class="form-select w-44">
<option value="">All statuses</option>
<option value="active">Active</option>
<option value="maintenance">Maintenance</option>
<option value="retired">Retired</option>
</select>
<input id="filter-q" type="search" placeholder="Search plate, model…" class="form-input flex-1 min-w-[200px]">
<button id="filter-apply" class="btn-primary">Apply</button>
</div>
<div class="table-wrap">
<table class="table">
<thead>
<tr>
<th>Plate #</th>
<th>Model</th>
<th>Capacity</th>
<th>Status</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody id="rows">
<tr><td colspan="5" class="py-10 text-center text-sm text-neutral-400">Loading…</td></tr>
</tbody>
</table>
</div>
</div>
<div id="form-modal" class="hidden">
<div class="slide-over-mask" data-close></div>
<div class="slide-over-panel translate-x-0 p-6">
<div class="mb-4 flex items-center justify-between">
<h3 id="modal-title" class="text-base font-semibold text-neutral-900">New truck</h3>
<button data-close class="text-neutral-400 hover:text-neutral-600">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
</button>
</div>
<form id="create-form" class="space-y-4">
<div><label class="form-label">Plate number</label><input name="plate_number" id="form-plate" required class="form-input" placeholder="NCR-1234"></div>
<div><label class="form-label">Model</label><input name="model" class="form-input" placeholder="Isuzu Forward"></div>
<div><label class="form-label">Capacity (kg)</label><input name="capacity_kg" type="number" min="0" class="form-input"></div>
<div><label class="form-label">Status</label>
<select name="status" class="form-select">
<option value="active">Active</option>
<option value="maintenance">Maintenance</option>
<option value="retired">Retired</option>
</select>
</div>
<div><label class="form-label">Notes</label><textarea name="notes" rows="2" class="form-textarea"></textarea></div>
<div class="flex justify-end gap-2 pt-2">
<button type="button" data-close class="btn-ghost">Cancel</button>
<button type="submit" id="submit-btn" class="btn-primary">Create</button>
</div>
<div class="pt-2 border-t border-neutral-100 mt-4" id="delete-container">
<button type="button" id="delete-btn" class="btn-danger w-full hidden">Delete Truck</button>
</div>
</form>
</div>
</div>
<script type="module">
const rows = document.getElementById('rows');
const modal = document.getElementById('form-modal');
const createForm = document.getElementById('create-form');
const deleteBtn = document.getElementById('delete-btn');
const submitBtn = document.getElementById('submit-btn');
const modalTitle = document.getElementById('modal-title');
const formPlate = document.getElementById('form-plate');
let editingTruckId = null;
function badge(s) {
const m = { active: 'badge-active', maintenance: 'badge-pending', retired: 'badge-rejected' };
return `<span class="badge ${m[s] || 'badge-inactive'}">${s}</span>`;
}
async function editTruck(id) {
editingTruckId = id;
modalTitle.textContent = 'Edit truck';
submitBtn.textContent = 'Save Changes';
deleteBtn.classList.remove('hidden');
// Plate number is read-only after creation
formPlate.readOnly = true;
formPlate.classList.add('bg-neutral-100');
const res = await window.Verde.apiFetch(`/api/v1/admin/trucks/${id}`);
if (!res.ok) {
window.Verde.toast('Failed to load truck details', 'error');
return;
}
const t = res.body.data;
createForm.querySelector('[name="plate_number"]').value = t.plate_number;
createForm.querySelector('[name="model"]').value = t.model ?? '';
createForm.querySelector('[name="capacity_kg"]').value = t.capacity_kg ?? '';
createForm.querySelector('[name="status"]').value = t.status;
createForm.querySelector('[name="notes"]').value = t.notes ?? '';
modal.classList.remove('hidden');
}
async function load() {
rows.innerHTML = `<tr><td colspan="5" class="py-10 text-center text-sm text-neutral-400">Loading…</td></tr>`;
const params = new URLSearchParams();
const s = document.getElementById('filter-status').value;
const q = document.getElementById('filter-q').value.trim();
if (s) params.set('status', s);
if (q) params.set('q', q);
params.set('per_page', '50');
const res = await window.Verde.apiFetch(`/api/v1/admin/trucks?${params}`);
if (!res.ok) { rows.innerHTML = `<tr><td colspan="5" class="py-10 text-center text-sm text-red-500">Failed to load.</td></tr>`; return; }
const items = res.body.data ?? [];
if (items.length === 0) { rows.innerHTML = `<tr><td colspan="5" class="py-10 text-center text-sm text-neutral-400">No trucks yet.</td></tr>`; return; }
rows.innerHTML = items.map(t => `
<tr>
<td class="font-mono text-sm font-medium text-neutral-900">${window.Verde.escapeHtml(t.plate_number)}</td>
<td>${window.Verde.escapeHtml(t.model ?? '—')}</td>
<td>${t.capacity_kg ? `${t.capacity_kg.toLocaleString()} kg` : '—'}</td>
<td>${badge(t.status)}</td>
<td class="text-right">
<button data-id="${t.id}" data-action="edit" class="btn-ghost px-3 py-1 text-xs">Edit</button>
</td>
</tr>
`).join('');
rows.querySelectorAll('button[data-action="edit"]').forEach(btn => {
btn.addEventListener('click', () => editTruck(btn.dataset.id));
});
}
document.getElementById('new-btn').addEventListener('click', () => {
editingTruckId = null;
createForm.reset();
modalTitle.textContent = 'New truck';
submitBtn.textContent = 'Create';
deleteBtn.classList.add('hidden');
formPlate.readOnly = false;
formPlate.classList.remove('bg-neutral-100');
modal.classList.remove('hidden');
});
modal.querySelectorAll('[data-close]').forEach(el => el.addEventListener('click', () => modal.classList.add('hidden')));
document.getElementById('filter-apply').addEventListener('click', load);
document.getElementById('filter-status').addEventListener('change', load);
createForm.addEventListener('submit', async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
const payload = Object.fromEntries(fd.entries());
if (payload.capacity_kg) payload.capacity_kg = parseInt(payload.capacity_kg, 10);
else delete payload.capacity_kg;
if (!payload.model) payload.model = null;
if (!payload.notes) payload.notes = null;
const url = editingTruckId ? `/api/v1/admin/trucks/${editingTruckId}` : '/api/v1/admin/trucks';
const method = editingTruckId ? 'PATCH' : 'POST';
const res = await window.Verde.apiFetch(url, {
method: method,
body: JSON.stringify(payload),
});
if (res.ok) {
window.Verde.toast(editingTruckId ? 'Truck updated' : 'Truck created', 'success');
modal.classList.add('hidden');
createForm.reset();
load();
} else {
window.Verde.toast(res.body?.message ?? 'Save failed', 'error');
}
});
deleteBtn.addEventListener('click', async () => {
if (!editingTruckId) return;
if (!confirm('Are you sure you want to delete this truck? This action cannot be undone.')) return;
const res = await window.Verde.apiFetch(`/api/v1/admin/trucks/${editingTruckId}`, {
method: 'DELETE'
});
if (res.ok) {
window.Verde.toast('Truck deleted successfully', 'success');
modal.classList.add('hidden');
createForm.reset();
load();
} else {
window.Verde.toast(res.body?.message ?? 'Delete failed', 'error');
}
});
load();
</script>
@endsection