diff --git a/app/Livewire/DashboardTable.php b/app/Livewire/DashboardTable.php index 64e3013..76d2abd 100644 --- a/app/Livewire/DashboardTable.php +++ b/app/Livewire/DashboardTable.php @@ -3,7 +3,6 @@ namespace App\Livewire; use App\Models\Postcard; -use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Facades\Auth; use Livewire\Component; use Livewire\WithPagination; @@ -29,7 +28,7 @@ class DashboardTable extends Component public $filter_status = ''; - public $sort = 'tanggal_kirim'; // default + public $sort = 'tanggal_kirim'; public $perPage = 20; @@ -60,87 +59,79 @@ public function render() $query->whereBetween('tanggal_terima', [$this->start_terima, $this->end_terima]); } - // SQL JOIN for Country Search + // Filters if ($this->filter_negara) { - $searchTerm = $this->filter_negara; - $query->whereHas('country', function ($q) use ($searchTerm) { - $q->where('nama_indonesia', 'like', '%'.$searchTerm.'%') - ->orWhere('nama_inggris', 'like', '%'.$searchTerm.'%'); - }); - } - - if ($this->filter_kategori === 'postcrossing') { - $query->where('postcard_id', 'like', '%-%'); - } elseif ($this->filter_kategori === 'swap') { - $query->where(function ($q) { - $q->where('postcard_id', 'not like', '%-%') - ->orWhereNull('postcard_id') - ->orWhere('postcard_id', ''); - }); - } - - if ($this->filter_status === 'arrived') { - $query->whereNotNull('tanggal_terima')->where('tanggal_terima', '!=', '0000-00-00'); - } elseif ($this->filter_status === 'travelling') { - $query->where(function ($q) { - $q->whereNull('tanggal_terima')->orWhere('tanggal_terima', '0000-00-00'); - }); - } - - $allRecords = $query->get(); - - if ($this->filter_negara) { - $searchTerm = strtolower($this->filter_negara); - $allRecords = $allRecords->filter(function ($row) use ($searchTerm) { - // Country search already handled by SQL whereHas, but adding others here - return str_contains(strtolower((string) $row->contact?->nama_kontak), $searchTerm) || - str_contains(strtolower((string) $row->contact?->alamat), $searchTerm) || - str_contains(strtolower((string) $row->contact?->nomor_telepon), $searchTerm) || - str_contains(strtolower((string) $row->postcard_id), $searchTerm) || - str_contains(strtolower((string) $row->deskripsi_gambar), $searchTerm); + $searchTerm = '%'.$this->filter_negara.'%'; + $query->where(function ($q) use ($searchTerm) { + $q->whereHas('country', function ($sq) use ($searchTerm) { + $sq->where('nama_indonesia', 'like', $searchTerm) + ->orWhere('nama_inggris', 'like', $searchTerm); + }) + ->orWhereHas('contact', function ($sq) use ($searchTerm) { + $sq->where('nama_kontak', 'like', $searchTerm) + ->orWhere('alamat', 'like', $searchTerm) + ->orWhere('nomor_telepon', 'like', $searchTerm); + }) + ->orWhere('postcard_id', 'like', $searchTerm) + ->orWhere('deskripsi_gambar', 'like', $searchTerm); }); } + // Handle Sorting if (str_starts_with($this->sort, 'jarak')) { - $allRecords->each(function ($row) use ($myLat, $myLng) { - $row->jarak_hitung = ($row->contact?->lat && $row->contact?->lng && is_numeric($row->contact->lat)) - ? $this->calculateDistance($myLat, $myLng, (float) $row->contact->lat, (float) $row->contact->lng) - : 0; + $allRows = $query->get()->each(function ($row) use ($myLat, $myLng) { + if ($row->contact && $row->contact->lat && $row->contact->lng) { + $row->jarak_hitung = $this->calculateDistance($myLat, $myLng, $row->contact->lat, $row->contact->lng); + } else { + $row->jarak_hitung = 0; + } }); if ($this->sort === 'jarak_desc') { - $allRecords = $allRecords->sortByDesc('jarak_hitung'); + $allRows = $allRows->sortByDesc('jarak_hitung'); } else { - $allRecords = $allRecords->sortBy('jarak_hitung'); + $allRows = $allRows->sort(function ($a, $b) { + if ($a->jarak_hitung == 0 && $b->jarak_hitung == 0) { + return 0; + } + if ($a->jarak_hitung == 0) { + return 1; + } + if ($b->jarak_hitung == 0) { + return -1; + } + + return $a->jarak_hitung <=> $b->jarak_hitung; + }); } - } elseif ($this->sort === 'tanggal_terima') { - $allRecords = $allRecords->sortByDesc('tanggal_kirim') - ->sortByDesc('tanggal_terima'); + + $currentPage = \Illuminate\Pagination\Paginator::resolveCurrentPage(); + $rows = new \Illuminate\Pagination\LengthAwarePaginator( + $allRows->forPage($currentPage, $this->perPage)->values(), + $allRows->count(), + $this->perPage, + $currentPage, + ['path' => request()->url(), 'query' => request()->query()] + ); } else { - // Default: tanggal_kirim DESC - $allRecords = $allRecords->sortByDesc('tanggal_kirim'); - } + if ($this->sort === 'tanggal_terima') { + $query->orderByDesc('tanggal_terima')->orderByDesc('tanggal_kirim'); + } else { + $query->orderByDesc('tanggal_kirim')->orderByDesc('id'); + } + + $rows = $query->paginate($this->perPage); - $page = $this->getPage(); - $rows = new LengthAwarePaginator( - $allRecords->forPage($page, $this->perPage), - $allRecords->count(), - $this->perPage, - $page, - ['path' => \Illuminate\Pagination\Paginator::resolveCurrentPath()] - ); - - // Ensure distance is calculated for result page if not done in sort step - if (! str_starts_with($this->sort, 'jarak')) { $rows->getCollection()->each(function ($row) use ($myLat, $myLng) { - $row->jarak_hitung = ($row->contact?->lat && $row->contact?->lng && is_numeric($row->contact->lat)) - ? $this->calculateDistance($myLat, $myLng, (float) $row->contact->lat, (float) $row->contact->lng) - : 0; + if ($row->contact && $row->contact->lat && $row->contact->lng) { + $row->jarak_hitung = $this->calculateDistance($myLat, $myLng, $row->contact->lat, $row->contact->lng); + } else { + $row->jarak_hitung = 0; + } }); } - // Grand Total based on PHP-filtered records - $totalCost = $allRecords->sum('biaya_prangko'); + $totalCost = Postcard::where('user_id', $user_id)->where('type', $this->type)->sum('biaya_prangko'); return view('livewire.dashboard-table', [ 'rows' => $rows, diff --git a/app/Livewire/EditPostcard.php b/app/Livewire/EditPostcard.php index 7fb02f1..40a758d 100644 --- a/app/Livewire/EditPostcard.php +++ b/app/Livewire/EditPostcard.php @@ -35,24 +35,21 @@ class EditPostcard extends Component public $deskripsi_gambar; - // Currency fields (for received) public $nilai_asal; public $mata_uang = 'IDR'; public $kurs_idr = 1; - // Images public $currentFotoDepan; public $currentFotoBelakang; - // JS Scanner will fill these Base64 strings public $newFotoDepanBase64; public $newFotoBelakangBase64; - public $newStampsBase64 = []; // Array of Base64 strings + public $newStampsBase64 = []; public $existingStamps = []; diff --git a/app/Livewire/RegisterPostcard.php b/app/Livewire/RegisterPostcard.php index 2489506..674604d 100644 --- a/app/Livewire/RegisterPostcard.php +++ b/app/Livewire/RegisterPostcard.php @@ -31,7 +31,6 @@ class RegisterPostcard extends Component public $deskripsi_gambar; - // Currency public $nilai_asal; public $mata_uang = 'IDR'; @@ -114,6 +113,14 @@ protected function saveImage($base64Data, $prefix) public function save(GeocodingService $geoService) { + $this->validate([ + 'negara' => 'required', + 'alamat' => 'required', + 'tanggal_kirim' => 'required|date', + 'img_d_data' => 'nullable', // Optional but handled + 'img_b_data' => 'nullable', + ]); + $coords = $geoService->getCoordinates($this->alamat, $this->negara); $lat = $coords['lat']; $lng = $coords['lng']; diff --git a/composer.lock b/composer.lock index 2db51d1..684cee7 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "brick/math", - "version": "0.14.6", + "version": "0.14.7", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "32498d5e1897e7642c0b961ace2df6d7dc9a3bc3" + "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/32498d5e1897e7642c0b961ace2df6d7dc9a3bc3", - "reference": "32498d5e1897e7642c0b961ace2df6d7dc9a3bc3", + "url": "https://api.github.com/repos/brick/math/zipball/07ff363b16ef8aca9692bba3be9e73fe63f34e50", + "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50", "shasum": "" }, "require": { @@ -56,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.6" + "source": "https://github.com/brick/math/tree/0.14.7" }, "funding": [ { @@ -64,7 +64,7 @@ "type": "github" } ], - "time": "2026-02-05T07:59:58+00:00" + "time": "2026-02-07T10:57:35+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -2021,16 +2021,16 @@ }, { "name": "livewire/livewire", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "8adef21f35f4ffa87fd2f3655b350236df0c39a8" + "reference": "69c871cb15fb95f10cda5acd1ee7e63cd3c494c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/8adef21f35f4ffa87fd2f3655b350236df0c39a8", - "reference": "8adef21f35f4ffa87fd2f3655b350236df0c39a8", + "url": "https://api.github.com/repos/livewire/livewire/zipball/69c871cb15fb95f10cda5acd1ee7e63cd3c494c8", + "reference": "69c871cb15fb95f10cda5acd1ee7e63cd3c494c8", "shasum": "" }, "require": { @@ -2085,7 +2085,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v4.1.2" + "source": "https://github.com/livewire/livewire/tree/v4.1.3" }, "funding": [ { @@ -2093,7 +2093,7 @@ "type": "github" } ], - "time": "2026-02-03T03:01:29+00:00" + "time": "2026-02-06T12:19:55+00:00" }, { "name": "monolog/monolog", @@ -2305,16 +2305,16 @@ }, { "name": "nette/schema", - "version": "v1.3.3", + "version": "v1.3.4", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" + "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", - "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", + "url": "https://api.github.com/repos/nette/schema/zipball/086497a2f34b82fede9b5a41cc8e131d087cd8f7", + "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7", "shasum": "" }, "require": { @@ -2322,8 +2322,8 @@ "php": "8.1 - 8.5" }, "require-dev": { - "nette/tester": "^2.5.2", - "phpstan/phpstan-nette": "^2.0@stable", + "nette/tester": "^2.6", + "phpstan/phpstan": "^2.0@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -2364,9 +2364,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.3" + "source": "https://github.com/nette/schema/tree/v1.3.4" }, - "time": "2025-10-30T22:57:59+00:00" + "time": "2026-02-08T02:54:00+00:00" }, { "name": "nette/utils", diff --git a/public/sw.js b/public/sw.js index a18672a..3384486 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,8 +1,8 @@ -const CACHE_NAME = 'postcrossing-v4'; +const CACHE_NAME = 'postcrossing-v7'; const ASSETS = [ - '/logo-app.png', + '/logo.png', '/vendor/bootstrap-icons/bootstrap-icons.css', - '/offline.html' // Optional: Create an offline page + '/offline.html' ]; self.addEventListener('install', (event) => { @@ -28,7 +28,20 @@ self.addEventListener('activate', (event) => { }); self.addEventListener('fetch', (event) => { - // Navigation requests (HTML pages) -> Network Only (Audit: User reported stale data) + const url = new URL(event.request.url); + + // Cache-first for static assets + const isAsset = ASSETS.some(asset => url.pathname === asset); + if (isAsset) { + event.respondWith( + caches.match(event.request).then((response) => { + return response || fetch(event.request); + }) + ); + return; + } + + // Network-only with offline fallback for navigation if (event.request.mode === 'navigate') { event.respondWith( fetch(event.request).catch(() => { @@ -38,10 +51,5 @@ self.addEventListener('fetch', (event) => { return; } - // Static Assets -> Cache First, then Network - event.respondWith( - caches.match(event.request).then((response) => { - return response || fetch(event.request); - }) - ); + // Bypass for dynamic requests (Livewire/API) }); diff --git a/resources/views/components/layouts/app.blade.php b/resources/views/components/layouts/app.blade.php index 22fc8c6..64b8aae 100644 --- a/resources/views/components/layouts/app.blade.php +++ b/resources/views/components/layouts/app.blade.php @@ -7,6 +7,9 @@ + + + @vite(['resources/css/app.css', 'resources/js/app.js']) @@ -238,7 +241,7 @@ const installBtn = document.getElementById('pwaInstallBtn'); if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('/sw.js'); + navigator.serviceWorker.register('/sw.js?v=7'); } window.addEventListener('beforeinstallprompt', (e) => { diff --git a/resources/views/emails/postcard-arrived.blade.php b/resources/views/emails/postcard-arrived.blade.php index 9c44250..1252b84 100644 --- a/resources/views/emails/postcard-arrived.blade.php +++ b/resources/views/emails/postcard-arrived.blade.php @@ -181,7 +181,7 @@
- View Postcard Details + View Postcard Details
diff --git a/resources/views/livewire/dashboard-table.blade.php b/resources/views/livewire/dashboard-table.blade.php index 0b3a948..80c2a29 100644 --- a/resources/views/livewire/dashboard-table.blade.php +++ b/resources/views/livewire/dashboard-table.blade.php @@ -35,8 +35,8 @@ .id-link { font-weight: bold; color: #2c3e50; text-decoration: none; } .id-link:hover { text-decoration: underline; color: #e63946; } - .thumb-img { width: 45px; height: 30px; object-fit: cover; border-radius: 3px; border: 1px solid #ccc; cursor: pointer; transition: transform 0.2s; } - .thumb-img:hover { transform: scale(3); z-index: 10; position: relative; box-shadow: 0 5px 15px rgba(0,0,0,0.3); } + .thumb-img { width: 50px; height: auto; max-height: 50px; border-radius: 3px; border: 1px solid #ccc; cursor: pointer; transition: transform 0.2s; background: #eee; } + .thumb-img:hover { transform: scale(3.5); z-index: 100; position: relative; box-shadow: 0 5px 20px rgba(0,0,0,0.4); outline: 2px solid white; } .btn-action { color: #666; margin-right: 5px; font-size: 1rem; transition: color 0.2s; } .btn-action:hover { color: #000; } @@ -47,18 +47,18 @@
-
- +
+ - - +
-
- +
+ - - +
@@ -147,7 +147,9 @@ {{ $this->getDuration($row->tanggal_kirim, $row->tanggal_terima) }} - {{ number_format($row->jarak_hitung) }} km + + {{ number_format($row->jarak_hitung) }} km + {{ $row->deskripsi_gambar }} diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index b760b2f..aa9ce1b 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -259,12 +259,7 @@ function initDashboardMap() { dashboardMap = new google.maps.Map(document.getElementById('dashboard-map'), { zoom: 2, center: { lat: 20, lng: 10 }, - streetViewControl: false, - styles: [ - { "elementType": "geometry", "stylers": [{ "color": "#ebe3cd" }] }, - { "elementType": "labels.text.fill", "stylers": [{ "color": "#523735" }] }, - { "featureType": "water", "elementType": "geometry.fill", "stylers": [{ "color": "#b9d3c2" }] } - ] + streetViewControl: false }); const coordsTracker = {}; diff --git a/resources/views/livewire/edit-postcard.blade.php b/resources/views/livewire/edit-postcard.blade.php index b15a15f..92fca08 100644 --- a/resources/views/livewire/edit-postcard.blade.php +++ b/resources/views/livewire/edit-postcard.blade.php @@ -86,10 +86,6 @@ .btn-rot-stamp { position: absolute; top: -8px; left: -8px; background: var(--pc-blue); color: white; border-radius: 50%; width: 24px; height: 24px; text-align: center; line-height: 20px; border: 2px solid white; font-size: 16px; cursor: pointer; z-index: 5; } - - - -
@@ -113,7 +109,7 @@
- + (DD/MM/YYYY) @error('tanggal_kirim') {{ $message }} @enderror
- + (DD/MM/YYYY)
@@ -223,10 +240,10 @@
@endforeach - @foreach($newStampsBase64 as $index => $base64)
+
@endforeach @@ -255,10 +272,8 @@ class="text-red-500 hover:text-red-700 transition font-bold" style="font-family:
- -
+ +