improvement/design #3
@@ -27,7 +27,7 @@ spec:
|
|||||||
mountPath: /data
|
mountPath: /data
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: git.baumann.gr/adebaumann/labhelper:0.027
|
image: git.baumann.gr/adebaumann/labhelper:0.028
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8000
|
- containerPort: 8000
|
||||||
|
|||||||
@@ -1,213 +1,128 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Add Things to Box {{ box.id }} - LabHelper</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
margin: 20px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.form-row {
|
|
||||||
display: table-row;
|
|
||||||
}
|
|
||||||
.form-cell {
|
|
||||||
display: table-cell;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
.form-header {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
|
||||||
.form-header-cell {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
.form-cell input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.form-cell input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #4a90a4;
|
|
||||||
}
|
|
||||||
.form-cell textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 14px;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
.form-cell textarea:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #4a90a4;
|
|
||||||
}
|
|
||||||
.form-cell select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
.form-cell select:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #4a90a4;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
background-color: #4a90a4;
|
|
||||||
color: white;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.btn:hover {
|
|
||||||
background-color: #3d7a96;
|
|
||||||
}
|
|
||||||
.back-link {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: inline-block;
|
|
||||||
color: #4a90a4;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.back-link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.error-list {
|
|
||||||
color: #d9534f;
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.error-list li {
|
|
||||||
padding: 8px 0;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.success-message {
|
|
||||||
background-color: #d4edda;
|
|
||||||
color: #155724;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.required {
|
|
||||||
color: #d9534f;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<a href="/" class="back-link">← Home</a>
|
|
||||||
|
|
||||||
<h1>Add Things to Box {{ box.id }}</h1>
|
{% block title %}Add Things to Box {{ box.id }} - LabHelper{% endblock %}
|
||||||
|
|
||||||
<div class="container">
|
{% block page_header %}
|
||||||
<p>
|
<div class="page-header">
|
||||||
<strong>Box:</strong> {{ box.id }} ({{ box.box_type.name }})
|
<h1><i class="fas fa-plus-circle"></i> Add Things to Box {{ box.id }}</h1>
|
||||||
|
<p class="breadcrumb">
|
||||||
|
<a href="/"><i class="fas fa-home"></i> Home</a> /
|
||||||
|
<a href="/box/{{ box.id }}/"><i class="fas fa-box"></i> Box {{ box.id }}</a> /
|
||||||
|
Add Things
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div style="margin-bottom: 20px; padding: 15px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 10px; border-left: 4px solid #667eea;">
|
||||||
|
<span style="color: #555; font-size: 16px;">
|
||||||
|
<strong><i class="fas fa-info-circle"></i> Box:</strong> {{ box.id }} ({{ box.box_type.name }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if formset.non_form_errors %}
|
{% if formset.non_form_errors %}
|
||||||
<div class="error-list">
|
<div class="alert alert-error">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
{% for form_errors in formset.non_form_errors %}
|
{% for form_errors in formset.non_form_errors %}
|
||||||
{% for field, errors in form_errors.items %}
|
{% for field, errors in form_errors.items %}
|
||||||
{% for error in errors %}
|
{% for error in errors %}
|
||||||
<li>{{ error }}</li>
|
{{ error }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if success_message %}
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<i class="fas fa-check-circle"></i> {{ success_message }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if formset.total_form_count %}
|
{% if formset.total_form_count %}
|
||||||
<form method="post" action="">
|
<form method="post" style="overflow-x: auto;">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<table>
|
<table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">
|
||||||
<tr class="form-row">
|
<thead>
|
||||||
<th class="form-header-cell"></th>
|
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||||
<th class="form-header form-header-cell">Name</th>
|
<th style="padding: 12px 15px; text-align: left; font-weight: 600;"></th>
|
||||||
<th class="form-header form-header-cell">Type</th>
|
<th style="padding: 12px 15px; text-align: left; font-weight: 600;">Name</th>
|
||||||
<th class="form-header form-header-cell">Description</th>
|
<th style="padding: 12px 15px; text-align: left; font-weight: 600;">Type</th>
|
||||||
<th class="form-header form-header-cell">Picture</th>
|
<th style="padding: 12px 15px; text-align: left; font-weight: 600;">Description</th>
|
||||||
|
<th style="padding: 12px 15px; text-align: left; font-weight: 600;">Picture</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
{{ formset.management_form }}
|
{{ formset.management_form }}
|
||||||
{% for form in formset %}
|
{% for form in formset %}
|
||||||
<tr class="form-row">
|
<tr style="border-bottom: 1px solid #e0e0e0; background: {% if forloop.counter|divisibleby:2 %}#f8f9fa{% endif %};">
|
||||||
<td class="form-cell">
|
<td style="padding: 12px 15px;">
|
||||||
{{ form.id }}
|
{{ form.id }}
|
||||||
</td>
|
</td>
|
||||||
<td class="form-cell">
|
<td style="padding: 12px 15px;">
|
||||||
|
<div style="display: flex; gap: 5px; flex-wrap: wrap; align-items: center;">
|
||||||
{{ form.name }}
|
{{ form.name }}
|
||||||
{% for error in form.name.errors %}
|
{% for error in form.name.errors %}
|
||||||
<div class="error-list">
|
<div style="color: #e74c3c; font-size: 13px; margin-top: 5px;">
|
||||||
<li>{{ error }}</li>
|
<i class="fas fa-exclamation-circle"></i> {{ error }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<label class="required">*</label>
|
<span style="color: #e74c3c;">*</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="form-cell">
|
<td style="padding: 12px 15px;">
|
||||||
|
<div style="display: flex; gap: 5px; flex-wrap: wrap; align-items: center;">
|
||||||
{{ form.thing_type }}
|
{{ form.thing_type }}
|
||||||
{% for error in form.thing_type.errors %}
|
{% for error in form.thing_type.errors %}
|
||||||
<div class="error-list">
|
<div style="color: #e74c3c; font-size: 13px; margin-top: 5px;">
|
||||||
<li>{{ error }}</li>
|
<i class="fas fa-exclamation-circle"></i> {{ error }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<label class="required">*</label>
|
<span style="color: #e74c3c;">*</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="form-cell">
|
<td style="padding: 12px 15px;">
|
||||||
|
<div style="display: flex; gap: 5px; flex-wrap: wrap; align-items: center;">
|
||||||
{{ form.description }}
|
{{ form.description }}
|
||||||
{% for error in form.description.errors %}
|
{% for error in form.description.errors %}
|
||||||
<div class="error-list">
|
<div style="color: #e74c3c; font-size: 13px; margin-top: 5px;">
|
||||||
<li>{{ error }}</li>
|
<i class="fas fa-exclamation-circle"></i> {{ error }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="form-cell">
|
<td style="padding: 12px 15px;">
|
||||||
|
<div style="display: flex; gap: 5px; flex-wrap: wrap; align-items: center;">
|
||||||
{{ form.picture }}
|
{{ form.picture }}
|
||||||
{% for error in form.picture.errors %}
|
{% for error in form.picture.errors %}
|
||||||
<div class="error-list">
|
<div style="color: #e74c3c; font-size: 13px; margin-top: 5px;">
|
||||||
<li>{{ error }}</li>
|
<i class="fas fa-exclamation-circle"></i> {{ error }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr class="form-row">
|
|
||||||
<td class="form-cell" colspan="5">
|
|
||||||
<button type="submit" class="btn">Save Things</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div style="text-align: center; margin-top: 30px;">
|
||||||
|
<button type="submit" class="btn">
|
||||||
|
<i class="fas fa-save"></i> Save Things
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% if success_message %}
|
{% block extra_js %}
|
||||||
<div class="success-message">
|
<script>
|
||||||
{{ success_message }}
|
$('form input, form select, form textarea').on('focus', function() {
|
||||||
</div>
|
$(this).css('border-color', '#667eea');
|
||||||
{% endif %}
|
$(this).css('box-shadow', '0 0 0 3px rgba(102, 126, 234, 0.1)');
|
||||||
</div>
|
}).on('blur', function() {
|
||||||
</body>
|
$(this).css('border-color', '#e0e0e0');
|
||||||
</html>
|
$(this).css('box-shadow', 'none');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,122 +1,90 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Box {{ box.id }} - LabHelper</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
margin: 20px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.box-info {
|
|
||||||
background: white;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
th, td {
|
|
||||||
padding: 12px 15px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
th {
|
|
||||||
background-color: #4a90a4;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
tr:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
.thumbnail {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.no-image {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #999;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.back-link {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.empty-message {
|
|
||||||
background: white;
|
|
||||||
padding: 40px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<a href="/" class="back-link">← Back to Home</a>
|
|
||||||
|
|
||||||
<h1>Box {{ box.id }}</h1>
|
{% block title %}Box {{ box.id }} - LabHelper{% endblock %}
|
||||||
|
|
||||||
<div class="box-info">
|
{% block page_header %}
|
||||||
<strong>Type:</strong> {{ box.box_type.name }}
|
<div class="page-header">
|
||||||
({{ box.box_type.width }} x {{ box.box_type.height }} x {{ box.box_type.length }} mm)
|
<h1><i class="fas fa-box"></i> Box {{ box.id }}</h1>
|
||||||
<br><br>
|
<p class="breadcrumb">
|
||||||
<a href="/box/{{ box.id }}/add/">+ Add Things</a>
|
<a href="/"><i class="fas fa-home"></i> Home</a> / Box {{ box.id }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 15px;">
|
||||||
|
<div>
|
||||||
|
<div style="font-size: 16px; color: #555; margin-bottom: 5px;">
|
||||||
|
<strong><i class="fas fa-cube"></i> Type:</strong> {{ box.box_type.name }}
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 14px; color: #777;">
|
||||||
|
<i class="fas fa-ruler-combined"></i> {{ box.box_type.width }} x {{ box.box_type.height }} x {{ box.box_type.length }} mm
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'add_things' box.id %}" class="btn">
|
||||||
|
<i class="fas fa-plus"></i> Add Things
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if things %}
|
{% if things %}
|
||||||
<table>
|
<div class="section">
|
||||||
|
<div style="overflow-x: auto;">
|
||||||
|
<table style="width: 100%; border-collapse: collapse;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||||
<th>Picture</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Picture</th>
|
||||||
<th>Name</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Name</th>
|
||||||
<th>Type</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Type</th>
|
||||||
<th>Description</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for thing in things %}
|
{% for thing in things %}
|
||||||
<tr>
|
<tr style="border-bottom: 1px solid #e0e0e0; transition: background 0.2s;">
|
||||||
<td>
|
<td style="padding: 15px 20px;">
|
||||||
{% if thing.picture %}
|
{% if thing.picture %}
|
||||||
{% thumbnail thing.picture "50x50" crop="center" as thumb %}
|
{% thumbnail thing.picture "50x50" crop="center" as thumb %}
|
||||||
<img src="{{ thumb.url }}" alt="{{ thing.name }}" class="thumbnail">
|
<img src="{{ thumb.url }}" alt="{{ thing.name }}" style="width: 50px; height: 50px; object-fit: cover; border-radius: 8px;">
|
||||||
{% endthumbnail %}
|
{% endthumbnail %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="no-image">No image</div>
|
<div style="width: 50px; height: 50px; background: linear-gradient(135deg, #e0e0e0 0%, #f0f0f0 100%); display: flex; align-items: center; justify-content: center; color: #999; border-radius: 8px; font-size: 11px;">No image</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td><a href="{% url 'thing_detail' thing.id %}">{{ thing.name }}</a></td>
|
<td style="padding: 15px 20px;">
|
||||||
<td>{{ thing.thing_type.name }}</td>
|
<a href="{% url 'thing_detail' thing.id %}" style="color: #667eea; text-decoration: none; font-weight: 500;">{{ thing.name }}</a>
|
||||||
<td>{{ thing.description|default:"-" }}</td>
|
</td>
|
||||||
|
<td style="padding: 15px 20px; color: #555;">{{ thing.thing_type.name }}</td>
|
||||||
|
<td style="padding: 15px 20px; color: #777;">{{ thing.description|default:"-" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="empty-message">
|
<div class="section" style="text-align: center; padding: 60px 30px;">
|
||||||
This box is empty.
|
<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;">This box is empty</h3>
|
||||||
|
<p style="color: #999; margin-top: 10px;">Add some items to get started!</p>
|
||||||
|
<a href="{% url 'add_things' box.id %}" class="btn" style="margin-top: 20px;">
|
||||||
|
<i class="fas fa-plus"></i> Add Things
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
$('tbody tr').hover(
|
||||||
|
function() {
|
||||||
|
$(this).css('background', '#f8f9fa');
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
$(this).css('background', 'white');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,195 +1,66 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
{% load mptt_tags %}
|
{% load mptt_tags %}
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>LabHelper</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
margin: 20px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
.nav-links {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.nav-links a {
|
|
||||||
color: #4a90a4;
|
|
||||||
text-decoration: none;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
.nav-links a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.section {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.section h2 {
|
|
||||||
color: #333;
|
|
||||||
margin-top: 0;
|
|
||||||
font-size: 20px;
|
|
||||||
border-bottom: 2px solid #4a90a4;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
.box-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
.box-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
.box-card:hover {
|
|
||||||
border-color: #4a90a4;
|
|
||||||
box-shadow: 0 2px 5px rgba(74, 144, 164, 0.2);
|
|
||||||
}
|
|
||||||
.box-card a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #333;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.box-card .box-id {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #4a90a4;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.box-card .box-type {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
.tree {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
.tree ul {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
.tree li {
|
|
||||||
padding: 5px 0;
|
|
||||||
}
|
|
||||||
.tree li a {
|
|
||||||
color: #4a90a4;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
.tree li a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.tree-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
.tree-toggle {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.tree ul {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 20px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.tree > li > ul {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.tree li {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.tree li a {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.toggle-handle {
|
|
||||||
display: inline-block;
|
|
||||||
width: 20px;
|
|
||||||
color: #999;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
$('.toggle-handle').click(function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
var $ul = $(this).closest('li').children('ul');
|
|
||||||
if ($ul.length) {
|
|
||||||
$ul.toggle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>LabHelper</h1>
|
|
||||||
|
|
||||||
<div class="nav-links">
|
{% block title %}LabHelper - Home{% endblock %}
|
||||||
<a href="/search/">Search Things</a>
|
|
||||||
<a href="/admin/">Admin</a>
|
{% 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>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>Boxes</h2>
|
<h2><i class="fas fa-box"></i> Boxes</h2>
|
||||||
{% if boxes %}
|
{% if boxes %}
|
||||||
<div class="box-grid">
|
<div class="box-grid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px;">
|
||||||
{% for box in boxes %}
|
{% for box in boxes %}
|
||||||
<div class="box-card">
|
<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 %}">
|
<a href="{% url 'box_detail' box.id %}" style="text-decoration: none; color: #333; display: block;">
|
||||||
<div class="box-id">Box {{ box.id }}</div>
|
<div class="box-id" style="font-size: 20px; font-weight: 700; color: #667eea; margin-bottom: 8px;">
|
||||||
<div class="box-type">{{ box.box_type.name }}</div>
|
<i class="fas fa-cube"></i> Box {{ box.id }}
|
||||||
<div class="box-type" style="font-size: 12px; margin-top: 3px;">
|
|
||||||
{{ box.box_type.width }} x {{ box.box_type.height }} x {{ box.box_type.length }} mm
|
|
||||||
</div>
|
</div>
|
||||||
<div class="box-type" style="font-size: 12px; margin-top: 3px;">
|
<div class="box-type" style="font-size: 15px; color: #555; margin-bottom: 5px;">
|
||||||
{{ box.things.count }} item{{ box.things.count|pluralize }}
|
{{ 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>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No boxes found.</p>
|
<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.
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>Thing Types</h2>
|
<h2><i class="fas fa-folder-tree"></i> Thing Types</h2>
|
||||||
{% if thing_types %}
|
{% if thing_types %}
|
||||||
<ul class="tree">
|
<ul class="tree" style="list-style: none; padding-left: 0;">
|
||||||
{% recursetree thing_types %}
|
{% recursetree thing_types %}
|
||||||
<li>
|
<li style="padding: 8px 0;">
|
||||||
|
<div class="tree-item" style="display: flex; align-items: center; gap: 8px;">
|
||||||
{% if children %}
|
{% if children %}
|
||||||
<span class="toggle-handle">[-]</span>
|
<span class="toggle-handle" style="display: inline-block; width: 24px; color: #667eea; font-weight: bold; cursor: pointer; transition: transform 0.2s;">[-]</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="toggle-handle"> </span>
|
<span class="toggle-handle" style="display: inline-block; width: 24px; color: #ccc;"> </span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'thing_type_detail' node.pk %}">{{ node.name }}</a>
|
<a href="{% url 'thing_type_detail' node.pk %}" style="color: #667eea; text-decoration: none; font-size: 16px; font-weight: 500; transition: color 0.2s;">{{ node.name }}</a>
|
||||||
{% if node.things.exists %}
|
{% if node.things.exists %}
|
||||||
<span style="color: #999; font-size: 13px;">({{ node.things.count }})</span>
|
<span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: 600;">{{ node.things.count }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% if children %}
|
{% if children %}
|
||||||
<ul>
|
<ul style="list-style: none; padding-left: 32px;">
|
||||||
{{ children }}
|
{{ children }}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -197,9 +68,36 @@
|
|||||||
{% endrecursetree %}
|
{% endrecursetree %}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No thing types found.</p>
|
<p style="text-align: center; color: #888; font-size: 16px; padding: 40px;">
|
||||||
|
<i class="fas fa-folder-open" style="font-size: 48px; margin-bottom: 15px; display: block;"></i>
|
||||||
|
No thing types found.
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endblock %}
|
||||||
</body>
|
|
||||||
</html>
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('.toggle-handle').click(function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
var $ul = $(this).closest('li').children('ul');
|
||||||
|
if ($ul.length) {
|
||||||
|
$ul.slideToggle(200);
|
||||||
|
$(this).text($ul.is(':visible') ? '[-]' : '[+]');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.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 %}
|
||||||
@@ -1,127 +1,52 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Search - LabHelper</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
margin: 20px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.search-container {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.search-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 15px;
|
|
||||||
font-size: 16px;
|
|
||||||
border: 2px solid #ddd;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.search-input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #4a90a4;
|
|
||||||
}
|
|
||||||
.search-hint {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
th, td {
|
|
||||||
padding: 10px 12px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
th {
|
|
||||||
background-color: #4a90a4;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
tr:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #4a90a4;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.back-link {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.no-results {
|
|
||||||
background: white;
|
|
||||||
padding: 40px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
#results-container {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.description {
|
|
||||||
color: #666;
|
|
||||||
font-size: 13px;
|
|
||||||
max-width: 300px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<a href="/" class="back-link">← Back to Home</a>
|
|
||||||
|
|
||||||
<h1>Search Things</h1>
|
{% block title %}Search - LabHelper{% endblock %}
|
||||||
|
|
||||||
<div class="search-container">
|
{% block page_header %}
|
||||||
|
<div class="page-header">
|
||||||
|
<h1><i class="fas fa-search"></i> Search Things</h1>
|
||||||
|
<p class="breadcrumb">
|
||||||
|
<a href="/"><i class="fas fa-home"></i> Home</a> / Search
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="search-input"
|
id="search-input"
|
||||||
class="search-input"
|
|
||||||
placeholder="Search for things..."
|
placeholder="Search for things..."
|
||||||
autocomplete="off">
|
style="width: 100%; padding: 16px 20px; font-size: 18px; border: 2px solid #e0e0e0; border-radius: 12px; box-sizing: border-box; transition: all 0.3s;">
|
||||||
<div class="search-hint">Type at least 2 characters to search</div>
|
<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>
|
||||||
|
|
||||||
<div id="results-container">
|
<div id="results-container" style="display: none;">
|
||||||
<table>
|
<div class="section" style="overflow-x: auto; padding: 0;">
|
||||||
|
<table style="width: 100%; border-collapse: collapse;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||||
<th>Name</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Name</th>
|
||||||
<th>Type</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Type</th>
|
||||||
<th>Box</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Box</th>
|
||||||
<th>Description</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="results-body">
|
<tbody id="results-body">
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="no-results" class="no-results" style="display: none;">
|
|
||||||
No results found.
|
|
||||||
</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>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
const searchInput = document.getElementById('search-input');
|
const searchInput = document.getElementById('search-input');
|
||||||
const resultsContainer = document.getElementById('results-container');
|
const resultsContainer = document.getElementById('results-container');
|
||||||
@@ -133,19 +58,19 @@
|
|||||||
searchInput.addEventListener('input', function() {
|
searchInput.addEventListener('input', function() {
|
||||||
const query = this.value.trim();
|
const query = this.value.trim();
|
||||||
|
|
||||||
// Clear previous timeout
|
|
||||||
if (searchTimeout) {
|
if (searchTimeout) {
|
||||||
clearTimeout(searchTimeout);
|
clearTimeout(searchTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide results if query too short
|
|
||||||
if (query.length < 2) {
|
if (query.length < 2) {
|
||||||
resultsContainer.style.display = 'none';
|
resultsContainer.style.display = 'none';
|
||||||
noResults.style.display = 'none';
|
noResults.style.display = 'none';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debounce search
|
searchInput.style.borderColor = '#667eea';
|
||||||
|
searchInput.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||||
|
|
||||||
searchTimeout = setTimeout(function() {
|
searchTimeout = setTimeout(function() {
|
||||||
fetch('/search/api/?q=' + encodeURIComponent(query))
|
fetch('/search/api/?q=' + encodeURIComponent(query))
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@@ -163,25 +88,38 @@
|
|||||||
|
|
||||||
data.results.forEach(function(thing) {
|
data.results.forEach(function(thing) {
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
|
row.style.borderBottom = '1px solid #e0e0e0';
|
||||||
|
row.style.transition = 'background 0.2s';
|
||||||
row.innerHTML =
|
row.innerHTML =
|
||||||
'<td><a href="/thing/' + thing.id + '/">' + escapeHtml(thing.name) + '</a></td>' +
|
'<td style="padding: 15px 20px;"><a href="/thing/' + thing.id + '/">' + escapeHtml(thing.name) + '</a></td>' +
|
||||||
'<td>' + escapeHtml(thing.type) + '</td>' +
|
'<td style="padding: 15px 20px; color: #555;">' + escapeHtml(thing.type) + '</td>' +
|
||||||
'<td><a href="/box/' + escapeHtml(thing.box) + '/">' + escapeHtml(thing.box) + '</a></td>' +
|
'<td style="padding: 15px 20px;"><a href="/box/' + escapeHtml(thing.box) + '/">' + escapeHtml(thing.box) + '</a></td>' +
|
||||||
'<td class="description">' + escapeHtml(thing.description) + '</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);
|
resultsBody.appendChild(row);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
searchInput.addEventListener('blur', function() {
|
||||||
|
searchInput.style.borderColor = '#e0e0e0';
|
||||||
|
searchInput.style.boxShadow = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.textContent = text;
|
div.textContent = text;
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus search input on page load
|
|
||||||
searchInput.focus();
|
searchInput.focus();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
||||||
@@ -1,177 +1,79 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
{% block title %}{{ thing.name }} - LabHelper{% endblock %}
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
{% block page_header %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<div class="page-header">
|
||||||
<title>{{ thing.name }} - LabHelper</title>
|
<h1><i class="fas fa-cube"></i> {{ thing.name }}</h1>
|
||||||
<style>
|
<p class="breadcrumb">
|
||||||
body {
|
<a href="/"><i class="fas fa-home"></i> Home</a> /
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
<a href="/box/{{ thing.box.id }}/"><i class="fas fa-box"></i> Box {{ thing.box.id }}</a> /
|
||||||
margin: 20px;
|
{{ thing.name }}
|
||||||
background-color: #f5f5f5;
|
</p>
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.thing-card {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
display: flex;
|
|
||||||
gap: 30px;
|
|
||||||
}
|
|
||||||
.thing-image {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.thing-image img {
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
.no-image {
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #999;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
.thing-details {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
.detail-row {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
.detail-label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.detail-value {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.detail-value a {
|
|
||||||
color: #4a90a4;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.detail-value a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.description {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
.back-link {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: inline-block;
|
|
||||||
color: #4a90a4;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.back-link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.nav-links {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.nav-links a {
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
.move-form {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
.move-form label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.move-form select {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: white;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
.move-form select:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #4a90a4;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
background-color: #4a90a4;
|
|
||||||
color: white;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.btn:hover {
|
|
||||||
background-color: #3d7a96;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="nav-links">
|
|
||||||
<a href="/" class="back-link">← Home</a>
|
|
||||||
<a href="/search/" class="back-link">Search</a>
|
|
||||||
<a href="/box/{{ thing.box.id }}/" class="back-link">Box {{ thing.box.id }}</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<h1>{{ thing.name }}</h1>
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
<div class="thing-card">
|
<div class="thing-card" style="display: flex; gap: 40px; flex-wrap: wrap;">
|
||||||
<div class="thing-image">
|
<div class="thing-image" style="flex-shrink: 0;">
|
||||||
{% if thing.picture %}
|
{% if thing.picture %}
|
||||||
{% thumbnail thing.picture "300x300" crop="center" as thumb %}
|
{% thumbnail thing.picture "400x400" crop="center" as thumb %}
|
||||||
<img src="{{ thumb.url }}" alt="{{ thing.name }}">
|
<img src="{{ thumb.url }}" alt="{{ thing.name }}" style="width: 400px; height: 400px; object-fit: cover; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.15);">
|
||||||
{% endthumbnail %}
|
{% endthumbnail %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="no-image">No image</div>
|
<div style="width: 400px; height: 400px; background: linear-gradient(135deg, #e0e0e0 0%, #f0f0f0 100%); display: flex; align-items: center; justify-content: center; color: #999; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.15);">
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<i class="fas fa-image" style="font-size: 64px; margin-bottom: 15px; display: block;"></i>
|
||||||
|
No image
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="thing-details">
|
<div class="thing-details" style="flex-grow: 1; min-width: 300px;">
|
||||||
<div class="detail-row">
|
<div class="detail-row" style="margin-bottom: 25px;">
|
||||||
<div class="detail-label">Type</div>
|
<div style="font-size: 14px; color: #888; font-weight: 600; margin-bottom: 8px;">
|
||||||
<div class="detail-value">{{ thing.thing_type.name }}</div>
|
<i class="fas fa-tag"></i> Type
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 18px; color: #333; font-weight: 500;">
|
||||||
|
{{ thing.thing_type.name }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-row">
|
<div class="detail-row" style="margin-bottom: 25px;">
|
||||||
<div class="detail-label">Location</div>
|
<div style="font-size: 14px; color: #888; font-weight: 600; margin-bottom: 8px;">
|
||||||
<div class="detail-value">
|
<i class="fas fa-map-marker-alt"></i> Location
|
||||||
<a href="/box/{{ thing.box.id }}/">Box {{ thing.box.id }}</a>
|
</div>
|
||||||
({{ thing.box.box_type.name }})
|
<div style="font-size: 18px; color: #333;">
|
||||||
|
<a href="{% url 'box_detail' thing.box.id %}" style="color: #667eea; text-decoration: none; font-weight: 500;">Box {{ thing.box.id }}</a>
|
||||||
|
<span style="color: #999;"> ({{ thing.box.box_type.name }})</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if thing.description %}
|
{% if thing.description %}
|
||||||
<div class="detail-row">
|
<div class="detail-row" style="margin-bottom: 25px;">
|
||||||
<div class="detail-label">Description</div>
|
<div style="font-size: 14px; color: #888; font-weight: 600; margin-bottom: 8px;">
|
||||||
<div class="detail-value description">{{ thing.description }}</div>
|
<i class="fas fa-align-left"></i> Description
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 16px; color: #555; line-height: 1.6; white-space: pre-wrap;">
|
||||||
|
{{ thing.description }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" class="move-form">
|
<form method="post" class="section">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<label for="new_box">Move to:</label>
|
<div style="display: flex; align-items: center; gap: 15px; flex-wrap: wrap;">
|
||||||
<select name="new_box" id="new_box">
|
<div style="flex-grow: 1;">
|
||||||
|
<label for="new_box" style="font-weight: 600; color: #666; font-size: 14px; margin-bottom: 8px; display: block;">
|
||||||
|
<i class="fas fa-exchange-alt"></i> Move to:
|
||||||
|
</label>
|
||||||
|
<select name="new_box" id="new_box" style="width: 100%; max-width: 400px; padding: 12px 16px; border: 2px solid #e0e0e0; border-radius: 10px; font-size: 15px; background: white; cursor: pointer; transition: all 0.3s;">
|
||||||
<option value="">Select a box...</option>
|
<option value="">Select a box...</option>
|
||||||
{% for box in boxes %}
|
{% for box in boxes %}
|
||||||
<option value="{{ box.id }}" {% if box.id == thing.box.id %}selected{% endif %}>
|
<option value="{{ box.id }}" {% if box.id == thing.box.id %}selected{% endif %}>
|
||||||
@@ -179,7 +81,23 @@
|
|||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<button type="submit" class="btn">Move</button>
|
</div>
|
||||||
|
<button type="submit" class="btn" style="height: 48px; min-width: 120px; margin-top: 24px;">
|
||||||
|
<i class="fas fa-arrows-alt"></i> Move
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
|
||||||
</html>
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
$('#new_box').on('focus', function() {
|
||||||
|
$(this).css('border-color', '#667eea');
|
||||||
|
$(this).css('box-shadow', '0 0 0 3px rgba(102, 126, 234, 0.1)');
|
||||||
|
}).on('blur', function() {
|
||||||
|
$(this).css('border-color', '#e0e0e0');
|
||||||
|
$(this).css('box-shadow', 'none');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,183 +1,106 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{ thing_type.name }} - LabHelper</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
margin: 20px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.type-header {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.type-section {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.type-section h2 {
|
|
||||||
color: #333;
|
|
||||||
margin-top: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.type-hierarchy {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
.type-hierarchy span {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
th, td {
|
|
||||||
padding: 12px 15px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
th {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
color: #666;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
tr:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
.thumbnail {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.no-image {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #999;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.back-link {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: inline-block;
|
|
||||||
color: #4a90a4;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.back-link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.thing-name {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.thing-name a {
|
|
||||||
color: #4a90a4;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.thing-name a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.box-link {
|
|
||||||
color: #4a90a4;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.box-link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.empty-message {
|
|
||||||
color: #999;
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<a href="/" class="back-link">← Home</a>
|
|
||||||
|
|
||||||
<h1>{{ thing_type.name }}</h1>
|
{% block title %}{{ thing_type.name }} - LabHelper{% endblock %}
|
||||||
|
|
||||||
<div class="type-header">
|
{% block page_header %}
|
||||||
|
<div class="page-header">
|
||||||
|
<h1><i class="fas fa-folder"></i> {{ thing_type.name }}</h1>
|
||||||
|
<p class="breadcrumb">
|
||||||
|
<a href="/"><i class="fas fa-home"></i> Home</a> / {{ thing_type.name }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section" style="padding: 20px;">
|
||||||
{% if thing_type.parent %}
|
{% if thing_type.parent %}
|
||||||
<div class="type-hierarchy">
|
<div style="margin-bottom: 15px; padding: 15px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 10px; border-left: 4px solid #667eea;">
|
||||||
Parent: <a href="{% url 'thing_type_detail' thing_type.parent.id %}" class="box-link">{{ thing_type.parent.name }}</a>
|
<span style="color: #666; font-size: 14px;">
|
||||||
|
<i class="fas fa-level-up-alt"></i> Parent:
|
||||||
|
<a href="{% url 'thing_type_detail' thing_type.parent.id %}" style="color: #667eea; text-decoration: none; font-weight: 500;">{{ thing_type.parent.name }}</a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if thing_type.children.exists %}
|
{% if thing_type.children.exists %}
|
||||||
<div class="type-hierarchy">
|
<div style="margin-bottom: 20px; padding: 15px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 10px; border-left: 4px solid #764ba2;">
|
||||||
Subtypes:
|
<span style="color: #666; font-size: 14px;">
|
||||||
|
<i class="fas fa-sitemap"></i> Subtypes:
|
||||||
{% for child in thing_type.children.all %}
|
{% for child in thing_type.children.all %}
|
||||||
<span><a href="{% url 'thing_type_detail' child.id %}" class="box-link">{{ child.name }}</a></span>
|
<a href="{% url 'thing_type_detail' child.id %}" style="color: #667eea; text-decoration: none; font-weight: 500; margin-left: 8px;">{{ child.name }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if things_by_type %}
|
{% if things_by_type %}
|
||||||
{% for subtype, things in things_by_type.items %}
|
{% for subtype, things in things_by_type.items %}
|
||||||
<div class="type-section">
|
<div class="section">
|
||||||
<h2>{{ subtype.name }}</h2>
|
<h2><i class="fas fa-cubes"></i> {{ subtype.name }}</h2>
|
||||||
{% if things %}
|
{% if things %}
|
||||||
<table>
|
<div style="overflow-x: auto;">
|
||||||
|
<table style="width: 100%; border-collapse: collapse;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||||
<th>Picture</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Picture</th>
|
||||||
<th>Name</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Name</th>
|
||||||
<th>Box</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Box</th>
|
||||||
<th>Description</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for thing in things %}
|
{% for thing in things %}
|
||||||
<tr>
|
<tr style="border-bottom: 1px solid #e0e0e0; transition: background 0.2s;">
|
||||||
<td>
|
<td style="padding: 15px 20px;">
|
||||||
{% if thing.picture %}
|
{% if thing.picture %}
|
||||||
{% thumbnail thing.picture "50x50" crop="center" as thumb %}
|
{% thumbnail thing.picture "50x50" crop="center" as thumb %}
|
||||||
<img src="{{ thumb.url }}" alt="{{ thing.name }}" class="thumbnail">
|
<img src="{{ thumb.url }}" alt="{{ thing.name }}" style="width: 50px; height: 50px; object-fit: cover; border-radius: 8px;">
|
||||||
{% endthumbnail %}
|
{% endthumbnail %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="no-image">No image</div>
|
<div style="width: 50px; height: 50px; background: linear-gradient(135deg, #e0e0e0 0%, #f0f0f0 100%); display: flex; align-items: center; justify-content: center; color: #999; border-radius: 8px; font-size: 11px;">No image</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="thing-name">
|
<td style="padding: 15px 20px;">
|
||||||
<a href="{% url 'thing_detail' thing.id %}">{{ thing.name }}</a>
|
<a href="{% url 'thing_detail' thing.id %}" style="color: #667eea; text-decoration: none; font-weight: 500;">{{ thing.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td style="padding: 15px 20px;">
|
||||||
<a href="{% url 'box_detail' thing.box.id %}" class="box-link">Box {{ thing.box.id }}</a>
|
<a href="{% url 'box_detail' thing.box.id %}" style="color: #667eea; text-decoration: none;">Box {{ thing.box.id }}</a>
|
||||||
<br><small>{{ thing.box.box_type.name }}</small>
|
<br><span style="color: #999; font-size: 13px;">{{ thing.box.box_type.name }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ thing.description|default:"-" }}</td>
|
<td style="padding: 15px 20px; color: #777;">{{ thing.description|default:"-" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="empty-message">No things in this category</div>
|
<div style="text-align: center; padding: 40px; color: #999;">
|
||||||
|
<i class="fas fa-inbox" style="font-size: 48px; margin-bottom: 15px; display: block;"></i>
|
||||||
|
No things in this category
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="type-section">
|
<div class="section" style="text-align: center; padding: 60px 30px;">
|
||||||
<div class="empty-message">No things found in this category or its subcategories</div>
|
<i class="fas fa-folder-open" style="font-size: 64px; color: #ddd; margin-bottom: 20px; display: block;"></i>
|
||||||
|
<h3 style="color: #888; font-size: 20px;">No things found</h3>
|
||||||
|
<p style="color: #999; margin-top: 10px;">This category or its subcategories are empty.</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
$('tbody tr').hover(
|
||||||
|
function() {
|
||||||
|
$(this).css('background', '#f8f9fa');
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
$(this).css('background', 'white');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
BIN
data/media/cache/60/d1/60d1bb6c53ee06eb26d4708f19b149d9.jpg
vendored
Normal file
BIN
data/media/cache/60/d1/60d1bb6c53ee06eb26d4708f19b149d9.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 775 B |
BIN
data/media/cache/71/d0/71d025fefc5668ca6b1ff83985afe6ec.jpg
vendored
Normal file
BIN
data/media/cache/71/d0/71d025fefc5668ca6b1ff83985afe6ec.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
data/media/cache/8b/16/8b1685afb6011f553aa30d3992f9624d.jpg
vendored
Normal file
BIN
data/media/cache/8b/16/8b1685afb6011f553aa30d3992f9624d.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
@@ -59,7 +59,7 @@ ROOT_URLCONF = 'labhelper.urls'
|
|||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [BASE_DIR / 'labhelper' / 'templates'],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
|
|||||||
247
labhelper/templates/base.html
Normal file
247
labhelper/templates/base.html
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}LabHelper{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 15px 30px;
|
||||||
|
margin: 20px auto;
|
||||||
|
max-width: 1200px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand i {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a {
|
||||||
|
color: #555;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a:hover {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a i {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
color: #333;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .breadcrumb {
|
||||||
|
color: #888;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .breadcrumb a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .breadcrumb a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section h2 {
|
||||||
|
color: #667eea;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 3px solid #667eea;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section h2 i {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: linear-gradient(135deg, #7f8c8d 0%, #95a5a6 100%);
|
||||||
|
box-shadow: 0 4px 15px rgba(127, 140, 141, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
box-shadow: 0 6px 20px rgba(127, 140, 141, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: linear-gradient(135deg, #00b894 0%, #00cec9 100%);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 184, 148, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-error {
|
||||||
|
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% block extra_css %}{% endblock %}
|
||||||
|
</style>
|
||||||
|
{% block extra_head %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar">
|
||||||
|
<a href="/" class="navbar-brand">
|
||||||
|
<i class="fas fa-flask"></i>
|
||||||
|
LabHelper
|
||||||
|
</a>
|
||||||
|
<div class="navbar-nav">
|
||||||
|
<a href="/"><i class="fas fa-home"></i> Home</a>
|
||||||
|
<a href="/search/"><i class="fas fa-search"></i> Search</a>
|
||||||
|
<a href="/admin/"><i class="fas fa-cog"></i> Admin</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{% block page_header %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<p>© 2025 LabHelper. Built with <i class="fas fa-heart"></i> for science.</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
{% block extra_js %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user