Skip to main content

My Pending and Delayed POSTs SUMMARY [APPs]
MADE by ChatGPT [THIRD MILESTONE → SOURCE CODE]

<!-- MY ChatGPT SESSION -->

<!-- =========================================================

Berikut ringkasan lengkap (summary) dari seluruh alur percakapan di sesi ini — mulai dari awal hingga versi final:

🧩 SUMMARY OF THIS SESSION

1️⃣ User Request: Perbaikan CRUD Firebase (Parent & Child)

Anda memiliki sistem CRUD besar (dua modul: Pending Posts & To-Do List) yang bekerja di Blogger dengan Firebase. Anda meminta:

  • sorting by newest

  • timestamp 24-hour format

  • status PENDING / DONE

  • row highlight untuk DONE

  • tambahan kolom status di database + UI

  • inline edit form yang muncul di bawah setiap record

  • namun form input utama tetap di atas

  • seluruh kode harus compatible dengan Blogger (tanpa <head> dan <body>)


2️⃣ Upload File Awal (pending v0.txt)

Anda mengunggah file besar berisi kode CRUD yang belum memiliki inline edit dan status system.

Saya melakukan:

  • analisis struktur

  • identifikasi bug UI

  • merencanakan refactor total


3️⃣ Permintaan Anda: Biarkan saya membuat FULL VERSION FINAL

Anda meminta:

“A) Langsung lanjut membuat versi FINAL yang lengkap”

Saya membuat versi awal:

  • inline edit

  • status system

  • sorting

  • timestamp

  • dua modul CRUD sudah jalan

  • downloadable file HTML


4️⃣ Bug Report: Radio button UI berantakan

Anda memberi screenshot error:

  • status box tidak rapi

  • inline edit layout buruk

  • Blogger tidak menerima <head> & <body>


5️⃣ Perbaikan Status Box (v3)

Saya memperbaiki:

  • status-box menggunakan flex

  • padding, gap, alignment

  • shaded box stabil

  • versi .txt yang siap tempel di Blogger


6️⃣ Permintaan Besar: Gabungkan seluruh kode asli → file FINAL siap upload

Anda berkata:

“Gabungkan seluruh kode asli saya menjadi FILE FINAL utuh siap upload ke Blogger”

Saya meminta file lengkap → Anda upload pending v2.1.txt.


7️⃣ Versi Akhir yang Anda pilih: “B) DOCUMENTED VERSION”

Anda ingin:

  • versi final lengkap

  • penuh komentar blok

  • mudah dipelajari

  • mudah diedit


8️⃣ Pembuatan FILE FINAL UTUH (v3)

Saya membuat:

  • satu file .txt

  • memuat seluruh:

    • CSS lengkap

    • Firebase config

    • Parent CRUD logic

    • Child CRUD logic

    • Inline edit system

    • status box

    • timestamp

    • sorting

    • row highlight

    • responsive layout

    • tanpa tag <html>, <head>, <body>

✔ Cocok ditempel langsung ke Blogger
✔ 100% documented

Anda menerima link:

FINAL_Pending_Inline_Documented_v3.txt


🟢 HASIL AKHIR SESI INI

Anda kini memiliki:

🎯 File final utuh, documented, siap upload ke Blogger 

✔ Dua modul CRUD lengkap (Pending Posts + ToDo List)

✔ Inline edit rapi di bawah setiap record

✔ Status DONE/PENDING dengan shaded box

✔ Sorting default newest first

✔ Timestamp 24 jam otomatis

✔ Firebase realtime working

✔ Kode aman untuk Blogger (tidak ada <head> & <body>)

✔ Downloadable file final

========================================================= -->

<!-- =========================================================
     Job Pending + To-Do Inline CRUD
     Blogger-Compatible, Documented Version (v3)
     - No <html>, <head>, or <body> tags
     - Parent module  : urlCrudData  (Pending / Delayed Posts)
     - Child module   : webAppData   (TO DO List)
     - Inline Edit    : Form muncul tepat di bawah record
     - Status field   : PENDING / DONE + row highlight
     - Timestamp      : YYYY-MM-DD HH:MM:SS (24 jam)
   ========================================================= -->

<!-- ====================================== -->
<!-- 1. GLOBAL STYLES (CSS)                -->
<!-- ====================================== -->
<style>
/* Font default untuk seluruh konten */
body {
    font-family: Arial, sans-serif;
}

/* Judul section utama */
.section-title {
    text-align: center;
    font-size: 26px;
    margin-bottom: 25px;
}

/* Box pembungkus modul (parent & child) */
.res-box {
    margin: auto;
    max-width: 900px;
    padding-bottom: 40px;
}

/* Search bar global */
.search-bar {
    width: 100%;
    padding: 10px;
    border-radius: 6px;
    border: 1px solid #aaa;
    margin-bottom: 15px;
}

/* Box pengaturan sort */
.sort-box {
    display: flex;
    gap: 10px;
    margin-bottom: 15px;
}
.sort-box select {
    padding: 8px;
    border-radius: 6px;
    border: 1px solid #999;
}

