You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

267 lines
11 KiB

{% extends "admin/base.html" %}
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
{% block branding %}
{% include "unfold/helpers/site_branding.html" %}
{% endblock %}
{% block extrahead %}
{{ block.super }}
{% load static %}
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
<script defer src="{% static 'js/admin_row_click.js' %}"></script>
<style>
/* Clickable admin changelist rows */
#result_list tbody tr {
cursor: pointer;
transition: background-color 0.15s ease-in-out;
}
#result_list tbody tr:hover {
background-color: rgba(99, 102, 241, 0.05) !important;
}
#result_list tbody tr input,
#result_list tbody tr button,
#result_list tbody tr select,
#result_list tbody tr label,
#result_list tbody tr .actions-row {
cursor: default;
}
#result_list tbody tr .action-checkbox input {
cursor: pointer;
}
/* Clean and spacious filter_horizontal (dual listbox) layout */
.selector {
width: 100% !important;
max-width: 100% !important;
display: flex !important;
flex-direction: row !important;
align-items: stretch !important;
gap: 1.25rem !important;
margin-top: 0.5rem !important;
margin-bottom: 1.5rem !important;
}
.selector-available,
.selector-chosen {
flex: 1 1 0% !important;
min-width: 220px !important;
width: auto !important;
max-width: none !important;
display: flex !important;
flex-direction: column !important;
border-radius: 0.5rem !important;
border: 1px solid rgba(203, 213, 225, 0.8) !important;
background-color: #f8fafc !important;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05) !important;
overflow: hidden !important;
}
.dark .selector-available,
.dark .selector-chosen {
border-color: rgba(51, 65, 85, 0.8) !important;
background-color: #0f172a !important;
}
.selector-available h2,
.selector-chosen h2 {
font-size: 0.875rem !important;
font-weight: 600 !important;
padding: 0.625rem 0.875rem !important;
margin: 0 !important;
border-bottom: 1px solid rgba(203, 213, 225, 0.6) !important;
background-color: rgba(241, 245, 249, 0.6) !important;
}
.dark .selector-available h2,
.dark .selector-chosen h2 {
border-color: rgba(51, 65, 85, 0.6) !important;
background-color: rgba(30, 41, 59, 0.5) !important;
}
.selector-filter input {
width: calc(100% - 1rem) !important;
margin: 0.5rem !important;
padding: 0.4rem 0.75rem !important;
font-size: 0.8125rem !important;
border: 1px solid rgba(203, 213, 225, 0.8) !important;
border-radius: 0.375rem !important;
background-color: #ffffff !important;
}
.dark .selector-filter input {
border-color: rgba(51, 65, 85, 0.8) !important;
background-color: #1e293b !important;
color: #f8fafc !important;
}
.selector select {
flex: 1 1 auto !important;
min-height: 220px !important;
max-height: 320px !important;
height: auto !important;
width: 100% !important;
padding: 0.5rem !important;
border: none !important;
outline: none !important;
background: transparent !important;
}
.selector select option {
padding: 0.35rem 0.5rem !important;
border-radius: 0.25rem !important;
margin-bottom: 1px !important;
}
.selector-chooser {
display: flex !important;
flex-direction: column !important;
justify-content: center !important;
align-items: center !important;
gap: 0.5rem !important;
margin: 0 !important;
padding: 0 0.5rem !important;
width: 3.5rem !important;
flex-shrink: 0 !important;
}
.selector-chooser li {
list-style: none !important;
margin: 0 !important;
padding: 0 !important;
}
.selector-chooser a {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
width: 2.25rem !important;
height: 2.25rem !important;
border-radius: 0.375rem !important;
background-color: #f1f5f9 !important;
border: 1px solid #cbd5e1 !important;
color: #334155 !important;
cursor: pointer !important;
transition: all 0.15s ease-in-out !important;
}
.selector-chooser a:hover {
background-color: #6366f1 !important;
border-color: #6366f1 !important;
color: #ffffff !important;
}
.dark .selector-chooser a {
background-color: #1e293b !important;
border-color: #334155 !important;
color: #cbd5e1 !important;
}
.dark .selector-chooser a:hover {
background-color: #6366f1 !important;
border-color: #6366f1 !important;
color: #ffffff !important;
}
.selector-chooseall,
.selector-clearall {
display: block !important;
padding: 0.4rem !important;
font-size: 0.75rem !important;
font-weight: 500 !important;
text-align: center !important;
border-top: 1px solid rgba(203, 213, 225, 0.6) !important;
}
.dark .selector-chooseall,
.dark .selector-clearall {
border-color: rgba(51, 65, 85, 0.6) !important;
}
@media (max-width: 768px) {
.selector {
flex-direction: column !important;
}
.selector-chooser {
flex-direction: row !important;
width: 100% !important;
padding: 0.5rem 0 !important;
}
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function () {
const resultTable = document.getElementById('result_list');
if (!resultTable) return;
resultTable.addEventListener('click', function (e) {
const target = e.target;
// Ignore interactive controls like checkboxes, action selects, buttons, and row expanders
if (target.closest('input, button, select, textarea, label, [data-action], .actions-row, [data-label="Expand row"]')) {
return;
}
// If user clicks a standard hyperlink, allow native browser navigation
const clickedLink = target.closest('a');
if (clickedLink && clickedLink.getAttribute('href') && clickedLink.getAttribute('href') !== '#') {
return;
}
const tr = target.closest('tbody tr');
if (!tr) return;
// Find the change/details link in this row
const changeLink = tr.querySelector('a[href*="/change/"], th a, td a.text-font-important-light, td a[href]');
if (changeLink && changeLink.href) {
if (e.ctrlKey || e.metaKey) {
window.open(changeLink.href, '_blank');
} else {
window.location.href = changeLink.href;
}
}
});
});
// Modern Dual-Listbox with Real-time Counters and Badges
(function() {
function setupListboxEnhancements() {
const selectors = document.querySelectorAll('.selector');
if (!selectors.length) return;
selectors.forEach(selector => {
const availableSelect = selector.querySelector('.selector-available select');
const chosenSelect = selector.querySelector('.selector-chosen select');
const availableH2 = selector.querySelector('.selector-available h2');
const chosenH2 = selector.querySelector('.selector-chosen h2');
function updateCounters() {
if (availableSelect && availableH2) {
const count = availableSelect.options.length;
let badge = availableH2.querySelector('.counter-badge');
if (!badge) {
badge = document.createElement('span');
badge.className = 'counter-badge text-xs px-2.5 py-0.5 rounded-full font-medium ml-2.5 bg-slate-200 text-slate-700 dark:bg-slate-700 dark:text-slate-300';
availableH2.appendChild(badge);
}
badge.textContent = count + ' available';
}
if (chosenSelect && chosenH2) {
const count = chosenSelect.options.length;
let badge = chosenH2.querySelector('.counter-badge');
if (!badge) {
badge = document.createElement('span');
badge.className = 'counter-badge text-xs px-2.5 py-0.5 rounded-full font-semibold ml-2.5 bg-indigo-100 text-indigo-700 dark:bg-indigo-900/60 dark:text-indigo-300';
chosenH2.appendChild(badge);
}
badge.textContent = count + ' assigned';
}
}
updateCounters();
selector.addEventListener('click', function () {
setTimeout(updateCounters, 100);
});
if (availableSelect) availableSelect.addEventListener('change', updateCounters);
if (chosenSelect) chosenSelect.addEventListener('change', updateCounters);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupListboxEnhancements);
} else {
setupListboxEnhancements();
}
window.addEventListener('load', function() {
setTimeout(setupListboxEnhancements, 100);
});
})();
</script>
{% if plausible_domain %}
<script defer data-domain="{{ plausible_domain }}" src="https://plausible.io/js/script.js"></script>
{% endif %}
{% endblock %}