Files
alro65 67a0e674ca Initial commit — MarineMaintenance v1.0
Marine maintenance management: work orders with photos, ISM/SWP procedures,
MSDS, inventory, RFQ/purchases, vessel history, bilingual PDF reports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 01:54:20 -04:00

141 lines
6.2 KiB
HTML

{% extends 'base.html' %}
{% block title %}Inventario{% endblock %}
{% block page_title %}Inventario{% endblock %}
{% block topbar_actions %}
<a href="{{ url_for('part_new') }}" class="btn btn-primary">+ Agregar Item</a>
{% endblock %}
{% block head %}
<style>
.inv-table { display:block; }
.inv-cards { display:none; }
@media (max-width:768px) {
.inv-table { display:none; }
.inv-cards { display:block; }
}
.icard {
background:var(--navy2);border:1px solid rgba(0,180,216,0.12);
border-radius:10px;margin-bottom:10px;overflow:hidden;
}
.icard-header {
padding:10px 14px;background:rgba(0,0,0,0.2);
border-bottom:1px solid rgba(255,255,255,0.05);
display:flex;justify-content:space-between;align-items:center;
}
.icard-name { font-size:14px;font-weight:600;color:var(--white); }
.icard-body { padding:10px 14px;font-size:13px; }
.icard-meta { display:flex;gap:12px;flex-wrap:wrap;color:var(--gray);margin-bottom:8px; }
.icard-stock { font-size:22px;font-weight:700;color:var(--cyan); }
.icard-actions {
display:flex;gap:8px;padding:8px 14px;
border-top:1px solid rgba(255,255,255,0.05);
}
.stock-low { color:var(--danger) !important; }
</style>
{% endblock %}
{% block content %}
<div style="display:flex;gap:10px;margin-bottom:14px;flex-wrap:wrap;align-items:center">
<select id="catFilter" onchange="filterInv()"
style="padding:7px 12px;border-radius:6px;background:rgba(255,255,255,0.06);
border:1px solid rgba(0,180,216,0.25);color:var(--white);font-size:13px">
<option value="">Todas las categorías</option>
{% for c in categories %}
<option value="{{ c.name|lower }}">{{ c.name }}</option>
{% endfor %}
</select>
<input type="text" id="invSearch" placeholder="🔍 Buscar repuesto o material..."
oninput="filterInv()"
style="flex:1;min-width:180px;padding:7px 12px;border-radius:6px;
background:rgba(255,255,255,0.06);border:1px solid rgba(0,180,216,0.25);
color:var(--white);font-size:13px">
</div>
<!-- TABLA DESKTOP -->
<div class="inv-table card">
<div class="table-wrap">
<table>
<thead><tr><th>Nombre</th><th>Categoría</th><th>N° Parte</th><th>Marca</th><th>Stock</th><th>Mín.</th><th>Precio</th><th></th></tr></thead>
<tbody>
{% for p in parts %}
<tr class="inv-row" data-search="{{ (p.name ~ ' ' ~ (p.part_number or '') ~ ' ' ~ (p.brand or '') ~ ' ' ~ (p.category_name or ''))|lower }}" data-cat="{{ (p.category_name or '')|lower }}">
<td>
<strong>{{ p.name }}</strong>
{% if p.description %}<br><span class="text-gray" style="font-size:11px">{{ p.description[:60] }}</span>{% endif %}
</td>
<td><span class="badge badge-open" style="font-size:11px">{{ p.category_name or '—' }}</span></td>
<td class="text-gray" style="font-family:monospace;font-size:12px">{{ p.part_number or '—' }}</td>
<td class="text-gray">{{ p.brand or '—' }}</td>
<td>
<span style="font-weight:600;{% if p.quantity <= p.min_quantity %}color:var(--danger){% else %}color:var(--success){% endif %}">
{{ p.quantity }} {{ p.unit }}
</span>
</td>
<td class="text-gray">{{ p.min_quantity }}</td>
<td>${{ "%.2f"|format(p.cost_price or 0) }}</td>
<td class="flex gap-2">
<a href="{{ url_for('part_edit', pid=p.id) }}" class="btn btn-sm btn-secondary">✏️</a>
</td>
</tr>
{% else %}
<tr><td colspan="8" class="text-gray" style="text-align:center;padding:30px">Sin items en inventario.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- TARJETAS MÓVIL -->
<div class="inv-cards">
{% for p in parts %}
<div class="icard inv-row" data-search="{{ (p.name ~ ' ' ~ (p.part_number or '') ~ ' ' ~ (p.brand or '') ~ ' ' ~ (p.category_name or ''))|lower }}" data-cat="{{ (p.category_name or '')|lower }}">
<div class="icard-header">
<div>
<div class="icard-name">{{ p.name }}</div>
{% if p.part_number %}<div style="font-size:11px;color:var(--gray);font-family:monospace">{{ p.part_number }}</div>{% endif %}
</div>
<span class="badge badge-open" style="font-size:11px">{{ p.category_name or '—' }}</span>
</div>
<div class="icard-body">
<div class="icard-meta">
{% if p.brand %}<span>🏷️ {{ p.brand }}</span>{% endif %}
{% if p.location %}<span>📍 {{ p.location }}</span>{% endif %}
<span>💲{{ "%.2f"|format(p.cost_price or 0) }}</span>
</div>
<div style="display:flex;align-items:center;gap:16px">
<div>
<div style="font-size:11px;color:var(--gray);margin-bottom:2px">Stock actual</div>
<div class="icard-stock {% if p.quantity <= p.min_quantity %}stock-low{% endif %}">
{{ p.quantity }} <span style="font-size:14px">{{ p.unit }}</span>
</div>
</div>
<div>
<div style="font-size:11px;color:var(--gray);margin-bottom:2px">Mínimo</div>
<div style="font-size:16px;color:var(--gray)">{{ p.min_quantity }} {{ p.unit }}</div>
</div>
{% if p.quantity <= p.min_quantity %}
<span style="color:var(--danger);font-size:12px;font-weight:600">⚠️ Stock bajo</span>
{% endif %}
</div>
</div>
<div class="icard-actions">
<a href="{{ url_for('part_edit', pid=p.id) }}" class="btn btn-sm btn-secondary" style="flex:1;text-align:center">✏️ Editar</a>
</div>
</div>
{% else %}
<div style="text-align:center;padding:40px;color:var(--gray)">Sin items en inventario.</div>
{% endfor %}
</div>
{% endblock %}
{% block scripts %}
<script>
function filterInv() {
const q = (document.getElementById('invSearch').value || '').toLowerCase().trim();
const cat = (document.getElementById('catFilter').value || '').toLowerCase();
document.querySelectorAll('.inv-row').forEach(r => {
const matchQ = !q || r.dataset.search.includes(q);
const matchC = !cat || r.dataset.cat.includes(cat);
r.style.display = (matchQ && matchC) ? '' : 'none';
});
}
</script>
{% endblock %}