/* Tabel fleksibel berbasis flexbox */
.responsive-table {
    width: 100%;
    display: block;
}
.responsive-row {
    display: flex;
    flex-wrap: wrap;
    border-bottom: 1px solid #ccc;
    padding: 10px 0;
}
.responsive-head {
    background: #eee;
    font-weight: bold;
}

/* Lebar kolom default (desktop) */
.col-date   { flex: 0 0 18%; max-width: 18%; padding: 5px; }
.col-status { flex: 0 0 10%; max-width: 10%; padding: 5px; text-align:center; }
.col-auto   { flex: 0 0 26%; max-width: 26%; padding: 5px; }
.col-my     { flex: 0 0 26%; max-width: 26%; padding: 5px; }
.col-url    { flex: 0 0 10%; max-width: 10%; padding: 5px; text-align:center; }
.col-act    { flex: 0 0 10%; max-width: 10%; padding: 5px; text-align:center; }

/* Tombol link ke URL / postingan */
.link-btn {
    background: #0078ff;
    color: white;
    padding: 6px 12px;
    border-radius: 6px;
    text-decoration: none;
    display: inline-block;
}

/* Tombol icon edit / delete */
.icon-btn {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 20px;
    margin: 6px;
}
.icon-edit  { color: #ffaa00; }
.icon-del   { color: #ff4444; }

/* Row dengan status DONE diberi background sedikit lebih gelap */
.done-row {
    background: #c2e6c2; /* hijau muda gelap (Opsi 3) */
}

/* Baris editor inline (muncul di bawah record ketika Edit diklik) */
.inline-edit-row {
    display: none;
    width: 100%;
    box-sizing: border-box;
}
.inline-edit-inner {
    background: #f4f4f4;
    border: 1px solid #ccc;
    border-radius: 8px;
    padding: 10px;
    margin: 8px 0 4px 0;
}
.inline-edit-inner b {
    display: block;
    margin-bottom: 6px;
}
.inline-edit-inner input,
.inline-edit-inner textarea,
.inline-edit-inner select {
    width: 100%;
    padding: 6px;
    margin: 4px 0 10px 0;
    box-sizing: border-box;
    border-radius: 5px;
    border: 1px solid #aaa;
}

/* Wrapper tombol di editor inline */
.inline-btn-row {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}

/* Wrapper status PENDING/DONE (shaded box rapi) */
.status-box {
    display: flex;
    align-items: center;
    gap: 25px;
    padding: 8px 14px;
    border: 1px solid #ccc;
    border-radius: 10px;
    background: #f5f5f5;
    width: max-content;
    margin: 6px 0;
}
.status-box label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 15px;
    white-space: nowrap;
}

/* Jika status-box berada di dalam inline editor, beri jarak sedikit di atas */
.inline-edit-inner .status-box {
    margin-top: 10px;
}

/* Responsif untuk layar mobile (<768px) */
@media (max-width: 768px) {
    .col-date,
    .col-status,
    .col-auto,
    .col-my,
    .col-url,
    .col-act {
        flex: 0 0 100%;
        max-width: 100%;
        text-align: left;
    }
}
</style>

<!-- ====================================== -->
<!-- 2. FIREBASE CDN (WAJIB)               -->
<!-- ====================================== -->
<script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-database-compat.js"></script>

<!-- ====================================== -->
<!-- 3. FIREBASE CONFIG + HELPER FUNGSI    -->
<!-- ====================================== -->
<script>
/* -------------------------------------------------
   3.1. Inisialisasi Firebase
   ------------------------------------------------- */
const firebaseConfig = {
    apiKey: "AIzaSyAF5r6Rvu-DmoV-vf47wYTZfarpVGmNYR0",
    authDomain: "ronin-11938.firebaseapp.com",
    databaseURL: "https://ronin-11938-default-rtdb.firebaseio.com",
    projectId: "ronin-11938",
    storageBucket: "ronin-11938.firebasestorage.app",
    messagingSenderId: "823368889742",
    appId: "1:823368889742:web:609e16ace214b94a0df"
};
if (!firebase.apps.length) firebase.initializeApp(firebaseConfig);

/* Referensi Realtime Database */
const rdb      = firebase.database();
const refCRUD  = rdb.ref("urlCrudData");   // Parent: Pending / Delayed Posts
const refAPP   = rdb.ref("webAppData");    // Child : TO DO List

/* -------------------------------------------------
   3.2. HELPER TANGGAL & JAM
   ------------------------------------------------- */

/* Membuat timestamp 24 jam: YYYY-MM-DD HH:MM:SS */
function nowDateTime() {
    const d  = new Date();
    const yy = d.getFullYear();
    const mm = String(d.getMonth() + 1).padStart(2, "0");
    const dd = String(d.getDate()).padStart(2, "0");
    const hh = String(d.getHours()).padStart(2, "0");
    const mi = String(d.getMinutes()).padStart(2, "0");
    const ss = String(d.getSeconds()).padStart(2, "0");
    return yy + "-" + mm + "-" + dd + " " + hh + ":" + mi + ":" + ss;
}

/* Dipakai untuk header "Today: ..." */
function todayLong() {
    return new Date().toLocaleString("en-US", {
        month: "short",
        day: "2-digit",
        year: "numeric"
    });
}

/* Jika data lama hanya berisi tanggal (tanpa jam), tambahkan 00:00:00 */
function formatDateWithFallback(val) {
    if (!val) return "";
    if (val.includes(":")) return val;
    return val + " 00:00:00";
}

/* Parse tanggal aman untuk sorting (kalau gagal, kembalikan 0) */
function parseDateSafe(val) {
    if (!val) return 0;
    const t = Date.parse(val);
    if (!isNaN(t)) return t;
    return 0;
}

/* -------------------------------------------------
   3.3. HELPER JUDUL OTOMATIS UNTUK URL BLOG
   ------------------------------------------------- */

function isBlog(url) { 
    return url.includes("ronin1985.blogspot.com");
}

/* Mengubah slug terakhir URL blog menjadi judul "Title Case" */
function blogTitle(url) {
    try {
        let last = url.split("/").pop().replace(".html", "");
        return last
            .replace(/[-_]+/g, " ")
            .replace(/\b\w/g, function(m){ return m.toUpperCase(); });
    } catch (e) {
        return url;
    }
}

/* Jika bukan blog sendiri, pakai domain utama sebagai judul kasar */
function guessTitle(url) {
    return url.replace("https://", "").replace("http://", "").split("/")[0];
}
</script>

<!-- ====================================== -->
<!-- 4. PARENT MODULE — urlCrudData         -->
<!--    (My Pending and Delayed POSTs)     -->
<!-- ====================================== -->

<div class="res-box">
    <h2 class="section-title" id="todayHeader"></h2>
    <h2 style="text-align: center;">🔗 My Pending and Delayed POSTs SUMMARY</h2>

    <!-- 4.1. Search + Sort -->
    <input class="search-bar" id="searchCRUD" placeholder="Search title or URL…" />

    <div class="sort-box">
        <select id="sortByCRUD">
            <option value="date">Sort by Date</option>
            <option value="autoTitle">Sort by Auto Title</option>
            <option value="myTitle">Sort by My Title</option>
        </select>

        <select id="sortOrderCRUD">
            <option value="asc">Ascending</option>
            <option value="desc" selected>Descending (Newest First)</option>
        </select>
    </div>

    <!-- 4.2. MAIN FORM (tetap di atas) -->
    <div style="background: rgb(244, 244, 244);
                border-radius: 10px;
                border: 1px solid rgb(204, 204, 204);
                margin-bottom: 25px;
                padding: 15px;">
        <b>Insert URL:</b><br />
        <input id="urlInputCRUD"
               style="border-radius: 6px;
                      border: 1px solid rgb(170, 170, 170);
                      margin-top: 5px;
                      padding: 10px;
                      width: 100%;" />

        <br /><br />

        <b>Your Own Title (Optional):</b><br />
        <input id="myTitleInputCRUD"
               style="border-radius: 6px;
                      border: 1px solid rgb(170, 170, 170);
                      margin-top: 5px;
                      padding: 10px;
                      width: 100%;" />

        <br /><br />

        <b>Status:</b><br />
        <!-- Status utama PENDING / DONE di main form parent -->
        <div class="status-box">
            <label>
                <input type="radio" name="statusCRUD" value="PENDING" checked /> Pending
            </label>
            <label>
                <input type="radio" name="statusCRUD" value="DONE" /> Done
            </label>
        </div>

        <br />

        <button onclick="autoAddCRUD()"
                style="background: rgb(0, 120, 255);
                       border-radius: 8px;
                       border: none;
                       color: white;
                       padding: 10px 18px;">
            ➕ ADD ENTRY
        </button>

        <button id="saveBtnCRUD"
                onclick="saveEditCRUD()"
                style="background: rgb(255, 170, 0);
                       border-radius: 8px;
                       border: none;
                       color: white;
                       display: none;
                       padding: 10px 18px;">
            💾 SAVE EDIT (MAIN FORM)
        </button>
    </div>

    <!-- 4.3. FLEX TABLE PARENT -->
    <div class="responsive-table">
        <div class="responsive-row responsive-head">
            <div class="col-date">DATE / TIME</div>
            <div class="col-auto">AUTO TITLE</div>
            <div class="col-my">MY TITLE</div>
            <div class="col-status">STATUS</div>
            <div class="col-url">URL</div>
            <div class="col-act">ACTIONS</div>
        </div>

        <!-- Isi tabel di-render lewat JavaScript -->
        <div id="tableBodyCRUD"></div>
    </div>
</div>

<!-- ====================================== -->
<!-- 5. PARENT LOGIC (JS)                  -->
<!-- ====================================== -->
<script>
/* Header "Today: ..." */
document.getElementById("todayHeader").innerHTML = "📅 Today: " + todayLong();

/* Array penampung data parent dari Firebase */
let crudData = [];
let currentEditID_CRUD = null;  // ID record yang sedang diedit (parent)

/* --------------------------------------------- */
/* 5.1. Realtime listener ke node urlCrudData    */
/* --------------------------------------------- */
refCRUD.on("value", function(snap){
    crudData = snap.val() ? Object.values(snap.val()) : [];
    renderCRUD();
});

/* Event search & sort */
document.getElementById("searchCRUD").oninput   = renderCRUD;
document.getElementById("sortByCRUD").onchange  = renderCRUD;
document.getElementById("sortOrderCRUD").onchange = renderCRUD;

/* Ambil status terpilih di MAIN FORM parent */
function getStatusCRUD(){
    const radios = document.querySelectorAll('input[name="statusCRUD"]');
    for (const r of radios) {
        if (r.checked) return r.value;
    }
    return "PENDING";
}

/* Cari record parent berdasarkan id */
function findCRUDById(id){
    return crudData.find(function(e){ return e.id === id; }) || null;
}

/* Menutup semua inline editor parent */
function closeAllInlineCRUD(){
    crudData.forEach(function(e){
        const box = document.getElementById("inlineCRUD-" + e.id);
        if (box) box.style.display = "none";
    });
}

/* --------------------------------------------- */
/* 5.2. Render daftar parent ke tabel            */
/* --------------------------------------------- */
function renderCRUD(){
    let search = document.getElementById("searchCRUD").value.toLowerCase();
    let sortBy = document.getElementById("sortByCRUD").value;
    let order  = document.getElementById("sortOrderCRUD").value;

    /* Filter berdasarkan search (autoTitle, myTitle, url) */
    let data = crudData.filter(function(e){
        const autoT = (e.autoTitle || "").toLowerCase();
        const myT   = (e.myTitle   || "").toLowerCase();
        const url   = (e.url       || "").toLowerCase();
        return autoT.includes(search) || myT.includes(search) || url.includes(search);
    });

    /* Sorting – default sortBy=date (timestamp) */
    data.sort(function(a,b){
        if (sortBy === "date") {
            const x = parseDateSafe(a.date);
            const y = parseDateSafe(b.date);
            return (order === "asc") ? (x - y) : (y - x);
        } else {
            let x = (a[sortBy] || "").toString().toLowerCase();
            let y = (b[sortBy] || "").toString().toLowerCase();
            return (order === "asc") ? x.localeCompare(y) : y.localeCompare(x);
        }
    });

    /* Bangun HTML row + inline editor */
    let html = "";
    data.forEach(function(e){
        const statusLabel = e.status || "PENDING";
        const rowClass    = (statusLabel === "DONE") ? "responsive-row done-row" : "responsive-row";

        html += ""
        + "<div class='" + rowClass + "'>"
        +   "<div class='col-date'>" + formatDateWithFallback(e.date) + "</div>"
        +   "<div class='col-auto' style='font-weight:bold;'>" + (e.autoTitle || "") + "</div>"
        +   "<div class='col-my'>"   + (e.myTitle   || "") + "</div>"
        +   "<div class='col-status'><span>" + statusLabel + "</span></div>"
        +   "<div class='col-url'><a href='" + (e.url || "#") + "' target='_blank' class='link-btn'>LINK</a></div>"
        +   "<div class='col-act'>"
        +       "<button class='icon-btn icon-edit' onclick=\"openInlineCRUD('" + e.id + "')\">✏</button>"
        +       "<button class='icon-btn icon-del'  onclick=\"deleteCRUD('" + e.id + "')\">🗑</button>"
        +   "</div>"
        + "</div>"

        /* INLINE EDITOR – muncul di bawah record */
        + "<div class='inline-edit-row' id='inlineCRUD-" + e.id + "'>"
        +   "<div class='inline-edit-inner'>"
        +       "<b>Inline Edit — Pending POST</b>"
        +       "<label>URL:</label>"
        +       "<input id='inlineCRUD-url-" + e.id + "' value='" + (e.url || "") + "' />"
        +       "<label>My Title:</label>"
        +       "<input id='inlineCRUD-myTitle-" + e.id + "' value='" + (e.myTitle || "") + "' />"
        +       "<label>Status:</label>"
        +       "<div class='status-box'>"
        +         "<label><input type='radio' name='inlineCRUD-status-" + e.id + "' value='PENDING'" + (statusLabel === "PENDING" ? " checked" : "") + "> Pending</label>"
        +         "<label><input type='radio' name='inlineCRUD-status-" + e.id + "' value='DONE'"    + (statusLabel === "DONE"    ? " checked" : "") + "> Done</label>"
        +       "</div>"
        +       "<div class='inline-btn-row'>"
        +           "<button onclick=\"saveInlineCRUD('" + e.id + "')\">💾 Save Inline</button>"
        +           "<button onclick=\"closeInlineCRUD('" + e.id + "')\">✖ Cancel</button>"
        +       "</div>"
        +   "</div>"
        + "</div>";
    });

    document.getElementById("tableBodyCRUD").innerHTML = html;
}

/* --------------------------------------------- */
/* 5.3. ADD (main form parent)                   */
/* --------------------------------------------- */
function autoAddCRUD(){
    let url = document.getElementById("urlInputCRUD").value.trim();
    let myT = document.getElementById("myTitleInputCRUD").value.trim();
    if (!url) {
        alert("URL cannot be empty.");
        return;
    }

    let autoT  = isBlog(url) ? blogTitle(url) : guessTitle(url);
    let key    = refCRUD.push().key;
    let status = getStatusCRUD();

    refCRUD.child(key).set({
        id:        key,
        date:      nowDateTime(),   // timestamp baru
        autoTitle: autoT,
        myTitle:   myT,
        url:       url,
        status:    status
    });

    /* Reset form */
    document.getElementById("urlInputCRUD").value    = "";
    document.getElementById("myTitleInputCRUD").value = "";
}

/* --------------------------------------------- */
/* 5.4. OPEN INLINE EDITOR PARENT                */
/* --------------------------------------------- */
function openInlineCRUD(id){
    const rec = findCRUDById(id);
    if (!rec) return;

    currentEditID_CRUD = id;

    /* Tutup semua editor lain dulu */
    closeAllInlineCRUD();

    const box = document.getElementById("inlineCRUD-" + id);
    if (box) box.style.display = "block";

    /* Set nilai field inline dari record */
    const urlInput = document.getElementById("inlineCRUD-url-" + id);
    const myInput  = document.getElementById("inlineCRUD-myTitle-" + id);
    if (urlInput) urlInput.value = rec.url || "";
    if (myInput)  myInput.value  = rec.myTitle || "";

    const radios = document.querySelectorAll("input[name='inlineCRUD-status-" + id + "']");
    radios.forEach(function(r){
        r.checked = (r.value === (rec.status || "PENDING"));
    });

    /* Optional: sinkronkan juga ke MAIN FORM atas */
    document.getElementById("urlInputCRUD").value     = rec.url || "";
    document.getElementById("myTitleInputCRUD").value = rec.myTitle || "";
    document.getElementById("saveBtnCRUD").style.display = "inline-block";

    const mainStatus = rec.status || "PENDING";
    const mainRadios = document.querySelectorAll('input[name="statusCRUD"]');
    mainRadios.forEach(function(r){
        r.checked = (r.value === mainStatus);
    });
}

/* Tutup satu inline editor parent */
function closeInlineCRUD(id){
    const box = document.getElementById("inlineCRUD-" + id);
    if (box) box.style.display = "none";
}

/* --------------------------------------------- */
/* 5.5. SAVE EDIT via MAIN FORM (parent)         */
/* --------------------------------------------- */
function saveEditCRUD(){
    if (!currentEditID_CRUD){
        alert("No record selected for editing (click ✏ Edit first).");
        return;
    }
    let newUrl    = document.getElementById("urlInputCRUD").value.trim();
    let newMyT    = document.getElementById("myTitleInputCRUD").value.trim();
    let newStatus = getStatusCRUD();

    refCRUD.child(currentEditID_CRUD).update({
        url:       newUrl,
        myTitle:   newMyT,
        autoTitle: isBlog(newUrl) ? blogTitle(newUrl) : guessTitle(newUrl),
        status:    newStatus
    });

    /* Reset state & form */
    currentEditID_CRUD = null;
    document.getElementById("urlInputCRUD").value     = "";
    document.getElementById("myTitleInputCRUD").value = "";
    document.getElementById("saveBtnCRUD").style.display = "none";
    closeAllInlineCRUD();
}

/* --------------------------------------------- */
/* 5.6. SAVE EDIT via INLINE EDITOR (parent)     */
/* --------------------------------------------- */
function saveInlineCRUD(id){
    const urlInput = document.getElementById("inlineCRUD-url-" + id);
    const myInput  = document.getElementById("inlineCRUD-myTitle-" + id);
    if (!urlInput || !myInput) return;

    const radios = document.querySelectorAll("input[name='inlineCRUD-status-" + id + "']");
    let st = "PENDING";
    radios.forEach(function(r){
        if (r.checked) st = r.value;
    });

    const newUrl = urlInput.value.trim();
    const newMyT = myInput.value.trim();

    refCRUD.child(id).update({
        url:       newUrl,
        myTitle:   newMyT,
        autoTitle: isBlog(newUrl) ? blogTitle(newUrl) : guessTitle(newUrl),
        status:    st
    });

    /* Reset main form dan tutup inline */
    currentEditID_CRUD = null;
    document.getElementById("urlInputCRUD").value     = "";
    document.getElementById("myTitleInputCRUD").value = "";
    document.getElementById("saveBtnCRUD").style.display = "none";
    closeInlineCRUD(id);
}

/* --------------------------------------------- */
/* 5.7. DELETE PARENT RECORD                     */
/* --------------------------------------------- */
function deleteCRUD(id){
    if (confirm("Delete this entry?")){
        refCRUD.child(id).remove();
    }
}
</script>

<!-- ====================================== -->
<!-- 6. CHILD MODULE — webAppData           -->
<!--    (TO DO LIST SUMMARY)               -->
<!-- ====================================== -->

<div class="res-box">
    <h2 class="section-title" style="text-align: center;">📝 TO DO LIST SUMMARY</h2>

    <!-- 6.1. Search + Sort -->
    <input class="search-bar" id="searchAPP" placeholder="Search header or detail…" />

    <div class="sort-box">
        <select id="sortByAPP">
            <option value="date">Sort by Date</option>
            <option value="header">Sort by Header</option>
            <option value="detail">Sort by Detail</option>
        </select>

        <select id="sortOrderAPP">
            <option value="asc">Ascending</option>
            <option value="desc" selected>Descending (Newest First)</option>
        </select>
    </div>

    <!-- 6.2. MAIN FORM CHILD -->
    <div style="background: rgb(244, 244, 244);
                border-radius: 10px;
                border: 1px solid rgb(204, 204, 204);
                margin-bottom: 25px;
                padding: 15px;">
        <b>Header (Title):</b><br />
        <input id="headerInputAPP"
               style="border-radius: 6px;
                      border: 1px solid rgb(170, 170, 170);
                      margin-top: 5px;
                      padding: 10px;
                      width: 100%;" />

        <br /><br />

        <b>Detail:</b><br />
        <textarea id="detailInputAPP"
                  style="border-radius: 6px;
                         border: 1px solid rgb(170, 170, 170);
                         height: 80px;
                         margin-top: 5px;
                         padding: 10px;
                         width: 100%;"></textarea>

        <br /><br />

        <b>Status:</b><br />
        <!-- Status utama PENDING / DONE di main form child -->
        <div class="status-box">
            <label>
                <input type="radio" name="statusAPP" value="PENDING" checked /> Pending
            </label>
            <label>
                <input type="radio" name="statusAPP" value="DONE" /> Done
            </label>
        </div>

        <br />

        <button onclick="addAPP()"
                style="background: rgb(0, 120, 255);
                       border-radius: 8px;
                       border: none;
                       color: white;
                       padding: 10px 18px;">
            ➕ ADD ENTRY
        </button>

        <button id="saveBtnAPP"
                onclick="saveAPP()"
                style="background: rgb(255, 170, 0);
                       border-radius: 8px;
                       border: none;
                       color: white;
                       display: none;
                       padding: 10px 18px;">
            💾 SAVE EDIT (MAIN FORM)
        </button>
    </div>

    <!-- 6.3. FLEX TABLE CHILD -->
    <div class="responsive-table">
        <div class="responsive-row responsive-head">
            <div class="col-date">DATE / TIME</div>
            <div class="col-auto">HEADER</div>
            <div class="col-my">DETAIL</div>
            <div class="col-status">STATUS</div>
            <div class="col-act">ACTIONS</div>
        </div>

        <!-- Isi tabel child di-render lewat JS -->
        <div id="tableBodyAPP"></div>
    </div>
</div>

<!-- ====================================== -->
<!-- 7. CHILD LOGIC (JS)                    -->
<!-- ====================================== -->
<script>
let appData           = [];
let currentEditID_APP = null;  // ID record yang sedang diedit (child)

/* --------------------------------------------- */
/* 7.1. Realtime listener ke node webAppData     */
/* --------------------------------------------- */
refAPP.on("value", function(snap){
    appData = snap.val() ? Object.values(snap.val()) : [];
    renderAPP();
});

/* Event search & sort (child) */
document.getElementById("searchAPP").oninput   = renderAPP;
document.getElementById("sortByAPP").onchange  = renderAPP;
document.getElementById("sortOrderAPP").onchange = renderAPP;

/* Ambil status terpilih di MAIN FORM child */
function getStatusAPP(){
    const radios = document.querySelectorAll('input[name="statusAPP"]');
    for (const r of radios) {
        if (r.checked) return r.value;
    }
    return "PENDING";
}

/* Cari record child berdasarkan id */
function findAPPById(id){
    return appData.find(function(e){ return e.id === id; }) || null;
}

/* Tutup semua inline editor child */
function closeAllInlineAPP(){
    appData.forEach(function(e){
        const box = document.getElementById("inlineAPP-" + e.id);
        if (box) box.style.display = "none";
    });
}

/* Helper kecil untuk ambil properti by key */
function eVal(obj, key){
    return obj[key];
}

/* --------------------------------------------- */
/* 7.2. Render daftar child ke tabel             */
/* --------------------------------------------- */
function renderAPP(){
    let search = document.getElementById("searchAPP").value.toLowerCase();
    let sortBy = document.getElementById("sortByAPP").value;
    let order  = document.getElementById("sortOrderAPP").value;

    let data = appData.filter(function(e){
        const h = (e.header || "").toLowerCase();
        const d = (e.detail || "").toLowerCase();
        return h.includes(search) || d.includes(search);
    });

    data.sort(function(a,b){
        if (sortBy === "date") {
            const x = parseDateSafe(a.date);
            const y = parseDateSafe(b.date);
            return (order === "asc") ? (x - y) : (y - x);
        } else {
            let x = (eVal(a, sortBy) || "").toString().toLowerCase();
            let y = (eVal(b, sortBy) || "").toString().toLowerCase();
            return (order === "asc") ? x.localeCompare(y) : y.localeCompare(x);
        }
    });

    let html = "";
    data.forEach(function(e){
        const statusLabel = e.status || "PENDING";
        const rowClass    = (statusLabel === "DONE") ? "responsive-row done-row" : "responsive-row";

        html += ""
        + "<div class='" + rowClass + "'>"
        +   "<div class='col-date'>" + formatDateWithFallback(e.date) + "</div>"
        +   "<div class='col-auto' style='font-weight:bold;'>" + (e.header || "") + "</div>"
        +   "<div class='col-my'>"   + (e.detail || "") + "</div>"
        +   "<div class='col-status'><span>" + statusLabel + "</span></div>"
        +   "<div class='col-act'>"
        +       "<button class='icon-btn icon-edit' onclick=\"openInlineAPP('" + e.id + "')\">✏</button>"
        +       "<button class='icon-btn icon-del'  onclick=\"deleteAPP('" + e.id + "')\">🗑</button>"
        +   "</div>"
        + "</div>"

        /* INLINE EDITOR CHILD */
        + "<div class='inline-edit-row' id='inlineAPP-" + e.id + "'>"
        +   "<div class='inline-edit-inner'>"
        +       "<b>Inline Edit — TO DO ITEM</b>"
        +       "<label>Header (Title):</label>"
        +       "<input id='inlineAPP-header-" + e.id + "' value='" + (e.header || "") + "' />"
        +       "<label>Detail:</label>"
        +       "<textarea id='inlineAPP-detail-" + e.id + "'>" + (e.detail || "") + "</textarea>"
        +       "<label>Status:</label>"
        +       "<div class='status-box'>"
        +         "<label><input type='radio' name='inlineAPP-status-" + e.id + "' value='PENDING'" + (statusLabel === "PENDING" ? " checked" : "") + "> Pending</label>"
        +         "<label><input type='radio' name='inlineAPP-status-" + e.id + "' value='DONE'"    + (statusLabel === "DONE"    ? " checked" : "") + "> Done</label>"
        +       "</div>"
        +       "<div class='inline-btn-row'>"
        +           "<button onclick=\"saveInlineAPP('" + e.id + "')\">💾 Save Inline</button>"
        +           "<button onclick=\"closeInlineAPP('" + e.id + "')\">✖ Cancel</button>"
        +       "</div>"
        +   "</div>"
        + "</div>";
    });

    document.getElementById("tableBodyAPP").innerHTML = html;
}

/* --------------------------------------------- */
/* 7.3. ADD (main child form)                    */
/* --------------------------------------------- */
function addAPP(){
    let h = document.getElementById("headerInputAPP").value.trim();
    let d = document.getElementById("detailInputAPP").value.trim();

    if (!h){
        alert("Header cannot be empty.");
        return;
    }

    let key    = refAPP.push().key;
    let status = getStatusAPP();

    refAPP.child(key).set({
        id:     key,
        date:   nowDateTime(),   // timestamp baru
        header: h,
        detail: d,
        status: status
    });

    document.getElementById("headerInputAPP").value = "";
    document.getElementById("detailInputAPP").value = "";
}

/* --------------------------------------------- */
/* 7.4. OPEN INLINE EDITOR CHILD                 */
/* --------------------------------------------- */
function openInlineAPP(id){
    const rec = findAPPById(id);
    if (!rec) return;

    currentEditID_APP = id;
    closeAllInlineAPP();

    const box = document.getElementById("inlineAPP-" + id);
    if (box) box.style.display = "block";

    const hInput = document.getElementById("inlineAPP-header-" + id);
    const dInput = document.getElementById("inlineAPP-detail-" + id);
    if (hInput) hInput.value = rec.header || "";
    if (dInput) dInput.value = rec.detail || "";

    const radios = document.querySelectorAll("input[name='inlineAPP-status-" + id + "']");
    radios.forEach(function(r){
        r.checked = (r.value === (rec.status || "PENDING"));
    });

    /* Sinkron ke MAIN FORM child */
    document.getElementById("headerInputAPP").value = rec.header || "";
    document.getElementById("detailInputAPP").value = rec.detail || "";
    document.getElementById("saveBtnAPP").style.display = "inline-block";

    const mainStatus = rec.status || "PENDING";
    const mainRadios = document.querySelectorAll('input[name="statusAPP"]');
    mainRadios.forEach(function(r){
        r.checked = (r.value === mainStatus);
    });
}

/* Tutup satu inline editor child */
function closeInlineAPP(id){
    const box = document.getElementById("inlineAPP-" + id);
    if (box) box.style.display = "none";
}

/* --------------------------------------------- */
/* 7.5. SAVE EDIT via MAIN FORM (child)          */
/* --------------------------------------------- */
function saveAPP(){
    if (!currentEditID_APP){
        alert("No record selected for editing (click ✏ Edit first).");
        return;
    }
    let h      = document.getElementById("headerInputAPP").value.trim();
    let d      = document.getElementById("detailInputAPP").value.trim();
    let status = getStatusAPP();

    refAPP.child(currentEditID_APP).update({
        header: h,
        detail: d,
        status: status
    });

    currentEditID_APP = null;

    document.getElementById("headerInputAPP").value = "";
    document.getElementById("detailInputAPP").value = "";
    document.getElementById("saveBtnAPP").style.display = "none";
    closeAllInlineAPP();
}

/* --------------------------------------------- */
/* 7.6. SAVE EDIT via INLINE CHILD               */
/* --------------------------------------------- */
function saveInlineAPP(id){
    const hInput = document.getElementById("inlineAPP-header-" + id);
    const dInput = document.getElementById("inlineAPP-detail-" + id);
    if (!hInput || !dInput) return;

    const radios = document.querySelectorAll("input[name='inlineAPP-status-" + id + "']");
    let st = "PENDING";
    radios.forEach(function(r){
        if (r.checked) st = r.value;
    });

    const h = hInput.value.trim();
    const d = dInput.value.trim();

    refAPP.child(id).update({
        header: h,
        detail: d,
        status: st
    });

    currentEditID_APP = null;
    document.getElementById("headerInputAPP").value = "";
    document.getElementById("detailInputAPP").value = "";
    document.getElementById("saveBtnAPP").style.display = "none";
    closeInlineAPP(id);
}

/* --------------------------------------------- */
/* 7.7. DELETE CHILD RECORD                      */
/* --------------------------------------------- */
function deleteAPP(id){
    if (confirm("Delete entry?")){
        refAPP.child(id).remove();
    }
}
</script>

Comments

Popular posts from this blog

[ERROR BUG]
ChatGPT+Gemini: TikTok → Blogger Embed Converter using Cloudflare/Online Server

🔄 Refresh Page ERROR BUG: The connection is blocked because it was initiated by a public page to connect to devices or servers on your local network. Planning: Revise Program CODE Code USING Javascript/Online Server Code NOT USING Javascript Sample Working Code aka Already Repaired! Temporary Solution is by Asking AI Assistant to do REPAIR CODE of (Not yet Repaired) Current Conversion Program Code-Output TikTok Archive – Embedded Preview TikTok Embed ▶ View this video on TikTok ⚠️ DISCLAIMER: INPUT URL LIMITATION This program is currently restricted to processing Full Browser URLs only. It does not support TikTok’s mobile "short-link" format (e.g., vt.tiktok.com ). Required Action: Users must open the video in a web browser and copy the expanded URL from the address bar before pasting it into this program. URL Conversion Example ❌ UNSUPPORTED: https://vt.tiktok.com/ZSaXoFyov/ ✅ REQ...

Repost! Web-Based to Android Apps Convertion (MEDIAN.CO etc.)

CONTOH HASIL Android APK "PROGRAM" SAMPLE: Youtube and Instagram EMBEDded to Blogger/Blogspot.com SOURCE CODE Click this box to download Contoh Sample SHORTCUT-APPs "precise" click to download : median.co R8: ronin1985.blogspot.com R2M: ronin-manu.blogspot.com Gw udah coba Median.co utk mengubah Website gw menjadi Aplikasi Android Keren bet!! Median.co Cekidot Software lain yg mirip! ChatGPT : If you're looking for tools similar to Median.co to convert websites into Android apps, here are some top alternatives, especially for no-code or low-code users: 🔧 Best Tools Like Median.co to Convert Website to Android Apps 1. WebViewGold Platform: Windows/macOS (Xcode/Android Studio) Key Feature: Converts any website into iOS/Android app via WebView. Pros: One-time purchase Custom splash screen, push notifications Can open external links in external browser Cons:  Requi...

REPOST: Studying WATER PUMP by ROMAN ENGINEERING

*^ Ini yg Asli Gan! Mekanisme pada Concrete Pump: Kok ky Sistem Mekanik Romawi ya?! Tapi malah bisa HANYA pake PER aka bukan "MATA BOR look a like" Mekanisme Drill yg Cost Pembuatan bikin REPOT aka harus Tool SUPER Khusus Dari Material Besi yg digunakan terlihat langsung secara kasat mata Jauh Lebih Banyak drpd Per Biasa seperti yg ditunjukkan pd Video Alternatif dgn Penggunaan PER Video dr Instagram: Source: YouTube Rome's drainage machines #history #romanempire #engineering