REFINEMENT-DELAYed SUMMARY SOURCE-CODE

 <!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>JobsDone App - Supabase Blogger Edition</title>

    

    <!-- STYLES: Tailwind CSS & Google Fonts -->

    <script src="https://cdn.tailwindcss.com"></script>

    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">

    

    <style type="text/tailwindcss">

        @layer base {

            html { font-family: 'Inter', sans-serif; }

        }

        @layer components {

            .input-select {

                @apply w-full h-12 px-4 bg-slate-50 border border-slate-200 rounded-xl text-slate-700 focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all appearance-none;

            }

            .btn-primary {

                @apply w-full h-14 bg-gradient-to-br from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white text-lg font-bold uppercase tracking-widest rounded-2xl shadow-lg shadow-blue-500/20 active:scale-[0.98] transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2;

            }

        }

    </style>


    <!-- DEPENDENCIES -->

    <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>

    <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

    <script src="https://unpkg.com/@supabase/supabase-js@2"></script>

    <script src="https://unpkg.com/lucide@latest"></script>

    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

</head>

<body class="bg-slate-50">

    <!-- Application Root -->

    <div id="jobsdone-blogger-root"></div>


    <script type="text/babel">

        /**

         * JOBSDONE APP for Blogger - Supabase Edition

         * Optimized for Blogger Gadget usage.

         */

        const { useState, useEffect, useMemo } = React;

        

        // --- CONFIGURATION ---

        const SUPABASE_URL = 'https://ipjlqlsaxhvzvelcgqav.supabase.co';

        const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImlwamxxbHNheGh2enZlbGNncWF2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzY2MDA4NTUsImV4cCI6MjA5MjE3Njg1NX0.ceuuTpDjAHS4vmwgczr6pV0Kf1DQo97p25nPfW-JLTQ';

        const TABLE_NAME = 'web-tasks'; // Your main database table

        

        const supabaseClient = supabase.createClient(SUPABASE_URL, SUPABASE_KEY);


        const MONTH_NAMES = [

            "January", "February", "March", "April", "May", "June", 

            "July", "August", "September", "October", "November", "December"

        ];


        function App() {

            const [jobs, setJobs] = useState([]);

            const [loading, setLoading] = useState(true);

            const [connStatus, setConnStatus] = useState('checking');

            const [newMonth, setNewMonth] = useState(MONTH_NAMES[new Date().getMonth()]);

            const [newYear, setNewYear] = useState(new Date().getFullYear().toString());

            const [newDesc, setNewDesc] = useState('');

            const [isSaving, setIsSaving] = useState(false);

            const [searchQuery, setSearchQuery] = useState('');

            const [sortOrder, setSortOrder] = useState('desc');


            const fetchJobs = async () => {

                setLoading(true);

                try {

                    const { data, error } = await supabaseClient

                        .from(TABLE_NAME)

                        .select('*')

                        .order('created_at', { ascending: sortOrder === 'asc' });

                    

                    if (error) throw error;

                    setJobs(data || []);

                    setConnStatus('ok');

                } catch (err) {

                    console.error('Connection error:', err);

                    setConnStatus('failed');

                } finally {

                    setLoading(false);

                }

            };


            useEffect(() => {

                fetchJobs();

                // Initialize icons

                setTimeout(() => lucide.createIcons(), 500);

            }, [sortOrder]);


            useEffect(() => {

                lucide.createIcons();

            }, [jobs, connStatus, loading]);


            const handleAdd = async () => {

                if (!newDesc.trim()) { alert("Please enter a description."); return; }

                setIsSaving(true);

                

                const headerStr = `${newMonth.toUpperCase()} ${newYear}`;

                const dateStr = `${newMonth} ${newYear}`;

                

                const jobData = {

                    header: headerStr,

                    detail: newDesc,

                    date: dateStr,

                    status: 'DONE',

                    id: Math.random().toString(36).substring(7) // Local ID for immediate state update if needed

                };


                try {

                    const { error } = await supabaseClient.from(TABLE_NAME).insert([jobData]);

                    if (error) throw error;

                    setNewDesc('');

                    fetchJobs();

                } catch (err) {

                    alert("Sync failed. Check connection.");

                } finally {

                    setIsSaving(false);

                }

            };


            const handleDelete = async (id) => {

                if (!confirm('Are you sure you want to delete this record?')) return;

                try {

                    const { error } = await supabaseClient.from(TABLE_NAME).delete().eq('id', id);

                    if (error) throw error;

                    fetchJobs();

                } catch (err) { console.error(err); }

            };


            const filteredJobs = useMemo(() => {

                return jobs.filter(job => 

                    (job.detail || "").toLowerCase().includes(searchQuery.toLowerCase()) ||

                    (job.header || "").toLowerCase().includes(searchQuery.toLowerCase()) ||

                    (job.date || "").toLowerCase().includes(searchQuery.toLowerCase())

                );

            }, [jobs, searchQuery]);


            const todayStr = new Date().toLocaleDateString('en-US', {

                month: 'long', day: 'numeric', year: 'numeric'

            });


            return (

                <div className="min-h-screen bg-slate-50 text-slate-900 p-4 md:p-8 max-w-4xl mx-auto border-x border-slate-100">

                    <header className="mb-10 flex flex-col md:flex-row md:items-center justify-between gap-6">

                        <div className="space-y-1">

                            <div className="flex items-center gap-3">

                                <div className="p-2 bg-blue-600 rounded-lg shadow-lg">

                                    <i data-lucide="check-circle-2" className="w-6 h-6 text-white"></i>

                                </div>

                                <h1 className="text-3xl font-extrabold tracking-tight text-slate-900">JOBSDONE APP</h1>

                            </div>

                            <p className="text-sm font-medium text-slate-500 italic pl-11">Refined Activity Logging — {todayStr}</p>

                        </div>

                        <div className="flex items-center">

                            {connStatus === 'ok' ? (

                                <span className="flex items-center gap-2 px-3 py-1 bg-emerald-50 text-emerald-700 text-[10px] font-bold rounded-full border border-emerald-100 uppercase tracking-wider">

                                    <div className="w-1.5 h-1.5 bg-emerald-500 rounded-full shadow-[0_0_8px_rgba(16,185,129,0.5)]"></div>

                                    Supabase Connected

                                </span>

                            ) : (

                                <span className="flex items-center gap-2 px-3 py-1 bg-rose-50 text-rose-700 text-[10px] font-bold rounded-full border border-rose-100 uppercase tracking-wider">

                                    <div className="w-1.5 h-1.5 bg-rose-500 rounded-full"></div>

                                    Connection Error

                                </span>

                            )}

                        </div>

                    </header>


                    <div className="flex justify-center mb-10 text-emerald-500 opacity-20">

                        <i data-lucide="history" className="w-10 h-10 animate-[spin_10s_linear_infinite]"></i>

                    </div>


                    <div className="space-y-10">

                        {/* INPUT SECTION */}

                        <section className="bg-white border border-slate-200 rounded-3xl shadow-sm p-6 md:p-8 space-y-6 relative overflow-hidden">

                            <div className="absolute top-0 left-0 w-2 h-full bg-blue-600"></div>

                            

                            <div className="flex items-center gap-3 text-xl font-bold text-slate-800">

                                <i data-lucide="plus-circle" className="text-blue-600 w-6 h-6"></i>

                                <span>Log New Milestone</span>

                            </div>


                            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">

                                <div className="space-y-2">

                                    <label className="text-[10px] font-bold text-slate-400 uppercase tracking-[0.15em] ml-1">Select Month</label>

                                    <select value={newMonth} onChange={(e) => setNewMonth(e.target.value)} className="input-select">

                                        {MONTH_NAMES.map(m => <option key={m} value={m}>{m}</option>)}

                                    </select>

                                </div>

                                <div className="space-y-2">

                                    <label className="text-[10px] font-bold text-slate-400 uppercase tracking-[0.15em] ml-1">Select Year</label>

                                    <select value={newYear} onChange={(e) => setNewYear(e.target.value)} className="input-select">

                                        {Array.from({length: 10}, (_, i) => 2023 + i).map(y => <option key={y} value={y}>{y}</option>)}

                                    </select>

                                </div>

                            </div>


                            <div className="space-y-2">

                                <label className="text-[10px] font-bold text-slate-400 uppercase tracking-[0.15em] ml-1">Description</label>

                                <textarea 

                                    placeholder="What did you achieve?..." 

                                    value={newDesc} 

                                    onChange={(e) => setNewDesc(e.target.value)} 

                                    className="w-full min-h-[140px] p-5 text-lg border border-slate-200 rounded-2xl focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all bg-slate-50/50 outline-none resize-none"

                                ></textarea>

                            </div>


                            <button onClick={handleAdd} disabled={isSaving || connStatus !== 'ok'} className="btn-primary">

                                <i data-lucide="plus-circle" className="w-5 h-5"></i>

                                <span>{isSaving ? "Syncing..." : "Commit to Database"}</span>

                            </button>

                        </section>


                        {/* TOOLS SECTION */}

                        <section className="bg-white border border-slate-200 rounded-2xl p-4 flex flex-col md:flex-row gap-4 items-center justify-between">

                            <div className="relative w-full md:max-w-xs">

                                <i data-lucide="search" className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400"></i>

                                <input 

                                    type="text"

                                    placeholder="Search milestones..."

                                    value={searchQuery}

                                    onChange={(e) => setSearchQuery(e.target.value)}

                                    className="w-full h-11 pl-11 pr-4 bg-slate-50 border border-slate-100 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500/10 transition-all"

                                />

                            </div>

                            

                            <button 

                                onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}

                                className="flex items-center gap-2 px-4 h-11 bg-slate-50 text-slate-600 text-sm font-semibold rounded-xl border border-slate-100 hover:bg-slate-100 transition-colors"

                            >

                                <i data-lucide="arrow-up-down" className="w-4 h-4"></i>

                                <span>{sortOrder === 'desc' ? 'Newest First' : 'Oldest First'}</span>

                            </button>

                        </section>


                        {/* DATA TABLE */}

                        <section className="bg-white border border-slate-200 rounded-3xl shadow-sm overflow-hidden min-h-[300px]">

                            <div className="overflow-x-auto">

                                <table className="w-full text-left">

                                    <thead className="bg-slate-50/80 border-b border-slate-100 uppercase text-[10px] font-bold text-slate-400 tracking-widest">

                                        <tr>

                                            <th className="px-8 py-5">Period</th>

                                            <th className="px-8 py-5 text-center">Milestone Details</th>

                                            <th className="px-8 py-5 text-right">Action</th>

                                        </tr>

                                    </thead>

                                    <tbody className="divide-y divide-slate-50 text-sm">

                                        {loading && filteredJobs.length === 0 ? (

                                            <tr><td colSpan="3" className="py-24 text-center text-slate-300 italic">Pulling records from cloud...</td></tr>

                                        ) : filteredJobs.length === 0 ? (

                                            <tr><td colSpan="3" className="py-24 text-center text-slate-300 italic">No milestones captured yet.</td></tr>

                                        ) : (

                                            filteredJobs.map(job => (

                                                <tr key={job.id} className="hover:bg-slate-50/50 transition-colors">

                                                    <td className="px-8 py-7 w-48">

                                                        <div className="font-bold text-slate-900 leading-tight">

                                                            {(job.header || job.date || "").split(' ')[0]}

                                                        </div>

                                                        <div className="text-[10px] font-bold text-blue-600 uppercase tracking-wider mt-1 opacity-70">

                                                            Year: {(job.header || job.date || "").split(' ')[1]}

                                                        </div>

                                                    </td>

                                                    <td className="px-8 py-7 text-slate-600 leading-relaxed italic border-x border-slate-100/30">

                                                        <div className="max-w-md mx-auto">{job.detail}</div>

                                                    </td>

                                                    <td className="px-8 py-7 text-right">

                                                        <button onClick={() => handleDelete(job.id)} className="p-3 text-slate-200 hover:text-rose-500 hover:bg-rose-50 rounded-2xl transition-all">

                                                            <i data-lucide="trash-2" className="w-5 h-5"></i>

                                                        </button>

                                                    </td>

                                                </tr>

                                            ))

                                        )}

                                    </tbody>

                                </table>

                            </div>

                        </section>

                    </div>


                    <footer className="mt-20 py-10 border-t border-slate-100 text-center">

                        <p className="text-[10px] font-bold text-slate-300 uppercase tracking-[0.5em]">Household++ Productivity Core</p>

                    </footer>

                </div>

            );

        }


        const root = ReactDOM.createRoot(document.getElementById('jobsdone-blogger-root'));

        root.render(<App />);

    </script>

</body>

</html>


Comments