{"id":16,"date":"2026-05-09T12:12:30","date_gmt":"2026-05-09T10:12:30","guid":{"rendered":"https:\/\/dzieci.julsoft.pl\/?page_id=16"},"modified":"2026-05-09T12:13:04","modified_gmt":"2026-05-09T10:13:04","slug":"glowna","status":"publish","type":"page","link":"https:\/\/dzieci.julsoft.pl\/","title":{"rendered":"G\u0142\u00f3wna"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"16\" class=\"elementor elementor-16\">\n\t\t\t\t<div class=\"elementor-element elementor-element-45e682a e-flex e-con-boxed e-con e-parent\" data-id=\"45e682a\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f09f24e elementor-widget elementor-widget-shortcode\" data-id=\"f09f24e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"shortcode.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-shortcode\">    <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\r\n    <script src=\"https:\/\/unpkg.com\/react@18\/umd\/react.production.min.js\"><\/script>\r\n    <script src=\"https:\/\/unpkg.com\/react-dom@18\/umd\/react-dom.production.min.js\"><\/script>\r\n    <script src=\"https:\/\/unpkg.com\/@babel\/standalone\/babel.min.js\"><\/script>\r\n    <script src=\"https:\/\/unpkg.com\/lucide@latest\"><\/script>\r\n\r\n    <script>\r\n        window.tgwRestUrl = 'https:\/\/dzieci.julsoft.pl\/index.php?rest_route=\/tgw\/v1\/hours';\r\n        window.tgwAjaxUrl = 'https:\/\/dzieci.julsoft.pl\/wp-admin\/admin-ajax.php';\r\n    <\/script>\r\n\r\n    <style>\r\n        .tgw-container { font-family: 'Inter', system-ui, sans-serif; }\r\n        .no-scrollbar::-webkit-scrollbar { display: none; }\r\n        .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }\r\n    <\/style>\r\n\r\n    <div id=\"tgw-app-root\" class=\"tgw-container w-full max-w-md mx-auto bg-gray-50 min-h-[600px] shadow-2xl rounded-2xl overflow-hidden relative border border-gray-200 flex flex-col\">\r\n        <div class=\"flex items-center justify-center h-64 text-gray-400 font-bold\">\u0141adowanie aplikacji...<\/div>\r\n    <\/div>\r\n\r\n    <!-- PWA Instalacja Banner -->\r\n    <div id=\"tgw-install-banner\" class=\"hidden fixed bottom-4 left-4 right-4 bg-blue-600 text-white p-4 rounded-xl shadow-lg flex justify-between items-center z-50 max-w-md mx-auto\">\r\n        <span class=\"text-sm font-bold\">Zainstaluj jako aplikacj\u0119 na telefonie!<\/span>\r\n        <button onclick=\"tgwInstallPWA()\" class=\"bg-white text-blue-600 px-4 py-2 rounded-lg font-bold text-xs uppercase\">Instaluj<\/button>\r\n    <\/div>\r\n\r\n    <script type=\"text\/babel\" data-presets=\"react,env\">\r\n        const { useState, useEffect } = React;\r\n\r\n        const App = () => {\r\n            const [entries, setEntries] = useState([]);\r\n            const [isLoading, setIsLoading] = useState(true);\r\n            const [view, setView] = useState('list'); \/\/ 'list' lub 'add'\r\n            const [isSending, setIsSending] = useState(false);\r\n            const [notification, setNotification] = useState(null);\r\n\r\n            \/\/ Fetch data\r\n            useEffect(() => {\r\n                fetch(window.tgwRestUrl)\r\n                    .then(res => res.json())\r\n                    .then(data => {\r\n                        setEntries(Array.isArray(data) ? data : []);\r\n                        setIsLoading(false);\r\n                        setTimeout(() => { if(window.lucide) lucide.createIcons(); }, 100);\r\n                    })\r\n                    .catch(err => {\r\n                        console.error('B\u0142\u0105d:', err);\r\n                        setIsLoading(false);\r\n                    });\r\n            }, []);\r\n\r\n            useEffect(() => {\r\n                if(window.lucide) lucide.createIcons();\r\n            }, [view, entries, notification]);\r\n\r\n            const showNotify = (msg, type = 'success') => {\r\n                setNotification({ msg, type });\r\n                setTimeout(() => setNotification(null), 4000);\r\n            };\r\n\r\n            const saveData = (newEntries) => {\r\n                setEntries(newEntries);\r\n                fetch(window.tgwRestUrl, {\r\n                    method: 'POST',\r\n                    headers: { 'Content-Type': 'application\/json' },\r\n                    body: JSON.stringify(newEntries)\r\n                });\r\n            };\r\n\r\n            const handleAddEntry = (e) => {\r\n                e.preventDefault();\r\n                const fd = new FormData(e.target);\r\n                const newEntry = {\r\n                    id: Date.now().toString(),\r\n                    date: fd.get('date'),\r\n                    start: fd.get('start'),\r\n                    end: fd.get('end'),\r\n                    notes: fd.get('notes')\r\n                };\r\n                saveData([...entries, newEntry]);\r\n                setView('list');\r\n                showNotify('Zapisano pomy\u015blnie!');\r\n            };\r\n\r\n            const handleDelete = (id) => {\r\n                if(window.confirm('Na pewno usun\u0105\u0107 ten wpis?')) {\r\n                    saveData(entries.filter(e => e.id !== id));\r\n                    showNotify('Wpis usuni\u0119ty.', 'error');\r\n                }\r\n            };\r\n\r\n            const handleSendReport = () => {\r\n                if(entries.length === 0) return alert('Brak godzin do wys\u0142ania!');\r\n                if(!window.confirm('Czy na pewno chcesz wygenerowa\u0107 i wys\u0142a\u0107 raport na maila?')) return;\r\n\r\n                setIsSending(true);\r\n                const fd = new FormData();\r\n                fd.append('action', 'tgw_send_report');\r\n\r\n                fetch(window.tgwAjaxUrl, { method: 'POST', body: fd })\r\n                    .then(res => res.json())\r\n                    .then(data => {\r\n                        setIsSending(false);\r\n                        if(data.success) {\r\n                            showNotify(data.data, 'success');\r\n                            \/\/ saveData([]); \/\/ Odkomentuj je\u015bli chcesz kasowa\u0107 wpisy po wys\u0142aniu\r\n                        } else {\r\n                            showNotify(data.data || 'B\u0142\u0105d wysy\u0142ki', 'error');\r\n                        }\r\n                    })\r\n                    .catch(() => {\r\n                        setIsSending(false);\r\n                        showNotify('B\u0142\u0105d komunikacji z serwerem.', 'error');\r\n                    });\r\n            };\r\n\r\n            const calcDuration = (start, end) => {\r\n                if(!start || !end) return { h: 0, m: 0, totalMins: 0 };\r\n                let [sh, sm] = start.split(':').map(Number);\r\n                let [eh, em] = end.split(':').map(Number);\r\n                \r\n                let d1 = new Date(0,0,0, sh, sm);\r\n                let d2 = new Date(0,0,0, eh, em);\r\n                if (d2 < d1) d2.setDate(d2.getDate() + 1);\r\n                \r\n                let diff = (d2 - d1) \/ 60000; \/\/ w minutach\r\n                return {\r\n                    h: Math.floor(diff \/ 60),\r\n                    m: diff % 60,\r\n                    totalMins: diff\r\n                };\r\n            };\r\n\r\n            if (isLoading) return <div className=\"flex-1 flex items-center justify-center font-bold text-blue-600 animate-pulse\">Wczytywanie danych...<\/div>;\r\n\r\n            \/\/ Obliczenie sumy\r\n            let totalMinutesAll = 0;\r\n            entries.forEach(e => { totalMinutesAll += calcDuration(e.start, e.end).totalMins; });\r\n            const totalH = Math.floor(totalMinutesAll \/ 60);\r\n            const totalM = totalMinutesAll % 60;\r\n\r\n            \/\/ Sortowanie do wy\u015bwietlenia (najnowsze na g\u00f3rze)\r\n            const sortedEntries = [...entries].sort((a,b) => new Date(b.date) - new Date(a.date));\r\n\r\n            return (\r\n                <div className=\"flex flex-col h-full bg-slate-50 relative\">\r\n                    \r\n                    {\/* Header *\/}\r\n                    <div className=\"bg-blue-600 text-white p-5 shadow-md rounded-b-3xl relative z-10\">\r\n                        <div className=\"flex justify-between items-center mb-4\">\r\n                            <h1 className=\"text-xl font-extrabold flex items-center tracking-tight\">\r\n                                <i data-lucide=\"heart\" className=\"w-5 h-5 mr-2 text-red-400 fill-red-400\"><\/i> Wolontariat\r\n                            <\/h1>\r\n                            {entries.length > 0 && view === 'list' && (\r\n                                <button onClick={handleSendReport} disabled={isSending} className=\"bg-white\/20 hover:bg-white\/30 p-2 rounded-full transition-colors relative group\">\r\n                                    {isSending ? <i data-lucide=\"loader\" className=\"w-5 h-5 animate-spin\"><\/i> : <i data-lucide=\"send\" className=\"w-5 h-5\"><\/i>}\r\n                                <\/button>\r\n                            )}\r\n                        <\/div>\r\n\r\n                        <div className=\"text-center pb-2\">\r\n                            <p className=\"text-blue-100 text-xs font-bold uppercase tracking-widest mb-1\">Przepracowane godziny<\/p>\r\n                            <div className=\"text-5xl font-black tabular-nums tracking-tighter\">\r\n                                {totalH}<span className=\"text-2xl text-blue-200\">h<\/span> {totalM}<span className=\"text-2xl text-blue-200\">m<\/span>\r\n                            <\/div>\r\n                        <\/div>\r\n                    <\/div>\r\n\r\n                    {\/* Notification Toast *\/}\r\n                    {notification && (\r\n                        <div className={`absolute top-4 left-1\/2 -translate-x-1\/2 z-50 px-4 py-2 rounded-full font-bold text-sm shadow-xl flex items-center transform transition-all ${notification.type === 'success' ? 'bg-green-500 text-white' : 'bg-red-500 text-white'}`}>\r\n                            <i data-lucide={notification.type === 'success' ? 'check-circle' : 'alert-circle'} className=\"w-4 h-4 mr-2\"><\/i> {notification.msg}\r\n                        <\/div>\r\n                    )}\r\n\r\n                    {\/* Content Area *\/}\r\n                    <div className=\"flex-1 overflow-y-auto p-4 no-scrollbar pb-24\">\r\n                        {view === 'list' ? (\r\n                            <div className=\"space-y-3\">\r\n                                {sortedEntries.length === 0 ? (\r\n                                    <div className=\"text-center mt-10 text-gray-400 flex flex-col items-center\">\r\n                                        <div className=\"bg-gray-100 p-4 rounded-full mb-3\"><i data-lucide=\"clock\" className=\"w-8 h-8 text-gray-300\"><\/i><\/div>\r\n                                        <p className=\"font-bold\">Brak wprowadzonych godzin.<\/p>\r\n                                        <p className=\"text-sm mt-1\">Kliknij przycisk +, aby doda\u0107 pierwszy wpis.<\/p>\r\n                                    <\/div>\r\n                                ) : (\r\n                                    sortedEntries.map(entry => {\r\n                                        const dur = calcDuration(entry.start, entry.end);\r\n                                        return (\r\n                                            <div key={entry.id} className=\"bg-white p-4 rounded-2xl shadow-sm border border-gray-100 flex items-center justify-between group transition-all hover:shadow-md\">\r\n                                                <div>\r\n                                                    <div className=\"flex items-center text-xs font-bold text-gray-400 mb-1\">\r\n                                                        <i data-lucide=\"calendar\" className=\"w-3 h-3 mr-1\"><\/i> {entry.date}\r\n                                                    <\/div>\r\n                                                    <div className=\"font-bold text-gray-800 text-lg\">\r\n                                                        {entry.start} <span className=\"text-gray-400 mx-1\">\u2192<\/span> {entry.end}\r\n                                                    <\/div>\r\n                                                    {entry.notes && <div className=\"text-xs text-gray-500 mt-1 italic leading-tight\">\"{entry.notes}\"<\/div>}\r\n                                                <\/div>\r\n                                                <div className=\"flex flex-col items-end\">\r\n                                                    <span className=\"bg-green-100 text-green-700 font-bold px-3 py-1 rounded-xl text-sm mb-2 shadow-inner\">\r\n                                                        {dur.h}h {dur.m}m\r\n                                                    <\/span>\r\n                                                    <button onClick={() => handleDelete(entry.id)} className=\"text-red-400 hover:text-red-600 bg-red-50 hover:bg-red-100 p-1.5 rounded-lg transition-colors\">\r\n                                                        <i data-lucide=\"trash-2\" className=\"w-4 h-4\"><\/i>\r\n                                                    <\/button>\r\n                                                <\/div>\r\n                                            <\/div>\r\n                                        );\r\n                                    })\r\n                                )}\r\n                            <\/div>\r\n                        ) : (\r\n                            <div className=\"bg-white p-6 rounded-2xl shadow-lg border border-gray-100 animate-in fade-in slide-in-from-bottom-4\">\r\n                                <div className=\"flex justify-between items-center mb-6\">\r\n                                    <h2 className=\"text-xl font-bold text-gray-800\">Dodaj godziny<\/h2>\r\n                                    <button onClick={() => setView('list')} className=\"text-gray-400 hover:text-gray-600 bg-gray-100 p-2 rounded-full\"><i data-lucide=\"x\" className=\"w-5 h-5\"><\/i><\/button>\r\n                                <\/div>\r\n                                <form onSubmit={handleAddEntry} className=\"space-y-5\">\r\n                                    <div>\r\n                                        <label className=\"block text-xs font-bold text-gray-500 uppercase tracking-wide mb-1\">Data<\/label>\r\n                                        <input required name=\"date\" type=\"date\" defaultValue={new Date().toISOString().split('T')[0]} className=\"w-full bg-gray-50 border border-gray-200 p-3 rounded-xl focus:ring-2 focus:ring-blue-500 outline-none transition-all font-bold text-gray-700\"\/>\r\n                                    <\/div>\r\n                                    <div className=\"grid grid-cols-2 gap-4\">\r\n                                        <div>\r\n                                            <label className=\"block text-xs font-bold text-gray-500 uppercase tracking-wide mb-1\">Start<\/label>\r\n                                            <input required name=\"start\" type=\"time\" className=\"w-full bg-gray-50 border border-gray-200 p-3 rounded-xl focus:ring-2 focus:ring-blue-500 outline-none transition-all font-bold text-gray-700 text-center text-lg\"\/>\r\n                                        <\/div>\r\n                                        <div>\r\n                                            <label className=\"block text-xs font-bold text-gray-500 uppercase tracking-wide mb-1\">Koniec<\/label>\r\n                                            <input required name=\"end\" type=\"time\" className=\"w-full bg-gray-50 border border-gray-200 p-3 rounded-xl focus:ring-2 focus:ring-blue-500 outline-none transition-all font-bold text-gray-700 text-center text-lg\"\/>\r\n                                        <\/div>\r\n                                    <\/div>\r\n                                    <div>\r\n                                        <label className=\"block text-xs font-bold text-gray-500 uppercase tracking-wide mb-1\">Czynno\u015bci \/ Notatki (Opcjonalnie)<\/label>\r\n                                        <textarea name=\"notes\" rows=\"2\" placeholder=\"np. Pomoc w lekcjach...\" className=\"w-full bg-gray-50 border border-gray-200 p-3 rounded-xl focus:ring-2 focus:ring-blue-500 outline-none transition-all text-sm resize-none\"><\/textarea>\r\n                                    <\/div>\r\n                                    <button type=\"submit\" className=\"w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-4 rounded-xl shadow-lg shadow-blue-600\/30 transition-all active:scale-95 flex items-center justify-center text-lg\">\r\n                                        <i data-lucide=\"check\" className=\"w-5 h-5 mr-2\"><\/i> Zapisz\r\n                                    <\/button>\r\n                                <\/form>\r\n                            <\/div>\r\n                        )}\r\n                    <\/div>\r\n\r\n                    {\/* FAB Button (Add) *\/}\r\n                    {view === 'list' && (\r\n                        <div className=\"absolute bottom-6 left-1\/2 -translate-x-1\/2\">\r\n                            <button onClick={() => setView('add')} className=\"bg-blue-600 text-white p-4 rounded-full shadow-2xl shadow-blue-600\/50 hover:bg-blue-700 hover:scale-110 transition-all flex items-center justify-center\">\r\n                                <i data-lucide=\"plus\" className=\"w-8 h-8\"><\/i>\r\n                            <\/button>\r\n                        <\/div>\r\n                    )}\r\n                <\/div>\r\n            );\r\n        };\r\n\r\n        const root = ReactDOM.createRoot(document.getElementById('tgw-app-root'));\r\n        root.render(<App \/>);\r\n    <\/script>\r\n\r\n    <script>\r\n        \/\/ PWA Obs\u0142uga instalacji\r\n        let deferredPrompt;\r\n        if ('serviceWorker' in navigator) {\r\n            window.addEventListener('load', () => {\r\n                navigator.serviceWorker.register('https:\/\/dzieci.julsoft.pl\/?tgw_pwa=sw');\r\n            });\r\n        }\r\n\r\n        window.addEventListener('beforeinstallprompt', (e) => {\r\n            e.preventDefault();\r\n            deferredPrompt = e;\r\n            document.getElementById('tgw-install-banner').classList.remove('hidden');\r\n        });\r\n\r\n        function tgwInstallPWA() {\r\n            if (deferredPrompt) {\r\n                deferredPrompt.prompt();\r\n                deferredPrompt.userChoice.then((choiceResult) => {\r\n                    if (choiceResult.outcome === 'accepted') {\r\n                        document.getElementById('tgw-install-banner').classList.add('hidden');\r\n                    }\r\n                    deferredPrompt = null;\r\n                });\r\n            }\r\n        }\r\n    <\/script>\r\n    <\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_canvas","meta":{"footnotes":""},"class_list":["post-16","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/dzieci.julsoft.pl\/index.php?rest_route=\/wp\/v2\/pages\/16","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dzieci.julsoft.pl\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/dzieci.julsoft.pl\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/dzieci.julsoft.pl\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dzieci.julsoft.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=16"}],"version-history":[{"count":4,"href":"https:\/\/dzieci.julsoft.pl\/index.php?rest_route=\/wp\/v2\/pages\/16\/revisions"}],"predecessor-version":[{"id":20,"href":"https:\/\/dzieci.julsoft.pl\/index.php?rest_route=\/wp\/v2\/pages\/16\/revisions\/20"}],"wp:attachment":[{"href":"https:\/\/dzieci.julsoft.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=16"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}