Files
labhelper/boxes/templates/boxes/index.html
2026-01-06 12:55:04 +01:00

199 lines
8.6 KiB
HTML

{% extends "base.html" %}
{% block title %}LabHelper - Home{% endblock %}
{% block page_header %}
<div class="page-header">
<h1><i class="fas fa-home"></i> Welcome to LabHelper</h1>
<p class="breadcrumb">Organize and track your lab inventory</p>
</div>
{% endblock %}
{% block content %}
<div class="section">
<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>
</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">
<h2><i class="fas fa-tags"></i> Tags</h2>
{% if facet_tag_counts %}
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;">
{% for facet, tags_with_counts in facet_tag_counts.items %}
<div class="facet-card" style="background: white; border-radius: 12px; border: 1px solid #e0e0e0; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
<div class="facet-header" style="padding: 15px 20px; background: linear-gradient(135deg, {{ facet.color }} 0%, {{ facet.color }}dd 100%); color: white; display: flex; align-items: center; justify-content: space-between; cursor: pointer;">
<div style="display: flex; align-items: center; gap: 10px;">
<i class="fas fa-chevron-right facet-toggle" style="transition: transform 0.3s;"></i>
<span style="font-size: 18px; font-weight: 700;">{{ facet.name }}</span>
</div>
<span style="background: rgba(255,255,255,0.3); padding: 4px 12px; border-radius: 20px; font-size: 13px; font-weight: 600;">{{ facet.cardinality }}</span>
</div>
<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="/?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>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p style="text-align: center; color: #888; font-size: 16px; padding: 40px;">
<i class="fas fa-tag" style="font-size: 48px; margin-bottom: 15px; display: block;"></i>
No tags found.
</p>
{% endif %}
</div>
{% endblock %}
{% 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');
$content.slideToggle(200);
if ($content.is(':visible')) {
$icon.css('transform', 'rotate(90deg)');
} else {
$icon.css('transform', 'rotate(0deg)');
}
});
$('.facet-card a').hover(
function() {
$(this).css('transform', 'scale(1.05)');
},
function() {
$(this).css('transform', 'scale(1)');
}
);
});
</script>
{% endblock %}