828 lines
24 KiB
Markdown
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. |