This commit is contained in:
2025-09-17 20:47:22 +03:00
parent 76b8246edf
commit 18a1c638e1
9 changed files with 565 additions and 203 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
recticel/ recticel/
.venv/ .venv/
__pycache__/ __pycache__/
app/__pycache__/

View File

@@ -939,6 +939,130 @@ def label_templates():
def create_template(): def create_template():
return render_template('create_template.html') return render_template('create_template.html')
@bp.route('/get_database_tables', methods=['GET'])
def get_database_tables():
"""Get list of database tables for template creation"""
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
return jsonify({'error': 'Access denied'}), 403
try:
# Get database connection using the same method as other functions
settings_file = current_app.instance_path + '/external_server.conf'
settings = {}
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
settings[key] = value
connection = mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
cursor = connection.cursor()
# Get all tables in the database
cursor.execute("SHOW TABLES")
all_tables = [table[0] for table in cursor.fetchall()]
# Filter to show relevant tables (prioritize order_for_labels)
relevant_tables = []
if 'order_for_labels' in all_tables:
relevant_tables.append('order_for_labels')
# Add other potentially relevant tables
for table in all_tables:
if table not in relevant_tables and any(keyword in table.lower() for keyword in ['order', 'label', 'product', 'customer']):
relevant_tables.append(table)
connection.close()
return jsonify({
'success': True,
'tables': relevant_tables,
'recommended': 'order_for_labels'
})
except Exception as e:
return jsonify({'error': f'Database error: {str(e)}'}), 500
@bp.route('/get_table_columns/<table_name>', methods=['GET'])
def get_table_columns(table_name):
"""Get column names and descriptions for a specific table"""
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
return jsonify({'error': 'Access denied'}), 403
try:
# Get database connection
settings_file = current_app.instance_path + '/external_server.conf'
settings = {}
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
settings[key] = value
connection = mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
cursor = connection.cursor()
# Verify table exists
cursor.execute("SHOW TABLES LIKE %s", (table_name,))
if not cursor.fetchone():
return jsonify({'error': f'Table {table_name} not found'}), 404
# Get column information
cursor.execute(f"DESCRIBE {table_name}")
columns_info = cursor.fetchall()
columns = []
for col_info in columns_info:
column_name = col_info[0]
column_type = col_info[1]
is_nullable = col_info[2] == 'YES'
column_default = col_info[4]
# Get column comment if available
cursor.execute(f"""
SELECT COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = %s AND COLUMN_NAME = %s AND TABLE_SCHEMA = DATABASE()
""", (table_name, column_name))
comment_result = cursor.fetchone()
column_comment = comment_result[0] if comment_result and comment_result[0] else ''
# Create user-friendly description
description = column_comment or column_name.replace('_', ' ').title()
columns.append({
'name': column_name,
'type': column_type,
'nullable': is_nullable,
'default': column_default,
'description': description,
'field_id': f"db_{column_name}" # Unique ID for template fields
})
connection.close()
return jsonify({
'success': True,
'table': table_name,
'columns': columns
})
except Exception as e:
return jsonify({'error': f'Database error: {str(e)}'}), 500
@bp.route('/edit_template/<int:template_id>') @bp.route('/edit_template/<int:template_id>')
def edit_template(template_id): def edit_template(template_id):
# Logic for editing a template will go here # Logic for editing a template will go here

View File

