Implements a working in-memory ERP demo inspired by Mago4, covering the core modules: dashboard with KPI widgets, customers, suppliers, products, sales orders, purchase orders, and warehouse inventory. - FastAPI backend with Pydantic models and REST API (auto-docs at /docs) - Jinja2 HTML templates with Mago4-style UI (dark sidebar, blue/orange/ green/red palette, Material Design icons) - In-memory fake database seeded with sample customers, suppliers, products, and orders — ready to swap for SQLAlchemy + SQL Server - Workarounds for Python 3.14 + Starlette 1.3 / Jinja2 compatibility (cache_size=0, new TemplateResponse(request, name, ctx) signature) - launch.json for one-click preview; run.bat for manual startup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
121 lines
3.2 KiB
JavaScript
121 lines
3.2 KiB
JavaScript
// Utility functions for API calls
|
|
async function apiCall(endpoint, method = 'GET', data = null) {
|
|
const options = {
|
|
method: method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
};
|
|
|
|
if (data) {
|
|
options.body = JSON.stringify(data);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(endpoint, options);
|
|
if (!response.ok) {
|
|
throw new Error(`API Error: ${response.statusText}`);
|
|
}
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('API call failed:', error);
|
|
showNotification('Errore nella richiesta', 'error');
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Notification system
|
|
function showNotification(message, type = 'info') {
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification notification-${type}`;
|
|
notification.textContent = message;
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
padding: 12px 20px;
|
|
background-color: ${type === 'error' ? '#D32F2F' : '#1976D2'};
|
|
color: white;
|
|
border-radius: 4px;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
|
z-index: 2000;
|
|
animation: slideIn 0.3s ease-in-out;
|
|
`;
|
|
|
|
document.body.appendChild(notification);
|
|
setTimeout(() => notification.remove(), 3000);
|
|
}
|
|
|
|
// Format currency
|
|
function formatCurrency(value) {
|
|
return new Intl.NumberFormat('it-IT', {
|
|
style: 'currency',
|
|
currency: 'EUR'
|
|
}).format(value);
|
|
}
|
|
|
|
// Format date
|
|
function formatDate(dateString) {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('it-IT');
|
|
}
|
|
|
|
// Initialize page animations
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Add smooth scrolling
|
|
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
|
anchor.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const target = document.querySelector(this.getAttribute('href'));
|
|
if (target) {
|
|
target.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add keyboard shortcuts
|
|
document.addEventListener('keydown', function(e) {
|
|
// Close modals with Escape
|
|
if (e.key === 'Escape') {
|
|
document.querySelectorAll('.modal.show').forEach(modal => {
|
|
modal.classList.remove('show');
|
|
});
|
|
}
|
|
});
|
|
|
|
// Initialize tooltips
|
|
document.querySelectorAll('[title]').forEach(element => {
|
|
element.style.cursor = 'help';
|
|
});
|
|
});
|
|
|
|
// Add CSS animation for notifications
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
@keyframes slideIn {
|
|
from {
|
|
transform: translateX(400px);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.material-icons {
|
|
font-family: 'Material Icons';
|
|
font-weight: normal;
|
|
font-style: normal;
|
|
font-size: 24px;
|
|
display: inline-block;
|
|
line-height: 1;
|
|
text-transform: none;
|
|
letter-spacing: normal;
|
|
word-wrap: normal;
|
|
white-space: nowrap;
|
|
direction: ltr;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|