feat: Refactor project creation and management to support modular project types

- Updated project creation modal to allow selection of optional modules (Sound and Vibration Monitoring).
- Modified project dashboard and header to display active modules and provide options to add/remove them.
- Enhanced project detail view to dynamically adjust UI based on enabled modules.
- Implemented a new migration script to create a `project_modules` table and seed it based on existing project types.
- Adjusted form submissions to handle module selections and ensure proper API interactions for module management.
This commit is contained in:
2026-03-30 21:44:15 +00:00
parent 184f0ddd13
commit 73a6ff4d20
9 changed files with 493 additions and 175 deletions

View File

@@ -770,7 +770,7 @@
<script>
const projectId = "{{ project_id }}";
let editingLocationId = null;
let projectTypeId = null;
let projectModules = []; // list of enabled module_type strings, e.g. ['sound_monitoring']
async function quickUpdateStatus(newStatus) {
try {
@@ -828,7 +828,7 @@ async function loadProjectDetails() {
throw new Error('Failed to load project details');
}
const data = await response.json();
projectTypeId = data.project_type_id || null;
projectModules = data.modules || [];
// Update breadcrumb
document.getElementById('project-name-breadcrumb').textContent = data.name || 'Project';
@@ -849,10 +849,10 @@ async function loadProjectDetails() {
if (modeRadio) modeRadio.checked = true;
settingsUpdateModeStyles();
// Update tab labels and visibility based on project type
const isSoundProject = projectTypeId === 'sound_monitoring';
const isVibrationProject = projectTypeId === 'vibration_monitoring';
if (isSoundProject) {
// Update tab labels and visibility based on active modules
const hasSoundModule = projectModules.includes('sound_monitoring');
const hasVibrationModule = projectModules.includes('vibration_monitoring');
if (hasSoundModule && !hasVibrationModule) {
document.getElementById('locations-tab-label').textContent = 'NRLs';
document.getElementById('locations-header').textContent = 'Noise Recording Locations';
document.getElementById('add-location-label').textContent = 'Add NRL';
@@ -860,11 +860,11 @@ async function loadProjectDetails() {
// Monitoring Sessions and Data Files tabs are sound-only
// Data Files also hides the FTP browser section for manual projects
const isRemote = mode === 'remote';
document.getElementById('sessions-tab-btn').classList.toggle('hidden', !isSoundProject);
document.getElementById('data-tab-btn').classList.toggle('hidden', !isSoundProject);
// Schedules and Assigned Units: hidden for vibration; for sound, only show if remote
document.getElementById('schedules-tab-btn')?.classList.toggle('hidden', isVibrationProject || (isSoundProject && !isRemote));
document.getElementById('units-tab-btn')?.classList.toggle('hidden', isVibrationProject || (isSoundProject && !isRemote));
document.getElementById('sessions-tab-btn').classList.toggle('hidden', !hasSoundModule);
document.getElementById('data-tab-btn').classList.toggle('hidden', !hasSoundModule);
// Schedules and Assigned Units: hidden when no sound module; for sound, only show if remote
document.getElementById('schedules-tab-btn')?.classList.toggle('hidden', !hasSoundModule || !isRemote);
document.getElementById('units-tab-btn')?.classList.toggle('hidden', !hasSoundModule || !isRemote);
// FTP browser within Data Files tab
document.getElementById('ftp-browser')?.classList.toggle('hidden', !isRemote);
@@ -996,11 +996,13 @@ function openLocationModal(defaultType) {
if (connectedRadio) { connectedRadio.checked = true; updateModeLabels(); }
const locationTypeSelect = document.getElementById('location-type');
const locationTypeWrapper = locationTypeSelect.closest('div');
if (projectTypeId === 'sound_monitoring') {
const hasSoundMod = projectModules.includes('sound_monitoring');
const hasVibMod = projectModules.includes('vibration_monitoring');
if (hasSoundMod && !hasVibMod) {
locationTypeSelect.value = 'sound';
locationTypeSelect.disabled = true;
if (locationTypeWrapper) locationTypeWrapper.classList.add('hidden');
} else if (projectTypeId === 'vibration_monitoring') {
} else if (hasVibMod && !hasSoundMod) {
locationTypeSelect.value = 'vibration';
locationTypeSelect.disabled = true;
if (locationTypeWrapper) locationTypeWrapper.classList.add('hidden');
@@ -1030,11 +1032,13 @@ function openEditLocationModal(button) {
if (modeRadio) { modeRadio.checked = true; updateModeLabels(); }
const locationTypeSelect = document.getElementById('location-type');
const locationTypeWrapper = locationTypeSelect.closest('div');
if (projectTypeId === 'sound_monitoring') {
const hasSoundModE = projectModules.includes('sound_monitoring');
const hasVibModE = projectModules.includes('vibration_monitoring');
if (hasSoundModE && !hasVibModE) {
locationTypeSelect.value = 'sound';
locationTypeSelect.disabled = true;
if (locationTypeWrapper) locationTypeWrapper.classList.add('hidden');
} else if (projectTypeId === 'vibration_monitoring') {
} else if (hasVibModE && !hasSoundModE) {
locationTypeSelect.value = 'vibration';
locationTypeSelect.disabled = true;
if (locationTypeWrapper) locationTypeWrapper.classList.add('hidden');
@@ -1060,9 +1064,9 @@ document.getElementById('location-form').addEventListener('submit', async functi
const address = document.getElementById('location-address').value.trim();
const coordinates = document.getElementById('location-coordinates').value.trim();
let locationType = document.getElementById('location-type').value;
if (projectTypeId === 'sound_monitoring') {
if (projectModules.includes('sound_monitoring') && !projectModules.includes('vibration_monitoring')) {
locationType = 'sound';
} else if (projectTypeId === 'vibration_monitoring') {
} else if (projectModules.includes('vibration_monitoring') && !projectModules.includes('sound_monitoring')) {
locationType = 'vibration';
}