Skip to content

Gemini Budget  #596

@jerione33

Description

@jerione33
<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é 1

Organisation 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').

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions