448 lines
18 KiB
HTML
448 lines
18 KiB
HTML
{% extends 'layouts/base.html' %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}My Dashboard - PX360{% endblock %}
|
|
|
|
{% block page_title %}My Dashboard{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Summary Cards -->
|
|
<div class="row g-3 mb-4">
|
|
<!-- Total Items -->
|
|
<div class="col-lg-2 col-md-4 col-6">
|
|
<div class="card table-card border-primary">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="bi bi-inbox fs-2 text-primary"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h3 class="mb-0">{{ total_stats.total }}</h3>
|
|
<p class="mb-0 text-muted small">Total Items</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Open -->
|
|
<div class="col-lg-2 col-md-4 col-6">
|
|
<div class="card table-card border-warning">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="bi bi-envelope-open fs-2 text-warning"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h3 class="mb-0">{{ total_stats.open }}</h3>
|
|
<p class="mb-0 text-muted small">Open</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- In Progress -->
|
|
<div class="col-lg-2 col-md-4 col-6">
|
|
<div class="card table-card border-info">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="bi bi-hourglass-split fs-2 text-info"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h3 class="mb-0">{{ total_stats.in_progress }}</h3>
|
|
<p class="mb-0 text-muted small">In Progress</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resolved -->
|
|
<div class="col-lg-2 col-md-4 col-6">
|
|
<div class="card table-card border-success">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="bi bi-check-circle fs-2 text-success"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h3 class="mb-0">{{ total_stats.resolved }}</h3>
|
|
<p class="mb-0 text-muted small">Resolved</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Closed -->
|
|
<div class="col-lg-2 col-md-4 col-6">
|
|
<div class="card table-card border-secondary">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="bi bi-x-circle fs-2 text-secondary"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h3 class="mb-0">{{ total_stats.closed }}</h3>
|
|
<p class="mb-0 text-muted small">Closed</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Overdue -->
|
|
<div class="col-lg-2 col-md-4 col-6">
|
|
<div class="card table-card border-danger">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="bi bi-exclamation-triangle fs-2 text-danger"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h3 class="mb-0">{{ total_stats.overdue }}</h3>
|
|
<p class="mb-0 text-muted small">Overdue</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Chart Row -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12">
|
|
<div class="card table-card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0"><i class="bi bi-graph-up me-2"></i>{% trans "Completion Trend (Last 30 Days)" %}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="completionTrendChart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filter Bar -->
|
|
<div class="card table-card mb-4">
|
|
<div class="card-body">
|
|
<form method="GET" class="row g-3">
|
|
<div class="col-md-3">
|
|
<label class="form-label">Date Range</label>
|
|
<select name="date_range" class="form-select">
|
|
<option value="7" {% if date_range == 7 %}selected{% endif %}>Last 7 days</option>
|
|
<option value="30" {% if date_range == 30 %}selected{% endif %}>Last 30 days</option>
|
|
<option value="90" {% if date_range == 90 %}selected{% endif %}>Last 90 days</option>
|
|
<option value="-1" {% if date_range == -1 %}selected{% endif %}>All time</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Search</label>
|
|
<input type="text" name="search" class="form-control" value="{{ search_query }}" placeholder="Search...">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label">Status</label>
|
|
<select name="status" class="form-select">
|
|
<option value="">All</option>
|
|
<option value="open" {% if status_filter == 'open' %}selected{% endif %}>Open</option>
|
|
<option value="in_progress" {% if status_filter == 'in_progress' %}selected{% endif %}>In Progress</option>
|
|
<option value="resolved" {% if status_filter == 'resolved' %}selected{% endif %}>Resolved</option>
|
|
<option value="closed" {% if status_filter == 'closed' %}selected{% endif %}>Closed</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label">Priority/Severity</label>
|
|
<select name="priority" class="form-select">
|
|
<option value="">All</option>
|
|
<option value="low" {% if priority_filter == 'low' %}selected{% endif %}>Low</option>
|
|
<option value="medium" {% if priority_filter == 'medium' %}selected{% endif %}>Medium</option>
|
|
<option value="high" {% if priority_filter == 'high' %}selected{% endif %}>High</option>
|
|
<option value="critical" {% if priority_filter == 'critical' %}selected{% endif %}>Critical</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2 d-flex align-items-end">
|
|
<button type="submit" class="btn btn-primary w-100">
|
|
<i class="bi bi-filter"></i> Apply Filters
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<ul class="nav nav-tabs mb-4" id="dashboardTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link {% if active_tab == 'complaints' %}active{% endif %}"
|
|
data-bs-toggle="tab" data-bs-target="#complaints-tab" type="button">
|
|
<i class="bi bi-exclamation-triangle"></i>
|
|
Complaints <span class="badge bg-danger ms-1">{{ stats.complaints.total }}</span>
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link {% if active_tab == 'inquiries' %}active{% endif %}"
|
|
data-bs-toggle="tab" data-bs-target="#inquiries-tab" type="button">
|
|
<i class="bi bi-question-circle"></i>
|
|
Inquiries <span class="badge bg-info ms-1">{{ stats.inquiries.total }}</span>
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link {% if active_tab == 'observations' %}active{% endif %}"
|
|
data-bs-toggle="tab" data-bs-target="#observations-tab" type="button">
|
|
<i class="bi bi-eye"></i>
|
|
Observations <span class="badge bg-warning ms-1">{{ stats.observations.total }}</span>
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link {% if active_tab == 'actions' %}active{% endif %}"
|
|
data-bs-toggle="tab" data-bs-target="#actions-tab" type="button">
|
|
<i class="bi bi-clipboard-check"></i>
|
|
PX Actions <span class="badge bg-primary ms-1">{{ stats.actions.total }}</span>
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link {% if active_tab == 'tasks' %}active{% endif %}"
|
|
data-bs-toggle="tab" data-bs-target="#tasks-tab" type="button">
|
|
<i class="bi bi-list-task"></i>
|
|
Tasks <span class="badge bg-secondary ms-1">{{ stats.tasks.total }}</span>
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link {% if active_tab == 'feedback' %}active{% endif %}"
|
|
data-bs-toggle="tab" data-bs-target="#feedback-tab" type="button">
|
|
<i class="bi bi-chat-dots"></i>
|
|
Feedback <span class="badge bg-success ms-1">{{ stats.feedback.total }}</span>
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="tab-content">
|
|
<!-- Complaints Tab -->
|
|
<div class="tab-pane fade {% if active_tab == 'complaints' %}show active{% endif %}" id="complaints-tab">
|
|
{% include 'dashboard/partials/complaints_table.html' with data=paginated_data.complaints stats=stats.complaints %}
|
|
</div>
|
|
|
|
<!-- Inquiries Tab -->
|
|
<div class="tab-pane fade {% if active_tab == 'inquiries' %}show active{% endif %}" id="inquiries-tab">
|
|
{% include 'dashboard/partials/inquiries_table.html' with data=paginated_data.inquiries stats=stats.inquiries %}
|
|
</div>
|
|
|
|
<!-- Observations Tab -->
|
|
<div class="tab-pane fade {% if active_tab == 'observations' %}show active{% endif %}" id="observations-tab">
|
|
{% include 'dashboard/partials/observations_table.html' with data=paginated_data.observations stats=stats.observations %}
|
|
</div>
|
|
|
|
<!-- Actions Tab -->
|
|
<div class="tab-pane fade {% if active_tab == 'actions' %}show active{% endif %}" id="actions-tab">
|
|
{% include 'dashboard/partials/actions_table.html' with data=paginated_data.actions stats=stats.actions %}
|
|
</div>
|
|
|
|
<!-- Tasks Tab -->
|
|
<div class="tab-pane fade {% if active_tab == 'tasks' %}show active{% endif %}" id="tasks-tab">
|
|
{% include 'dashboard/partials/tasks_table.html' with data=paginated_data.tasks stats=stats.tasks %}
|
|
</div>
|
|
|
|
<!-- Feedback Tab -->
|
|
<div class="tab-pane fade {% if active_tab == 'feedback' %}show active{% endif %}" id="feedback-tab">
|
|
{% include 'dashboard/partials/feedback_table.html' with data=paginated_data.feedback stats=stats.feedback %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bulk Action Modal -->
|
|
<div class="modal fade" id="bulkActionModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Bulk Action</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<input type="hidden" id="bulkActionTab">
|
|
<div class="mb-3">
|
|
<label class="form-label">Select Action</label>
|
|
<select id="bulkActionType" class="form-select">
|
|
<option value="bulk_status">Change Status</option>
|
|
<option value="bulk_assign">Assign to User</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3" id="bulkStatusContainer">
|
|
<label class="form-label">New Status</label>
|
|
<select id="bulkStatus" class="form-select">
|
|
<option value="open">Open</option>
|
|
<option value="in_progress">In Progress</option>
|
|
<option value="resolved">Resolved</option>
|
|
<option value="closed">Closed</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3 d-none" id="bulkAssignContainer">
|
|
<label class="form-label">Assign To</label>
|
|
<select id="bulkAssignUser" class="form-select">
|
|
<!-- Users will be loaded dynamically -->
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" id="executeBulkAction">Execute</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Completion Trend Chart
|
|
const chartElement = document.getElementById('completionTrendChart');
|
|
if (chartElement) {
|
|
const completionData = {{ chart_data.completion_trend|safe }};
|
|
|
|
var options = {
|
|
series: [{
|
|
name: 'Completed Items',
|
|
data: completionData.data
|
|
}],
|
|
chart: {
|
|
type: 'bar',
|
|
height: 350,
|
|
toolbar: {
|
|
show: false
|
|
}
|
|
},
|
|
plotOptions: {
|
|
bar: {
|
|
borderRadius: 4,
|
|
columnWidth: '60%'
|
|
}
|
|
},
|
|
colors: ['#22c55e'],
|
|
xaxis: {
|
|
categories: completionData.labels,
|
|
labels: {
|
|
style: {
|
|
fontSize: '12px'
|
|
}
|
|
}
|
|
},
|
|
yaxis: {
|
|
min: 0,
|
|
forceNiceScale: true,
|
|
labels: {
|
|
style: {
|
|
fontSize: '12px'
|
|
}
|
|
}
|
|
},
|
|
grid: {
|
|
borderColor: '#e7e7e7',
|
|
strokeDashArray: 5
|
|
},
|
|
tooltip: {
|
|
theme: 'light'
|
|
},
|
|
dataLabels: {
|
|
enabled: false
|
|
}
|
|
};
|
|
|
|
var chart = new ApexCharts(chartElement, options);
|
|
chart.render();
|
|
}
|
|
|
|
// Bulk action handling
|
|
const bulkActionModal = new bootstrap.Modal(document.getElementById('bulkActionModal'));
|
|
let selectedItems = [];
|
|
|
|
document.querySelectorAll('.bulk-checkbox').forEach(checkbox => {
|
|
checkbox.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
if (!selectedItems.includes(this.value)) {
|
|
selectedItems.push(this.value);
|
|
}
|
|
} else {
|
|
selectedItems = selectedItems.filter(id => id !== this.value);
|
|
}
|
|
updateBulkActionButtons();
|
|
});
|
|
});
|
|
|
|
function updateBulkActionButtons() {
|
|
const buttons = document.querySelectorAll('.bulk-action-btn');
|
|
buttons.forEach(btn => {
|
|
btn.disabled = selectedItems.length === 0;
|
|
});
|
|
}
|
|
|
|
document.querySelectorAll('.bulk-action-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const tab = this.dataset.tab;
|
|
document.getElementById('bulkActionTab').value = tab;
|
|
bulkActionModal.show();
|
|
});
|
|
});
|
|
|
|
document.getElementById('bulkActionType').addEventListener('change', function() {
|
|
if (this.value === 'bulk_status') {
|
|
document.getElementById('bulkStatusContainer').classList.remove('d-none');
|
|
document.getElementById('bulkAssignContainer').classList.add('d-none');
|
|
} else if (this.value === 'bulk_assign') {
|
|
document.getElementById('bulkStatusContainer').classList.add('d-none');
|
|
document.getElementById('bulkAssignContainer').classList.remove('d-none');
|
|
// Load users dynamically
|
|
// This is a placeholder - you'd fetch users from an API endpoint
|
|
}
|
|
});
|
|
|
|
document.getElementById('executeBulkAction').addEventListener('click', function() {
|
|
const tab = document.getElementById('bulkActionTab').value;
|
|
const actionType = document.getElementById('bulkActionType').value;
|
|
|
|
let data = {
|
|
action: actionType,
|
|
tab: tab,
|
|
item_ids: selectedItems
|
|
};
|
|
|
|
if (actionType === 'bulk_status') {
|
|
data.new_status = document.getElementById('bulkStatus').value;
|
|
} else if (actionType === 'bulk_assign') {
|
|
data.user_id = document.getElementById('bulkAssignUser').value;
|
|
}
|
|
|
|
fetch('{% url "dashboard:bulk_action" %}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
|
},
|
|
body: JSON.stringify(data)
|
|
})
|
|
.then(response => response.json())
|
|
.then(result => {
|
|
if (result.success) {
|
|
alert(`Successfully processed ${result.updated_count} items`);
|
|
location.reload();
|
|
} else {
|
|
alert('Error: ' + result.error);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('Error: ' + error.message);
|
|
});
|
|
});
|
|
|
|
// Tab switching preserves filters
|
|
document.querySelectorAll('[data-bs-toggle="tab"]').forEach(tab => {
|
|
tab.addEventListener('shown.bs.tab', function(e) {
|
|
const targetTab = e.target.getAttribute('data-bs-target').replace('-tab', '').replace('#', '');
|
|
const url = new URL(window.location);
|
|
url.searchParams.set('tab', targetTab);
|
|
window.history.pushState({}, '', url);
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %} |