HH/templates/dashboard/my_dashboard.html
2026-02-22 08:35:53 +03:00

483 lines
24 KiB
HTML

{% extends 'layouts/base.html' %}
{% load i18n %}
{% block title %}My Dashboard - PX360{% endblock %}
{% block page_title %}My Dashboard{% endblock %}
{% block content %}
<div class="space-y-6">
<!-- Summary Cards -->
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
<!-- Total Items -->
<div class="bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-2xl p-5 text-white shadow-lg shadow-indigo-200">
<div class="flex items-center gap-3 mb-3">
<div class="bg-white/20 p-2.5 rounded-xl">
<i data-lucide="inbox" class="w-5 h-5"></i>
</div>
<div class="text-sm font-medium opacity-90">Total Items</div>
</div>
<div class="text-3xl font-bold">{{ total_stats.total }}</div>
</div>
<!-- Open -->
<div class="bg-gradient-to-br from-yellow-500 to-yellow-600 rounded-2xl p-5 text-white shadow-lg shadow-yellow-200">
<div class="flex items-center gap-3 mb-3">
<div class="bg-white/20 p-2.5 rounded-xl">
<i data-lucide="mail-open" class="w-5 h-5"></i>
</div>
<div class="text-sm font-medium opacity-90">Open</div>
</div>
<div class="text-3xl font-bold">{{ total_stats.open }}</div>
</div>
<!-- In Progress -->
<div class="bg-gradient-to-br from-sky-500 to-sky-600 rounded-2xl p-5 text-white shadow-lg shadow-sky-200">
<div class="flex items-center gap-3 mb-3">
<div class="bg-white/20 p-2.5 rounded-xl">
<i data-lucide="hourglass" class="w-5 h-5"></i>
</div>
<div class="text-sm font-medium opacity-90">In Progress</div>
</div>
<div class="text-3xl font-bold">{{ total_stats.in_progress }}</div>
</div>
<!-- Resolved -->
<div class="bg-gradient-to-br from-emerald-500 to-emerald-600 rounded-2xl p-5 text-white shadow-lg shadow-emerald-200">
<div class="flex items-center gap-3 mb-3">
<div class="bg-white/20 p-2.5 rounded-xl">
<i data-lucide="check-circle" class="w-5 h-5"></i>
</div>
<div class="text-sm font-medium opacity-90">Resolved</div>
</div>
<div class="text-3xl font-bold">{{ total_stats.resolved }}</div>
</div>
<!-- Closed -->
<div class="bg-gradient-to-br from-gray-500 to-gray-600 rounded-2xl p-5 text-white shadow-lg shadow-gray-200">
<div class="flex items-center gap-3 mb-3">
<div class="bg-white/20 p-2.5 rounded-xl">
<i data-lucide="x-circle" class="w-5 h-5"></i>
</div>
<div class="text-sm font-medium opacity-90">Closed</div>
</div>
<div class="text-3xl font-bold">{{ total_stats.closed }}</div>
</div>
<!-- Overdue -->
<div class="bg-gradient-to-br from-navy to-navy rounded-2xl p-5 text-white shadow-lg shadow-blue-200">
<div class="flex items-center gap-3 mb-3">
<div class="bg-white/20 p-2.5 rounded-xl">
<i data-lucide="alert-triangle" class="w-5 h-5"></i>
</div>
<div class="text-sm font-medium opacity-90">Overdue</div>
</div>
<div class="text-3xl font-bold">{{ total_stats.overdue }}</div>
</div>
</div>
<!-- Chart Row -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-50">
<div class="p-6 border-b border-gray-100">
<h3 class="text-lg font-bold flex items-center gap-2">
<i data-lucide="trending-up" class="w-5 h-5 text-navy"></i>
{% trans "Completion Trend (Last 30 Days)" %}
</h3>
</div>
<div class="p-6">
<div id="completionTrendChart" class="h-[350px]"></div>
</div>
</div>
<!-- Filter Bar -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-50 p-6">
<form method="GET" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Date Range" %}</label>
<select name="date_range" class="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-navy focus:ring-2 focus:ring-navy/20 outline-none transition">
<option value="7" {% if date_range == 7 %}selected{% endif %}>{% trans "Last 7 days" %}</option>
<option value="30" {% if date_range == 30 %}selected{% endif %}>{% trans "Last 30 days" %}</option>
<option value="90" {% if date_range == 90 %}selected{% endif %}>{% trans "Last 90 days" %}</option>
<option value="-1" {% if date_range == -1 %}selected{% endif %}>{% trans "All time" %}</option>
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Search" %}</label>
<input type="text" name="search" class="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-navy focus:ring-2 focus:ring-navy/20 outline-none transition" value="{{ search_query }}" placeholder="{% trans 'Search...' %}">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Status" %}</label>
<select name="status" class="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-navy focus:ring-2 focus:ring-navy/20 outline-none transition">
<option value="">{% trans "All" %}</option>
<option value="open" {% if status_filter == 'open' %}selected{% endif %}>{% trans "Open" %}</option>
<option value="in_progress" {% if status_filter == 'in_progress' %}selected{% endif %}>{% trans "In Progress" %}</option>
<option value="resolved" {% if status_filter == 'resolved' %}selected{% endif %}>{% trans "Resolved" %}</option>
<option value="closed" {% if status_filter == 'closed' %}selected{% endif %}>{% trans "Closed" %}</option>
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Priority/Severity" %}</label>
<select name="priority" class="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-navy focus:ring-2 focus:ring-navy/20 outline-none transition">
<option value="">{% trans "All" %}</option>
<option value="low" {% if priority_filter == 'low' %}selected{% endif %}>{% trans "Low" %}</option>
<option value="medium" {% if priority_filter == 'medium' %}selected{% endif %}>{% trans "Medium" %}</option>
<option value="high" {% if priority_filter == 'high' %}selected{% endif %}>{% trans "High" %}</option>
<option value="critical" {% if priority_filter == 'critical' %}selected{% endif %}>{% trans "Critical" %}</option>
</select>
</div>
<div class="flex items-end">
<button type="submit" class="w-full px-6 py-2.5 bg-light0 text-white rounded-xl font-semibold hover:bg-navy transition flex items-center justify-center gap-2">
<i data-lucide="filter" class="w-4 h-4"></i>
{% trans "Apply Filters" %}
</button>
</div>
</form>
</div>
<!-- Tabs -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-50">
<div class="border-b border-gray-100">
<nav class="flex gap-1 p-2 overflow-x-auto" id="dashboardTabs">
<button class="px-5 py-2.5 rounded-xl font-semibold flex items-center gap-2 {% if active_tab == 'complaints' %}bg-light0 text-white{% else %}text-gray-500 hover:bg-gray-100{% endif %}" data-tab="complaints">
<i data-lucide="alert-triangle" class="w-4 h-4"></i>
{% trans "Complaints" %}
<span class="px-2 py-0.5 rounded-lg text-xs font-bold {% if active_tab == 'complaints' %}bg-white/30 text-white{% else %}bg-red-100 text-red-600{% endif %}">{{ stats.complaints.total }}</span>
</button>
<button class="px-5 py-2.5 rounded-xl font-semibold flex items-center gap-2 {% if active_tab == 'inquiries' %}bg-light0 text-white{% else %}text-gray-500 hover:bg-gray-100{% endif %}" data-tab="inquiries">
<i data-lucide="help-circle" class="w-4 h-4"></i>
{% trans "Inquiries" %}
<span class="px-2 py-0.5 rounded-lg text-xs font-bold {% if active_tab == 'inquiries' %}bg-white/30 text-white{% else %}bg-sky-100 text-sky-600{% endif %}">{{ stats.inquiries.total }}</span>
</button>
<button class="px-5 py-2.5 rounded-xl font-semibold flex items-center gap-2 {% if active_tab == 'observations' %}bg-light0 text-white{% else %}text-gray-500 hover:bg-gray-100{% endif %}" data-tab="observations">
<i data-lucide="eye" class="w-4 h-4"></i>
{% trans "Observations" %}
<span class="px-2 py-0.5 rounded-lg text-xs font-bold {% if active_tab == 'observations' %}bg-white/30 text-white{% else %}bg-yellow-100 text-yellow-600{% endif %}">{{ stats.observations.total }}</span>
</button>
<button class="px-5 py-2.5 rounded-xl font-semibold flex items-center gap-2 {% if active_tab == 'actions' %}bg-light0 text-white{% else %}text-gray-500 hover:bg-gray-100{% endif %}" data-tab="actions">
<i data-lucide="clipboard-check" class="w-4 h-4"></i>
{% trans "PX Actions" %}
<span class="px-2 py-0.5 rounded-lg text-xs font-bold {% if active_tab == 'actions' %}bg-white/30 text-white{% else %}bg-indigo-100 text-indigo-600{% endif %}">{{ stats.actions.total }}</span>
</button>
<button class="px-5 py-2.5 rounded-xl font-semibold flex items-center gap-2 {% if active_tab == 'tasks' %}bg-light0 text-white{% else %}text-gray-500 hover:bg-gray-100{% endif %}" data-tab="tasks">
<i data-lucide="list-todo" class="w-4 h-4"></i>
{% trans "Tasks" %}
<span class="px-2 py-0.5 rounded-lg text-xs font-bold {% if active_tab == 'tasks' %}bg-white/30 text-white{% else %}bg-gray-100 text-gray-600{% endif %}">{{ stats.tasks.total }}</span>
</button>
<button class="px-5 py-2.5 rounded-xl font-semibold flex items-center gap-2 {% if active_tab == 'feedback' %}bg-light0 text-white{% else %}text-gray-500 hover:bg-gray-100{% endif %}" data-tab="feedback">
<i data-lucide="message-square" class="w-4 h-4"></i>
{% trans "Feedback" %}
<span class="px-2 py-0.5 rounded-lg text-xs font-bold {% if active_tab == 'feedback' %}bg-white/30 text-white{% else %}bg-emerald-100 text-emerald-600{% endif %}">{{ stats.feedback.total }}</span>
</button>
</nav>
</div>
<!-- Tab Content -->
<div class="p-6">
<!-- Complaints Tab -->
<div class="tab-content {% if active_tab == 'complaints' %}block{% else %}hidden{% endif %}" id="complaints-tab">
{% include 'dashboard/partials/complaints_table.html' with data=paginated_data.complaints stats=stats.complaints %}
</div>
<!-- Inquiries Tab -->
<div class="tab-content {% if active_tab == 'inquiries' %}block{% else %}hidden{% endif %}" id="inquiries-tab">
{% include 'dashboard/partials/inquiries_table.html' with data=paginated_data.inquiries stats=stats.inquiries %}
</div>
<!-- Observations Tab -->
<div class="tab-content {% if active_tab == 'observations' %}block{% else %}hidden{% endif %}" id="observations-tab">
{% include 'dashboard/partials/observations_table.html' with data=paginated_data.observations stats=stats.observations %}
</div>
<!-- Actions Tab -->
<div class="tab-content {% if active_tab == 'actions' %}block{% else %}hidden{% endif %}" id="actions-tab">
{% include 'dashboard/partials/actions_table.html' with data=paginated_data.actions stats=stats.actions %}
</div>
<!-- Tasks Tab -->
<div class="tab-content {% if active_tab == 'tasks' %}block{% else %}hidden{% endif %}" id="tasks-tab">
{% include 'dashboard/partials/tasks_table.html' with data=paginated_data.tasks stats=stats.tasks %}
</div>
<!-- Feedback Tab -->
<div class="tab-content {% if active_tab == 'feedback' %}block{% else %}hidden{% endif %}" id="feedback-tab">
{% include 'dashboard/partials/feedback_table.html' with data=paginated_data.feedback stats=stats.feedback %}
</div>
</div>
</div>
</div>
<!-- Bulk Action Modal -->
<div id="bulkActionModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50">
<div class="bg-white rounded-2xl w-full max-w-md mx-4 shadow-2xl">
<div class="flex justify-between items-center p-6 border-b border-gray-100">
<h3 class="text-xl font-bold">{% trans "Bulk Action" %}</h3>
<button onclick="closeBulkActionModal()" class="p-2 hover:bg-gray-100 rounded-xl transition">
<i data-lucide="x" class="w-5 h-5 text-gray-500"></i>
</button>
</div>
<div class="p-6">
<input type="hidden" id="bulkActionTab">
<div class="mb-4">
<label class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Select Action" %}</label>
<select id="bulkActionType" class="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-navy focus:ring-2 focus:ring-navy/20 outline-none transition">
<option value="bulk_status">{% trans "Change Status" %}</option>
<option value="bulk_assign">{% trans "Assign to User" %}</option>
</select>
</div>
<div class="mb-4" id="bulkStatusContainer">
<label class="block text-sm font-semibold text-gray-700 mb-2">{% trans "New Status" %}</label>
<select id="bulkStatus" class="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-navy focus:ring-2 focus:ring-navy/20 outline-none transition">
<option value="open">{% trans "Open" %}</option>
<option value="in_progress">{% trans "In Progress" %}</option>
<option value="resolved">{% trans "Resolved" %}</option>
<option value="closed">{% trans "Closed" %}</option>
</select>
</div>
<div class="mb-4 hidden" id="bulkAssignContainer">
<label class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Assign To" %}</label>
<select id="bulkAssignUser" class="w-full px-4 py-2.5 rounded-xl border border-gray-200 focus:border-navy focus:ring-2 focus:ring-navy/20 outline-none transition">
<!-- Users will be loaded dynamically -->
</select>
</div>
</div>
<div class="flex gap-3 p-6 border-t border-gray-100">
<button onclick="closeBulkActionModal()" class="flex-1 px-6 py-2.5 border-2 border-gray-200 text-gray-600 rounded-xl font-semibold hover:bg-gray-50 transition">
{% trans "Cancel" %}
</button>
<button id="executeBulkAction" class="flex-1 px-6 py-2.5 bg-light0 text-white rounded-xl font-semibold hover:bg-navy transition">
{% trans "Execute" %}
</button>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
lucide.createIcons();
// Tab switching
const tabs = document.querySelectorAll('#dashboardTabs button[data-tab]');
const contents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', function() {
const tabName = this.dataset.tab;
// Update tab buttons
tabs.forEach(t => {
t.classList.remove('bg-light0', 'text-white');
t.classList.add('text-gray-500', 'hover:bg-gray-100');
const badge = t.querySelector('span');
if (badge) {
badge.classList.remove('bg-white/30', 'text-white');
badge.classList.add('bg-' + getBadgeColor(tabName) + '-100', 'text-' + getBadgeColor(tabName) + '-600');
}
});
this.classList.remove('text-gray-500', 'hover:bg-gray-100');
this.classList.add('bg-light0', 'text-white');
const currentBadge = this.querySelector('span');
if (currentBadge) {
currentBadge.classList.remove('bg-' + getBadgeColor(tabName) + '-100', 'text-' + getBadgeColor(tabName) + '-600');
currentBadge.classList.add('bg-white/30', 'text-white');
}
// Show/hide content
contents.forEach(content => {
if (content.id === `${tabName}-tab`) {
content.classList.remove('hidden');
content.classList.add('block');
} else {
content.classList.remove('block');
content.classList.add('hidden');
}
});
// Update URL
const url = new URL(window.location);
url.searchParams.set('tab', tabName);
window.history.pushState({}, '', url);
});
});
function getBadgeColor(tabName) {
const colors = {
'complaints': 'red',
'inquiries': 'sky',
'observations': 'yellow',
'actions': 'indigo',
'tasks': 'gray',
'feedback': 'emerald'
};
return colors[tabName] || 'gray';
}
// Completion Trend Chart - ApexCharts
const chartElement = document.getElementById('completionTrendChart');
if (chartElement) {
const completionData = {{ chart_data.completion_trend|safe }};
var options = {
series: [{
name: '{% trans "Completed Items" %}',
data: completionData.data
}],
chart: {
type: 'bar',
height: 350,
toolbar: {
show: false
},
fontFamily: 'Inter, sans-serif'
},
plotOptions: {
bar: {
borderRadius: 8,
columnWidth: '60%'
}
},
colors: ['#22c55e'],
xaxis: {
categories: completionData.labels,
labels: {
style: {
fontSize: '12px',
fontFamily: 'Inter, sans-serif',
colors: ['#6b7280']
}
},
axisBorder: {
show: false
},
axisTicks: {
show: false
}
},
yaxis: {
min: 0,
forceNiceScale: true,
labels: {
style: {
fontSize: '12px',
fontFamily: 'Inter, sans-serif',
colors: ['#6b7280']
}
}
},
grid: {
borderColor: '#f3f4f6',
strokeDashArray: 5
},
tooltip: {
theme: 'light',
style: {
fontSize: '12px',
fontFamily: 'Inter, sans-serif'
}
},
dataLabels: {
enabled: false
}
};
var chart = new ApexCharts(chartElement, options);
chart.render();
}
// Bulk action handling
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;
openBulkActionModal();
});
});
function openBulkActionModal() {
const modal = document.getElementById('bulkActionModal');
modal.classList.remove('hidden');
modal.classList.add('flex');
}
function closeBulkActionModal() {
const modal = document.getElementById('bulkActionModal');
modal.classList.remove('flex');
modal.classList.add('hidden');
}
document.getElementById('bulkActionType').addEventListener('change', function() {
if (this.value === 'bulk_status') {
document.getElementById('bulkStatusContainer').classList.remove('hidden');
document.getElementById('bulkAssignContainer').classList.add('hidden');
} else if (this.value === 'bulk_assign') {
document.getElementById('bulkStatusContainer').classList.add('hidden');
document.getElementById('bulkAssignContainer').classList.remove('hidden');
// 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('{% trans "Successfully processed" %} ' + result.updated_count + ' {% trans "items" %}');
location.reload();
} else {
alert('{% trans "Error" %}: ' + result.error);
}
})
.catch(error => {
alert('{% trans "Error" %}: ' + error.message);
});
});
});
</script>
{% endblock %}