@@ -0,0 +1,440 @@
{% extends "base.html" %}
{% block title %}Create Template{% endblock %}
{% block head %}
<style>
.btn-secondary {
background: #6c757d !important;
color: white !important;
border: 1px solid #6c757d !important;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
}
.btn-outline {
background: white !important;
color: #333 !important;
border: 1px solid #ddd !important;
}
.btn-outline:hover {
background: #f8f9fa !important;
border-color: #007bff !important;
}
.template-field {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
user-select: none;
}
.database-field {
background: linear-gradient(135deg, #e3f2fd, #bbdefb) !important;
border: 2px solid #2196f3 !important;
}
.database-field:hover {
transform: scale(1.02);
box-shadow: 0 4px 8px rgba(33, 150, 243, 0.3);
}
#label-preview {
background: #fafafa;
min-width: 300px;
min-height: 200px;
position: relative;
overflow: hidden;
}
.column-btn {
transition: all 0.2s ease;
}
.table-btn {
transition: all 0.2s ease;
}
.table-btn:hover {
transform: translateX(5px);
}
#tables-container h5,
#columns-container h5 {
color: #333;
border-bottom: 2px solid #007bff;
padding-bottom: 5px;
margin-bottom: 10px;
}
</style>
{% endblock %}
{% block content %}
<div class="card-container" style="display: flex; gap: 20px;">
<!-- Left Column -->
<div class="left-column" style="flex: 1; max-width: 33%;">
<div class="dashboard-card">
<h3>Template Settings</h3>
<p>Set the dimensions and retrieve database headers for the label template:</p>
<!-- Dimensions Section -->
<div>
<h4>Dimensions</h4>
<form id="dimensions-form">
<div style="margin-bottom: 10px;">
<label for="label-width">Width (mm):</label>
<input type="number" id="label-width" name="label_width" required>
</div>
<div style="margin-bottom: 10px;">
<label for="label-height">Height (mm):</label>
<input type="number" id="label-height" name="label_height" required>
</div>
<button type="button" id="set-dimensions-btn" class="btn">Set Label Dimensions</button>
</form>
</div>
<hr style="margin: 20px 0;">
<!-- Add Fields Section -->
<div>
<h4>Add Fields</h4>
<button type="button" class="btn add-field-btn" data-type="text-label">Add Text Label</button>
.big-card {
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
border-radius: 10px;
padding: 30px 24px;
min-height: 600px;
background: #f5faff;
}
.small-card {
box-shadow: 0 2px 6px rgba(0,0,0,0.10);
border-radius: 10px;
padding: 18px 12px;
min-height: 300px;
background: #fff;
}
<button type="button" class="btn add-field-btn" data-type="text-input">Add Text Input</button>
<button type="button" class="btn add-field-btn" data-type="barcode">Add Barcode</button>
<button type="button" class="btn add-field-btn" data-type="qrcode">Add QR Code</button>
</div>
<!-- Get Database Headers Section -->
<div>
<h4>Get Database Headers</h4>
<p>Retrieve column names from a selected database table:</p>
<button type="button" id="get-tables-btn" class="btn">Get Database Tables</button>
<div id="tables-container" style="margin-top: 10px;">
<!-- Tables will be dynamically populated here -->
</div>
<div id="columns-container" style="margin-top: 10px;">
<!-- Columns will be dynamically populated here -->
</div>
</div>
<hr style="margin: 20px 0;">
</div>
</div>
<!-- Right Column -->
<div class="right-column" style="flex: 2;">
<div class="dashboard-card">
<h3>Interactive Label Preview</h3>
<p>Drag and drop fields to design your label:</p>
<div class="label-preview-container" style="display: flex; justify-content: center; align-items: center; height: 100%; position: relative;">
<div id="label-preview" style="border: 1px solid #ddd; padding: 10px; min-height: 400px; position: relative;">
<!-- Fields will be dynamically added here -->
</div>
</div>
<button type="button" id="preview-btn" class="btn" style="margin-top: 10px;">Preview</button>
</div>
</div>
</div>
<script src="{{ url_for('static', filename='pdfjs/pdf.worker.js') }}"></script>
<script>
// Set the worker source for PDF.js
pdfjsLib.GlobalWorkerOptions.workerSrc = "{{ url_for('static', filename='pdfjs/pdf.worker.min.js') }}";
// Database Tables Functionality
document.addEventListener('DOMContentLoaded', function() {
const getTablesBtn = document.getElementById('get-tables-btn');
const tablesContainer = document.getElementById('tables-container');
const columnsContainer = document.getElementById('columns-container');
// Handle Get Database Tables button click
getTablesBtn.addEventListener('click', function() {
getTablesBtn.textContent = 'Loading...';
getTablesBtn.disabled = true;
fetch('/get_database_tables')
.then(response => response.json())
.then(data => {
if (data.success) {
displayTables(data.tables, data.recommended);
} else {
showError('Failed to load tables: ' + data.error);
}
})
.catch(error => {
showError('Error: ' + error.message);
})
.finally(() => {
getTablesBtn.textContent = 'Get Database Tables';
getTablesBtn.disabled = false;
});
});
function displayTables(tables, recommended) {
if (tables.length === 0) {
tablesContainer.innerHTML = '<p style="color: #999;">No relevant tables found.</p>';
return;
}
let html = '<h5>Available Tables:</h5><div style="margin-top: 10px;">';
tables.forEach(table => {
const isRecommended = table === recommended;
const buttonClass = isRecommended ? 'btn' : 'btn-secondary';
const recommendedText = isRecommended ? ' ⭐ (Recommended)' : '';
html += `
<button type="button"
class="table-btn ${buttonClass}"
data-table="${table}"
style="display: block; margin: 5px 0; width: 100%; text-align: left;">
${table}${recommendedText}
</button>
`;
});
html += '</div>';
tablesContainer.innerHTML = html;
// Add event listeners to table buttons
document.querySelectorAll('.table-btn').forEach(btn => {
btn.addEventListener('click', function() {
const tableName = this.getAttribute('data-table');
loadTableColumns(tableName);
// Update button states
document.querySelectorAll('.table-btn').forEach(b => {
b.classList.remove('btn');
b.classList.add('btn-secondary');
});
this.classList.remove('btn-secondary');
this.classList.add('btn');
});
});
// Auto-load recommended table
if (recommended) {
setTimeout(() => {
const recommendedBtn = document.querySelector(`[data-table="${recommended}"]`);
if (recommendedBtn) {
recommendedBtn.click();
}
}, 100);
}
}
function loadTableColumns(tableName) {
columnsContainer.innerHTML = '<p>Loading columns...</p>';
fetch(`/get_table_columns/${tableName}`)
.then(response => response.json())
.then(data => {
if (data.success) {
displayColumns(data.table, data.columns);
} else {
showError('Failed to load columns: ' + data.error);
}
})
.catch(error => {
showError('Error loading columns: ' + error.message);
});
}
function displayColumns(tableName, columns) {
if (columns.length === 0) {
columnsContainer.innerHTML = '<p style="color: #999;">No columns found.</p>';
return;
}
let html = `
<h5>Columns from "${tableName}" table:</h5>
<p style="font-size: 12px; color: #666; margin: 5px 0;">
Click on a column to add it as a database field to your label template.
</p>
<div style="margin-top: 10px; max-height: 300px; overflow-y: auto;">
`;
columns.forEach(column => {
// Determine icon based on column type
let icon = '📝'; // Default text icon
if (column.name.toLowerCase().includes('id')) icon = '🔢';
else if (column.name.toLowerCase().includes('date') || column.name.toLowerCase().includes('time')) icon = '📅';
else if (column.name.toLowerCase().includes('quantity') || column.name.toLowerCase().includes('cantitate')) icon = '🔢';
else if (column.name.toLowerCase().includes('code') || column.name.toLowerCase().includes('cod')) icon = '🏷️';
else if (column.name.toLowerCase().includes('customer') || column.name.toLowerCase().includes('client')) icon = '👤';
else if (column.name.toLowerCase().includes('product') || column.name.toLowerCase().includes('articol')) icon = '📦';
html += `
<button type="button"
class="column-btn btn-outline"
data-column="${column.name}"
data-field-id="${column.field_id}"
data-description="${column.description}"
style="display: block; margin: 3px 0; padding: 8px; width: 100%; text-align: left; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer;">
<div style="display: flex; align-items: center;">
<span style="margin-right: 8px; font-size: 16px;">${icon}</span>
<div style="flex: 1;">
<strong>${column.name}</strong>
<br>
<small style="color: #666;">${column.description}</small>
<br>
<small style="color: #999; font-style: italic;">${column.type}</small>
</div>
</div>
</button>
`;
});
html += '</div>';
columnsContainer.innerHTML = html;
// Add event listeners to column buttons
document.querySelectorAll('.column-btn').forEach(btn => {
btn.addEventListener('click', function() {
const columnName = this.getAttribute('data-column');
const fieldId = this.getAttribute('data-field-id');
const description = this.getAttribute('data-description');
addDatabaseFieldToTemplate(columnName, fieldId, description);
// Visual feedback
this.style.background = '#e7f3ff';
this.style.borderColor = '#007bff';
setTimeout(() => {
this.style.background = 'white';
this.style.borderColor = '#ddd';
}, 1000);
});
// Hover effects
btn.addEventListener('mouseenter', function() {
this.style.background = '#f8f9fa';
this.style.borderColor = '#007bff';
});
btn.addEventListener('mouseleave', function() {
if (this.style.background !== 'rgb(231, 243, 255)') {
this.style.background = 'white';
this.style.borderColor = '#ddd';
}
});
});
}
function addDatabaseFieldToTemplate(columnName, fieldId, description) {
const labelPreview = document.getElementById('label-preview');
// Create a database field element
const fieldElement = document.createElement('div');
fieldElement.className = 'template-field database-field';
fieldElement.id = fieldId;
fieldElement.style.cssText = `
position: absolute;
left: 20px;
top: ${20 + (document.querySelectorAll('.template-field').length * 30)}px;
padding: 5px 10px;
background: #e3f2fd;
border: 2px solid #2196f3;
border-radius: 4px;
cursor: move;
font-size: 12px;
min-width: 120px;
text-align: center;
`;
fieldElement.innerHTML = `
<div style="font-weight: bold; color: #1976d2;">${'{{'}${columnName}${'}}'}}</div>
<div style="font-size: 10px; color: #666;">${description}</div>
`;
// Add data attributes
fieldElement.setAttribute('data-field-type', 'database');
fieldElement.setAttribute('data-column-name', columnName);
fieldElement.setAttribute('data-description', description);
labelPreview.appendChild(fieldElement);
// Make it draggable (basic implementation)
makeDraggable(fieldElement);
console.log(`Added database field: ${columnName} (${description})`);
}
function makeDraggable(element) {
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
element.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
function dragStart(e) {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
if (e.target === element) {
isDragging = true;
}
}
function drag(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
element.style.left = currentX + 'px';
element.style.top = currentY + 'px';
}
}
function dragEnd() {
initialX = currentX;
initialY = currentY;
isDragging = false;
}
}
function showError(message) {
const errorDiv = document.createElement('div');
errorDiv.style.cssText = `
background: #ffe6e6;
border: 1px solid #ff9999;
color: #cc0000;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
`;
errorDiv.textContent = message;
tablesContainer.appendChild(errorDiv);
setTimeout(() => {
errorDiv.remove();
}, 5000);
}
});
</script>
{% endblock %}

