diff --git a/app/Http/Controllers/PromotionController.php b/app/Http/Controllers/PromotionController.php new file mode 100644 index 0000000..9b8f9cb --- /dev/null +++ b/app/Http/Controllers/PromotionController.php @@ -0,0 +1,402 @@ +route('login')->with('error', 'Please log in to view promotions.'); + } + + $page = $request->input('page', 1); + $pageSize = 5; + $search = $request->input('_search', null); + + $response = Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $accessToken, + ])->get("{$this->apiBaseUrl}/promotion", [ + 'page' => $page, + 'page_size' => $pageSize, + '_search' => $search, + ]); + + if ($response->status() === 401 || $response->status() === 403) { + Log::warning('Unauthorized or Forbidden API response: ', $response->json()); + return redirect()->route('login')->with('error', 'Your session has expired. Please log in again.'); + } + + $json = $response->json(); + + if ($response->successful() && isset($json['data']) && is_array($json['data'])) { + $promotions = array_map(function ($promotion) { + return [ + 'id' => $promotion['promotion_uuid'] ?? null, + 'title' => $promotion['title'] ?? '', + 'type' => $promotion['type'] ?? '', + 'startDate' => $promotion['date_start'] ?? '', + 'endDate' => $promotion['date_end'] ?? '', + 'status' => $promotion['status'] ?? '', + ]; + }, $json['data']); + + $total = $json['meta']['total'] ?? count($promotions); + $lastPage = $json['meta']['last_page'] ?? ceil($total / $pageSize); + } else { + Log::warning('No promotion data found or invalid API response: ', $json); + $promotions = []; + $total = 0; + $lastPage = 1; + } + + return view('pages.promotions', [ + 'promotions' => $promotions, + 'currentPage' => $page, + 'lastPage' => $lastPage, + 'total' => $total, + 'search' => $search, + ]); + } catch (\Exception $e) { + Log::error('Error fetching promotions: ' . $e->getMessage()); + return view('pages.promotions', [ + 'promotions' => [], + 'currentPage' => 1, + 'lastPage' => 1, + 'total' => 0, + 'search' => $search, + ]); + } + } +public function create() +{ + try { + $user = Session::get('user'); + $accessToken = $user['access_token'] ?? null; + + if (!$accessToken) { + return redirect()->route('login')->with('error', 'Please log in to add a promotion.'); + } + + $response = Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $accessToken, + ])->get("{$this->apiBaseUrl}/promoTypes"); + + $json = $response->json(); + Log::info('Promo types API response: ', $json); + + $promoTypes = $response->successful() && isset($json['data']) ? $json['data'] : []; + + if (empty($promoTypes)) { + Log::warning('Failed to fetch promo types from API, using fallback: ', $json); + $promoTypes = ['1' => 'General', '2' => 'Loyalty Exclusive', '3' => 'User Specific']; + } + + return view('pages.add-promotions', ['promoTypes' => $promoTypes]); + } catch (\Exception $e) { + Log::error('Error fetching promo types: ' . $e->getMessage()); + return view('pages.add-promotions', ['promoTypes' => ['1' => 'General', '2' => 'Loyalty Exclusive', '3' => 'User Specific']]); + } +} +public function store(Request $request) +{ + try { + $user = Session::get('user'); + $accessToken = $user['access_token'] ?? null; + + if (!$accessToken) { + return redirect()->route('login')->with('error', 'Please log in to add a promotion.'); + } + + $request->validate([ + 'title' => 'required|string|max:100', + 'type' => 'required', + 'promo_type' => 'required|in:1,2,3', // Based on the promo types IDs + 'description' => 'required|string', + 'image' => 'required|file|mimes:jpeg,png,jpg,gif|max:2048', // 2MB max + 'start_date' => 'required|date', + 'end_date' => 'required|date|after_or_equal:start_date', + 'status' => 'required|in:On Going,Done', + 'is_toppromotion' => 'nullable|in:1,0', + 'is_gps' => 'nullable|in:1,0', + ]); + + $payload = [ + 'title' => $request->input('title'), + 'type' => $request->input('type'), + 'promo_type' => $request->input('promo_type'), + 'description' => $request->input('description'), + 'date_start' => $request->input('start_date'), + 'date_end' => $request->input('end_date'), + 'status' => $request->input('status'), + 'is_toppromotion' => $request->has('is_toppromotion') ? 1 : 0, + 'is_gps' => $request->has('is_gps') ? 1 : 0, + 'station_uuid' => $request->input('station_uuid', '[]'), + ]; + + // Handle the image upload + if ($request->hasFile('image')) { + $image = $request->file('image'); + $payload['image'] = fopen($image->getPathname(), 'r'); + } + + Log::info('Data being sent to API for creating promotion: ', $payload); + + $response = Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $accessToken, + ])->attach('image', $payload['image'], $image->getClientOriginalName()) + ->post("{$this->apiBaseUrl}/promotion", array_diff_key($payload, ['image' => null])); + + $json = $response->json(); + Log::info('API response for creating promotion: ', $json); + + if ($response->successful()) { + Log::info('Promotion created successfully: ', $json); + return redirect()->route('promotions')->with('success', $json['message'] ?? 'Promotion added successfully.'); + } else { + $errorMessage = $json['message'] ?? 'Failed to add promotion. Please try again.'; + if (isset($json['errors'])) { + $errorMessage .= ' Errors: ' . json_encode($json['errors']); + } + throw new \Exception($errorMessage); + } + } catch (\Exception $e) { + Log::error('Error creating promotion: ' . $e->getMessage()); + return redirect()->back()->with('error', 'An error occurred while adding the promotion: ' . $e->getMessage()); + } +} + public function show($uuid) + { + try { + $user = Session::get('user'); + $accessToken = $user['access_token'] ?? null; + + if (!$accessToken) { + return redirect()->route('login')->with('error', 'Please log in to view a promotion.'); + } + + $response = Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $accessToken, + ])->get("{$this->apiBaseUrl}/promotion/{$uuid}"); + + $json = $response->json(); + + if ($response->successful() && isset($json['data'])) { + $promotion = [ + 'id' => $json['data']['promotion_uuid'] ?? null, + 'title' => $json['data']['title'] ?? '', + 'type' => $json['data']['type'] ?? '', + 'startDate' => $json['data']['date_start'] ?? '', + 'endDate' => $json['data']['date_end'] ?? '', + 'status' => $json['data']['status'] ?? '', + ]; + return view('pages.promotion-view', ['promotion' => $promotion]); + } else { + Log::warning('No promotion found or invalid API response: ', $json); + return redirect()->back()->with('error', 'Promotion not found.'); + } + } catch (\Exception $e) { + Log::error('Error fetching promotion for view: ' . $e->getMessage()); + return redirect()->back()->with('error', 'An error occurred while loading the promotion.'); + } + } + + public function edit($uuid) +{ + try { + $user = Session::get('user'); + $accessToken = $user['access_token'] ?? null; + + if (!$accessToken) { + return redirect()->route('login')->with('error', 'Please log in to edit a promotion.'); + } + + $response = Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $accessToken, + ])->get("{$this->apiBaseUrl}/promotion/{$uuid}"); + + $json = $response->json(); + + if ($response->successful() && isset($json['data'])) { + $promotion = [ + 'id' => $json['data']['promotion_uuid'] ?? null, + 'title' => $json['data']['title'] ?? '', + 'type' => $json['data']['type'] ?? '', + 'description' => $json['data']['description'] ?? '', + 'image' => $json['data']['image'] ?? '', + 'startDate' => $json['data']['date_start'] ?? '', + 'endDate' => $json['data']['date_end'] ?? '', + 'status' => $json['data']['status'] ?? '', + 'is_toppromotion' => $json['data']['is_toppromotion'] ?? 0, + 'is_gps' => $json['data']['is_gps'] ?? 0, + ]; + + // Determine promo_type ID based on type + $typeResponse = Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $accessToken, + ])->get("{$this->apiBaseUrl}/promoTypes"); + + $promoTypes = $typeResponse->successful() ? $typeResponse->json()['data'] ?? [] : ['1' => 'General', '2' => 'Loyalty Exclusive', '3' => 'User Specific']; + + $promotion['promo_type'] = array_search($promotion['type'], $promoTypes) ?: ''; + + return view('pages.promotion-edit', ['promotion' => $promotion, 'promoTypes' => $promoTypes]); + } else { + Log::warning('No promotion found or invalid API response: ', $json); + return redirect()->back()->with('error', 'Promotion not found.'); + } + } catch (\Exception $e) { + Log::error('Error fetching promotion for edit: ' . $e->getMessage()); + return redirect()->back()->with('error', 'An error occurred while loading the promotion.'); + } +} + public function update(Request $request, $uuid) +{ + try { + $user = Session::get('user'); + $accessToken = $user['access_token'] ?? null; + + if (!$accessToken) { + return redirect()->route('login')->with('error', 'Please log in to update a promotion.'); + } + + $request->validate([ + 'title' => 'required|string|max:100', + 'type' => 'required', + 'promo_type' => 'required|in:1,2,3', + 'description' => 'required|string', + 'image' => 'nullable|file|mimes:jpeg,png,jpg,gif|max:2048', + 'start_date' => 'required|date', + 'end_date' => 'required|date|after_or_equal:start_date', + 'status' => 'required|in:On Going,Done', + 'is_toppromotion' => 'nullable|in:1,0', + 'is_gps' => 'nullable|in:1,0', + ]); + + $payload = [ + 'title' => $request->input('title'), + 'type' => $request->input('type'), + 'promo_type' => $request->input('promo_type'), + 'description' => $request->input('description'), + 'date_start' => $request->input('start_date'), + 'date_end' => $request->input('end_date'), + 'status' => $request->input('status'), + 'is_toppromotion' => $request->has('is_toppromotion') ? 1 : 0, + 'is_gps' => $request->has('is_gps') ? 1 : 0, + 'station_uuid' => $request->input('station_uuid', '[]'), + ]; + + $client = Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $accessToken, + ]); + + if ($request->hasFile('image')) { + $image = $request->file('image'); + $payload['image'] = fopen($image->getPathname(), 'r'); + $response = $client->attach('image', $payload['image'], $image->getClientOriginalName()) + ->post("{$this->apiBaseUrl}/updatePromotion/{$uuid}", array_diff_key($payload, ['image' => null])); + } else { + $response = $client->post("{$this->apiBaseUrl}/updatePromotion/{$uuid}", $payload); + } + + $json = $response->json(); + Log::info('API response for updating promotion: ', $json); + + if ($response->successful()) { + Log::info('Promotion updated successfully: ', $json); + return redirect()->route('promotions')->with('success', $json['message'] ?? 'Promotion updated successfully.'); + } else { + $errorMessage = $json['message'] ?? 'Failed to update promotion. Please try again.'; + if (isset($json['errors'])) { + $errorMessage .= ' Errors: ' . json_encode($json['errors']); + } + return redirect()->back()->with('error', $errorMessage); + } + } catch (\Exception $e) { + Log::error('Error updating promotion: ' . $e->getMessage()); + return redirect()->back()->with('error', 'An error occurred while updating the promotion: ' . $e->getMessage()); + } +} + public function destroy($uuid) + { + try { + $user = Session::get('user'); + $accessToken = $user['access_token'] ?? null; + + if (!$accessToken) { + return redirect()->route('login')->with('error', 'Please log in to delete a promotion.'); + } + + $response = Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $accessToken, + ])->delete("{$this->apiBaseUrl}/promotion/{$uuid}"); + + if ($response->successful()) { + Log::info('Promotion deleted successfully: ' . $uuid); + return redirect()->route('promotions')->with('success', 'Promotion deleted successfully.'); + } else { + Log::warning('Failed to delete promotion: ', $response->json()); + return redirect()->back()->with('error', $response->json()['message'] ?? 'Failed to delete promotion. Please try again.'); + } + } catch (\Exception $e) { + Log::error('Error deleting promotion: ' . $e->getMessage()); + return redirect()->back()->with('error', 'An error occurred while deleting the promotion.'); + } + } + + public function batchDelete(Request $request) + { + try { + $user = Session::get('user'); + $accessToken = $user['access_token'] ?? null; + + if (!$accessToken) { + return redirect()->route('login')->with('error', 'Please log in to delete promotions.'); + } + + $uuids = $request->input('promotion_uuid', []); + + if (empty($uuids)) { + return redirect()->back()->with('error', 'No promotions selected for deletion.'); + } + + $response = Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $accessToken, + ])->delete("{$this->apiBaseUrl}/promotionBatchDelete", [ + 'promotion_uuid' => $uuids, + ]); + + if ($response->successful()) { + Log::info('Promotions batch deleted successfully: ', $uuids); + return redirect()->route('promotions')->with('success', 'Selected promotions deleted successfully.'); + } else { + Log::warning('Failed to batch delete promotions: ', $response->json()); + return redirect()->back()->with('error', $response->json()['message'] ?? 'Failed to delete promotions. Please try again.'); + } + } catch (\Exception $e) { + Log::error('Error batch deleting promotions: ' . $e->getMessage()); + return redirect()->back()->with('error', 'An error occurred while deleting the promotions.'); + } + } +} \ No newline at end of file diff --git a/resources/views/components/promotion-component.blade.php b/resources/views/components/promotion-component.blade.php new file mode 100644 index 0000000..b1f01a3 --- /dev/null +++ b/resources/views/components/promotion-component.blade.php @@ -0,0 +1,391 @@ +@props([ + 'pageTitle' => '', + 'data' => [], + 'columns' => [], + 'actions' => [], + 'showAddButton' => false, + 'addButtonUrl' => '#', + 'showCheckboxes' => false, + 'showBatchDelete' => false, + 'showEditModal' => false, + 'showViewModal' => false, + 'currentPage' => 1, + 'lastPage' => 1, + 'total' => 0, +]) + +
+
+
{{ $pageTitle }}
+ @if ($showAddButton) + + Add {{ $pageTitle }} + + @endif +
+
+
+ +
+
+
+ + + + +
+
+
+ +
+
+ + +
+ + + + @if ($showCheckboxes) + + @endif + @foreach ($columns as $index => $column) + + @endforeach + @if (!empty($actions)) + + @endif + + + +
+ + + {{ $column['name'] }} + @if ($column['sortable']) + + @endif + Action
+
+ + +
+ @if ($showBatchDelete) +
+ +
+ @endif + +
+
+ + + + + \ No newline at end of file diff --git a/resources/views/pages/add-promotions.blade.php b/resources/views/pages/add-promotions.blade.php index 48d6e75..6e6b766 100644 --- a/resources/views/pages/add-promotions.blade.php +++ b/resources/views/pages/add-promotions.blade.php @@ -3,102 +3,119 @@ @section('page_title', 'Add Promotion') @section('content') -
-
Add Promotion
-
-
-
-
+
+
+
Add New Promotion
+
+
+ @if (session('error')) + + @endif + @if (session('success')) + + @endif + + @csrf
- + + @error('title') +
{{ $message }}
+ @enderror
- + @foreach ($promoTypes as $key => $type) + + @endforeach + + @error('type') +
{{ $message }}
+ @enderror + @error('promo_type') +
{{ $message }}
+ @enderror
- - + + + @error('description') +
{{ $message }}
+ @enderror
- - + + + @error('image') +
{{ $message }}
+ @enderror +
+
+ + + @error('start_date') +
{{ $message }}
+ @enderror +
+
+ + + @error('end_date') +
{{ $message }}
+ @enderror
- + + + @error('status') +
{{ $message }}
+ @enderror
-
- - +
+ + + @error('is_toppromotion') +
{{ $message }}
+ @enderror
+
+ + + @error('is_gps') +
{{ $message }}
+ @enderror +
+ + + Cancel
- - @endsection \ No newline at end of file diff --git a/resources/views/pages/promotion-edit.blade.php b/resources/views/pages/promotion-edit.blade.php new file mode 100644 index 0000000..20df688 --- /dev/null +++ b/resources/views/pages/promotion-edit.blade.php @@ -0,0 +1,120 @@ +@extends('layouts.app') + +@section('page_title', 'Edit Promotion') + +@section('content') +
+
+
Edit Promotion
+
+
+ @if (session('error')) + + @endif + @if (session('success')) + + @endif +
+ @csrf + @method('PUT') +
+ + + @error('title') +
{{ $message }}
+ @enderror +
+
+ + + + @error('type') +
{{ $message }}
+ @enderror + @error('promo_type') +
{{ $message }}
+ @enderror +
+
+ + + @error('description') +
{{ $message }}
+ @enderror +
+
+ + @if ($promotion['image']) +
+

Current Image: View Image

+
+ @endif + + Leave blank to keep the current image. + @error('image') +
{{ $message }}
+ @enderror +
+
+ + + @error('start_date') +
{{ $message }}
+ @enderror +
+
+ + + @error('end_date') +
{{ $message }}
+ @enderror +
+
+ + + @error('status') +
{{ $message }}
+ @enderror +
+
+ + + @error('is_toppromotion') +
{{ $message }}
+ @enderror +
+
+ + + @error('is_gps') +
{{ $message }}
+ @enderror +
+ + + Cancel +
+
+
+ + +@endsection \ No newline at end of file diff --git a/resources/views/pages/promotion-view.blade.php b/resources/views/pages/promotion-view.blade.php new file mode 100644 index 0000000..c58dbee --- /dev/null +++ b/resources/views/pages/promotion-view.blade.php @@ -0,0 +1,25 @@ +@extends('layouts.app') + +@section('page_title', 'View Promotion') + +@section('content') +
+
+
View Promotion
+
+
+ @if (session('error')) + + @endif +
Title: {{ $promotion['title'] }}
+
Type: {{ $promotion['type'] }}
+
Start Date: {{ $promotion['startDate'] }}
+
End Date: {{ $promotion['endDate'] }}
+
Status: {{ $promotion['status'] }}
+ Back +
+
+@endsection \ No newline at end of file diff --git a/resources/views/pages/promotions.blade.php b/resources/views/pages/promotions.blade.php index fcb241c..54c0f8f 100644 --- a/resources/views/pages/promotions.blade.php +++ b/resources/views/pages/promotions.blade.php @@ -3,85 +3,42 @@ @section('page_title', 'Promotions') @section('content') - @php - $promotions = [ - [ - 'id' => 1, - 'title' => 'Spring Sale', - 'type' => 'Discount', - 'startDate' => '2025-04-10', - 'endDate' => '2025-04-20', - 'status' => 'Done' +
+ @if (session('success')) + + @endif + @if (session('error')) + + @endif + @include('components.promotion-component', [ + 'pageTitle' => 'Promotions', + 'data' => $promotions ?? [], + 'columns' => [ + ['name' => 'Title', 'key' => 'title', 'sortable' => true], + ['name' => 'Type', 'key' => 'type', 'sortable' => true], + ['name' => 'Start Date', 'key' => 'startDate', 'sortable' => true], + ['name' => 'End Date', 'key' => 'endDate', 'sortable' => true], + ['name' => 'Status', 'key' => 'status', 'sortable' => true] ], - [ - 'id' => 2, - 'title' => 'Flash Deal', - 'type' => 'Flash Sale', - 'startDate' => '2025-04-15', - 'endDate' => '2025-04-17', - 'status' => 'On Going' - ], - [ - 'id' => 3, - 'title' => 'Loyalty Promo', - 'type' => 'Reward', - 'startDate' => '2025-04-20', - 'endDate' => '2025-05-01', - 'status' => 'Done' - ], - [ - 'id' => 4, - 'title' => 'Holiday Bundle', - 'type' => 'Bundle', - 'startDate' => '2025-04-25', - 'endDate' => '2025-05-05', - 'status' => 'On Going' - ], - [ - 'id' => 5, - 'title' => 'Back-to-School', - 'type' => 'Discount', - 'startDate' => '2025-04-30', - 'endDate' => '2025-05-10', - 'status' => 'Done' - ], - [ - 'id' => 6, - 'title' => 'Clearance Sale', - 'type' => 'Flash Sale', - 'startDate' => '2025-05-01', - 'endDate' => '2025-05-03', - 'status' => 'On Going' - ] - ]; - @endphp - - @include('components.table-component', [ - 'pageTitle' => 'Promotions', - 'data' => $promotions, - 'columns' => [ - ['name' => 'Title', 'key' => 'title', 'sortable' => true], - ['name' => 'Type', 'key' => 'type', 'sortable' => true], - ['name' => 'Start Date', 'key' => 'startDate', 'sortable' => true], - ['name' => 'End Date', 'key' => 'endDate', 'sortable' => true], - ['name' => 'Status', 'key' => 'status', 'sortable' => true] - ], - 'actions' => ['edit', 'view', 'delete'], - 'showAddButton' => true, - 'addButtonUrl' => '/add-promotions', - 'showCheckboxes' => true, - 'showBatchDelete' => true, - 'showEditModal' => true, - 'showViewModal' => true - ]) - - + 'actions' => ['edit', 'view', 'delete'], + 'showAddButton' => true, + 'addButtonUrl' => route('promotions.create'), + 'showCheckboxes' => true, + 'showBatchDelete' => true, + 'showEditModal' => false, + 'showViewModal' => false, + 'currentPage' => $currentPage ?? 1, + 'lastPage' => $lastPage ?? 1, + 'total' => $total ?? 0, + ]) +
+

No promotions found.

+
+
@endsection \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 7af9ada..f020678 100644 --- a/routes/web.php +++ b/routes/web.php @@ -8,6 +8,9 @@ use App\Http\Controllers\PhotoSliderController; use App\Http\Controllers\TopUpController; use App\Http\Controllers\NotificationController; use App\Http\Controllers\CardMemberController; +use App\Http\Controllers\PromotionController; + + Route::get('/', function () { return redirect()->route('login'); @@ -34,12 +37,6 @@ Route::get('/locked-accounts', function () { return view('pages.member management.locked-accounts'); })->name('locked-accounts'); - - -Route::get('/promotions', function () { - return view('pages.promotions'); -})->name('promotions'); - Route::get('/top-up', function () { return view('pages.top-up'); })->name('top-up'); @@ -188,4 +185,14 @@ Route::get('/card-member', [CardMemberController::class, 'index'])->name('card-m Route::get('/locked-account', [CardMemberController::class, 'lockedAccounts'])->name('locked-account'); Route::get('/card-member/{uuid}', [CardMemberController::class, 'show'])->name('card-member.show'); Route::get('/locked-account/{uuid}', [CardMemberController::class, 'show'])->name('locked-account.show'); -Route::post('/locked-account/activate/{uuid}', [CardMemberController::class, 'activate'])->name('locked-account.activate'); \ No newline at end of file +Route::post('/locked-account/activate/{uuid}', [CardMemberController::class, 'activate'])->name('locked-account.activate'); + +//Promotion +Route::get('/promotions', [PromotionController::class, 'index'])->name('promotions'); +Route::get('/promotions/create', [PromotionController::class, 'create'])->name('promotions.create'); +Route::post('/promotions', [PromotionController::class, 'store'])->name('promotions.store'); +Route::get('/promotions/{uuid}', [PromotionController::class, 'show'])->name('promotions.show'); +Route::get('/promotions/{uuid}/edit', [PromotionController::class, 'edit'])->name('promotions.edit'); +Route::put('/promotions/{uuid}', [PromotionController::class, 'update'])->name('promotions.update'); +Route::delete('/promotions/{uuid}', [PromotionController::class, 'destroy'])->name('promotions.destroy'); +Route::post('/promotions/batch-delete', [PromotionController::class, 'batchDelete'])->name('promotions.batch-delete'); \ No newline at end of file