Feat: expands project reservation system.
-Reservation list view -expandable project cards
This commit is contained in:
@@ -389,7 +389,7 @@
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Active Reservations</h2>
|
||||
<div id="reservations-list"
|
||||
hx-get="/api/fleet-calendar/reservations-list?year={{ start_year }}&month={{ start_month }}&device_type={{ device_type }}"
|
||||
hx-trigger="load"
|
||||
hx-trigger="calendar-reservations-refresh from:body"
|
||||
hx-swap="innerHTML">
|
||||
<p class="text-gray-500">Loading reservations...</p>
|
||||
</div>
|
||||
@@ -417,7 +417,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Sub-tab: Reservations list -->
|
||||
<div id="ptab-list" class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6 flex flex-col gap-4 flex-1">
|
||||
<div id="ptab-list" class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6 flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Project Reservations</h2>
|
||||
<button onclick="plannerReset(); switchPlannerTab('assign')"
|
||||
@@ -428,7 +428,7 @@
|
||||
New
|
||||
</button>
|
||||
</div>
|
||||
<div id="planner-reservations-list" class="overflow-y-auto" style="max-height: 60vh;"
|
||||
<div id="planner-reservations-list" class="overflow-y-visible"
|
||||
hx-get="/api/fleet-calendar/reservations-list?year={{ start_year }}&month={{ start_month }}&device_type={{ device_type }}"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
@@ -447,15 +447,18 @@
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white" id="planner-form-title">New Reservation</h2>
|
||||
</div>
|
||||
|
||||
<!-- Metadata fields: only shown when creating a new reservation -->
|
||||
<div id="planner-meta-fields">
|
||||
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Job / Reservation Name *</label>
|
||||
<input type="text" id="planner-name" placeholder="e.g., Pine Street – May Deployment"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<!-- Device Type -->
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Device Type *</label>
|
||||
<div class="flex rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden">
|
||||
<label class="flex-1 cursor-pointer">
|
||||
@@ -474,7 +477,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Project -->
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Link to Project (optional)</label>
|
||||
<select id="planner-project"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500">
|
||||
@@ -486,7 +489,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Dates -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="grid grid-cols-2 gap-3 mb-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Start Date *</label>
|
||||
<input type="date" id="planner-start"
|
||||
@@ -502,7 +505,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Color -->
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Color</label>
|
||||
<div class="flex gap-2" id="planner-colors">
|
||||
{% for color in ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899'] %}
|
||||
@@ -516,12 +519,14 @@
|
||||
</div>
|
||||
|
||||
<!-- Estimated Units Needed -->
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Estimated Units Needed</label>
|
||||
<input type="number" id="planner-est-units" min="1" placeholder="e.g. 5"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
</div><!-- end #planner-meta-fields -->
|
||||
|
||||
<!-- Monitoring Locations -->
|
||||
<div class="flex items-center justify-between mt-2">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">Monitoring Locations</h3>
|
||||
@@ -851,17 +856,16 @@ function toggleResCard(id) {
|
||||
const chevron = document.getElementById('chevron-' + id);
|
||||
if (!detail) return;
|
||||
const isHidden = detail.classList.contains('hidden');
|
||||
detail.classList.toggle('hidden', !isHidden);
|
||||
if (isHidden) {
|
||||
detail.classList.remove('hidden');
|
||||
detail.style.display = 'block';
|
||||
} else {
|
||||
detail.classList.add('hidden');
|
||||
detail.style.display = 'none';
|
||||
}
|
||||
if (chevron) chevron.style.transform = isHidden ? 'rotate(180deg)' : '';
|
||||
}
|
||||
|
||||
// Event delegation for reservation cards (handles HTMX-loaded content)
|
||||
document.addEventListener('click', function(e) {
|
||||
const header = e.target.closest('.res-card-header');
|
||||
if (!header) return;
|
||||
const id = header.dataset.resId;
|
||||
if (id) toggleResCard(id);
|
||||
});
|
||||
|
||||
async function deleteReservation(id, name) {
|
||||
if (!confirm(`Delete reservation "${name}"?\n\nThis will remove all unit assignments.`)) return;
|
||||
@@ -1156,6 +1160,7 @@ function switchPlannerTab(tab) {
|
||||
function switchTab(tab) {
|
||||
document.getElementById('view-calendar').classList.toggle('hidden', tab !== 'calendar');
|
||||
document.getElementById('view-planner').classList.toggle('hidden', tab !== 'planner');
|
||||
if (tab === 'calendar') htmx.trigger(document.body, 'calendar-reservations-refresh');
|
||||
|
||||
['calendar', 'planner'].forEach(t => {
|
||||
const btn = document.getElementById(`tab-btn-${t}`);
|
||||
@@ -1452,6 +1457,7 @@ function plannerReset() {
|
||||
const titleEl = document.getElementById('planner-form-title');
|
||||
if (titleEl) titleEl.textContent = 'New Reservation';
|
||||
document.getElementById('planner-save-btn').textContent = 'Save Reservation';
|
||||
document.getElementById('planner-meta-fields').style.display = '';
|
||||
plannerRenderSlots();
|
||||
plannerRenderUnits();
|
||||
}
|
||||
@@ -1504,13 +1510,17 @@ async function plannerSave() {
|
||||
// Always call assign-units (even with empty list) — endpoint does a full replace
|
||||
const unitIds = filledSlots.map(s => s.unit_id);
|
||||
const powerTypes = {};
|
||||
filledSlots.forEach(s => { if (s.power_type) powerTypes[s.unit_id] = s.power_type; });
|
||||
const locationNotes = {};
|
||||
filledSlots.forEach(s => {
|
||||
if (s.power_type) powerTypes[s.unit_id] = s.power_type;
|
||||
if (s.notes) locationNotes[s.unit_id] = s.notes;
|
||||
});
|
||||
const assignResp = await fetch(
|
||||
`/api/fleet-calendar/reservations/${reservationId}/assign-units`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ unit_ids: unitIds, power_types: powerTypes })
|
||||
body: JSON.stringify({ unit_ids: unitIds, power_types: powerTypes, location_notes: locationNotes })
|
||||
}
|
||||
);
|
||||
const assignResult = await assignResp.json();
|
||||
@@ -1553,9 +1563,11 @@ async function openPlanner(reservationId) {
|
||||
for (const u of (res.assigned_units || [])) {
|
||||
plannerState.slots.push({ unit_id: u.id, power_type: u.power_type || null, notes: u.notes || null });
|
||||
}
|
||||
|
||||
const titleEl = document.getElementById('planner-form-title');
|
||||
if (titleEl) titleEl.textContent = 'Edit: ' + res.name;
|
||||
if (titleEl) titleEl.textContent = res.name;
|
||||
document.getElementById('planner-save-btn').textContent = 'Save Changes';
|
||||
document.getElementById('planner-meta-fields').style.display = 'none';
|
||||
plannerRenderSlots();
|
||||
if (res.start_date && res.end_date) plannerLoadUnits();
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user