starting daily mirror
This commit is contained in:
@@ -39,18 +39,15 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="right-header">
|
||||
<button id="theme-toggle" class="theme-toggle">Change to dark theme</button>
|
||||
{% if request.endpoint in ['main.upload_data', 'main.upload_orders', 'main.print_module', 'main.label_templates', 'main.create_template', 'main.print_lost_labels', 'main.view_orders'] %}
|
||||
<a href="{{ url_for('main.etichete') }}" class="btn go-to-main-etichete-btn">Main Page Etichete</a>
|
||||
{% endif %}
|
||||
{% if request.endpoint in ['main.quality', 'main.fg_quality'] %}
|
||||
<a href="{{ url_for('main.reports') }}" class="btn go-to-main-reports-btn">Main Page Reports</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('main.dashboard') }}" class="btn go-to-dashboard-btn">Go to Dashboard</a>
|
||||
{% if 'user' in session %}
|
||||
<span class="user-info">You are logged in as {{ session['user'] }}</span>
|
||||
<a href="{{ url_for('main.logout') }}" class="logout-button">Logout</a>
|
||||
{% endif %}
|
||||
<button id="theme-toggle" class="theme-toggle">Change to dark theme</button>
|
||||
{% if request.endpoint.startswith('daily_mirror') %}
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_main_route') }}" class="btn btn-info btn-sm ms-2"> <i class="fas fa-home"></i> Daily Mirror Main</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('main.dashboard') }}" class="btn go-to-dashboard-btn ms-2">Go to Dashboard</a>
|
||||
{% if 'user' in session %}
|
||||
<span class="user-info ms-2">You are logged in as {{ session['user'] }}</span>
|
||||
<a href="{{ url_for('main.logout') }}" class="logout-button ms-2">Logout</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
447
py_app/app/templates/daily_mirror.html
Normal file
447
py_app/app/templates/daily_mirror.html
Normal file
@@ -0,0 +1,447 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Daily Mirror - Quality Recticel{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">📈 Daily Mirror</h1>
|
||||
<p class="text-muted">Generate comprehensive daily production reports</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_history_route') }}" class="btn btn-outline-primary">
|
||||
<i class="fas fa-history"></i> View History
|
||||
</a>
|
||||
<a href="{{ url_for('main.dashboard') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Selection Card -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-calendar-alt"></i> Select Report Date
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<label for="reportDate" class="form-label">Report Date:</label>
|
||||
<input type="date" class="form-control" id="reportDate" value="{{ today }}">
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-primary" onclick="generateDailyReport()">
|
||||
<i class="fas fa-chart-line"></i> Generate Report
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-success" onclick="setTodayDate()">
|
||||
<i class="fas fa-calendar-day"></i> Today's Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading Indicator -->
|
||||
<div id="loadingIndicator" class="row mb-4" style="display: none;">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-2 mb-0">Generating daily report...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Daily Report Results -->
|
||||
<div id="reportResults" style="display: none;">
|
||||
<!-- Key Metrics Overview -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-tachometer-alt"></i> Daily Production Overview
|
||||
<span id="reportDateDisplay" class="badge bg-primary ms-2"></span>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="metric-card orders-quantity">
|
||||
<div class="metric-icon">
|
||||
<i class="fas fa-clipboard-list"></i>
|
||||
</div>
|
||||
<div class="metric-content">
|
||||
<h3 id="ordersQuantity">-</h3>
|
||||
<p>Orders Quantity</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="metric-card production-launched">
|
||||
<div class="metric-icon">
|
||||
<i class="fas fa-play-circle"></i>
|
||||
</div>
|
||||
<div class="metric-content">
|
||||
<h3 id="productionLaunched">-</h3>
|
||||
<p>Production Launched</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="metric-card production-finished">
|
||||
<div class="metric-icon">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<div class="metric-content">
|
||||
<h3 id="productionFinished">-</h3>
|
||||
<p>Production Finished</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="metric-card orders-delivered">
|
||||
<div class="metric-icon">
|
||||
<i class="fas fa-truck"></i>
|
||||
</div>
|
||||
<div class="metric-content">
|
||||
<h3 id="ordersDelivered">-</h3>
|
||||
<p>Orders Delivered</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quality Control Metrics -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-search"></i> Quality Control Scans
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="quality-stats">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div class="stat-item">
|
||||
<h4 id="qualityTotalScans">-</h4>
|
||||
<p>Total Scans</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="stat-item approved">
|
||||
<h4 id="qualityApprovedScans">-</h4>
|
||||
<p>Approved</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="stat-item rejected">
|
||||
<h4 id="qualityRejectedScans">-</h4>
|
||||
<p>Rejected</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<div class="progress">
|
||||
<div id="qualityApprovalBar" class="progress-bar bg-success" role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
<p class="text-center mt-2 mb-0">
|
||||
Approval Rate: <span id="qualityApprovalRate" class="fw-bold">0%</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-clipboard-check"></i> Finish Goods Quality
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="quality-stats">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div class="stat-item">
|
||||
<h4 id="fgQualityTotalScans">-</h4>
|
||||
<p>Total Scans</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="stat-item approved">
|
||||
<h4 id="fgQualityApprovedScans">-</h4>
|
||||
<p>Approved</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="stat-item rejected">
|
||||
<h4 id="fgQualityRejectedScans">-</h4>
|
||||
<p>Rejected</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<div class="progress">
|
||||
<div id="fgQualityApprovalBar" class="progress-bar bg-success" role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
<p class="text-center mt-2 mb-0">
|
||||
Approval Rate: <span id="fgQualityApprovalRate" class="fw-bold">0%</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Export and Actions -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-download"></i> Export Options
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-success" onclick="exportReportPDF()">
|
||||
<i class="fas fa-file-pdf"></i> Export PDF
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary" onclick="exportReportExcel()">
|
||||
<i class="fas fa-file-excel"></i> Export Excel
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-info" onclick="printReport()">
|
||||
<i class="fas fa-print"></i> Print Report
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="shareReport()">
|
||||
<i class="fas fa-share"></i> Share Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div id="errorMessage" class="row mb-4" style="display: none;">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Error:</strong> <span id="errorText"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.metric-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 1rem;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.metric-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.metric-card.orders-quantity {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
}
|
||||
|
||||
.metric-card.production-launched {
|
||||
background: linear-gradient(135deg, #f3e5f5 0%, #ce93d8 100%);
|
||||
}
|
||||
|
||||
.metric-card.production-finished {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #a5d6a7 100%);
|
||||
}
|
||||
|
||||
.metric-card.orders-delivered {
|
||||
background: linear-gradient(135deg, #fff3e0 0%, #ffcc02 100%);
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-right: 1rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.metric-content h3 {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.metric-content p {
|
||||
margin: 0;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.quality-stats .stat-item {
|
||||
text-align: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.quality-stats .stat-item h4 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.quality-stats .stat-item.approved h4 {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.quality-stats .stat-item.rejected h4 {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.quality-stats .stat-item p {
|
||||
margin: 0;
|
||||
color: #6c757d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.metric-card {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
margin-right: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function setTodayDate() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('reportDate').value = today;
|
||||
generateDailyReport();
|
||||
}
|
||||
|
||||
function generateDailyReport() {
|
||||
const reportDate = document.getElementById('reportDate').value;
|
||||
|
||||
if (!reportDate) {
|
||||
showError('Please select a report date');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading indicator
|
||||
document.getElementById('loadingIndicator').style.display = 'block';
|
||||
document.getElementById('reportResults').style.display = 'none';
|
||||
document.getElementById('errorMessage').style.display = 'none';
|
||||
|
||||
// Make API call to get daily data
|
||||
fetch(`/daily_mirror/api/data?date=${reportDate}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
showError(data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update display with data
|
||||
updateDailyReport(data);
|
||||
|
||||
// Hide loading and show results
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
document.getElementById('reportResults').style.display = 'block';
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error generating daily report:', error);
|
||||
showError('Failed to generate daily report. Please try again.');
|
||||
});
|
||||
}
|
||||
|
||||
function updateDailyReport(data) {
|
||||
// Update date display
|
||||
document.getElementById('reportDateDisplay').textContent = data.date;
|
||||
|
||||
// Update key metrics
|
||||
document.getElementById('ordersQuantity').textContent = data.orders_quantity.toLocaleString();
|
||||
document.getElementById('productionLaunched').textContent = data.production_launched.toLocaleString();
|
||||
document.getElementById('productionFinished').textContent = data.production_finished.toLocaleString();
|
||||
document.getElementById('ordersDelivered').textContent = data.orders_delivered.toLocaleString();
|
||||
|
||||
// Update quality control data
|
||||
document.getElementById('qualityTotalScans').textContent = data.quality_scans.total_scans.toLocaleString();
|
||||
document.getElementById('qualityApprovedScans').textContent = data.quality_scans.approved_scans.toLocaleString();
|
||||
document.getElementById('qualityRejectedScans').textContent = data.quality_scans.rejected_scans.toLocaleString();
|
||||
document.getElementById('qualityApprovalRate').textContent = data.quality_scans.approval_rate + '%';
|
||||
document.getElementById('qualityApprovalBar').style.width = data.quality_scans.approval_rate + '%';
|
||||
|
||||
// Update FG quality data
|
||||
document.getElementById('fgQualityTotalScans').textContent = data.fg_quality_scans.total_scans.toLocaleString();
|
||||
document.getElementById('fgQualityApprovedScans').textContent = data.fg_quality_scans.approved_scans.toLocaleString();
|
||||
document.getElementById('fgQualityRejectedScans').textContent = data.fg_quality_scans.rejected_scans.toLocaleString();
|
||||
document.getElementById('fgQualityApprovalRate').textContent = data.fg_quality_scans.approval_rate + '%';
|
||||
document.getElementById('fgQualityApprovalBar').style.width = data.fg_quality_scans.approval_rate + '%';
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
document.getElementById('errorText').textContent = message;
|
||||
document.getElementById('errorMessage').style.display = 'block';
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
document.getElementById('reportResults').style.display = 'none';
|
||||
}
|
||||
|
||||
function exportReportPDF() {
|
||||
alert('PDF export functionality will be implemented soon.');
|
||||
}
|
||||
|
||||
function exportReportExcel() {
|
||||
alert('Excel export functionality will be implemented soon.');
|
||||
}
|
||||
|
||||
function printReport() {
|
||||
window.print();
|
||||
}
|
||||
|
||||
function shareReport() {
|
||||
alert('Share functionality will be implemented soon.');
|
||||
}
|
||||
|
||||
// Auto-generate today's report on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
generateDailyReport();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
719
py_app/app/templates/daily_mirror_build_database.html
Normal file
719
py_app/app/templates/daily_mirror_build_database.html
Normal file
@@ -0,0 +1,719 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Build Database - Daily Mirror{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">🔨 Build Database</h1>
|
||||
<p class="text-muted">Upload Excel files to populate Daily Mirror database tables</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6 mb-4">
|
||||
<!-- Card 1: Upload Excel File -->
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-upload"></i> Upload Excel File
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" enctype="multipart/form-data" id="uploadForm">
|
||||
<!-- Table Selection -->
|
||||
<div class="form-group mb-4">
|
||||
<label for="target_table" class="form-label">
|
||||
<strong>Select Target Table:</strong>
|
||||
</label>
|
||||
<select class="form-control" name="target_table" id="target_table" required>
|
||||
<option value="">-- Choose a table --</option>
|
||||
{% for table in available_tables %}
|
||||
<option value="{{ table.name }}" data-description="{{ table.description }}">
|
||||
{{ table.display }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small id="tableDescription" class="form-text text-muted mt-2">
|
||||
Select a table to see its description.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- File Upload -->
|
||||
<div class="form-group mb-4">
|
||||
<label for="excel_file" class="form-label">
|
||||
<strong>Select Excel File:</strong>
|
||||
</label>
|
||||
<input type="file" class="form-control" name="excel_file" id="excel_file"
|
||||
accept=".xlsx,.xls" required>
|
||||
<small class="form-text text-muted">
|
||||
Accepted formats: .xlsx, .xls (Maximum file size: 10MB)
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Upload Button -->
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-primary btn-lg" id="uploadBtn">
|
||||
<i class="fas fa-cloud-upload-alt"></i> Upload and Process File
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 mb-4">
|
||||
<!-- Card 2: Excel File Format Instructions -->
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-info-circle"></i> Excel File Format Instructions
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="accordion" id="formatAccordion">
|
||||
<!-- Production Data Format -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#productionCollapse" aria-expanded="false">
|
||||
🏭 Production Data Format
|
||||
</button>
|
||||
</h2>
|
||||
<div id="productionCollapse" class="accordion-collapse collapse" data-bs-parent="#formatAccordion">
|
||||
<div class="accordion-body">
|
||||
<p><strong>Expected columns for Production Data:</strong></p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Production Order ID</code> <span class="text-muted">Unique identifier</span></li>
|
||||
<li><code>Customer Code</code> <span class="text-muted">Customer code</span></li>
|
||||
<li><code>Customer Name</code> <span class="text-muted">Customer name</span></li>
|
||||
<li><code>Article Code</code> <span class="text-muted">Article code</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Article Description</code> <span class="text-muted">Description</span></li>
|
||||
<li><code>Quantity</code> <span class="text-muted">To produce</span></li>
|
||||
<li><code>Production Date</code> <span class="text-muted">Date</span></li>
|
||||
<li><code>Status</code> <span class="text-muted">Production status</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Orders Data Format -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#ordersCollapse" aria-expanded="false">
|
||||
🛒 Orders Data Format
|
||||
</button>
|
||||
</h2>
|
||||
<div id="ordersCollapse" class="accordion-collapse collapse" data-bs-parent="#formatAccordion">
|
||||
<div class="accordion-body">
|
||||
<p><strong>Expected columns for Orders Data:</strong></p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Order ID</code> <span class="text-muted">Unique identifier</span></li>
|
||||
<li><code>Customer Code</code> <span class="text-muted">Customer code</span></li>
|
||||
<li><code>Customer Name</code> <span class="text-muted">Customer name</span></li>
|
||||
<li><code>Article Code</code> <span class="text-muted">Article code</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Article Description</code> <span class="text-muted">Description</span></li>
|
||||
<li><code>Quantity Ordered</code> <span class="text-muted">Ordered</span></li>
|
||||
<li><code>Order Date</code> <span class="text-muted">Date</span></li>
|
||||
<li><code>Status</code> <span class="text-muted">Order status</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delivery Data Format -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#deliveryCollapse" aria-expanded="false">
|
||||
🚚 Delivery Data Format (Articole livrate)
|
||||
</button>
|
||||
</h2>
|
||||
<div id="deliveryCollapse" class="accordion-collapse collapse" data-bs-parent="#formatAccordion">
|
||||
<div class="accordion-body">
|
||||
<p><strong>Expected columns for Delivery Data:</strong></p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Shipment ID</code> <span class="text-muted">Unique shipment identifier</span></li>
|
||||
<li><code>Order ID</code> <span class="text-muted">Related order</span></li>
|
||||
<li><code>Customer</code> <span class="text-muted">Customer info</span></li>
|
||||
<li><code>Article</code> <span class="text-muted">Code/description</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Quantity Delivered</code> <span class="text-muted">Delivered quantity</span></li>
|
||||
<li><code>Delivery Date</code> <span class="text-muted">Date</span></li>
|
||||
<li><code>Status</code> <span class="text-muted">Delivery status</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Result Modal (Better Solution) -->
|
||||
<div class="modal fade" id="uploadResultModal" tabindex="-1" aria-labelledby="uploadResultModalLabel" aria-hidden="true" data-bs-backdrop="true" data-bs-keyboard="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header" id="modalHeader">
|
||||
<h5 class="modal-title" id="uploadResultModalLabel">
|
||||
<i class="fas fa-check-circle"></i> Upload Result
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" id="modalCloseBtn"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="uploadResultContent" class="text-center py-3">
|
||||
<!-- Result content will be inserted here -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" id="modalOkBtn">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.accordion-button:not(.collapsed) {
|
||||
background-color: #e7f3ff;
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Result stats styling */
|
||||
.upload-stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
min-width: 100px;
|
||||
margin: 5px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.stat-box.success {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.stat-box.warning {
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeeba;
|
||||
}
|
||||
|
||||
.stat-box.error {
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Reduce font size for Excel Format Instructions card rows */
|
||||
.col-lg-6:nth-child(2) .card-body {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Make accordion button labels smaller */
|
||||
.accordion-button {
|
||||
font-size: 1rem;
|
||||
padding-top: 0.4rem;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
/* Override h2 size in accordion headers */
|
||||
.accordion-header {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.accordion-header h2 {
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Modal summary list styling */
|
||||
#uploadResultContent ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
margin: 10px auto;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
#uploadResultContent ul li {
|
||||
padding: 5px 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#uploadResultContent ul li::before {
|
||||
content: '✓ ';
|
||||
color: #28a745;
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* Make "Expected columns" text smaller in accordion bodies */
|
||||
.accordion-body p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.accordion-body strong {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Dark mode styles */
|
||||
body.dark-mode .card {
|
||||
background-color: #2d3748;
|
||||
border-color: #4a5568;
|
||||
color: #e2e8f0;
|
||||
box-shadow: 0 4px 6px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
body.dark-mode .card-header {
|
||||
background-color: #4a5568;
|
||||
color: #e2e8f0;
|
||||
border-bottom: 1px solid rgba(226, 232, 240, 0.2);
|
||||
}
|
||||
|
||||
body.dark-mode .form-control {
|
||||
background-color: #4a5568;
|
||||
border-color: #6b7280;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .form-control:focus {
|
||||
background-color: #4a5568;
|
||||
border-color: #007bff;
|
||||
color: #e2e8f0;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
body.dark-mode .accordion-button {
|
||||
background-color: #374151;
|
||||
color: #e2e8f0;
|
||||
border-color: #6b7280;
|
||||
}
|
||||
|
||||
body.dark-mode .accordion-button:not(.collapsed) {
|
||||
background-color: #1e3a8a;
|
||||
color: #bfdbfe;
|
||||
}
|
||||
|
||||
body.dark-mode .accordion-body {
|
||||
background-color: #374151;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode code {
|
||||
background-color: #374151;
|
||||
color: #e2e8f0;
|
||||
border: 1px solid #6b7280;
|
||||
}
|
||||
|
||||
body.dark-mode .modal-content {
|
||||
background-color: #2d3748;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .modal-header {
|
||||
border-bottom-color: #4a5568;
|
||||
}
|
||||
|
||||
body.dark-mode .modal-footer {
|
||||
border-top-color: #4a5568;
|
||||
}
|
||||
|
||||
body.dark-mode .stat-box.success {
|
||||
background-color: #1e4620;
|
||||
border-color: #2d5a2e;
|
||||
color: #a3d9a5;
|
||||
}
|
||||
|
||||
body.dark-mode .stat-box.warning {
|
||||
background-color: #5a4a1e;
|
||||
border-color: #6b5a2d;
|
||||
color: #f4d88f;
|
||||
}
|
||||
|
||||
body.dark-mode .stat-box.error {
|
||||
background-color: #5a1e1e;
|
||||
border-color: #6b2d2d;
|
||||
color: #f8a3a8;
|
||||
}
|
||||
|
||||
body.dark-mode .stat-label {
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
body.dark-mode .text-muted {
|
||||
color: #a0aec0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize theme on page load
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const body = document.body;
|
||||
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
}
|
||||
|
||||
const tableSelect = document.getElementById('target_table');
|
||||
const tableDescription = document.getElementById('tableDescription');
|
||||
const uploadBtn = document.getElementById('uploadBtn');
|
||||
const uploadForm = document.getElementById('uploadForm');
|
||||
const fileInput = document.getElementById('excel_file');
|
||||
|
||||
// Update table description when selection changes
|
||||
tableSelect.addEventListener('change', function() {
|
||||
const selectedOption = this.options[this.selectedIndex];
|
||||
if (selectedOption.value) {
|
||||
const description = selectedOption.getAttribute('data-description');
|
||||
tableDescription.innerHTML = `<strong>Selected:</strong> ${description}`;
|
||||
tableDescription.className = 'form-text text-info mt-2';
|
||||
} else {
|
||||
tableDescription.innerHTML = 'Select a table to see its description.';
|
||||
tableDescription.className = 'form-text text-muted mt-2';
|
||||
}
|
||||
});
|
||||
|
||||
// File input change handler to show file info
|
||||
fileInput.addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const fileName = file.name;
|
||||
const fileSize = (file.size / 1024 / 1024).toFixed(2);
|
||||
console.log(`Selected file: ${fileName} (${fileSize} MB)`);
|
||||
}
|
||||
});
|
||||
|
||||
// Upload button click handler (AJAX submission)
|
||||
uploadBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Validate file selection
|
||||
if (!fileInput.files.length) {
|
||||
alert('Please select an Excel file to upload.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate table selection
|
||||
if (!tableSelect.value) {
|
||||
alert('Please select a target table.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check file size (10MB limit)
|
||||
const file = fileInput.files[0];
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
alert('File size must be less than 10MB.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare form data
|
||||
const formData = new FormData(uploadForm);
|
||||
|
||||
// Show loading state
|
||||
uploadBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
|
||||
uploadBtn.disabled = true;
|
||||
|
||||
// Submit via AJAX
|
||||
fetch('/daily_mirror/build_database', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then(err => Promise.reject(err));
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(result => {
|
||||
// Reset button
|
||||
uploadBtn.innerHTML = '<i class="fas fa-cloud-upload-alt"></i> Upload and Process File';
|
||||
uploadBtn.disabled = false;
|
||||
|
||||
// Show result in modal
|
||||
showUploadResult(result);
|
||||
|
||||
// Reset form
|
||||
uploadForm.reset();
|
||||
tableDescription.innerHTML = 'Select a table to see its description.';
|
||||
tableDescription.className = 'form-text text-muted mt-2';
|
||||
})
|
||||
.catch(error => {
|
||||
// Reset button
|
||||
uploadBtn.innerHTML = '<i class="fas fa-cloud-upload-alt"></i> Upload and Process File';
|
||||
uploadBtn.disabled = false;
|
||||
|
||||
// Show error in modal
|
||||
showUploadError(error);
|
||||
});
|
||||
});
|
||||
|
||||
function showUploadResult(result) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('uploadResultModal'));
|
||||
const modalHeader = document.getElementById('modalHeader');
|
||||
const modalTitle = document.getElementById('uploadResultModalLabel');
|
||||
const content = document.getElementById('uploadResultContent');
|
||||
|
||||
// Determine overall status
|
||||
const hasErrors = result.error_count && result.error_count > 0;
|
||||
const hasSuccess = result.created_rows > 0 || result.updated_rows > 0;
|
||||
|
||||
// Update modal header color
|
||||
if (hasErrors && !hasSuccess) {
|
||||
modalHeader.className = 'modal-header bg-danger text-white';
|
||||
modalTitle.innerHTML = '<i class="fas fa-times-circle"></i> Upload Failed';
|
||||
} else if (hasErrors && hasSuccess) {
|
||||
modalHeader.className = 'modal-header bg-warning text-dark';
|
||||
modalTitle.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Upload Completed with Warnings';
|
||||
} else {
|
||||
modalHeader.className = 'modal-header bg-success text-white';
|
||||
modalTitle.innerHTML = '<i class="fas fa-check-circle"></i> Upload Successful';
|
||||
}
|
||||
|
||||
// Build result content with stats
|
||||
let html = '<div class="upload-stats">';
|
||||
|
||||
// Total rows processed from Excel
|
||||
html += `
|
||||
<div class="stat-box ${hasErrors ? 'warning' : 'success'}">
|
||||
<span class="stat-value">${result.total_rows || 0}</span>
|
||||
<span class="stat-label">Rows Processed</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Created rows (new in database)
|
||||
if (result.created_rows > 0) {
|
||||
html += `
|
||||
<div class="stat-box success">
|
||||
<span class="stat-value">${result.created_rows}</span>
|
||||
<span class="stat-label">New Rows Created</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Updated rows (existing in database)
|
||||
if (result.updated_rows > 0) {
|
||||
html += `
|
||||
<div class="stat-box success">
|
||||
<span class="stat-value">${result.updated_rows}</span>
|
||||
<span class="stat-label">Rows Updated</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Errors
|
||||
if (hasErrors) {
|
||||
html += `
|
||||
<div class="stat-box error">
|
||||
<span class="stat-value">${result.error_count}</span>
|
||||
<span class="stat-label">Errors</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Add detailed summary message
|
||||
const successCount = (result.created_rows || 0) + (result.updated_rows || 0);
|
||||
if (successCount > 0) {
|
||||
let msg = `<p class="mt-3 mb-0"><strong>Successfully processed ${result.total_rows} rows from Excel:</strong></p>`;
|
||||
msg += '<ul class="text-start">';
|
||||
if (result.created_rows > 0) {
|
||||
msg += `<li>${result.created_rows} new ${result.created_rows === 1 ? 'record' : 'records'} created in database</li>`;
|
||||
}
|
||||
if (result.updated_rows > 0) {
|
||||
msg += `<li>${result.updated_rows} existing ${result.updated_rows === 1 ? 'record' : 'records'} updated</li>`;
|
||||
}
|
||||
msg += '</ul>';
|
||||
html += msg;
|
||||
}
|
||||
if (hasErrors) {
|
||||
html += `<p class="text-danger mb-0"><strong>⚠️ ${result.error_count} ${result.error_count === 1 ? 'row' : 'rows'} could not be processed due to errors.</strong></p>`;
|
||||
}
|
||||
|
||||
// Add auto-close countdown for successful uploads without errors
|
||||
if (!hasErrors && successCount > 0) {
|
||||
html += `<p class="text-muted mt-2 mb-0" id="autoCloseCountdown"><small>This window will close automatically in <span id="countdown">3</span> seconds...</small></p>`;
|
||||
}
|
||||
|
||||
content.innerHTML = html;
|
||||
modal.show();
|
||||
|
||||
// Get modal element
|
||||
const modalElement = document.getElementById('uploadResultModal');
|
||||
|
||||
// Add explicit close handlers
|
||||
const okBtn = document.getElementById('modalOkBtn');
|
||||
const closeBtn = document.getElementById('modalCloseBtn');
|
||||
|
||||
if (okBtn) {
|
||||
okBtn.onclick = function() {
|
||||
modal.hide();
|
||||
// Also trigger Bootstrap's native close
|
||||
modalElement.classList.remove('show');
|
||||
document.querySelector('.modal-backdrop')?.remove();
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.paddingRight = '';
|
||||
};
|
||||
}
|
||||
|
||||
if (closeBtn) {
|
||||
closeBtn.onclick = function() {
|
||||
modal.hide();
|
||||
// Also trigger Bootstrap's native close
|
||||
modalElement.classList.remove('show');
|
||||
document.querySelector('.modal-backdrop')?.remove();
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.paddingRight = '';
|
||||
};
|
||||
}
|
||||
|
||||
// Auto-close after 3 seconds for successful uploads without errors
|
||||
if (!hasErrors && successCount > 0) {
|
||||
let countdown = 3;
|
||||
const countdownInterval = setInterval(function() {
|
||||
countdown--;
|
||||
const countdownSpan = document.getElementById('countdown');
|
||||
if (countdownSpan) {
|
||||
countdownSpan.textContent = countdown;
|
||||
}
|
||||
if (countdown <= 0) {
|
||||
clearInterval(countdownInterval);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
setTimeout(function() {
|
||||
clearInterval(countdownInterval);
|
||||
modal.hide();
|
||||
modalElement.classList.remove('show');
|
||||
document.querySelector('.modal-backdrop')?.remove();
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.paddingRight = '';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function showUploadError(error) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('uploadResultModal'));
|
||||
const modalHeader = document.getElementById('modalHeader');
|
||||
const modalTitle = document.getElementById('uploadResultModalLabel');
|
||||
const content = document.getElementById('uploadResultContent');
|
||||
|
||||
// Update modal header
|
||||
modalHeader.className = 'modal-header bg-danger text-white';
|
||||
modalTitle.innerHTML = '<i class="fas fa-times-circle"></i> Upload Error';
|
||||
|
||||
// Show error message
|
||||
const errorMsg = error.error || error.message || 'An unexpected error occurred during upload.';
|
||||
content.innerHTML = `
|
||||
<div class="alert alert-danger mb-0">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Error:</strong> ${errorMsg}
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.show();
|
||||
|
||||
// Get modal element
|
||||
const modalElement = document.getElementById('uploadResultModal');
|
||||
|
||||
// Add explicit close handlers
|
||||
const okBtn = document.getElementById('modalOkBtn');
|
||||
const closeBtn = document.getElementById('modalCloseBtn');
|
||||
|
||||
if (okBtn) {
|
||||
okBtn.onclick = function() {
|
||||
modal.hide();
|
||||
// Also trigger Bootstrap's native close
|
||||
modalElement.classList.remove('show');
|
||||
document.querySelector('.modal-backdrop')?.remove();
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.paddingRight = '';
|
||||
};
|
||||
}
|
||||
|
||||
if (closeBtn) {
|
||||
closeBtn.onclick = function() {
|
||||
modal.hide();
|
||||
// Also trigger Bootstrap's native close
|
||||
modalElement.classList.remove('show');
|
||||
document.querySelector('.modal-backdrop')?.remove();
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.paddingRight = '';
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
449
py_app/app/templates/daily_mirror_history.html
Normal file
449
py_app/app/templates/daily_mirror_history.html
Normal file
@@ -0,0 +1,449 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Daily Mirror History - Quality Recticel{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">📋 Daily Mirror History</h1>
|
||||
<p class="text-muted">Analyze historical daily production reports and trends</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_route') }}" class="btn btn-outline-success">
|
||||
<i class="fas fa-chart-line"></i> Create New Report
|
||||
</a>
|
||||
<a href="{{ url_for('main.dashboard') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Range Selection -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-calendar-week"></i> Select Date Range
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label for="startDate" class="form-label">Start Date:</label>
|
||||
<input type="date" class="form-control" id="startDate" value="{{ start_date }}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="endDate" class="form-label">End Date:</label>
|
||||
<input type="date" class="form-control" id="endDate" value="{{ end_date }}">
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-primary" onclick="loadHistoryData()">
|
||||
<i class="fas fa-search"></i> Load History
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="setDateRange(7)">Last 7 days</button>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="setDateRange(30)">Last 30 days</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading Indicator -->
|
||||
<div id="loadingIndicator" class="row mb-4" style="display: none;">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-2 mb-0">Loading historical data...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Statistics -->
|
||||
<div id="summaryStats" style="display: none;">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-chart-bar"></i> Period Summary
|
||||
<span id="periodRange" class="badge bg-secondary ms-2"></span>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="summary-metric">
|
||||
<h4 id="totalOrdersQuantity">-</h4>
|
||||
<p>Total Orders Quantity</p>
|
||||
<small id="avgOrdersQuantity" class="text-muted">Avg: -</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="summary-metric">
|
||||
<h4 id="totalProductionLaunched">-</h4>
|
||||
<p>Total Production Launched</p>
|
||||
<small id="avgProductionLaunched" class="text-muted">Avg: -</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="summary-metric">
|
||||
<h4 id="totalProductionFinished">-</h4>
|
||||
<p>Total Production Finished</p>
|
||||
<small id="avgProductionFinished" class="text-muted">Avg: -</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="summary-metric">
|
||||
<h4 id="totalOrdersDelivered">-</h4>
|
||||
<p>Total Orders Delivered</p>
|
||||
<small id="avgOrdersDelivered" class="text-muted">Avg: -</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Historical Data Table -->
|
||||
<div id="historyTable" style="display: none;">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-table"></i> Historical Daily Reports
|
||||
</h5>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button type="button" class="btn btn-outline-primary" onclick="exportHistoryCSV()">
|
||||
<i class="fas fa-file-csv"></i> CSV
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-success" onclick="exportHistoryExcel()">
|
||||
<i class="fas fa-file-excel"></i> Excel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover" id="historyDataTable">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Orders Quantity</th>
|
||||
<th>Production Launched</th>
|
||||
<th>Production Finished</th>
|
||||
<th>Orders Delivered</th>
|
||||
<th>Quality Approval Rate</th>
|
||||
<th>FG Quality Approval Rate</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="historyTableBody">
|
||||
<!-- Data will be populated here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<nav aria-label="History pagination" id="historyPagination" style="display: none;">
|
||||
<ul class="pagination pagination-sm justify-content-center">
|
||||
<!-- Pagination will be populated here -->
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chart Visualization -->
|
||||
<div id="chartVisualization" style="display: none;">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-chart-line"></i> Trend Analysis
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="trendChart" height="100"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div id="errorMessage" class="row mb-4" style="display: none;">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Error:</strong> <span id="errorText"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.summary-metric {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.summary-metric h4 {
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.summary-metric p {
|
||||
margin: 0.5rem 0;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.table th {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.approval-rate {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.approval-rate.high {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.approval-rate.medium {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.approval-rate.low {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.table-responsive {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
let historyData = [];
|
||||
let currentPage = 1;
|
||||
const itemsPerPage = 20;
|
||||
|
||||
function setDateRange(days) {
|
||||
const endDate = new Date();
|
||||
const startDate = new Date();
|
||||
startDate.setDate(endDate.getDate() - days);
|
||||
|
||||
document.getElementById('endDate').value = endDate.toISOString().split('T')[0];
|
||||
document.getElementById('startDate').value = startDate.toISOString().split('T')[0];
|
||||
|
||||
loadHistoryData();
|
||||
}
|
||||
|
||||
function loadHistoryData() {
|
||||
const startDate = document.getElementById('startDate').value;
|
||||
const endDate = document.getElementById('endDate').value;
|
||||
|
||||
if (!startDate || !endDate) {
|
||||
showError('Please select both start and end dates');
|
||||
return;
|
||||
}
|
||||
|
||||
if (new Date(startDate) > new Date(endDate)) {
|
||||
showError('Start date cannot be after end date');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading indicator
|
||||
document.getElementById('loadingIndicator').style.display = 'block';
|
||||
document.getElementById('summaryStats').style.display = 'none';
|
||||
document.getElementById('historyTable').style.display = 'none';
|
||||
document.getElementById('chartVisualization').style.display = 'none';
|
||||
document.getElementById('errorMessage').style.display = 'none';
|
||||
|
||||
// Make API call to get historical data
|
||||
fetch(`/daily_mirror/api/history_data?start_date=${startDate}&end_date=${endDate}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
showError(data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
historyData = data.history;
|
||||
|
||||
// Update displays
|
||||
updateSummaryStats(data);
|
||||
updateHistoryTable();
|
||||
updateTrendChart();
|
||||
|
||||
// Hide loading and show results
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
document.getElementById('summaryStats').style.display = 'block';
|
||||
document.getElementById('historyTable').style.display = 'block';
|
||||
document.getElementById('chartVisualization').style.display = 'block';
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading history data:', error);
|
||||
showError('Failed to load historical data. Please try again.');
|
||||
});
|
||||
}
|
||||
|
||||
function updateSummaryStats(data) {
|
||||
const history = data.history;
|
||||
|
||||
if (history.length === 0) {
|
||||
document.getElementById('periodRange').textContent = 'No Data';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('periodRange').textContent = `${data.start_date} to ${data.end_date}`;
|
||||
|
||||
// Calculate totals and averages
|
||||
const totals = history.reduce((acc, day) => {
|
||||
acc.ordersQuantity += day.orders_quantity;
|
||||
acc.productionLaunched += day.production_launched;
|
||||
acc.productionFinished += day.production_finished;
|
||||
acc.ordersDelivered += day.orders_delivered;
|
||||
return acc;
|
||||
}, { ordersQuantity: 0, productionLaunched: 0, productionFinished: 0, ordersDelivered: 0 });
|
||||
|
||||
const avgDivisor = history.length;
|
||||
|
||||
document.getElementById('totalOrdersQuantity').textContent = totals.ordersQuantity.toLocaleString();
|
||||
document.getElementById('avgOrdersQuantity').textContent = `Avg: ${Math.round(totals.ordersQuantity / avgDivisor).toLocaleString()}`;
|
||||
|
||||
document.getElementById('totalProductionLaunched').textContent = totals.productionLaunched.toLocaleString();
|
||||
document.getElementById('avgProductionLaunched').textContent = `Avg: ${Math.round(totals.productionLaunched / avgDivisor).toLocaleString()}`;
|
||||
|
||||
document.getElementById('totalProductionFinished').textContent = totals.productionFinished.toLocaleString();
|
||||
document.getElementById('avgProductionFinished').textContent = `Avg: ${Math.round(totals.productionFinished / avgDivisor).toLocaleString()}`;
|
||||
|
||||
document.getElementById('totalOrdersDelivered').textContent = totals.ordersDelivered.toLocaleString();
|
||||
document.getElementById('avgOrdersDelivered').textContent = `Avg: ${Math.round(totals.ordersDelivered / avgDivisor).toLocaleString()}`;
|
||||
}
|
||||
|
||||
function updateHistoryTable() {
|
||||
const tbody = document.getElementById('historyTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = startIndex + itemsPerPage;
|
||||
const pageData = historyData.slice(startIndex, endIndex);
|
||||
|
||||
pageData.forEach(day => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const qualityRate = day.quality_scans.approval_rate;
|
||||
const fgQualityRate = day.fg_quality_scans.approval_rate;
|
||||
|
||||
row.innerHTML = `
|
||||
<td><strong>${day.date}</strong></td>
|
||||
<td>${day.orders_quantity.toLocaleString()}</td>
|
||||
<td>${day.production_launched.toLocaleString()}</td>
|
||||
<td>${day.production_finished.toLocaleString()}</td>
|
||||
<td>${day.orders_delivered.toLocaleString()}</td>
|
||||
<td><span class="approval-rate ${getApprovalRateClass(qualityRate)}">${qualityRate}%</span></td>
|
||||
<td><span class="approval-rate ${getApprovalRateClass(fgQualityRate)}">${fgQualityRate}%</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="viewDayDetails('${day.date}')">
|
||||
<i class="fas fa-eye"></i> View
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
updatePagination();
|
||||
}
|
||||
|
||||
function getApprovalRateClass(rate) {
|
||||
if (rate >= 95) return 'high';
|
||||
if (rate >= 85) return 'medium';
|
||||
return 'low';
|
||||
}
|
||||
|
||||
function updatePagination() {
|
||||
const totalPages = Math.ceil(historyData.length / itemsPerPage);
|
||||
|
||||
if (totalPages <= 1) {
|
||||
document.getElementById('historyPagination').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('historyPagination').style.display = 'block';
|
||||
// Pagination implementation can be added here
|
||||
}
|
||||
|
||||
function updateTrendChart() {
|
||||
// Chart implementation using Chart.js can be added here
|
||||
// For now, we'll show a placeholder
|
||||
const canvas = document.getElementById('trendChart');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Clear canvas
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Draw placeholder text
|
||||
ctx.font = '16px Arial';
|
||||
ctx.fillStyle = '#6c757d';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('Trend chart visualization will be implemented here', canvas.width / 2, canvas.height / 2);
|
||||
}
|
||||
|
||||
function viewDayDetails(date) {
|
||||
// Navigate to daily mirror with specific date
|
||||
window.open(`{{ url_for('daily_mirror.daily_mirror_route') }}?date=${date}`, '_blank');
|
||||
}
|
||||
|
||||
function exportHistoryCSV() {
|
||||
alert('CSV export functionality will be implemented soon.');
|
||||
}
|
||||
|
||||
function exportHistoryExcel() {
|
||||
alert('Excel export functionality will be implemented soon.');
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
document.getElementById('errorText').textContent = message;
|
||||
document.getElementById('errorMessage').style.display = 'block';
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
document.getElementById('summaryStats').style.display = 'none';
|
||||
document.getElementById('historyTable').style.display = 'none';
|
||||
document.getElementById('chartVisualization').style.display = 'none';
|
||||
}
|
||||
|
||||
// Auto-load data on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadHistoryData();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
262
py_app/app/templates/daily_mirror_main.html
Normal file
262
py_app/app/templates/daily_mirror_main.html
Normal file
@@ -0,0 +1,262 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Daily Mirror - Quality Recticel{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">📊 Daily Mirror</h1>
|
||||
<p class="text-muted">Business Intelligence and Production Reporting</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Buttons removed; now present in top header -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Daily Mirror Cards -->
|
||||
<div class="row">
|
||||
<!-- Card 1: Build Database -->
|
||||
<div class="col-lg-6 col-md-6 mb-4">
|
||||
<div class="card h-100 daily-mirror-card">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="text-center mb-3">
|
||||
<div class="feature-icon bg-primary">
|
||||
<i class="fas fa-database"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title text-center">Build Database</h5>
|
||||
<p class="card-text flex-grow-1 text-center">
|
||||
Upload Excel files to create and populate tables.
|
||||
</p>
|
||||
<div class="mt-auto">
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_build_database') }}" class="btn btn-primary btn-block w-100">
|
||||
<i class="fas fa-hammer"></i> Build Database
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 2: Tune Database -->
|
||||
<div class="col-lg-6 col-md-6 mb-4">
|
||||
<div class="card h-100 daily-mirror-card">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="text-center mb-3">
|
||||
<div class="feature-icon bg-warning">
|
||||
<i class="fas fa-edit"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title text-center">Tune Database</h5>
|
||||
<p class="card-text flex-grow-1 text-center">
|
||||
Edit and update records after import.
|
||||
</p>
|
||||
<div class="mt-auto">
|
||||
<a href="{{ url_for('daily_mirror.tune_production_data') }}" class="btn btn-warning btn-block w-100 btn-sm mb-2">
|
||||
<i class="fas fa-industry"></i> Production Orders
|
||||
</a>
|
||||
<a href="{{ url_for('daily_mirror.tune_orders_data') }}" class="btn btn-warning btn-block w-100 btn-sm mb-2">
|
||||
<i class="fas fa-shopping-cart"></i> Customer Orders
|
||||
</a>
|
||||
<a href="{{ url_for('daily_mirror.tune_delivery_data') }}" class="btn btn-warning btn-block w-100 btn-sm">
|
||||
<i class="fas fa-truck"></i> Delivery Records
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 3: Daily Mirror -->
|
||||
<div class="col-lg-6 col-md-6 mb-4">
|
||||
<div class="card h-100 daily-mirror-card">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="text-center mb-3">
|
||||
<div class="feature-icon bg-success">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title text-center">Daily Mirror</h5>
|
||||
<p class="card-text flex-grow-1 text-center">
|
||||
Generate daily production reports.
|
||||
</p>
|
||||
<div class="mt-auto">
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_route') }}" class="btn btn-success btn-block w-100">
|
||||
<i class="fas fa-plus-circle"></i> Create Daily Report
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 4: Daily Mirror History -->
|
||||
<div class="col-lg-6 col-md-6 mb-4">
|
||||
<div class="card h-100 daily-mirror-card">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="text-center mb-3">
|
||||
<div class="feature-icon bg-info">
|
||||
<i class="fas fa-history"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title text-center">Daily Mirror History</h5>
|
||||
<p class="card-text flex-grow-1 text-center">
|
||||
View historical production reports.
|
||||
</p>
|
||||
<div class="mt-auto">
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_history_route') }}" class="btn btn-info btn-block w-100">
|
||||
<i class="fas fa-chart-bar"></i> View History
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.daily-mirror-card {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
border: none;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
background-color: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.daily-mirror-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
/* Light mode styles */
|
||||
body:not(.dark-mode) .daily-mirror-card {
|
||||
background-color: #ffffff;
|
||||
color: #333333;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
body:not(.dark-mode) .card-text {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
body:not(.dark-mode) .text-muted {
|
||||
color: #6c757d !important;
|
||||
}
|
||||
|
||||
/* Dark mode styles */
|
||||
body.dark-mode .daily-mirror-card {
|
||||
background-color: #2d3748;
|
||||
color: #e2e8f0;
|
||||
border: 1px solid #4a5568;
|
||||
box-shadow: 0 2px 4px rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
body.dark-mode .daily-mirror-card:hover {
|
||||
box-shadow: 0 4px 15px rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
body.dark-mode .card-text {
|
||||
color: #cbd5e0;
|
||||
}
|
||||
|
||||
body.dark-mode .text-muted {
|
||||
color: #a0aec0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .h3 {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .container-fluid {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
/* Ensure buttons maintain their intended colors in both themes */
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: linear-gradient(135deg, #ffc107 0%, #e0a800 100%);
|
||||
border: none;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(135deg, #28a745 0%, #1e7e34 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #6c757d 0%, #545b62 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.feature-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}</style>
|
||||
|
||||
<script>
|
||||
// Initialize theme on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Apply saved theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const body = document.body;
|
||||
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
}
|
||||
|
||||
// Update theme toggle button text if it exists
|
||||
const themeToggleButton = document.getElementById('theme-toggle');
|
||||
if (themeToggleButton) {
|
||||
if (body.classList.contains('dark-mode')) {
|
||||
themeToggleButton.textContent = 'Change to Light Mode';
|
||||
} else {
|
||||
themeToggleButton.textContent = 'Change to Dark Mode';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function showComingSoon(feature) {
|
||||
alert(`${feature} functionality will be available in a future update!\n\nThis feature is currently under development and will include advanced capabilities for enhanced Daily Mirror operations.`);
|
||||
}
|
||||
|
||||
// Auto-refresh quick stats every 5 minutes
|
||||
setInterval(function() {
|
||||
// This could be implemented to refresh the quick stats
|
||||
console.log('Auto-refresh daily stats (not implemented yet)');
|
||||
}, 300000); // 5 minutes
|
||||
</script>
|
||||
{% endblock %}
|
||||
503
py_app/app/templates/daily_mirror_tune_delivery.html
Normal file
503
py_app/app/templates/daily_mirror_tune_delivery.html
Normal file
@@ -0,0 +1,503 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Tune Delivery Data - Daily Mirror{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/daily_mirror_tune.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">🚚 Tune Delivery Data</h1>
|
||||
<p class="text-muted">Edit and update delivery records information</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Buttons removed; now present in top header -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-filter"></i> Filters and Search
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="searchInput" class="form-label">Search</label>
|
||||
<input type="text" class="form-control" id="searchInput"
|
||||
placeholder="Search by shipment, customer, or article...">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="statusFilter" class="form-label">Delivery Status</label>
|
||||
<select class="form-control" id="statusFilter">
|
||||
<option value="">All Statuses</option>
|
||||
<!-- Will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="customerFilter" class="form-label">Customer</label>
|
||||
<select class="form-control" id="customerFilter">
|
||||
<option value="">All Customers</option>
|
||||
<!-- Will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="recordsPerPage" class="form-label">Records per page</label>
|
||||
<select class="form-control" id="recordsPerPage">
|
||||
<option value="25">25</option>
|
||||
<option value="50" selected>50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" onclick="loadDeliveryData()">
|
||||
<i class="fas fa-search"></i> Apply Filters
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="clearFilters()">
|
||||
<i class="fas fa-times"></i> Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table Section -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-table"></i> Delivery Records Data
|
||||
</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<span id="recordsInfo" class="text-muted me-3"></span>
|
||||
<button class="btn btn-success btn-sm" onclick="saveAllChanges()">
|
||||
<i class="fas fa-save"></i> Save All Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover" id="deliveryTable">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Shipment ID</th>
|
||||
<th>Customer</th>
|
||||
<th>Order ID</th>
|
||||
<th>Article Code</th>
|
||||
<th>Description</th>
|
||||
<th>Quantity</th>
|
||||
<th>Shipment Date</th>
|
||||
<th>Delivery Date</th>
|
||||
<th>Status</th>
|
||||
<th>Total Value</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="deliveryTableBody">
|
||||
<!-- Data will be loaded here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div id="loadingIndicator" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
||||
<p class="mt-2">Loading data...</p>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div id="noDataMessage" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-info-circle fa-2x text-muted"></i>
|
||||
<p class="mt-2 text-muted">No delivery records found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="card-footer">
|
||||
<nav aria-label="Delivery data pagination">
|
||||
<ul class="pagination pagination-sm justify-content-center mb-0" id="pagination">
|
||||
<!-- Pagination will be generated here -->
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit Delivery Record</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editForm">
|
||||
<input type="hidden" id="editRecordId">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editShipmentId" class="form-label">Shipment ID</label>
|
||||
<input type="text" class="form-control" id="editShipmentId" readonly>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editOrderId" class="form-label">Order ID</label>
|
||||
<input type="text" class="form-control" id="editOrderId">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerCode" class="form-label">Customer Code</label>
|
||||
<input type="text" class="form-control" id="editCustomerCode">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerName" class="form-label">Customer Name</label>
|
||||
<input type="text" class="form-control" id="editCustomerName">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editArticleCode" class="form-label">Article Code</label>
|
||||
<input type="text" class="form-control" id="editArticleCode">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editQuantity" class="form-label">Quantity Delivered</label>
|
||||
<input type="number" class="form-control" id="editQuantity">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="editDescription" class="form-label">Article Description</label>
|
||||
<textarea class="form-control" id="editDescription" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editShipmentDate" class="form-label">Shipment Date</label>
|
||||
<input type="date" class="form-control" id="editShipmentDate">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editDeliveryDate" class="form-label">Delivery Date</label>
|
||||
<input type="date" class="form-control" id="editDeliveryDate">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editDeliveryStatus" class="form-label">Delivery Status</label>
|
||||
<select class="form-control" id="editDeliveryStatus">
|
||||
<option value="Finalizat">Finalizat</option>
|
||||
<option value="Proiect">Proiect</option>
|
||||
<option value="SHIPPED">Shipped</option>
|
||||
<option value="DELIVERED">Delivered</option>
|
||||
<option value="RETURNED">Returned</option>
|
||||
<option value="PARTIAL">Partial</option>
|
||||
<option value="CANCELLED">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editTotalValue" class="form-label">Total Value (€)</label>
|
||||
<input type="number" step="0.01" class="form-control" id="editTotalValue">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveRecord()">
|
||||
<i class="fas fa-save"></i> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentPerPage = 50;
|
||||
let currentSearch = '';
|
||||
let currentStatusFilter = '';
|
||||
let currentCustomerFilter = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize theme
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
|
||||
// Load initial data
|
||||
loadDeliveryData();
|
||||
|
||||
// Setup search on enter key
|
||||
document.getElementById('searchInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
loadDeliveryData();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadDeliveryData(page = 1) {
|
||||
currentPage = page;
|
||||
currentPerPage = document.getElementById('recordsPerPage').value;
|
||||
currentSearch = document.getElementById('searchInput').value;
|
||||
currentStatusFilter = document.getElementById('statusFilter').value;
|
||||
currentCustomerFilter = document.getElementById('customerFilter').value;
|
||||
|
||||
// Show loading indicator
|
||||
document.getElementById('loadingIndicator').style.display = 'block';
|
||||
document.getElementById('deliveryTableBody').style.display = 'none';
|
||||
document.getElementById('noDataMessage').style.display = 'none';
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: currentPage,
|
||||
per_page: currentPerPage,
|
||||
search: currentSearch,
|
||||
status: currentStatusFilter,
|
||||
customer: currentCustomerFilter
|
||||
});
|
||||
|
||||
fetch(`/daily_mirror/api/tune/delivery_data?${params}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
|
||||
if (data.success) {
|
||||
if (data.data.length === 0) {
|
||||
document.getElementById('noDataMessage').style.display = 'block';
|
||||
} else {
|
||||
displayDeliveryData(data.data);
|
||||
updatePagination(data);
|
||||
updateRecordsInfo(data);
|
||||
|
||||
// Populate filter dropdowns on first load
|
||||
if (currentPage === 1) {
|
||||
populateCustomerFilter(data.customers);
|
||||
populateStatusFilter(data.statuses);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('Error loading data:', data.error);
|
||||
alert('Error loading delivery data: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
console.error('Error:', error);
|
||||
alert('Error loading delivery data: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function displayDeliveryData(data) {
|
||||
const tbody = document.getElementById('deliveryTableBody');
|
||||
tbody.innerHTML = '';
|
||||
tbody.style.display = 'table-row-group';
|
||||
|
||||
data.forEach(record => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td><strong>${record.shipment_id}</strong></td>
|
||||
<td>
|
||||
<small class="text-muted d-block">${record.customer_code}</small>
|
||||
${record.customer_name}
|
||||
</td>
|
||||
<td>${record.order_id || '-'}</td>
|
||||
<td><code>${record.article_code}</code></td>
|
||||
<td><small>${record.article_description || '-'}</small></td>
|
||||
<td><span class="badge bg-info">${record.quantity_delivered}</span></td>
|
||||
<td>${record.shipment_date || '-'}</td>
|
||||
<td>${record.delivery_date || '-'}</td>
|
||||
<td><span class="badge bg-success">${record.delivery_status}</span></td>
|
||||
<td><strong>€${parseFloat(record.total_value || 0).toFixed(2)}</strong></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="editRecord(${record.id})"
|
||||
title="Edit Delivery">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function populateCustomerFilter(customers) {
|
||||
const filter = document.getElementById('customerFilter');
|
||||
const currentValue = filter.value;
|
||||
filter.innerHTML = '<option value="">All Customers</option>';
|
||||
|
||||
customers.forEach(customer => {
|
||||
const option = document.createElement('option');
|
||||
option.value = customer.code;
|
||||
option.textContent = `${customer.code} - ${customer.name}`;
|
||||
filter.appendChild(option);
|
||||
});
|
||||
|
||||
filter.value = currentValue;
|
||||
}
|
||||
|
||||
function populateStatusFilter(statuses) {
|
||||
const filter = document.getElementById('statusFilter');
|
||||
const currentValue = filter.value;
|
||||
filter.innerHTML = '<option value="">All Statuses</option>';
|
||||
|
||||
statuses.forEach(status => {
|
||||
const option = document.createElement('option');
|
||||
option.value = status;
|
||||
option.textContent = status;
|
||||
filter.appendChild(option);
|
||||
});
|
||||
|
||||
filter.value = currentValue;
|
||||
}
|
||||
|
||||
function updatePagination(data) {
|
||||
const pagination = document.getElementById('pagination');
|
||||
pagination.innerHTML = '';
|
||||
|
||||
if (data.total_pages <= 1) return;
|
||||
|
||||
// Previous button
|
||||
const prevLi = document.createElement('li');
|
||||
prevLi.className = `page-item ${data.page === 1 ? 'disabled' : ''}`;
|
||||
prevLi.innerHTML = `<a class="page-link" href="#" onclick="loadDeliveryData(${data.page - 1})">Previous</a>`;
|
||||
pagination.appendChild(prevLi);
|
||||
|
||||
// Page numbers
|
||||
const startPage = Math.max(1, data.page - 2);
|
||||
const endPage = Math.min(data.total_pages, data.page + 2);
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
const li = document.createElement('li');
|
||||
li.className = `page-item ${i === data.page ? 'active' : ''}`;
|
||||
li.innerHTML = `<a class="page-link" href="#" onclick="loadDeliveryData(${i})">${i}</a>`;
|
||||
pagination.appendChild(li);
|
||||
}
|
||||
|
||||
// Next button
|
||||
const nextLi = document.createElement('li');
|
||||
nextLi.className = `page-item ${data.page === data.total_pages ? 'disabled' : ''}`;
|
||||
nextLi.innerHTML = `<a class="page-link" href="#" onclick="loadDeliveryData(${data.page + 1})">Next</a>`;
|
||||
pagination.appendChild(nextLi);
|
||||
}
|
||||
|
||||
function updateRecordsInfo(data) {
|
||||
const start = (data.page - 1) * data.per_page + 1;
|
||||
const end = Math.min(data.page * data.per_page, data.total_records);
|
||||
document.getElementById('recordsInfo').textContent =
|
||||
`Showing ${start}-${end} of ${data.total_records} deliveries`;
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
document.getElementById('searchInput').value = '';
|
||||
document.getElementById('statusFilter').value = '';
|
||||
document.getElementById('customerFilter').value = '';
|
||||
loadDeliveryData(1);
|
||||
}
|
||||
|
||||
function editRecord(recordId) {
|
||||
// Get data via API for editing
|
||||
fetch(`/daily_mirror/api/tune/delivery_data?page=${currentPage}&per_page=${currentPerPage}&search=${currentSearch}&status=${currentStatusFilter}&customer=${currentCustomerFilter}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const recordData = data.data.find(record => record.id === recordId);
|
||||
if (recordData) {
|
||||
populateEditModal(recordData);
|
||||
const editModal = new bootstrap.Modal(document.getElementById('editModal'));
|
||||
editModal.show();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error loading record data: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function populateEditModal(record) {
|
||||
document.getElementById('editRecordId').value = record.id;
|
||||
document.getElementById('editShipmentId').value = record.shipment_id;
|
||||
document.getElementById('editOrderId').value = record.order_id || '';
|
||||
document.getElementById('editCustomerCode').value = record.customer_code;
|
||||
document.getElementById('editCustomerName').value = record.customer_name;
|
||||
document.getElementById('editArticleCode').value = record.article_code;
|
||||
document.getElementById('editDescription').value = record.article_description || '';
|
||||
document.getElementById('editQuantity').value = record.quantity_delivered;
|
||||
document.getElementById('editShipmentDate').value = record.shipment_date;
|
||||
document.getElementById('editDeliveryDate').value = record.delivery_date;
|
||||
document.getElementById('editDeliveryStatus').value = record.delivery_status;
|
||||
document.getElementById('editTotalValue').value = record.total_value;
|
||||
}
|
||||
|
||||
function saveRecord() {
|
||||
const recordId = document.getElementById('editRecordId').value;
|
||||
const data = {
|
||||
customer_code: document.getElementById('editCustomerCode').value,
|
||||
customer_name: document.getElementById('editCustomerName').value,
|
||||
order_id: document.getElementById('editOrderId').value,
|
||||
article_code: document.getElementById('editArticleCode').value,
|
||||
article_description: document.getElementById('editDescription').value,
|
||||
quantity_delivered: parseInt(document.getElementById('editQuantity').value) || 0,
|
||||
shipment_date: document.getElementById('editShipmentDate').value,
|
||||
delivery_date: document.getElementById('editDeliveryDate').value,
|
||||
delivery_status: document.getElementById('editDeliveryStatus').value,
|
||||
total_value: parseFloat(document.getElementById('editTotalValue').value) || 0
|
||||
};
|
||||
|
||||
fetch(`/daily_mirror/api/tune/delivery_data/${recordId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
// Close modal
|
||||
const editModal = bootstrap.Modal.getInstance(document.getElementById('editModal'));
|
||||
editModal.hide();
|
||||
|
||||
// Reload data
|
||||
loadDeliveryData(currentPage);
|
||||
|
||||
// Show success message
|
||||
alert('Delivery record updated successfully!');
|
||||
} else {
|
||||
alert('Error updating delivery record: ' + result.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error updating delivery record: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function saveAllChanges() {
|
||||
alert('Save All Changes functionality will be implemented for bulk operations.');
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
522
py_app/app/templates/daily_mirror_tune_orders.html
Normal file
522
py_app/app/templates/daily_mirror_tune_orders.html
Normal file
@@ -0,0 +1,522 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Tune Orders Data - Daily Mirror{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/daily_mirror_tune.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">🛒 Tune Orders Data</h1>
|
||||
<p class="text-muted">Edit and update customer orders information</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Buttons removed; now present in top header -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-filter"></i> Filters and Search
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="searchInput" class="form-label">Search</label>
|
||||
<input type="text" class="form-control" id="searchInput"
|
||||
placeholder="Search by order, customer, or article...">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="statusFilter" class="form-label">Order Status</label>
|
||||
<select class="form-control" id="statusFilter">
|
||||
<option value="">All Statuses</option>
|
||||
<!-- Will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="customerFilter" class="form-label">Customer</label>
|
||||
<select class="form-control" id="customerFilter">
|
||||
<option value="">All Customers</option>
|
||||
<!-- Will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="recordsPerPage" class="form-label">Records per page</label>
|
||||
<select class="form-control" id="recordsPerPage">
|
||||
<option value="25">25</option>
|
||||
<option value="50" selected>50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" onclick="loadOrdersData()">
|
||||
<i class="fas fa-search"></i> Apply Filters
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="clearFilters()">
|
||||
<i class="fas fa-times"></i> Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table Section -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-table"></i> Customer Orders Data
|
||||
</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<span id="recordsInfo" class="text-muted me-3"></span>
|
||||
<button class="btn btn-success btn-sm" onclick="saveAllChanges()">
|
||||
<i class="fas fa-save"></i> Save All Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover" id="ordersTable">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Order ID</th>
|
||||
<th>Customer</th>
|
||||
<th>Client Order</th>
|
||||
<th>Article Code</th>
|
||||
<th>Description</th>
|
||||
<th>Quantity</th>
|
||||
<th>Delivery Date</th>
|
||||
<th>Status</th>
|
||||
<th>Priority</th>
|
||||
<th>Product Group</th>
|
||||
<th>Order Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ordersTableBody">
|
||||
<!-- Data will be loaded here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div id="loadingIndicator" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
||||
<p class="mt-2">Loading data...</p>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div id="noDataMessage" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-info-circle fa-2x text-muted"></i>
|
||||
<p class="mt-2 text-muted">No orders found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="card-footer">
|
||||
<nav aria-label="Orders data pagination">
|
||||
<ul class="pagination pagination-sm justify-content-center mb-0" id="pagination">
|
||||
<!-- Pagination will be generated here -->
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit Customer Order</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editForm">
|
||||
<input type="hidden" id="editRecordId">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editOrderId" class="form-label">Order ID</label>
|
||||
<input type="text" class="form-control" id="editOrderId" readonly>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerCode" class="form-label">Customer Code</label>
|
||||
<input type="text" class="form-control" id="editCustomerCode">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerName" class="form-label">Customer Name</label>
|
||||
<input type="text" class="form-control" id="editCustomerName">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editClientOrder" class="form-label">Client Order</label>
|
||||
<input type="text" class="form-control" id="editClientOrder">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editArticleCode" class="form-label">Article Code</label>
|
||||
<input type="text" class="form-control" id="editArticleCode">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editQuantity" class="form-label">Quantity</label>
|
||||
<input type="number" class="form-control" id="editQuantity">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="editDescription" class="form-label">Article Description</label>
|
||||
<textarea class="form-control" id="editDescription" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editDeliveryDate" class="form-label">Delivery Date</label>
|
||||
<input type="date" class="form-control" id="editDeliveryDate">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editOrderStatus" class="form-label">Order Status</label>
|
||||
<select class="form-control" id="editOrderStatus">
|
||||
<option value="PENDING">Pending</option>
|
||||
<option value="CONFIRMED">Confirmed</option>
|
||||
<option value="Confirmat">Confirmat</option>
|
||||
<option value="IN_PROGRESS">In Progress</option>
|
||||
<option value="FINISHED">Finished</option>
|
||||
<option value="DELIVERED">Delivered</option>
|
||||
<option value="CANCELLED">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editPriority" class="form-label">Priority</label>
|
||||
<select class="form-control" id="editPriority">
|
||||
<option value="LOW">Low</option>
|
||||
<option value="NORMAL">Normal</option>
|
||||
<option value="HIGH">High</option>
|
||||
<option value="URGENT">Urgent</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editProductGroup" class="form-label">Product Group</label>
|
||||
<input type="text" class="form-control" id="editProductGroup">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="editOrderDate" class="form-label">Order Date</label>
|
||||
<input type="date" class="form-control" id="editOrderDate">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveRecord()">
|
||||
<i class="fas fa-save"></i> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentPerPage = 50;
|
||||
let currentSearch = '';
|
||||
let currentStatusFilter = '';
|
||||
let currentCustomerFilter = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize theme
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
|
||||
// Load initial data
|
||||
loadOrdersData();
|
||||
|
||||
// Setup search on enter key
|
||||
document.getElementById('searchInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
loadOrdersData();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadOrdersData(page = 1) {
|
||||
currentPage = page;
|
||||
currentPerPage = document.getElementById('recordsPerPage').value;
|
||||
currentSearch = document.getElementById('searchInput').value;
|
||||
currentStatusFilter = document.getElementById('statusFilter').value;
|
||||
currentCustomerFilter = document.getElementById('customerFilter').value;
|
||||
|
||||
// Show loading indicator
|
||||
document.getElementById('loadingIndicator').style.display = 'block';
|
||||
document.getElementById('ordersTableBody').style.display = 'none';
|
||||
document.getElementById('noDataMessage').style.display = 'none';
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: currentPage,
|
||||
per_page: currentPerPage,
|
||||
search: currentSearch,
|
||||
status: currentStatusFilter,
|
||||
customer: currentCustomerFilter
|
||||
});
|
||||
|
||||
fetch(`/daily_mirror/api/tune/orders_data?${params}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
|
||||
if (data.success) {
|
||||
if (data.data.length === 0) {
|
||||
document.getElementById('noDataMessage').style.display = 'block';
|
||||
} else {
|
||||
displayOrdersData(data.data);
|
||||
updatePagination(data);
|
||||
updateRecordsInfo(data);
|
||||
|
||||
// Populate filter dropdowns on first load
|
||||
if (currentPage === 1) {
|
||||
populateCustomerFilter(data.customers);
|
||||
populateStatusFilter(data.statuses);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('Error loading data:', data.error);
|
||||
alert('Error loading orders data: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
console.error('Error:', error);
|
||||
alert('Error loading orders data: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function displayOrdersData(data) {
|
||||
const tbody = document.getElementById('ordersTableBody');
|
||||
tbody.innerHTML = '';
|
||||
tbody.style.display = 'table-row-group';
|
||||
|
||||
data.forEach(record => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td><strong>${record.order_id}</strong></td>
|
||||
<td>
|
||||
<small class="text-muted d-block">${record.customer_code}</small>
|
||||
${record.customer_name}
|
||||
</td>
|
||||
<td>${record.client_order || '-'}</td>
|
||||
<td><code>${record.article_code}</code></td>
|
||||
<td><small>${record.article_description || '-'}</small></td>
|
||||
<td><span class="badge bg-info">${record.quantity_requested}</span></td>
|
||||
<td>${record.delivery_date || '-'}</td>
|
||||
<td><span class="badge bg-primary">${record.order_status}</span></td>
|
||||
<td><span class="badge bg-warning">${record.priority || 'NORMAL'}</span></td>
|
||||
<td><small>${record.product_group || '-'}</small></td>
|
||||
<td>${record.order_date || '-'}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="editRecord(${record.id})"
|
||||
title="Edit Order">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function populateCustomerFilter(customers) {
|
||||
const filter = document.getElementById('customerFilter');
|
||||
// Keep the "All Customers" option and add new ones
|
||||
const currentValue = filter.value;
|
||||
filter.innerHTML = '<option value="">All Customers</option>';
|
||||
|
||||
customers.forEach(customer => {
|
||||
const option = document.createElement('option');
|
||||
option.value = customer.code;
|
||||
option.textContent = `${customer.code} - ${customer.name}`;
|
||||
filter.appendChild(option);
|
||||
});
|
||||
|
||||
filter.value = currentValue;
|
||||
}
|
||||
|
||||
function populateStatusFilter(statuses) {
|
||||
const filter = document.getElementById('statusFilter');
|
||||
const currentValue = filter.value;
|
||||
filter.innerHTML = '<option value="">All Statuses</option>';
|
||||
|
||||
statuses.forEach(status => {
|
||||
const option = document.createElement('option');
|
||||
option.value = status;
|
||||
option.textContent = status;
|
||||
filter.appendChild(option);
|
||||
});
|
||||
|
||||
filter.value = currentValue;
|
||||
}
|
||||
|
||||
function updatePagination(data) {
|
||||
const pagination = document.getElementById('pagination');
|
||||
pagination.innerHTML = '';
|
||||
|
||||
if (data.total_pages <= 1) return;
|
||||
|
||||
// Previous button
|
||||
const prevLi = document.createElement('li');
|
||||
prevLi.className = `page-item ${data.page === 1 ? 'disabled' : ''}`;
|
||||
prevLi.innerHTML = `<a class="page-link" href="#" onclick="loadOrdersData(${data.page - 1})">Previous</a>`;
|
||||
pagination.appendChild(prevLi);
|
||||
|
||||
// Page numbers
|
||||
const startPage = Math.max(1, data.page - 2);
|
||||
const endPage = Math.min(data.total_pages, data.page + 2);
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
const li = document.createElement('li');
|
||||
li.className = `page-item ${i === data.page ? 'active' : ''}`;
|
||||
li.innerHTML = `<a class="page-link" href="#" onclick="loadOrdersData(${i})">${i}</a>`;
|
||||
pagination.appendChild(li);
|
||||
}
|
||||
|
||||
// Next button
|
||||
const nextLi = document.createElement('li');
|
||||
nextLi.className = `page-item ${data.page === data.total_pages ? 'disabled' : ''}`;
|
||||
nextLi.innerHTML = `<a class="page-link" href="#" onclick="loadOrdersData(${data.page + 1})">Next</a>`;
|
||||
pagination.appendChild(nextLi);
|
||||
}
|
||||
|
||||
function updateRecordsInfo(data) {
|
||||
const start = (data.page - 1) * data.per_page + 1;
|
||||
const end = Math.min(data.page * data.per_page, data.total_records);
|
||||
document.getElementById('recordsInfo').textContent =
|
||||
`Showing ${start}-${end} of ${data.total_records} orders`;
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
document.getElementById('searchInput').value = '';
|
||||
document.getElementById('statusFilter').value = '';
|
||||
document.getElementById('customerFilter').value = '';
|
||||
loadOrdersData(1);
|
||||
}
|
||||
|
||||
function editRecord(recordId) {
|
||||
// Find the record data from the current display
|
||||
const rows = document.querySelectorAll('#ordersTableBody tr');
|
||||
let recordData = null;
|
||||
|
||||
// Get data via API for editing
|
||||
fetch(`/daily_mirror/api/tune/orders_data?page=${currentPage}&per_page=${currentPerPage}&search=${currentSearch}&status=${currentStatusFilter}&customer=${currentCustomerFilter}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
recordData = data.data.find(record => record.id === recordId);
|
||||
if (recordData) {
|
||||
populateEditModal(recordData);
|
||||
const editModal = new bootstrap.Modal(document.getElementById('editModal'));
|
||||
editModal.show();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error loading record data: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function populateEditModal(record) {
|
||||
document.getElementById('editRecordId').value = record.id;
|
||||
document.getElementById('editOrderId').value = record.order_id;
|
||||
document.getElementById('editCustomerCode').value = record.customer_code;
|
||||
document.getElementById('editCustomerName').value = record.customer_name;
|
||||
document.getElementById('editClientOrder').value = record.client_order || '';
|
||||
document.getElementById('editArticleCode').value = record.article_code;
|
||||
document.getElementById('editDescription').value = record.article_description || '';
|
||||
document.getElementById('editQuantity').value = record.quantity_requested;
|
||||
document.getElementById('editDeliveryDate').value = record.delivery_date;
|
||||
document.getElementById('editOrderStatus').value = record.order_status;
|
||||
document.getElementById('editPriority').value = record.priority || 'NORMAL';
|
||||
document.getElementById('editProductGroup').value = record.product_group || '';
|
||||
document.getElementById('editOrderDate').value = record.order_date;
|
||||
}
|
||||
|
||||
function saveRecord() {
|
||||
const recordId = document.getElementById('editRecordId').value;
|
||||
const data = {
|
||||
customer_code: document.getElementById('editCustomerCode').value,
|
||||
customer_name: document.getElementById('editCustomerName').value,
|
||||
client_order: document.getElementById('editClientOrder').value,
|
||||
article_code: document.getElementById('editArticleCode').value,
|
||||
article_description: document.getElementById('editDescription').value,
|
||||
quantity_requested: parseInt(document.getElementById('editQuantity').value) || 0,
|
||||
delivery_date: document.getElementById('editDeliveryDate').value,
|
||||
order_status: document.getElementById('editOrderStatus').value,
|
||||
priority: document.getElementById('editPriority').value,
|
||||
product_group: document.getElementById('editProductGroup').value,
|
||||
order_date: document.getElementById('editOrderDate').value
|
||||
};
|
||||
|
||||
fetch(`/daily_mirror/api/tune/orders_data/${recordId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
// Close modal
|
||||
const editModal = bootstrap.Modal.getInstance(document.getElementById('editModal'));
|
||||
editModal.hide();
|
||||
|
||||
// Reload data
|
||||
loadOrdersData(currentPage);
|
||||
|
||||
// Show success message
|
||||
alert('Order updated successfully!');
|
||||
} else {
|
||||
alert('Error updating order: ' + result.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error updating order: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function saveAllChanges() {
|
||||
alert('Save All Changes functionality will be implemented for bulk operations.');
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
516
py_app/app/templates/daily_mirror_tune_production.html
Normal file
516
py_app/app/templates/daily_mirror_tune_production.html
Normal file
@@ -0,0 +1,516 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Tune Production Data - Daily Mirror{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/daily_mirror_tune.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">🏭 Tune Production Data</h1>
|
||||
<p class="text-muted">Edit and update production orders information</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Buttons removed; now present in top header -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-filter"></i> Filters and Search
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="searchInput" class="form-label">Search</label>
|
||||
<input type="text" class="form-control" id="searchInput"
|
||||
placeholder="Search by order, customer, or article...">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="statusFilter" class="form-label">Production Status</label>
|
||||
<select class="form-control" id="statusFilter">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="PENDING">Pending</option>
|
||||
<option value="IN_PROGRESS">In Progress</option>
|
||||
<option value="FINISHED">Finished</option>
|
||||
<option value="CANCELLED">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="customerFilter" class="form-label">Customer</label>
|
||||
<select class="form-control" id="customerFilter">
|
||||
<option value="">All Customers</option>
|
||||
<!-- Will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="recordsPerPage" class="form-label">Records per page</label>
|
||||
<select class="form-control" id="recordsPerPage">
|
||||
<option value="25">25</option>
|
||||
<option value="50" selected>50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" onclick="loadProductionData()">
|
||||
<i class="fas fa-search"></i> Apply Filters
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="clearFilters()">
|
||||
<i class="fas fa-times"></i> Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table Section -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-table"></i> Production Orders Data
|
||||
</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<span id="recordsInfo" class="text-muted me-3"></span>
|
||||
<button class="btn btn-success btn-sm" onclick="saveAllChanges()">
|
||||
<i class="fas fa-save"></i> Save All Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover" id="productionTable">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Production Order</th>
|
||||
<th>Customer</th>
|
||||
<th>Client Order</th>
|
||||
<th>Article Code</th>
|
||||
<th>Description</th>
|
||||
<th>Quantity</th>
|
||||
<th>Delivery Date</th>
|
||||
<th>Status</th>
|
||||
<th>Machine</th>
|
||||
<th>Planning Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="productionTableBody">
|
||||
<!-- Data will be loaded here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div id="loadingIndicator" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
||||
<p class="mt-2">Loading data...</p>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div id="noDataMessage" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-info-circle fa-2x text-muted"></i>
|
||||
<p class="mt-2 text-muted">No production orders found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="card-footer">
|
||||
<nav aria-label="Production data pagination">
|
||||
<ul class="pagination pagination-sm justify-content-center mb-0" id="pagination">
|
||||
<!-- Pagination will be generated here -->
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true" data-bs-backdrop="true" data-bs-keyboard="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit Production Order</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" id="modalCloseBtn"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editForm">
|
||||
<input type="hidden" id="editRecordId">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editProductionOrder" class="form-label">Production Order</label>
|
||||
<input type="text" class="form-control" id="editProductionOrder" readonly>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerCode" class="form-label">Customer Code</label>
|
||||
<input type="text" class="form-control" id="editCustomerCode">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerName" class="form-label">Customer Name</label>
|
||||
<input type="text" class="form-control" id="editCustomerName">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editClientOrder" class="form-label">Client Order</label>
|
||||
<input type="text" class="form-control" id="editClientOrder">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editArticleCode" class="form-label">Article Code</label>
|
||||
<input type="text" class="form-control" id="editArticleCode">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editQuantity" class="form-label">Quantity</label>
|
||||
<input type="number" class="form-control" id="editQuantity">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="editDescription" class="form-label">Article Description</label>
|
||||
<textarea class="form-control" id="editDescription" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editDeliveryDate" class="form-label">Delivery Date</label>
|
||||
<input type="date" class="form-control" id="editDeliveryDate">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editStatus" class="form-label">Production Status</label>
|
||||
<select class="form-control" id="editStatus">
|
||||
<option value="PENDING">Pending</option>
|
||||
<option value="IN_PROGRESS">In Progress</option>
|
||||
<option value="FINISHED">Finished</option>
|
||||
<option value="CANCELLED">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="editMachine" class="form-label">Machine Code</label>
|
||||
<input type="text" class="form-control" id="editMachine">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveRecord()">
|
||||
<i class="fas fa-save"></i> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentData = [];
|
||||
let hasChanges = false;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize theme
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
|
||||
// Load initial data
|
||||
loadProductionData();
|
||||
});
|
||||
|
||||
function loadProductionData(page = 1) {
|
||||
currentPage = page;
|
||||
|
||||
// Show loading
|
||||
document.getElementById('loadingIndicator').style.display = 'block';
|
||||
document.getElementById('productionTableBody').innerHTML = '';
|
||||
document.getElementById('noDataMessage').style.display = 'none';
|
||||
|
||||
// Get filter values
|
||||
const search = document.getElementById('searchInput').value;
|
||||
const status = document.getElementById('statusFilter').value;
|
||||
const customer = document.getElementById('customerFilter').value;
|
||||
const perPage = document.getElementById('recordsPerPage').value;
|
||||
|
||||
// Build query parameters
|
||||
const params = new URLSearchParams({
|
||||
page: page,
|
||||
per_page: perPage
|
||||
});
|
||||
|
||||
if (search) params.append('search', search);
|
||||
if (status) params.append('status', status);
|
||||
if (customer) params.append('customer', customer);
|
||||
|
||||
// Fetch data
|
||||
fetch(`/daily_mirror/api/tune/production_data?${params}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
currentData = data.records;
|
||||
renderTable(data);
|
||||
renderPagination(data);
|
||||
updateRecordsInfo(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading production data:', error);
|
||||
alert('Error loading data: ' + error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(data) {
|
||||
const tbody = document.getElementById('productionTableBody');
|
||||
|
||||
if (data.records.length === 0) {
|
||||
document.getElementById('noDataMessage').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = data.records.map((record, index) => `
|
||||
<tr id="row-${record.id}">
|
||||
<td><strong>${record.production_order}</strong></td>
|
||||
<td>${record.customer_code}<br><small class="text-muted">${record.customer_name || ''}</small></td>
|
||||
<td>${record.client_order || ''}</td>
|
||||
<td>${record.article_code || ''}</td>
|
||||
<td><small>${record.article_description || ''}</small></td>
|
||||
<td>${record.quantity_requested || ''}</td>
|
||||
<td>${record.delivery_date || ''}</td>
|
||||
<td><span class="badge bg-${getStatusColor(record.production_status)}">${record.production_status || ''}</span></td>
|
||||
<td>${record.machine_code || ''}</td>
|
||||
<td>${record.data_planificare || ''}</td>
|
||||
<td>
|
||||
<button class="btn btn-primary btn-action btn-sm" onclick="editRecord(${record.id})" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function getStatusColor(status) {
|
||||
switch(status) {
|
||||
case 'PENDING': return 'warning';
|
||||
case 'IN_PROGRESS': return 'info';
|
||||
case 'FINISHED': return 'success';
|
||||
case 'CANCELLED': return 'danger';
|
||||
default: return 'secondary';
|
||||
}
|
||||
}
|
||||
|
||||
function renderPagination(data) {
|
||||
const pagination = document.getElementById('pagination');
|
||||
|
||||
if (data.total_pages <= 1) {
|
||||
pagination.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
let paginationHTML = '';
|
||||
|
||||
// Previous button
|
||||
if (data.page > 1) {
|
||||
paginationHTML += `<li class="page-item"><a class="page-link" href="#" onclick="loadProductionData(${data.page - 1})">Previous</a></li>`;
|
||||
}
|
||||
|
||||
// Page numbers
|
||||
for (let i = Math.max(1, data.page - 2); i <= Math.min(data.total_pages, data.page + 2); i++) {
|
||||
const active = i === data.page ? 'active' : '';
|
||||
paginationHTML += `<li class="page-item ${active}"><a class="page-link" href="#" onclick="loadProductionData(${i})">${i}</a></li>`;
|
||||
}
|
||||
|
||||
// Next button
|
||||
if (data.page < data.total_pages) {
|
||||
paginationHTML += `<li class="page-item"><a class="page-link" href="#" onclick="loadProductionData(${data.page + 1})">Next</a></li>`;
|
||||
}
|
||||
|
||||
pagination.innerHTML = paginationHTML;
|
||||
}
|
||||
|
||||
function updateRecordsInfo(data) {
|
||||
const info = document.getElementById('recordsInfo');
|
||||
const start = (data.page - 1) * data.per_page + 1;
|
||||
const end = Math.min(data.page * data.per_page, data.total);
|
||||
info.textContent = `Showing ${start}-${end} of ${data.total} records`;
|
||||
}
|
||||
|
||||
function editRecord(recordId) {
|
||||
const record = currentData.find(r => r.id === recordId);
|
||||
if (!record) return;
|
||||
|
||||
// Populate the edit form
|
||||
document.getElementById('editRecordId').value = record.id;
|
||||
document.getElementById('editProductionOrder').value = record.production_order;
|
||||
document.getElementById('editCustomerCode').value = record.customer_code || '';
|
||||
document.getElementById('editCustomerName').value = record.customer_name || '';
|
||||
document.getElementById('editClientOrder').value = record.client_order || '';
|
||||
document.getElementById('editArticleCode').value = record.article_code || '';
|
||||
document.getElementById('editDescription').value = record.article_description || '';
|
||||
document.getElementById('editQuantity').value = record.quantity_requested || '';
|
||||
document.getElementById('editDeliveryDate').value = record.delivery_date || '';
|
||||
document.getElementById('editStatus').value = record.production_status || '';
|
||||
document.getElementById('editMachine').value = record.machine_code || '';
|
||||
|
||||
// Explicitly enable all editable fields
|
||||
const editableFields = ['editCustomerCode', 'editCustomerName', 'editClientOrder',
|
||||
'editArticleCode', 'editDescription', 'editQuantity',
|
||||
'editDeliveryDate', 'editStatus', 'editMachine'];
|
||||
editableFields.forEach(fieldId => {
|
||||
const field = document.getElementById(fieldId);
|
||||
if (field) {
|
||||
field.disabled = false;
|
||||
field.removeAttribute('disabled');
|
||||
field.removeAttribute('readonly');
|
||||
field.style.backgroundColor = '#ffffff';
|
||||
field.style.color = '#000000';
|
||||
field.style.opacity = '1';
|
||||
field.style.pointerEvents = 'auto';
|
||||
field.style.cursor = 'text';
|
||||
field.style.userSelect = 'text';
|
||||
field.tabIndex = 0;
|
||||
}
|
||||
});
|
||||
|
||||
// Show the modal with proper configuration
|
||||
const modalElement = document.getElementById('editModal');
|
||||
|
||||
// Remove any existing modal instances to prevent conflicts
|
||||
const existingModal = bootstrap.Modal.getInstance(modalElement);
|
||||
if (existingModal) {
|
||||
existingModal.dispose();
|
||||
}
|
||||
|
||||
const modal = new bootstrap.Modal(modalElement, {
|
||||
backdrop: true,
|
||||
keyboard: true,
|
||||
focus: true
|
||||
});
|
||||
|
||||
modal.show();
|
||||
|
||||
// Ensure form inputs are focusable and interactive after modal is shown
|
||||
modalElement.addEventListener('shown.bs.modal', function () {
|
||||
// Re-enable all fields after modal animation completes
|
||||
editableFields.forEach(fieldId => {
|
||||
const field = document.getElementById(fieldId);
|
||||
if (field) {
|
||||
field.disabled = false;
|
||||
field.removeAttribute('disabled');
|
||||
field.style.pointerEvents = 'auto';
|
||||
}
|
||||
});
|
||||
|
||||
// Focus on the first editable field
|
||||
const firstField = document.getElementById('editCustomerCode');
|
||||
if (firstField) {
|
||||
setTimeout(() => {
|
||||
firstField.focus();
|
||||
firstField.select();
|
||||
}, 100);
|
||||
}
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
function saveRecord() {
|
||||
const recordId = document.getElementById('editRecordId').value;
|
||||
|
||||
const formData = {
|
||||
customer_code: document.getElementById('editCustomerCode').value,
|
||||
customer_name: document.getElementById('editCustomerName').value,
|
||||
client_order: document.getElementById('editClientOrder').value,
|
||||
article_code: document.getElementById('editArticleCode').value,
|
||||
article_description: document.getElementById('editDescription').value,
|
||||
quantity_requested: parseInt(document.getElementById('editQuantity').value) || 0,
|
||||
delivery_date: document.getElementById('editDeliveryDate').value,
|
||||
production_status: document.getElementById('editStatus').value,
|
||||
machine_code: document.getElementById('editMachine').value
|
||||
};
|
||||
|
||||
fetch(`/daily_mirror/api/tune/production_data/${recordId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
// Close modal and reload data
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('editModal'));
|
||||
modal.hide();
|
||||
|
||||
alert('Record updated successfully!');
|
||||
loadProductionData(currentPage);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving record:', error);
|
||||
alert('Error saving record: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
document.getElementById('searchInput').value = '';
|
||||
document.getElementById('statusFilter').value = '';
|
||||
document.getElementById('customerFilter').value = '';
|
||||
loadProductionData(1);
|
||||
}
|
||||
|
||||
function saveAllChanges() {
|
||||
alert('Bulk save functionality will be implemented in a future update!');
|
||||
}
|
||||
|
||||
// Add event listeners for real-time filtering
|
||||
document.getElementById('searchInput').addEventListener('input', function() {
|
||||
clearTimeout(this.searchTimeout);
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
loadProductionData(1);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
document.getElementById('statusFilter').addEventListener('change', function() {
|
||||
loadProductionData(1);
|
||||
});
|
||||
|
||||
document.getElementById('customerFilter').addEventListener('change', function() {
|
||||
loadProductionData(1);
|
||||
});
|
||||
|
||||
document.getElementById('recordsPerPage').addEventListener('change', function() {
|
||||
loadProductionData(1);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -34,5 +34,17 @@
|
||||
<a href="{{ url_for('main.settings') }}" class="btn">Access Settings Page</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h3>📊 Daily Mirror</h3>
|
||||
<p>Business Intelligence and Production Reporting - Generate comprehensive daily reports including order quantities, production status, and delivery tracking.</p>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_main_route') }}" class="btn">📊 Daily Mirror Hub</a>
|
||||
</div>
|
||||
<div style="margin-top: 8px; font-size: 12px; color: #666;">
|
||||
<strong>Tracks:</strong> Orders quantity • Production launched • Production finished • Orders delivered
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -408,6 +408,10 @@
|
||||
<input type="checkbox" id="module_labels" name="modules" value="labels">
|
||||
<label for="module_labels">Label Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="module_daily_mirror" name="modules" value="daily_mirror">
|
||||
<label for="module_daily_mirror">Daily Mirror (Business Intelligence)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="accessLevelInfo" class="access-level-info" style="display: none;"></div>
|
||||
</div>
|
||||
@@ -454,6 +458,10 @@
|
||||
<input type="checkbox" id="quick_module_labels" name="quick_modules" value="labels">
|
||||
<label for="quick_module_labels">Label Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="quick_module_daily_mirror" name="quick_modules" value="daily_mirror">
|
||||
<label for="quick_module_daily_mirror">Daily Mirror</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -621,6 +629,10 @@
|
||||
<input type="checkbox" id="edit_module_labels" name="modules" value="labels">
|
||||
<label for="edit_module_labels">Label Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="edit_module_daily_mirror" name="modules" value="daily_mirror">
|
||||
<label for="edit_module_daily_mirror">Daily Mirror</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editAccessLevelInfo" class="access-level-info" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user