BIG update: Update to 0.5.1. Added:
-Project management -Modem Managerment -Modem/unit pairing and more
This commit is contained in:
233
templates/partials/project_create_modal.html
Normal file
233
templates/partials/project_create_modal.html
Normal file
@@ -0,0 +1,233 @@
|
||||
{#
|
||||
Quick Create Project Modal
|
||||
Allows inline creation of a new project from the project picker.
|
||||
|
||||
Include this modal in pages that use the project picker.
|
||||
#}
|
||||
|
||||
<div id="quickCreateProjectModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-2xl max-w-md w-full mx-4">
|
||||
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white">Create New Project</h2>
|
||||
<button type="button" onclick="closeCreateProjectModal()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<form id="quickCreateProjectForm" class="p-6 space-y-4">
|
||||
<!-- Hidden field to track which picker opened this modal -->
|
||||
<input type="hidden" id="qcp-picker-id" value="">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Project Number
|
||||
<span class="text-gray-400 font-normal">(xxxx-YY)</span>
|
||||
</label>
|
||||
<input type="text"
|
||||
name="project_number"
|
||||
id="qcp-project-number"
|
||||
pattern="\d{4}-\d{2}"
|
||||
placeholder="2567-23"
|
||||
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">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">TMI internal project number (optional)</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Client Name <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="text"
|
||||
name="client_name"
|
||||
id="qcp-client-name"
|
||||
required
|
||||
placeholder="PJ Dick, Turner Construction, etc."
|
||||
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">
|
||||
Project Name <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="text"
|
||||
name="name"
|
||||
id="qcp-project-name"
|
||||
required
|
||||
placeholder="RKM Hall, CMU Campus, Building 7, etc."
|
||||
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">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Site or building name</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Project Type <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="project_type_id"
|
||||
id="qcp-project-type"
|
||||
required
|
||||
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">
|
||||
<option value="vibration_monitoring">Vibration Monitoring</option>
|
||||
<option value="sound_monitoring">Sound Monitoring</option>
|
||||
<option value="combined">Combined (Vibration + Sound)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="qcp-error" class="hidden p-3 bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg text-sm">
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 pt-2">
|
||||
<button type="submit"
|
||||
id="qcp-submit-btn"
|
||||
class="flex-1 px-4 py-2 bg-seismo-orange hover:bg-orange-600 text-white rounded-lg font-medium transition-colors flex items-center justify-center gap-2">
|
||||
<svg class="w-5 h-5" 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>
|
||||
Create & Select
|
||||
</button>
|
||||
<button type="button"
|
||||
onclick="closeCreateProjectModal()"
|
||||
class="px-4 py-2 bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-500 text-gray-700 dark:text-white rounded-lg font-medium transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Quick create project modal functions
|
||||
if (typeof openCreateProjectModal === 'undefined') {
|
||||
function openCreateProjectModal(searchQuery, pickerId = '') {
|
||||
const modal = document.getElementById('quickCreateProjectModal');
|
||||
const pickerIdInput = document.getElementById('qcp-picker-id');
|
||||
const projectNumInput = document.getElementById('qcp-project-number');
|
||||
const clientNameInput = document.getElementById('qcp-client-name');
|
||||
const projectNameInput = document.getElementById('qcp-project-name');
|
||||
const errorDiv = document.getElementById('qcp-error');
|
||||
|
||||
// Store which picker opened this
|
||||
if (pickerIdInput) pickerIdInput.value = pickerId;
|
||||
|
||||
// Reset form
|
||||
document.getElementById('quickCreateProjectForm').reset();
|
||||
if (errorDiv) errorDiv.classList.add('hidden');
|
||||
|
||||
// Try to parse the search query intelligently
|
||||
if (searchQuery) {
|
||||
// Check if it looks like a project number (xxxx-YY pattern)
|
||||
const projectNumMatch = searchQuery.match(/(\d{4}-\d{2})/);
|
||||
if (projectNumMatch) {
|
||||
if (projectNumInput) projectNumInput.value = projectNumMatch[1];
|
||||
// If there's more after the number, use it as client name
|
||||
const remainder = searchQuery.replace(projectNumMatch[1], '').replace(/^[\s\-]+/, '').trim();
|
||||
if (remainder && clientNameInput) clientNameInput.value = remainder;
|
||||
} else {
|
||||
// Not a project number - assume it's client or project name
|
||||
// If short (likely a name fragment), put it in client name
|
||||
if (clientNameInput) clientNameInput.value = searchQuery;
|
||||
}
|
||||
}
|
||||
|
||||
// Show modal
|
||||
if (modal) modal.classList.remove('hidden');
|
||||
|
||||
// Focus the first empty required field
|
||||
if (clientNameInput && !clientNameInput.value) {
|
||||
clientNameInput.focus();
|
||||
} else if (projectNameInput) {
|
||||
projectNameInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function closeCreateProjectModal() {
|
||||
const modal = document.getElementById('quickCreateProjectModal');
|
||||
if (modal) modal.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Handle quick create form submission
|
||||
document.getElementById('quickCreateProjectForm')?.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const submitBtn = document.getElementById('qcp-submit-btn');
|
||||
const errorDiv = document.getElementById('qcp-error');
|
||||
const pickerId = document.getElementById('qcp-picker-id')?.value || '';
|
||||
|
||||
// Show loading state
|
||||
const originalBtnText = submitBtn.innerHTML;
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = `
|
||||
<svg class="w-5 h-5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
Creating...
|
||||
`;
|
||||
if (errorDiv) errorDiv.classList.add('hidden');
|
||||
|
||||
const formData = new FormData(this);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/projects/create', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result.success) {
|
||||
// Build display text from form values
|
||||
const parts = [];
|
||||
const projectNumber = formData.get('project_number');
|
||||
const clientName = formData.get('client_name');
|
||||
const projectName = formData.get('name');
|
||||
|
||||
if (projectNumber) parts.push(projectNumber);
|
||||
if (clientName) parts.push(clientName);
|
||||
if (projectName) parts.push(projectName);
|
||||
|
||||
const displayText = parts.join(' - ');
|
||||
|
||||
// Select the newly created project in the picker
|
||||
selectProject(result.project_id, displayText, pickerId);
|
||||
|
||||
// Close modal
|
||||
closeCreateProjectModal();
|
||||
} else {
|
||||
// Show error
|
||||
if (errorDiv) {
|
||||
errorDiv.textContent = result.detail || result.message || 'Failed to create project';
|
||||
errorDiv.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (errorDiv) {
|
||||
errorDiv.textContent = `Error: ${error.message}`;
|
||||
errorDiv.classList.remove('hidden');
|
||||
}
|
||||
} finally {
|
||||
// Restore button
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = originalBtnText;
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal on backdrop click
|
||||
document.getElementById('quickCreateProjectModal')?.addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeCreateProjectModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal on Escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
const modal = document.getElementById('quickCreateProjectModal');
|
||||
if (modal && !modal.classList.contains('hidden')) {
|
||||
closeCreateProjectModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user