WIP: Add to lot view search support and UI changes #62
13
lot/forms.py
13
lot/forms.py
|
@ -4,10 +4,21 @@ from lot.models import Lot
|
||||||
|
|
||||||
class LotsForm(forms.Form):
|
class LotsForm(forms.Form):
|
||||||
lots = forms.ModelMultipleChoiceField(
|
lots = forms.ModelMultipleChoiceField(
|
||||||
queryset=Lot.objects.all(),
|
queryset=Lot.objects.filter(archived=False),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
#grouping lots by their lot group for more readability
|
||||||
|
self.grouped_lots = {}
|
||||||
|
for lot in self.fields['lots'].queryset:
|
||||||
|
group_name = lot.type.name if lot.type else "No group"
|
||||||
|
if group_name not in self.grouped_lots:
|
||||||
|
self.grouped_lots[group_name] = []
|
||||||
|
self.grouped_lots[group_name].append(lot)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self._lots = self.cleaned_data.get("lots")
|
self._lots = self.cleaned_data.get("lots")
|
||||||
return self._lots
|
return self._lots
|
||||||
|
|
|
@ -2,37 +2,163 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.lot-item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #fff;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
.lot-item:hover {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
.lot-item.selected {
|
||||||
|
background-color: #e2f0ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3>{{ subtitle }}</h3>
|
<h3>{{ subtitle }}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-3 offset-md-9 mb-3">
|
||||||
|
<input type="text" id="searchFilter" class="form-control" placeholder="Search by name or description...">
|
||||||
|
</div>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{% for group_name, lots in form.grouped_lots.items %}
|
||||||
<table class="table">
|
<div class="card mb-1">
|
||||||
<thead>
|
<div class="card-header d-flex justify-content-between align-items-center" data-bs-toggle="collapse" data-bs-target="#collapse{{ forloop.counter }}" aria-expanded="false" aria-controls="collapse{{ forloop.counter }}" style="cursor: pointer;">
|
||||||
<tr>
|
<h5 class="mb-0">{{ group_name }} <small class="text-muted">({{ lots|length }} {% trans "Lot/s" %})</small>
|
||||||
<th>Select</th>
|
<span class="badge bg-primary ms-2" id="selectedCount{{ forloop.counter }}">0 {% trans "selected" %}</span>
|
||||||
<th>Lot Group / Lot</th>
|
</h5>
|
||||||
</tr>
|
<i class="bi bi-chevron-down"></i>
|
||||||
</thead>
|
</div>
|
||||||
<tbody>
|
<div id="collapse{{ forloop.counter }}" class="collapse">
|
||||||
{% for tag in lot_tags %}
|
<div class="card-body">
|
||||||
{% for lot in lots %}
|
{% for lot in lots %}
|
||||||
{% if lot.type == tag %}
|
<div class="form-check lot-item" data-name="{{ lot.name }}" data-code="{{ lot.code }}">
|
||||||
<tr>
|
<input class="form-check-input mt-2" type="checkbox" name="lots" value="{{ lot.id }}" id="lot{{ lot.id }}">
|
||||||
<td><input type="checkbox" name="lots" value="{{ lot.id }}" /></td>
|
<label class="form-check-label w-100" for="lot{{ lot.id }}">
|
||||||
<td>{{ tag }}/{{ lot.name }}</td>
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
</tr>
|
<div class="ps-2">
|
||||||
{% endif %}
|
<strong>{{ lot.name }}</strong>
|
||||||
|
<div class="text-muted small">{{ lot.description }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-end text-muted small">
|
||||||
|
<div><i class="bi bi-calendar"></i> {{ lot.created|date:"M d, Y" }}</div>
|
||||||
|
<div><i class="bi bi-person"></i> {{ lot.user.username|default:"N/A" }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
<button type="submit" id="assignButton" class="btn btn-green-user mt-3" disabled>{% trans "Assign" %}</button>
|
||||||
</table>
|
|
||||||
|
|
||||||
<button class="btn btn-green-admin" type="submit">Save</button>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// event listener for the filter (search) function
|
||||||
|
document.getElementById('searchFilter').addEventListener('input', function () {
|
||||||
|
const searchTerm = this.value.toLowerCase();
|
||||||
|
const lotItems = document.querySelectorAll('.lot-item');
|
||||||
|
const groups = document.querySelectorAll('.card');
|
||||||
|
|
||||||
|
let hasMatches = false;
|
||||||
|
|
||||||
|
groups.forEach(function (group) {
|
||||||
|
const groupLots = group.querySelectorAll('.lot-item');
|
||||||
|
let groupHasMatches = false;
|
||||||
|
|
||||||
|
groupLots.forEach(function (item) {
|
||||||
|
const lotName = item.getAttribute('data-name').toLowerCase();
|
||||||
|
const lotCode = item.getAttribute('data-code').toLowerCase();
|
||||||
|
const isVisible = lotName.includes(searchTerm) || lotCode.includes(searchTerm);
|
||||||
|
|
||||||
|
item.style.display = isVisible ? 'block' : 'none';
|
||||||
|
|
||||||
|
if (isVisible) {
|
||||||
|
groupHasMatches = true;
|
||||||
|
hasMatches = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Expand or collapse the group based on whether it has matching lots
|
||||||
|
const collapseId = group.querySelector('.collapse').id;
|
||||||
|
const collapseElement = document.getElementById(collapseId);
|
||||||
|
const collapseInstance = bootstrap.Collapse.getInstance(collapseElement) || new bootstrap.Collapse(collapseElement, { toggle: false });
|
||||||
|
|
||||||
|
if (groupHasMatches) {
|
||||||
|
collapseInstance.show();
|
||||||
|
} else {
|
||||||
|
collapseInstance.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no search term, collapse all groups
|
||||||
|
if (!searchTerm) {
|
||||||
|
document.querySelectorAll('.collapse').forEach(function (collapse) {
|
||||||
|
new bootstrap.Collapse(collapse, { toggle: false }).hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function applySelectionState() {
|
||||||
|
document.querySelectorAll('.form-check-input').forEach(function (checkbox) {
|
||||||
|
const lotItem = checkbox.closest('.lot-item');
|
||||||
|
if (checkbox.checked) {
|
||||||
|
lotItem.classList.add('selected');
|
||||||
|
} else {
|
||||||
|
lotItem.classList.remove('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.form-check-input').forEach(function (checkbox) {
|
||||||
|
checkbox.addEventListener('change', function () {
|
||||||
|
const lotItem = this.closest('.lot-item');
|
||||||
|
if (this.checked) {
|
||||||
|
lotItem.classList.add('selected');
|
||||||
|
} else {
|
||||||
|
lotItem.classList.remove('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupIndex = this.closest('.collapse').id.replace('collapse', '');
|
||||||
|
updateSelectedCount(groupIndex);
|
||||||
|
updateAssignButton();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// update selection state (bg color) on dom reload, so that it properly displays when f5
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
applySelectionState();
|
||||||
|
updateAssignButton();
|
||||||
|
|
||||||
|
// also update count for selected
|
||||||
|
document.querySelectorAll('.card').forEach(function (card, index) {
|
||||||
|
updateSelectedCount(index + 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateSelectedCount(groupIndex) {
|
||||||
|
const group = document.querySelector(`#collapse${groupIndex}`);
|
||||||
|
const checkboxes = group.querySelectorAll('.form-check-input:checked');
|
||||||
|
const selectedCount = document.querySelector(`#selectedCount${groupIndex}`);
|
||||||
|
selectedCount.textContent = `${checkboxes.length} {% trans "selected" %}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable assign button only if at least one lot is selected
|
||||||
|
function updateAssignButton() {
|
||||||
|
const assignButton = document.getElementById('assignButton');
|
||||||
|
const anyChecked = document.querySelectorAll('.form-check-input:checked').length > 0;
|
||||||
|
assignButton.disabled = !anyChecked;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue