From 82191543351603a23d0e33dcccbb528b3031969f Mon Sep 17 00:00:00 2001 From: Watheq Alshowaiter Date: Sun, 1 Jun 2025 00:15:29 +0300 Subject: [PATCH] feat: implement pagination and enhance UI with modern design --- README.md | 19 +- app/Providers/AppServiceProvider.php | 4 + app/Services/RateService.php | 65 ++- resources/views/rates.blade.php | 526 +++++++++++++++--- .../pagination/simple-default.blade.php | 29 + tests/Feature/RateControllerTest.php | 4 +- 6 files changed, 532 insertions(+), 115 deletions(-) create mode 100644 resources/views/vendor/pagination/simple-default.blade.php diff --git a/README.md b/README.md index 6931f69..950bd8a 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,20 @@ to be added ## Plan - [x] Add html view to show the currency data - [x] Save command data to database -- [ ] email when failing fetching currency using resen -- [ ] caching for fetching command (6 hours), and for the controller (1 hour) -- [ ] make proper pagination -- [ ] save tabs state as query param -- [ ] make a complete test for the project +- [x] make proper pagination +- [x] make a complete test for the project +- [x] save tabs state as local storage +- [ ] email when failing fetching currency using resend +- [ ] caching for the controller (1 hour) - [ ] make the app A PWA - [ ] put a place for ads - [ ] add some useful information like what is buy price and sell price, the resource of the data, etc - - +- [ ] improve the SEO +- [ ] add place for ads +- [ ] add place for some useful information like + - [ ] 1. what is sell price & buy price + - [ ] 2. why our data is different from online apis + - [ ] 3. where is the data from + ## Future Plans - add react native app to show the currency data diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c78051a..f89cc57 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -3,11 +3,13 @@ namespace App\Providers; use Carbon\CarbonImmutable; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; +use Illuminate\Pagination\Paginator; class AppServiceProvider extends ServiceProvider { @@ -31,5 +33,7 @@ class AppServiceProvider extends ServiceProvider Model::shouldBeStrict(! app()->isProduction()); Date::use(CarbonImmutable::class); + + Paginator::defaultSimpleView('vendor/pagination/simple-default'); } } diff --git a/app/Services/RateService.php b/app/Services/RateService.php index 7882d25..8de491c 100644 --- a/app/Services/RateService.php +++ b/app/Services/RateService.php @@ -7,45 +7,56 @@ use App\Models\Rate; class RateService { - public function getFormattedRates(): array + public function getFormattedRates() { $supportedCities = City::query() ->whereIn('name', City::supportedCities()) // Filter only supported cities ->get() ->sortByDesc(fn($city) => $city->name === 'sanaa') // Sort to put 'sanaa' first ->values(); // Reset array keys to be sequential - - // Get the latest exchange rates for each currency in each city + $latestRates = Rate::query() + ->whereIn('city_id', $supportedCities->pluck('id')) ->with('currency', 'city') - ->orderBy('city_id', 'asc') - ->orderBy('currency_id', 'asc') - ->orderBy('date', 'desc') - ->get() - ->groupBy(fn($rate) => $rate->city_id . '-' . $rate->currency_id) - ->map(fn($group) => $group->first()) - ->groupBy('city_id'); + ->orderBy('date', 'desc') // first sort by latest date + ->orderBy('city_id', 'asc') // then sort by city_id + ->orderBy('currency_id', 'asc') // then sort by currency_id + ->simplePaginate(4); - - // Format the rates - $rates = $supportedCities->map(function ($city) use ($latestRates) { - $cityRates = $latestRates->get($city->id, collect())->map(function ($rate) { + $transformedRates = $latestRates->getCollection() + ->map(function($rate) { + return [ + 'city_name' => $rate->city->label, + 'currency_name' => $rate->currency->name, + 'buy_price' => $rate->buy_price, + 'sell_price' => $rate->sell_price, + 'date' => $rate->date->toDateString(), + 'day' => $rate->date->locale('ar')->isoFormat('dddd'), + 'is_today' => $rate->date->isToday(), + 'last_update' => $rate->updated_at->diffForHumans(), + ]; + }) + ->groupBy('city_name') + ->map(function($cityRates, $cityName) { return [ - 'currency' => $rate->currency->name, - 'buy_price' => $rate->buy_price, - 'sell_price' => $rate->sell_price, - 'date' => $rate->date->toDateString(), - 'day' => $rate->date->locale('ar')->isoFormat('dddd'), - 'last_update' => $rate->updated_at->diffForHumans(), + 'city' => $cityName, + 'rates' => $cityRates->map(function($rate) { + return [ + 'currency' => $rate['currency_name'], + 'buy_price' => $rate['buy_price'], + 'sell_price' => $rate['sell_price'], + 'date' => $rate['date'], + 'day' => $rate['day'], + 'is_today' => $rate['is_today'], + 'last_update' => $rate['last_update'], + ]; + })->values()->toArray() ]; - })->values()->toArray(); + }) + ->values(); // Reset keys to be sequential - return [ - 'city' => $city->label , - 'rates' => $cityRates, - ]; - })->toArray(); + $latestRates->setCollection($transformedRates); - return $rates; + return $latestRates; } } diff --git a/resources/views/rates.blade.php b/resources/views/rates.blade.php index f2dd994..08d6e55 100644 --- a/resources/views/rates.blade.php +++ b/resources/views/rates.blade.php @@ -5,154 +5,522 @@ أسعار العملات في اليمن
-

