Initial commit - Fire alarm management application

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-19 21:57:25 -05:00
commit 892ac3d23b
24 changed files with 4183 additions and 0 deletions

108
app/static/css/style.css Normal file
View File

@@ -0,0 +1,108 @@
/* Fire Alarm Management App Styles */
body {
background-color: #f8f9fa;
}
.navbar-brand {
font-weight: bold;
}
.card {
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
border: none;
}
.card-header {
background-color: #fff;
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
font-weight: 600;
}
.table th {
font-weight: 600;
background-color: #f8f9fa;
}
.progress {
border-radius: 0.5rem;
}
.btn-group-sm > .btn {
padding: 0.25rem 0.5rem;
}
/* Dashboard cards */
.card.bg-primary,
.card.bg-success,
.card.bg-info,
.card.bg-warning {
border: none;
}
.card.bg-primary .card-title,
.card.bg-success .card-title,
.card.bg-info .card-title {
opacity: 0.9;
}
/* Form improvements */
.form-control:focus,
.form-select:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
/* Table hover effect */
.table-hover tbody tr:hover {
background-color: rgba(13, 110, 253, 0.05);
}
/* Badge styles */
.badge {
font-weight: 500;
}
/* Responsive tables */
@media (max-width: 768px) {
.table-responsive {
font-size: 0.875rem;
}
}
/* Chart containers */
canvas {
max-height: 300px;
}
/* Breadcrumb */
.breadcrumb {
background: none;
padding: 0;
}
/* Modal improvements */
.modal-header {
border-bottom: 1px solid #dee2e6;
}
.modal-footer {
border-top: 1px solid #dee2e6;
}
/* Phase cards in schedule */
.card.border-success {
border-width: 2px !important;
}
/* Filter row */
.row.mb-4 .form-control,
.row.mb-4 .form-select {
background-color: #fff;
}
/* Loading spinner */
.spinner-border {
width: 3rem;
height: 3rem;
}

110
app/static/js/app.js Normal file
View File

@@ -0,0 +1,110 @@
// Fire Alarm Management App - Common JavaScript
// Format currency
function formatCurrency(value) {
return '$' + (value || 0).toLocaleString('en-US', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
});
}
// Format date
function formatDate(dateStr) {
if (!dateStr) return '-';
const date = new Date(dateStr);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
}
// Get progress bar class based on completion percentage
function getProgressClass(completion) {
if (completion >= 100) return 'bg-success';
if (completion > 50) return 'bg-info';
if (completion > 0) return 'bg-warning';
return 'bg-secondary';
}
// Show toast notification
function showToast(message, type = 'success') {
const toastContainer = document.getElementById('toastContainer') || createToastContainer();
const toast = document.createElement('div');
toast.className = `toast align-items-center text-white bg-${type} border-0`;
toast.setAttribute('role', 'alert');
toast.innerHTML = `
<div class="d-flex">
<div class="toast-body">${message}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
`;
toastContainer.appendChild(toast);
const bsToast = new bootstrap.Toast(toast);
bsToast.show();
toast.addEventListener('hidden.bs.toast', () => toast.remove());
}
function createToastContainer() {
const container = document.createElement('div');
container.id = 'toastContainer';
container.className = 'toast-container position-fixed bottom-0 end-0 p-3';
document.body.appendChild(container);
return container;
}
// Confirm dialog
function confirmAction(message) {
return new Promise((resolve) => {
resolve(confirm(message));
});
}
// API helper functions
async function apiGet(url) {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
}
async function apiPost(url, data) {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
}
async function apiPut(url, data) {
const response = await fetch(url, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
}
async function apiDelete(url) {
const response = await fetch(url, { method: 'DELETE' });
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return true;
}
// Debounce function for search inputs
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}