View File

@@ -1,78 +0,0 @@
{% extends "base.html" %}
{% block title %}Create Template{% endblock %}
{% block content %}
<div class="card-container" style="display: flex; gap: 20px;">
<!-- Left Column -->
<div class="left-column" style="flex: 1; max-width: 33%;">
<div class="dashboard-card">
<h3>Template Settings</h3>
<p>Set the dimensions and retrieve database headers for the label template:</p>
<!-- Dimensions Section -->
<div>
<h4>Dimensions</h4>
<form id="dimensions-form">
<div style="margin-bottom: 10px;">
<label for="label-width">Width (mm):</label>
<input type="number" id="label-width" name="label_width" required>
</div>
<div style="margin-bottom: 10px;">
<label for="label-height">Height (mm):</label>
<input type="number" id="label-height" name="label_height" required>
</div>
<button type="button" id="set-dimensions-btn" class="btn">Set Label Dimensions</button>
</form>
</div>
<hr style="margin: 20px 0;">
<!-- Add Fields Section -->
<div>
<h4>Add Fields</h4>
<button type="button" class="btn add-field-btn" data-type="text-label">Add Text Label</button>
<button type="button" class="btn add-field-btn" data-type="text-input">Add Text Input</button>
<button type="button" class="btn add-field-btn" data-type="barcode">Add Barcode</button>
<button type="button" class="btn add-field-btn" data-type="qrcode">Add QR Code</button>
</div>
<!-- Get Database Headers Section -->
<div>
<h4>Get Database Headers</h4>
<p>Retrieve column names from a selected database table:</p>
<button type="button" id="get-tables-btn" class="btn">Get Database Tables</button>
<div id="tables-container" style="margin-top: 10px;">
<!-- Tables will be dynamically populated here -->
</div>
<div id="columns-container" style="margin-top: 10px;">
<!-- Columns will be dynamically populated here -->
</div>
</div>
<hr style="margin: 20px 0;">
</div>
</div>
<!-- Right Column -->
<div class="right-column" style="flex: 2;">
<div class="dashboard-card">
<h3>Interactive Label Preview</h3>
<p>Drag and drop fields to design your label:</p>
<div class="label-preview-container" style="display: flex; justify-content: center; align-items: center; height: 100%; position: relative;">
<div id="label-preview" style="border: 1px solid #ddd; padding: 10px; min-height: 400px; position: relative;">
<!-- Fields will be dynamically added here -->
</div>
</div>
<button type="button" id="preview-btn" class="btn" style="margin-top: 10px;">Preview</button>
</div>
</div>
</div>
<script src="{{ url_for('static', filename='pdfjs/pdf.worker.js') }}"></script>
<script>
// Set the worker source for PDF.js
pdfjsLib.GlobalWorkerOptions.workerSrc = "{{ url_for('static', filename='pdfjs/pdf.worker.min.js') }}";
</script>
{% endblock %}

