news-cms-module 0.1.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,312 @@
1
+ <!DOCTYPE html>
2
+ <html lang="id">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Tambah Berita - DeNews</title>
8
+ <link rel="stylesheet" href="/berita/style.css" />
9
+ </head>
10
+
11
+ <body>
12
+ <!-- Sidebar -->
13
+ <nav class="sidebar">
14
+ <div class="logo">
15
+ <a href="./dashboard" style="display: flex; justify-content: center;"><img src="/berita/img/logo.png"
16
+ alt="Logo DeNews" /></a>
17
+ </div>
18
+
19
+ <ul class="nav-menu">
20
+ <li class="nav-item">
21
+ <a href="./dashboard" class="nav-link">
22
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
23
+ <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
24
+ <polyline points="9 22 9 12 15 12 15 22"></polyline>
25
+ </svg>
26
+ Dashboard
27
+ </a>
28
+ </li>
29
+ <li class="nav-item">
30
+ <a href="management.html" class="nav-link active">
31
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
32
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
33
+ <line x1="3" y1="9" x2="21" y2="9"></line>
34
+ <line x1="9" y1="21" x2="9" y2="9"></line>
35
+ </svg>
36
+ Manajemen Berita
37
+ </a>
38
+ </li>
39
+ </ul>
40
+
41
+
42
+ </nav>
43
+
44
+ <!-- Main Content -->
45
+ <main class="main-content">
46
+ <a href="/berita/cms-admin/list" class="back-link">
47
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
48
+ <polyline points="15 18 9 12 15 6"></polyline>
49
+ </svg>
50
+ Kembali
51
+ </a>
52
+
53
+ <div class="form-card" style="max-width: 100%;">
54
+ <form id="newsForm">
55
+ <h3>Detail Berita</h3>
56
+ <br />
57
+
58
+ <div class="form-group">
59
+ <label class="form-label">Judul <span class="text-red">*</span></label>
60
+ <input type="text" class="form-control" id="newsTitle" value="" required />
61
+ </div>
62
+
63
+ <div class="form-group">
64
+ <label class="form-label">Nama Author <span class="text-red">*</span></label>
65
+ <input type="text" class="form-control" id="newsAuthor" value="" required />
66
+ </div>
67
+
68
+ <div class="form-group">
69
+ <label class="form-label">Kategori <span class="text-red">*</span></label>
70
+ <select class="form-control" id="newsCategory" required>
71
+ <option value="">Pilih Kategori</option>
72
+ <option value="Ekonomi" selected>Ekonomi</option>
73
+ <option value="Hiburan">Hiburan</option>
74
+ <option value="Olahraga">Olahraga</option>
75
+ <option value="Nasional">Nasional</option>
76
+ <option value="Teknologi">Teknologi</option>
77
+ <option value="Game">Game</option>
78
+ <option value="Kesehatan">Kesehatan</option>
79
+ </select>
80
+ </div>
81
+
82
+ <div class="form-group">
83
+ <label class="form-label">Gambar Thumbnail <span class="text-red">*</span></label>
84
+ <div class="input-group">
85
+ <input type="file" class="form-control" id="newsThumbnail" accept="image/*" required />
86
+ </div>
87
+ </div>
88
+
89
+ <h4>Konten Berita</h4>
90
+ <div id="contentContainer">
91
+ <!-- Dynamic Content blocks will be appended here -->
92
+ </div>
93
+
94
+ <!-- Add Content Trigger -->
95
+ <div id="addContentTrigger" class="dashed-area" onclick="toggleOptions(true)">
96
+ + Tambah Detail Berita
97
+ </div>
98
+
99
+ <!-- Content Type Options -->
100
+ <div id="contentTypeOptions" class="content-type-selector" style="display: none;">
101
+ <button type="button" class="btn btn-primary" onclick="addBlock('paragraph')">📝 Paragraf</button>
102
+ <button type="button" class="btn btn-primary" onclick="addBlock('video')">🎥 Video</button>
103
+ <button type="button" class="btn btn-primary" onclick="addBlock('image')">🖼️ Gambar</button>
104
+ <button type="button" class="btn btn-primary" onclick="addBlock('subheading')">H2 Sub Heading</button>
105
+ <button type="button" class="btn btn-outline text-red" onclick="toggleOptions(false)">❌ Batal</button>
106
+ </div>
107
+
108
+ <div class="form-actions">
109
+ <a href="management.html" class="btn btn-outline"
110
+ style="border-color: #007bff; color: #333; text-align: center; text-decoration: none;">
111
+ Batal
112
+ </a>
113
+ <button type="submit" class="btn btn-primary">Simpan</button>
114
+ </div>
115
+ </form>
116
+ </div>
117
+ </main>
118
+
119
+ <script>
120
+ const contentContainer = document.getElementById('contentContainer');
121
+ const addTrigger = document.getElementById('addContentTrigger');
122
+ const optionsDiv = document.getElementById('contentTypeOptions');
123
+
124
+ function toggleOptions(show) {
125
+ if (show) {
126
+ window.scrollTo(0, document.body.scrollHeight);
127
+ addTrigger.style.display = 'none';
128
+ optionsDiv.style.display = 'flex';
129
+ } else {
130
+ addTrigger.style.display = 'block';
131
+ optionsDiv.style.display = 'none';
132
+ }
133
+ }
134
+
135
+ function createRemoveBtn(elementToRemove) {
136
+ const btn = document.createElement('button');
137
+ btn.className = 'remove-block-btn';
138
+ btn.innerHTML = '🗑️';
139
+ btn.type = 'button';
140
+ btn.onclick = () => elementToRemove.remove();
141
+ return btn;
142
+ }
143
+
144
+ function addBlock(type) {
145
+ const blockDiv = document.createElement('div');
146
+ blockDiv.className = 'content-block';
147
+ blockDiv.dataset.type = type;
148
+
149
+ blockDiv.appendChild(createRemoveBtn(blockDiv));
150
+ const label = document.createElement('label');
151
+ label.className = 'form-label';
152
+
153
+ // Labeling
154
+ const labels = {
155
+ 'paragraph': '📝 Paragraf',
156
+ 'video': '🎥 Video (URL)',
157
+ 'image': '🖼️ Gambar Konten',
158
+ 'subheading': 'H2 Sub Heading'
159
+ };
160
+ label.textContent = labels[type];
161
+
162
+ let inputContainer = document.createElement('div');
163
+
164
+ if (type === 'paragraph') {
165
+ const input = document.createElement('textarea');
166
+ input.className = 'form-control';
167
+ input.name = 'contentValue';
168
+ input.rows = 4;
169
+ input.placeholder = 'Tulis paragraf disini...';
170
+ inputContainer.appendChild(input);
171
+
172
+ } else if (type === 'video') {
173
+ const urlInput = document.createElement('input');
174
+ urlInput.type = 'text';
175
+ urlInput.className = 'form-control mb-2';
176
+ urlInput.name = 'contentValue';
177
+ urlInput.placeholder = 'Masukkan URL Video YouTube...';
178
+
179
+ const capInput = document.createElement('input');
180
+ capInput.type = 'text';
181
+ capInput.className = 'form-control';
182
+ capInput.name = 'contentCaption';
183
+ capInput.placeholder = 'Keterangan video (opsional)...';
184
+
185
+ inputContainer.appendChild(urlInput);
186
+ inputContainer.appendChild(capInput);
187
+
188
+ } else if (type === 'image') {
189
+ const fileInput = document.createElement('input');
190
+ fileInput.type = 'file';
191
+ fileInput.className = 'form-control mb-2';
192
+ fileInput.name = 'contentFile'; // Hanya sementara di FE
193
+ fileInput.accept = 'image/*';
194
+
195
+ const capInput = document.createElement('input');
196
+ capInput.type = 'text';
197
+ capInput.className = 'form-control';
198
+ capInput.name = 'contentCaption';
199
+ capInput.placeholder = 'Keterangan gambar (opsional)...';
200
+
201
+ inputContainer.appendChild(fileInput);
202
+ inputContainer.appendChild(capInput);
203
+
204
+ } else if (type === 'subheading') {
205
+ const input = document.createElement('input');
206
+ input.type = 'text';
207
+ input.className = 'form-control';
208
+ input.name = 'contentValue';
209
+ input.placeholder = 'Judul Sub-heading...';
210
+ inputContainer.appendChild(input);
211
+ }
212
+
213
+ blockDiv.appendChild(label);
214
+ blockDiv.appendChild(inputContainer);
215
+ contentContainer.appendChild(blockDiv);
216
+ toggleOptions(false);
217
+ }
218
+
219
+ // Handler Submit Form
220
+ document.getElementById('newsForm').addEventListener('submit', async function (e) {
221
+ e.preventDefault();
222
+
223
+ console.log("step 1");
224
+
225
+ const formData = new FormData();
226
+
227
+ // 1. Ambil data utama sesuai kunci di Controller BE
228
+ const title = document.getElementById('newsTitle').value;
229
+ const authorName = document.getElementById('newsAuthor').value;
230
+ const category = document.getElementById('newsCategory').value;
231
+ const status = 'PUBLISHED'; // Atau sesuaikan jika ada inputnya
232
+ const thumbnailFile = document.getElementById('newsThumbnail').files[0];
233
+
234
+ console.log("step 2");
235
+
236
+ formData.append('title', title);
237
+ formData.append('authorName', authorName);
238
+ formData.append('category', category);
239
+ formData.append('status', status);
240
+
241
+ console.table(title, authorName, category, status);
242
+
243
+ console.log("step 3");
244
+
245
+ if (thumbnailFile) {
246
+ formData.append('thumbnailImage', thumbnailFile); // Sesuai BE: req.files['thumbnailImage']
247
+ }
248
+
249
+ console.log("step 4");
250
+
251
+ // 2. Olah Content Blocks
252
+ const blocks = contentContainer.querySelectorAll('.content-block');
253
+ const contentBlocksData = [];
254
+
255
+ console.log("step 5");
256
+
257
+ blocks.forEach((block) => {
258
+ let type = block.dataset.type;
259
+ type = type.toUpperCase();
260
+ const item = { blockType: type };
261
+
262
+ if (type === 'PARAGRAPH' || type === 'SUBHEADING' || type === 'VIDEO') {
263
+ item.contentValue = block.querySelector('[name="contentValue"]').value;
264
+ console.log(item.value);
265
+
266
+ if (type === 'VIDEO') {
267
+ item.caption = block.querySelector('[name="contentCaption"]').value;
268
+ console.log(item.caption);
269
+
270
+ }
271
+ } else if (type === 'IMAGE') {
272
+ const fileInput = block.querySelector('[name="contentFile"]');
273
+ if (fileInput.files.length > 0) {
274
+ // Masukkan file ke key 'contentImages' sesuai BE
275
+ formData.append('contentImages', fileInput.files[0]);
276
+ item.value = fileInput.files[0].name; // Referensi nama file
277
+ }
278
+ item.caption = block.querySelector('[name="contentCaption"]').value;
279
+ }
280
+ contentBlocksData.push(item);
281
+ });
282
+
283
+ console.log(contentBlocksData);
284
+
285
+
286
+ // Kirim metadata blok sebagai string JSON (Standar multipart/form-data untuk array of objects)
287
+ formData.append('contentBlocks', JSON.stringify(contentBlocksData));
288
+ try {
289
+ const response = await fetch('<%= apiBaseUrl %>', {
290
+ method: 'POST',
291
+ body: formData
292
+ // Jangan set Header Content-Type, browser akan otomatis mengurusnya untuk FormData
293
+ });
294
+
295
+ const result = await response.json();
296
+
297
+
298
+ if (response.ok && result.success) {
299
+ alert('Berita berhasil disimpan!');
300
+ window.location.href = '<%= nextUrl %>';
301
+ } else {
302
+ alert('Gagal membuat berita: ' + (result.error || 'Terjadi kesalahan server'));
303
+ }
304
+ } catch (error) {
305
+ console.error('Error saat submit:', error);
306
+ alert('Terjadi kesalahan koneksi ke server. ' + error.message);
307
+ }
308
+ });
309
+ </script>
310
+ </body>
311
+
312
+ </html>
@@ -0,0 +1,104 @@
1
+ <!DOCTYPE html>
2
+ <html lang="id">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Dashboard - DeNews</title>
8
+ <link rel="stylesheet" href="/berita/style.css" />
9
+ </head>
10
+
11
+ <body>
12
+ <!-- Sidebar -->
13
+ <nav class="sidebar">
14
+ <div class="logo">
15
+ <a href="./dashboard" style="display: flex; justify-content: center;">
16
+ <img src="/berita/img/logo.png" alt="Logo DeNews" />
17
+ </a>
18
+ </div>
19
+
20
+ <ul class="nav-menu">
21
+ <li class="nav-item">
22
+ <a href="./dashboard" class="nav-link active">
23
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
24
+ <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
25
+ <polyline points="9 22 9 12 15 12 15 22"></polyline>
26
+ </svg>
27
+ Dashboard
28
+ </a>
29
+ </li>
30
+ <li class="nav-item">
31
+ <a href="./list" class="nav-link">
32
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
33
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
34
+ <line x1="3" y1="9" x2="21" y2="9"></line>
35
+ <line x1="9" y1="21" x2="9" y2="9"></line>
36
+ </svg>
37
+ Manajemen Berita
38
+ </a>
39
+ </li>
40
+ </ul>
41
+ </nav>
42
+
43
+ <!-- Main Content -->
44
+ <main class="main-content">
45
+ <!-- Statistik Atas -->
46
+ <div class="stats-grid">
47
+ <div class="card">
48
+ <div class="stat-number">
49
+ <%= data.newsCount %>
50
+ </div>
51
+ <div class="stat-label text-blue">Jumlah Berita</div>
52
+ </div>
53
+
54
+ <div class="card">
55
+ <div class="stat-number">
56
+ <%= data.totalYearlyVisitors %>
57
+ </div>
58
+ <div class="stat-label text-green">Total Pengunjung</div>
59
+ </div>
60
+
61
+ <div class="card">
62
+ <div class="stat-number">
63
+ <%= data.categoryCount %>
64
+ </div>
65
+ <div class="stat-label text-orange">Kategori</div>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- Grafik -->
70
+ <section class="chart-container">
71
+ <div class="chart-header">
72
+ <h2 class="chart-title">Statistik Pengunjung</h2>
73
+ <select class="year-select">
74
+ <option>
75
+ <%= new Date().getFullYear() %>
76
+ </option>
77
+ </select>
78
+ </div>
79
+
80
+ <div class="bar-chart">
81
+ <% const months=["Jan", "Feb" , "Mar" , "Apr" , "Mei" , "Jun" , "Jul" , "Agt" , "Spt" , "Okt" , "Nov"
82
+ , "Des" ]; const maxValue=Math.max(...data.monthlyVisitors.map(v=> v.total), 1);
83
+ %>
84
+
85
+ <% months.forEach((monthName, index)=> {
86
+ const monthData = data.monthlyVisitors.find(v => v.month === index + 1);
87
+ const total = monthData ? monthData.total : 0;
88
+ const heightPercent = (total / maxValue) * 100;
89
+ %>
90
+ <div class="bar-wrapper">
91
+ <div class="bar" data-height="<%= Math.round(heightPercent) %>"
92
+ style="height: <%= Math.round(heightPercent) %>%">
93
+ </div>
94
+ <span class="bar-label">
95
+ <%= monthName %>
96
+ </span>
97
+ </div>
98
+ <% }) %>
99
+ </div>
100
+ </section>
101
+ </main>
102
+ </body>
103
+
104
+ </html>
@@ -0,0 +1,202 @@
1
+ <!DOCTYPE html>
2
+ <html lang="id">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Detail Berita (Admin) - <%= news.title %>
8
+ </title>
9
+ <link rel="stylesheet" href="/berita/style.css" />
10
+ </head>
11
+
12
+ <body>
13
+ <!-- Sidebar -->
14
+ <nav class="sidebar">
15
+ <div class="logo">
16
+ <a href="./dashboard" style="display: flex; justify-content: center;">
17
+ <img src="/berita/img/logo.png" alt="Logo DeNews" />
18
+ </a>
19
+ </div>
20
+
21
+ <ul class="nav-menu">
22
+ <li class="nav-item">
23
+ <a href="./dashboard" class="nav-link">
24
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
25
+ <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
26
+ <polyline points="9 22 9 12 15 12 15 22"></polyline>
27
+ </svg>
28
+ Dashboard
29
+ </a>
30
+ </li>
31
+ <li class="nav-item">
32
+ <a href="./list" class="nav-link">
33
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
34
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
35
+ <line x1="3" y1="9" x2="21" y2="9"></line>
36
+ <line x1="9" y1="21" x2="9" y2="9"></line>
37
+ </svg>
38
+ Manajemen Berita
39
+ </a>
40
+ </li>
41
+ </ul>
42
+ </nav>
43
+
44
+ <main class="main-content">
45
+ <a href="./list" class="back-link"
46
+ style="text-decoration: none; color: inherit; display: inline-flex; align-items: center; gap: 8px; margin-bottom: 20px;">
47
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
48
+ <line x1="19" y1="12" x2="5" y2="12"></line>
49
+ <polyline points="12 19 5 12 12 5"></polyline>
50
+ </svg>
51
+ Kembali
52
+ </a>
53
+
54
+ <div style="max-width: 900px; margin: 0 auto; padding-bottom: 50px;">
55
+ <article class="main-article-content">
56
+ <!-- Hero Image -->
57
+ <% if (news.imagePath) { %>
58
+ <img src="<%= news.imagePath.replace('public/', '/berita/') %>" alt="<%= news.title %>"
59
+ class="detail-image" style="margin-top: 0; max-height: 400px; object-fit: cover; width: 100%;">
60
+ <% } %>
61
+
62
+ <header class="detail-header">
63
+ <h1 class="detail-title" style="font-size: 32px; margin: 20px 0;">
64
+ <%= news.title %>
65
+ </h1>
66
+ <div class="detail-meta"
67
+ style="display: flex; align-items: center; flex-wrap: wrap; gap: 15px;">
68
+ <span style="display: flex; align-items: center; gap: 5px">
69
+ Oleh: <%= news.authorName %>
70
+ </span>
71
+ <span>|</span>
72
+ <span>
73
+ <%= new Date(news.createdAt).toLocaleDateString('id-ID', { day: 'numeric' ,
74
+ month: 'long' , year: 'numeric' }) %>
75
+ </span>
76
+ <span>|</span>
77
+ <span>Kategori: <%= news.category %></span>
78
+ <span>|</span>
79
+
80
+ <div class="dropdown">
81
+ <button class="badge badge-<%= news.status.toLowerCase() %> dropdown-toggle"
82
+ type="button" onclick="toggleDropdown(this)">
83
+ <%= news.status %>
84
+ </button>
85
+ <div class="dropdown-menu">
86
+ <div class="dropdown-item"
87
+ onclick="updateNewsStatus('<%= news.id %>', 'PUBLISHED')">
88
+ <span class="status-dot published"></span> Publish
89
+ </div>
90
+ <div class="dropdown-item"
91
+ onclick="updateNewsStatus('<%= news.id %>', 'DRAFT')">
92
+ <span class="status-dot draft"></span> Draft
93
+ </div>
94
+ <div class="dropdown-item"
95
+ onclick="updateNewsStatus('<%= news.id %>', 'ARCHIVED')">
96
+ <span class="status-dot archived"></span> Archived
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </header>
102
+
103
+ <div class="detail-content">
104
+ <% if (news.contentBlocks && news.contentBlocks.length> 0) { %>
105
+ <% news.contentBlocks.forEach(function(block) { %>
106
+ <% if (block.blockType==='PARAGRAPH' ) { %>
107
+ <p>
108
+ <%= block.contentValue %>
109
+ </p>
110
+ <% } else if (block.blockType==='SUBHEADING' ) { %>
111
+ <h2
112
+ style="font-size: 24px; font-weight: bold; margin-top: 30px; margin-bottom: 15px;">
113
+ <%= block.contentValue %>
114
+ </h2>
115
+ <% } else if (block.blockType==='IMAGE' ) { %>
116
+ <figure style="margin: 30px 0;">
117
+ <img src="<%= block.contentValue.replace('public/', '/berita/') %>"
118
+ class="detail-image"
119
+ alt="<%= block.caption || 'Gambar Berita' %>"
120
+ style="width: 100%; border-radius: 8px;">
121
+ <% if (block.caption) { %>
122
+ <figcaption class="caption"
123
+ style="text-align: center; margin-top: 10px; color: #666; font-size: 0.9em;">
124
+ <%= block.caption %>
125
+ </figcaption>
126
+ <% } %>
127
+ </figure>
128
+ <% } else if (block.blockType==='VIDEO' ) { %>
129
+ <div class="video-container" style="margin: 30px 0;">
130
+ <iframe width="100%" height="400"
131
+ src="<%= block.contentValue %>" frameborder="0"
132
+ allowfullscreen style="border-radius: 8px;"></iframe>
133
+ <% if (block.caption) { %>
134
+ <p class="caption"
135
+ style="text-align: center; margin-top: 10px; color: #666; font-size: 0.9em;">
136
+ <%= block.caption %>
137
+ </p>
138
+ <% } %>
139
+ </div>
140
+ <% } %>
141
+ <% }); %>
142
+ <% } else { %>
143
+ <p>Konten berita tidak tersedia.</p>
144
+ <% } %>
145
+ </div>
146
+ </article>
147
+ </div>
148
+ </main>
149
+
150
+ <script>
151
+ // Close dropdowns when clicking outside
152
+ window.onclick = function (event) {
153
+ if (!event.target.closest('.dropdown')) {
154
+ var dropdowns = document.getElementsByClassName("dropdown-menu");
155
+ for (var i = 0; i < dropdowns.length; i++) {
156
+ var openDropdown = dropdowns[i];
157
+ if (openDropdown.classList.contains('show')) {
158
+ openDropdown.classList.remove('show');
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ function toggleDropdown(button) {
165
+ // Close all other dropdowns first
166
+ var dropdowns = document.getElementsByClassName("dropdown-menu");
167
+ for (var i = 0; i < dropdowns.length; i++) {
168
+ if (dropdowns[i] !== button.nextElementSibling) {
169
+ dropdowns[i].classList.remove('show');
170
+ }
171
+ }
172
+ // Toggle the clicked one
173
+ button.nextElementSibling.classList.toggle("show");
174
+ }
175
+
176
+ async function updateNewsStatus(id, newStatus) {
177
+ if (!confirm(`Ubah status menjadi ${newStatus}?`)) return;
178
+
179
+ try {
180
+ // Path relative to the current URL (/berita/admin/:slug)
181
+ // Need to go up one level to reach /berita/admin/update/:id
182
+ const response = await fetch(`./update/${id}`, {
183
+ method: 'PATCH',
184
+ headers: { 'Content-Type': 'application/json' },
185
+ body: JSON.stringify({ status: newStatus })
186
+ });
187
+
188
+ const result = await response.json();
189
+ if (result.success) {
190
+ location.reload();
191
+ } else {
192
+ alert('Gagal memperbarui status: ' + result.error);
193
+ }
194
+ } catch (error) {
195
+ console.error(error);
196
+ alert('Terjadi kesalahan saat memperbarui status.');
197
+ }
198
+ }
199
+ </script>
200
+ </body>
201
+
202
+ </html>