converting src/ files to laravel php code

This commit is contained in:
armiejean 2025-04-08 17:20:12 +08:00
parent f45d1949e0
commit e87558224e
98 changed files with 12000 additions and 7478 deletions

View File

@ -0,0 +1,38 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* The list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
$this->reportable(function (Throwable $e) {
//
});
}
public function render($request, Throwable $exception)
{
if ($exception instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException) {
return response()->view('page404', [], 404);
}
return parent::render($request, $exception);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ConfirmsPasswords;
class ConfirmPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Confirm Password Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password confirmations and
| uses a simple trait to include the behavior. You're free to explore
| this trait and override any functions that require customization.
|
*/
use ConfirmsPasswords;
/**
* Where to redirect users when the intended url fails.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
$this->middleware('auth')->only('logout');
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\Models\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Where to redirect users after resetting their password.
*
* @var string
*/
protected $redirectTo = '/home';
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\VerifiesEmails;
class VerificationController extends Controller
{
/*
|--------------------------------------------------------------------------
| Email Verification Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling email verification for any
| user that recently registered with the application. Emails may also
| be re-sent if the user didn't receive the original email message.
|
*/
use VerifiesEmails;
/**
* Where to redirect users after verification.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
}

View File

@ -2,7 +2,11 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
abstract class Controller use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{ {
// use AuthorizesRequests, ValidatesRequests;
} }

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Pagination\LengthAwarePaginator;
class CustomTableController extends Controller
{
public function index(Request $request)
{
$data = User::query();
$search = $request->input('search');
if ($search) {
$data->where('name', 'like', "%$search%");
}
$sortField = $request->input('sort_field');
$sortOrder = $request->input('sort_order', 'asc');
if ($sortField) {
$data->orderBy($sortField, $sortOrder);
}
$perPage = $request->input('per_page', 10);
$paginatedData = $data->paginate($perPage);
$columns = [
['title' => 'ID', 'dataIndex' => 'id'],
['title' => 'Name', 'dataIndex' => 'name'],
];
$actions = [
['type' => 'edit', 'name' => 'Edit', 'path' => '/edit', 'access' => true],
['type' => 'delete', 'name' => 'Delete', 'path' => '#', 'access' => true],
];
return view('custom-table', compact('paginatedData', 'columns', 'actions'));
}
public function destroy($id)
{
User::find($id)->delete();
return redirect()->back()->with('success', 'User deleted');
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\Dashboard;
use Illuminate\Http\Request;
class BreadcrumbController extends Controller
{
public function index(Request $request)
{
$path = $request->path();
$pathSnippets = array_filter(explode('/', $path));
$root = empty($pathSnippets);
// Simulated pageRoutes (replace with your actual routes)
$pageRoutes = [
['path' => '/my-profile', 'name' => 'Home'],
['path' => '/my-profile/users', 'name' => 'Users', 'params' => true],
];
$extraBreadcrumbItems = [];
foreach ($pathSnippets as $index => $snippet) {
$url = '/' . implode('/', array_slice($pathSnippets, 0, $index + 1));
$route = collect($pageRoutes)->firstWhere('path', $url);
if ($route) {
$paramsId = end($pathSnippets);
$extraBreadcrumbItems[] = [
'url' => $route['params'] ? "$url/$paramsId" : $url,
'name' => $route['name'],
];
}
}
return view('dashboard.breadcrumbs', compact('root', 'extraBreadcrumbItems'));
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Dashboard;
use Illuminate\Http\Request;
class ContentController extends Controller
{
public function index(Request $request)
{
// Simulated pageRoutes (replace with your actual routes)
$pageRoutes = [
['path' => '/my-profile', 'name' => 'Home'],
['path' => '/my-profile/users', 'name' => 'Users', 'params' => true],
];
$root = $request->path() === '/my-profile';
return view('dashboard.content', compact('pageRoutes', 'root'));
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Dashboard;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class DashboardController extends Controller
{
public function index(Request $request)
{
$user = Auth::user();
if (!$user) {
return redirect()->route('login');
}
$collapsed = session()->get('collapsed', false);
$pageRoutes = [
['path' => '/my-profile', 'name' => 'Home'],
['path' => '/my-profile/users', 'name' => 'Users', 'params' => true],
];
$root = $request->path() === '/my-profile';
return view('dashboard.layout', compact('collapsed', 'user', 'pageRoutes', 'root'));
}
public function logout(Request $request)
{
Auth::logout();
return redirect()->route('login')->with('success', 'Logged out due to inactivity');
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Dashboard;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class DataDisplayController extends Controller
{
public function index(Request $request)
{
$user = Auth::user();
if (!$user) {
return redirect()->route('login');
}
$data = User::all();
$layout = 'horizontal';
$avatar = true;
$viewPath = '/view';
$header = 'User List';
$footer = 'Total: ' . count($data) . ' users';
return view('dashboard.data-display', compact('data', 'layout', 'avatar', 'viewPath', 'header', 'footer'));
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers\Dashboard;
use Illuminate\Http\Request;
class FooterController extends Controller
{
public function index(Request $request)
{
return view('dashboard.footer');
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Dashboard;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class HeaderController extends Controller
{
public function index(Request $request)
{
$user = Auth::user();
if (!$user) {
return redirect()->route('login');
}
$collapsed = session()->get('collapsed', false); // Simulate toggle state
return view('dashboard.header', compact('user', 'collapsed'));
}
public function toggle(Request $request)
{
$collapsed = !$request->input('collapsed', false);
session()->put('collapsed', $collapsed);
return response()->json(['collapsed' => $collapsed]);
}
public function logout(Request $request)
{
Auth::logout();
return redirect()->route('login')->with('success', 'Logged out successfully');
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Dashboard;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RouteController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index(Request $request, $component = null)
{
$view = $component ? "dashboard.{$component}" : 'dashboard.default';
if (!view()->exists($view)) {
return redirect()->route('login')->with('error', 'You must log in to enter this page');
}
return view('dashboard.route', compact('view'));
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\Dashboard;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class SidebarController extends Controller
{
public function index(Request $request)
{
$user = Auth::user();
if (!$user) {
return redirect()->route('login');
}
$collapsed = session()->get('collapsed', false);
$navigation = [
['key' => 0, 'label' => 'User Management', 'path' => '/user-management', 'icon' => 'team', 'access' => $user && $user->role == 1],
['key' => 9, 'label' => 'Notifications', 'path' => '/notifications', 'icon' => 'notification', 'access' => true],
['key' => 4, 'label' => 'Member Management', 'path' => '/member-management', 'icon' => 'credit-card', 'access' => $user && $user->role == 1, 'child' => [
['key' => 0.0, 'label' => 'Card Member', 'path' => '/member-management/card-member', 'access' => true],
['key' => 0.1, 'label' => 'Locked Accounts', 'path' => '/member-management/lock-account', 'access' => true],
]],
['key' => 8, 'label' => 'Home Page ( Mobile )', 'path' => '/home-page', 'icon' => 'home', 'access' => true, 'child' => [
['key' => 0.0, 'label' => 'Photo Slider', 'path' => '/home-page/photo-slider', 'access' => true],
]],
['key' => 3, 'label' => 'Promotions', 'path' => '/promotions', 'icon' => 'tags', 'access' => true],
['key' => 2, 'label' => 'Top-Up', 'path' => '/top-up', 'icon' => 'plus-circle', 'access' => $user && $user->role == 1],
['key' => 6, 'label' => 'About Us', 'path' => '/about-us', 'icon' => 'info-circle', 'access' => $user && $user->role == 1, 'child' => [
['key' => 0.6, 'label' => 'Card Types', 'path' => '/about-us/card-types', 'access' => true],
['key' => 0.5, 'label' => 'Terms & Privacy', 'path' => '/about-us/term-privacy', 'access' => true],
]],
['key' => 7, 'label' => 'Reports', 'path' => '/reports', 'icon' => 'file-text', 'access' => true, 'child' => [
['key' => 0.7, 'label' => 'Registration Report', 'path' => '/reports/registration-report', 'access' => true],
['key' => 0.8, 'label' => 'Top-Up Usage Report', 'path' => '/reports/top-up', 'access' => true],
['key' => 0.9, 'label' => 'Mobile Usage Report', 'path' => '/reports/mobile-report', 'access' => true],
['key' => 0.1, 'label' => 'Station Rating Report', 'path' => '/reports/station-rating', 'access' => true],
]],
['key' => 8, 'label' => 'System Parameters', 'path' => '/system-parameters', 'icon' => 'setting', 'access' => $user && $user->role == 1],
['key' => 12, 'label' => 'Station Locator', 'path' => '', 'icon' => 'environment', 'access' => true, 'child' => [
['key' => 0.11, 'label' => 'Branches', 'path' => '/branches', 'access' => true],
['key' => 0.12, 'label' => 'Stations', 'path' => '/stations', 'access' => true],
['key' => 0.13, 'label' => 'Fuels', 'path' => '/fuels', 'access' => true],
]],
];
return view('dashboard.sidebar', compact('collapsed', 'navigation', 'user'));
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Dropdown;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Response;
use App\Http\Controllers\Controller;
use App\Models\Export;
class DropdownExportController extends Controller
{
public function export(Request $request)
{
$params = $request->all();
if ($request->has('defaultFilter')) {
$params = array_merge($params, $request->input('defaultFilter'));
}
$data = User::all()->map(function ($user) {
return implode(',', [$user->id, $user->name]) . "\n";
})->implode('');
$filename = 'export_' . now()->format('mmddyyyy') . '.csv';
Storage::put("exports/{$filename}", $data);
Export::create(['filename' => $filename, 'exported_at' => now()]);
return Response::download(storage_path("app/exports/{$filename}"))->deleteFileAfterSend(true);
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Show the application dashboard.
*
* @return \Illuminate\Contracts\Support\Renderable
*/
public function index()
{
return view('home');
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class LoadingController extends Controller
{
public function index(Request $request)
{
return view('loading');
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers\Login;
use Illuminate\Http\Request;
class LayoutController extends Controller
{
public function index(Request $request)
{
return view('login.layout');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Controllers\Login;
use Illuminate\Http\Request;
class RouteController extends Controller
{
public function index(Request $request, $component = null)
{
$view = $component ? "login.{$component}" : 'login.default';
if (!view()->exists($view)) {
$view = 'login.default';
}
return view('login.route', compact('view'));
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Modal;
use Illuminate\Http\Request;
class ModalCancelController extends Controller
{
public function index(Request $request)
{
$path = $request->input('path', '/');
$title = $request->input('title', 'Your Title');
$message = $request->input('message', 'Your Message');
$dirty = $request->input('dirty', true);
$name = $request->input('name', 'Cancel');
return view('modal.cancel', compact('path', 'title', 'message', 'dirty', 'name'));
}
public function confirm(Request $request)
{
return redirect($request->input('path', '/'));
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Notification;
use Illuminate\Pagination\LengthAwarePaginator;
class NotificationTablesController extends Controller
{
public function index(Request $request)
{
$data = Notification::query();
$search = $request->input('search');
if ($search) {
$data->where('title', 'like', "%$search%");
}
$sortField = $request->input('_sort_by');
$sortOrder = $request->input('_sort_order', 'asc') === 'asc' ? 'asc' : 'desc';
if ($sortField) {
$data->orderBy($sortField, $sortOrder);
}
$dateStart = $request->input('date_start');
$dateEnd = $request->input('date_end');
if ($dateStart && $dateEnd) {
$data->whereBetween('created_at', [$dateStart, $dateEnd]);
}
$perPage = $request->input('page_size', 10);
$currentPage = $request->input('page', 1);
$paginatedData = $data->paginate($perPage);
$columns = [
['title' => 'ID', 'dataIndex' => 'id'],
['title' => 'Title', 'dataIndex' => 'title'],
];
$actions = [
['key' => 'edit', 'title' => 'Edit', 'icon' => 'edit'],
['key' => 'delete', 'title' => 'Delete', 'icon' => 'delete'],
];
return view('notification-tables', compact('paginatedData', 'columns', 'actions'));
}
public function destroy($id)
{
Notification::find($id)->delete();
return redirect()->back()->with('success', 'Notification deleted');
}
public function batchDestroy(Request $request)
{
$ids = $request->input('ids', []);
Notification::whereIn('id', $ids)->delete();
return redirect()->back()->with('success', 'Notifications deleted');
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class Page404Controller extends Controller
{
public function index(Request $request)
{
return view('page404');
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Pagination\LengthAwarePaginator;
class UserController extends Controller
{
public function index(Request $request)
{
$data = User::query();
$search = $request->input('search');
if ($search) {
$data->where('name', 'like', "%$search%");
}
$sortField = $request->input('sort_field');
$sortOrder = $request->input('sort_order', 'asc');
if ($sortField) {
$data->orderBy($sortField, $sortOrder);
}
$paginatedData = $data->paginate($request->input('per_page', 10));
return view('users', compact('paginatedData'));
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Export extends Model
{
protected $fillable = ['filename', 'exported_at'];
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Notification extends Model
{
protected $fillable = ['title', 'content', 'created_at'];
}

View File

@ -17,11 +17,8 @@ class User extends Authenticatable
* *
* @var list<string> * @var list<string>
*/ */
protected $fillable = [ protected $fillable = ['name', 'first_name', 'last_name', 'avatar'];
'name',
'email',
'password',
];
/** /**
* The attributes that should be hidden for serialization. * The attributes that should be hidden for serialization.
@ -45,4 +42,5 @@ class User extends Authenticatable
'password' => 'hashed', 'password' => 'hashed',
]; ];
} }
} }

View File

@ -8,7 +8,8 @@
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"laravel/framework": "^11.31", "laravel/framework": "^11.31",
"laravel/tinker": "^2.9" "laravel/tinker": "^2.9",
"laravel/ui": "^4.6"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",

View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "626b9e7ddd47fb7eff9aaa53cce0c9ad", "content-hash": "a7159c5237c3c2e75dbd1d23c85e732c",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@ -1455,6 +1455,69 @@
}, },
"time": "2025-01-27T14:24:01+00:00" "time": "2025-01-27T14:24:01+00:00"
}, },
{
"name": "laravel/ui",
"version": "v4.6.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/ui.git",
"reference": "7d6ffa38d79f19c9b3e70a751a9af845e8f41d88"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/ui/zipball/7d6ffa38d79f19c9b3e70a751a9af845e8f41d88",
"reference": "7d6ffa38d79f19c9b3e70a751a9af845e8f41d88",
"shasum": ""
},
"require": {
"illuminate/console": "^9.21|^10.0|^11.0|^12.0",
"illuminate/filesystem": "^9.21|^10.0|^11.0|^12.0",
"illuminate/support": "^9.21|^10.0|^11.0|^12.0",
"illuminate/validation": "^9.21|^10.0|^11.0|^12.0",
"php": "^8.0",
"symfony/console": "^6.0|^7.0"
},
"require-dev": {
"orchestra/testbench": "^7.35|^8.15|^9.0|^10.0",
"phpunit/phpunit": "^9.3|^10.4|^11.5"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laravel\\Ui\\UiServiceProvider"
]
},
"branch-alias": {
"dev-master": "4.x-dev"
}
},
"autoload": {
"psr-4": {
"Laravel\\Ui\\": "src/",
"Illuminate\\Foundation\\Auth\\": "auth-backend/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel UI utilities and presets.",
"keywords": [
"laravel",
"ui"
],
"support": {
"source": "https://github.com/laravel/ui/tree/v4.6.1"
},
"time": "2025-01-28T15:15:29+00:00"
},
{ {
"name": "league/commonmark", "name": "league/commonmark",
"version": "2.6.1", "version": "2.6.1",

View File

@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('first_name')->nullable();
$table->string('last_name')->nullable();
$table->string('avatar')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('users');
}
}

View File

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateExportsTable extends Migration
{
public function up()
{
Schema::create('exports', function (Blueprint $table) {
$table->id();
$table->string('filename');
$table->timestamp('exported_at');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('exports');
}
}

View File

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateNotificationsTable extends Migration
{
public function up()
{
Schema::create('notifications', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('notifications');
}
}

View File

@ -1,56 +1,40 @@
version: '3.8' # Upgrade version for healthcheck support version: '3.8'
services: services:
# Backend: Laravel
laravel-app: laravel-app:
image: laravel-app:latest
build: build:
context: . context: .
dockerfile: docker/laravel/Dockerfile dockerfile: docker/laravel/Dockerfile
image: laravel-app:latest
volumes: volumes:
- ./app:/var/www/html - ./app:/var/www/html
networks: networks:
- app-network - app-network
depends_on: depends_on:
- mysql mysql:
condition: service_healthy
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost || exit 1"] test: ["CMD", "pgrep", "php-fpm"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 5 retries: 5
start_period: 30s start_period: 30s
# Frontend: React (served with Nginx inside container) nginx:
react-app: image: nginx:latest
build:
context: ./frontend
dockerfile: Dockerfile
volumes: volumes:
- ./frontend:/app - ./nginx.conf:/etc/nginx/nginx.conf
- ./public:/var/www/html/public
- ./frontend/build:/usr/share/nginx/html
ports: ports:
- "80:80" - "80:80"
networks: networks:
- app-network - app-network
command: "nginx -g 'daemon off;'"
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
# Nginx: Serves both Laravel and React
nginx:
image: nginx:latest
volumes:
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./public:/var/www/html/public
ports:
- "8080:80"
networks:
- app-network
depends_on: depends_on:
- laravel-app laravel-app:
condition: service_healthy
react-app:
condition: service_healthy
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"] test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s interval: 30s
@ -58,7 +42,6 @@ services:
retries: 3 retries: 3
start_period: 20s start_period: 20s
# MySQL: Database
mysql: mysql:
image: mysql:5.7 image: mysql:5.7
environment: environment:

View File

@ -1,12 +1,20 @@
# Set up PHP for Laravel # Set up PHP for Laravel
FROM php:8.1-fpm FROM php:8.2-fpm
# Install necessary extensions and dependencies for Laravel # Install necessary system dependencies, including oniguruma
RUN apt-get update && apt-get install -y libpng-dev libjpeg-dev libfreetype6-dev zip git unzip RUN apt-get update && apt-get install -y --no-install-recommends \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
zip \
git \
unzip \
libonig-dev && \
rm -rf /var/lib/apt/lists/*
# Install PHP extensions # Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \ RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \
docker-php-ext-install gd docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
# Install Composer # Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
@ -14,12 +22,19 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set working directory # Set working directory
WORKDIR /var/www/html WORKDIR /var/www/html
# Copy Laravel files into container # Copy entire Laravel project (including artisan)
COPY . . COPY . .
# Install PHP dependencies # Install PHP dependencies
RUN composer install --no-interaction --optimize-autoloader --no-dev RUN composer install --no-interaction --optimize-autoloader --no-dev
# Set permissions for storage and cache
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
RUN chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache
# Make artisan executable
RUN chmod +x /var/www/html/artisan
# Expose port for PHP-FPM # Expose port for PHP-FPM
EXPOSE 9000 EXPOSE 9000

View File

@ -4,33 +4,35 @@ server {
# Serve the React app (static files) # Serve the React app (static files)
location / { location / {
root /var/www/html; # React build output directory root /usr/share/nginx/html; # Point to React build output
try_files $uri $uri/ /index.html; # Ensure React routing works properly with single-page app try_files $uri $uri/ /index.html; # Ensure React routing works
index index.html;
} }
# Proxy API requests to Laravel (assuming Laravel is running on port 9000) # Proxy API requests to Laravel
location /api/ { location /api/ {
proxy_pass http://laravel-app:9000; # Laravel service name from Docker Compose proxy_pass http://laravel-app:9000;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
} }
# Optionally, handle assets like images or other media files # Handle PHP files for Laravel
location ~ ^/index.php(/|$) {
fastcgi_pass laravel-app:9000;
fastcgi_param SCRIPT_FILENAME /var/www/html/public$fastcgi_script_name;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
}
# Serve static files (e.g., from Laravel public)
location /static/ {
root /var/www/html/public; # Laravel static files
}
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|txt|xml)$ { location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|txt|xml)$ {
expires 365d; expires 365d;
} }
# Handle other static files
location /static/ {
root /var/www/html; # React static files
}
# Prevent caching for the Laravel dynamic content
location ~ \.php$ {
fastcgi_pass laravel-app:9000; # Forward PHP requests to the Laravel app
fastcgi_param SCRIPT_FILENAME /var/www/html/public$fastcgi_script_name;
include fastcgi_params;
}
} }

View File

@ -21,7 +21,7 @@ RUN yarn install # Install dependencies
COPY . /usr/src/app COPY . /usr/src/app
# Build the app # Build the app
RUN yarn build RUN yarn build --ignore-scripts
# Stage 2 - the production environment # Stage 2 - the production environment
FROM nginx:1.13.9-alpine FROM nginx:1.13.9-alpine

View File

@ -35,7 +35,7 @@
"react-redux": "^5.0.7", "react-redux": "^5.0.7",
"react-router-dom": "^4.2.2", "react-router-dom": "^4.2.2",
"react-router-redux": "^4.0.8", "react-router-redux": "^4.0.8",
"react-scripts": "1.1.5", "react-scripts": "^5.0.1",
"redux": "^4.0.0", "redux": "^4.0.0",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-saga": "^0.16.0", "redux-saga": "^0.16.0",
@ -45,14 +45,15 @@
}, },
"scripts": { "scripts": {
"start": "react-app-rewired start", "start": "react-app-rewired start",
"build": "react-scripts build && cp -r build/* ../public",
"test": "react-app-rewired test --env=jsdom", "test": "react-app-rewired test --env=jsdom",
"eject": "react-app-rewired eject", "eject": "react-app-rewired eject",
"dev": "docker-compose up -d --build", "dev": "docker-compose up -d --build",
"dev:stop": "docker-compose down", "dev:stop": "docker-compose down",
"prod": "docker-compose -f docker-compose-prod.yml up -d --build", "prod": "docker-compose -f docker-compose-prod.yml up -d --build",
"prod:stop": "docker-compose -f docker-compose-prod.yml down", "prod:stop": "docker-compose -f docker-compose-prod.yml down",
"flow": "flow" "flow": "flow",
"build": "react-scripts build"
}, },
"devDependencies": { "devDependencies": {
"babel-plugin-import": "^1.7.0", "babel-plugin-import": "^1.7.0",

View File

@ -1,6 +0,0 @@
export function customAction({type, payload}) {
return {
type: type,
payload
};
}

View File

@ -1,683 +0,0 @@
import React from "react";
import {
Table,
Input,
Icon,
Divider,
Tooltip,
Popconfirm,
message,
Button,
Pagination,
Row,
Col
} from "antd";
//import { callApi } from "utils/Api";
import { Link, withRouter } from "react-router-dom";
import querystring from "querystring";
import { encrypt, decrypt } from 'utils/encrypto'
// const querystring = require("querystring");
const { Column, ColumnGroup } = Table;
const Search = Input.Search;
class CustomTable extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
data: [],
loading: false,
filteredInfo: null,
sortedInfo: null,
pageName: '_page',
currentPage: 1,
sizeName: '_limit',
pageSize: 10,
totalData: 100,
searchValue: null,
headerColumns: props.columns.map(column => {
if (column.sorter) {
column.sorter = (a, b) => a[column.dataIndex] - b[column.dataIndex];
column.sortOrder = null;
}
return column;
})
};
}
componentDidMount() {
this.handleInitialLoad()
}
componentDidUpdate(prevProps, prevState, snapshot) {
const { currentPage, pageSize, pageName } = this.state;
if (prevProps.location.search !== this.props.location.search) {
if (this.props.location.search === "") {
this.handleclearAll();
}
}
if (prevState.sortedInfo !== this.state.sortedInfo) {
//Sort Columns Function
this.setState({
headerColumns: this.state.headerColumns.map(column => {
if (
this.state.sortedInfo &&
this.state.sortedInfo.columnKey === column.dataIndex
) {
column.sortOrder = this.state.sortedInfo.order;
}
return column;
})
});
}
}
handleInitialLoad = () => {
const { search } = this.props.location;
const { currentPage, pageSize, pageName, sizeName } = this.state;
if (search) {
let parsed = querystring.parse(search.substring(1));
console.log('====================================');
console.log(parsed,pageName, sizeName,parsed[pageName], parsed[sizeName], "&&&&&&SEARCH!!");
console.log('====================================');
if (parsed) {
if (parsed[pageName] && parsed[sizeName] && parsed.q && parsed._sort && parsed._order ) {
this.handleUpdateData({
page: parseInt(parsed[pageName]),
size: parseInt(parsed[sizeName]),
search: parsed.q
});
} else if (parsed[pageName] && parsed[sizeName] && parsed.q) {
alert("Search")
this.handleUpdateData({
page: parseInt(parsed[pageName]),
size: parseInt(parsed[sizeName]),
search: parsed.q
});
} else if (parsed[pageName] && parsed[sizeName]) {
this.handleUpdateData({
page: parseInt(parsed[pageName]),
size: parseInt(parsed[sizeName]),
});
}
// this.fetch({
// ...parsed
// });
}
} else {
this.handleUpdateData({
page: currentPage,
size: pageSize,
});
}
}
handleTableChange = (pagination, filters, sorter) => {
const { currentPage, pageSize, searchValue } = this.state;
console.log("====================================");
console.log(filters, sorter, "tesadasdas");
console.log("====================================");
// this.setState({
// filteredInfo: filters,
// sortedInfo: sorter
// });
if (Object.keys(sorter).length !== 0) {
// this.fetch({
// _page: currentPage,
// _limit: pageSize,
// _sort: sorter.field,
// _order: sorter.order === "ascend" ? "asc" : "desc",
// ...filters
// });
if (searchValue) {
this.handleUpdateData({
sort: sorter,
filter: filters,
search: searchValue
});
} else {
this.handleUpdateData({ sort: sorter, filter: filters });
}
} else {
if (searchValue) {
this.handleUpdateData({ filter: filters, search: searchValue });
} else {
this.handleUpdateData({ filter: filters });
}
// this.fetch({
// _page: currentPage,
// _limit: pageSize,
// ...filters
// });
}
};
fetch = async (params = {}) => {
let { url, history } = this.props;
const stringified = querystring.stringify(params);
console.log("GGGGG3333", url.default, stringified, params, window.location);
this.setState({ loading: true });
// history.push(`${url.default}?${stringified}`);
history.push({
pathname: url.default,
search: stringified
});
try {
// let response = await callApi({
// url: url.default,
// params: {
// _page: params._page,
// _limit: params._limit,
// ...params
// }
// });
// if (response.status === 200) {
// this.setState({
// loading: false,
// data: response.data,
// totalData: response.data.total ? response.data.total : 100
// });
// }
} catch (error) {}
};
handleUpdateData = ({ search, sort, page, size, filter }) => {
const {
currentPage,
pageSize,
filteredInfo,
sortedInfo,
searchValue
} = this.state;
console.log("====================================");
console.log(
search, sort, page, size, filter,
"WWOOOPS!!"
);
console.log("====================================");
if (search && sort && filter) {
this.setState({
filteredInfo: filter,
sortedInfo: sort,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc",
...filter
});
//filteredInfo value
} else if (filter) {
if (sort) {
this.setState({
filteredInfo: filter,
sortedInfo: sort,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc",
...filter
});
} else if (search) {
this.setState({
filteredInfo: filter,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search,
...filter
});
} else {
this.setState({
filteredInfo: filter,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
...filter
});
}
//sortedInfo value
} else if (sort) {
if (filter) {
this.setState({
filteredInfo: filter,
sortedInfo: sort,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc",
...filter
});
} else if (search) {
this.setState({
sortedInfo: sort,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc"
});
} else {
this.setState({
sortedInfo: sort,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc"
});
}
//search Value
} else if (search) {
if (filter) {
this.setState({
filteredInfo: filter,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search,
...filter
});
} else if (sort) {
this.setState({
sortedInfo: sort,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc"
});
} else if (page && size) {
alert(page, size, "OOOPS@")
this.setState({
currentPage: page,
pageSize: size,
searchValue: search,
})
this.fetch({
_page: page,
_limit: size,
q: search
});
} else if (page) {
this.setState({
currentPage: page,
searchValue: search,
})
this.fetch({
_page: page,
_limit: pageSize,
q: search
});
} else if (size) {
this.setState({
pageSize: size,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: size,
q: search
});
} else {
this.setState({
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search
});
}
} else {
if (page && size) {
this.setState({
currentPage: page,
pageSize: size,
})
this.fetch({
_page: page,
_limit: size
});
} else if (page) {
this.setState({
currentPage: page,
})
this.fetch({
_page: page,
_limit: pageSize
});
} else if (size) {
this.setState({
pageSize: size,
})
this.fetch({
_page: currentPage,
_limit: size
});
} else {
this.fetch({
_page: currentPage,
_limit: pageSize
});
}
}
};
handleSearch = e => {
const { currentPage, pageSize, filteredInfo, sortedInfo } = this.state;
console.log("====================================");
console.log(e);
console.log("====================================");
// this.setState({
// searchValue: e
// });
if (e) {
this.handleUpdateData({ search: e, page: 1 });
} else {
this.handleUpdateData({ search: null, page: 1 });
}
};
handleSearchChange = e => {
const { currentPage, pageSize, filteredInfo, sortedInfo } = this.state;
// this.setState({
// searchValue: e
// });
if (e) {
this.setState({
searchValue: e.target.value
})
} else {
this.setState({
searchValue: null
})
}
};
handleDeleteConfirmYes = e => {
console.log(e);
message.success("Click on Yes");
};
handleDeleteConfirmNo = e => {
console.log(e);
message.error("Click on No");
};
handleclearAll = () => {
console.log("====================================");
console.log("reset");
console.log("====================================");
this.setState({
filteredInfo: null,
sortedInfo: null,
searchValue: null,
// currentPage: 1,
// pageSize: 10
});
this.handleUpdateData({
page: 1,
size: 10,
});
// this.fetch({
// _page: 1,
// _limit: 10
// });
};
handlePagination = page => {
const { pageSize, searchValue } = this.state;
// this.fetch({
// _page: page,
// _limit: pageSize
// });
if (searchValue) {
this.handleUpdateData({ page, search: searchValue });
} else {
this.handleUpdateData({ page });
}
// this.setState({
// currentPage: page
// });
};
handleSizeChange = (current, pageSize) => {
console.log("TEST!", current, pageSize, searchValue);
const { searchValue } = this.state;
// this.fetch({
// _page: current,
// _limit: pageSize
// });
if (searchValue) {
this.handleUpdateData({
page: current,
size: pageSize,
search: searchValue
});
} else {
this.handleUpdateData({ page: current, size: pageSize });
}
// this.setState({
// currentPage: current,
// pageSize
// });
};
handleRenderActionButton = ({action, item}) => {
let {keyValue} = this.props
let idValue = item[keyValue].toString()
console.log('====================================');
console.log('====================================');
switch (action.type) {
case 'edit':
return <Tooltip key={action.key} placement="top" title={action.name}>
<Link to={`${action.path}/1`} style={{padding: '5px 8px'}}>
<Icon type='edit' />
</Link>
</Tooltip>
break;
case 'view':
return <Tooltip key={action.key} placement="top" title={action.name}>
<Link to={`${action.path}/2`} style={{padding: '5px 8px'}}>
<Icon type='right-circle-o' />
</Link>
</Tooltip>
break;
case 'delete':
return <Tooltip key={action.key} placement="top" title={action.name}>
<Popconfirm
title="Are you sure delete this item?"
onConfirm={() => this.handleDeleteConfirmYes(item)}
onCancel={() => this.handleDeleteConfirmNo(item)}
okText="Yes"
cancelText="No" >
<a href="javascript:;" style={{padding: '5px 8px'}}>
<Icon type='delete' />
</a>
</Popconfirm>
</Tooltip>
break;
default:
return null
break;
}
}
render() {
let { columns, keyValue, actions } = this.props;
let {
sortedInfo,
filteredInfo,
headerColumns,
currentPage,
pageSize,
totalData,
data,
searchValue
} = this.state;
// let headerColumns = columns.map(column => {
// if (column.sorter) {
// column.sorter = (a, b) => a[column.dataIndex] - b[column.dataIndex];
// column.sortOrder =
// sortedInfo &&
// sortedInfo.columnKey === column.dataIndex &&
// sortedInfo.order;
// }
// return column;
// });
console.log("====================================");
console.log(currentPage, pageSize, "TESTASDASD");
console.log("====================================");
return (
<div>
<div className="table-operations">
<Button onClick={this.handleclearAll}>
Clear filters
</Button>
</div>
<Search
value={searchValue}
placeholder="input search text"
onSearch={this.handleSearch}
onChange={this.handleSearchChange}
enterButton
/>
<Table
rowKey={keyValue}
dataSource={data}
loading={this.state.loading}
onChange={this.handleTableChange}
pagination={false}
>
{columns &&
columns.map(column => (
<Column
key={column.dataIndex ? column.dataIndex : column.key}
{...column}
/>
))}
{
actions && actions.length > 0 && <Column
title="Action"
align="center"
key="action"
width={130}
render={(text, record) => (
actions.map(item => {
return item.access === true && this.handleRenderActionButton({action: item, item: record})
})
)}
/>
}
</Table>
<Row style={{paddingTop: 20}}>
<Col span={8}>col-12</Col>
<Col span={16} style={{textAlign: 'right'}}>
<Pagination
// size="small"
current={currentPage}
pageSize={pageSize}
showSizeChanger
onChange={this.handlePagination}
onShowSizeChange={this.handleSizeChange}
// defaultCurrent={currentPage}
total={totalData}
/>
</Col>
</Row>
</div>
);
}
}
export default withRouter(CustomTable);

View File

@ -1,90 +0,0 @@
import React, { Component } from "react";
import { Icon, Avatar, Dropdown, Menu, notification } from "antd";
import { Link, withRouter } from "react-router-dom";
import Helmet from 'react-helmet';
import { connect } from "react-redux";
import { customAction } from 'actions'
import styled from 'styled-components';
import { API_UNI_OIL , API_POST } from 'utils/Api'
const HeaderButton = styled.a`
/* This renders the buttons above... Edit me! */
padding: 0 10px;
display: inline-block;
vertical-align: top;
cursor: pointer;
-webkit-transition: all .3s,padding 0s;
transition: all .3s,padding 0s;
&:hover {
background-color: rgb(243, 243, 243);
color: #fff
}
`
class HeaderDropdown extends Component {
state = {
}
handleLogout = () => {
this.props.customAction({type: 'LOGOUT'});
}
render() {
//const { userInfo } = this.state
const { history, userInfo } = this.props;
const menu = (
<Menu style={{width: 150 , margin: '0 0 0 auto'}} >
<Menu.Item key="0">
<a
onClick={()=> history.push("/my-profile")}
role="button"
rel="noopener noreferrer" >
<Icon type="user" /> My Profile
</a>
</Menu.Item>
<Menu.Divider />
<Menu.Item key="1">
<a
role="button"
onClick={this.handleLogout}>
<Icon type="logout" /> Logout
</a>
</Menu.Item>
</Menu>
);
return (
<div>
<Helmet title = "Dashboard" />
<Dropdown overlay={menu} placement="bottomRight">
<HeaderButton role="button" style={{
marginRight: 16, color: '#8E8E93', maxWidth: '256px',
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' , position: 'relative'
}} >
<Avatar size="small"
style={{ background: '#B8BBC9', marginRight: 5 }} icon="user"
/> { userInfo && (`${userInfo.firstname} ${userInfo.lastname}`) } <Icon type="down" />
</HeaderButton>
</Dropdown>
</div>
);
}
}
HeaderDropdown = connect(
state => ({
//fetchData: state.fetchData
// put initial values from account reducer
}),
{ customAction }
)(HeaderDropdown)
export default withRouter(HeaderDropdown);

View File

@ -1,75 +0,0 @@
import React from "react";
import { Breadcrumb, Icon } from 'antd';
// import styled from 'styled-components';
import { Link, withRouter } from 'react-router-dom';
function MainBreadcrumbs(props) {
const {
pageRoutes,
// match,
location,
root
} = props;
const pathSnippets = location.pathname.split('/').filter(i => i);
const extraBreadcrumbItems = pathSnippets.map((route, index) => {
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
const routeCompare = pageRoutes.find((myRoute) => myRoute.path === url)
const paramsId = pathSnippets[pathSnippets.length - 1]
if (routeCompare) {
if (routeCompare.params) {
return (
<Breadcrumb.Item key={index}>
<Link to={`${url}/${paramsId}`}>
{routeCompare.name}
</Link>
</Breadcrumb.Item>
);
} else {
return (
<Breadcrumb.Item key={index}>
<Link to={url}>
{routeCompare.name}
</Link>
</Breadcrumb.Item>
);
}
}
// return <Breadcrumb.Item key={index}></Breadcrumb.Item>;
})
if (root) {
return (
<Breadcrumb
separator={<Icon type="right" style={{fontSize: '10px', opacity: 0.6}} />}
routeComparestyle={{ padding: '11px 24px 9px', fontSize: '12px' }}
>
<Breadcrumb.Item>
<Link to='/my-profile'>
<Icon type="home" /> {` Home`}
</Link>
</Breadcrumb.Item>
</Breadcrumb>
);
} else {
return (
<Breadcrumb
separator={<Icon type="right" style={{fontSize: '10px', opacity: 0.6}} />}
style={{ padding: '11px 24px 9px', fontSize: '12px' }}
>
<Breadcrumb.Item>
<Link to='/my-profile'>
<Icon type="home" /> {` Home`}
</Link>
</Breadcrumb.Item>
{extraBreadcrumbItems}
</Breadcrumb>
);
}
}
export default withRouter(MainBreadcrumbs);

View File

@ -1,36 +0,0 @@
import React from "react";
import { Layout } from 'antd';
import { withRouter } from 'react-router-dom'
import MainBreadcrumbs from './MainBreadcrumbs'
const { Content } = Layout;
function MainContent(props) {
const {
children,
pageRoutes,
root
} = props;
return (
[
props.location && props.location.key && (
<div key={1} style={{
background: '#fff' ,
marginBottom: '75px',
position: 'fixed',
marginTop: '-110px',
width: '100%'}}
>
<MainBreadcrumbs root={root} pageRoutes={pageRoutes} />
</div>
)
,
<Content key={2} style={{ margin: '0 16px', padding: '0', background: '#fff', }}>
{children}
</Content>,
]
);
}
export default withRouter(MainContent);

View File

@ -1,19 +0,0 @@
import React from "react";
import { Layout } from "antd";
// import styled from 'styled-components';
// import { Link } from 'react-router-dom';
const { Footer } = Layout;
function MainFooter(props) {
// const {} = props;
return (
<Footer style={{ textAlign: "center", background: '#fcfcfc' }} >
{/* YONDU-SDG ©2018 Created by Front End Team! */}
</Footer>
);
}
export default MainFooter;

View File

@ -1,83 +0,0 @@
import React from "react";
import { Layout, Icon } from "antd";
import { connect } from "react-redux";
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import HeaderDropdown from './HeaderDropdown';
const { Header } = Layout;
const HeaderLink = styled(Link)`
/* This renders the buttons above... Edit me! */
padding: 0 10px;
display: inline-block;
vertical-align: top;
cursor: pointer;
-webkit-transition: all .3s,padding 0s;
transition: all .3s,padding 0s;
&:hover {
background-color: #1890ff;
color: #fff
}
`
const RightHeader = styled.div`
/* This renders the buttons above... Edit me! */
float: right;
`
const IconTrigger = styled(Icon)`
/* This renders the buttons above... Edit me! */
font-size: 20px;
line-height: 69px;
cursor: pointer;
-webkit-transition: all .3s,padding 0s;
transition: all .3s,padding 0s;
padding: 0 24px;
&:hover {
color: #1890ff;
}
`
function MainHeader(props) {
const {
collapsed,
toggle,
userInfo,
} = props
return (
<Header style={{ background: '#fff', padding: 0, height: '66px', lineHeight: '69px', borderBottom: '1px solid rgb(230, 236, 245)', }}>
<IconTrigger
className="trigger"
type={collapsed ? 'menu-unfold' : 'menu-fold'}
onClick={toggle}
/>
<RightHeader>
<HeaderDropdown userInfo={userInfo}/>
</RightHeader>
</Header>
);
}
MainHeader = connect(
state => ({
// pull initial values from account reducer
}),
// { customAction }
)(MainHeader);
export default MainHeader;

View File

@ -1,262 +0,0 @@
import React from 'react';
import { Layout, Icon, Menu } from 'antd';
import styled from 'styled-components';
import { withRouter, Link } from 'react-router-dom';
const { SubMenu } = Menu;
const { Sider } = Layout;
const LogoPlaceholder = styled.div`
height: 32px;
margin: 16px;
background-repeat: no-repeat;
background-size: contain;
background-position: center;
`;
function MainSidebar(props) {
const { collapsed, match, location, userInfo, systemPreferences } = props;
const navigation = [
{
key: 0,
label: 'User Management',
path: '/user-management',
icon: 'team',
access: userInfo && userInfo.role == 1 ? true : false,
//access: userInfo && (userInfo.role == 1 || userInfo.role == 3) ? true : false,
},
{
key: 9,
label: 'Notifications',
path: '/notifications',
icon: 'notification',
access: true,
//access: userInfo && (userInfo.role == 1 || userInfo.role == 3) ? true : false,
},
{
key: 4,
label: 'Member Management',
path: '/member-management',
icon: 'credit-card',
access: userInfo && userInfo.role == 1 ? true : false,
child: [
{
key: 0.0,
label: 'Card Member',
path: '/member-management/card-member',
access: true,
},
{
key: 0.1,
label: 'Locked Accounts',
path: '/member-management/lock-account',
access: true,
},
],
},
{
key: 8,
label: 'Home Page ( Mobile ) ',
path: '/home-page',
icon: 'home',
access: true,
child: [
{
key: 0.0,
label: 'Photo Slider',
path: '/home-page/photo-slider',
access: true,
},
],
},
{
key: 3,
label: 'Promotions',
path: '/promotions',
icon: 'tags',
access: true,
},
{
key: 2,
label: 'Top-Up',
path: '/top-up',
icon: 'plus-circle',
access: userInfo && userInfo.role == 1 ? true : false,
},
{
key: 6,
label: 'About Us',
path: '/about-us',
icon: 'info-circle',
access: userInfo && userInfo.role == 1 ? true : false,
child: [
{
key: 0.6,
label: 'Card Types',
path: '/about-us/card-types',
access: true,
},
{
key: 0.5,
label: 'Terms & Privacy',
path: '/about-us/term-privacy',
access: true,
},
],
},
{
key: 7,
label: 'Reports',
path: '/reports',
icon: 'file-text',
access: true,
child: [
{
key: 0.7,
label: 'Registration Report',
path: '/reports/registration-report',
access: true,
},
{
key: 0.8,
label: 'Top-Up Usage Report',
path: '/reports/top-up',
access: true,
},
{
key: 0.9,
label: 'Mobile Usage Report',
path: '/reports/mobile-report',
access: true,
},
{
key: 0.1,
label: 'Station Rating Report',
path: '/reports/station-rating',
access: true,
},
],
},
{
key: 8,
label: 'System Parameters',
path: '/system-parameters',
icon: 'setting',
access: userInfo && userInfo.role == 1 ? true : false,
},
{
key: 12,
label: 'Station Locator',
path:'',
icon:'environment',
access: true,
child: [
{
key: 0.11,
label: 'Branches',
path:'/branches',
access: true
},
{
key: 0.12,
label: 'Stations',
path:'/stations',
access: true
},
{
key: 0.13,
label: 'Fuels',
path:'/fuels',
access: true
}
],
},
];
let newURL = location.pathname.split('/');
let appendedUrl = newURL[2];
if (appendedUrl == 'create' || appendedUrl == 'view' || appendedUrl == 'edit') appendedUrl = null;
let isSeondDaryPathExist = appendedUrl ? `/${appendedUrl}` : '';
let secondaryURL = `${match.path}${isSeondDaryPathExist}`;
return (
<Sider
trigger={null}
collapsible
width={295}
collapsed={collapsed}
style={{ background: '#fff', border: 'solid 1px #e6ecf5', zIndex: '999' }}
>
{!collapsed ? (
<div style={{ height: '65px', padding: '12px 0', textAlign: 'center', borderBottom: '1px solid #e6ecf5' }}>
{/* <img src={ require("assets/img/logo_unioil.png") } style={{ height: 40 }} /> */}
{userInfo && (
<img src={`${systemPreferences ? systemPreferences : userInfo.logo}`} style={{ height: '100%' }} />
)}
</div>
) : (
<LogoPlaceholder
className='logo'
style={{ backgroundImage: `url(${systemPreferences ? systemPreferences : userInfo.logo})` }}
/>
)}
<Menu
style={{ borderRight: !collapsed ? 0 : null, height: '90vh', overflow: 'auto', paddingTop: '17px' }}
//inlineIndent={10}
defaultOpenKeys={[ match.path ]}
selectedKeys={[ secondaryURL ]}
mode='inline'
>
{navigation.map((item) => {
if (item.access) {
if (item.child) {
return (
<SubMenu
key={item.path}
title={
<span>
<Icon type={item.icon} />
<span>{item.label}</span>
</span>
}
>
{item.child.map((subItem) => {
if (subItem.access) {
return (
<Menu.Item key={subItem.path}>
<Link to={subItem.path} style={{ paddingLeft: '15px' }}>
{subItem.icon && <Icon type={subItem.icon} />}
{subItem.label}
</Link>
</Menu.Item>
);
} else {
return null;
}
})}
</SubMenu>
);
} else {
return (
<Menu.Item key={item.path}>
<Link to={item.path}>
{item.icon && <Icon type={item.icon} />}
<span>{item.label}</span>
</Link>
</Menu.Item>
);
}
} else {
return null;
}
})}
</Menu>
</Sider>
);
}
export default withRouter(MainSidebar);

View File

@ -1,107 +0,0 @@
import React from 'react'
import { Layout } from 'antd';
import { notification } from "antd";
import { connect } from 'react-redux';
import IdleTimer from 'react-idle-timer'
import MainFooter from './components/MainFooter'
import MainHeader from './components/MainHeader'
import MainSidebar from './components/MainSidebar'
import { API_UNI_OIL , API_POST } from 'utils/Api'
import { customAction } from 'actions'
class DashboardLayout extends React.Component {
constructor(props) {
super(props)
this.idleTimer = null
this.onActive = this.handleActive.bind(this)
this.onIdle = this.handleIdle.bind(this)
this.state = {
collapsed: false,
userInfo: null,
updatedLogo: null
};
}
componentWillReceiveProps(nexProps, prevProps) {
if(nexProps && nexProps.systemPreferences) {
if(nexProps.systemPreferences.data && nexProps.systemPreferences.data) {
this.setState({
updatedLogo: nexProps.systemPreferences.data.logo
})
}
}
}
componentDidUpdate(nexProps) {
}
handleActive(e) {
// console.log('user is active', e)
}
handleIdle(e) {
// console.log('user is idle', e)
notification.error({
message: "Error",
description: <div>You are logout automatically for being idle more than 10 minutes.</div>,
duration: 0,
key: 'idle-notification-1'
});
this.props.customAction({type: 'LOGOUT'});
}
toggle = () => {
this.setState({
collapsed: !this.state.collapsed,
});
}
render() {
//const { userInfo } = this.state
const { children, userInfo } = this.props
return (
<Layout style={{ height: '100%' }}>
<MainSidebar collapsed={this.state.collapsed} userInfo={userInfo.data.userInfo} systemPreferences={this.state.updatedLogo}/>
<Layout style={{background: '#fcfcfc', paddingBottom: '10px'}}>
<MainHeader
collapsed={this.state.collapsed}
toggle={this.toggle}
userInfo={userInfo.data.userInfo}
/>
<div style={{ overflow: 'auto', marginTop: '94px', paddingTop: '16px', position: 'relative' }}>
<IdleTimer
ref={ref => { this.idleTimer = ref }}
element={document}
onActive={this.onActive}
onIdle={this.onIdle}
timeout={600000}
>
{children}
</IdleTimer>
</div>
</Layout>
</Layout>
);
}
}
DashboardLayout = connect(
state => ({
// pull initial values from account reducer
userInfo: state.login,
systemPreferences: state.systemPreferences
}),
{ customAction }
)(DashboardLayout);
export default DashboardLayout

View File

@ -1,31 +0,0 @@
import React from 'react';
import DashboardLayout from '../Layout'
import { Route, Redirect } from 'react-router-dom'
import { connect } from 'react-redux'
function DashboardRoute({ component: Component, isAuthenticated, ...rest }) {
return (
<Route {...rest} render={props => isAuthenticated ? (
<DashboardLayout>
<Component {...props} />
</DashboardLayout>
) : <Redirect
to={{
pathname: "/login",
state: { from: props.location, message: "You must log in to Enter this page" }
}}
/>
}
/>
)
};
export default DashboardRoute = connect(
state => ({
isAuthenticated: state.auth.isAuthenticated
}),
)(DashboardRoute);

View File

@ -1,48 +0,0 @@
import React, { Component, Fragment } from 'react';
import { Link } from 'react-router-dom';
import { List, Avatar } from 'antd';
import { fetchData } from 'utils/Api';
class ListDataDisplay extends Component {
state = {
data: []
};
async componentDidMount() {
// const { url } = this.props;
// const response = await fetchData(url);
// this.setState({
// data: response.data
// })
}
render() {
const { layout, avatar, viewPath, header, footer } = this.props;
return (
<Fragment>
<List
header={header}
footer={footer}
itemLayout={layout}
dataSource={this.state && this.state.data.data}
renderItem={item => (
<List.Item>
<List.Item.Meta
avatar={avatar && <Avatar
src={item.avatar}
/>}
title={<Link to={viewPath.replace(':id', item.id)}>{item.first_name}</Link>}
description={`${item.first_name.toLowerCase()}_${item.last_name.toLowerCase()}@gmail.com`}
/>
</List.Item>
)}
/>
</Fragment>
)
}
}
export default ListDataDisplay;

View File

@ -1,5 +0,0 @@
import List from './List';
export {
List,
}

View File

@ -1,66 +0,0 @@
import React, { PureComponent } from "react";
import { withRouter } from "react-router-dom";
import { Menu, Dropdown, Button, notification } from "antd";
import DownloadFile from "js-file-download";
import queryString from "query-string";
import moment from 'moment';
import { API_UNI_OIL } from "utils/Api";
class DropdownExport extends PureComponent {
state = {
loading: false
}
handleExportCSV = async() => {
this.setState({ loading: true });
let { location } = this.props;
let { search } = location;
let params = queryString.parse(search);
if(this.props.defaultFilter){
params = {
...params,
...this.props.defaultFilter
}
}
try {
let response = await API_UNI_OIL.get(this.props.url.path, {
params,
responseType: 'blob'
});
if (response.status === 200 || response.status === 201) {
//let dateNow = moment(new Date()).format('DD-MMM-YYYY')
let dateNow = moment(new Date()).format('MMDDYYYY')
DownloadFile(response.data, `${this.props.url.fileName}_${dateNow}.csv`);
this.setState({ loading: false });
}
} catch (error) {
this.setState({ loading: false });
}
}
render() {
const { loading } = this.state;
return(
<Button
loading={loading}
onClick={this.handleExportCSV}
style={{background: 'rgb(231, 70, 16)', borderColor: 'rgb(231, 70, 16)', color: '#fff'}}
>
Export CSV
</Button>
)
}
}
export default withRouter(DropdownExport);

View File

@ -1 +0,0 @@
export { default as DropdownExport } from "./DropdownExport";

View File

@ -9,7 +9,7 @@ const CheckboxForm = ({
inline, inline,
layout, layout,
required, required,
...props, ...props
}) => { }) => {
if (inline) { if (inline) {

View File

@ -40,7 +40,7 @@ class InputForm extends Component {
layout, layout,
optionsList, optionsList,
isRadioButton, isRadioButton,
...props, ...props
} = this.props; } = this.props;
let _props = {...props}; let _props = {...props};

View File

@ -1,16 +0,0 @@
import { Icon } from 'antd';
import React from 'react';
const Loading = () => {
return (
<div style={{padding: 20, display: 'flex' , justifyContent: 'center' , marginLeft: '-144px'}}>
<div>
<Icon type="sync" spin /> Loading Data Please wait...
</div>
</div>
);
};
export default Loading;

View File

@ -1,23 +0,0 @@
import React from 'react'
import { Layout } from 'antd';
const { Header, Footer, Content, Sider } = Layout;
function LoginLayout({children, ...rest}) {
return (
<Layout style={{height: "100%"}}>
<Sider width='50%' style={{ background: `url(${require("assets/img/bg_cms.png")}) center`, backgroundSize: 'cover' } }></Sider>
<Layout>
<Content style={{padding: 16}} >{children}</Content>
<Footer style={{textAlign: 'center', fontSize: '12px'}}>
<div style={{margin: '25px auto', padding: '17px 0', width: '325px', borderTop: '1px solid #e0e0e0', textAlign: 'left', color: '#8E8E93' }}>
By logging in you agree to Unioil's Terms of Service, <br/>Privacy Policy and Content Policies.
</div>
</Footer>
</Layout>
</Layout>
)
}
export default LoginLayout

View File

@ -1,16 +0,0 @@
import React from 'react';
import LoginLayout from '../Layout'
import { Route } from 'react-router-dom'
const LoginLayoutRoute = ({component: Component, ...rest}) => {
return (
<Route {...rest} render={matchProps => (
<LoginLayout>
<Component {...matchProps} />
</LoginLayout>
)} />
)
};
export default LoginLayoutRoute

View File

@ -1,84 +0,0 @@
import React, { Component } from "react";
import { Modal, Button } from "antd";
import { Link } from 'react-router-dom';
class ModalCancel extends React.Component {
state = { visible: false };
showModal = () => {
this.setState({
visible: true
});
};
handleOk = e => {
console.log(e);
this.setState({
visible: false
});
};
handleCancel = e => {
console.log(e);
this.setState({
visible: false
});
};
render() {
const {
path,
title,
message,
id,
dirty,
name,
loading
} = this.props
if (dirty) {
return [
<Button
key="button"
disabled={loading}
onClick={this.showModal}>
{name}
</Button>,
<Modal
getContainer={() => document.getElementById(id)}
key="modal"
width={300}
title={title ? title : "Your Title"}
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key={1} type="primary" onClick={this.handleCancel}>No</Button>,
<Link key={2} to={path} style={{marginLeft: 10}}>
<Button >
Yes
</Button>
</Link>
]}
>
{message ? message : "Your Message"}
</Modal>
];
} else {
return <Link to={path} disabled={loading}>
<Button key="back" disabled={loading}>
{name}
</Button>
</Link>
}
}
}
export default ModalCancel;

View File

@ -1,429 +0,0 @@
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import queryString from 'query-string';
import { getCookie } from '../../utils/cookie';
import _ from 'lodash';
import {
Table,
Button,
Row,
Col,
Input,
Icon,
Pagination,
Tooltip,
notification,
Popconfirm,
message,
DatePicker,
} from 'antd';
import { DropdownExport } from 'components/Dropdown/index';
import { fnQueryParams } from 'utils/helper';
import { API_UNI_OIL, API_GET, API_DELETE } from 'utils/Api';
import { API_GET_NOTIF } from 'utils/NotificationApi';
import '../Tables/index.css';
const { RangePicker } = DatePicker;
class Index extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
total: null,
loading: false,
selectedRowKeys: [],
columns: [],
search_filter: '',
visible: false,
mounted: false,
test: true,
updating: false,
};
this.delayFetchRequest = _.debounce(this.fetch, 500);
this.handleSearchChangeDebounce = _.debounce(this.handleSearchStateChange, 1000);
}
componentDidMount() {
this.setState({ mounted: true });
this.handleFilterChange({});
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevState.updating !== prevProps.updating) {
this.setState({ updating: prevProps.updating });
}
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.updating !== nextState.updating) {
this.handleFilterChange({});
return true;
}
return true;
}
componentWillMount() {
this.delayFetchRequest.cancel();
this.handleSearchChangeDebounce.cancel();
}
handleTableChange = (pagination, filters, sorter) => {
let _sort_order;
if (sorter.order) _sort_order = sorter.order === 'ascend' ? 'asc' : 'desc';
if (sorter.column) {
if (sorter.column.sortByValue) sorter.field = sorter.column.sortByValue;
}
this.handleFilterChange({
...filters,
_sort_by: sorter.field,
_sort_order,
});
};
handleSearchChange = (e) => {
this.setState({ search_filter: e.target.value });
this.handleSearchChangeDebounce(e.target.value);
};
handleSearchStateChange = (search_filter) => {
this.setState({ search_filter });
this.handleFilterChange({ search: this.state.search_filter });
};
onPaginationChange = (page, page_size) => {
console.log("page")
console.log(page)
this.handleFilterChange({ page });
};
handleFilterChange = (props, isClearFilter) => {
console.log(props)
this.setState({ loading: true });
let { history, location } = this.props;
let { search, pathname } = location;
let urlParamsObject = isClearFilter ? props : queryString.parse(search);
urlParamsObject = props ? { ...urlParamsObject,page: 1, ...props } : {};
urlParamsObject = fnQueryParams(urlParamsObject);
urlParamsObject = queryString.parse(urlParamsObject);
history.push({ pathname, search: fnQueryParams(urlParamsObject) });
console.log({ pathname, search: fnQueryParams(urlParamsObject) })
this.delayFetchRequest(urlParamsObject);
};
clearFilters = () => {
let { history, location } = this.props;
let { search, pathname } = location;
let urlParamsObject = queryString.parse(search);
delete urlParamsObject['search'];
Object.keys(urlParamsObject).map((key, index) => {
if (this.props.filterValues.includes(key)) delete urlParamsObject[key];
});
history.push({ pathname, search: fnQueryParams(urlParamsObject) });
this.handleFilterChange(urlParamsObject, true);
};
clearAll = () => {
this.setState({ search_filter: '' });
this.handleFilterChange();
};
fetch = async (params = {}) => {
let defaulUrl;
if (this.props.defaultFilter) {
params = {
...params,
...this.props.defaultFilter,
};
}
if (this.props.url.defaultWithFilter) {
defaulUrl = this.props.url.defaultWithFilter;
} else {
defaulUrl = this.props.url.default;
}
try {
let response, data, total;
if(defaulUrl == 'notification'){
console.log(defaulUrl, params)
response = await API_GET_NOTIF('notification', params);
console.log(response.data, params, 'response');
console.log(getCookie('TOKEN').token);
data = response.data.data.length > 0 ? response.data.data : null;
total = response.data.total
}
console.table(data, 'data');
this.setState({ data, total, loading: false });
if (data == null && this.props.isEmptyMessagePopUp) {
message.info('No records found.');
}
if (this.props.dataResponse) {
this.props.dataResponse(data.length);
}
} catch (error) {
this.setState({ loading: false, total: 0 });
console.log('An error encountered: ' + error);
}
};
update = async (params = {}) => {
notification.success({
message: 'Success',
description: `Delete Successful.`,
});
};
remove = async (params = {}) => {
notification.error({
message: 'Error',
description: `Error message.`,
});
};
delete = async (uuid) => {
console.log(uuid)
let search = this.props.location;
console.log(search.pathname)
let api = process.env.REACT_APP_STATION_API
let path = search.pathname.substring(1)
try {
await API_UNI_OIL.delete(`${api}${path}/${uuid}`);
this.handleFilterChange({});
message.success('Record was successfully deleted.');
} catch ({ response: error }) {
this.handleFilterChange({});
notification.error({
message: 'Something went wrong deleting record!',
description: (
<div>
<h3>
{error && error.data && error.data.message}
</h3>
</div>
),
duration: 4,
});
}
};
handleBatchDelete = async () => {
const data = { [this.props.keyValue]: this.state.selectedRowKeys };
this.setState({ selectedRowKeys: [] });
try {
// await API_UNI_OIL.delete(this.props.url.apiDelete, { data });
// this.handleFilterChange({});
// message.success('Record was successfully deleted.');
console.log(this.props.url.apiDelete)
} catch ({ response: error }) {
this.handleFilterChange({});
notification.error({
message: 'Error',
description: (
<div>
<div>Something went wrong deleting records.</div>
- {error && error.data && error.data.message}
</div>
),
duration: 3,
});
}
};
onSelectChange = (selectedRowKeys) => {
this.setState({ selectedRowKeys });
};
handleDateRangePicker = async (date, dateString) => {
this.handleFilterChange({
date_start: dateString[0],
date_end: dateString[1],
});
};
render() {
if (!this.state.mounted) return null;
const { loading, selectedRowKeys } = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
getCheckboxProps: (record) => ({
disabled: record.editable == false, // Column configuration not to be checked
//name: record.name,
}),
};
const hasSelected = selectedRowKeys.length > 0;
let { history, keyValue, location, url: { apiDelete } } = this.props;
let { search } = this.props.location;
let urlParamsObject = queryString.parse(search);
let { _sort_order } = urlParamsObject;
if (_sort_order) _sort_order = _sort_order === 'asc' ? 'ascend' : 'descend';
const columns = this.props.columns.map((data) => {
if (data.dataIndex === 'action') {
return {
...data,
render: (text, record) =>
data.buttons.map((action) => {
let actionBtn;
if(action.key == 'location'){
actionBtn = () => this.props.locationData(record)
}
if(action.key == 'edit'){
actionBtn = () => history.push({ pathname: `${location.pathname}/view/${record.id}` });
}
if (action.key == 'delete') {
actionBtn = action.action;
if (record.editable == false) {
return;
} else {
return (
<Popconfirm
placement='bottomRight'
key={action.key}
title={'Are you sure you want to delete this record?'}
onConfirm={() => this.delete(record.id)}
okText='Yes'
cancelText='No'
icon={<Icon type='close-circle' />}
>
<Tooltip key={action.key} placement='top' title={action.title}>
<Icon
type={action.icon}
style={{
padding: '5px 14px 5px 0',
color: 'rgb(231, 70, 16)',
cursor: 'pointer',
}}
/>
</Tooltip>
</Popconfirm>
);
}
}
return (
<Tooltip key={action.key} placement='top' title={action.title}>
<Icon
type={action.icon}
style={{
padding: '5px 14px 5px 0',
color: 'rgb(231, 70, 16)',
cursor: 'pointer',
}}
onClick={actionBtn}
/>
</Tooltip>
);
}),
};
}
let filteredValue = null;
if (Array.isArray(urlParamsObject[data.dataIndex])) {
filteredValue = urlParamsObject[data.dataIndex];
} else if (urlParamsObject[data.dataIndex]) {
filteredValue = [ urlParamsObject[data.dataIndex] ];
}
return {
...data,
filteredValue,
sortOrder: data.sorter ? urlParamsObject._sort_by === data.dataIndex && _sort_order : null,
};
});
return (
<div style={{ margin: '0 24px', padding: '24px 0' }}>
<Row type='flex' justify='space-between' align='bottom' style={{ paddingBottom: 25 }}>
<Col>
{this.props.url.csv ? (
<RangePicker onChange={this.handleDateRangePicker} />
) : (
<Input
onChange={this.handleSearchChange}
style={{ width: 300 }}
value={this.state.search_filter}
prefix={<Icon type='search' style={{ color: 'rgba(0,0,0,.25)' }} />}
type='text'
placeholder='Search'
/>
)}
</Col>
<Col className='table-operations'>
{/* <Button onClick = {this.clearFilters}><b>Clear filters</b></Button>*/}
<Button onClick={this.clearAll}>Clear filters</Button>
{this.props.url.csv && <DropdownExport defaultFilter={this.props.defaultFilter} url={this.props.url.csv} />}
</Col>
</Row>
<Table
size='middle'
rowSelection={apiDelete && rowSelection}
columns={columns}
dataSource={this.state.data ? this.state.data : null}
pagination={false}
rowKey={(record) => record[this.props.keyValue]}
onChange={this.handleTableChange}
loading={loading}
/>
<Row type='flex' justify='space-between' style={{ marginTop: 20 }}>
<Col>
{apiDelete && (
<div>
<Popconfirm
placement='top'
title={'Are you sure you want to delete this record?'}
onConfirm={this.handleBatchDelete}
okText='Yes'
cancelText='No'
icon={<Icon type='close-circle' />}
>
<Button type='danger' disabled={!hasSelected} icon='delete' loading={loading}>
Delete
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected ? `Selected ${selectedRowKeys.length} item(s)` : ''}
</span>
</Popconfirm>
</div>
)}
</Col>
<Col>
{this.state.total > 0 ? (
<Pagination
style={{ float: 'right' }}
showSizeChanger
defaultCurrent={parseInt(urlParamsObject.page, 10) || 1}
defaultPageSize={parseInt(urlParamsObject.page_size, 10) || 10}
pageSizeOptions={[ '10','20']}
total={this.state.total}
showTotal={(total, range) =>
`Showing ${this.state.total > 0 ? range[0] : 0}-${this.state.total > 0 ? range[1] : 0} of ${this.state
.total > 0
? total
: 0}`}
onChange={this.onPaginationChange}
onShowSizeChange={this.onPaginationChange}
/>
) : null}
</Col>
</Row>
</div>
);
}
}
export default withRouter(Index);

View File

@ -1,17 +0,0 @@
import React, { Fragment } from 'react';
import { withRouter} from 'react-router-dom'
function Page404(){
const error = "Page not found";
const errorMessage = "Sorry, but the page you are looking for doesn't exist";
return <Fragment>
<div align="center" style={{ marginTop: "10%" }}>
<h1 style={{ fontSize: 150, fontWeight: 'bold', margin: 0 }}>404</h1>
<p style={{ fontSize: 30, fontWeight: 'bold', margin: 0 }}>{error}</p>
<p>{errorMessage}</p>
</div>
</Fragment>
}
export default withRouter(Page404);

File diff suppressed because it is too large Load Diff

3338
cms-laravel/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,12 +6,15 @@
"dev": "vite" "dev": "vite"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.4.20", "@popperjs/core": "^2.11.6",
"autoprefixer": "^10.4.21",
"axios": "^1.7.4", "axios": "^1.7.4",
"bootstrap": "^5.2.3",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.2.0", "laravel-vite-plugin": "^1.2.0",
"postcss": "^8.4.47", "postcss": "^8.5.3",
"tailwindcss": "^3.4.13", "sass": "^1.56.1",
"tailwindcss": "^3.4.17",
"vite": "^6.0.11" "vite": "^6.0.11"
} }
} }

View File

@ -1,4 +1,34 @@
import 'bootstrap';
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
import axios from 'axios'; import axios from 'axios';
window.axios = axios; window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo';
// import Pusher from 'pusher-js';
// window.Pusher = Pusher;
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: import.meta.env.VITE_PUSHER_APP_KEY,
// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
// wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
// enabledTransports: ['ws', 'wss'],
// });

View File

@ -0,0 +1,7 @@
// Body
$body-bg: #f8fafc;
// Typography
$font-family-sans-serif: 'Nunito', sans-serif;
$font-size-base: 0.9rem;
$line-height-base: 1.6;

View File

@ -0,0 +1,8 @@
// Fonts
@import url('https://fonts.bunny.net/css?family=Nunito');
// Variables
@import 'variables';
// Bootstrap
@import 'bootstrap/scss/bootstrap';

View File

@ -0,0 +1,73 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Login') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('login') }}">
@csrf
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label" for="remember">
{{ __('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Login') }}
</button>
@if (Route::has('password.request'))
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,49 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Confirm Password') }}</div>
<div class="card-body">
{{ __('Please confirm your password before continuing.') }}
<form method="POST" action="{{ route('password.confirm') }}">
@csrf
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Confirm Password') }}
</button>
@if (Route::has('password.request'))
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,47 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
<form method="POST" action="{{ route('password.email') }}">
@csrf
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Send Password Reset Link') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,65 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('password.update') }}">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ $email ?? old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password-confirm" class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Reset Password') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,77 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Register') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('register') }}">
@csrf
<div class="row mb-3">
<label for="name" class="col-md-4 col-form-label text-md-end">{{ __('Name') }}</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password-confirm" class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Register') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,28 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Verify Your Email Address') }}</div>
<div class="card-body">
@if (session('resent'))
<div class="alert alert-success" role="alert">
{{ __('A fresh verification link has been sent to your email address.') }}
</div>
@endif
{{ __('Before proceeding, please check your email for a verification link.') }}
{{ __('If you did not receive the email') }},
<form class="d-inline" method="POST" action="{{ route('verification.resend') }}">
@csrf
<button type="submit" class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another') }}</button>.
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,55 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto p-4">
@if (session('success'))
<div class="text-green-500 mb-4">{{ session('success') }}</div>
@endif
<div class="flex justify-between mb-4">
<form method="GET" action="{{ route('custom-table.index') }}" class="mb-4">
<input type="text" name="search" value="{{ request()->input('search') }}" placeholder="Search..." class="border p-2 rounded" onkeyup="this.form.submit()">
</form>
<button onclick="window.location.href='{{ route('custom-table.index') }}?page=1'" class="bg-blue-500 text-white p-2 rounded">Clear Filters</button>
</div>
<table class="w-full border-collapse border">
<thead>
<tr>
@foreach ($columns as $column)
<th class="border p-2">
{{ $column['title'] }}
<a href="{{ route('custom-table.index') }}?sort_field={{ $column['dataIndex'] }}&sort_order={{ request()->input('sort_field') === $column['dataIndex'] && request()->input('sort_order') === 'asc' ? 'desc' : 'asc' }}" class="text-blue-500 ml-2">
{{ request()->input('sort_field') === $column['dataIndex'] && request()->input('sort_order') === 'asc' ? '↓' : '↑' }}
</a>
</th>
@endforeach
<th class="border p-2">Action</th>
</tr>
</thead>
<tbody>
@foreach ($paginatedData as $item)
<tr>
@foreach ($columns as $column)
<td class="border p-2">{{ $item[$column['dataIndex']] }}</td>
@endforeach
<td class="border p-2">
@foreach ($actions as $action)
@if ($action['access'])
@if ($action['type'] === 'edit')
<a href="{{ $action['path'] }}/{{ $item['id'] }}" class="text-blue-500 mr-2">Edit</a>
@elseif ($action['type'] === 'delete')
<form action="{{ route('custom-table.destroy', $item['id']) }}" method="POST" class="inline">
@csrf
@method('DELETE')
<button type="submit" class="text-red-500" onclick="return confirm('Are you sure?')">Delete</button>
</form>
@endif
@endif
@endforeach
</td>
</tr>
@endforeach
</tbody>
</table>
{{ $paginatedData->links() }}
</div>
@endsection

View File

@ -0,0 +1,11 @@
@extends('layouts.app')
@section('content')
@include('dashboard.header')
@include('dashboard.content')
@endsection
@section('content-body')
<h2>Dashboard Content</h2>
<p>This is the main content area.</p>
@endsection

View File

@ -0,0 +1,15 @@
<div style="padding: 11px 24px 9px; font-size: 12px;">
<ol style="list-style: none; display: flex; align-items: center; padding: 0;">
<li style="margin-right: 8px;">
<a href="{{ route('my-profile') }}" style="color: #000;">
<span style="font-size: 10px; opacity: 0.6;"></span> Home
</a>
</li>
@foreach ($extraBreadcrumbItems as $item)
<li style="margin-right: 8px;">
<span style="font-size: 10px; opacity: 0.6;"></span>
<a href="{{ $item['url'] }}" style="color: #000;">{{ $item['name'] }}</a>
</li>
@endforeach
</ol>
</div>

View File

@ -0,0 +1,12 @@
@extends('layouts.app')
@section('content')
@if (request()->path() && request()->path() !== '/')
<div style="background: #fff; margin-bottom: 75px; position: fixed; margin-top: -110px; width: 100%;">
@include('dashboard.breadcrumbs')
</div>
@endif
<div style="margin: 0 16px; padding: 0; background: #fff;">
@yield('content-body')
</div>
@endsection

View File

@ -0,0 +1,17 @@
@extends('dashboard.route')
@section('route-content')
<h2>{{ $header }}</h2>
<ul style="@if($layout === 'horizontal') display: flex; flex-wrap: wrap; @else list-style-type: none; padding: 0; @endif">
@foreach ($data as $item)
<li style="margin: 10px; @if($layout === 'horizontal') width: 200px; @endif">
@if ($avatar)
<img src="{{ asset($item['avatar'] ?? '/images/default-avatar.png') }}" alt="Avatar" style="width: 40px; height: 40px; border-radius: 50%; margin-right: 10px;" />
@endif
<a href="{{ route('view', $item['id']) }}">{{ $item['first_name'] ?? $item['name'] }}</a>
<p>{{ strtolower($item['first_name'] ?? $item['name']) }}_{{ strtolower($item['last_name'] ?? '') }}@gmail.com</p>
</li>
@endforeach
</ul>
<div style="text-align: right;">{{ $footer }}</div>
@endsection

View File

@ -0,0 +1,3 @@
<div style="text-align: center; background: #fcfcfc; padding: 10px;">
<!-- YONDU-SDG ©2018 Created by Front End Team! -->
</div>

View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html>
<head>
<title>Dashboard Header</title>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<style>
.header {
background: #fff;
padding: 0;
height: 66px;
line-height: 69px;
border-bottom: 1px solid rgb(230, 236, 245);
}
.header-link {
padding: 0 10px;
display: inline-block;
vertical-align: top;
cursor: pointer;
transition: all 0.3s, padding 0s;
}
.header-link:hover {
background-color: #1890ff;
color: #fff;
}
.right-header { float: right; }
.icon-trigger {
font-size: 20px;
line-height: 69px;
cursor: pointer;
transition: all 0.3s, padding 0s;
padding: 0 24px;
}
.icon-trigger:hover { color: #1890ff; }
</style>
</head>
<body>
@if (session('success'))
<div style="color: green;">{{ session('success') }}</div>
@endif
<div class="header">
<span class="icon-trigger {{ $collapsed ? 'menu-unfold' : 'menu-fold' }}" onclick="toggleSidebar()">
&#9776; <!-- Hamburger icon -->
</span>
<div class="right-header">
@include('dashboard.header-dropdown')
</div>
</div>
<script>
function toggleSidebar() {
fetch('/dashboard/toggle', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Content-Type': 'application/json',
},
body: JSON.stringify({ collapsed: !{{ $collapsed }} })
})
.then(response => response.json())
.then(data => {
location.reload(); // Reload to reflect collapsed state
});
}
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
@extends('layouts.app')
@section('content')
<div style="height: 100%;">
@include('dashboard.sidebar')
<div style="background: #fcfcfc; padding-bottom: 10px;">
@include('dashboard.header')
<div style="overflow: auto; margin-top: 94px; padding-top: 16px; position: relative;">
@include('dashboard.content')
@include('dashboard.footer')
</div>
</div>
</div>
@endsection
@section('content-body')
@yield('content-body')
@endsection

View File

@ -0,0 +1,15 @@
@extends('layouts.app')
@section('content')
@include('dashboard.layout')
@if (Auth::check())
@include($view)
@else
<div>{{ session('error') ?? 'You must log in to enter this page.' }}</div>
<a href="{{ route('login') }}">Login</a>
@endif
@endsection
@section('content-body')
@yield('route-content')
@endsection

View File

@ -0,0 +1,45 @@
<div style="background: #fff; border: solid 1px #e6ecf5; z-index: 999; height: 100%;">
@if (!$collapsed)
<div style="height: 65px; padding: 12px 0; text-align: center; border-bottom: 1px solid #e6ecf5;">
<!-- Replace with actual logo image -->
<img src="{{ asset('images/logo.png') }}" style="height: 40px;" alt="Logo" />
</div>
@else
<div style="height: 32px; margin: 16px; background-repeat: no-repeat; background-size: contain; background-position: center; background-image: url('{{ asset('images/logo.png') }}');"></div>
@endif
<ul style="border-right: {{ $collapsed ? '0' : 'none' }}; height: 90vh; overflow: auto; padding-top: 17px; list-style: none; padding: 0;">
@foreach ($navigation as $item)
@if ($item['access'])
@if (isset($item['child']))
<li style="margin-bottom: 10px;">
<details>
<summary style="padding: 0 24px; cursor: pointer;">
<span style="margin-right: 8px;">{{ $item['icon'] ? '<i class="anticon anticon-' . $item['icon'] . '"></i>' : '' }}</span>
<span>{{ $item['label'] }}</span>
</summary>
<ul style="padding-left: 24px;">
@foreach ($item['child'] as $subItem)
@if ($subItem['access'])
<li style="margin-bottom: 5px;">
<a href="{{ $subItem['path'] }}" style="padding-left: 15px; display: block;">
{{ $subItem['icon'] ? '<i class="anticon anticon-' . $subItem['icon'] . '"></i>' : '' }}
{{ $subItem['label'] }}
</a>
</li>
@endif
@endforeach
</ul>
</details>
</li>
@else
<li style="margin-bottom: 10px;">
<a href="{{ $item['path'] }}" style="padding: 0 24px; display: block;">
{{ $item['icon'] ? '<i class="anticon anticon-' . $item['icon'] . '"></i>' : '' }}
<span>{{ $item['label'] }}</span>
</a>
</li>
@endif
@endif
@endforeach
</ul>
</div>

View File

@ -0,0 +1,5 @@
@extends('dashboard.route')
@section('route-content')
<h2>Users Page</h2>
@endsection

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>Export</title>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<style>
.export-button {
background: rgb(231, 70, 16);
border-color: rgb(231, 70, 16);
color: #fff;
padding: 0 15px;
cursor: pointer;
}
.export-button:disabled { opacity: 0.6; cursor: not-allowed; }
</style>
</head>
<body>
<form action="{{ route('dropdown.export') }}" method="POST">
@csrf
<button type="submit" class="export-button">Export CSV</button>
</form>
@if (session('success'))
<div style="color: green;">{{ session('success') }}</div>
@endif
</body>
</html>

View File

@ -0,0 +1,23 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Dashboard') }}</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
{{ __('You are logged in!') }}
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title inertia>@yield('title', 'Unioil CMS')</title>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
@vite('resources/css/app.css')
</head>
<body class="font-sans antialiased">
<div class="min-h-screen bg-gray-100">
@yield('content')
</div>
</body>
</html>

View File

@ -0,0 +1,9 @@
<div style="padding: 20px; display: flex; justify-content: center; margin-left: -144px;">
<div>
<span style="animation: spin 1s linear infinite;"></span> Loading Data Please wait...
</div>
</div>
<style>
@keyframes spin { to { transform: rotate(360deg); } }
span { font-size: 24px; }
</style>

View File

@ -0,0 +1,9 @@
@section('content')
<h2>Login</h2>
<form method="POST" action="{{ route('login') }}">
@csrf
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
@endsection

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>Login Layout</title>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<style>
.login-layout { height: 100%; display: flex; }
.sider { width: 50%; background: url('{{ asset('images/bg_cms.png') }}') center; background-size: cover; }
.content { padding: 16px; }
.footer { text-align: center; font-size: 12px; }
.footer div { margin: 25px auto; padding: 17px 0; width: 325px; border-top: 1px solid #e0e0e0; text-align: left; color: #8E8E93; }
</style>
</head>
<body>
<div class="login-layout">
<div class="sider"></div>
<div>
<div class="content">
@yield('content')
</div>
<div class="footer">
<div>
By logging in you agree to Unioil's Terms of Service, <br/>Privacy Policy and Content Policies.
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,15 @@
@extends('layouts.app')
@section('content')
@include('dashboard.layout')
@if (Auth::check())
@include($view)
@else
<div class="text-red-500">{{ session('error') ?? 'You must log in to enter this page.' }}</div>
<a href="{{ route('login') }}" class="text-blue-500">Login</a>
@endif
@endsection
@section('content-body')
@yield('route-content')
@endsection

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Modal Cancel</title>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
@if ($dirty)
<button onclick="document.getElementById('modal').style.display='block'">{{ $name }}</button>
<div id="modal" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; padding: 20px; border: 1px solid #ccc; z-index: 1000;">
<h3>{{ $title }}</h3>
<p>{{ $message }}</p>
<button onclick="document.getElementById('modal').style.display='none'">No</button>
<a href="{{ route('modal.cancel.confirm', ['path' => $path]) }}" style="margin-left: 10px;">
<button>Yes</button>
</a>
</div>
@else
<a href="{{ $path }}">
<button>{{ $name }}</button>
</a>
@endif
</body>
</html>

View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html>
<head>
<title>Notification Tables</title>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<link href="{{ asset('css/tables.css') }}" rel="stylesheet">
<style>
.table-container { margin: 24px; padding: 24px 0; }
.table-header { display: flex; justify-content: space-between; margin-bottom: 25px; }
.action-icons { color: rgb(231, 70, 16); cursor: pointer; padding: 5px 14px 5px 0; }
</style>
</head>
<body>
@if (session('success'))
<div style="color: green;">{{ session('success') }}</div>
@endif
<div class="table-container">
<div class="table-header">
<div>
<input type="text" name="search" value="{{ request()->input('search') }}" placeholder="Search" onkeyup="this.form.submit()" style="width: 300px;">
</div>
<div>
<button onclick="window.location.href='{{ route('notification-tables.index') }}?page=1&page_size=10'">Clear Filters</button>
<a href="{{ route('dropdown.export') }}">Export CSV</a>
</div>
</div>
<table border="1" cellpadding="5" cellspacing="0">
<thead>
<tr>
@foreach ($columns as $column)
<th>
{{ $column['title'] }}
<a href="{{ route('notification-tables.index') }}?_sort_by={{ $column['dataIndex'] }}&_sort_order={{ request()->input('_sort_by') === $column['dataIndex'] && request()->input('_sort_order') === 'asc' ? 'desc' : 'asc' }}">
{{ request()->input('_sort_by') === $column['dataIndex'] && request()->input('_sort_order') === 'asc' ? '↓' : '↑' }}
</a>
</th>
@endforeach
<th>Action</th>
</tr>
</thead>
<tbody>
@foreach ($paginatedData as $item)
<tr>
@foreach ($columns as $column)
<td>{{ $item[$column['dataIndex']] }}</td>
@endforeach
<td>
@foreach ($actions as $action)
@if ($action['key'] === 'edit')
<a href="{{ route('edit', $item['id']) }}" class="action-icons">Edit</a>
@elseif ($action['key'] === 'delete')
<form action="{{ route('notification-tables.destroy', $item['id']) }}" method="POST" style="display: inline;">
@csrf
@method('DELETE')
<button type="submit" class="action-icons" onclick="return confirm('Are you sure?')">Delete</button>
</form>
@endif
@endforeach
</td>
</tr>
@endforeach
</tbody>
</table>
<div style="margin-top: 20px; display: flex; justify-content: space-between;">
<div>
@if ($paginatedData->total() == 0)
<p>No records found.</p>
@endif
</div>
<div>
{{ $paginatedData->links() }}
<select name="page_size" onchange="this.form.submit()" style="margin-left: 10px;">
<option value="10" {{ $paginatedData->perPage() == 10 ? 'selected' : '' }}>10</option>
<option value="20" {{ $paginatedData->perPage() == 20 ? 'selected' : '' }}>20</option>
</select>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>404 - Page Not Found</title>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div align="center" style="margin-top: 10%;">
<h1 style="font-size: 150px; font-weight: bold; margin: 0;">404</h1>
<p style="font-size: 30px; font-weight: bold; margin: 0;">Page not found</p>
<p>Sorry, but the page you are looking for doesn't exist</p>
</div>
</body>
</html>

View File

@ -0,0 +1,29 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto p-4">
<h1 class="text-2xl font-bold mb-4">Users List</h1>
<form method="GET" action="{{ route('users.index') }}" class="mb-4">
<input type="text" name="search" value="{{ request()->input('search') }}" placeholder="Search..." class="border p-2 rounded" onkeyup="this.form.submit()">
</form>
<table class="w-full border-collapse border">
<thead>
<tr>
<th class="border p-2">
<a href="{{ route('users.index') }}?sort_field=name&sort_order={{ request()->input('sort_field') === 'name' && request()->input('sort_order') === 'asc' ? 'desc' : 'asc' }}" class="text-blue-500">
Name {{ request()->input('sort_field') === 'name' && request()->input('sort_order') === 'asc' ? '↓' : '↑' }}
</a>
</th>
</tr>
</thead>
<tbody>
@foreach ($paginatedData as $user)
<tr>
<td class="border p-2">{{ $user->name }}</td>
</tr>
@endforeach
</tbody>
</table>
{{ $paginatedData->links() }}
</div>
@endsection

View File

@ -1,7 +1,46 @@
<?php <?php
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;
use App\Http\Controllers\CustomTableController;
use App\Http\Controllers\Dashboard\RouteController;
use App\Http\Controllers\Dashboard\DataDisplayController;
use App\Http\Controllers\Dropdown\DropdownExportController;
use App\Http\Controllers\LoadingController;
use App\Http\Controllers\Modal\ModalCancelController;
use App\Http\Controllers\Login\LayoutController;
use App\Http\Controllers\NotificationTablesController;
use App\Http\Controllers\Page404Controller;
Route::get('/404', [Page404Controller::class, 'index'])->name('page404');
Route::get('/notification-tables', [NotificationTablesController::class, 'index'])->name('notification-tables.index');
Route::delete('/notification-tables/{id}', [NotificationTablesController::class, 'destroy'])->name('notification-tables.destroy');
Route::post('/notification-tables/batch-delete', [NotificationTablesController::class, 'batchDestroy'])->name('notification-tables.batch-delete');
Route::get('/login/layout', [LayoutController::class, 'index'])->name('login.layout');
Route::get('/modal/cancel', [ModalCancelController::class, 'index'])->name('modal.cancel');
Route::get('/modal/cancel/confirm', [ModalCancelController::class, 'confirm'])->name('modal.cancel.confirm');
Route::get('/loading', [LoadingController::class, 'index'])->name('loading');
Route::post('/dropdown/export', [DropdownExportController::class, 'export'])->name('dropdown.export');
Route::get('/dashboard/data-display', [DataDisplayController::class, 'index'])->name('dashboard.data-display');
Route::get('/view/{id}', function ($id) {
return "View page for ID: $id";
})->name('view');
Route::get('/dashboard/{component?}', [RouteController::class, 'index'])->name('dashboard.route')->where('component', '[a-zA-Z]+');
Route::get('/custom-table', [CustomTableController::class, 'index'])->name('custom-table.index');
Route::delete('/custom-table/{id}', [CustomTableController::class, 'destroy'])->name('custom-table.destroy');
Route::get('/users', [UserController::class, 'index']);
Route::get('/', function () { Route::get('/', function () {
return view('welcome'); return view('welcome');
}); });
Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

View File

@ -4,7 +4,10 @@ import laravel from 'laravel-vite-plugin';
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
laravel({ laravel({
input: ['resources/css/app.css', 'resources/js/app.js'], input: [
'resources/sass/app.scss',
'resources/js/app.js',
],
refresh: true, refresh: true,
}), }),
], ],