View File

@@ -1,99 +0,0 @@
{% extends "base.html" %}
{% block title %}Import Warehouse Locations from CSV{% endblock %}
{% block content %}
<div class="scan-container">
<!-- Import Locations from CSV Card (first, fixed position) -->
<div class="card scan-form-card" style="margin-bottom: 24px;">
<h3>Import Locations from CSV</h3>
<form method="POST" enctype="multipart/form-data" class="form-centered" id="csv-upload-form">
<label for="csv_file">Choose CSV file:</label>
{% if not locations %}
<input type="file" name="csv_file" accept=".csv" required><br>
<button type="submit" class="btn">Upload & Preview</button>
{% else %}
<label style="font-weight: bold;">Selected file: {{ session['csv_filename'] if session['csv_filename'] else 'Unknown' }}</label><br>
<button type="button" class="btn" onclick="showPopupAndSubmit()">Create Locations</button>
{% endif %}
<!-- CSV File Format Guidance (moved here) -->
<div style="margin-top: 8px;">
<h4 style="margin-bottom: 4px;">CSV File Format</h4>
<label style="margin-bottom: 8px; display: block;">Fisierul CSV trebuie sa aiba urmatorul format:</label>
<table class="scan-table" style="width: 100%;">
<thead>
<tr>
<th>Location Code</th>
<th>Size</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>EX123</td>
<td>100</td>
<td>Zona depozitare A</td>
</tr>
</tbody>
</table>
</div>
</form>
<!-- Popup Modal -->
<div id="popup-modal" class="popup" style="display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; background:var(--app-overlay-bg, rgba(30,41,59,0.85)); z-index:9999; align-items:center; justify-content:center;">
<div class="popup-content" style="margin:auto; padding:32px; border-radius:8px; box-shadow:0 2px 8px #333; min-width:320px; max-width:400px; text-align:center;">
<h3 style="color:var(--app-label-text);">Performing the creation of the warehouse locations</h3>
</div>
</div>
<script>
function showPopupAndSubmit() {
document.getElementById('popup-modal').style.display = 'flex';
// Submit the form after showing popup
setTimeout(function() {
var form = document.getElementById('csv-upload-form');
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'create_locations';
input.value = '1';
form.appendChild(input);
form.submit();
}, 500);
}
window.onload = function() {
if (window.location.hash === '#created') {
document.getElementById('popup-modal').style.display = 'none';
}
}
</script>
</div>
<!-- Preview Table Card (expandable height, scrollable) -->
<div class="card scan-table-card" style="margin-bottom: 24px; max-height: 480px; overflow-y: auto;">
<h3>Preview Table</h3>
<table class="scan-table">
<thead>
<tr>
<th>Location Code</th>
<th>Size</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% if locations %}
{% for loc in locations %}
<tr>
<td>{{ loc[0] }}</td>
<td>{{ loc[1] }}</td>
<td>{{ loc[2] }}</td>
</tr>
{% endfor %}
{% else %}
<tr><td colspan="3" style="text-align:center;">No CSV file uploaded yet.</td></tr>
{% endif %}
</tbody>
</table>
</div>
{% if report %}
<div class="card" style="margin-bottom: 24px;">
<h4>Import Report</h4>
<p>{{ report }}</p>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -1,21 +0,0 @@
{% extends "base.html" %}
{% block title %}Label Templates{% endblock %}
{% block content %}
<div class="card-container">
<div class="card">
<h3>List of Label Templates</h3>
<ul class="user-list">
{% for template in templates %}
<li data-template-id="{{ template.id }}">
<span class="template-name">{{ template.name }}</span>
<button class="btn edit-btn">Edit</button>
<button class="btn delete-btn">Delete</button>
</li>
{% endfor %}
</ul>
<button id="create-template-btn" class="btn create-btn">Create New Template</button>
</div>
</div>
{% endblock %}

View File

@@ -24,11 +24,6 @@
</div> </div>
<!-- Card 3: Manage Label Templates --> <!-- Card 3: Manage Label Templates -->
<div class="dashboard-card">
<h3>Manage Label Templates</h3>
<p>Manage and configure label templates.</p>
<a href="{{ url_for('main.label_templates') }}" class="btn">Manage Templates</a>
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}