Restructured pages
This commit is contained in:
74
boxes/templates/boxes/boxes_list.html
Normal file
74
boxes/templates/boxes/boxes_list.html
Normal file
@@ -0,0 +1,74 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Boxes - LabHelper{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class="page-header">
|
||||
<h1><i class="fas fa-boxes"></i> Boxes</h1>
|
||||
<p class="breadcrumb">
|
||||
<a href="/"><i class="fas fa-home"></i> Home</a> / Boxes
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if boxes %}
|
||||
<div class="section" style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Box ID</th>
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Type</th>
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Dimensions (mm)</th>
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Contents</th>
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Item Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for box in boxes %}
|
||||
<tr style="border-bottom: 1px solid #e0e0e0; transition: background 0.2s;" class="box-row">
|
||||
<td style="padding: 15px 20px; font-weight: 700; color: #667eea;">
|
||||
<a href="{% url 'box_detail' box.id %}" style="text-decoration: none; color: inherit;">Box {{ box.id }}</a>
|
||||
</td>
|
||||
<td style="padding: 15px 20px;">{{ box.box_type.name }}</td>
|
||||
<td style="padding: 15px 20px;">{{ box.box_type.width }} x {{ box.box_type.height }} x {{ box.box_type.length }}</td>
|
||||
<td style="padding: 15px 20px; color: #555;">
|
||||
{% if box.things.all %}
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 5px;">
|
||||
{% for thing in box.things.all %}
|
||||
<a href="{% url 'thing_detail' thing.id %}" style="display: inline-block; padding: 4px 10px; background: #f0f0f0; border-radius: 12px; text-decoration: none; color: #333; font-size: 13px; font-weight: 500;">{{ thing.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<span style="color: #999; font-style: italic;">Empty</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="padding: 15px 20px; font-weight: 600;">{{ box.things.count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="section" style="text-align: center; padding: 60px 30px;">
|
||||
<i class="fas fa-box-open" style="font-size: 64px; color: #ddd; margin-bottom: 20px; display: block;"></i>
|
||||
<h3 style="color: #888; font-size: 20px;">No boxes found</h3>
|
||||
<p style="color: #999; margin-top: 10px;">Create your first box to get started.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.box-row').hover(
|
||||
function() {
|
||||
$(this).css('background', '#f8f9fa');
|
||||
},
|
||||
function() {
|
||||
$(this).css('background', 'white');
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -11,34 +11,37 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<h2><i class="fas fa-box"></i> Boxes</h2>
|
||||
{% if boxes %}
|
||||
<div class="box-grid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px;">
|
||||
{% for box in boxes %}
|
||||
<div class="box-card" style="background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); padding: 20px; border-radius: 12px; border: 1px solid #e0e0e0; transition: all 0.3s ease; cursor: pointer;">
|
||||
<a href="{% url 'box_detail' box.id %}" style="text-decoration: none; color: #333; display: block;">
|
||||
<div class="box-id" style="font-size: 20px; font-weight: 700; color: #667eea; margin-bottom: 8px;">
|
||||
<i class="fas fa-cube"></i> Box {{ box.id }}
|
||||
</div>
|
||||
<div class="box-type" style="font-size: 15px; color: #555; margin-bottom: 5px;">
|
||||
{{ box.box_type.name }}
|
||||
</div>
|
||||
<div class="box-type" style="font-size: 13px; color: #777; margin-bottom: 5px;">
|
||||
<i class="fas fa-ruler-combined"></i> {{ box.box_type.width }} x {{ box.box_type.height }} x {{ box.box_type.length }} mm
|
||||
</div>
|
||||
<div class="box-type" style="font-size: 13px; color: #777;">
|
||||
<i class="fas fa-layer-group"></i> {{ box.things.count }} item{{ box.things.count|pluralize }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p style="text-align: center; color: #888; font-size: 16px; padding: 40px;">
|
||||
<i class="fas fa-box-open" style="font-size: 48px; margin-bottom: 15px; display: block;"></i>
|
||||
No boxes found.
|
||||
<input type="text"
|
||||
id="search-input"
|
||||
placeholder="Search for things..."
|
||||
style="width: 100%; padding: 16px 20px; font-size: 18px; border: 2px solid #e0e0e0; border-radius: 12px; box-sizing: border-box; transition: all 0.3s;"
|
||||
{% if request.GET.q %}value="{{ request.GET.q }}"{% endif %}>
|
||||
<p style="color: #888; font-size: 14px; margin-top: 10px;">
|
||||
<i class="fas fa-info-circle"></i> Type at least 2 characters to search
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="results-container" style="display: none;">
|
||||
<div class="section" style="overflow-x: auto; padding: 0;">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Name</th>
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Tags</th>
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Box</th>
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="results-body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="no-results" class="section" style="text-align: center; padding: 60px 30px; display: none;">
|
||||
<i class="fas fa-search-minus" style="font-size: 64px; color: #ddd; margin-bottom: 20px; display: block;"></i>
|
||||
<h3 style="color: #888; font-size: 20px;">No results found</h3>
|
||||
<p style="color: #999; margin-top: 10px;">Try different keywords or browse the full inventory.</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
@@ -57,7 +60,7 @@
|
||||
<div class="facet-tags" style="padding: 15px 20px; display: none;">
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
|
||||
{% for tag, count in tags_with_counts %}
|
||||
<a href="/search/?q={{ facet.name }}:{{ tag.name }}" style="display: inline-block; padding: 6px 12px; background: {{ facet.color }}20; color: {{ facet.color }}; border: 1px solid {{ facet.color }}; border-radius: 15px; text-decoration: none; font-size: 14px; font-weight: 600; transition: all 0.2s;">
|
||||
<a href="/?q={{ facet.name }}:{{ tag.name }}" style="display: inline-block; padding: 6px 12px; background: {{ facet.color }}20; color: {{ facet.color }}; border: 1px solid {{ facet.color }}; border-radius: 15px; text-decoration: none; font-size: 14px; font-weight: 600; transition: all 0.2s;">
|
||||
{{ tag.name }}
|
||||
<span style="background: {{ facet.color }}; color: white; padding: 1px 8px; border-radius: 10px; margin-left: 6px; font-size: 12px;">{{ count }}</span>
|
||||
</a>
|
||||
@@ -79,6 +82,97 @@
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const resultsContainer = document.getElementById('results-container');
|
||||
const resultsBody = document.getElementById('results-body');
|
||||
const noResults = document.getElementById('no-results');
|
||||
|
||||
let searchTimeout = null;
|
||||
|
||||
function performSearch(query) {
|
||||
if (query.length < 2) {
|
||||
resultsContainer.style.display = 'none';
|
||||
noResults.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
searchInput.style.borderColor = '#667eea';
|
||||
searchInput.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
|
||||
searchTimeout = setTimeout(function() {
|
||||
fetch('/search/api/?q=' + encodeURIComponent(query))
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
resultsBody.innerHTML = '';
|
||||
|
||||
if (data.results.length === 0) {
|
||||
resultsContainer.style.display = 'none';
|
||||
noResults.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
noResults.style.display = 'none';
|
||||
resultsContainer.style.display = 'block';
|
||||
|
||||
data.results.forEach(function(thing) {
|
||||
const row = document.createElement('tr');
|
||||
row.style.borderBottom = '1px solid #e0e0e0';
|
||||
row.style.transition = 'background 0.2s';
|
||||
|
||||
let tagsHtml = thing.tags.length > 0
|
||||
? thing.tags.map(tag =>
|
||||
'<span style="display: inline-block; padding: 3px 8px; margin: 2px; border-radius: 12px; font-size: 11px; background: ' + escapeHtml(tag.color) + '20; color: ' + escapeHtml(tag.color) + '; border: 1px solid ' + escapeHtml(tag.color) + '40;">' + escapeHtml(tag.name) + '</span>'
|
||||
).join('')
|
||||
: '<span style="color: #999; font-style: italic; font-size: 13px;">-</span>';
|
||||
|
||||
row.innerHTML =
|
||||
'<td style="padding: 15px 20px;"><a href="/thing/' + thing.id + '/">' + escapeHtml(thing.name) + '</a></td>' +
|
||||
'<td style="padding: 15px 20px;">' + tagsHtml + '</td>' +
|
||||
'<td style="padding: 15px 20px;"><a href="/box/' + escapeHtml(thing.box) + '/">' + escapeHtml(thing.box) + '</a></td>' +
|
||||
'<td style="padding: 15px 20px; color: #777;" class="description">' + escapeHtml(thing.description) + '</td>';
|
||||
|
||||
row.addEventListener('mouseenter', function() {
|
||||
this.style.background = '#f8f9fa';
|
||||
});
|
||||
row.addEventListener('mouseleave', function() {
|
||||
this.style.background = 'white';
|
||||
});
|
||||
|
||||
resultsBody.appendChild(row);
|
||||
});
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
|
||||
searchInput.addEventListener('input', function() {
|
||||
const query = this.value.trim();
|
||||
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
performSearch(query);
|
||||
});
|
||||
|
||||
searchInput.addEventListener('blur', function() {
|
||||
searchInput.style.borderColor = '#e0e0e0';
|
||||
searchInput.style.boxShadow = 'none';
|
||||
});
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const initialQuery = urlParams.get('q');
|
||||
|
||||
if (initialQuery) {
|
||||
searchInput.value = initialQuery;
|
||||
performSearch(initialQuery.trim());
|
||||
}
|
||||
|
||||
$('.facet-header').click(function() {
|
||||
const $content = $(this).next('.facet-tags');
|
||||
const $icon = $(this).find('.facet-toggle');
|
||||
@@ -99,17 +193,6 @@ $(document).ready(function() {
|
||||
$(this).css('transform', 'scale(1)');
|
||||
}
|
||||
);
|
||||
|
||||
$('.box-card').hover(
|
||||
function() {
|
||||
$(this).css('transform', 'translateY(-5px)');
|
||||
$(this).css('box-shadow', '0 12px 24px rgba(102, 126, 234, 0.2)');
|
||||
},
|
||||
function() {
|
||||
$(this).css('transform', 'translateY(0)');
|
||||
$(this).css('box-shadow', 'none');
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user