add reservation mechanic to dev #35
@@ -224,8 +224,9 @@ async def get_reservation(
|
|||||||
unit_ids = [a.unit_id for a in assignments]
|
unit_ids = [a.unit_id for a in assignments]
|
||||||
units = db.query(RosterUnit).filter(RosterUnit.id.in_(unit_ids)).all() if unit_ids else []
|
units = db.query(RosterUnit).filter(RosterUnit.id.in_(unit_ids)).all() if unit_ids else []
|
||||||
units_by_id = {u.id: u for u in units}
|
units_by_id = {u.id: u for u in units}
|
||||||
# Build power_type lookup from assignments
|
# Build power_type and notes lookup from assignments
|
||||||
power_type_map = {a.unit_id: a.power_type for a in assignments}
|
power_type_map = {a.unit_id: a.power_type for a in assignments}
|
||||||
|
notes_map = {a.unit_id: a.notes for a in assignments}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": reservation.id,
|
"id": reservation.id,
|
||||||
@@ -245,7 +246,8 @@ async def get_reservation(
|
|||||||
"id": uid,
|
"id": uid,
|
||||||
"last_calibrated": units_by_id[uid].last_calibrated.isoformat() if uid in units_by_id and units_by_id[uid].last_calibrated else None,
|
"last_calibrated": units_by_id[uid].last_calibrated.isoformat() if uid in units_by_id and units_by_id[uid].last_calibrated else None,
|
||||||
"deployed": units_by_id[uid].deployed if uid in units_by_id else False,
|
"deployed": units_by_id[uid].deployed if uid in units_by_id else False,
|
||||||
"power_type": power_type_map.get(uid)
|
"power_type": power_type_map.get(uid),
|
||||||
|
"notes": notes_map.get(uid)
|
||||||
}
|
}
|
||||||
for uid in unit_ids
|
for uid in unit_ids
|
||||||
]
|
]
|
||||||
@@ -343,6 +345,7 @@ async def assign_units_to_reservation(
|
|||||||
unit_ids = data.get("unit_ids", [])
|
unit_ids = data.get("unit_ids", [])
|
||||||
# Optional per-unit power types: {"BE17354": "ac", "BE9441": "solar"}
|
# Optional per-unit power types: {"BE17354": "ac", "BE9441": "solar"}
|
||||||
power_types = data.get("power_types", {})
|
power_types = data.get("power_types", {})
|
||||||
|
location_notes = data.get("location_notes", {})
|
||||||
|
|
||||||
# Verify units exist (allow empty list to clear all assignments)
|
# Verify units exist (allow empty list to clear all assignments)
|
||||||
if unit_ids:
|
if unit_ids:
|
||||||
@@ -384,7 +387,8 @@ async def assign_units_to_reservation(
|
|||||||
reservation_id=reservation_id,
|
reservation_id=reservation_id,
|
||||||
unit_id=unit_id,
|
unit_id=unit_id,
|
||||||
assignment_source="filled" if reservation.assignment_type == "quantity" else "specific",
|
assignment_source="filled" if reservation.assignment_type == "quantity" else "specific",
|
||||||
power_type=power_types.get(unit_id)
|
power_type=power_types.get(unit_id),
|
||||||
|
notes=location_notes.get(unit_id)
|
||||||
)
|
)
|
||||||
db.add(assignment)
|
db.add(assignment)
|
||||||
|
|
||||||
@@ -538,6 +542,7 @@ async def get_reservations_list(
|
|||||||
{
|
{
|
||||||
"id": a.unit_id,
|
"id": a.unit_id,
|
||||||
"power_type": a.power_type,
|
"power_type": a.power_type,
|
||||||
|
"notes": a.notes,
|
||||||
"deployed": units_by_id[a.unit_id].deployed if a.unit_id in units_by_id else False,
|
"deployed": units_by_id[a.unit_id].deployed if a.unit_id in units_by_id else False,
|
||||||
"last_calibrated": units_by_id[a.unit_id].last_calibrated if a.unit_id in units_by_id else None,
|
"last_calibrated": units_by_id[a.unit_id].last_calibrated if a.unit_id in units_by_id else None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -389,7 +389,7 @@
|
|||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Active Reservations</h2>
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Active Reservations</h2>
|
||||||
<div id="reservations-list"
|
<div id="reservations-list"
|
||||||
hx-get="/api/fleet-calendar/reservations-list?year={{ start_year }}&month={{ start_month }}&device_type={{ device_type }}"
|
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">
|
hx-swap="innerHTML">
|
||||||
<p class="text-gray-500">Loading reservations...</p>
|
<p class="text-gray-500">Loading reservations...</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -417,7 +417,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sub-tab: Reservations list -->
|
<!-- 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">
|
<div class="flex items-center justify-between">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Project Reservations</h2>
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Project Reservations</h2>
|
||||||
<button onclick="plannerReset(); switchPlannerTab('assign')"
|
<button onclick="plannerReset(); switchPlannerTab('assign')"
|
||||||
@@ -428,7 +428,7 @@
|
|||||||
New
|
New
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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-get="/api/fleet-calendar/reservations-list?year={{ start_year }}&month={{ start_month }}&device_type={{ device_type }}"
|
||||||
hx-trigger="load"
|
hx-trigger="load"
|
||||||
hx-swap="innerHTML">
|
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>
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white" id="planner-form-title">New Reservation</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Metadata fields: only shown when creating a new reservation -->
|
||||||
|
<div id="planner-meta-fields">
|
||||||
|
|
||||||
<!-- Name -->
|
<!-- 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>
|
<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"
|
<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">
|
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>
|
||||||
|
|
||||||
<!-- Device Type -->
|
<!-- 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>
|
<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">
|
<div class="flex rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden">
|
||||||
<label class="flex-1 cursor-pointer">
|
<label class="flex-1 cursor-pointer">
|
||||||
@@ -474,7 +477,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Project -->
|
<!-- 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>
|
<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"
|
<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">
|
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>
|
</div>
|
||||||
|
|
||||||
<!-- Dates -->
|
<!-- Dates -->
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3 mb-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Start Date *</label>
|
<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"
|
<input type="date" id="planner-start"
|
||||||
@@ -502,7 +505,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Color -->
|
<!-- Color -->
|
||||||
<div>
|
<div class="mb-4">
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Color</label>
|
<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">
|
<div class="flex gap-2" id="planner-colors">
|
||||||
{% for color in ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899'] %}
|
{% for color in ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899'] %}
|
||||||
@@ -516,12 +519,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Estimated Units Needed -->
|
<!-- 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>
|
<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"
|
<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">
|
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>
|
||||||
|
|
||||||
|
</div><!-- end #planner-meta-fields -->
|
||||||
|
|
||||||
<!-- Monitoring Locations -->
|
<!-- Monitoring Locations -->
|
||||||
<div class="flex items-center justify-between mt-2">
|
<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>
|
<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);
|
const chevron = document.getElementById('chevron-' + id);
|
||||||
if (!detail) return;
|
if (!detail) return;
|
||||||
const isHidden = detail.classList.contains('hidden');
|
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)' : '';
|
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) {
|
async function deleteReservation(id, name) {
|
||||||
if (!confirm(`Delete reservation "${name}"?\n\nThis will remove all unit assignments.`)) return;
|
if (!confirm(`Delete reservation "${name}"?\n\nThis will remove all unit assignments.`)) return;
|
||||||
@@ -1156,6 +1160,7 @@ function switchPlannerTab(tab) {
|
|||||||
function switchTab(tab) {
|
function switchTab(tab) {
|
||||||
document.getElementById('view-calendar').classList.toggle('hidden', tab !== 'calendar');
|
document.getElementById('view-calendar').classList.toggle('hidden', tab !== 'calendar');
|
||||||
document.getElementById('view-planner').classList.toggle('hidden', tab !== 'planner');
|
document.getElementById('view-planner').classList.toggle('hidden', tab !== 'planner');
|
||||||
|
if (tab === 'calendar') htmx.trigger(document.body, 'calendar-reservations-refresh');
|
||||||
|
|
||||||
['calendar', 'planner'].forEach(t => {
|
['calendar', 'planner'].forEach(t => {
|
||||||
const btn = document.getElementById(`tab-btn-${t}`);
|
const btn = document.getElementById(`tab-btn-${t}`);
|
||||||
@@ -1452,6 +1457,7 @@ function plannerReset() {
|
|||||||
const titleEl = document.getElementById('planner-form-title');
|
const titleEl = document.getElementById('planner-form-title');
|
||||||
if (titleEl) titleEl.textContent = 'New Reservation';
|
if (titleEl) titleEl.textContent = 'New Reservation';
|
||||||
document.getElementById('planner-save-btn').textContent = 'Save Reservation';
|
document.getElementById('planner-save-btn').textContent = 'Save Reservation';
|
||||||
|
document.getElementById('planner-meta-fields').style.display = '';
|
||||||
plannerRenderSlots();
|
plannerRenderSlots();
|
||||||
plannerRenderUnits();
|
plannerRenderUnits();
|
||||||
}
|
}
|
||||||
@@ -1504,13 +1510,17 @@ async function plannerSave() {
|
|||||||
// Always call assign-units (even with empty list) — endpoint does a full replace
|
// Always call assign-units (even with empty list) — endpoint does a full replace
|
||||||
const unitIds = filledSlots.map(s => s.unit_id);
|
const unitIds = filledSlots.map(s => s.unit_id);
|
||||||
const powerTypes = {};
|
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(
|
const assignResp = await fetch(
|
||||||
`/api/fleet-calendar/reservations/${reservationId}/assign-units`,
|
`/api/fleet-calendar/reservations/${reservationId}/assign-units`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
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();
|
const assignResult = await assignResp.json();
|
||||||
@@ -1553,9 +1563,11 @@ async function openPlanner(reservationId) {
|
|||||||
for (const u of (res.assigned_units || [])) {
|
for (const u of (res.assigned_units || [])) {
|
||||||
plannerState.slots.push({ unit_id: u.id, power_type: u.power_type || null, notes: u.notes || null });
|
plannerState.slots.push({ unit_id: u.id, power_type: u.power_type || null, notes: u.notes || null });
|
||||||
}
|
}
|
||||||
|
|
||||||
const titleEl = document.getElementById('planner-form-title');
|
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-save-btn').textContent = 'Save Changes';
|
||||||
|
document.getElementById('planner-meta-fields').style.display = 'none';
|
||||||
plannerRenderSlots();
|
plannerRenderSlots();
|
||||||
if (res.start_date && res.end_date) plannerLoadUnits();
|
if (res.start_date && res.end_date) plannerLoadUnits();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
{% set card_id = "res-card-" ~ res.id %}
|
{% set card_id = "res-card-" ~ res.id %}
|
||||||
{% set detail_id = "res-detail-" ~ res.id %}
|
{% set detail_id = "res-detail-" ~ res.id %}
|
||||||
|
|
||||||
<div class="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden"
|
<div class="rounded-lg border border-gray-200 dark:border-gray-700"
|
||||||
style="border-left: 4px solid {{ res.color }};">
|
style="border-left: 4px solid {{ res.color }};">
|
||||||
|
|
||||||
<!-- Header row (always visible, clickable) -->
|
<!-- Header row (always visible, clickable) -->
|
||||||
<div class="res-card-header flex items-center justify-between p-4 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors select-none"
|
<div class="res-card-header flex items-center justify-between p-4 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors select-none"
|
||||||
data-res-id="{{ res.id }}">
|
data-res-id="{{ res.id }}"
|
||||||
|
onclick="toggleResCard('{{ res.id }}')">
|
||||||
|
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-center gap-2 flex-wrap">
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
@@ -59,30 +60,30 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action buttons (stop propagation so clicks don't toggle card) -->
|
<!-- Action buttons (stop propagation so clicks don't toggle card) -->
|
||||||
<div class="flex items-center gap-1 flex-shrink-0" onclick="event.stopPropagation(); event.preventDefault();">
|
<div class="flex items-center gap-1 flex-shrink-0">
|
||||||
<button onclick="openPlanner('{{ res.id }}')"
|
<button onclick="event.stopPropagation(); openPlanner('{{ res.id }}')"
|
||||||
class="p-2 text-gray-400 hover:text-green-600 dark:hover:text-green-400 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
|
class="p-2 text-gray-400 hover:text-green-600 dark:hover:text-green-400 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||||
title="Plan units">
|
title="Plan units">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="editReservation('{{ res.id }}')"
|
<button onclick="event.stopPropagation(); editReservation('{{ res.id }}')"
|
||||||
class="p-2 text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
|
class="p-2 text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||||
title="Edit">
|
title="Edit">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="deleteReservation('{{ res.id }}', '{{ res.name }}')"
|
<button onclick="event.stopPropagation(); deleteReservation('{{ res.id }}', '{{ res.name }}')"
|
||||||
class="p-2 text-gray-400 hover:text-red-600 dark:hover:text-red-400 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
|
class="p-2 text-gray-400 hover:text-red-600 dark:hover:text-red-400 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||||
title="Delete">
|
title="Delete">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<!-- Chevron -->
|
<!-- Chevron (not in stopPropagation zone so clicking it still toggles the card) -->
|
||||||
<svg id="chevron-{{ res.id }}" class="w-4 h-4 text-gray-400 transition-transform duration-200 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg id="chevron-{{ res.id }}" class="w-4 h-4 text-gray-400 transition-transform duration-200 ml-1 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,23 +117,28 @@
|
|||||||
<p class="text-xs font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500 mb-2">Monitoring Locations</p>
|
<p class="text-xs font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500 mb-2">Monitoring Locations</p>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
{% for u in item.assigned_units %}
|
{% for u in item.assigned_units %}
|
||||||
<div class="flex items-center gap-3 px-3 py-1.5 rounded bg-white dark:bg-slate-700 border border-gray-100 dark:border-gray-600 text-sm">
|
<div class="rounded bg-white dark:bg-slate-700 border border-gray-100 dark:border-gray-600 text-sm">
|
||||||
<span class="text-gray-400 dark:text-gray-500 text-xs w-12 flex-shrink-0">Loc. {{ loop.index }}</span>
|
<div class="flex items-center gap-3 px-3 py-1.5">
|
||||||
<button onclick="openUnitDetailModal('{{ u.id }}')"
|
<span class="text-gray-400 dark:text-gray-500 text-xs w-12 flex-shrink-0">Loc. {{ loop.index }}</span>
|
||||||
class="font-medium text-blue-600 dark:text-blue-400 hover:underline">{{ u.id }}</button>
|
<button onclick="openUnitDetailModal('{{ u.id }}')"
|
||||||
<span class="flex-1"></span>
|
class="font-medium text-blue-600 dark:text-blue-400 hover:underline">{{ u.id }}</button>
|
||||||
{% if u.power_type == 'ac' %}
|
<span class="flex-1"></span>
|
||||||
<span class="text-xs px-1.5 py-0.5 bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 rounded">A/C</span>
|
{% if u.power_type == 'ac' %}
|
||||||
{% elif u.power_type == 'solar' %}
|
<span class="text-xs px-1.5 py-0.5 bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 rounded">A/C</span>
|
||||||
<span class="text-xs px-1.5 py-0.5 bg-yellow-50 dark:bg-yellow-900/20 text-yellow-600 dark:text-yellow-400 rounded">Solar</span>
|
{% elif u.power_type == 'solar' %}
|
||||||
{% endif %}
|
<span class="text-xs px-1.5 py-0.5 bg-yellow-50 dark:bg-yellow-900/20 text-yellow-600 dark:text-yellow-400 rounded">Solar</span>
|
||||||
{% if u.deployed %}
|
{% endif %}
|
||||||
<span class="text-xs px-1.5 py-0.5 bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 rounded">Deployed</span>
|
{% if u.deployed %}
|
||||||
{% else %}
|
<span class="text-xs px-1.5 py-0.5 bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 rounded">Deployed</span>
|
||||||
<span class="text-xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-600 text-gray-500 dark:text-gray-400 rounded">Benched</span>
|
{% else %}
|
||||||
{% endif %}
|
<span class="text-xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-600 text-gray-500 dark:text-gray-400 rounded">Benched</span>
|
||||||
{% if u.last_calibrated %}
|
{% endif %}
|
||||||
<span class="text-xs text-gray-400 dark:text-gray-500">Cal: {{ u.last_calibrated.strftime('%b %d, %Y') }}</span>
|
{% if u.last_calibrated %}
|
||||||
|
<span class="text-xs text-gray-400 dark:text-gray-500">Cal: {{ u.last_calibrated.strftime('%b %d, %Y') }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if u.notes %}
|
||||||
|
<p class="px-3 pb-1.5 text-xs text-gray-400 dark:text-gray-500 italic">{{ u.notes }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
Reference in New Issue
Block a user