v0.2.1. many features added and cleaned up.
This commit is contained in:
@@ -31,22 +31,56 @@
|
||||
<!-- Loading placeholder -->
|
||||
</div>
|
||||
|
||||
<!-- Auto-refresh roster every 10 seconds -->
|
||||
<div hx-get="/partials/roster-table" hx-trigger="load, every 10s" hx-swap="innerHTML">
|
||||
<!-- Initial loading state -->
|
||||
<div class="rounded-xl shadow-lg bg-white dark:bg-slate-800 p-6">
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<div class="animate-pulse flex space-x-4">
|
||||
<div class="flex-1 space-y-4 py-1">
|
||||
<div class="h-4 bg-gray-300 dark:bg-gray-600 rounded w-3/4"></div>
|
||||
<div class="space-y-2">
|
||||
<div class="h-4 bg-gray-300 dark:bg-gray-600 rounded"></div>
|
||||
<div class="h-4 bg-gray-300 dark:bg-gray-600 rounded w-5/6"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Fleet Roster with Tabs -->
|
||||
<div class="rounded-xl shadow-lg bg-white dark:bg-slate-800 p-6">
|
||||
|
||||
<!-- Tab Bar -->
|
||||
<div class="flex border-b border-gray-200 dark:border-gray-700 mb-4">
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium roster-tab-button active-roster-tab"
|
||||
data-endpoint="/partials/roster-deployed"
|
||||
hx-get="/partials/roster-deployed"
|
||||
hx-target="#roster-content"
|
||||
hx-swap="innerHTML">
|
||||
Deployed
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium roster-tab-button"
|
||||
data-endpoint="/partials/roster-benched"
|
||||
hx-get="/partials/roster-benched"
|
||||
hx-target="#roster-content"
|
||||
hx-swap="innerHTML">
|
||||
Benched
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium roster-tab-button"
|
||||
data-endpoint="/partials/roster-retired"
|
||||
hx-get="/partials/roster-retired"
|
||||
hx-target="#roster-content"
|
||||
hx-swap="innerHTML">
|
||||
Retired
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium roster-tab-button"
|
||||
data-endpoint="/partials/roster-ignored"
|
||||
hx-get="/partials/roster-ignored"
|
||||
hx-target="#roster-content"
|
||||
hx-swap="innerHTML">
|
||||
Ignored
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content Target -->
|
||||
<div id="roster-content"
|
||||
hx-get="/partials/roster-deployed"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<p class="text-gray-500 dark:text-gray-400">Loading roster data...</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Add Unit Modal -->
|
||||
@@ -143,6 +177,11 @@
|
||||
class="w-4 h-4 text-seismo-orange focus:ring-seismo-orange rounded">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">Deployed</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" name="retired" id="retiredCheckbox" value="true"
|
||||
class="w-4 h-4 text-seismo-orange focus:ring-seismo-orange rounded">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">Retired</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Notes</label>
|
||||
@@ -175,7 +214,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form id="editUnitForm" hx-post="" hx-swap="none" class="p-6 space-y-4">
|
||||
<form id="editUnitForm" class="p-6 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Unit ID</label>
|
||||
<input type="text" name="id" id="editUnitId" readonly
|
||||
@@ -200,10 +239,15 @@
|
||||
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Location</label>
|
||||
<input type="text" name="location" id="editLocation"
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Address</label>
|
||||
<input type="text" name="address" id="editAddress" placeholder="123 Main St, City, State"
|
||||
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Coordinates</label>
|
||||
<input type="text" name="coordinates" id="editCoordinates" placeholder="34.0522,-118.2437"
|
||||
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange font-mono">
|
||||
</div>
|
||||
|
||||
<!-- Seismograph-specific fields -->
|
||||
<div id="editSeismographFields" class="space-y-4 border-t border-gray-200 dark:border-gray-700 pt-4">
|
||||
@@ -406,8 +450,11 @@
|
||||
document.getElementById('addUnitForm').addEventListener('htmx:afterRequest', function(event) {
|
||||
if (event.detail.successful) {
|
||||
closeAddUnitModal();
|
||||
// Trigger roster refresh
|
||||
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
|
||||
// Trigger roster refresh for current active tab
|
||||
htmx.ajax('GET', currentRosterEndpoint, {
|
||||
target: '#roster-content',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
// Show success message
|
||||
alert('Unit added successfully!');
|
||||
} else {
|
||||
@@ -469,7 +516,8 @@
|
||||
document.getElementById('editDeviceTypeSelect').value = unit.device_type;
|
||||
document.getElementById('editUnitType').value = unit.unit_type;
|
||||
document.getElementById('editProjectId').value = unit.project_id;
|
||||
document.getElementById('editLocation').value = unit.location;
|
||||
document.getElementById('editAddress').value = unit.address;
|
||||
document.getElementById('editCoordinates').value = unit.coordinates;
|
||||
document.getElementById('editNote').value = unit.note;
|
||||
|
||||
// Checkboxes
|
||||
@@ -486,8 +534,8 @@
|
||||
document.getElementById('editPhoneNumber').value = unit.phone_number;
|
||||
document.getElementById('editHardwareModel').value = unit.hardware_model;
|
||||
|
||||
// Set form action
|
||||
document.getElementById('editUnitForm').setAttribute('hx-post', `/api/roster/edit/${unitId}`);
|
||||
// Store unit ID for form submission
|
||||
document.getElementById('editUnitForm').dataset.unitId = unitId;
|
||||
|
||||
// Show/hide fields based on device type
|
||||
toggleEditDeviceFields();
|
||||
@@ -500,14 +548,37 @@
|
||||
}
|
||||
|
||||
// Handle Edit Unit form submission
|
||||
document.getElementById('editUnitForm').addEventListener('htmx:afterRequest', function(event) {
|
||||
if (event.detail.successful) {
|
||||
closeEditUnitModal();
|
||||
// Trigger roster refresh
|
||||
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
|
||||
alert('Unit updated successfully!');
|
||||
} else {
|
||||
alert('Error updating unit. Please check the form and try again.');
|
||||
document.getElementById('editUnitForm').addEventListener('submit', async function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const unitId = this.dataset.unitId;
|
||||
if (!unitId) {
|
||||
alert('Error: Unit ID not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(this);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/roster/edit/${unitId}`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
closeEditUnitModal();
|
||||
// Trigger roster refresh for current active tab
|
||||
htmx.ajax('GET', currentRosterEndpoint, {
|
||||
target: '#roster-content',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
alert('Unit updated successfully!');
|
||||
} else {
|
||||
const result = await response.json();
|
||||
alert(`Error: ${result.detail || 'Unknown error'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`Error updating unit: ${error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -528,8 +599,11 @@
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Trigger roster refresh
|
||||
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
|
||||
// Trigger roster refresh for current active tab
|
||||
htmx.ajax('GET', currentRosterEndpoint, {
|
||||
target: '#roster-content',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
alert(`Unit ${deployed ? 'deployed' : 'benched'} successfully!`);
|
||||
} else {
|
||||
const result = await response.json();
|
||||
@@ -557,8 +631,11 @@
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Trigger roster refresh
|
||||
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
|
||||
// Trigger roster refresh for current active tab
|
||||
htmx.ajax('GET', currentRosterEndpoint, {
|
||||
target: '#roster-content',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
alert(`Unit ${unitId} moved to ignore list`);
|
||||
} else {
|
||||
const result = await response.json();
|
||||
@@ -581,8 +658,11 @@
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Trigger roster refresh
|
||||
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
|
||||
// Trigger roster refresh for current active tab
|
||||
htmx.ajax('GET', currentRosterEndpoint, {
|
||||
target: '#roster-content',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
alert(`Unit ${unitId} deleted successfully`);
|
||||
} else {
|
||||
const result = await response.json();
|
||||
@@ -621,8 +701,11 @@
|
||||
`;
|
||||
resultDiv.classList.remove('hidden');
|
||||
|
||||
// Trigger roster refresh
|
||||
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
|
||||
// Trigger roster refresh for current active tab
|
||||
htmx.ajax('GET', currentRosterEndpoint, {
|
||||
target: '#roster-content',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
|
||||
// Close modal after 2 seconds
|
||||
setTimeout(() => closeImportModal(), 2000);
|
||||
@@ -637,6 +720,105 @@
|
||||
resultDiv.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle roster tab switching with auto-refresh
|
||||
let currentRosterEndpoint = '/partials/roster-deployed'; // Default to deployed tab
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const tabButtons = document.querySelectorAll('.roster-tab-button');
|
||||
|
||||
tabButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
// Remove active-roster-tab class from all buttons
|
||||
tabButtons.forEach(btn => btn.classList.remove('active-roster-tab'));
|
||||
// Add active-roster-tab class to clicked button
|
||||
this.classList.add('active-roster-tab');
|
||||
|
||||
// Update current endpoint for auto-refresh
|
||||
currentRosterEndpoint = this.getAttribute('data-endpoint');
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-refresh the current active tab every 10 seconds
|
||||
setInterval(() => {
|
||||
const rosterContent = document.getElementById('roster-content');
|
||||
if (rosterContent) {
|
||||
// Use HTMX to trigger a refresh of the current endpoint
|
||||
htmx.ajax('GET', currentRosterEndpoint, {
|
||||
target: '#roster-content',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
}
|
||||
}, 10000); // 10 seconds
|
||||
});
|
||||
|
||||
// Un-ignore Unit (remove from ignored list)
|
||||
async function unignoreUnit(unitId) {
|
||||
if (!confirm(`Remove unit ${unitId} from ignore list?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/roster/ignore/${unitId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Trigger ignored tab refresh
|
||||
htmx.trigger(document.querySelector('[hx-get="/partials/roster-ignored"]'), 'load');
|
||||
alert(`Unit ${unitId} removed from ignore list`);
|
||||
} else {
|
||||
const result = await response.json();
|
||||
alert(`Error: ${result.detail || 'Unknown error'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete ignored unit completely (from emitters table)
|
||||
async function deleteIgnoredUnit(unitId) {
|
||||
if (!confirm(`Are you sure you want to PERMANENTLY delete unit ${unitId}?\n\nThis will remove it from the ignore list and delete all records.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// First remove from ignore list
|
||||
await fetch(`/api/roster/ignore/${unitId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
// Then delete the unit
|
||||
const response = await fetch(`/api/roster/${unitId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Trigger ignored tab refresh
|
||||
htmx.trigger(document.querySelector('[hx-get="/partials/roster-ignored"]'), 'load');
|
||||
alert(`Unit ${unitId} deleted successfully`);
|
||||
} else {
|
||||
const result = await response.json();
|
||||
alert(`Error: ${result.detail || 'Unknown error'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.roster-tab-button {
|
||||
color: #6b7280; /* gray-500 */
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
.roster-tab-button:hover {
|
||||
color: #374151; /* gray-700 */
|
||||
}
|
||||
.active-roster-tab {
|
||||
color: #f48b1c !important; /* seismo orange */
|
||||
border-bottom: 2px solid #f48b1c !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user