Django_Basic_Manufacturing_3/dashboard_plan.md
2025-08-22 17:05:22 +07:00

24 KiB

Dashboard Implementation Plan for Django Manufacturing App

Overview

This document outlines the implementation plan for the dashboard module, including data visualization, widgets, and printable functionality.

Dashboard Models

1. Dashboard Widget Model

# dashboard/models.py

from django.db import models
from django.contrib.auth.models import User

class DashboardWidget(models.Model):
    WIDGET_TYPES = [
        ('chart', 'Chart'),
        ('table', 'Table'),
        ('kpi', 'Key Performance Indicator'),
        ('summary', 'Summary'),
        ('list', 'List'),
    ]
    
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    widget_type = models.CharField(max_length=20, choices=WIDGET_TYPES)
    template_name = models.CharField(max_length=200, 
                                   help_text="Template to render this widget")
    data_source = models.CharField(max_length=200,
                                 help_text="Function or view that provides data")
    config = models.JSONField(default=dict, blank=True,
                            help_text="Configuration options for the widget")
    is_active = models.BooleanField(default=True)
    order = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        ordering = ['order']
    
    def __str__(self):
        return self.title

class UserDashboard(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    widgets = models.ManyToManyField(DashboardWidget, through='UserDashboardWidget')
    layout_config = models.JSONField(default=dict, blank=True,
                                   help_text="Grid layout configuration")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    def __str__(self):
        return f"Dashboard for {self.user.username}"

class UserDashboardWidget(models.Model):
    user_dashboard = models.ForeignKey(UserDashboard, on_delete=models.CASCADE)
    widget = models.ForeignKey(DashboardWidget, on_delete=models.CASCADE)
    position = models.JSONField(default=dict, blank=True,
                              help_text="Position in grid layout")
    config = models.JSONField(default=dict, blank=True,
                            help_text="User-specific configuration")
    is_visible = models.BooleanField(default=True)
    
    class Meta:
        unique_together = ('user_dashboard', 'widget')

Dashboard Views

2. Dashboard Views Implementation

# dashboard/views.py

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.template.loader import render_to_string
from django.contrib import messages
from .models import DashboardWidget, UserDashboard, UserDashboardWidget
from inventory.models import Product, Inventory
from sales.models import SalesOrder
from purchasing.models import PurchaseOrder
from manufacturing.models import ManufacturingOrder

@login_required
def home(request):
    """Main dashboard view"""
    # Get or create user dashboard
    user_dashboard, created = UserDashboard.objects.get_or_create(
        user=request.user
    )
    
    # Get visible widgets for this user
    user_widgets = UserDashboardWidget.objects.filter(
        user_dashboard=user_dashboard,
        is_visible=True
    ).select_related('widget').order_by('widget__order')
    
    # Prepare widget data
    widgets_data = []
    for user_widget in user_widgets:
        widget = user_widget.widget
        data = get_widget_data(widget.data_source, request)
        widgets_data.append({
            'widget': widget,
            'data': data,
            'config': user_widget.config,
            'position': user_widget.position,
        })
    
    context = {
        'widgets_data': widgets_data,
        'user_dashboard': user_dashboard,
    }
    
    return render(request, 'dashboard/home.html', context)

@login_required
def print_dashboard(request):
    """Printable dashboard view"""
    # Similar to home view but with print-specific template
    user_dashboard, created = UserDashboard.objects.get_or_create(
        user=request.user
    )
    
    user_widgets = UserDashboardWidget.objects.filter(
        user_dashboard=user_dashboard,
        is_visible=True
    ).select_related('widget').order_by('widget__order')
    
    widgets_data = []
    for user_widget in user_widgets:
        widget = user_widget.widget
        data = get_widget_data(widget.data_source, request)
        widgets_data.append({
            'widget': widget,
            'data': data,
        })
    
    context = {
        'widgets_data': widgets_data,
        'print_mode': True,
    }
    
    # Render to string for PDF generation if needed
    html = render_to_string('dashboard/print.html', context, request)
    
    return render(request, 'dashboard/print.html', context)

def get_widget_data(data_source, request):
    """Get data for a widget based on its data source"""
    data_functions = {
        'inventory_summary': get_inventory_summary,
        'sales_summary': get_sales_summary,
        'purchase_summary': get_purchase_summary,
        'manufacturing_summary': get_manufacturing_summary,
        'low_stock_alerts': get_low_stock_alerts,
        'recent_activities': get_recent_activities,
    }
    
    if data_source in data_functions:
        return data_functions[data_source](request)
    return {}

def get_inventory_summary(request):
    """Get inventory summary data"""
    total_products = Product.objects.count()
    low_stock_items = Inventory.objects.filter(
        quantity__lte=models.F('product__reorder_level')
    ).count()
    
    return {
        'total_products': total_products,
        'low_stock_items': low_stock_items,
    }

def get_sales_summary(request):
    """Get sales summary data"""
    from django.db.models import Sum
    from datetime import datetime, timedelta
    
    # Get data for last 30 days
    thirty_days_ago = datetime.now() - timedelta(days=30)
    
    total_orders = SalesOrder.objects.filter(
        order_date__gte=thirty_days_ago
    ).count()
    
    total_value = SalesOrder.objects.filter(
        order_date__gte=thirty_days_ago
    ).aggregate(
        total=Sum('total_amount')
    )['total'] or 0
    
    return {
        'total_orders': total_orders,
        'total_value': total_value,
    }

def get_purchase_summary(request):
    """Get purchase summary data"""
    from django.db.models import Sum
    from datetime import datetime, timedelta
    
    # Get data for last 30 days
    thirty_days_ago = datetime.now() - timedelta(days=30)
    
    total_orders = PurchaseOrder.objects.filter(
        order_date__gte=thirty_days_ago
    ).count()
    
    total_value = PurchaseOrder.objects.filter(
        order_date__gte=thirty_days_ago
    ).aggregate(
        total=Sum('total_amount')
    )['total'] or 0
    
    return {
        'total_orders': total_orders,
        'total_value': total_value,
    }

def get_manufacturing_summary(request):
    """Get manufacturing summary data"""
    total_orders = ManufacturingOrder.objects.count()
    completed_orders = ManufacturingOrder.objects.filter(
        status='completed'
    ).count()
    
    return {
        'total_orders': total_orders,
        'completed_orders': completed_orders,
    }

def get_low_stock_alerts(request):
    """Get low stock alerts"""
    from django.db.models import F
    
    low_stock_items = Inventory.objects.filter(
        quantity__lte=F('product__reorder_level')
    ).select_related('product', 'warehouse')[:10]  # Limit to 10 items
    
    return {
        'items': low_stock_items,
    }

def get_recent_activities(request):
    """Get recent activities"""
    # This would integrate with an activity logging system
    # For now, return empty data
    return {
        'activities': [],
    }

Dashboard Templates

3. Main Dashboard Template

<!-- templates/dashboard/home.html -->

{% extends 'base.html' %}

{% block title %}Dashboard - Manufacturing App{% endblock %}

{% block content %}
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
    <h1 class="h2">Dashboard</h1>
    <div class="btn-toolbar mb-2 mb-md-0">
        <button type="button" class="btn btn-sm btn-outline-secondary me-2" 
                onclick="window.print()">
            <i class="fas fa-print"></i> Print Dashboard
        </button>
        <button type="button" class="btn btn-sm btn-outline-secondary"
                id="customize-dashboard">
            <i class="fas fa-cog"></i> Customize
        </button>
    </div>
</div>

<!-- Dashboard Grid -->
<div class="dashboard-grid" id="dashboard-grid">
    {% for widget_data in widgets_data %}
    <div class="dashboard-widget" 
         data-widget-id="{{ widget_data.widget.id }}"
         data-position="{{ widget_data.position|json_script }}">
        {% include widget_data.widget.template_name with data=widget_data.data config=widget_data.config %}
    </div>
    {% empty %}
    <div class="col-12">
        <div class="alert alert-info">
            <h4>No widgets configured</h4>
            <p>You don't have any dashboard widgets configured yet.</p>
        </div>
    </div>
    {% endfor %}
</div>
{% endblock %}

{% block extra_css %}
<style>
.dashboard-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 1rem;
    margin-bottom: 2rem;
}

