diff --git a/app.py b/app.py index a951365..be4b0ab 100644 --- a/app.py +++ b/app.py @@ -9,6 +9,7 @@ import io, os, base64, re, uuid, requests, json, qrcode, math from dotenv import load_dotenv # Import the logging handler from logger_handler import AppLogger, log_user_activity, log_database_operations +from PIL import Image, ImageDraw # Load environment variables in .env load_dotenv() @@ -109,6 +110,16 @@ class QRCode(db.Model): project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=True) # The project_id field: project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=True) + # QR Code Customization fields + fill_color = db.Column(db.String(7), default="#000000") # Hex color + back_color = db.Column(db.String(7), default="#FFFFFF") # Background color + box_size = db.Column(db.Integer, default=10) + border = db.Column(db.Integer, default=4) + error_correction = db.Column(db.String(1), default='L') + style_id = db.Column(db.Integer, db.ForeignKey('qr_code_styles.id'), nullable=True) + + # Relationship to style + style = db.relationship('QRCodeStyle', backref='qr_codes') @property def has_coordinates(self): @@ -129,6 +140,24 @@ class QRCode(db.Model): self.coordinate_accuracy = accuracy self.coordinates_updated_date = datetime.utcnow() +class QRCodeStyle(db.Model): + """QR Code customization styles""" + __tablename__ = 'qr_code_styles' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) # Style name + fill_color = db.Column(db.String(7), default="#000000") # Hex color for QR modules + back_color = db.Column(db.String(7), default="#FFFFFF") # Hex color for background + box_size = db.Column(db.Integer, default=10) # Size of each QR module + border = db.Column(db.Integer, default=4) # Border size + error_correction = db.Column(db.String(1), default='L') # L, M, Q, H + is_default = db.Column(db.Boolean, default=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + created_by = db.Column(db.Integer, db.ForeignKey('users.id')) + + def __repr__(self): + return f'' + class Project(db.Model): """ Project model to organize QR codes by projects @@ -1131,8 +1160,55 @@ def is_admin_user(user_id): return False # Utility function to generate QR code -def generate_qr_code(data): - """Generate QR code image and return as base64 string""" +def generate_qr_code(data, fill_color="black", back_color="white", box_size=10, border=4, error_correction='L'): + # Error correction mapping + error_correction_map = { + 'L': qrcode.constants.ERROR_CORRECT_L, + 'M': qrcode.constants.ERROR_CORRECT_M, + 'Q': qrcode.constants.ERROR_CORRECT_Q, + 'H': qrcode.constants.ERROR_CORRECT_H + } + + try: + qr = qrcode.QRCode( + version=1, + error_correction=error_correction_map.get(error_correction, qrcode.constants.ERROR_CORRECT_L), + box_size=int(box_size), + border=int(border), + ) + qr.add_data(data) + qr.make(fit=True) + + # Generate QR code image with custom colors + img = qr.make_image(fill_color=fill_color, back_color=back_color) + + # Convert to base64 + buffer = io.BytesIO() + img.save(buffer, format='PNG') + img_str = base64.b64encode(buffer.getvalue()).decode() + + # Log successful generation if logger is available + try: + logger_handler.log_qr_code_generated( + data_length=len(data), + fill_color=fill_color, + back_color=back_color, + box_size=box_size, + border=border, + error_correction=error_correction + ) + except: + pass # Ignore logging errors + + return img_str + + except Exception as e: + logger_handler.log_database_error('qr_code_generation', e) + # Return default QR code on error + return generate_default_qr_code(data) + +def generate_default_qr_code(data): + """Fallback function for basic QR code generation""" qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, @@ -1144,13 +1220,22 @@ def generate_qr_code(data): img = qr.make_image(fill_color="black", back_color="white") - # Convert to base64 buffer = io.BytesIO() img.save(buffer, format='PNG') img_str = base64.b64encode(buffer.getvalue()).decode() return img_str +def get_qr_styling(qr_code): + """Extract QR code styling parameters from database record""" + return { + 'fill_color': getattr(qr_code, 'fill_color', '#000000') or '#000000', + 'back_color': getattr(qr_code, 'back_color', '#FFFFFF') or '#FFFFFF', + 'box_size': getattr(qr_code, 'box_size', 10) or 10, + 'border': getattr(qr_code, 'border', 4) or 4, + 'error_correction': getattr(qr_code, 'error_correction', 'L') or 'L' + } + @app.template_filter('strftime') def strftime_filter(value, format='%m/%d/%Y'): """Format datetime/date/string as strftime""" @@ -2648,9 +2733,10 @@ def api_active_projects(): @login_required @log_database_operations('qr_code_creation') def create_qr_code(): - """Enhanced create QR code with readable URL format and coordinates saving""" + """Enhanced create QR code with customization options""" if request.method == 'POST': try: + # Existing form data name = request.form['name'] location = request.form['location'] location_address = request.form['location_address'] @@ -2662,6 +2748,20 @@ def create_qr_code(): longitude = request.form.get('longitude') coordinate_accuracy = request.form.get('coordinate_accuracy', 'geocoded') + # NEW: QR Code customization data + fill_color = request.form.get('fill_color', '#000000') + back_color = request.form.get('back_color', '#FFFFFF') + box_size = int(request.form.get('box_size', 10)) + border = int(request.form.get('border', 4)) + error_correction = request.form.get('error_correction', 'L') + style_id = request.form.get('style_id') # Pre-defined style + + # Validate colors (basic hex validation) + if not (fill_color.startswith('#') and len(fill_color) == 7): + fill_color = '#000000' + if not (back_color.startswith('#') and len(back_color) == 7): + back_color = '#FFFFFF' + # Convert coordinates to float if they exist address_latitude = None address_longitude = None @@ -2682,11 +2782,19 @@ def create_qr_code(): # Validate project_id if provided project = None if project_id: - project_id = int(project_id) - project = Project.query.get(project_id) - if not project or not project.active_status: - flash('Selected project is not valid or inactive.', 'error') - return render_template('create_qr_code.html') + try: + project_id = int(project_id) + project = Project.query.get(project_id) + if not project or not project.active_status: + flash('Selected project is not valid or inactive.', 'error') + return render_template('create_qr_code.html', + projects=Project.query.filter_by(active_status=True).all(), + styles=QRCodeStyle.query.all() if 'QRCodeStyle' in globals() else []) + except (ValueError, TypeError): + flash('Invalid project selection.', 'error') + return render_template('create_qr_code.html', + projects=Project.query.filter_by(active_status=True).all(), + styles=QRCodeStyle.query.all() if 'QRCodeStyle' in globals() else []) # Create new QR code record first (without URL and image) new_qr_code = QRCode( @@ -2701,7 +2809,16 @@ def create_qr_code(): address_latitude=address_latitude, address_longitude=address_longitude, coordinate_accuracy=coordinate_accuracy if has_coordinates else None, - coordinates_updated_date=datetime.utcnow() if has_coordinates else None + coordinates_updated_date=datetime.utcnow() if has_coordinates else None, + # NEW: Customization fields (only if columns exist) + **({ + 'fill_color': fill_color, + 'back_color': back_color, + 'box_size': box_size, + 'border': border, + 'error_correction': error_correction, + 'style_id': int(style_id) if style_id and style_id.isdigit() else None + } if hasattr(QRCode, 'fill_color') else {}) ) # Add to session and flush to get the ID @@ -2711,9 +2828,16 @@ def create_qr_code(): # Now generate the readable URL using the ID qr_url = generate_qr_url(name, new_qr_code.id) - # Generate QR code data with the destination URL + # Generate QR code data with the destination URL and custom styling qr_data = f"{request.url_root}qr/{qr_url}" - qr_image = generate_qr_code(qr_data) + qr_image = generate_qr_code( + data=qr_data, + fill_color=fill_color, + back_color=back_color, + box_size=box_size, + border=border, + error_correction=error_correction + ) # Update the QR code with the URL and image new_qr_code.qr_url = qr_url @@ -2722,41 +2846,32 @@ def create_qr_code(): # Now commit all changes db.session.commit() - # Enhanced logging with project and coordinates information - log_data = { - 'qr_code_id': new_qr_code.id, - 'name': name, - 'location': location, - 'qr_url': qr_url, # Log the readable URL - 'project_id': project_id, - 'project_name': project.name if project else None, - 'has_coordinates': has_coordinates, - 'latitude': address_latitude, - 'longitude': address_longitude, - 'coordinate_accuracy': coordinate_accuracy - } - qr_data_for_logging = { - 'location': location, - 'location_address': location_address, - 'location_event': location_event, - 'has_coordinates': has_coordinates, - 'latitude': address_latitude, - 'longitude': address_longitude, - 'coordinate_accuracy': coordinate_accuracy - } - + # Enhanced logging with customization information logger_handler.log_qr_code_created( qr_code_id=new_qr_code.id, qr_code_name=name, created_by_user_id=session['user_id'], - qr_data=qr_data_for_logging + qr_data={ + 'location': location, + 'location_address': location_address, + 'location_event': location_event, + 'has_coordinates': has_coordinates, + 'customization': { + 'fill_color': fill_color, + 'back_color': back_color, + 'box_size': box_size, + 'border': border, + 'error_correction': error_correction + } + } ) - # Success message with coordinates info + # Success message with customization info project_info = f" in project '{project.name}'" if project else "" coord_info = f" with coordinates ({new_qr_code.coordinates_display})" if has_coordinates else "" + style_info = f" with custom styling (Fill: {fill_color}, Background: {back_color})" - flash(f'QR Code "{name}" created successfully{project_info}{coord_info}! URL: {qr_url}', 'success') + flash(f'QR Code "{name}" created successfully{project_info}{coord_info}{style_info}! URL: {qr_url}', 'success') return redirect(url_for('dashboard')) except Exception as e: @@ -2765,15 +2880,17 @@ def create_qr_code(): flash('QR Code creation failed. Please try again.', 'error') print(f"❌ QR Code creation error: {e}") - # Get active projects for dropdown + # Get active projects and styles for dropdown projects = Project.query.filter_by(active_status=True).order_by(Project.name.asc()).all() - return render_template('create_qr_code.html', projects=projects) + styles = QRCodeStyle.query.order_by(QRCodeStyle.name.asc()).all() if 'QRCodeStyle' in globals() else [] + + return render_template('create_qr_code.html', projects=projects, styles=styles) @app.route('/qr-codes//edit', methods=['GET', 'POST']) @login_required @log_database_operations('qr_code_edit') def edit_qr_code(qr_id): - """Enhanced edit QR code with project association and URL regeneration - COORDINATE FIX""" + """Enhanced edit QR code with customization support""" try: qr_code = QRCode.query.get_or_404(qr_id) @@ -2788,7 +2905,10 @@ def edit_qr_code(qr_id): 'qr_url': qr_code.qr_url, 'address_latitude': qr_code.address_latitude, 'address_longitude': qr_code.address_longitude, - 'coordinate_accuracy': qr_code.coordinate_accuracy + 'coordinate_accuracy': qr_code.coordinate_accuracy, + # Track old styling + 'fill_color': getattr(qr_code, 'fill_color', '#000000'), + 'back_color': getattr(qr_code, 'back_color', '#FFFFFF') } # Update QR code fields @@ -2798,12 +2918,11 @@ def edit_qr_code(qr_id): qr_code.location_address = request.form['location_address'] qr_code.location_event = request.form.get('location_event', '') - # SIMPLIFIED COORDINATE HANDLING - Remove validation requirement - latitude = request.form.get('address_latitude') # Changed from 'latitude' to match form - longitude = request.form.get('address_longitude') # Changed from 'longitude' to match form + # Handle coordinates + latitude = request.form.get('address_latitude') + longitude = request.form.get('address_longitude') coordinate_accuracy = request.form.get('coordinate_accuracy', 'geocoded') - # Update coordinates if provided, or keep existing ones if latitude and longitude: try: qr_code.address_latitude = float(latitude) @@ -2811,19 +2930,16 @@ def edit_qr_code(qr_id): qr_code.coordinate_accuracy = coordinate_accuracy qr_code.coordinates_updated_date = datetime.utcnow() except (ValueError, TypeError): - # If invalid coordinates, keep existing ones pass elif latitude == '' and longitude == '': - # Explicitly clear coordinates if empty strings are sent qr_code.address_latitude = None qr_code.address_longitude = None qr_code.coordinate_accuracy = None qr_code.coordinates_updated_date = None - # If no coordinate data is provided, keep existing coordinates unchanged - # Handle project association - FIXED LOGIC + # Handle project association new_project_id = request.form.get('project_id') - if new_project_id and new_project_id.strip(): # Check for non-empty string + if new_project_id and new_project_id.strip(): try: new_project_id = int(new_project_id) project = Project.query.get(new_project_id) @@ -2831,104 +2947,70 @@ def edit_qr_code(qr_id): qr_code.project_id = new_project_id else: flash('Selected project is not valid or inactive.', 'error') - return render_template('edit_qr_code.html', qr_code=qr_code, projects=Project.query.filter_by(active_status=True).all()) + return render_template('edit_qr_code.html', qr_code=qr_code, + projects=Project.query.filter_by(active_status=True).all(), + styles=QRCodeStyle.query.all() if 'QRCodeStyle' in globals() else []) except (ValueError, TypeError): - # Invalid project_id format flash('Invalid project selection.', 'error') - return render_template('edit_qr_code.html', qr_code=qr_code, projects=Project.query.filter_by(active_status=True).all()) + return render_template('edit_qr_code.html', qr_code=qr_code, + projects=Project.query.filter_by(active_status=True).all(), + styles=QRCodeStyle.query.all() if 'QRCodeStyle' in globals() else []) else: - # Empty or None project_id means unassign from project qr_code.project_id = None - # Regenerate URL if name changed + # Handle QR code customization (only if columns exist) + fill_color = request.form.get('fill_color', '#000000') + back_color = request.form.get('back_color', '#FFFFFF') + box_size = int(request.form.get('box_size', 10)) + border = int(request.form.get('border', 4)) + error_correction = request.form.get('error_correction', 'L') + style_id = request.form.get('style_id') + + # Update styling fields if they exist + if hasattr(qr_code, 'fill_color'): + qr_code.fill_color = fill_color + qr_code.back_color = back_color + qr_code.box_size = box_size + qr_code.border = border + qr_code.error_correction = error_correction + qr_code.style_id = int(style_id) if style_id and style_id.isdigit() else None + + # Check if QR code needs regeneration name_changed = old_data['name'] != new_name + styling_changed = (hasattr(qr_code, 'fill_color') and + (old_data['fill_color'] != fill_color or + old_data['back_color'] != back_color)) + if name_changed: new_qr_url = generate_qr_url(new_name, qr_code.id) - - # Regenerate QR code image with new URL - qr_data = f"{request.url_root}qr/{new_qr_url}" - new_qr_image = generate_qr_code(qr_data) - qr_code.qr_url = new_qr_url - qr_code.qr_code_image = new_qr_image - # ENHANCED CHANGE DETECTION - project_changed = old_data['project_id'] != qr_code.project_id - coordinates_changed = (old_data['address_latitude'] != qr_code.address_latitude or - old_data['address_longitude'] != qr_code.address_longitude) + # Regenerate QR code if name or styling changed + if name_changed or styling_changed: + qr_data = f"{request.url_root}qr/{qr_code.qr_url}" + + # Use new styling if available, otherwise use defaults + styling = get_qr_styling(qr_code) + qr_code.qr_code_image = generate_qr_code( + data=qr_data, + fill_color=styling['fill_color'], + back_color=styling['back_color'], + box_size=styling['box_size'], + border=styling['border'], + error_correction=styling['error_correction'] + ) db.session.commit() - # Log changes with improved tracking - new_data = { - 'name': qr_code.name, - 'location': qr_code.location, - 'location_address': qr_code.location_address, - 'location_event': qr_code.location_event, - 'project_id': qr_code.project_id, - 'qr_url': qr_code.qr_url, - 'address_latitude': qr_code.address_latitude, - 'address_longitude': qr_code.address_longitude, - 'coordinate_accuracy': qr_code.coordinate_accuracy - } - - changes = {} - for key in old_data: - if old_data[key] != new_data[key]: - changes[key] = {'old': old_data[key], 'new': new_data[key]} - - # Enhanced logging for all changes - if changes: - logger_handler.logger.info(f"User {session['username']} updated QR code {qr_id}: {json.dumps(changes)}") - - # Log specific change actions - if project_changed: - old_project_name = None - new_project_name = None - - if old_data['project_id']: - old_project = Project.query.get(old_data['project_id']) - old_project_name = old_project.name if old_project else f"Project ID {old_data['project_id']}" - - if qr_code.project_id: - new_project = Project.query.get(qr_code.project_id) - new_project_name = new_project.name if new_project else f"Project ID {qr_code.project_id}" - - project_change_msg = f"QR code '{qr_code.name}' project changed from '{old_project_name or 'No Project'}' to '{new_project_name or 'No Project'}'" - logger_handler.logger.info(f"User {session['username']} - {project_change_msg}") - - if coordinates_changed: - coord_change_msg = f"QR code '{qr_code.name}' coordinates updated" - if qr_code.has_coordinates: - coord_change_msg += f" to {qr_code.coordinates_display}" - else: - coord_change_msg += " (coordinates cleared)" - logger_handler.logger.info(f"User {session['username']} - {coord_change_msg}") - - # Success message with detailed information - url_message = f" URL updated to: {qr_code.qr_url}" if name_changed else "" - project_message = "" - coord_message = "" - - if project_changed: - if qr_code.project_id: - current_project = Project.query.get(qr_code.project_id) - project_message = f" Project updated to: {current_project.name if current_project else 'Unknown'}" - else: - project_message = " Removed from project" - - if coordinates_changed: - if qr_code.has_coordinates: - coord_message = f" Coordinates updated: {qr_code.coordinates_display}" - else: - coord_message = " Coordinates cleared" - - flash(f'QR Code "{qr_code.name}" updated successfully!{url_message}{project_message}{coord_message}', 'success') + # Success message + flash(f'QR Code "{qr_code.name}" updated successfully!', 'success') return redirect(url_for('dashboard')) - # Get active projects for dropdown + # GET request - render edit form projects = Project.query.filter_by(active_status=True).order_by(Project.name.asc()).all() - return render_template('edit_qr_code.html', qr_code=qr_code, projects=projects) + styles = QRCodeStyle.query.order_by(QRCodeStyle.name.asc()).all() if 'QRCodeStyle' in globals() else [] + + return render_template('edit_qr_code.html', qr_code=qr_code, projects=projects, styles=styles) except Exception as e: db.session.rollback() @@ -4508,7 +4590,17 @@ def update_existing_qr_codes(): # Generate QR image if missing if not qr_code.qr_code_image: qr_data = f"{request.url_root}qr/{qr_code.qr_url}" - qr_code.qr_code_image = generate_qr_code(qr_data) + + # Use styling from database if available, otherwise defaults + styling = get_qr_styling(qr_code) + qr_code.qr_code_image = generate_qr_code( + data=qr_data, + fill_color=styling['fill_color'], + back_color=styling['back_color'], + box_size=styling['box_size'], + border=styling['border'], + error_correction=styling['error_correction'] + ) updated_count += 1 @@ -4524,8 +4616,50 @@ def update_existing_qr_codes(): logger_handler.logger.info(f"Updated {updated_count} existing QR codes with missing URLs/images") except Exception as e: - db.session.rollback() logger_handler.log_database_error('update_existing_qr_codes', e) + print(f"Error updating existing QR codes: {e}") + +def add_qr_customization_columns(): + """Add QR code customization columns to existing table""" + try: + with app.app_context(): + # Add columns to QRCode table if they don't exist + db.engine.execute(""" + ALTER TABLE qr_codes + ADD COLUMN IF NOT EXISTS fill_color VARCHAR(7) DEFAULT '#000000', + ADD COLUMN IF NOT EXISTS back_color VARCHAR(7) DEFAULT '#FFFFFF', + ADD COLUMN IF NOT EXISTS box_size INT DEFAULT 10, + ADD COLUMN IF NOT EXISTS border INT DEFAULT 4, + ADD COLUMN IF NOT EXISTS error_correction VARCHAR(1) DEFAULT 'L', + ADD COLUMN IF NOT EXISTS style_id INT, + ADD FOREIGN KEY (style_id) REFERENCES qr_code_styles(id) + """) + + # Create QRCodeStyle table + db.create_all() + + # Insert default styles + default_styles = [ + QRCodeStyle(name="Classic Black", fill_color="#000000", back_color="#FFFFFF", is_default=True), + QRCodeStyle(name="Blue Professional", fill_color="#2563eb", back_color="#FFFFFF"), + QRCodeStyle(name="Green Success", fill_color="#10b981", back_color="#FFFFFF"), + QRCodeStyle(name="Red Alert", fill_color="#ef4444", back_color="#FFFFFF"), + QRCodeStyle(name="Dark Mode", fill_color="#FFFFFF", back_color="#1f2937"), + QRCodeStyle(name="High Contrast", fill_color="#000000", back_color="#FFFF00"), + QRCodeStyle(name="Corporate Blue", fill_color="#1e40af", back_color="#f8fafc"), + QRCodeStyle(name="Minimalist Gray", fill_color="#6b7280", back_color="#FFFFFF") + ] + + for style in default_styles: + if not QRCodeStyle.query.filter_by(name=style.name).first(): + db.session.add(style) + + db.session.commit() + print("QR Code customization tables and default styles created successfully!") + + except Exception as e: + db.session.rollback() + print(f"Error adding QR customization columns: {e}") def add_coordinate_columns(): """Add coordinate columns to existing qr_codes table (MySQL compatible)""" @@ -4639,7 +4773,8 @@ if __name__ == '__main__': # Initialize database and logging create_tables() update_existing_qr_codes() - + add_qr_customization_columns() + # Log application startup logger_handler.logger.info("QR Attendance Management System started successfully") diff --git a/logger_handler.py b/logger_handler.py index d363973..142a49e 100644 --- a/logger_handler.py +++ b/logger_handler.py @@ -472,6 +472,17 @@ class AppLogger: 'event': 'qr_code_accessed', 'data': event_data })) + + def log_qr_code_generated(self, data_length, fill_color, back_color, box_size, border, error_correction): + """Log QR code generation with customization details""" + try: + self.logger.info(f"QR code generated with customization - " + f"Data length: {data_length}, " + f"Fill: {fill_color}, Background: {back_color}, " + f"Box size: {box_size}, Border: {border}, " + f"Error correction: {error_correction}") + except Exception as e: + self.logger.error(f"Failed to log QR code generation: {e}") # DATABASE ERROR LOGGING METHODS diff --git a/templates/create_qr_code.html b/templates/create_qr_code.html index 2dbc621..da272ba 100644 --- a/templates/create_qr_code.html +++ b/templates/create_qr_code.html @@ -178,6 +178,157 @@ color: var(--warning-color); } + /* QR Code Customization Styles */ + .form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + } + + .color-input-group { + display: flex; + gap: 0.5rem; + align-items: center; + } + + .color-input-group input[type="color"] { + width: 50px; + height: 40px; + border: none; + border-radius: var(--radius-lg); + cursor: pointer; + padding: 0; + } + + .color-input-group input[type="text"] { + flex: 1; + font-family: 'Courier New', monospace; + text-transform: uppercase; + } + + .range-input-group { + display: flex; + align-items: center; + gap: 1rem; + } + + .range-input-group input[type="range"] { + flex: 1; + height: 6px; + background: var(--gray-200); + border-radius: 3px; + outline: none; + } + + .range-input-group input[type="range"]::-webkit-slider-thumb { + appearance: none; + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--primary-color); + cursor: pointer; + border: 2px solid white; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + } + + .range-input-group input[type="range"]::-moz-range-thumb { + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--primary-color); + cursor: pointer; + border: 2px solid white; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + } + + .range-value { + font-weight: 600; + color: var(--primary-color); + min-width: 80px; + text-align: center; + background: var(--gray-100); + padding: 0.25rem 0.5rem; + border-radius: var(--radius-lg); + font-size: 0.875rem; + } + + /* QR Preview Styles */ + .qr-preview-section { + margin-top: 2rem; + padding-top: 2rem; + border-top: 1px solid var(--gray-200); + } + + .qr-preview-section h4 { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 1rem; + color: var(--gray-800); + font-size: 1.1rem; + } + + .qr-preview-container { + display: flex; + align-items: center; + gap: 2rem; + background: var(--gray-50); + padding: 1.5rem; + border-radius: var(--radius-xl); + border: 2px dashed var(--gray-300); + } + + .qr-preview { + display: flex; + align-items: center; + justify-content: center; + width: 120px; + height: 120px; + background: var(--white); + border-radius: var(--radius-lg); + box-shadow: var(--shadow); + transition: var(--transition); + } + + .qr-sample { + padding: 16px; + background: #FFFFFF; + border-radius: 4px; + transition: all 0.3s ease; + } + + .qr-pattern { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 1px; + width: 70px; + height: 70px; + } + + .qr-module { + background: #000000; + transition: background-color 0.3s ease; + border-radius: 1px; + } + + .qr-module.empty { + background: transparent; + } + + .preview-info { + flex: 1; + } + + .preview-info p { + margin-bottom: 0.5rem; + color: var(--gray-700); + } + + .preview-text { + color: var(--gray-500); + font-size: 0.875rem; + } + /* Coordinate Display Section */ .coordinates-section { background: var(--primary-color); @@ -185,103 +336,69 @@ border-radius: var(--radius-xl); padding: 1.5rem; margin-top: 1rem; + display: none; } .coordinates-header { display: flex; align-items: center; - gap: 0.75rem; + gap: 0.5rem; margin-bottom: 1rem; } .coordinates-header h4 { - font-size: 1.125rem; + margin: 0; + font-size: 1.1rem; font-weight: 600; } - .coordinates-content { - display: flex; - flex-direction: column; - gap: 1rem; - } - .coordinate-display { - background: rgba(255, 255, 255, 0.1); - padding: 1rem; - border-radius: var(--radius-lg); - display: flex; - align-items: center; - justify-content: space-between; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-bottom: 1rem; } .coordinate-info { - display: flex; - flex-direction: column; - gap: 0.25rem; + text-align: center; } .coordinate-value { - font-family: "Courier New", monospace; - font-size: 1.125rem; + font-size: 1.1rem; font-weight: 600; + font-family: 'Courier New', monospace; + margin-bottom: 0.25rem; } .coordinate-label { font-size: 0.875rem; - opacity: 0.8; + opacity: 0.9; } .coordinates-actions { display: flex; - gap: 0.75rem; - margin-top: 1rem; + gap: 0.5rem; + justify-content: center; } .btn-coordinate { - padding: 0.5rem 1rem; background: rgba(255, 255, 255, 0.2); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: var(--radius-lg); color: white; + border: 1px solid rgba(255, 255, 255, 0.3); + padding: 0.5rem 1rem; + border-radius: var(--radius-lg); font-size: 0.875rem; cursor: pointer; transition: var(--transition); + display: inline-flex; + align-items: center; + gap: 0.5rem; } .btn-coordinate:hover { background: rgba(255, 255, 255, 0.3); } - .btn-coordinate:disabled { - opacity: 0.5; - cursor: not-allowed; - } - - .loading-spinner { - display: inline-block; - width: 1rem; - height: 1rem; - border: 2px solid rgba(255, 255, 255, 0.3); - border-radius: 50%; - border-top-color: white; - animation: spin 1s ease-in-out infinite; - } - - @keyframes spin { - to { - transform: rotate(360deg); - } - } - - .status-message { - padding: 0.75rem; - border-radius: var(--radius-lg); - margin-top: 1rem; - display: flex; - align-items: center; - gap: 0.5rem; - } - .status-success { background: rgba(16, 185, 129, 0.1); color: var(--success-color); @@ -341,6 +458,18 @@ background: var(--gray-300); } + .btn-outline { + background: transparent; + color: var(--gray-600); + border: 2px solid var(--gray-300); + } + + .btn-outline:hover { + background: var(--gray-100); + border-color: var(--gray-400); + color: var(--gray-700); + } + .error-message { color: var(--error-color); font-size: 0.875rem; @@ -352,6 +481,28 @@ border-color: var(--error-color); } + /* Responsive adjustments */ + @media (max-width: 768px) { + .form-row { + grid-template-columns: 1fr; + } + + .qr-preview-container { + flex-direction: column; + text-align: center; + } + + .range-input-group { + flex-direction: column; + gap: 0.5rem; + align-items: stretch; + } + + .range-value { + text-align: center; + } + } + @media (max-width: 640px) { .container { padding: 0 0.5rem; @@ -365,6 +516,33 @@ flex-direction: column; } } + + /* Animation for preview updates */ + .qr-preview { + animation: pulse 0.3s ease-in-out; + } + + @keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); } + 100% { transform: scale(1); } + } + + /* Toast notification styles */ + .toast { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + } + + .toast-content { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--gray-800); + } + + .toast-content i { + font-size: 1.1rem; + } @@ -558,6 +736,179 @@ + +
+
+

