diff --git a/horilla/decorators.py b/horilla/decorators.py
index 58de6be21..a36498212 100755
--- a/horilla/decorators.py
+++ b/horilla/decorators.py
@@ -258,6 +258,8 @@ def login_required(view_func):
or not employee
or not employee.is_active
):
+ if request.headers.get("HX-Request"):
+ return HttpResponse(status=204, headers={"HX-Refresh": "true"})
return redirect(redirect_url)
try:
func = view_func(request, *args, **kwargs)
diff --git a/horilla_views/templates/generic/pipeline/kanban.html b/horilla_views/templates/generic/pipeline/kanban.html
index 17148c336..e57ab1493 100644
--- a/horilla_views/templates/generic/pipeline/kanban.html
+++ b/horilla_views/templates/generic/pipeline/kanban.html
@@ -99,9 +99,9 @@
$(document).ready(function () {
// Define the viewId
var viewId = '#{{view_id}}'
-
+
let draggedCard = null
-
+
// Initialize drag events for cards
function initDragAndDrop() {
// Drag start
@@ -111,44 +111,44 @@
draggedCard.addClass('dragging')
}, 0)
})
-
+
// Drag end
$(viewId + ' .kanban-card').on('dragend', function () {
$(this).removeClass('dragging')
draggedCard = null
updateTaskCounts()
})
-
+
// Handle dragover to allow drops
$(viewId + ' .cards-container').on('dragover', function (e) {
e.preventDefault()
$(this).addClass('drop-zone')
})
-
+
// Remove drop zone highlighting
$(viewId + ' .cards-container').on('dragleave', function () {
$(this).removeClass('drop-zone')
})
-
+
// Handle drops
$(viewId + ' .cards-container').on('drop', function (e) {
e.preventDefault()
$(this).removeClass('drop-zone')
-
+
if (draggedCard) {
// Find the "Add task" button and insert before it
const addTaskButton = $(this).find('.add-task')
draggedCard.insertBefore(addTaskButton)
-
+
const itemId = draggedCard.data('id')
const column = $(this).closest('.kanban-column')
const newStage = column.data('stage')
-
+
console.log(`Item ${itemId} moved to ${newStage}`)
-
+
// Update card styling based on new column
updateCardStyling(draggedCard, newStage)
-
+
// 🔁 Replace with AJAX to update backend
/*
$.ajax({
@@ -167,28 +167,28 @@
}
})
}
-
+
// Initialize profile dropdowns for cards
function initProfileDropdowns() {
// Avatar click to show dropdown
$(viewId).on('click', '.avatar, .avatar-placeholder', function (e) {
e.stopPropagation()
const dropdown = $(this).siblings('.assignee-dropdown')
-
+
// Close all other dropdowns first
$(viewId + ' .assignee-dropdown.active')
.not(dropdown)
.removeClass('active')
-
+
// Toggle this dropdown
dropdown.toggleClass('active')
})
-
+
// Assignee selection
$(viewId).on('click', '.assignee-option', function () {
const profileContainer = $(this).closest('.profile-container')
const initials = $(this).data('initials')
-
+
if (initials === 'UN') {
// Unassign
profileContainer.find('.avatar, .avatar-placeholder').replaceWith($('
+
'))
@@ -197,39 +197,39 @@
const personName = $(this).find('span').text()
const avatar = $(this).find('.avatar').clone()
avatar.attr('title', personName)
-
+
profileContainer.find('.avatar, .avatar-placeholder').replaceWith(avatar)
}
-
+
// Close dropdown
profileContainer.find('.assignee-dropdown').removeClass('active')
})
-
+
// Close dropdowns when clicking elsewhere
$(document).on('click', function () {
$(viewId + ' .assignee-dropdown.active').removeClass('active')
})
}
-
+
// Initialize column collapse/expand functionality
function initColumnCollapse() {
$(viewId + ' .column-header').on('click', function () {
$(this).closest('.kanban-column').toggleClass('collapsed')
})
}
-
+
// Handle adding new tasks
function initAddTask() {
$(viewId + ' .add-task').on('click', function () {
const column = $(this).closest('.kanban-column')
const stage = column.data('stage')
-
+
// Create a new card
const newCard = createNewTaskCard(stage)
-
+
// Add it before the add button
$(this).before(newCard)
-
+
// Initialize drag events for the new card
newCard
.on('dragstart', function () {
@@ -243,19 +243,19 @@
draggedCard = null
updateTaskCounts()
})
-
+
// Update counts
updateTaskCounts()
-
+
console.log(`New task created in ${stage}`)
})
}
-
+
// Create a new task card
function createNewTaskCard(stage) {
// Create unique ID for the card
const newId = Date.now()
-
+
// Create the card with proper structure
const newCard = $('').attr('data-id', newId).append(`
New Task
@@ -288,13 +288,13 @@
`)
-
+
// Apply styling based on column
updateCardStyling(newCard, stage)
-
+
return newCard
}
-
+
// Update card styling based on column
function updateCardStyling(card, stage) {
if (stage === 'todo') {
@@ -314,7 +314,7 @@
})
}
}
-
+
// Update task counts for each column
function updateTaskCounts() {
$(viewId + ' .kanban-column').each(function () {
@@ -322,7 +322,7 @@
$(this).find('.task-count').text(cardCount)
})
}
-
+
// Initialize everything
function init() {
initDragAndDrop()
@@ -331,7 +331,7 @@
initAddTask()
updateTaskCounts()
}
-
+
// Run initialization
init()
})
@@ -342,9 +342,9 @@
const columns = document.querySelectorAll('.kanban-column')
const cardsContainers = document.querySelectorAll('.cards-container')
const addTaskButtons = document.querySelectorAll('.add-task')
-
+
let draggedCard = null
-
+
// Initialize card dragging
cards.forEach((card) => {
card.addEventListener('dragstart', () => {
@@ -353,19 +353,19 @@
card.classList.add('dragging')
}, 0)
})
-
+
card.addEventListener('dragend', () => {
draggedCard = null
card.classList.remove('dragging')
updateTaskCounts()
})
-
+
// Set up profile dropdown functionality
const profileContainer = card.querySelector('.profile-container')
if (profileContainer) {
const avatar = profileContainer.querySelector('.avatar, .avatar-placeholder')
const dropdown = profileContainer.querySelector('.assignee-dropdown')
-
+
avatar.addEventListener('click', (e) => {
e.stopPropagation()
// Close all other dropdowns first
@@ -376,14 +376,14 @@
})
dropdown.classList.toggle('active')
})
-
+
// Handle assignee selection
const assigneeOptions = dropdown.querySelectorAll('.assignee-option')
assigneeOptions.forEach((option) => {
option.addEventListener('click', () => {
const initials = option.getAttribute('data-initials')
const currentAvatar = profileContainer.querySelector('.avatar, .avatar-placeholder')
-
+
if (initials === 'UN') {
// Unassign
profileContainer.innerHTML = `
@@ -403,16 +403,16 @@
`
}
-
+
// Reset event listeners for the new elements
const newDropdown = profileContainer.querySelector('.assignee-dropdown')
const newAvatar = profileContainer.querySelector('.avatar, .avatar-placeholder')
-
+
newAvatar.addEventListener('click', (e) => {
e.stopPropagation()
newDropdown.classList.toggle('active')
})
-
+
// Reset assignee option event listeners
const newOptions = newDropdown.querySelectorAll('.assignee-option')
newOptions.forEach((newOpt) => {
@@ -422,13 +422,13 @@
this.click() // This is a simplified approach
})
})
-
+
dropdown.classList.remove('active')
})
})
}
})
-
+
// Handle column collapse
document.querySelectorAll('.column-header').forEach((header) => {
header.addEventListener('click', () => {
@@ -436,43 +436,43 @@
column.classList.toggle('collapsed')
})
})
-
+
// Close dropdowns when clicking outside
document.addEventListener('click', () => {
document.querySelectorAll('.assignee-dropdown.active').forEach((dropdown) => {
dropdown.classList.remove('active')
})
})
-
+
// Handle drag and drop for columns
cardsContainers.forEach((container) => {
container.addEventListener('dragover', (e) => {
e.preventDefault()
container.classList.add('drop-zone')
})
-
+
container.addEventListener('dragleave', () => {
container.classList.remove('drop-zone')
})
-
+
container.addEventListener('drop', (e) => {
e.preventDefault()
container.classList.remove('drop-zone')
-
+
if (draggedCard) {
// Find the "Add task" button and insert before it
const addTaskButton = container.querySelector('.add-task')
container.insertBefore(draggedCard, addTaskButton)
-
+
const itemId = draggedCard.getAttribute('data-id')
const column = container.closest('.kanban-column')
const newStage = column.getAttribute('data-stage')
-
+
console.log(`Item ${itemId} moved to ${newStage}`)
-
+
// Update card styling based on new column
updateCardStyling(draggedCard, newStage)
-
+
// 🔁 Replace with AJAX to update backend
/*
fetch('/api/kanban/update-stage/', {
@@ -490,21 +490,21 @@
}
})
})
-
+
// Handle add task button clicks
addTaskButtons.forEach((button) => {
button.addEventListener('click', () => {
const column = button.closest('.kanban-column')
const stage = column.getAttribute('data-stage')
const container = column.querySelector('.cards-container')
-
+
// Create a new card with a unique ID (this is just for demo purposes)
const newId = Date.now()
const newCard = document.createElement('div')
newCard.className = 'kanban-card'
newCard.setAttribute('draggable', 'true')
newCard.setAttribute('data-id', newId)
-
+
// Set the default content for the new card
newCard.innerHTML = `
New Task
@@ -514,13 +514,13 @@
Today
`
-
+
// Apply styling based on the column
updateCardStyling(newCard, stage)
-
+
// Add the new card before the add task button
container.insertBefore(newCard, button)
-
+
// Set up drag events for the new card
newCard.addEventListener('dragstart', () => {
draggedCard = newCard
@@ -528,26 +528,26 @@
newCard.classList.add('dragging')
}, 0)
})
-
+
newCard.addEventListener('dragend', () => {
draggedCard = null
newCard.classList.remove('dragging')
updateTaskCounts()
})
-
+
// Update counts
updateTaskCounts()
-
+
console.log(`New task created in ${stage}`)
})
})
-
+
// Function to update card styling based on column
function updateCardStyling(card, stage) {
// Remove all potential styling classes first
card.style.borderLeftColor = ''
card.style.background = ''
-
+
// Apply styling based on stage
if (stage === 'todo') {
card.style.borderLeftColor = '#f43f5e'
@@ -560,7 +560,7 @@
card.style.background = '#ecfdf5'
}
}
-
+
// Function to update task counts
function updateTaskCounts() {
columns.forEach((column) => {
@@ -569,7 +569,7 @@
countSpan.textContent = cards
})
}
-
+
// Initial count update
updateTaskCounts()