mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-04 10:40:23 -06:00
- Improve web interface layout for better user-friendliness and mobile responsiveness * Update CSS variables for consistent spacing and component sizing * Enhance card layouts with improved padding, borders, and shadows * Optimize button and form element dimensions for better touch targets * Add hover effects and animations for improved user interaction * Implement responsive grid system with mobile-first approach - Refactor mobile JavaScript to prevent duplicate initialization * Consolidate mobile enhancements into dedicated utility classes * Add initialization guards to prevent double loading * Implement MobileUtils and MobileNavigation classes * Remove duplicate event listeners and mobile enhancements - Fix circular import issue in logo handling * Replace problematic 'from app import app' with Flask's current_app * Add error handling for cases where current_app is unavailable * Improve logo path resolution with fallback mechanisms * Fix settings model to use proper Flask context - Clean up template code and remove duplication * Remove duplicate mobile enhancements from base template * Clean up dashboard template JavaScript * Centralize all mobile functionality in mobile.js * Add proper error handling and debugging - Update CSS variables and spacing system * Introduce --section-spacing and --card-spacing variables * Add mobile-specific spacing variables * Improve border-radius and shadow consistency * Enhance typography and visual hierarchy This commit resolves the double loading issue and logo import errors while significantly improving the overall user experience and mobile responsiveness of the web interface.
204 lines
11 KiB
Python
204 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Migration script to update logo system from file paths to file uploads
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from sqlalchemy import create_engine, text, MetaData, Table, Column, String
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
def migrate_logo_system():
|
|
"""Migrate from company_logo_path to company_logo_filename"""
|
|
|
|
# Database connection
|
|
database_url = os.getenv('DATABASE_URL', 'sqlite:///timetracker.db')
|
|
|
|
try:
|
|
engine = create_engine(database_url)
|
|
metadata = MetaData()
|
|
|
|
# Check if settings table exists
|
|
inspector = engine.inspect(engine)
|
|
if 'settings' not in inspector.get_table_names():
|
|
print("❌ Settings table does not exist. Please run the company branding migration first.")
|
|
return False
|
|
|
|
# Get current columns
|
|
columns = [col['name'] for col in inspector.get_columns('settings')]
|
|
print(f"Current columns: {columns}")
|
|
|
|
# Check if migration is needed
|
|
if 'company_logo_filename' in columns and 'company_logo_path' not in columns:
|
|
print("✅ Logo upload system already migrated")
|
|
return True
|
|
|
|
if 'company_logo_path' not in columns:
|
|
print("❌ company_logo_path column not found. Please run the company branding migration first.")
|
|
return False
|
|
|
|
# Create the uploads directory structure
|
|
create_uploads_directories()
|
|
|
|
# Rename column from company_logo_path to company_logo_filename
|
|
with engine.connect() as conn:
|
|
# For SQLite
|
|
if 'sqlite' in database_url:
|
|
# SQLite doesn't support ALTER COLUMN, so we need to recreate the table
|
|
print("🔄 SQLite detected - recreating table structure...")
|
|
|
|
# Get current data
|
|
result = conn.execute(text("SELECT * FROM settings"))
|
|
rows = result.fetchall()
|
|
column_names = result.keys()
|
|
|
|
if rows:
|
|
# Create new table with updated schema
|
|
conn.execute(text("""
|
|
CREATE TABLE settings_new (
|
|
id INTEGER PRIMARY KEY,
|
|
timezone VARCHAR(50) NOT NULL DEFAULT 'Europe/Rome',
|
|
currency VARCHAR(3) NOT NULL DEFAULT 'EUR',
|
|
rounding_minutes INTEGER NOT NULL DEFAULT 1,
|
|
single_active_timer BOOLEAN NOT NULL DEFAULT 1,
|
|
allow_self_register BOOLEAN NOT NULL DEFAULT 1,
|
|
idle_timeout_minutes INTEGER NOT NULL DEFAULT 30,
|
|
backup_retention_days INTEGER NOT NULL DEFAULT 30,
|
|
backup_time VARCHAR(5) NOT NULL DEFAULT '02:00',
|
|
export_delimiter VARCHAR(1) NOT NULL DEFAULT ',',
|
|
company_name VARCHAR(200) NOT NULL DEFAULT 'Your Company Name',
|
|
company_address TEXT NOT NULL DEFAULT 'Your Company Address',
|
|
company_email VARCHAR(200) NOT NULL DEFAULT 'info@yourcompany.com',
|
|
company_phone VARCHAR(50) NOT NULL DEFAULT '+1 (555) 123-4567',
|
|
company_website VARCHAR(200) NOT NULL DEFAULT 'www.yourcompany.com',
|
|
company_logo_filename VARCHAR(255) DEFAULT '',
|
|
company_tax_id VARCHAR(100) DEFAULT '',
|
|
company_bank_info TEXT DEFAULT '',
|
|
invoice_prefix VARCHAR(10) NOT NULL DEFAULT 'INV',
|
|
invoice_start_number INTEGER NOT NULL DEFAULT 1000,
|
|
invoice_terms TEXT NOT NULL DEFAULT 'Payment is due within 30 days of invoice date.',
|
|
invoice_notes TEXT NOT NULL DEFAULT 'Thank you for your business!',
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
"""))
|
|
|
|
# Copy data, converting company_logo_path to company_logo_filename
|
|
for row in rows:
|
|
row_dict = dict(zip(column_names, row))
|
|
|
|
# Convert logo path to filename if it exists
|
|
logo_filename = ''
|
|
if row_dict.get('company_logo_path'):
|
|
old_path = row_dict['company_logo_path']
|
|
if old_path and os.path.exists(old_path):
|
|
# Extract filename from path
|
|
logo_filename = os.path.basename(old_path)
|
|
print(f"📁 Converting logo path: {old_path} -> {logo_filename}")
|
|
else:
|
|
print(f"⚠️ Logo path not found: {old_path}")
|
|
|
|
# Insert into new table
|
|
conn.execute(text("""
|
|
INSERT INTO settings_new (
|
|
id, timezone, currency, rounding_minutes, single_active_timer,
|
|
allow_self_register, idle_timeout_minutes, backup_retention_days,
|
|
backup_time, export_delimiter, company_name, company_address,
|
|
company_email, company_phone, company_website, company_logo_filename,
|
|
company_tax_id, company_bank_info, invoice_prefix, invoice_start_number,
|
|
invoice_terms, invoice_notes, created_at, updated_at
|
|
) VALUES (
|
|
:id, :timezone, :currency, :rounding_minutes, :single_active_timer,
|
|
:allow_self_register, :idle_timeout_minutes, :backup_retention_days,
|
|
:backup_time, :export_delimiter, :company_name, :company_address,
|
|
:company_email, :company_phone, :company_website, :company_logo_filename,
|
|
:company_tax_id, :company_bank_info, :invoice_prefix, :invoice_start_number,
|
|
:invoice_terms, :invoice_notes, :created_at, :updated_at
|
|
)
|
|
"""), {
|
|
'id': row_dict.get('id'),
|
|
'timezone': row_dict.get('timezone', 'Europe/Rome'),
|
|
'currency': row_dict.get('currency', 'EUR'),
|
|
'rounding_minutes': row_dict.get('rounding_minutes', 1),
|
|
'single_active_timer': row_dict.get('single_active_timer', True),
|
|
'allow_self_register': row_dict.get('allow_self_register', True),
|
|
'idle_timeout_minutes': row_dict.get('idle_timeout_minutes', 30),
|
|
'backup_retention_days': row_dict.get('backup_retention_days', 30),
|
|
'backup_time': row_dict.get('backup_time', '02:00'),
|
|
'export_delimiter': row_dict.get('export_delimiter', ','),
|
|
'company_name': row_dict.get('company_name', 'Your Company Name'),
|
|
'company_address': row_dict.get('company_address', 'Your Company Address'),
|
|
'company_email': row_dict.get('company_email', 'info@yourcompany.com'),
|
|
'company_phone': row_dict.get('company_phone', '+1 (555) 123-4567'),
|
|
'company_website': row_dict.get('company_website', 'www.yourcompany.com'),
|
|
'company_logo_filename': logo_filename,
|
|
'company_tax_id': row_dict.get('company_tax_id', ''),
|
|
'company_bank_info': row_dict.get('company_bank_info', ''),
|
|
'invoice_prefix': row_dict.get('invoice_prefix', 'INV'),
|
|
'invoice_start_number': row_dict.get('invoice_start_number', 1000),
|
|
'invoice_terms': row_dict.get('invoice_terms', 'Payment is due within 30 days of invoice date.'),
|
|
'invoice_notes': row_dict.get('invoice_notes', 'Thank you for your business!'),
|
|
'created_at': row_dict.get('created_at'),
|
|
'updated_at': row_dict.get('updated_at')
|
|
})
|
|
|
|
# Drop old table and rename new one
|
|
conn.execute(text("DROP TABLE settings"))
|
|
conn.execute(text("ALTER TABLE settings_new RENAME TO settings"))
|
|
|
|
else:
|
|
# No data to migrate, just update schema
|
|
conn.execute(text("ALTER TABLE settings RENAME COLUMN company_logo_path TO company_logo_filename"))
|
|
|
|
else:
|
|
# For other databases (PostgreSQL, MySQL)
|
|
print("🔄 Updating column name...")
|
|
conn.execute(text("ALTER TABLE settings RENAME COLUMN company_logo_path TO company_logo_filename"))
|
|
|
|
conn.commit()
|
|
print("✅ Logo upload system migration completed successfully")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"❌ Migration failed: {e}")
|
|
return False
|
|
|
|
def create_uploads_directories():
|
|
"""Create the uploads directory structure"""
|
|
try:
|
|
# Get the app root directory
|
|
app_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
uploads_dir = os.path.join(app_root, 'app', 'static', 'uploads', 'logos')
|
|
|
|
# Create directories
|
|
os.makedirs(uploads_dir, exist_ok=True)
|
|
|
|
# Create .gitkeep file to preserve directory in git
|
|
gitkeep_file = os.path.join(uploads_dir, '.gitkeep')
|
|
if not os.path.exists(gitkeep_file):
|
|
with open(gitkeep_file, 'w') as f:
|
|
f.write('# This file ensures the uploads directory is preserved in git\n')
|
|
|
|
print(f"✅ Created uploads directory: {uploads_dir}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"❌ Failed to create uploads directories: {e}")
|
|
return False
|
|
|
|
if __name__ == '__main__':
|
|
print("🚀 Starting logo upload system migration...")
|
|
|
|
success = migrate_logo_system()
|
|
|
|
if success:
|
|
print("🎉 Migration completed successfully!")
|
|
print("\nNext steps:")
|
|
print("1. Restart your application")
|
|
print("2. Go to Admin → Settings")
|
|
print("3. Upload your company logo using the new file upload interface")
|
|
print("4. The logo will automatically appear throughout the application")
|
|
else:
|
|
print("💥 Migration failed. Please check the error messages above.")
|
|
sys.exit(1)
|