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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions