Add schedule and unit list templates for project management
- Created `schedule_list.html` to display scheduled actions with execution status, location, and timestamps. - Implemented buttons for executing and canceling schedules, along with a details view placeholder. - Created `unit_list.html` to show assigned units with their status, location, model, and session/file counts. - Added conditional rendering for active sessions and links to view unit and location details.
This commit is contained in:
@@ -3,113 +3,308 @@
|
||||
{% block title %}Project Dashboard - Terra-View{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Project Dashboard</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400 mt-1">Sound monitoring project overview and assignments</p>
|
||||
<!-- Breadcrumb Navigation -->
|
||||
<div class="mb-6">
|
||||
<nav class="flex items-center space-x-2 text-sm">
|
||||
<a href="/projects" class="text-seismo-orange hover:text-seismo-navy flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
Projects
|
||||
</a>
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
<span class="text-gray-900 dark:text-white font-medium" id="project-name-breadcrumb">Project</span>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Header (loads dynamically) -->
|
||||
<div id="project-header" hx-get="/api/projects/{{ project_id }}/header" hx-trigger="load" hx-swap="innerHTML">
|
||||
<div class="mb-8 animate-pulse">
|
||||
<div class="h-10 bg-gray-200 dark:bg-gray-700 rounded w-1/3 mb-2"></div>
|
||||
<div class="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/4"></div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button onclick="openProjectEditModal()" class="px-4 py-2 text-sm bg-seismo-orange hover:bg-seismo-navy text-white rounded-lg">
|
||||
Edit Project
|
||||
</div>
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="mb-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<nav class="flex space-x-6 overflow-x-auto">
|
||||
<button onclick="switchTab('overview')"
|
||||
data-tab="overview"
|
||||
class="tab-button px-4 py-3 border-b-2 font-medium text-sm transition-colors border-seismo-orange text-seismo-orange whitespace-nowrap">
|
||||
Overview
|
||||
</button>
|
||||
<a href="/projects" class="text-sm text-seismo-orange hover:text-seismo-navy">Back to projects</a>
|
||||
</div>
|
||||
<button onclick="switchTab('locations')"
|
||||
data-tab="locations"
|
||||
class="tab-button px-4 py-3 border-b-2 border-transparent font-medium text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-300 dark:hover:border-gray-600 transition-colors whitespace-nowrap">
|
||||
<span id="locations-tab-label">Locations</span>
|
||||
</button>
|
||||
<button onclick="switchTab('units')"
|
||||
data-tab="units"
|
||||
class="tab-button px-4 py-3 border-b-2 border-transparent font-medium text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-300 dark:hover:border-gray-600 transition-colors whitespace-nowrap">
|
||||
Assigned Units
|
||||
</button>
|
||||
<button onclick="switchTab('schedules')"
|
||||
data-tab="schedules"
|
||||
class="tab-button px-4 py-3 border-b-2 border-transparent font-medium text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-300 dark:hover:border-gray-600 transition-colors whitespace-nowrap">
|
||||
Schedules
|
||||
</button>
|
||||
<button onclick="switchTab('sessions')"
|
||||
data-tab="sessions"
|
||||
class="tab-button px-4 py-3 border-b-2 border-transparent font-medium text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-300 dark:hover:border-gray-600 transition-colors whitespace-nowrap">
|
||||
Recording Sessions
|
||||
</button>
|
||||
<button onclick="switchTab('data')"
|
||||
data-tab="data"
|
||||
class="tab-button px-4 py-3 border-b-2 border-transparent font-medium text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-300 dark:hover:border-gray-600 transition-colors whitespace-nowrap">
|
||||
Data Files
|
||||
</button>
|
||||
<button onclick="switchTab('settings')"
|
||||
data-tab="settings"
|
||||
class="tab-button px-4 py-3 border-b-2 border-transparent font-medium text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-300 dark:hover:border-gray-600 transition-colors whitespace-nowrap">
|
||||
Settings
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div id="project-dashboard"
|
||||
hx-get="/api/projects/{{ project_id }}/dashboard"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="animate-pulse space-y-4">
|
||||
<div class="h-24 bg-gray-200 dark:bg-gray-700 rounded-xl"></div>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div class="h-64 bg-gray-200 dark:bg-gray-700 rounded-xl"></div>
|
||||
<div class="h-64 bg-gray-200 dark:bg-gray-700 rounded-xl"></div>
|
||||
<div class="h-64 bg-gray-200 dark:bg-gray-700 rounded-xl"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Project Modal -->
|
||||
<div id="project-edit-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-y-auto">
|
||||
<div class="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">Edit Project</h2>
|
||||
<p class="text-gray-600 dark:text-gray-400 mt-1">Update project details and status</p>
|
||||
</div>
|
||||
<button onclick="closeProjectEditModal()" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="project-edit-form" class="p-6 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Project Name</label>
|
||||
<input type="text" name="name" id="edit-name" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Description</label>
|
||||
<textarea name="description" id="edit-description" rows="3" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Client Name</label>
|
||||
<input type="text" name="client_name" id="edit-client-name" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<!-- Tab Content -->
|
||||
<div id="tab-content">
|
||||
<!-- Overview Tab -->
|
||||
<div id="overview-tab" class="tab-panel">
|
||||
<div id="project-dashboard"
|
||||
hx-get="/api/projects/{{ project_id }}/dashboard"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="animate-pulse space-y-4">
|
||||
<div class="h-24 bg-gray-200 dark:bg-gray-700 rounded-xl"></div>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div class="h-64 bg-gray-200 dark:bg-gray-700 rounded-xl"></div>
|
||||
<div class="h-64 bg-gray-200 dark:bg-gray-700 rounded-xl"></div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Status</label>
|
||||
<select name="status" id="edit-status" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<option value="active">Active</option>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Locations Tab -->
|
||||
<div id="locations-tab" class="tab-panel hidden">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
<span id="locations-header">Locations</span>
|
||||
</h2>
|
||||
<button onclick="openLocationModal()" id="add-location-btn"
|
||||
class="px-4 py-2 bg-seismo-orange text-white rounded-lg hover:bg-seismo-navy transition-colors">
|
||||
<svg class="w-5 h-5 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||
</svg>
|
||||
<span id="add-location-label">Add Location</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="project-locations"
|
||||
hx-get="/api/projects/{{ project_id }}/locations"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<div class="text-center py-8 text-gray-500">Loading locations...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Units Tab -->
|
||||
<div id="units-tab" class="tab-panel hidden">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Assigned Units</h2>
|
||||
<div class="text-sm text-gray-500">
|
||||
Units currently assigned to this project's locations
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="project-units"
|
||||
hx-get="/api/projects/{{ project_id }}/units"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="text-center py-8 text-gray-500">Loading units...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schedules Tab -->
|
||||
<div id="schedules-tab" class="tab-panel hidden">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Scheduled Actions</h2>
|
||||
<button onclick="openScheduleModal()"
|
||||
class="px-4 py-2 bg-seismo-orange text-white rounded-lg hover:bg-seismo-navy transition-colors">
|
||||
<svg class="w-5 h-5 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||
</svg>
|
||||
Schedule Action
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="project-schedules"
|
||||
hx-get="/api/projects/{{ project_id }}/schedules"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="text-center py-8 text-gray-500">Loading schedules...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recording Sessions Tab -->
|
||||
<div id="sessions-tab" class="tab-panel hidden">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Recording Sessions</h2>
|
||||
<div class="flex items-center gap-4">
|
||||
<select id="sessions-filter" onchange="filterSessions()"
|
||||
class="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm">
|
||||
<option value="all">All Sessions</option>
|
||||
<option value="recording">Recording</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="archived">Archived</option>
|
||||
<option value="failed">Failed</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Site Address</label>
|
||||
<input type="text" name="site_address" id="edit-site-address" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<div id="project-sessions"
|
||||
hx-get="/api/projects/{{ project_id }}/sessions"
|
||||
hx-trigger="load, every 30s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="text-center py-8 text-gray-500">Loading sessions...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Site Coordinates</label>
|
||||
<input type="text" name="site_coordinates" id="edit-site-coordinates" placeholder="40.7128,-74.0060" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<p class="text-xs text-gray-500 mt-1">Format: latitude,longitude</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Start Date</label>
|
||||
<input type="date" name="start_date" id="edit-start-date" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">End Date</label>
|
||||
<input type="date" name="end_date" id="edit-end-date" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<!-- Data Files Tab -->
|
||||
<div id="data-tab" class="tab-panel hidden">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Data Files</h2>
|
||||
<div class="flex items-center gap-4">
|
||||
<select id="files-filter" onchange="filterFiles()"
|
||||
class="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm">
|
||||
<option value="all">All Files</option>
|
||||
<option value="audio">Audio</option>
|
||||
<option value="data">Data</option>
|
||||
<option value="log">Logs</option>
|
||||
</select>
|
||||
<button onclick="exportProjectData()"
|
||||
class="px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">
|
||||
<svg class="w-5 h-5 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
|
||||
</svg>
|
||||
Export All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="edit-error" class="hidden text-sm text-red-600"></div>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-2">
|
||||
<button type="button" onclick="closeProjectEditModal()" class="px-6 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="px-6 py-2 bg-seismo-orange hover:bg-seismo-navy text-white rounded-lg font-medium">
|
||||
Save Changes
|
||||
</button>
|
||||
<div id="project-files"
|
||||
hx-get="/api/projects/{{ project_id }}/files"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<div class="text-center py-8 text-gray-500">Loading data files...</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Tab -->
|
||||
<div id="settings-tab" class="tab-panel hidden">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-6">Project Settings</h2>
|
||||
|
||||
<form id="project-settings-form" class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Project Name</label>
|
||||
<input type="text" name="name" id="settings-name"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Description</label>
|
||||
<textarea name="description" id="settings-description" rows="3"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Client Name</label>
|
||||
<input type="text" name="client_name" id="settings-client-name"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Status</label>
|
||||
<select name="status" id="settings-status"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<option value="active">Active</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="archived">Archived</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Site Address</label>
|
||||
<input type="text" name="site_address" id="settings-site-address"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Site Coordinates</label>
|
||||
<input type="text" name="site_coordinates" id="settings-site-coordinates" placeholder="40.7128,-74.0060"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<p class="text-xs text-gray-500 mt-1">Format: latitude,longitude</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Start Date</label>
|
||||
<input type="date" name="start_date" id="settings-start-date"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">End Date</label>
|
||||
<input type="date" name="end_date" id="settings-end-date"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings-error" class="hidden text-sm text-red-600"></div>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-2">
|
||||
<button type="button" onclick="loadProjectDetails()"
|
||||
class="px-6 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
Reset
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="px-6 py-2 bg-seismo-orange hover:bg-seismo-navy text-white rounded-lg font-medium">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Danger Zone -->
|
||||
<div class="mt-8 pt-8 border-t border-gray-200 dark:border-gray-700">
|
||||
<h3 class="text-lg font-semibold text-red-600 dark:text-red-400 mb-4">Danger Zone</h3>
|
||||
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 mb-3">
|
||||
Archive this project to remove it from active listings. All data will be preserved.
|
||||
</p>
|
||||
<button onclick="archiveProject()"
|
||||
class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">
|
||||
Archive Project
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location Modal -->
|
||||
<div id="location-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-y-auto">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-y-auto m-4">
|
||||
<div class="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 id="location-modal-title" class="text-2xl font-bold text-gray-900 dark:text-white">Add Location</h2>
|
||||
@@ -127,40 +322,47 @@
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Name</label>
|
||||
<input type="text" name="name" id="location-name" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white" required>
|
||||
<input type="text" name="name" id="location-name"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Description</label>
|
||||
<textarea name="description" id="location-description" rows="3" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"></textarea>
|
||||
<textarea name="description" id="location-description" rows="3"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Type</label>
|
||||
<select name="location_type" id="location-type" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<select name="location_type" id="location-type"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<option value="sound">Sound</option>
|
||||
<option value="vibration">Vibration</option>
|
||||
</select>
|
||||
</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="location-coordinates" placeholder="40.7128,-74.0060" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<input type="text" name="coordinates" id="location-coordinates" placeholder="40.7128,-74.0060"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Address</label>
|
||||
<input type="text" name="address" id="location-address" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
<input type="text" name="address" id="location-address"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||||
</div>
|
||||
|
||||
<div id="location-error" class="hidden text-sm text-red-600"></div>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-2">
|
||||
<button type="button" onclick="closeLocationModal()" class="px-6 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<button type="button" onclick="closeLocationModal()"
|
||||
class="px-6 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="px-6 py-2 bg-seismo-orange hover:bg-seismo-navy text-white rounded-lg font-medium">
|
||||
<button type="submit"
|
||||
class="px-6 py-2 bg-seismo-orange hover:bg-seismo-navy text-white rounded-lg font-medium">
|
||||
Save Location
|
||||
</button>
|
||||
</div>
|
||||
@@ -170,7 +372,7 @@
|
||||
|
||||
<!-- Assign Unit Modal -->
|
||||
<div id="assign-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto m-4">
|
||||
<div class="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">Assign Unit</h2>
|
||||
@@ -189,7 +391,8 @@
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Available Units</label>
|
||||
<select id="assign-unit-id" name="unit_id" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white" required>
|
||||
<select id="assign-unit-id" name="unit_id"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white" required>
|
||||
<option value="">Select a unit</option>
|
||||
</select>
|
||||
<p id="assign-empty" class="hidden text-xs text-gray-500 mt-2">No available units match this location type.</p>
|
||||
@@ -197,16 +400,19 @@
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Notes</label>
|
||||
<textarea id="assign-notes" name="notes" rows="2" class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"></textarea>
|
||||
<textarea id="assign-notes" name="notes" rows="2"
|
||||
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"></textarea>
|
||||
</div>
|
||||
|
||||
<div id="assign-error" class="hidden text-sm text-red-600"></div>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-2">
|
||||
<button type="button" onclick="closeAssignModal()" class="px-6 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<button type="button" onclick="closeAssignModal()"
|
||||
class="px-6 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="px-6 py-2 bg-seismo-orange hover:bg-seismo-navy text-white rounded-lg font-medium">
|
||||
<button type="submit"
|
||||
class="px-6 py-2 bg-seismo-orange hover:bg-seismo-navy text-white rounded-lg font-medium">
|
||||
Assign Unit
|
||||
</button>
|
||||
</div>
|
||||
@@ -219,14 +425,67 @@ const projectId = "{{ project_id }}";
|
||||
let editingLocationId = null;
|
||||
let projectTypeId = null;
|
||||
|
||||
function openProjectEditModal() {
|
||||
const modal = document.getElementById('project-edit-modal');
|
||||
modal.classList.remove('hidden');
|
||||
loadProjectDetails();
|
||||
// Tab switching
|
||||
function switchTab(tabName) {
|
||||
// Hide all tab panels
|
||||
document.querySelectorAll('.tab-panel').forEach(panel => {
|
||||
panel.classList.add('hidden');
|
||||
});
|
||||
|
||||
// Reset all tab buttons
|
||||
document.querySelectorAll('.tab-button').forEach(button => {
|
||||
button.classList.remove('border-seismo-orange', 'text-seismo-orange');
|
||||
button.classList.add('border-transparent', 'text-gray-600', 'dark:text-gray-400');
|
||||
});
|
||||
|
||||
// Show selected tab panel
|
||||
const panel = document.getElementById(`${tabName}-tab`);
|
||||
if (panel) {
|
||||
panel.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Highlight selected tab button
|
||||
const button = document.querySelector(`[data-tab="${tabName}"]`);
|
||||
if (button) {
|
||||
button.classList.remove('border-transparent', 'text-gray-600', 'dark:text-gray-400');
|
||||
button.classList.add('border-seismo-orange', 'text-seismo-orange');
|
||||
}
|
||||
}
|
||||
|
||||
function closeProjectEditModal() {
|
||||
document.getElementById('project-edit-modal').classList.add('hidden');
|
||||
// Load project details
|
||||
async function loadProjectDetails() {
|
||||
try {
|
||||
const response = await fetch(`/api/projects/${projectId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load project details');
|
||||
}
|
||||
const data = await response.json();
|
||||
projectTypeId = data.project_type_id || null;
|
||||
|
||||
// Update breadcrumb
|
||||
document.getElementById('project-name-breadcrumb').textContent = data.name || 'Project';
|
||||
|
||||
// Update settings form
|
||||
document.getElementById('settings-name').value = data.name || '';
|
||||
document.getElementById('settings-description').value = data.description || '';
|
||||
document.getElementById('settings-client-name').value = data.client_name || '';
|
||||
document.getElementById('settings-status').value = data.status || 'active';
|
||||
document.getElementById('settings-site-address').value = data.site_address || '';
|
||||
document.getElementById('settings-site-coordinates').value = data.site_coordinates || '';
|
||||
document.getElementById('settings-start-date').value = formatDate(data.start_date);
|
||||
document.getElementById('settings-end-date').value = formatDate(data.end_date);
|
||||
|
||||
// Update tab labels based on project type
|
||||
if (projectTypeId === 'sound_monitoring') {
|
||||
document.getElementById('locations-tab-label').textContent = 'NRLs';
|
||||
document.getElementById('locations-header').textContent = 'Noise Recording Locations';
|
||||
document.getElementById('add-location-label').textContent = 'Add NRL';
|
||||
}
|
||||
|
||||
document.getElementById('settings-error').classList.add('hidden');
|
||||
} catch (err) {
|
||||
console.error('Failed to load project details:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(value) {
|
||||
@@ -236,54 +495,19 @@ function formatDate(value) {
|
||||
return date.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
async function loadProjectDetails() {
|
||||
try {
|
||||
const response = await fetch(`/api/projects/${projectId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load project details');
|
||||
}
|
||||
const data = await response.json();
|
||||
projectTypeId = data.project_type_id || null;
|
||||
document.getElementById('edit-name').value = data.name || '';
|
||||
document.getElementById('edit-description').value = data.description || '';
|
||||
document.getElementById('edit-client-name').value = data.client_name || '';
|
||||
document.getElementById('edit-status').value = data.status || 'active';
|
||||
document.getElementById('edit-site-address').value = data.site_address || '';
|
||||
document.getElementById('edit-site-coordinates').value = data.site_coordinates || '';
|
||||
document.getElementById('edit-start-date').value = formatDate(data.start_date);
|
||||
document.getElementById('edit-end-date').value = formatDate(data.end_date);
|
||||
document.getElementById('edit-error').classList.add('hidden');
|
||||
} catch (err) {
|
||||
const errorEl = document.getElementById('edit-error');
|
||||
errorEl.textContent = err.message || 'Failed to load project details.';
|
||||
errorEl.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProjectType() {
|
||||
try {
|
||||
const response = await fetch(`/api/projects/${projectId}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
projectTypeId = data.project_type_id || null;
|
||||
}
|
||||
} catch (err) {
|
||||
projectTypeId = null;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('project-edit-form').addEventListener('submit', async function(e) {
|
||||
// Project settings form submission
|
||||
document.getElementById('project-settings-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const payload = {
|
||||
name: document.getElementById('edit-name').value.trim(),
|
||||
description: document.getElementById('edit-description').value.trim() || null,
|
||||
client_name: document.getElementById('edit-client-name').value.trim() || null,
|
||||
status: document.getElementById('edit-status').value,
|
||||
site_address: document.getElementById('edit-site-address').value.trim() || null,
|
||||
site_coordinates: document.getElementById('edit-site-coordinates').value.trim() || null,
|
||||
start_date: document.getElementById('edit-start-date').value || null,
|
||||
end_date: document.getElementById('edit-end-date').value || null
|
||||
name: document.getElementById('settings-name').value.trim(),
|
||||
description: document.getElementById('settings-description').value.trim() || null,
|
||||
client_name: document.getElementById('settings-client-name').value.trim() || null,
|
||||
status: document.getElementById('settings-status').value,
|
||||
site_address: document.getElementById('settings-site-address').value.trim() || null,
|
||||
site_coordinates: document.getElementById('settings-site-coordinates').value.trim() || null,
|
||||
start_date: document.getElementById('settings-start-date').value || null,
|
||||
end_date: document.getElementById('settings-end-date').value || null
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -297,10 +521,10 @@ document.getElementById('project-edit-form').addEventListener('submit', async fu
|
||||
throw new Error('Failed to update project');
|
||||
}
|
||||
|
||||
closeProjectEditModal();
|
||||
refreshProjectDashboard();
|
||||
// Reload page to show updated data
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
const errorEl = document.getElementById('edit-error');
|
||||
const errorEl = document.getElementById('settings-error');
|
||||
errorEl.textContent = err.message || 'Failed to update project.';
|
||||
errorEl.classList.remove('hidden');
|
||||
}
|
||||
@@ -311,8 +535,13 @@ function refreshProjectDashboard() {
|
||||
target: '#project-dashboard',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
htmx.ajax('GET', `/api/projects/${projectId}/header`, {
|
||||
target: '#project-header',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
}
|
||||
|
||||
// Location modal functions
|
||||
function openLocationModal(defaultType) {
|
||||
editingLocationId = null;
|
||||
document.getElementById('location-modal-title').textContent = 'Add Location';
|
||||
@@ -413,6 +642,11 @@ document.getElementById('location-form').addEventListener('submit', async functi
|
||||
|
||||
closeLocationModal();
|
||||
refreshProjectDashboard();
|
||||
// Refresh locations tab if visible
|
||||
htmx.ajax('GET', `/api/projects/${projectId}/locations`, {
|
||||
target: '#project-locations',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
} catch (err) {
|
||||
const errorEl = document.getElementById('location-error');
|
||||
errorEl.textContent = err.message || 'Failed to save location.';
|
||||
@@ -432,16 +666,21 @@ async function deleteLocation(locationId) {
|
||||
throw new Error(data.detail || 'Failed to delete location');
|
||||
}
|
||||
refreshProjectDashboard();
|
||||
htmx.ajax('GET', `/api/projects/${projectId}/locations`, {
|
||||
target: '#project-locations',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
} catch (err) {
|
||||
alert(err.message || 'Failed to delete location.');
|
||||
}
|
||||
}
|
||||
|
||||
// Assign modal functions
|
||||
function openAssignModal(locationId, locationType) {
|
||||
const safeType = locationType || 'sound';
|
||||
document.getElementById('assign-location-id').value = locationId;
|
||||
document.getElementById('assign-location-type').value = safeType;
|
||||
document.getElementById('assign-unit-id').innerHTML = '<option value=\"\">Loading units...</option>';
|
||||
document.getElementById('assign-unit-id').innerHTML = '<option value="">Loading units...</option>';
|
||||
document.getElementById('assign-empty').classList.add('hidden');
|
||||
document.getElementById('assign-error').classList.add('hidden');
|
||||
document.getElementById('assign-modal').classList.remove('hidden');
|
||||
@@ -460,7 +699,7 @@ async function loadAvailableUnits(locationType) {
|
||||
}
|
||||
const data = await response.json();
|
||||
const select = document.getElementById('assign-unit-id');
|
||||
select.innerHTML = '<option value=\"\">Select a unit</option>';
|
||||
select.innerHTML = '<option value="">Select a unit</option>';
|
||||
if (!data.length) {
|
||||
document.getElementById('assign-empty').classList.remove('hidden');
|
||||
return;
|
||||
@@ -505,6 +744,10 @@ document.getElementById('assign-form').addEventListener('submit', async function
|
||||
}
|
||||
closeAssignModal();
|
||||
refreshProjectDashboard();
|
||||
htmx.ajax('GET', `/api/projects/${projectId}/locations`, {
|
||||
target: '#project-locations',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
} catch (err) {
|
||||
const errorEl = document.getElementById('assign-error');
|
||||
errorEl.textContent = err.message || 'Failed to assign unit.';
|
||||
@@ -524,29 +767,65 @@ async function unassignUnit(assignmentId) {
|
||||
throw new Error(data.detail || 'Failed to unassign unit');
|
||||
}
|
||||
refreshProjectDashboard();
|
||||
htmx.ajax('GET', `/api/projects/${projectId}/locations`, {
|
||||
target: '#project-locations',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
} catch (err) {
|
||||
alert(err.message || 'Failed to unassign unit.');
|
||||
}
|
||||
}
|
||||
|
||||
// Filter functions
|
||||
function filterSessions() {
|
||||
const filter = document.getElementById('sessions-filter').value;
|
||||
const url = filter === 'all'
|
||||
? `/api/projects/${projectId}/sessions`
|
||||
: `/api/projects/${projectId}/sessions?status=${filter}`;
|
||||
|
||||
htmx.ajax('GET', url, {
|
||||
target: '#project-sessions',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
}
|
||||
|
||||
function filterFiles() {
|
||||
const filter = document.getElementById('files-filter').value;
|
||||
const url = filter === 'all'
|
||||
? `/api/projects/${projectId}/files`
|
||||
: `/api/projects/${projectId}/files?type=${filter}`;
|
||||
|
||||
htmx.ajax('GET', url, {
|
||||
target: '#project-files',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
function openScheduleModal() {
|
||||
alert('Schedule modal coming soon');
|
||||
}
|
||||
|
||||
function exportProjectData() {
|
||||
window.location.href = `/api/projects/${projectId}/export`;
|
||||
}
|
||||
|
||||
function archiveProject() {
|
||||
if (!confirm('Archive this project? You can restore it later from the archived projects list.')) return;
|
||||
|
||||
document.getElementById('settings-status').value = 'archived';
|
||||
document.getElementById('project-settings-form').dispatchEvent(new Event('submit'));
|
||||
}
|
||||
|
||||
// Keyboard shortcuts
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeProjectEditModal();
|
||||
closeLocationModal();
|
||||
closeAssignModal();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadProjectType();
|
||||
});
|
||||
|
||||
document.getElementById('project-edit-modal')?.addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeProjectEditModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Click outside to close modals
|
||||
document.getElementById('location-modal')?.addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeLocationModal();
|
||||
@@ -558,5 +837,10 @@ document.getElementById('assign-modal')?.addEventListener('click', function(e) {
|
||||
closeAssignModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Load project details on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadProjectDetails();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user