أسعار العملات في اليمن

- -
+
+

أسعار العملات في اليمن

+

أحدث أسعار الصرف لليوم

+
+ +
@foreach ($rates as $index => $rate) -
+
+ @endforeach
- - @foreach ($rates as $index => $cityRates) -
- @foreach ($cityRates['rates'] as $rate) -
-
- العملة: - {{ $rate['currency'] }} + @foreach ($rates as $index => $cityRates) +
+ + @forelse ($cityRates['rates'] as $rate) +
+
+ {{ $rate['currency'] }}
-
- سعر الشراء: - {{ $rate['buy_price'] }} ريال يمني + +
+
+
شراء
+
{{ number_format($rate['buy_price']) }}
+
+
+
بيع
+
{{ number_format($rate['sell_price']) }}
+
-
- سعر البيع: - {{ $rate['sell_price'] }} ريال يمني -
-
- التاريخ: - {{ $rate['date'] }} ({{ $rate['day'] }}) -
-
- آخر تحديث: {{ $rate['last_update'] }} + +
+ {{ $rate['day'] }} + + {{ $rate['date'] }} + @if(isset($rate['is_today']) && $rate['is_today']) + - اليوم + @endif + + {{ $rate['last_update'] }}
- @endforeach + @empty +
+ لا توجد بيانات متاحة +
+ @endforelse
@endforeach + + {{ $rates->links() }}
- + \ No newline at end of file diff --git a/resources/views/vendor/pagination/simple-default.blade.php b/resources/views/vendor/pagination/simple-default.blade.php new file mode 100644 index 0000000..8f249b9 --- /dev/null +++ b/resources/views/vendor/pagination/simple-default.blade.php @@ -0,0 +1,29 @@ +
+ @if ($paginator->hasPages()) + + @endif +
\ No newline at end of file diff --git a/tests/Feature/RateControllerTest.php b/tests/Feature/RateControllerTest.php index 34f16f4..b4bbb7f 100644 --- a/tests/Feature/RateControllerTest.php +++ b/tests/Feature/RateControllerTest.php @@ -102,11 +102,11 @@ class RateControllerTest extends TestCase // Assert $response->assertStatus(200) ->assertViewIs('rates') - ->assertViewHasAll(['rates']); + ->assertViewHas('rates'); $viewData = $response->original->getData(); - $ratesCities = array_column($viewData['rates'], 'city'); + $ratesCities = $viewData['rates']->pluck('city')->toArray(); $this->assertContains($sanaa->label, $ratesCities); $this->assertContains($aden->label, $ratesCities);