Files
fastapi-inventory/templates/inventory.html

281 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Inventory Management - Media Inventory App{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<h2>📦 Inventory Management</h2>
<p class="text-muted">Manage your InvenTree storage locations and inventory items</p>
{% if error %}
<div class="alert alert-danger" role="alert">
<h5>⚠️ Configuration Error</h5>
<p>{{ error }}</p>
<hr>
<p class="mb-0">
<strong>To configure InvenTree integration:</strong><br>
1. Set up your InvenTree instance<br>
2. Add your API credentials to the .env file<br>
3. Restart the application
</p>
</div>
{% else %}
<!-- Storage Locations Section -->
<div class="row mb-4">
<div class="col-md-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">📍 Storage Locations</h5>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#createLocationModal">
Add Location
</button>
</div>
<div class="card-body">
{% if locations %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Path</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for location in locations %}
<tr id="location-row-{{ location.pk }}">
<td>{{ location.pk }}</td>
<td>{{ location.name }}</td>
<td><small class="text-muted">{{ location.pathstring }}</small></td>
<td>
<button class="btn btn-outline-danger btn-sm"
onclick="deleteLocation({{ location.pk }}, '{{ location.name }}')"
title="Delete empty location">
🗑️ Delete
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<p class="text-muted">No storage locations found</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createLocationModal">
Create Your First Location
</button>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5 class="mb-0"> How to Use</h5>
</div>
<div class="card-body">
<ol class="small">
<li><strong>Create Locations:</strong> Set up storage locations for your media</li>
<li><strong>Search Media:</strong> Use the search function to find items</li>
<li><strong>Add to Inventory:</strong> Click "Add to Inventory" on search results</li>
<li><strong>Delete Empty Locations:</strong> Remove unused locations with the delete button</li>
<li><strong>Manage:</strong> Use InvenTree web interface for advanced management</li>
</ol>
<hr>
<p class="small text-muted mb-0">
<strong>Connected to:</strong><br>
InvenTree API ✅
</p>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Create Location Modal -->
<div class="modal fade" id="createLocationModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"> Create Storage Location</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="createLocationForm">
<div class="mb-3">
<label for="locationName" class="form-label">Location Name *</label>
<input type="text" class="form-control" id="locationName" name="name" required
placeholder="e.g., Living Room Shelf, Bedroom Closet">
</div>
<div class="mb-3">
<label for="locationDescription" class="form-label">Description</label>
<textarea class="form-control" id="locationDescription" name="description" rows="3"
placeholder="Optional description of this storage location"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="createLocation()">Create Location</button>
</div>
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteLocationModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">🗑️ Delete Storage Location</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-warning">
<strong>⚠️ Warning:</strong> This action cannot be undone.
</div>
<p>Are you sure you want to delete the storage location <strong id="deleteLocationName"></strong>?</p>
<p class="text-muted small">
<strong>Note:</strong> You can only delete empty locations. If this location contains any stock items,
you'll need to move or remove them first.
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">Delete Location</button>
</div>
</div>
</div>
</div>
<script>
async function createLocation() {
const form = document.getElementById('createLocationForm');
const formData = new FormData(form);
const data = Object.fromEntries(formData);
try {
const response = await fetch('/api/storage-locations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
// Close modal and reload page
const modal = bootstrap.Modal.getInstance(document.getElementById('createLocationModal'));
modal.hide();
location.reload();
} else {
alert('Error creating location: ' + (result.error || 'Unknown error'));
}
} catch (error) {
alert('Error creating location: ' + error.message);
}
}
let locationToDelete = null;
function deleteLocation(locationId, locationName) {
// Store the location info for deletion
locationToDelete = { id: locationId, name: locationName };
// Update modal content
document.getElementById('deleteLocationName').textContent = locationName;
// Show the modal
const modal = new bootstrap.Modal(document.getElementById('deleteLocationModal'));
modal.show();
}
// Handle the actual deletion when confirmed
document.addEventListener('DOMContentLoaded', function() {
const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
if (confirmDeleteBtn) {
confirmDeleteBtn.addEventListener('click', async function() {
if (!locationToDelete) return;
// Disable button and show loading
confirmDeleteBtn.disabled = true;
confirmDeleteBtn.textContent = 'Deleting...';
try {
const response = await fetch(`/api/storage-locations/${locationToDelete.id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
}
});
const result = await response.json();
// Close modal
const modal = bootstrap.Modal.getInstance(document.getElementById('deleteLocationModal'));
modal.hide();
if (result.success) {
// Remove the row from the table
const row = document.getElementById(`location-row-${locationToDelete.id}`);
if (row) {
row.remove();
}
// Show success message
showAlert('success', result.message || 'Storage location deleted successfully');
} else {
// Show error message
showAlert('danger', result.message || result.error || 'Failed to delete location');
}
} catch (error) {
// Close modal
const modal = bootstrap.Modal.getInstance(document.getElementById('deleteLocationModal'));
modal.hide();
showAlert('danger', 'Error deleting location: ' + error.message);
} finally {
// Reset button
confirmDeleteBtn.disabled = false;
confirmDeleteBtn.textContent = 'Delete Location';
locationToDelete = null;
}
});
}
});
function showAlert(type, message) {
// Create alert element
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// Insert at the top of the content
const container = document.querySelector('.container-fluid, .container');
if (container) {
container.insertBefore(alertDiv, container.firstChild);
// Auto-dismiss after 5 seconds
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
}
</script>
{% endblock %}