+ + QR Code Customization +

+

Customize the appearance and style of your QR code

+
+ + +
+ + + Choose a pre-defined style or customize manually below +
+ + +
+
+ +
+ + +
+ Color of the QR code modules +
+ +
+ +
+ + +
+ Background color of the QR code +
+
+ + +
+
+ +
+ + 10px +
+ Size of each QR code module (affects overall size) +
+ +
+ +
+ + 4 modules +
+ White border around the QR code +
+
+ + +
+ + + Higher levels allow QR code to work even if partially damaged +
+ + +
+

+ + Live Preview +

+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+

Sample QR Code Preview

+

Colors and styling will be applied to your actual QR code

+
+
+
+
+ @@ -574,8 +925,12 @@ Cancel + @@ -584,190 +939,368 @@ - + \ No newline at end of file diff --git a/templates/edit_qr_code.html b/templates/edit_qr_code.html index dcb3c40..a19d286 100644 --- a/templates/edit_qr_code.html +++ b/templates/edit_qr_code.html @@ -1,1089 +1,1453 @@ - - - - - - Edit QR Code - QR Management System - - - - -
-
- -
-
-

- - Edit QR Code -

-

Update the information for your QR code

-
- -
- -
-
-

- - Basic Information -

-

Update the basic details for your QR code