.dashboard-widget {
    background: white;
    border: 1px solid #dee2e6;
    border-radius: 0.375rem;
    padding: 1rem;
    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}

.widget-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid #dee2e6;
}

.widget-title {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 500;
}

.widget-actions {
    display: flex;
    gap: 0.5rem;
}

.widget-content {
    min-height: 150px;
}
</style>
{% endblock %}

{% block extra_js %}
<script>
// Dashboard customization functionality
document.getElementById('customize-dashboard').addEventListener('click', function() {
    window.location.href = "{% url 'dashboard:configure_widgets' %}";
});

// Make widgets sortable
// This would require a JavaScript library like SortableJS
// Implementation details would go here
</script>
{% endblock %}

4. Widget Templates

KPI Widget Template

<!-- templates/dashboard/widgets/kpi_widget.html -->

<div class="card">
    <div class="card-body">
        <div class="widget-header">
            <h5 class="widget-title">{{ widget.title }}</h5>
            <div class="widget-actions">
                <button class="btn btn-sm btn-outline-secondary" 
                        onclick="refreshWidget({{ widget.id }})">
                    <i class="fas fa-sync"></i>
                </button>
            </div>
        <div class="widget-content">
            {% if data.total_products %}
            <div class="row text-center">
                <div class="col">
                    <div class="display-6">{{ data.total_products }}</div>
                    <div class="text-muted">Total Products</div>
                </div>
                <div class="col">
                    <div class="display-6">{{ data.low_stock_items }}</div>
                    <div class="text-muted">Low Stock Items</div>
                </div>
            {% else %}
            <div class="text-center text-muted">
                No data available
            </div>
            {% endif %}
        </div>
    </div>
