Add index.html
This commit is contained in:
512
index.html
Normal file
512
index.html
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Calcolatore Prezzi Software (Italia)</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
.card:hover {
|
||||||
|
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||||
|
}
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.input-group label {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #4b5563;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.input-field {
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
.input-field:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4);
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
}
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background-color: #d1d5db;
|
||||||
|
}
|
||||||
|
.results-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.results-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.result-box {
|
||||||
|
padding: 1.5rem;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.result-box h3 {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.result-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
}
|
||||||
|
.result-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.result-item-label {
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
.result-item-value {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
.total {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
.net-total {
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
.info-tooltip {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.info-tooltip .tooltip-text {
|
||||||
|
visibility: hidden;
|
||||||
|
width: 250px;
|
||||||
|
background-color: #374151;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
bottom: 125%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -125px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.info-tooltip:hover .tooltip-text {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="p-4 sm:p-6 lg:p-8">
|
||||||
|
<div class="max-w-7xl mx-auto">
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="text-center mb-8">
|
||||||
|
<h1 class="text-3xl sm:text-4xl font-bold text-gray-800">Calcolatore Prezzi Software</h1>
|
||||||
|
<p id="sub-header" class="mt-2 text-lg text-gray-600">Per Liberi Professionisti in Italia</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
<!-- Colonna Sinistra: Input -->
|
||||||
|
<div class="lg:col-span-1 space-y-6">
|
||||||
|
<!-- Card Configurazione -->
|
||||||
|
<div class="card p-6">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-700 border-b pb-3 mb-4">Configurazione</h2>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="taxRegime">Seleziona Tipologia Fiscale</label>
|
||||||
|
<select id="taxRegime" class="input-field bg-white">
|
||||||
|
<option value="forfettario">Regime Forfettario</option>
|
||||||
|
<option value="ordinario">Regime Ordinario (Semplificato)</option>
|
||||||
|
<option value="occasionale">Lavoro Autonomo Occasionale</option>
|
||||||
|
<option value="minimi">Regime dei Minimi (ad esaurimento)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="forfettario-options">
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="coeffRedditivita">Coefficiente di Redditività (%)</label>
|
||||||
|
<input type="number" id="coeffRedditivita" class="input-field" value="78">
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="impostaSostitutiva">Aliquota Imposta Sostitutiva</label>
|
||||||
|
<select id="impostaSostitutiva" class="input-field bg-white">
|
||||||
|
<option value="0.05">5% (Startup)</option>
|
||||||
|
<option value="0.15">15% (Standard)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="devRate">Tariffa Oraria Sviluppo (€)</label>
|
||||||
|
<input type="number" id="devRate" class="input-field" value="50">
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="supportRate">Tariffa Oraria Supporto (€)</label>
|
||||||
|
<input type="number" id="supportRate" class="input-field" value="40">
|
||||||
|
</div>
|
||||||
|
<div id="annualIncomeGroup" class="input-group">
|
||||||
|
<label for="estimatedAnnualTaxable" class="flex items-center">
|
||||||
|
Altri Redditi IRPEF St. (€)
|
||||||
|
<div class="info-tooltip ml-2">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" /></svg>
|
||||||
|
<span class="tooltip-text">Inserisci altri redditi imponibili IRPEF per calcolare l'imposta marginale per questo progetto in modo più accurato.</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<input type="number" id="estimatedAnnualTaxable" class="input-field" value="25000">
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between" id="inps-rivalsa-group">
|
||||||
|
<label for="includeINPS" class="text-gray-700 flex items-center">
|
||||||
|
Includi rivalsa INPS 4%?
|
||||||
|
<div class="info-tooltip ml-2">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" /></svg>
|
||||||
|
<span class="tooltip-text">Aggiunge un contributo previdenziale INPS del 4% (rivalsa) alla fattura del cliente. Questo importo fa parte del tuo fatturato.</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<input type="checkbox" id="includeINPS" class="h-5 w-5 text-blue-600 border-gray-300 rounded focus:ring-blue-500" checked>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Card MVP -->
|
||||||
|
<div class="card p-6">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-700 border-b pb-3 mb-4">Prima Milestone (MVP)</h2>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="mvpDevHours">Token Sviluppo (Ore)</label>
|
||||||
|
<input type="number" id="mvpDevHours" class="input-field" value="100">
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="mvpSupportHours">Token Supporto (Ore)</label>
|
||||||
|
<input type="number" id="mvpSupportHours" class="input-field" value="20">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Card Milestone Aggiuntive -->
|
||||||
|
<div class="card p-6">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-700 border-b pb-3 mb-4">Milestone Aggiuntive</h2>
|
||||||
|
<div id="custom-milestones" class="space-y-4"></div>
|
||||||
|
<button id="add-milestone" class="btn btn-secondary w-full mt-4">Aggiungi Milestone</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Colonna Destra: Risultati -->
|
||||||
|
<div class="lg:col-span-2">
|
||||||
|
<div class="card p-6 sm:p-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-800 mb-6">Riepilogo Preventivo e Guadagni</h2>
|
||||||
|
<div class="results-grid">
|
||||||
|
|
||||||
|
<!-- Box Preventivo Cliente -->
|
||||||
|
<div class="result-box bg-blue-50">
|
||||||
|
<h3>Preventivo per il Cliente</h3>
|
||||||
|
<div class="result-item"><span class="result-item-label" id="subtotal-label">Imponibile</span><span id="subtotal" class="result-item-value font-bold">€0.00</span></div>
|
||||||
|
<div id="inpsChargeRow" class="result-item"><span class="result-item-label">Rivalsa INPS (4%)</span><span id="inpsCharge" class="result-item-value">€0.00</span></div>
|
||||||
|
<div id="ivaRow" class="result-item"><span class="result-item-label">IVA (22%)</span><span id="iva" class="result-item-value">€0.00</span></div>
|
||||||
|
<div id="totalInvoiceRow" class="result-item"><span class="result-item-label font-bold">Totale Fattura</span><span id="totalInvoice" class="result-item-value font-bold">€0.00</span></div>
|
||||||
|
<div id="withholdingTaxRow" class="result-item"><span class="result-item-label text-red-600">Ritenuta d'acconto (20%)</span><span id="withholdingTax" class="result-item-value text-red-600">- €0.00</span></div>
|
||||||
|
<div id="amountDueRow" class="result-item mt-4"><span class="result-item-label font-bold text-lg" id="amountDueLabel">Importo dovuto dal Cliente</span><span id="amountDue" class="result-item-value total">€0.00</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Box Riepilogo Guadagni -->
|
||||||
|
<div class="result-box bg-green-50">
|
||||||
|
<h3>Riepilogo Guadagni (Stima)</h3>
|
||||||
|
<div class="result-item"><span id="grossRevenueLabel" class="result-item-label">Fatturato</span><span id="grossRevenue" class="result-item-value">€0.00</span></div>
|
||||||
|
<div class="result-item"><span class="result-item-label text-red-600">Commissione Rete Vendita (10%)</span><span id="sellerFee" class="result-item-value text-red-600">- €0.00</span></div>
|
||||||
|
<div class="result-item"><span id="netRevenueLabel" class="result-item-label">Ricavo Netto</span><span id="netRevenue" class="result-item-value font-bold">€0.00</span></div>
|
||||||
|
<div id="inpsDueRow" class="result-item"><span class="result-item-label text-red-600">Contributi INPS (~26.07%)</span><span id="inpsDue" class="result-item-value text-red-600">- €0.00</span></div>
|
||||||
|
<div class="result-item"><span id="taxDueLabel" class="result-item-label text-red-600">Imposta Dovuta</span><span id="taxDue" class="result-item-value text-red-600">- €0.00</span></div>
|
||||||
|
<div class="result-item mt-4"><span class="result-item-label font-bold text-lg">Reddito Netto Stimato</span><span id="netIncome" class="result-item-value total net-total">€0.00</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 text-xs text-gray-500 text-center">
|
||||||
|
<p><strong>Disclaimer:</strong> Questo è uno strumento di stima. Il calcolo è basato sui principi dei regimi fiscali italiani e della Gestione Separata INPS (aliquota assunta al 26,07%). Per il Lavoro Occasionale, l'INPS non è dovuta sotto i 5.000€ annui di compensi. L'IVA è considerata neutrale per il professionista. La tassazione effettiva dipende dal reddito annuo totale e dai costi deducibili. Consulta sempre un commercialista per una consulenza finanziaria accurata.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const inputs = document.querySelectorAll('input, select');
|
||||||
|
const addMilestoneBtn = document.getElementById('add-milestone');
|
||||||
|
const customMilestonesContainer = document.getElementById('custom-milestones');
|
||||||
|
const taxRegimeSelector = document.getElementById('taxRegime');
|
||||||
|
|
||||||
|
const calculateIrpef = (income) => {
|
||||||
|
if (income <= 0) return 0;
|
||||||
|
let tax = 0;
|
||||||
|
const bracket1 = 28000;
|
||||||
|
const bracket2 = 50000;
|
||||||
|
|
||||||
|
if (income <= bracket1) {
|
||||||
|
tax = income * 0.23;
|
||||||
|
} else if (income <= bracket2) {
|
||||||
|
tax = (bracket1 * 0.23) + ((income - bracket1) * 0.35);
|
||||||
|
} else {
|
||||||
|
tax = (bracket1 * 0.23) + ((bracket2 - bracket1) * 0.35) + ((income - bracket2) * 0.43);
|
||||||
|
}
|
||||||
|
return tax;
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculate = () => {
|
||||||
|
const regime = taxRegimeSelector.value;
|
||||||
|
const devRate = parseFloat(document.getElementById('devRate').value) || 0;
|
||||||
|
const supportRate = parseFloat(document.getElementById('supportRate').value) || 0;
|
||||||
|
const mvpDevHours = parseFloat(document.getElementById('mvpDevHours').value) || 0;
|
||||||
|
const mvpSupportHours = parseFloat(document.getElementById('mvpSupportHours').value) || 0;
|
||||||
|
const includeINPS = document.getElementById('includeINPS').checked;
|
||||||
|
|
||||||
|
let totalCustomCost = 0;
|
||||||
|
document.querySelectorAll('.custom-milestone').forEach(milestone => {
|
||||||
|
const devHours = parseFloat(milestone.querySelector('.milestone-dev-hours').value) || 0;
|
||||||
|
const supportHours = parseFloat(milestone.querySelector('.milestone-support-hours').value) || 0;
|
||||||
|
totalCustomCost += (devHours * devRate) + (supportHours * supportRate);
|
||||||
|
});
|
||||||
|
|
||||||
|
const subtotal = (mvpDevHours * devRate) + (mvpSupportHours * supportRate) + totalCustomCost;
|
||||||
|
|
||||||
|
let uiData = {};
|
||||||
|
const INPS_RATE = 0.2607;
|
||||||
|
const inpsCharge = (regime !== 'occasionale' && includeINPS) ? subtotal * 0.04 : 0;
|
||||||
|
|
||||||
|
switch(regime) {
|
||||||
|
case 'ordinario': {
|
||||||
|
const estimatedAnnualTaxable = parseFloat(document.getElementById('estimatedAnnualTaxable').value) || 0;
|
||||||
|
const ivaBase = subtotal + inpsCharge;
|
||||||
|
const iva = ivaBase * 0.22;
|
||||||
|
const totalInvoice = ivaBase + iva;
|
||||||
|
const withholdingTax = subtotal * 0.20;
|
||||||
|
const amountDue = totalInvoice - withholdingTax;
|
||||||
|
|
||||||
|
const grossRevenue = subtotal + inpsCharge;
|
||||||
|
const sellerFee = grossRevenue * 0.10;
|
||||||
|
const netRevenue = grossRevenue - sellerFee;
|
||||||
|
const inpsDue = netRevenue * INPS_RATE;
|
||||||
|
const taxableForIrpef = netRevenue - inpsDue;
|
||||||
|
|
||||||
|
const totalIrpefWithProject = calculateIrpef(estimatedAnnualTaxable + taxableForIrpef);
|
||||||
|
const irpefOnPreviousIncome = calculateIrpef(estimatedAnnualTaxable);
|
||||||
|
const taxDue = totalIrpefWithProject - irpefOnPreviousIncome;
|
||||||
|
|
||||||
|
const netIncome = netRevenue - inpsDue - taxDue;
|
||||||
|
|
||||||
|
uiData = { regime, subtotal, inpsCharge, iva, totalInvoice, withholdingTax, amountDue, grossRevenue, sellerFee, netRevenue, inpsDue, taxDue, netIncome };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'forfettario': {
|
||||||
|
const coeffRedditivita = (parseFloat(document.getElementById('coeffRedditivita').value) || 0) / 100;
|
||||||
|
const impostaSostitutiva = parseFloat(document.getElementById('impostaSostitutiva').value) || 0;
|
||||||
|
const totalInvoice = subtotal + inpsCharge;
|
||||||
|
|
||||||
|
const grossRevenue = totalInvoice;
|
||||||
|
const sellerFee = grossRevenue * 0.10;
|
||||||
|
const revenueAfterFee = grossRevenue - sellerFee;
|
||||||
|
const taxableIncome = revenueAfterFee * coeffRedditivita;
|
||||||
|
|
||||||
|
const inpsDue = taxableIncome * INPS_RATE;
|
||||||
|
const taxDue = taxableIncome * impostaSostitutiva;
|
||||||
|
const netIncome = revenueAfterFee - inpsDue - taxDue;
|
||||||
|
|
||||||
|
uiData = { regime, subtotal, inpsCharge, iva: 0, totalInvoice, withholdingTax: 0, amountDue: totalInvoice, grossRevenue, sellerFee, netRevenue: revenueAfterFee, inpsDue, taxDue, netIncome };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'occasionale': {
|
||||||
|
const estimatedAnnualTaxable = parseFloat(document.getElementById('estimatedAnnualTaxable').value) || 0;
|
||||||
|
const totalReceipt = subtotal; // No INPS charge, no VAT
|
||||||
|
const withholdingTax = subtotal * 0.20;
|
||||||
|
const amountDue = totalReceipt - withholdingTax;
|
||||||
|
|
||||||
|
const grossRevenue = subtotal;
|
||||||
|
const sellerFee = grossRevenue * 0.10;
|
||||||
|
const netRevenue = grossRevenue - sellerFee;
|
||||||
|
|
||||||
|
const inpsDue = 0; // Assuming under 5k threshold
|
||||||
|
const taxableForIrpef = netRevenue;
|
||||||
|
|
||||||
|
const totalIrpefWithProject = calculateIrpef(estimatedAnnualTaxable + taxableForIrpef);
|
||||||
|
const irpefOnPreviousIncome = calculateIrpef(estimatedAnnualTaxable);
|
||||||
|
const taxDue = totalIrpefWithProject - irpefOnPreviousIncome;
|
||||||
|
|
||||||
|
const netIncome = netRevenue - taxDue;
|
||||||
|
|
||||||
|
uiData = { regime, subtotal, inpsCharge: 0, iva: 0, totalInvoice: totalReceipt, withholdingTax, amountDue, grossRevenue, sellerFee, netRevenue, inpsDue, taxDue, netIncome };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'minimi': {
|
||||||
|
const TAX_RATE = 0.05;
|
||||||
|
const totalInvoice = subtotal + inpsCharge;
|
||||||
|
|
||||||
|
const grossRevenue = totalInvoice;
|
||||||
|
const sellerFee = grossRevenue * 0.10;
|
||||||
|
const taxableIncome = grossRevenue - sellerFee;
|
||||||
|
|
||||||
|
const inpsDue = taxableIncome * INPS_RATE;
|
||||||
|
const taxDue = taxableIncome * TAX_RATE;
|
||||||
|
const netIncome = taxableIncome - inpsDue - taxDue;
|
||||||
|
|
||||||
|
uiData = { regime, subtotal, inpsCharge, iva: 0, totalInvoice, withholdingTax: 0, amountDue: totalInvoice, grossRevenue, sellerFee, netRevenue: taxableIncome, inpsDue, taxDue, netIncome };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateUI(uiData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUI = (data) => {
|
||||||
|
const formatCurrency = (value) => `€${value.toFixed(2)}`;
|
||||||
|
|
||||||
|
const forfettarioOptions = document.getElementById('forfettario-options');
|
||||||
|
const annualIncomeGroup = document.getElementById('annualIncomeGroup');
|
||||||
|
const ivaRow = document.getElementById('ivaRow');
|
||||||
|
const withholdingTaxRow = document.getElementById('withholdingTaxRow');
|
||||||
|
const amountDueRow = document.getElementById('amountDueRow');
|
||||||
|
const inpsRivalsaGroup = document.getElementById('inps-rivalsa-group');
|
||||||
|
const inpsChargeRow = document.getElementById('inpsChargeRow');
|
||||||
|
const totalInvoiceRow = document.getElementById('totalInvoiceRow');
|
||||||
|
const inpsDueRow = document.getElementById('inpsDueRow');
|
||||||
|
|
||||||
|
// Reset all to hidden, then enable based on regime
|
||||||
|
forfettarioOptions.style.display = 'none';
|
||||||
|
annualIncomeGroup.style.display = 'none';
|
||||||
|
ivaRow.style.display = 'none';
|
||||||
|
withholdingTaxRow.style.display = 'none';
|
||||||
|
inpsRivalsaGroup.style.display = 'flex';
|
||||||
|
inpsChargeRow.style.display = 'flex';
|
||||||
|
totalInvoiceRow.style.display = 'flex';
|
||||||
|
inpsDueRow.style.display = 'flex';
|
||||||
|
|
||||||
|
let subHeaderText = '';
|
||||||
|
|
||||||
|
switch(data.regime) {
|
||||||
|
case 'ordinario':
|
||||||
|
annualIncomeGroup.style.display = 'flex';
|
||||||
|
ivaRow.style.display = 'flex';
|
||||||
|
withholdingTaxRow.style.display = 'flex';
|
||||||
|
document.getElementById('subtotal-label').textContent = "Imponibile";
|
||||||
|
document.getElementById('netRevenueLabel').textContent = "Ricavo Netto";
|
||||||
|
document.getElementById('taxDueLabel').textContent = "IRPEF Stimato";
|
||||||
|
document.getElementById('grossRevenueLabel').textContent = "Fatturato (Imponibile + Rivalsa)";
|
||||||
|
subHeaderText = 'Regime Ordinario (Semplificato)';
|
||||||
|
break;
|
||||||
|
case 'forfettario':
|
||||||
|
forfettarioOptions.style.display = 'block';
|
||||||
|
document.getElementById('subtotal-label').textContent = "Imponibile";
|
||||||
|
document.getElementById('netRevenueLabel').textContent = "Ricavo Post-Commissioni";
|
||||||
|
const taxRateText = (parseFloat(document.getElementById('impostaSostitutiva').value) * 100) + '%';
|
||||||
|
document.getElementById('taxDueLabel').textContent = `Imposta Sostitutiva (${taxRateText})`;
|
||||||
|
document.getElementById('grossRevenueLabel').textContent = "Fatturato";
|
||||||
|
subHeaderText = 'Regime Forfettario';
|
||||||
|
break;
|
||||||
|
case 'occasionale':
|
||||||
|
annualIncomeGroup.style.display = 'flex';
|
||||||
|
withholdingTaxRow.style.display = 'flex';
|
||||||
|
inpsRivalsaGroup.style.display = 'none';
|
||||||
|
inpsChargeRow.style.display = 'none';
|
||||||
|
totalInvoiceRow.style.display = 'none';
|
||||||
|
inpsDueRow.style.display = 'none';
|
||||||
|
document.getElementById('subtotal-label').textContent = "Compenso Lordo";
|
||||||
|
document.getElementById('amountDueLabel').textContent = "Netto a pagare";
|
||||||
|
document.getElementById('netRevenueLabel').textContent = "Ricavo Netto";
|
||||||
|
document.getElementById('taxDueLabel').textContent = "IRPEF Stimata";
|
||||||
|
document.getElementById('grossRevenueLabel').textContent = "Compenso Lordo";
|
||||||
|
subHeaderText = 'Lavoro Autonomo Occasionale';
|
||||||
|
break;
|
||||||
|
case 'minimi':
|
||||||
|
document.getElementById('subtotal-label').textContent = "Imponibile";
|
||||||
|
document.getElementById('netRevenueLabel').textContent = "Reddito Imponibile";
|
||||||
|
document.getElementById('taxDueLabel').textContent = "Imposta Sostitutiva (5%)";
|
||||||
|
document.getElementById('grossRevenueLabel').textContent = "Fatturato";
|
||||||
|
subHeaderText = 'Regime dei Minimi';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('sub-header').textContent = `Per ${subHeaderText}`;
|
||||||
|
const finalAmountDue = data.amountDue;
|
||||||
|
amountDueRow.style.display = 'flex';
|
||||||
|
|
||||||
|
document.getElementById('subtotal').textContent = formatCurrency(data.subtotal);
|
||||||
|
document.getElementById('inpsCharge').textContent = formatCurrency(data.inpsCharge);
|
||||||
|
document.getElementById('iva').textContent = formatCurrency(data.iva);
|
||||||
|
document.getElementById('totalInvoice').textContent = formatCurrency(data.totalInvoice);
|
||||||
|
document.getElementById('withholdingTax').textContent = `- ${formatCurrency(data.withholdingTax)}`;
|
||||||
|
document.getElementById('amountDue').textContent = formatCurrency(finalAmountDue);
|
||||||
|
|
||||||
|
document.getElementById('grossRevenue').textContent = formatCurrency(data.grossRevenue);
|
||||||
|
document.getElementById('sellerFee').textContent = `- ${formatCurrency(data.sellerFee)}`;
|
||||||
|
document.getElementById('netRevenue').textContent = formatCurrency(data.netRevenue);
|
||||||
|
document.getElementById('inpsDue').textContent = `- ${formatCurrency(data.inpsDue)}`;
|
||||||
|
document.getElementById('taxDue').textContent = `- ${formatCurrency(data.taxDue)}`;
|
||||||
|
document.getElementById('netIncome').textContent = formatCurrency(data.netIncome);
|
||||||
|
};
|
||||||
|
|
||||||
|
inputs.forEach(input => input.addEventListener('input', calculate));
|
||||||
|
|
||||||
|
let milestoneCounter = 0;
|
||||||
|
addMilestoneBtn.addEventListener('click', () => {
|
||||||
|
milestoneCounter++;
|
||||||
|
const milestoneId = `milestone-${milestoneCounter}`;
|
||||||
|
const milestoneDiv = document.createElement('div');
|
||||||
|
milestoneDiv.className = 'custom-milestone p-4 border border-dashed rounded-lg relative';
|
||||||
|
milestoneDiv.innerHTML = `
|
||||||
|
<button class="remove-milestone absolute -top-2 -right-2 bg-red-500 text-white rounded-full h-6 w-6 flex items-center justify-center text-xs font-bold">×</button>
|
||||||
|
<div class="input-group">
|
||||||
|
<label class="text-sm" for="${milestoneId}-name">Nome Milestone</label>
|
||||||
|
<input type="text" id="${milestoneId}-name" class="input-field text-sm" placeholder="es. Integrazione API">
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<div class="input-group mb-0">
|
||||||
|
<label class="text-sm" for="${milestoneId}-dev">Sviluppo (h)</label>
|
||||||
|
<input type="number" id="${milestoneId}-dev" class="input-field text-sm milestone-dev-hours" value="0">
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-0">
|
||||||
|
<label class="text-sm" for="${milestoneId}-support">Supporto (h)</label>
|
||||||
|
<input type="number" id="${milestoneId}-support" class="input-field text-sm milestone-support-hours" value="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
customMilestonesContainer.appendChild(milestoneDiv);
|
||||||
|
|
||||||
|
milestoneDiv.querySelectorAll('input').forEach(input => input.addEventListener('input', calculate));
|
||||||
|
|
||||||
|
milestoneDiv.querySelector('.remove-milestone').addEventListener('click', () => {
|
||||||
|
milestoneDiv.remove();
|
||||||
|
calculate();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
calculate();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
Reference in New Issue
Block a user