867 lines
27 KiB
HTML
867 lines
27 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}FG Scan Reports - {{ app_name }}{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
/* FG Reports Page Styles */
|
|
.fg-reports-container {
|
|
max-width: 1400px;
|
|
margin: 2rem auto;
|
|
padding: 0 1rem;
|
|
}
|
|
|
|
.page-header {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.page-header h1 {
|
|
color: var(--text-primary);
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.page-header p {
|
|
color: var(--text-secondary);
|
|
margin: 0;
|
|
}
|
|
|
|
/* Query Section */
|
|
.query-card {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.query-card h3 {
|
|
color: var(--text-primary);
|
|
font-weight: 600;
|
|
margin-bottom: 1.5rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.query-card h3 i {
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.reports-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.report-option {
|
|
background: var(--bg-primary);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 6px;
|
|
padding: 1rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
text-align: center;
|
|
}
|
|
|
|
.report-option:hover {
|
|
border-color: var(--primary-color);
|
|
background: var(--bg-secondary);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.report-option.active {
|
|
border-color: var(--primary-color);
|
|
background: var(--primary-color);
|
|
color: white;
|
|
}
|
|
|
|
.report-option.active .report-icon {
|
|
color: white;
|
|
}
|
|
|
|
.report-icon {
|
|
font-size: 1.5rem;
|
|
color: var(--primary-color);
|
|
margin-bottom: 0.5rem;
|
|
transition: color 0.3s ease;
|
|
}
|
|
|
|
.report-option-title {
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
font-size: 0.95rem;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.report-option.active .report-option-title {
|
|
color: white;
|
|
}
|
|
|
|
.report-option-desc {
|
|
font-size: 0.85rem;
|
|
color: var(--text-secondary);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.report-option.active .report-option-desc {
|
|
color: rgba(255, 255, 255, 0.9);
|
|
}
|
|
|
|
/* Filter Section */
|
|
.filter-section {
|
|
background: var(--bg-primary);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 6px;
|
|
padding: 1rem;
|
|
margin-bottom: 1rem;
|
|
display: none;
|
|
}
|
|
|
|
.filter-section.active {
|
|
display: block;
|
|
}
|
|
|
|
.filter-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.filter-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.filter-group label {
|
|
color: var(--text-primary);
|
|
font-weight: 500;
|
|
margin-bottom: 0.5rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.filter-group input,
|
|
.filter-group select {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border-color);
|
|
color: var(--text-primary);
|
|
padding: 0.6rem 0.8rem;
|
|
border-radius: 4px;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.filter-group input:focus,
|
|
.filter-group select:focus {
|
|
outline: none;
|
|
border-color: var(--primary-color);
|
|
background: var(--bg-secondary);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.button-row {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
justify-content: flex-end;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.btn-query {
|
|
background: var(--primary-color);
|
|
color: white;
|
|
border: none;
|
|
padding: 0.6rem 1.5rem;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.btn-query:hover {
|
|
background: var(--primary-color-dark);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.btn-query:disabled {
|
|
background: var(--text-disabled);
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
.btn-reset {
|
|
background: var(--bg-tertiary);
|
|
color: var(--text-primary);
|
|
border: 1px solid var(--border-color);
|
|
}
|
|
|
|
.btn-reset:hover {
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
/* Data Display Section */
|
|
.data-card {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.data-card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1.5rem;
|
|
padding-bottom: 1rem;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.data-card h3 {
|
|
color: var(--text-primary);
|
|
font-weight: 600;
|
|
margin: 0;
|
|
flex: 1;
|
|
}
|
|
|
|
.data-stats {
|
|
display: flex;
|
|
gap: 1rem;
|
|
margin-left: 1rem;
|
|
}
|
|
|
|
.stat-box {
|
|
background: var(--bg-primary);
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 4px;
|
|
border-left: 3px solid var(--primary-color);
|
|
}
|
|
|
|
.stat-label {
|
|
color: var(--text-secondary);
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.stat-value {
|
|
color: var(--text-primary);
|
|
font-weight: 600;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.export-section {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.btn-export {
|
|
background: var(--success-color);
|
|
color: white;
|
|
border: none;
|
|
padding: 0.6rem 1.2rem;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.btn-export:hover {
|
|
background: var(--success-color-dark);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.btn-export:disabled {
|
|
background: var(--text-disabled);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* Table Styles */
|
|
.report-table-container {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.report-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.report-table thead {
|
|
background: var(--bg-primary);
|
|
border-bottom: 2px solid var(--border-color);
|
|
position: sticky;
|
|
top: 0;
|
|
}
|
|
|
|
.report-table th {
|
|
color: var(--text-primary);
|
|
padding: 1rem 0.8rem;
|
|
text-align: left;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.report-table td {
|
|
color: var(--text-primary);
|
|
padding: 0.8rem;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.report-table tbody tr:hover {
|
|
background: var(--bg-primary);
|
|
}
|
|
|
|
.status-badge {
|
|
display: inline-block;
|
|
padding: 0.4rem 0.8rem;
|
|
border-radius: 20px;
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-approved {
|
|
background: rgba(76, 175, 80, 0.2);
|
|
color: #4CAF50;
|
|
}
|
|
|
|
.status-rejected {
|
|
background: rgba(244, 67, 54, 0.2);
|
|
color: #F44336;
|
|
}
|
|
|
|
/* Empty State */
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 3rem 1rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.empty-state i {
|
|
font-size: 3rem;
|
|
margin-bottom: 1rem;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.empty-state h4 {
|
|
color: var(--text-primary);
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
/* Loading State */
|
|
.loading-spinner {
|
|
display: none;
|
|
text-align: center;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.loading-spinner.active {
|
|
display: block;
|
|
}
|
|
|
|
.spinner {
|
|
border: 4px solid var(--bg-primary);
|
|
border-top: 4px solid var(--primary-color);
|
|
border-radius: 50%;
|
|
width: 40px;
|
|
height: 40px;
|
|
animation: spin 1s linear infinite;
|
|
margin: 0 auto 1rem;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
.spinner-text {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* Success Message */
|
|
.success-message {
|
|
display: none;
|
|
background: rgba(76, 175, 80, 0.1);
|
|
border: 1px solid rgba(76, 175, 80, 0.3);
|
|
color: #4CAF50;
|
|
padding: 0.8rem 1rem;
|
|
border-radius: 4px;
|
|
margin-bottom: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.success-message.active {
|
|
display: flex;
|
|
}
|
|
|
|
.success-message i {
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.reports-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.filter-row {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.data-stats {
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.button-row {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.btn-query, .btn-reset {
|
|
width: 100%;
|
|
}
|
|
|
|
.data-card-header {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.data-stats {
|
|
margin-left: 0;
|
|
width: 100%;
|
|
}
|
|
|
|
.report-table {
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.report-table th,
|
|
.report-table td {
|
|
padding: 0.6rem 0.4rem;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="fg-reports-container">
|
|
<!-- Page Header -->
|
|
<div class="page-header">
|
|
<h1><i class="fas fa-file-alt"></i> FG Scan Reports</h1>
|
|
<p>Generate and export quality reports for finished goods scans</p>
|
|
</div>
|
|
|
|
<!-- Query Section -->
|
|
<div class="query-card">
|
|
<h3><i class="fas fa-filter"></i> Select Report Type</h3>
|
|
|
|
<div class="reports-grid">
|
|
<!-- Daily Report -->
|
|
<div class="report-option" data-report="daily">
|
|
<div class="report-icon"><i class="fas fa-calendar-day"></i></div>
|
|
<div class="report-option-title">Today's Report</div>
|
|
<div class="report-option-desc">All scans from today</div>
|
|
</div>
|
|
|
|
<!-- Select Day Report -->
|
|
<div class="report-option" data-report="select-day">
|
|
<div class="report-icon"><i class="fas fa-calendar"></i></div>
|
|
<div class="report-option-title">Select Day</div>
|
|
<div class="report-option-desc">Choose a specific date</div>
|
|
</div>
|
|
|
|
<!-- Date Range Report -->
|
|
<div class="report-option" data-report="date-range">
|
|
<div class="report-icon"><i class="fas fa-calendar-alt"></i></div>
|
|
<div class="report-option-title">Date Range</div>
|
|
<div class="report-option-desc">Custom date range</div>
|
|
</div>
|
|
|
|
<!-- 5-Day Report -->
|
|
<div class="report-option" data-report="5-day">
|
|
<div class="report-icon"><i class="fas fa-chart-line"></i></div>
|
|
<div class="report-option-title">Last 5 Days</div>
|
|
<div class="report-option-desc">Last 5 days of scans</div>
|
|
</div>
|
|
|
|
<!-- Defects Today -->
|
|
<div class="report-option" data-report="defects-today">
|
|
<div class="report-icon"><i class="fas fa-exclamation-circle"></i></div>
|
|
<div class="report-option-title">Defects Today</div>
|
|
<div class="report-option-desc">Rejected scans today</div>
|
|
</div>
|
|
|
|
<!-- Defects by Date -->
|
|
<div class="report-option" data-report="defects-date">
|
|
<div class="report-icon"><i class="fas fa-search"></i></div>
|
|
<div class="report-option-title">Defects by Date</div>
|
|
<div class="report-option-desc">Select date for defects</div>
|
|
</div>
|
|
|
|
<!-- Defects by Range -->
|
|
<div class="report-option" data-report="defects-range">
|
|
<div class="report-icon"><i class="fas fa-exclamation-triangle"></i></div>
|
|
<div class="report-option-title">Defects Range</div>
|
|
<div class="report-option-desc">Date range for defects</div>
|
|
</div>
|
|
|
|
<!-- Defects 5-Day -->
|
|
<div class="report-option" data-report="defects-5day">
|
|
<div class="report-icon"><i class="fas fa-ban"></i></div>
|
|
<div class="report-option-title">Defects 5 Days</div>
|
|
<div class="report-option-desc">Last 5 days defects</div>
|
|
</div>
|
|
|
|
<!-- Complete Database -->
|
|
<div class="report-option" data-report="all">
|
|
<div class="report-icon"><i class="fas fa-database"></i></div>
|
|
<div class="report-option-title">All Data</div>
|
|
<div class="report-option-desc">Complete database</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filter Section (appears based on report type) -->
|
|
<div class="filter-section" id="filterSection">
|
|
<div id="filterContent"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Data Display Section -->
|
|
<div class="data-card">
|
|
<div class="data-card-header">
|
|
<h3 id="reportTitle">Select a report to view data</h3>
|
|
<div class="data-stats" id="dataStats" style="display: none;">
|
|
<div class="stat-box">
|
|
<div class="stat-label">Total Scans</div>
|
|
<div class="stat-value" id="statTotal">0</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div class="stat-label">Approved</div>
|
|
<div class="stat-value" id="statApproved" style="border-left-color: #4CAF50;">0</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div class="stat-label">Rejected</div>
|
|
<div class="stat-value" id="statRejected" style="border-left-color: #F44336;">0</div>
|
|
</div>
|
|
</div>
|
|
<div class="export-section" id="exportSection" style="display: none;">
|
|
<button class="btn-export" id="exportExcelBtn">
|
|
<i class="fas fa-file-excel"></i> Export Excel
|
|
</button>
|
|
<button class="btn-export" id="exportCsvBtn">
|
|
<i class="fas fa-file-csv"></i> Export CSV
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="success-message" id="successMessage">
|
|
<i class="fas fa-check-circle"></i>
|
|
<span id="successText">Report generated successfully</span>
|
|
</div>
|
|
|
|
<div class="loading-spinner" id="loadingSpinner">
|
|
<div class="spinner"></div>
|
|
<div class="spinner-text">Generating report...</div>
|
|
</div>
|
|
|
|
<div class="report-table-container">
|
|
<table class="report-table" id="reportTable" style="display: none;">
|
|
<thead>
|
|
<tr id="tableHead"></tr>
|
|
</thead>
|
|
<tbody id="tableBody"></tbody>
|
|
</table>
|
|
<div class="empty-state" id="emptyState">
|
|
<i class="fas fa-inbox"></i>
|
|
<h4>No data to display</h4>
|
|
<p>Select a report type and filters above to view data</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Import SheetJS for Excel export -->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
|
|
|
<!-- Report Script -->
|
|
<script>
|
|
// Report generation logic
|
|
class FGReportManager {
|
|
constructor() {
|
|
this.currentReport = null;
|
|
this.currentData = [];
|
|
this.initializeEventListeners();
|
|
}
|
|
|
|
initializeEventListeners() {
|
|
// Report option selection
|
|
document.querySelectorAll('.report-option').forEach(option => {
|
|
option.addEventListener('click', () => this.selectReport(option.dataset.report));
|
|
});
|
|
|
|
// Export buttons
|
|
document.getElementById('exportExcelBtn').addEventListener('click', () => this.exportExcel());
|
|
document.getElementById('exportCsvBtn').addEventListener('click', () => this.exportCSV());
|
|
}
|
|
|
|
selectReport(reportType) {
|
|
this.currentReport = reportType;
|
|
|
|
// Update UI
|
|
document.querySelectorAll('.report-option').forEach(opt => {
|
|
opt.classList.toggle('active', opt.dataset.report === reportType);
|
|
});
|
|
|
|
// Show/hide filters based on report type
|
|
this.updateFilters(reportType);
|
|
}
|
|
|
|
updateFilters(reportType) {
|
|
const filterSection = document.getElementById('filterSection');
|
|
const filterContent = document.getElementById('filterContent');
|
|
|
|
let html = '';
|
|
let needsFilter = false;
|
|
|
|
switch(reportType) {
|
|
case 'select-day':
|
|
case 'defects-date':
|
|
html = `
|
|
<div class="filter-row">
|
|
<div class="filter-group">
|
|
<label for="filterDate">Select Date:</label>
|
|
<input type="date" id="filterDate" max="${new Date().toISOString().split('T')[0]}">
|
|
</div>
|
|
</div>
|
|
<div class="button-row">
|
|
<button class="btn-query" id="queryBtn"><i class="fas fa-search"></i> Generate Report</button>
|
|
<button class="btn-reset" id="resetBtn"><i class="fas fa-redo"></i> Reset</button>
|
|
</div>
|
|
`;
|
|
needsFilter = true;
|
|
break;
|
|
|
|
case 'date-range':
|
|
case 'defects-range':
|
|
html = `
|
|
<div class="filter-row">
|
|
<div class="filter-group">
|
|
<label for="filterStartDate">Start Date:</label>
|
|
<input type="date" id="filterStartDate" max="${new Date().toISOString().split('T')[0]}">
|
|
</div>
|
|
<div class="filter-group">
|
|
<label for="filterEndDate">End Date:</label>
|
|
<input type="date" id="filterEndDate" max="${new Date().toISOString().split('T')[0]}">
|
|
</div>
|
|
</div>
|
|
<div class="button-row">
|
|
<button class="btn-query" id="queryBtn"><i class="fas fa-search"></i> Generate Report</button>
|
|
<button class="btn-reset" id="resetBtn"><i class="fas fa-redo"></i> Reset</button>
|
|
</div>
|
|
`;
|
|
needsFilter = true;
|
|
break;
|
|
|
|
default:
|
|
// Auto-generate for daily, 5-day, defects-today, defects-5day, all
|
|
html = `
|
|
<div class="button-row">
|
|
<button class="btn-query" id="queryBtn"><i class="fas fa-search"></i> Generate Report</button>
|
|
<button class="btn-reset" id="resetBtn"><i class="fas fa-redo"></i> Reset</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
filterContent.innerHTML = html;
|
|
filterSection.classList.toggle('active', needsFilter || ['daily', '5-day', 'defects-today', 'defects-5day', 'all'].includes(reportType));
|
|
|
|
// Attach button listeners
|
|
document.getElementById('queryBtn')?.addEventListener('click', () => this.generateReport());
|
|
document.getElementById('resetBtn')?.addEventListener('click', () => this.resetReport());
|
|
}
|
|
|
|
async generateReport() {
|
|
const filterDate = document.getElementById('filterDate')?.value;
|
|
const startDate = document.getElementById('filterStartDate')?.value;
|
|
const endDate = document.getElementById('filterEndDate')?.value;
|
|
|
|
// Show loading
|
|
this.showLoading(true);
|
|
|
|
try {
|
|
const response = await fetch('/quality/api/fg_report', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
report_type: this.currentReport,
|
|
filter_date: filterDate,
|
|
start_date: startDate,
|
|
end_date: endDate
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
this.currentData = data.data;
|
|
this.displayReport(data);
|
|
this.showSuccess('Report generated successfully');
|
|
} else {
|
|
this.showError(data.message || 'Failed to generate report');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
this.showError('Error generating report: ' + error.message);
|
|
} finally {
|
|
this.showLoading(false);
|
|
}
|
|
}
|
|
|
|
displayReport(data) {
|
|
const reportTable = document.getElementById('reportTable');
|
|
const tableHead = document.getElementById('tableHead');
|
|
const tableBody = document.getElementById('tableBody');
|
|
const emptyState = document.getElementById('emptyState');
|
|
const reportTitle = document.getElementById('reportTitle');
|
|
const dataStats = document.getElementById('dataStats');
|
|
const exportSection = document.getElementById('exportSection');
|
|
|
|
if (!data.data || data.data.length === 0) {
|
|
reportTable.style.display = 'none';
|
|
emptyState.style.display = 'block';
|
|
dataStats.style.display = 'none';
|
|
exportSection.style.display = 'none';
|
|
reportTitle.textContent = 'No data found for this report';
|
|
return;
|
|
}
|
|
|
|
// Build table headers
|
|
const headers = Object.keys(data.data[0]);
|
|
tableHead.innerHTML = headers.map(h => `<th>${this.formatHeader(h)}</th>`).join('');
|
|
|
|
// Build table body
|
|
tableBody.innerHTML = data.data.map(row => {
|
|
return `<tr>${headers.map(h => {
|
|
let value = row[h];
|
|
let cellClass = '';
|
|
|
|
if (h === 'quality_code' || h === 'defect_code') {
|
|
const isApproved = value === 0 || value === '0' || value === '000';
|
|
cellClass = isApproved ? 'status-approved' : 'status-rejected';
|
|
const statusText = isApproved ? 'APPROVED' : 'REJECTED';
|
|
return `<td><span class="status-badge ${cellClass}">${statusText}</span></td>`;
|
|
}
|
|
|
|
return `<td>${value}</td>`;
|
|
}).join('')}</tr>`;
|
|
}).join('');
|
|
|
|
// Update stats
|
|
const total = data.data.length;
|
|
const approved = data.summary.approved_count;
|
|
const rejected = data.summary.rejected_count;
|
|
|
|
document.getElementById('statTotal').textContent = total;
|
|
document.getElementById('statApproved').textContent = approved;
|
|
document.getElementById('statRejected').textContent = rejected;
|
|
|
|
// Update title and show sections
|
|
reportTitle.textContent = data.title;
|
|
reportTable.style.display = 'table';
|
|
emptyState.style.display = 'none';
|
|
dataStats.style.display = 'flex';
|
|
exportSection.style.display = 'flex';
|
|
}
|
|
|
|
formatHeader(header) {
|
|
return header
|
|
.split('_')
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
.join(' ');
|
|
}
|
|
|
|
exportExcel() {
|
|
if (this.currentData.length === 0) {
|
|
this.showError('No data to export');
|
|
return;
|
|
}
|
|
|
|
const worksheet = XLSX.utils.json_to_sheet(this.currentData);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, 'FG Scans');
|
|
XLSX.writeFile(workbook, `fg_report_${new Date().toISOString().split('T')[0]}.xlsx`);
|
|
this.showSuccess('Report exported to Excel');
|
|
}
|
|
|
|
exportCSV() {
|
|
if (this.currentData.length === 0) {
|
|
this.showError('No data to export');
|
|
return;
|
|
}
|
|
|
|
const csv = [
|
|
Object.keys(this.currentData[0]).join(','),
|
|
...this.currentData.map(row =>
|
|
Object.values(row).map(v => `"${v}"`).join(',')
|
|
)
|
|
].join('\n');
|
|
|
|
const blob = new Blob([csv], { type: 'text/csv' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `fg_report_${new Date().toISOString().split('T')[0]}.csv`;
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
this.showSuccess('Report exported to CSV');
|
|
}
|
|
|
|
resetReport() {
|
|
document.getElementById('filterDate').value = '';
|
|
document.getElementById('filterStartDate').value = '';
|
|
document.getElementById('filterEndDate').value = '';
|
|
document.getElementById('reportTable').style.display = 'none';
|
|
document.getElementById('emptyState').style.display = 'block';
|
|
document.getElementById('dataStats').style.display = 'none';
|
|
document.getElementById('exportSection').style.display = 'none';
|
|
document.getElementById('reportTitle').textContent = 'Select a report to view data';
|
|
}
|
|
|
|
showLoading(show) {
|
|
document.getElementById('loadingSpinner').classList.toggle('active', show);
|
|
}
|
|
|
|
showSuccess(message) {
|
|
const successMsg = document.getElementById('successMessage');
|
|
document.getElementById('successText').textContent = message;
|
|
successMsg.classList.add('active');
|
|
setTimeout(() => successMsg.classList.remove('active'), 3000);
|
|
}
|
|
|
|
showError(message) {
|
|
console.error(message);
|
|
alert(message);
|
|
}
|
|
}
|
|
|
|
// Initialize report manager when page loads
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new FGReportManager();
|
|
});
|
|
</script>
|
|
{% endblock %}
|