integration

This commit is contained in:
Armie 2025-05-13 19:54:49 +08:00
parent 93fb6eff66
commit b2bc92e2b9
58 changed files with 6719 additions and 1744 deletions

View File

@ -0,0 +1,56 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class AuthController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
try {
$response = $this->apiService->post('/auth/login', $credentials);
if ($response->successful()) {
$data = $response->json();
session(['api_token' => $data['token']]);
return redirect()->intended('/dashboard');
}
return back()->withErrors([
'email' => 'The provided credentials do not match our records.',
]);
} catch (\Exception $e) {
return back()->withErrors([
'error' => 'Unable to connect to the authentication service.',
]);
}
}
public function logout()
{
try {
$this->apiService->post('/auth/logout');
} catch (\Exception $e) {
// Log the error but proceed with local logout
}
Session::forget('api_token');
return redirect('/login');
}
}

View File

@ -0,0 +1,175 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
class CardMemberController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function index()
{
try {
$response = $this->apiService->get('/card-members');
if ($response->successful()) {
return view('pages.member management.card-member', [
'members' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch card members.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function store(Request $request)
{
$validated = $request->validate([
'card_number' => 'required|string|unique:card_members,card_number',
'member_type' => 'required|string',
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'email' => 'required|email|unique:card_members,email',
'phone' => 'required|string',
'birth_date' => 'required|date',
'address' => 'required|string',
'city' => 'required|string',
'state' => 'required|string',
'postal_code' => 'required|string',
'status' => 'required|string|in:active,inactive,locked'
]);
try {
$response = $this->apiService->post('/card-members', $validated);
if ($response->successful()) {
return redirect()->route('card-member')
->with('success', 'Card member created successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to create card member.');
}
}
public function update(Request $request, $id)
{
$validated = $request->validate([
'member_type' => 'required|string',
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'email' => 'required|email|unique:card_members,email,'.$id,
'phone' => 'required|string',
'birth_date' => 'required|date',
'address' => 'required|string',
'city' => 'required|string',
'state' => 'required|string',
'postal_code' => 'required|string',
'status' => 'required|string|in:active,inactive,locked'
]);
try {
$response = $this->apiService->put("/card-members/{$id}", $validated);
if ($response->successful()) {
return redirect()->route('card-member')
->with('success', 'Card member updated successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to update card member.');
}
}
public function destroy($id)
{
try {
$response = $this->apiService->delete("/card-members/{$id}");
if ($response->successful()) {
return redirect()->route('card-member')
->with('success', 'Card member deleted successfully.');
}
return back()->with('error', 'Unable to delete card member.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function lockAccount($id)
{
try {
$response = $this->apiService->post("/card-members/{$id}/lock");
if ($response->successful()) {
return redirect()->route('card-member')
->with('success', 'Account locked successfully.');
}
return back()->with('error', 'Unable to lock account.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function unlockAccount($id)
{
try {
$response = $this->apiService->post("/card-members/{$id}/unlock");
if ($response->successful()) {
return redirect()->route('card-member')
->with('success', 'Account unlocked successfully.');
}
return back()->with('error', 'Unable to unlock account.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function getLockedAccounts()
{
try {
$response = $this->apiService->get('/card-members/locked');
if ($response->successful()) {
return view('pages.member management.locked-accounts', [
'lockedAccounts' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch locked accounts.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function getTransactionHistory($id)
{
try {
$response = $this->apiService->get("/card-members/{$id}/transactions");
if ($response->successful()) {
return response()->json($response->json());
}
return response()->json(['error' => 'Unable to fetch transaction history'], 400);
} catch (\Exception $e) {
return response()->json(['error' => 'Service unavailable'], 503);
}
}
}

View File

@ -0,0 +1,201 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
class FuelPriceController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function onDemand()
{
try {
$response = $this->apiService->get('/fuel-prices/current');
$stationsResponse = $this->apiService->get('/stations');
$fuelTypesResponse = $this->apiService->get('/fuel-types');
if ($response->successful() && $stationsResponse->successful() && $fuelTypesResponse->successful()) {
return view('pages.fuel-price-on-demand', [
'prices' => $response->json()['data'],
'stations' => $stationsResponse->json()['data'],
'fuelTypes' => $fuelTypesResponse->json()['data']
]);
}
return back()->with('error', 'Unable to fetch fuel prices.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function updateOnDemand(Request $request)
{
$validated = $request->validate([
'prices' => 'required|array',
'prices.*.*' => 'nullable|numeric|min:0'
]);
try {
$response = $this->apiService->post('/fuel-prices/update', $validated);
if ($response->successful()) {
return redirect()->route('fuel-price.on-demand')
->with('success', 'Fuel prices updated successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to update fuel prices.');
}
}
public function importPrices(Request $request)
{
$request->validate([
'csv_file' => 'required|file|mimes:csv,txt'
]);
try {
$response = $this->apiService->post('/fuel-prices/import', [
'file' => $request->file('csv_file')
], true);
if ($response->successful()) {
return redirect()->route('fuel-price.on-demand')
->with('success', 'Fuel prices imported successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to import fuel prices.');
}
}
public function exportPrices()
{
try {
$response = $this->apiService->get('/fuel-prices/export');
if ($response->successful()) {
return response()->streamDownload(
function () use ($response) {
echo $response->body();
},
'fuel-prices.csv'
);
}
return back()->with('error', 'Unable to export fuel prices.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function schedule()
{
try {
$response = $this->apiService->get('/fuel-prices/schedule');
$stationsResponse = $this->apiService->get('/stations');
$fuelTypesResponse = $this->apiService->get('/fuel-types');
if ($response->successful() && $stationsResponse->successful() && $fuelTypesResponse->successful()) {
return view('pages.fuel-price-schedule', [
'schedules' => $response->json()['data'],
'stations' => $stationsResponse->json()['data'],
'fuelTypes' => $fuelTypesResponse->json()['data']
]);
}
return back()->with('error', 'Unable to fetch scheduled updates.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function storeSchedule(Request $request)
{
$validated = $request->validate([
'station_id' => 'required|integer',
'fuel_type_id' => 'required|integer',
'new_price' => 'required|numeric|min:0',
'scheduled_for' => 'required|date|after:now'
]);
try {
$response = $this->apiService->post('/fuel-prices/schedule', $validated);
if ($response->successful()) {
return redirect()->route('fuel-price.schedule')
->with('success', 'Price update scheduled successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to schedule price update.');
}
}
public function updateSchedule(Request $request, $id)
{
$validated = $request->validate([
'station_id' => 'required|integer',
'fuel_type_id' => 'required|integer',
'new_price' => 'required|numeric|min:0',
'scheduled_for' => 'required|date|after:now'
]);
try {
$response = $this->apiService->put("/fuel-prices/schedule/{$id}", $validated);
if ($response->successful()) {
return redirect()->route('fuel-price.schedule')
->with('success', 'Scheduled update modified successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to modify scheduled update.');
}
}
public function deleteSchedule($id)
{
try {
$response = $this->apiService->delete("/fuel-prices/schedule/{$id}");
if ($response->successful()) {
return redirect()->route('fuel-price.schedule')
->with('success', 'Scheduled update deleted successfully.');
}
return back()->with('error', 'Unable to delete scheduled update.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function logs()
{
try {
$response = $this->apiService->get('/fuel-prices/logs');
if ($response->successful()) {
return view('pages.fuel-price-update-logs', [
'logs' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch update logs.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
}

View File

@ -0,0 +1,120 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
class NotificationsController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function index()
{
try {
$response = $this->apiService->get('/notifications');
if ($response->successful()) {
return view('pages.notification', [
'notifications' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch notifications.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'message' => 'required|string',
'type' => 'required|string',
'target_users' => 'required|array',
'schedule_date' => 'nullable|date',
'status' => 'required|string|in:draft,scheduled,sent'
]);
try {
$response = $this->apiService->post('/notifications', $validated);
if ($response->successful()) {
return redirect()->route('notification')
->with('success', 'Notification created successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to create notification.');
}
}
public function update(Request $request, $id)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'message' => 'required|string',
'type' => 'required|string',
'target_users' => 'required|array',
'schedule_date' => 'nullable|date',
'status' => 'required|string|in:draft,scheduled,sent'
]);
try {
$response = $this->apiService->put("/notifications/{$id}", $validated);
if ($response->successful()) {
return redirect()->route('notification')
->with('success', 'Notification updated successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to update notification.');
}
}
public function destroy($id)
{
try {
$response = $this->apiService->delete("/notifications/{$id}");
if ($response->successful()) {
return redirect()->route('notification')
->with('success', 'Notification deleted successfully.');
}
return back()->with('error', 'Unable to delete notification.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function batchDestroy(Request $request)
{
$ids = $request->validate([
'ids' => 'required|array',
'ids.*' => 'required|integer'
]);
try {
$response = $this->apiService->post("/notifications/batch-delete", $ids);
if ($response->successful()) {
return response()->json(['message' => 'Notifications deleted successfully']);
}
return response()->json(['error' => 'Unable to delete notifications'], 400);
} catch (\Exception $e) {
return response()->json(['error' => 'Service unavailable'], 503);
}
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
class PhotoSliderController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function index()
{
try {
$response = $this->apiService->get('/photo-sliders');
if ($response->successful()) {
return view('pages.home page.photo-slider', [
'sliders' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch photo sliders.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'image' => 'required|image|mimes:jpeg,png,jpg|max:2048',
'order' => 'required|integer|min:1',
'status' => 'required|string|in:active,inactive',
'link_url' => 'nullable|url',
'start_date' => 'required|date',
'end_date' => 'required|date|after:start_date'
]);
try {
// Handle file upload
if ($request->hasFile('image')) {
$path = $request->file('image')->store('photo-sliders', 'public');
$validated['image_path'] = $path;
}
$response = $this->apiService->post('/photo-sliders', $validated);
if ($response->successful()) {
return redirect()->route('photo-slider')
->with('success', 'Photo slider created successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to create photo slider.');
}
}
public function update(Request $request, $id)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'image' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
'order' => 'required|integer|min:1',
'status' => 'required|string|in:active,inactive',
'link_url' => 'nullable|url',
'start_date' => 'required|date',
'end_date' => 'required|date|after:start_date'
]);
try {
// Handle file upload if new image is provided
if ($request->hasFile('image')) {
$path = $request->file('image')->store('photo-sliders', 'public');
$validated['image_path'] = $path;
}
$response = $this->apiService->put("/photo-sliders/{$id}", $validated);
if ($response->successful()) {
return redirect()->route('photo-slider')
->with('success', 'Photo slider updated successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to update photo slider.');
}
}
public function destroy($id)
{
try {
$response = $this->apiService->delete("/photo-sliders/{$id}");
if ($response->successful()) {
return redirect()->route('photo-slider')
->with('success', 'Photo slider deleted successfully.');
}
return back()->with('error', 'Unable to delete photo slider.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function updateOrder(Request $request)
{
$validated = $request->validate([
'orders' => 'required|array',
'orders.*.id' => 'required|integer',
'orders.*.order' => 'required|integer|min:1'
]);
try {
$response = $this->apiService->post("/photo-sliders/reorder", $validated);
if ($response->successful()) {
return response()->json(['message' => 'Order updated successfully']);
}
return response()->json(['error' => 'Unable to update order'], 400);
} catch (\Exception $e) {
return response()->json(['error' => 'Service unavailable'], 503);
}
}
}

View File

@ -0,0 +1,120 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
class PromotionsController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function index()
{
try {
$response = $this->apiService->get('/promotions');
if ($response->successful()) {
return view('pages.promotions', [
'promotions' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch promotions.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'type' => 'required|string',
'startDate' => 'required|date',
'endDate' => 'required|date|after:startDate',
'description' => 'nullable|string',
'terms_conditions' => 'nullable|string',
]);
try {
$response = $this->apiService->post('/promotions', $validated);
if ($response->successful()) {
return redirect()->route('promotions')
->with('success', 'Promotion created successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to create promotion.');
}
}
public function update(Request $request, $id)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'type' => 'required|string',
'startDate' => 'required|date',
'endDate' => 'required|date|after:startDate',
'description' => 'nullable|string',
'terms_conditions' => 'nullable|string',
]);
try {
$response = $this->apiService->put("/promotions/{$id}", $validated);
if ($response->successful()) {
return redirect()->route('promotions')
->with('success', 'Promotion updated successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to update promotion.');
}
}
public function destroy($id)
{
try {
$response = $this->apiService->delete("/promotions/{$id}");
if ($response->successful()) {
return redirect()->route('promotions')
->with('success', 'Promotion deleted successfully.');
}
return back()->with('error', 'Unable to delete promotion.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function batchDestroy(Request $request)
{
$ids = $request->validate([
'ids' => 'required|array',
'ids.*' => 'required|integer'
]);
try {
$response = $this->apiService->post("/promotions/batch-delete", $ids);
if ($response->successful()) {
return response()->json(['message' => 'Promotions deleted successfully']);
}
return response()->json(['error' => 'Unable to delete promotions'], 400);
} catch (\Exception $e) {
return response()->json(['error' => 'Service unavailable'], 503);
}
}
}

View File

@ -0,0 +1,136 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
class ReportsController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function registrationReport(Request $request)
{
$filters = $request->validate([
'start_date' => 'nullable|date',
'end_date' => 'nullable|date|after_or_equal:start_date',
'member_type' => 'nullable|string'
]);
try {
$response = $this->apiService->get('/reports/registrations', $filters);
if ($response->successful()) {
return view('pages.reports.registration-report', [
'report' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch registration report.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function topUpUsageReport(Request $request)
{
$filters = $request->validate([
'start_date' => 'nullable|date',
'end_date' => 'nullable|date|after_or_equal:start_date',
'payment_method' => 'nullable|string'
]);
try {
$response = $this->apiService->get('/reports/top-up-usage', $filters);
if ($response->successful()) {
return view('pages.reports.top-up-usage-report', [
'report' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch top-up usage report.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function mobileUsageReport(Request $request)
{
$filters = $request->validate([
'start_date' => 'nullable|date',
'end_date' => 'nullable|date|after_or_equal:start_date',
'platform' => 'nullable|string'
]);
try {
$response = $this->apiService->get('/reports/mobile-usage', $filters);
if ($response->successful()) {
return view('pages.reports.mobile-usage-report', [
'report' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch mobile usage report.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function stationRatingReport(Request $request)
{
$filters = $request->validate([
'start_date' => 'nullable|date',
'end_date' => 'nullable|date|after_or_equal:start_date',
'station_id' => 'nullable|integer',
'rating' => 'nullable|integer|min:1|max:5'
]);
try {
$response = $this->apiService->get('/reports/station-ratings', $filters);
if ($response->successful()) {
return view('pages.reports.station-rating-report', [
'report' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch station rating report.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function exportReport(Request $request, $reportType)
{
$filters = $request->validate([
'start_date' => 'nullable|date',
'end_date' => 'nullable|date|after_or_equal:start_date',
'format' => 'required|string|in:csv,excel,pdf'
]);
try {
$response = $this->apiService->get("/reports/{$reportType}/export", $filters);
if ($response->successful()) {
return response()->streamDownload(
function () use ($response) {
echo $response->body();
},
"report.{$filters['format']}"
);
}
return back()->with('error', 'Unable to export report.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
}

View File

@ -0,0 +1,148 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
class StationController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function index()
{
try {
$response = $this->apiService->get('/stations');
if ($response->successful()) {
return view('pages.station locator.stations', [
'stations' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch stations.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'address' => 'required|string',
'city' => 'required|string',
'state' => 'required|string',
'postal_code' => 'required|string',
'latitude' => 'required|numeric',
'longitude' => 'required|numeric',
'contact_number' => 'required|string',
'operating_hours' => 'required|string',
'services' => 'required|array',
'status' => 'required|string|in:active,inactive,maintenance'
]);
try {
$response = $this->apiService->post('/stations', $validated);
if ($response->successful()) {
return redirect()->route('stations')
->with('success', 'Station created successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to create station.');
}
}
public function update(Request $request, $id)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'address' => 'required|string',
'city' => 'required|string',
'state' => 'required|string',
'postal_code' => 'required|string',
'latitude' => 'required|numeric',
'longitude' => 'required|numeric',
'contact_number' => 'required|string',
'operating_hours' => 'required|string',
'services' => 'required|array',
'status' => 'required|string|in:active,inactive,maintenance'
]);
try {
$response = $this->apiService->put("/stations/{$id}", $validated);
if ($response->successful()) {
return redirect()->route('stations')
->with('success', 'Station updated successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to update station.');
}
}
public function destroy($id)
{
try {
$response = $this->apiService->delete("/stations/{$id}");
if ($response->successful()) {
return redirect()->route('stations')
->with('success', 'Station deleted successfully.');
}
return back()->with('error', 'Unable to delete station.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function updateFuelPrices(Request $request, $id)
{
$validated = $request->validate([
'fuel_prices' => 'required|array',
'fuel_prices.*.fuel_type' => 'required|string',
'fuel_prices.*.price' => 'required|numeric|min:0',
'effective_date' => 'required|date'
]);
try {
$response = $this->apiService->post("/stations/{$id}/fuel-prices", $validated);
if ($response->successful()) {
return redirect()->route('stations')
->with('success', 'Fuel prices updated successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to update fuel prices.');
}
}
public function getFuelPriceHistory($id)
{
try {
$response = $this->apiService->get("/stations/{$id}/fuel-price-history");
if ($response->successful()) {
return response()->json($response->json());
}
return response()->json(['error' => 'Unable to fetch fuel price history'], 400);
} catch (\Exception $e) {
return response()->json(['error' => 'Service unavailable'], 503);
}
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
class SystemParameterController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function index()
{
try {
$response = $this->apiService->get('/system-parameters');
if ($response->successful()) {
return view('pages.system-parameters', [
'parameters' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch system parameters.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255|unique:system_parameters,name',
'value' => 'required|string',
'type' => 'required|string|in:String,Number,Boolean,JSON',
'description' => 'required|string'
]);
try {
// Format value based on type
$validated['value'] = $this->formatValue($validated['value'], $validated['type']);
$response = $this->apiService->post('/system-parameters', $validated);
if ($response->successful()) {
return redirect()->route('system-parameters')
->with('success', 'System parameter created successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to create system parameter.');
}
}
public function update(Request $request, $id)
{
$validated = $request->validate([
'name' => 'required|string|max:255|unique:system_parameters,name,'.$id,
'value' => 'required|string',
'type' => 'required|string|in:String,Number,Boolean,JSON',
'description' => 'required|string'
]);
try {
// Format value based on type
$validated['value'] = $this->formatValue($validated['value'], $validated['type']);
$response = $this->apiService->put("/system-parameters/{$id}", $validated);
if ($response->successful()) {
return redirect()->route('system-parameters')
->with('success', 'System parameter updated successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to update system parameter.');
}
}
public function destroy($id)
{
try {
$response = $this->apiService->delete("/system-parameters/{$id}");
if ($response->successful()) {
return redirect()->route('system-parameters')
->with('success', 'System parameter deleted successfully.');
}
return back()->with('error', 'Unable to delete system parameter.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
protected function formatValue($value, $type)
{
switch ($type) {
case 'Number':
return is_numeric($value) ? (float) $value : $value;
case 'Boolean':
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
case 'JSON':
return is_string($value) && json_decode($value) ? $value : json_encode($value);
default:
return (string) $value;
}
}
}

View File

@ -0,0 +1,122 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
class TopUpController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function index()
{
try {
$response = $this->apiService->get('/top-ups');
if ($response->successful()) {
return view('pages.top-up', [
'topUps' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch top-up records.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function store(Request $request)
{
$validated = $request->validate([
'member_id' => 'required|string',
'amount' => 'required|numeric|min:0',
'payment_method' => 'required|string',
'reference_number' => 'required|string|unique:top_ups,reference_number',
'status' => 'required|string|in:pending,completed,failed',
'notes' => 'nullable|string'
]);
try {
$response = $this->apiService->post('/top-ups', $validated);
if ($response->successful()) {
return redirect()->route('top-up')
->with('success', 'Top-up transaction created successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to create top-up transaction.');
}
}
public function getSettings()
{
try {
$response = $this->apiService->get('/top-up-settings');
if ($response->successful()) {
return view('pages.top-up-settings', [
'settings' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch top-up settings.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function updateSettings(Request $request)
{
$validated = $request->validate([
'min_amount' => 'required|numeric|min:0',
'max_amount' => 'required|numeric|min:0|gt:min_amount',
'allowed_payment_methods' => 'required|array',
'processing_fee' => 'required|numeric|min:0',
'is_enabled' => 'required|boolean'
]);
try {
$response = $this->apiService->put('/top-up-settings', $validated);
if ($response->successful()) {
return redirect()->route('top-up-settings')
->with('success', 'Top-up settings updated successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to update top-up settings.');
}
}
public function getTransactionHistory(Request $request)
{
$filters = $request->validate([
'start_date' => 'nullable|date',
'end_date' => 'nullable|date|after_or_equal:start_date',
'status' => 'nullable|string|in:pending,completed,failed',
'payment_method' => 'nullable|string'
]);
try {
$response = $this->apiService->get('/top-ups/history', $filters);
if ($response->successful()) {
return response()->json($response->json());
}
return response()->json(['error' => 'Unable to fetch transaction history'], 400);
} catch (\Exception $e) {
return response()->json(['error' => 'Service unavailable'], 503);
}
}
}

View File

@ -0,0 +1,120 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
class TopUpSettingController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function index()
{
try {
$response = $this->apiService->get('/top-up-settings');
if ($response->successful()) {
return view('pages.top-up-settings', [
'settings' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch top-up settings.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255|unique:top_up_settings,name',
'value' => 'required|numeric|min:0',
'description' => 'required|string',
'type' => 'required|string|in:minimum,maximum,fee,bonus',
'status' => 'required|boolean'
]);
try {
$response = $this->apiService->post('/top-up-settings', $validated);
if ($response->successful()) {
return redirect()->route('top-up-settings')
->with('success', 'Top-up setting created successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to create top-up setting.');
}
}
public function update(Request $request, $id)
{
$validated = $request->validate([
'name' => 'required|string|max:255|unique:top_up_settings,name,'.$id,
'value' => 'required|numeric|min:0',
'description' => 'required|string',
'type' => 'required|string|in:minimum,maximum,fee,bonus',
'status' => 'required|boolean'
]);
try {
$response = $this->apiService->put("/top-up-settings/{$id}", $validated);
if ($response->successful()) {
return redirect()->route('top-up-settings')
->with('success', 'Top-up setting updated successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to update top-up setting.');
}
}
public function destroy($id)
{
try {
$response = $this->apiService->delete("/top-up-settings/{$id}");
if ($response->successful()) {
return redirect()->route('top-up-settings')
->with('success', 'Top-up setting deleted successfully.');
}
return back()->with('error', 'Unable to delete top-up setting.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function batchUpdate(Request $request)
{
$validated = $request->validate([
'settings' => 'required|array',
'settings.*.id' => 'required|integer',
'settings.*.value' => 'required|numeric|min:0',
'settings.*.status' => 'required|boolean'
]);
try {
$response = $this->apiService->post('/top-up-settings/batch-update', $validated);
if ($response->successful()) {
return response()->json(['message' => 'Settings updated successfully']);
}
return response()->json(['error' => 'Unable to update settings'], 400);
} catch (\Exception $e) {
return response()->json(['error' => 'Service unavailable'], 503);
}
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Api\ApiService;
use Illuminate\Http\Request;
class UserController extends Controller
{
protected ApiService $apiService;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function index()
{
try {
$response = $this->apiService->get('/users');
if ($response->successful()) {
return view('users.index', [
'users' => $response->json()['data']
]);
}
return back()->with('error', 'Unable to fetch users.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|max:255',
'password' => 'required|min:8',
]);
try {
$response = $this->apiService->post('/users', $validated);
if ($response->successful()) {
return redirect()->route('users.index')
->with('success', 'User created successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to create user.');
}
}
public function update(Request $request, $id)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|max:255',
]);
try {
$response = $this->apiService->put("/users/{$id}", $validated);
if ($response->successful()) {
return redirect()->route('users.index')
->with('success', 'User updated successfully.');
}
return back()->withErrors($response->json()['errors']);
} catch (\Exception $e) {
return back()->with('error', 'Unable to update user.');
}
}
public function destroy($id)
{
try {
$response = $this->apiService->delete("/users/{$id}");
if ($response->successful()) {
return redirect()->route('users.index')
->with('success', 'User deleted successfully.');
}
return back()->with('error', 'Unable to delete user.');
} catch (\Exception $e) {
return back()->with('error', 'Service unavailable.');
}
}
}

View File

@ -0,0 +1,164 @@
<?php
namespace App\Http\Controllers;
use App\Services\Api\CardMemberService;
use Illuminate\Http\Request;
class CardMemberController extends Controller
{
protected $cardMemberService;
public function __construct(CardMemberService $cardMemberService)
{
$this->cardMemberService = $cardMemberService;
}
public function index()
{
$response = $this->cardMemberService->getAllMembers();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.member-management.index', [
'members' => $response['data']
]);
}
public function create()
{
$cardTypes = $this->cardMemberService->getCardTypes();
return view('pages.member-management.create', [
'cardTypes' => $cardTypes['data'] ?? []
]);
}
public function store(Request $request)
{
$response = $this->cardMemberService->createMember($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('card-members.index')
->with('success', 'Card member created successfully');
}
public function edit($id)
{
$memberResponse = $this->cardMemberService->getMember($id);
$cardTypesResponse = $this->cardMemberService->getCardTypes();
if (!$memberResponse['success']) {
return back()->with('error', $memberResponse['message']);
}
return view('pages.member-management.edit', [
'member' => $memberResponse['data'],
'cardTypes' => $cardTypesResponse['data'] ?? []
]);
}
public function update(Request $request, $id)
{
$response = $this->cardMemberService->updateMember($id, $request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('card-members.index')
->with('success', 'Card member updated successfully');
}
public function destroy($id)
{
$response = $this->cardMemberService->deleteMember($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return redirect()->route('card-members.index')
->with('success', 'Card member deleted successfully');
}
public function cardTypes()
{
$response = $this->cardMemberService->getCardTypes();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.add-card-types', [
'cardTypes' => $response['data']
]);
}
public function storeCardType(Request $request)
{
$response = $this->cardMemberService->createCardType($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('card-types.index')
->with('success', 'Card type created successfully');
}
public function updateCardType(Request $request, $id)
{
$response = $this->cardMemberService->updateCardType($id, $request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('card-types.index')
->with('success', 'Card type updated successfully');
}
public function deleteCardType($id)
{
$response = $this->cardMemberService->deleteCardType($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return redirect()->route('card-types.index')
->with('success', 'Card type deleted successfully');
}
public function topUpHistory(Request $request)
{
$memberId = $request->query('member_id');
$response = $this->cardMemberService->getTopUpHistory($memberId);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.top-up', [
'history' => $response['data']
]);
}
public function processTopUp(Request $request)
{
$response = $this->cardMemberService->topUp($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('top-up.index')
->with('success', 'Top-up processed successfully');
}
}

View File

@ -0,0 +1,207 @@
<?php
namespace App\Http\Controllers;
use App\Services\Api\ContentService;
use Illuminate\Http\Request;
class ContentController extends Controller
{
protected $contentService;
public function __construct(ContentService $contentService)
{
$this->contentService = $contentService;
}
// Promotions
public function promotionsIndex()
{
$response = $this->contentService->getAllPromotions();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.promotions', [
'promotions' => $response['data']
]);
}
public function createPromotion()
{
return view('pages.add-promotions');
}
public function storePromotion(Request $request)
{
$response = $this->contentService->createPromotion($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('promotions.index')
->with('success', 'Promotion created successfully');
}
public function updatePromotion(Request $request, $id)
{
$response = $this->contentService->updatePromotion($id, $request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('promotions.index')
->with('success', 'Promotion updated successfully');
}
public function deletePromotion($id)
{
$response = $this->contentService->deletePromotion($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return redirect()->route('promotions.index')
->with('success', 'Promotion deleted successfully');
}
// Notifications
public function notificationsIndex()
{
$response = $this->contentService->getAllNotifications();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.notification', [
'notifications' => $response['data']
]);
}
public function createNotification()
{
return view('pages.add-notification');
}
public function storeNotification(Request $request)
{
$response = $this->contentService->createNotification($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('notifications.index')
->with('success', 'Notification created successfully');
}
public function updateNotification(Request $request, $id)
{
$response = $this->contentService->updateNotification($id, $request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('notifications.index')
->with('success', 'Notification updated successfully');
}
public function deleteNotification($id)
{
$response = $this->contentService->deleteNotification($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return redirect()->route('notifications.index')
->with('success', 'Notification deleted successfully');
}
// Photo Slider
public function slidesIndex()
{
$response = $this->contentService->getAllSlides();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.photo-slider', [
'slides' => $response['data']
]);
}
public function createSlide()
{
return view('pages.add-photo-slider');
}
public function storeSlide(Request $request)
{
$response = $this->contentService->createSlide($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('slides.index')
->with('success', 'Slide created successfully');
}
public function updateSlide(Request $request, $id)
{
$response = $this->contentService->updateSlide($id, $request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('slides.index')
->with('success', 'Slide updated successfully');
}
public function deleteSlide($id)
{
$response = $this->contentService->deleteSlide($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return redirect()->route('slides.index')
->with('success', 'Slide deleted successfully');
}
// Terms and Privacy
public function termsAndPrivacy()
{
$response = $this->contentService->getTermsAndPrivacy();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.add-terms-and-privacy', [
'content' => $response['data']
]);
}
public function updateTermsAndPrivacy(Request $request)
{
$response = $this->contentService->updateTermsAndPrivacy($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('terms-and-privacy.index')
->with('success', 'Terms and Privacy updated successfully');
}
}

View File

@ -0,0 +1,120 @@
<?php
namespace App\Http\Controllers;
use App\Services\Api\FuelPriceService;
use Illuminate\Http\Request;
class FuelPriceController extends Controller
{
protected $fuelPriceService;
public function __construct(FuelPriceService $fuelPriceService)
{
$this->fuelPriceService = $fuelPriceService;
}
public function onDemand()
{
$response = $this->fuelPriceService->getCurrentPrices();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.fuel-price-on-demand', [
'prices' => $response['data']
]);
}
public function updateOnDemand(Request $request)
{
$response = $this->fuelPriceService->updatePrices($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('fuel-price.on-demand')
->with('success', 'Fuel prices updated successfully');
}
public function schedule()
{
$response = $this->fuelPriceService->getScheduledUpdates();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.fuel-price-schedule', [
'schedules' => $response['data']
]);
}
public function storeSchedule(Request $request)
{
$response = $this->fuelPriceService->createSchedule($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('fuel-price.schedule')
->with('success', 'Price update scheduled successfully');
}
public function updateSchedule(Request $request, $id)
{
$response = $this->fuelPriceService->updateSchedule($id, $request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('fuel-price.schedule')
->with('success', 'Schedule updated successfully');
}
public function deleteSchedule($id)
{
$response = $this->fuelPriceService->deleteSchedule($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return redirect()->route('fuel-price.schedule')
->with('success', 'Schedule deleted successfully');
}
public function logs()
{
$response = $this->fuelPriceService->getUpdateLogs();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.fuel-price-update-logs', [
'logs' => $response['data']
]);
}
public function importPrices(Request $request)
{
$response = $this->fuelPriceService->importPrices($request->file('csv_file'));
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('fuel-price.on-demand')
->with('success', 'Fuel prices imported successfully');
}
public function exportPrices()
{
return $this->fuelPriceService->exportPrices();
}
}

View File

@ -0,0 +1,133 @@
<?php
namespace App\Http\Controllers;
use App\Services\Api\StationService;
use Illuminate\Http\Request;
class StationController extends Controller
{
protected $stationService;
public function __construct(StationService $stationService)
{
$this->stationService = $stationService;
}
public function index()
{
$response = $this->stationService->getAllStations();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.station-locator.index', [
'stations' => $response['data']
]);
}
public function create()
{
return view('pages.station-locator.create');
}
public function store(Request $request)
{
$response = $this->stationService->createStation($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('stations.index')
->with('success', 'Station created successfully');
}
public function edit($id)
{
$response = $this->stationService->getStation($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.station-locator.edit', [
'station' => $response['data']
]);
}
public function update(Request $request, $id)
{
$response = $this->stationService->updateStation($id, $request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('stations.index')
->with('success', 'Station updated successfully');
}
public function destroy($id)
{
$response = $this->stationService->deleteStation($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return redirect()->route('stations.index')
->with('success', 'Station deleted successfully');
}
public function fuelPrices()
{
$response = $this->stationService->getFuelPrices();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.fuel-price-on-demand', [
'fuelPrices' => $response['data']
]);
}
public function updateFuelPrices(Request $request)
{
$response = $this->stationService->updateFuelPrices($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('fuel-prices.index')
->with('success', 'Fuel prices updated successfully');
}
public function fuelPriceSchedule()
{
$response = $this->stationService->getFuelPriceSchedule();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.fuel-price-schedule', [
'schedules' => $response['data']
]);
}
public function storeFuelPriceSchedule(Request $request)
{
$response = $this->stationService->createFuelPriceSchedule($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('fuel-prices.schedule')
->with('success', 'Fuel price schedule created successfully');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Http\Controllers;
use App\Services\Api\SystemParameterService;
use Illuminate\Http\Request;
class SystemParameterController extends Controller
{
protected $systemParamService;
public function __construct(SystemParameterService $systemParamService)
{
$this->systemParamService = $systemParamService;
}
public function index()
{
$response = $this->systemParamService->getAllParameters();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.system-parameters', [
'parameters' => $response['data']
]);
}
public function store(Request $request)
{
$response = $this->systemParamService->createParameter($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('system-parameters.index')
->with('success', 'Parameter created successfully');
}
public function update(Request $request, $id)
{
$response = $this->systemParamService->updateParameter($id, $request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('system-parameters.index')
->with('success', 'Parameter updated successfully');
}
public function destroy($id)
{
$response = $this->systemParamService->deleteParameter($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return redirect()->route('system-parameters.index')
->with('success', 'Parameter deleted successfully');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Http\Controllers;
use App\Services\Api\TopUpSettingService;
use Illuminate\Http\Request;
class TopUpSettingController extends Controller
{
protected $topUpSettingService;
public function __construct(TopUpSettingService $topUpSettingService)
{
$this->topUpSettingService = $topUpSettingService;
}
public function index()
{
$response = $this->topUpSettingService->getAllSettings();
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.top-up-settings', [
'settings' => $response['data']
]);
}
public function store(Request $request)
{
$response = $this->topUpSettingService->createSetting($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('top-up-settings.index')
->with('success', 'Setting created successfully');
}
public function update(Request $request, $id)
{
$response = $this->topUpSettingService->updateSetting($id, $request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('top-up-settings.index')
->with('success', 'Setting updated successfully');
}
public function destroy($id)
{
$response = $this->topUpSettingService->deleteSetting($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return redirect()->route('top-up-settings.index')
->with('success', 'Setting deleted successfully');
}
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Services\Api\UserManagementService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
@ -9,62 +10,81 @@ use Illuminate\Support\Facades\Session;
class UserManagementController extends Controller
{
protected $userService;
protected $apiBaseUrl = 'http://192.168.100.6:8081/api'; // Same as in AuthController
public function __construct(UserManagementService $userService)
{
$this->userService = $userService;
}
/**
* Display the user management page with user data
*/
public function index()
{
try {
// Fetch the access token from the session
$user = Session::get('user');
$accessToken = $user['access_token'] ?? null;
$response = $this->userService->getAllUsers();
if (!$accessToken) {
Log::info('No access token found, redirecting to login from user-management');
return redirect()->route('login')->with('error', 'Please log in to view user management.');
if (!$response['success']) {
return back()->with('error', $response['message']);
}
// Make the API call to fetch admin users
$response = Http::withHeaders([
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $accessToken,
])->get("{$this->apiBaseUrl}/cms/admin");
$json = $response->json();
Log::info('User Management API Response: ', $json);
if ($response->successful() && isset($json['data']) && is_array($json['data'])) {
// Transform the API response into the format expected by the table component
$users = array_map(function ($admin) {
return [
'username' => $admin['username'],
'firstName' => $admin['firstname'],
'lastName' => $admin['lastname'],
'role' => 'Admin', // Adjust if the API provides role data
'email' => $admin['email'],
// 'status' => $admin['is_active'] ? 'Active' : 'Inactive',
];
}, $json['data']);
// Pass the transformed data to the view
return view('pages.user-management', [
'users' => $users,
]);
} else {
Log::warning('No user data found or invalid API response: ', $json);
return view('pages.user-management', [
'users' => [], // Pass an empty array if no data
return view('pages.user-management.index', [
'users' => $response['data']
]);
}
} catch (\Exception $e) {
Log::error('Error fetching user data: ' . $e->getMessage());
return view('pages.user-management', [
'users' => [], // Pass an empty array on error
public function create()
{
return view('pages.user-management.create');
}
public function store(Request $request)
{
$response = $this->userService->createUser($request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('user-management.index')
->with('success', 'User created successfully');
}
public function edit($id)
{
$response = $this->userService->getUser($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return view('pages.user-management.edit', [
'user' => $response['data']
]);
}
public function update(Request $request, $id)
{
$response = $this->userService->updateUser($id, $request->all());
if (!$response['success']) {
return back()->withInput()->with('error', $response['message']);
}
return redirect()->route('user-management.index')
->with('success', 'User updated successfully');
}
public function destroy($id)
{
$response = $this->userService->deleteUser($id);
if (!$response['success']) {
return back()->with('error', $response['message']);
}
return redirect()->route('user-management.index')
->with('success', 'User deleted successfully');
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace App\Services\Api;
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\Response;
use Exception;
class ApiService
{
protected string $baseUrl;
public function __construct()
{
// Get the external API URL from environment variable
$this->baseUrl = env('EXTERNAL_API_URL', 'http://localhost:8000/api');
}
/**
* Make a GET request to the API
*/
public function fetch($endpoint, array $params = [])
{
try {
$response = Http::withHeaders($this->getHeaders())
->get($this->baseUrl . $endpoint, $params);
if ($response->successful()) {
return [
'success' => true,
'data' => $response->json()['data'] ?? $response->json(),
'message' => 'Data fetched successfully'
];
}
return [
'success' => false,
'message' => $response->json()['message'] ?? 'Failed to fetch data',
'errors' => $response->json()['errors'] ?? []
];
} catch (Exception $e) {
return [
'success' => false,
'message' => 'API service is unavailable',
'errors' => [$e->getMessage()]
];
}
}
/**
* Make a POST request to the API
*/
public function submit($endpoint, array $data = [])
{
try {
$response = Http::withHeaders($this->getHeaders())
->post($this->baseUrl . $endpoint, $data);
if ($response->successful()) {
return [
'success' => true,
'data' => $response->json()['data'] ?? $response->json(),
'message' => 'Data submitted successfully'
];
}
return [
'success' => false,
'message' => $response->json()['message'] ?? 'Failed to submit data',
'errors' => $response->json()['errors'] ?? []
];
} catch (Exception $e) {
return [
'success' => false,
'message' => 'API service is unavailable',
'errors' => [$e->getMessage()]
];
}
}
/**
* Get the headers for API requests
*/
protected function getHeaders(): array
{
return [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . session('api_token'),
];
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace App\Services\Api;
use Illuminate\Support\Facades\Http;
use Exception;
class BaseApiService
{
protected string $baseUrl;
public function __construct()
{
$this->baseUrl = env('EXTERNAL_API_URL', 'http://localhost:8000/api');
}
protected function get($endpoint, array $params = [])
{
try {
$response = Http::withHeaders($this->getHeaders())
->get($this->baseUrl . $endpoint, $params);
return $this->handleResponse($response);
} catch (Exception $e) {
return $this->handleException($e);
}
}
protected function post($endpoint, array $data = [], $hasFile = false)
{
try {
$http = Http::withHeaders($this->getHeaders());
if ($hasFile) {
$http = $http->asMultipart();
}
$response = $http->post($this->baseUrl . $endpoint, $data);
return $this->handleResponse($response);
} catch (Exception $e) {
return $this->handleException($e);
}
}
protected function handleResponse($response)
{
if ($response->successful()) {
return [
'success' => true,
'data' => $response->json()['data'] ?? $response->json(),
'message' => 'Operation successful'
];
}
return [
'success' => false,
'message' => $response->json()['message'] ?? 'Operation failed',
'errors' => $response->json()['errors'] ?? []
];
}
protected function handleException(Exception $e)
{
return [
'success' => false,
'message' => 'API service is unavailable',
'errors' => [$e->getMessage()]
];
}
protected function getHeaders()
{
return [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . session('api_token')
];
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Services\Api;
class CardMemberService extends BaseApiService
{
public function getAllMembers(array $params = [])
{
return $this->get('/card-members', $params);
}
public function getMember($id)
{
return $this->get("/card-members/{$id}");
}
public function createMember(array $data)
{
return $this->post('/card-members', $data);
}
public function updateMember($id, array $data)
{
return $this->post("/card-members/{$id}", array_merge($data, ['_method' => 'PUT']));
}
public function deleteMember($id)
{
return $this->post("/card-members/{$id}", ['_method' => 'DELETE']);
}
public function getCardTypes()
{
return $this->get('/card-types');
}
public function createCardType(array $data)
{
return $this->post('/card-types', $data);
}
public function updateCardType($id, array $data)
{
return $this->post("/card-types/{$id}", array_merge($data, ['_method' => 'PUT']));
}
public function deleteCardType($id)
{
return $this->post("/card-types/{$id}", ['_method' => 'DELETE']);
}
public function topUp(array $data)
{
return $this->post('/card-members/top-up', $data);
}
public function getTopUpHistory($memberId, array $params = [])
{
return $this->get("/card-members/{$memberId}/top-up-history", $params);
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace App\Services\Api;
class ContentService extends BaseApiService
{
// Promotions
public function getAllPromotions(array $params = [])
{
return $this->get('/promotions', $params);
}
public function createPromotion(array $data)
{
return $this->post('/promotions', $data, true); // true for image upload
}
public function updatePromotion($id, array $data)
{
return $this->post("/promotions/{$id}", array_merge($data, ['_method' => 'PUT']), true);
}
public function deletePromotion($id)
{
return $this->post("/promotions/{$id}", ['_method' => 'DELETE']);
}
// Notifications
public function getAllNotifications(array $params = [])
{
return $this->get('/notifications', $params);
}
public function createNotification(array $data)
{
return $this->post('/notifications', $data);
}
public function updateNotification($id, array $data)
{
return $this->post("/notifications/{$id}", array_merge($data, ['_method' => 'PUT']));
}
public function deleteNotification($id)
{
return $this->post("/notifications/{$id}", ['_method' => 'DELETE']);
}
// Photo Slider
public function getAllSlides(array $params = [])
{
return $this->get('/photo-slider', $params);
}
public function createSlide(array $data)
{
return $this->post('/photo-slider', $data, true); // true for image upload
}
public function updateSlide($id, array $data)
{
return $this->post("/photo-slider/{$id}", array_merge($data, ['_method' => 'PUT']), true);
}
public function deleteSlide($id)
{
return $this->post("/photo-slider/{$id}", ['_method' => 'DELETE']);
}
// Terms and Privacy
public function getTermsAndPrivacy()
{
return $this->get('/terms-and-privacy');
}
public function updateTermsAndPrivacy(array $data)
{
return $this->post('/terms-and-privacy', $data);
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Services\Api;
use Illuminate\Http\UploadedFile;
class FuelPriceService extends BaseApiService
{
public function getCurrentPrices(array $params = [])
{
return $this->get('/fuel-prices/current', $params);
}
public function updatePrices(array $data)
{
return $this->post('/fuel-prices/update', $data);
}
public function getScheduledUpdates(array $params = [])
{
return $this->get('/fuel-prices/schedule', $params);
}
public function createSchedule(array $data)
{
return $this->post('/fuel-prices/schedule', $data);
}
public function updateSchedule($id, array $data)
{
return $this->post("/fuel-prices/schedule/{$id}", array_merge($data, ['_method' => 'PUT']));
}
public function deleteSchedule($id)
{
return $this->post("/fuel-prices/schedule/{$id}", ['_method' => 'DELETE']);
}
public function getUpdateLogs(array $params = [])
{
return $this->get('/fuel-prices/logs', $params);
}
public function importPrices(UploadedFile $file)
{
return $this->post('/fuel-prices/import', [
'file' => $file
], true);
}
public function exportPrices()
{
return $this->get('/fuel-prices/export');
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Services\Api;
class StationService extends BaseApiService
{
public function getAllStations(array $params = [])
{
return $this->get('/stations', $params);
}
public function getStation($id)
{
return $this->get("/stations/{$id}");
}
public function createStation(array $data)
{
return $this->post('/stations', $data, true); // true for file upload support
}
public function updateStation($id, array $data)
{
return $this->post("/stations/{$id}", array_merge($data, ['_method' => 'PUT']), true);
}
public function deleteStation($id)
{
return $this->post("/stations/{$id}", ['_method' => 'DELETE']);
}
public function getFuelPrices(array $params = [])
{
return $this->get('/stations/fuel-prices', $params);
}
public function updateFuelPrices(array $data)
{
return $this->post('/stations/fuel-prices', $data);
}
public function getFuelPriceSchedule(array $params = [])
{
return $this->get('/stations/fuel-prices/schedule', $params);
}
public function createFuelPriceSchedule(array $data)
{
return $this->post('/stations/fuel-prices/schedule', $data);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Services\Api;
class SystemParameterService extends BaseApiService
{
public function getAllParameters(array $params = [])
{
return $this->get('/system-parameters', $params);
}
public function getParameter($id)
{
return $this->get("/system-parameters/{$id}");
}
public function createParameter(array $data)
{
return $this->post('/system-parameters', $data);
}
public function updateParameter($id, array $data)
{
return $this->post("/system-parameters/{$id}", array_merge($data, ['_method' => 'PUT']));
}
public function deleteParameter($id)
{
return $this->post("/system-parameters/{$id}", ['_method' => 'DELETE']);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Services\Api;
class TopUpSettingService extends BaseApiService
{
public function getAllSettings(array $params = [])
{
return $this->get('/top-up-settings', $params);
}
public function getSetting($id)
{
return $this->get("/top-up-settings/{$id}");
}
public function createSetting(array $data)
{
return $this->post('/top-up-settings', $data);
}
public function updateSetting($id, array $data)
{
return $this->post("/top-up-settings/{$id}", array_merge($data, ['_method' => 'PUT']));
}
public function deleteSetting($id)
{
return $this->post("/top-up-settings/{$id}", ['_method' => 'DELETE']);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Services\Api;
class UserManagementService extends BaseApiService
{
public function getAllUsers(array $params = [])
{
return $this->get('/users', $params);
}
public function getUser($id)
{
return $this->get("/users/{$id}");
}
public function createUser(array $data)
{
return $this->post('/users', $data);
}
public function updateUser($id, array $data)
{
return $this->post("/users/{$id}", array_merge($data, ['_method' => 'PUT']));
}
public function deleteUser($id)
{
return $this->post("/users/{$id}", ['_method' => 'DELETE']);
}
public function changePassword(array $data)
{
return $this->post('/users/change-password', $data);
}
}

View File

@ -31,8 +31,9 @@ services:
- DB_DATABASE=unioil-database
- DB_USERNAME=rootuser
- DB_PASSWORD=password
- MYSQL_ROOT_PASSWORD=newpassword
- CACHE_DRIVER=file
- API_URL=http://backend-web:8081
- API_URL=http://nginx:8081
web-frontend:
image: nginx:1.26.3-alpine

View File

@ -1,25 +1,31 @@
{
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite"
"dev": "npm run development",
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "mix --production"
},
"devDependencies": {
"@popperjs/core": "^2.11.6",
"autoprefixer": "^10.4.20",
"axios": "^1.9.0",
"bootstrap": "^5.2.3",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.2.0",
"postcss": "^8.4.47",
"sass": "^1.56.1",
"sass-embedded": "^1.87.0",
"tailwindcss": "^3.4.13",
"vite": "^6.3.4"
"@popperjs/core": "^2.11.8",
"axios": "^1.6.7",
"bootstrap": "^5.3.3",
"jquery": "^3.7.1",
"laravel-mix": "^6.0.49",
"lodash": "^4.17.21",
"resolve-url-loader": "^5.0.0",
"sass": "^1.71.1",
"sass-loader": "^14.1.1"
},
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.5"
"@fortawesome/fontawesome-free": "^6.5.1",
"datatables.net-bs5": "^1.13.7",
"datatables.net-responsive-bs5": "^2.5.0",
"daterangepicker": "^3.1.0",
"moment": "^2.30.1",
"toastr": "^2.1.4"
}
}

View File

@ -777,3 +777,198 @@ footer .copyright {
/* ---------------------------------------------------
MEDIAQUERIES
----------------------------------------------------- */
/* Global Styles */
body {
font-family: 'Roboto', sans-serif;
background-color: #f8f9fa;
}
.wrapper {
display: flex;
width: 100%;
min-height: 100vh;
}
#content {
width: 100%;
padding: 20px;
transition: all 0.3s;
}
/* Card Styles */
.card {
border: none;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.card-header {
background-color: transparent;
border-bottom: 1px solid #eee;
padding: 1.25rem;
}
.card-title {
margin-bottom: 0;
color: #333;
font-weight: 500;
}
/* Table Styles */
.table {
margin-bottom: 0;
}
.table th {
border-top: none;
font-weight: 500;
color: #333;
background-color: #f8f9fa;
}
.table td {
vertical-align: middle;
}
/* Form Styles */
.form-control {
border-radius: 5px;
border: 1px solid #ced4da;
padding: 0.5rem 0.75rem;
}
.form-control:focus {
border-color: #E74610;
box-shadow: 0 0 0 0.2rem rgba(231, 70, 16, 0.25);
}
/* Button Styles */
.btn-primary {
background-color: #E74610;
border-color: #E74610;
}
.btn-primary:hover {
background-color: #d63d0c;
border-color: #d63d0c;
}
.btn-outline-primary {
color: #E74610;
border-color: #E74610;
}
.btn-outline-primary:hover {
background-color: #E74610;
border-color: #E74610;
}
/* Badge Styles */
.badge {
padding: 0.5em 0.75em;
font-weight: 500;
border-radius: 5px;
}
.badge-success {
background-color: #28a745;
}
.badge-danger {
background-color: #dc3545;
}
.badge-warning {
background-color: #ffc107;
color: #333;
}
.badge-info {
background-color: #17a2b8;
}
/* Modal Styles */
.modal-content {
border: none;
border-radius: 10px;
}
.modal-header {
border-bottom: 1px solid #eee;
padding: 1.25rem;
}
.modal-footer {
border-top: 1px solid #eee;
padding: 1.25rem;
}
/* DataTables Styles */
.dataTables_wrapper .dataTables_paginate .paginate_button.current {
background: #E74610;
border-color: #E74610;
color: #fff !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
background: #d63d0c;
border-color: #d63d0c;
color: #fff !important;
}
/* Toastr Styles */
.toast-success {
background-color: #28a745;
}
.toast-error {
background-color: #dc3545;
}
.toast-info {
background-color: #17a2b8;
}
.toast-warning {
background-color: #ffc107;
}
/* Responsive Styles */
@media (max-width: 768px) {
#content {
padding: 15px;
}
.card-header {
padding: 1rem;
}
.table th,
.table td {
padding: 0.5rem;
}
.btn {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
}
}
/* Utility Classes */
.cursor-pointer {
cursor: pointer;
}
.text-primary {
color: #E74610 !important;
}
.bg-primary {
background-color: #E74610 !important;
}
.border-primary {
border-color: #E74610 !important;
}

80
resources/js/app.js Normal file
View File

@ -0,0 +1,80 @@
import './bootstrap';
import UserService from './services/UserService';
import StationService from './services/StationService';
import CardMemberService from './services/CardMemberService';
import ContentService from './services/ContentService';
// Initialize services
window.services = {
user: new UserService(),
station: new StationService(),
cardMember: new CardMemberService(),
content: new ContentService()
};
// Configure global AJAX settings
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
// Configure Toastr notifications
toastr.options = {
closeButton: true,
progressBar: true,
positionClass: 'toast-top-right',
timeOut: 3000
};
// Initialize Bootstrap components
$(function () {
$('[data-bs-toggle="tooltip"]').tooltip();
$('[data-bs-toggle="popover"]').popover();
});
// Sidebar toggle functionality
$(document).ready(function() {
$('#sidebarCollapse').on('click', function() {
$('#sidebar').toggleClass('active');
$('#content').toggleClass('active');
});
$('.more-button,.body-overlay').on('click', function() {
$('#sidebar,.body-overlay').toggleClass('show-nav');
});
});
// Handle file inputs
$(document).on('change', '.custom-file-input', function() {
let fileName = $(this).val().split('\\').pop();
$(this).next('.custom-file-label').addClass("selected").html(fileName);
});
// Handle form validation
$(document).on('submit', 'form.needs-validation', function(event) {
if (!this.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
$(this).addClass('was-validated');
});
// Handle DataTables initialization
$.extend(true, $.fn.dataTable.defaults, {
language: {
search: "_INPUT_",
searchPlaceholder: "Search...",
lengthMenu: "_MENU_ records per page",
info: "Showing _START_ to _END_ of _TOTAL_ records",
infoEmpty: "Showing 0 to 0 of 0 records",
infoFiltered: "(filtered from _MAX_ total records)"
},
dom: '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6"f>>' +
'<"row"<"col-sm-12"tr>>' +
'<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
pageLength: 10,
processing: true,
responsive: true,
stateSave: true
});

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,47 @@
import BaseApiService from './BaseApiService';
export default class CardMemberService extends BaseApiService {
constructor() {
super('/api/card-members');
}
async getCardMembers(params = {}) {
return this.get('', params);
}
async getLockedAccounts(params = {}) {
return this.get('/locked', params);
}
async unlockAccount(id) {
return this.post(`/${id}/unlock`);
}
async getCardTypes(params = {}) {
return this.get('/card-types', params);
}
async createCardType(cardTypeData) {
return this.post('/card-types', cardTypeData);
}
async updateCardType(id, cardTypeData) {
return this.put(`/card-types/${id}`, cardTypeData);
}
async deleteCardType(id) {
return this.delete(`/card-types/${id}`);
}
async getTermsAndPrivacy() {
return this.get('/terms-and-privacy');
}
async updateTermsAndPrivacy(data) {
return this.put('/terms-and-privacy', data);
}
async batchDelete(ids) {
return this.post('/batch-delete', { ids });
}
}

View File

@ -0,0 +1,84 @@
import BaseApiService from './BaseApiService';
export default class ContentService extends BaseApiService {
constructor() {
super('/api/content');
}
// Photo Slider Management
async getPhotoSliders(params = {}) {
return this.get('/photo-sliders', params);
}
async createPhotoSlider(sliderData) {
return this.upload('/photo-sliders', sliderData);
}
async updatePhotoSlider(id, sliderData) {
return this.upload(`/photo-sliders/${id}`, sliderData);
}
async deletePhotoSlider(id) {
return this.delete(`/photo-sliders/${id}`);
}
// Promotions Management
async getPromotions(params = {}) {
return this.get('/promotions', params);
}
async createPromotion(promotionData) {
return this.upload('/promotions', promotionData);
}
async updatePromotion(id, promotionData) {
return this.upload(`/promotions/${id}`, promotionData);
}
async deletePromotion(id) {
return this.delete(`/promotions/${id}`);
}
// System Parameters
async getSystemParameters(params = {}) {
return this.get('/system-parameters', params);
}
async updateSystemParameter(id, paramData) {
return this.put(`/system-parameters/${id}`, paramData);
}
// Top-up Settings
async getTopUpSettings(params = {}) {
return this.get('/top-up-settings', params);
}
async updateTopUpSetting(id, settingData) {
return this.put(`/top-up-settings/${id}`, settingData);
}
// Notifications
async getNotifications(params = {}) {
return this.get('/notifications', params);
}
async createNotification(notificationData) {
return this.post('/notifications', notificationData);
}
async updateNotification(id, notificationData) {
return this.put(`/notifications/${id}`, notificationData);
}
async deleteNotification(id) {
return this.delete(`/notifications/${id}`);
}
async sendNotification(id) {
return this.post(`/notifications/${id}/send`);
}
async batchDelete(type, ids) {
return this.post(`/${type}/batch-delete`, { ids });
}
}

View File

@ -0,0 +1,51 @@
import BaseApiService from './BaseApiService';
export default class StationService extends BaseApiService {
constructor() {
super('/api/stations');
}
async getStations(params = {}) {
return this.get('', params);
}
async createStation(stationData) {
return this.post('', stationData);
}
async updateStation(id, stationData) {
return this.put(`/${id}`, stationData);
}
async deleteStation(id) {
return this.delete(`/${id}`);
}
async getFuelPrices(stationId) {
return this.get(`/${stationId}/fuel-prices`);
}
async updateFuelPrices(stationId, priceData) {
return this.put(`/${stationId}/fuel-prices`, priceData);
}
async schedulePriceUpdate(stationId, scheduleData) {
return this.post(`/${stationId}/schedule-price-update`, scheduleData);
}
async getPriceUpdateLogs(stationId, params = {}) {
return this.get(`/${stationId}/price-update-logs`, params);
}
async getBranches(params = {}) {
return this.get('/branches', params);
}
async getFuelTypes(params = {}) {
return this.get('/fuel-types', params);
}
async batchDelete(ids) {
return this.post('/batch-delete', { ids });
}
}

View File

@ -0,0 +1,43 @@
import BaseApiService from './BaseApiService';
export default class UserService extends BaseApiService {
constructor() {
super('/api/users');
}
async getUsers(params = {}) {
return this.get('', params);
}
async createUser(userData) {
return this.post('', userData);
}
async updateUser(id, userData) {
return this.put(`/${id}`, userData);
}
async deleteUser(id) {
return this.delete(`/${id}`);
}
async getUserProfile() {
return this.get('/profile');
}
async updateProfile(profileData) {
return this.put('/profile', profileData);
}
async updateAvatar(formData) {
return this.upload('/profile/avatar', formData);
}
async changePassword(passwordData) {
return this.post('/profile/change-password', passwordData);
}
async batchDelete(ids) {
return this.post('/batch-delete', { ids });
}
}

View File

@ -2,156 +2,310 @@
'pageTitle' => '',
'data' => [],
'columns' => [],
'allFields' => [],
'actions' => [],
'showAddButton' => false,
'addButtonUrl' => '#',
'showCheckboxes' => false,
'showBatchDelete' => false,
'showEditModal' => false,
'showViewModal' => false
'showViewModal' => false,
'baseRoute' => ''
])
<div class="card-header border-0 bg-transparent">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold text-dark">{{ $pageTitle }}</h5>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title">{{ $pageTitle }}</h5>
@if($showAddButton)
<a href="{{ $addButtonUrl }}" class="btn btn-primary btn-sm px-3">
<i class="fa-solid fa-plus me-1"></i> Add {{ $pageTitle }}
<a href="{{ $addButtonUrl }}" class="btn btn-primary">
<i class="fa-solid fa-plus me-1"></i>Add New
</a>
@endif
</div>
</div>
<div class="card-body">
<!-- Search and Filters -->
<div class="row mb-3 align-items-center">
<div class="col-12 col-md-6 mb-2 mb-md-0">
<div class="input-group input-group-sm">
<span class="input-group-text bg-light border-end-0">
<i class="fa-solid fa-magnifying-glass text-muted"></i>
</span>
<input type="text" class="form-control border-start-0" placeholder="Search..." id="searchInput">
</div>
</div>
<div class="col-12 col-md-6 d-flex justify-content-end">
<button class="btn btn-outline-secondary btn-sm" id="clearFilters">
<i class="fa-solid fa-filter-circle-xmark me-1"></i> Clear Filters
</button>
</div>
</div>
<!-- Table -->
<div class="table-container">
<table class="table table-hover align-middle">
<thead class="table-light" >
<div class="table-responsive">
<table class="table table-hover" id="dataTable">
<thead>
<tr>
@if($showCheckboxes)
<th class="text-center" style="width: 40px;">
<input type="checkbox" id="selectAll">
<th width="50">
<input type="checkbox" class="form-check-input" id="selectAll">
</th>
@endif
@foreach ($columns as $index => $column)
<th class="{{ $column['sortable'] ? 'sortable' : '' }}" data-column="{{ $index + 1 }}">
{{ $column['name'] }}
@if ($column['sortable'])
<i class="fa-solid fa-sort"></i>
@endif
</th>
@foreach($columns as $column)
<th>{{ $column['name'] }}</th>
@endforeach
@if (!empty($actions))
<th class="text-center" style="width: 120px;">Action</th>
@if(count($actions) > 0)
<th width="150">Actions</th>
@endif
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- Batch Delete and Pagination -->
<div class="d-flex justify-content-between align-items-center mt-4">
@if ($showBatchDelete)
<div>
<button class="btn btn-danger btn-sm" id="deleteSelected" disabled>
<i class="fa-solid fa-trash-can me-1"></i> Delete Selected
</button>
</div>
@endif
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm" id="pagination"></ul>
</nav>
</div>
</div>
<!-- Edit Modal -->
@if($showEditModal)
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<!-- Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editModalLabel">Edit {{ $pageTitle }}</h5>
<button type="button" class="modal-close-btn" data-bs-dismiss="modal" aria-label="Close">
<i class="fa-solid fa-xmark"></i>
</button>
<h5 class="modal-title">Edit {{ $pageTitle }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="editForm">
@foreach ($columns as $column)
<div class="modal-body">
@foreach($allFields as $field)
<div class="mb-3">
<label for="edit{{ ucfirst($column['key']) }}" class="form-label">{{ $column['name'] }}</label>
@if ($column['key'] === 'status')
<select class="form-select" id="edit{{ ucfirst($column['key']) }}">
<option value="Active">Active</option>
<option value="Inactive">Inactive</option>
<label class="form-label">{{ $field['name'] }}</label>
@if($field['type'] === 'select')
<select class="form-select" name="{{ $field['key'] }}" required="{{ $field['required'] ?? false }}">
@foreach($field['options'] as $option)
<option value="{{ $option }}">{{ $option }}</option>
@endforeach
</select>
@elseif($field['type'] === 'textarea')
<textarea class="form-control" name="{{ $field['key'] }}" rows="3" required="{{ $field['required'] ?? false }}"></textarea>
@else
<input type="{{ $column['key'] === 'email' ? 'email' : 'text' }}"
<input type="{{ $field['type'] }}"
class="form-control"
id="edit{{ ucfirst($column['key']) }}"
{{ $column['key'] === 'username' || $column['key'] === 'memberId' ? 'readonly' : '' }}>
name="{{ $field['key'] }}"
required="{{ $field['required'] ?? false }}"
@if(isset($field['min'])) min="{{ $field['min'] }}" @endif
@if(isset($field['step'])) step="{{ $field['step'] }}" @endif>
@endif
</div>
@endforeach
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="updateBtn">Update</button>
</div>
</div>
</div>
</div>
@endif
<!-- View Modal -->
@if($showViewModal)
<div class="modal fade" id="viewModal" tabindex="-1" aria-labelledby="viewModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<!-- View Modal -->
<div class="modal fade" id="viewModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="viewModalLabel">View {{ $pageTitle }} Details</h5>
<button type="button" class="modal-close-btn" data-bs-dismiss="modal" aria-label="Close">
<i class="fa-solid fa-xmark"></i>
</button>
<h5 class="modal-title">View {{ $pageTitle }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="view-details">
@foreach ($columns as $column)
<p><strong>{{ $column['name'] }}:</strong> <span id="view{{ ucfirst($column['key']) }}"></span></p>
@foreach($allFields as $field)
<div class="mb-3">
<label class="fw-bold">{{ $field['name'] }}:</label>
<div id="view_{{ $field['key'] }}"></div>
</div>
@endforeach
</div>
</div>
<div class="modal-footer">
@if ($pageTitle === 'Locked Account')
<button type="button" class="btn btn-success" id="activateAccountBtn">Activate Account</button>
@endif
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
@endif
@push('scripts')
<script>
$(document).ready(function() {
// Initialize DataTable
const table = $('#dataTable').DataTable({
processing: true,
serverSide: true,
ajax: {
url: '{{ route($baseRoute . ".data") }}',
type: 'GET'
},
columns: [
@if($showCheckboxes)
{
data: null,
orderable: false,
searchable: false,
render: function() {
return '<input type="checkbox" class="form-check-input row-checkbox">';
}
},
@endif
@foreach($columns as $column)
{
data: '{{ $column["key"] }}',
name: '{{ $column["key"] }}',
render: function(data, type, row) {
if ('{{ $column["key"] }}' === 'status') {
const colors = {
'Active': 'success',
'Inactive': 'danger',
'Pending': 'warning',
'Completed': 'success',
'Cancelled': 'danger'
};
return `<span class="badge bg-${colors[data] || 'secondary'}">${data}</span>`;
}
@if(isset($column['format']) && $column['format'] === 'currency')
return new Intl.NumberFormat('en-PH', {
style: 'currency',
currency: 'PHP'
}).format(data);
@elseif(isset($column['format']) && $column['format'] === 'date')
return moment(data).format('YYYY-MM-DD HH:mm:ss');
@else
return data;
@endif
}
},
@endforeach
@if(count($actions) > 0)
{
data: null,
orderable: false,
searchable: false,
render: function(data, type, row) {
let actions = '';
@foreach($actions as $action)
@if($action === 'edit')
actions += `<button class="btn btn-sm btn-primary me-1 edit-btn" data-id="${row.id}">
<i class="fa-solid fa-pen-to-square"></i>
</button>`;
@elseif($action === 'view')
actions += `<button class="btn btn-sm btn-info me-1 view-btn" data-id="${row.id}">
<i class="fa-solid fa-eye"></i>
</button>`;
@elseif($action === 'delete')
actions += `<button class="btn btn-sm btn-danger delete-btn" data-id="${row.id}">
<i class="fa-solid fa-trash"></i>
</button>`;
@endif
@endforeach
return actions;
}
}
@endif
],
order: [[1, 'desc']]
});
// Select All Checkbox
$('#selectAll').change(function() {
$('.row-checkbox').prop('checked', $(this).prop('checked'));
updateBatchDeleteButton();
});
$(document).on('change', '.row-checkbox', function() {
updateBatchDeleteButton();
});
function updateBatchDeleteButton() {
const checkedCount = $('.row-checkbox:checked').length;
$('#batchDeleteBtn').prop('disabled', checkedCount === 0);
}
// Edit Record
$(document).on('click', '.edit-btn', function() {
const id = $(this).data('id');
$.get(`{{ route($baseRoute . ".show", ":id") }}`.replace(':id', id), function(data) {
$('#editForm').data('id', id);
@foreach($allFields as $field)
$(`#editForm [name="{{ $field['key'] }}"]`).val(data.{{ $field['key'] }});
@endforeach
$('#editModal').modal('show');
});
});
// View Record
$(document).on('click', '.view-btn', function() {
const id = $(this).data('id');
$.get(`{{ route($baseRoute . ".show", ":id") }}`.replace(':id', id), function(data) {
@foreach($allFields as $field)
@if($field['type'] === 'currency')
$('#view_{{ $field["key"] }}').text(new Intl.NumberFormat('en-PH', {
style: 'currency',
currency: 'PHP'
}).format(data.{{ $field['key'] }}));
@elseif($field['type'] === 'date')
$('#view_{{ $field["key"] }}').text(moment(data.{{ $field['key'] }}).format('YYYY-MM-DD HH:mm:ss'));
@else
$('#view_{{ $field["key"] }}').text(data.{{ $field['key'] }});
@endif
@endforeach
$('#viewModal').modal('show');
});
});
// Delete Record
$(document).on('click', '.delete-btn', function() {
const id = $(this).data('id');
if (confirm('Are you sure you want to delete this record?')) {
$.ajax({
url: `{{ route($baseRoute . ".destroy", ":id") }}`.replace(':id', id),
type: 'DELETE',
success: function() {
table.ajax.reload();
toastr.success('Record deleted successfully');
},
error: function() {
toastr.error('Failed to delete record');
}
});
}
});
// Submit Edit Form
$('#editForm').submit(function(e) {
e.preventDefault();
const id = $(this).data('id');
$.ajax({
url: `{{ route($baseRoute . ".update", ":id") }}`.replace(':id', id),
type: 'PUT',
data: $(this).serialize(),
success: function() {
$('#editModal').modal('hide');
table.ajax.reload();
toastr.success('Record updated successfully');
},
error: function(xhr) {
const errors = xhr.responseJSON.errors;
Object.keys(errors).forEach(key => {
toastr.error(errors[key][0]);
});
}
});
});
// Batch Delete
$('#batchDeleteBtn').click(function() {
const ids = $('.row-checkbox:checked').map(function() {
return $(this).closest('tr').find('.delete-btn').data('id');
}).get();
if (ids.length && confirm('Are you sure you want to delete the selected records?')) {
$.ajax({
url: '{{ route($baseRoute . ".batch-destroy") }}',
type: 'DELETE',
data: { ids: ids },
success: function() {
table.ajax.reload();
$('#selectAll').prop('checked', false);
updateBatchDeleteButton();
toastr.success('Records deleted successfully');
},
error: function() {
toastr.error('Failed to delete records');
}
});
}
});
});
</script>
@endpush
<style>
.card,
@ -369,399 +523,3 @@
}
</style>
<!-- JavaScript -->
<script>
const tableConfig = {
data: @json($data),
columns: @json($columns),
actions: @json($actions),
showCheckboxes: {{ json_encode($showCheckboxes) }},
showBatchDelete: {{ json_encode($showBatchDelete) }},
showEditModal: {{ json_encode($showEditModal) }},
showViewModal: {{ json_encode($showViewModal) }},
pageTitle: @json($pageTitle)
};
const rowsPerPage = 5;
let currentPage = 1;
let filteredRows = [...tableConfig.data];
let originalRows = [...tableConfig.data].map(row => ({ ...row }));
let sortDirection = {};
let currentRowId = null; // Track the row being viewed
function renderTable() {
const tableBody = document.getElementById('tableBody');
if (!tableBody) return;
tableBody.innerHTML = '';
const start = (currentPage - 1) * rowsPerPage;
const end = start + rowsPerPage;
const paginatedRows = filteredRows.slice(start, end);
paginatedRows.forEach(row => {
const tr = document.createElement('tr');
tr.setAttribute('data-id', row.id);
let rowHtml = '';
if (tableConfig.showCheckboxes) {
rowHtml += `<td class="text-center"><input type="checkbox" class="rowCheckbox"></td>`;
}
tableConfig.columns.forEach(col => {
if (col.key === 'status') {
rowHtml += `
<td>
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary status-btn" type="button" id="statusDropdown${row.id}" data-bs-toggle="dropdown" aria-expanded="false">
<span class="status-text">${row[col.key]}</span>
</button>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-sm" aria-labelledby="statusDropdown${row.id}">
<li>
<a class="dropdown-item d-flex align-items-center gap-2 status-option" href="#" data-status="Active">
<i class="fa-solid fa-check text-success" style="font-size: 14px;"></i>
<span>Active</span>
</a>
</li>
<li>
<a class="dropdown-item d-flex align-items-center gap-2 status-option" href="#" data-status="Inactive">
<i class="fa-solid fa-xmark text-danger" style="font-size: 14px;"></i>
<span>Inactive</span>
</a>
</li>
</ul>
</div>
</td>`;
} else {
rowHtml += `<td>${row[col.key] || ''}</td>`;
}
});
if (tableConfig.actions.length > 0) {
rowHtml += `<td class="text-center">`;
tableConfig.actions.forEach(action => {
if (action === 'edit' && tableConfig.showEditModal) {
rowHtml += `
<a href="#" class="btn btn-sm edit-btn me-1" title="Edit" data-id="${row.id}">
<i class="fa-solid fa-pen-to-square"></i>
</a>`;
} else if (action === 'view' && tableConfig.showViewModal) {
rowHtml += `
<a href="#" class="btn btn-sm btn-outline-secondary me-1 view-btn" title="View" data-id="${row.id}">
<i class="fa-solid fa-eye"></i>
</a>`;
} else if (action === 'delete') {
rowHtml += `
<button class="btn btn-sm btn-outline-danger delete-btn" title="Delete" data-id="${row.id}">
<i class="fa-solid fa-trash-can"></i>
</button>`;
}
});
rowHtml += `</td>`;
}
tr.innerHTML = rowHtml;
tableBody.appendChild(tr);
});
attachEventListeners();
updateDeleteButtonState();
}
function renderPagination() {
const pagination = document.getElementById('pagination');
if (!pagination) return;
pagination.innerHTML = '';
const pageCount = Math.ceil(filteredRows.length / rowsPerPage);
const prevLi = document.createElement('li');
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
prevLi.innerHTML = `<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">«</span></a>`;
prevLi.addEventListener('click', (e) => {
e.preventDefault();
if (currentPage > 1) {
currentPage--;
renderTable();
renderPagination();
}
});
pagination.appendChild(prevLi);
for (let i = 1; i <= pageCount; i++) {
const li = document.createElement('li');
li.className = `page-item ${currentPage === i ? 'active' : ''}`;
li.innerHTML = `<a class="page-link" href="#">${i}</a>`;
li.addEventListener('click', (e) => {
e.preventDefault();
currentPage = i;
renderTable();
renderPagination();
});
pagination.appendChild(li);
}
const nextLi = document.createElement('li');
nextLi.className = `page-item ${currentPage === pageCount ? 'disabled' : ''}`;
nextLi.innerHTML = `<a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">»</span></a>`;
nextLi.addEventListener('click', (e) => {
e.preventDefault();
if (currentPage < pageCount) {
currentPage++;
renderTable();
renderPagination();
}
});
pagination.appendChild(nextLi);
}
function attachEventListeners() {
// Search
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
filteredRows = tableConfig.data.filter(row => {
return Object.values(row).some(value =>
value && value.toString().toLowerCase().includes(searchTerm)
);
});
currentPage = 1;
renderTable();
renderPagination();
});
}
// Sort
document.querySelectorAll('.sortable').forEach(header => {
header.addEventListener('click', function() {
const columnIndex = parseInt(this.getAttribute('data-column')) - 1;
const key = tableConfig.columns[columnIndex].key;
sortDirection[columnIndex] = !sortDirection[columnIndex] ? 'asc' : sortDirection[columnIndex] === 'asc' ? 'desc' : 'asc';
document.querySelectorAll('.sortable i').forEach(icon => {
icon.classList.remove('fa-sort-up', 'fa-sort-down');
icon.classList.add('fa-sort');
});
const icon = this.querySelector('i');
if (icon) {
icon.classList.remove('fa-sort');
icon.classList.add(sortDirection[columnIndex] === 'asc' ? 'fa-sort-up' : 'fa-sort-down');
}
filteredRows.sort((a, b) => {
const aValue = (a[key] || '').toString().toLowerCase();
const bValue = (b[key] || '').toString().toLowerCase();
return sortDirection[columnIndex] === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
});
currentPage = 1;
renderTable();
renderPagination();
});
});
// Clear Filters
const clearFilters = document.getElementById('clearFilters');
if (clearFilters) {
clearFilters.addEventListener('click', function() {
if (searchInput) searchInput.value = '';
document.querySelectorAll('.sortable i').forEach(icon => {
icon.classList.remove('fa-sort-up', 'fa-sort-down');
icon.classList.add('fa-sort');
});
sortDirection = {};
filteredRows = [...tableConfig.data];
currentPage = 1;
renderTable();
renderPagination();
});
}
// Checkboxes
if (tableConfig.showCheckboxes) {
const selectAll = document.getElementById('selectAll');
if (selectAll) {
selectAll.addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.rowCheckbox');
checkboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
updateDeleteButtonState();
});
}
document.querySelectorAll('.rowCheckbox').forEach(checkbox => {
checkbox.addEventListener('change', updateDeleteButtonState);
});
}
// Batch Delete
if (tableConfig.showBatchDelete) {
const deleteSelected = document.getElementById('deleteSelected');
if (deleteSelected) {
deleteSelected.addEventListener('click', function() {
if (confirm('Are you sure you want to delete the selected items?')) {
const checkboxes = document.querySelectorAll('.rowCheckbox');
const selectedIds = Array.from(checkboxes)
.filter(cb => cb.checked)
.map(cb => cb.closest('tr').getAttribute('data-id'));
filteredRows = filteredRows.filter(row => !selectedIds.includes(row.id.toString()));
tableConfig.data = tableConfig.data.filter(row => !selectedIds.includes(row.id.toString()));
originalRows = originalRows.filter(row => !selectedIds.includes(row.id.toString()));
const maxPage = Math.ceil(filteredRows.length / rowsPerPage);
if (currentPage > maxPage) {
currentPage = maxPage || 1;
}
renderTable();
renderPagination();
if (selectAll) selectAll.checked = false;
}
});
}
}
// Status
document.querySelectorAll('.status-option').forEach(option => {
option.addEventListener('click', function(e) {
e.preventDefault();
const newStatus = this.getAttribute('data-status');
const row = this.closest('tr');
const userId = row.getAttribute('data-id');
const statusText = row.querySelector('.status-text');
if (statusText) {
statusText.textContent = newStatus;
}
const rowData = filteredRows.find(r => r.id == userId);
if (rowData) {
rowData.status = newStatus;
}
});
});
// Edit
if (tableConfig.showEditModal) {
document.querySelectorAll('.edit-btn').forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault();
const userId = this.getAttribute('data-id');
const user = filteredRows.find(row => row.id == userId);
if (user) {
tableConfig.columns.forEach(col => {
const input = document.getElementById(`edit${col.key.charAt(0).toUpperCase() + col.key.slice(1)}`);
if (input) {
input.value = user[col.key] || '';
}
});
const modal = new bootstrap.Modal(document.getElementById('editModal'));
modal.show();
}
});
});
const updateBtn = document.getElementById('updateBtn');
if (updateBtn) {
updateBtn.addEventListener('click', function() {
const userId = document.querySelector('.edit-btn[data-id]')?.getAttribute('data-id');
const user = filteredRows.find(row => row.id == userId);
if (user) {
tableConfig.columns.forEach(col => {
const input = document.getElementById(`edit${col.key.charAt(0).toUpperCase() + col.key.slice(1)}`);
if (input && !input.readOnly) {
user[col.key] = input.value;
}
});
}
const modal = bootstrap.Modal.getInstance(document.getElementById('editModal'));
modal.hide();
renderTable();
});
}
}
// View
if (tableConfig.showViewModal) {
document.querySelectorAll('.view-btn').forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault();
currentRowId = this.getAttribute('data-id');
const user = filteredRows.find(row => row.id == currentRowId);
if (user) {
tableConfig.columns.forEach(col => {
const span = document.getElementById(`view${col.key.charAt(0).toUpperCase() + col.key.slice(1)}`);
if (span) {
span.textContent = user[col.key] || '';
}
});
const modal = new bootstrap.Modal(document.getElementById('viewModal'));
modal.show();
}
});
});
// Activate Account for Locked Account page
if (tableConfig.pageTitle === 'Locked Account') {
const activateAccountBtn = document.getElementById('activateAccountBtn');
if (activateAccountBtn) {
activateAccountBtn.addEventListener('click', function() {
if (currentRowId) {
const user = filteredRows.find(row => row.id == currentRowId);
if (user) {
user.status = 'Active';
}
const modal = bootstrap.Modal.getInstance(document.getElementById('viewModal'));
modal.hide();
renderTable();
currentRowId = null;
}
});
}
}
}
// Delete
document.querySelectorAll('.delete-btn').forEach(button => {
button.addEventListener('click', function() {
const userId = this.getAttribute('data-id');
if (confirm('Are you sure you want to delete this item?')) {
filteredRows = filteredRows.filter(row => row.id != userId);
tableConfig.data = tableConfig.data.filter(row => row.id != userId);
originalRows = originalRows.filter(row => row.id != userId);
const maxPage = Math.ceil(filteredRows.length / rowsPerPage);
if (currentPage > maxPage) {
currentPage = maxPage || 1;
}
renderTable();
renderPagination();
}
});
});
}
function updateDeleteButtonState() {
if (tableConfig.showBatchDelete) {
const checkboxes = document.querySelectorAll('.rowCheckbox');
const checkedCount = Array.from(checkboxes).filter(cb => cb.checked).length;
const deleteButton = document.getElementById('deleteSelected');
if (deleteButton) {
deleteButton.disabled = checkedCount < 2;
}
}
}
renderTable();
renderPagination();
</script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

View File

@ -5,32 +5,21 @@
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>CMS-Laravel</title>
<link rel="stylesheet" href="{{ asset('css/bootstrap.min.css') }}">
<link rel="stylesheet" href="{{ asset('css/custom.css') }}">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700;900&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" href="{{ asset('img/favicon_unioil.ico') }}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
integrity="sha512-..." crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- Inline CSS to fix sidebar spacing -->
<style>
#sidebar .components li {
margin-bottom: 5px; /* Reduce gap between sidebar items */
}
#sidebar .components li.dropdown {
margin-bottom: 0; /* Ensure dropdown items don't add extra space when collapsed */
}
#sidebar .components li.dropdown .menu {
margin-top: 0; /* Remove extra space above dropdown menu */
}
#sidebar .components li a {
padding: 10px 15px; /* Consistent padding for all sidebar links */
}
</style>
<!-- CSS Dependencies -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ asset('css/custom.css') }}">
<link rel="icon" type="image/png" href="{{ asset('img/favicon_unioil.ico') }}">
@stack('styles')
</head>
<body>
@ -272,20 +261,49 @@
<footer class="bg-light py-3 mt-4">
<div class="container text-center">
<p class="mb-0 text-muted" style="font-family: 'Roboto', sans-serif; font-size: 0.9rem;">
All Rights Reserved &copy; 2025 Unioil CMS
All Rights Reserved &copy; {{ date('Y') }} Unioil CMS
</p>
</div>
</footer>
</div>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="js/jquery-3.3.1.slim.min.js"></script>
<script src="js/popper.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
<!-- JavaScript Dependencies -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-validation@1.20.0/dist/jquery.validate.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!-- Initialize global settings -->
<script>
// Set up CSRF token for all AJAX requests
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
// Configure toastr notifications
toastr.options = {
closeButton: true,
progressBar: true,
positionClass: 'toast-top-right',
timeOut: 3000
};
// Initialize Bootstrap tooltips and popovers
$(function () {
$('[data-bs-toggle="tooltip"]').tooltip();
$('[data-bs-toggle="popover"]').popover();
});
// Sidebar toggle functionality
$(document).ready(function() {
$('#sidebarCollapse').on('click', function() {
$('#sidebar').toggleClass('active');
@ -297,6 +315,7 @@
});
});
</script>
@stack('scripts')
</body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</html>

View File

@ -0,0 +1,94 @@
<!-- Top Navbar -->
<nav class="navbar navbar-expand-lg bg-white shadow-sm">
<div class="container-fluid">
<button type="button" id="sidebarCollapse" class="btn">
<i class="fa-solid fa-bars"></i>
</button>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent">
<i class="fa-solid fa-ellipsis-vertical"></i>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fa-solid fa-user-circle me-1"></i>
<span>{{ auth()->user()->name ?? 'Guest' }}</span>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="{{ route('my-profile') }}">
<i class="fa-solid fa-user me-2"></i>My Profile
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<form action="{{ route('logout') }}" method="POST">
@csrf
<button type="submit" class="dropdown-item text-danger">
<i class="fa-solid fa-right-from-bracket me-2"></i>Logout
</button>
</form>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<style>
.navbar {
padding: 15px;
background-color: #fff;
border-bottom: 1px solid #eee;
}
#sidebarCollapse {
padding: 5px 10px;
background: transparent;
border: none;
color: #333;
}
#sidebarCollapse:hover {
color: #E74610;
}
.navbar-nav .nav-link {
color: #333;
padding: 8px 15px;
}
.navbar-nav .nav-link:hover {
color: #E74610;
}
.dropdown-menu {
border: none;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
padding: 0.5rem 0;
}
.dropdown-item {
padding: 0.5rem 1rem;
color: #333;
}
.dropdown-item:hover {
background-color: #f8f9fa;
color: #E74610;
}
.dropdown-item.text-danger:hover {
background-color: #dc3545;
color: #fff;
}
@media (max-width: 768px) {
.navbar {
padding: 10px;
}
}
</style>

View File

@ -0,0 +1,138 @@
<!-- Sidebar -->
<nav id="sidebar">
<div class="sidebar-header">
<h3><img src="{{ asset('img/logo.png') }}" class="img-fluid" alt="Logo" /><span></span></h3>
</div>
<ul class="list-unstyled components">
<li class="{{ Request::is('user-management') ? 'active' : '' }}">
<a href="{{ route('user.management') }}" class="dashboard">
<i class="fa-solid fa-users"></i><span>User Management</span>
</a>
</li>
<li class="{{ Request::is('notification') ? 'active' : '' }}">
<a href="{{ route('notification') }}" class="dashboard">
<i class="fa-solid fa-bell"></i><span>Notifications</span>
</a>
</li>
<li class="dropdown {{ Request::is('card-member', 'locked-accounts') ? 'active' : '' }}">
<a href="#memberManagement" data-bs-toggle="collapse" aria-expanded="false" class="dropdown-toggle">
<i class="fa-solid fa-users-gear"></i><span>Member Management</span>
</a>
<ul class="collapse list-unstyled menu" id="memberManagement">
<li class="{{ Request::is('card-member') ? 'active' : '' }}">
<a href="{{ route('card-member') }}">Card Member</a>
</li>
<li class="{{ Request::is('locked-accounts') ? 'active' : '' }}">
<a href="{{ route('locked-accounts') }}">Locked Accounts</a>
</li>
</ul>
</li>
<li class="{{ Request::is('top-up-settings') ? 'active' : '' }}">
<a href="{{ route('top-up-settings') }}" class="dashboard">
<i class="fa-solid fa-cog"></i><span>Top Up Settings</span>
</a>
</li>
<li class="{{ Request::is('system-parameters') ? 'active' : '' }}">
<a href="{{ route('system-parameters') }}">
<i class="fa-solid fa-sliders"></i><span>System Parameters</span>
</a>
</li>
<li class="dropdown {{ Request::is('fuel-price-*') ? 'active' : '' }}">
<a href="#fuelPriceUpdate" data-bs-toggle="collapse" aria-expanded="false" class="dropdown-toggle">
<i class="fa-solid fa-gas-pump"></i><span>Fuel Price Update</span>
</a>
<ul class="collapse list-unstyled menu" id="fuelPriceUpdate">
<li class="{{ Request::is('fuel-price-on-demand') ? 'active' : '' }}">
<a href="{{ route('fuel-price-on-demand') }}">On-Demand</a>
</li>
<li class="{{ Request::is('fuel-price-schedule') ? 'active' : '' }}">
<a href="{{ route('fuel-price-schedule') }}">Schedule</a>
</li>
<li class="{{ Request::is('fuel-price-update-logs') ? 'active' : '' }}">
<a href="{{ route('fuel-price-update-logs') }}">Update Logs</a>
</li>
</ul>
</li>
</ul>
</nav>
<style>
#sidebar {
min-width: 250px;
max-width: 250px;
background: #fff;
transition: all 0.3s;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
#sidebar.active {
margin-left: -250px;
}
#sidebar .sidebar-header {
padding: 20px;
background: #fff;
border-bottom: 1px solid #eee;
}
#sidebar .sidebar-header img {
max-height: 40px;
}
#sidebar ul.components {
padding: 20px 0;
}
#sidebar ul li {
margin-bottom: 5px;
}
#sidebar ul li a {
padding: 10px 20px;
display: flex;
align-items: center;
gap: 10px;
color: #333;
text-decoration: none;
transition: all 0.3s;
}
#sidebar ul li a:hover,
#sidebar ul li.active > a {
background: #E74610;
color: #fff;
}
#sidebar ul li a i {
width: 20px;
text-align: center;
}
#sidebar .dropdown-toggle::after {
margin-left: auto;
}
#sidebar ul.menu {
padding-left: 35px;
}
#sidebar ul.menu li a {
padding: 8px 15px;
font-size: 0.9rem;
}
@media (max-width: 768px) {
#sidebar {
margin-left: -250px;
}
#sidebar.active {
margin-left: 0;
}
}
</style>

View File

@ -3,84 +3,287 @@
@section('page_title', 'Add Station')
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ isset($station) ? 'Edit Station' : 'Add New Station' }}</h3>
</div>
<div class="card-body">
@include('components.alerts')
<div class="card-header border-0 bg-transparent py-2">
<h5 class="mb-0 fw-bold text-dark" style="font-size: 1.25rem;">Add New Station</h5>
</div>
<div class="card-body py-2">
<form action="#" method="POST" id="addStationForm">
<form id="stationForm"
action="{{ isset($station) ? route('stations.update', $station['id']) : route('stations.store') }}"
method="POST"
enctype="multipart/form-data"
class="needs-validation"
novalidate>
@csrf
<div class="mb-2">
<label for="stationCode" class="form-label mb-1">Station Code</label>
<input type="text" class="form-control form-control-sm" id="stationCode" name="stationCode" required>
@if(isset($station))
@method('PUT')
@endif
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="name">Station Name <span class="text-danger">*</span></label>
<input type="text"
class="form-control"
id="name"
name="name"
value="{{ old('name', $station['name'] ?? '') }}"
required>
</div>
<div class="mb-2">
<label for="stationName" class="form-label mb-1">Station Name</label>
<input type="text" class="form-control form-control-sm" id="stationName" name="stationName" required>
</div>
<div class="mb-2">
<label for="stationLocation" class="form-label mb-1">Station Location</label>
<input type="text" class="form-control form-control-sm" id="stationLocation" name="stationLocation" required>
<div class="col-md-6">
<div class="form-group">
<label for="code">Station Code <span class="text-danger">*</span></label>
<input type="text"
class="form-control"
id="code"
name="code"
value="{{ old('code', $station['code'] ?? '') }}"
required>
</div>
<div class="mb-2">
<label for="mapUrl" class="form-label mb-1">Map URL</label>
<input type="url" class="form-control form-control-sm" id="mapUrl" name="mapUrl" required>
</div>
<div class="mb-2">
<label for="latitude" class="form-label mb-1">Latitude</label>
<input type="text" class="form-control form-control-sm" id="latitude" name="latitude" required>
</div>
<div class="mb-2">
<label for="longitude" class="form-label mb-1">Longitude</label>
<input type="text" class="form-control form-control-sm" id="longitude" name="longitude" required>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="address">Address <span class="text-danger">*</span></label>
<textarea class="form-control"
id="address"
name="address"
rows="3"
required>{{ old('address', $station['address'] ?? '') }}</textarea>
</div>
<div class="mb-2">
<label for="contactNumber" class="form-label mb-1">Contact Number</label>
<input type="text" class="form-control form-control-sm" id="contactNumber" name="contactNumber" required>
</div>
<div class="mb-2">
<label for="branch" class="form-label mb-1">Branch</label>
<input type="text" class="form-control form-control-sm" id="branch" name="branch" required>
<div class="col-md-6">
<div class="form-group">
<label for="region">Region <span class="text-danger">*</span></label>
<select class="form-control"
id="region"
name="region"
required>
<option value="">Select Region</option>
@foreach($regions ?? [] as $region)
<option value="{{ $region['id'] }}"
{{ old('region', $station['region_id'] ?? '') == $region['id'] ? 'selected' : '' }}>
{{ $region['name'] }}
</option>
@endforeach
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="latitude">Latitude <span class="text-danger">*</span></label>
<input type="number"
class="form-control"
id="latitude"
name="latitude"
step="any"
value="{{ old('latitude', $station['latitude'] ?? '') }}"
required>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="longitude">Longitude <span class="text-danger">*</span></label>
<input type="number"
class="form-control"
id="longitude"
name="longitude"
step="any"
value="{{ old('longitude', $station['longitude'] ?? '') }}"
required>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="status">Status <span class="text-danger">*</span></label>
<select class="form-control"
id="status"
name="status"
required>
<option value="active" {{ old('status', $station['status'] ?? '') == 'active' ? 'selected' : '' }}>Active</option>
<option value="inactive" {{ old('status', $station['status'] ?? '') == 'inactive' ? 'selected' : '' }}>Inactive</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="image">Station Image</label>
<input type="file"
class="form-control-file"
id="image"
name="image"
accept="image/*">
@if(isset($station) && $station['image'])
<div class="mt-2">
<img src="{{ $station['image'] }}"
alt="Station Image"
class="img-thumbnail"
style="max-height: 100px;">
</div>
@endif
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<button type="submit" class="btn btn-primary">
{{ isset($station) ? 'Update Station' : 'Add Station' }}
</button>
<a href="{{ route('stations.index') }}" class="btn btn-secondary">Cancel</a>
</div>
<div class="d-flex justify-content-end mt-3">
<a href="{{ url('stations') }}" class="btn btn-outline-secondary btn-sm me-2" style="margin-right:5px">Cancel</a>
<button type="submit" class="btn btn-primary btn-sm">Add Station</button>
</div>
</form>
</div>
<style>
.card {
border-radius: 8px;
}
.form-control,
.form-select {
font-size: 0.85rem;
border-radius: 4px;
padding: 0.375rem 0.75rem;
}
.form-label {
font-weight: 500;
font-size: 0.85rem;
line-height: 1.2;
}
.btn-primary {
background-color: #E74610;
border-color: #E74610;
font-size: 0.85rem;
padding: 0.375rem 0.75rem;
}
.btn-primary:hover {
background-color: #d63f0e;
border-color: #d63f0e;
}
.btn-outline-secondary {
font-size: 0.85rem;
padding: 0.375rem 0.75rem;
}
.form-control-sm,
.form-select-sm {
height: calc(1.5em + 0.5rem + 2px);
}
</style>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
$(document).ready(function() {
// Form validation
$('#stationForm').validate({
rules: {
name: {
required: true,
minlength: 3
},
code: {
required: true,
minlength: 2
},
address: {
required: true,
minlength: 10
},
region: {
required: true
},
latitude: {
required: true,
number: true
},
longitude: {
required: true,
number: true
},
status: {
required: true
}
},
messages: {
name: {
required: "Please enter station name",
minlength: "Station name must be at least 3 characters"
},
code: {
required: "Please enter station code",
minlength: "Station code must be at least 2 characters"
},
address: {
required: "Please enter station address",
minlength: "Address must be at least 10 characters"
},
region: {
required: "Please select a region"
},
latitude: {
required: "Please enter latitude",
number: "Please enter a valid number"
},
longitude: {
required: "Please enter longitude",
number: "Please enter a valid number"
},
status: {
required: "Please select status"
}
},
errorElement: 'span',
errorPlacement: function(error, element) {
error.addClass('invalid-feedback');
element.closest('.form-group').append(error);
},
highlight: function(element, errorClass, validClass) {
$(element).addClass('is-invalid');
},
unhighlight: function(element, errorClass, validClass) {
$(element).removeClass('is-invalid');
},
submitHandler: function(form) {
// Show loading indicator
const submitBtn = $(form).find('button[type="submit"]');
const originalText = submitBtn.text();
submitBtn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Loading...');
// Submit form
$.ajax({
url: form.action,
type: form.method,
data: new FormData(form),
processData: false,
contentType: false,
success: function(response) {
// Show success message
toastr.success(response.message);
// Redirect to stations list
setTimeout(function() {
window.location.href = '{{ route("stations.index") }}';
}, 1500);
},
error: function(xhr) {
// Show error message
const errors = xhr.responseJSON.errors;
Object.keys(errors).forEach(function(key) {
toastr.error(errors[key][0]);
});
// Reset button
submitBtn.prop('disabled', false).text(originalText);
}
});
}
});
// Image preview
$('#image').change(function() {
const file = this.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const img = $('<img>')
.attr('src', e.target.result)
.addClass('img-thumbnail')
.css('max-height', '100px');
const imgContainer = $('#image').siblings('div');
if (imgContainer.length) {
imgContainer.html(img);
} else {
$('#image').after($('<div class="mt-2">').append(img));
}
}
reader.readAsDataURL(file);
}
});
});
</script>
@endpush

View File

@ -0,0 +1,151 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ isset($promotion) ? 'Edit Promotion' : 'Create New Promotion' }}</h3>
</div>
<div class="card-body">
@if(session('error'))
<div class="alert alert-danger">{{ session('error') }}</div>
@endif
<form action="{{ isset($promotion) ? route('promotions.update', $promotion['id']) : route('promotions.store') }}"
method="POST"
enctype="multipart/form-data">
@csrf
@if(isset($promotion))
@method('PUT')
@endif
<div class="form-group">
<label for="title">Title</label>
<input type="text"
class="form-control @error('title') is-invalid @enderror"
id="title"
name="title"
value="{{ old('title', isset($promotion) ? $promotion['title'] : '') }}"
required>
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea class="form-control @error('description') is-invalid @enderror"
id="description"
name="description"
rows="4"
required>{{ old('description', isset($promotion) ? $promotion['description'] : '') }}</textarea>
@error('description')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label for="image">Promotion Image</label>
<div class="custom-file">
<input type="file"
class="custom-file-input @error('image') is-invalid @enderror"
id="image"
name="image"
accept="image/*"
{{ !isset($promotion) ? 'required' : '' }}>
<label class="custom-file-label" for="image">Choose file</label>
</div>
@error('image')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
@if(isset($promotion) && $promotion['image_url'])
<div class="mt-2">
<img src="{{ $promotion['image_url'] }}"
alt="Current promotion image"
class="img-thumbnail"
style="max-height: 200px;">
</div>
@endif
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="start_date">Start Date</label>
<input type="date"
class="form-control @error('start_date') is-invalid @enderror"
id="start_date"
name="start_date"
value="{{ old('start_date', isset($promotion) ? \Carbon\Carbon::parse($promotion['start_date'])->format('Y-m-d') : '') }}"
required>
@error('start_date')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="end_date">End Date</label>
<input type="date"
class="form-control @error('end_date') is-invalid @enderror"
id="end_date"
name="end_date"
value="{{ old('end_date', isset($promotion) ? \Carbon\Carbon::parse($promotion['end_date'])->format('Y-m-d') : '') }}"
required>
@error('end_date')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox"
class="custom-control-input"
id="is_active"
name="is_active"
value="1"
{{ old('is_active', isset($promotion) ? $promotion['is_active'] : true) ? 'checked' : '' }}>
<label class="custom-control-label" for="is_active">Active</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">
{{ isset($promotion) ? 'Update' : 'Create' }} Promotion
</button>
<a href="{{ route('promotions.index') }}" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
$(document).ready(function() {
// Update file input label with selected filename
$('.custom-file-input').on('change', function() {
let fileName = $(this).val().split('\\').pop();
$(this).next('.custom-file-label').addClass("selected").html(fileName);
});
// Validate end date is after start date
$('#end_date').on('change', function() {
let startDate = new Date($('#start_date').val());
let endDate = new Date($(this).val());
if (endDate < startDate) {
alert('End date must be after start date');
$(this).val('');
}
});
});
</script>
@endpush

View File

@ -0,0 +1,307 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Promotions</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-toggle="dropdown">
<i class="fas fa-filter"></i> Filter
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item {{ request('status') == 'active' ? 'active' : '' }}"
href="{{ route('promotions.index', ['status' => 'active']) }}">
Active Promotions
</a>
<a class="dropdown-item {{ request('status') == 'inactive' ? 'active' : '' }}"
href="{{ route('promotions.index', ['status' => 'inactive']) }}">
Inactive Promotions
</a>
<a class="dropdown-item {{ !request('status') ? 'active' : '' }}"
href="{{ route('promotions.index') }}">
All Promotions
</a>
</div>
<a href="{{ route('promotions.create') }}" class="btn btn-primary ml-2">
<i class="fas fa-plus"></i> Add New Promotion
</a>
</div>
</div>
<div class="card-body">
@include('components.alerts')
<div class="mb-3">
<div class="row">
<div class="col-md-6">
<div class="input-group">
<input type="text"
class="form-control"
id="searchInput"
placeholder="Search promotions..."
value="{{ request('search') }}">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="searchButton">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</div>
<div class="col-md-6">
<div class="btn-group float-right">
<button type="button"
class="btn btn-outline-secondary view-switcher {{ session('view_type', 'grid') == 'grid' ? 'active' : '' }}"
data-view="grid">
<i class="fas fa-th-large"></i> Grid
</button>
<button type="button"
class="btn btn-outline-secondary view-switcher {{ session('view_type') == 'list' ? 'active' : '' }}"
data-view="list">
<i class="fas fa-list"></i> List
</button>
</div>
</div>
</div>
</div>
<div id="promotionsGrid" class="{{ session('view_type') == 'list' ? 'd-none' : '' }}">
<div class="row">
@forelse($promotions as $promotion)
<div class="col-md-4 col-lg-3 mb-4">
<div class="card h-100 promotion-card">
<div class="position-relative">
<img src="{{ $promotion['image_url'] }}"
class="card-img-top promotion-image"
alt="{{ $promotion['title'] }}"
loading="lazy">
<div class="promotion-overlay">
<span class="badge badge-{{ $promotion['is_active'] ? 'success' : 'danger' }}">
{{ $promotion['is_active'] ? 'Active' : 'Inactive' }}
</span>
</div>
</div>
<div class="card-body">
<h5 class="card-title text-truncate" title="{{ $promotion['title'] }}">
{{ $promotion['title'] }}
</h5>
<p class="card-text">{{ Str::limit($promotion['description'], 100) }}</p>
</div>
<div class="card-footer bg-transparent">
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<i class="far fa-calendar-alt"></i>
{{ \Carbon\Carbon::parse($promotion['end_date'])->format('M d, Y') }}
</small>
<div class="btn-group">
<a href="{{ route('promotions.edit', $promotion['id']) }}"
class="btn btn-sm btn-info"
data-toggle="tooltip"
title="Edit Promotion">
<i class="fas fa-edit"></i>
</a>
<form action="{{ route('promotions.destroy', $promotion['id']) }}"
method="POST"
class="d-inline delete-form">
@csrf
@method('DELETE')
<button type="submit"
class="btn btn-sm btn-danger"
data-toggle="tooltip"
title="Delete Promotion">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</div>
</div>
</div>
</div>
@empty
<div class="col-12">
<div class="alert alert-info">
<i class="fas fa-info-circle mr-2"></i>
No promotions found. Click the "Add New Promotion" button to create one.
</div>
</div>
@endforelse
</div>
</div>
<div id="promotionsList" class="{{ session('view_type') != 'list' ? 'd-none' : '' }}">
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Image</th>
<th>Title</th>
<th>Description</th>
<th>Status</th>
<th>Period</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($promotions as $promotion)
<tr>
<td class="text-center">
<img src="{{ $promotion['image_url'] }}"
alt="{{ $promotion['title'] }}"
class="img-thumbnail"
style="max-height: 50px;">
</td>
<td>{{ $promotion['title'] }}</td>
<td>{{ Str::limit($promotion['description'], 100) }}</td>
<td>
<span class="badge badge-{{ $promotion['is_active'] ? 'success' : 'danger' }}">
{{ $promotion['is_active'] ? 'Active' : 'Inactive' }}
</span>
</td>
<td>
<small>
Start: {{ \Carbon\Carbon::parse($promotion['start_date'])->format('M d, Y') }}<br>
End: {{ \Carbon\Carbon::parse($promotion['end_date'])->format('M d, Y') }}
</small>
</td>
<td>
<div class="btn-group">
<a href="{{ route('promotions.edit', $promotion['id']) }}"
class="btn btn-sm btn-info"
data-toggle="tooltip"
title="Edit Promotion">
<i class="fas fa-edit"></i>
</a>
<form action="{{ route('promotions.destroy', $promotion['id']) }}"
method="POST"
class="d-inline delete-form">
@csrf
@method('DELETE')
<button type="submit"
class="btn btn-sm btn-danger"
data-toggle="tooltip"
title="Delete Promotion">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center">No promotions found</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="mt-3">
{{ $promotions->links() }}
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('styles')
<style>
.promotion-card {
transition: transform 0.2s;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.promotion-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.promotion-image {
height: 200px;
object-fit: cover;
}
.promotion-overlay {
position: absolute;
top: 10px;
right: 10px;
}
.btn-group {
gap: 5px;
}
.view-switcher.active {
background-color: #007bff;
color: white;
}
</style>
@endpush
@push('scripts')
<script>
$(document).ready(function() {
// View switcher
$('.view-switcher').click(function() {
const view = $(this).data('view');
$('.view-switcher').removeClass('active');
$(this).addClass('active');
if (view === 'grid') {
$('#promotionsGrid').removeClass('d-none');
$('#promotionsList').addClass('d-none');
} else {
$('#promotionsGrid').addClass('d-none');
$('#promotionsList').removeClass('d-none');
}
// Save preference via AJAX
$.post('{{ route("preferences.store") }}', {
key: 'promotions_view_type',
value: view,
_token: '{{ csrf_token() }}'
});
});
// Search functionality
$('#searchButton').click(function() {
const searchTerm = $('#searchInput').val();
window.location.href = '{{ route("promotions.index") }}?search=' + encodeURIComponent(searchTerm);
});
$('#searchInput').keypress(function(e) {
if (e.which == 13) {
$('#searchButton').click();
}
});
// Delete confirmation
$('.delete-form').on('submit', function(e) {
e.preventDefault();
const title = $(this).closest('.promotion-card, tr').find('.card-title, td:eq(1)').text().trim();
if (confirm(`Are you sure you want to delete the promotion "${title}"? This action cannot be undone.`)) {
this.submit();
}
});
// Initialize tooltips
$('[data-toggle="tooltip"]').tooltip();
// Lazy load images
if ('loading' in HTMLImageElement.prototype) {
const images = document.querySelectorAll('img[loading="lazy"]');
images.forEach(img => {
img.src = img.src;
});
} else {
// Fallback for browsers that don't support lazy loading
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/lozad.js/1.16.0/lozad.min.js';
document.body.appendChild(script);
script.onload = function() {
const observer = lozad();
observer.observe();
}
}
});
</script>
@endpush

View File

@ -1,84 +1,151 @@
@extends('layouts.app')
@section('page_title', 'Fuel Price On-Demand')
@section('page_title', 'Fuel Price On Demand')
@section('content')
<div class="card-header border-0 bg-transparent py-2">
<h5 class="mb-0 fw-bold text-dark" style="font-size: 1.25rem;font-weight:500">Fuel Price On-Demand</h5>
</div>
<div class="row justify-content-center align-items-center">
<div class="col-12 col-md-8 col-lg-6">
<div class="card-body p-3">
<form id="fuelPriceForm">
<!-- Import File Section -->
<div class="mb-3">
<label class="form-label fw-bold" style="font-size: 1.5rem;">Import File</label>
<div class="input-group mb-3">
<input type="file" class="form-control" id="fuelPriceFile" style="font-size: 0.9rem; margin: 10px;">
</div>
</div>
<!-- Buttons -->
<div class="d-flex justify-content-center gap-3" >
<button type="submit" class="btn btn-primary d-flex align-items-center" style="background-color: #E74610; border-color: #E74610; font-size: 1.2rem; padding: 10px 20px;">
SUBMIT <i class="fas fa-paper-plane ms-2"></i>
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Fuel Price On Demand</h3>
<div class="card-tools">
<div class="btn-group">
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#importModal">
<i class="fas fa-file-import"></i> Import
</button>
<button type="button" id="exportFuelPrices" class="btn btn-secondary d-flex align-items-center" style="font-size: 1.2rem; padding: 10px 20px;">
EXPORT FUEL PRICES <i class="fas fa-cloud-download-alt ms-2"></i>
<a href="{{ route('fuel-price.on-demand.export') }}" class="btn btn-primary">
<i class="fas fa-file-export"></i> Export
</a>
</div>
</div>
</div>
<div class="card-body">
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
@if(session('error'))
<div class="alert alert-danger">{{ session('error') }}</div>
@endif
<form action="{{ route('fuel-price.on-demand.update') }}" method="POST" class="mb-4">
@csrf
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Station</th>
@foreach($fuelTypes ?? [] as $fuelType)
<th>{{ $fuelType['name'] }}</th>
@endforeach
<th>Last Updated</th>
</tr>
</thead>
<tbody>
@forelse($prices ?? [] as $station)
<tr>
<td>{{ $station['name'] }}</td>
@foreach($fuelTypes ?? [] as $fuelType)
<td>
<input type="number"
class="form-control"
name="prices[{{ $station['id'] }}][{{ $fuelType['id'] }}]"
value="{{ $station['prices'][$fuelType['id']] ?? '' }}"
step="0.01"
min="0">
</td>
@endforeach
<td>{{ $station['last_updated'] ? \Carbon\Carbon::parse($station['last_updated'])->format('Y-m-d H:i:s') : '-' }}</td>
</tr>
@empty
<tr>
<td colspan="{{ count($fuelTypes ?? []) + 2 }}" class="text-center">No stations found</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="text-right mt-3">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Update Prices
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<style>
.card {
border-radius: 5px;
border: 1px solid #dee2e6;
}
.form-label {
font-size: 1.2rem;
}
.form-control {
font-size: 1.2rem;
}
.btn-primary:hover {
background-color: #e07b30;
border-color: #e07b30;
}
.btn-secondary {
background-color: #6c757d;
border-color: #6c757d;
}
.btn-secondary:hover {
background-color: #5a6268;
border-color: #5a6268;
}
.d-flex.justify-content-center {
gap: 1rem; /* Space between buttons */
}
</style>
<!-- Import Modal -->
<div class="modal fade" id="importModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form action="{{ route('fuel-price.on-demand.import') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="modal-header">
<h5 class="modal-title">Import Fuel Prices</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="csv_file" class="form-label">CSV File</label>
<input type="file"
class="form-control"
id="csv_file"
name="csv_file"
accept=".csv"
required>
<small class="form-text text-muted">
Please upload a CSV file with the following columns: Station ID, Fuel Type ID, Price
</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Import</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
document.getElementById('fuelPriceForm').addEventListener('submit', function(e) {
e.preventDefault();
const fileInput = document.getElementById('fuelPriceFile');
if (!fileInput.files.length) {
alert('Please select a file to import.');
return;
$(document).ready(function() {
// Format number inputs
$('input[type="number"]').on('change', function() {
const value = parseFloat($(this).val());
if (!isNaN(value)) {
$(this).val(value.toFixed(2));
}
// Simulate file import (frontend-only)
alert('File imported successfully!');
fileInput.value = ''; // Reset file input
});
document.getElementById('exportFuelPrices').addEventListener('click', function() {
// Simulate export action (frontend-only)
alert('Fuel prices exported successfully!');
// Handle form submission
$('form').on('submit', function() {
const emptyInputs = $(this).find('input[type="number"]').filter(function() {
return !this.value;
});
if (emptyInputs.length > 0) {
if (!confirm('Some prices are empty. Do you want to continue?')) {
return false;
}
}
});
// Handle file input
$('#csv_file').on('change', function() {
const file = this.files[0];
const fileType = file.type || 'application/octet-stream';
if (fileType !== 'text/csv' && !file.name.endsWith('.csv')) {
alert('Please upload a CSV file');
this.value = '';
}
});
});
</script>
@endsection
@endpush

View File

@ -3,192 +3,199 @@
@section('page_title', 'Fuel Price Schedule')
@section('content')
<div class="card-header border-0 bg-transparent py-2">
<h5 class="mb-0 fw-bold text-dark" style="font-size: 1.25rem;font-weight:500">Fuel Price Schedule</h5>
</div>
<div class="row justify-content-center">
<div class="card-body p-3">
<form id="fuelPriceScheduleForm">
<!-- Import File Section -->
<div class="mb-3">
<div class="custom-file" style="width: 400px;">
<input type="file" class="custom-file-input" id="fuelPriceFile" style="font-size: 0.9rem;">
<label class="custom-file-label" for="fuelPriceFile" style="font-size: 0.9rem; padding: 8px;">Choose File</label>
</div>
</div>
@include('components.table-component', [
'pageTitle' => 'Fuel Price Schedule',
'data' => $schedules ?? [],
'columns' => [
['name' => 'Station', 'key' => 'station_name', 'sortable' => true],
['name' => 'Fuel Type', 'key' => 'fuel_type', 'sortable' => true],
['name' => 'Current Price', 'key' => 'current_price', 'sortable' => true],
['name' => 'New Price', 'key' => 'new_price', 'sortable' => true],
['name' => 'Schedule Date', 'key' => 'schedule_date', 'sortable' => true],
['name' => 'Status', 'key' => 'status', 'sortable' => true],
['name' => 'Created By', 'key' => 'created_by', 'sortable' => true],
['name' => 'Created At', 'key' => 'created_at', 'sortable' => true]
],
'allFields' => [
['name' => 'Station', 'key' => 'station_id', 'type' => 'select', 'options' => $stations ?? [], 'required' => true],
['name' => 'Fuel Type', 'key' => 'fuel_type', 'type' => 'select', 'options' => $fuelTypes ?? [], 'required' => true],
['name' => 'New Price', 'key' => 'new_price', 'type' => 'number', 'step' => '0.01', 'required' => true],
['name' => 'Schedule Date', 'key' => 'schedule_date', 'type' => 'datetime-local', 'required' => true],
['name' => 'Status', 'key' => 'status', 'type' => 'select', 'options' => ['Pending', 'Completed', 'Cancelled'], 'required' => true],
['name' => 'Remarks', 'key' => 'remarks', 'type' => 'textarea', 'required' => false]
],
'actions' => ['edit', 'view', 'delete', 'cancel'],
'showAddButton' => true,
'addButtonUrl' => route('fuel-price-schedule.create'),
'showCheckboxes' => true,
'showBatchDelete' => true,
'showEditModal' => true,
'showViewModal' => true,
'baseRoute' => 'fuel-price-schedule'
])
@endsection
<!-- Date Input -->
<div class="mb-3">
<label for="scheduleDate" class="form-label">Date</label>
<div class="input-group">
<input type="text" class="form-control" id="scheduleDate" placeholder="dd/mm/yyyy" required>
<div class="input-group-append">
<span class="input-group-text"><i class="fas fa-calendar-alt"></i></span>
</div>
</div>
</div>
@push('scripts')
<script>
$(document).ready(function() {
const dataTable = initializeDataTable({
route: '{{ route("fuel-price-schedule.data") }}',
columns: [
{ data: 'station_name' },
{ data: 'fuel_type' },
{
data: 'current_price',
render: function(data) {
return new Intl.NumberFormat('en-PH', {
style: 'currency',
currency: 'PHP'
}).format(data);
}
},
{
data: 'new_price',
render: function(data) {
return new Intl.NumberFormat('en-PH', {
style: 'currency',
currency: 'PHP'
}).format(data);
}
},
{
data: 'schedule_date',
render: function(data) {
return moment(data).format('YYYY-MM-DD HH:mm:ss');
}
},
{
data: 'status',
render: function(data) {
const colors = {
'Pending': 'warning',
'Completed': 'success',
'Cancelled': 'danger'
};
return `<span class="badge badge-${colors[data]}">${data}</span>`;
}
},
{ data: 'created_by' },
{
data: 'created_at',
render: function(data) {
return moment(data).format('YYYY-MM-DD HH:mm:ss');
}
}
],
order: [[4, 'asc']] // Sort by schedule_date by default
});
<!-- Time Input -->
<div class="mb-3">
<label for="scheduleTime" class="form-label">Time</label>
<div class="input-group">
<input type="text" class="form-control" id="scheduleTime" placeholder="--:-- --" required>
<div class="input-group-append">
<span class="input-group-text"><i class="fas fa-clock"></i></span>
</div>
</div>
</div>
// Handle form submissions
handleFormSubmission({
addRoute: '{{ route("fuel-price-schedule.store") }}',
editRoute: '{{ route("fuel-price-schedule.update", ":id") }}',
deleteRoute: '{{ route("fuel-price-schedule.destroy", ":id") }}',
dataTable: dataTable,
validation: {
station_id: {
required: true
},
fuel_type: {
required: true
},
new_price: {
required: true,
number: true,
min: 0
},
schedule_date: {
required: true,
date: true,
min: function() {
return moment().format('YYYY-MM-DDTHH:mm');
}
}
},
customButtons: [
{
text: 'Cancel Schedule',
action: function(data) {
if (confirm('Are you sure you want to cancel this schedule?')) {
$.ajax({
url: '{{ route("fuel-price-schedule.cancel", ":id") }}'.replace(':id', data.id),
type: 'POST',
success: function(response) {
toastr.success('Schedule cancelled successfully');
dataTable.ajax.reload();
},
error: function(xhr) {
toastr.error('Failed to cancel schedule');
}
});
}
},
visible: function(data) {
return data.status === 'Pending';
},
className: 'btn-danger'
}
]
});
<!-- Buttons -->
<div class="d-flex justify-content-center gap-2 mt-4">
<button type="submit" class="btn btn-primary d-flex align-items-center" style="background-color: #E74610; border-color: #E74610; font-size: 0.9rem; padding: 8px 20px;">
SUBMIT <i class="fas fa-paper-plane ms-2"></i>
</button>
<button type="button" id="exportFuelPrices" class="btn btn-secondary d-flex align-items-center" style="font-size: 0.9rem; padding: 8px 20px;">
EXPORT FUEL PRICES <i class="fas fa-cloud-download-alt ms-2"></i>
</button>
</div>
</form>
</div>
</div>
// Add date range filter
$('#date-range').daterangepicker({
ranges: {
'Today': [moment(), moment()],
'Tomorrow': [moment().add(1, 'days'), moment().add(1, 'days')],
'Next 7 Days': [moment(), moment().add(6, 'days')],
'Next 30 Days': [moment(), moment().add(29, 'days')],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Next Month': [moment().add(1, 'month').startOf('month'), moment().add(1, 'month').endOf('month')]
}
}, function(start, end) {
dataTable.ajax.reload();
});
// Station change handler
$(document).on('change', '#station_id', function() {
const stationId = $(this).val();
if (stationId) {
$.get('{{ route("stations.fuel-prices", ":id") }}'.replace(':id', stationId), function(data) {
const fuelTypeSelect = $('#fuel_type');
const currentPrice = data.find(item => item.fuel_type === fuelTypeSelect.val())?.price || 0;
$('#current_price').val(currentPrice);
});
}
});
// Fuel type change handler
$(document).on('change', '#fuel_type', function() {
const stationId = $('#station_id').val();
const fuelType = $(this).val();
if (stationId && fuelType) {
$.get('{{ route("stations.fuel-prices", ":id") }}'.replace(':id', stationId), function(data) {
const currentPrice = data.find(item => item.fuel_type === fuelType)?.price || 0;
$('#current_price').val(currentPrice);
});
}
});
});
</script>
@endpush
@push('styles')
<style>
.card {
border-radius: 5px;
border: 1px solid #dee2e6;
.badge {
font-size: 0.875rem;
padding: 0.375rem 0.5rem;
}
.form-label {
font-size: 0.95rem;
.price-change {
font-weight: bold;
}
.form-control {
font-size: 0.9rem;
width: 100%;
.price-increase {
color: #dc3545;
}
.custom-file {
position: relative;
display: inline-block;
width: 100%;
height: calc(2.25rem + 2px);
margin-bottom: 0;
}
.custom-file-input {
position: relative;
z-index: 2;
width: 100%;
height: calc(2.25rem + 2px);
margin: 0;
opacity: 0;
}
.custom-file-label {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 1;
height: calc(2.25rem + 2px);
padding: 0.5rem 1rem;
line-height: 1.5;
color: #495057;
background-color: #fff;
border: 1px solid #ced4da;
border-radius: 0.25rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.custom-file-label::after {
content: "Choose File";
position: absolute;
top: 0;
right: 0;
bottom: 0;
z-index: 3;
padding: 0.5rem 1rem;
line-height: 1.5;
color: #495057;
background-color: #e9ecef;
border-left: 1px solid #ced4da;
border-radius: 0 0.25rem 0.25rem 0;
}
.btn-primary:hover {
background-color: #e07b30;
border-color: #e07b30;
}
.btn-secondary {
background-color: #6c757d;
border-color: #6c757d;
}
.btn-secondary:hover {
background-color: #5a6268;
border-color: #5a6268;
}
.d-flex.justify-content-center {
gap: 0.75rem; /* Space between buttons */
}
.input-group-text {
background-color: #e9ecef;
border: 1px solid #ced4da;
border-radius: 0 0.25rem 0.25rem 0;
.price-decrease {
color: #28a745;
}
</style>
<script>
document.getElementById('fuelPriceScheduleForm').addEventListener('submit', function(e) {
e.preventDefault();
const fileInput = document.getElementById('fuelPriceFile');
const scheduleDate = document.getElementById('scheduleDate').value;
const scheduleTime = document.getElementById('scheduleTime').value;
// Validate file
if (!fileInput.files.length) {
alert('Please select a file to import.');
return;
}
// Validate date format (dd/mm/yyyy)
const datePattern = /^\d{2}\/\d{2}\/\d{4}$/;
if (!datePattern.test(scheduleDate)) {
alert('Please enter a valid date in the format dd/mm/yyyy.');
return;
}
// Validate time format (hh:mm AM/PM)
const timePattern = /^(0?[1-9]|1[0-2]):[0-5][0-9] (AM|PM)$/;
if (!timePattern.test(scheduleTime)) {
alert('Please enter a valid time in the format hh:mm AM/PM.');
return;
}
// Simulate scheduling (frontend-only)
const scheduleData = {
file: fileInput.files[0]?.name,
date: scheduleDate,
time: scheduleTime
};
sessionStorage.setItem('fuelPriceSchedule', JSON.stringify(scheduleData));
alert('Fuel price update scheduled successfully!');
fileInput.value = ''; // Reset file input
document.getElementById('scheduleDate').value = '';
document.getElementById('scheduleTime').value = '';
});
document.getElementById('exportFuelPrices').addEventListener('click', function() {
// Simulate export action (frontend-only)
alert('Fuel prices exported successfully!');
});
// Update the file input label when a file is selected
document.getElementById('fuelPriceFile').addEventListener('change', function(e) {
const fileName = e.target.files.length > 0 ? e.target.files[0].name : 'No file chosen';
document.querySelector('.custom-file-label').textContent = fileName;
});
// Load saved data if available
const savedData = JSON.parse(sessionStorage.getItem('fuelPriceSchedule') || '{}');
if (savedData.date) {
document.getElementById('scheduleDate').value = savedData.date;
document.getElementById('scheduleTime').value = savedData.time;
}
</script>
@endsection
@endpush

View File

@ -3,219 +3,137 @@
@section('page_title', 'Fuel Price Update Logs')
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Price Update History</h3>
<div class="card-tools">
<form class="form-inline">
<div class="input-group">
<input type="text"
class="form-control"
id="dateRange"
name="date_range"
value="{{ request('date_range') }}"
placeholder="Select date range">
<button type="submit" class="btn btn-primary">
<i class="fas fa-search"></i>
</button>
</div>
</form>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Station</th>
<th>Fuel Type</th>
<th>Old Price</th>
<th>New Price</th>
<th>Change</th>
<th>Update Type</th>
<th>Updated By</th>
<th>Updated At</th>
</tr>
</thead>
<tbody>
@forelse($logs ?? [] as $log)
<tr>
<td>{{ $log['station_name'] }}</td>
<td>{{ $log['fuel_type'] }}</td>
<td>{{ number_format($log['old_price'], 2) }}</td>
<td>{{ number_format($log['new_price'], 2) }}</td>
<td>
@php
$fuelPriceUpdateLogs = [
[
'id' => 1,
'schedule' => 'On-demand',
'isCompleted' => 'Yes',
'isSuccess' => 'Yes',
'updatedBy' => 'Graxia Montino',
'createdAt' => '2024-04-18 10:39 am'
],
[
'id' => 2,
'schedule' => '2023-11-17 09:24 am',
'isCompleted' => 'Yes',
'isSuccess' => 'No',
'updatedBy' => 'Graxia Montino',
'createdAt' => '2023-11-17 09:23 am',
'errorMessage' => 'Please recheck the CSV file, some fuels doesn\'t exist.'
],
[
'id' => 3,
'schedule' => '2023-10-05 14:15 pm',
'isCompleted' => 'Yes',
'isSuccess' => 'Yes',
'updatedBy' => 'John Doe',
'createdAt' => '2023-10-05 14:10 pm'
],
[
'id' => 4,
'schedule' => '2023-09-20 08:30 am',
'isCompleted' => 'No',
'isSuccess' => 'No',
'updatedBy' => 'Jane Smith',
'createdAt' => '2023-09-20 08:25 am',
'errorMessage' => 'Invalid CSV format.'
],
[
'id' => 5,
'schedule' => 'On-demand',
'isCompleted' => 'Yes',
'isSuccess' => 'Yes',
'updatedBy' => 'Graxia Montino',
'createdAt' => '2023-08-15 11:45 am'
],
[
'id' => 6,
'schedule' => '2023-07-10 16:20 pm',
'isCompleted' => 'Yes',
'isSuccess' => 'No',
'updatedBy' => 'Alex Brown',
'createdAt' => '2023-07-10 16:15 pm',
'errorMessage' => 'Server error during update.'
],
];
$change = $log['new_price'] - $log['old_price'];
$changeClass = $change > 0 ? 'text-danger' : ($change < 0 ? 'text-success' : 'text-secondary');
@endphp
<div class="mb-3">
<h5 class="mb-0 fw-bold text-dark" style="font-size: 1.25rem;">Fuel Price Update Logs</h5>
</div>
<div class="row justify-content-center">
<div class="card-body p-3">
<!-- Filters -->
<div class="d-flex justify-content-between mb-3 flex-wrap gap-2">
<div class="d-flex align-items-center gap-2">
<div class="d-flex flex-column">
<label for="startDate" class="form-label mb-1">Start Date</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-calendar-alt"></i></span>
<input type="date" class="form-control" id="startDate">
</div>
</div>
<div class="d-flex flex-column">
<label for="endDate" class="form-label mb-1">End Date</label>
<div class="input-group">
<input type="date" class="form-control" id="endDate">
</div>
</div>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-export-csv" id="exportCsv" style="margin:20px">Export CSV</button>
</div>
<span class="{{ $changeClass }}">
{{ $change > 0 ? '+' : '' }}{{ number_format($change, 2) }}
</span>
</td>
<td>
@php
$typeClass = [
'manual' => 'info',
'scheduled' => 'warning',
'import' => 'primary'
][$log['update_type']] ?? 'secondary';
@endphp
<span class="badge bg-{{ $typeClass }}">
{{ ucfirst($log['update_type']) }}
</span>
</td>
<td>{{ $log['updated_by'] }}</td>
<td>{{ \Carbon\Carbon::parse($log['updated_at'])->format('Y-m-d H:i:s') }}</td>
</tr>
@empty
<tr>
<td colspan="8" class="text-center">No update logs found</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Table -->
@include('components.table-component', [
'data' => $fuelPriceUpdateLogs,
'columns' => [
['name' => 'Schedule', 'key' => 'schedule', 'sortable' => true],
['name' => 'Is Completed', 'key' => 'isCompleted', 'sortable' => true],
[
'name' => 'Is Success',
'key' => 'isSuccess',
'sortable' => true,
'render' => function($item) {
return $item['isSuccess'] === 'Yes' ? 'Yes' : ($item['errorMessage'] ?? 'No');
@if(isset($logs) && count($logs) > 0)
<div class="mt-3">
{{ $logs->links() }}
</div>
@endif
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
$(document).ready(function() {
// Initialize DateRangePicker
$('#dateRange').daterangepicker({
autoUpdateInput: false,
locale: {
cancelLabel: 'Clear'
}
],
[
'name' => 'Description',
'key' => 'errorMessage',
'sortable' => true,
'render' => function($item) {
return $item['errorMessage'] ?? '-';
}
],
['name' => 'Updated By', 'key' => 'updatedBy', 'sortable' => true],
['name' => 'Created At', 'key' => 'createdAt', 'sortable' => true]
],
'allFields' => [],
'actions' => [],
'showAddButton' => false,
'showCheckboxes' => false,
'showBatchDelete' => false,
'showEditModal' => false,
'showViewModal' => false,
'showSearch' => false
])
</div>
</div>
});
$('#dateRange').on('apply.daterangepicker', function(ev, picker) {
$(this).val(picker.startDate.format('YYYY-MM-DD') + ' - ' + picker.endDate.format('YYYY-MM-DD'));
});
$('#dateRange').on('cancel.daterangepicker', function(ev, picker) {
$(this).val('');
});
// Initialize DataTable
$('table').DataTable({
order: [[7, 'desc']], // Sort by updated_at by default
pageLength: 25,
dom: 'Bfrtip',
buttons: [
'copy', 'csv', 'excel', 'pdf', 'print'
]
});
});
</script>
@endpush
@push('styles')
<style>
.card {
border-radius: 5px;
border: 1px solid #dee2e6;
.text-danger {
color: #dc3545 !important;
}
.form-control {
font-size: 0.9rem;
.text-success {
color: #28a745 !important;
}
.btn-outline-secondary {
border-color: #6c757d;
color: #6c757d;
}
.btn-outline-secondary:hover {
background-color: #f8f9fa;
}
.btn-export-csv {
background-color: #ff6200;
color: white;
border: none;
}
.btn-export-csv:hover {
background-color: #e65a00;
}
.input-group-text {
background-color: #f8f9fa;
.text-secondary {
color: #6c757d !important;
}
</style>
<script>
let originalData = [...tableConfig.data];
const storedData = JSON.parse(sessionStorage.getItem('fuelPriceUpdateLogs') || '[]');
if (storedData.length > 0) {
tableConfig.data = [...tableConfig.data, ...storedData];
originalData = [...tableConfig.data];
}
const startDateInput = document.getElementById('startDate');
const endDateInput = document.getElementById('endDate');
const exportCsvBtn = document.getElementById('exportCsv');
function filterData() {
const startDate = startDateInput.value ? new Date(startDateInput.value) : null;
const endDate = endDateInput.value ? new Date(endDateInput.value) : null;
tableConfig.data = originalData.filter(item => {
// Parse the createdAt date (format: "YYYY-MM-DD hh:mm am/pm")
const dateParts = item.createdAt.split(' ');
const dateStr = dateParts[0]; // "YYYY-MM-DD"
const timeStr = dateParts[1] + ' ' + dateParts[2]; // "hh:mm am/pm"
const itemDate = new Date(`${dateStr} ${timeStr}`);
if (startDate && itemDate < startDate) return false;
if (endDate) {
const endDateAdjusted = new Date(endDate);
endDateAdjusted.setHours(23, 59, 59, 999);
if (itemDate > endDateAdjusted) return false;
}
return true;
});
currentPage = 1;
renderTable();
renderPagination();
}
startDateInput.addEventListener('change', filterData);
endDateInput.addEventListener('change', filterData);
exportCsvBtn.addEventListener('click', () => {
const headers = tableConfig.columns.map(col => col.name).join(',');
const rows = tableConfig.data.map(item => {
return [
item.schedule,
item.isCompleted,
item.isSuccess === 'Yes' ? 'Yes' : (item.errorMessage || 'No'),
item.errorMessage || '-',
item.updatedBy,
item.createdAt
].join(',');
});
const csvContent = [headers, ...rows].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', 'fuel-price-update-logs.csv');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
// Initial render
renderTable();
renderPagination();
</script>
@endsection
@endpush

View File

@ -3,99 +3,260 @@
@section('page_title', 'My Profile')
@section('content')
<div class="card w-100">
<!-- Banner -->
<div class="banner d-flex align-items-center p-3">
<div class="banner-icon me-3">
<i class="fas fa-user-circle" style="font-size: 40px; color: #6c757d;"></i>
</div>
<h4 class="fw-bold text-primary mb-0" style="margin-left:10px">{{ $user['admin']['username'] ?? 'N/A' }}</h4>
</div>
<!-- Profile Section -->
<div class="card-body p-4">
<div class="container-fluid">
<div class="row">
<!-- Profile Information -->
<div class="col-md-9">
<h3 class="fw-bold mb-3" style="font-size: 20px; font-weight:400">My Information</h3>
<div class="mb-2">
<span class="fw-bold text-dark">Username: </span>
<span>{{ $user['admin']['username'] ?? 'N/A' }}</span>
<div class="col-md-4">
<div class="card">
<div class="card-body text-center">
<div class="profile-image mb-3">
@if(isset($user['avatar']))
<img src="{{ $user['avatar'] }}" alt="Profile Picture" class="rounded-circle" style="width: 150px; height: 150px; object-fit: cover;">
@else
<div class="default-avatar rounded-circle d-flex align-items-center justify-content-center" style="width: 150px; height: 150px; background-color: #e9ecef; margin: 0 auto;">
<i class="fas fa-user fa-4x text-secondary"></i>
</div>
<div class="mb-2">
<span class="fw-bold text-dark">Email: </span>
<a href="mailto:{{ $user['admin']['email'] ?? 'N/A' }}" class="text-primary">{{ $user['admin']['email'] ?? 'N/A' }}</a>
</div>
<div>
<span class="fw-bold text-dark">Access Role: </span>
<span>{{ $user['admin']['role'] ?? 'N/A' }}</span>
@endif
<div class="mt-2">
<label for="avatar" class="btn btn-sm btn-outline-primary">
Change Photo
<input type="file" id="avatar" name="avatar" class="d-none" accept="image/*">
</label>
</div>
</div>
<h4 class="mb-1">{{ $user['name'] ?? 'User Name' }}</h4>
<p class="text-muted">{{ $user['role'] ?? 'Role' }}</p>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Profile Information</h5>
</div>
<div class="card-body">
<form id="profileForm" method="POST" action="{{ route('profile.update') }}">
@csrf
@method('PUT')
<div class="row mb-3">
<div class="col-md-6">
<div class="form-group">
<label for="firstName">First Name</label>
<input type="text" class="form-control" id="firstName" name="firstName" value="{{ old('firstName', $user['firstName'] ?? '') }}" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="lastName">Last Name</label>
<input type="text" class="form-control" id="lastName" name="lastName" value="{{ old('lastName', $user['lastName'] ?? '') }}" required>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" id="email" name="email" value="{{ old('email', $user['email'] ?? '') }}" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="phone">Phone Number</label>
<input type="tel" class="form-control" id="phone" name="phone" value="{{ old('phone', $user['phone'] ?? '') }}">
</div>
</div>
</div>
<div class="form-group mb-3">
<label for="address">Address</label>
<textarea class="form-control" id="address" name="address" rows="3">{{ old('address', $user['address'] ?? '') }}</textarea>
</div>
<button type="submit" class="btn btn-primary">Save Changes</button>
</form>
<hr>
<h5 class="mb-3">Change Password</h5>
<form id="passwordForm" method="POST" action="{{ route('profile.password.update') }}">
@csrf
@method('PUT')
<div class="row mb-3">
<div class="col-md-6">
<div class="form-group">
<label for="currentPassword">Current Password</label>
<input type="password" class="form-control" id="currentPassword" name="currentPassword" required>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-group">
<label for="newPassword">New Password</label>
<input type="password" class="form-control" id="newPassword" name="newPassword" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="confirmPassword">Confirm New Password</label>
<input type="password" class="form-control" id="confirmPassword" name="confirmPassword" required>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Update Password</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
$(document).ready(function() {
// Profile form validation and submission
$('#profileForm').validate({
rules: {
firstName: {
required: true,
minlength: 2
},
lastName: {
required: true,
minlength: 2
},
email: {
required: true,
email: true
},
phone: {
pattern: /^[0-9+\-\s()]*$/
}
},
messages: {
firstName: {
required: "Please enter your first name",
minlength: "First name must be at least 2 characters"
},
lastName: {
required: "Please enter your last name",
minlength: "Last name must be at least 2 characters"
},
email: {
required: "Please enter your email address",
email: "Please enter a valid email address"
},
phone: {
pattern: "Please enter a valid phone number"
}
},
submitHandler: function(form) {
$.ajax({
url: form.action,
type: form.method,
data: $(form).serialize(),
success: function(response) {
toastr.success('Profile updated successfully');
},
error: function(xhr) {
const errors = xhr.responseJSON.errors;
Object.keys(errors).forEach(function(key) {
toastr.error(errors[key][0]);
});
}
});
}
});
// Password form validation and submission
$('#passwordForm').validate({
rules: {
currentPassword: {
required: true,
minlength: 8
},
newPassword: {
required: true,
minlength: 8
},
confirmPassword: {
required: true,
equalTo: "#newPassword"
}
},
messages: {
currentPassword: {
required: "Please enter your current password",
minlength: "Password must be at least 8 characters"
},
newPassword: {
required: "Please enter a new password",
minlength: "Password must be at least 8 characters"
},
confirmPassword: {
required: "Please confirm your new password",
equalTo: "Passwords do not match"
}
},
submitHandler: function(form) {
$.ajax({
url: form.action,
type: form.method,
data: $(form).serialize(),
success: function(response) {
toastr.success('Password updated successfully');
form.reset();
},
error: function(xhr) {
const errors = xhr.responseJSON.errors;
Object.keys(errors).forEach(function(key) {
toastr.error(errors[key][0]);
});
}
});
}
});
// Avatar upload
$('#avatar').change(function() {
const file = this.files[0];
if (file) {
const formData = new FormData();
formData.append('avatar', file);
$.ajax({
url: '{{ route("profile.avatar.update") }}',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
toastr.success('Profile picture updated successfully');
location.reload();
},
error: function(xhr) {
toastr.error('Failed to update profile picture');
}
});
}
});
});
</script>
@endpush
@push('styles')
<style>
.profile-image img {
border: 5px solid #fff;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.card {
border-radius: 5px;
border: 1px solid #dee2e6;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
margin-top: 1rem;
}
.banner {
background-color: #e6f0fa;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom: 1px solid #dee2e6;
}
.text-primary {
color: #003087 !important;
}
.text-dark {
color: #343a40 !important;
}
.profile-picture img {
transition: all 0.3s ease;
}
.profile-picture img:hover {
opacity: 0.9;
}
.card-body {
font-size: 0.9rem;
}
.card-body .fw-bold {
font-size: 0.95rem;
}
.btn-primary {
background-color: #0d6efd;
border-color: #0d6efd;
font-size: 0.85rem;
padding: 0.25rem 0.75rem;
}
.btn-primary:hover {
background-color: #0b5ed7;
border-color: #0b5ed7;
}
@media (max-width: 576px) {
.profile-picture img {
width: 60px;
height: 60px;
}
.card-body {
font-size: 0.85rem;
}
.card-body .fw-bold {
font-size: 0.9rem;
}
.banner {
padding: 1.5rem;
}
.banner-icon i {
font-size: 30px;
}
.banner h4 {
font-size: 1.25rem;
}
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
</style>
@endsection
@endpush

View File

@ -1,44 +1,182 @@
@extends('layouts.app')
@section('page_title', 'Notification')
@section('page_title', 'Notifications')
@section('content')
@php
$notifications = [
['id' => 1, 'subject' => 'Welcome Message', 'content' => 'Welcome to our platform! Get started today.', 'isScheduled' => 'Scheduled', 'schedule' => '2025-04-16 10:00', 'expiration' => '2025-04-30 23:59'],
['id' => 2, 'subject' => 'System Update', 'content' => 'Scheduled maintenance on April 20th.', 'isScheduled' => 'Scheduled', 'schedule' => '2025-04-20 02:00', 'expiration' => '2025-04-21 02:00'],
['id' => 3, 'subject' => 'Promotion Offer', 'content' => '50% off your next purchase this week!', 'isScheduled' => 'Not Scheduled', 'schedule' => '', 'expiration' => '2025-04-22 23:59'],
['id' => 4, 'subject' => 'Account Reminder', 'content' => 'Please update your profile details.', 'isScheduled' => 'Scheduled', 'schedule' => '2025-04-18 09:00', 'expiration' => '2025-04-25 23:59']
];
@endphp
@include('components.table-component', [
'pageTitle' => 'Notification',
'data' => $notifications,
'pageTitle' => 'Notifications',
'data' => $notifications ?? [],
'columns' => [
['name' => 'ID', 'key' => 'id', 'sortable' => true],
['name' => 'Subject', 'key' => 'subject', 'sortable' => true],
['name' => 'Content', 'key' => 'content', 'sortable' => true],
['name' => 'Is Scheduled', 'key' => 'isScheduled', 'sortable' => true],
['name' => 'Schedule', 'key' => 'schedule', 'sortable' => true],
['name' => 'Expiration', 'key' => 'expiration', 'sortable' => true]
['name' => 'Title', 'key' => 'title', 'sortable' => true],
['name' => 'Message', 'key' => 'message', 'sortable' => true],
['name' => 'Type', 'key' => 'type', 'sortable' => true],
['name' => 'Status', 'key' => 'status', 'sortable' => true],
['name' => 'Created At', 'key' => 'created_at', 'sortable' => true],
['name' => 'Sent At', 'key' => 'sent_at', 'sortable' => true]
],
'allFields' => [
['name' => 'Title', 'key' => 'title', 'type' => 'text', 'required' => true],
['name' => 'Message', 'key' => 'message', 'type' => 'textarea', 'required' => true],
['name' => 'Type', 'key' => 'type', 'type' => 'select', 'options' => ['Info', 'Warning', 'Success', 'Error'], 'required' => true],
['name' => 'Status', 'key' => 'status', 'type' => 'select', 'options' => ['Draft', 'Sent', 'Scheduled'], 'required' => true],
['name' => 'Schedule Date', 'key' => 'schedule_date', 'type' => 'datetime-local', 'required' => false, 'showIf' => ['status', 'Scheduled']],
['name' => 'Target Users', 'key' => 'target_users', 'type' => 'select', 'options' => ['All Users', 'Selected Users'], 'required' => true],
['name' => 'User IDs', 'key' => 'user_ids', 'type' => 'text', 'required' => false, 'showIf' => ['target_users', 'Selected Users'], 'placeholder' => 'Comma-separated user IDs']
],
'actions' => ['edit', 'view', 'delete', 'send'],
'showAddButton' => true,
'addButtonUrl' => '/add-notification',
'showCheckboxes' => false,
'showBatchDelete' => false,
'showEditModal' => false,
'showViewModal' => true
'addButtonUrl' => route('notifications.create'),
'showCheckboxes' => true,
'showBatchDelete' => true,
'showEditModal' => true,
'showViewModal' => true,
'baseRoute' => 'notifications'
])
<script>
const storedNotifications = JSON.parse(sessionStorage.getItem('notifications') || '[]');
if (storedNotifications.length > 0) {
const tableConfig = window.tableConfig || {};
tableConfig.data = [...tableConfig.data, ...storedNotifications];
window.renderTable();
window.renderPagination();
}
</script>
@endsection
@push('scripts')
<script>
$(document).ready(function() {
const dataTable = initializeDataTable({
route: '{{ route("notifications.data") }}',
columns: [
{ data: 'title' },
{
data: 'message',
render: function(data) {
return data.length > 50 ? data.substring(0, 50) + '...' : data;
}
},
{
data: 'type',
render: function(data) {
const colors = {
'Info': 'info',
'Warning': 'warning',
'Success': 'success',
'Error': 'danger'
};
return `<span class="badge badge-${colors[data] || 'secondary'}">${data}</span>`;
}
},
{
data: 'status',
render: function(data) {
const colors = {
'Draft': 'secondary',
'Sent': 'success',
'Scheduled': 'info'
};
return `<span class="badge badge-${colors[data] || 'secondary'}">${data}</span>`;
}
},
{
data: 'created_at',
render: function(data) {
return moment(data).format('YYYY-MM-DD HH:mm:ss');
}
},
{
data: 'sent_at',
render: function(data) {
return data ? moment(data).format('YYYY-MM-DD HH:mm:ss') : '-';
}
}
]
});
// Handle form submissions
handleFormSubmission({
addRoute: '{{ route("notifications.store") }}',
editRoute: '{{ route("notifications.update", ":id") }}',
deleteRoute: '{{ route("notifications.destroy", ":id") }}',
dataTable: dataTable,
validation: {
title: {
required: true,
minlength: 3
},
message: {
required: true,
minlength: 10
},
type: {
required: true
},
status: {
required: true
},
schedule_date: {
required: function() {
return $('#status').val() === 'Scheduled';
},
date: true,
min: function() {
return moment().format('YYYY-MM-DDTHH:mm');
}
},
target_users: {
required: true
},
user_ids: {
required: function() {
return $('#target_users').val() === 'Selected Users';
},
pattern: /^(\d+,)*\d+$/
}
},
customButtons: [
{
text: 'Send Now',
action: function(data) {
if (confirm('Are you sure you want to send this notification now?')) {
$.ajax({
url: '{{ route("notifications.send", ":id") }}'.replace(':id', data.id),
type: 'POST',
success: function(response) {
toastr.success('Notification sent successfully');
dataTable.ajax.reload();
},
error: function(xhr) {
toastr.error('Failed to send notification');
}
});
}
},
visible: function(data) {
return data.status === 'Draft' || data.status === 'Scheduled';
}
}
]
});
// Handle conditional fields
$(document).on('change', '#status', function() {
const scheduleDateGroup = $('#schedule_date').closest('.form-group');
if ($(this).val() === 'Scheduled') {
scheduleDateGroup.show();
} else {
scheduleDateGroup.hide();
}
});
$(document).on('change', '#target_users', function() {
const userIdsGroup = $('#user_ids').closest('.form-group');
if ($(this).val() === 'Selected Users') {
userIdsGroup.show();
} else {
userIdsGroup.hide();
}
});
});
</script>
@endpush
@push('styles')
<style>
.badge {
font-size: 0.875rem;
padding: 0.375rem 0.5rem;
}
</style>
@endpush

View File

@ -3,85 +3,143 @@
@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'
],
[
'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,
'data' => $promotions ?? [],
'columns' => [
['name' => 'Title', 'key' => 'title', 'sortable' => true],
['name' => 'Type', 'key' => 'type', 'sortable' => true],
['name' => 'Description', 'key' => 'description', 'sortable' => true],
['name' => 'Start Date', 'key' => 'startDate', 'sortable' => true],
['name' => 'End Date', 'key' => 'endDate', 'sortable' => true],
['name' => 'Status', 'key' => 'status', 'sortable' => true]
['name' => 'Status', 'key' => 'status', 'sortable' => true],
['name' => 'Image', 'key' => 'image', 'sortable' => false]
],
'allFields' => [
['name' => 'Title', 'key' => 'title', 'type' => 'text', 'required' => true],
['name' => 'Description', 'key' => 'description', 'type' => 'textarea', 'required' => true],
['name' => 'Start Date', 'key' => 'startDate', 'type' => 'date', 'required' => true],
['name' => 'End Date', 'key' => 'endDate', 'type' => 'date', 'required' => true],
['name' => 'Status', 'key' => 'status', 'type' => 'select', 'options' => ['Active', 'Inactive'], 'required' => true],
['name' => 'Image', 'key' => 'image', 'type' => 'file', 'accept' => 'image/*', 'required' => true]
],
'actions' => ['edit', 'view', 'delete'],
'showAddButton' => true,
'addButtonUrl' => '/add-promotions',
'addButtonUrl' => route('promotions.create'),
'showCheckboxes' => true,
'showBatchDelete' => true,
'showEditModal' => true,
'showViewModal' => true
'showViewModal' => true,
'baseRoute' => 'promotions'
])
<script>
const storedPromotions = JSON.parse(sessionStorage.getItem('promotions') || '[]');
if (storedPromotions.length > 0) {
const tableConfig = window.tableConfig || {};
tableConfig.data = [...tableConfig.data, ...storedPromotions];
window.renderTable();
window.renderPagination();
}
</script>
@endsection
@push('scripts')
<script>
$(document).ready(function() {
const dataTable = initializeDataTable({
route: '{{ route("promotions.data") }}',
columns: [
{ data: 'title' },
{
data: 'description',
render: function(data) {
return data.length > 50 ? data.substring(0, 50) + '...' : data;
}
},
{
data: 'startDate',
render: function(data) {
return moment(data).format('YYYY-MM-DD');
}
},
{
data: 'endDate',
render: function(data) {
return moment(data).format('YYYY-MM-DD');
}
},
{
data: 'status',
render: function(data) {
return `<span class="badge badge-${data === 'Active' ? 'success' : 'danger'}">${data}</span>`;
}
},
{
data: 'image',
render: function(data) {
return data ? `<img src="${data}" alt="Promotion" class="img-thumbnail" style="max-height: 50px;">` : '';
}
}
]
});
// Handle form submissions
handleFormSubmission({
addRoute: '{{ route("promotions.store") }}',
editRoute: '{{ route("promotions.update", ":id") }}',
deleteRoute: '{{ route("promotions.destroy", ":id") }}',
dataTable: dataTable,
validation: {
title: {
required: true,
minlength: 3
},
description: {
required: true,
minlength: 10
},
startDate: {
required: true,
date: true
},
endDate: {
required: true,
date: true,
greaterThan: '#startDate'
},
image: {
required: function() {
return !$('#editForm').length; // Required only for new promotions
},
accept: "image/*"
}
},
processData: false,
contentType: false,
isMultipart: true
});
// Date picker initialization
$('.datepicker').datepicker({
format: 'yyyy-mm-dd',
autoclose: true,
todayHighlight: true,
startDate: '0d'
});
// Image preview
$('input[type="file"]').change(function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
$('#imagePreview').attr('src', e.target.result);
};
reader.readAsDataURL(file);
}
});
});
</script>
@endpush
@push('styles')
<style>
.promotion-image {
max-height: 50px;
width: auto;
}
.datepicker {
z-index: 1060;
}
</style>
@endpush

View File

@ -0,0 +1,240 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Station Management</h3>
<div class="card-tools">
<a href="{{ route('stations.create') }}" class="btn btn-primary">
<i class="fas fa-plus"></i> Add New Station
</a>
</div>
</div>
<div class="card-body">
@include('components.alerts')
<div class="mb-3">
<div class="row">
<div class="col-md-4">
<div class="input-group">
<input type="text" class="form-control" id="searchInput" placeholder="Search stations...">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</div>
<div class="col-md-4">
<select class="form-control" id="regionFilter">
<option value="">All Regions</option>
@foreach($regions as $region)
<option value="{{ $region }}">{{ $region }}</option>
@endforeach
</select>
</div>
<div class="col-md-4">
<select class="form-control" id="statusFilter">
<option value="">All Status</option>
<option value="1">Active</option>
<option value="0">Inactive</option>
</select>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped" id="stationsTable">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Address</th>
<th>Region</th>
<th>Contact</th>
<th>Status</th>
<th>Last Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($stations as $station)
<tr>
<td>{{ $station['id'] }}</td>
<td>{{ $station['name'] }}</td>
<td>
<address class="mb-0">
{{ $station['address'] }}
<br>
<small class="text-muted">
<i class="fas fa-map-marker-alt"></i>
<a href="https://maps.google.com/?q={{ urlencode($station['address']) }}"
target="_blank"
class="text-info">
View on Map
</a>
</small>
</address>
</td>
<td>{{ $station['region'] }}</td>
<td>
<i class="fas fa-phone"></i> {{ $station['contact_number'] }}
</td>
<td>
<span class="badge badge-{{ $station['is_active'] ? 'success' : 'danger' }}">
{{ $station['is_active'] ? 'Active' : 'Inactive' }}
</span>
</td>
<td>{{ \Carbon\Carbon::parse($station['updated_at'])->format('Y-m-d H:i') }}</td>
<td>
<div class="btn-group">
<a href="{{ route('stations.edit', $station['id']) }}"
class="btn btn-sm btn-info"
data-toggle="tooltip"
title="Edit Station">
<i class="fas fa-edit"></i>
</a>
<a href="{{ route('fuel-prices.index', ['station_id' => $station['id']]) }}"
class="btn btn-sm btn-warning"
data-toggle="tooltip"
title="Manage Fuel Prices">
<i class="fas fa-gas-pump"></i>
</a>
<form action="{{ route('stations.destroy', $station['id']) }}"
method="POST"
class="d-inline delete-form">
@csrf
@method('DELETE')
<button type="submit"
class="btn btn-sm btn-danger"
data-toggle="tooltip"
title="Delete Station">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="text-center">No stations found</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-3">
{{ $stations->links() }}
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('styles')
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.24/css/dataTables.bootstrap4.min.css">
<style>
.station-address {
max-width: 250px;
white-space: normal;
}
.btn-group {
gap: 5px;
}
</style>
@endpush
@push('scripts')
<script src="https://cdn.datatables.net/1.10.24/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.24/js/dataTables.bootstrap4.min.js"></script>
<script>
$(document).ready(function() {
// Initialize DataTable
const table = $('#stationsTable').DataTable({
"responsive": true,
"processing": true,
"serverSide": true,
"ajax": "{{ route('stations.data') }}",
"order": [[0, "desc"]],
"columns": [
{ "data": "id" },
{ "data": "name" },
{
"data": "address",
"render": function(data, type, row) {
if (type === 'display') {
return `<address class="mb-0 station-address">
${data}<br>
<small class="text-muted">
<i class="fas fa-map-marker-alt"></i>
<a href="https://maps.google.com/?q=${encodeURIComponent(data)}"
target="_blank"
class="text-info">
View on Map
</a>
</small>
</address>`;
}
return data;
}
},
{ "data": "region" },
{
"data": "contact_number",
"render": function(data) {
return `<i class="fas fa-phone"></i> ${data}`;
}
},
{
"data": "is_active",
"render": function(data) {
const badge = data ? 'success' : 'danger';
const text = data ? 'Active' : 'Inactive';
return `<span class="badge badge-${badge}">${text}</span>`;
}
},
{
"data": "updated_at",
"render": function(data) {
return moment(data).format('YYYY-MM-DD HH:mm');
}
},
{
"data": "actions",
"orderable": false,
"searchable": false
}
]
});
// Apply filters
$('#regionFilter, #statusFilter').change(function() {
table.ajax.reload();
});
// Search functionality
$('#searchInput').keyup(function() {
table.search($(this).val()).draw();
});
// Delete confirmation
$(document).on('submit', '.delete-form', function(e) {
e.preventDefault();
const stationName = $(this).closest('tr').find('td:eq(1)').text();
if (confirm(`Are you sure you want to delete the station "${stationName}"? This action cannot be undone.`)) {
this.submit();
}
});
// Initialize tooltips
$('[data-toggle="tooltip"]').tooltip();
});
</script>
@endpush

View File

@ -3,182 +3,101 @@
@section('page_title', 'System Parameters')
@section('content')
<div class="card-header border-0 bg-transparent py-2">
<h5 class="mb-0 fw-bold text-dark" style="font-size: 1.25rem;">System Parameters</h5>
</div>
<div class="row justify-content-center">
<div class="card-body p-3">
<form id="systemParametersForm">
<!-- Company Logo -->
<h4 class="fw-bold mb-3 mt-4">Company Logo</h4>
<div class="mb-3">
<label for="companyLogo" class="form-label">Upload Logo</label>
<input type="file" class="form-control" id="companyLogo" accept=".jpg,.jpeg,.png">
</div>
@include('components.table-component', [
'pageTitle' => 'System Parameters',
'data' => $parameters ?? [],
'columns' => [
['name' => 'Name', 'key' => 'name', 'sortable' => true],
['name' => 'Value', 'key' => 'value', 'sortable' => true],
['name' => 'Type', 'key' => 'type', 'sortable' => true],
['name' => 'Description', 'key' => 'description', 'sortable' => true],
['name' => 'Last Updated', 'key' => 'updated_at', 'sortable' => true]
],
'allFields' => [
['name' => 'Name', 'key' => 'name', 'type' => 'text', 'required' => true],
['name' => 'Value', 'key' => 'value', 'type' => 'text', 'required' => true],
['name' => 'Type', 'key' => 'type', 'type' => 'select', 'options' => ['String', 'Number', 'Boolean', 'JSON'], 'required' => true],
['name' => 'Description', 'key' => 'description', 'type' => 'textarea', 'required' => true]
],
'actions' => ['edit', 'view', 'delete'],
'showAddButton' => true,
'addButtonUrl' => route('system-parameters.create'),
'showCheckboxes' => true,
'showBatchDelete' => true,
'showEditModal' => true,
'showViewModal' => true,
'baseRoute' => 'system-parameters'
])
@endsection
<!-- GPS Radius -->
<h4 class="fw-bold mb-3 mt-4">GPS Radius</h4>
<div class="mb-3">
<label for="gpsRadius" class="form-label">GPS Radius (in meters)</label>
<input type="number" class="form-control" id="gpsRadius" min="0" placeholder="Enter radius" required>
</div>
@push('scripts')
<script>
$(document).ready(function() {
// Handle parameter type change
$(document).on('change', '[name="type"]', function() {
const type = $(this).val();
const valueInput = $(this).closest('form').find('[name="value"]');
<!-- Customer Service Details -->
<h4 class="fw-bold mb-3 mt-4">Customer Service Details</h4>
<div class="mb-3">
<label for="contactEmail" class="form-label">Contact Email Address</label>
<input type="email" class="form-control" id="contactEmail" placeholder="Enter email" required>
</div>
<div class="mb-3">
<label for="contactNumber" class="form-label">Contact Number</label>
<input type="tel" class="form-control" id="contactNumber" placeholder="Enter number" required>
</div>
<!-- System Administrator Details -->
<h4 class="fw-bold mb-3 mt-4">System Administrator Details</h4>
<div class="mb-3">
<label for="adminContact" class="form-label">Contact Details</label>
<textarea class="form-control" id="adminContact" rows="4" placeholder="Enter details" required></textarea>
</div>
<!-- Information Guide Details -->
<h4 class="fw-bold mb-3 mt-4">Information Guide Details</h4>
<div class="mb-3">
<label for="infoGuideDetails" class="form-label">Details</label>
<textarea class="form-control" id="infoGuideDetails" rows="4" placeholder="Enter details" required></textarea>
</div>
<!-- Android Version Update -->
<h4 class="fw-bold mb-3 mt-4">Android Version Update</h4>
<div class="mb-3">
<label for="androidVersion" class="form-label">Android Version</label>
<input type="text" class="form-control" id="androidVersion" placeholder="e.g., 1.0.0" required>
</div>
<div class="mb-3">
<label for="androidVersionMessage" class="form-label">Version Update Message</label>
<textarea class="form-control" id="androidVersionMessage" rows="4" placeholder="Enter message" required></textarea>
</div>
<div class="mb-3">
<label for="androidVersionAction" class="form-label">Version Action</label>
<select class="form-select" id="androidVersionAction" required>
<option value="" disabled selected>Select action</option>
<option value="Force Update">Force Update</option>
<option value="Optional Update">Optional Update</option>
switch(type) {
case 'Number':
valueInput.attr('type', 'number');
break;
case 'Boolean':
valueInput.replaceWith(`
<select name="value" class="form-control">
<option value="true">True</option>
<option value="false">False</option>
</select>
</div>
`);
break;
case 'JSON':
valueInput.replaceWith(`<textarea name="value" class="form-control" rows="3"></textarea>`);
break;
default:
valueInput.attr('type', 'text');
}
});
<!-- iOS Version Update -->
<h4 class="fw-bold mb-3 mt-4">iOS Version Update</h4>
<div class="mb-3">
<label for="iosVersion" class="form-label">iOS Version</label>
<input type="text" class="form-control" id="iosVersion" placeholder="e.g., 1.0.0" required>
</div>
<div class="mb-3">
<label for="iosVersionMessage" class="form-label">Version Update Message</label>
<textarea class="form-control" id="iosVersionMessage" rows="4" placeholder="Enter message" required></textarea>
</div>
<div class="mb-3">
<label for="iosVersionAction" class="form-label">Version Action</label>
<select class="form-select" id="iosVersionAction" required>
<option value="" disabled selected>Select action</option>
<option value="Force Update">Force Update</option>
<option value="Optional Update">Optional Update</option>
</select>
</div>
// Validate JSON input
$(document).on('submit', 'form', function(e) {
const type = $(this).find('[name="type"]').val();
const value = $(this).find('[name="value"]').val();
<!-- Update Details -->
<h4 class="fw-bold mb-3 mt-4">Update Details</h4>
<div class="mb-3">
<button type="button" class="btn btn-primary" id="syncData">Sync Data</button>
</div>
if (type === 'JSON') {
try {
JSON.parse(value);
} catch (error) {
e.preventDefault();
alert('Please enter valid JSON');
}
}
});
<!-- Submit -->
<div class="d-flex justify-content-end mt-4">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
// Format JSON display
$('.table td:nth-child(2)').each(function() {
const type = $(this).closest('tr').find('td:nth-child(3)').text().trim();
const value = $(this).text().trim();
if (type === 'JSON') {
try {
const formatted = JSON.stringify(JSON.parse(value), null, 2);
$(this).html(`<pre class="mb-0">${formatted}</pre>`);
} catch (error) {
// Keep original value if not valid JSON
}
}
});
});
</script>
@endpush
@push('styles')
<style>
.card {
border-radius: 5px;
border: 1px solid #dee2e6;
}
.form-label {
font-size: 0.95rem;
}
.form-control,
.form-select,
textarea.form-control {
font-size: 0.9rem;
width: 100%;
}
h4 {
color: #343a40;
pre {
background-color: #f8f9fa;
padding: 0.5rem;
border-radius: 0.25rem;
font-size: 0.875rem;
}
</style>
<script>
document.getElementById('systemParametersForm').addEventListener('submit', function(e) {
e.preventDefault();
const fields = {
companyLogo: document.getElementById('companyLogo').files[0]?.name || '',
gpsRadius: document.getElementById('gpsRadius').value,
contactEmail: document.getElementById('contactEmail').value,
contactNumber: document.getElementById('contactNumber').value,
adminContact: document.getElementById('adminContact').value,
infoGuideDetails: document.getElementById('infoGuideDetails').value,
androidVersion: document.getElementById('androidVersion').value,
androidVersionMessage: document.getElementById('androidVersionMessage').value,
androidVersionAction: document.getElementById('androidVersionAction').value,
iosVersion: document.getElementById('iosVersion').value,
iosVersionMessage: document.getElementById('iosVersionMessage').value,
iosVersionAction: document.getElementById('iosVersionAction').value
};
// Validate required fields (exclude companyLogo)
for (const [key, value] of Object.entries(fields)) {
if (key !== 'companyLogo' && !value) {
alert('Please fill out all required fields.');
return;
}
}
// Validate email format
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(fields.contactEmail)) {
alert('Please enter a valid email address.');
return;
}
sessionStorage.setItem('systemParameters', JSON.stringify(fields));
alert('Settings saved successfully!');
});
document.getElementById('syncData').addEventListener('click', function() {
alert('Data synced!');
});
document.querySelector('.btn-outline-secondary').addEventListener('click', function() {
window.location.href = '/';
});
// Load saved data
const savedData = JSON.parse(sessionStorage.getItem('systemParameters') || '{}');
if (savedData) {
document.getElementById('gpsRadius').value = savedData.gpsRadius || '';
document.getElementById('contactEmail').value = savedData.contactEmail || '';
document.getElementById('contactNumber').value = savedData.contactNumber || '';
document.getElementById('adminContact').value = savedData.adminContact || '';
document.getElementById('infoGuideDetails').value = savedData.infoGuideDetails || '';
document.getElementById('androidVersion').value = savedData.androidVersion || '';
document.getElementById('androidVersionMessage').value = savedData.androidVersionMessage || '';
document.getElementById('androidVersionAction').value = savedData.androidVersionAction || '';
document.getElementById('iosVersion').value = savedData.iosVersion || '';
document.getElementById('iosVersionMessage').value = savedData.iosVersionMessage || '';
document.getElementById('iosVersionAction').value = savedData.iosVersionAction || '';
}
</script>
@endsection
@endpush

View File

@ -1,83 +1,84 @@
@extends('layouts.app')
@section('page_title', 'Top-Up Settings')
@section('page_title', 'Top-up Settings')
@section('content')
<div class="card-header border-0 bg-transparent py-2">
<h5 class="mb-0 fw-bold text-dark" style="font-size: 1.25rem;font-weight:500">Top-Up Settings</h5>
</div>
<div class="row justify-content-center align-items-center">
<div class="col-12 col-md-8 col-lg-6">
<div class="card-body p-3">
<form id="topUpSettingsForm">
<!-- Current Discount Display -->
<div class="mb-2">
<label class="form-label fw-bold" style="font-size: 1.5rem;">
Current Discount: <span style="color: #E74610;">2%</span>
</label>
</div>
<!-- Top Up Discount Input -->
<div class="mb-2">
<label for="discount" class="form-label" style="font-size: 1.2rem;">Top Up Discount (%)</label>
<div class="d-flex align-items-center">
<input type="number" class="form-control me-2" id="discount" placeholder="Enter discount percentage" value="3" step="0.01" required style="width: 300px; font-size: 1.2rem; padding: 10px;">
<button type="submit" class="btn btn-primary" style="background-color: #E74610; border-color: #E74610; font-size: 1rem; padding: 10px 20px;">SUBMIT</button>
</div>
</div>
<!-- Card Image -->
<div class="mt-3 text-center">
<img src="{{ asset('img/card.png') }}" alt="Loyalty Card" style="max-width: 300px;">
</div>
</form>
</div>
</div>
</div>
<style>
.card {
border-radius: 5px;
border: 1px solid #dee2e6;
}
.form-label {
font-size: 1.2rem;
}
.form-control {
font-size: 1.2rem;
}
.btn-primary:hover {
background-color: #e07b30;
border-color: #e07b30;
}
/* Ensure the input and button are closely aligned */
.d-flex.align-items-center {
gap: 0.5rem; /* Small gap between input and button */
}
</style>
@include('components.table-component', [
'pageTitle' => 'Top-up Settings',
'data' => $topUpSettings ?? [],
'columns' => [
['name' => 'Amount', 'key' => 'amount', 'sortable' => true],
['name' => 'Description', 'key' => 'description', 'sortable' => true],
['name' => 'Status', 'key' => 'status', 'sortable' => true],
['name' => 'Created At', 'key' => 'created_at', 'sortable' => true]
],
'allFields' => [
['name' => 'Amount', 'key' => 'amount', 'type' => 'number', 'required' => true, 'min' => 0],
['name' => 'Description', 'key' => 'description', 'type' => 'textarea', 'required' => true],
['name' => 'Status', 'key' => 'status', 'type' => 'select', 'options' => ['Active', 'Inactive'], 'required' => true]
],
'actions' => ['edit', 'delete'],
'showAddButton' => true,
'addButtonUrl' => route('top-up-settings.create'),
'showCheckboxes' => true,
'showBatchDelete' => true,
'showEditModal' => true,
'baseRoute' => 'top-up-settings'
])
@endsection
@push('scripts')
<script>
document.getElementById('topUpSettingsForm').addEventListener('submit', function(e) {
e.preventDefault();
const discount = document.getElementById('discount').value;
if (!discount || discount < 0) {
alert('Please enter a valid discount percentage.');
return;
$(document).ready(function() {
const dataTable = initializeDataTable({
route: '{{ route("top-up-settings.data") }}',
columns: [
{
data: 'amount',
render: function(data) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'PHP'
}).format(data);
}
// Simulate updating the discount (frontend-only)
sessionStorage.setItem('topUpDiscount', discount);
alert('Discount updated successfully!');
window.location.reload(); // Reload to reflect the new "Current Discount"
},
{ data: 'description' },
{
data: 'status',
render: function(data) {
return `<span class="badge badge-${data === 'Active' ? 'success' : 'danger'}">${data}</span>`;
}
},
{
data: 'created_at',
render: function(data) {
return moment(data).format('YYYY-MM-DD HH:mm:ss');
}
}
]
});
// On page load, update the current discount display if stored in sessionStorage
window.addEventListener('load', function() {
const storedDiscount = sessionStorage.getItem('topUpDiscount') || '2';
document.querySelector('.form-label.fw-bold span').textContent = `${storedDiscount}%`;
// Handle form submissions
handleFormSubmission({
addRoute: '{{ route("top-up-settings.store") }}',
editRoute: '{{ route("top-up-settings.update", ":id") }}',
deleteRoute: '{{ route("top-up-settings.destroy", ":id") }}',
dataTable: dataTable,
validation: {
amount: {
required: true,
number: true,
min: 0
},
description: {
required: true,
minlength: 3
},
status: {
required: true
}
}
});
});
</script>
@endsection
@endpush

View File

@ -4,10 +4,9 @@
@section('page_title', 'User Management')
@section('content')
<div id="user-table">
@include('components.table-component', [
'pageTitle' => 'User Management',
'data' => $users, // Use the data passed from the controller
'data' => $users ?? [], // Data from API
'columns' => [
['name' => 'Username', 'key' => 'username', 'sortable' => true],
['name' => 'First Name', 'key' => 'firstName', 'sortable' => true],
@ -16,16 +15,69 @@
['name' => 'Email', 'key' => 'email', 'sortable' => true],
['name' => 'Status', 'key' => 'status', 'sortable' => true]
],
'allFields' => [
['name' => 'Username', 'key' => 'username', 'type' => 'text', 'required' => true],
['name' => 'First Name', 'key' => 'firstName', 'type' => 'text', 'required' => true],
['name' => 'Last Name', 'key' => 'lastName', 'type' => 'text', 'required' => true],
['name' => 'Email', 'key' => 'email', 'type' => 'email', 'required' => true],
['name' => 'Password', 'key' => 'password', 'type' => 'password', 'required' => true, 'showOnEdit' => false],
['name' => 'Role', 'key' => 'role', 'type' => 'select', 'options' => $roles ?? ['admin', 'user'], 'required' => true],
['name' => 'Status', 'key' => 'status', 'type' => 'select', 'options' => ['Active', 'Inactive'], 'required' => true]
],
'actions' => ['edit', 'view', 'delete'],
'showAddButton' => true,
'addButtonUrl' => '/add-user',
'addButtonUrl' => route('user-management.create'),
'showCheckboxes' => true,
'showBatchDelete' => true,
'showEditModal' => true,
'showViewModal' => true
'showViewModal' => true,
'baseRoute' => 'user-management'
])
<div id="no-data-message" style="display: {{ empty($users) ? 'block' : 'none' }}; text-align: center; margin-top: 20px;">
<p>No Data Found</p>
</div>
</div>
@endsection
@push('scripts')
<script>
$(document).ready(function() {
const dataTable = initializeDataTable({
route: '{{ route("user-management.data") }}',
columns: [
{ data: 'username' },
{ data: 'firstName' },
{ data: 'lastName' },
{ data: 'role' },
{ data: 'email' },
{
data: 'status',
render: function(data) {
return `<span class="badge badge-${data === 'Active' ? 'success' : 'danger'}">${data}</span>`;
}
}
]
});
// Handle form submissions
handleFormSubmission({
addRoute: '{{ route("user-management.store") }}',
editRoute: '{{ route("user-management.update", ":id") }}',
deleteRoute: '{{ route("user-management.destroy", ":id") }}',
dataTable: dataTable,
validation: {
username: {
required: true,
minlength: 3
},
email: {
required: true,
email: true
},
password: {
required: function() {
return !$('#editForm').length; // Required only for new users
},
minlength: 8
}
}
});
});
</script>
@endpush

View File

@ -0,0 +1,218 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ isset($user) ? 'Edit User' : 'Create New User' }}</h3>
</div>
<div class="card-body">
@include('components.alerts')
<form id="userForm"
action="{{ isset($user) ? route('user-management.update', $user['id']) : route('user-management.store') }}"
method="POST"
class="needs-validation"
novalidate>
@csrf
@if(isset($user))
@method('PUT')
@endif
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="username">Username <span class="text-danger">*</span></label>
<input type="text"
class="form-control @error('username') is-invalid @enderror"
id="username"
name="username"
value="{{ old('username', isset($user) ? $user['username'] : '') }}"
required
pattern="[a-zA-Z0-9_]{3,}"
title="Username must be at least 3 characters long and can only contain letters, numbers, and underscores">
@error('username')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="form-text text-muted">
Minimum 3 characters, only letters, numbers, and underscores allowed
</small>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="email">Email Address <span class="text-danger">*</span></label>
<input type="email"
class="form-control @error('email') is-invalid @enderror"
id="email"
name="email"
value="{{ old('email', isset($user) ? $user['email'] : '') }}"
required>
@error('email')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
</div>
@if(!isset($user))
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="password">Password <span class="text-danger">*</span></label>
<div class="input-group">
<input type="password"
class="form-control @error('password') is-invalid @enderror"
id="password"
name="password"
required
minlength="8">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="togglePassword">
<i class="fas fa-eye"></i>
</button>
</div>
</div>
@error('password')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<small class="form-text text-muted">
Minimum 8 characters, must include uppercase, lowercase, number, and special character
</small>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="password_confirmation">Confirm Password <span class="text-danger">*</span></label>
<input type="password"
class="form-control"
id="password_confirmation"
name="password_confirmation"
required>
<div class="invalid-feedback">
Passwords do not match
</div>
</div>
</div>
</div>
@endif
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="role">Role <span class="text-danger">*</span></label>
<select class="form-control @error('role') is-invalid @enderror"
id="role"
name="role"
required>
<option value="">Select Role</option>
@foreach($roles as $roleOption)
<option value="{{ $roleOption }}"
{{ old('role', isset($user) ? $user['role'] : '') == $roleOption ? 'selected' : '' }}>
{{ ucfirst($roleOption) }}
</option>
@endforeach
</select>
@error('role')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="d-block">Status</label>
<div class="custom-control custom-switch">
<input type="checkbox"
class="custom-control-input"
id="is_active"
name="is_active"
value="1"
{{ old('is_active', isset($user) ? $user['is_active'] : true) ? 'checked' : '' }}>
<label class="custom-control-label" for="is_active">Active</label>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save mr-1"></i>
{{ isset($user) ? 'Update' : 'Create' }} User
</button>
<a href="{{ route('user-management.index') }}" class="btn btn-secondary">
<i class="fas fa-times mr-1"></i>
Cancel
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
$(document).ready(function() {
// Form validation
const form = document.getElementById('userForm');
form.addEventListener('submit', function(event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
});
// Password toggle visibility
$('#togglePassword').click(function() {
const passwordInput = $('#password');
const icon = $(this).find('i');
if (passwordInput.attr('type') === 'password') {
passwordInput.attr('type', 'text');
icon.removeClass('fa-eye').addClass('fa-eye-slash');
} else {
passwordInput.attr('type', 'password');
icon.removeClass('fa-eye-slash').addClass('fa-eye');
}
});
// Password confirmation validation
$('#password_confirmation').on('input', function() {
const password = $('#password').val();
const confirmation = $(this).val();
if (password !== confirmation) {
$(this).addClass('is-invalid');
} else {
$(this).removeClass('is-invalid');
}
});
// Password strength validation
$('#password').on('input', function() {
const password = $(this).val();
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
const isLongEnough = password.length >= 8;
if (!(hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChar && isLongEnough)) {
$(this).addClass('is-invalid');
} else {
$(this).removeClass('is-invalid');
}
});
});
</script>
@endpush

View File

@ -0,0 +1,170 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">User Management</h3>
<div class="card-tools">
<a href="{{ route('user-management.create') }}" class="btn btn-primary">
<i class="fas fa-plus"></i> Add New User
</a>
</div>
</div>
<div class="card-body">
@include('components.alerts')
<div class="mb-3">
<div class="row">
<div class="col-md-6">
<div class="input-group">
<input type="text" class="form-control" id="searchInput" placeholder="Search users...">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</div>
<div class="col-md-3">
<select class="form-control" id="roleFilter">
<option value="">All Roles</option>
@foreach($roles as $role)
<option value="{{ $role }}">{{ ucfirst($role) }}</option>
@endforeach
</select>
</div>
<div class="col-md-3">
<select class="form-control" id="statusFilter">
<option value="">All Status</option>
<option value="1">Active</option>
<option value="0">Inactive</option>
</select>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped" id="usersTable">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
<th>Role</th>
<th>Status</th>
<th>Created At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($users as $user)
<tr>
<td>{{ $user['id'] }}</td>
<td>{{ $user['username'] }}</td>
<td>{{ $user['email'] }}</td>
<td>
<span class="badge badge-info">
{{ ucfirst($user['role']) }}
</span>
</td>
<td>
<span class="badge badge-{{ $user['is_active'] ? 'success' : 'danger' }}">
{{ $user['is_active'] ? 'Active' : 'Inactive' }}
</span>
</td>
<td>{{ \Carbon\Carbon::parse($user['created_at'])->format('Y-m-d H:i') }}</td>
<td>
<div class="btn-group">
<a href="{{ route('user-management.edit', $user['id']) }}"
class="btn btn-sm btn-info"
data-toggle="tooltip"
title="Edit User">
<i class="fas fa-edit"></i>
</a>
<form action="{{ route('user-management.destroy', $user['id']) }}"
method="POST"
class="d-inline delete-form">
@csrf
@method('DELETE')
<button type="submit"
class="btn btn-sm btn-danger"
data-toggle="tooltip"
title="Delete User">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center">No users found</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-3">
{{ $users->links() }}
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('styles')
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.24/css/dataTables.bootstrap4.min.css">
@endpush
@push('scripts')
<script src="https://cdn.datatables.net/1.10.24/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.24/js/dataTables.bootstrap4.min.js"></script>
<script>
$(document).ready(function() {
// Initialize DataTable
const table = $('#usersTable').DataTable({
"responsive": true,
"processing": true,
"serverSide": true,
"ajax": "{{ route('user-management.data') }}",
"order": [[0, "desc"]],
"columns": [
{ "data": "id" },
{ "data": "username" },
{ "data": "email" },
{ "data": "role" },
{ "data": "status" },
{ "data": "created_at" },
{ "data": "actions", "orderable": false }
]
});
// Apply filters
$('#roleFilter, #statusFilter').change(function() {
table.ajax.reload();
});
// Search functionality
$('#searchInput').keyup(function() {
table.search($(this).val()).draw();
});
// Delete confirmation
$('.delete-form').on('submit', function(e) {
e.preventDefault();
if (confirm('Are you sure you want to delete this user?')) {
this.submit();
}
});
// Initialize tooltips
$('[data-toggle="tooltip"]').tooltip();
});
</script>
@endpush

View File

@ -1,158 +1,146 @@
<?php
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Http;
use App\Http\Controllers\AuthController;
use App\Http\Controllers\HomeController;
use App\Http\Controllers\UserManagementController;
use App\Http\Controllers\StationController;
use App\Http\Controllers\ContentController;
use App\Http\Controllers\CardMemberController;
use App\Http\Controllers\ReportsController;
use App\Http\Controllers\SystemParameterController;
use App\Http\Controllers\FuelPriceController;
use App\Http\Controllers\TopUpSettingController;
Route::get('/', function () {
return redirect()->route('login');
});
Route::get('/login', [AuthController::class, 'showLoginForm'])->name('login');
Route::post('/login', [AuthController::class, 'login'])->name('login');
// Auth Routes
Route::get('/', [AuthController::class, 'showLoginForm'])->name('login');
Route::post('/login', [AuthController::class, 'login'])->name('login.submit');
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
Route::get('/change-password', [AuthController::class, 'showChangePasswordForm'])->name('change-password');
Route::post('/change-password', [AuthController::class, 'changePassword'])->name('password.change');
// Protected Routes
Route::middleware(['auth'])->group(function () {
// Dashboard
Route::get('/dashboard', [HomeController::class, 'index'])->name('dashboard');
Route::get('/my-profile', [AuthController::class, 'showMyProfile'])->name('my-profile');
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
Route::get('/dashboard', function () {
return view('dashboard');
// User Management
Route::prefix('user-management')->group(function () {
Route::get('/', [UserManagementController::class, 'index'])->name('user-management.index');
Route::get('/create', [UserManagementController::class, 'create'])->name('user-management.create');
Route::post('/', [UserManagementController::class, 'store'])->name('user-management.store');
Route::get('/{id}/edit', [UserManagementController::class, 'edit'])->name('user-management.edit');
Route::put('/{id}', [UserManagementController::class, 'update'])->name('user-management.update');
Route::delete('/{id}', [UserManagementController::class, 'destroy'])->name('user-management.destroy');
});
Route::get('/user-management', [UserManagementController::class, 'index'])->name('user.management');
// Station Management
Route::prefix('stations')->group(function () {
Route::get('/', [StationController::class, 'index'])->name('stations.index');
Route::get('/create', [StationController::class, 'create'])->name('stations.create');
Route::post('/', [StationController::class, 'store'])->name('stations.store');
Route::get('/{id}/edit', [StationController::class, 'edit'])->name('stations.edit');
Route::put('/{id}', [StationController::class, 'update'])->name('stations.update');
Route::delete('/{id}', [StationController::class, 'destroy'])->name('stations.destroy');
Route::get('/notification', function () {
return view('pages.notification');
})->name('notification');
// Fuel Prices
Route::get('/fuel-prices', [StationController::class, 'fuelPrices'])->name('fuel-prices.index');
Route::post('/fuel-prices', [StationController::class, 'updateFuelPrices'])->name('fuel-prices.update');
Route::get('/fuel-prices/schedule', [StationController::class, 'fuelPriceSchedule'])->name('fuel-prices.schedule');
Route::post('/fuel-prices/schedule', [StationController::class, 'storeFuelPriceSchedule'])->name('fuel-prices.schedule.store');
Route::get('/fuel-prices/logs', [StationController::class, 'fuelPriceLogs'])->name('fuel-prices.logs');
});
Route::get('/card-member', function () {
return view('pages.member management.card-member');
})->name('card-member');
// Content Management
Route::prefix('content')->group(function () {
// Promotions
Route::get('/promotions', [ContentController::class, 'promotionsIndex'])->name('promotions.index');
Route::get('/promotions/create', [ContentController::class, 'createPromotion'])->name('promotions.create');
Route::post('/promotions', [ContentController::class, 'storePromotion'])->name('promotions.store');
Route::put('/promotions/{id}', [ContentController::class, 'updatePromotion'])->name('promotions.update');
Route::delete('/promotions/{id}', [ContentController::class, 'deletePromotion'])->name('promotions.destroy');
Route::get('/locked-accounts', function () {
return view('pages.member management.locked-accounts');
})->name('locked-accounts');
// Notifications
Route::get('/notifications', [ContentController::class, 'notificationsIndex'])->name('notifications.index');
Route::get('/notifications/create', [ContentController::class, 'createNotification'])->name('notifications.create');
Route::post('/notifications', [ContentController::class, 'storeNotification'])->name('notifications.store');
Route::put('/notifications/{id}', [ContentController::class, 'updateNotification'])->name('notifications.update');
Route::delete('/notifications/{id}', [ContentController::class, 'deleteNotification'])->name('notifications.destroy');
Route::get('/photo-slider', function () {
return view('pages.home page.photo-slider');
})->name('photo-slider');
// Photo Slider
Route::get('/slides', [ContentController::class, 'slidesIndex'])->name('slides.index');
Route::get('/slides/create', [ContentController::class, 'createSlide'])->name('slides.create');
Route::post('/slides', [ContentController::class, 'storeSlide'])->name('slides.store');
Route::put('/slides/{id}', [ContentController::class, 'updateSlide'])->name('slides.update');
Route::delete('/slides/{id}', [ContentController::class, 'deleteSlide'])->name('slides.destroy');
Route::get('/promotions', function () {
return view('pages.promotions');
})->name('promotions');
// Terms and Privacy
Route::get('/terms-and-privacy', [ContentController::class, 'termsAndPrivacy'])->name('terms-and-privacy.index');
Route::put('/terms-and-privacy', [ContentController::class, 'updateTermsAndPrivacy'])->name('terms-and-privacy.update');
});
Route::get('/top-up', function () {
return view('pages.top-up');
})->name('top-up');
// Card Member Management
Route::prefix('card-members')->group(function () {
Route::get('/', [CardMemberController::class, 'index'])->name('card-members.index');
Route::get('/create', [CardMemberController::class, 'create'])->name('card-members.create');
Route::post('/', [CardMemberController::class, 'store'])->name('card-members.store');
Route::get('/{id}/edit', [CardMemberController::class, 'edit'])->name('card-members.edit');
Route::put('/{id}', [CardMemberController::class, 'update'])->name('card-members.update');
Route::delete('/{id}', [CardMemberController::class, 'destroy'])->name('card-members.destroy');
Route::get('/card-types', function () {
return view('pages.about us.card-types');
})->name('card-types');
// Card Types
Route::get('/types', [CardMemberController::class, 'cardTypes'])->name('card-types.index');
Route::post('/types', [CardMemberController::class, 'storeCardType'])->name('card-types.store');
Route::put('/types/{id}', [CardMemberController::class, 'updateCardType'])->name('card-types.update');
Route::delete('/types/{id}', [CardMemberController::class, 'deleteCardType'])->name('card-types.destroy');
Route::get('/terms-and-privacy', function () {
return view('pages.about us.terms-and-privacy');
})->name('terms-and-privacy');
// Top-up
Route::get('/top-up', [CardMemberController::class, 'topUpHistory'])->name('top-up.index');
Route::post('/top-up', [CardMemberController::class, 'processTopUp'])->name('top-up.store');
});
Route::get('/registration-report', function () {
return view('pages.reports.registration-report');
})->name('registration-report');
// Reports
Route::prefix('reports')->group(function () {
Route::get('/registration', [ReportsController::class, 'registrationReport'])->name('reports.registration');
Route::get('/top-up-usage', [ReportsController::class, 'topUpUsageReport'])->name('reports.top-up-usage');
Route::get('/mobile-usage', [ReportsController::class, 'mobileUsageReport'])->name('reports.mobile-usage');
Route::get('/station-rating', [ReportsController::class, 'stationRatingReport'])->name('reports.station-rating');
Route::get('/{type}/export', [ReportsController::class, 'exportReport'])->name('reports.export');
});
Route::get('/top-up-usage-report', function () {
return view('pages.reports.top-up-usage-report');
})->name('top-up-usage-report');
// System Parameters
Route::prefix('system-parameters')->group(function () {
Route::get('/', [SystemParameterController::class, 'index'])->name('system-parameters.index');
Route::post('/', [SystemParameterController::class, 'store'])->name('system-parameters.store');
Route::put('/{id}', [SystemParameterController::class, 'update'])->name('system-parameters.update');
Route::delete('/{id}', [SystemParameterController::class, 'destroy'])->name('system-parameters.destroy');
});
Route::get('/mobile-usage-report', function () {
return view('pages.reports.mobile-usage-report');
})->name('mobile-usage-report');
// Fuel Price Management
Route::prefix('fuel-prices')->group(function () {
// On Demand
Route::get('/on-demand', [FuelPriceController::class, 'onDemand'])->name('fuel-price.on-demand');
Route::post('/on-demand', [FuelPriceController::class, 'updateOnDemand'])->name('fuel-price.on-demand.update');
Route::post('/on-demand/import', [FuelPriceController::class, 'importPrices'])->name('fuel-price.on-demand.import');
Route::get('/on-demand/export', [FuelPriceController::class, 'exportPrices'])->name('fuel-price.on-demand.export');
Route::get('/station-rating-report', function () {
return view('pages.reports.station-rating-report');
})->name('station-rating-report');
// Schedule
Route::get('/schedule', [FuelPriceController::class, 'schedule'])->name('fuel-price.schedule');
Route::post('/schedule', [FuelPriceController::class, 'storeSchedule'])->name('fuel-price.schedule.store');
Route::put('/schedule/{id}', [FuelPriceController::class, 'updateSchedule'])->name('fuel-price.schedule.update');
Route::delete('/schedule/{id}', [FuelPriceController::class, 'deleteSchedule'])->name('fuel-price.schedule.destroy');
Route::get('/system-parameters', function () {
return view('pages.system-parameters');
})->name('system-parameters');
// Logs
Route::get('/logs', [FuelPriceController::class, 'logs'])->name('fuel-price.logs');
});
Route::get('/branches', function () {
return view('pages.station locator.branches');
})->name('branches');
Route::get('/stations', function () {
return view('pages.station locator.stations');
})->name('stations');
Route::get('/fuels', function () {
return view('pages.station locator.fuels');
})->name('fuels');
Route::get('/add-user', function () {
return view('pages.user-management.add-user');
})->name('add-user');
Route::get('/add-notification', function () {
return view('pages.add-notification');
})->name('add-notification');
Route::get('/photo-slider', function () {
return view('pages.home page.photo-slider');
})->name('photo-slider');
Route::get('/promotions', function () {
return view('pages.promotions');
})->name('promotions');
Route::get('/add-photo-slider', function () {
return view('pages.add-photo-slider');
})->name('add-photo-slider');
Route::get('/add-promotions', function () {
return view('pages.add-promotions');
})->name('add-promotions');
Route::get('/add-top-up', function () {
return view('pages.add-top-up');
})->name('add-top-up');
Route::get('/add-card-types', function () {
return view('pages.add-card-types');
})->name('add-card-types');
Route::get('/add-terms-and-privacy', function () {
return view('pages.add-terms-and-privacy');
})->name('add-terms-and-privacy');
Route::get('/add-branches', function () {
return view('pages.add-branches');
})->name('add-branches');
Route::get('/add-fuels', function () {
return view('pages.add-fuels');
})->name('add-fuels');
Route::get('/add-station', function () {
return view('pages.add-station');
})->name('add-station');
//CMS-extension
Route::get('/top-up-settings', function () {
return view('pages.top-up-settings');
})->name('top-up-settings');
Route::get('/fuel-price-on-demand', function () {
return view('pages.fuel-price-on-demand');
})->name('fuel-price-on-demand');
Route::get('/fuel-price-schedule', function () {
return view('pages.fuel-price-schedule');
})->name('fuel-price-schedule');
Route::get('/fuel-price-update-logs', function () {
return view('pages.fuel-price-update-logs');
})->name('fuel-price-update-logs');
// Top-up Settings
Route::prefix('top-up-settings')->group(function () {
Route::get('/', [TopUpSettingController::class, 'index'])->name('top-up-settings.index');
Route::post('/', [TopUpSettingController::class, 'store'])->name('top-up-settings.store');
Route::put('/{id}', [TopUpSettingController::class, 'update'])->name('top-up-settings.update');
Route::delete('/{id}', [TopUpSettingController::class, 'destroy'])->name('top-up-settings.destroy');
});
});

27
webpack.mix.js Normal file
View File

@ -0,0 +1,27 @@
const mix = require('laravel-mix');
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css')
.copy('resources/css/custom.css', 'public/css')
.version();
// Copy assets
mix.copy('resources/img', 'public/img');
// Configure options
mix.options({
processCssUrls: false,
terser: {
extractComments: false,
terserOptions: {
compress: {
drop_console: true
}
}
}
});
// Enable source maps in development
if (!mix.inProduction()) {
mix.sourceMaps();
}