Restyle UI to match Mago4 and add invoicing, warehouse, scadenzario
Rework the demo into a faithful Mago4 look-and-feel and expand it with core ERP modules modelled on the real Mago4 database schema. UI (ispirata a Mago4): - Blue header with logo/search/operation date, desktop-style window tabs - White sidebar with coloured module icons (Anagrafiche, Vendite, Acquisti, Amministrazione, Logistica) - Card-based module landing pages with breadcrumb + tab strip - Document detail forms with left tabs, line grid and totals box Fatturazione (InvoiceMng / MA_SaleDoc): - Document types (Fattura Immediata, DDT, Nota di Credito, ProForma) - Lines with discount/IVA, automatic taxable/tax/total computation - Document states (draft -> printed -> issued / posted_to_inventory) Magazzino (Inventory / MA_InventoryEntries + MA_InventoryReasons): - Inventory movements with load/unload reasons (DebitCreditSign) - Posting a movement actually updates item balances - Causali and stock valuation pages Amministrazione / Scadenzario (AP_AR / MA_PyblsRcvbls): - Open items (receivables/payables) with due date, method, state - Settle action closes the item and recomputes the cash forecast Dashboard riprogettata (focus su scadenze e to-do): - Overdue alert banner, KPI for overdue payables/receivables - Upcoming deadlines table, prioritised "Cose da fare" action list - 30-day cash-flow forecast and customer credit-limit check Adds REST endpoints for invoices, movements, schedules and dashboard data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+44
-108
@@ -1,120 +1,56 @@
|
||||
// Utility functions for API calls
|
||||
async function apiCall(endpoint, method = 'GET', data = null) {
|
||||
const options = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
};
|
||||
// ============================================================
|
||||
// Mago4 Demo — helper frontend
|
||||
// ============================================================
|
||||
|
||||
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;
|
||||
}
|
||||
// Genera una field-row in stile form Mago4
|
||||
function field(label, value) {
|
||||
return `<div class="field-row"><label>${label}</label><div class="field-value">${value ?? ''}</div></div>`;
|
||||
}
|
||||
|
||||
// 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);
|
||||
// Modal generico
|
||||
function openGenericModal(title, html) {
|
||||
const t = document.getElementById('gmTitle');
|
||||
const b = document.getElementById('gmBody');
|
||||
if (t) t.textContent = title;
|
||||
if (b) b.innerHTML = html;
|
||||
const m = document.getElementById('genericModal');
|
||||
if (m) m.classList.add('show');
|
||||
}
|
||||
function closeGenericModal() {
|
||||
const m = document.getElementById('genericModal');
|
||||
if (m) m.classList.remove('show');
|
||||
}
|
||||
|
||||
// Format currency
|
||||
function formatCurrency(value) {
|
||||
return new Intl.NumberFormat('it-IT', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(value);
|
||||
// Ricerca globale (demo)
|
||||
function globalSearch(q) {
|
||||
if (!q || !q.trim()) return;
|
||||
alert('Ricerca globale (demo): "' + q + '"');
|
||||
}
|
||||
|
||||
// Format date
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('it-IT');
|
||||
// Formattazione valuta
|
||||
function fmtEur(v) {
|
||||
return new Intl.NumberFormat('it-IT', { style: 'currency', currency: 'EUR' }).format(v || 0);
|
||||
}
|
||||
function fmtDate(s) {
|
||||
if (!s) return '—';
|
||||
return new Date(s).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' });
|
||||
}
|
||||
});
|
||||
});
|
||||
// Toast notifiche
|
||||
function showNotification(message, type) {
|
||||
const n = document.createElement('div');
|
||||
n.textContent = message;
|
||||
n.style.cssText = `position:fixed;top:64px;right:20px;padding:11px 18px;border-radius:4px;
|
||||
background:${type === 'error' ? '#D32F2F' : '#1577be'};color:#fff;
|
||||
box-shadow:0 4px 16px rgba(0,0,0,0.2);z-index:2000;font-size:13px;animation:slideIn .25s ease;`;
|
||||
document.body.appendChild(n);
|
||||
setTimeout(() => n.remove(), 3000);
|
||||
}
|
||||
|
||||
// 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';
|
||||
});
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') document.querySelectorAll('.modal.show').forEach(m => m.classList.remove('show'));
|
||||
});
|
||||
|
||||
// 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);
|
||||
const _style = document.createElement('style');
|
||||
_style.textContent = `@keyframes slideIn{from{transform:translateX(60px);opacity:0}to{transform:none;opacity:1}}`;
|
||||
document.head.appendChild(_style);
|
||||
|
||||
Reference in New Issue
Block a user