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

828 lines
24 KiB
Markdown

# 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
```python
# 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
```python
# 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
```html
<!-- 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
```html
<!-- 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
```html
<!-- 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
```html
<!-- 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
```html
<!-- 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
```python
# 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
```python
# 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
```html
<!-- 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.