Add multi-mode HTML, Docker, Helm chart, and deploy script
All checks were successful
Build and Deploy / build (push) Successful in 46s
All checks were successful
Build and Deploy / build (push) Successful in 46s
- Add shop-mode.html and project-mode.html for separate calculation modes - Refactor index.html as a landing page for mode selection - Add Dockerfile with optimized nginx config and healthcheck - Add .dockerignore for cleaner Docker builds - Add deploy.sh for Helm/Kubernetes deployment automation - Add helm-chart/ with values.yaml, Chart.yaml, templates, and documentation - Update README.md with full instructions, features, and CI/CD examples
This commit is contained in:
658
shop-mode.html
Normal file
658
shop-mode.html
Normal file
@@ -0,0 +1,658 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Calcolatore Prezzi - Modalità Negozio</title>
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- Alpine.js -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
|
||||
<!-- jsPDF per generazione PDF -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||
|
||||
<!-- Font Awesome per icone -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-slide-in {
|
||||
animation: slideIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-emerald-400 via-teal-500 to-cyan-500 min-h-screen" x-data="shopApp()" x-init="init()">
|
||||
<div class="container mx-auto p-4 max-w-7xl">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="glass rounded-2xl p-8 mb-6 animate-slide-in text-center">
|
||||
<h1 class="text-4xl font-bold text-gray-800 flex items-center justify-center gap-3">
|
||||
<i class="fas fa-store text-emerald-600"></i>
|
||||
Calcolatore Prezzi - Modalità Negozio
|
||||
</h1>
|
||||
<p class="text-gray-600 mt-2">Sistema di preventivi basato su articoli e quantità</p>
|
||||
</div>
|
||||
|
||||
<!-- Notifiche Toast -->
|
||||
<div x-show="notification.show"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 transform translate-y-2"
|
||||
x-transition:enter-end="opacity-100 transform translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="fixed top-4 right-4 z-50">
|
||||
<div :class="'p-4 rounded-lg shadow-lg flex items-center gap-3 ' +
|
||||
(notification.type === 'success' ? 'bg-green-500' :
|
||||
notification.type === 'error' ? 'bg-red-500' :
|
||||
notification.type === 'warning' ? 'bg-orange-500' : 'bg-blue-500') +
|
||||
' text-white'">
|
||||
<i :class="'fas fa-' +
|
||||
(notification.type === 'success' ? 'check-circle' :
|
||||
notification.type === 'error' ? 'exclamation-circle' :
|
||||
notification.type === 'warning' ? 'exclamation-triangle' : 'info-circle')"></i>
|
||||
<span x-text="notification.message"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dati Venditore -->
|
||||
<div class="glass rounded-2xl p-6 mb-6 animate-slide-in">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-building text-emerald-600"></i>
|
||||
Dati Venditore
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="relative">
|
||||
<input type="text" x-model="venditore.nome"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Nome Azienda">
|
||||
<i class="fas fa-signature absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" x-model="venditore.piva"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="P.IVA">
|
||||
<i class="fas fa-id-card absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" x-model="venditore.indirizzo"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Indirizzo">
|
||||
<i class="fas fa-map-marker-alt absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" x-model="venditore.telefono"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Telefono">
|
||||
<i class="fas fa-phone absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dati Cliente -->
|
||||
<div class="glass rounded-2xl p-6 mb-6 animate-slide-in">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-user text-emerald-600"></i>
|
||||
Dati Cliente
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="relative">
|
||||
<input type="text" x-model="cliente.nome"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Nome Cliente">
|
||||
<i class="fas fa-user-tie absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="email" x-model="cliente.email"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Email">
|
||||
<i class="fas fa-envelope absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" x-model="cliente.telefono"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Telefono">
|
||||
<i class="fas fa-phone absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="date" x-model="cliente.data"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all">
|
||||
<i class="fas fa-calendar absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Catalogo Prodotti -->
|
||||
<div class="glass rounded-2xl p-6 mb-6 animate-slide-in">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-boxes text-emerald-600"></i>
|
||||
Catalogo Articoli
|
||||
</h2>
|
||||
|
||||
<div class="space-y-3 mb-4">
|
||||
<template x-for="(item, index) in items" :key="item.id">
|
||||
<div class="p-4 bg-gradient-to-r from-emerald-50 to-teal-50 rounded-lg border-2 border-emerald-200">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="flex-1 grid grid-cols-1 md:grid-cols-5 gap-3">
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Nome Articolo</label>
|
||||
<input type="text" x-model="item.name"
|
||||
class="w-full p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
placeholder="es. Licenza Software XYZ">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Codice SKU</label>
|
||||
<input type="text" x-model="item.sku"
|
||||
class="w-full p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
placeholder="SKU-001">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Prezzo Unitario (€)</label>
|
||||
<input type="number" x-model.number="item.price"
|
||||
class="w-full p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
step="0.01" min="0">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Quantità</label>
|
||||
<input type="number" x-model.number="item.quantity"
|
||||
class="w-full p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
step="1" min="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-end gap-2">
|
||||
<button @click="removeItem(index)"
|
||||
class="text-red-500 hover:text-red-700 transition-colors">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
<div class="text-right">
|
||||
<p class="text-xs text-gray-600">Totale</p>
|
||||
<p class="text-lg font-bold text-emerald-600" x-text="'€' + (item.price * item.quantity).toFixed(2)"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Descrizione</label>
|
||||
<textarea x-model="item.description"
|
||||
class="w-full p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
rows="2"
|
||||
placeholder="Descrizione dettagliata dell'articolo"></textarea>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Sconto (%)</label>
|
||||
<input type="number" x-model.number="item.discount"
|
||||
class="w-24 p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
step="1" min="0" max="100">
|
||||
</div>
|
||||
<div class="flex-1 text-right">
|
||||
<span class="text-xs text-gray-600">Prezzo scontato: </span>
|
||||
<span class="font-bold text-emerald-600" x-text="'€' + calculateDiscountedTotal(item).toFixed(2)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<button @click="addItem()"
|
||||
class="w-full bg-gradient-to-r from-emerald-500 to-teal-500 text-white font-bold py-3 px-6 rounded-lg hover:from-emerald-600 hover:to-teal-600 transition-all flex items-center justify-center gap-2">
|
||||
<i class="fas fa-plus"></i>
|
||||
Aggiungi Articolo
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Configurazione Fattura -->
|
||||
<div class="glass rounded-2xl p-6 mb-6 animate-slide-in">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-cog text-emerald-600"></i>
|
||||
Configurazione Fattura
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">IVA (%)</label>
|
||||
<select x-model.number="taxRate"
|
||||
class="w-full p-3 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none">
|
||||
<option value="0">Esente IVA (0%)</option>
|
||||
<option value="4">4% (Beni di prima necessità)</option>
|
||||
<option value="10">10% (Ridotta)</option>
|
||||
<option value="22">22% (Ordinaria)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Spese di Spedizione (€)</label>
|
||||
<input type="number" x-model.number="shippingCost"
|
||||
class="w-full p-3 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
step="0.01" min="0">
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Note Fattura</label>
|
||||
<textarea x-model="invoiceNotes"
|
||||
class="w-full p-3 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
rows="3"
|
||||
placeholder="Inserisci eventuali note o condizioni di pagamento"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Riepilogo Totali -->
|
||||
<div class="glass rounded-2xl p-6 mb-6 animate-slide-in">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center gap-2">
|
||||
<i class="fas fa-calculator text-emerald-600"></i>
|
||||
Riepilogo Preventivo
|
||||
</h2>
|
||||
|
||||
<div class="bg-gradient-to-br from-emerald-50 to-teal-100 p-6 rounded-xl shadow-lg">
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between p-2 bg-white/70 rounded">
|
||||
<span class="text-gray-700">Subtotale Articoli</span>
|
||||
<span class="font-bold" x-text="formatCurrency(calculateSubtotal())"></span>
|
||||
</div>
|
||||
<div class="flex justify-between p-2 bg-white/70 rounded" x-show="calculateTotalDiscount() > 0">
|
||||
<span class="text-orange-600">Sconto Totale</span>
|
||||
<span class="font-bold text-orange-600" x-text="'- ' + formatCurrency(calculateTotalDiscount())"></span>
|
||||
</div>
|
||||
<div class="flex justify-between p-2 bg-white/70 rounded" x-show="shippingCost > 0">
|
||||
<span class="text-gray-700">Spese di Spedizione</span>
|
||||
<span class="font-bold" x-text="formatCurrency(shippingCost)"></span>
|
||||
</div>
|
||||
<div class="flex justify-between p-2 bg-white/70 rounded">
|
||||
<span class="text-gray-700">Imponibile</span>
|
||||
<span class="font-bold" x-text="formatCurrency(calculateTaxableAmount())"></span>
|
||||
</div>
|
||||
<div class="flex justify-between p-2 bg-white/70 rounded" x-show="taxRate > 0">
|
||||
<span class="text-gray-700" x-text="'IVA (' + taxRate + '%)'"></span>
|
||||
<span class="font-bold" x-text="formatCurrency(calculateTax())"></span>
|
||||
</div>
|
||||
<div class="border-t-2 border-emerald-300 pt-3 mt-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xl font-bold text-emerald-800">TOTALE</span>
|
||||
<span class="text-3xl font-bold text-emerald-600" x-text="formatCurrency(calculateTotal())"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistiche -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-4 mt-6">
|
||||
<div class="text-center p-4 bg-emerald-50 rounded-lg">
|
||||
<i class="fas fa-box text-2xl text-emerald-600 mb-2"></i>
|
||||
<p class="text-sm text-gray-600">Articoli</p>
|
||||
<p class="text-xl font-bold text-emerald-600" x-text="items.length"></p>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-teal-50 rounded-lg">
|
||||
<i class="fas fa-layer-group text-2xl text-teal-600 mb-2"></i>
|
||||
<p class="text-sm text-gray-600">Quantità Totale</p>
|
||||
<p class="text-xl font-bold text-teal-600" x-text="calculateTotalQuantity()"></p>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-cyan-50 rounded-lg">
|
||||
<i class="fas fa-percentage text-2xl text-cyan-600 mb-2"></i>
|
||||
<p class="text-sm text-gray-600">Sconto Medio</p>
|
||||
<p class="text-xl font-bold text-cyan-600" x-text="calculateAverageDiscount().toFixed(1) + '%'"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pulsanti Azione -->
|
||||
<div class="glass rounded-2xl p-6 animate-slide-in">
|
||||
<div class="flex flex-wrap gap-3 justify-center">
|
||||
<button @click="generaPDF()"
|
||||
class="bg-emerald-600 hover:bg-emerald-700 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-all flex items-center gap-2">
|
||||
<i class="fas fa-file-pdf"></i>
|
||||
Genera PDF
|
||||
</button>
|
||||
<button @click="salvaDati()"
|
||||
class="bg-teal-500 hover:bg-teal-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-all flex items-center gap-2">
|
||||
<i class="fas fa-save"></i>
|
||||
Salva
|
||||
</button>
|
||||
<button @click="caricaDati()"
|
||||
class="bg-cyan-500 hover:bg-cyan-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-all flex items-center gap-2">
|
||||
<i class="fas fa-upload"></i>
|
||||
Carica
|
||||
</button>
|
||||
<button @click="stampa()"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-all flex items-center gap-2">
|
||||
<i class="fas fa-print"></i>
|
||||
Stampa
|
||||
</button>
|
||||
<button @click="resetForm()"
|
||||
class="bg-red-500 hover:bg-red-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-all flex items-center gap-2">
|
||||
<i class="fas fa-redo"></i>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function shopApp() {
|
||||
return {
|
||||
notification: {
|
||||
show: false,
|
||||
message: '',
|
||||
type: 'info'
|
||||
},
|
||||
|
||||
venditore: {
|
||||
nome: '',
|
||||
piva: '',
|
||||
indirizzo: '',
|
||||
telefono: ''
|
||||
},
|
||||
|
||||
cliente: {
|
||||
nome: '',
|
||||
email: '',
|
||||
telefono: '',
|
||||
data: new Date().toISOString().split('T')[0]
|
||||
},
|
||||
|
||||
items: [],
|
||||
itemCounter: 0,
|
||||
|
||||
taxRate: 22,
|
||||
shippingCost: 0,
|
||||
invoiceNotes: '',
|
||||
|
||||
init() {
|
||||
this.addItem();
|
||||
},
|
||||
|
||||
addItem() {
|
||||
this.itemCounter++;
|
||||
this.items.push({
|
||||
id: this.itemCounter,
|
||||
name: '',
|
||||
sku: '',
|
||||
description: '',
|
||||
price: 0,
|
||||
quantity: 1,
|
||||
discount: 0
|
||||
});
|
||||
},
|
||||
|
||||
removeItem(index) {
|
||||
this.items.splice(index, 1);
|
||||
},
|
||||
|
||||
calculateDiscountedTotal(item) {
|
||||
const subtotal = item.price * item.quantity;
|
||||
const discount = subtotal * (item.discount / 100);
|
||||
return subtotal - discount;
|
||||
},
|
||||
|
||||
calculateSubtotal() {
|
||||
return this.items.reduce((sum, item) => {
|
||||
return sum + (item.price * item.quantity);
|
||||
}, 0);
|
||||
},
|
||||
|
||||
calculateTotalDiscount() {
|
||||
return this.items.reduce((sum, item) => {
|
||||
const subtotal = item.price * item.quantity;
|
||||
return sum + (subtotal * (item.discount / 100));
|
||||
}, 0);
|
||||
},
|
||||
|
||||
calculateTaxableAmount() {
|
||||
const subtotal = this.calculateSubtotal();
|
||||
const discount = this.calculateTotalDiscount();
|
||||
return subtotal - discount + this.shippingCost;
|
||||
},
|
||||
|
||||
calculateTax() {
|
||||
return this.calculateTaxableAmount() * (this.taxRate / 100);
|
||||
},
|
||||
|
||||
calculateTotal() {
|
||||
return this.calculateTaxableAmount() + this.calculateTax();
|
||||
},
|
||||
|
||||
calculateTotalQuantity() {
|
||||
return this.items.reduce((sum, item) => sum + item.quantity, 0);
|
||||
},
|
||||
|
||||
calculateAverageDiscount() {
|
||||
if (this.items.length === 0) return 0;
|
||||
const totalDiscount = this.items.reduce((sum, item) => sum + item.discount, 0);
|
||||
return totalDiscount / this.items.length;
|
||||
},
|
||||
|
||||
formatCurrency(value) {
|
||||
return `€${value.toFixed(2)}`;
|
||||
},
|
||||
|
||||
showNotification(message, type = 'info') {
|
||||
this.notification = {
|
||||
show: true,
|
||||
message: message,
|
||||
type: type
|
||||
};
|
||||
setTimeout(() => {
|
||||
this.notification.show = false;
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
generaPDF() {
|
||||
const { jsPDF } = window.jspdf;
|
||||
const doc = new jsPDF();
|
||||
|
||||
let yPos = 20;
|
||||
|
||||
// Header
|
||||
doc.setFontSize(20);
|
||||
doc.setTextColor(16, 185, 129);
|
||||
doc.text(this.venditore.nome || 'Venditore', 105, yPos, { align: 'center' });
|
||||
yPos += 10;
|
||||
|
||||
doc.setFontSize(14);
|
||||
doc.setTextColor(100);
|
||||
doc.text('PREVENTIVO', 105, yPos, { align: 'center' });
|
||||
yPos += 15;
|
||||
|
||||
// Dati venditore e cliente
|
||||
doc.setFontSize(10);
|
||||
doc.setTextColor(0);
|
||||
doc.text('VENDITORE:', 20, yPos);
|
||||
doc.text('CLIENTE:', 120, yPos);
|
||||
yPos += 5;
|
||||
|
||||
doc.setFontSize(9);
|
||||
doc.text(this.venditore.nome, 20, yPos);
|
||||
doc.text(this.cliente.nome, 120, yPos);
|
||||
yPos += 5;
|
||||
doc.text(`P.IVA: ${this.venditore.piva}`, 20, yPos);
|
||||
doc.text(this.cliente.email, 120, yPos);
|
||||
yPos += 5;
|
||||
doc.text(this.venditore.indirizzo, 20, yPos);
|
||||
doc.text(this.cliente.telefono, 120, yPos);
|
||||
yPos += 10;
|
||||
|
||||
// Tabella articoli
|
||||
doc.setFontSize(10);
|
||||
doc.setFillColor(16, 185, 129);
|
||||
doc.rect(20, yPos, 170, 7, 'F');
|
||||
doc.setTextColor(255);
|
||||
doc.text('Articolo', 25, yPos + 5);
|
||||
doc.text('Q.tà', 120, yPos + 5);
|
||||
doc.text('Prezzo', 140, yPos + 5);
|
||||
doc.text('Sconto', 160, yPos + 5);
|
||||
doc.text('Totale', 175, yPos + 5);
|
||||
yPos += 10;
|
||||
|
||||
doc.setTextColor(0);
|
||||
this.items.forEach(item => {
|
||||
if (yPos > 270) {
|
||||
doc.addPage();
|
||||
yPos = 20;
|
||||
}
|
||||
doc.text(item.name.substring(0, 40), 25, yPos);
|
||||
doc.text(String(item.quantity), 125, yPos);
|
||||
doc.text(`€${item.price.toFixed(2)}`, 140, yPos);
|
||||
doc.text(`${item.discount}%`, 163, yPos);
|
||||
doc.text(`€${this.calculateDiscountedTotal(item).toFixed(2)}`, 175, yPos);
|
||||
yPos += 7;
|
||||
});
|
||||
|
||||
yPos += 5;
|
||||
|
||||
// Totali
|
||||
doc.line(120, yPos, 190, yPos);
|
||||
yPos += 7;
|
||||
|
||||
doc.text('Subtotale:', 120, yPos);
|
||||
doc.text(this.formatCurrency(this.calculateSubtotal()), 175, yPos);
|
||||
yPos += 7;
|
||||
|
||||
if (this.calculateTotalDiscount() > 0) {
|
||||
doc.text('Sconto:', 120, yPos);
|
||||
doc.text(`-${this.formatCurrency(this.calculateTotalDiscount())}`, 175, yPos);
|
||||
yPos += 7;
|
||||
}
|
||||
|
||||
if (this.shippingCost > 0) {
|
||||
doc.text('Spedizione:', 120, yPos);
|
||||
doc.text(this.formatCurrency(this.shippingCost), 175, yPos);
|
||||
yPos += 7;
|
||||
}
|
||||
|
||||
doc.text('Imponibile:', 120, yPos);
|
||||
doc.text(this.formatCurrency(this.calculateTaxableAmount()), 175, yPos);
|
||||
yPos += 7;
|
||||
|
||||
if (this.taxRate > 0) {
|
||||
doc.text(`IVA (${this.taxRate}%):`, 120, yPos);
|
||||
doc.text(this.formatCurrency(this.calculateTax()), 175, yPos);
|
||||
yPos += 7;
|
||||
}
|
||||
|
||||
doc.setFontSize(12);
|
||||
doc.setFont(undefined, 'bold');
|
||||
doc.text('TOTALE:', 120, yPos);
|
||||
doc.text(this.formatCurrency(this.calculateTotal()), 175, yPos);
|
||||
|
||||
const filename = `Preventivo_${this.cliente.nome || 'Cliente'}_${new Date().toISOString().split('T')[0]}.pdf`;
|
||||
doc.save(filename);
|
||||
|
||||
this.showNotification('PDF generato con successo!', 'success');
|
||||
},
|
||||
|
||||
salvaDati() {
|
||||
const data = {
|
||||
venditore: this.venditore,
|
||||
cliente: this.cliente,
|
||||
items: this.items,
|
||||
taxRate: this.taxRate,
|
||||
shippingCost: this.shippingCost,
|
||||
invoiceNotes: this.invoiceNotes,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
const dataStr = JSON.stringify(data, null, 2);
|
||||
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
|
||||
const clientName = this.cliente.nome || 'preventivo';
|
||||
const exportFileDefaultName = `${clientName}_${new Date().toISOString().split('T')[0]}.json`;
|
||||
|
||||
const linkElement = document.createElement('a');
|
||||
linkElement.setAttribute('href', dataUri);
|
||||
linkElement.setAttribute('download', exportFileDefaultName);
|
||||
linkElement.click();
|
||||
|
||||
this.showNotification('Dati salvati con successo!', 'success');
|
||||
},
|
||||
|
||||
caricaDati() {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.target.result);
|
||||
this.venditore = data.venditore || this.venditore;
|
||||
this.cliente = data.cliente || this.cliente;
|
||||
this.items = data.items || [];
|
||||
this.taxRate = data.taxRate || 22;
|
||||
this.shippingCost = data.shippingCost || 0;
|
||||
this.invoiceNotes = data.invoiceNotes || '';
|
||||
this.showNotification('Dati caricati con successo!', 'success');
|
||||
} catch (error) {
|
||||
this.showNotification('Errore nel caricamento dei dati', 'error');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
input.click();
|
||||
},
|
||||
|
||||
stampa() {
|
||||
window.print();
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
if (confirm('Sei sicuro di voler resettare tutti i campi?')) {
|
||||
this.cliente = {
|
||||
nome: '',
|
||||
email: '',
|
||||
telefono: '',
|
||||
data: new Date().toISOString().split('T')[0]
|
||||
};
|
||||
this.items = [];
|
||||
this.shippingCost = 0;
|
||||
this.invoiceNotes = '';
|
||||
this.addItem();
|
||||
this.showNotification('Form resettato!', 'info');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user