-
- -
- - - A unique name to identify this QR code -
- {{ qr_code.name|length }}/100 -
-
- -
- - - The name of the physical location where this QR code will be - used -
- {{ qr_code.location|length }}/100 -
-
- -
- - - - - {% if qr_code.project %} - Currently assigned to: {{ qr_code.project.name }} - {% else %} - This QR code is not assigned to any project - {% endif %} - -
-
- - -
-
-

- - Location Details -

-

Update the complete address and event information

-
- -
- - - Include all address details for accurate location - identification -
- {{ qr_code.location_address|length }}/500 -
- - -
-
- -

Address Coordinates

-
- -
-
-
-
- {% if qr_code.has_coordinates %} {{ - "%.6f"|format(qr_code.address_latitude) }} {% else %} - ---.------ {% endif %} -
-
Latitude
-
-
-
- {% if qr_code.has_coordinates %} {{ - "%.6f"|format(qr_code.address_longitude) }} {% else %} - ---.------ {% endif %} -
-
Longitude
-
-
- -
- - -
- -
-
-
-
- -
- - - Select the event type for this QR code -
-
- - - - - - - -
- - - Cancel - - -
-
-
- - -
-
+{% extends "base_authenticated.html" %} +{% block title %}Edit QR Code - QR Management System{% endblock %} + +{% block extra_head %} + +{% endblock %} + +{% block content %} +
+
+ +
+
+

