integration
This commit is contained in:
parent
93fb6eff66
commit
b2bc92e2b9
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Services\Api\UserManagementService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
@ -9,62 +10,81 @@ use Illuminate\Support\Facades\Session;
|
||||||
|
|
||||||
class UserManagementController extends Controller
|
class UserManagementController extends Controller
|
||||||
{
|
{
|
||||||
|
protected $userService;
|
||||||
protected $apiBaseUrl = 'http://192.168.100.6:8081/api'; // Same as in AuthController
|
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
|
* Display the user management page with user data
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
try {
|
$response = $this->userService->getAllUsers();
|
||||||
// Fetch the access token from the session
|
|
||||||
$user = Session::get('user');
|
if (!$response['success']) {
|
||||||
$accessToken = $user['access_token'] ?? null;
|
return back()->with('error', $response['message']);
|
||||||
|
|
||||||
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.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('Error fetching user data: ' . $e->getMessage());
|
|
||||||
return view('pages.user-management', [
|
|
||||||
'users' => [], // Pass an empty array on error
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return view('pages.user-management.index', [
|
||||||
|
'users' => $response['data']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -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')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,8 +31,9 @@ services:
|
||||||
- DB_DATABASE=unioil-database
|
- DB_DATABASE=unioil-database
|
||||||
- DB_USERNAME=rootuser
|
- DB_USERNAME=rootuser
|
||||||
- DB_PASSWORD=password
|
- DB_PASSWORD=password
|
||||||
|
- MYSQL_ROOT_PASSWORD=newpassword
|
||||||
- CACHE_DRIVER=file
|
- CACHE_DRIVER=file
|
||||||
- API_URL=http://backend-web:8081
|
- API_URL=http://nginx:8081
|
||||||
|
|
||||||
web-frontend:
|
web-frontend:
|
||||||
image: nginx:1.26.3-alpine
|
image: nginx:1.26.3-alpine
|
||||||
|
|
38
package.json
38
package.json
|
@ -1,25 +1,31 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"dev": "npm run development",
|
||||||
"dev": "vite"
|
"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": {
|
"devDependencies": {
|
||||||
"@popperjs/core": "^2.11.6",
|
"@popperjs/core": "^2.11.8",
|
||||||
"autoprefixer": "^10.4.20",
|
"axios": "^1.6.7",
|
||||||
"axios": "^1.9.0",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap": "^5.2.3",
|
"jquery": "^3.7.1",
|
||||||
"concurrently": "^9.0.1",
|
"laravel-mix": "^6.0.49",
|
||||||
"laravel-vite-plugin": "^1.2.0",
|
"lodash": "^4.17.21",
|
||||||
"postcss": "^8.4.47",
|
"resolve-url-loader": "^5.0.0",
|
||||||
"sass": "^1.56.1",
|
"sass": "^1.71.1",
|
||||||
"sass-embedded": "^1.87.0",
|
"sass-loader": "^14.1.1"
|
||||||
"tailwindcss": "^3.4.13",
|
|
||||||
"vite": "^6.3.4"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.8",
|
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||||
"bootstrap": "^5.3.5"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -776,4 +776,199 @@ footer .copyright {
|
||||||
|
|
||||||
/* ---------------------------------------------------
|
/* ---------------------------------------------------
|
||||||
MEDIAQUERIES
|
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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
});
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -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 });
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 });
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 });
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 });
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,156 +2,310 @@
|
||||||
'pageTitle' => '',
|
'pageTitle' => '',
|
||||||
'data' => [],
|
'data' => [],
|
||||||
'columns' => [],
|
'columns' => [],
|
||||||
|
'allFields' => [],
|
||||||
'actions' => [],
|
'actions' => [],
|
||||||
'showAddButton' => false,
|
'showAddButton' => false,
|
||||||
'addButtonUrl' => '#',
|
'addButtonUrl' => '#',
|
||||||
'showCheckboxes' => false,
|
'showCheckboxes' => false,
|
||||||
'showBatchDelete' => false,
|
'showBatchDelete' => false,
|
||||||
'showEditModal' => false,
|
'showEditModal' => false,
|
||||||
'showViewModal' => false
|
'showViewModal' => false,
|
||||||
|
'baseRoute' => ''
|
||||||
])
|
])
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
<div class="card-header border-0 bg-transparent">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<h5 class="card-title">{{ $pageTitle }}</h5>
|
||||||
<h5 class="mb-0 fw-bold text-dark">{{ $pageTitle }}</h5>
|
@if($showAddButton)
|
||||||
@if ($showAddButton)
|
<a href="{{ $addButtonUrl }}" class="btn btn-primary">
|
||||||
<a href="{{ $addButtonUrl }}" class="btn btn-primary btn-sm px-3">
|
<i class="fa-solid fa-plus me-1"></i>Add New
|
||||||
<i class="fa-solid fa-plus me-1"></i> Add {{ $pageTitle }}
|
</a>
|
||||||
</a>
|
@endif
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<!-- Search and Filters -->
|
<div class="table-responsive">
|
||||||
<div class="row mb-3 align-items-center">
|
<table class="table table-hover" id="dataTable">
|
||||||
<div class="col-12 col-md-6 mb-2 mb-md-0">
|
<thead>
|
||||||
<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" >
|
|
||||||
<tr>
|
<tr>
|
||||||
@if ($showCheckboxes)
|
@if($showCheckboxes)
|
||||||
<th class="text-center" style="width: 40px;">
|
<th width="50">
|
||||||
<input type="checkbox" id="selectAll">
|
<input type="checkbox" class="form-check-input" id="selectAll">
|
||||||
</th>
|
</th>
|
||||||
@endif
|
@endif
|
||||||
@foreach ($columns as $index => $column)
|
@foreach($columns as $column)
|
||||||
<th class="{{ $column['sortable'] ? 'sortable' : '' }}" data-column="{{ $index + 1 }}">
|
<th>{{ $column['name'] }}</th>
|
||||||
{{ $column['name'] }}
|
|
||||||
@if ($column['sortable'])
|
|
||||||
<i class="fa-solid fa-sort"></i>
|
|
||||||
@endif
|
|
||||||
</th>
|
|
||||||
@endforeach
|
@endforeach
|
||||||
@if (!empty($actions))
|
@if(count($actions) > 0)
|
||||||
<th class="text-center" style="width: 120px;">Action</th>
|
<th width="150">Actions</th>
|
||||||
@endif
|
@endif
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="tableBody"></tbody>
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($showEditModal)
|
||||||
<!-- Edit Modal -->
|
<!-- Edit Modal -->
|
||||||
@if ($showEditModal)
|
<div class="modal fade" id="editModal" tabindex="-1">
|
||||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
<div class="modal-dialog">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-content">
|
||||||
<div class="modal-content">
|
<div class="modal-header">
|
||||||
<div class="modal-header">
|
<h5 class="modal-title">Edit {{ $pageTitle }}</h5>
|
||||||
<h5 class="modal-title" id="editModalLabel">Edit {{ $pageTitle }}</h5>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
<button type="button" class="modal-close-btn" data-bs-dismiss="modal" aria-label="Close">
|
</div>
|
||||||
<i class="fa-solid fa-xmark"></i>
|
<form id="editForm">
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="editForm">
|
@foreach($allFields as $field)
|
||||||
@foreach ($columns as $column)
|
<div class="mb-3">
|
||||||
<div class="mb-3">
|
<label class="form-label">{{ $field['name'] }}</label>
|
||||||
<label for="edit{{ ucfirst($column['key']) }}" class="form-label">{{ $column['name'] }}</label>
|
@if($field['type'] === 'select')
|
||||||
@if ($column['key'] === 'status')
|
<select class="form-select" name="{{ $field['key'] }}" required="{{ $field['required'] ?? false }}">
|
||||||
<select class="form-select" id="edit{{ ucfirst($column['key']) }}">
|
@foreach($field['options'] as $option)
|
||||||
<option value="Active">Active</option>
|
<option value="{{ $option }}">{{ $option }}</option>
|
||||||
<option value="Inactive">Inactive</option>
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
@else
|
@elseif($field['type'] === 'textarea')
|
||||||
<input type="{{ $column['key'] === 'email' ? 'email' : 'text' }}"
|
<textarea class="form-control" name="{{ $field['key'] }}" rows="3" required="{{ $field['required'] ?? false }}"></textarea>
|
||||||
class="form-control"
|
@else
|
||||||
id="edit{{ ucfirst($column['key']) }}"
|
<input type="{{ $field['type'] }}"
|
||||||
{{ $column['key'] === 'username' || $column['key'] === 'memberId' ? 'readonly' : '' }}>
|
class="form-control"
|
||||||
@endif
|
name="{{ $field['key'] }}"
|
||||||
</div>
|
required="{{ $field['required'] ?? false }}"
|
||||||
@endforeach
|
@if(isset($field['min'])) min="{{ $field['min'] }}" @endif
|
||||||
</form>
|
@if(isset($field['step'])) step="{{ $field['step'] }}" @endif>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
<button type="button" class="btn btn-primary" id="updateBtn">Update</button>
|
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if($showViewModal)
|
||||||
<!-- View Modal -->
|
<!-- View Modal -->
|
||||||
@if ($showViewModal)
|
<div class="modal fade" id="viewModal" tabindex="-1">
|
||||||
<div class="modal fade" id="viewModal" tabindex="-1" aria-labelledby="viewModalLabel" aria-hidden="true">
|
<div class="modal-dialog">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-content">
|
||||||
<div class="modal-content">
|
<div class="modal-header">
|
||||||
<div class="modal-header">
|
<h5 class="modal-title">View {{ $pageTitle }}</h5>
|
||||||
<h5 class="modal-title" id="viewModalLabel">View {{ $pageTitle }} Details</h5>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
<button type="button" class="modal-close-btn" data-bs-dismiss="modal" aria-label="Close">
|
</div>
|
||||||
<i class="fa-solid fa-xmark"></i>
|
<div class="modal-body">
|
||||||
</button>
|
@foreach($allFields as $field)
|
||||||
</div>
|
<div class="mb-3">
|
||||||
<div class="modal-body">
|
<label class="fw-bold">{{ $field['name'] }}:</label>
|
||||||
<div class="view-details">
|
<div id="view_{{ $field['key'] }}"></div>
|
||||||
@foreach ($columns as $column)
|
|
||||||
<p><strong>{{ $column['name'] }}:</strong> <span id="view{{ ucfirst($column['key']) }}"></span></p>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endforeach
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
@if ($pageTitle === 'Locked Account')
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-success" id="activateAccountBtn">Activate Account</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
@endif
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
@endif
|
@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>
|
<style>
|
||||||
.card,
|
.card,
|
||||||
|
@ -368,400 +522,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</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>
|
|
|
@ -5,32 +5,21 @@
|
||||||
<!-- Required meta tags -->
|
<!-- Required meta tags -->
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<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>
|
<title>CMS-Laravel</title>
|
||||||
<link rel="stylesheet" href="{{ asset('css/bootstrap.min.css') }}">
|
|
||||||
<link rel="stylesheet" href="{{ asset('css/custom.css') }}">
|
<!-- CSS Dependencies -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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 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') }}">
|
<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 -->
|
@stack('styles')
|
||||||
<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>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -272,20 +261,49 @@
|
||||||
<footer class="bg-light py-3 mt-4">
|
<footer class="bg-light py-3 mt-4">
|
||||||
<div class="container text-center">
|
<div class="container text-center">
|
||||||
<p class="mb-0 text-muted" style="font-family: 'Roboto', sans-serif; font-size: 0.9rem;">
|
<p class="mb-0 text-muted" style="font-family: 'Roboto', sans-serif; font-size: 0.9rem;">
|
||||||
All Rights Reserved © 2025 Unioil CMS
|
All Rights Reserved © {{ date('Y') }} Unioil CMS
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Optional JavaScript -->
|
<!-- JavaScript Dependencies -->
|
||||||
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||||
<script src="js/jquery-3.3.1.slim.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script>
|
||||||
<script src="js/popper.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="js/bootstrap.min.js"></script>
|
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
|
||||||
<script src="js/jquery-3.3.1.min.js"></script>
|
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>
|
||||||
<script type="text/javascript">
|
<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() {
|
$(document).ready(function() {
|
||||||
$('#sidebarCollapse').on('click', function() {
|
$('#sidebarCollapse').on('click', function() {
|
||||||
$('#sidebar').toggleClass('active');
|
$('#sidebar').toggleClass('active');
|
||||||
|
@ -297,6 +315,7 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@stack('scripts')
|
||||||
</body>
|
</body>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
||||||
</html>
|
</html>
|
|
@ -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>
|
|
@ -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>
|
|
@ -3,84 +3,287 @@
|
||||||
@section('page_title', 'Add Station')
|
@section('page_title', 'Add Station')
|
||||||
|
|
||||||
@section('content')
|
@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">
|
<form id="stationForm"
|
||||||
<h5 class="mb-0 fw-bold text-dark" style="font-size: 1.25rem;">Add New Station</h5>
|
action="{{ isset($station) ? route('stations.update', $station['id']) : route('stations.store') }}"
|
||||||
</div>
|
method="POST"
|
||||||
<div class="card-body py-2">
|
enctype="multipart/form-data"
|
||||||
<form action="#" method="POST" id="addStationForm">
|
class="needs-validation"
|
||||||
@csrf
|
novalidate>
|
||||||
<div class="mb-2">
|
@csrf
|
||||||
<label for="stationCode" class="form-label mb-1">Station Code</label>
|
@if(isset($station))
|
||||||
<input type="text" class="form-control form-control-sm" id="stationCode" name="stationCode" required>
|
@method('PUT')
|
||||||
</div>
|
@endif
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<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>
|
<div class="row">
|
||||||
.card {
|
<div class="col-md-6">
|
||||||
border-radius: 8px;
|
<div class="form-group">
|
||||||
}
|
<label for="name">Station Name <span class="text-danger">*</span></label>
|
||||||
.form-control,
|
<input type="text"
|
||||||
.form-select {
|
class="form-control"
|
||||||
font-size: 0.85rem;
|
id="name"
|
||||||
border-radius: 4px;
|
name="name"
|
||||||
padding: 0.375rem 0.75rem;
|
value="{{ old('name', $station['name'] ?? '') }}"
|
||||||
}
|
required>
|
||||||
.form-label {
|
</div>
|
||||||
font-weight: 500;
|
</div>
|
||||||
font-size: 0.85rem;
|
<div class="col-md-6">
|
||||||
line-height: 1.2;
|
<div class="form-group">
|
||||||
}
|
<label for="code">Station Code <span class="text-danger">*</span></label>
|
||||||
.btn-primary {
|
<input type="text"
|
||||||
background-color: #E74610;
|
class="form-control"
|
||||||
border-color: #E74610;
|
id="code"
|
||||||
font-size: 0.85rem;
|
name="code"
|
||||||
padding: 0.375rem 0.75rem;
|
value="{{ old('code', $station['code'] ?? '') }}"
|
||||||
}
|
required>
|
||||||
.btn-primary:hover {
|
</div>
|
||||||
background-color: #d63f0e;
|
</div>
|
||||||
border-color: #d63f0e;
|
</div>
|
||||||
}
|
|
||||||
.btn-outline-secondary {
|
<div class="row">
|
||||||
font-size: 0.85rem;
|
<div class="col-md-6">
|
||||||
padding: 0.375rem 0.75rem;
|
<div class="form-group">
|
||||||
}
|
<label for="address">Address <span class="text-danger">*</span></label>
|
||||||
.form-control-sm,
|
<textarea class="form-control"
|
||||||
.form-select-sm {
|
id="address"
|
||||||
height: calc(1.5em + 0.5rem + 2px);
|
name="address"
|
||||||
}
|
rows="3"
|
||||||
</style>
|
required>{{ old('address', $station['address'] ?? '') }}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@endsection
|
@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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,84 +1,151 @@
|
||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('page_title', 'Fuel Price On-Demand')
|
@section('page_title', 'Fuel Price On Demand')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="card-header border-0 bg-transparent py-2">
|
<div class="container-fluid">
|
||||||
<h5 class="mb-0 fw-bold text-dark" style="font-size: 1.25rem;font-weight:500">Fuel Price On-Demand</h5>
|
<div class="row">
|
||||||
</div>
|
<div class="col-12">
|
||||||
<div class="row justify-content-center align-items-center">
|
<div class="card">
|
||||||
<div class="col-12 col-md-8 col-lg-6">
|
<div class="card-header">
|
||||||
<div class="card-body p-3">
|
<h3 class="card-title">Fuel Price On Demand</h3>
|
||||||
<form id="fuelPriceForm">
|
<div class="card-tools">
|
||||||
<!-- Import File Section -->
|
<div class="btn-group">
|
||||||
<div class="mb-3">
|
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#importModal">
|
||||||
<label class="form-label fw-bold" style="font-size: 1.5rem;">Import File</label>
|
<i class="fas fa-file-import"></i> Import
|
||||||
<div class="input-group mb-3">
|
</button>
|
||||||
<input type="file" class="form-control" id="fuelPriceFile" style="font-size: 0.9rem; margin: 10px;">
|
<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>
|
||||||
|
</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
|
||||||
|
|
||||||
<!-- Buttons -->
|
<form action="{{ route('fuel-price.on-demand.update') }}" method="POST" class="mb-4">
|
||||||
<div class="d-flex justify-content-center gap-3" >
|
@csrf
|
||||||
<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;">
|
<div class="table-responsive">
|
||||||
SUBMIT <i class="fas fa-paper-plane ms-2"></i>
|
<table class="table table-bordered">
|
||||||
</button>
|
<thead>
|
||||||
<button type="button" id="exportFuelPrices" class="btn btn-secondary d-flex align-items-center" style="font-size: 1.2rem; padding: 10px 20px;">
|
<tr>
|
||||||
EXPORT FUEL PRICES <i class="fas fa-cloud-download-alt ms-2"></i>
|
<th>Station</th>
|
||||||
</button>
|
@foreach($fuelTypes ?? [] as $fuelType)
|
||||||
</div>
|
<th>{{ $fuelType['name'] }}</th>
|
||||||
</form>
|
@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>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<!-- Import Modal -->
|
||||||
.card {
|
<div class="modal fade" id="importModal" tabindex="-1">
|
||||||
border-radius: 5px;
|
<div class="modal-dialog">
|
||||||
border: 1px solid #dee2e6;
|
<div class="modal-content">
|
||||||
}
|
<form action="{{ route('fuel-price.on-demand.import') }}" method="POST" enctype="multipart/form-data">
|
||||||
.form-label {
|
@csrf
|
||||||
font-size: 1.2rem;
|
<div class="modal-header">
|
||||||
}
|
<h5 class="modal-title">Import Fuel Prices</h5>
|
||||||
.form-control {
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
font-size: 1.2rem;
|
</div>
|
||||||
}
|
<div class="modal-body">
|
||||||
.btn-primary:hover {
|
<div class="mb-3">
|
||||||
background-color: #e07b30;
|
<label for="csv_file" class="form-label">CSV File</label>
|
||||||
border-color: #e07b30;
|
<input type="file"
|
||||||
}
|
class="form-control"
|
||||||
.btn-secondary {
|
id="csv_file"
|
||||||
background-color: #6c757d;
|
name="csv_file"
|
||||||
border-color: #6c757d;
|
accept=".csv"
|
||||||
}
|
required>
|
||||||
.btn-secondary:hover {
|
<small class="form-text text-muted">
|
||||||
background-color: #5a6268;
|
Please upload a CSV file with the following columns: Station ID, Fuel Type ID, Price
|
||||||
border-color: #5a6268;
|
</small>
|
||||||
}
|
</div>
|
||||||
.d-flex.justify-content-center {
|
</div>
|
||||||
gap: 1rem; /* Space between buttons */
|
<div class="modal-footer">
|
||||||
}
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
</style>
|
<button type="submit" class="btn btn-primary">Import</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
<script>
|
@push('scripts')
|
||||||
document.getElementById('fuelPriceForm').addEventListener('submit', function(e) {
|
<script>
|
||||||
e.preventDefault();
|
$(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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const fileInput = document.getElementById('fuelPriceFile');
|
// Handle form submission
|
||||||
if (!fileInput.files.length) {
|
$('form').on('submit', function() {
|
||||||
alert('Please select a file to import.');
|
const emptyInputs = $(this).find('input[type="number"]').filter(function() {
|
||||||
return;
|
return !this.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (emptyInputs.length > 0) {
|
||||||
|
if (!confirm('Some prices are empty. Do you want to continue?')) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Simulate file import (frontend-only)
|
// Handle file input
|
||||||
alert('File imported successfully!');
|
$('#csv_file').on('change', function() {
|
||||||
fileInput.value = ''; // Reset file input
|
const file = this.files[0];
|
||||||
});
|
const fileType = file.type || 'application/octet-stream';
|
||||||
|
|
||||||
document.getElementById('exportFuelPrices').addEventListener('click', function() {
|
if (fileType !== 'text/csv' && !file.name.endsWith('.csv')) {
|
||||||
// Simulate export action (frontend-only)
|
alert('Please upload a CSV file');
|
||||||
alert('Fuel prices exported successfully!');
|
this.value = '';
|
||||||
});
|
}
|
||||||
</script>
|
});
|
||||||
@endsection
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
|
@ -3,192 +3,199 @@
|
||||||
@section('page_title', 'Fuel Price Schedule')
|
@section('page_title', 'Fuel Price Schedule')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="card-header border-0 bg-transparent py-2">
|
@include('components.table-component', [
|
||||||
<h5 class="mb-0 fw-bold text-dark" style="font-size: 1.25rem;font-weight:500">Fuel Price Schedule</h5>
|
'pageTitle' => 'Fuel Price Schedule',
|
||||||
</div>
|
'data' => $schedules ?? [],
|
||||||
<div class="row justify-content-center">
|
'columns' => [
|
||||||
<div class="card-body p-3">
|
['name' => 'Station', 'key' => 'station_name', 'sortable' => true],
|
||||||
<form id="fuelPriceScheduleForm">
|
['name' => 'Fuel Type', 'key' => 'fuel_type', 'sortable' => true],
|
||||||
<!-- Import File Section -->
|
['name' => 'Current Price', 'key' => 'current_price', 'sortable' => true],
|
||||||
<div class="mb-3">
|
['name' => 'New Price', 'key' => 'new_price', 'sortable' => true],
|
||||||
<div class="custom-file" style="width: 400px;">
|
['name' => 'Schedule Date', 'key' => 'schedule_date', 'sortable' => true],
|
||||||
<input type="file" class="custom-file-input" id="fuelPriceFile" style="font-size: 0.9rem;">
|
['name' => 'Status', 'key' => 'status', 'sortable' => true],
|
||||||
<label class="custom-file-label" for="fuelPriceFile" style="font-size: 0.9rem; padding: 8px;">Choose File</label>
|
['name' => 'Created By', 'key' => 'created_by', 'sortable' => true],
|
||||||
</div>
|
['name' => 'Created At', 'key' => 'created_at', 'sortable' => true]
|
||||||
</div>
|
],
|
||||||
|
'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 -->
|
@push('scripts')
|
||||||
<div class="mb-3">
|
<script>
|
||||||
<label for="scheduleDate" class="form-label">Date</label>
|
$(document).ready(function() {
|
||||||
<div class="input-group">
|
const dataTable = initializeDataTable({
|
||||||
<input type="text" class="form-control" id="scheduleDate" placeholder="dd/mm/yyyy" required>
|
route: '{{ route("fuel-price-schedule.data") }}',
|
||||||
<div class="input-group-append">
|
columns: [
|
||||||
<span class="input-group-text"><i class="fas fa-calendar-alt"></i></span>
|
{ data: 'station_name' },
|
||||||
</div>
|
{ data: 'fuel_type' },
|
||||||
</div>
|
{
|
||||||
</div>
|
data: 'current_price',
|
||||||
|
render: function(data) {
|
||||||
<!-- Time Input -->
|
return new Intl.NumberFormat('en-PH', {
|
||||||
<div class="mb-3">
|
style: 'currency',
|
||||||
<label for="scheduleTime" class="form-label">Time</label>
|
currency: 'PHP'
|
||||||
<div class="input-group">
|
}).format(data);
|
||||||
<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>
|
data: 'new_price',
|
||||||
</div>
|
render: function(data) {
|
||||||
</div>
|
return new Intl.NumberFormat('en-PH', {
|
||||||
|
style: 'currency',
|
||||||
<!-- Buttons -->
|
currency: 'PHP'
|
||||||
<div class="d-flex justify-content-center gap-2 mt-4">
|
}).format(data);
|
||||||
<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;">
|
data: 'schedule_date',
|
||||||
EXPORT FUEL PRICES <i class="fas fa-cloud-download-alt ms-2"></i>
|
render: function(data) {
|
||||||
</button>
|
return moment(data).format('YYYY-MM-DD HH:mm:ss');
|
||||||
</div>
|
}
|
||||||
</form>
|
},
|
||||||
</div>
|
{
|
||||||
</div>
|
data: 'status',
|
||||||
|
render: function(data) {
|
||||||
<style>
|
const colors = {
|
||||||
.card {
|
'Pending': 'warning',
|
||||||
border-radius: 5px;
|
'Completed': 'success',
|
||||||
border: 1px solid #dee2e6;
|
'Cancelled': 'danger'
|
||||||
}
|
};
|
||||||
.form-label {
|
return `<span class="badge badge-${colors[data]}">${data}</span>`;
|
||||||
font-size: 0.95rem;
|
}
|
||||||
}
|
},
|
||||||
.form-control {
|
{ data: 'created_by' },
|
||||||
font-size: 0.9rem;
|
{
|
||||||
width: 100%;
|
data: 'created_at',
|
||||||
}
|
render: function(data) {
|
||||||
.custom-file {
|
return moment(data).format('YYYY-MM-DD HH:mm:ss');
|
||||||
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;
|
|
||||||
}
|
|
||||||
</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;
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
order: [[4, 'asc']] // Sort by schedule_date by default
|
||||||
|
});
|
||||||
|
|
||||||
// Validate date format (dd/mm/yyyy)
|
// Handle form submissions
|
||||||
const datePattern = /^\d{2}\/\d{2}\/\d{4}$/;
|
handleFormSubmission({
|
||||||
if (!datePattern.test(scheduleDate)) {
|
addRoute: '{{ route("fuel-price-schedule.store") }}',
|
||||||
alert('Please enter a valid date in the format dd/mm/yyyy.');
|
editRoute: '{{ route("fuel-price-schedule.update", ":id") }}',
|
||||||
return;
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
// Validate time format (hh:mm AM/PM)
|
customButtons: [
|
||||||
const timePattern = /^(0?[1-9]|1[0-2]):[0-5][0-9] (AM|PM)$/;
|
{
|
||||||
if (!timePattern.test(scheduleTime)) {
|
text: 'Cancel Schedule',
|
||||||
alert('Please enter a valid time in the format hh:mm AM/PM.');
|
action: function(data) {
|
||||||
return;
|
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'
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
// Simulate scheduling (frontend-only)
|
// Add date range filter
|
||||||
const scheduleData = {
|
$('#date-range').daterangepicker({
|
||||||
file: fileInput.files[0]?.name,
|
ranges: {
|
||||||
date: scheduleDate,
|
'Today': [moment(), moment()],
|
||||||
time: scheduleTime
|
'Tomorrow': [moment().add(1, 'days'), moment().add(1, 'days')],
|
||||||
};
|
'Next 7 Days': [moment(), moment().add(6, 'days')],
|
||||||
sessionStorage.setItem('fuelPriceSchedule', JSON.stringify(scheduleData));
|
'Next 30 Days': [moment(), moment().add(29, 'days')],
|
||||||
alert('Fuel price update scheduled successfully!');
|
'This Month': [moment().startOf('month'), moment().endOf('month')],
|
||||||
fileInput.value = ''; // Reset file input
|
'Next Month': [moment().add(1, 'month').startOf('month'), moment().add(1, 'month').endOf('month')]
|
||||||
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>
|
}, function(start, end) {
|
||||||
@endsection
|
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>
|
||||||
|
.badge {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0.375rem 0.5rem;
|
||||||
|
}
|
||||||
|
.price-change {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.price-increase {
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
.price-decrease {
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
|
@ -3,219 +3,137 @@
|
||||||
@section('page_title', 'Fuel Price Update Logs')
|
@section('page_title', 'Fuel Price Update Logs')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@php
|
<div class="container-fluid">
|
||||||
$fuelPriceUpdateLogs = [
|
<div class="row">
|
||||||
[
|
<div class="col-12">
|
||||||
'id' => 1,
|
<div class="card">
|
||||||
'schedule' => 'On-demand',
|
<div class="card-header">
|
||||||
'isCompleted' => 'Yes',
|
<h3 class="card-title">Price Update History</h3>
|
||||||
'isSuccess' => 'Yes',
|
<div class="card-tools">
|
||||||
'updatedBy' => 'Graxia Montino',
|
<form class="form-inline">
|
||||||
'createdAt' => '2024-04-18 10:39 am'
|
<div class="input-group">
|
||||||
],
|
<input type="text"
|
||||||
[
|
class="form-control"
|
||||||
'id' => 2,
|
id="dateRange"
|
||||||
'schedule' => '2023-11-17 09:24 am',
|
name="date_range"
|
||||||
'isCompleted' => 'Yes',
|
value="{{ request('date_range') }}"
|
||||||
'isSuccess' => 'No',
|
placeholder="Select date range">
|
||||||
'updatedBy' => 'Graxia Montino',
|
<button type="submit" class="btn btn-primary">
|
||||||
'createdAt' => '2023-11-17 09:23 am',
|
<i class="fas fa-search"></i>
|
||||||
'errorMessage' => 'Please recheck the CSV file, some fuels doesn\'t exist.'
|
</button>
|
||||||
],
|
</div>
|
||||||
[
|
</form>
|
||||||
'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.'
|
|
||||||
],
|
|
||||||
];
|
|
||||||
@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>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="card-body">
|
||||||
<button type="button" class="btn btn-export-csv" id="exportCsv" style="margin:20px">Export CSV</button>
|
<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
|
||||||
|
$change = $log['new_price'] - $log['old_price'];
|
||||||
|
$changeClass = $change > 0 ? 'text-danger' : ($change < 0 ? 'text-success' : 'text-secondary');
|
||||||
|
@endphp
|
||||||
|
<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>
|
||||||
|
|
||||||
|
@if(isset($logs) && count($logs) > 0)
|
||||||
|
<div class="mt-3">
|
||||||
|
{{ $logs->links() }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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');
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'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>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
<style>
|
@push('scripts')
|
||||||
.card {
|
<script>
|
||||||
border-radius: 5px;
|
$(document).ready(function() {
|
||||||
border: 1px solid #dee2e6;
|
// Initialize DateRangePicker
|
||||||
|
$('#dateRange').daterangepicker({
|
||||||
|
autoUpdateInput: false,
|
||||||
|
locale: {
|
||||||
|
cancelLabel: 'Clear'
|
||||||
}
|
}
|
||||||
.form-control {
|
});
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
$('#dateRange').on('apply.daterangepicker', function(ev, picker) {
|
||||||
let originalData = [...tableConfig.data];
|
$(this).val(picker.startDate.format('YYYY-MM-DD') + ' - ' + picker.endDate.format('YYYY-MM-DD'));
|
||||||
const storedData = JSON.parse(sessionStorage.getItem('fuelPriceUpdateLogs') || '[]');
|
});
|
||||||
if (storedData.length > 0) {
|
|
||||||
tableConfig.data = [...tableConfig.data, ...storedData];
|
|
||||||
originalData = [...tableConfig.data];
|
|
||||||
}
|
|
||||||
|
|
||||||
const startDateInput = document.getElementById('startDate');
|
$('#dateRange').on('cancel.daterangepicker', function(ev, picker) {
|
||||||
const endDateInput = document.getElementById('endDate');
|
$(this).val('');
|
||||||
const exportCsvBtn = document.getElementById('exportCsv');
|
});
|
||||||
|
|
||||||
function filterData() {
|
// Initialize DataTable
|
||||||
const startDate = startDateInput.value ? new Date(startDateInput.value) : null;
|
$('table').DataTable({
|
||||||
const endDate = endDateInput.value ? new Date(endDateInput.value) : null;
|
order: [[7, 'desc']], // Sort by updated_at by default
|
||||||
|
pageLength: 25,
|
||||||
|
dom: 'Bfrtip',
|
||||||
|
buttons: [
|
||||||
|
'copy', 'csv', 'excel', 'pdf', 'print'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
tableConfig.data = originalData.filter(item => {
|
@push('styles')
|
||||||
// Parse the createdAt date (format: "YYYY-MM-DD hh:mm am/pm")
|
<style>
|
||||||
const dateParts = item.createdAt.split(' ');
|
.text-danger {
|
||||||
const dateStr = dateParts[0]; // "YYYY-MM-DD"
|
color: #dc3545 !important;
|
||||||
const timeStr = dateParts[1] + ' ' + dateParts[2]; // "hh:mm am/pm"
|
}
|
||||||
const itemDate = new Date(`${dateStr} ${timeStr}`);
|
.text-success {
|
||||||
|
color: #28a745 !important;
|
||||||
if (startDate && itemDate < startDate) return false;
|
}
|
||||||
if (endDate) {
|
.text-secondary {
|
||||||
const endDateAdjusted = new Date(endDate);
|
color: #6c757d !important;
|
||||||
endDateAdjusted.setHours(23, 59, 59, 999);
|
}
|
||||||
if (itemDate > endDateAdjusted) return false;
|
</style>
|
||||||
}
|
@endpush
|
||||||
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
|
|
|
@ -3,99 +3,260 @@
|
||||||
@section('page_title', 'My Profile')
|
@section('page_title', 'My Profile')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="card w-100">
|
<div class="container-fluid">
|
||||||
<!-- Banner -->
|
<div class="row">
|
||||||
<div class="banner d-flex align-items-center p-3">
|
<div class="col-md-4">
|
||||||
<div class="banner-icon me-3">
|
<div class="card">
|
||||||
<i class="fas fa-user-circle" style="font-size: 40px; color: #6c757d;"></i>
|
<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>
|
||||||
|
@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>
|
||||||
<h4 class="fw-bold text-primary mb-0" style="margin-left:10px">{{ $user['admin']['username'] ?? 'N/A' }}</h4>
|
|
||||||
</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')
|
||||||
|
|
||||||
<!-- Profile Section -->
|
<div class="row mb-3">
|
||||||
<div class="card-body p-4">
|
<div class="col-md-6">
|
||||||
<div class="row">
|
<div class="form-group">
|
||||||
<!-- Profile Information -->
|
<label for="firstName">First Name</label>
|
||||||
<div class="col-md-9">
|
<input type="text" class="form-control" id="firstName" name="firstName" value="{{ old('firstName', $user['firstName'] ?? '') }}" required>
|
||||||
<h3 class="fw-bold mb-3" style="font-size: 20px; font-weight:400">My Information</h3>
|
</div>
|
||||||
<div class="mb-2">
|
</div>
|
||||||
<span class="fw-bold text-dark">Username: </span>
|
<div class="col-md-6">
|
||||||
<span>{{ $user['admin']['username'] ?? 'N/A' }}</span>
|
<div class="form-group">
|
||||||
</div>
|
<label for="lastName">Last Name</label>
|
||||||
<div class="mb-2">
|
<input type="text" class="form-control" id="lastName" name="lastName" value="{{ old('lastName', $user['lastName'] ?? '') }}" required>
|
||||||
<span class="fw-bold text-dark">Email: </span>
|
</div>
|
||||||
<a href="mailto:{{ $user['admin']['email'] ?? 'N/A' }}" class="text-primary">{{ $user['admin']['email'] ?? 'N/A' }}</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<span class="fw-bold text-dark">Access Role: </span>
|
<div class="row mb-3">
|
||||||
<span>{{ $user['admin']['role'] ?? 'N/A' }}</span>
|
<div class="col-md-6">
|
||||||
</div>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
<style>
|
@push('scripts')
|
||||||
.card {
|
<script>
|
||||||
border-radius: 5px;
|
$(document).ready(function() {
|
||||||
border: 1px solid #dee2e6;
|
// Profile form validation and submission
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
$('#profileForm').validate({
|
||||||
margin-top: 1rem;
|
rules: {
|
||||||
}
|
firstName: {
|
||||||
.banner {
|
required: true,
|
||||||
background-color: #e6f0fa;
|
minlength: 2
|
||||||
border-top-left-radius: 5px;
|
},
|
||||||
border-top-right-radius: 5px;
|
lastName: {
|
||||||
border-bottom: 1px solid #dee2e6;
|
required: true,
|
||||||
}
|
minlength: 2
|
||||||
.text-primary {
|
},
|
||||||
color: #003087 !important;
|
email: {
|
||||||
}
|
required: true,
|
||||||
.text-dark {
|
email: true
|
||||||
color: #343a40 !important;
|
},
|
||||||
}
|
phone: {
|
||||||
.profile-picture img {
|
pattern: /^[0-9+\-\s()]*$/
|
||||||
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;
|
messages: {
|
||||||
}
|
firstName: {
|
||||||
.card-body .fw-bold {
|
required: "Please enter your first name",
|
||||||
font-size: 0.9rem;
|
minlength: "First name must be at least 2 characters"
|
||||||
}
|
},
|
||||||
.banner {
|
lastName: {
|
||||||
padding: 1.5rem;
|
required: "Please enter your last name",
|
||||||
}
|
minlength: "Last name must be at least 2 characters"
|
||||||
.banner-icon i {
|
},
|
||||||
font-size: 30px;
|
email: {
|
||||||
}
|
required: "Please enter your email address",
|
||||||
.banner h4 {
|
email: "Please enter a valid email address"
|
||||||
font-size: 1.25rem;
|
},
|
||||||
|
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]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</style>
|
});
|
||||||
@endsection
|
|
||||||
|
// 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 {
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
|
@ -1,44 +1,182 @@
|
||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('page_title', 'Notification')
|
@section('page_title', 'Notifications')
|
||||||
|
|
||||||
@section('content')
|
@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', [
|
@include('components.table-component', [
|
||||||
'pageTitle' => 'Notification',
|
'pageTitle' => 'Notifications',
|
||||||
'data' => $notifications,
|
'data' => $notifications ?? [],
|
||||||
'columns' => [
|
'columns' => [
|
||||||
['name' => 'ID', 'key' => 'id', 'sortable' => true],
|
['name' => 'Title', 'key' => 'title', 'sortable' => true],
|
||||||
['name' => 'Subject', 'key' => 'subject', 'sortable' => true],
|
['name' => 'Message', 'key' => 'message', 'sortable' => true],
|
||||||
['name' => 'Content', 'key' => 'content', 'sortable' => true],
|
['name' => 'Type', 'key' => 'type', 'sortable' => true],
|
||||||
['name' => 'Is Scheduled', 'key' => 'isScheduled', 'sortable' => true],
|
['name' => 'Status', 'key' => 'status', 'sortable' => true],
|
||||||
['name' => 'Schedule', 'key' => 'schedule', 'sortable' => true],
|
['name' => 'Created At', 'key' => 'created_at', 'sortable' => true],
|
||||||
['name' => 'Expiration', 'key' => 'expiration', '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,
|
'showAddButton' => true,
|
||||||
'addButtonUrl' => '/add-notification',
|
'addButtonUrl' => route('notifications.create'),
|
||||||
'showCheckboxes' => false,
|
'showCheckboxes' => true,
|
||||||
'showBatchDelete' => false,
|
'showBatchDelete' => true,
|
||||||
'showEditModal' => false,
|
'showEditModal' => true,
|
||||||
'showViewModal' => true
|
'showViewModal' => true,
|
||||||
|
'baseRoute' => 'notifications'
|
||||||
])
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
<script>
|
@push('scripts')
|
||||||
const storedNotifications = JSON.parse(sessionStorage.getItem('notifications') || '[]');
|
<script>
|
||||||
if (storedNotifications.length > 0) {
|
$(document).ready(function() {
|
||||||
const tableConfig = window.tableConfig || {};
|
const dataTable = initializeDataTable({
|
||||||
tableConfig.data = [...tableConfig.data, ...storedNotifications];
|
route: '{{ route("notifications.data") }}',
|
||||||
window.renderTable();
|
columns: [
|
||||||
window.renderPagination();
|
{ 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();
|
||||||
}
|
}
|
||||||
</script>
|
});
|
||||||
@endsection
|
|
||||||
|
$(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
|
|
@ -3,85 +3,143 @@
|
||||||
@section('page_title', 'Promotions')
|
@section('page_title', 'Promotions')
|
||||||
|
|
||||||
@section('content')
|
@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', [
|
@include('components.table-component', [
|
||||||
'pageTitle' => 'Promotions',
|
'pageTitle' => 'Promotions',
|
||||||
'data' => $promotions,
|
'data' => $promotions ?? [],
|
||||||
'columns' => [
|
'columns' => [
|
||||||
['name' => 'Title', 'key' => 'title', 'sortable' => true],
|
['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' => 'Start Date', 'key' => 'startDate', 'sortable' => true],
|
||||||
['name' => 'End Date', 'key' => 'endDate', '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'],
|
'actions' => ['edit', 'view', 'delete'],
|
||||||
'showAddButton' => true,
|
'showAddButton' => true,
|
||||||
'addButtonUrl' => '/add-promotions',
|
'addButtonUrl' => route('promotions.create'),
|
||||||
'showCheckboxes' => true,
|
'showCheckboxes' => true,
|
||||||
'showBatchDelete' => true,
|
'showBatchDelete' => true,
|
||||||
'showEditModal' => true,
|
'showEditModal' => true,
|
||||||
'showViewModal' => true
|
'showViewModal' => true,
|
||||||
|
'baseRoute' => 'promotions'
|
||||||
])
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
<script>
|
@push('scripts')
|
||||||
const storedPromotions = JSON.parse(sessionStorage.getItem('promotions') || '[]');
|
<script>
|
||||||
if (storedPromotions.length > 0) {
|
$(document).ready(function() {
|
||||||
const tableConfig = window.tableConfig || {};
|
const dataTable = initializeDataTable({
|
||||||
tableConfig.data = [...tableConfig.data, ...storedPromotions];
|
route: '{{ route("promotions.data") }}',
|
||||||
window.renderTable();
|
columns: [
|
||||||
window.renderPagination();
|
{ 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>
|
});
|
||||||
@endsection
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<style>
|
||||||
|
.promotion-image {
|
||||||
|
max-height: 50px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.datepicker {
|
||||||
|
z-index: 1060;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
|
@ -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
|
|
@ -3,182 +3,101 @@
|
||||||
@section('page_title', 'System Parameters')
|
@section('page_title', 'System Parameters')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="card-header border-0 bg-transparent py-2">
|
@include('components.table-component', [
|
||||||
<h5 class="mb-0 fw-bold text-dark" style="font-size: 1.25rem;">System Parameters</h5>
|
'pageTitle' => 'System Parameters',
|
||||||
</div>
|
'data' => $parameters ?? [],
|
||||||
<div class="row justify-content-center">
|
'columns' => [
|
||||||
<div class="card-body p-3">
|
['name' => 'Name', 'key' => 'name', 'sortable' => true],
|
||||||
<form id="systemParametersForm">
|
['name' => 'Value', 'key' => 'value', 'sortable' => true],
|
||||||
<!-- Company Logo -->
|
['name' => 'Type', 'key' => 'type', 'sortable' => true],
|
||||||
<h4 class="fw-bold mb-3 mt-4">Company Logo</h4>
|
['name' => 'Description', 'key' => 'description', 'sortable' => true],
|
||||||
<div class="mb-3">
|
['name' => 'Last Updated', 'key' => 'updated_at', 'sortable' => true]
|
||||||
<label for="companyLogo" class="form-label">Upload Logo</label>
|
],
|
||||||
<input type="file" class="form-control" id="companyLogo" accept=".jpg,.jpeg,.png">
|
'allFields' => [
|
||||||
</div>
|
['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 -->
|
@push('scripts')
|
||||||
<h4 class="fw-bold mb-3 mt-4">GPS Radius</h4>
|
<script>
|
||||||
<div class="mb-3">
|
$(document).ready(function() {
|
||||||
<label for="gpsRadius" class="form-label">GPS Radius (in meters)</label>
|
// Handle parameter type change
|
||||||
<input type="number" class="form-control" id="gpsRadius" min="0" placeholder="Enter radius" required>
|
$(document).on('change', '[name="type"]', function() {
|
||||||
</div>
|
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>
|
switch(type) {
|
||||||
<div class="mb-3">
|
case 'Number':
|
||||||
<label for="contactEmail" class="form-label">Contact Email Address</label>
|
valueInput.attr('type', 'number');
|
||||||
<input type="email" class="form-control" id="contactEmail" placeholder="Enter email" required>
|
break;
|
||||||
</div>
|
case 'Boolean':
|
||||||
<div class="mb-3">
|
valueInput.replaceWith(`
|
||||||
<label for="contactNumber" class="form-label">Contact Number</label>
|
<select name="value" class="form-control">
|
||||||
<input type="tel" class="form-control" id="contactNumber" placeholder="Enter number" required>
|
<option value="true">True</option>
|
||||||
</div>
|
<option value="false">False</option>
|
||||||
|
|
||||||
<!-- 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>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
`);
|
||||||
|
break;
|
||||||
<!-- iOS Version Update -->
|
case 'JSON':
|
||||||
<h4 class="fw-bold mb-3 mt-4">iOS Version Update</h4>
|
valueInput.replaceWith(`<textarea name="value" class="form-control" rows="3"></textarea>`);
|
||||||
<div class="mb-3">
|
break;
|
||||||
<label for="iosVersion" class="form-label">iOS Version</label>
|
default:
|
||||||
<input type="text" class="form-control" id="iosVersion" placeholder="e.g., 1.0.0" required>
|
valueInput.attr('type', 'text');
|
||||||
</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>
|
|
||||||
|
|
||||||
<!-- 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>
|
|
||||||
|
|
||||||
<!-- Submit -->
|
|
||||||
<div class="d-flex justify-content-end mt-4">
|
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
// Validate JSON input
|
||||||
document.getElementById('systemParametersForm').addEventListener('submit', function(e) {
|
$(document).on('submit', 'form', function(e) {
|
||||||
e.preventDefault();
|
const type = $(this).find('[name="type"]').val();
|
||||||
|
const value = $(this).find('[name="value"]').val();
|
||||||
|
|
||||||
const fields = {
|
if (type === 'JSON') {
|
||||||
companyLogo: document.getElementById('companyLogo').files[0]?.name || '',
|
try {
|
||||||
gpsRadius: document.getElementById('gpsRadius').value,
|
JSON.parse(value);
|
||||||
contactEmail: document.getElementById('contactEmail').value,
|
} catch (error) {
|
||||||
contactNumber: document.getElementById('contactNumber').value,
|
e.preventDefault();
|
||||||
adminContact: document.getElementById('adminContact').value,
|
alert('Please enter valid JSON');
|
||||||
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
|
|
||||||
|
// 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>
|
||||||
|
pre {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
|
@ -1,83 +1,84 @@
|
||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('page_title', 'Top-Up Settings')
|
@section('page_title', 'Top-up Settings')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="card-header border-0 bg-transparent py-2">
|
@include('components.table-component', [
|
||||||
<h5 class="mb-0 fw-bold text-dark" style="font-size: 1.25rem;font-weight:500">Top-Up Settings</h5>
|
'pageTitle' => 'Top-up Settings',
|
||||||
</div>
|
'data' => $topUpSettings ?? [],
|
||||||
<div class="row justify-content-center align-items-center">
|
'columns' => [
|
||||||
<div class="col-12 col-md-8 col-lg-6">
|
['name' => 'Amount', 'key' => 'amount', 'sortable' => true],
|
||||||
<div class="card-body p-3">
|
['name' => 'Description', 'key' => 'description', 'sortable' => true],
|
||||||
<form id="topUpSettingsForm">
|
['name' => 'Status', 'key' => 'status', 'sortable' => true],
|
||||||
<!-- Current Discount Display -->
|
['name' => 'Created At', 'key' => 'created_at', 'sortable' => true]
|
||||||
<div class="mb-2">
|
],
|
||||||
<label class="form-label fw-bold" style="font-size: 1.5rem;">
|
'allFields' => [
|
||||||
Current Discount: <span style="color: #E74610;">2%</span>
|
['name' => 'Amount', 'key' => 'amount', 'type' => 'number', 'required' => true, 'min' => 0],
|
||||||
</label>
|
['name' => 'Description', 'key' => 'description', 'type' => 'textarea', 'required' => true],
|
||||||
</div>
|
['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
|
||||||
|
|
||||||
<!-- Top Up Discount Input -->
|
@push('scripts')
|
||||||
<div class="mb-2">
|
<script>
|
||||||
<label for="discount" class="form-label" style="font-size: 1.2rem;">Top Up Discount (%)</label>
|
$(document).ready(function() {
|
||||||
<div class="d-flex align-items-center">
|
const dataTable = initializeDataTable({
|
||||||
<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;">
|
route: '{{ route("top-up-settings.data") }}',
|
||||||
<button type="submit" class="btn btn-primary" style="background-color: #E74610; border-color: #E74610; font-size: 1rem; padding: 10px 20px;">SUBMIT</button>
|
columns: [
|
||||||
</div>
|
{
|
||||||
</div>
|
data: 'amount',
|
||||||
|
render: function(data) {
|
||||||
<!-- Card Image -->
|
return new Intl.NumberFormat('en-US', {
|
||||||
<div class="mt-3 text-center">
|
style: 'currency',
|
||||||
<img src="{{ asset('img/card.png') }}" alt="Loyalty Card" style="max-width: 300px;">
|
currency: 'PHP'
|
||||||
</div>
|
}).format(data);
|
||||||
</form>
|
}
|
||||||
</div>
|
},
|
||||||
</div>
|
{ data: 'description' },
|
||||||
</div>
|
{
|
||||||
|
data: 'status',
|
||||||
<style>
|
render: function(data) {
|
||||||
.card {
|
return `<span class="badge badge-${data === 'Active' ? 'success' : 'danger'}">${data}</span>`;
|
||||||
border-radius: 5px;
|
}
|
||||||
border: 1px solid #dee2e6;
|
},
|
||||||
}
|
{
|
||||||
.form-label {
|
data: 'created_at',
|
||||||
font-size: 1.2rem;
|
render: function(data) {
|
||||||
}
|
return moment(data).format('YYYY-MM-DD HH:mm:ss');
|
||||||
.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>
|
|
||||||
|
|
||||||
<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;
|
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
// Simulate updating the discount (frontend-only)
|
// Handle form submissions
|
||||||
sessionStorage.setItem('topUpDiscount', discount);
|
handleFormSubmission({
|
||||||
|
addRoute: '{{ route("top-up-settings.store") }}',
|
||||||
alert('Discount updated successfully!');
|
editRoute: '{{ route("top-up-settings.update", ":id") }}',
|
||||||
window.location.reload(); // Reload to reflect the new "Current Discount"
|
deleteRoute: '{{ route("top-up-settings.destroy", ":id") }}',
|
||||||
});
|
dataTable: dataTable,
|
||||||
|
validation: {
|
||||||
// On page load, update the current discount display if stored in sessionStorage
|
amount: {
|
||||||
window.addEventListener('load', function() {
|
required: true,
|
||||||
const storedDiscount = sessionStorage.getItem('topUpDiscount') || '2';
|
number: true,
|
||||||
document.querySelector('.form-label.fw-bold span').textContent = `${storedDiscount}%`;
|
min: 0
|
||||||
});
|
},
|
||||||
</script>
|
description: {
|
||||||
@endsection
|
required: true,
|
||||||
|
minlength: 3
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
|
@ -4,28 +4,80 @@
|
||||||
@section('page_title', 'User Management')
|
@section('page_title', 'User Management')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div id="user-table">
|
@include('components.table-component', [
|
||||||
@include('components.table-component', [
|
'pageTitle' => 'User Management',
|
||||||
'pageTitle' => 'User Management',
|
'data' => $users ?? [], // Data from API
|
||||||
'data' => $users, // Use the data passed from the controller
|
'columns' => [
|
||||||
'columns' => [
|
['name' => 'Username', 'key' => 'username', 'sortable' => true],
|
||||||
['name' => 'Username', 'key' => 'username', 'sortable' => true],
|
['name' => 'First Name', 'key' => 'firstName', 'sortable' => true],
|
||||||
['name' => 'First Name', 'key' => 'firstName', 'sortable' => true],
|
['name' => 'Last Name', 'key' => 'lastName', 'sortable' => true],
|
||||||
['name' => 'Last Name', 'key' => 'lastName', 'sortable' => true],
|
['name' => 'User Role', 'key' => 'role', 'sortable' => true],
|
||||||
['name' => 'User Role', 'key' => 'role', 'sortable' => true],
|
['name' => 'Email', 'key' => 'email', 'sortable' => true],
|
||||||
['name' => 'Email', 'key' => 'email', 'sortable' => true],
|
['name' => 'Status', 'key' => 'status', 'sortable' => true]
|
||||||
['name' => 'Status', 'key' => 'status', 'sortable' => true]
|
],
|
||||||
],
|
'allFields' => [
|
||||||
'actions' => ['edit', 'view', 'delete'],
|
['name' => 'Username', 'key' => 'username', 'type' => 'text', 'required' => true],
|
||||||
'showAddButton' => true,
|
['name' => 'First Name', 'key' => 'firstName', 'type' => 'text', 'required' => true],
|
||||||
'addButtonUrl' => '/add-user',
|
['name' => 'Last Name', 'key' => 'lastName', 'type' => 'text', 'required' => true],
|
||||||
'showCheckboxes' => true,
|
['name' => 'Email', 'key' => 'email', 'type' => 'email', 'required' => true],
|
||||||
'showBatchDelete' => true,
|
['name' => 'Password', 'key' => 'password', 'type' => 'password', 'required' => true, 'showOnEdit' => false],
|
||||||
'showEditModal' => true,
|
['name' => 'Role', 'key' => 'role', 'type' => 'select', 'options' => $roles ?? ['admin', 'user'], 'required' => true],
|
||||||
'showViewModal' => true
|
['name' => 'Status', 'key' => 'status', 'type' => 'select', 'options' => ['Active', 'Inactive'], 'required' => true]
|
||||||
])
|
],
|
||||||
<div id="no-data-message" style="display: {{ empty($users) ? 'block' : 'none' }}; text-align: center; margin-top: 20px;">
|
'actions' => ['edit', 'view', 'delete'],
|
||||||
<p>No Data Found</p>
|
'showAddButton' => true,
|
||||||
</div>
|
'addButtonUrl' => route('user-management.create'),
|
||||||
</div>
|
'showCheckboxes' => true,
|
||||||
@endsection
|
'showBatchDelete' => true,
|
||||||
|
'showEditModal' => true,
|
||||||
|
'showViewModal' => true,
|
||||||
|
'baseRoute' => 'user-management'
|
||||||
|
])
|
||||||
|
@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
|
|
@ -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
|
|
@ -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
|
280
routes/web.php
280
routes/web.php
|
@ -1,158 +1,146 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use App\Http\Controllers\AuthController;
|
use App\Http\Controllers\AuthController;
|
||||||
|
use App\Http\Controllers\HomeController;
|
||||||
use App\Http\Controllers\UserManagementController;
|
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;
|
||||||
|
|
||||||
|
// Auth Routes
|
||||||
|
Route::get('/', [AuthController::class, 'showLoginForm'])->name('login');
|
||||||
Route::get('/', function () {
|
Route::post('/login', [AuthController::class, 'login'])->name('login.submit');
|
||||||
return redirect()->route('login');
|
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
Route::get('/login', [AuthController::class, 'showLoginForm'])->name('login');
|
|
||||||
Route::post('/login', [AuthController::class, 'login'])->name('login');
|
|
||||||
Route::get('/change-password', [AuthController::class, 'showChangePasswordForm'])->name('change-password');
|
Route::get('/change-password', [AuthController::class, 'showChangePasswordForm'])->name('change-password');
|
||||||
Route::post('/change-password', [AuthController::class, 'changePassword'])->name('password.change');
|
Route::post('/change-password', [AuthController::class, 'changePassword'])->name('password.change');
|
||||||
Route::get('/my-profile', [AuthController::class, 'showMyProfile'])->name('my-profile');
|
|
||||||
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
|
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// 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('/dashboard', function () {
|
// Station Management
|
||||||
return view('dashboard');
|
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');
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// Top-up
|
||||||
|
Route::get('/top-up', [CardMemberController::class, 'topUpHistory'])->name('top-up.index');
|
||||||
|
Route::post('/top-up', [CardMemberController::class, 'processTopUp'])->name('top-up.store');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// Logs
|
||||||
|
Route::get('/logs', [FuelPriceController::class, 'logs'])->name('fuel-price.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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/user-management', [UserManagementController::class, 'index'])->name('user.management');
|
|
||||||
|
|
||||||
Route::get('/notification', function () {
|
|
||||||
return view('pages.notification');
|
|
||||||
})->name('notification');
|
|
||||||
|
|
||||||
Route::get('/card-member', function () {
|
|
||||||
return view('pages.member management.card-member');
|
|
||||||
})->name('card-member');
|
|
||||||
|
|
||||||
Route::get('/locked-accounts', function () {
|
|
||||||
return view('pages.member management.locked-accounts');
|
|
||||||
})->name('locked-accounts');
|
|
||||||
|
|
||||||
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('/top-up', function () {
|
|
||||||
return view('pages.top-up');
|
|
||||||
})->name('top-up');
|
|
||||||
|
|
||||||
Route::get('/card-types', function () {
|
|
||||||
return view('pages.about us.card-types');
|
|
||||||
})->name('card-types');
|
|
||||||
|
|
||||||
Route::get('/terms-and-privacy', function () {
|
|
||||||
return view('pages.about us.terms-and-privacy');
|
|
||||||
})->name('terms-and-privacy');
|
|
||||||
|
|
||||||
Route::get('/registration-report', function () {
|
|
||||||
return view('pages.reports.registration-report');
|
|
||||||
})->name('registration-report');
|
|
||||||
|
|
||||||
Route::get('/top-up-usage-report', function () {
|
|
||||||
return view('pages.reports.top-up-usage-report');
|
|
||||||
})->name('top-up-usage-report');
|
|
||||||
|
|
||||||
Route::get('/mobile-usage-report', function () {
|
|
||||||
return view('pages.reports.mobile-usage-report');
|
|
||||||
})->name('mobile-usage-report');
|
|
||||||
|
|
||||||
Route::get('/station-rating-report', function () {
|
|
||||||
return view('pages.reports.station-rating-report');
|
|
||||||
})->name('station-rating-report');
|
|
||||||
|
|
||||||
Route::get('/system-parameters', function () {
|
|
||||||
return view('pages.system-parameters');
|
|
||||||
})->name('system-parameters');
|
|
||||||
|
|
||||||
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');
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
Loading…
Reference in New Issue