Compare commits

..

6 Commits

Author SHA1 Message Date
ske087 b87c398977 new updates 2025-10-10 22:53:04 +03:00
ske087 05394697a0 updated validation and auto submit 2025-10-10 22:49:57 +03:00
ske087 e257f6be18 updated to scan module 2025-10-09 01:10:05 +03:00
ske087 b8e85180c7 updated to scrol betwen the rows in the card 2025-10-09 00:41:32 +03:00
ske087 c99ff70da7 updated to do 2025-10-09 00:37:10 +03:00
ske087 b0e17b69e7 lets add settings new to css 2025-10-09 00:34:52 +03:00
26 changed files with 2949 additions and 125 deletions
+152
View File
@@ -0,0 +1,152 @@
# CSS Modular Structure Guide
## Overview
This guide explains how to migrate from a monolithic CSS file to a modular CSS structure for better maintainability and organization.
## New CSS Structure
```
app/static/css/
├── base.css # Global styles, header, buttons, theme
├── login.css # Login page specific styles
├── dashboard.css # Dashboard and module cards
├── warehouse.css # Warehouse module styles
├── etichete.css # Labels/etiquette module styles (to be created)
├── quality.css # Quality module styles (to be created)
└── scan.css # Scan module styles (to be created)
```
## Implementation Strategy
### Phase 1: Setup Modular Structure ✅
- [x] Created `css/` directory
- [x] Created `base.css` with global styles
- [x] Created `login.css` for login page
- [x] Created `warehouse.css` for warehouse module
- [x] Updated `base.html` to include modular CSS
- [x] Updated `login.html` to use new structure
### Phase 2: Migration Plan (Next Steps)
1. **Extract module-specific styles from style.css:**
- Etiquette/Labels module → `etichete.css`
- Quality module → `quality.css`
- Scan module → `scan.css`
2. **Update templates to use modular CSS:**
```html
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/module-name.css') }}">
{% endblock %}
```
3. **Clean up original style.css:**
- Remove extracted styles
- Keep only legacy/common styles temporarily
- Eventually eliminate when all modules migrated
## Template Usage Pattern
### Standard Template Structure:
```html
{% extends "base.html" %}
{% block title %}Page Title{% endblock %}
{% block head %}
<!-- Include module-specific CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/module-name.css') }}">
<!-- Page-specific overrides -->
<style>
/* Only use this for page-specific customizations */
</style>
{% endblock %}
{% block content %}
<!-- Page content -->
{% endblock %}
```
## CSS Loading Order
1. `base.css` - Global styles, header, buttons, theme
2. `style.css` - Legacy styles (temporary, for backward compatibility)
3. Module-specific CSS (e.g., `warehouse.css`)
4. Inline `<style>` blocks for page-specific overrides
## Benefits of This Structure
### 1. **Maintainability**
- Easy to find and edit module-specific styles
- Reduced conflicts between different modules
- Clear separation of concerns
### 2. **Performance**
- Only load CSS needed for specific pages
- Smaller file sizes per page
- Better caching (module CSS rarely changes)
### 3. **Team Development**
- Different developers can work on different modules
- Less merge conflicts in CSS files
- Clear ownership of styles
### 4. **Scalability**
- Easy to add new modules
- Simple to deprecate old styles
- Clear migration path
## Migration Checklist
### For Each Template:
- [ ] Identify module/page type
- [ ] Extract relevant styles to module CSS file
- [ ] Update template to include module CSS
- [ ] Test styling works correctly
- [ ] Remove old styles from style.css
### Current Status:
- [x] Login page - Fully migrated
- [x] Warehouse module - Partially migrated (create_locations.html updated)
- [ ] Dashboard - CSS created, templates need updating
- [ ] Etiquette module - Needs CSS extraction
- [ ] Quality module - Needs CSS extraction
- [ ] Scan module - Needs CSS extraction
## Example: Migrating a Template
### Before:
```html
{% block head %}
<style>
.my-module-specific-class {
/* styles here */
}
</style>
{% endblock %}
```
### After:
1. Move styles to `css/module-name.css`
2. Update template:
```html
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/module-name.css') }}">
{% endblock %}
```
## Best Practices
1. **Use semantic naming:** `warehouse.css`, `login.css`, not `page1.css`
2. **Keep base.css minimal:** Only truly global styles
3. **Avoid deep nesting:** Keep CSS selectors simple
4. **Use consistent naming:** Follow existing patterns
5. **Document changes:** Update this guide when adding new modules
## Next Steps
1. Extract etiquette module styles to `etichete.css`
2. Update all etiquette templates to use new CSS
3. Extract quality module styles to `quality.css`
4. Extract scan module styles to `scan.css`
5. Gradually remove migrated styles from `style.css`
6. Eventually remove `style.css` dependency from `base.html`
+133
View File
@@ -0,0 +1,133 @@
# Mobile-Responsive Login Page
## Overview
The login page has been enhanced with comprehensive mobile-responsive CSS to provide an optimal user experience across all device types and screen sizes.
## Mobile-Responsive Features Added
### 1. **Responsive Breakpoints**
- **Tablet (≤768px)**: Column layout, optimized logo and form sizing
- **Mobile (≤480px)**: Enhanced touch targets, better spacing
- **Small Mobile (≤320px)**: Minimal padding, compact design
- **Landscape (height ≤500px)**: Horizontal layout for landscape phones
### 2. **Layout Adaptations**
#### Desktop (>768px)
- Side-by-side logo and form layout
- Large logo (90vh height)
- Fixed form width (600px)
#### Tablet (≤768px)
- Vertical stacked layout
- Logo height reduced to 30vh
- Form width becomes responsive (100%, max 400px)
#### Mobile (≤480px)
- Optimized touch targets (44px minimum)
- Increased padding and margins
- Better visual hierarchy
- Enhanced shadows and border radius
#### Small Mobile (≤320px)
- Minimal padding to maximize space
- Compact logo (20vh height)
- Reduced font sizes where appropriate
### 3. **Touch Optimizations**
#### iOS/Safari Specific
- `font-size: 16px` on inputs prevents automatic zoom
- Proper touch target sizing (44px minimum)
#### Touch Device Enhancements
- Active states for button presses
- Optimized image rendering for high DPI screens
- Hover effects disabled on touch devices
### 4. **Accessibility Improvements**
- Proper contrast ratios maintained
- Touch targets meet accessibility guidelines
- Readable font sizes across all devices
- Smooth transitions and animations
### 5. **Performance Considerations**
- CSS-only responsive design (no JavaScript required)
- Efficient media queries
- Optimized image rendering for retina displays
## Key CSS Features
### Flexible Layout
```css
.login-page {
display: flex;
flex-direction: column; /* Mobile */
justify-content: center;
}
```
### Responsive Images
```css
.login-logo {
max-height: 25vh; /* Mobile */
max-width: 85vw;
}
```
### Touch-Friendly Inputs
```css
.form-container input {
padding: 12px;
font-size: 16px; /* Prevents iOS zoom */
min-height: 44px; /* Touch target size */
}
```
### Landscape Optimization
```css
@media screen and (max-height: 500px) and (orientation: landscape) {
.login-page {
flex-direction: row; /* Back to horizontal */
}
}
```
## Testing Recommendations
### Device Testing
- [ ] iPhone (various sizes)
- [ ] Android phones (various sizes)
- [ ] iPad/Android tablets
- [ ] Desktop browsers with responsive mode
### Orientation Testing
- [ ] Portrait mode on all devices
- [ ] Landscape mode on phones
- [ ] Landscape mode on tablets
### Browser Testing
- [ ] Safari (iOS)
- [ ] Chrome (Android/iOS)
- [ ] Firefox Mobile
- [ ] Samsung Internet
- [ ] Desktop browsers (Chrome, Firefox, Safari, Edge)
## Browser Support
- Modern browsers (ES6+ support)
- iOS Safari 12+
- Android Chrome 70+
- Desktop browsers (last 2 versions)
## Performance Impact
- **CSS Size**: Increased by ~2KB (compressed)
- **Load Time**: No impact (CSS only)
- **Rendering**: Optimized for mobile GPUs
- **Memory**: Minimal additional usage
## Future Enhancements
1. **Dark mode mobile optimizations**
2. **Progressive Web App (PWA) features**
3. **Biometric authentication UI**
4. **Loading states and animations**
5. **Error message responsive design**
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
@@ -0,0 +1,41 @@
import mariadb
# Database connection credentials
# (reuse from create_scan_1db.py or update as needed)
db_config = {
"user": "trasabilitate",
"password": "Initial01!",
"host": "localhost",
"database": "trasabilitate_database"
}
try:
conn = mariadb.connect(**db_config)
cursor = conn.cursor()
print("Connected to the database successfully!")
# Create the scanfg_orders table (same structure as scan1_orders)
create_table_query = """
CREATE TABLE IF NOT EXISTS scanfg_orders (
Id INT AUTO_INCREMENT PRIMARY KEY,
operator_code VARCHAR(4) NOT NULL,
CP_full_code VARCHAR(15) NOT NULL UNIQUE,
OC1_code VARCHAR(4) NOT NULL,
OC2_code VARCHAR(4) NOT NULL,
CP_base_code VARCHAR(10) GENERATED ALWAYS AS (LEFT(CP_full_code, 10)) STORED,
quality_code INT(3) NOT NULL,
date DATE NOT NULL,
time TIME NOT NULL,
approved_quantity INT DEFAULT 0,
rejected_quantity INT DEFAULT 0
);
"""
cursor.execute(create_table_query)
print("Table 'scanfg_orders' created successfully!")
conn.commit()
cursor.close()
conn.close()
except mariadb.Error as e:
print(f"Error connecting to the database: {e}")
@@ -0,0 +1,73 @@
import mariadb
# Database connection credentials
db_config = {
"user": "trasabilitate",
"password": "Initial01!",
"host": "localhost",
"database": "trasabilitate_database"
}
# Connect to the database
try:
conn = mariadb.connect(**db_config)
cursor = conn.cursor()
print("Connected to the database successfully!")
# Delete old triggers if they exist
try:
cursor.execute("DROP TRIGGER IF EXISTS increment_approved_quantity_fg;")
print("Old trigger 'increment_approved_quantity_fg' deleted successfully.")
except mariadb.Error as e:
print(f"Error deleting old trigger 'increment_approved_quantity_fg': {e}")
try:
cursor.execute("DROP TRIGGER IF EXISTS increment_rejected_quantity_fg;")
print("Old trigger 'increment_rejected_quantity_fg' deleted successfully.")
except mariadb.Error as e:
print(f"Error deleting old trigger 'increment_rejected_quantity_fg': {e}")
# Create corrected trigger for approved_quantity in scanfg_orders
create_approved_trigger_fg = """
CREATE TRIGGER increment_approved_quantity_fg
BEFORE INSERT ON scanfg_orders
FOR EACH ROW
BEGIN
IF NEW.quality_code = 000 THEN
SET NEW.approved_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code = 000
) + 1;
SET NEW.rejected_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code != 000
);
ELSE
SET NEW.approved_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code = 000
);
SET NEW.rejected_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code != 000
) + 1;
END IF;
END;
"""
cursor.execute(create_approved_trigger_fg)
print("Trigger 'increment_approved_quantity_fg' created successfully for scanfg_orders table!")
# Commit changes and close the connection
conn.commit()
cursor.close()
conn.close()
print("\n✅ All triggers for scanfg_orders table created successfully!")
print("The approved_quantity and rejected_quantity will now be calculated automatically.")
except mariadb.Error as e:
print(f"Error connecting to the database or creating triggers: {e}")
+118 -5
View File
@@ -7,7 +7,6 @@ from .models import User
from . import db
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
from flask import Blueprint, render_template, request, redirect, url_for, flash
import csv
from .warehouse import add_location
from app.settings import (
@@ -27,6 +26,12 @@ from .print_module import get_unprinted_orders_data
bp = Blueprint('main', __name__)
warehouse_bp = Blueprint('warehouse', __name__)
@bp.route('/main_scan')
def main_scan():
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'scan']:
flash('Access denied: Scan users only.')
return redirect(url_for('main.dashboard'))
return render_template('main_page_scan.html')
@bp.route('/', methods=['GET', 'POST'])
def login():
@@ -294,6 +299,79 @@ def logout():
session.pop('role', None)
return redirect(url_for('main.login'))
# Finish Goods Scan Route
@bp.route('/fg_scan', methods=['GET', 'POST'])
def fg_scan():
if 'role' not in session or session['role'] not in ['superadmin', 'administrator', 'admin', 'scan']:
flash('Access denied: Scan users only.')
return redirect(url_for('main.dashboard'))
if request.method == 'POST':
# Handle form submission
operator_code = request.form.get('operator_code')
cp_code = request.form.get('cp_code')
oc1_code = request.form.get('oc1_code')
oc2_code = request.form.get('oc2_code')
defect_code = request.form.get('defect_code')
date = request.form.get('date')
time = request.form.get('time')
try:
# Connect to the database
conn = get_db_connection()
cursor = conn.cursor()
# Check if the CP_full_code already exists in scanfg_orders
cursor.execute("SELECT Id FROM scanfg_orders WHERE CP_full_code = ?", (cp_code,))
existing_entry = cursor.fetchone()
if existing_entry:
# Update the existing entry
update_query = """
UPDATE scanfg_orders
SET operator_code = ?, OC1_code = ?, OC2_code = ?, quality_code = ?, date = ?, time = ?
WHERE CP_full_code = ?
"""
cursor.execute(update_query, (operator_code, oc1_code, oc2_code, defect_code, date, time, cp_code))
flash('Existing entry updated successfully.')
else:
# Insert a new entry
insert_query = """
INSERT INTO scanfg_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time)
VALUES (?, ?, ?, ?, ?, ?, ?)
"""
cursor.execute(insert_query, (operator_code, cp_code, oc1_code, oc2_code, defect_code, date, time))
flash('New entry inserted successfully.')
# Commit the transaction
conn.commit()
conn.close()
except mariadb.Error as e:
print(f"Error saving finish goods scan data: {e}")
flash(f"Error saving scan data: {e}")
# Fetch the latest scan data for display from scanfg_orders
scan_data = []
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT Id, operator_code, CP_base_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity
FROM scanfg_orders
ORDER BY Id DESC
LIMIT 15
""")
raw_scan_data = cursor.fetchall()
# Apply formatting to scan data for consistent date display
scan_data = [[format_cell_data(cell) for cell in row] for row in raw_scan_data]
conn.close()
except mariadb.Error as e:
print(f"Error fetching finish goods scan data: {e}")
flash(f"Error fetching scan data: {e}")
return render_template('fg_scan.html', scan_data=scan_data)
@bp.route('/create_user', methods=['POST'])
def create_user():
return create_user_handler()
@@ -1143,8 +1221,6 @@ For support, contact your system administrator.
except Exception as e:
print(f"Error creating extension package: {e}")
import traceback
traceback.print_exc()
return jsonify({
'success': False,
'error': str(e)
@@ -1365,7 +1441,6 @@ Installation Time: ~5 minutes
Maintenance Required: Zero (auto-starts with Windows)
Ready to use immediately after installation!"""
zipf.writestr('INSTALLATION_README.txt', installation_readme)
files_added += 1
@@ -1425,7 +1500,7 @@ def create_zero_dependency_service_package():
if not os.path.exists(service_dir):
return jsonify({
'success': False,
'error': f'Windows service directory not found: {service_dir}'
'error': f'Windows service directory not found: {service_dir}'
}), 500
# Create static directory if it doesn't exist
@@ -1715,6 +1790,9 @@ This package contains EVERYTHING needed to run the Quality Print Service:
Quality Web App → Chrome Extension → Windows Service → Printer
```
**Printing Methods (automatic fallback):**
1. Adobe Reader (silent printing)
**Printing Methods (automatic fallback):**
1. Adobe Reader (silent printing)
2. SumatraPDF (if Adobe unavailable)
@@ -2382,6 +2460,41 @@ def generate_location_label_pdf():
from app.warehouse import generate_location_label_pdf
return generate_location_label_pdf()
@warehouse_bp.route('/update_location', methods=['POST'])
def update_location():
from app.warehouse import update_location
try:
data = request.get_json()
location_id = data.get('location_id')
location_code = data.get('location_code')
size = data.get('size')
description = data.get('description')
if not location_id or not location_code:
return jsonify({'success': False, 'error': 'Location ID and code are required'})
result = update_location(location_id, location_code, size, description)
return jsonify(result)
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
@warehouse_bp.route('/delete_location', methods=['POST'])
def delete_location():
from app.warehouse import delete_location_by_id
try:
data = request.get_json()
location_id = data.get('location_id')
if not location_id:
return jsonify({'success': False, 'error': 'Location ID is required'})
result = delete_location_by_id(location_id)
return jsonify(result)
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
# NOTE for frontend/extension developers:
# To print labels, call the Chrome extension and pass the PDF URL:
+149
View File
@@ -0,0 +1,149 @@
/* Global Base Styles - Common across all pages */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f9;
}
.main-content {
width: 100vw;
max-width: 100vw;
margin: 0;
padding: 0;
background: none;
border-radius: 0;
box-shadow: none;
}
/* Header Styles */
header {
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #ccc;
}
.header-content {
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
}
.left-header {
display: flex;
align-items: center;
}
.left-header .logo {
width: 60px;
height: auto;
margin-right: 10px;
}
.left-header .page-title {
font-size: 1.5em;
font-weight: bold;
}
.right-header {
display: flex;
align-items: center;
}
.right-header .user-info {
margin-right: 15px;
font-size: 1em;
color: #333;
}
.right-header .logout-button {
padding: 5px 10px;
font-size: 1em;
background-color: #007bff;
color: #fff;
text-decoration: none;
border-radius: 5px;
border: none;
cursor: pointer;
}
.right-header .logout-button:hover {
background-color: #0056b3;
color: #fff;
}
/* Common Button Styles */
.btn {
padding: 8px 16px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
display: inline-block;
font-size: 14px;
}
.btn:hover {
background-color: #0056b3;
}
.btn-success {
background-color: #28a745;
}
.btn-success:hover {
background-color: #218838;
}
.btn-danger {
background-color: #dc3545;
}
.btn-danger:hover {
background-color: #c82333;
}
.btn-info {
background-color: #17a2b8;
}
.btn-info:hover {
background-color: #138496;
}
/* Theme Toggle */
.theme-toggle {
background-color: #6c757d;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
font-size: 12px;
}
.theme-toggle:hover {
background-color: #5a6268;
}
/* Dark Mode Base Styles */
body.dark-mode {
background-color: #121212;
color: #e0e0e0;
}
body.dark-mode header {
background-color: #1e1e1e;
color: #fff;
}
body.dark-mode .user-info {
color: #ccc;
}
+67
View File
@@ -0,0 +1,67 @@
/* Dashboard and General Page Styles */
.dashboard-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.module-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.module-card {
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.module-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.module-card h3 {
margin: 0 0 15px 0;
color: #333;
font-size: 1.2em;
}
.module-card p {
color: #666;
margin-bottom: 15px;
line-height: 1.5;
}
.module-card a {
display: inline-block;
padding: 8px 16px;
background-color: #007bff;
color: #fff;
text-decoration: none;
border-radius: 4px;
transition: background-color 0.2s;
}
.module-card a:hover {
background-color: #0056b3;
}
/* Dark mode for dashboard */
body.dark-mode .module-card {
background: #2d2d2d;
color: #e0e0e0;
}
body.dark-mode .module-card h3 {
color: #fff;
}
body.dark-mode .module-card p {
color: #ccc;
}
+216
View File
@@ -0,0 +1,216 @@
/* Login Page Specific Styles */
.login-page {
display: flex;
align-items: center;
justify-content: space-between;
height: 100vh;
background-color: #f4f4f9;
padding: 0 20px;
}
.logo-container {
width: 100%;
text-align: center;
margin-bottom: 10px;
}
.login-logo {
max-height: 90vh;
width: auto;
}
.form-container {
width: 600px;
background: #fff;
padding: 15px 30px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
margin: 0;
align-self: center;
}
.form-container h2 {
text-align: center;
margin-bottom: 15px;
}
.form-container form {
display: flex;
flex-direction: column;
}
.form-container label {
margin-bottom: 5px;
font-weight: bold;
}
.form-container input {
margin-bottom: 10px;
padding: 8px;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 16px; /* Prevents zoom on iOS */
}
.form-container button {
padding: 10px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 16px;
}
.form-container button:hover {
background-color: #0056b3;
}
/* Hide the header only on the login page */
body.light-mode.login-page header,
body.dark-mode.login-page header {
display: none;
}
/* Mobile Responsive Design */
/* Tablet styles (768px and down) */
@media screen and (max-width: 768px) {
.login-page {
flex-direction: column;
justify-content: center;
padding: 20px 15px;
gap: 20px;
}
.logo-container {
margin-bottom: 0;
}
.login-logo {
max-height: 30vh;
max-width: 80vw;
}
.form-container {
width: 100%;
max-width: 400px;
padding: 20px 25px;
}
}
/* Mobile styles (480px and down) */
@media screen and (max-width: 480px) {
.login-page {
padding: 15px 10px;
gap: 15px;
}
.login-logo {
max-height: 25vh;
max-width: 85vw;
}
.form-container {
padding: 20px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.form-container h2 {
font-size: 1.5em;
margin-bottom: 20px;
}
.form-container input {
padding: 12px;
font-size: 16px;
border-radius: 5px;
margin-bottom: 15px;
}
.form-container button {
padding: 12px;
font-size: 16px;
border-radius: 5px;
font-weight: 600;
}
.form-container label {
font-size: 14px;
margin-bottom: 8px;
}
}
/* Small mobile styles (320px and down) */
@media screen and (max-width: 320px) {
.login-page {
padding: 10px 8px;
}
.login-logo {
max-height: 20vh;
max-width: 90vw;
}
.form-container {
padding: 15px;
width: calc(100% - 16px);
}
.form-container h2 {
font-size: 1.3em;
}
}
/* Landscape orientation adjustments */
@media screen and (max-height: 500px) and (orientation: landscape) {
.login-page {
flex-direction: row;
justify-content: space-around;
align-items: center;
gap: 20px;
}
.logo-container {
flex: 1;
margin-bottom: 0;
}
.login-logo {
max-height: 80vh;
max-width: 40vw;
}
.form-container {
flex: 1;
max-width: 350px;
align-self: center;
}
}
/* Touch device optimizations */
@media (hover: none) and (pointer: coarse) {
.form-container input {
min-height: 44px; /* Apple's recommended touch target size */
}
.form-container button {
min-height: 44px;
font-weight: 600;
}
.form-container button:active {
background-color: #004494;
transform: translateY(1px);
}
}
/* High DPI displays */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.login-logo {
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
}
}
+50
View File
@@ -0,0 +1,50 @@
/* Scan Module Specific Styles */
.scan-form-card {
width: 380px;
max-width: 380px;
margin: 0 auto 20px auto;
max-height: 660px;
overflow-y: auto;
padding: 18px 18px 12px 18px;
}
.scan-form-card form {
display: flex;
flex-direction: column;
gap: 4px;
}
.scan-form-card label {
font-weight: 500;
margin-bottom: 1px;
font-size: 13px;
}
.scan-form-card input[type="text"] {
padding: 5px 10px;
font-size: 13px;
margin-bottom: 2px;
}
.scan-table-card {
overflow-x: auto;
}
.scan-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
font-size: 11px;
}
.scan-table th, .scan-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
.scan-table th {
background-color: #f4f4f4;
font-weight: bold;
}
+739
View File
@@ -0,0 +1,739 @@
/* Make the warehouse locations table scrollable within the card */
.warehouse-table-scroll {
max-height: 520px; /* fits inside 640px card with header and hint */
overflow-y: auto;
margin-bottom: 0;
}
/* Ensure .main-content is full width for warehouse pages */
/* Fix horizontal overflow: use 100% width, not 100vw */
.main-content {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
border-radius: 0 !important;
box-shadow: none !important;
}
/* Normalize box-sizing for all elements */
*, *::before, *::after {
box-sizing: border-box;
}
/* WAREHOUSE PAGE CSS - FRESH START */
/* Reset and override all base styles for warehouse pages */
.main-content {
width: 100% !important;
height: calc(100vh - 60px) !important; /* Full height minus header */
margin: 0 !important;
padding: 10px !important; /* 10px margin from page edges */
background: #f4f4f9 !important; /* Match body background */
border: none !important;
box-shadow: none !important;
box-sizing: border-box !important;
display: flex !important;
flex-direction: column !important;
}
/* Main warehouse container - full width with 10px padding */
.warehouse-page-container {
width: 100%;
height: 100%;
display: flex;
gap: 10px; /* 10px between containers */
box-sizing: border-box;
}
/* The 3 main containers with 1:2:1 ratio */
.warehouse-container-1 {
flex: 1; /* 1 part */
background: #f8fafc; /* Theme card background */
color: #1e293b; /* Theme text color */
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
box-sizing: border-box;
overflow-y: auto;
align-self: flex-start;
max-height: 640px;
min-height: 320px;
}
.warehouse-container-2 {
flex: 2; /* 2 parts */
background: #f8fafc; /* Theme card background */
color: #1e293b; /* Theme text color */
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
box-sizing: border-box;
overflow-y: auto;
max-height: 640px;
}
.warehouse-container-3 {
flex: 1; /* 1 part */
background: #f8fafc; /* Theme card background */
color: #1e293b; /* Theme text color */
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
box-sizing: border-box;
overflow-y: auto;
max-height: 640px;
}
/* Form and table styles for warehouse components */
form {
display: flex;
flex-direction: column;
gap: 4px; /* Reduced gap for compactness */
}
form label {
font-weight: bold;
margin-bottom: 2px; /* Reduced margin for compactness */
}
form input, form select {
padding: 8px 12px;
border: 1px solid #cbd5e1;
border-radius: 4px;
font-size: 14px;
background: #e2e8f0; /* Theme input background */
color: #1e293b; /* Theme input text */
}
.btn {
padding: 8px 16px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
display: inline-block;
font-size: 14px;
}
.btn:hover {
background-color: #0056b3;
}
.btn-success {
background-color: #28a745;
}
.btn-info {
background-color: #17a2b8;
}
.btn-danger {
background-color: #dc3545;
}
/* Table styles */
.scan-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
font-size: 12px;
}
.scan-table th, .scan-table td {
border: 1px solid #cbd5e1; /* Theme border color */
padding: 8px;
text-align: left;
}
.scan-table th {
background-color: #e2e8f0; /* Theme input background for headers */
font-weight: bold;
color: #334155; /* Theme label color */
}
.location-row {
cursor: pointer;
transition: background-color 0.2s;
}
.location-row:hover {
background-color: #e2e8f0; /* Theme hover background */
}
.location-row.selected {
background-color: #ffb300 !important;
color: #222 !important;
font-weight: bold;
}
/* Edit/Delete card styles */
.edit-delete-card {
margin-bottom: 20px;
}
.selection-message {
color: #64748b; /* Slightly lighter theme text */
font-style: italic;
margin-bottom: 10px;
}
.selection-info {
margin-bottom: 10px;
}
.button-group {
display: flex;
gap: 10px;
margin-top: 10px;
}
/* Barcode preview */
#barcode-label-preview {
width: 8cm;
height: 4cm;
border: 2px solid #333;
background: white;
display: flex;
align-items: center;
justify-content: center;
margin: 10px auto;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
#barcode-label-preview .location-text {
font-size: 12px;
font-weight: bold;
text-align: center;
margin-bottom: 10px;
}
/* Modal styles */
.modal {
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
width: 90%;
max-width: 500px;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #ddd;
background-color: #f8f9fa;
}
.modal-body {
padding: 20px;
}
.close {
font-size: 24px;
font-weight: bold;
cursor: pointer;
color: #666;
}
.close:hover {
color: #333;
}
.confirm-table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
.confirm-table td {
padding: 8px 12px;
border-bottom: 1px solid #eee;
}
.confirm-table td:first-child {
width: 30%;
background-color: #f8f9fa;
font-weight: bold;
}
.modal-buttons {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
html, body {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
.scan-container {
width: 100vw !important;
margin: 0 !important;
padding: 0 !important;
display: block !important; /* Override any grid from style.css */
grid-template-columns: unset !important; /* Remove any grid columns */
gap: unset !important; /* Remove any gap */
max-width: 100vw !important; /* Override max-width from style.css */
box-shadow: none !important; /* Remove box shadow */
border-radius: 0 !important; /* Remove border radius */
}
/* Full-width container for warehouse pages - avoids scan-container restrictions */
.warehouse-full-container {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
display: block !important;
box-sizing: border-box;
background: transparent !important;
}
/* Container card with left/right margins */
.warehouse-container-card {
margin-left: 15px;
margin-right: 15px;
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
width: calc(100% - 30px);
box-sizing: border-box;
}
/* 3-column layout for warehouse create locations */
.warehouse-grid {
display: grid !important;
grid-template-columns: 1fr 2fr 1fr !important;
gap: 20px;
margin: 0;
min-height: auto;
width: 100%;
box-sizing: border-box;
}
/* Make cards equal height in grid with higher specificity */
.warehouse-grid .card {
height: fit-content;
align-self: start;
width: 100% !important;
max-width: none !important;
margin-left: 8px;
margin-right: 8px;
}
/* Responsive grid - collapse to single column on smaller screens */
@media (max-width: 1024px) {
.warehouse-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.warehouse-grid {
gap: 15px;
}
.scan-container {
padding: 15px;
}
}
.card {
background: #fff;
border-radius: 8px;
padding: 20px;
margin: 0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Location row selection styles */
.location-row {
cursor: pointer;
transition: background-color 0.2s;
}
.location-row:hover {
background-color: #f8f9fa;
}
tr.location-row.selected, .location-row.selected td {
background-color: #ffb300 !important;
color: #222 !important;
font-weight: bold;
box-shadow: 0 2px 8px rgba(255,179,0,0.12);
border-left: 4px solid #ff9800;
transition: background 0.2s, color 0.2s;
}
/* Print section styles */
.print-section {
margin-top: 24px;
padding: 16px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f9f9f9;
}
/* Edit/Delete card styles - compact version */
.edit-delete-card {
margin-top: 16px;
padding: 12px 16px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f8f9fa;
}
.edit-delete-card h3 {
font-size: 1.05em;
margin: 0 0 8px 0;
color: #333;
}
.edit-delete-card .selection-message {
color: #666;
font-style: italic;
margin-bottom: 8px;
font-size: 0.9em;
}
.edit-delete-card .selection-info {
margin-bottom: 8px;
font-size: 0.9em;
}
.edit-delete-card .button-group {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 8px;
}
.edit-delete-card .btn {
font-size: 11px;
padding: 5px 12px;
}
/* Barcode label preview */
#barcode-label-preview {
width: 8cm;
height: 4cm;
border: 2px solid #333;
background: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 10px auto;
position: relative;
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
}
#barcode-label-preview .location-text {
font-size: 12px;
font-weight: bold;
text-align: center;
margin-bottom: 10px;
}
#barcode-label-preview #location-barcode {
display: block;
margin: 0 auto;
max-width: 90%;
max-height: 70%;
}
#barcode-label-preview #barcode-placeholder {
text-align: center;
width: 100%;
}
/* Dark mode for warehouse */
body.dark-mode .card {
background: #2d2d2d;
color: #e0e0e0;
}
body.dark-mode .scan-table {
background-color: #1e1e1e;
color: #fff;
}
body.dark-mode .scan-table th {
background-color: #333;
}
body.dark-mode .scan-table td {
color: #ddd;
}
body.dark-mode .scan-table tr:nth-child(even) {
background-color: #2a2a2a;
}
body.dark-mode .print-section {
background-color: #333;
border-color: #555;
}
body.dark-mode .edit-delete-card {
background-color: #333;
border-color: #555;
}
body.dark-mode .edit-delete-card h3 {
color: #fff;
}
body.dark-mode .edit-delete-card .selection-message {
color: #ccc;
}
/* Modal Styles */
.modal {
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
width: 90%;
max-width: 500px;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #ddd;
background-color: #f8f9fa;
border-radius: 8px 8px 0 0;
}
.modal-header h3 {
margin: 0;
color: #333;
}
.close {
font-size: 24px;
font-weight: bold;
cursor: pointer;
color: #666;
line-height: 1;
}
.close:hover {
color: #333;
}
.modal-body {
padding: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #333;
}
.form-group input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
.modal-buttons {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.confirm-table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
.confirm-table td {
padding: 8px 12px;
border-bottom: 1px solid #eee;
}
.confirm-table td:first-child {
width: 30%;
background-color: #f8f9fa;
font-weight: bold;
}
.btn-secondary {
background-color: #6c757d;
}
.btn-secondary:hover {
background-color: #5a6268;
}
/* Dark mode for modals */
body.dark-mode .modal-content {
background-color: #2d2d2d;
color: #e0e0e0;
}
body.dark-mode .modal-header {
background-color: #333;
border-bottom-color: #555;
}
body.dark-mode .modal-header h3 {
color: #fff;
}
body.dark-mode .close {
color: #ccc;
}
body.dark-mode .close:hover {
color: #fff;
}
body.dark-mode .form-group input {
background-color: #333;
border-color: #555;
color: #e0e0e0;
}
body.dark-mode .confirm-table td:first-child {
background-color: #333;
}
body.dark-mode .modal-buttons {
border-top-color: #555;
}
/* ENHANCED DARK MODE SUPPORT FOR NEW WAREHOUSE LAYOUT */
body.dark-mode .main-content {
background: #121212 !important; /* Match body dark background */
}
body.dark-mode .warehouse-container-1,
body.dark-mode .warehouse-container-2,
body.dark-mode .warehouse-container-3 {
background: #1e1e1e !important; /* Match header dark background */
color: #e0e0e0 !important; /* Match body dark text */
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
body.dark-mode form label {
color: #e0e0e0 !important; /* Match body dark text */
}
body.dark-mode form input,
body.dark-mode form select {
background: #2d2d2d !important; /* Slightly lighter than containers */
color: #e0e0e0 !important; /* Match body dark text */
border: 1px solid #444 !important;
}
body.dark-mode .btn {
background-color: #4299e1 !important;
}
body.dark-mode .btn:hover {
background-color: #3182ce !important;
}
body.dark-mode .btn-success {
background-color: #48bb78 !important;
}
body.dark-mode .btn-info {
background-color: #38b2ac !important;
}
body.dark-mode .btn-danger {
background-color: #f56565 !important;
}
body.dark-mode .scan-table {
background-color: #1e1e1e !important; /* Match header background */
color: #e0e0e0 !important; /* Match body dark text */
}
body.dark-mode .scan-table th {
background-color: #2d2d2d !important; /* Slightly lighter */
color: #e0e0e0 !important; /* Match body dark text */
border: 1px solid #444 !important;
}
body.dark-mode .scan-table td {
color: #e0e0e0 !important; /* Match body dark text */
border: 1px solid #444 !important;
}
body.dark-mode .location-row:hover {
background-color: #2d2d2d !important; /* Slightly lighter than table */
}
body.dark-mode .location-row.selected {
background-color: #ed8936 !important;
color: #1a202c !important;
}
body.dark-mode .selection-message {
color: #999 !important; /* Lighter gray for secondary text */
}
body.dark-mode .selection-info {
color: #e0e0e0 !important; /* Match body dark text */
}
body.dark-mode #barcode-label-preview {
background: #f8fafc !important; /* Keep white for readability */
border: 2px solid #444 !important;
}
+6 -1
View File
@@ -4,7 +4,12 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Flask App{% endblock %}</title>
<!-- Base CSS for common styles -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}">
<!-- Legacy CSS for backward compatibility (temporarily) -->
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<!-- Page-specific CSS -->
{% block head %}{% endblock %}
</head>
<body class="light-mode">
{% if request.endpoint != 'main.login' %}
@@ -38,7 +43,7 @@
</div>
</header>
{% endif %}
<div class="container">
<div class="main-content">
{% block content %}{% endblock %}
</div>
<script src="{{ url_for('static', filename='script.js') }}"></script>
+237 -113
View File
@@ -2,72 +2,13 @@
{% block title %}Create Warehouse Locations{% endblock %}
{% block head %}
<style>
/* Barcode label preview */
#barcode-label-preview {
width: 8cm;
height: 4cm;
border: 2px solid #333;
background: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 10px auto;
position: relative;
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
}
#barcode-label-preview .location-text {
font-size: 12px;
font-weight: bold;
text-align: center;
margin-bottom: 10px;
}
#barcode-label-preview #location-barcode {
display: block;
margin: 0 auto;
max-width: 90%;
max-height: 70%;
}
#barcode-label-preview #barcode-placeholder {
text-align: center;
width: 100%;
}
.print-section {
margin-top: 24px;
padding: 16px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f9f9f9;
}
.location-row.selected {
background-color: #ffb300 !important; /* Accent color: Amber */
color: #222 !important;
font-weight: bold;
box-shadow: 0 2px 8px rgba(255,179,0,0.12);
border-left: 4px solid #ff9800;
transition: background 0.2s, color 0.2s;
}
.location-row {
cursor: pointer;
}
.location-row:hover {
background-color: #f8f9fa;
}
</style>
<link rel="stylesheet" href="{{ url_for('static', filename='css/warehouse.css') }}">
{% endblock %}
{% block content %}
<div class="scan-container">
<!-- Add Warehouse Location Card -->
<div class="card scan-form-card">
<div class="warehouse-page-container">
<!-- Container 1: Add Location (1 part width) -->
<div class="warehouse-container-1">
<h3>Add Warehouse Location</h3>
{% if message %}
<div class="form-message">{{ message }}</div>
@@ -81,7 +22,7 @@
<input type="text" name="description" maxlength="250"><br>
<button type="submit" class="btn">Add Location</button>
</form>
<!-- Import from CSV content moved here -->
<!-- Import from CSV section -->
<div style="margin-top: 24px;">
<h3 style="font-size: 1.1em;">Import Locations from CSV</h3>
<div style="display: flex; flex-direction: row; gap: 16px; align-items: center;">
@@ -89,11 +30,77 @@
<a href="{{ url_for('warehouse.import_locations_csv') }}" class="btn" style="padding: 4px 12px; font-size: 0.95em;">Go to Import Page</a>
</div>
</div>
</div>
<!-- Print Barcode Section -->
<div class="print-section">
<!-- Container 2: Warehouse Locations Table (2 parts width) -->
<div class="warehouse-container-2">
<h3>Warehouse Locations</h3>
<div id="location-selection-hint" style="margin-bottom: 10px; font-size: 11px; color: #666; text-align: center;">
Click on a row to select a location for printing
</div>
<div class="warehouse-table-scroll">
<table class="scan-table" id="locations-table">
<thead>
<tr>
<th>ID</th>
<th>Location Code</th>
<th>Size</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for loc in locations %}
<tr class="location-row" data-location-code="{{ loc[1] }}"
data-location-id="{{ loc[0] }}"
data-location-size="{{ loc[2] or '' }}"
data-location-description="{{ loc[3] or '' }}">
<td>{{ loc[0] }}</td>
<td>{{ loc[1] }}</td>
<td>{{ loc[2] or '' }}</td>
<td>{{ loc[3] or '' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Container 3: Edit/Delete and Print (1 part width) -->
<div class="warehouse-container-3">
<div class="edit-delete-card">
<h3>Edit/Delete Selected Location</h3>
<div id="no-selection-message" class="selection-message">
Select a location from the table to edit or delete it.
</div>
<div id="selected-location-info" class="selection-info" style="display: none;">
<strong>Selected:</strong> <span id="selected-location-display"></span>
</div>
<div class="button-group">
<button id="edit-location-btn" class="btn btn-info" disabled>
✏️ Edit Selected Location
</button>
<button id="delete-location-btn" class="btn btn-danger" disabled>
🗑️ Delete Selected Location
</button>
</div>
</div>
{% if session['role'] in ['administrator', 'management'] %}
<div style="margin-top: 32px; padding: 12px; border-top: 1px solid #eee;">
<label style="font-weight:bold;">Delete location from table</label>
<div style="font-size:0.95em; margin-bottom:8px;">To delete a location, enter the ID of the location and press delete.<br>To delete 2 or multiple locations, enter the IDs separated by "," and then press delete.</div>
<form method="POST" style="display:flex; gap:8px; align-items:center;" onsubmit="return confirmDeleteLocations();">
<input type="text" name="delete_ids" placeholder="e.g. 5,7,12" style="width:160px;">
<button type="submit" name="delete_locations" value="1" class="btn" style="padding:4px 16px;">Delete Locations</button>
</form>
<script>
function confirmDeleteLocations() {
return confirm('Do you really want to delete the selected locations?');
}
</script>
</div>
{% endif %}
<hr style="margin: 32px 0 24px 0;">
<h3 style="font-size: 1.1em; margin-bottom: 12px;">Print Location Barcode</h3>
<!-- Label Preview -->
<div id="barcode-label-preview" style="display: flex; align-items: center; justify-content: center;">
<div style="width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;">
@@ -104,7 +111,6 @@
<div id="barcode-placeholder" style="color: #999; font-style: italic; text-align: center;">No location selected</div>
</div>
</div>
<!-- Print Controls -->
<div style="text-align: center; margin-top: 15px;">
<div style="margin-bottom: 10px;">
@@ -121,51 +127,29 @@
</button>
<div id="print-status" style="margin-top: 8px; font-size: 11px;"></div>
</div>
</div>
<!-- Delete Location Area -->
{% if session['role'] in ['administrator', 'management'] %}
<div style="margin-top: 32px; padding: 12px; border-top: 1px solid #eee;">
<label style="font-weight:bold;">Delete location from table</label>
<div style="font-size:0.95em; margin-bottom:8px;">To delete a location, enter the ID of the location and press delete.<br>To delete 2 or multiple locations, enter the IDs separated by "," and then press delete.</div>
<form method="POST" style="display:flex; gap:8px; align-items:center;" onsubmit="return confirmDeleteLocations();">
<input type="text" name="delete_ids" placeholder="e.g. 5,7,12" style="width:160px;">
<button type="submit" name="delete_locations" value="1" class="btn" style="padding:4px 16px;">Delete Locations</button>
</form>
<script>
function confirmDeleteLocations() {
return confirm('Do you really want to delete the selected locations?');
}
</script>
</div>
{% endif %}
</div>
<!-- Locations Table Card -->
<div class="card scan-table-card">
<h3>Warehouse Locations</h3>
<div id="location-selection-hint" style="margin-bottom: 10px; font-size: 11px; color: #666; text-align: center;">
Click on a row to select a location for printing
</div>
<!-- Delete Confirmation Modal -->
<div id="delete-modal" class="modal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h3>Confirm Delete</h3>
<span class="close" onclick="closeDeleteModal()">&times;</span>
</div>
<div class="modal-body">
<p>Are you sure you want to delete the following location?</p>
<table class="confirm-table">
<tr><td><strong>ID:</strong></td><td id="delete-confirm-id"></td></tr>
<tr><td><strong>Location Code:</strong></td><td id="delete-confirm-code"></td></tr>
<tr><td><strong>Size:</strong></td><td id="delete-confirm-size"></td></tr>
<tr><td><strong>Description:</strong></td><td id="delete-confirm-description"></td></tr>
</table>
<div class="modal-buttons">
<button type="button" class="btn btn-secondary" onclick="closeDeleteModal()">Cancel</button>
<button type="button" class="btn btn-danger" onclick="confirmDelete()">Delete Location</button>
</div>
</div>
<table class="scan-table" id="locations-table">
<thead>
<tr>
<th>ID</th>
<th>Location Code</th>
<th>Size</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for loc in locations %}
<tr class="location-row" data-location-code="{{ loc[1] }}">
<td>{{ loc[0] }}</td>
<td>{{ loc[1] }}</td>
<td>{{ loc[2] }}</td>
<td>{{ loc[3] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
@@ -369,6 +353,13 @@ document.addEventListener('DOMContentLoaded', function() {
const locationRows = document.querySelectorAll('.location-row');
const locationCodeDisplay = document.getElementById('location-code-display');
const printButton = document.getElementById('print-barcode-btn');
const editButton = document.getElementById('edit-location-btn');
const deleteButton = document.getElementById('delete-location-btn');
const noSelectionMessage = document.getElementById('no-selection-message');
const selectedLocationInfo = document.getElementById('selected-location-info');
const selectedLocationDisplay = document.getElementById('selected-location-display');
let selectedLocation = null;
locationRows.forEach(row => {
row.addEventListener('click', function() {
@@ -378,22 +369,49 @@ document.addEventListener('DOMContentLoaded', function() {
// Select this row
this.classList.add('selected');
// Get location code
// Get location data
selectedLocationCode = this.dataset.locationCode;
selectedLocation = {
id: this.cells[0].textContent,
code: this.cells[1].textContent,
size: this.cells[2].textContent,
description: this.cells[3].textContent
};
// Update display
locationCodeDisplay.textContent = selectedLocationCode;
selectedLocationDisplay.textContent = `${selectedLocation.code} (ID: ${selectedLocation.id})`;
// Show/hide selection info
noSelectionMessage.style.display = 'none';
selectedLocationInfo.style.display = 'block';
// Generate barcode
generateLocationBarcode(selectedLocationCode);
// Enable print button
// Enable buttons
printButton.disabled = false;
editButton.disabled = false;
deleteButton.disabled = false;
showNotification(`📍 Selected location: ${selectedLocationCode}`, 'info');
});
});
// Edit button functionality
editButton.addEventListener('click', function() {
if (selectedLocation) {
openEditModal(selectedLocation);
}
});
// Delete button functionality
deleteButton.addEventListener('click', function() {
if (selectedLocation) {
openDeleteModal(selectedLocation);
}
});
// Initialize QZ Tray
initializeQZTray();
});
@@ -519,5 +537,111 @@ async function testQZConnection() {
// Add test button functionality (for debugging - can be removed in production)
console.log('🔧 QZ Tray test function available: testQZConnection()');
// Modal Functions for Edit/Delete
function openEditModal(location) {
document.getElementById('edit-location-id').value = location.id;
document.getElementById('edit-location-code').value = location.code;
document.getElementById('edit-size').value = location.size || '';
document.getElementById('edit-description').value = location.description || '';
document.getElementById('edit-modal').style.display = 'flex';
}
function closeEditModal() {
document.getElementById('edit-modal').style.display = 'none';
}
function openDeleteModal(location) {
document.getElementById('delete-confirm-id').textContent = location.id;
document.getElementById('delete-confirm-code').textContent = location.code;
document.getElementById('delete-confirm-size').textContent = location.size || '-';
document.getElementById('delete-confirm-description').textContent = location.description || '-';
document.getElementById('delete-modal').style.display = 'flex';
}
function closeDeleteModal() {
document.getElementById('delete-modal').style.display = 'none';
}
// Handle edit form submission
document.getElementById('edit-form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = {
location_id: formData.get('location_id'),
location_code: formData.get('location_code'),
size: formData.get('size'),
description: formData.get('description')
};
// Send update request
fetch('/update_location', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.success) {
showNotification('✅ Location updated successfully!', 'success');
closeEditModal();
// Reload page to show changes
setTimeout(() => window.location.reload(), 1000);
} else {
showNotification('❌ Error updating location: ' + result.error, 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('❌ Error updating location: ' + error.message, 'error');
});
});
// Handle delete confirmation
function confirmDelete() {
const locationId = document.getElementById('delete-confirm-id').textContent;
// Send delete request
fetch('/delete_location', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ location_id: locationId })
})
.then(response => response.json())
.then(result => {
if (result.success) {
showNotification('✅ Location deleted successfully!', 'success');
closeDeleteModal();
// Reload page to show changes
setTimeout(() => window.location.reload(), 1000);
} else {
showNotification('❌ Error deleting location: ' + result.error, 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('❌ Error deleting location: ' + error.message, 'error');
});
}
// Close modals when clicking outside
window.addEventListener('click', function(event) {
const editModal = document.getElementById('edit-modal');
const deleteModal = document.getElementById('delete-modal');
if (event.target === editModal) {
closeEditModal();
}
if (event.target === deleteModal) {
closeDeleteModal();
}
});
</script>
</div>
</div>
{% endblock %}
+1 -1
View File
@@ -9,7 +9,7 @@
<div class="dashboard-card">
<h3>Access Scanning Module</h3>
<p>Final scanning module for production orders</p>
<a href="{{ url_for('main.scan') }}" class="btn">Launch Scanning Module</a>
<a href="{{ url_for('main.main_scan') }}" class="btn">Launch Scanning Module</a>
</div>
<div class="dashboard-card">
+473
View File
@@ -0,0 +1,473 @@
{% extends "base.html" %}
{% block title %}Finish Good Scan{% endblock %}
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/scan.css') }}">
<style>
.error-message {
color: #ff4444;
font-size: 0.9em;
margin-top: 5px;
display: none;
}
.error-message.show {
display: block;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const operatorCodeInput = document.getElementById('operator_code');
const cpCodeInput = document.getElementById('cp_code');
const oc1CodeInput = document.getElementById('oc1_code');
const oc2CodeInput = document.getElementById('oc2_code');
const defectCodeInput = document.getElementById('defect_code');
const form = document.getElementById('fg-scan-form');
// Create error message element for operator code
const operatorErrorMessage = document.createElement('div');
operatorErrorMessage.className = 'error-message';
operatorErrorMessage.id = 'operator-error';
operatorErrorMessage.textContent = 'Please scan Quality Operator code (must start with OP)';
operatorCodeInput.parentNode.insertBefore(operatorErrorMessage, operatorCodeInput.nextSibling);
// Create error message element for CP code
const cpErrorMessage = document.createElement('div');
cpErrorMessage.className = 'error-message';
cpErrorMessage.id = 'cp-error';
cpErrorMessage.textContent = 'Please scan a valid CP';
cpCodeInput.parentNode.insertBefore(cpErrorMessage, cpCodeInput.nextSibling);
// Create error message element for OC1 code
const oc1ErrorMessage = document.createElement('div');
oc1ErrorMessage.className = 'error-message';
oc1ErrorMessage.id = 'oc1-error';
oc1ErrorMessage.textContent = 'Please scan a valid OC (must start with OC)';
oc1CodeInput.parentNode.insertBefore(oc1ErrorMessage, oc1CodeInput.nextSibling);
// Create error message element for OC2 code
const oc2ErrorMessage = document.createElement('div');
oc2ErrorMessage.className = 'error-message';
oc2ErrorMessage.id = 'oc2-error';
oc2ErrorMessage.textContent = 'Please scan a valid OC (must start with OC)';
oc2CodeInput.parentNode.insertBefore(oc2ErrorMessage, oc2CodeInput.nextSibling);
// Create error message element for defect code
const defectErrorMessage = document.createElement('div');
defectErrorMessage.className = 'error-message';
defectErrorMessage.id = 'defect-error';
defectErrorMessage.textContent = 'Defect code must be a 3-digit number (e.g., 000, 001, 123)';
defectCodeInput.parentNode.insertBefore(defectErrorMessage, defectCodeInput.nextSibling);
// Validate operator code on input
operatorCodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value; // Convert to uppercase
if (value.length >= 2 && !value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
} else {
operatorErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// Prevent leaving operator code field if invalid
operatorCodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
// Return focus to this field
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if operator code is invalid
operatorCodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
this.select();
}
});
// Validate CP code on input
cpCodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value; // Convert to uppercase
if (value.length >= 2 && !value.startsWith('CP')) {
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
} else {
cpErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// Prevent leaving CP code field if invalid
cpCodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('CP')) {
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
// Return focus to this field
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if CP code is invalid
cpCodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
this.select();
}
});
// Prevent focusing on CP code if operator code is invalid
cpCodeInput.addEventListener('focus', function(e) {
if (operatorCodeInput.value.length > 0 && !operatorCodeInput.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
operatorCodeInput.focus();
operatorCodeInput.select();
}
});
// Prevent focusing on OC1 code if CP code is invalid
oc1CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
});
// Validate OC1 code on input
oc1CodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value; // Convert to uppercase
if (value.length >= 2 && !value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
} else {
oc1ErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// Prevent leaving OC1 code field if invalid
oc1CodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
// Return focus to this field
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if OC1 code is invalid
oc1CodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
this.select();
}
});
// Prevent focusing on OC2 code if CP code is invalid
oc2CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
// Also check if OC1 is invalid
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
});
// Validate OC2 code on input
oc2CodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value; // Convert to uppercase
if (value.length >= 2 && !value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
} else {
oc2ErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// Prevent leaving OC2 code field if invalid
oc2CodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
// Return focus to this field
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if OC2 code is invalid
oc2CodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
this.select();
}
});
// Prevent focusing on defect code if CP code is invalid
defectCodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
// Also check if OC1 is invalid
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
// Also check if OC2 is invalid
if (oc2CodeInput.value.length > 0 && !oc2CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
oc2CodeInput.focus();
oc2CodeInput.select();
}
});
// Validate defect code on input - only allow digits
defectCodeInput.addEventListener('input', function() {
// Remove any non-digit characters
this.value = this.value.replace(/\D/g, '');
// Validate if it's a valid 3-digit number when length is 3
if (this.value.length === 3) {
const isValid = /^\d{3}$/.test(this.value);
if (!isValid) {
defectErrorMessage.classList.add('show');
this.setCustomValidity('Must be a 3-digit number');
} else {
defectErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
} else {
defectErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
// Auto-submit when 3 characters are entered and all validations pass
if (this.value.length === 3) {
// Validate operator code before submitting
if (!operatorCodeInput.value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
operatorCodeInput.focus();
operatorCodeInput.setCustomValidity('Must start with OP');
return;
}
// Validate CP code before submitting
if (!cpCodeInput.value.startsWith('CP')) {
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.setCustomValidity('Must start with CP');
return;
}
// Validate OC1 code before submitting
if (!oc1CodeInput.value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.setCustomValidity('Must start with OC');
return;
}
// Validate OC2 code before submitting
if (!oc2CodeInput.value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
oc2CodeInput.focus();
oc2CodeInput.setCustomValidity('Must start with OC');
return;
}
// Validate defect code is a valid 3-digit number
const isValidDefectCode = /^\d{3}$/.test(this.value);
if (!isValidDefectCode) {
defectErrorMessage.classList.add('show');
this.focus();
this.setCustomValidity('Must be a 3-digit number');
return;
}
// Update time field before submitting
const timeInput = document.getElementById('time');
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
timeInput.value = `${hours}:${minutes}:${seconds}`;
// Submit the form
form.submit();
}
});
// Validate form on submit
form.addEventListener('submit', function(e) {
let hasError = false;
if (!operatorCodeInput.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
operatorCodeInput.setCustomValidity('Must start with OP');
if (!hasError) {
operatorCodeInput.focus();
hasError = true;
}
}
if (!cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.setCustomValidity('Must start with CP');
if (!hasError) {
cpCodeInput.focus();
hasError = true;
}
}
if (!oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.setCustomValidity('Must start with OC');
if (!hasError) {
oc1CodeInput.focus();
hasError = true;
}
}
if (!oc2CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
oc2CodeInput.setCustomValidity('Must start with OC');
if (!hasError) {
oc2CodeInput.focus();
hasError = true;
}
}
// Validate defect code is a 3-digit number
const isValidDefectCode = /^\d{3}$/.test(defectCodeInput.value);
if (!isValidDefectCode) {
e.preventDefault();
defectErrorMessage.classList.add('show');
defectCodeInput.setCustomValidity('Must be a 3-digit number');
if (!hasError) {
defectCodeInput.focus();
hasError = true;
}
}
});
});
</script>
{% endblock %}
{% block content %}
<div class="scan-container">
<!-- Input Form Card -->
<div class="card scan-form-card">
<h3>Scan Input</h3>
<form method="POST" class="form-centered" id="fg-scan-form">
<label for="operator_code">Quality Operator Code:</label>
<input type="text" id="operator_code" name="operator_code" maxlength="4" required>
<label for="cp_code">CP Code:</label>
<input type="text" id="cp_code" name="cp_code" maxlength="15" required>
<label for="oc1_code">OC1 Code:</label>
<input type="text" id="oc1_code" name="oc1_code" maxlength="4" required>
<label for="oc2_code">OC2 Code:</label>
<input type="text" id="oc2_code" name="oc2_code" maxlength="4" required>
<label for="defect_code">Defect Code:</label>
<input type="text" id="defect_code" name="defect_code" maxlength="4" required>
<label for="date">Date:</label>
<input type="text" id="date" name="date" value="{{ now().strftime('%Y-%m-%d') }}" placeholder="yyyy-mm-dd" pattern="\d{4}-\d{2}-\d{2}" required>
<label for="time">Time:</label>
<input type="text" id="time" name="time" value="{{ now().strftime('%H:%M:%S') }}" readonly>
<button type="submit" class="btn">Submit</button>
</form>
</div>
<!-- Latest Scans Card -->
<div class="card scan-table-card">
<h3>Latest Scans</h3>
<table class="scan-table">
<thead>
<tr>
<th>ID</th>
<th>Op Code</th>
<th>CP Code</th>
<th>OC1 Code</th>
<th>OC2 Code</th>
<th>Defect Code</th>
<th>Date</th>
<th>Time</th>
<th>Apr. Quantity</th>
<th>Rejec. Quantity</th>
</tr>
</thead>
<tbody>
{% for row in scan_data %}
<tr>
<td>{{ row[0] }}</td> <!-- Id -->
<td>{{ row[1] }}</td> <!-- operator_code -->
<td>{{ row[2] }}</td> <!-- CP_full_code -->
<td>{{ row[3] }}</td> <!-- OC1_code -->
<td>{{ row[4] }}</td> <!-- OC2_code -->
<td>{{ row[5] }}</td> <!-- quality_code -->
<td>{{ row[6] }}</td> <!-- date -->
<td>{{ row[7] }}</td> <!-- time -->
<td>{{ row[8] }}</td> <!-- approved quantity -->
<td>{{ row[9] }}</td> <!-- rejected quantity -->
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
+4
View File
@@ -2,6 +2,10 @@
{% block title %}Login{% endblock %}
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
{% endblock %}
{% block content %}
<div class="login-page">
<div class="logo-container">
+25
View File
@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}Main Scanning Module{% endblock %}
{% block content %}
<div class="etichete-container">
<h1>Main Scanning Module</h1>
<p>This is the main page for accessing scanning features.</p>
<div class="dashboard-container">
<!-- Card 1: Finish goods scanning -->
<div class="dashboard-card">
<h3>Finish goods scanning</h3>
<p>Access the scanning module for production orders.</p>
<a href="{{ url_for('main.fg_scan') }}" class="btn">Finish goods scan</a>
</div>
<!-- Card 2: View Scan History -->
<div class="dashboard-card">
<h3>View Scan History</h3>
<p>Review previous scan records and results.</p>
<a href="#" class="btn" disabled>Coming Soon</a>
</div>
</div>
</div>
{% endblock %}
+397
View File
@@ -1,5 +1,402 @@
{% extends "base.html" %}
{% block title %}Scan Module{% endblock %}
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/scan.css') }}">
<style>
.error-message {
color: #ff4444;
font-size: 0.9em;
margin-top: 5px;
display: none;
}
.error-message.show {
display: block;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const operatorCodeInput = document.getElementById('operator_code');
const cpCodeInput = document.getElementById('cp_code');
const oc1CodeInput = document.getElementById('oc1_code');
const oc2CodeInput = document.getElementById('oc2_code');
const defectCodeInput = document.getElementById('defect_code');
const form = document.querySelector('.form-centered');
// Create error message element for operator code
const operatorErrorMessage = document.createElement('div');
operatorErrorMessage.className = 'error-message';
operatorErrorMessage.id = 'operator-error';
operatorErrorMessage.textContent = 'Please scan Quality Operator code (must start with OP)';
operatorCodeInput.parentNode.insertBefore(operatorErrorMessage, operatorCodeInput.nextSibling);
// Create error message element for CP code
const cpErrorMessage = document.createElement('div');
cpErrorMessage.className = 'error-message';
cpErrorMessage.id = 'cp-error';
cpErrorMessage.textContent = 'Please scan a valid CP';
cpCodeInput.parentNode.insertBefore(cpErrorMessage, cpCodeInput.nextSibling);
// Create error message element for OC1 code
const oc1ErrorMessage = document.createElement('div');
oc1ErrorMessage.className = 'error-message';
oc1ErrorMessage.id = 'oc1-error';
oc1ErrorMessage.textContent = 'Please scan a valid OC (must start with OC)';
oc1CodeInput.parentNode.insertBefore(oc1ErrorMessage, oc1CodeInput.nextSibling);
// Create error message element for OC2 code
const oc2ErrorMessage = document.createElement('div');
oc2ErrorMessage.className = 'error-message';
oc2ErrorMessage.id = 'oc2-error';
oc2ErrorMessage.textContent = 'Please scan a valid OC (must start with OC)';
oc2CodeInput.parentNode.insertBefore(oc2ErrorMessage, oc2CodeInput.nextSibling);
// Create error message element for defect code
const defectErrorMessage = document.createElement('div');
defectErrorMessage.className = 'error-message';
defectErrorMessage.id = 'defect-error';
defectErrorMessage.textContent = 'Defect code must be a 3-digit number (e.g., 000, 001, 123)';
defectCodeInput.parentNode.insertBefore(defectErrorMessage, defectCodeInput.nextSibling);
// Validate operator code on input
operatorCodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value;
if (value.length >= 2 && !value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
} else {
operatorErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// Prevent leaving operator code field if invalid
operatorCodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if operator code is invalid
operatorCodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OP');
this.select();
}
});
// Validate CP code on input
cpCodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value;
if (value.length >= 2 && !value.startsWith('CP')) {
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
} else {
cpErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// Prevent leaving CP code field if invalid
cpCodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('CP')) {
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if CP code is invalid
cpCodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
this.setCustomValidity('Must start with CP');
this.select();
}
});
// Prevent focusing on CP code if operator code is invalid
cpCodeInput.addEventListener('focus', function(e) {
if (operatorCodeInput.value.length > 0 && !operatorCodeInput.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
operatorCodeInput.focus();
operatorCodeInput.select();
}
});
// Prevent focusing on OC1 code if CP code is invalid
oc1CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
});
// Validate OC1 code on input
oc1CodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value;
if (value.length >= 2 && !value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
} else {
oc1ErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// Prevent leaving OC1 code field if invalid
oc1CodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if OC1 code is invalid
oc1CodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
this.select();
}
});
// Prevent focusing on OC2 code if CP code is invalid
oc2CodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
// Also check if OC1 is invalid
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
});
// Validate OC2 code on input
oc2CodeInput.addEventListener('input', function() {
const value = this.value.toUpperCase();
this.value = value;
if (value.length >= 2 && !value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
} else {
oc2ErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
});
// Prevent leaving OC2 code field if invalid
oc2CodeInput.addEventListener('blur', function(e) {
if (this.value.length > 0 && !this.value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
setTimeout(() => {
this.focus();
this.select();
}, 0);
}
});
// Prevent Tab/Enter from moving to next field if OC2 code is invalid
oc2CodeInput.addEventListener('keydown', function(e) {
if ((e.key === 'Tab' || e.key === 'Enter') && this.value.length > 0 && !this.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
this.setCustomValidity('Must start with OC');
this.select();
}
});
// Prevent focusing on defect code if CP code is invalid
defectCodeInput.addEventListener('focus', function(e) {
if (cpCodeInput.value.length > 0 && !cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.select();
}
// Also check if OC1 is invalid
if (oc1CodeInput.value.length > 0 && !oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.select();
}
// Also check if OC2 is invalid
if (oc2CodeInput.value.length > 0 && !oc2CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
oc2CodeInput.focus();
oc2CodeInput.select();
}
});
// Validate defect code on input - only allow digits
defectCodeInput.addEventListener('input', function() {
// Remove any non-digit characters
this.value = this.value.replace(/\D/g, '');
// Validate if it's a valid 3-digit number when length is 3
if (this.value.length === 3) {
const isValid = /^\d{3}$/.test(this.value);
if (!isValid) {
defectErrorMessage.classList.add('show');
this.setCustomValidity('Must be a 3-digit number');
} else {
defectErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
} else {
defectErrorMessage.classList.remove('show');
this.setCustomValidity('');
}
// Auto-submit when 3 characters are entered and all validations pass
if (this.value.length === 3) {
// Validate operator code before submitting
if (!operatorCodeInput.value.startsWith('OP')) {
operatorErrorMessage.classList.add('show');
operatorCodeInput.focus();
operatorCodeInput.setCustomValidity('Must start with OP');
return;
}
// Validate CP code before submitting
if (!cpCodeInput.value.startsWith('CP')) {
cpErrorMessage.classList.add('show');
cpCodeInput.focus();
cpCodeInput.setCustomValidity('Must start with CP');
return;
}
// Validate OC1 code before submitting
if (!oc1CodeInput.value.startsWith('OC')) {
oc1ErrorMessage.classList.add('show');
oc1CodeInput.focus();
oc1CodeInput.setCustomValidity('Must start with OC');
return;
}
// Validate OC2 code before submitting
if (!oc2CodeInput.value.startsWith('OC')) {
oc2ErrorMessage.classList.add('show');
oc2CodeInput.focus();
oc2CodeInput.setCustomValidity('Must start with OC');
return;
}
// Validate defect code is a valid 3-digit number
const isValidDefectCode = /^\d{3}$/.test(this.value);
if (!isValidDefectCode) {
defectErrorMessage.classList.add('show');
this.focus();
this.setCustomValidity('Must be a 3-digit number');
return;
}
// Update time field before submitting
const timeInput = document.getElementById('time');
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
timeInput.value = `${hours}:${minutes}:${seconds}`;
// Submit the form
form.submit();
}
});
// Validate form on submit
form.addEventListener('submit', function(e) {
let hasError = false;
if (!operatorCodeInput.value.startsWith('OP')) {
e.preventDefault();
operatorErrorMessage.classList.add('show');
operatorCodeInput.setCustomValidity('Must start with OP');
if (!hasError) {
operatorCodeInput.focus();
hasError = true;
}
}
if (!cpCodeInput.value.startsWith('CP')) {
e.preventDefault();
cpErrorMessage.classList.add('show');
cpCodeInput.setCustomValidity('Must start with CP');
if (!hasError) {
cpCodeInput.focus();
hasError = true;
}
}
if (!oc1CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc1ErrorMessage.classList.add('show');
oc1CodeInput.setCustomValidity('Must start with OC');
if (!hasError) {
oc1CodeInput.focus();
hasError = true;
}
}
if (!oc2CodeInput.value.startsWith('OC')) {
e.preventDefault();
oc2ErrorMessage.classList.add('show');
oc2CodeInput.setCustomValidity('Must start with OC');
if (!hasError) {
oc2CodeInput.focus();
hasError = true;
}
}
// Validate defect code is a 3-digit number
const isValidDefectCode = /^\d{3}$/.test(defectCodeInput.value);
if (!isValidDefectCode) {
e.preventDefault();
defectErrorMessage.classList.add('show');
defectCodeInput.setCustomValidity('Must be a 3-digit number');
if (!hasError) {
defectCodeInput.focus();
hasError = true;
}
}
});
});
</script>
{% endblock %}
{% block content %}
<div class="scan-container">
<!-- Input Form Card -->
+56
View File
@@ -218,3 +218,59 @@ def generate_location_label_pdf():
except Exception as e:
print(f"Error generating location label PDF: {e}")
return jsonify({'error': str(e)}), 500
def update_location(location_id, location_code, size, description):
"""Update an existing warehouse location"""
try:
conn = get_db_connection()
cursor = conn.cursor()
# Check if location exists
cursor.execute("SELECT id FROM warehouse_locations WHERE id = ?", (location_id,))
if not cursor.fetchone():
conn.close()
return {"success": False, "error": "Location not found"}
# Check if location code already exists for different location
cursor.execute("SELECT id FROM warehouse_locations WHERE location_code = ? AND id != ?", (location_code, location_id))
if cursor.fetchone():
conn.close()
return {"success": False, "error": "Location code already exists"}
# Update location
cursor.execute(
"UPDATE warehouse_locations SET location_code = ?, size = ?, description = ? WHERE id = ?",
(location_code, size if size else None, description, location_id)
)
conn.commit()
conn.close()
return {"success": True, "message": "Location updated successfully"}
except Exception as e:
print(f"Error updating location: {e}")
return {"success": False, "error": str(e)}
def delete_location_by_id(location_id):
"""Delete a warehouse location by ID"""
try:
conn = get_db_connection()
cursor = conn.cursor()
# Check if location exists
cursor.execute("SELECT location_code FROM warehouse_locations WHERE id = ?", (location_id,))
location = cursor.fetchone()
if not location:
conn.close()
return {"success": False, "error": "Location not found"}
# Delete location
cursor.execute("DELETE FROM warehouse_locations WHERE id = ?", (location_id,))
conn.commit()
conn.close()
return {"success": True, "message": f"Location '{location[0]}' deleted successfully"}
except Exception as e:
print(f"Error deleting location: {e}")
return {"success": False, "error": str(e)}
+3 -3
View File
@@ -1,5 +1,5 @@
server_domain=localhost
port=3602
database_name=recticel
username=sa
password=12345678
database_name=trasabilitate_database
username=trasabilitate
password=Initial01!
+7
View File
@@ -14,3 +14,10 @@ sudo apt-get install libmariadb-dev libmariadb-dev-compat
GRANT ALL PRIVILEGES ON trasabilitate_database.* TO 'trasabilitate'@'localhost';
FLUSH PRIVILEGES;
EXIT
Server Domain/IP Address: testserver.com
Port: 3602
Database Name: recticel
Username: sa
Password: 12345678