</div>

Chart Widget Template

<!-- templates/dashboard/widgets/chart_widget.html -->

<div class="card">
    <div class="card-body">
        <div class="widget-header">
            <h5 class="widget-title">{{ widget.title }}</h5>
        </div>
        <div class="widget-content">
            {% if data.chart_data %}
            <canvas id="chart-{{ widget.id }}" width="400" height="200"></canvas>
            {% else %}
            <div class="text-center text-muted">
                No data available for chart
            </div>
            {% endif %}
        </div>
    </div>
</div>

{% block extra_js %}
{% if data.chart_data %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
    var ctx = document.getElementById('chart-{{ widget.id }}').getContext('2d');
    var chart = new Chart(ctx, {
        type: '{{ config.chart_type|default:"bar" }}',
        data: {
            labels: {{ data.chart_labels|safe }},
            datasets: [{
                label: '{{ widget.title }}',
                data: {{ data.chart_data|safe }},
                backgroundColor: 'rgba(54, 162, 235, 0.2)',
                borderColor: 'rgba(54, 162, 235, 1)',
                borderWidth: 1
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            scales: {
                y: {
                    beginAtZero: true
                }
            }
        }
    });
});
</script>
{% endif %}
{% endblock %}

Table Widget Template

<!-- templates/dashboard/widgets/table_widget.html -->

<div class="card">
    <div class="card-body">
        <div class="widget-header">
            <h5 class="widget-title">{{ widget.title }}</h5>
        </div>
        <div class="widget-content">
            {% if data.items %}
            <div class="table-responsive">
                <table class="table table-striped table-hover">
                    <thead>
                        <tr>
                            <th>Product</th>
                            <th>Warehouse</th>
                            <th>Quantity</th>
                            <th>Reorder Level</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for item in data.items %}
                        <tr>
                            <td>{{ item.product.name }}</td>
                            <td>{{ item.warehouse.name }}</td>
                            <td>{{ item.quantity }}</td>
                            <td>{{ item.product.reorder_level }}</td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
            {% else %}
            <div class="text-center text-muted">
                No data to display
            </div>
            {% endif %}
        </div>
    </div>
</div>

5. Printable Dashboard Template

<!-- templates/dashboard/print.html -->

{% extends 'base_print.html' %}

{% block title %}Dashboard - Manufacturing App{% endblock %}

{% block content %}
<div class="print-header text-center mb-4">
    <h1>Manufacturing Dashboard</h1>
    <p>Printed on: {% now "d M Y H:i" %}</p>
    <p>Printed by: {{ request.user.get_full_name|default:request.user.username }}</p>
