From f3d6b18d53c6ba7f5e71f5b6e9581d3282009b7c Mon Sep 17 00:00:00 2001 From: Habibie Date: Mon, 24 Aug 2026 20:24:08 +0700 Subject: [PATCH 1/2] artikel openkab frontend, gabung dengan artikel opensid --- .../Controllers/CMS/ArticleController.php | 7 + .../Controllers/Web/ArtikelController.php | 84 +++++++---- app/Services/ArtikelService.php | 136 +++++++++++++++--- resources/views/web/article.blade.php | 55 ++++++- resources/views/web/artikel/index.blade.php | 17 ++- .../web/partials/artikel_terbaru.blade.php | 35 ++--- routes/web.php | 1 + tests/Feature/ArtikelWebTest.php | 77 +++++++++- 8 files changed, 329 insertions(+), 83 deletions(-) diff --git a/app/Http/Controllers/CMS/ArticleController.php b/app/Http/Controllers/CMS/ArticleController.php index 994e44e1d..870936795 100644 --- a/app/Http/Controllers/CMS/ArticleController.php +++ b/app/Http/Controllers/CMS/ArticleController.php @@ -63,6 +63,8 @@ public function store(CreateArticleRequest $request) } $this->articleRepository->create($input); + (new \App\Services\ArtikelService)->clearAllCache(); + Session::flash('success', 'Artikel berhasil disimpan.'); return redirect(route('articles.index')); @@ -135,6 +137,8 @@ public function update($id, UpdateArticleRequest $request) } $article = $this->articleRepository->update($input, $id); + (new \App\Services\ArtikelService)->clearAllCache(); + Session::flash('success', 'Artikel berhasil diupdate.'); return redirect(route('articles.index')); @@ -159,6 +163,9 @@ public function destroy($id) $this->authorize('delete', $article); $this->articleRepository->delete($id); + + (new \App\Services\ArtikelService)->clearAllCache(); + if (request()->ajax()) { return $this->sendSuccess('Artikel berhasil dihapus.'); } diff --git a/app/Http/Controllers/Web/ArtikelController.php b/app/Http/Controllers/Web/ArtikelController.php index 7f1785f19..f3e913bde 100644 --- a/app/Http/Controllers/Web/ArtikelController.php +++ b/app/Http/Controllers/Web/ArtikelController.php @@ -3,8 +3,12 @@ namespace App\Http\Controllers\Web; use App\Http\Controllers\Controller; +use App\Models\CMS\Article; use App\Services\ArtikelService; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\View\View; class ArtikelController extends Controller { @@ -16,62 +20,94 @@ public function __construct(ArtikelService $artikelService) } /** - * Tampilkan daftar artikel OpenSID. + * Tampilkan daftar artikel gabungan (OpenKab & OpenSID). * * @param Request $request - * @return \Illuminate\View\View + * @return View */ - public function index(Request $request) + public function index(Request $request): View { $search = $request->get('search', ''); $categoryId = $request->get('kategori', ''); $filters = []; if (!empty($search)) { + $filters['search'] = $search; $filters['filter[search]'] = $search; } if (!empty($categoryId)) { + $filters['kategori'] = $categoryId; $filters['filter[id_kategori]'] = $categoryId; } - // Ambil data melalui service - // Format Pagination API json format - $filters['page[number]'] = $request->get('page', 1); - $filters['page[size]'] = 6; - $filters['sort'] = '-tgl_upload'; // Terurut berdasarkan tanggal terbaru + // Ambil data gabungan melalui service + $combinedArticles = $this->artikelService->getCombinedArticles($filters); - // Caching ditangani oleh ArtikelService - $articles = $this->artikelService->artikel($filters); + // Pagination menggunakan LengthAwarePaginator + $page = max((int) $request->get('page', 1), 1); + $perPage = 6; + $offset = ($page - 1) * $perPage; + $itemsForCurrentPage = $combinedArticles->slice($offset, $perPage)->values(); - // Filter out disabled articles just in case API returns them - $articles = $articles->filter(function ($item) { - return isset($item->enabled) && $item->enabled == 1; - }); + $articles = new LengthAwarePaginator( + $itemsForCurrentPage, + $combinedArticles->count(), + $perPage, + $page, + ['path' => $request->url(), 'query' => $request->query()] + ); return view('web.artikel.index', [ 'title' => 'Artikel Berita', 'articles' => $articles, 'search' => $search, - 'categoryId' => $categoryId + 'categoryId' => $categoryId, ]); } /** - * Tampilkan detail artikel OpenSID. + * Endpoint JSON untuk widget Artikel Terbaru di Beranda. * - * @param int $id - * @return \Illuminate\View\View + * @param Request $request + * @return JsonResponse + */ + public function terbaru(Request $request): JsonResponse + { + $limit = max((int) $request->get('limit', 6), 1); + $articles = $this->artikelService->getArtikelTerbaru($limit); + + return response()->json([ + 'status' => 'success', + 'data' => $articles, + ]); + } + + /** + * Tampilkan detail artikel OpenSID (dengan fallback ke artikel CMS lokal jika ID/slug cocok). + * + * @param int|string $id + * @return View|\Illuminate\Http\RedirectResponse */ public function show($id) { - $article = $this->artikelService->artikelById($id); + $article = is_numeric($id) ? $this->artikelService->artikelById((int) $id) : null; - if (!$article || !isset($article->enabled) || $article->enabled == 0) { - abort(404, 'Artikel tidak ditemukan atau tidak aktif'); + if ($article && isset($article->enabled) && $article->enabled == 1) { + return view('web.artikel.show', [ + 'object' => $article, + ]); } - return view('web.artikel.show', [ - 'object' => $article - ]); + // Fallback: Cek apakah ID/slug merujuk pada artikel CMS OpenKab lokal + $localArticle = Article::where('id', $id) + ->orWhere('slug', $id) + ->first(); + + if ($localArticle && $localArticle->state == Article::PUBLISH && $localArticle->published_at <= now()) { + return redirect()->route('article', ['aSlug' => $localArticle->slug]); + } + + abort(404, 'Artikel tidak ditemukan atau tidak aktif'); } } + diff --git a/app/Services/ArtikelService.php b/app/Services/ArtikelService.php index f8ccc2543..0b0874f3a 100644 --- a/app/Services/ArtikelService.php +++ b/app/Services/ArtikelService.php @@ -2,8 +2,10 @@ namespace App\Services; +use App\Models\CMS\Article; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Storage; use stdClass; class ArtikelService extends BaseApiService @@ -33,7 +35,51 @@ private function registerCacheKey(string $key): void } /** - * Mendapatkan daftar artikel dengan filter opsional + * Mendapatkan daftar artikel dari CMS lokal OpenKab + * + * @param array $filters + */ + public function getLocalArticles(array $filters = []): Collection + { + $search = $filters['search'] ?? $filters['filter[search]'] ?? null; + $categoryId = $filters['kategori'] ?? $filters['category_id'] ?? $filters['filter[id_kategori]'] ?? null; + + $query = Article::with('category') + ->where('state', Article::PUBLISH) + ->where('published_at', '<=', now()); + + if (! empty($search)) { + $query->where(function ($q) use ($search) { + $q->where('title', 'like', "%{$search}%") + ->orWhere('content', 'like', "%{$search}%"); + }); + } + + if (! empty($categoryId)) { + $query->where('category_id', $categoryId); + } + + $articles = $query->orderBy('published_at', 'desc')->get(); + + return $articles->map(function (Article $article): stdClass { + return (object) [ + 'id' => $article->id, + 'slug' => $article->slug, + 'judul' => $article->title, + 'isi' => $article->content, + 'gambar' => $article->thumbnail ? Storage::url($article->thumbnail) : null, + 'id_kategori' => $article->category_id, + 'kategori_nama' => $article->category?->name ?? 'Berita OpenKab', + 'tgl_upload' => $article->published_at ? $article->published_at->format('Y-m-d H:i:s') : ($article->created_at ? $article->created_at->format('Y-m-d H:i:s') : ''), + 'enabled' => 1, + 'source' => 'openkab', + 'detail_url' => route('article', ['aSlug' => $article->slug]), + ]; + }); + } + + /** + * Mendapatkan daftar artikel dari API upstream OpenSID dengan filter opsional * * @param array $filters */ @@ -46,33 +92,72 @@ public function artikel(array $filters = []): Collection // Ambil dari cache dulu return Cache::remember($cacheKey, $this->cacheTtl, function () use ($filters): Collection { - $data = $this->apiRequest('/api/v1/artikel/list', $filters); + try { + $data = $this->apiRequest('/api/v1/artikel/list', $filters); - if (empty($data)) { + if (empty($data)) { + return collect([]); + } + + return collect($data)->map(function (array $item): stdClass { + // Return 'attributes' but with 'id' populated + $attributes = $item['attributes'] ?? []; + $attributes['id'] = $item['id'] ?? null; + $attributes['source'] = 'opensid'; + $attributes['detail_url'] = isset($attributes['id']) ? route('web.artikel.show', $attributes['id']) : '#'; + + // Fetch detail to enrich with gambar and isi if missing + if (isset($attributes['id']) && (! isset($attributes['gambar']) || ! isset($attributes['isi']))) { + $detail = $this->artikelById((int) $attributes['id']); + if ($detail !== null) { + $attributes['gambar'] = $detail->gambar ?? null; + $attributes['isi'] = $detail->isi ?? null; + } + } + + return (object) $attributes; + }); + } catch (\Throwable $e) { return collect([]); } + }); + } - return collect($data)->map(function (array $item): stdClass { - // Return 'attributes' but with 'id' populated - $attributes = $item['attributes'] ?? []; - $attributes['id'] = $item['id'] ?? null; - - // Fetch detail to enrich with gambar and isi if missing - if (isset($attributes['id']) && (! isset($attributes['gambar']) || ! isset($attributes['isi']))) { - $detail = $this->artikelById((int) $attributes['id']); - if ($detail !== null) { - $attributes['gambar'] = $detail->gambar ?? null; - $attributes['isi'] = $detail->isi ?? null; - } - } + /** + * Mendapatkan gabungan artikel OpenKab lokal dan artikel OpenSID API, terurut berdasarkan tanggal terbaru + * + * @param array $filters + */ + public function getCombinedArticles(array $filters = []): Collection + { + $cacheKey = $this->buildCacheKey('artikel_combined', $filters); + $this->registerCacheKey($cacheKey); - return (object) $attributes; + return Cache::remember($cacheKey, $this->cacheTtl, function () use ($filters): Collection { + $localArticles = $this->getLocalArticles($filters); + $apiArticles = $this->artikel($filters); + + // Filter out disabled articles just in case + $apiArticles = $apiArticles->filter(function ($item) { + return isset($item->enabled) && $item->enabled == 1; }); + + return $localArticles->concat($apiArticles)->sortByDesc(function ($item) { + return $item->tgl_upload ?? '1970-01-01 00:00:00'; + })->values(); }); } /** - * Mendapatkan detail artikel berdasarkan ID + * Mendapatkan daftar artikel terbaru gabungan untuk widget beranda + */ + public function getArtikelTerbaru(int $limit = 6): Collection + { + return $this->getCombinedArticles(['page[size]' => $limit])->take($limit)->values(); + } + + /** + * Mendapatkan detail artikel berdasarkan ID (dari API) */ public function artikelById(int $id): ?stdClass { @@ -82,12 +167,16 @@ public function artikelById(int $id): ?stdClass $this->registerCacheKey($cacheKey); return Cache::remember($cacheKey, $this->cacheTtl, function () use ($id): ?stdClass { - $data = $this->apiRequest('/api/v1/artikel/tampil', [ - 'id' => $id, - ]); + try { + $data = $this->apiRequest('/api/v1/artikel/tampil', [ + 'id' => $id, + ]); - if (is_array($data) && count($data) > 0) { - return (object) $data; + if (is_array($data) && count($data) > 0) { + return (object) $data; + } + } catch (\Throwable $e) { + return null; } return null; @@ -141,3 +230,4 @@ public function clearAllCache(): void Cache::forget($this->cacheRegistryKey); } } + diff --git a/resources/views/web/article.blade.php b/resources/views/web/article.blade.php index db46b0554..18ee1378e 100644 --- a/resources/views/web/article.blade.php +++ b/resources/views/web/article.blade.php @@ -9,20 +9,65 @@ + -
-
- {!! clean($object->content) !!} +
+
+
+
+
+

{{ $object->title ?? '' }}

+
+ {{ $object->category?->name ?? 'Berita' }} + {{ $object->published_at ? \Carbon\Carbon::parse($object->published_at)->translatedFormat('d F Y') : '' }} +
+
+ + @if (!empty($object->thumbnail)) + {{ $object->title ?? '' }} + @endif + +
+
+ {!! clean($object->content ?? '') !!} +
+
+ + +
+
@endsection + +@push('styles') + +@endpush + diff --git a/resources/views/web/artikel/index.blade.php b/resources/views/web/artikel/index.blade.php index ed9e759cf..86cbf709c 100644 --- a/resources/views/web/artikel/index.blade.php +++ b/resources/views/web/artikel/index.blade.php @@ -54,6 +54,9 @@
{{ $article->judul ?? '' }}
+ @if (isset($article->source) && $article->source === 'openkab') + OpenKab + @endif {{ $article->kategori_nama ?? 'Kategori' }}
@@ -61,13 +64,11 @@
- @if (isset($article->id)) - Selengkapnya - @endif
- {{ isset($article->tgl_upload) ? \Carbon\Carbon::parse($article->tgl_upload)->translatedFormat('d F Y') : '' }} + {{ isset($article->tgl_upload) && !empty($article->tgl_upload) ? \Carbon\Carbon::parse($article->tgl_upload)->translatedFormat('d F Y') : '' }}
@@ -82,9 +83,11 @@ class="text-decoration-none btn btn-sm btn-outline-primary">Selengkapnya @endforelse
- + @if ($articles->hasPages()) +
+ {{ $articles->links('pagination::bootstrap-5') }} +
+ @endif diff --git a/resources/views/web/partials/artikel_terbaru.blade.php b/resources/views/web/partials/artikel_terbaru.blade.php index c02b8f8ba..35f5fcd7a 100644 --- a/resources/views/web/partials/artikel_terbaru.blade.php +++ b/resources/views/web/partials/artikel_terbaru.blade.php @@ -44,50 +44,45 @@ document.addEventListener("DOMContentLoaded", function (event) { "use strict"; - const urlArtikel = new URL("{{ config('app.databaseGabunganUrl') . '/api/v1/artikel-public/list' }}"); - - const routeDetailBase = "{{ url('artikel-opensid') }}"; + const urlArtikel = "{{ route('web.artikel.terbaru') }}"; $.ajax({ - url: urlArtikel.href, - method: 'GET', + url: urlArtikel, + method: 'GET', dataType: 'json', - data: { - "page[number]": 1, - "page[size]": 6, - "sort": "-tgl_upload", - "filter[enabled]": "1" // Only get active articles + limit: 6 }, success: function (result) { if (result.data && result.data.length > 0) { let htmlContent = ''; result.data.forEach((item) => { - let attr = item.attributes || {}; // Use dummy image if none provided - let imgSrc = attr.gambar ? attr.gambar : `data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22100%25%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20100%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_18e19c362b5%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A11pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_18e19c362b5%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2250%25%22%20y%3D%2250%25%22%20text-anchor%3D%22middle%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E`; + let imgSrc = item.gambar ? item.gambar : `data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22100%25%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20100%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_18e19c362b5%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A11pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_18e19c362b5%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2250%25%22%20y%3D%2250%25%22%20text-anchor%3D%22middle%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E`; // Strip HTML tags from isi let tmp = document.createElement("DIV"); - tmp.innerHTML = attr.isi || ''; + tmp.innerHTML = item.isi || ''; let textLength = 100; let plainText = tmp.textContent || tmp.innerText || ""; let isiExcerpt = plainText.length > textLength ? plainText.substring(0, textLength) + "..." : plainText; // Format date simple - let dateStr = attr.tgl_upload ? attr.tgl_upload.split(' ')[0] : ''; - - let detailUrl = `${routeDetailBase}/${item.id}`; + let dateStr = item.tgl_upload ? item.tgl_upload.split(' ')[0] : ''; + let detailUrl = item.detail_url || '#'; + let kategori = item.kategori_nama || 'Kategori'; + let badgeSource = item.source === 'openkab' ? 'OpenKab' : ''; htmlContent += `
- ${attr.judul || ''} + ${item.judul || ''}
-
${attr.judul || ''}
+
${item.judul || ''}
- ${attr.kategori_nama || 'Kategori'} + ${badgeSource} + ${kategori}
${isiExcerpt} @@ -114,7 +109,7 @@ error: function () { $('.replace-content-artikel').html(`
- Gagal memuat artikel jaringan atau server bermasalah + Gagal memuat artikel terbaru
`); } diff --git a/routes/web.php b/routes/web.php index aa5d0c5a2..0c60c0c59 100644 --- a/routes/web.php +++ b/routes/web.php @@ -445,6 +445,7 @@ Route::middleware(['website.enable', 'log.visitor'])->group(function () { Route::get('/', [PageController::class, 'getIndex'])->name('web.index'); + Route::get('artikel/terbaru', [ArtikelController::class, 'terbaru'])->name('web.artikel.terbaru'); Route::get('artikel-opensid', [ArtikelController::class, 'index'])->name('web.artikel.index'); Route::get('artikel-opensid/{id}', [ArtikelController::class, 'show'])->name('web.artikel.show'); Route::get('a/{aSlug}', [PageController::class, 'getArticle'])->name('article'); diff --git a/tests/Feature/ArtikelWebTest.php b/tests/Feature/ArtikelWebTest.php index 824eb172c..16abb93e4 100644 --- a/tests/Feature/ArtikelWebTest.php +++ b/tests/Feature/ArtikelWebTest.php @@ -2,6 +2,8 @@ namespace Tests\Feature; +use App\Models\CMS\Article; +use App\Models\CMS\Category; use App\Services\ArtikelService; use Mockery; use PHPUnit\Framework\Attributes\Test; @@ -22,7 +24,7 @@ public function it_can_access_public_artikel_index() // Mock the ArtikelService $mockService = Mockery::mock(ArtikelService::class); - $mockService->shouldReceive('artikel')->andReturn(collect([ + $mockService->shouldReceive('getCombinedArticles')->andReturn(collect([ (object) [ 'id' => 1, 'judul' => 'Test Artikel OpenSID', @@ -31,13 +33,14 @@ public function it_can_access_public_artikel_index() 'kategori_nama' => 'Berita Desa', 'tgl_upload' => '2023-10-01 10:00:00', 'enabled' => 1, + 'source' => 'opensid', + 'detail_url' => route('web.artikel.show', 1), ] ])); $this->app->instance(ArtikelService::class, $mockService); $response = $this->get(route('web.artikel.index')); - $response->dump(); $response->assertStatus(200); $response->assertViewIs('web.artikel.index'); $response->assertSee('Artikel Berita'); @@ -45,6 +48,49 @@ public function it_can_access_public_artikel_index() $response->assertSee('Berita Desa'); } + #[Test] + public function it_displays_openkab_cms_articles_in_public_index() + { + $this->withoutMiddleware([\App\Http\Middleware\WebsiteEnable::class]); + + $category = Category::factory()->create(['name' => 'Kategori OpenKab']); + $localArticle = Article::factory()->create([ + 'category_id' => $category->id, + 'title' => 'Judul Artikel OpenKab CMS', + 'content' => 'Konten artikel cms openkab lokal', + 'state' => Article::PUBLISH, + 'published_at' => now()->subDay(), + ]); + + $response = $this->get(route('web.artikel.index')); + $response->assertStatus(200); + $response->assertSee('Judul Artikel OpenKab CMS'); + $response->assertSee('OpenKab'); + } + + #[Test] + public function it_can_access_artikel_terbaru_json_endpoint() + { + $this->withoutMiddleware([\App\Http\Middleware\WebsiteEnable::class]); + + $category = Category::factory()->create(['name' => 'Kategori OpenKab']); + $localArticle = Article::factory()->create([ + 'category_id' => $category->id, + 'title' => 'Berita Terkini OpenKab', + 'content' => 'Cuplikan berita terkini', + 'state' => Article::PUBLISH, + 'published_at' => now(), + ]); + + $response = $this->get(route('web.artikel.terbaru')); + $response->assertStatus(200); + $response->assertJsonStructure([ + 'status', + 'data', + ]); + $response->assertSee('Berita Terkini OpenKab'); + } + #[Test] public function it_can_access_public_artikel_show() { @@ -71,6 +117,28 @@ public function it_can_access_public_artikel_show() $response->assertSee('Konten detail artikel test'); } + #[Test] + public function it_redirects_to_local_cms_article_if_id_matches_local_article() + { + $this->withoutMiddleware([\App\Http\Middleware\WebsiteEnable::class]); + + $category = Category::factory()->create(); + $localArticle = Article::factory()->create([ + 'category_id' => $category->id, + 'title' => 'Artikel Lokal Redirect', + 'slug' => 'artikel-lokal-redirect', + 'state' => Article::PUBLISH, + 'published_at' => now()->subDay(), + ]); + + $mockService = Mockery::mock(ArtikelService::class); + $mockService->shouldReceive('artikelById')->with($localArticle->id)->andReturn(null); + $this->app->instance(ArtikelService::class, $mockService); + + $response = $this->get(route('web.artikel.show', ['id' => $localArticle->id])); + $response->assertRedirect(route('article', ['aSlug' => $localArticle->slug])); + } + #[Test] public function it_aborts_404_for_disabled_or_missing_artikel() { @@ -78,7 +146,7 @@ public function it_aborts_404_for_disabled_or_missing_artikel() // Mock the ArtikelService $mockService = Mockery::mock(ArtikelService::class); - $mockService->shouldReceive('artikelById')->with(99)->andReturn(null); + $mockService->shouldReceive('artikelById')->with(999999)->andReturn(null); $mockService->shouldReceive('artikelById')->with(2)->andReturn((object) [ 'id' => 2, @@ -89,7 +157,7 @@ public function it_aborts_404_for_disabled_or_missing_artikel() $this->app->instance(ArtikelService::class, $mockService); // Test non-existent article - $response404 = $this->get(route('web.artikel.show', ['id' => 99])); + $response404 = $this->get(route('web.artikel.show', ['id' => 999999])); $response404->assertStatus(404); // Test disabled article @@ -97,3 +165,4 @@ public function it_aborts_404_for_disabled_or_missing_artikel() $responseDisabled->assertStatus(404); } } + From c6c51c075c553788311fb2f67bbcffe425cae356 Mon Sep 17 00:00:00 2001 From: Abah Roland <59082428+vickyrolanda@users.noreply.github.com> Date: Thu, 27 Aug 2026 03:52:24 +0700 Subject: [PATCH 2/2] [ci skip] memutahirkan catatan rilis --- catatan_rilis.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/catatan_rilis.md b/catatan_rilis.md index 60a0a76a1..7be1e64b5 100644 --- a/catatan_rilis.md +++ b/catatan_rilis.md @@ -2,6 +2,8 @@ Di rilis ini, versi 2608.0.2 berisi penambahan dan perbaikan yang diminta penggu #### Penambahan Fitur +1. [#1268](https://github.com/OpenSID/OpenKab/issues/1268) Penambahan fitur artikel tidak tampil di website. + #### Perbaikan BUG 1. [#1105](https://github.com/OpenSID/OpenKab/issues/1105) Perbaikan filter kecamatan tampil tanpa memilih kabupaten terlebih dahulu.