GestionTablets/docs/FRONTEND_OPTIONS.md
ijuanes 531b3a9048 docs: add separated frontend architecture options for mobile support
- Create docs/FRONTEND_OPTIONS.md with comprehensive analysis
- Explore 4 options for separated frontend:
  1. SPA with REST API (React/Vue/Svelte)
  2. Hybrid approach (mobile SPA + desktop server templates)
  3. HTMX for lightweight dynamic UI
  4. Native mobile app (React Native/Flutter/Capacitor)
- Include comparison matrix with pros/cons
- Provide implementation examples for each option
- Add responsive design guidelines
- Include API endpoints specification
- Recommend phased implementation roadmap
- No code changes - documentation only

This addresses mobile responsiveness while keeping backend intact.
2026-06-18 12:13:59 +01:00

34 KiB

Separated Frontend Architecture Options

Overview

The current Tablet Management System uses server-side rendering with Flask templates. While this works well for tablets, the interface may be too wide for mobile devices. This document explores separated frontend architectures that would provide better mobile responsiveness while keeping the existing backend intact.


Current Architecture

┌─────────────────────────────────────┐
│           Flask Backend               │
│  ┌─────────┐  ┌─────────┐  ┌─────┐ │
│  │ Routes  │  │ Models  │  │ DB   │ │
│  └─────────┘  └─────────┘  └─────┘ │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│         Flask Templates (Jinja2)      │
│  ┌─────────┐  ┌─────────┐  ┌─────┐ │
│  │ HTML    │  │ CSS     │  │ JS   │ │
│  └─────────┘  └─────────┘  └─────┘ │
└─────────────────────────────────────┘
       │
       ▼
   Browser (Tablet/Desktop)

Limitations:

  • Server-rendered HTML (not ideal for dynamic mobile UIs)
  • Limited interactivity without page reloads
  • CSS is basic and not responsive for mobile
  • Tight coupling between backend and frontend

Option 1: Single Page Application (SPA) with REST API

Architecture

┌─────────────────────────────────────┐
│           Flask Backend               │
│  ┌─────────────────────────────────┐ │
│  │         REST API                 │ │
│  │  /api/tablets                   │ │
│  │  /api/users                     │ │
│  │  /api/loans                     │ │
│  │  /api/non-loanable-devices      │ │
│  └─────────────────────────────────┘ │
└──────────────┬──────────────────────┘
               │ HTTP/JSON
               ▼
┌─────────────────────────────────────┐
│         Frontend (React/Vue/Svelte)   │
│  ┌─────────┐  ┌─────────┐  ┌─────┐ │
│  │ Components │  │ State   │  │ Router│ │
│  └─────────┘  └─────────┘  └─────┘ │
└─────────────────────────────────────┘
       │
       ▼
   Browser (Mobile/Tablet/Desktop)

Implementation Steps

1. Create REST API Layer

Add new routes to app.py (without removing existing ones):

# API Routes (add to app.py)
@app.route('/api/tablets', methods=['GET'])
def api_get_tablets():
    """GET /api/tablets - List all tablets"""
    with get_db() as conn:
        cursor = conn.cursor()
        cursor.execute("SELECT * FROM tablets")
        tablets = [dict(row) for row in cursor.fetchall()]
    return jsonify(tablets)

@app.route('/api/tablets/<int:tablet_id>', methods=['GET'])
def api_get_tablet(tablet_id):
    """GET /api/tablets/<id> - Get single tablet"""
    with get_db() as conn:
        cursor = conn.cursor()
        cursor.execute("SELECT * FROM tablets WHERE id = ?", (tablet_id,))
        tablet = cursor.fetchone()
    if tablet:
        return jsonify(dict(tablet))
    return jsonify({'error': 'Tablet not found'}), 404

@app.route('/api/tablets', methods=['POST'])
def api_create_tablet():
    """POST /api/tablets - Create new tablet"""
    data = request.get_json()
    # Validate and create
    with get_db() as conn:
        cursor = conn.cursor()
        cursor.execute('''
            INSERT INTO tablets (brand, model, serial_number, status, notes)
            VALUES (?, ?, ?, 'available', ?)
        ''', (data['brand'], data['model'], data['serial_number'], data.get('notes')))
        conn.commit()
        tablet_id = cursor.lastrowid
        cursor.execute("SELECT * FROM tablets WHERE id = ?", (tablet_id,))
        return jsonify(dict(cursor.fetchone())), 201

