Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
255 lines
13 KiB
HTML
255 lines
13 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}My Labels - Admin{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/labels-bricklink.css') }}">
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="bl">
|
|
{% set section = 'labels' %}
|
|
{% set bar_sub = total_labels ~ ' label' ~ ('' if total_labels == 1 else 's') ~ ' defined' %}
|
|
{% set crumbs = [{'name': 'My Labels'}] + ([{'name': active_category.full_name}] if active_category else []) %}
|
|
{% include 'admin/labels/_nav.html' %}
|
|
|
|
{% if not rebrickable_ready %}
|
|
<div class="bl-panel" style="border-top:0;border-radius:0 0 3px 3px;">
|
|
<div class="bl-panel-body">
|
|
<span class="bl-tag warn">No API key</span>
|
|
Part lookup and photos are off. You can still add labels by hand and upload your own
|
|
images, and the <a href="{{ url_for('catalog.index') }}">part catalogue</a> works
|
|
without a key. <a href="{{ url_for('labels.settings') }}">Add a key</a> — it's free.
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="row g-2">
|
|
<!-- Drawer category tree -->
|
|
<div class="col-lg-3">
|
|
<div class="bl-panel">
|
|
<div class="bl-panel-head">
|
|
<span>My Categories</span>
|
|
<a href="{{ url_for('labels.index') }}" class="bl-btn bl-btn-sm">All</a>
|
|
</div>
|
|
<div class="bl-panel-body tight bl-tree" style="max-height:66vh;overflow-y:auto;">
|
|
{% for root in roots %}
|
|
<a href="{{ url_for('labels.index', category=root.id) }}"
|
|
class="{% if active_category and active_category.id == root.id %}active{% endif %}">
|
|
<span class="text-truncate">
|
|
<span class="bl-swatch" style="background:{{ root.accent_colour() }};"></span>
|
|
<strong>{{ root.display_code }}</strong> {{ root.name }}
|
|
</span>
|
|
<span class="bl-count">{{ root.label_count() }}</span>
|
|
</a>
|
|
{% for child in root.children %}
|
|
<a href="{{ url_for('labels.index', category=child.id) }}"
|
|
class="child {% if active_category and active_category.id == child.id %}active{% endif %}">
|
|
<span class="text-truncate">{{ child.name }}</span>
|
|
<span class="bl-count">{{ child.label_count() }}</span>
|
|
</a>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="bl-empty">No categories yet.</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Labels -->
|
|
<div class="col-lg-9">
|
|
<div class="bl-panel">
|
|
<div class="bl-panel-body">
|
|
<form method="GET" class="bl-filters">
|
|
{% if active_category %}<input type="hidden" name="category" value="{{ active_category.id }}">{% endif %}
|
|
<div style="flex:2 1 280px;">
|
|
<label class="fld">Search my labels</label>
|
|
<input type="search" name="q" value="{{ search }}" class="w-100"
|
|
placeholder="Part number, name, dimensions or drawer…">
|
|
</div>
|
|
<button class="bl-btn bl-btn-primary"><i class="bi bi-search"></i> Search</button>
|
|
<a href="{{ url_for('catalog.index') }}" class="bl-btn">
|
|
<i class="bi bi-box-seam"></i> Add from catalogue
|
|
</a>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{% if rebrickable_ready %}
|
|
<div class="bl-panel">
|
|
<div class="bl-panel-head"><span>Quick lookup on Rebrickable</span></div>
|
|
<div class="bl-panel-body">
|
|
<div class="bl-filters">
|
|
<input type="text" id="lookupQuery" style="flex:2 1 280px;"
|
|
placeholder="3001 or "brick 2 x 4"">
|
|
<button class="bl-btn" type="button" id="lookupBtn">Search</button>
|
|
</div>
|
|
<div id="lookupResults" class="row g-2 mt-2"></div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<form method="POST" action="{{ url_for('labels.print_sheet') }}" id="printForm">
|
|
<div class="bl-panel">
|
|
<div class="bl-panel-head">
|
|
<span>
|
|
<input type="checkbox" id="checkAll"> Select all on page
|
|
</span>
|
|
<span class="d-flex gap-1 align-items-center flex-wrap">
|
|
<select name="preset" style="max-width:240px;">
|
|
{% for key, preset in presets.items() %}
|
|
<option value="{{ key }}" {% if key == default_preset %}selected{% endif %}>{{ preset.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button class="bl-btn bl-btn-primary" type="submit">
|
|
<i class="bi bi-printer"></i> Sheet
|
|
</button>
|
|
<button class="bl-btn bl-btn-go" type="submit"
|
|
formaction="{{ url_for('tape.index') }}" formmethod="POST">
|
|
<i class="bi bi-upc"></i> Tape
|
|
</button>
|
|
<button class="bl-btn" type="submit"
|
|
formaction="{{ url_for('dymo.index') }}" formmethod="POST">
|
|
<i class="bi bi-stickies"></i> Dymo
|
|
</button>
|
|
</span>
|
|
</div>
|
|
<div class="bl-panel-body tight">
|
|
<table class="bl-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:26px;"></th>
|
|
<th style="width:46px;">Img</th>
|
|
<th style="width:110px;">Part no.</th>
|
|
<th>Name</th>
|
|
<th style="width:170px;">Category</th>
|
|
<th style="width:150px;">Drawer</th>
|
|
<th style="width:80px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for label in labels %}
|
|
<tr>
|
|
<td><input type="checkbox" class="label-check" name="ids" value="{{ label.id }}"></td>
|
|
<td>
|
|
{% if label.get_image() %}
|
|
<img class="thumb" src="{{ label.get_image() }}" alt="" loading="lazy">
|
|
{% endif %}
|
|
</td>
|
|
<td class="num">{{ label.part_numbers_text }}</td>
|
|
<td>
|
|
<span class="bl-name">{{ label.name }}</span>
|
|
{% if label.dimensions %}<span class="bl-dim">{{ label.dimensions }}</span>{% endif %}
|
|
{% if label.colour_name %}<span class="bl-tag">{{ label.colour_name }}</span>{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if label.category %}
|
|
<span class="bl-swatch" style="background:{{ label.category.accent_colour() }};"></span>
|
|
{{ label.category.full_name }}
|
|
{% else %}<span class="bl-dim">Uncategorised</span>{% endif %}
|
|
</td>
|
|
<td>{{ label.location or '' }}</td>
|
|
<td class="text-end">
|
|
<a href="{{ url_for('labels.edit_label', label_id=label.id) }}"
|
|
class="bl-btn bl-btn-sm"><i class="bi bi-pencil"></i></a>
|
|
<a href="{{ url_for('tape.index', ids=label.id) }}"
|
|
class="bl-btn bl-btn-sm" title="Print to tape"><i class="bi bi-upc"></i></a>
|
|
<a href="{{ url_for('dymo.index', ids=label.id) }}"
|
|
class="bl-btn bl-btn-sm" title="Print on Dymo stock"><i class="bi bi-stickies"></i></a>
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr><td colspan="7" class="bl-empty">
|
|
No labels here yet.
|
|
<a href="{{ url_for('catalog.index') }}">Pick some from the catalogue</a>
|
|
or <a href="{{ url_for('labels.new_label', category=active_category.id if active_category else None) }}">add one by hand</a>.
|
|
</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if pagination.pages > 1 %}
|
|
<div class="bl-pager">
|
|
<a class="bl-btn bl-btn-sm" href="{{ url_for('labels.index', page=pagination.prev_num, category=active_category.id if active_category else None, q=search) }}">« Prev</a>
|
|
<span>Page {{ pagination.page }} of {{ pagination.pages }}</span>
|
|
<a class="bl-btn bl-btn-sm" href="{{ url_for('labels.index', page=pagination.next_num, category=active_category.id if active_category else None, q=search) }}">Next »</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</form>
|
|
|
|
{% if active_category %}
|
|
<div class="d-flex gap-1 justify-content-end">
|
|
<a href="{{ url_for('labels.print_sheet', category=active_category.id, preset=default_preset) }}"
|
|
class="bl-btn">Print whole category to sheet</a>
|
|
<a href="{{ url_for('tape.index', category=active_category.id) }}"
|
|
class="bl-btn">…to tape</a>
|
|
<a href="{{ url_for('dymo.index', category=active_category.id) }}"
|
|
class="bl-btn">…to Dymo</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="POST" action="{{ url_for('labels.import_part') }}" id="importForm">
|
|
<input type="hidden" name="part_number" id="importPartNumber">
|
|
<input type="hidden" name="category_id" value="{{ active_category.id if active_category else '' }}">
|
|
</form>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
document.getElementById('checkAll')?.addEventListener('change', function () {
|
|
document.querySelectorAll('.label-check').forEach(b => { b.checked = this.checked; });
|
|
});
|
|
|
|
const lookupBtn = document.getElementById('lookupBtn');
|
|
if (lookupBtn) {
|
|
const runLookup = async () => {
|
|
const query = document.getElementById('lookupQuery').value.trim();
|
|
const target = document.getElementById('lookupResults');
|
|
if (!query) { target.innerHTML = ''; return; }
|
|
target.innerHTML = '<div class="col-12 bl-dim">Searching…</div>';
|
|
try {
|
|
const r = await fetch('{{ url_for("labels.lookup") }}?q=' + encodeURIComponent(query));
|
|
const data = await r.json();
|
|
if (!data.ok) {
|
|
target.innerHTML = '<div class="col-12"><span class="bl-tag err">Error</span> ' +
|
|
(data.reason || 'Lookup failed.') + '</div>';
|
|
return;
|
|
}
|
|
if (!data.results.length) {
|
|
target.innerHTML = '<div class="col-12 bl-dim">Nothing found.</div>';
|
|
return;
|
|
}
|
|
target.innerHTML = data.results.map(p => `
|
|
<div class="col-6 col-md-4 col-xl-3">
|
|
<div class="bl-panel mb-0" style="height:100%;">
|
|
<div class="bl-panel-body text-center" style="padding:6px;">
|
|
${p.image_url ? `<img src="${p.image_url}" alt="" style="height:52px;object-fit:contain;">` : ''}
|
|
<div class="bl-name text-truncate" title="${p.name}">${p.name}</div>
|
|
<div class="num">${p.part_number}</div>
|
|
<button type="button" class="bl-btn bl-btn-sm bl-btn-go mt-1 import-part"
|
|
data-part="${p.part_number}">Add label</button>
|
|
</div>
|
|
</div>
|
|
</div>`).join('');
|
|
target.querySelectorAll('.import-part').forEach(b => {
|
|
b.addEventListener('click', () => {
|
|
document.getElementById('importPartNumber').value = b.dataset.part;
|
|
document.getElementById('importForm').submit();
|
|
});
|
|
});
|
|
} catch (e) {
|
|
target.innerHTML = '<div class="col-12"><span class="bl-tag err">Failed</span></div>';
|
|
}
|
|
};
|
|
lookupBtn.addEventListener('click', runLookup);
|
|
document.getElementById('lookupQuery').addEventListener('keydown', e => {
|
|
if (e.key === 'Enter') { e.preventDefault(); runLookup(); }
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %}
|