-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Open
Description
<title>Mon Budget Prioritaire ✨</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #0f172a;
color: #f1f5f9;
}
.card {
background-color: #1e293b;
border: 1px solid #334155;
border-radius: 1rem;
padding: 1.25rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.input-field {
width: 100%;
background-color: #334155;
border: 1px solid #475569;
color: #f1f5f9;
padding: 0.75rem;
border-radius: 0.5rem;
}
.btn-action { transition: transform 0.1s; }
.btn-action:active { transform: scale(0.95); }
.ai-box {
background: linear-gradient(135deg, #1e293b 0%, #2e1065 100%);
border: 1px solid #7c3aed;
}
.loader {
border: 2px solid #334155;
border-top: 2px solid #a855f7;
border-radius: 50%;
width: 16px;
height: 16px;
animation: spin 1s linear infinite;
display: inline-block;
}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.check-container {
display: block;
position: relative;
padding-left: 35px;
cursor: pointer;
user-select: none;
}
.check-container input { position: absolute; opacity: 0; cursor: pointer; height: 0; width: 0; }
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 25px;
width: 25px;
background-color: #334155;
border-radius: 6px;
border: 2px solid #475569;
}
.check-container input:checked ~ .checkmark { background-color: #10b981; border-color: #10b981; }
.checkmark:after {
content: "";
position: absolute;
display: none;
left: 9px;
top: 5px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 3px 3px 0;
transform: rotate(45deg);
}
.check-container input:checked ~ .checkmark:after { display: block; }
/* Animation pour la priorité */
.priority-badge {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
100% { transform: scale(1); opacity: 1; }
}
/* Modal simple */
#installModal {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.8);
z-index: 50;
align-items: center;
justify-content: center;
padding: 1rem;
}
</style>
Mon Budget ✨
Priorité 1Organisation zen pour Jéri-one
Connexion...
Garder en priorité
Jérika
<!-- Résumé -->
<section class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<div class="card border-l-4 border-green-500">
<h3 class="text-xs font-bold text-slate-400 uppercase">Revenu Jr Villeneuve</h3>
<p id="incomeJr" class="text-xl font-bold text-green-400">0.00 $</p>
</div>
<div class="card border-l-4 border-emerald-500">
<h3 class="text-xs font-bold text-slate-400 uppercase">Revenu Chômage</h3>
<p id="incomeChomage" class="text-xl font-bold text-emerald-400">0.00 $</p>
</div>
<div class="card border-l-4 border-red-500">
<h3 class="text-xs font-bold text-slate-400 uppercase">Total Dépenses</h3>
<p id="totalExpenses" class="text-xl font-bold text-red-400">0.00 $</p>
</div>
<div id="balanceCard" class="card border-2 border-yellow-500 bg-slate-800">
<h3 class="text-xs font-bold text-yellow-500 uppercase">Argent Restant</h3>
<p id="balance" class="text-2xl font-black">0.00 $</p>
</div>
</section>
<div class="grid lg:grid-cols-3 gap-6">
<!-- Colonne de Gauche -->
<div class="lg:col-span-2 space-y-6">
<!-- Factures Mensuelles -->
<div class="card border-t-4 border-blue-500">
<h2 class="text-lg font-bold flex items-center gap-2 mb-4"><i class="fas fa-calendar-check text-blue-400"></i> Mes Factures du Mois</h2>
<div class="grid grid-cols-1 md:grid-cols-4 gap-2 mb-6 p-3 bg-slate-900/50 rounded-xl border border-slate-700">
<select id="billCategory" class="input-field text-sm">
<option value="Loyer">Loyer 🏠</option>
<option value="Assurance">Assurance 🛡️</option>
<option value="Proposition">Prop. Consommateur 📄</option>
<option value="Internet">Internet/Cell 📱</option>
<option value="Hydro">Hydro/Énergie ⚡</option>
<option value="Autre">Autre 💡</option>
</select>
<input type="number" id="billDay" placeholder="Jour (1-31)" class="input-field text-sm">
<input type="number" id="billAmount" placeholder="Montant" class="input-field text-sm">
<button onclick="addBillTemplate()" class="bg-blue-600 hover:bg-blue-500 text-white font-bold py-2 rounded-lg text-sm btn-action">Prévoir</button>
</div>
<div id="billsList" class="space-y-3"></div>
</div>
<div class="grid md:grid-cols-2 gap-6">
<!-- Entrées d'argent -->
<div class="card">
<h2 class="text-lg font-bold mb-4 flex items-center gap-2"><i class="fas fa-wallet text-green-400"></i> Entrées</h2>
<div class="flex flex-col gap-2 mb-4">
<select id="incomeType" class="input-field text-sm">
<option value="Jr Villeneuve">Jr Villeneuve</option>
<option value="Chômage">Chômage</option>
<option value="Autre">Autre</option>
</select>
<input type="number" id="incomeAmount" placeholder="Montant" class="input-field text-sm">
<button onclick="addItem('income')" class="bg-green-600 hover:bg-green-500 text-white font-bold py-2 rounded-lg btn-action">Ajouter</button>
</div>
<div id="incomeList" class="space-y-1 max-h-40 overflow-y-auto pr-1"></div>
</div>
<!-- Dépenses Ponctuelles -->
<div class="card">
<h2 class="text-lg font-bold mb-4 flex items-center gap-2"><i class="fas fa-shopping-bag text-red-400"></i> Dépenses</h2>
<div class="flex flex-col gap-2 mb-4">
<input type="text" id="expenseSource" placeholder="C'est pour quoi ?" class="input-field text-sm">
<input type="number" id="expenseAmount" placeholder="Montant" class="input-field text-sm">
<button onclick="addItem('expense')" class="bg-red-600 hover:bg-red-500 text-white font-bold py-2 rounded-lg btn-action">Retirer</button>
</div>
<div id="expenseList" class="space-y-1 max-h-40 overflow-y-auto pr-1"></div>
</div>
</div>
</div>
<!-- Coach IA -->
<div class="lg:col-span-1">
<div class="card ai-box h-full sticky top-6 flex flex-col">
<h2 class="text-xl font-black text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-400 mb-4 flex items-center gap-2">
<i class="fas fa-sparkles"></i> Coach Gémi
</h2>
<p class="text-xs text-slate-300 mb-6 leading-relaxed italic">
"Jéri-one, ton budget est sauvegardé et prêt. On lâche rien ! ✨"
</p>
<button onclick="askGemini()" class="w-full bg-purple-600 hover:bg-purple-500 text-white font-bold py-4 rounded-xl shadow-lg flex items-center justify-center gap-2 btn-action">
<span id="aiLoader" class="loader hidden"></span>
<span>Analyser mon mois ✨</span>
</button>
<div id="aiOutput" class="mt-6 text-sm text-slate-200 leading-relaxed overflow-y-auto flex-1"></div>
</div>
</div>
</div>
</div>
<!-- Modal Instructions Priorité -->
<div id="installModal">
<div class="bg-slate-800 p-6 rounded-2xl max-w-sm w-full border border-slate-700 shadow-2xl">
<h2 class="text-xl font-bold text-white mb-4">Garder en priorité ⭐</h2>
<div class="space-y-4 text-sm text-slate-300">
<div class="flex items-start gap-3">
<div class="bg-purple-600 text-white w-6 h-6 rounded-full flex items-center justify-center flex-shrink-0">1</div>
<p>Sur ton **Pixel 9**, clique sur les <i class="fas fa-ellipsis-v"></i> en haut à droite de Chrome et choisis **"Ajouter à l'écran d'accueil"**.</p>
</div>
<div class="flex items-start gap-3">
<div class="bg-purple-600 text-white w-6 h-6 rounded-full flex items-center justify-center flex-shrink-0">2</div>
<p>Sur **ordinateur**, fais un clic-droit sur l'onglet en haut et choisis **"Épingler"**.</p>
</div>
<p class="text-xs text-slate-500 pt-2 italic">Comme ça, ton budget ne disparaîtra jamais de tes yeux, Jéri-one !</p>
</div>
<button onclick="toggleModal(false)" class="w-full mt-6 bg-slate-700 hover:bg-slate-600 text-white font-bold py-2 rounded-lg">Compris !</button>
</div>
</div>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
import { getFirestore, doc, setDoc, onSnapshot, collection } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
const firebaseConfig = JSON.parse(__firebase_config);
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
let user = null;
let bills = [];
let transactions = [];
const initAuth = async () => {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
};
initAuth();
onAuthStateChanged(auth, (u) => {
user = u;
if (user) {
document.getElementById('syncStatus').innerHTML = '<i class="fas fa-cloud text-green-400"></i> Sauvegardé';
setupSync();
}
});
function setupSync() {
if (!user) return;
const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'settings', 'budget_state');
onSnapshot(docRef, (docSnap) => {
if (docSnap.exists()) {
const data = docSnap.data();
bills = data.bills || [];
transactions = data.transactions || [];
updateUI();
}
});
}
async function saveToCloud() {
if (!user) return;
document.getElementById('syncStatus').innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sync...';
const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'settings', 'budget_state');
await setDoc(docRef, { bills, transactions });
document.getElementById('syncStatus').innerHTML = '<i class="fas fa-cloud text-green-400"></i> Sauvegardé';
}
window.toggleModal = function(show) {
document.getElementById('installModal').style.display = show ? 'flex' : 'none';
}
window.addItem = function(category) {
const typeInput = document.getElementById(category === 'income' ? 'incomeType' : 'expenseSource');
const amountInput = document.getElementById(category === 'income' ? 'incomeAmount' : 'expenseAmount');
const type = typeInput.value.trim() || (category === 'income' ? 'Autre' : 'Dépense');
const amount = parseFloat(amountInput.value);
if (amount > 0) {
transactions.push({ id: Date.now(), category, type, amount });
amountInput.value = '';
saveToCloud();
}
};
window.removeTransaction = function(id) {
const trans = transactions.find(t => t.id === id);
if (trans?.originBillId) {
const bill = bills.find(b => b.id === trans.originBillId);
if (bill) bill.paid = false;
}
transactions = transactions.filter(t => t.id !== id);
saveToCloud();
};
window.addBillTemplate = function() {
const cat = document.getElementById('billCategory').value;
const day = parseInt(document.getElementById('billDay').value);
const amount = parseFloat(document.getElementById('billAmount').value);
if (amount > 0 && day >= 1 && day <= 31) {
bills.push({ id: 'bill-' + Date.now(), category: cat, day, amount, paid: false });
document.getElementById('billDay').value = '';
document.getElementById('billAmount').value = '';
saveToCloud();
}
};
window.toggleBill = function(id) {
const bill = bills.find(b => b.id === id);
if (bill) {
bill.paid = !bill.paid;
if (bill.paid) {
transactions.push({ id: 't-' + id, category: 'expense', type: `${bill.category} (le ${bill.day})`, amount: bill.amount, originBillId: id });
} else {
transactions = transactions.filter(t => t.originBillId !== id);
}
saveToCloud();
}
};
window.removeBill = function(id) {
bills = bills.filter(b => b.id !== id);
transactions = transactions.filter(t => t.originBillId !== id);
saveToCloud();
};
window.updateUI = function() {
const incList = document.getElementById('incomeList');
const expList = document.getElementById('expenseList');
const bList = document.getElementById('billsList');
incList.innerHTML = ''; expList.innerHTML = ''; bList.innerHTML = '';
let totalJr = 0, totalChom = 0, totalAutre = 0, totalExp = 0;
bills.sort((a,b) => a.day - b.day).forEach(b => {
const div = document.createElement('div');
div.className = `flex items-center justify-between p-3 rounded-xl border ${b.paid ? 'bg-green-500/10 border-green-500/30' : 'bg-slate-800 border-slate-700'}`;
div.innerHTML = `<div class="flex items-center gap-3"><label class="check-container"><input type="checkbox" ${b.paid ? 'checked' : ''} onchange="toggleBill('${b.id}')"><span class="checkmark"></span></label><div><p class="text-sm font-bold ${b.paid ? 'text-green-400 line-through' : 'text-slate-100'}">${b.category}</p><p class="text-[10px] text-slate-400 font-bold">Le ${b.day}</p></div></div><div class="flex items-center gap-4"><span class="font-black ${b.paid ? 'text-green-400' : 'text-white'}">${b.amount.toFixed(2)} $</span><button onclick="removeBill('${b.id}')" class="text-slate-600 hover:text-red-400 text-xs"><i class="fas fa-trash"></i></button></div>`;
bList.appendChild(div);
});
transactions.forEach(t => {
const div = document.createElement('div');
div.className = 'flex justify-between items-center bg-slate-800/40 p-2 rounded-lg text-sm mb-1 border border-slate-700/50';
div.innerHTML = `<span>${t.type}</span><div class="flex items-center gap-2"><span class="font-bold">${t.amount.toFixed(2)} $</span><button onclick="removeTransaction(${t.id})" class="text-red-500/50 hover:text-red-500"><i class="fas fa-times"></i></button></div>`;
if (t.category === 'income') {
incList.appendChild(div);
if (t.type === 'Jr Villeneuve') totalJr += t.amount;
else if (t.type === 'Chômage') totalChom += t.amount;
else totalAutre += t.amount;
} else {
expList.appendChild(div);
totalExp += t.amount;
}
});
const totalInc = totalJr + totalChom + totalAutre;
const balance = totalInc - totalExp;
document.getElementById('incomeJr').textContent = `${totalJr.toFixed(2)} $`;
document.getElementById('incomeChomage').
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels