{"id":12,"date":"2025-11-11T17:28:05","date_gmt":"2025-11-11T17:28:05","guid":{"rendered":"https:\/\/auliawo.art\/?page_id=12"},"modified":"2025-12-08T03:09:31","modified_gmt":"2025-12-08T03:09:31","slug":"rating","status":"publish","type":"page","link":"https:\/\/auliawo.art\/?page_id=12","title":{"rendered":"Rating"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"12\" class=\"elementor elementor-12\">\n\t\t\t\t<div class=\"elementor-element elementor-element-809b572 e-flex e-con-boxed e-con e-parent\" data-id=\"809b572\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-ce12774 elementor-widget elementor-widget-html\" data-id=\"ce12774\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!doctype html>\r\n<html lang=\"id\">\r\n<head>\r\n<meta charset=\"utf-8\" \/>\r\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" \/>\r\n<title>AuliaWO \u2014 Reviews<\/title>\r\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Playfair+Display:wght@600&family=Poppins:wght@300;400;500&display=swap\" rel=\"stylesheet\">\r\n<style>\r\n  :root{\r\n    --bg: #fbf7fb;\r\n    --card:#fff;\r\n    --accent1:#4b204b;\r\n    --accent2:#a66db1;\r\n    --muted:#6d5070;\r\n    --glass: rgba(255,255,255,0.7);\r\n  }\r\n  *{box-sizing:border-box}\r\n  body{\r\n    margin:0;\r\n    font-family:\"Poppins\",sans-serif;\r\n    background: linear-gradient(180deg,#f8f4f1,#fff);\r\n    color:var(--accent1);\r\n    -webkit-font-smoothing:antialiased;\r\n  }\r\n  .wrap{max-width:1100px;margin:48px auto;padding:28px}\r\n  .hero{\r\n    text-align:center;margin-bottom:20px;\r\n  }\r\n  .hero h1{font-family:'Playfair Display',serif;font-size:2.4rem;margin:0;color:var(--accent1)}\r\n  .hero p{color:var(--muted);margin-top:8px}\r\n  .grid{display:grid;grid-template-columns:1fr 420px;gap:26px}\r\n  @media(max-width:920px){ .grid{grid-template-columns:1fr} }\r\n\r\n  \/* Form card *\/\r\n  .card{\r\n    background:var(--card);\r\n    padding:22px;\r\n    border-radius:16px;\r\n    box-shadow:0 10px 30px rgba(75,32,75,0.08);\r\n    border:1px solid rgba(166,109,177,0.08);\r\n  }\r\n  label{display:block;font-weight:500;margin-bottom:8px;color:var(--accent1)}\r\n  .row{display:flex;gap:12px}\r\n  .row .col{flex:1}\r\n  input[type=\"text\"], input[type=\"email\"], input[type=\"date\"], textarea, select{\r\n    width:100%;padding:10px 12px;border-radius:10px;border:1px solid #e7d6ea;background:#fbf8fb;color:var(--accent1);\r\n    font-size:14px;margin-bottom:12px;\r\n  }\r\n  textarea{min-height:110px;resize:vertical}\r\n  .star-row{display:flex;gap:6px;align-items:center;margin-bottom:12px}\r\n  .star{\r\n    font-size:28px;cursor:pointer;color:#e8d6ef;transition:transform .15s, color .15s;\r\n  }\r\n  .star.active{color:var(--accent2);transform:scale(1.08)}\r\n  .actions{display:flex;gap:10px;flex-wrap:wrap}\r\n  .btn{\r\n    display:inline-block;padding:10px 18px;border-radius:999px;border:none;background:linear-gradient(135deg,var(--accent1),var(--accent2));color:white;font-weight:600;cursor:pointer;\r\n    text-decoration:none;\r\n  }\r\n  .btn.ghost{background:transparent;color:var(--accent1);border:1px solid rgba(75,32,75,0.06)}\r\n  .small{padding:8px 12px;font-size:13px;border-radius:10px}\r\n\r\n  \/* Reviews list *\/\r\n  .list-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}\r\n  .reviews{display:flex;flex-direction:column;gap:14px}\r\n  .review{\r\n    background:linear-gradient(180deg,#fff,#faf5fa);\r\n    padding:16px;border-radius:14px;border:1px solid #f0e2f5;box-shadow:0 6px 16px rgba(166,109,177,0.06);\r\n    display:flex;gap:14px;align-items:flex-start;\r\n  }\r\n  .avatar{\r\n    width:56px;height:56px;border-radius:50%;flex:0 0 56px;background:linear-gradient(135deg,#a66db1,#d8b4e2);display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:20px;\r\n  }\r\n  .meta{flex:1}\r\n  .meta .name{font-weight:600;color:var(--accent1)}\r\n  .meta .meta-sub{font-size:13px;color:var(--muted);margin-bottom:6px}\r\n  .meta .text{color:#5d3c5d;line-height:1.55}\r\n  .meta .stars{color:var(--accent2);margin-top:8px}\r\n  .review .ctrls{display:flex;flex-direction:column;gap:8px}\r\n  .tiny{padding:6px 8px;border-radius:10px;font-size:13px}\r\n\r\n  .note{font-size:13px;color:#6b4d6b;margin-top:8px}\r\n\r\n  \/* footer utility *\/\r\n  .util{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px}\r\n  .import-file{display:none}\r\n\r\n  \/* subtle animations *\/\r\n  .fade-in{animation:fadeIn .5s ease both}\r\n  @keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}\r\n<\/style>\r\n<\/head>\r\n<body>\r\n  <div class=\"wrap\">\r\n    <div class=\"hero\">\r\n      <h1>Bagikan Pengalaman Anda \u2014 AuliaWO<\/h1>\r\n      <p>Tulis review jujur, simpan lokal, atau kirim langsung ke server kami (opsional).<\/p>\r\n    <\/div>\r\n\r\n    <div class=\"grid\">\r\n      <!-- LEFT: Reviews List -->\r\n      <div>\r\n        <div class=\"card\">\r\n          <div class=\"list-head\">\r\n            <strong>Review Pelanggan<\/strong>\r\n            <div>\r\n              <button class=\"btn ghost small\" id=\"btnExport\">Export JSON<\/button>\r\n              <label class=\"btn ghost small\" style=\"cursor:pointer\">\r\n                <input id=\"fileImport\" class=\"import-file\" type=\"file\" accept=\".json\" \/>\r\n                Import\r\n              <\/label>\r\n            <\/div>\r\n          <\/div>\r\n\r\n          <div id=\"reviews\" class=\"reviews\" aria-live=\"polite\"><\/div>\r\n\r\n          <div style=\"margin-top:14px;font-size:13px;color:var(--muted)\">\r\n            Menyimpan ke <code>localStorage<\/code>. Untuk produksi, hubungkan dengan endpoint server.\r\n          <\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <!-- RIGHT: Form -->\r\n      <aside>\r\n        <div class=\"card fade-in\" aria-labelledby=\"formTitle\">\r\n          <h3 id=\"formTitle\" style=\"margin:0 0 12px;font-family:'Playfair Display',serif;color:var(--accent1)\">Tulis Review<\/h3>\r\n\r\n          <div>\r\n            <label>Rating<\/label>\r\n            <div class=\"star-row\" id=\"starRow\" role=\"radiogroup\" aria-label=\"Rating\">\r\n              <span class=\"star\" data-value=\"1\" title=\"1\">\u2605<\/span>\r\n              <span class=\"star\" data-value=\"2\" title=\"2\">\u2605<\/span>\r\n              <span class=\"star\" data-value=\"3\" title=\"3\">\u2605<\/span>\r\n              <span class=\"star\" data-value=\"4\" title=\"4\">\u2605<\/span>\r\n              <span class=\"star\" data-value=\"5\" title=\"5\">\u2605<\/span>\r\n            <\/div>\r\n          <\/div>\r\n\r\n          <label>Nama<\/label>\r\n          <input id=\"inpName\" type=\"text\" placeholder=\"Nama Anda\" \/>\r\n\r\n          <label>Email<\/label>\r\n          <input id=\"inpEmail\" type=\"email\" placeholder=\"emailanda@example.com\" \/>\r\n\r\n          <label>Tanggal Acara<\/label>\r\n          <input id=\"inpDate\" type=\"date\" \/>\r\n\r\n          <label>Catatan<\/label>\r\n          <textarea id=\"inpNote\" placeholder=\"Tulis pengalaman singkat...\"><\/textarea>\r\n\r\n          <div class=\"actions\" style=\"margin-top:8px\">\r\n            <button id=\"btnSave\" class=\"btn\">Simpan Lokal<\/button>\r\n            <button id=\"btnPost\" class=\"btn ghost\">Post ke Server<\/button>\r\n            <button id=\"btnClear\" class=\"btn ghost small\">Hapus Semuanya<\/button>\r\n          <\/div>\r\n\r\n          <div id=\"msg\" style=\"margin-top:10px;color:var(--accent2);font-weight:600;display:none\"><\/div>\r\n\r\n          <hr style=\"border:none;border-top:1px solid #f0e6f6;margin:18px 0\">\r\n\r\n          <div style=\"font-size:13px;color:var(--muted)\">\r\n            Jika ingin langsung dikirim, klik <em>Post ke Server<\/em>. (Endpoint diset di script).\r\n          <\/div>\r\n        <\/div>\r\n      <\/aside>\r\n    <\/div>\r\n  <\/div>\r\n\r\n<script>\r\n\/* ====== Configuration ======\r\n - POST_ENDPOINT: ganti dengan URL servermu bila ingin mengirim review\r\n - USE_POST true untuk mengaktifkan fetch posting (otherwise will show simulated)\r\n*\/\r\nconst POST_ENDPOINT = ''; \/\/ contoh: 'https:\/\/example.com\/api\/reviews'\r\nconst USE_POST = POST_ENDPOINT.length>0;\r\n\r\n\/* ====== Utilities ====== *\/\r\nconst qs = s => document.querySelector(s);\r\nconst qsa = s => Array.from(document.querySelectorAll(s));\r\n\r\n\/* ====== State & Storage Keys ====== *\/\r\nconst STORAGE_KEY = 'auliawo_reviews_v1';\r\nlet reviews = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');\r\n\r\n\/* ====== DOM refs ====== *\/\r\nconst reviewsEl = qs('#reviews');\r\nconst inpName = qs('#inpName');\r\nconst inpEmail = qs('#inpEmail');\r\nconst inpDate = qs('#inpDate');\r\nconst inpNote = qs('#inpNote');\r\nconst btnSave = qs('#btnSave');\r\nconst btnPost = qs('#btnPost');\r\nconst btnClear = qs('#btnClear');\r\nconst msgEl = qs('#msg');\r\nconst fileImport = qs('#fileImport');\r\nconst btnExport = qs('#btnExport');\r\n\r\n\/* ====== Rating widget ====== *\/\r\nlet currentRating = 5;\r\nconst stars = qsa('.star');\r\nfunction renderStars(r){\r\n  stars.forEach(s=>{\r\n    s.classList.toggle('active', Number(s.dataset.value) <= r);\r\n  });\r\n}\r\nstars.forEach(s=>{\r\n  s.addEventListener('click', ()=> {\r\n    currentRating = Number(s.dataset.value);\r\n    renderStars(currentRating);\r\n  });\r\n  s.addEventListener('keydown', (e)=> {\r\n    if(e.key === 'Enter' || e.key === ' '){\r\n      e.preventDefault(); currentRating = Number(s.dataset.value); renderStars(currentRating);\r\n    }\r\n  });\r\n});\r\nrenderStars(currentRating);\r\n\r\n\/* ====== Render reviews list ====== *\/\r\nfunction avatarLetter(name){\r\n  if(!name) return '?';\r\n  return name.trim().split(\/\\s+\/).map(p=>p[0].toUpperCase()).slice(0,2).join('');\r\n}\r\nfunction formatDate(d){\r\n  if(!d) return '';\r\n  try { return new Date(d).toLocaleDateString(); } catch(e){ return d; }\r\n}\r\nfunction render(){\r\n  reviewsEl.innerHTML = '';\r\n  if(reviews.length === 0){\r\n    reviewsEl.innerHTML = '<div style=\"color:var(--muted);padding:12px;border-radius:10px;background:#fff7fb;border:1px dashed #f0dff5\">Belum ada review. Jadilah yang pertama!<\/div>';\r\n    return;\r\n  }\r\n  reviews.slice().reverse().forEach((r, idx)=>{\r\n    const div = document.createElement('div');\r\n    div.className = 'review fade-in';\r\n    div.innerHTML = `\r\n      <div class=\"avatar\">${avatarLetter(r.name)}<\/div>\r\n      <div class=\"meta\">\r\n        <div class=\"name\">${escapeHtml(r.name || 'Anonim')}<\/div>\r\n        <div class=\"meta-sub\">${escapeHtml(r.email || '')} ${r.date? ' \u2022 '+formatDate(r.date) : ''}<\/div>\r\n        <div class=\"text\">${escapeHtml(r.note || '')}<\/div>\r\n        <div class=\"stars\">${'\u2605'.repeat(r.rating)}${'\u2606'.repeat(5-r.rating)}<\/div>\r\n        ${r.serverResponse ? `<div class=\"note\">Status: ${escapeHtml(r.serverResponse)}<\/div>` : ''}\r\n      <\/div>\r\n      <div class=\"ctrls\">\r\n        <button class=\"tiny btn-edit tiny\">Edit<\/button>\r\n        <button class=\"tiny btn-delete tiny\">Hapus<\/button>\r\n      <\/div>\r\n    `;\r\n    \/\/ attach handlers\r\n    const editBtn = div.querySelector('.btn-edit');\r\n    const delBtn = div.querySelector('.btn-delete');\r\n    editBtn.addEventListener('click', ()=> loadForEdit(reviews.length-1-idx));\r\n    delBtn.addEventListener('click', ()=> {\r\n      if(!confirm('Hapus review ini?')) return;\r\n      reviews.splice(reviews.length-1-idx,1);\r\n      saveAndRender();\r\n    });\r\n    reviewsEl.appendChild(div);\r\n  });\r\n}\r\n\r\n\/* ====== Save & Render ====== *\/\r\nfunction saveAndRender(){\r\n  localStorage.setItem(STORAGE_KEY, JSON.stringify(reviews));\r\n  render();\r\n}\r\n\r\n\/* ====== Input Helpers ====== *\/\r\nfunction clearForm(){\r\n  inpName.value=''; inpEmail.value=''; inpDate.value=''; inpNote.value='';\r\n  currentRating = 5; renderStars(currentRating);\r\n}\r\nfunction loadForEdit(index){\r\n  const r = reviews[index];\r\n  if(!r) return;\r\n  inpName.value = r.name || '';\r\n  inpEmail.value = r.email || '';\r\n  inpDate.value = r.date || '';\r\n  inpNote.value = r.note || '';\r\n  currentRating = r.rating || 5;\r\n  renderStars(currentRating);\r\n  \/\/ remove the old before saving new to avoid duplicates\r\n  reviews.splice(index,1);\r\n}\r\n\r\n\/* ====== Input Validation ====== *\/\r\nfunction validateForm(){\r\n  if(!inpName.value.trim()) return 'Nama harus diisi';\r\n  if(inpEmail.value && !\/^\\S+@\\S+\\.\\S+$\/.test(inpEmail.value)) return 'Format email tidak valid';\r\n  if(!inpNote.value.trim()) return 'Tulis sedikit pengalamanmu';\r\n  return null;\r\n}\r\n\r\n\/* ====== Post to Server (optional) ====== *\/\r\nasync function postToServer(review){\r\n  if(!USE_POST) {\r\n    \/\/ simulate\r\n    return { ok:true, msg:'Simulated (no endpoint configured)' };\r\n  }\r\n  try{\r\n    const res = await fetch(POST_ENDPOINT, {\r\n      method:'POST',\r\n      headers:{ 'Content-Type':'application\/json' },\r\n      body: JSON.stringify(review)\r\n    });\r\n    const j = await res.json().catch(()=>({}));\r\n    return { ok: res.ok, msg: j.message || res.statusText || 'OK' };\r\n  }catch(e){\r\n    return { ok:false, msg: e.message || 'Network error' };\r\n  }\r\n}\r\n\r\n\/* ====== Button handlers ====== *\/\r\nbtnSave.addEventListener('click', async ()=>{\r\n  const v = validateForm();\r\n  if(v){ alert(v); return; }\r\n  const review = {\r\n    id: Date.now(),\r\n    rating: currentRating,\r\n    name: inpName.value.trim(),\r\n    email: inpEmail.value.trim(),\r\n    date: inpDate.value || '',\r\n    note: inpNote.value.trim(),\r\n    createdAt: new Date().toISOString()\r\n  };\r\n  reviews.push(review);\r\n  saveAndRender();\r\n  clearForm();\r\n  showMsg('Review tersimpan lokal.');\r\n});\r\n\r\nbtnPost.addEventListener('click', async ()=>{\r\n  const v = validateForm();\r\n  if(v){ alert(v); return; }\r\n  const review = {\r\n    id: Date.now(),\r\n    rating: currentRating,\r\n    name: inpName.value.trim(),\r\n    email: inpEmail.value.trim(),\r\n    date: inpDate.value || '',\r\n    note: inpNote.value.trim(),\r\n    createdAt: new Date().toISOString()\r\n  };\r\n  \/\/ Optimistically add to UI\r\n  reviews.push(review); saveAndRender(); clearForm();\r\n  showMsg('Mengirim ke server...');\r\n  const result = await postToServer(review);\r\n  \/\/ attach server response to last review\r\n  reviews[reviews.length-1].serverResponse = result.ok ? 'Posted: '+result.msg : 'Failed: '+result.msg;\r\n  saveAndRender();\r\n  showMsg(result.ok ? 'Berhasil dipost.' : 'Gagal kirim: '+result.msg, !result.ok);\r\n});\r\n\r\nbtnClear.addEventListener('click', ()=>{\r\n  if(!confirm('Hapus semua review lokal?')) return;\r\n  reviews = [];\r\n  saveAndRender();\r\n  showMsg('Semua review dihapus.');\r\n});\r\n\r\n\/* ====== Export \/ Import JSON ====== *\/\r\nbtnExport.addEventListener('click', ()=>{\r\n  const data = JSON.stringify(reviews, null, 2);\r\n  const blob = new Blob([data], {type:'application\/json'});\r\n  const a = document.createElement('a');\r\n  a.href = URL.createObjectURL(blob);\r\n  a.download = 'auliawo_reviews.json';\r\n  document.body.appendChild(a); a.click(); a.remove();\r\n});\r\n\r\nfileImport.addEventListener('change', (ev)=>{\r\n  const f = ev.target.files[0];\r\n  if(!f) return;\r\n  const reader = new FileReader();\r\n  reader.onload = e => {\r\n    try {\r\n      const imported = JSON.parse(e.target.result);\r\n      if(!Array.isArray(imported)) throw new Error('Format JSON tidak valid');\r\n      \/\/ merge gracefully\r\n      reviews = reviews.concat(imported);\r\n      \/\/ dedupe by id\r\n      const map = new Map();\r\n      reviews.forEach(r => map.set(r.id, r));\r\n      reviews = Array.from(map.values());\r\n      saveAndRender();\r\n      showMsg('Import berhasil.');\r\n    }catch(err){\r\n      alert('Import gagal: '+err.message);\r\n    }\r\n  };\r\n  reader.readAsText(f);\r\n});\r\n\r\n\/* ====== Small helpers ====== *\/\r\nfunction showMsg(text, isError=false){\r\n  msgEl.style.display = 'block';\r\n  msgEl.style.color = isError ? '#b02b2b' : 'var(--accent2)';\r\n  msgEl.textContent = text;\r\n  setTimeout(()=> msgEl.style.display='none', 3000);\r\n}\r\nfunction escapeHtml(s){\r\n  if(!s) return '';\r\n  return s.replace(\/[&<>\"']\/g, c=>({ '&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;' })[c]);\r\n}\r\n\r\n\/* ====== Init ====== *\/\r\nsaveAndRender();\r\n<\/script>\r\n<\/body>\r\n<\/html>\r\n\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":"<p>AuliaWO \u2014 Reviews Bagikan Pengalaman Anda \u2014 AuliaWO Tulis review jujur, simpan lokal, atau kirim langsung ke server kami (opsional). Review Pelanggan Export JSON Import Menyimpan ke localStorage. Untuk produksi, hubungkan dengan endpoint server. Tulis Review Rating \u2605 \u2605 \u2605 \u2605 \u2605 Nama Email Tanggal Acara Catatan Simpan Lokal Post ke Server Hapus Semuanya Jika [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_glsr_average":0,"_glsr_ranking":0,"_glsr_reviews":0,"footnotes":""},"class_list":["post-12","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/auliawo.art\/index.php?rest_route=\/wp\/v2\/pages\/12","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/auliawo.art\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/auliawo.art\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/auliawo.art\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/auliawo.art\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=12"}],"version-history":[{"count":28,"href":"https:\/\/auliawo.art\/index.php?rest_route=\/wp\/v2\/pages\/12\/revisions"}],"predecessor-version":[{"id":349,"href":"https:\/\/auliawo.art\/index.php?rest_route=\/wp\/v2\/pages\/12\/revisions\/349"}],"wp:attachment":[{"href":"https:\/\/auliawo.art\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=12"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}