Initial commit - Fire alarm management application
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
182
app/templates/schedule.html
Normal file
182
app/templates/schedule.html
Normal file
@@ -0,0 +1,182 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Schedule - Fire Alarm Management{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h1><i class="bi bi-calendar3"></i> Schedule Overview</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-3">
|
||||
<select id="jobFilter" class="form-select">
|
||||
<option value="">All Jobs</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select id="phaseTypeFilter" class="form-select">
|
||||
<option value="">All Phase Types</option>
|
||||
<option value="rough_in">Rough-in</option>
|
||||
<option value="trim">Trim</option>
|
||||
<option value="commissioning">Commissioning</option>
|
||||
<option value="final">Final</option>
|
||||
<option value="turnover">Turnover</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select id="statusFilter" class="form-select">
|
||||
<option value="">All Status</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="in_progress">In Progress</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schedule Cards by Job -->
|
||||
<div id="scheduleContainer" class="row">
|
||||
<div class="col text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
let allJobs = [];
|
||||
let allPhases = {};
|
||||
|
||||
async function loadSchedule() {
|
||||
try {
|
||||
// Load all jobs
|
||||
const jobsResponse = await fetch('/api/jobs');
|
||||
allJobs = await jobsResponse.json();
|
||||
|
||||
// Populate job filter
|
||||
const jobFilter = document.getElementById('jobFilter');
|
||||
allJobs.forEach(job => {
|
||||
const option = document.createElement('option');
|
||||
option.value = job.id;
|
||||
option.textContent = `${job.job_number} - ${job.job_name}`;
|
||||
jobFilter.appendChild(option);
|
||||
});
|
||||
|
||||
// Load phases for each job
|
||||
for (const job of allJobs) {
|
||||
const phasesResponse = await fetch(`/api/jobs/${job.id}/phases`);
|
||||
allPhases[job.id] = await phasesResponse.json();
|
||||
}
|
||||
|
||||
renderSchedule();
|
||||
} catch (error) {
|
||||
console.error('Error loading schedule:', error);
|
||||
document.getElementById('scheduleContainer').innerHTML =
|
||||
'<div class="col"><div class="alert alert-danger">Error loading schedule</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderSchedule() {
|
||||
const container = document.getElementById('scheduleContainer');
|
||||
const jobFilter = document.getElementById('jobFilter').value;
|
||||
const phaseTypeFilter = document.getElementById('phaseTypeFilter').value;
|
||||
const statusFilter = document.getElementById('statusFilter').value;
|
||||
|
||||
let jobs = jobFilter ? allJobs.filter(j => j.id == jobFilter) : allJobs;
|
||||
|
||||
if (jobs.length === 0) {
|
||||
container.innerHTML = '<div class="col"><div class="alert alert-info">No jobs found</div></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = jobs.map(job => {
|
||||
let phases = allPhases[job.id] || [];
|
||||
|
||||
if (phaseTypeFilter) {
|
||||
phases = phases.filter(p => p.phase_type === phaseTypeFilter);
|
||||
}
|
||||
if (statusFilter) {
|
||||
phases = phases.filter(p =>
|
||||
(statusFilter === 'completed' && p.completed) ||
|
||||
(statusFilter === 'in_progress' && !p.completed)
|
||||
);
|
||||
}
|
||||
|
||||
if (phases.length === 0 && (phaseTypeFilter || statusFilter)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const phaseTypes = ['rough_in', 'trim', 'commissioning', 'final', 'turnover'];
|
||||
const phaseLabels = {
|
||||
'rough_in': 'Rough-in',
|
||||
'trim': 'Trim',
|
||||
'commissioning': 'Commissioning',
|
||||
'final': 'Final',
|
||||
'turnover': 'Turnover'
|
||||
};
|
||||
|
||||
return `
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
<strong>${job.job_number}</strong> - ${job.job_name}
|
||||
<span class="badge bg-info">${(job.percent_complete * 100).toFixed(0)}% Complete</span>
|
||||
</span>
|
||||
<a href="/jobs/${job.id}" class="btn btn-sm btn-outline-primary">View Job</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
${phases.length === 0 ? '<p class="text-muted">No phases scheduled yet</p>' : `
|
||||
<div class="row">
|
||||
${phaseTypes.map(type => {
|
||||
const typePhases = phases.filter(p => p.phase_type === type);
|
||||
if (typePhases.length === 0) return '';
|
||||
return `
|
||||
<div class="col-md-4 mb-3">
|
||||
<h6 class="text-uppercase text-muted">${phaseLabels[type]}</h6>
|
||||
${typePhases.map(p => `
|
||||
<div class="card mb-2 ${p.completed ? 'border-success' : ''}">
|
||||
<div class="card-body py-2 px-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span>Phase ${p.phase_number}</span>
|
||||
<span class="badge ${p.completed ? 'bg-success' : 'bg-secondary'}">
|
||||
${p.completed ? 'Done' : 'Pending'}
|
||||
</span>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
${p.points ? p.points + ' pts' : ''}
|
||||
${p.start_date ? ' | Start: ' + p.start_date : ''}
|
||||
${p.due_date ? ' | Due: ' + p.due_date : ''}
|
||||
</small>
|
||||
${p.men_on_site ? '<br><small>Men: ' + p.men_on_site + '</small>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
if (!container.innerHTML.trim()) {
|
||||
container.innerHTML = '<div class="col"><div class="alert alert-info">No matching phases found</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadSchedule();
|
||||
|
||||
document.getElementById('jobFilter').addEventListener('change', renderSchedule);
|
||||
document.getElementById('phaseTypeFilter').addEventListener('change', renderSchedule);
|
||||
document.getElementById('statusFilter').addEventListener('change', renderSchedule);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user