Files
Fire-alarm-managment/app/templates/materials.html
2026-01-19 21:57:25 -05:00

257 lines
8.6 KiB
HTML

{% extends "base.html" %}
{% block title %}Materials - Fire Alarm Management{% endblock %}
{% block content %}
<div class="row mb-4">
<div class="col">
<h1><i class="bi bi-box-seam"></i> Materials Tracking</h1>
</div>
</div>
<!-- Summary Cards -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<h5 class="card-title">Total Items</h5>
<h2 id="totalItems">-</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-dark">
<div class="card-body">
<h5 class="card-title">Pending Order</h5>
<h2 id="pendingOrder">-</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<h5 class="card-title">Ordered</h5>
<h2 id="ordered">-</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<h5 class="card-title">Received</h5>
<h2 id="received">-</h2>
</div>
</div>
</div>
</div>
<!-- Filters -->
<div class="row mb-4">
<div class="col-md-3">
<input type="text" id="searchInput" class="form-control" placeholder="Search part number...">
</div>
<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="statusFilter" class="form-select">
<option value="">All Status</option>
<option value="pending">Pending Order</option>
<option value="ordered">Ordered</option>
<option value="received">Received</option>
</select>
</div>
</div>
<!-- Materials Table -->
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Job</th>
<th>Part #</th>
<th>Qty Needed</th>
<th>Ordered</th>
<th>Received</th>
<th>Received Qty</th>
<th>Received By</th>
<th>Delivered Qty</th>
<th>Delivered To</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="materialsTable">
<tr>
<td colspan="10" class="text-center">Loading...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let allMaterials = [];
let allJobs = [];
async function loadData() {
try {
// Load 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 all materials
allMaterials = [];
for (const job of allJobs) {
const materialsResponse = await fetch(`/api/jobs/${job.id}/materials`);
const materials = await materialsResponse.json();
materials.forEach(m => {
m.job = job;
allMaterials.push(m);
});
}
updateSummary();
renderMaterials(allMaterials);
} catch (error) {
console.error('Error loading data:', error);
}
}
function updateSummary() {
document.getElementById('totalItems').textContent = allMaterials.length;
document.getElementById('pendingOrder').textContent = allMaterials.filter(m => !m.ordered).length;
document.getElementById('ordered').textContent = allMaterials.filter(m => m.ordered && !m.received).length;
document.getElementById('received').textContent = allMaterials.filter(m => m.received).length;
}
function renderMaterials(materials) {
const tbody = document.getElementById('materialsTable');
if (materials.length === 0) {
tbody.innerHTML = '<tr><td colspan="10" class="text-center">No materials found</td></tr>';
return;
}
tbody.innerHTML = materials.map(m => `
<tr>
<td>
<a href="/jobs/${m.job.id}">${m.job.job_number}</a>
<br><small class="text-muted">${m.job.job_name}</small>
</td>
<td><strong>${m.part_number}</strong></td>
<td>${m.quantity}</td>
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox" ${m.ordered ? 'checked' : ''}
onchange="updateMaterial(${m.id}, 'ordered', this.checked)">
</div>
</td>
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox" ${m.received ? 'checked' : ''}
onchange="updateMaterial(${m.id}, 'received', this.checked)">
</div>
</td>
<td>
<input type="number" class="form-control form-control-sm" style="width: 80px;"
value="${m.received_qty || 0}"
onchange="updateMaterial(${m.id}, 'received_qty', parseInt(this.value))">
</td>
<td>
<input type="text" class="form-control form-control-sm" style="width: 100px;"
value="${m.received_by || ''}"
onchange="updateMaterial(${m.id}, 'received_by', this.value)">
</td>
<td>
<input type="number" class="form-control form-control-sm" style="width: 80px;"
value="${m.delivered_qty || 0}"
onchange="updateMaterial(${m.id}, 'delivered_qty', parseInt(this.value))">
</td>
<td>${m.delivered_to || '-'}</td>
<td>
<button class="btn btn-sm btn-outline-danger" onclick="deleteMaterial(${m.id})">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
`).join('');
}
async function updateMaterial(materialId, field, value) {
try {
await fetch(`/api/materials/${materialId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ [field]: value })
});
// Update local data
const material = allMaterials.find(m => m.id === materialId);
if (material) {
material[field] = value;
}
updateSummary();
} catch (error) {
console.error('Error updating material:', error);
alert('Error updating material');
}
}
async function deleteMaterial(materialId) {
if (!confirm('Delete this material?')) return;
try {
await fetch(`/api/materials/${materialId}`, { method: 'DELETE' });
allMaterials = allMaterials.filter(m => m.id !== materialId);
updateSummary();
filterMaterials();
} catch (error) {
console.error('Error deleting material:', error);
}
}
function filterMaterials() {
const search = document.getElementById('searchInput').value.toLowerCase();
const jobId = document.getElementById('jobFilter').value;
const status = document.getElementById('statusFilter').value;
let filtered = allMaterials.filter(m => {
const matchSearch = !search || (m.part_number && m.part_number.toLowerCase().includes(search));
const matchJob = !jobId || m.job.id == jobId;
const matchStatus = !status ||
(status === 'pending' && !m.ordered) ||
(status === 'ordered' && m.ordered && !m.received) ||
(status === 'received' && m.received);
return matchSearch && matchJob && matchStatus;
});
renderMaterials(filtered);
}
document.addEventListener('DOMContentLoaded', () => {
loadData();
document.getElementById('searchInput').addEventListener('input', filterMaterials);
document.getElementById('jobFilter').addEventListener('change', filterMaterials);
document.getElementById('statusFilter').addEventListener('change', filterMaterials);
});
</script>
{% endblock %}