+ + Edit QR Code +

+

Update the information for your QR code

+
+ +
+ +
+

- Current Information + Basic Information

+

Update the basic details for your QR code

-
-
- QR Code Name: - {{ qr_code.name }} +
+ + + A unique name to identify this QR code +
+ {{ qr_code.name|length }}/100
-
- Location: - {{ qr_code.location }} +
+ +
+ + + The name of the physical location where this QR code will be + used +
+ {{ qr_code.location|length }}/100
-
- Event: - {{ qr_code.location_event }} -
-
- Address: - {{ qr_code.location_address }} +
+ +
+ + + + + Organize your QR code by assigning it to a project + +
+
+ + +
+
+

+ + Location Details +

+

Update the complete address and event information

+
+ +
+ + + Include all address details for accurate location + identification +
+ {{ qr_code.location_address|length }}/500
- - {% if qr_code.has_coordinates %} -
-
- {{ qr_code.coordinates_display }} + +
+
+ +

Address Coordinates

-
Current Coordinates
-
- Accuracy: {{ qr_code.coordinate_accuracy|title }} {% if - qr_code.coordinates_updated_date %} -
Updated: {{ - qr_code.coordinates_updated_date.strftime('%Y-%m-%d') }} {% - endif %} + +
+
+
+
+ {% if qr_code.has_coordinates %} + {{ "%.6f"|format(qr_code.address_latitude) }} + {% else %} + ---.---------- + {% endif %} +
+
Latitude
+
+
+
+ {% if qr_code.has_coordinates %} + {{ "%.6f"|format(qr_code.address_longitude) }} + {% else %} + ---.---------- + {% endif %} +
+
Longitude
+
+
+ +
+ + +
+ +
- {% else %} -
- -
No coordinates available
-
- Add coordinates using the form +
+ +
+ + + Select the event type for this QR code +
+
+ + +
+
+