</div>

<div class="dashboard-widgets">
    {% for widget_data in widgets_data %}
    <div class="widget-print mb-4">
        <h3>{{ widget_data.widget.title }}</h3>
        <div class="widget-content">
            {% if widget_data.widget.widget_type == 'kpi' %}
                {% include 'dashboard/widgets/kpi_widget_print.html' with data=widget_data.data %}
            {% elif widget_data.widget.widget_type == 'table' %}
                {% include 'dashboard/widgets/table_widget_print.html' with data=widget_data.data %}
            {% else %}
                <!-- Default rendering for other widget types -->
                <pre>{{ widget_data.data|pprint }}</pre>
            {% endif %}
        </div>
    {% endfor %}
</div>

<div class="print-footer text-center mt-4">
    <hr>
    <p>Manufacturing App - Confidential</p>
</div>
{% endblock %}

{% block extra_css %}
<style>
/* Print-specific styles */
body {
    font-family: Arial, sans-serif;
    font-size: 12pt;
    line-height: 1.4;
}

.print-header h1 {
    color: #333;
    margin-bottom: 0.5rem;
}

.widget-print {
    page-break-inside: avoid;
    margin-bottom: 2rem;
}

.widget-print h3 {
    border-bottom: 2px solid #333;
    padding-bottom: 0.5rem;
    margin-bottom: 1rem;
    color: #555;
}

.table-print {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 1rem;
}

.table-print th,
.table-print td {
    border: 1px solid #333;
    padding: 6px;
    text-align: left;
}

.table-print th {
    background-color: #f0f0f0;
    font-weight: bold;
}

.display-6 {
    font-size: 2rem;
    font-weight: bold;
}

.text-muted {
    color: #666;
}

@media print {
    .no-print {
        display: none !important;
    }
    
    body {
        font-size: 10pt;
    }
    
    .widget-print {
        page-break-inside: avoid;
    }
    
    /* Ensure charts are visible in print */
    canvas {
        max-width: 100% !important;
        height: auto !important;
    }
}
</style>
{% endblock %}

Dashboard URLs

6. Dashboard URL Configuration

# dashboard/urls.py

from django.urls import path
from . import views

app_name = 'dashboard'
urlpatterns = [
    path('', views.home, name='home'),
    path('print/', views.print_dashboard, name='print_dashboard'),
    path('widgets/configure/', views.configure_widgets, name='configure_widgets'),
    path('widgets/<int:widget_id>/toggle/', views.toggle_widget, name='toggle_widget'),
    path('widgets/<int:widget_id>/position/', views.update_widget_position, name='update_widget_position'),
]

Dashboard Configuration Views

7. Widget Configuration Views

# dashboard/views.py (additional views)

@login_required
def configure_widgets(request):
    """Configure dashboard widgets"""
    user_dashboard, created = UserDashboard.objects.get_or_create(
        user=request.user
    )
    
    # Get all available widgets
    all_widgets = DashboardWidget.objects.filter(is_active=True)
    
    # Get user's widgets
    user_widgets = UserDashboardWidget.objects.filter(
        user_dashboard=user_dashboard
    ).select_related('widget')
    
    # Create a set of user's widget IDs for easy checking
    user_widget_ids = set(uw.widget.id for uw in user_widgets)
    
    context = {
        'all_widgets': all_widgets,
        'user_widgets': user_widgets,
        'user_widget_ids': user_widget_ids,
        'user_dashboard': user_dashboard,
    }
    
    return render(request, 'dashboard/configure.html', context)

@login_required
def toggle_widget(request, widget_id):
    """Toggle widget visibility"""
    if request.method == 'POST':
        user_dashboard, created = UserDashboard.objects.get_or_create(
            user=request.user
        )
        
        widget = get_object_or_404(DashboardWidget, id=widget_id)
        
        # Get or create the user widget relationship
        user_widget, created = UserDashboardWidget.objects.get_or_create(
            user_dashboard=user_dashboard,
            widget=widget
        )
        
        # Toggle visibility
        user_widget.is_visible = not user_widget.is_visible
        user_widget.save()
        
        messages.success(request, 
                        f"Widget {'enabled' if user_widget.is_visible else 'disabled'} successfully")
        
        return redirect('dashboard:configure_widgets')
    
    return redirect('dashboard:home')

