- Get link
- X
- Other Apps
<!-- 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
.txtyang 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