+ + QR Code Customization +

+

Update the appearance and style of your QR code

+
+ + +
+ + + Choose a pre-defined style or customize manually below +
+ + +
+
+ +
+ + +
+ Color of the QR code modules +
+ +
+ +
+ + +
+ Background color of the QR code +
+
+ + +
+
+ +
+ + {{ qr_code.box_size or 10 }}px +
+ Size of each QR code module (affects overall size) +
+ +
+ +
+ + {{ qr_code.border or 4 }} modules +
+ White border around the QR code +
+
+ + +
+ + + Higher levels allow QR code to work even if partially damaged +
+ + +
+

+ + Live Preview +

+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+

Sample QR Code Preview

+

Colors and styling will be applied to your updated QR code

+
+
+ + + + + + + +
+ + + Cancel + + + +
+ +
+ + +
+
+

+ + Current Information +

+
+ +
+
+ QR Code Name: + {{ qr_code.name }} +
+
+ Location: + {{ qr_code.location }} +
+
+ Event: + {{ qr_code.location_event }} +
+
+ Address: + {{ qr_code.location_address }} +
+ + + {% if qr_code.has_coordinates %} +
+
+ {{ qr_code.coordinates_display }} +
+
Current Coordinates
+
+ Accuracy: {{ qr_code.coordinate_accuracy|title }} + {% if qr_code.coordinates_updated_date %} +
Updated: {{ qr_code.coordinates_updated_date.strftime('%Y-%m-%d') }} {% endif %}
+ {% else %} +
+ +
No coordinates available
+
+ Use "Get Coordinates" to add location data +
+
+ {% endif %} + + +
+ QR Code Style: + + {% if qr_code.style %} + {{ qr_code.style.name }} + {% else %} + Custom + {% endif %} + +
+
+ Colors: + + + on + + +
+
+
- - - + clearBtn.addEventListener('click', clearCoordinates); + }); + +{% endblock %} \ No newline at end of file