483 lines
24 KiB
HTML
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 %} |