# Similar endpoints for users, loans, non_loanable_devices

2. Frontend Structure (React Example)

frontend/
├── public/
│   └── index.html
├── src/
│   ├── components/
│   │   ├── TabletList.jsx
│   │   ├── TabletForm.jsx
│   │   ├── UserList.jsx
│   │   ├── LoanForm.jsx
│   │   ├── LoanHistory.jsx
│   │   ├── UserLoans.jsx
│   │   └── NonLoanableDevices.jsx
│   ├── hooks/
│   │   └── useApi.js
│   ├── services/
│   │   └── api.js
│   ├── App.jsx
│   ├── index.js
│   └── styles/
│       ├── main.css
│       └── responsive.css
├── package.json
└── README.md

3. API Service (frontend/src/services/api.js)

const API_BASE = '/api';

export const api = {
    // Tablets
    getTablets: async (status = null) => {
        const url = status ? `${API_BASE}/tablets?status=${status}` : `${API_BASE}/tablets`;
        const response = await fetch(url);
        return response.json();
    },
    
    getTablet: async (id) => {
        const response = await fetch(`${API_BASE}/tablets/${id}`);
        return response.json();
    },
    
    createTablet: async (tablet) => {
        const response = await fetch(`${API_BASE}/tablets`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(tablet)
        });
        return response.json();
    },
    
    updateTablet: async (id, tablet) => {
        const response = await fetch(`${API_BASE}/tablets/${id}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(tablet)
        });
        return response.json();
    },
    
    // Similar methods for users, loans, non_loanable_devices
};

4. React Components Example

// frontend/src/components/TabletList.jsx
import React, { useState, useEffect } from 'react';
import { api } from '../services/api';

export function TabletList() {
    const [tablets, setTablets] = useState([]);
    const [loading, setLoading] = useState(true);
    
    useEffect(() => {
        api.getTablets('available').then(data => {
            setTablets(data);
            setLoading(false);
        });
    }, []);
    
    if (loading) return <div>Loading...</div>;
    
    return (
        <div className="tablet-list">
            <h2>Available Tablets</h2>
            <div className="responsive-table">
                {tablets.map(tablet => (
                    <div key={tablet.id} className="tablet-card">
                        <h3>{tablet.brand} {tablet.model}</h3>
                        <p>Serial: {tablet.serial_number}</p>
                        <p>Status: {tablet.status}</p>
                    </div>
                ))}
            </div>
        </div>
    );
}

5. Responsive CSS

/* frontend/src/styles/responsive.css */

/* Mobile-first approach */
* {
    box-sizing: border-box;
}

body {
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background-color: #f5f5f5;
    min-height: 100vh;
}

.container {
    max-width: 100%;
    padding: 1rem;
}

/* Cards for mobile */
.tablet-card, .user-card, .loan-card {
    background: white;
    border-radius: 8px;
    padding: 1rem;
    margin-bottom: 1rem;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    border: 1px solid #e0e0e0;
}

/* Navigation */
.nav {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.nav a {
    padding: 0.75rem 1rem;
    background-color: #4CAF50;
    color: white;
    text-decoration: none;
    border-radius: 4px;
    text-align: center;
}

/* Tables - responsive */
.responsive-table {
    overflow-x: auto;
}

table {
    width: 100%;
    min-width: 600px; /* Allows horizontal scrolling on mobile */
}

th, td {
    padding: 0.75rem;
    white-space: nowrap;
}

/* Forms */
form {
    max-width: 100%;
}

input, select, textarea {
    width: 100%;
    padding: 0.75rem;
    margin-bottom: 1rem;
    border: 1px solid #ddd;
    border-radius: 4px;
}

/* Buttons */
.btn {
    padding: 0.75rem 1.5rem;
    width: 100%;
    margin-bottom: 0.5rem;
}

/* Breakpoints */
@media (min-width: 600px) {
    .nav {
        flex-direction: row;
        flex-wrap: wrap;
    }
    
    .nav a {
        flex: 1 1 auto;
        min-width: 120px;
    }
    
    .tablet-card, .user-card, .loan-card {
        display: flex;
        justify-content: space-between;
        align-items: center;
    }
}

@media (min-width: 768px) {
    .container {
        max-width: 720px;
        margin: 0 auto;
    }
    
    table {
        min-width: auto;
    }
}

@media (min-width: 1024px) {
    .container {
        max-width: 960px;
    }
    
    .nav {
        flex-wrap: nowrap;
    }
}

@media (min-width: 1200px) {
    .container {
        max-width: 1140px;
    }
}

Pros and Cons

Aspect Pros Cons
User Experience Rich, dynamic UI; no page reloads More complex to develop
Performance Fast after initial load; client-side rendering Larger initial bundle
Mobile Support Excellent with responsive design Needs careful CSS work
Development Modern tooling (React/Vue); component-based Separate codebase to maintain
SEO Poor (SPA) Needs SSR for better SEO
Backend Impact Minimal (just add API routes) Need to maintain both templates and API
Deployment Can be hosted separately More complex deployment
  • Framework: React (most popular) or Vue (simpler) or Svelte (smaller bundle)
  • State Management: React Query or SWR for data fetching
  • Styling: Tailwind CSS or CSS Modules
  • Routing: React Router
  • Build Tool: Vite (fast) or Create React App
  • TypeScript: Optional but recommended for large projects

Option 2: Hybrid Approach (Progressive Enhancement)

Architecture

┌─────────────────────────────────────┐
│           Flask Backend               │
│  ┌─────────────────────────────────┐ │
│  │  Dual Mode:                      │ │
│  │  - Server templates (existing)  │ │
│  │  - REST API (new)               │ │
│  └─────────────────────────────────┘ │
└──────────────┬──────────────────────┘
               │
         ┌─────┴─────┐
         ▼           ▼
┌─────────────┐ ┌─────────────┐
│  Desktop    │ │   Mobile    │
│  (Existing) │ │  (New SPA)  │
└─────────────┘ └─────────────┘

How It Works

  1. Desktop/Tablet: Uses existing server-rendered templates
  2. Mobile: Detects mobile device and serves a minimal HTML page that loads the SPA
  3. Shared Backend: Both use the same Flask backend

Implementation

1. Device Detection Middleware

# app.py
from flask import request, redirect, url_for
import re

MOBILE_USER_AGENTS = re.compile(
    r'android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini|mobile',
    re.IGNORECASE
)

@app.before_request
def detect_mobile():
    user_agent = request.headers.get('User-Agent', '')
    if MOBILE_USER_AGENTS.search(user_agent):
        request.is_mobile = True
    else:
        request.is_mobile = False

2. Mobile-Specific Route

@app.route('/mobile')
def mobile_app():
    """Serve mobile SPA entry point"""
    return render_template('mobile.html')

@app.before_request
def redirect_mobile():
    """Redirect mobile users to SPA"""
    if hasattr(request, 'is_mobile') and request.is_mobile:
        if not request.path.startswith('/api') and not request.path.startswith('/mobile'):
            return redirect(url_for('mobile_app'))

3. Mobile Entry Point Template

<!-- templates/mobile.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Tablet Management - Mobile</title>
    <link rel="stylesheet" href="/static/mobile.css">
</head>
<body>
    <div id="root"></div>
    <script type="module" src="/static/mobile.js"></script>
</body>
</html>

Pros and Cons

Aspect Pros Cons
User Experience Best of both worlds Two UIs to maintain
Mobile Support Excellent Desktop UI unchanged (may still be wide)
Development Gradual migration possible More complex logic
Backend Impact Minimal Device detection logic
SEO Good (server-rendered desktop) Mobile SPA has poor SEO
Deployment Single deployment Larger asset bundle

Option 3: Flask + HTMX (Lightweight Dynamic UI)

Architecture

┌─────────────────────────────────────┐
│           Flask Backend               │
│  ┌─────────────────────────────────┐ │
│  │  Enhanced Templates              │ │
│  │  - HTML + HTMX attributes        │ │
│  │  - Partial updates via AJAX       │ │
│  └─────────────────────────────────┘ │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│         Browser (Any Device)          │
│  - HTMX handles dynamic updates      │
│  - CSS handles responsiveness        │
└─────────────────────────────────────┘

What is HTMX?

HTMX allows you to add interactivity to HTML without writing JavaScript. It uses attributes to:

  • Make AJAX requests
  • Update DOM elements
  • Handle form submissions
  • Show loading indicators

Implementation Example

1. Add HTMX to Base Template

<!-- templates/base.html -->
<head>
    <!-- Existing head content -->
    <script src="https://unpkg.com/htmx.org@1.9.10"></script>
    <script src="https://unpkg.com/htmx.org@1.9.10/dist/ext/head-support.js"></script>
</head>

2. Enhance Templates with HTMX

<!-- templates/index.html -->
<div class="section">
    <h2>Available Tablets</h2>
    
    <!-- Search with HTMX -->
    <input type="text" name="search" placeholder="Search tablets..."
           hx-get="/api/tablets/search"
           hx-trigger="keyup changed delay:500ms"
           hx-target="#tablet-results">
    
    <!-- Results updated via HTMX -->
    <div id="tablet-results">
        {% for tablet in available_tablets %}
        <div class="tablet-card">
            <h3>{{ tablet.brand }} {{ tablet.model }}</h3>
            <p>Serial: {{ tablet.serial_number }}</p>
            <button hx-post="/api/tablets/{{ tablet.id }}/loan"
                    hx-target="#loan-modal"
                    hx-swap="innerHTML">
                Loan
            </button>
        </div>
        {% endfor %}
    </div>
</div>

<!-- Loan modal -->
<div id="loan-modal"></div>

3. Add HTMX Endpoints

@app.route('/api/tablets/search')
def search_tablets():
    query = request.args.get('search', '')
    with get_db() as conn:
        cursor = conn.cursor()
        cursor.execute("""
            SELECT * FROM tablets 
            WHERE brand LIKE ? OR model LIKE ? OR serial_number LIKE ?
        """, (f'%{query}%', f'%{query}%', f'%{query}%'))
        tablets = cursor.fetchall()
    return render_template('partials/tablet_list.html', tablets=tablets)

@app.route('/api/tablets/<int:tablet_id>/loan', methods=['POST'])
def loan_tablet_htmx(tablet_id):
    # Get user from form
    user_id = request.form.get('user_id')
    # Loan logic...
    return render_template('partials/loan_form.html', tablet_id=tablet_id)

4. Responsive CSS

/* Add to base.html or separate CSS file */

/* Mobile-first responsive design */
.tablet-card, .user-card, .loan-card {
    background: white;
    border-radius: 8px;
    padding: 1rem;
    margin-bottom: 1rem;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.nav {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.nav a {
    padding: 0.75rem;
    text-align: center;
}

@media (min-width: 600px) {
    .nav {
        flex-direction: row;
        flex-wrap: wrap;
    }
    
    .tablet-card {
        display: flex;
        justify-content: space-between;
    }
}

@media (min-width: 768px) {
    .container {
        max-width: 720px;
        margin: 0 auto;
    }
}

@media (min-width: 1024px) {
    .container {
        max-width: 960px;
    }
    
    .nav {
        flex-wrap: nowrap;
    }
}

Pros and Cons

Aspect Pros Cons
User Experience Dynamic updates without full page reloads Less powerful than full SPA
Mobile Support Good with responsive CSS Still limited by server rendering
Development Minimal changes to existing code Need to learn HTMX
Backend Impact Very minimal (just add endpoints) More routes to maintain
SEO Excellent (server-rendered) Best of all options
Deployment No changes needed Simple
Bundle Size Tiny (~14KB for HTMX) No build step

Option 4: Mobile App (Native or Cross-Platform)

Architecture

┌─────────────────────────────────────┐
│           Flask Backend               │
│  ┌─────────────────────────────────┐ │
│  │         REST API                 │ │
│  │  (Same as Option 1)              │ │
│  └─────────────────────────────────┘ │
└──────────────┬──────────────────────┘
               │ HTTP/JSON
               ▼
┌─────────────────────────────────────┐
│         Mobile App                    │
│  (React Native / Flutter / Capacitor)│
└─────────────────────────────────────┘
       │
       ▼
   Mobile Device

Implementation Options

A. React Native (JavaScript)

// App.js
import React from 'react';
import { View, Text, FlatList, TouchableOpacity, StyleSheet } from 'react-native';

const API_BASE = 'http://your-server:5000/api';

export default function App() {
    const [tablets, setTablets] = React.useState([]);
    
    React.useEffect(() => {
        fetch(`${API_BASE}/tablets`)
            .then(res => res.json())
            .then(data => setTablets(data));
    }, []);
    
    return (
        <View style={styles.container}>
            <Text style={styles.title}>Tablet Management</Text>
            <FlatList
                data={tablets}
                keyExtractor={item => item.id.toString()}
                renderItem={({item}) => (
                    <TouchableOpacity style={styles.card}>
                        <Text style={styles.brand}>{item.brand} {item.model}</Text>
                        <Text style={styles.serial}>SN: {item.serial_number}</Text>
                        <Text style={styles.status}>Status: {item.status}</Text>
                    </TouchableOpacity>
                )}
            />
        </View>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        padding: 20,
        backgroundColor: '#f5f5f5',
    },
    title: {
        fontSize: 24,
        fontWeight: 'bold',
        marginBottom: 20,
        textAlign: 'center',
    },
    card: {
        backgroundColor: 'white',
        padding: 15,
        borderRadius: 8,
        marginBottom: 10,
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.1,
        shadowRadius: 4,
        elevation: 2,
    },
    brand: {
        fontSize: 18,
        fontWeight: '600',
    },
    serial: {
        fontSize: 14,
        color: '#666',
    },
    status: {
        fontSize: 14,
        color: '#4CAF50',
    },
});

B. Flutter (Dart)

// main.dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Tablet Management',
            home: TabletListScreen(),
        );
    }
}

class TabletListScreen extends StatefulWidget {
    @override
    _TabletListScreenState createState() => _TabletListScreenState();
}

class _TabletListScreenState extends State<TabletListScreen> {
    List<dynamic> tablets = [];
    
    @override
    void initState() {
        super.initState();
        fetchTablets();
    }
    
    Future<void> fetchTablets() async {
        final response = await http.get(Uri.parse('http://your-server:5000/api/tablets'));
        if (response.statusCode == 200) {
            setState(() {
                tablets = json.decode(response.body);
            });
        }
    }
    
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: Text('Tablet Management')),
            body: ListView.builder(
                itemCount: tablets.length,
                itemBuilder: (context, index) {
                    final tablet = tablets[index];
                    return Card(
                        child: ListTile(
                            title: Text('${tablet['brand']} ${tablet['model']}'),
                            subtitle: Text('SN: ${tablet['serial_number']}'),
                            trailing: Text(tablet['status']),
                        ),
                    );
                },
            ),
        );
    }
}

C. Capacitor (Web App as Mobile App)

Use your existing web app (Option 1 SPA) and wrap it with Capacitor:

# Install Capacitor
npm install @capacitor/core @capacitor/cli
npx cap init

# Add platforms
npm install @capacitor/android @capacitor/ios
npx cap add android
npx cap add ios

# Build and sync
npm run build
npx cap sync
npx cap open android  # or ios

Pros and Cons

Aspect React Native Flutter Capacitor
Language JavaScript Dart JavaScript
Performance Native Native WebView
Code Reuse ~80% with web ~50% with web ~100% with web
Learning Curve Medium (if know React) High (new language) Low (web devs)
Access to Native Good Excellent Limited
Bundle Size Medium Large Small
Offline Support Yes Yes Yes

Comparison Matrix

Feature Current SPA (Option 1) Hybrid (Option 2) HTMX (Option 3) Mobile App (Option 4)
Mobile Friendly No Yes Yes ⚠️ Partial Yes
Desktop Friendly Yes Yes Yes Yes No
Tablet Friendly Yes Yes Yes Yes Yes
Development Effort N/A High Medium Low High
Backend Changes N/A Low Low Very Low Low (API only)
Learning Curve N/A Medium Medium Low High
SEO Good Poor Good Good Poor
Offline Support No Yes No No Yes
Performance ⚠️ OK Good ⚠️ OK Good Excellent
Deployment Simple Complex Medium Simple Complex
Maintenance Simple Medium Complex Simple Medium

Recommendations

For Immediate Improvement (Low Effort)

Choose: Option 3 (HTMX)

  • Minimal code changes
  • No new build process
  • Progressive enhancement
  • Good mobile support with responsive CSS
  • Keeps existing server rendering

For Best User Experience (Medium Effort)

Choose: Option 1 (SPA with REST API)

  • Modern, dynamic UI
  • Excellent mobile support
  • Can be deployed separately
  • Backend changes are minimal (just add API routes)

For Native Mobile Experience (High Effort)

Choose: Option 4 (Mobile App)

  • Best mobile UX
  • Offline capabilities
  • Native device features (camera, etc.)
  • Requires separate mobile development

For Gradual Migration

Choose: Option 2 (Hybrid)

  • Start with mobile SPA
  • Keep desktop as-is
  • Migrate desktop later if needed
  • Minimal risk

Implementation Roadmap

Phase 1: Quick Win (1-2 days)

  1. Add responsive CSS to existing templates
  2. Add viewport meta tag
  3. Test on mobile devices

Result: Better mobile experience with minimal changes

Phase 2: Enhanced Interactivity (3-5 days)

  1. Add HTMX to templates
  2. Create partial templates for updates
  3. Add new API endpoints for HTMX
  4. Test all interactions

Result: Dynamic UI without full SPA complexity

Phase 3: Full SPA (1-2 weeks)

  1. Set up React/Vue project
  2. Create API layer in Flask
  3. Build frontend components
  4. Add responsive design
  5. Test on all devices
  6. Deploy frontend separately

Result: Modern, mobile-first web application

Phase 4: Mobile App (2-4 weeks)

  1. Choose framework (React Native/Flutter)
  2. Set up mobile project
  3. Connect to existing API
  4. Build mobile-specific UI
  5. Add offline support
  6. Test on devices
  7. Publish to app stores

Result: Native mobile application


File Structure for Separated Frontend

If you choose Option 1 (SPA), here's the recommended structure:

GestionTablets/
├── backend/                      # Existing Flask backend
│   ├── app.py                    # Flask app + API routes
│   ├── templates/               # Existing templates (keep for now)
│   ├── static/                  # Static files
│   └── ...
│
├── frontend/                     # NEW: Separated frontend
│   ├── public/
│   │   └── index.html
│   ├── src/
│   │   ├── components/
│   │   │   ├── common/
│   │   │   │   ├── Button.jsx
│   │   │   │   ├── Card.jsx
│   │   │   │   ├── Modal.jsx
│   │   │   │   └── Table.jsx
│   │   │   ├── TabletList.jsx
│   │   │   ├── TabletForm.jsx
│   │   │   ├── UserList.jsx
│   │   │   ├── UserForm.jsx
│   │   │   ├── LoanList.jsx
│   │   │   ├── LoanForm.jsx
│   │   │   ├── LoanHistory.jsx
│   │   │   ├── UserLoans.jsx
│   │   │   └── NonLoanableDevices.jsx
│   │   ├── hooks/
│   │   │   ├── useTablets.js
│   │   │   ├── useUsers.js
│   │   │   ├── useLoans.js
│   │   │   └── useApi.js
│   │   ├── services/
│   │   │   └── api.js
│   │   ├── utils/
│   │   │   ├── formatters.js
│   │   │   └── validators.js
│   │   ├── App.jsx
│   │   ├── App.css
│   │   ├── index.js
│   │   └── index.css
│   ├── package.json
│   ├── vite.config.js
│   └── README.md
│
├── docs/                         # Documentation
│   ├── MIGRATION_TO_POSTGRES.md
│   └── FRONTEND_OPTIONS.md        # This document
│
├── scripts/                      # Utility scripts
│   └── migrate_to_postgres.py
│
├── .gitignore
├── README.md
├── pyproject.toml
└── docker-compose.yml

API Endpoints Needed

For any separated frontend, you'll need these API endpoints:

Tablets

  • GET /api/tablets - List all tablets
  • GET /api/tablets?status=available - Filter by status
  • GET /api/tablets/<id> - Get single tablet
  • POST /api/tablets - Create tablet
  • PUT /api/tablets/<id> - Update tablet
  • DELETE /api/tablets/<id> - Delete tablet
  • GET /api/tablets/search?q=query - Search tablets

Users

  • GET /api/users - List all users
  • GET /api/users/<id> - Get single user
  • POST /api/users - Create user
  • PUT /api/users/<id> - Update user
  • DELETE /api/users/<id> - Delete user
  • GET /api/users/search?q=query - Search users

Loans

  • GET /api/loans - List all loans
  • GET /api/loans?status=active - Filter by status
  • GET /api/loans/<id> - Get single loan
  • POST /api/loans - Create loan
  • PUT /api/loans/<id>/return - Return tablet
  • GET /api/loans/user/<user_id> - Get loans by user
  • GET /api/loans/tablet/<tablet_id> - Get loans by tablet

Non-Loanable Devices

  • GET /api/non-loanable-devices - List all
  • GET /api/non-loanable-devices/<id> - Get single device
  • POST /api/non-loanable-devices - Create device
  • PUT /api/non-loanable-devices/<id> - Update device
  • DELETE /api/non-loanable-devices/<id> - Delete device

Statistics

  • GET /api/stats - Get dashboard statistics

Responsive Design Guidelines

Breakpoints

/* Mobile-first approach */
:root {
    --breakpoint-xs: 0px;
    --breakpoint-sm: 576px;
    --breakpoint-md: 768px;
    --breakpoint-lg: 992px;
    --breakpoint-xl: 1200px;
}

/* Usage */
@media (min-width: 576px) { /* Small devices (landscape phones) */ }
@media (min-width: 768px) { /* Medium devices (tablets) */ }
@media (min-width: 992px) { /* Large devices (desktops) */ }
@media (min-width: 1200px) { /* Extra large devices */ }

Mobile-First Principles

  1. Start with mobile - Design for smallest screen first
  2. Progressive enhancement - Add features for larger screens
  3. Touch targets - Minimum 48x48px for touch elements
  4. Font sizes - Minimum 16px for readability
  5. Spacing - Adequate padding for touch
  6. Navigation - Bottom navigation for mobile, top for desktop
  7. Forms - Large, easy-to-use inputs
  8. Tables - Consider cards instead of tables on mobile

Touch Target Sizes

Element Minimum Size Recommended Size
Buttons 48x48px 56x56px
Form inputs 48px height 56px height
List items 48px height 64px height
Checkboxes/Radios 24x24px 32x32px

Deployment Options

Option A: Separate Servers

┌─────────────────┐     ┌─────────────────┐
│  Backend Server  │────▶│  Frontend Server │
│  (Flask)         │     │  (Nginx/Apache)  │
│  :5000          │     │  :80/:443        │
└─────────────────┘     └─────────────────┘
       │                        │
       ▼                        ▼
   API Requests            Static Files

Pros: Separate scaling, independent deployment Cons: More complex setup, CORS configuration

Option B: Same Server, Different Routes

┌─────────────────────────────────────┐
│           Flask Server                │
│  ┌─────────────────────────────────┐ │
│  │  /api/*          → Backend routes  │ │
│  │  /*              → Frontend (SPA) │ │
│  └─────────────────────────────────┘ │
└─────────────────────────────────────┘
       │
       ▼
   Nginx (reverse proxy)
       │
       ▼
   Client

Pros: Simpler deployment, no CORS issues Cons: Backend serves static files

Option C: Docker Compose

# docker-compose.yml
version: '3.8'

services:
  backend:
    build: ./backend
    ports:
      - "5000:5000"
    environment:
      - FLASK_ENV=production
    restart: unless-stopped

  frontend:
    build: ./frontend
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - backend
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - backend
      - frontend
    restart: unless-stopped

Conclusion

For the Tablet Management System, I recommend the following approach:

Short Term (1-2 days)

Start with Option 3 (HTMX) to add dynamic updates and responsive CSS to the existing templates. This provides:

  • Immediate mobile improvements
  • Minimal code changes
  • No new dependencies (just HTMX)
  • Progressive enhancement

Medium Term (1-2 weeks)

Migrate to Option 1 (SPA with REST API) for:

  • Better mobile experience
  • Modern development workflow
  • Separate frontend deployment
  • Easier to maintain long-term

Long Term (Optional)

Consider Option 4 (Mobile App) if:

  • Users need offline access
  • Need native device features
  • Want app store presence

The current backend (Flask + SQLite) can remain completely unchanged for all these options. You only need to add API endpoints, which don't affect the existing template-based functionality.