Merge pull request 'improvement/design' (#3) from improvement/design into master
All checks were successful
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/labhelper) (push) Successful in 7s
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/labhelper-data-loader) (push) Successful in 7s
All checks were successful
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/labhelper) (push) Successful in 7s
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/labhelper-data-loader) (push) Successful in 7s
Reviewed-on: #3
This commit is contained in:
@@ -27,7 +27,7 @@ spec:
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: web
|
||||
image: git.baumann.gr/adebaumann/labhelper:0.027
|
||||
image: git.baumann.gr/adebaumann/labhelper:0.028
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
|
||||
@@ -1,213 +1,128 @@
|
||||
<!DOCTYPE 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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
<h1>Add Things to Box {{ box.id }}</h1>
|
||||
{% block title %}Add Things to Box {{ box.id }} - LabHelper{% endblock %}
|
||||
|
||||
<div class="container">
|
||||
<p>
|
||||
<strong>Box:</strong> {{ box.id }} ({{ box.box_type.name }})
|
||||
{% block page_header %}
|
||||
<div class="page-header">
|
||||
<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>
|
||||
</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 %}
|
||||
<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 field, errors in form_errors.items %}
|
||||
{% for error in errors %}
|
||||
<li>{{ error }}</li>
|
||||
{{ error }}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% 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 %}
|
||||
<form method="post" action="">
|
||||
<form method="post" style="overflow-x: auto;">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
<tr class="form-row">
|
||||
<th class="form-header-cell"></th>
|
||||
<th class="form-header form-header-cell">Name</th>
|
||||
<th class="form-header form-header-cell">Type</th>
|
||||
<th class="form-header form-header-cell">Description</th>
|
||||
<th class="form-header form-header-cell">Picture</th>
|
||||
<table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">
|
||||
<thead>
|
||||
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<th style="padding: 12px 15px; text-align: left; font-weight: 600;"></th>
|
||||
<th style="padding: 12px 15px; text-align: left; font-weight: 600;">Name</th>
|
||||
<th style="padding: 12px 15px; text-align: left; font-weight: 600;">Type</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>
|
||||
</thead>
|
||||
{{ formset.management_form }}
|
||||
{% for form in formset %}
|
||||
<tr class="form-row">
|
||||
<td class="form-cell">
|
||||
<tr style="border-bottom: 1px solid #e0e0e0; background: {% if forloop.counter|divisibleby:2 %}#f8f9fa{% endif %};">
|
||||
<td style="padding: 12px 15px;">
|
||||
{{ form.id }}
|
||||
</td>
|
||||
<td class="form-cell">
|
||||
<td style="padding: 12px 15px;">
|
||||
<div style="display: flex; gap: 5px; flex-wrap: wrap; align-items: center;">
|
||||
{{ form.name }}
|
||||
{% for error in form.name.errors %}
|
||||
<div class="error-list">
|
||||
<li>{{ error }}</li>
|
||||
<div style="color: #e74c3c; font-size: 13px; margin-top: 5px;">
|
||||
<i class="fas fa-exclamation-circle"></i> {{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<label class="required">*</label>
|
||||
<span style="color: #e74c3c;">*</span>
|
||||
</div>
|
||||
</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 }}
|
||||
{% for error in form.thing_type.errors %}
|
||||
<div class="error-list">
|
||||
<li>{{ error }}</li>
|
||||
<div style="color: #e74c3c; font-size: 13px; margin-top: 5px;">
|
||||
<i class="fas fa-exclamation-circle"></i> {{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<label class="required">*</label>
|
||||
<span style="color: #e74c3c;">*</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="form-cell">
|
||||
<td style="padding: 12px 15px;">
|
||||
<div style="display: flex; gap: 5px; flex-wrap: wrap; align-items: center;">
|
||||
{{ form.description }}
|
||||
{% for error in form.description.errors %}
|
||||
<div class="error-list">
|
||||
<li>{{ error }}</li>
|
||||
<div style="color: #e74c3c; font-size: 13px; margin-top: 5px;">
|
||||
<i class="fas fa-exclamation-circle"></i> {{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="form-cell">
|
||||
<td style="padding: 12px 15px;">
|
||||
<div style="display: flex; gap: 5px; flex-wrap: wrap; align-items: center;">
|
||||
{{ form.picture }}
|
||||
{% for error in form.picture.errors %}
|
||||
<div class="error-list">
|
||||
<li>{{ error }}</li>
|
||||
<div style="color: #e74c3c; font-size: 13px; margin-top: 5px;">
|
||||
<i class="fas fa-exclamation-circle"></i> {{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="form-row">
|
||||
<td class="form-cell" colspan="5">
|
||||
<button type="submit" class="btn">Save Things</button>
|
||||
</td>
|
||||
</tr>
|
||||
</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>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% if success_message %}
|
||||
<div class="success-message">
|
||||
{{ success_message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
$('form input, form select, form textarea').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,122 +1,90 @@
|
||||
{% extends "base.html" %}
|
||||
{% 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">
|
||||
<strong>Type:</strong> {{ box.box_type.name }}
|
||||
({{ box.box_type.width }} x {{ box.box_type.height }} x {{ box.box_type.length }} mm)
|
||||
<br><br>
|
||||
<a href="/box/{{ box.id }}/add/">+ Add Things</a>
|
||||
{% block page_header %}
|
||||
<div class="page-header">
|
||||
<h1><i class="fas fa-box"></i> Box {{ box.id }}</h1>
|
||||
<p class="breadcrumb">
|
||||
<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>
|
||||
|
||||
{% if things %}
|
||||
<table>
|
||||
{% if things %}
|
||||
<div class="section">
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Picture</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Picture</th>
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Name</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;">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for thing in things %}
|
||||
<tr>
|
||||
<td>
|
||||
<tr style="border-bottom: 1px solid #e0e0e0; transition: background 0.2s;">
|
||||
<td style="padding: 15px 20px;">
|
||||
{% if thing.picture %}
|
||||
{% 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 %}
|
||||
{% 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 %}
|
||||
</td>
|
||||
<td><a href="{% url 'thing_detail' thing.id %}">{{ thing.name }}</a></td>
|
||||
<td>{{ thing.thing_type.name }}</td>
|
||||
<td>{{ thing.description|default:"-" }}</td>
|
||||
<td style="padding: 15px 20px;">
|
||||
<a href="{% url 'thing_detail' thing.id %}" style="color: #667eea; text-decoration: none; font-weight: 500;">{{ thing.name }}</a>
|
||||
</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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty-message">
|
||||
This box is empty.
|
||||
</div>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
</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;">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>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% 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 %}
|
||||
<!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">
|
||||
<a href="/search/">Search Things</a>
|
||||
<a href="/admin/">Admin</a>
|
||||
</div>
|
||||
{% block title %}LabHelper - Home{% endblock %}
|
||||
|
||||
<div class="section">
|
||||
<h2>Boxes</h2>
|
||||
{% 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">
|
||||
<h2><i class="fas fa-box"></i> Boxes</h2>
|
||||
{% 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 %}
|
||||
<div class="box-card">
|
||||
<a href="{% url 'box_detail' box.id %}">
|
||||
<div class="box-id">Box {{ box.id }}</div>
|
||||
<div class="box-type">{{ box.box_type.name }}</div>
|
||||
<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 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: 12px; margin-top: 3px;">
|
||||
{{ box.things.count }} item{{ box.things.count|pluralize }}
|
||||
<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>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 %}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2><i class="fas fa-folder-tree"></i> Thing Types</h2>
|
||||
{% if thing_types %}
|
||||
<ul class="tree" style="list-style: none; padding-left: 0;">
|
||||
{% recursetree thing_types %}
|
||||
<li style="padding: 8px 0;">
|
||||
<div class="tree-item" style="display: flex; align-items: center; gap: 8px;">
|
||||
{% if children %}
|
||||
<span class="toggle-handle" style="display: inline-block; width: 24px; color: #667eea; font-weight: bold; cursor: pointer; transition: transform 0.2s;">[-]</span>
|
||||
{% else %}
|
||||
<span class="toggle-handle" style="display: inline-block; width: 24px; color: #ccc;"> </span>
|
||||
{% endif %}
|
||||
<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 %}
|
||||
<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 %}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Thing Types</h2>
|
||||
{% if thing_types %}
|
||||
<ul class="tree">
|
||||
{% recursetree thing_types %}
|
||||
<li>
|
||||
{% if children %}
|
||||
<span class="toggle-handle">[-]</span>
|
||||
{% else %}
|
||||
<span class="toggle-handle"> </span>
|
||||
{% endif %}
|
||||
<a href="{% url 'thing_type_detail' node.pk %}">{{ node.name }}</a>
|
||||
{% if node.things.exists %}
|
||||
<span style="color: #999; font-size: 13px;">({{ node.things.count }})</span>
|
||||
{% endif %}
|
||||
{% if children %}
|
||||
<ul>
|
||||
<ul style="list-style: none; padding-left: 32px;">
|
||||
{{ children }}
|
||||
</ul>
|
||||
{% endif %}
|
||||
@@ -197,9 +68,36 @@
|
||||
{% endrecursetree %}
|
||||
</ul>
|
||||
{% 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 %}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% 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,151 +1,76 @@
|
||||
<!DOCTYPE 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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
<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"
|
||||
id="search-input"
|
||||
class="search-input"
|
||||
placeholder="Search for things..."
|
||||
autocomplete="off">
|
||||
<div class="search-hint">Type at least 2 characters to search</div>
|
||||
</div>
|
||||
style="width: 100%; padding: 16px 20px; font-size: 18px; border: 2px solid #e0e0e0; border-radius: 12px; box-sizing: border-box; transition: all 0.3s;">
|
||||
<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">
|
||||
<table>
|
||||
<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>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Box</th>
|
||||
<th>Description</th>
|
||||
<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;">Type</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="no-results" style="display: none;">
|
||||
No results found.
|
||||
</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 %}
|
||||
|
||||
<script>
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const resultsContainer = document.getElementById('results-container');
|
||||
const resultsBody = document.getElementById('results-body');
|
||||
const noResults = document.getElementById('no-results');
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
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;
|
||||
let searchTimeout = null;
|
||||
|
||||
searchInput.addEventListener('input', function() {
|
||||
searchInput.addEventListener('input', function() {
|
||||
const query = this.value.trim();
|
||||
|
||||
// Clear previous timeout
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
// Hide results if query too short
|
||||
if (query.length < 2) {
|
||||
resultsContainer.style.display = 'none';
|
||||
noResults.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce search
|
||||
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())
|
||||
@@ -163,25 +88,38 @@
|
||||
|
||||
data.results.forEach(function(thing) {
|
||||
const row = document.createElement('tr');
|
||||
row.style.borderBottom = '1px solid #e0e0e0';
|
||||
row.style.transition = 'background 0.2s';
|
||||
row.innerHTML =
|
||||
'<td><a href="/thing/' + thing.id + '/">' + escapeHtml(thing.name) + '</a></td>' +
|
||||
'<td>' + escapeHtml(thing.type) + '</td>' +
|
||||
'<td><a href="/box/' + escapeHtml(thing.box) + '/">' + escapeHtml(thing.box) + '</a></td>' +
|
||||
'<td class="description">' + escapeHtml(thing.description) + '</td>';
|
||||
'<td style="padding: 15px 20px;"><a href="/thing/' + thing.id + '/">' + escapeHtml(thing.name) + '</a></td>' +
|
||||
'<td style="padding: 15px 20px; color: #555;">' + escapeHtml(thing.type) + '</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);
|
||||
});
|
||||
});
|
||||
|
||||
function escapeHtml(text) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Focus search input on page load
|
||||
searchInput.focus();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
searchInput.focus();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,177 +1,79 @@
|
||||
{% extends "base.html" %}
|
||||
{% 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.name }} - LabHelper</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
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>
|
||||
|
||||
<h1>{{ thing.name }}</h1>
|
||||
{% block title %}{{ thing.name }} - LabHelper{% endblock %}
|
||||
|
||||
<div class="thing-card">
|
||||
<div class="thing-image">
|
||||
{% block page_header %}
|
||||
<div class="page-header">
|
||||
<h1><i class="fas fa-cube"></i> {{ thing.name }}</h1>
|
||||
<p class="breadcrumb">
|
||||
<a href="/"><i class="fas fa-home"></i> Home</a> /
|
||||
<a href="/box/{{ thing.box.id }}/"><i class="fas fa-box"></i> Box {{ thing.box.id }}</a> /
|
||||
{{ thing.name }}
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<div class="thing-card" style="display: flex; gap: 40px; flex-wrap: wrap;">
|
||||
<div class="thing-image" style="flex-shrink: 0;">
|
||||
{% if thing.picture %}
|
||||
{% thumbnail thing.picture "300x300" crop="center" as thumb %}
|
||||
<img src="{{ thumb.url }}" alt="{{ thing.name }}">
|
||||
{% thumbnail thing.picture "400x400" crop="center" as thumb %}
|
||||
<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 %}
|
||||
{% 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 %}
|
||||
</div>
|
||||
|
||||
<div class="thing-details">
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">Type</div>
|
||||
<div class="detail-value">{{ thing.thing_type.name }}</div>
|
||||
<div class="thing-details" style="flex-grow: 1; min-width: 300px;">
|
||||
<div class="detail-row" style="margin-bottom: 25px;">
|
||||
<div style="font-size: 14px; color: #888; font-weight: 600; margin-bottom: 8px;">
|
||||
<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 class="detail-row">
|
||||
<div class="detail-label">Location</div>
|
||||
<div class="detail-value">
|
||||
<a href="/box/{{ thing.box.id }}/">Box {{ thing.box.id }}</a>
|
||||
({{ thing.box.box_type.name }})
|
||||
<div class="detail-row" style="margin-bottom: 25px;">
|
||||
<div style="font-size: 14px; color: #888; font-weight: 600; margin-bottom: 8px;">
|
||||
<i class="fas fa-map-marker-alt"></i> Location
|
||||
</div>
|
||||
<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>
|
||||
|
||||
{% if thing.description %}
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">Description</div>
|
||||
<div class="detail-value description">{{ thing.description }}</div>
|
||||
<div class="detail-row" style="margin-bottom: 25px;">
|
||||
<div style="font-size: 14px; color: #888; font-weight: 600; margin-bottom: 8px;">
|
||||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" class="move-form">
|
||||
<form method="post" class="section">
|
||||
{% csrf_token %}
|
||||
<label for="new_box">Move to:</label>
|
||||
<select name="new_box" id="new_box">
|
||||
<div style="display: flex; align-items: center; gap: 15px; flex-wrap: wrap;">
|
||||
<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>
|
||||
{% for box in boxes %}
|
||||
<option value="{{ box.id }}" {% if box.id == thing.box.id %}selected{% endif %}>
|
||||
@@ -179,7 +81,23 @@
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="submit" class="btn">Move</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
</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>
|
||||
|
||||
{% 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 %}
|
||||
<!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 %}
|
||||
<div class="type-hierarchy">
|
||||
Parent: <a href="{% url 'thing_type_detail' thing_type.parent.id %}" class="box-link">{{ thing_type.parent.name }}</a>
|
||||
<div style="margin-bottom: 15px; padding: 15px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 10px; border-left: 4px solid #667eea;">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if thing_type.children.exists %}
|
||||
<div class="type-hierarchy">
|
||||
Subtypes:
|
||||
{% for child in thing_type.children.all %}
|
||||
<span><a href="{% url 'thing_type_detail' child.id %}" class="box-link">{{ child.name }}</a></span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if things_by_type %}
|
||||
{% for subtype, things in things_by_type.items %}
|
||||
<div class="type-section">
|
||||
<h2>{{ subtype.name }}</h2>
|
||||
{% if thing_type.children.exists %}
|
||||
<div style="margin-bottom: 20px; padding: 15px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 10px; border-left: 4px solid #764ba2;">
|
||||
<span style="color: #666; font-size: 14px;">
|
||||
<i class="fas fa-sitemap"></i> Subtypes:
|
||||
{% for child in thing_type.children.all %}
|
||||
<a href="{% url 'thing_type_detail' child.id %}" style="color: #667eea; text-decoration: none; font-weight: 500; margin-left: 8px;">{{ child.name }}</a>
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if things_by_type %}
|
||||
{% for subtype, things in things_by_type.items %}
|
||||
<div class="section">
|
||||
<h2><i class="fas fa-cubes"></i> {{ subtype.name }}</h2>
|
||||
{% if things %}
|
||||
<table>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Picture</th>
|
||||
<th>Name</th>
|
||||
<th>Box</th>
|
||||
<th>Description</th>
|
||||
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Picture</th>
|
||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Name</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>
|
||||
{% for thing in things %}
|
||||
<tr>
|
||||
<td>
|
||||
<tr style="border-bottom: 1px solid #e0e0e0; transition: background 0.2s;">
|
||||
<td style="padding: 15px 20px;">
|
||||
{% if thing.picture %}
|
||||
{% 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 %}
|
||||
{% 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 %}
|
||||
</td>
|
||||
<td class="thing-name">
|
||||
<a href="{% url 'thing_detail' thing.id %}">{{ thing.name }}</a>
|
||||
<td style="padding: 15px 20px;">
|
||||
<a href="{% url 'thing_detail' thing.id %}" style="color: #667eea; text-decoration: none; font-weight: 500;">{{ thing.name }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'box_detail' thing.box.id %}" class="box-link">Box {{ thing.box.id }}</a>
|
||||
<br><small>{{ thing.box.box_type.name }}</small>
|
||||
<td style="padding: 15px 20px;">
|
||||
<a href="{% url 'box_detail' thing.box.id %}" style="color: #667eea; text-decoration: none;">Box {{ thing.box.id }}</a>
|
||||
<br><span style="color: #999; font-size: 13px;">{{ thing.box.box_type.name }}</span>
|
||||
</td>
|
||||
<td>{{ thing.description|default:"-" }}</td>
|
||||
<td style="padding: 15px 20px; color: #777;">{{ thing.description|default:"-" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty-message">No things in this category</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="type-section">
|
||||
<div class="empty-message">No things found in this category or its subcategories</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 %}
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="section" style="text-align: center; padding: 60px 30px;">
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% 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 = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'DIRS': [BASE_DIR / 'labhelper' / 'templates'],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'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