[UPDT] DASHBOARDS: Updated dashboard designs

This commit is contained in:
Horilla
2025-06-27 15:48:21 +05:30
parent 32084d409f
commit 2880624a1c
5 changed files with 405 additions and 30 deletions

View File

@@ -515,6 +515,31 @@ class AssetAssignment(HorillaModel):
date_col=date_col,
)
def get_asset_of_offboarding_employee(self):
url = f"{reverse('asset-request-allocation-view')}?assigned_to_employee_id={self.assigned_to_employee_id.id}"
return url
def get_send_mail_employee_link(self):
if not self.assigned_to_employee_id:
return ""
url = reverse(
"send-mail-employee", kwargs={"emp_id": self.assigned_to_employee_id.id}
)
title = _("Send Mail")
html = f"""
<a
onclick="event.stopPropagation()"
hx-get="{url}"
data-toggle="oh-modal-toggle"
data-target="#sendMailModal"
title="{title}"
hx-target="#mail-content"
>
<ion-icon name="mail-outline"></ion-icon>
</a>
"""
return format_html(html)
class AssetRequest(HorillaModel):
"""

View File

@@ -4,8 +4,10 @@ This page handles the cbv methods for existing process
import re
from datetime import datetime, timedelta
from typing import Any
from django import forms
from django.apps import apps
from django.contrib import messages
from django.http import HttpResponse
from django.urls import reverse, reverse_lazy
@@ -14,6 +16,7 @@ from django.utils.translation import gettext_lazy as _
from base.context_processors import intial_notice_period
from base.methods import eval_validate
from horilla.methods import get_horilla_model_class
from horilla_views.cbv_methods import login_required, permission_required
from horilla_views.generic.cbv.pipeline import Pipeline
from horilla_views.generic.cbv.views import (
@@ -619,3 +622,132 @@ class OffboardingEmployeeList(HorillaListView):
employee_id__in=instance_ids, task_id=pk
).update(status=status)
return response
@method_decorator(login_required, name="dispatch")
class DashboardTaskListview(HorillaListView):
"""
For dashboard task status table
"""
# view_id = "view-container"
model = OffboardingEmployee
filter_class = PipelineEmployeeFilter
bulk_select_option = False
view_id = "dashboard_task_status"
def get_queryset(self):
"""
Returns a filtered queryset of records assigned to a specific employee
"""
qs = OffboardingEmployee.objects.entire()
queryset = super().get_queryset(queryset=qs)
return queryset
columns = [
("Employee", "employee_id", "employee_id__get_avatar"),
("Stage", "stage_id"),
("Task Status", "get_task_status_col"),
]
if apps.is_installed("asset"):
@method_decorator(login_required, name="dispatch")
@method_decorator(
any_manager_can_enter("offboarding.view_offboarding"), name="dispatch"
)
class DashboardNotReturndAsssets(HorillaListView):
"""
For dashboard task status table
"""
# view_id = "view-container"
AssetAssignment = get_horilla_model_class(
app_label="asset", model="assetassignment"
)
model = AssetAssignment
bulk_select_option = False
view_id = "dashboard_task_status"
def get_queryset(self):
"""
Returns a filtered queryset of records assigned to a specific employee
"""
offboarding_employees = OffboardingEmployee.objects.entire().values_list(
"employee_id__id", flat=True
)
qs = self.model.objects.entire().filter(
return_status__isnull=True,
assigned_to_employee_id__in=offboarding_employees,
)
queryset = (
super().get_queryset().filter(id__in=qs.values_list("id", flat=True))
)
return queryset
columns = [
(
"Employee",
"assigned_to_employee_id__get_full_name",
"assigned_to_employee_id__get_avatar",
),
("Asset", "asset_id__asset_name"),
("Reminder", "get_send_mail_employee_link"),
]
row_attrs = """
onclick="
localStorage.setItem('activeTabAsset','#tab_2');
window.location.href='{get_asset_of_offboarding_employee}'"
"""
if apps.is_installed("pms"):
@method_decorator(login_required, name="dispatch")
@method_decorator(
any_manager_can_enter("offboarding.view_offboarding"), name="dispatch"
)
class DashboardFeedbackView(HorillaListView):
"""
For dashboard task status table
"""
# view_id = "view-container"
Feedback = get_horilla_model_class(app_label="pms", model="feedback")
model = Feedback
bulk_select_option = False
view_id = "dashboard_task_status"
def get_queryset(self):
"""
Returns a filtered queryset of records assigned to a specific employee
"""
offboarding_employees = OffboardingEmployee.objects.entire().values_list(
"employee_id__id", "notice_period_starts"
)
if offboarding_employees:
id_list, date_list = map(list, zip(*offboarding_employees))
else:
id_list, date_list = [], []
qs = (
self.model.objects.entire()
.filter(employee_id__in=id_list)
.exclude(status="Closed")
)
queryset = (
super().get_queryset().filter(id__in=qs.values_list("id", flat=True))
)
return queryset
columns = [
("Employee", "employee_id__get_full_name", "employee_id__get_avatar"),
("Feedback", "review_cycle"),
("Status", "status"),
]

View File

@@ -11,29 +11,76 @@ $(document).ready(function () {
if (departmentChart) {
departmentChart.destroy();
}
// Extract colors and create visibility array for dynamic datasets
const colors = ['#a5b4fc', '#fca5a5', '#fdba74', '#34d399', '#fbbf24', '#c084fc', '#fb7185'];
const visibility = data.datasets.map(() => true);
// Apply the new styling to datasets
const styledDatasets = data.datasets.map((dataset, index) => ({
...dataset,
backgroundColor: colors[index % colors.length],
borderRadius: 20,
barPercentage: 0.6,
categoryPercentage: 0.6
}));
departmentChart = new Chart(ctx, {
type: "bar",
data: {
labels: data.labels,
datasets: data.datasets,
datasets: styledDatasets
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: { enabled: true }
},
scales: {
x: {
stacked: true,
ticks: { display: true },
grid: { display: false },
border: { display: true, color: '#d1d5db' }
},
y: {
stacked: true,
},
},
},
plugins: [
{
afterRender: (chart) => emptyChart(chart),
},
],
beginAtZero: true,
ticks: { stepSize: 10 },
grid: { drawBorder: false, color: '#e5e7eb' }
}
}
}
});
// Create clickable legend dynamically for multiple datasets
const legendContainer = $("#departmentLegend");
legendContainer.empty(); // Clear existing legend
data.datasets.forEach((dataset, i) => {
const item = $(`
<div class="flex items-center gap-2 cursor-pointer legend-item" data-index="${i}">
<span class="w-4 h-4 rounded-full inline-block legend-dot" style="background:${colors[i % colors.length]}; transition: 0.3s;"></span>
<span class="legend-text">${dataset.label}</span>
</div>
`);
item.on('click', function () {
const index = parseInt($(this).data('index'));
visibility[index] = !visibility[index];
// Use Chart.js built-in visibility toggle
departmentChart.setDatasetVisibility(index, visibility[index]);
departmentChart.update();
// Update legend visuals
const dot = $(this).find('.legend-dot');
const text = $(this).find('.legend-text');
dot.css('opacity', visibility[index] ? '1' : '0.4');
text.css('text-decoration', visibility[index] ? 'none' : 'line-through');
});
legendContainer.append(item);
});
},
error: function (xhr, status, error) {
@@ -51,30 +98,88 @@ $(document).ready(function () {
if (joinChart) {
joinChart.destroy();
}
// Colors for join chart
const colors = ['#a5b4fc', '#fca5a5', '#fdba74', '#34d399', '#fbbf24', '#c084fc', '#fb7185'];
// Style the dataset based on chart type
const styledDataset = {
label: "Employees",
data: data.items,
backgroundColor: type === 'line' ? '#a5b4fc' : colors.slice(0, data.items.length),
borderColor: type === 'line' ? '#a5b4fc' : undefined,
borderWidth: type === 'line' ? 2 : undefined,
borderRadius: (type === 'bar') ? 20 : undefined,
barPercentage: (type === 'bar') ? 0.6 : undefined,
categoryPercentage: (type === 'bar') ? 0.6 : undefined,
fill: type === 'line' ? false : undefined,
tension: type === 'line' ? 0.4 : undefined
};
joinChart = new Chart(ctx, {
type: type,
data: {
labels: data.labels,
datasets: [
{
label: "Employees",
data: data.items,
},
],
datasets: [styledDataset],
},
options: {
responsive: true,
maintainAspectRatio: false,
},
plugins: [
{
afterRender: (chart) => emptyChart(chart),
plugins: {
// Disable default legend for all chart types
legend: { display: false },
tooltip: { enabled: true }
},
],
scales: (type === 'pie' || type === 'doughnut') ? {} : {
x: {
ticks: { display: true },
grid: { display: false },
border: { display: true, color: '#d1d5db' }
},
y: {
beginAtZero: true,
ticks: { stepSize: 10 },
grid: { drawBorder: false, color: '#e5e7eb' }
}
}
}
});
// Create custom legend for all chart types
const joinLegendContainer = $("#joinLegend");
joinLegendContainer.empty(); // Clear existing legend
data.labels.forEach((label, i) => {
const item = $(`
<div class="flex items-center gap-2 cursor-pointer legend-item" data-index="${i}">
<span class="w-4 h-4 rounded-full inline-block legend-dot" style="background:${colors[i % colors.length]}; transition: 0.3s;"></span>
<span class="legend-text">${label}</span>
</div>
`);
item.on('click', function () {
const index = parseInt($(this).data('index'));
const currentData = [...joinChart.data.datasets[0].data];
const originalValue = data.items[index];
// Toggle visibility by setting data to 0 or original value
currentData[index] = currentData[index] === 0 ? originalValue : 0;
joinChart.data.datasets[0].data = currentData;
joinChart.update();
// Update legend visuals
const dot = $(this).find('.legend-dot');
const text = $(this).find('.legend-text');
const isVisible = currentData[index] !== 0;
dot.css('opacity', isVisible ? '1' : '0.4');
text.css('text-decoration', isVisible ? 'none' : 'line-through');
});
joinLegendContainer.append(item);
});
},
error: function (xhr, status, error) {
console.error("Error fetching department chart data:", error);
console.error("Error fetching join chart data:", error);
},
});
};
@@ -93,6 +198,105 @@ $(document).ready(function () {
join_chart(chartType);
});
department_chart("pie");
department_chart();
join_chart("bar");
});
// $(document).ready(function () {
// var departmentChart;
// var joinChart;
// var department_chart = () => {
// $.ajax({
// url: "/offboarding/dashboard-department-chart",
// type: "GET",
// success: function (data) {
// var ctx = $("#departmentChart");
// if (departmentChart) {
// departmentChart.destroy();
// }
// departmentChart = new Chart(ctx, {
// type: "bar",
// data: {
// labels: data.labels,
// datasets: data.datasets,
// },
// options: {
// responsive: true,
// maintainAspectRatio: false,
// scales: {
// x: {
// stacked: true,
// },
// y: {
// stacked: true,
// },
// },
// },
// plugins: [
// {
// afterRender: (chart) => emptyChart(chart),
// },
// ],
// });
// },
// error: function (xhr, status, error) {
// console.error("Error fetching department chart data:", error);
// },
// });
// };
// var join_chart = (type) => {
// $.ajax({
// url: "/offboarding/dashboard-join-chart",
// type: "GET",
// success: function (data) {
// var ctx = $("#joinChart");
// if (joinChart) {
// joinChart.destroy();
// }
// joinChart = new Chart(ctx, {
// type: type,
// data: {
// labels: data.labels,
// datasets: [
// {
// label: "Employees",
// data: data.items,
// },
// ],
// },
// options: {
// responsive: true,
// maintainAspectRatio: false,
// },
// plugins: [
// {
// afterRender: (chart) => emptyChart(chart),
// },
// ],
// });
// },
// error: function (xhr, status, error) {
// console.error("Error fetching department chart data:", error);
// },
// });
// };
// $("#joinChartChange").click(function (e) {
// var chartType = joinChart.config.type;
// if (chartType === "line") {
// chartType = "bar";
// } else if (chartType === "bar") {
// chartType = "doughnut";
// } else if (chartType === "doughnut") {
// chartType = "pie";
// } else if (chartType === "pie") {
// chartType = "line";
// }
// join_chart(chartType);
// });
// department_chart("pie");
// join_chart("bar");
// });

View File

@@ -256,6 +256,11 @@ urlpatterns = [
views.dashboard_join_chart,
name="dashboard-join-chart",
),
path(
"list-dashboard-task-status/",
exit_process.DashboardTaskListview.as_view(),
name="list-dashboard-task-status",
),
]
if apps.is_installed("asset"):
@@ -265,8 +270,14 @@ if apps.is_installed("asset"):
views.dashboard_asset_table,
name="dashboard-asset-table",
),
path(
"dashboard-asset-table-cbv",
exit_process.DashboardNotReturndAsssets.as_view(),
name="dashboard-asset-table-cbv",
),
]
if apps.is_installed("pms"):
urlpatterns += [
path(
@@ -274,4 +285,9 @@ if apps.is_installed("pms"):
views.dashboard_feedback_table,
name="dashboard-feedback-table",
),
path(
"dashboard-feedback-table-cbv/",
exit_process.DashboardFeedbackView.as_view(),
name="dashboard-feedback-table-cbv",
),
]

View File

@@ -34,7 +34,7 @@ $(document).ready(function(){
backgroundColor: dataset.backgroundColor || defaultColors[index % defaultColors.length],
borderRadius: 20,
barPercentage: 0.9,
categoryPercentage: 0.9
categoryPercentage: 0.6
}));
const data = {
@@ -59,8 +59,7 @@ $(document).ready(function(){
scales: {
y: {
beginAtZero: true,
max: Math.max(...dataSet.flatMap(set => set.data)) + 20,
ticks: { stepSize: 20 },
ticks: { stepSize: 5 },
grid: { drawBorder: false, color: '#e5e7eb' }
},
x: {
@@ -167,7 +166,7 @@ $(document).ready(function(){
backgroundColor: dataset.backgroundColor || defaultColors[index % defaultColors.length],
borderRadius: 20,
barPercentage: 0.9,
categoryPercentage: 0.9
categoryPercentage: 0.6
}));
const data = {
@@ -192,8 +191,7 @@ $(document).ready(function(){
scales: {
y: {
beginAtZero: true,
max: Math.max(...dataSet.flatMap(set => set.data)) + 20,
ticks: { stepSize: 20 },
ticks: { stepSize: 5 },
grid: { drawBorder: false, color: '#e5e7eb' }
},
x: {