@login_required
def update_widget_position(request, widget_id):
    """Update widget position in grid"""
    if request.method == 'POST':
        user_dashboard, created = UserDashboard.objects.get_or_create(
            user=request.user
        )
        
        widget = get_object_or_404(DashboardWidget, id=widget_id)
        
        # Get or create the user widget relationship
        user_widget, created = UserDashboardWidget.objects.get_or_create(
            user_dashboard=user_dashboard,
            widget=widget
        )
        
        # Update position from POST data
        position = {
            'x': request.POST.get('x', 0),
            'y': request.POST.get('y', 0),
            'w': request.POST.get('w', 1),
            'h': request.POST.get('h', 1),
        }
        
        user_widget.position = position
        user_widget.save()
        
        return JsonResponse({'status': 'success'})
    
    return JsonResponse({'status': 'error'})

Dashboard Configuration Template

8. Widget Configuration Template

<!-- templates/dashboard/configure.html -->

{% extends 'base.html' %}

{% block title %}Configure Dashboard - Manufacturing App{% endblock %}

{% block content %}
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
    <h1 class="h2">Configure Dashboard</h1>
    <div class="btn-toolbar mb-2 mb-md-0">
        <a href="{% url 'dashboard:home' %}" class="btn btn-sm btn-outline-secondary">
            <i class="fas fa-arrow-left"></i> Back to Dashboard
        </a>
    </div>
</div>

<div class="row">
    <div class="col-md-8">
        <div class="card">
            <div class="card-header">
                <h5 class="card-title mb-0">Available Widgets</h5>
            </div>
            <div class="card-body">
                <div class="row">
                    {% for widget in all_widgets %}
                    <div class="col-md-6 mb-3">
                        <div class="card">
                            <div class="card-body">
                                <h6 class="card-title">{{ widget.title }}</h6>
                                <p class="card-text">{{ widget.description|truncatewords:15 }}</p>
                                <div class="form-check form-switch">
                                    <input class="form-check-input" type="checkbox" 
                                           id="widget-{{ widget.id }}"
                                           {% if widget.id in user_widget_ids %}checked{% endif %}
                                           onchange="toggleWidget({{ widget.id }}, this.checked)">
                                    <label class="form-check-label" for="widget-{{ widget.id }}">
                                        {% if widget.id in user_widget_ids %}Enabled{% else %}Disabled{% endif %}
                                    </label>
                                </div>
                            </div>
                        </div>
                    </div>
                    {% endfor %}
                </div>
            </div>
        </div>
    </div>
    
    <div class="col-md-4">
        <div class="card">
            <div class="card-header">
                <h5 class="card-title mb-0">Your Dashboard</h5>
            </div>
            <div class="card-body">
                <h6>Enabled Widgets ({{ user_widgets|length }})</h6>
                <ul class="list-group">
                    {% for user_widget in user_widgets %}
                    {% if user_widget.is_visible %}
                    <li class="list-group-item d-flex justify-content-between align-items-center">
                        {{ user_widget.widget.title }}
                        <span class="badge bg-primary rounded-pill">
                            {{ user_widget.widget.widget_type }}
                        </span>
                    </li>
                    {% endif %}
                    {% empty %}
                    <li class="list-group-item">No widgets enabled</li>
                    {% endfor %}
                </ul>
            </div>
        </div>
    </div>
</div>
{% endblock %}

{% block extra_js %}
<script>
function toggleWidget(widgetId, isEnabled) {
    const url = "{% url 'dashboard:toggle_widget' 0 %}".replace('0', widgetId);
    const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
    
    fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'X-CSRFToken': csrfToken,
        },
        body: `isEnabled=${isEnabled}`
    })
    .then(response => response.json())
    .then(data => {
        if (data.status === 'success') {
            location.reload();
        } else {
            alert('Error updating widget');
        }
    })
    .catch(error => {
        console.error('Error:', error);
        alert('Error updating widget');
    });
}
</script>
{% endblock %}

This dashboard implementation plan provides a comprehensive solution for the dashboard requirements, including data visualization, customizable widgets, and printable functionality. The modular design allows for easy extension and customization based on specific business needs.