permit and admin refactoring
This commit is contained in:
parent
351c445442
commit
176cd6f6ff
105
php_code/app/Http/Controllers/Admin/UserController.php
Normal file
105
php_code/app/Http/Controllers/Admin/UserController.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User; // adjust if you have a custom model
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
return view('admin.users'); // Blade file below
|
||||
}
|
||||
|
||||
// Returns JSON list; supports category, search, status filters
|
||||
public function list(Request $request)
|
||||
{
|
||||
$q = $request->query('q');
|
||||
$category = $request->query('category'); // District/Regional/National or null
|
||||
$statuses = $request->query('status') ?: []; // array
|
||||
|
||||
$query = User::query();
|
||||
|
||||
if ($category) $query->where('category', $category);
|
||||
if ($q) {
|
||||
$query->where(function($sub) use ($q){
|
||||
$sub->where('name','like',"%{$q}%")
|
||||
->orWhere('email','like',"%{$q}%")
|
||||
->orWhere('id','like',"%{$q}%");
|
||||
});
|
||||
}
|
||||
if (!empty($statuses)) $query->whereIn('status', $statuses);
|
||||
|
||||
$users = $query->orderBy('last_activity_at','desc')->paginate(25);
|
||||
|
||||
return response()->json($users);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$v = Validator::make($request->all(), [
|
||||
'name'=>'required|string|max:255',
|
||||
'email'=>'required|email|unique:users,email',
|
||||
'category'=>['required', Rule::in(['District','Regional','National'])],
|
||||
]);
|
||||
if ($v->fails()) return response()->json(['errors'=>$v->errors()], 422);
|
||||
|
||||
$user = User::create($request->only(['name','email','category']) + ['status'=>'Active', 'password'=>bcrypt('temporary')]);
|
||||
return response()->json($user, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
$v = Validator::make($request->all(), [
|
||||
'name'=>'required|string|max:255',
|
||||
'email'=>['required','email', Rule::unique('users','email')->ignore($user->id)],
|
||||
'category'=>['required', Rule::in(['District','Regional','National'])],
|
||||
]);
|
||||
if ($v->fails()) return response()->json(['errors'=>$v->errors()], 422);
|
||||
|
||||
$user->update($request->only(['name','email','category']));
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
public function move(Request $request)
|
||||
{
|
||||
$v = Validator::make($request->all(), [
|
||||
'ids'=>'required|array',
|
||||
'target'=>'required|in:District,Regional,National',
|
||||
]);
|
||||
if ($v->fails()) return response()->json(['errors'=>$v->errors()], 422);
|
||||
|
||||
User::whereIn('id', $request->ids)->update(['category'=>$request->target]);
|
||||
return response()->json(['moved'=>count($request->ids)]);
|
||||
}
|
||||
|
||||
public function deactivate(Request $request)
|
||||
{
|
||||
$v = Validator::make($request->all(), [
|
||||
'ids'=>'required|array',
|
||||
]);
|
||||
if ($v->fails()) return response()->json(['errors'=>$v->errors()], 422);
|
||||
|
||||
User::whereIn('id', $request->ids)->update(['status'=>'Suspended']);
|
||||
return response()->json(['deactivated'=>count($request->ids)]);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
$user->delete();
|
||||
return response()->json(['deleted'=>true]);
|
||||
}
|
||||
}
|
||||
@ -9,10 +9,11 @@ use Illuminate\Support\Collection;
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
public function index(){
|
||||
public function index()
|
||||
{
|
||||
if (session('current_user.user_type') == 'district_user') {
|
||||
$users_url = "user_mgt/get_all_users_by_district.php";
|
||||
$data = ['district_id' => session('district_id'), 'api_token' => env('LUPMISAPIKEY')];
|
||||
$data = ['district_id' => session('current_user.district_id'), 'api_token' => env('LUPMISAPIKEY')];
|
||||
}
|
||||
elseif(session('current_user.user_type') == 'regional_user'){
|
||||
$users_url = "user_mgt/get_all_users_by_district.php";
|
||||
@ -20,13 +21,116 @@ class AdminController extends Controller
|
||||
}
|
||||
else{
|
||||
$users_url = "user_mgt/get_all_users.php";
|
||||
$data = ['api_token' => env('LUPMISAPIKEY')];
|
||||
$data = ['api_token' => env('LUPMISAPIKEY') ];
|
||||
}
|
||||
// dd($users_url);
|
||||
|
||||
$result = ApiCalls::CurlPost(json_encode($data), $users_url);
|
||||
|
||||
|
||||
$users_arr = json_decode($result, true);
|
||||
// dd($users_arr);
|
||||
|
||||
if ($users_arr == null || $users_arr['success'] == false) {
|
||||
return redirect()->back()->withErrors('Your request cannot be handled at this time. Try again later');
|
||||
}
|
||||
|
||||
|
||||
$regions_url = "user_mgt/get_all_regions.php";
|
||||
$data = ['api_token' => env('LUPMISAPIKEY')];
|
||||
$result = ApiCalls::CurlPost(json_encode($data), $regions_url);
|
||||
$regions_arr = json_decode($result, true);
|
||||
if ($regions_arr == null || $regions_arr['success'] == false) {
|
||||
return redirect()->back()->withErrors('Your request cannot be handled at this time. Try again later');
|
||||
}
|
||||
|
||||
|
||||
$users = collect($users_arr['data']);
|
||||
// dd($users->sortBy('ua_id'));
|
||||
// dump($users);
|
||||
if (request()->has('search') && request()->search != '') {
|
||||
$search = strtolower(request()->search);
|
||||
|
||||
$users = $users->filter(function($u) use ($search) {
|
||||
$name = strtolower($u['full_name'] ?? '');
|
||||
$email = strtolower($u['email'] ?? '');
|
||||
$username = strtolower($u['username'] ?? '');
|
||||
$phone = strtolower($u['phone'] ?? '');
|
||||
$ua_position = strtolower($u['ua_position'] ?? '');
|
||||
$allowed_apps = strtolower($u['allowed_apps'] ?? '');
|
||||
|
||||
return str_contains($name, $search) ||
|
||||
str_contains($email, $search) ||
|
||||
str_contains($username, $search) ||
|
||||
str_contains($phone, $search) ||
|
||||
str_contains($ua_position, $search) ||
|
||||
str_contains($allowed_apps, $search);
|
||||
});
|
||||
}
|
||||
$user_type_arr = [
|
||||
'district_user' => 'District User',
|
||||
'regional_user' => 'Regional User',
|
||||
'national_user' => 'National User'
|
||||
];
|
||||
$nationalUsers = $users->filter(function($u) {
|
||||
$type = trim(strtolower((string)($u['user_type'] ?? '')));
|
||||
$position = trim(strtolower((string)($u['ua_position'] ?? '')));
|
||||
|
||||
// Check if either the type OR the position contains 'national'
|
||||
return str_contains($type, 'national') || str_contains($position, 'national');
|
||||
});
|
||||
|
||||
$regionalUsers = $users->filter(function($u) {
|
||||
$type = trim(strtolower((string)($u['user_type'] ?? '')));
|
||||
$position = trim(strtolower((string)($u['ua_position'] ?? '')));
|
||||
|
||||
return str_contains($type, 'regional') || str_contains($position, 'regional');
|
||||
});
|
||||
|
||||
$districtUsers = $users->reject(function($u) {
|
||||
$type = trim(strtolower((string)($u['user_type'] ?? '')));
|
||||
$position = trim(strtolower((string)($u['ua_position'] ?? '')));
|
||||
|
||||
$isNational = str_contains($type, 'national') || str_contains($position, 'national');
|
||||
$isRegional = str_contains($type, 'regional') || str_contains($position, 'regional');
|
||||
return $isNational || $isRegional;
|
||||
});
|
||||
$paginatedDistrict = $this->paginateCollection($districtUsers, 10, 'district_page');
|
||||
$paginatedRegional = $this->paginateCollection($regionalUsers, 10, 'regional_page');
|
||||
$paginatedNational = $this->paginateCollection($nationalUsers, 10, 'national_page');
|
||||
// dd($paginatedRegional);
|
||||
// return view('admin.home_cats', [
|
||||
// 'page_title' => 'User Management',
|
||||
// 'nationalUsers' => $nationalUsers,
|
||||
// 'regionalUsers' => $regionalUsers,
|
||||
// 'districtUsers' => $districtUsers,
|
||||
// 'totalUsers' => $users->count()
|
||||
// ]);
|
||||
// dd($regionalUsers->count());
|
||||
return view('admin.home_cats', [
|
||||
'page_title' => 'User Admin',
|
||||
'regions_arr' => $regions_arr['data'],
|
||||
'user_type_arr' => $user_type_arr,
|
||||
'nationalUsers' => $paginatedNational,
|
||||
'regionalUsers' => $paginatedRegional,
|
||||
'districtUsers' => $paginatedDistrict,
|
||||
'totalUsers' => $users->count()
|
||||
]);
|
||||
|
||||
}
|
||||
public function indexSingle(){
|
||||
if (session('current_user.user_type') == 'district_user' || session('current_user.user_type') == 'District User' ) {
|
||||
$users_url = "user_mgt/get_all_users_by_district.php";
|
||||
$data = ['district_id' => session('current_user.district_id'), 'api_token' => env('LUPMISAPIKEY')];
|
||||
}
|
||||
elseif(session('current_user.user_type') == 'regional_user'){
|
||||
$users_url = "user_mgt/get_all_users_by_district.php";
|
||||
$data = ['region_id' => session('region_id'), 'api_token' => env('LUPMISAPIKEY')];
|
||||
}
|
||||
else{
|
||||
$users_url = "user_mgt/get_all_users.php";
|
||||
$data = ['api_token' => env('LUPMISAPIKEY') ];
|
||||
}
|
||||
$result = ApiCalls::CurlPost(json_encode($data), $users_url);
|
||||
$users_arr = json_decode($result, true);
|
||||
// dump($users_arr);
|
||||
if ($users_arr == null || $users_arr['success'] == false) {
|
||||
return redirect()->back()->withErrors('Your request cannot be handled at this time. Try again later');
|
||||
}
|
||||
@ -67,6 +171,7 @@ class AdminController extends Controller
|
||||
$currentPage,
|
||||
['path' => request()->url(), 'query' => request()->query()]
|
||||
);
|
||||
// dd($paginatedItems);
|
||||
$user_type_arr = [
|
||||
'district_user' => 'District User',
|
||||
'regional_user' => 'Regional User',
|
||||
@ -81,6 +186,7 @@ class AdminController extends Controller
|
||||
'items' => $paginatedItems
|
||||
];
|
||||
|
||||
// return view('admin.home_new', $data);
|
||||
return view('admin.paginated', $data);
|
||||
// return view('admin.home', $data);
|
||||
|
||||
@ -182,4 +288,30 @@ class AdminController extends Controller
|
||||
];
|
||||
return view('common.notready', $data);
|
||||
}
|
||||
/**
|
||||
* Manually paginate a collection.
|
||||
*/
|
||||
private function paginateCollection(Collection $items, $perPage = 10, $pageName = 'page')
|
||||
{
|
||||
// 1. Get the current page from the URL (e.g., ?district_page=2)
|
||||
$page = LengthAwarePaginator::resolveCurrentPage($pageName);
|
||||
|
||||
// 2. Slice the collection to get only the items for the current page
|
||||
$currentPageItems = $items->slice(($page - 1) * $perPage, $perPage)->values();
|
||||
|
||||
// 3. Create the paginator instance
|
||||
$paginator = new LengthAwarePaginator(
|
||||
$currentPageItems,
|
||||
$items->count(),
|
||||
$perPage,
|
||||
$page,
|
||||
[
|
||||
'path' => LengthAwarePaginator::resolveCurrentPath(),
|
||||
'pageName' => $pageName,
|
||||
]
|
||||
);
|
||||
|
||||
// Keep any other query parameters in the URL (like search keywords)
|
||||
return $paginator->withQueryString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,4 +9,34 @@ use Illuminate\Routing\Controller as BaseController;
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
|
||||
public function validateGhanaPhone($phone) {
|
||||
// Remove spaces, dashes, etc.
|
||||
$phone = preg_replace('/\D+/', '', $phone);
|
||||
|
||||
// If it starts with 0 (e.g. 0241234567), strip the leading 0
|
||||
if (strpos($phone, '0') === 0) {
|
||||
$phone = substr($phone, 1);
|
||||
}
|
||||
|
||||
// If it starts with Ghana country code without + (233...), add +
|
||||
if (strpos($phone, '233') === 0) {
|
||||
$phone = '+'.$phone;
|
||||
}
|
||||
|
||||
// If it doesn’t start with +233, prepend it
|
||||
if (strpos($phone, '+233') !== 0) {
|
||||
$phone = '+233'.$phone;
|
||||
}
|
||||
|
||||
// Now validate length: Ghana mobile numbers are 9 digits after +233
|
||||
$pattern = '/^\+233\d{9}$/';
|
||||
|
||||
if (preg_match($pattern, $phone)) {
|
||||
return $phone; // valid, normalized
|
||||
} else {
|
||||
return false; // invalid
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -83,6 +83,8 @@ class PermitsController extends Controller
|
||||
]);
|
||||
$result = ApiCalls::CurlPost($data, $url);
|
||||
$result = json_decode($result, true);
|
||||
|
||||
// dd($result);
|
||||
$allowed_users_to_comment = ['PPD Head', 'Works Department Head', 'luspa-it-head'];
|
||||
$data = [
|
||||
'page_title' => 'Permits Details',
|
||||
@ -93,6 +95,46 @@ class PermitsController extends Controller
|
||||
}
|
||||
public function statusUpdate($id){
|
||||
|
||||
}
|
||||
public function reports(){
|
||||
$data = [
|
||||
'page_title' => 'Permit Reports'
|
||||
];
|
||||
return view('common.notready', $data);
|
||||
}
|
||||
public function addComment(Request $request){
|
||||
|
||||
$url = "permit_comments/insert_application_comment.php";
|
||||
$data = json_encode([
|
||||
"application_code" => $request->application_code,
|
||||
"client_generated_id" => "CLIENT-001",
|
||||
"app_comments" => $request->comment_body,
|
||||
"created_by" => session('current_user.username'),
|
||||
"created_by_id" => session('current_user.user_id'),
|
||||
'api_token' => env('LUPMISAPIKEY')
|
||||
]);
|
||||
|
||||
|
||||
$result = ApiCalls::CurlPost($data, $url);
|
||||
$result = json_decode($result, true);
|
||||
// dd($result);
|
||||
return response()->json($result);
|
||||
}
|
||||
public function getComments(Request $request){
|
||||
|
||||
$url = "permit_comments/select_application_comments.php";
|
||||
$data = json_encode([
|
||||
"application_code" => $request->application_code,
|
||||
"client_generated_id" => "CLIENT-001",
|
||||
"app_comments" => $request->comment_body,
|
||||
"created_by" => session('current_user.username'),
|
||||
"created_by_id" => session('current_user.user_id'),
|
||||
'api_token' => env('LUPMISAPIKEY')
|
||||
]);
|
||||
$result = ApiCalls::CurlPost($data, $url);
|
||||
$result = json_decode($result, true);
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
public function viewPdf($filename){
|
||||
$path = storage_path('app/public/site_plans/' . $filename);
|
||||
|
||||
@ -29,11 +29,12 @@ class UserloginController extends Controller
|
||||
$data = ['user' => $request->username, 'pass' => $request->password, 'api_token' => env('LUPMISAPIKEY')];
|
||||
$check_user = ApiCalls::CurlPost(json_encode($data), $check_user_url);
|
||||
|
||||
|
||||
// dd($check_user);
|
||||
if($check_user == false){
|
||||
return redirect("user-login")->withErrors(array("System not available at the moment. Try again later!"))->withInput();
|
||||
}
|
||||
$result = json_decode($check_user, true);
|
||||
|
||||
if($result['success'] == false){
|
||||
return redirect("user-login")->withErrors(array("Incorrect Email/Password. Check and try again!"))->withInput();
|
||||
}
|
||||
@ -41,6 +42,7 @@ class UserloginController extends Controller
|
||||
##return redirect("user-login")->withErrors(array("Your Account has been disabled. Contact your administrator!"))->withInput();
|
||||
}
|
||||
$logged_in = $result['data'];
|
||||
// dd($logged_in);
|
||||
$plainToken = Str::random(60);
|
||||
|
||||
// $hashedToken = hash('sha256', $plainToken);
|
||||
@ -49,7 +51,6 @@ class UserloginController extends Controller
|
||||
'token' => hash('sha256', $plainToken),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$request->session()->regenerate(true);
|
||||
$request->session()->put('current_user.ua_id', $logged_in['ua_id']);
|
||||
$request->session()->put('current_user.user_id', $logged_in['user_id']);
|
||||
@ -64,6 +65,7 @@ class UserloginController extends Controller
|
||||
$request->session()->put('current_user.region_id', $logged_in['region_id']);
|
||||
$request->session()->put('current_user.is_password_changed', $logged_in['is_password_changed']);
|
||||
$request->session()->put('current_user.district_id', $logged_in['district_id']);
|
||||
$request->session()->put('current_user.district_name', $logged_in['vr_district_name']);
|
||||
// $request->session()->put('current_user.hashedToken', $hashedToken);
|
||||
$request->session()->put('current_user.plainToken', $plainToken);
|
||||
|
||||
|
||||
@ -9,6 +9,8 @@ use Illuminate\Support\Facades\Mail;
|
||||
use App\Mail\UserAccountsMail;
|
||||
use App\Mail\PasswordResetMail;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Rules\GhanaPhoneRule;
|
||||
|
||||
|
||||
class UsersController extends Controller
|
||||
@ -26,8 +28,6 @@ class UsersController extends Controller
|
||||
return view('user-auth.reset', $data);
|
||||
}
|
||||
public function check_reset_email(Request $request){
|
||||
// code...
|
||||
// dd('foo bar');
|
||||
$url = "user_mgt/get_user_by_user_id.php";
|
||||
$user_id = "34ba702b-18f8-4d85-948d-8c55e8500f32";
|
||||
$data = json_encode([
|
||||
@ -139,12 +139,52 @@ class UsersController extends Controller
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function store(Request $request){
|
||||
$url = "user_mgt/add_usr_user.php";
|
||||
|
||||
// return ['success' => true];
|
||||
// $password = ApiCalls::generatePassword(10);
|
||||
$password = $randomString = Str::random(10);
|
||||
$this->validate($request, [
|
||||
'full_name' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255|unique:users,username',
|
||||
'ua_position' => 'required|string',
|
||||
'allowed_apps'=> 'required|array',
|
||||
'user_status' => 'required|string',
|
||||
'gender' => 'required|in:male,female',
|
||||
'districtid' => [
|
||||
Rule::requiredIf(function () use ($request) {
|
||||
return in_array($request->user_type, ['district_user', 'regional_luspa']);
|
||||
}),
|
||||
'integer'
|
||||
],
|
||||
'region_id' => [
|
||||
Rule::requiredIf(function () use ($request) {
|
||||
return in_array($request->user_type, ['regional_luspa']);
|
||||
}),
|
||||
'integer'
|
||||
],
|
||||
'user_type' => 'required|string',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'phone' => ['required', new GhanaPhoneRule],
|
||||
], [
|
||||
'full_name.required' => 'Please provide the full name.',
|
||||
'username.required' => 'A username is required.',
|
||||
'username.unique' => 'This username is already taken.',
|
||||
'ua_position.required' => 'Position is mandatory.',
|
||||
'allowed_apps.required'=> 'Select at least one application.',
|
||||
'gender.required' => 'Gender is required.',
|
||||
'district_id.required' => 'District must be selected.',
|
||||
'region_id.required' => 'Region is required',
|
||||
'user_type.required' => 'User type is required.',
|
||||
'email.required' => 'Email address is required.',
|
||||
'email.email' => 'Please enter a valid email address.',
|
||||
'user_status.required' => 'Please select user status.',
|
||||
'email.unique' => 'This email is already registered.',
|
||||
'phone.required' => 'Phone number is required.',
|
||||
]);
|
||||
|
||||
// Generate random password
|
||||
$password = Str::random(10);
|
||||
$data = json_encode([
|
||||
'full_name' => $request['full_name'],
|
||||
'username' => $request['username'],
|
||||
@ -154,46 +194,90 @@ class UsersController extends Controller
|
||||
'allowed_apps' => implode(", ", $request['allowed_apps']),
|
||||
'is_password_changed' => false,
|
||||
'password_hint' => 'none',
|
||||
'phone' => $request['phone'],
|
||||
'phone' => str_replace('+', '',$request['phone']),
|
||||
'gender' => $request['gender'],
|
||||
'user_type' => $request['user_type'],
|
||||
'pass' => $password,
|
||||
'is_disabled' => false,
|
||||
'region_id' => $request['region_id'],
|
||||
'district_id' => $request['districtid'],
|
||||
'api_token' => env('LUPMISAPIKEY'), //'1c46538c712e9b5b' // make the API token a constant
|
||||
'api_token' => env('LUPMISAPIKEY'),
|
||||
]);
|
||||
// dd($data);
|
||||
$result = ApiCalls::CurlPost($data, $url);
|
||||
$result = json_decode($result, true);
|
||||
|
||||
if ($result['success'] == false) {
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
\Log::info("Your Password is $password");
|
||||
|
||||
$recipientEmail = 'recipient@example.com';
|
||||
Mail::to($recipientEmail)->send(new UserAccountsMail($password, $request->username));
|
||||
//dd('Email sent!');
|
||||
$sms_message = "Hello $request->full_name your LUPMIS account has been successfully created\n";
|
||||
$sms_message .= "Username : . $request->username \n";
|
||||
$sms_message .= "Password : $password\n";
|
||||
$sms_message .= 'Login URL : https://lupmis4luspa.org';
|
||||
Mail::to('recipient@example.com')->send(new UserAccountsMail($password, $request->username));
|
||||
|
||||
$sms_message = "Hello {$request->full_name}, your LUPMIS account has been successfully created\n";
|
||||
$sms_message .= "Username: {$request->username}\n";
|
||||
$sms_message .= "Password: $password\n";
|
||||
$sms_message .= "Login URL: https://lupmis4luspa.org";
|
||||
|
||||
$sms_data = [
|
||||
'recipient' => $request['phone'],
|
||||
'message' => $sms_message
|
||||
];
|
||||
|
||||
#$sms_result = SmsLibrary::SendMnotitySms($sms_data); // will send this the API
|
||||
|
||||
\Log::info("SMS Body : $sms_message");
|
||||
#\Log::info("SMS API Response : $sms_result");
|
||||
|
||||
if (request()->expectsJson()) {
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json($result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function update(Request $request){
|
||||
$url = "user_mgt/update_usr_user.php";
|
||||
|
||||
// return ['success' => true];
|
||||
// dd($request->all());
|
||||
$this->validate($request, [
|
||||
'full_name' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255|unique:users,username',
|
||||
'ua_position' => 'required|string',
|
||||
'allowed_apps'=> 'required|array',
|
||||
'user_status' => 'required|string',
|
||||
'gender' => 'required|in:male,female',
|
||||
// 'districtid' => 'required|integer',
|
||||
'districtid' => [
|
||||
Rule::requiredIf(function () use ($request) {
|
||||
return in_array($request->user_type, ['district_user']);
|
||||
}),
|
||||
'integer'
|
||||
],
|
||||
'region_id' => [
|
||||
Rule::requiredIf(function () use ($request) {
|
||||
return in_array($request->user_type, ['regional_luspa', 'national_luspa']);
|
||||
}),
|
||||
'integer'
|
||||
],
|
||||
'user_type' => 'required|string',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'phone' => ['required', new GhanaPhoneRule],
|
||||
], [
|
||||
'full_name.required' => 'Please provide the full name.',
|
||||
'username.required' => 'A username is required.',
|
||||
'username.unique' => 'This username is already taken.',
|
||||
'ua_position.required' => 'Position is mandatory.',
|
||||
'allowed_apps.required'=> 'Select at least one application.',
|
||||
'gender.required' => 'Gender is required.',
|
||||
'district_id.required' => 'District must be selected.',
|
||||
'region_id.required' => 'Region is required',
|
||||
'user_type.required' => 'User type is required.',
|
||||
'user_status.required' => 'Please select user status.',
|
||||
'email.required' => 'Email address is required.',
|
||||
'email.email' => 'Please enter a valid email address.',
|
||||
'email.unique' => 'This email is already registered.',
|
||||
'phone.required' => 'Phone number is required.',
|
||||
]);
|
||||
$is_disabled = ($request->user_status == 'active') ? 'false' : 'true';
|
||||
$user_data = [
|
||||
'full_name' => $request['full_name'],
|
||||
'username' => $request['username'],
|
||||
@ -202,11 +286,15 @@ class UsersController extends Controller
|
||||
'email' => $request['email'],
|
||||
'title' => $request['title'],
|
||||
'allowed_apps' => implode(", ", $request['allowed_apps']),
|
||||
'phone' => $request['phone'],
|
||||
'phone' => str_replace('+', '',$request['phone']),
|
||||
'gender' => $request['gender'],
|
||||
'user_type' => 'District User',
|
||||
'user_type' => $request['user_type'],
|
||||
'api_token' => env('LUPMISAPIKEY'),
|
||||
'is_disabled' => $is_disabled,
|
||||
// 'region_id' => $request['region_id'],
|
||||
'district_id' => $request['districtid'],
|
||||
];
|
||||
|
||||
if ($request->has('expire_password')) {
|
||||
$user_data['is_password_changed'] = 'NO';
|
||||
}
|
||||
|
||||
10
php_code/app/Models/PermitApplicationComments.php
Normal file
10
php_code/app/Models/PermitApplicationComments.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PermitApplicationComments extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
@ -16,6 +16,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
if (config('app.env') === 'production') {
|
||||
URL::forceScheme('https');
|
||||
}
|
||||
Paginator::useBootstrap();
|
||||
// Paginator::useBootstrap();
|
||||
Paginator::useBootstrapFive();
|
||||
}
|
||||
}
|
||||
|
||||
40
php_code/app/Rules/GhanaPhoneRule.php
Normal file
40
php_code/app/Rules/GhanaPhoneRule.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
// namespace App\Rules;
|
||||
|
||||
// use Closure;
|
||||
// use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
// class GhanaPhoneRule implements ValidationRule
|
||||
// {
|
||||
// /**
|
||||
// * Run the validation rule.
|
||||
// *
|
||||
// * @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
// */
|
||||
// public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
// {
|
||||
// //
|
||||
// }
|
||||
// }
|
||||
namespace App\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
class GhanaPhoneRule implements Rule
|
||||
{
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
// Ghana phone numbers: start with 0 or +233, followed by 9 digits
|
||||
// return preg_match('/^(?:\+233|0)[0-9]{9}$/', $value);
|
||||
return preg_match('/^(?:\+233|233|0)[0-9]{9}$/', $value);
|
||||
#/^(?:\+233|233|0)[0-9]{9}$/
|
||||
|
||||
}
|
||||
|
||||
public function message()
|
||||
{
|
||||
return 'Please enter a valid Ghanaian phone number (e.g., 024XXXXXXX or +23324XXXXXXX).';
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('permit_application_comments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->nullOnDelete();
|
||||
$table->string('application_code')->index();
|
||||
$table->string('status');
|
||||
$table->text('comment_body')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('permit_application_comments');
|
||||
}
|
||||
};
|
||||
@ -2,6 +2,9 @@
|
||||
- assiamah/win
|
||||
- kwesilupmis/7aa0478bce
|
||||
|
||||
Username: saxiquzipa/lwCDfAMaBy </li>
|
||||
|
||||
|
||||
composer create-project laravel/laravel:^11.0 my-laravel-11-project
|
||||
chmod -R 0777 storage/
|
||||
chmod -R 0777 bootstrap/
|
||||
@ -26,6 +29,11 @@ add measurements to the backend for works department
|
||||
216.55.137.19
|
||||
1c46538c712e9b5b
|
||||
|
||||
206.225.87.174
|
||||
|
||||
|
||||
216.55.185.131
|
||||
|
||||
ua_id": 10,
|
||||
"user_id": "34ba702b-18f8-4d85-948d-8c55e8500f32",
|
||||
"username": "hyhix",
|
||||
@ -46,4 +54,17 @@ ua_id": 10,
|
||||
"allowed_apps": "permit-tools",
|
||||
"region_id": 3,
|
||||
"district_id": 180,
|
||||
"user_type": "District User"
|
||||
"user_type": "District User"
|
||||
|
||||
|
||||
|
||||
|
||||
# 1. Strip the restrictive header coming from the backend server
|
||||
proxy_hide_header X-Frame-Options;
|
||||
|
||||
# 2. Inject the correct Content-Security-Policy
|
||||
# IMPORTANT: Replace the URL below with your actual LIVE Laravel domain (No trailing slash)
|
||||
add_header Content-Security-Policy "frame-ancestors 'self' https://lupmis4luspa.org" always;
|
||||
add_header Content-Security-Policy "frame-ancestors 'self' https://lupmis.housebanson.net" always;
|
||||
|
||||
|
||||
|
||||
55
php_code/live.env
Normal file
55
php_code/live.env
Normal file
@ -0,0 +1,55 @@
|
||||
cat .env
|
||||
APP_NAME=lupmisBackend
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:pmEAeuW8clKrfKKjcMWylo68exoDO/Xr2hUhXvB7dS0=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http:https://lupmis4luspa.org
|
||||
LOG_CHANNEL=daily
|
||||
|
||||
LUPMISAPIKEY=1c46538c712e9b5b
|
||||
MNOTOFYKEY=hFsiPMAPS3sIdwYSIthRO5JtS
|
||||
#DB_CONNECTION=mysql
|
||||
#DB_HOST=host.docker.internal
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
#DB_DATABASE=database/lupmis.sqlite
|
||||
DB_DATABASE=/var/www/html/database/lupmis.sqlite
|
||||
|
||||
DB_PORT=3306
|
||||
#DB_DATABASE=lupmis.sqlite
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=p@ssw0rd
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_DOMAIN=.lupmis4luspa.org
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS=lupmis@luspa.gov.gh
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
108
php_code/public/assets/css/permit_show.css
Normal file
108
php_code/public/assets/css/permit_show.css
Normal file
@ -0,0 +1,108 @@
|
||||
@media (max-width: 768px) {
|
||||
#permit-map { height: 350px; }
|
||||
}
|
||||
/* Container for the entire timeline */
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding-left: 2rem; /* Creates space for the vertical line and icons */
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* The Vertical Line */
|
||||
.timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0.85rem; /* Centers the line behind the 32px icons */
|
||||
width: 2px;
|
||||
background-color: #dee2e6; /* Standard Bootstrap gray border */
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Wrapper for each timeline event */
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* The Circular Icons / Dots */
|
||||
.timeline-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -2rem; /* Pulls the icon left to sit precisely on the vertical line */
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1rem;
|
||||
z-index: 1; /* Ensures icons sit above the vertical line */
|
||||
}
|
||||
|
||||
/* The Event Content Box */
|
||||
.timeline-content {
|
||||
margin-left: 0.5rem; /* Pushes the card slightly away from the icon */
|
||||
background-color: #fff;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.timeline-content:hover {
|
||||
box-shadow: 0 .5rem 1rem rgba(0,0,0,.08)!important;
|
||||
}
|
||||
|
||||
/* Vertical Line */
|
||||
.planning-stepper {
|
||||
position: relative;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
.planning-stepper::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
bottom: 0;
|
||||
left: 4px; /* Centers the line exactly under the dots */
|
||||
width: 2px;
|
||||
background-color: #e9ecef; /* Bootstrap light gray */
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Individual Steps */
|
||||
.stepper-item {
|
||||
position: relative;
|
||||
padding-bottom: 1.75rem;
|
||||
}
|
||||
.stepper-item:last-child {
|
||||
padding-bottom: 0; /* Removes extra space at the bottom of the list */
|
||||
}
|
||||
|
||||
/* The Dots */
|
||||
.stepper-dot {
|
||||
position: absolute;
|
||||
left: -1.25rem;
|
||||
top: 0.25rem;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
z-index: 1;
|
||||
background-color: #dee2e6; /* Default pending gray */
|
||||
}
|
||||
|
||||
/* Dot States */
|
||||
.stepper-dot.completed {
|
||||
background-color: #20c997; /* Teal/Success green */
|
||||
}
|
||||
.stepper-dot.active {
|
||||
background-color: #fd7e14; /* Orange */
|
||||
box-shadow: 0 0 0 5px rgba(253, 126, 20, 0.15); /* Soft orange halo */
|
||||
}
|
||||
.stepper-dot.pending {
|
||||
background-color: #e2e8f0; /* Muted gray */
|
||||
}
|
||||
|
||||
/* Faded text state for pending items */
|
||||
.stepper-item.pending .step-title,
|
||||
.stepper-item.pending .step-desc {
|
||||
opacity: 0.6;
|
||||
}
|
||||
477
php_code/public/assets/js/add_edit_user copy 2.js
Normal file
477
php_code/public/assets/js/add_edit_user copy 2.js
Normal file
@ -0,0 +1,477 @@
|
||||
$(document).ready(function () {
|
||||
|
||||
let iti;
|
||||
|
||||
$('#editUserModal').on('shown.bs.modal', function () {
|
||||
const phoneInput = document.querySelector("#editPhone");
|
||||
if (!iti) {
|
||||
iti = window.intlTelInput(phoneInput, {
|
||||
initialCountry: "gh",
|
||||
onlyCountries: ["gh"],
|
||||
nationalMode: false,
|
||||
autoPlaceholder: "polite",
|
||||
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"
|
||||
});
|
||||
|
||||
phoneInput.addEventListener("blur", function () {
|
||||
let rawNumber = phoneInput.value.trim();
|
||||
console.log(rawNumber);
|
||||
if (rawNumber.startsWith("0")) {
|
||||
rawNumber = rawNumber.substring(1);
|
||||
phoneInput.value = "+233" + rawNumber;
|
||||
}
|
||||
if (iti.isValidNumber()) {
|
||||
phoneInput.value = iti.getNumber(); // clean +233XXXXXXXXX
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#addUserModal').on('shown.bs.modal', function () {
|
||||
const phoneInput = document.querySelector("#addUserphone");
|
||||
if (!iti) {
|
||||
iti = window.intlTelInput(phoneInput, {
|
||||
initialCountry: "gh",
|
||||
onlyCountries: ["gh"],
|
||||
nationalMode: false,
|
||||
autoPlaceholder: "polite",
|
||||
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"
|
||||
});
|
||||
|
||||
phoneInput.addEventListener("blur", function () {
|
||||
let rawNumber = phoneInput.value.trim();
|
||||
|
||||
if (rawNumber.startsWith("0")) {
|
||||
rawNumber = rawNumber.substring(1);
|
||||
phoneInput.value = "+233" + rawNumber;
|
||||
}
|
||||
if (iti.isValidNumber()) {
|
||||
phoneInput.value = iti.getNumber();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let lastTrigger = null;
|
||||
// When edit modal closes, return focus to trigger
|
||||
$('#editUserModal').on('hidden.bs.modal', function(){
|
||||
if (lastTrigger && document.contains(lastTrigger)) lastTrigger.focus();
|
||||
lastTrigger = null;
|
||||
});
|
||||
|
||||
// Submit edit (updates row inline)
|
||||
$('#editFormXX').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const id = $('#editId').val();
|
||||
const name = $('#editName').val();
|
||||
const email = $('#editEmail').val();
|
||||
const category = $('#editCategory').val();
|
||||
|
||||
const row = $(`.user-row[data-id="${id}"]`);
|
||||
row.data('name', name).data('email', email).data('category', category);
|
||||
row.find('.fw-semibold').text(name);
|
||||
row.find('td').eq(2).text(email);
|
||||
row.find('.category-badge').removeClass('badge-district badge-regional badge-national');
|
||||
if (category === 'District') row.find('.category-badge').addClass('badge-district').text('District');
|
||||
if (category === 'Regional') row.find('.category-badge').addClass('badge-regional').text('Regional');
|
||||
if (category === 'National') row.find('.category-badge').addClass('badge-national').text('National');
|
||||
const targetBody = category === 'District' ? '#districtBody' : (category === 'Regional' ? '#regionalBody' : '#nationalBody');
|
||||
if (!row.parent().is(targetBody)) {
|
||||
row.appendTo($(targetBody));
|
||||
}
|
||||
|
||||
updateCounts();
|
||||
$('#editModal').modal('hide');
|
||||
});
|
||||
|
||||
|
||||
var selectedValue = $("input[name='user_type']:checked").val();
|
||||
const $dropdown = $("#uaPostionAdd");
|
||||
|
||||
const optionsMap = {
|
||||
district_user: [
|
||||
"PPD Head", "Works Head", "MIS",
|
||||
"PPD Staff", "Works Staff",
|
||||
"Devt Planning Officer",
|
||||
"MCE", "DCE",
|
||||
"Urban Roads Department Head",
|
||||
"District Fire Officer",
|
||||
"District Disaster Prevention Department",
|
||||
"Head of District Health Department",
|
||||
"Representative of the Lands Commission",
|
||||
"Representative of the Environmental Protection Agency",
|
||||
"Rep from Traditional Council",
|
||||
"Chairman of the Works Sub Committee",
|
||||
"Chairman of Development Sub Planning Committee",
|
||||
"Nominated Elected Assembly Memebers"
|
||||
],
|
||||
national_luspa: ["Director", "IT Head", "Staff"],
|
||||
regional_luspa: ["Director", "Staff"]
|
||||
|
||||
};
|
||||
$dropdown.empty();
|
||||
$dropdown.append('<option value="">-- Select an option --</option>');
|
||||
|
||||
if (optionsMap['district_user']) {
|
||||
$.each(optionsMap['district_user'], function(index, value) {
|
||||
$dropdown.append($("<option></option>").attr("value", value.toLowerCase()).text(value));
|
||||
});
|
||||
}
|
||||
|
||||
$("input[name='user_type']").change(function() {
|
||||
var userValue = $(this).val();
|
||||
if (userValue == 'district_user') {
|
||||
$('#regionID').prop('disabled', false);
|
||||
$('#districtID').prop('disabled', false);
|
||||
|
||||
$('#editRegionID').prop('disabled', false);
|
||||
$('#editdistrictID').prop('disabled', false);
|
||||
|
||||
}
|
||||
if (userValue == 'national_luspa') {
|
||||
$('#regionID').prop('disabled', true);
|
||||
$('#districtID').prop('disabled', true);
|
||||
|
||||
$('#editRegionID').prop('disabled', true);
|
||||
$('#editdistrictID').prop('disabled', true);
|
||||
|
||||
}
|
||||
if (userValue == 'regional_luspa') {
|
||||
$('#districtID').prop('disabled', true);
|
||||
$('#regionID').prop('disabled', false);
|
||||
|
||||
$('#editdistrictID').prop('disabled', true);
|
||||
$('#editRegionID').prop('disabled', false);
|
||||
|
||||
}
|
||||
|
||||
$dropdown.empty();
|
||||
$dropdown.append('<option value="">-- Select an option --</option>');
|
||||
|
||||
if (optionsMap[userValue]) {
|
||||
$.each(optionsMap[userValue], function(index, value) {
|
||||
$dropdown.append($("<option></option>")
|
||||
.attr("value", value.toLowerCase())
|
||||
.text(value));
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
$('#editAllowedApps').select2({
|
||||
dropdownParent: $('#editUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('#allowedApps').select2({
|
||||
dropdownParent: $('#addUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('#inputPermissions').select2({
|
||||
dropdownParent: $('#editUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('.editUserBtn').click(function(evnt){
|
||||
evnt.preventDefault();
|
||||
// var selectedUserId = $(this).siblings('.userIdinput').val();
|
||||
const row = $(this).closest('.user-row');
|
||||
var selectedUserId = row.data('id');
|
||||
console.log('selectedUserId');
|
||||
lastTrigger = this;
|
||||
|
||||
// $('#editId').val(row.data('id'));
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('user_id', selectedUserId);
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/users/edit/' + selectedUserId,
|
||||
type: 'GET',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'crossDomain': false
|
||||
},
|
||||
beforeSend: function() {
|
||||
$('#editSuccessArea').text("");
|
||||
$('#editErrorArea').text("Please wait ... loading user details!!");
|
||||
},
|
||||
success: function(data) {
|
||||
$('#editErrorArea').text('');
|
||||
var jason = data.data;
|
||||
if(data.success == true){
|
||||
var allowedAppsArray = [];
|
||||
if (jason['allowed_apps']) {
|
||||
allowedAppsArray = jason['allowed_apps'].split(",");
|
||||
}
|
||||
$('#editFullName').val(jason['full_name']);
|
||||
$('#editEmail').val(jason['email']);
|
||||
$('#editUsername').val(jason['username']);
|
||||
$('#editGender').val(jason['gender']);
|
||||
$('#editTitle').val(jason['title']);
|
||||
$('#editUaPostion').val(jason['ua_position']);
|
||||
$('#editPhone').val(jason['phone']);
|
||||
$('#editAllowedApps').val(allowedAppsArray).trigger('change');
|
||||
$('#editRegionID').val(jason['region_id']);
|
||||
$('#editDistrictId').val(jason['district_id']);
|
||||
$('#editUaPostion').val(jason['ua_position']);
|
||||
$('#editGender').val(jason['gender']);
|
||||
$("input[name='user_id']").val(jason.user_id);
|
||||
|
||||
}
|
||||
//$('#editUserModal').modal('show');
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
$('#errorArea').text(error);
|
||||
$('#errorArea').text(error);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
$("#editUserForm").submit(function(evt){
|
||||
evt.preventDefault();
|
||||
const $successArea = $("#editSuccessArea");
|
||||
const $errorArea = $("#editErrorArea");
|
||||
$('#editSuccessArea').addClass('d-none');
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
|
||||
const formData = new FormData($(this)[0]);
|
||||
// formData = new FormData(this);
|
||||
let errors = [];
|
||||
if (iti) {
|
||||
let formattedPhone = iti.getNumber();
|
||||
if (formattedPhone.startsWith("+2330")) {
|
||||
formattedPhone = "+233" + formattedPhone.substring(5);
|
||||
}
|
||||
formattedPhone = formattedPhone.trim();
|
||||
if (!iti.isValidNumber()) {
|
||||
errors.push("001 | Please enter a valid Ghana phone number.");
|
||||
} else {
|
||||
formData.set("phone", formattedPhone);
|
||||
}
|
||||
}
|
||||
const username = $("#editUsername").val().trim();
|
||||
const email = $("#editEmail").val().trim();
|
||||
const fullName = $("#editFullName").val().trim();
|
||||
|
||||
if (!fullName) errors.push("Full name is required.");
|
||||
if (!username) errors.push("Username is required.");
|
||||
if (!email) {
|
||||
errors.push("Email is required.");
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
errors.push("Invalid email format.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
$('#editSuccessArea').addClass("d-none").text("");
|
||||
$errorArea.removeClass("d-none").html(errors.join("<br>"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/userupdate',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
dataType: 'json',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function() {
|
||||
// $('#updateBtn').addClass('d-none');
|
||||
// $('#uodateProgressBtn').removeClass('d-none');
|
||||
// $('#updateResultsDiv').removeClass('d-none');
|
||||
// $('#updateResultsParagraph').text("Processing Please wait ...");
|
||||
},
|
||||
success: function(data) {
|
||||
console.log(data);
|
||||
console.log(data['success']);
|
||||
if (data['success'] == true) {
|
||||
$('#editSuccessArea').removeClass('d-none');
|
||||
$('#editErrorArea').addClass('d-none');
|
||||
$('#editSuccessArea').text("");
|
||||
$('#editSuccessArea').text("User successfully details updated!");
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: 'User successfully details updated!!',
|
||||
});
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
}
|
||||
else{
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
$('#editErrorArea').text(data['msg']);
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: data['msg'],
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
$('#editErrorArea').text(error);
|
||||
|
||||
if (xhr.status === 422) {
|
||||
// Laravel validation error
|
||||
let errors = xhr.responseJSON.errors;
|
||||
let messages = [];
|
||||
|
||||
$.each(errors, function (field, msgs) {
|
||||
messages.push(msgs.join("<br>"));
|
||||
});
|
||||
|
||||
$('#editErrorArea').removeClass("d-none").html(messages.join("<br>"));
|
||||
|
||||
$.alert({
|
||||
title: "Validation Error",
|
||||
content: messages.join("<br>"),
|
||||
});
|
||||
} else {
|
||||
$('#editErrorArea').removeClass('d-none').text("Request failed : " + error);
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: "Request failed: " + error,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#regionID').change(function(){
|
||||
console.log('change is coming');
|
||||
var options = $('#districtID');
|
||||
var region_id = $('#regionID').val();
|
||||
$.ajax({
|
||||
url: base_url + '/admin/districts/' + region_id,
|
||||
type: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'crossDomain': false
|
||||
},
|
||||
success: function(data) {
|
||||
$.each(data['districts'], function(id, row) {
|
||||
$('#districtID').append($("<option />").val(row.districtid).text(row.district_name));
|
||||
});
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$("#newUserForm").on("submit", function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
const $successArea = $("#newUserSuccessArea");
|
||||
const $errorArea = $("#newUserErrorArea");
|
||||
const formData = new FormData(this);
|
||||
|
||||
let errors = [];
|
||||
if (iti) {
|
||||
let formattedPhone = iti.getNumber(); // +233XXXXXXXXX
|
||||
if (formattedPhone.startsWith("+2330")) {
|
||||
formattedPhone = "233" + formattedPhone.substring(5);
|
||||
}
|
||||
|
||||
if (!iti.isValidNumber()) {
|
||||
errors.push("Please enter a valid Ghana phone number.");
|
||||
} else {
|
||||
formData.set("phone", formattedPhone);
|
||||
}
|
||||
}
|
||||
const username = $("#addUserUsername").val().trim();
|
||||
const email = $("#addUserEmail").val().trim();
|
||||
const fullName = $("#addUserFullname").val().trim();
|
||||
|
||||
if (!fullName) errors.push("Full name is required.");
|
||||
if (!username) errors.push("Username is required.");
|
||||
if (!email) {
|
||||
errors.push("Email is required.");
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
errors.push("Invalid email format.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
$successArea.addClass("d-none").text("");
|
||||
$errorArea.removeClass("d-none").html(errors.join("<br>"));
|
||||
return;
|
||||
}
|
||||
|
||||
$successArea.addClass("d-none").text("");
|
||||
$errorArea.addClass("d-none").text("");
|
||||
|
||||
$.ajax({
|
||||
url: base_url + "/users",
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
|
||||
},
|
||||
beforeSend: function () {
|
||||
$successArea.removeClass("d-none").text("Please wait ... user creation in progress!");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.success) {
|
||||
$successArea.removeClass("d-none").text("User successfully created!");
|
||||
$errorArea.addClass("d-none");
|
||||
|
||||
$.alert({
|
||||
title: "Success",
|
||||
content: "User successfully created!",
|
||||
});
|
||||
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
} else {
|
||||
$successArea.addClass("d-none");
|
||||
$errorArea.removeClass("d-none").text(data.msg || "An error occurred.");
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: data.msg || "An error occurred.",
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
$successArea.addClass("d-none");
|
||||
|
||||
if (xhr.status === 422) {
|
||||
// Laravel validation error
|
||||
let errors = xhr.responseJSON.errors;
|
||||
let messages = [];
|
||||
|
||||
$.each(errors, function (field, msgs) {
|
||||
messages.push(msgs.join("<br>"));
|
||||
});
|
||||
|
||||
$errorArea.removeClass("d-none").html(messages.join("<br>"));
|
||||
|
||||
$.alert({
|
||||
title: "Validation Error",
|
||||
content: messages.join("<br>"),
|
||||
});
|
||||
} else {
|
||||
// Other errors
|
||||
$errorArea.removeClass("d-none").text("Request failed: " + error);
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: "Request failed: " + error,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
485
php_code/public/assets/js/add_edit_user copy.js
Normal file
485
php_code/public/assets/js/add_edit_user copy.js
Normal file
@ -0,0 +1,485 @@
|
||||
$(document).ready(function () {
|
||||
|
||||
let iti;
|
||||
|
||||
$('#editUserModal').on('shown.bs.modal', function () {
|
||||
const phoneInput = document.querySelector("#editPhone");
|
||||
if (!iti) {
|
||||
iti = window.intlTelInput(phoneInput, {
|
||||
initialCountry: "gh",
|
||||
onlyCountries: ["gh"],
|
||||
nationalMode: false,
|
||||
autoPlaceholder: "polite",
|
||||
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"
|
||||
});
|
||||
|
||||
phoneInput.addEventListener("blur", function () {
|
||||
let rawNumber = phoneInput.value.trim();
|
||||
console.log(rawNumber);
|
||||
if (rawNumber.startsWith("0")) {
|
||||
rawNumber = rawNumber.substring(1);
|
||||
phoneInput.value = "+233" + rawNumber;
|
||||
}
|
||||
if (iti.isValidNumber()) {
|
||||
phoneInput.value = iti.getNumber(); // clean +233XXXXXXXXX
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#addUserModal').on('shown.bs.modal', function () {
|
||||
const phoneInput = document.querySelector("#addUserphone");
|
||||
if (!iti) {
|
||||
iti = window.intlTelInput(phoneInput, {
|
||||
initialCountry: "gh",
|
||||
onlyCountries: ["gh"],
|
||||
nationalMode: false,
|
||||
autoPlaceholder: "polite",
|
||||
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"
|
||||
});
|
||||
|
||||
phoneInput.addEventListener("blur", function () {
|
||||
let rawNumber = phoneInput.value.trim();
|
||||
|
||||
if (rawNumber.startsWith("0")) {
|
||||
rawNumber = rawNumber.substring(1);
|
||||
phoneInput.value = "+233" + rawNumber;
|
||||
}
|
||||
if (iti.isValidNumber()) {
|
||||
phoneInput.value = iti.getNumber();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let lastTrigger = null;
|
||||
// $(document).on('click', '.editBtnXX', function(){
|
||||
// lastTrigger = this;
|
||||
// const row = $(this).closest('.user-row');
|
||||
// $('#editId').val(row.data('id'));
|
||||
// $('#editName').val(row.data('name'));
|
||||
// $('#editEmail').val(row.data('email'));
|
||||
// $('#editCategory').val(row.data('category'));
|
||||
// });
|
||||
// When edit modal closes, return focus to trigger
|
||||
$('#editUserModal').on('hidden.bs.modal', function(){
|
||||
if (lastTrigger && document.contains(lastTrigger)) lastTrigger.focus();
|
||||
lastTrigger = null;
|
||||
});
|
||||
|
||||
// Submit edit (updates row inline)
|
||||
$('#editFormXX').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const id = $('#editId').val();
|
||||
const name = $('#editName').val();
|
||||
const email = $('#editEmail').val();
|
||||
const category = $('#editCategory').val();
|
||||
|
||||
const row = $(`.user-row[data-id="${id}"]`);
|
||||
row.data('name', name).data('email', email).data('category', category);
|
||||
row.find('.fw-semibold').text(name);
|
||||
row.find('td').eq(2).text(email);
|
||||
row.find('.category-badge').removeClass('badge-district badge-regional badge-national');
|
||||
if (category === 'District') row.find('.category-badge').addClass('badge-district').text('District');
|
||||
if (category === 'Regional') row.find('.category-badge').addClass('badge-regional').text('Regional');
|
||||
if (category === 'National') row.find('.category-badge').addClass('badge-national').text('National');
|
||||
const targetBody = category === 'District' ? '#districtBody' : (category === 'Regional' ? '#regionalBody' : '#nationalBody');
|
||||
if (!row.parent().is(targetBody)) {
|
||||
row.appendTo($(targetBody));
|
||||
}
|
||||
|
||||
updateCounts();
|
||||
$('#editModal').modal('hide');
|
||||
});
|
||||
|
||||
|
||||
var selectedValue = $("input[name='user_type']:checked").val();
|
||||
const $dropdown = $("#uaPostionAdd");
|
||||
|
||||
const optionsMap = {
|
||||
district_user: [
|
||||
"PPD Head", "Works Head", "MIS",
|
||||
"PPD Staff", "Works Staff",
|
||||
"Devt Planning Officer",
|
||||
"MCE", "DCE",
|
||||
"Urban Roads Department Head",
|
||||
"District Fire Officer",
|
||||
"District Disaster Prevention Department",
|
||||
"Head of District Health Department",
|
||||
"Representative of the Lands Commission",
|
||||
"Representative of the Environmental Protection Agency",
|
||||
"Rep from Traditional Council",
|
||||
"Chairman of the Works Sub Committee",
|
||||
"Chairman of Development Sub Planning Committee",
|
||||
"Nominated Elected Assembly Memebers"
|
||||
],
|
||||
national_luspa: ["Director", "IT Head", "Staff"],
|
||||
regional_luspa: ["Director", "Staff"]
|
||||
|
||||
};
|
||||
$dropdown.empty();
|
||||
$dropdown.append('<option value="">-- Select an option --</option>');
|
||||
|
||||
if (optionsMap['district_user']) {
|
||||
$.each(optionsMap['district_user'], function(index, value) {
|
||||
$dropdown.append($("<option></option>").attr("value", value.toLowerCase()).text(value));
|
||||
});
|
||||
}
|
||||
|
||||
$("input[name='user_type']").change(function() {
|
||||
var userValue = $(this).val();
|
||||
if (userValue == 'district_user') {
|
||||
$('#regionID').prop('disabled', false);
|
||||
$('#districtID').prop('disabled', false);
|
||||
|
||||
$('#editRegionID').prop('disabled', false);
|
||||
$('#editdistrictID').prop('disabled', false);
|
||||
|
||||
}
|
||||
if (userValue == 'national_luspa') {
|
||||
$('#regionID').prop('disabled', true);
|
||||
$('#districtID').prop('disabled', true);
|
||||
|
||||
$('#editRegionID').prop('disabled', true);
|
||||
$('#editdistrictID').prop('disabled', true);
|
||||
|
||||
}
|
||||
if (userValue == 'regional_luspa') {
|
||||
$('#districtID').prop('disabled', true);
|
||||
$('#regionID').prop('disabled', false);
|
||||
|
||||
$('#editdistrictID').prop('disabled', true);
|
||||
$('#editRegionID').prop('disabled', false);
|
||||
|
||||
}
|
||||
|
||||
$dropdown.empty();
|
||||
$dropdown.append('<option value="">-- Select an option --</option>');
|
||||
|
||||
if (optionsMap[userValue]) {
|
||||
$.each(optionsMap[userValue], function(index, value) {
|
||||
$dropdown.append($("<option></option>")
|
||||
.attr("value", value.toLowerCase())
|
||||
.text(value));
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
$('#editAllowedApps').select2({
|
||||
dropdownParent: $('#editUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('#allowedApps').select2({
|
||||
dropdownParent: $('#addUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('#inputPermissions').select2({
|
||||
dropdownParent: $('#editUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('.editUserBtn').click(function(evnt){
|
||||
evnt.preventDefault();
|
||||
// var selectedUserId = $(this).siblings('.userIdinput').val();
|
||||
const row = $(this).closest('.user-row');
|
||||
var selectedUserId = row.data('id');
|
||||
console.log('selectedUserId');
|
||||
lastTrigger = this;
|
||||
|
||||
// $('#editId').val(row.data('id'));
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('user_id', selectedUserId);
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/users/edit/' + selectedUserId,
|
||||
type: 'GET',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'crossDomain': false
|
||||
},
|
||||
beforeSend: function() {
|
||||
$('#editSuccessArea').text("");
|
||||
$('#editErrorArea').text("Please wait ... loading user details!!");
|
||||
},
|
||||
success: function(data) {
|
||||
$('#editErrorArea').text('');
|
||||
var jason = data.data;
|
||||
if(data.success == true){
|
||||
var allowedAppsArray = [];
|
||||
if (jason['allowed_apps']) {
|
||||
allowedAppsArray = jason['allowed_apps'].split(",");
|
||||
}
|
||||
$('#editFullName').val(jason['full_name']);
|
||||
$('#editEmail').val(jason['email']);
|
||||
$('#editUsername').val(jason['username']);
|
||||
$('#editGender').val(jason['gender']);
|
||||
$('#editTitle').val(jason['title']);
|
||||
$('#editUaPostion').val(jason['ua_position']);
|
||||
$('#editPhone').val(jason['phone']);
|
||||
$('#editAllowedApps').val(allowedAppsArray).trigger('change');
|
||||
$('#editRegionID').val(jason['region_id']);
|
||||
$('#editDistrictId').val(jason['district_id']);
|
||||
$('#editUaPostion').val(jason['ua_position']);
|
||||
$('#editGender').val(jason['gender']);
|
||||
$("input[name='user_id']").val(jason.user_id);
|
||||
|
||||
}
|
||||
//$('#editUserModal').modal('show');
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
$('#errorArea').text(error);
|
||||
$('#errorArea').text(error);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
$("#editUserForm").submit(function(evt){
|
||||
evt.preventDefault();
|
||||
const $successArea = $("#editSuccessArea");
|
||||
const $errorArea = $("#editErrorArea");
|
||||
$('#editSuccessArea').addClass('d-none');
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
|
||||
const formData = new FormData($(this)[0]);
|
||||
// formData = new FormData(this);
|
||||
let errors = [];
|
||||
if (iti) {
|
||||
let formattedPhone = iti.getNumber();
|
||||
if (formattedPhone.startsWith("+2330")) {
|
||||
formattedPhone = "+233" + formattedPhone.substring(5);
|
||||
}
|
||||
formattedPhone = formattedPhone.trim();
|
||||
if (!iti.isValidNumber()) {
|
||||
errors.push("001 | Please enter a valid Ghana phone number.");
|
||||
} else {
|
||||
formData.set("phone", formattedPhone);
|
||||
}
|
||||
}
|
||||
const username = $("#editUsername").val().trim();
|
||||
const email = $("#editEmail").val().trim();
|
||||
const fullName = $("#editFullName").val().trim();
|
||||
|
||||
if (!fullName) errors.push("Full name is required.");
|
||||
if (!username) errors.push("Username is required.");
|
||||
if (!email) {
|
||||
errors.push("Email is required.");
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
errors.push("Invalid email format.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
$('#editSuccessArea').addClass("d-none").text("");
|
||||
$errorArea.removeClass("d-none").html(errors.join("<br>"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/userupdate',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
dataType: 'json',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function() {
|
||||
// $('#updateBtn').addClass('d-none');
|
||||
// $('#uodateProgressBtn').removeClass('d-none');
|
||||
// $('#updateResultsDiv').removeClass('d-none');
|
||||
// $('#updateResultsParagraph').text("Processing Please wait ...");
|
||||
},
|
||||
success: function(data) {
|
||||
console.log(data);
|
||||
console.log(data['success']);
|
||||
if (data['success'] == true) {
|
||||
$('#editSuccessArea').removeClass('d-none');
|
||||
$('#editErrorArea').addClass('d-none');
|
||||
$('#editSuccessArea').text("");
|
||||
$('#editSuccessArea').text("User successfully details updated!");
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: 'User successfully details updated!!',
|
||||
});
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
}
|
||||
else{
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
$('#editErrorArea').text(data['msg']);
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: data['msg'],
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
$('#editErrorArea').text(error);
|
||||
|
||||
if (xhr.status === 422) {
|
||||
// Laravel validation error
|
||||
let errors = xhr.responseJSON.errors;
|
||||
let messages = [];
|
||||
|
||||
$.each(errors, function (field, msgs) {
|
||||
messages.push(msgs.join("<br>"));
|
||||
});
|
||||
|
||||
$('#editErrorArea').removeClass("d-none").html(messages.join("<br>"));
|
||||
|
||||
$.alert({
|
||||
title: "Validation Error",
|
||||
content: messages.join("<br>"),
|
||||
});
|
||||
} else {
|
||||
$('#editErrorArea').removeClass('d-none').text("Request failed : " + error);
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: "Request failed: " + error,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#regionID').change(function(){
|
||||
console.log('change is coming');
|
||||
var options = $('#districtID');
|
||||
var region_id = $('#regionID').val();
|
||||
$.ajax({
|
||||
url: base_url + '/admin/districts/' + region_id,
|
||||
type: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'crossDomain': false
|
||||
},
|
||||
success: function(data) {
|
||||
$.each(data['districts'], function(id, row) {
|
||||
$('#districtID').append($("<option />").val(row.districtid).text(row.district_name));
|
||||
});
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$("#newUserForm").on("submit", function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
const $successArea = $("#newUserSuccessArea");
|
||||
const $errorArea = $("#newUserErrorArea");
|
||||
const formData = new FormData(this);
|
||||
|
||||
let errors = [];
|
||||
if (iti) {
|
||||
let formattedPhone = iti.getNumber(); // +233XXXXXXXXX
|
||||
if (formattedPhone.startsWith("+2330")) {
|
||||
formattedPhone = "233" + formattedPhone.substring(5);
|
||||
}
|
||||
|
||||
if (!iti.isValidNumber()) {
|
||||
errors.push("Please enter a valid Ghana phone number.");
|
||||
} else {
|
||||
formData.set("phone", formattedPhone);
|
||||
}
|
||||
}
|
||||
const username = $("#addUserUsername").val().trim();
|
||||
const email = $("#addUserEmail").val().trim();
|
||||
const fullName = $("#addUserFullname").val().trim();
|
||||
|
||||
if (!fullName) errors.push("Full name is required.");
|
||||
if (!username) errors.push("Username is required.");
|
||||
if (!email) {
|
||||
errors.push("Email is required.");
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
errors.push("Invalid email format.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
$successArea.addClass("d-none").text("");
|
||||
$errorArea.removeClass("d-none").html(errors.join("<br>"));
|
||||
return;
|
||||
}
|
||||
|
||||
$successArea.addClass("d-none").text("");
|
||||
$errorArea.addClass("d-none").text("");
|
||||
|
||||
$.ajax({
|
||||
url: base_url + "/users",
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
|
||||
},
|
||||
beforeSend: function () {
|
||||
$successArea.removeClass("d-none").text("Please wait ... user creation in progress!");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.success) {
|
||||
$successArea.removeClass("d-none").text("User successfully created!");
|
||||
$errorArea.addClass("d-none");
|
||||
|
||||
$.alert({
|
||||
title: "Success",
|
||||
content: "User successfully created!",
|
||||
});
|
||||
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
} else {
|
||||
$successArea.addClass("d-none");
|
||||
$errorArea.removeClass("d-none").text(data.msg || "An error occurred.");
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: data.msg || "An error occurred.",
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
$successArea.addClass("d-none");
|
||||
|
||||
if (xhr.status === 422) {
|
||||
// Laravel validation error
|
||||
let errors = xhr.responseJSON.errors;
|
||||
let messages = [];
|
||||
|
||||
$.each(errors, function (field, msgs) {
|
||||
messages.push(msgs.join("<br>"));
|
||||
});
|
||||
|
||||
$errorArea.removeClass("d-none").html(messages.join("<br>"));
|
||||
|
||||
$.alert({
|
||||
title: "Validation Error",
|
||||
content: messages.join("<br>"),
|
||||
});
|
||||
} else {
|
||||
// Other errors
|
||||
$errorArea.removeClass("d-none").text("Request failed: " + error);
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: "Request failed: " + error,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
273
php_code/public/assets/js/add_edit_user.js
Normal file
273
php_code/public/assets/js/add_edit_user.js
Normal file
@ -0,0 +1,273 @@
|
||||
$(document).ready(function () {
|
||||
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content'),
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const phoneInstances = {};
|
||||
|
||||
function initPhoneInput(selector) {
|
||||
const input = document.querySelector(selector);
|
||||
if (!input) return null;
|
||||
|
||||
// If it was already initialized, don't do it again
|
||||
if (phoneInstances[selector]) return phoneInstances[selector];
|
||||
|
||||
phoneInstances[selector] = window.intlTelInput(input, {
|
||||
initialCountry: "gh",
|
||||
onlyCountries: ["gh"],
|
||||
nationalMode: true, // ALLOW users to type 024... natively
|
||||
autoPlaceholder: "polite",
|
||||
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"
|
||||
});
|
||||
|
||||
// Auto-format on blur using the plugin's built-in engine
|
||||
input.addEventListener("blur", function () {
|
||||
if (phoneInstances[selector].isValidNumber()) {
|
||||
// Reformats to local standard (e.g., 024 123 4567) for UI
|
||||
phoneInstances[selector].setNumber(phoneInstances[selector].getNumber());
|
||||
}
|
||||
});
|
||||
|
||||
return phoneInstances[selector];
|
||||
}
|
||||
|
||||
// Initialize inputs when their respective modals open
|
||||
$('#editUserModal').on('shown.bs.modal', () => initPhoneInput("#editPhone"));
|
||||
$('#addUserModal').on('shown.bs.modal', () => initPhoneInput("#addUserphone"));
|
||||
|
||||
$('#editAllowedApps, #inputPermissions').select2({
|
||||
dropdownParent: $('#editUserModal'),
|
||||
placeholder: "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('#allowedApps').select2({
|
||||
dropdownParent: $('#addUserModal'),
|
||||
placeholder: "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
const optionsMap = {
|
||||
district_user: [
|
||||
"PPD Head", "Works Head", "MIS", "PPD Staff", "Works Staff",
|
||||
"Devt Planning Officer", "MCE", "DCE", "Urban Roads Department Head",
|
||||
"District Fire Officer", "District Disaster Prevention Department",
|
||||
"Head of District Health Department", "Representative of the Lands Commission",
|
||||
"Representative of the Environmental Protection Agency", "Rep from Traditional Council",
|
||||
"Chairman of the Works Sub Committee", "Chairman of Development Sub Planning Committee",
|
||||
"Nominated Elected Assembly Memebers"
|
||||
],
|
||||
national_luspa: ["Director", "IT Head", "Staff"],
|
||||
regional_luspa: ["Director", "Staff"]
|
||||
};
|
||||
|
||||
function populatePositions(userType, targetDropdown) {
|
||||
targetDropdown.empty().append('<option value="">-- Select an option --</option>');
|
||||
if (optionsMap[userType]) {
|
||||
$.each(optionsMap[userType], function(index, value) {
|
||||
targetDropdown.append($("<option></option>").attr("value", value.toLowerCase()).text(value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let initialUserType = $("input[name='user_type']:checked").val();
|
||||
if(initialUserType) populatePositions(initialUserType, $("#uaPostionAdd"));
|
||||
|
||||
// Handle Radio Changes
|
||||
$("input[name='user_type']").change(function() {
|
||||
let userType = $(this).val();
|
||||
|
||||
let isDistrict = (userType === 'district_user');
|
||||
let isRegional = (userType === 'regional_luspa');
|
||||
|
||||
$('#regionID, #editRegionID').prop('disabled', !isDistrict && !isRegional);
|
||||
$('#districtID, #editdistrictID').prop('disabled', !isDistrict);
|
||||
|
||||
populatePositions(userType, $("#uaPostionAdd"));
|
||||
});
|
||||
|
||||
|
||||
$('.regionIDD').change(function() {
|
||||
let region_id = $(this).val();
|
||||
let $districtSelect = $('.districtIDD');
|
||||
|
||||
$districtSelect.empty().append('<option value="">-- Select District --</option>');
|
||||
|
||||
if(!region_id) return;
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/admin/districts/' + region_id,
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
if(data.districts) {
|
||||
$.each(data.districts, function(id, row) {
|
||||
$districtSelect.append($("<option />").val(row.districtid).text(row.district_name));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
let lastTrigger = null;
|
||||
|
||||
$(document).on('click', '.editUserBtn', function(evnt) {
|
||||
evnt.preventDefault();
|
||||
lastTrigger = this;
|
||||
|
||||
const row = $(this).closest('.user-row');
|
||||
let selectedUserId = row.data('id');
|
||||
|
||||
// Ensure phone instance exists before trying to set data into it
|
||||
let phoneIti = initPhoneInput("#editPhone");
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/users/edit/' + selectedUserId,
|
||||
type: 'GET',
|
||||
beforeSend: function() {
|
||||
$('#editSuccessArea').addClass('d-none');
|
||||
$('#editErrorArea').removeClass('d-none').text("Please wait ... loading user details!!");
|
||||
},
|
||||
success: function(response) {
|
||||
$('#editErrorArea').addClass('d-none').text('');
|
||||
|
||||
if (response.success) {
|
||||
let user = response.data;
|
||||
let allowedAppsArray = user.allowed_apps ? user.allowed_apps.split(",") : [];
|
||||
|
||||
$('#editFullName').val(user.full_name);
|
||||
$('#editEmail').val(user.email);
|
||||
$('#editUsername').val(user.username);
|
||||
$('#editGender').val(user.gender);
|
||||
$('#editTitle').val(user.title);
|
||||
$('#editUaPostion').val(user.ua_position);
|
||||
$('#editRegionID').val(user.region_id);
|
||||
$('#editDistrictId').val(user.district_id);
|
||||
$("input[name='user_id']").val(user.user_id);
|
||||
|
||||
$('#editAllowedApps').val(allowedAppsArray).trigger('change');
|
||||
if (user.user_type === 'district_user') {
|
||||
$('#editUserTypeDistrict').prop('checked', true);
|
||||
}
|
||||
else if(user.user_type === 'regional_luspa'){
|
||||
$('#editUserTypeLuspaRegion').prop('checked', true);
|
||||
}
|
||||
else{
|
||||
$('#editUserTypeLuspaNational').prop('checked', true);
|
||||
}
|
||||
|
||||
if (phoneIti && user.phone) {
|
||||
phoneIti.setNumber(user.phone);
|
||||
} else {
|
||||
$('#editPhone').val(user.phone);
|
||||
}
|
||||
// $('#editUserModal').modal('show')
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
$('#editErrorArea').removeClass('d-none').text("Failed to load user: " + error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#editUserModal').on('hidden.bs.modal', function(){
|
||||
if (lastTrigger && document.contains(lastTrigger)) lastTrigger.focus();
|
||||
lastTrigger = null;
|
||||
});
|
||||
|
||||
|
||||
function handleFormSubmission(formElement, url, successMessage, phoneInstanceSelector) {
|
||||
const $form = $(formElement);
|
||||
const isEdit = formElement.id === 'editUserForm';
|
||||
const prefix = isEdit ? '#edit' : '#newUser';
|
||||
|
||||
const $successArea = $(`${prefix}SuccessArea`);
|
||||
const $errorArea = $(`${prefix}ErrorArea`);
|
||||
const itiInstance = phoneInstances[phoneInstanceSelector];
|
||||
|
||||
$successArea.addClass('d-none').text("");
|
||||
$errorArea.addClass('d-none').text("");
|
||||
|
||||
const formData = new FormData(formElement);
|
||||
let errors = [];
|
||||
|
||||
// Validate Phone cleanly
|
||||
if (itiInstance) {
|
||||
if (!itiInstance.isValidNumber()) {
|
||||
errors.push("01|Please enter a valid Ghana phone number.");
|
||||
} else {
|
||||
// Get the clean E.164 database format (e.g. +233241234567)
|
||||
formData.set("phone", itiInstance.getNumber());
|
||||
}
|
||||
}
|
||||
|
||||
// Validate standard fields
|
||||
const fullName = $form.find('input[name="full_name"], [id*="FullName"], [id*="Fullname"]').val().trim();
|
||||
const username = $form.find('input[name="username"], [id*="Username"]').val().trim();
|
||||
const email = $form.find('input[type="email"], [id*="Email"]').val().trim();
|
||||
|
||||
if (!fullName) errors.push("Full name is required.");
|
||||
if (!username) errors.push("Username is required.");
|
||||
if (!email) {
|
||||
errors.push("Email is required.");
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
errors.push("Invalid email format.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
$errorArea.removeClass("d-none").html(errors.join("<br>"));
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function() {
|
||||
$successArea.removeClass('d-none').addClass('text-info').text("Processing request, please wait...");
|
||||
},
|
||||
success: function(data) {
|
||||
$successArea.removeClass('text-info');
|
||||
if (data.success) {
|
||||
$successArea.removeClass('d-none').text(successMessage);
|
||||
$.alert({ title: 'Success!', content: successMessage });
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
} else {
|
||||
$successArea.addClass('d-none');
|
||||
$errorArea.removeClass('d-none').text(data.msg || "An error occurred.");
|
||||
$.alert({ title: 'Error!', content: data.msg || "An error occurred." });
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
$successArea.addClass("d-none");
|
||||
if (xhr.status === 422 && xhr.responseJSON && xhr.responseJSON.errors) {
|
||||
let messages = [];
|
||||
$.each(xhr.responseJSON.errors, function (field, msgs) {
|
||||
messages.push(msgs.join("<br>"));
|
||||
});
|
||||
$errorArea.removeClass("d-none").html(messages.join("<br>"));
|
||||
$.alert({ title: "Validation Error", content: messages.join("<br>") });
|
||||
} else {
|
||||
$errorArea.removeClass("d-none").text("Request failed: " + error);
|
||||
$.alert({ title: "Error", content: "Request failed: " + error });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$("#editUserForm").on("submit", function (evt) {
|
||||
evt.preventDefault();
|
||||
handleFormSubmission(this, base_url + '/userupdate', "User details successfully updated!", "#editPhone");
|
||||
});
|
||||
|
||||
$("#newUserForm").on("submit", function (evt) {
|
||||
evt.preventDefault();
|
||||
handleFormSubmission(this, base_url + '/users', "User successfully created!", "#addUserphone");
|
||||
});
|
||||
|
||||
});
|
||||
477
php_code/public/assets/js/add_edit_user.js.bak
Normal file
477
php_code/public/assets/js/add_edit_user.js.bak
Normal file
@ -0,0 +1,477 @@
|
||||
$(document).ready(function () {
|
||||
|
||||
let iti;
|
||||
|
||||
$('#editUserModal').on('shown.bs.modal', function () {
|
||||
const phoneInput = document.querySelector("#editPhone");
|
||||
if (!iti) {
|
||||
iti = window.intlTelInput(phoneInput, {
|
||||
initialCountry: "gh",
|
||||
onlyCountries: ["gh"],
|
||||
nationalMode: false,
|
||||
autoPlaceholder: "polite",
|
||||
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"
|
||||
});
|
||||
|
||||
phoneInput.addEventListener("blur", function () {
|
||||
let rawNumber = phoneInput.value.trim();
|
||||
console.log(rawNumber);
|
||||
if (rawNumber.startsWith("0")) {
|
||||
rawNumber = rawNumber.substring(1);
|
||||
phoneInput.value = "+233" + rawNumber;
|
||||
}
|
||||
if (iti.isValidNumber()) {
|
||||
phoneInput.value = iti.getNumber(); // clean +233XXXXXXXXX
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#addUserModal').on('shown.bs.modal', function () {
|
||||
const phoneInput = document.querySelector("#addUserphone");
|
||||
if (!iti) {
|
||||
iti = window.intlTelInput(phoneInput, {
|
||||
initialCountry: "gh",
|
||||
onlyCountries: ["gh"],
|
||||
nationalMode: false,
|
||||
autoPlaceholder: "polite",
|
||||
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"
|
||||
});
|
||||
|
||||
phoneInput.addEventListener("blur", function () {
|
||||
let rawNumber = phoneInput.value.trim();
|
||||
|
||||
if (rawNumber.startsWith("0")) {
|
||||
rawNumber = rawNumber.substring(1);
|
||||
phoneInput.value = "+233" + rawNumber;
|
||||
}
|
||||
if (iti.isValidNumber()) {
|
||||
phoneInput.value = iti.getNumber();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let lastTrigger = null;
|
||||
// When edit modal closes, return focus to trigger
|
||||
$('#editUserModal').on('hidden.bs.modal', function(){
|
||||
if (lastTrigger && document.contains(lastTrigger)) lastTrigger.focus();
|
||||
lastTrigger = null;
|
||||
});
|
||||
|
||||
// Submit edit (updates row inline)
|
||||
$('#editFormXX').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const id = $('#editId').val();
|
||||
const name = $('#editName').val();
|
||||
const email = $('#editEmail').val();
|
||||
const category = $('#editCategory').val();
|
||||
|
||||
const row = $(`.user-row[data-id="${id}"]`);
|
||||
row.data('name', name).data('email', email).data('category', category);
|
||||
row.find('.fw-semibold').text(name);
|
||||
row.find('td').eq(2).text(email);
|
||||
row.find('.category-badge').removeClass('badge-district badge-regional badge-national');
|
||||
if (category === 'District') row.find('.category-badge').addClass('badge-district').text('District');
|
||||
if (category === 'Regional') row.find('.category-badge').addClass('badge-regional').text('Regional');
|
||||
if (category === 'National') row.find('.category-badge').addClass('badge-national').text('National');
|
||||
const targetBody = category === 'District' ? '#districtBody' : (category === 'Regional' ? '#regionalBody' : '#nationalBody');
|
||||
if (!row.parent().is(targetBody)) {
|
||||
row.appendTo($(targetBody));
|
||||
}
|
||||
|
||||
updateCounts();
|
||||
$('#editModal').modal('hide');
|
||||
});
|
||||
|
||||
|
||||
var selectedValue = $("input[name='user_type']:checked").val();
|
||||
const $dropdown = $("#uaPostionAdd");
|
||||
|
||||
const optionsMap = {
|
||||
district_user: [
|
||||
"PPD Head", "Works Head", "MIS",
|
||||
"PPD Staff", "Works Staff",
|
||||
"Devt Planning Officer",
|
||||
"MCE", "DCE",
|
||||
"Urban Roads Department Head",
|
||||
"District Fire Officer",
|
||||
"District Disaster Prevention Department",
|
||||
"Head of District Health Department",
|
||||
"Representative of the Lands Commission",
|
||||
"Representative of the Environmental Protection Agency",
|
||||
"Rep from Traditional Council",
|
||||
"Chairman of the Works Sub Committee",
|
||||
"Chairman of Development Sub Planning Committee",
|
||||
"Nominated Elected Assembly Memebers"
|
||||
],
|
||||
national_luspa: ["Director", "IT Head", "Staff"],
|
||||
regional_luspa: ["Director", "Staff"]
|
||||
|
||||
};
|
||||
$dropdown.empty();
|
||||
$dropdown.append('<option value="">-- Select an option --</option>');
|
||||
|
||||
if (optionsMap['district_user']) {
|
||||
$.each(optionsMap['district_user'], function(index, value) {
|
||||
$dropdown.append($("<option></option>").attr("value", value.toLowerCase()).text(value));
|
||||
});
|
||||
}
|
||||
|
||||
$("input[name='user_type']").change(function() {
|
||||
var userValue = $(this).val();
|
||||
if (userValue == 'district_user') {
|
||||
$('#regionID').prop('disabled', false);
|
||||
$('#districtID').prop('disabled', false);
|
||||
|
||||
$('#editRegionID').prop('disabled', false);
|
||||
$('#editdistrictID').prop('disabled', false);
|
||||
|
||||
}
|
||||
if (userValue == 'national_luspa') {
|
||||
$('#regionID').prop('disabled', true);
|
||||
$('#districtID').prop('disabled', true);
|
||||
|
||||
$('#editRegionID').prop('disabled', true);
|
||||
$('#editdistrictID').prop('disabled', true);
|
||||
|
||||
}
|
||||
if (userValue == 'regional_luspa') {
|
||||
$('#districtID').prop('disabled', true);
|
||||
$('#regionID').prop('disabled', false);
|
||||
|
||||
$('#editdistrictID').prop('disabled', true);
|
||||
$('#editRegionID').prop('disabled', false);
|
||||
|
||||
}
|
||||
|
||||
$dropdown.empty();
|
||||
$dropdown.append('<option value="">-- Select an option --</option>');
|
||||
|
||||
if (optionsMap[userValue]) {
|
||||
$.each(optionsMap[userValue], function(index, value) {
|
||||
$dropdown.append($("<option></option>")
|
||||
.attr("value", value.toLowerCase())
|
||||
.text(value));
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
$('#editAllowedApps').select2({
|
||||
dropdownParent: $('#editUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('#allowedApps').select2({
|
||||
dropdownParent: $('#addUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('#inputPermissions').select2({
|
||||
dropdownParent: $('#editUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('.editUserBtn').click(function(evnt){
|
||||
evnt.preventDefault();
|
||||
// var selectedUserId = $(this).siblings('.userIdinput').val();
|
||||
const row = $(this).closest('.user-row');
|
||||
var selectedUserId = row.data('id');
|
||||
console.log('selectedUserId');
|
||||
lastTrigger = this;
|
||||
|
||||
// $('#editId').val(row.data('id'));
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('user_id', selectedUserId);
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/users/edit/' + selectedUserId,
|
||||
type: 'GET',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'crossDomain': false
|
||||
},
|
||||
beforeSend: function() {
|
||||
$('#editSuccessArea').text("");
|
||||
$('#editErrorArea').text("Please wait ... loading user details!!");
|
||||
},
|
||||
success: function(data) {
|
||||
$('#editErrorArea').text('');
|
||||
var jason = data.data;
|
||||
if(data.success == true){
|
||||
var allowedAppsArray = [];
|
||||
if (jason['allowed_apps']) {
|
||||
allowedAppsArray = jason['allowed_apps'].split(",");
|
||||
}
|
||||
$('#editFullName').val(jason['full_name']);
|
||||
$('#editEmail').val(jason['email']);
|
||||
$('#editUsername').val(jason['username']);
|
||||
$('#editGender').val(jason['gender']);
|
||||
$('#editTitle').val(jason['title']);
|
||||
$('#editUaPostion').val(jason['ua_position']);
|
||||
$('#editPhone').val(jason['phone']);
|
||||
$('#editAllowedApps').val(allowedAppsArray).trigger('change');
|
||||
$('#editRegionID').val(jason['region_id']);
|
||||
$('#editDistrictId').val(jason['district_id']);
|
||||
$('#editUaPostion').val(jason['ua_position']);
|
||||
$('#editGender').val(jason['gender']);
|
||||
$("input[name='user_id']").val(jason.user_id);
|
||||
|
||||
}
|
||||
//$('#editUserModal').modal('show');
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
$('#errorArea').text(error);
|
||||
$('#errorArea').text(error);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
$("#editUserForm").submit(function(evt){
|
||||
evt.preventDefault();
|
||||
const $successArea = $("#editSuccessArea");
|
||||
const $errorArea = $("#editErrorArea");
|
||||
$('#editSuccessArea').addClass('d-none');
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
|
||||
const formData = new FormData($(this)[0]);
|
||||
// formData = new FormData(this);
|
||||
let errors = [];
|
||||
if (iti) {
|
||||
let formattedPhone = iti.getNumber();
|
||||
if (formattedPhone.startsWith("+2330")) {
|
||||
formattedPhone = "+233" + formattedPhone.substring(5);
|
||||
}
|
||||
formattedPhone = formattedPhone.trim();
|
||||
if (!iti.isValidNumber()) {
|
||||
errors.push("001 | Please enter a valid Ghana phone number.");
|
||||
} else {
|
||||
formData.set("phone", formattedPhone);
|
||||
}
|
||||
}
|
||||
const username = $("#editUsername").val().trim();
|
||||
const email = $("#editEmail").val().trim();
|
||||
const fullName = $("#editFullName").val().trim();
|
||||
|
||||
if (!fullName) errors.push("Full name is required.");
|
||||
if (!username) errors.push("Username is required.");
|
||||
if (!email) {
|
||||
errors.push("Email is required.");
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
errors.push("Invalid email format.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
$('#editSuccessArea').addClass("d-none").text("");
|
||||
$errorArea.removeClass("d-none").html(errors.join("<br>"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/userupdate',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
dataType: 'json',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function() {
|
||||
// $('#updateBtn').addClass('d-none');
|
||||
// $('#uodateProgressBtn').removeClass('d-none');
|
||||
// $('#updateResultsDiv').removeClass('d-none');
|
||||
// $('#updateResultsParagraph').text("Processing Please wait ...");
|
||||
},
|
||||
success: function(data) {
|
||||
console.log(data);
|
||||
console.log(data['success']);
|
||||
if (data['success'] == true) {
|
||||
$('#editSuccessArea').removeClass('d-none');
|
||||
$('#editErrorArea').addClass('d-none');
|
||||
$('#editSuccessArea').text("");
|
||||
$('#editSuccessArea').text("User successfully details updated!");
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: 'User successfully details updated!!',
|
||||
});
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
}
|
||||
else{
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
$('#editErrorArea').text(data['msg']);
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: data['msg'],
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
$('#editErrorArea').text(error);
|
||||
|
||||
if (xhr.status === 422) {
|
||||
// Laravel validation error
|
||||
let errors = xhr.responseJSON.errors;
|
||||
let messages = [];
|
||||
|
||||
$.each(errors, function (field, msgs) {
|
||||
messages.push(msgs.join("<br>"));
|
||||
});
|
||||
|
||||
$('#editErrorArea').removeClass("d-none").html(messages.join("<br>"));
|
||||
|
||||
$.alert({
|
||||
title: "Validation Error",
|
||||
content: messages.join("<br>"),
|
||||
});
|
||||
} else {
|
||||
$('#editErrorArea').removeClass('d-none').text("Request failed : " + error);
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: "Request failed: " + error,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#regionID').change(function(){
|
||||
console.log('change is coming');
|
||||
var options = $('#districtID');
|
||||
var region_id = $('#regionID').val();
|
||||
$.ajax({
|
||||
url: base_url + '/admin/districts/' + region_id,
|
||||
type: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'crossDomain': false
|
||||
},
|
||||
success: function(data) {
|
||||
$.each(data['districts'], function(id, row) {
|
||||
$('#districtID').append($("<option />").val(row.districtid).text(row.district_name));
|
||||
});
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$("#newUserForm").on("submit", function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
const $successArea = $("#newUserSuccessArea");
|
||||
const $errorArea = $("#newUserErrorArea");
|
||||
const formData = new FormData(this);
|
||||
|
||||
let errors = [];
|
||||
if (iti) {
|
||||
let formattedPhone = iti.getNumber(); // +233XXXXXXXXX
|
||||
if (formattedPhone.startsWith("+2330")) {
|
||||
formattedPhone = "233" + formattedPhone.substring(5);
|
||||
}
|
||||
|
||||
if (!iti.isValidNumber()) {
|
||||
errors.push("Please enter a valid Ghana phone number.");
|
||||
} else {
|
||||
formData.set("phone", formattedPhone);
|
||||
}
|
||||
}
|
||||
const username = $("#addUserUsername").val().trim();
|
||||
const email = $("#addUserEmail").val().trim();
|
||||
const fullName = $("#addUserFullname").val().trim();
|
||||
|
||||
if (!fullName) errors.push("Full name is required.");
|
||||
if (!username) errors.push("Username is required.");
|
||||
if (!email) {
|
||||
errors.push("Email is required.");
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
errors.push("Invalid email format.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
$successArea.addClass("d-none").text("");
|
||||
$errorArea.removeClass("d-none").html(errors.join("<br>"));
|
||||
return;
|
||||
}
|
||||
|
||||
$successArea.addClass("d-none").text("");
|
||||
$errorArea.addClass("d-none").text("");
|
||||
|
||||
$.ajax({
|
||||
url: base_url + "/users",
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
|
||||
},
|
||||
beforeSend: function () {
|
||||
$successArea.removeClass("d-none").text("Please wait ... user creation in progress!");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.success) {
|
||||
$successArea.removeClass("d-none").text("User successfully created!");
|
||||
$errorArea.addClass("d-none");
|
||||
|
||||
$.alert({
|
||||
title: "Success",
|
||||
content: "User successfully created!",
|
||||
});
|
||||
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
} else {
|
||||
$successArea.addClass("d-none");
|
||||
$errorArea.removeClass("d-none").text(data.msg || "An error occurred.");
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: data.msg || "An error occurred.",
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
$successArea.addClass("d-none");
|
||||
|
||||
if (xhr.status === 422) {
|
||||
// Laravel validation error
|
||||
let errors = xhr.responseJSON.errors;
|
||||
let messages = [];
|
||||
|
||||
$.each(errors, function (field, msgs) {
|
||||
messages.push(msgs.join("<br>"));
|
||||
});
|
||||
|
||||
$errorArea.removeClass("d-none").html(messages.join("<br>"));
|
||||
|
||||
$.alert({
|
||||
title: "Validation Error",
|
||||
content: messages.join("<br>"),
|
||||
});
|
||||
} else {
|
||||
// Other errors
|
||||
$errorArea.removeClass("d-none").text("Request failed: " + error);
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: "Request failed: " + error,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
0
php_code/public/assets/js/permit_backend.js
Normal file
0
php_code/public/assets/js/permit_backend.js
Normal file
190
php_code/public/assets/js/permit_comments.js
Normal file
190
php_code/public/assets/js/permit_comments.js
Normal file
@ -0,0 +1,190 @@
|
||||
$(document).ready(function(){
|
||||
const baseUrl = base_url + "/permits/getcomments";
|
||||
fetchPermitApplicationComments();
|
||||
// CREATE
|
||||
$(".submitPermitCommentBtn").click(function (e) {
|
||||
e.preventDefault();
|
||||
// let formData = $(this).serialize();
|
||||
|
||||
var commentBody = $('.commentBody').val();
|
||||
var applicationCode = $('.applicationCode').val();
|
||||
const formData = new FormData();
|
||||
formData.append('comment_body', commentBody);
|
||||
formData.append('application_code', applicationCode);
|
||||
|
||||
$.ajax({
|
||||
url: base_url + "/permits/addcomment",
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
|
||||
"Accept": "application/json"
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.success === true) {
|
||||
$.alert("Comment successfully submitted!");
|
||||
fetchPermitApplicationComments();
|
||||
}
|
||||
else{
|
||||
$.alert(response.msg);
|
||||
}
|
||||
|
||||
console.log(response);
|
||||
},
|
||||
error: function (xhr) {
|
||||
alert("Error: " + xhr.responseText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// READ (fetch all)
|
||||
function fetchPermitApplicationComments() {
|
||||
var applicationCode = $('.applicationCode').val();
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('application_code', applicationCode);
|
||||
|
||||
$.ajax({
|
||||
url: baseUrl,
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
|
||||
"Accept": "application/json"
|
||||
},
|
||||
success: function (data) {
|
||||
console.log("Fetched Comments:", data);
|
||||
// $("#permitTableBody").empty();
|
||||
populateCommentsTimeline(data)
|
||||
// $.each(data, function (index, app) {
|
||||
// $("#permitTableBody").append(`
|
||||
// <tr>
|
||||
// <td>${app.id}</td>
|
||||
// <td>${app.user_id}</td>
|
||||
// <td>${app.application_code}</td>
|
||||
// <td>${app.comment_body ?? ""}</td>
|
||||
// <td>
|
||||
// <button class="editBtn" data-id="${app.id}">Edit</button>
|
||||
// <button class="deleteBtn" data-id="${app.id}">Delete</button>
|
||||
// </td>
|
||||
// </tr>
|
||||
// `);
|
||||
// });
|
||||
},
|
||||
error: function (xhr) {
|
||||
alert("Failed to fetch applications.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
$(document).on("click", ".editBtn", function () {
|
||||
let id = $(this).data("id");
|
||||
let updatedData = {
|
||||
comment_body: prompt("Enter new comment:")
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: `${baseUrl}/${id}`,
|
||||
type: "PUT",
|
||||
data: updatedData,
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
|
||||
"Accept": "application/json"
|
||||
},
|
||||
success: function (response) {
|
||||
alert("Application Updated!");
|
||||
console.log(response);
|
||||
fetchPermitApplicationComments();
|
||||
},
|
||||
error: function (xhr) {
|
||||
alert("Update failed: " + xhr.responseText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// DELETE
|
||||
$(document).on("click", ".deleteBtn", function () {
|
||||
let id = $(this).data("id");
|
||||
|
||||
if (confirm("Are you sure you want to delete this application?")) {
|
||||
$.ajax({
|
||||
url: `${baseUrl}/${id}`,
|
||||
type: "DELETE",
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
|
||||
"Accept": "application/json"
|
||||
},
|
||||
success: function (response) {
|
||||
alert("Application Deleted!");
|
||||
console.log(response);
|
||||
fetchPermitApplicationComments();
|
||||
},
|
||||
error: function (xhr) {
|
||||
alert("Delete failed: " + xhr.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Example AJAX setup assuming your response is stored in a variable called 'response'
|
||||
function populateCommentsTimeline(response) {
|
||||
const $timeline = $('#commentsTimeline');
|
||||
|
||||
// Clear out any old data or loading spinners
|
||||
$timeline.empty();
|
||||
|
||||
// Check if we have a successful response with data
|
||||
if (response.success && response.count > 0) {
|
||||
|
||||
// Loop through each comment in the data array
|
||||
$.each(response.data, function(index, item) {
|
||||
|
||||
// Format the date to look like "19 Jun 2026, 06:55 PM"
|
||||
const dateObj = new Date(item.created_date);
|
||||
const formattedDate = dateObj.toLocaleDateString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
|
||||
// Construct the Timeline Item HTML
|
||||
const timelineHtml = `
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon bg-secondary text-white shadow-sm">
|
||||
<i class="bi bi-chat-text"></i>
|
||||
</div>
|
||||
<div class="timeline-content border rounded p-3 shadow-sm mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<strong class="text-dark">${item.created_by}</strong>
|
||||
<small class="text-muted">${formattedDate}</small>
|
||||
</div>
|
||||
<p class="mb-0 text-dark small">${item.app_comments}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Append it to the container
|
||||
$timeline.append(timelineHtml);
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
// Fallback UI if there are no comments yet
|
||||
$timeline.append(`
|
||||
<div class="text-center text-muted mt-5">
|
||||
<i class="bi bi-inbox fs-1"></i>
|
||||
<p class="mt-2">No history or comments found for this application.</p>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
}
|
||||
});
|
||||
186
php_code/public/assets/js/user_mgt_new.js
Normal file
186
php_code/public/assets/js/user_mgt_new.js
Normal file
@ -0,0 +1,186 @@
|
||||
(function(){
|
||||
$(document).on('keydown', function(e){
|
||||
if (e.key === '/' && !$(e.target).is('input, textarea')) {
|
||||
e.preventDefault();
|
||||
$('#globalSearch').focus().select();
|
||||
}
|
||||
});
|
||||
|
||||
// quick loading of district/regional/national rows
|
||||
function updateCounts(){
|
||||
// $('#count-district').text($('#districtBody .user-row:visible').length);
|
||||
// $('#count-regional').text($('#regionalBody .user-row:visible').length);
|
||||
// $('#count-national').text($('#nationalBody .user-row:visible').length);
|
||||
}
|
||||
|
||||
function getVisibleRowsInActiveTab(){
|
||||
const activePane = $('.tab-pane.active');
|
||||
return activePane.find('.user-row:visible');
|
||||
}
|
||||
|
||||
// Select all visible in active tab
|
||||
$('#globalSelect').on('change', function(){
|
||||
const checked = $(this).is(':checked');
|
||||
getVisibleRowsInActiveTab().find('.row-select').prop('checked', checked).trigger('change');
|
||||
});
|
||||
|
||||
// Row checkbox toggles bulk buttons
|
||||
$(document).on('change', '.row-select', function(){
|
||||
const anyChecked = $('.tab-pane.active .row-select:checked').length > 0;
|
||||
$('#bulkMoveBtn, #bulkDeactivate').prop('disabled', !anyChecked);
|
||||
// sync header select state
|
||||
const total = getVisibleRowsInActiveTab().find('.row-select').length;
|
||||
const checked = getVisibleRowsInActiveTab().find('.row-select:checked').length;
|
||||
$('#globalSelect').prop('checked', total>0 && total===checked);
|
||||
});
|
||||
|
||||
let searchTimer;
|
||||
|
||||
$('#globalSearch').on('keyup', function() {
|
||||
clearTimeout(searchTimer);
|
||||
let query = $(this).val();
|
||||
|
||||
// Optional: Slightly fade the tables to show it is loading
|
||||
$('#districtBody, #regionalBody, #nationalBody').css('opacity', '0.5');
|
||||
|
||||
// Wait 500ms after the user stops typing
|
||||
searchTimer = setTimeout(function() {
|
||||
|
||||
$.ajax({
|
||||
url: window.location.pathname, // Request the same page
|
||||
type: "GET",
|
||||
data: { search: query },
|
||||
success: function(response) {
|
||||
// Turn the returned text into a searchable DOM object
|
||||
let html = $(response);
|
||||
|
||||
// 1. Swap the Table Bodies
|
||||
$('#districtBody').html(html.find('#districtBody').html());
|
||||
$('#regionalBody').html(html.find('#regionalBody').html());
|
||||
$('#nationalBody').html(html.find('#nationalBody').html());
|
||||
|
||||
// 2. Swap the Tab Counts
|
||||
$('#count-district').text(html.find('#count-district').text());
|
||||
$('#count-regional').text(html.find('#count-regional').text());
|
||||
$('#count-national').text(html.find('#count-national').text());
|
||||
|
||||
// 3. Swap the Pagination links
|
||||
$('#district-pagination').html(html.find('#district-pagination').html());
|
||||
$('#regional-pagination').html(html.find('#regional-pagination').html());
|
||||
$('#national-pagination').html(html.find('#national-pagination').html());
|
||||
|
||||
// Remove the fade effect
|
||||
$('#districtBody, #regionalBody, #nationalBody').css('opacity', '1');
|
||||
},
|
||||
error: function() {
|
||||
console.error("Live search failed");
|
||||
$('#districtBody, #regionalBody, #nationalBody').css('opacity', '1');
|
||||
}
|
||||
});
|
||||
|
||||
}, 500); // 500ms delay
|
||||
});
|
||||
|
||||
// $('#globalSearch').on('input', applyFilters);
|
||||
// $('.filter-status').on('change', applyFilters);
|
||||
|
||||
// Activate tab event -> clear globalSelect and bulk buttons
|
||||
$('button[data-bs-toggle="tab"]').on('shown.bs.tab', function(){
|
||||
$('#globalSelect').prop('checked', false);
|
||||
$('#bulkMoveBtn, #bulkDeactivate').prop('disabled', true);
|
||||
});
|
||||
|
||||
// Edit button opens modal and populates fields
|
||||
let lastTrigger = null;
|
||||
$(document).on('click', '.editBtnXX', function(){
|
||||
lastTrigger = this;
|
||||
const row = $(this).closest('.user-row');
|
||||
$('#editId').val(row.data('id'));
|
||||
$('#editName').val(row.data('name'));
|
||||
$('#editEmail').val(row.data('email'));
|
||||
$('#editCategory').val(row.data('category'));
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Bulk move: gather selected rows and show count
|
||||
$('#bulkMoveBtn').on('click', function(){
|
||||
const selected = $('.tab-pane.active .row-select:checked');
|
||||
$('#moveCount').text(selected.length);
|
||||
// focus first control in modal for accessibility
|
||||
$('#moveModal').on('shown.bs.modal', function(){ $('#moveTarget').focus(); });
|
||||
});
|
||||
|
||||
// Remember trigger to restore focus after move modal
|
||||
$('#moveModal').on('hidden.bs.modal', function(){ $('.tab-pane.active').find('.row-select:checked').first().closest('tr').find('.row-select').focus(); });
|
||||
|
||||
// Execute move
|
||||
$('#moveForm').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const target = $('#moveTarget').val();
|
||||
const selected = $('.tab-pane.active .row-select:checked').closest('.user-row');
|
||||
selected.each(function(){
|
||||
const $r = $(this);
|
||||
$r.data('category', target);
|
||||
$r.find('.category-badge').removeClass('badge-district badge-regional badge-national');
|
||||
if (target === 'District') $r.find('.category-badge').addClass('badge-district').text('District');
|
||||
if (target === 'Regional') $r.find('.category-badge').addClass('badge-regional').text('Regional');
|
||||
if (target === 'National') $r.find('.category-badge').addClass('badge-national').text('National');
|
||||
const targetBody = target === 'District' ? '#districtBody' : (target === 'Regional' ? '#regionalBody' : '#nationalBody');
|
||||
$r.appendTo($(targetBody));
|
||||
$r.find('.row-select').prop('checked', false);
|
||||
});
|
||||
$('#moveModal').modal('hide');
|
||||
$('#globalSelect').prop('checked', false);
|
||||
$('#bulkMoveBtn, #bulkDeactivate').prop('disabled', true);
|
||||
updateCounts();
|
||||
});
|
||||
|
||||
// Bulk deactivate demo (toggles status)
|
||||
$('#bulkDeactivate').on('click', function(){
|
||||
const selected = $('.tab-pane.active .row-select:checked').closest('.user-row');
|
||||
selected.each(function(){
|
||||
const $r = $(this);
|
||||
$r.data('status', 'Suspended');
|
||||
// add visual cue
|
||||
$r.addClass('table-danger');
|
||||
setTimeout(()=> $r.removeClass('table-danger'), 1200);
|
||||
$r.find('.row-select').prop('checked', false);
|
||||
});
|
||||
$('#bulkMoveBtn, #bulkDeactivate').prop('disabled', true);
|
||||
});
|
||||
|
||||
|
||||
// ensure focus is moved first.
|
||||
// Update counts initially
|
||||
updateCounts();
|
||||
|
||||
// Demo: focus first edit button when pressing 'e' (example of keyboard shortcut)
|
||||
$(document).on('keydown', function(e){
|
||||
if (e.key === 'e' && !$(e.target).is('input, textarea')) {
|
||||
e.preventDefault();
|
||||
$('.editBtn').first().focus();
|
||||
}
|
||||
});
|
||||
$('#editRegionID').change(function(){
|
||||
console.log('change is coming');
|
||||
var options = $('#editDistrictID');
|
||||
var region_id = $('#editRegionID').val();
|
||||
$.ajax({
|
||||
url: base_url + '/admin/districts/' + region_id,
|
||||
type: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'crossDomain': false
|
||||
},
|
||||
success: function(data) {
|
||||
$.each(data['districts'], function(id, row) {
|
||||
$('#editDistrictID').append($("<option />").val(row.districtid).text(row.district_name));
|
||||
});
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
436
php_code/public/assets/js/usermgt copy.js
Normal file
436
php_code/public/assets/js/usermgt copy.js
Normal file
@ -0,0 +1,436 @@
|
||||
$(document).ready(function(){
|
||||
console.log('foo');
|
||||
// document.addEventListener("DOMContentLoaded", function () {
|
||||
// const phoneInput = document.querySelector("#newUserPhone");
|
||||
|
||||
// const iti = window.intlTelInput(phoneInput, {
|
||||
// initialCountry: "gh",
|
||||
// onlyCountries: ["gh"],
|
||||
// nationalMode: false,
|
||||
// utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"
|
||||
// });
|
||||
|
||||
// Optional: validate before form submit
|
||||
// $("#newUserForm").on("submit", function (evt) {
|
||||
// if (!iti.isValidNumber()) {
|
||||
// evt.preventDefault();
|
||||
// $("#newUserErrorArea")
|
||||
// .removeClass("d-none")
|
||||
// .text("Please enter a valid Ghana phone number.");
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
$('#addUserModal').on('shown.bs.modal', function () {
|
||||
const phoneInput = document.querySelector("#phone");
|
||||
window.intlTelInput(phoneInput, {
|
||||
initialCountry: "gh",
|
||||
onlyCountries: ["gh"],
|
||||
nationalMode: false,
|
||||
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
var selectedValue = $("input[name='user_type']:checked").val();
|
||||
const $dropdown = $("#uaPostionAdd");
|
||||
|
||||
const optionsMap = {
|
||||
district_user: [
|
||||
"PPD Head", "Works Head", "MIS",
|
||||
"PPD Staff", "Works Staff",
|
||||
"Devt Planning Officer",
|
||||
"MCE", "DCE",
|
||||
"Urban Roads Department Head",
|
||||
"District Fire Officer",
|
||||
"District Disaster Prevention Department",
|
||||
"Head of District Health Department",
|
||||
"Representative of the Lands Commission",
|
||||
"Representative of the Environmental Protection Agency",
|
||||
"Rep from Traditional Council",
|
||||
"Chairman of the Works Sub Committee",
|
||||
"Chairman of Development Sub Planning Committee",
|
||||
"Nominated Elected Assembly Memebers"
|
||||
],
|
||||
national_luspa: ["Director", "IT Head", "Staff"],
|
||||
regional_luspa: ["Director", "Staff"]
|
||||
|
||||
};
|
||||
$dropdown.empty();
|
||||
$dropdown.append('<option value="">-- Select an option --</option>');
|
||||
|
||||
if (optionsMap['district_user']) {
|
||||
$.each(optionsMap['district_user'], function(index, value) {
|
||||
$dropdown.append($("<option></option>").attr("value", value.toLowerCase()).text(value));
|
||||
});
|
||||
}
|
||||
|
||||
$("input[name='user_type']").change(function() {
|
||||
var userValue = $(this).val();
|
||||
if (userValue == 'district_user') {
|
||||
$('#regionID').prop('disabled', false);
|
||||
$('#districtID').prop('disabled', false);
|
||||
|
||||
}
|
||||
if (userValue == 'national_luspa') {
|
||||
$('#regionID').prop('disabled', true);
|
||||
$('#districtID').prop('disabled', true);
|
||||
|
||||
}
|
||||
if (userValue == 'regional_luspa') {
|
||||
$('#districtID').prop('disabled', true);
|
||||
$('#regionID').prop('disabled', false);
|
||||
|
||||
}
|
||||
|
||||
$dropdown.empty();
|
||||
$dropdown.append('<option value="">-- Select an option --</option>');
|
||||
|
||||
if (optionsMap[userValue]) {
|
||||
$.each(optionsMap[userValue], function(index, value) {
|
||||
$dropdown.append($("<option></option>")
|
||||
.attr("value", value.toLowerCase())
|
||||
.text(value));
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
$('#editAllowedApps').select2({
|
||||
dropdownParent: $('#editUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('#allowedApps').select2({
|
||||
dropdownParent: $('#addUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('#inputPermissions').select2({
|
||||
dropdownParent: $('#editUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
});
|
||||
|
||||
$('.editUserBtn').click(function(evnt){
|
||||
evnt.preventDefault();
|
||||
var selectedUserId = $(this).siblings('.userIdinput').val();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('user_id', selectedUserId);
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/users/edit/' + selectedUserId,
|
||||
type: 'GET',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'crossDomain': false
|
||||
},
|
||||
beforeSend: function() {
|
||||
$('#editSuccessArea').text("");
|
||||
$('#editErrorArea').text("Please wait ... loading user details!!");
|
||||
},
|
||||
success: function(data) {
|
||||
$('#editErrorArea').text('');
|
||||
var jason = data.data;
|
||||
if(data.success == true){
|
||||
var allowedAppsArray = [];
|
||||
if (jason['allowed_apps']) {
|
||||
allowedAppsArray = jason['allowed_apps'].split(",");
|
||||
}
|
||||
$('#editFullName').val(jason['full_name']);
|
||||
$('#editEmail').val(jason['email']);
|
||||
$('#editUsername').val(jason['username']);
|
||||
$('#editGender').val(jason['gender']);
|
||||
$('#editTitle').val(jason['title']);
|
||||
$('#editUaPostion').val(jason['ua_position']);
|
||||
$('#editPhone').val(jason['phone']);
|
||||
$('#editAllowedApps').val(allowedAppsArray).trigger('change');
|
||||
$('#editRegionID').val(jason['region_id']);
|
||||
$('#editDistrictId').val(jason['district_id']);
|
||||
$('#editUaPostion').val(jason['ua_position']);
|
||||
$('#editGender').val(jason['gender']);
|
||||
$("input[name='user_id']").val(jason.user_id);
|
||||
|
||||
}
|
||||
//$('#editUserModal').modal('show');
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
$('#errorArea').text(error);
|
||||
$('#errorArea').text(error);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
$('.viewUserBtn').click(function(evnt){
|
||||
evnt.preventDefault();
|
||||
var selectedUserId = $(this).siblings('.userIdinput').val();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('user_id', selectedUserId);
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/users/' + selectedUserId,
|
||||
type: 'GET',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function() {
|
||||
$('#viewSuccessArea').text("");
|
||||
$('#viewErrorArea').text("Please wait ... loading user details!");
|
||||
},
|
||||
success: function(data) {
|
||||
var jason = data.data;
|
||||
if(data.success == true){
|
||||
var allowedAppsArray = [];
|
||||
if (jason['allowed_apps']) {
|
||||
allowedAppsArray = jason['allowed_apps'].split(",");
|
||||
}
|
||||
console.log(jason['full_name']);
|
||||
$('#viewFullName').val(jason['full_name']);
|
||||
$('#viewEmail').val(jason['email']);
|
||||
$('#viewUsername').val(jason['username']);
|
||||
$('#viewGender').val(jason['gender']);
|
||||
$('#viewTitle').val(jason['title']);
|
||||
$('#viewUaPostion').val(jason['ua_position']);
|
||||
$('#viewPhone').val(jason['phone']);
|
||||
$('#viewAllowedApps').val(allowedAppsArray).trigger('change');
|
||||
$('#viewRegionID').val(jason['region_id']);
|
||||
$('#viewDistrictId').val(jason['district_id']);
|
||||
$('#viewUaPostion').val(jason['ua_position']);
|
||||
$('#viewGender').val(jason['gender']);
|
||||
$("input[name='user_id']").val(jason.ua_id);
|
||||
|
||||
}
|
||||
//$('#editUserModal').modal('show');
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
$('#errorArea').text(error);
|
||||
$('#errorArea').text(error);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$("#newUserFormXX").submit(function(evt){
|
||||
evt.preventDefault();
|
||||
$('#newUserSuccessArea').addClass('d-none');
|
||||
$('#newUserErrorsArea').removeClass('d-none');
|
||||
var formData = new FormData($(this)[0]);
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/users',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function() {
|
||||
$('#newUserSuccessArea').text("");
|
||||
$('#newUserSuccessArea').text("Please wait ... user creation in progress!");
|
||||
},
|
||||
success: function(data) {
|
||||
console.log(data);
|
||||
if (data['success'] == true) {
|
||||
$('#newUserSuccessArea').removeClass('d-none');
|
||||
$('#newUserErrorsArea').addClass('d-none');
|
||||
|
||||
$('#newUserSuccessArea').text("");
|
||||
$('#newUserSuccessArea').text("User successfully created!");
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: 'User successfully created!',
|
||||
});
|
||||
setTimeout(function() {
|
||||
location.reload(); // Reloads the current page
|
||||
}, 2000);
|
||||
}
|
||||
else{
|
||||
$('#newUserSuccessArea').addClass('d-none');
|
||||
$('#newUserErrorsArea').removeClass('d-none');
|
||||
$('#newUserErrorsArea').text("");
|
||||
$('#newUserErrorsArea').text(data['msg']);
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: data['msg'],
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
$('#newUserSuccessArea').text(error);
|
||||
$('#newUserSuccessArea').text(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#newUserForm").on("submit", function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
const $successArea = $("#newUserSuccessArea");
|
||||
const $errorArea = $("#newUserErrorArea"); // corrected selector
|
||||
const formData = new FormData(this);
|
||||
|
||||
// Example validation rules
|
||||
const username = $("#username").val().trim();
|
||||
const email = $("#email").val().trim();
|
||||
const fullName = $("#fullName").val().trim();
|
||||
|
||||
let errors = [];
|
||||
|
||||
if (!fullName) {
|
||||
errors.push("Full name is required.");
|
||||
}
|
||||
if (!username) {
|
||||
errors.push("Username is required.");
|
||||
}
|
||||
if (!email) {
|
||||
errors.push("Email is required.");
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
errors.push("Invalid email format.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
$successArea.addClass("d-none").text("");
|
||||
$errorArea.removeClass("d-none").html(errors.join("<br>"));
|
||||
return; // Stop submission if validation fails
|
||||
}
|
||||
|
||||
// Reset messages
|
||||
$successArea.addClass("d-none").text("");
|
||||
$errorArea.addClass("d-none").text("");
|
||||
|
||||
$.ajax({
|
||||
url: base_url + "/users",
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function () {
|
||||
$successArea.removeClass("d-none").text("Please wait ... user creation in progress!");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.success) {
|
||||
$successArea.removeClass("d-none").text("User successfully created!");
|
||||
$errorArea.addClass("d-none");
|
||||
|
||||
$.alert({
|
||||
title: "Success",
|
||||
content: "User successfully created!",
|
||||
});
|
||||
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
} else {
|
||||
$successArea.addClass("d-none");
|
||||
$errorArea.removeClass("d-none").text(data.msg || "An error occurred.");
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: data.msg || "An error occurred.",
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
$successArea.addClass("d-none");
|
||||
$errorArea.removeClass("d-none").text("Request failed: " + error);
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: "Request failed: " + error,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
$("#editUserForm").submit(function(evt){
|
||||
evt.preventDefault();
|
||||
$('#successArea').addClass('d-none');
|
||||
$('#errorsArea').removeClass('d-none');
|
||||
var formData = new FormData($(this)[0]);
|
||||
$.ajax({
|
||||
url: base_url + '/userupdate',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
dataType: 'json',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function() {
|
||||
// $('#updateBtn').addClass('d-none');
|
||||
// $('#uodateProgressBtn').removeClass('d-none');
|
||||
// $('#updateResultsDiv').removeClass('d-none');
|
||||
// $('#updateResultsParagraph').text("Processing Please wait ...");
|
||||
},
|
||||
success: function(data) {
|
||||
console.log(data);
|
||||
console.log(data['success']);
|
||||
if (data['success'] == true) {
|
||||
$('#editSuccessArea').removeClass('d-none');
|
||||
$('#editErrorArea').addClass('d-none');
|
||||
$('#editSuccessArea').text("");
|
||||
$('#editSuccessArea').text("User successfully details updated!");
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: 'User successfully details updated!!',
|
||||
});
|
||||
}
|
||||
else{
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
$('#editErrorArea').text(data['msg']);
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: data['msg'],
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
$('#editErrorArea').text(error);
|
||||
// location.reload();
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#regionID').change(function(){
|
||||
var options = $('#districtID');
|
||||
var region_id = $('#regionID').val();
|
||||
// $.get( base_url + '/admin/districts/' + region_id, function (data) {
|
||||
// $('#districtID').empty();
|
||||
// $.each(data['districts'], function(id, row) {
|
||||
// $('#districtID').append($("<option />").val(row.districtid).text(row.district_name));
|
||||
// });
|
||||
// });
|
||||
$.ajax({
|
||||
url: base_url + '/admin/districts/' + region_id,
|
||||
type: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'crossDomain': false
|
||||
},
|
||||
success: function(data) {
|
||||
// console.log('Success:', data);
|
||||
$.each(data['districts'], function(id, row) {
|
||||
// console.log(row);
|
||||
$('#districtID').append($("<option />").val(row.districtid).text(row.district_name));
|
||||
});
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,6 +1,166 @@
|
||||
$(document).ready(function(){
|
||||
$(document).ready(function () {
|
||||
|
||||
var selectedValue = $("input[name='user_type']:checked").val();
|
||||
let iti;
|
||||
|
||||
$('#addUserModal').on('shown.bs.modal', function () {
|
||||
const phoneInput = document.querySelector("#addUserphone");
|
||||
if (!iti) {
|
||||
iti = window.intlTelInput(phoneInput, {
|
||||
initialCountry: "gh",
|
||||
onlyCountries: ["gh"],
|
||||
nationalMode: false,
|
||||
autoPlaceholder: "polite",
|
||||
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"
|
||||
});
|
||||
|
||||
phoneInput.addEventListener("blur", function () {
|
||||
let rawNumber = phoneInput.value.trim();
|
||||
|
||||
if (rawNumber.startsWith("0")) {
|
||||
rawNumber = rawNumber.substring(1);
|
||||
phoneInput.value = "+233" + rawNumber;
|
||||
}
|
||||
if (iti.isValidNumber()) {
|
||||
phoneInput.value = iti.getNumber(); // clean +233XXXXXXXXX
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
$('#editUserModal').on('shown.bs.modal', function () {
|
||||
const phoneInput = document.querySelector("#editPhone");
|
||||
if (!iti) {
|
||||
iti = window.intlTelInput(phoneInput, {
|
||||
initialCountry: "gh",
|
||||
onlyCountries: ["gh"],
|
||||
nationalMode: false,
|
||||
autoPlaceholder: "polite",
|
||||
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"
|
||||
});
|
||||
|
||||
phoneInput.addEventListener("blur", function () {
|
||||
let rawNumber = phoneInput.value.trim();
|
||||
|
||||
if (rawNumber.startsWith("0")) {
|
||||
rawNumber = rawNumber.substring(1);
|
||||
phoneInput.value = "+233" + rawNumber;
|
||||
}
|
||||
if (iti.isValidNumber()) {
|
||||
phoneInput.value = iti.getNumber(); // clean +233XXXXXXXXX
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#newUserForm").on("submit", function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
const $successArea = $("#newUserSuccessArea");
|
||||
const $errorArea = $("#newUserErrorArea");
|
||||
const formData = new FormData(this);
|
||||
|
||||
let errors = [];
|
||||
if (iti) {
|
||||
let formattedPhone = iti.getNumber(); // +233XXXXXXXXX
|
||||
if (formattedPhone.startsWith("+2330")) {
|
||||
formattedPhone = "233" + formattedPhone.substring(5);
|
||||
}
|
||||
|
||||
if (!iti.isValidNumber()) {
|
||||
errors.push("Please enter a valid Ghana phone number.");
|
||||
} else {
|
||||
formData.set("phone", formattedPhone);
|
||||
}
|
||||
}
|
||||
const username = $("#addUserUsername").val().trim();
|
||||
const email = $("#addUserEmail").val().trim();
|
||||
const fullName = $("#addUserFullname").val().trim();
|
||||
|
||||
if (!fullName) errors.push("Full name is required.");
|
||||
if (!username) errors.push("Username is required.");
|
||||
if (!email) {
|
||||
errors.push("Email is required.");
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
errors.push("Invalid email format.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
$successArea.addClass("d-none").text("");
|
||||
$errorArea.removeClass("d-none").html(errors.join("<br>"));
|
||||
return;
|
||||
}
|
||||
|
||||
$successArea.addClass("d-none").text("");
|
||||
$errorArea.addClass("d-none").text("");
|
||||
|
||||
$.ajax({
|
||||
url: base_url + "/users",
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
|
||||
},
|
||||
beforeSend: function () {
|
||||
$successArea.removeClass("d-none").text("Please wait ... user creation in progress!");
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.success) {
|
||||
$successArea.removeClass("d-none").text("User successfully created!");
|
||||
$errorArea.addClass("d-none");
|
||||
|
||||
$.alert({
|
||||
title: "Success",
|
||||
content: "User successfully created!",
|
||||
});
|
||||
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
} else {
|
||||
$successArea.addClass("d-none");
|
||||
$errorArea.removeClass("d-none").text(data.msg || "An error occurred.");
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: data.msg || "An error occurred.",
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
$successArea.addClass("d-none");
|
||||
|
||||
if (xhr.status === 422) {
|
||||
// Laravel validation error
|
||||
let errors = xhr.responseJSON.errors;
|
||||
let messages = [];
|
||||
|
||||
$.each(errors, function (field, msgs) {
|
||||
messages.push(msgs.join("<br>"));
|
||||
});
|
||||
|
||||
$errorArea.removeClass("d-none").html(messages.join("<br>"));
|
||||
|
||||
$.alert({
|
||||
title: "Validation Error",
|
||||
content: messages.join("<br>"),
|
||||
});
|
||||
} else {
|
||||
// Other errors
|
||||
$errorArea.removeClass("d-none").text("Request failed: " + error);
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: "Request failed: " + error,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
var selectedValue = $("input[name='user_type']:checked").val();
|
||||
const $dropdown = $("#uaPostionAdd");
|
||||
|
||||
const optionsMap = {
|
||||
@ -39,16 +199,25 @@ $(document).ready(function(){
|
||||
$('#regionID').prop('disabled', false);
|
||||
$('#districtID').prop('disabled', false);
|
||||
|
||||
$('#editRegionID').prop('disabled', false);
|
||||
$('#editdistrictID').prop('disabled', false);
|
||||
|
||||
}
|
||||
if (userValue == 'national_luspa') {
|
||||
$('#regionID').prop('disabled', true);
|
||||
$('#districtID').prop('disabled', true);
|
||||
|
||||
$('#editRegionID').prop('disabled', true);
|
||||
$('#editdistrictID').prop('disabled', true);
|
||||
|
||||
}
|
||||
if (userValue == 'regional_luspa') {
|
||||
$('#districtID').prop('disabled', true);
|
||||
$('#regionID').prop('disabled', false);
|
||||
|
||||
$('#editdistrictID').prop('disabled', true);
|
||||
$('#editRegionID').prop('disabled', false);
|
||||
|
||||
}
|
||||
|
||||
$dropdown.empty();
|
||||
@ -187,62 +356,48 @@ $(document).ready(function(){
|
||||
|
||||
});
|
||||
|
||||
$("#newUserForm").submit(function(evt){
|
||||
|
||||
$("#editForm").submit(function(evt){
|
||||
evt.preventDefault();
|
||||
$('#successArea').addClass('d-none');
|
||||
$('#errorsArea').removeClass('d-none');
|
||||
var formData = new FormData($(this)[0]);
|
||||
const $successArea = $("#editSuccessArea");
|
||||
const $errorArea = $("#editErrorArea");
|
||||
$('#editSuccessArea').addClass('d-none');
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/users',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function() {
|
||||
$('#successArea').text("");
|
||||
$('#successArea').text("Please wait ... user creation in progress!");
|
||||
},
|
||||
success: function(data) {
|
||||
|
||||
if (data['success'] == true) {
|
||||
$('#successArea').removeClass('d-none');
|
||||
$('#errorsArea').addClass('d-none');
|
||||
|
||||
$('#successArea').text("");
|
||||
$('#successArea').text("User successfully created!");
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: 'User successfully created!',
|
||||
});
|
||||
setTimeout(function() {
|
||||
location.reload(); // Reloads the current page
|
||||
}, 2000);
|
||||
}
|
||||
else{
|
||||
$('#successArea').addClass('d-none');
|
||||
$('#errorArea').removeClass('d-none');
|
||||
$('#errorArea').text("");
|
||||
$('#errorArea').text("User could not be created!");
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: 'User could not be created!',
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
$('#successArea').text(error);
|
||||
$('#successArea').text(error);
|
||||
const formData = new FormData($(this)[0]);
|
||||
// formData = new FormData(this);
|
||||
let errors = [];
|
||||
if (iti) {
|
||||
let formattedPhone = iti.getNumber(); // +233XXXXXXXXX
|
||||
if (formattedPhone.startsWith("+2330")) {
|
||||
formattedPhone = "233" + formattedPhone.substring(5);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#editUserForm").submit(function(evt){
|
||||
evt.preventDefault();
|
||||
$('#successArea').addClass('d-none');
|
||||
$('#errorsArea').removeClass('d-none');
|
||||
var formData = new FormData($(this)[0]);
|
||||
if (!iti.isValidNumber()) {
|
||||
errors.push("001 | Please enter a valid Ghana phone number.");
|
||||
} else {
|
||||
formData.set("phone", formattedPhone);
|
||||
}
|
||||
}
|
||||
const username = $("#editUsername").val().trim();
|
||||
const email = $("#editEmail").val().trim();
|
||||
const fullName = $("#editFullName").val().trim();
|
||||
|
||||
if (!fullName) errors.push("Full name is required.");
|
||||
if (!username) errors.push("Username is required.");
|
||||
if (!email) {
|
||||
errors.push("Email is required.");
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
errors.push("Invalid email format.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
$('#editSuccessArea').addClass("d-none").text("");
|
||||
$errorArea.removeClass("d-none").html(errors.join("<br>"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/userupdate',
|
||||
type: 'POST',
|
||||
@ -268,6 +423,7 @@ $(document).ready(function(){
|
||||
title: 'Alert!',
|
||||
content: 'User successfully details updated!!',
|
||||
});
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
}
|
||||
else{
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
@ -282,41 +438,56 @@ $(document).ready(function(){
|
||||
console.error('Error:', error);
|
||||
$('#editErrorArea').removeClass('d-none');
|
||||
$('#editErrorArea').text(error);
|
||||
// location.reload();
|
||||
$.alert({
|
||||
title: 'Alert!',
|
||||
content: error,
|
||||
});
|
||||
|
||||
if (xhr.status === 422) {
|
||||
// Laravel validation error
|
||||
let errors = xhr.responseJSON.errors;
|
||||
let messages = [];
|
||||
|
||||
$.each(errors, function (field, msgs) {
|
||||
messages.push(msgs.join("<br>"));
|
||||
});
|
||||
|
||||
$('#editErrorArea').removeClass("d-none").html(messages.join("<br>"));
|
||||
|
||||
$.alert({
|
||||
title: "Validation Error",
|
||||
content: messages.join("<br>"),
|
||||
});
|
||||
} else {
|
||||
$('#editErrorArea').removeClass('d-none').text("Request failed : " + error);
|
||||
|
||||
$.alert({
|
||||
title: "Error",
|
||||
content: "Request failed: " + error,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#regionID').change(function(){
|
||||
var options = $('#districtID');
|
||||
var region_id = $('#regionID').val();
|
||||
// $.get( base_url + '/admin/districts/' + region_id, function (data) {
|
||||
// $('#districtID').empty();
|
||||
// $.each(data['districts'], function(id, row) {
|
||||
// $('#districtID').append($("<option />").val(row.districtid).text(row.district_name));
|
||||
// });
|
||||
// });
|
||||
$.ajax({
|
||||
url: base_url + '/admin/districts/' + region_id,
|
||||
type: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'crossDomain': false
|
||||
},
|
||||
success: function(data) {
|
||||
// console.log('Success:', data);
|
||||
$.each(data['districts'], function(id, row) {
|
||||
// console.log(row);
|
||||
$('#districtID').append($("<option />").val(row.districtid).text(row.district_name));
|
||||
});
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
});
|
||||
console.log('change is coming');
|
||||
var options = $('#districtID');
|
||||
var region_id = $('#regionID').val();
|
||||
$.ajax({
|
||||
url: base_url + '/admin/districts/' + region_id,
|
||||
type: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'crossDomain': false
|
||||
},
|
||||
success: function(data) {
|
||||
$.each(data['districts'], function(id, row) {
|
||||
$('#districtID').append($("<option />").val(row.districtid).text(row.district_name));
|
||||
});
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
@ -56,9 +56,7 @@
|
||||
<input type="hidden" class="userIdinput" value="<?php echo $row['user_id'] ?>" name="userId">
|
||||
|
||||
<!-- <a href="" data-bs-toggle="modal" data-bs-target="#viewUserModal" ><img src="{{ url('public/assets/libs/bootstrap-icons/eye.svg') }}" alt="view icon" width="16" height="16"></a> -->
|
||||
<a href="" class="editUserBtn" data-bs-toggle="modal" data-bs-target="#editUserModal">
|
||||
<img src="{{ url('public/assets/libs/bootstrap-icons/pencil-square.svg') }}" alt="edit icon" width="16" height="16">
|
||||
</a>
|
||||
<a href="" class="editUserBtn" data-bs-toggle="modal" data-bs-target="#editUserModal"> <i class="bi bi-pen"></i> </a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
@ -78,9 +76,7 @@
|
||||
|
||||
@endsection
|
||||
@section('page-js')
|
||||
<script src="{{ url('public/assets/libs/select2/dist/js/select2.full.min.js') }}" type="text/javascript" ></script>
|
||||
<script src="{{ url('public/assets/libs/select2/dist/js/select2.full.min.js') }}" type="text/javascript" ></script>
|
||||
<script src="{{ url('public/assets/js/usermgt.js') }}" type="text/javascript" ></script>
|
||||
|
||||
|
||||
|
||||
@endsection
|
||||
|
||||
286
php_code/resources/views/admin/home_cats.blade.php
Normal file
286
php_code/resources/views/admin/home_cats.blade.php
Normal file
@ -0,0 +1,286 @@
|
||||
@extends('layouts.master')
|
||||
@section('page-title')
|
||||
Admin | {{ $page_title }}
|
||||
@endsection
|
||||
@section('page-css')
|
||||
<?php //dd(session('current_user')); ?>
|
||||
<link rel="stylesheet" href="{{ url('public/assets/libs/select2/dist/css/select2.css') }}">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/css/intlTelInput.css"/>
|
||||
|
||||
<style>
|
||||
/* Small visual tweaks */
|
||||
.category-badge { font-weight: 600; color: #fff; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: .8rem; }
|
||||
.badge-district { background:#f6a623; } /* amber */
|
||||
.badge-regional { background:#17a2b8; } /* teal */
|
||||
.badge-national { background:#1f6feb; } /* blue */
|
||||
.user-row:hover .row-actions { visibility: visible; opacity: 1; }
|
||||
.row-actions { visibility: hidden; opacity: 0; transition: .12s ease-in-out; }
|
||||
.table-avatar { width:36px; height:36px; object-fit:cover; border-radius:50%; }
|
||||
.toolbar { gap:.5rem; display:flex; flex-wrap:wrap; align-items:center; }
|
||||
.select-all { margin-right:.5rem; }
|
||||
.status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; }
|
||||
.status-active { background-color: #28a745; }
|
||||
.status-suspended { background-color: #dc3545; }
|
||||
@media (max-width: 767px) {
|
||||
.toolbar { flex-direction:column; align-items:stretch; }
|
||||
}
|
||||
</style>
|
||||
@endsection
|
||||
|
||||
@section('page-content')
|
||||
@include('admin.partials.create-user')
|
||||
@include('admin.partials.edit-user')
|
||||
@include('admin.partials.edit-modal')
|
||||
@include('admin.partials.move-user')
|
||||
@include('admin.partials.view-user')
|
||||
@include('layouts.partials.navbar')
|
||||
<div class="container py-4">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<h3 class="me-auto">Manage Users</h3>
|
||||
<div class="text-muted">{{ \Carbon\Carbon::now()->format('F j, Y') }}</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs mb-3" id="userTabs" role="tablist">
|
||||
|
||||
@if (in_array(session('current_user.user_type'), ['district_user', 'regional_luspa', 'national_luspa']))
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="district-tab" data-bs-toggle="tab" data-bs-target="#district" type="button" role="tab" aria-controls="district" aria-selected="true">
|
||||
District <span class="badge bg-secondary ms-1" id="count-district">{{ $districtUsers->count() }}</span>
|
||||
</button>
|
||||
</li>
|
||||
@endif
|
||||
@if (in_array(session('current_user.user_type'), ['regional_luspa', 'national_luspa']))
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="regional-tab" data-bs-toggle="tab" data-bs-target="#regional" type="button" role="tab" aria-controls="regional" aria-selected="false">
|
||||
Regional <?php //dump($regionalUsers->count()); ?> <span class="badge bg-secondary ms-1" id="count-regional">{{ $regionalUsers->count() }}</span>
|
||||
</button>
|
||||
</li>
|
||||
@endif
|
||||
@if (in_array(session('current_user.user_type'), ['national_luspa']))
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="national-tab" data-bs-toggle="tab" data-bs-target="#national" type="button" role="tab" aria-controls="national" aria-selected="false">
|
||||
National <span class="badge bg-secondary ms-1" id="count-national">{{ $nationalUsers->count() }}</span>
|
||||
</button>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="toolbar">
|
||||
<div class="input-group" style="max-width:420px;">
|
||||
<span class="input-group-text">Search</span>
|
||||
<input id="globalSearch" class="form-control" type="search" placeholder="Search name, email" aria-label="Search users">
|
||||
</div>
|
||||
|
||||
<div class="ms-auto d-flex gap-2">
|
||||
<!-- <button id="addUserBtn" class="btn btn-outline-success" disabled>Add User</button> -->
|
||||
<button type="button" class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
||||
<i class="bi bi-plus-circle"></i> Add User</button>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Filter</button>
|
||||
<ul class="dropdown-menu p-3" style="min-width:220px;">
|
||||
<div class="mb-2"><strong>Status</strong></div>
|
||||
<div class="form-check"><input class="form-check-input filter-status" type="checkbox" value="Active" id="fActive"><label class="form-check-label" for="fActive">Active</label></div>
|
||||
<div class="form-check"><input class="form-check-input filter-status" type="checkbox" value="Inactive" id="fSuspended"><label class="form-check-label" for="fSuspended">Inactive</label></div>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button id="bulkMoveBtn" class="btn btn-primary" disabled data-bs-toggle="modal" data-bs-target="#moveModal">Move</button>
|
||||
<button id="bulkDeactivate" class="btn btn-outline-danger" disabled>Suspend</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="userTabsContent">
|
||||
@if (in_array(session('current_user.user_type'), ['district_user', 'regional_luspa', 'national_luspa']))
|
||||
<div class="tab-pane fade show active" id="district" role="tabpanel" aria-labelledby="district-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Status</th>
|
||||
<th>Allowed Apps</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="districtBody">
|
||||
@forelse($districtUsers as $user)
|
||||
@php $status = ($user['is_disabled'] === 'true' || $user['is_disabled'] === true) ? 'Inactive' : 'Active'; @endphp
|
||||
<tr class="user-row" data-category="District" data-status="{{ $status }}" data-name="{{ $user['full_name'] }}" data-email="{{ $user['email'] }}" data-id="{{ $user['user_id'] }}">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select {{ $user['full_name'] }}"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://ui-avatars.com/api/?name={{ urlencode($user['full_name']) }}&background=random" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">{{ $user['title'] }} {{ $user['full_name'] }}</div>
|
||||
<div class="text-muted small">Username: {{ $user['username'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">{{ $user['email'] }}</td>
|
||||
<td class="align-middle"><span class="category-badge badge-district">{{ ucwords(str_replace('-', ' ', $user['ua_position'])) }}</span></td>
|
||||
<td class="align-middle">
|
||||
<span class="status-dot {{ $status === 'Active' ? 'status-active' : 'status-suspended' }}"></span> {{ $status }}
|
||||
</td>
|
||||
<td class="align-middle">{{ $user['allowed_apps'] }}</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="{{ $user['user_id'] }}"><i class="bi bi-eye"></i> View</button>
|
||||
<button class="btn btn-sm btn-primary editUserBtn" data-id="{{ $user['user_id'] }}" data-bs-toggle="modal" data-bs-target="#editUserModal"><i class="bi bi-pen"></i> Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="7" class="text-center text-muted py-4">No district users found.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="district-pagination" class="mt-3 px-3 pb-3">
|
||||
{{ $districtUsers->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if (in_array(session('current_user.user_type'), ['regional_luspa', 'national_luspa']))
|
||||
<div class="tab-pane fade" id="regional" role="tabpanel" aria-labelledby="regional-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Person</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Status</th>
|
||||
<th>Allowed Apps</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="regionalBody">
|
||||
@forelse($regionalUsers as $user)
|
||||
<?php ?>
|
||||
@php $status = ($user['is_disabled'] === 'true' || $user['is_disabled'] === true) ? 'Inactive' : 'Active'; @endphp
|
||||
<tr class="user-row" data-category="Regional" data-status="{{ $status }}" data-name="{{ $user['full_name'] }}" data-email="{{ $user['email'] }}" data-id="{{ $user['user_id'] }}">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select {{ $user['full_name'] }}"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://ui-avatars.com/api/?name={{ urlencode($user['full_name']) }}&background=random" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">{{ $user['title'] }} {{ $user['full_name'] }}</div>
|
||||
<div class="text-muted small">Username: {{ $user['username'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">{{ $user['email'] }}</td>
|
||||
<td class="align-middle"><span class="category-badge badge-regional">{{ ucwords(str_replace('-', ' ', $user['ua_position'])) }}</span></td>
|
||||
<td class="align-middle">
|
||||
<span class="status-dot {{ $status === 'Active' ? 'status-active' : 'status-suspended' }}"></span> {{ $status }}
|
||||
</td>
|
||||
<td class="align-middle">{{ $user['allowed_apps'] }}</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="{{ $user['user_id'] }}"><i class="bi-eye"></i> View</button>
|
||||
<button class="btn btn-sm btn-primary editUserBtn" data-id="{{ $user['user_id'] }}" data-bs-toggle="modal" data-bs-target="#editUserModal"><i class="bi-pen"></i> Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="7" class="text-center text-muted py-4">No regional users found.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="regional-pagination" class="mt-3 px-3 pb-3">
|
||||
{{ $regionalUsers->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if (in_array(session('current_user.user_type'), ['district_user', 'regional_luspa', 'national_luspa']))
|
||||
<div class="tab-pane fade" id="national" role="tabpanel" aria-labelledby="national-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Person</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Status</th>
|
||||
<th>Allowed Apps</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="nationalBody">
|
||||
@forelse($nationalUsers as $user)
|
||||
@php $status = ($user['is_disabled'] === 'true' || $user['is_disabled'] === true) ? 'Inactive' : 'Active'; @endphp
|
||||
<tr class="user-row" data-category="National" data-status="{{ $status }}" data-name="{{ $user['full_name'] }}" data-email="{{ $user['email'] }}" data-id="{{ $user['user_id'] }}">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select {{ $user['full_name'] }}"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://ui-avatars.com/api/?name={{ urlencode($user['full_name']) }}&background=random" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">{{ $user['title'] }} {{ $user['full_name'] }}</div>
|
||||
<div class="text-muted small">Username: {{ $user['username'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">{{ $user['email'] }}</td>
|
||||
<td class="align-middle"><span class="category-badge badge-national">{{ ucwords(str_replace('-', ' ', $user['ua_position'])) }}</span></td>
|
||||
<td class="align-middle">
|
||||
<span class="status-dot {{ $status === 'Active' ? 'status-active' : 'status-suspended' }}"></span> {{ $status }}
|
||||
</td>
|
||||
<td class="align-middle">{{ $user['allowed_apps'] }}</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="{{ $user['user_id'] }}"><i class="bi bi-eye"></i> View</button>
|
||||
<button class="btn btn-sm btn-primary editUserBtn" data-id="{{ $user['user_id'] }}" data-bs-toggle="modal" data-bs-target="#editUserModal"><i class="bi bi-eye"></i> Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="7" class="text-center text-muted py-4">No national users found.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="national-pagination" class="mt-3 px-3 pb-3">
|
||||
{{ $nationalUsers->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@endsection
|
||||
|
||||
@section('page-js')
|
||||
<script src="{{ url('public/assets/libs/select2/dist/js/select2.full.min.js') }}" type="text/javascript"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/intlTelInput.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"></script>
|
||||
|
||||
<script src="{{ url('public/assets/js/user_mgt_new.js') }}" type="text/javascript"></script>
|
||||
<script src="{{ url('public/assets/js/add_edit_user.js') }}" type="text/javascript"></script>
|
||||
@endsection
|
||||
534
php_code/resources/views/admin/home_new.blade copy.php
Normal file
534
php_code/resources/views/admin/home_new.blade copy.php
Normal file
@ -0,0 +1,534 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Admin — Users by Category</title>
|
||||
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
/* Small visual tweaks */
|
||||
.category-badge { font-weight: 600; color: #fff; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: .8rem; }
|
||||
.badge-district { background:#f6a623; } /* amber */
|
||||
.badge-regional { background:#17a2b8; } /* teal */
|
||||
.badge-national { background:#1f6feb; } /* blue */
|
||||
.user-row:hover .row-actions { visibility: visible; opacity: 1; }
|
||||
.row-actions { visibility: hidden; opacity: 0; transition: .12s ease-in-out; }
|
||||
.table-avatar { width:36px; height:36px; object-fit:cover; border-radius:50%; }
|
||||
.toolbar { gap:.5rem; display:flex; flex-wrap:wrap; align-items:center; }
|
||||
.select-all { margin-right:.5rem; }
|
||||
@media (max-width: 767px) {
|
||||
.toolbar { flex-direction:column; align-items:stretch; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
|
||||
<div class="container py-4">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<h3 class="me-auto">Manage Users</h3>
|
||||
<div class="text-muted">June 15, 2026</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<ul class="nav nav-tabs mb-3" id="userTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="district-tab" data-bs-toggle="tab" data-bs-target="#district" type="button" role="tab" aria-controls="district" aria-selected="true">District <span class="badge bg-secondary ms-1" id="count-district">3</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="regional-tab" data-bs-toggle="tab" data-bs-target="#regional" type="button" role="tab" aria-controls="regional" aria-selected="false">Regional <span class="badge bg-secondary ms-1" id="count-regional">2</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="national-tab" data-bs-toggle="tab" data-bs-target="#national" type="button" role="tab" aria-controls="national" aria-selected="false">National <span class="badge bg-secondary ms-1" id="count-national">1</span></button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Shared toolbar -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="toolbar">
|
||||
<div class="d-flex align-items-center">
|
||||
<input type="checkbox" id="globalSelect" class="form-check-input select-all" aria-label="Select all visible users">
|
||||
<div class="me-2">Select</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group" style="max-width:420px;">
|
||||
<span class="input-group-text">Search</span>
|
||||
<input id="globalSearch" class="form-control" type="search" placeholder="Search name, email or ID (press /)" aria-label="Search users">
|
||||
</div>
|
||||
|
||||
<div class="ms-auto d-flex gap-2">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Filter</button>
|
||||
<ul class="dropdown-menu p-3" style="min-width:220px;">
|
||||
<div class="mb-2"><strong>Status</strong></div>
|
||||
<div class="form-check"><input class="form-check-input filter-status" type="checkbox" value="Active" id="fActive"><label class="form-check-label" for="fActive">Active</label></div>
|
||||
<div class="form-check"><input class="form-check-input filter-status" type="checkbox" value="Pending" id="fPending"><label class="form-check-label" for="fPending">Pending</label></div>
|
||||
<div class="form-check"><input class="form-check-input filter-status" type="checkbox" value="Suspended" id="fSuspended"><label class="form-check-label" for="fSuspended">Suspended</label></div>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button id="bulkMoveBtn" class="btn btn-primary" disabled data-bs-toggle="modal" data-bs-target="#moveModal">Move</button>
|
||||
<button id="bulkDeactivate" class="btn btn-outline-danger" disabled>Deactivate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab content -->
|
||||
<div class="tab-content" id="userTabsContent">
|
||||
<!-- District -->
|
||||
<div class="tab-pane fade show active" id="district" role="tabpanel" aria-labelledby="district-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Person</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Last activity</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="districtBody">
|
||||
<!-- Example rows -->
|
||||
<tr class="user-row" data-category="District" data-status="Active" data-name="Alice Doe" data-email="alice@example.com" data-id="D001">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Alice Doe"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=5" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Alice Doe</div>
|
||||
<div class="text-muted small">ID: D001</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">alice@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-district">District</span> · Manager</td>
|
||||
<td class="align-middle">2026-06-10</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="D001">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="D001" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="user-row" data-category="District" data-status="Pending" data-name="Ben Kyle" data-email="ben@example.com" data-id="D002">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Ben Kyle"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=6" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Ben Kyle</div>
|
||||
<div class="text-muted small">ID: D002</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">ben@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-district">District</span> · Staff</td>
|
||||
<td class="align-middle">2026-06-12</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="D002">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="D002" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="user-row" data-category="District" data-status="Suspended" data-name="Clara Park" data-email="clara@example.com" data-id="D003">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Clara Park"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=7" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Clara Park</div>
|
||||
<div class="text-muted small">ID: D003</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">clara@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-district">District</span> · Supervisor</td>
|
||||
<td class="align-middle">2026-05-30</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="D003">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="D003" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Regional -->
|
||||
<div class="tab-pane fade" id="regional" role="tabpanel" aria-labelledby="regional-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Person</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Last activity</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="regionalBody">
|
||||
<tr class="user-row" data-category="Regional" data-status="Active" data-name="Derek Holt" data-email="derek@example.com" data-id="R001">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Derek Holt"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=8" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Derek Holt</div>
|
||||
<div class="text-muted small">ID: R001</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">derek@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-regional">Regional</span> · Lead</td>
|
||||
<td class="align-middle">2026-06-13</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="R001">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="R001" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="user-row" data-category="Regional" data-status="Active" data-name="Eva Stone" data-email="eva@example.com" data-id="R002">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Eva Stone"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=9" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Eva Stone</div>
|
||||
<div class="text-muted small">ID: R002</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">eva@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-regional">Regional</span> · Coordinator</td>
|
||||
<td class="align-middle">2026-06-11</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="R002">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="R002" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- National -->
|
||||
<div class="tab-pane fade" id="national" role="tabpanel" aria-labelledby="national-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Person</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Last activity</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="nationalBody">
|
||||
<tr class="user-row" data-category="National" data-status="Active" data-name="Frank West" data-email="frank@example.com" data-id="N001">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Frank West"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=10" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Frank West</div>
|
||||
<div class="text-muted small">ID: N001</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">frank@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-national">National</span> · Director</td>
|
||||
<td class="align-middle">2026-06-05</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="N001">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="N001" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination stub -->
|
||||
<nav aria-label="User list pages" class="d-flex justify-content-end">
|
||||
<ul class="pagination pagination-sm">
|
||||
<li class="page-item disabled"><a class="page-link" href="#" tabindex="-1">Prev</a></li>
|
||||
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">Next</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Edit modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit user</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="editForm">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="editId">
|
||||
<div class="mb-3"><label class="form-label">Name</label><input id="editName" class="form-control"></div>
|
||||
<div class="mb-3"><label class="form-label">Email</label><input id="editEmail" class="form-control" type="email"></div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Category</label>
|
||||
<select id="editCategory" class="form-select">
|
||||
<option>District</option>
|
||||
<option>Regional</option>
|
||||
<option>National</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Move modal (bulk) -->
|
||||
<div class="modal fade" id="moveModal" tabindex="-1" aria-labelledby="moveModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="moveModalLabel">Move selected users</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="moveForm">
|
||||
<div class="modal-body">
|
||||
<p id="moveCountText">Move <span id="moveCount">0</span> users to:</p>
|
||||
<div class="mb-3">
|
||||
<select id="moveTarget" class="form-select" required>
|
||||
<option value="">Choose category</option>
|
||||
<option value="District">District</option>
|
||||
<option value="Regional">Regional</option>
|
||||
<option value="National">National</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Move</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap 5 JS + Popper + jQuery (for DOM convenience) -->
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
// keyboard shortcut to focus search
|
||||
$(document).on('keydown', function(e){
|
||||
if (e.key === '/' && !$(e.target).is('input, textarea')) {
|
||||
e.preventDefault();
|
||||
$('#globalSearch').focus().select();
|
||||
}
|
||||
});
|
||||
|
||||
// quick helpers
|
||||
function updateCounts(){
|
||||
$('#count-district').text($('#districtBody .user-row:visible').length);
|
||||
$('#count-regional').text($('#regionalBody .user-row:visible').length);
|
||||
$('#count-national').text($('#nationalBody .user-row:visible').length);
|
||||
}
|
||||
|
||||
function getVisibleRowsInActiveTab(){
|
||||
const activePane = $('.tab-pane.active');
|
||||
return activePane.find('.user-row:visible');
|
||||
}
|
||||
|
||||
// Select all visible in active tab
|
||||
$('#globalSelect').on('change', function(){
|
||||
const checked = $(this).is(':checked');
|
||||
getVisibleRowsInActiveTab().find('.row-select').prop('checked', checked).trigger('change');
|
||||
});
|
||||
|
||||
// Row checkbox toggles bulk buttons
|
||||
$(document).on('change', '.row-select', function(){
|
||||
const anyChecked = $('.tab-pane.active .row-select:checked').length > 0;
|
||||
$('#bulkMoveBtn, #bulkDeactivate').prop('disabled', !anyChecked);
|
||||
// sync header select state
|
||||
const total = getVisibleRowsInActiveTab().find('.row-select').length;
|
||||
const checked = getVisibleRowsInActiveTab().find('.row-select:checked').length;
|
||||
$('#globalSelect').prop('checked', total>0 && total===checked);
|
||||
});
|
||||
|
||||
// Search + filter (client-side demo)
|
||||
function applyFilters(){
|
||||
const q = $('#globalSearch').val().trim().toLowerCase();
|
||||
const statuses = $('.filter-status:checked').map(function(){ return this.value; }).get();
|
||||
|
||||
$('.user-row').each(function(){
|
||||
const $r = $(this);
|
||||
const name = $r.data('name').toLowerCase();
|
||||
const email = $r.data('email').toLowerCase();
|
||||
const status = $r.data('status');
|
||||
const matchesQuery = !q || name.includes(q) || email.includes(q) || $r.data('id').toLowerCase().includes(q);
|
||||
const matchesStatus = statuses.length === 0 || statuses.indexOf(status) !== -1;
|
||||
const show = matchesQuery && matchesStatus;
|
||||
$r.toggle(show);
|
||||
});
|
||||
|
||||
updateCounts();
|
||||
// reset bulk buttons if nothing selected
|
||||
$('.row-select').trigger('change');
|
||||
}
|
||||
|
||||
$('#globalSearch').on('input', applyFilters);
|
||||
$('.filter-status').on('change', applyFilters);
|
||||
|
||||
// Activate tab event -> clear globalSelect and bulk buttons
|
||||
$('button[data-bs-toggle="tab"]').on('shown.bs.tab', function(){
|
||||
$('#globalSelect').prop('checked', false);
|
||||
$('#bulkMoveBtn, #bulkDeactivate').prop('disabled', true);
|
||||
});
|
||||
|
||||
// Edit button opens modal and populates fields
|
||||
let lastTrigger = null;
|
||||
$(document).on('click', '.editBtn', function(){
|
||||
lastTrigger = this;
|
||||
const row = $(this).closest('.user-row');
|
||||
$('#editId').val(row.data('id'));
|
||||
$('#editName').val(row.data('name'));
|
||||
$('#editEmail').val(row.data('email'));
|
||||
$('#editCategory').val(row.data('category'));
|
||||
});
|
||||
|
||||
// When edit modal closes, return focus to trigger
|
||||
$('#editModal').on('hidden.bs.modal', function(){
|
||||
if (lastTrigger && document.contains(lastTrigger)) lastTrigger.focus();
|
||||
lastTrigger = null;
|
||||
});
|
||||
|
||||
// Submit edit (demo: updates row inline)
|
||||
$('#editForm').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const id = $('#editId').val();
|
||||
const name = $('#editName').val();
|
||||
const email = $('#editEmail').val();
|
||||
const category = $('#editCategory').val();
|
||||
|
||||
const row = $(`.user-row[data-id="${id}"]`);
|
||||
// Update data attrs and visible cells
|
||||
row.data('name', name).data('email', email).data('category', category);
|
||||
row.find('.fw-semibold').text(name);
|
||||
row.find('td').eq(2).text(email);
|
||||
row.find('.category-badge').removeClass('badge-district badge-regional badge-national');
|
||||
if (category === 'District') row.find('.category-badge').addClass('badge-district').text('District');
|
||||
if (category === 'Regional') row.find('.category-badge').addClass('badge-regional').text('Regional');
|
||||
if (category === 'National') row.find('.category-badge').addClass('badge-national').text('National');
|
||||
|
||||
// Move row to correct table if category changed
|
||||
const targetBody = category === 'District' ? '#districtBody' : (category === 'Regional' ? '#regionalBody' : '#nationalBody');
|
||||
if (!row.parent().is(targetBody)) {
|
||||
row.appendTo($(targetBody));
|
||||
}
|
||||
|
||||
updateCounts();
|
||||
$('#editModal').modal('hide');
|
||||
});
|
||||
|
||||
// Bulk move: gather selected rows and show count
|
||||
$('#bulkMoveBtn').on('click', function(){
|
||||
const selected = $('.tab-pane.active .row-select:checked');
|
||||
$('#moveCount').text(selected.length);
|
||||
// focus first control in modal for accessibility
|
||||
$('#moveModal').on('shown.bs.modal', function(){ $('#moveTarget').focus(); });
|
||||
});
|
||||
|
||||
// Remember trigger to restore focus after move modal
|
||||
$('#moveModal').on('hidden.bs.modal', function(){ $('.tab-pane.active').find('.row-select:checked').first().closest('tr').find('.row-select').focus(); });
|
||||
|
||||
// Execute move
|
||||
$('#moveForm').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const target = $('#moveTarget').val();
|
||||
const selected = $('.tab-pane.active .row-select:checked').closest('.user-row');
|
||||
selected.each(function(){
|
||||
const $r = $(this);
|
||||
$r.data('category', target);
|
||||
$r.find('.category-badge').removeClass('badge-district badge-regional badge-national');
|
||||
if (target === 'District') $r.find('.category-badge').addClass('badge-district').text('District');
|
||||
if (target === 'Regional') $r.find('.category-badge').addClass('badge-regional').text('Regional');
|
||||
if (target === 'National') $r.find('.category-badge').addClass('badge-national').text('National');
|
||||
const targetBody = target === 'District' ? '#districtBody' : (target === 'Regional' ? '#regionalBody' : '#nationalBody');
|
||||
$r.appendTo($(targetBody));
|
||||
$r.find('.row-select').prop('checked', false);
|
||||
});
|
||||
$('#moveModal').modal('hide');
|
||||
$('#globalSelect').prop('checked', false);
|
||||
$('#bulkMoveBtn, #bulkDeactivate').prop('disabled', true);
|
||||
updateCounts();
|
||||
});
|
||||
|
||||
// Bulk deactivate demo (toggles status)
|
||||
$('#bulkDeactivate').on('click', function(){
|
||||
const selected = $('.tab-pane.active .row-select:checked').closest('.user-row');
|
||||
selected.each(function(){
|
||||
const $r = $(this);
|
||||
$r.data('status', 'Suspended');
|
||||
// optionally add visual cue
|
||||
$r.addClass('table-danger');
|
||||
setTimeout(()=> $r.removeClass('table-danger'), 1200);
|
||||
$r.find('.row-select').prop('checked', false);
|
||||
});
|
||||
$('#bulkMoveBtn, #bulkDeactivate').prop('disabled', true);
|
||||
});
|
||||
|
||||
// Accessibility: avoid aria-hidden on focused elements (demo check)
|
||||
// If you manually toggle aria-hidden elsewhere, ensure focus is moved first.
|
||||
// Update counts initially
|
||||
updateCounts();
|
||||
|
||||
// Demo: focus first edit button when pressing 'e' (example of keyboard shortcut)
|
||||
$(document).on('keydown', function(e){
|
||||
if (e.key === 'e' && !$(e.target).is('input, textarea')) {
|
||||
e.preventDefault();
|
||||
$('.editBtn').first().focus();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
357
php_code/resources/views/admin/home_new.blade.php
Normal file
357
php_code/resources/views/admin/home_new.blade.php
Normal file
@ -0,0 +1,357 @@
|
||||
@extends('layouts.master')
|
||||
@section('page-title')
|
||||
Admin | {{ $page_title }}
|
||||
@endsection
|
||||
@section('page-css')
|
||||
<link rel="stylesheet" href="{{ url('public/assets/libs/select2/dist/css/select2.css') }}">
|
||||
<style>
|
||||
/* Small visual tweaks */
|
||||
.category-badge { font-weight: 600; color: #fff; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: .8rem; }
|
||||
.badge-district { background:#f6a623; } /* amber */
|
||||
.badge-regional { background:#17a2b8; } /* teal */
|
||||
.badge-national { background:#1f6feb; } /* blue */
|
||||
.user-row:hover .row-actions { visibility: visible; opacity: 1; }
|
||||
.row-actions { visibility: hidden; opacity: 0; transition: .12s ease-in-out; }
|
||||
.table-avatar { width:36px; height:36px; object-fit:cover; border-radius:50%; }
|
||||
.toolbar { gap:.5rem; display:flex; flex-wrap:wrap; align-items:center; }
|
||||
.select-all { margin-right:.5rem; }
|
||||
@media (max-width: 767px) {
|
||||
.toolbar { flex-direction:column; align-items:stretch; }
|
||||
}
|
||||
</style>
|
||||
@endsection
|
||||
@section('page-content')
|
||||
<div class="container py-4">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<h3 class="me-auto">Manage Users</h3>
|
||||
<div class="text-muted">June 15, 2026</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<ul class="nav nav-tabs mb-3" id="userTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="district-tab" data-bs-toggle="tab" data-bs-target="#district" type="button" role="tab" aria-controls="district" aria-selected="true">District <span class="badge bg-secondary ms-1" id="count-district">3</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="regional-tab" data-bs-toggle="tab" data-bs-target="#regional" type="button" role="tab" aria-controls="regional" aria-selected="false">Regional <span class="badge bg-secondary ms-1" id="count-regional">2</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="national-tab" data-bs-toggle="tab" data-bs-target="#national" type="button" role="tab" aria-controls="national" aria-selected="false">National <span class="badge bg-secondary ms-1" id="count-national">1</span></button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Shared toolbar -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="toolbar">
|
||||
<!-- <div class="d-flex align-items-center">
|
||||
<input type="checkbox" id="globalSelect" class="form-check-input select-all" aria-label="Select all visible users">
|
||||
<div class="me-2">Select</div>
|
||||
</div> -->
|
||||
|
||||
<div class="input-group" style="max-width:420px;">
|
||||
<span class="input-group-text">Search</span>
|
||||
<input id="globalSearch" class="form-control" type="search" placeholder="Search name, email or ID (press /)" aria-label="Search users">
|
||||
</div>
|
||||
|
||||
<div class="ms-auto d-flex gap-2">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Filter</button>
|
||||
<ul class="dropdown-menu p-3" style="min-width:220px;">
|
||||
<div class="mb-2"><strong>Status</strong></div>
|
||||
<div class="form-check"><input class="form-check-input filter-status" type="checkbox" value="Active" id="fActive"><label class="form-check-label" for="fActive">Active</label></div>
|
||||
<div class="form-check"><input class="form-check-input filter-status" type="checkbox" value="Suspended" id="fSuspended"><label class="form-check-label" for="fSuspended">Suspended</label></div>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button id="bulkMoveBtn" class="btn btn-primary" disabled data-bs-toggle="modal" data-bs-target="#moveModal">Move</button>
|
||||
<button id="bulkDeactivate" class="btn btn-outline-danger" disabled>Suspend</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab content -->
|
||||
<div class="tab-content" id="userTabsContent">
|
||||
<!-- District -->
|
||||
<div class="tab-pane fade show active" id="district" role="tabpanel" aria-labelledby="district-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Person</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Allowed Apps</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="districtBody">
|
||||
<!-- Example rows -->
|
||||
<tr class="user-row" data-category="District" data-status="Active" data-name="Alice Doe" data-email="alice@example.com" data-id="D001">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Alice Doe"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=5" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Alice Doe</div>
|
||||
<div class="text-muted small">ID: D001</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">alice@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-district">District</span> · Manager</td>
|
||||
<td class="align-middle">2026-06-10</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="D001">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="D001" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="user-row" data-category="District" data-status="Pending" data-name="Ben Kyle" data-email="ben@example.com" data-id="D002">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Ben Kyle"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=6" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Ben Kyle</div>
|
||||
<div class="text-muted small">ID: D002</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">ben@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-district">District</span> · Staff</td>
|
||||
<td class="align-middle">2026-06-12</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="D002">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="D002" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="user-row" data-category="District" data-status="Suspended" data-name="Clara Park" data-email="clara@example.com" data-id="D003">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Clara Park"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=7" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Clara Park</div>
|
||||
<div class="text-muted small">ID: D003</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">clara@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-district">District</span> · Supervisor</td>
|
||||
<td class="align-middle">2026-05-30</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="D003">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="D003" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Regional -->
|
||||
<div class="tab-pane fade" id="regional" role="tabpanel" aria-labelledby="regional-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Person</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Allowed Apps</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="regionalBody">
|
||||
<tr class="user-row" data-category="Regional" data-status="Active" data-name="Derek Holt" data-email="derek@example.com" data-id="R001">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Derek Holt"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=8" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Derek Holt</div>
|
||||
<div class="text-muted small">ID: R001</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">derek@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-regional">Regional</span> · Lead</td>
|
||||
<td class="align-middle">2026-06-13</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="R001">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="R001" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="user-row" data-category="Regional" data-status="Active" data-name="Eva Stone" data-email="eva@example.com" data-id="R002">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Eva Stone"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=9" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Eva Stone</div>
|
||||
<div class="text-muted small">ID: R002</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">eva@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-regional">Regional</span> · Coordinator</td>
|
||||
<td class="align-middle">2026-06-11</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="R002">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="R002" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- National -->
|
||||
<div class="tab-pane fade" id="national" role="tabpanel" aria-labelledby="national-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Person</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Allowed Apps</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="nationalBody">
|
||||
<tr class="user-row" data-category="National" data-status="Active" data-name="Frank West" data-email="frank@example.com" data-id="N001">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select Frank West"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://i.pravatar.cc/40?img=10" alt="avatar" class="table-avatar me-2">
|
||||
<div>
|
||||
<div class="fw-semibold">Frank West</div>
|
||||
<div class="text-muted small">ID: N001</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">frank@example.com</td>
|
||||
<td class="align-middle"><span class="category-badge badge-national">National</span> · Director</td>
|
||||
<td class="align-middle">2026-06-05</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="N001">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="N001" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination stub -->
|
||||
<nav aria-label="User list pages" class="d-flex justify-content-end">
|
||||
<ul class="pagination pagination-sm">
|
||||
<li class="page-item disabled"><a class="page-link" href="#" tabindex="-1">Prev</a></li>
|
||||
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">Next</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Edit modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit user</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="editForm">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="editId">
|
||||
<div class="mb-3"><label class="form-label">Name</label><input id="editName" class="form-control"></div>
|
||||
<div class="mb-3"><label class="form-label">Email</label><input id="editEmail" class="form-control" type="email"></div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Category</label>
|
||||
<select id="editCategory" class="form-select">
|
||||
<option>District</option>
|
||||
<option>Regional</option>
|
||||
<option>National</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Move modal (bulk) -->
|
||||
<div class="modal fade" id="moveModal" tabindex="-1" aria-labelledby="moveModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="moveModalLabel">Move selected users</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="moveForm">
|
||||
<div class="modal-body">
|
||||
<p id="moveCountText">Move <span id="moveCount">0</span> users to:</p>
|
||||
<div class="mb-3">
|
||||
<select id="moveTarget" class="form-select" required>
|
||||
<option value="">Choose category</option>
|
||||
<option value="District">District</option>
|
||||
<option value="Regional">Regional</option>
|
||||
<option value="National">National</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Move</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
@section('page-js')
|
||||
<script src="{{ url('public/assets/libs/select2/dist/js/select2.full.min.js') }}" type="text/javascript" ></script>
|
||||
<script src="{{ url('public/assets/js/user_mgt_new.js') }}" type="text/javascript" ></script>
|
||||
|
||||
|
||||
@endsection
|
||||
186
php_code/resources/views/admin/hometwo.blade copy.php
Normal file
186
php_code/resources/views/admin/hometwo.blade copy.php
Normal file
@ -0,0 +1,186 @@
|
||||
@extends('layouts.master')
|
||||
@section('content')
|
||||
<div class="container py-4">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<h3 class="me-auto">Manage Users</h3>
|
||||
<div class="text-muted">June 15, 2026</div>
|
||||
</div>
|
||||
|
||||
<!-- same HTML UI as before but with minor Blade AJAX integration -->
|
||||
<!-- ...copy the HTML from the previous snippet but remove static example rows and use empty tbody for JS to populate... -->
|
||||
|
||||
<!-- For brevity include only key changes shown below -->
|
||||
<ul class="nav nav-tabs mb-3" id="userTabs" role="tablist">
|
||||
<li class="nav-item"><button class="nav-link active" id="district-tab" data-bs-toggle="tab" data-bs-target="#district" type="button" role="tab">District <span class="badge bg-secondary ms-1" id="count-district">0</span></button></li>
|
||||
<li class="nav-item"><button class="nav-link" id="regional-tab" data-bs-toggle="tab" data-bs-target="#regional" type="button" role="tab">Regional <span class="badge bg-secondary ms-1" id="count-regional">0</span></button></li>
|
||||
<li class="nav-item"><button class="nav-link" id="national-tab" data-bs-toggle="tab" data-bs-target="#national" type="button" role="tab">National <span class="badge bg-secondary ms-1" id="count-national">0</span></button></li>
|
||||
</ul>
|
||||
|
||||
<!-- toolbar (same as earlier) -->
|
||||
<!-- tab content: empty bodies to be filled by JS -->
|
||||
<div class="tab-content" id="userTabsContent">
|
||||
<div class="tab-pane fade show active" id="district" role="tabpanel" aria-labelledby="district-tab">
|
||||
<div class="card mb-4"><div class="card-body p-0"><div class="table-responsive"><table class="table table-hover mb-0"><thead class="table-light"><tr><th style="width:42px;"></th><th>Person</th><th>Email</th><th>Role</th><th>Last activity</th><th style="width:130px;">Actions</th></tr></thead><tbody id="districtBody"></tbody></table></div></div></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="regional" role="tabpanel" aria-labelledby="regional-tab">
|
||||
<div class="card mb-4"><div class="card-body p-0"><div class="table-responsive"><table class="table table-hover mb-0"><thead class="table-light"><tr><th style="width:42px;"></th><th>Person</th><th>Email</th><th>Role</th><th>Last activity</th><th style="width:130px;">Actions</th></tr></thead><tbody id="regionalBody"></tbody></table></div></div></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="national" role="tabpanel" aria-labelledby="national-tab">
|
||||
<div class="card mb-4"><div class="card-body p-0"><div class="table-responsive"><table class="table table-hover mb-0"><thead class="table-light"><tr><th style="width:42px;"></th><th>Person</th><th>Email</th><th>Role</th><th>Last activity</th><th style="width:130px;">Actions</th></tr></thead><tbody id="nationalBody"></tbody></table></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- include modals from earlier (editModal, moveModal) unchanged -->
|
||||
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
const routes = {
|
||||
list: "{{ route('admin.users.list') }}",
|
||||
show: (id)=> "{{ url('admin/users') }}/"+id,
|
||||
update: (id)=> "{{ url('admin/users') }}/"+id,
|
||||
move: "{{ route('admin.users.move') }}",
|
||||
deactivate: "{{ route('admin.users.deactivate') }}"
|
||||
};
|
||||
|
||||
$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' } });
|
||||
|
||||
(function(){
|
||||
let currentCategory = 'District';
|
||||
|
||||
function fetchCategory(category){
|
||||
currentCategory = category;
|
||||
const params = { category, q: $('#globalSearch').val(), status: $('.filter-status:checked').map(function(){return this.value}).get() };
|
||||
$.get(routes.list, params, function(data){
|
||||
// data is a Laravel paginator; use data.data for items
|
||||
const items = data.data || data;
|
||||
const mapToTbody = { District: '#districtBody', Regional: '#regionalBody', National: '#nationalBody' };
|
||||
// clear all then populate respective bodies
|
||||
$('#districtBody,#regionalBody,#nationalBody').empty();
|
||||
items.forEach(renderRow);
|
||||
// if API returns mixed categories (when no category filter) you can instead append based on user.category
|
||||
// update counts using paginator meta if available, otherwise compute
|
||||
$('#count-district').text(items.filter(u=>u.category==='District').length);
|
||||
$('#count-regional').text(items.filter(u=>u.category==='Regional').length);
|
||||
$('#count-national').text(items.filter(u=>u.category==='National').length);
|
||||
});
|
||||
}
|
||||
|
||||
function renderRow(u){
|
||||
const badgeClass = u.category === 'District' ? 'badge-district' : (u.category === 'Regional' ? 'badge-regional' : 'badge-national');
|
||||
const row = $(`
|
||||
<tr class="user-row" data-category="${u.category}" data-status="${u.status}" data-name="${u.name}" data-email="${u.email}" data-id="${u.id}">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select ${u.name}"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="${u.avatar_url || 'https://i.pravatar.cc/40'}" alt="avatar" class="table-avatar me-2">
|
||||
<div><div class="fw-semibold">${u.name}</div><div class="text-muted small">ID: ${u.id}</div></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">${u.email}</td>
|
||||
<td class="align-middle"><span class="category-badge ${badgeClass}">${u.category}</span> · ${u.role || ''}</td>
|
||||
<td class="align-middle">${u.last_activity_at || ''}</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="${u.id}">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="${u.id}" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`);
|
||||
const body = u.category === 'District' ? '#districtBody' : (u.category === 'Regional' ? '#regionalBody' : '#nationalBody');
|
||||
$(body).append(row);
|
||||
}
|
||||
|
||||
// Initial load for all categories (no filter) — call list without category to fetch many, but for simplicity fetch per category:
|
||||
['District','Regional','National'].forEach(c => {
|
||||
$.get(routes.list, { category: c }, function(data){
|
||||
(data.data || data).forEach(renderRow);
|
||||
$('#count-'+c.toLowerCase()).text((data.meta?.total) ? data.meta.total : ( (data.data || data).length ));
|
||||
});
|
||||
});
|
||||
|
||||
// Search + filter debounce
|
||||
let t=null;
|
||||
$('#globalSearch, .filter-status').on('input change', function(){
|
||||
clearTimeout(t);
|
||||
t = setTimeout(()=> { fetchCategory(currentCategory); }, 300);
|
||||
});
|
||||
|
||||
// Edit: fetch details and populate modal
|
||||
let lastTrigger = null;
|
||||
$(document).on('click', '.editBtn', function(){
|
||||
lastTrigger = this;
|
||||
const id = $(this).data('id');
|
||||
$.get(routes.show(id), function(user){
|
||||
$('#editId').val(user.id);
|
||||
$('#editName').val(user.name);
|
||||
$('#editEmail').val(user.email);
|
||||
$('#editCategory').val(user.category);
|
||||
});
|
||||
});
|
||||
|
||||
// Update via AJAX
|
||||
$('#editForm').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const id = $('#editId').val();
|
||||
const payload = { name: $('#editName').val(), email: $('#editEmail').val(), category: $('#editCategory').val() };
|
||||
$.ajax({
|
||||
url: routes.update(id),
|
||||
method: 'PUT',
|
||||
data: payload,
|
||||
success: function(updated){
|
||||
// update DOM row if present
|
||||
const row = $(`.user-row[data-id="${updated.id}"]`);
|
||||
if (row.length){
|
||||
row.data('name', updated.name).data('email', updated.email).data('category', updated.category);
|
||||
row.find('.fw-semibold').text(updated.name);
|
||||
row.find('td').eq(2).text(updated.email);
|
||||
row.find('.category-badge').removeClass('badge-district badge-regional badge-national');
|
||||
if (updated.category === 'District') row.find('.category-badge').addClass('badge-district').text('District');
|
||||
if (updated.category === 'Regional') row.find('.category-badge').addClass('badge-regional').text('Regional');
|
||||
if (updated.category === 'National') row.find('.category-badge').addClass('badge-national').text('National');
|
||||
// move row if category changed
|
||||
const target = updated.category === 'District' ? '#districtBody' : (updated.category === 'Regional' ? '#regionalBody' : '#nationalBody');
|
||||
if (!row.parent().is(target)) row.appendTo($(target));
|
||||
} else {
|
||||
// if row wasn't loaded, re-fetch current category
|
||||
fetchCategory(currentCategory);
|
||||
}
|
||||
$('#editModal').modal('hide');
|
||||
},
|
||||
error: function(xhr){
|
||||
if (xhr.status === 422) {
|
||||
// show validation briefly (extend as needed)
|
||||
alert('Validation error');
|
||||
} else alert('Update failed');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Bulk move
|
||||
$('#moveForm').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const ids = $('.tab-pane.active .row-select:checked').closest('.user-row').map(function(){ return $(this).data('id'); }).get();
|
||||
const target = $('#moveTarget').val();
|
||||
$.post(routes.move, { ids, target }, function(res){
|
||||
// refresh current category view
|
||||
fetchCategory(currentCategory);
|
||||
$('#moveModal').modal('hide');
|
||||
}).fail(function(){ alert('Move failed'); });
|
||||
});
|
||||
|
||||
// Bulk deactivate
|
||||
$('#bulkDeactivate').on('click', function(){
|
||||
const ids = $('.tab-pane.active .row-select:checked').closest('.user-row').map(function(){ return $(this).data('id'); }).get();
|
||||
if (!ids.length) return;
|
||||
$.post(routes.deactivate, { ids }, function(res){
|
||||
fetchCategory(currentCategory);
|
||||
}).fail(function(){ alert('Deactivate failed'); });
|
||||
});
|
||||
|
||||
// Additional UX: pagination, error handling, loading states can be added.
|
||||
})();
|
||||
</script>
|
||||
@endpush
|
||||
427
php_code/resources/views/admin/hometwo.blade.php
Normal file
427
php_code/resources/views/admin/hometwo.blade.php
Normal file
@ -0,0 +1,427 @@
|
||||
@extends('layouts.master_two')
|
||||
|
||||
@section('content')
|
||||
<div class="container py-4">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<h3 class="me-auto">Manage Users</h3>
|
||||
<div class="text-muted">June 15, 2026</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<ul class="nav nav-tabs mb-3" id="userTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="district-tab" data-bs-toggle="tab" data-bs-target="#district" type="button" role="tab" aria-controls="district" aria-selected="true">District <span class="badge bg-secondary ms-1" id="count-district">0</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="regional-tab" data-bs-toggle="tab" data-bs-target="#regional" type="button" role="tab" aria-controls="regional" aria-selected="false">Regional <span class="badge bg-secondary ms-1" id="count-regional">0</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="national-tab" data-bs-toggle="tab" data-bs-target="#national" type="button" role="tab" aria-controls="national" aria-selected="false">National <span class="badge bg-secondary ms-1" id="count-national">0</span></button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Shared toolbar -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="toolbar">
|
||||
<div class="d-flex align-items-center">
|
||||
<input type="checkbox" id="globalSelect" class="form-check-input select-all" aria-label="Select all visible users">
|
||||
<div class="me-2">Select</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group" style="max-width:420px;">
|
||||
<span class="input-group-text">Search</span>
|
||||
<input id="globalSearch" class="form-control" type="search" placeholder="Search name, email or ID (press /)" aria-label="Search users">
|
||||
</div>
|
||||
|
||||
<div class="ms-auto d-flex gap-2">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Filter</button>
|
||||
<ul class="dropdown-menu p-3" style="min-width:220px;">
|
||||
<div class="mb-2"><strong>Status</strong></div>
|
||||
<div class="form-check"><input class="form-check-input filter-status" type="checkbox" value="Active" id="fActive"><label class="form-check-label" for="fActive">Active</label></div>
|
||||
<div class="form-check"><input class="form-check-input filter-status" type="checkbox" value="Pending" id="fPending"><label class="form-check-label" for="fPending">Pending</label></div>
|
||||
<div class="form-check"><input class="form-check-input filter-status" type="checkbox" value="Suspended" id="fSuspended"><label class="form-check-label" for="fSuspended">Suspended</label></div>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button id="bulkMoveBtn" class="btn btn-primary" disabled data-bs-toggle="modal" data-bs-target="#moveModal">Move</button>
|
||||
<button id="bulkDeactivate" class="btn btn-outline-danger" disabled>Deactivate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab content -->
|
||||
<div class="tab-content" id="userTabsContent">
|
||||
<!-- District -->
|
||||
<div class="tab-pane fade show active" id="district" role="tabpanel" aria-labelledby="district-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Person</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Last activity</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="districtBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Regional -->
|
||||
<div class="tab-pane fade" id="regional" role="tabpanel" aria-labelledby="regional-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Person</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Last activity</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="regionalBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- National -->
|
||||
<div class="tab-pane fade" id="national" role="tabpanel" aria-labelledby="national-tab">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:42px;"></th>
|
||||
<th>Person</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Last activity</th>
|
||||
<th style="width:130px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="nationalBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination stub -->
|
||||
<nav aria-label="User list pages" class="d-flex justify-content-end">
|
||||
<ul class="pagination pagination-sm" id="paginationContainer">
|
||||
<li class="page-item disabled"><a class="page-link" href="#" tabindex="-1">Prev</a></li>
|
||||
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">Next</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Edit modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit user</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="editForm">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="editId">
|
||||
<div class="mb-3"><label class="form-label">Name</label><input id="editName" class="form-control"></div>
|
||||
<div class="mb-3"><label class="form-label">Email</label><input id="editEmail" class="form-control" type="email"></div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Category</label>
|
||||
<select id="editCategory" class="form-select">
|
||||
<option>District</option>
|
||||
<option>Regional</option>
|
||||
<option>National</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Move modal (bulk) -->
|
||||
<div class="modal fade" id="moveModal" tabindex="-1" aria-labelledby="moveModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="moveModalLabel">Move selected users</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="moveForm">
|
||||
<div class="modal-body">
|
||||
<p id="moveCountText">Move <span id="moveCount">0</span> users to:</p>
|
||||
<div class="mb-3">
|
||||
<select id="moveTarget" class="form-select" required>
|
||||
<option value="">Choose category</option>
|
||||
<option value="District">District</option>
|
||||
<option value="Regional">Regional</option>
|
||||
<option value="National">National</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Move</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
/* Small visual tweaks */
|
||||
.category-badge { font-weight: 600; color: #fff; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: .8rem; }
|
||||
.badge-district { background:#f6a623; } /* amber */
|
||||
.badge-regional { background:#17a2b8; } /* teal */
|
||||
.badge-national { background:#1f6feb; } /* blue */
|
||||
.user-row:hover .row-actions { visibility: visible; opacity: 1; }
|
||||
.row-actions { visibility: hidden; opacity: 0; transition: .12s ease-in-out; }
|
||||
.table-avatar { width:36px; height:36px; object-fit:cover; border-radius:50%; }
|
||||
.toolbar { gap:.5rem; display:flex; flex-wrap:wrap; align-items:center; }
|
||||
.select-all { margin-right:.5rem; }
|
||||
@media (max-width: 767px) {
|
||||
.toolbar { flex-direction:column; align-items:stretch; }
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<!-- jQuery + Bootstrap bundle -->
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
const routes = {
|
||||
list: "{{ route('admin.users.list') }}",
|
||||
show: (id)=> "{{ url('admin/users') }}/"+id,
|
||||
update: (id)=> "{{ url('admin/users') }}/"+id,
|
||||
move: "{{ route('admin.users.move') }}",
|
||||
deactivate: "{{ route('admin.users.deactivate') }}"
|
||||
};
|
||||
|
||||
$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' } });
|
||||
|
||||
(function(){
|
||||
// keyboard shortcut to focus search
|
||||
$(document).on('keydown', function(e){
|
||||
if (e.key === '/' && !$(e.target).is('input, textarea')) {
|
||||
e.preventDefault();
|
||||
$('#globalSearch').focus().select();
|
||||
}
|
||||
});
|
||||
|
||||
let currentCategory = 'District';
|
||||
|
||||
function renderRow(u){
|
||||
const badgeClass = u.category === 'District' ? 'badge-district' : (u.category === 'Regional' ? 'badge-regional' : 'badge-national');
|
||||
const avatar = u.avatar_url || 'https://i.pravatar.cc/40';
|
||||
const role = u.role || '';
|
||||
const lastAct = u.last_activity_at || '';
|
||||
const row = $(`
|
||||
<tr class="user-row" data-category="${u.category}" data-status="${u.status}" data-name="${u.name}" data-email="${u.email}" data-id="${u.id}">
|
||||
<td><input class="row-select form-check-input" type="checkbox" aria-label="Select ${u.name}"></td>
|
||||
<td class="align-middle">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="${avatar}" alt="avatar" class="table-avatar me-2">
|
||||
<div><div class="fw-semibold">${u.name}</div><div class="text-muted small">ID: ${u.id}</div></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">${u.email}</td>
|
||||
<td class="align-middle"><span class="category-badge ${badgeClass}">${u.category}</span> · ${role}</td>
|
||||
<td class="align-middle">${lastAct}</td>
|
||||
<td class="align-middle">
|
||||
<div class="row-actions d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary viewBtn" data-id="${u.id}">View</button>
|
||||
<button class="btn btn-sm btn-primary editBtn" data-id="${u.id}" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`);
|
||||
const body = u.category === 'District' ? '#districtBody' : (u.category === 'Regional' ? '#regionalBody' : '#nationalBody');
|
||||
$(body).append(row);
|
||||
}
|
||||
|
||||
function clearBodies(){ $('#districtBody,#regionalBody,#nationalBody').empty(); }
|
||||
|
||||
function fetchCategory(category){
|
||||
currentCategory = category;
|
||||
const params = { category, q: $('#globalSearch').val(), status: $('.filter-status:checked').map(function(){return this.value}).get() };
|
||||
$.get(routes.list, params, function(data){
|
||||
clearBodies();
|
||||
const items = data.data || data;
|
||||
items.forEach(renderRow);
|
||||
// Try to use paginator meta for counts, fall back to counting rendered rows
|
||||
$('#count-district').text(data.meta?.total_by_category?.District ?? $('#districtBody .user-row').length);
|
||||
$('#count-regional').text(data.meta?.total_by_category?.Regional ?? $('#regionalBody .user-row').length);
|
||||
$('#count-national').text(data.meta?.total_by_category?.National ?? $('#nationalBody .user-row').length);
|
||||
});
|
||||
}
|
||||
|
||||
// Initial load per category
|
||||
['District','Regional','National'].forEach(c => {
|
||||
$.get(routes.list, { category: c }, function(data){
|
||||
(data.data || data).forEach(renderRow);
|
||||
$('#count-'+c.toLowerCase()).text(data.meta?.total ?? (data.data || data).length);
|
||||
});
|
||||
});
|
||||
|
||||
// Select all visible in active tab
|
||||
function getVisibleRowsInActiveTab(){
|
||||
const activePane = $('.tab-pane.active');
|
||||
return activePane.find('.user-row:visible');
|
||||
}
|
||||
|
||||
$('#globalSelect').on('change', function(){
|
||||
const checked = $(this).is(':checked');
|
||||
getVisibleRowsInActiveTab().find('.row-select').prop('checked', checked).trigger('change');
|
||||
});
|
||||
|
||||
// Row checkbox toggles bulk buttons
|
||||
$(document).on('change', '.row-select', function(){
|
||||
const anyChecked = $('.tab-pane.active .row-select:checked').length > 0;
|
||||
$('#bulkMoveBtn, #bulkDeactivate').prop('disabled', !anyChecked);
|
||||
const total = getVisibleRowsInActiveTab().find('.row-select').length;
|
||||
const checked = getVisibleRowsInActiveTab().find('.row-select:checked').length;
|
||||
$('#globalSelect').prop('checked', total>0 && total===checked);
|
||||
});
|
||||
|
||||
// Search + filter debounce
|
||||
let t=null;
|
||||
$('#globalSearch, .filter-status').on('input change', function(){
|
||||
clearTimeout(t);
|
||||
t = setTimeout(()=> { fetchCategory(currentCategory); }, 300);
|
||||
});
|
||||
|
||||
// Activate tab event -> set currentCategory and reset selects
|
||||
$('button[data-bs-toggle="tab"]').on('shown.bs.tab', function(e){
|
||||
const target = $(e.target).data('bsTarget') || $(e.target).data('bs-target');
|
||||
currentCategory = target.replace('#','');
|
||||
$('#globalSelect').prop('checked', false);
|
||||
$('#bulkMoveBtn, #bulkDeactivate').prop('disabled', true);
|
||||
});
|
||||
|
||||
// Edit: fetch details and populate modal
|
||||
let lastTrigger = null;
|
||||
$(document).on('click', '.editBtn', function(){
|
||||
lastTrigger = this;
|
||||
const id = $(this).data('id');
|
||||
$.get(routes.show(id), function(user){
|
||||
$('#editId').val(user.id);
|
||||
$('#editName').val(user.name);
|
||||
$('#editEmail').val(user.email);
|
||||
$('#editCategory').val(user.category);
|
||||
});
|
||||
});
|
||||
|
||||
// When edit modal closes, return focus to trigger
|
||||
$('#editModal').on('hidden.bs.modal', function(){
|
||||
if (lastTrigger && document.contains(lastTrigger)) lastTrigger.focus();
|
||||
lastTrigger = null;
|
||||
});
|
||||
|
||||
// Update via AJAX
|
||||
$('#editForm').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const id = $('#editId').val();
|
||||
const payload = { name: $('#editName').val(), email: $('#editEmail').val(), category: $('#editCategory').val() };
|
||||
$.ajax({
|
||||
url: routes.update(id),
|
||||
method: 'PUT',
|
||||
data: payload,
|
||||
success: function(updated){
|
||||
const row = $(`.user-row[data-id="${updated.id}"]`);
|
||||
if (row.length){
|
||||
row.data('name', updated.name).data('email', updated.email).data('category', updated.category);
|
||||
row.find('.fw-semibold').text(updated.name);
|
||||
row.find('td').eq(2).text(updated.email);
|
||||
row.find('.category-badge').removeClass('badge-district badge-regional badge-national');
|
||||
if (updated.category === 'District') row.find('.category-badge').addClass('badge-district').text('District');
|
||||
if (updated.category === 'Regional') row.find('.category-badge').addClass('badge-regional').text('Regional');
|
||||
if (updated.category === 'National') row.find('.category-badge').addClass('badge-national').text('National');
|
||||
const target = updated.category === 'District' ? '#districtBody' : (updated.category === 'Regional' ? '#regionalBody' : '#nationalBody');
|
||||
if (!row.parent().is(target)) row.appendTo($(target));
|
||||
} else {
|
||||
fetchCategory(currentCategory);
|
||||
}
|
||||
$('#editModal').modal('hide');
|
||||
},
|
||||
error: function(xhr){
|
||||
if (xhr.status === 422) {
|
||||
alert('Validation error');
|
||||
} else alert('Update failed');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Bulk move: prepare modal count
|
||||
$('#bulkMoveBtn').on('click', function(){
|
||||
const selected = $('.tab-pane.active .row-select:checked');
|
||||
$('#moveCount').text(selected.length);
|
||||
$('#moveTarget').val('');
|
||||
$('#moveModal').on('shown.bs.modal', function(){ $('#moveTarget').focus(); });
|
||||
});
|
||||
|
||||
// Execute move
|
||||
$('#moveForm').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const ids = $('.tab-pane.active .row-select:checked').closest('.user-row').map(function(){ return $(this).data('id'); }).get();
|
||||
const target = $('#moveTarget').val();
|
||||
$.post(routes.move, { ids, target }, function(res){
|
||||
fetchCategory(currentCategory);
|
||||
$('#moveModal').modal('hide');
|
||||
}).fail(function(){ alert('Move failed'); });
|
||||
});
|
||||
|
||||
// Bulk deactivate
|
||||
$('#bulkDeactivate').on('click', function(){
|
||||
const ids = $('.tab-pane.active .row-select:checked').closest('.user-row').map(function(){ return $(this).data('id'); }).get();
|
||||
if (!ids.length) return;
|
||||
$.post(routes.deactivate, { ids }, function(res){
|
||||
fetchCategory(currentCategory);
|
||||
}).fail(function(){ alert('Deactivate failed'); });
|
||||
});
|
||||
|
||||
// init counts if none loaded
|
||||
setTimeout(()=> {
|
||||
$('#count-district').text($('#districtBody .user-row').length);
|
||||
$('#count-regional').text($('#regionalBody .user-row').length);
|
||||
$('#count-national').text($('#nationalBody .user-row').length);
|
||||
}, 400);
|
||||
|
||||
// accessibility shortcut example
|
||||
$(document).on('keydown', function(e){
|
||||
if (e.key === 'e' && !$(e.target).is('input, textarea')) {
|
||||
e.preventDefault();
|
||||
$('.editBtn').first().focus();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@endpush
|
||||
@ -4,7 +4,7 @@
|
||||
@endsection
|
||||
@section('page-css')
|
||||
<link rel="stylesheet" href="{{ url('public/assets/libs/select2/dist/css/select2.css') }}">
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/css/intlTelInput.css"/>
|
||||
@endsection
|
||||
@section('page-content')
|
||||
@include('admin.partials.create-user')
|
||||
@ -12,7 +12,8 @@
|
||||
@include('admin.partials.view-user')
|
||||
@include('layouts.partials.navbar')
|
||||
|
||||
|
||||
<?php //dump(session('current_user'));
|
||||
?>
|
||||
<div class="container py-4">
|
||||
<div class="main">
|
||||
<div class="row">
|
||||
@ -85,10 +86,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<?php if (count($items) > 1): ?>
|
||||
<div class="float-end pb-2" >
|
||||
Showing {{ $items->currentPage() }} of {{ $items->lastPage() }} pages | {{ $items->total() }} records
|
||||
</div>
|
||||
<div class="d-flex justify-content-centerw">
|
||||
<div class=" mt-3 px-3 pb-3">
|
||||
{{ $items->links() }}
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@ -105,9 +103,13 @@
|
||||
|
||||
@endsection
|
||||
@section('page-js')
|
||||
<script src="{{ url('public/assets/libs/select2/dist/js/select2.full.min.js') }}" type="text/javascript" ></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/intlTelInput.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"></script>
|
||||
<script src="{{ url('public/assets/libs/select2/dist/js/select2.full.min.js') }}" type="text/javascript" ></script>
|
||||
<script src="{{ url('public/assets/js/usermgt.js') }}" type="text/javascript" ></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@endsection
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="addUserModal" tabindex="-1" aria-labelledby="addUserModalLabel" aria-hidden="true">
|
||||
<div class="modal fade" id="addUserModal" tabindex="-1" aria-labelledby="addUserModalLabel">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@ -7,15 +7,29 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-success d-none" id="successArea">Heere at the wall</div>
|
||||
<div class="alert alert-danger d-none" id="errorArea">Heere at the wall</div>
|
||||
<div class="alert alert-success d-none" id="newUserSuccessArea"></div>
|
||||
<p class="alert alert-danger d-none" id="newUserErrorArea"></p>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<form class="row g-3" action="" method="POST" id="newUserForm">
|
||||
@csrf
|
||||
<input type="hidden" name="action" value="newuser">
|
||||
|
||||
<div class="col-md-12">
|
||||
@if (session('current_user.user_type') == 'district_user')
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="district_user" id="userTypeDistrict" checked>
|
||||
<label class="form-check-label" for="userTypeDistrict">District User</label>
|
||||
</div>
|
||||
@elseif(session('current_user.user_type') == 'regional_user')
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="district_user" id="userTypeDistrict" checked>
|
||||
<label class="form-check-label" for="userTypeDistrict">District User</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="regional_luspa" id="userTypeLuspaRegion">
|
||||
<label class="form-check-label" for="userTypeLuspaRegion">Regional LUSPA</label>
|
||||
</div>
|
||||
@else
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="district_user" id="userTypeDistrict" checked>
|
||||
<label class="form-check-label" for="userTypeDistrict">District User</label>
|
||||
@ -28,12 +42,14 @@
|
||||
<input class="form-check-input" type="radio" name="user_type" value="national_luspa" id="userTypeLuspaNational">
|
||||
<label class="form-check-label" for="userTypeLuspaNational">National LUSPA</label>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="titleSelecton" class="form-label">Title</label>
|
||||
<select id="titleSelecton" name="title" class="form-select" required>
|
||||
<option selected disabled>Choose...</option>
|
||||
<option value="Mr">Mr</option>
|
||||
<option value="Mr">Miss</option>
|
||||
<option value="Mrs">Mrs</option>
|
||||
<option value="Dr">Dr</option>
|
||||
<option value="Proff">Proff</option>
|
||||
@ -41,31 +57,31 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
<label for="fullName" class="form-label">Fullname</label>
|
||||
<input type="text" name="full_name" class="form-control" id="fullName" required>
|
||||
<label for="addUserFullname" class="form-label">Fullname*</label>
|
||||
<input type="text" name="full_name" class="form-control" id="addUserFullname" required>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" id="email" required>
|
||||
<label for="addUserEmail" class="form-label">Email*</label>
|
||||
<input type="email" name="email" class="form-control" id="addUserEmail" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="username" name="username" class="form-control" id="username" required>
|
||||
<label for="addUserUsername" class="form-label">Username*</label>
|
||||
<input type="username" name="username" class="form-control" id="addUserUsername" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="gender" class="form-label">Gender</label>
|
||||
<select id="gender" name="gender" class="form-select" required>
|
||||
<option selected>Choose...</option>
|
||||
<label for="addUserGender" class="form-label">Gender*</label>
|
||||
<select id="addUserGender" name="gender" class="form-select" required>
|
||||
<option selected disabled>Choose...</option>
|
||||
<option value="female">Female</option>
|
||||
<option value="male">Male</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="phone" class="form-label">Phone</label>
|
||||
<input type="text" name="phone" class="form-control" id="phone" required>
|
||||
<label for="phone" class="form-label">Phone*</label>
|
||||
<input type="tel" name="phone" class="form-control" id="addUserphone" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-<?php echo (session('current_user.user_type')) == 'district_user' ? '12' : '4'; ?>">
|
||||
<label for="uaPostionAdd" class="form-label">Position</label>
|
||||
<select id="uaPostionAdd" name="ua_position" class="form-select" required>
|
||||
<option selected disabled>Choose...</option>
|
||||
@ -76,7 +92,10 @@
|
||||
<option value="district-works-head">Works Department Head</option>
|
||||
<option value="district-works-staff">Works Department Staff</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (session('current_user.user_type') == 'district_user' )
|
||||
<input type="hidden" name="districtid" value="{{ session('current_user.district_id') }}">
|
||||
@elseif(session('current_user.user_type') == 'regional_user')
|
||||
<div class="col-md-4">
|
||||
<label for="regionID" class="form-label">Region</label>
|
||||
<select name="region_id" id="regionID" class="form-select">
|
||||
@ -86,14 +105,33 @@
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@else
|
||||
<div class="col-md-4">
|
||||
<label for="districtID" class="form-label">District</label>
|
||||
<select id="districtID" name="districtid" class="form-select">
|
||||
<label for="regionID" class="form-label">Region*</label>
|
||||
<select name="region_id" id="regionID" class="form-select regionIDD">
|
||||
<option selected disabled>Choose...</option>
|
||||
<?php foreach ($regions_arr as $row): ?>
|
||||
<option value="<?php echo $row['regionid']; ?>"><?php echo $row['region_name']; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="districtID" class="form-label">District*</label>
|
||||
<select id="districtID" name="districtid" class="form-select districtIDD">
|
||||
<option selected disabled>Choose...</option>
|
||||
</select>
|
||||
</div>
|
||||
@endif
|
||||
<div class="col-md-6">
|
||||
<label for="addUserUserStatus" class="form-label">Status *</label>
|
||||
<select id="addUserUserStatus" name="user_status" class="form-select"required style="width:100%">
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label for="allowedApps" class="form-label">Allowed Apps</label>
|
||||
<label for="allowedApps" class="form-label">Allowed Apps*</label>
|
||||
<select id="allowedApps" name="allowed_apps[]"class="form-select" multiple required style="width:100%">
|
||||
<option value="drawing-tools">Drawing Tools</option>
|
||||
<option value="permit-tools">Permit Tools</option>
|
||||
@ -116,4 +154,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
29
php_code/resources/views/admin/partials/edit-modal.blade.php
Normal file
29
php_code/resources/views/admin/partials/edit-modal.blade.php
Normal file
@ -0,0 +1,29 @@
|
||||
<div class="modal fade" id="editModalSimple" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit user</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="editForm">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="editId">
|
||||
<div class="mb-3"><label class="form-label">Name</label><input id="editName" class="form-control"></div>
|
||||
<div class="mb-3"><label class="form-label">Email</label><input id="editEmail" class="form-control" type="email"></div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Category</label>
|
||||
<select id="editCategory" class="form-select">
|
||||
<option>District</option>
|
||||
<option>Regional</option>
|
||||
<option>National</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,5 +1,5 @@
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="editUserModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal fade" id="editUserModal" tabindex="-1" aria-labelledby="exampleModalLabel">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@ -15,6 +15,38 @@
|
||||
<form class="row g-3" action="" method="POST" id="editUserForm">
|
||||
@csrf
|
||||
<input type="hidden" name="user_id" value="">
|
||||
<input type="hidden" name="districtid" value="{{ session('current_user.district_id') }}">
|
||||
<!-- <input type="hidden" name="user_type" value="district_user"> -->
|
||||
<div class="col-md-12">
|
||||
@if (session('current_user.user_type') == 'district_user')
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="district_user" id="editUserTypeDistrict" >
|
||||
<label class="form-check-label" for="editUserTypeDistrict">District User</label>
|
||||
</div>
|
||||
@elseif(session('current_user.user_type') == 'regional_user')
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="district_user" id="editUserTypeDistrict" >
|
||||
<label class="form-check-label" for="editUserTypeDistrict">District User</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="regional_luspa" id="editUserTypeLuspaRegion">
|
||||
<label class="form-check-label" for="editUserTypeLuspaRegion">Regional LUSPA</label>
|
||||
</div>
|
||||
@else
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="district_user" id="editUserTypeDistrict" >
|
||||
<label class="form-check-label" for="editUserTypeDistrict">District User</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="regional_luspa" id="editUserTypeLuspaRegion">
|
||||
<label class="form-check-label" for="editUserTypeLuspaRegion">Regional LUSPA</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="national_luspa" id="editUserTypeLuspaNational">
|
||||
<label class="form-check-label" for="editUserTypeLuspaNational">National LUSPA</label>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="editTitle" class="form-label">Title</label>
|
||||
<select id="editTitle" name="title" class="form-select" required>
|
||||
@ -27,11 +59,11 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
<label for="editFullName" class="form-label">Fullname MM</label>
|
||||
<label for="editFullName" class="form-label">Fullname *</label>
|
||||
<input type="text" name="full_name" value="" class="form-control" id="editFullName" required>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label for="editEmail" class="form-label">Email</label>
|
||||
<label for="editEmail" class="form-label">Email *</label>
|
||||
<input type="email" name="email" value="" class="form-control" id="editEmail" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
@ -47,11 +79,11 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="editPhone" class="form-label">Phone</label>
|
||||
<label for="editPhone" class="form-label">Phone *</label>
|
||||
<input type="text" name="phone" value="" class="form-control" id="editPhone" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="editUaPostion" class="form-label">Position</label>
|
||||
<div class="col-md-4">
|
||||
<label for="editUaPostion" class="form-label">Position *</label>
|
||||
<select id="editUaPostion" name="ua_position" class="form-select" required>
|
||||
<option value="district-mis">District MIS</option>
|
||||
<option value="district-ppd-head">PPD Head</option>
|
||||
@ -60,9 +92,44 @@
|
||||
<option value="district-works-head">Works Department Head</option>
|
||||
<option value="district-works-staff">Works Department Staff</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (session('current_user.user_type') == 'district_user' )
|
||||
<input type="hidden" name="districtid" value="{{ session('current_user.district_id') }}">
|
||||
@elseif(session('current_user.user_type') == 'regional_user')
|
||||
<div class="col-md-4">
|
||||
<label for="editRegionID" class="form-label">Region *</label>
|
||||
<select name="region_id" id="editRegionID" class="form-select">
|
||||
<option selected disabled>Choose...</option>
|
||||
<?php foreach ($regions_arr as $row): ?>
|
||||
<option value="<?php echo $row['regionid']; ?>" ><?php echo $row['region_name']; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="editDistrictID" class="form-label">District *</label>
|
||||
<select id="editDistrictID" name="districtid" class="form-select">
|
||||
<option selected disabled>Choose...</option>
|
||||
</select>
|
||||
</div>
|
||||
@else
|
||||
<div class="col-md-4">
|
||||
<label for="editRegionID" class="form-label">Region *</label>
|
||||
<select name="region_id" id="editRegionID" class="form-select regionIDD">
|
||||
<option selected disabled>Choose...</option>
|
||||
<?php foreach ($regions_arr as $row): ?>
|
||||
<option value="<?php echo $row['regionid']; ?>"><?php echo $row['region_name']; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="editDistrictID" class="form-label">District *</label>
|
||||
<select id="editDistrictID" name="districtid" class="form-select districtIDD">
|
||||
<option selected disabled>Choose...</option>
|
||||
</select>
|
||||
</div>
|
||||
@endif
|
||||
<div class="col-md-6">
|
||||
<label for="editAllowedApps" class="form-label">Allowed Apps</label>
|
||||
<label for="editAllowedApps" class="form-label">Allowed Apps *</label>
|
||||
<select id="editAllowedApps" name="allowed_apps[]" class="form-select" multiple required style="width:100%">
|
||||
<!-- <option selected >Choose...</option> -->
|
||||
<option value="drawing-tools">Drawing Tools</option>
|
||||
@ -70,20 +137,18 @@
|
||||
<option value="admin-gui">Admin GUI</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="userStatus" class="form-label">Status *</label>
|
||||
<select id="userStatus" name="user_status" class="form-select"required style="width:100%">
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="expire_password" value="yes" id="expirePassword" checked>
|
||||
<label class="form-check-label" for="expirePassword">Expire Password</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label for="inputPermissions" class="form-label">Permissions</label>
|
||||
<select name="permission" id="inputPermissions" multiple class="form-select" style="width: 100%">
|
||||
<option value="1">Permission 1</option>
|
||||
<option value="2">Permission 2</option>
|
||||
<option value="3">Permission 3</option>
|
||||
<option value="4">Permission 4</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="d-grid gap-2">
|
||||
|
||||
27
php_code/resources/views/admin/partials/move-user.blade.php
Normal file
27
php_code/resources/views/admin/partials/move-user.blade.php
Normal file
@ -0,0 +1,27 @@
|
||||
<div class="modal fade" id="moveModal" tabindex="-1" aria-labelledby="moveModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="moveModalLabel">Move selected users</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="moveForm">
|
||||
<div class="modal-body">
|
||||
<p id="moveCountText">Move <span id="moveCount">0</span> users to:</p>
|
||||
<div class="mb-3">
|
||||
<select id="moveTarget" class="form-select" required>
|
||||
<option value="">Choose category</option>
|
||||
<option value="District">District</option>
|
||||
<option value="Regional">Regional</option>
|
||||
<option value="National">National</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Move</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
84
php_code/resources/views/admin/test.blade.php
Normal file
84
php_code/resources/views/admin/test.blade.php
Normal file
@ -0,0 +1,84 @@
|
||||
@extends('layouts.master')
|
||||
@section('page-title')
|
||||
Admin | {{ $page_title }}
|
||||
@endsection
|
||||
@section('page-css')
|
||||
<link rel="stylesheet" href="{{ url('public/assets/libs/select2/dist/css/select2.css') }}">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/css/intlTelInput.css"/>
|
||||
|
||||
@endsection
|
||||
@section('page-content')
|
||||
@include('admin.partials.create-user')
|
||||
|
||||
|
||||
<div class="container py-4">
|
||||
<div class="main">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Users</h3>
|
||||
<!-- <a href="" class="float-end">Add User</a> -->
|
||||
<div class="float-end pb-2" >
|
||||
<button type="button" class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
||||
<img src="{{ url('public/assets/libs/bootstrap-icons/person-plus.svg') }}" alt="person-plus" width="16" height="16"> Add User</button>
|
||||
</div>
|
||||
<table class="table table-hover table-bordered table-condensed">
|
||||
<thead class="">
|
||||
<tr class="table-active">
|
||||
<th scope="col">Fullname</th>
|
||||
<th scope="col">Username</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Phone</th>
|
||||
<th scope="col">Position</th>
|
||||
<th scope="col">Allowed Apps</th>
|
||||
<th scope="col">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (count($users_arr) < 1): ?>
|
||||
<tr>
|
||||
<td colspan="7">No users found</td>
|
||||
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($users_arr as $row): ?>
|
||||
<?php $ua_id = $row['ua_id']; ?>
|
||||
<tr>
|
||||
<td><?php echo $row['full_name'] ?></td>
|
||||
<td><?php echo $row['username'] ?></td>
|
||||
<td><?php echo $row['email'] ?></td>
|
||||
<td><?php echo $row['phone'] ?></td>
|
||||
<td><?php echo $row['ua_position'] ?></td>
|
||||
<td><?php echo $row['allowed_apps'] ?></td>
|
||||
<td>
|
||||
@csrf
|
||||
<input type="hidden" class="userIdinput" value="<?php echo $row['user_id'] ?>" name="userId">
|
||||
|
||||
<!-- <a href="" data-bs-toggle="modal" data-bs-target="#viewUserModal" ><img src="{{ url('public/assets/libs/bootstrap-icons/eye.svg') }}" alt="view icon" width="16" height="16"></a> -->
|
||||
<a href="" class="editUserBtn" data-bs-toggle="modal" data-bs-target="#editUserModal">
|
||||
<img src="{{ url('public/assets/libs/bootstrap-icons/pencil-square.svg') }}" alt="edit icon" width="16" height="16">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<footer class="py-5">
|
||||
<div class="row">
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
@section('page-js')
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/intlTelInput.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js"></script>
|
||||
<script src="{{ url('public/assets/libs/select2/dist/js/select2.full.min.js') }}" type="text/javascript" ></script>
|
||||
<script src="{{ url('public/assets/js/usermgt.js') }}" type="text/javascript" ></script>
|
||||
|
||||
@endsection
|
||||
@ -70,7 +70,58 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- Welcome / User Context Panel -->
|
||||
<?php
|
||||
//dump(session('current_user'));
|
||||
?>
|
||||
<div class="container mt-4 mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body py-3">
|
||||
<div class="row align-items-center">
|
||||
|
||||
<!-- User Info -->
|
||||
<div class="col-md-8">
|
||||
<h5 class="mb-1">
|
||||
Welcome, <strong>{{ ucfirst(session('current_user.username')) }}</strong>
|
||||
</h5>
|
||||
|
||||
<p class="text-muted mb-0">
|
||||
You are currently signed in as a
|
||||
<span class="badge bg-primary">
|
||||
{{ session('current_user.ua_position') }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- District Info -->
|
||||
<div class="col-md-4 text-md-end mt-3 mt-md-0">
|
||||
<div class="small text-muted">
|
||||
<?php
|
||||
$district_name = '';
|
||||
if (session('current_user.user_type') == 'district_user') {
|
||||
echo "Current District";
|
||||
$district_name = session('current_user.district_name');
|
||||
}
|
||||
elseif (session('current_user.user_type') == 'regional_luspa') {
|
||||
echo "Current User";
|
||||
$district_name = 'Regional LUSPA';
|
||||
}
|
||||
elseif (session('current_user.user_type') == 'national_luspa') {
|
||||
echo "Current User";
|
||||
$district_name = 'National LUSPA';
|
||||
}
|
||||
?>
|
||||
|
||||
</div>
|
||||
<div class="fw-semibold fs-5">
|
||||
{{ $district_name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container py-5">
|
||||
@include('common.notifications')
|
||||
<div class="row g-4">
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<!-- CSRF Token -->
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
|
||||
|
||||
|
||||
<title>{{ config('app.name', 'LUPMIS4LUSPA') }}</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>@yield('page-title')</title>
|
||||
|
||||
<!-- Custom fonts for this template-->
|
||||
|
||||
71
php_code/resources/views/layouts/master_two.blade.php
Normal file
71
php_code/resources/views/layouts/master_two.blade.php
Normal file
@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>@yield('page-title')</title>
|
||||
|
||||
<!-- Custom fonts for this template-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
<!-- <link rel="stylesheet" href="../assets/css/bootstrap.css"> -->
|
||||
<link rel="stylesheet" href="{{ url('public/assets/libs/bootstrap/css/bootstrap5.3.2.css') }}">
|
||||
<link rel="stylesheet" href="{{ url('public/assets/libs/ol/ol.css') }}" type="text/css">
|
||||
<link rel="stylesheet" href="{{ url('public/assets/libs/ol/ol-ext.css') }}" />
|
||||
<link rel="stylesheet" href="{{ url('public/assets/css/l4l.css') }}" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url('public/assets/css/styles.css') }}" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url('public/assets/libs/fontawesome-free-7.1.0-web/css/all.min.css') }}" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="{{ url('public/assets/libs/jquery-confirm/jquery-confirm.min.css') }}" type="text/css">
|
||||
|
||||
@yield('page-css')
|
||||
<script type="text/javascript">
|
||||
var base_url = "{!! url('/') !!}";
|
||||
</script>
|
||||
</head>
|
||||
@include('admin.partials.profile')
|
||||
|
||||
<!-- <body class="bg-gradient-primary"> -->
|
||||
<body class="bg-light">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/landing">LUPMIS4LUSPA</a>
|
||||
<div class="ms-auto">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-light dropdown-toggle" type="button" id="userDropdown" data-bs-toggle="dropdown">
|
||||
<span class="me-2"><?php echo ucfirst(session('current_user.username')); ?></span>
|
||||
<!-- <small class="text-muted">Municipality</small> -->
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item profileLink" >Profile</a></li>
|
||||
<li><a class="dropdown-item" href="/landing">Landing Page</a></li>
|
||||
<li><a class="dropdown-item" href="#">Settings</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="/user-logout">Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@yield('page-content')
|
||||
<script src="{{ url('public/assets/libs/jquery-3.2.1.min.js') }}" type="text/javascript"></script>
|
||||
<script src="{{ url('public/assets/libs/bootstrap/js/bootstrap5.3.2.js') }}"></script>
|
||||
<script src="{{ url('public/assets/libs/ol/ol.js') }}" type="text/javascript" ></script> <!-- ol6.15.1 -->
|
||||
<script src="{{ url('public/assets/libs/ol/ol-ext.js') }}" type="text/javascript" ></script>
|
||||
<script src="{{ url('public/assets/libs/fontawesome-free-7.1.0-web/js/all.min.js') }}"></script>
|
||||
<script src="{{ url('public/assets/libs/jquery-confirm/jquery-confirm.min.js') }}"></script>
|
||||
<script src="{{ url('public/assets/js/all_pages.js') }}"></script>
|
||||
|
||||
@yield('page-js')
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
|
||||
@ -0,0 +1,77 @@
|
||||
<button class="btn btn-outline-secondary w-100" type="button" data-bs-toggle="offcanvas" data-bs-target="#historyOffcanvas" aria-controls="historyOffcanvas">
|
||||
<i class="bi bi-clock-history"></i> View Full History & Comments
|
||||
</button>
|
||||
|
||||
<div class="offcanvas offcanvas-end shadow-lg" tabindex="-1" id="historyOffcanvas" aria-labelledby="historyOffcanvasLabel" style="width: 450px;">
|
||||
|
||||
<div class="offcanvas-header border-bottom bg-light sticky-top z-3">
|
||||
<h5 class="offcanvas-title fw-bold" id="historyOffcanvasLabel">Application History</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="offcanvas-body">
|
||||
<div class="timeline" id="commentsTimeline">
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon bg-primary text-white shadow-sm">
|
||||
<i class="bi bi-file-earmark-arrow-up"></i>
|
||||
</div>
|
||||
<div class="timeline-content border rounded p-3 shadow-sm">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<strong class="text-primary">Application Submitted</strong>
|
||||
<small class="text-muted">16th May, 2026 - 2:15 PM</small>
|
||||
</div>
|
||||
<p class="mb-0 small text-muted">by Applicant Olivia Sampson</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon bg-secondary text-white shadow-sm">
|
||||
<i class="bi bi-chat-text"></i>
|
||||
</div>
|
||||
<div class="timeline-content border rounded p-3 shadow-sm">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<strong>Applicatant to review</strong>
|
||||
<small class="text-muted">17th May, 2026 - 3:15 PM</small>
|
||||
</div>
|
||||
<p class="mb-0 text-dark small">Please ensure the architectural drawings include the updated drainage plan for the region.</p>
|
||||
<p class="mb-0 small text-muted">Updated by Kafui Amuzu (Works, Head )</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon bg-success text-white shadow-sm">
|
||||
<i class="bi bi-check-lg"></i>
|
||||
</div>
|
||||
<div class="timeline-content border rounded p-3 shadow-sm">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<strong class="text-success">Application Received</strong>
|
||||
<small class="text-muted">18th May, 2026 - 10:30 AM</small>
|
||||
</div>
|
||||
<p class="mb-2 small">All documents are in order</p>
|
||||
<div>
|
||||
<p class="mb-0 small text-muted">Updated by Luke Okyere (PPD Head)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="offcanvas-footer border-top p-3 bg-light z-3">
|
||||
<form>
|
||||
<div class="mb-2">
|
||||
<select class="form-select form-select-sm" aria-label="Update Status">
|
||||
<option selected>Update status (Optional)...</option>
|
||||
<option value="approved">Approved</option>
|
||||
<option value="rejected">Rejected</option>
|
||||
</select>
|
||||
</div>
|
||||
<textarea class="form-control mb-2" rows="2" placeholder="Write a comment..."></textarea>
|
||||
<button class="btn btn-primary w-100 btn-sm" type="button">
|
||||
<i class="bi bi-send"></i> Submit Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -0,0 +1,66 @@
|
||||
|
||||
|
||||
<div class="offcanvas offcanvas-end shadow-lg" tabindex="-1" id="historyOffcanvas" aria-labelledby="historyOffcanvasLabel" style="width: 450px; z-index: 1065;">
|
||||
|
||||
<div class="offcanvas-header border-bottom bg-light sticky-top z-3">
|
||||
<h5 class="offcanvas-title fw-bold" id="historyOffcanvasLabel">Comments History</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="offcanvas-body">
|
||||
<div class="timeline" id="commentsTimeline">
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon bg-primary text-white shadow-sm">
|
||||
<i class="bi bi-file-earmark-arrow-up"></i>
|
||||
</div>
|
||||
<div class="timeline-content border rounded p-3 shadow-sm">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<strong class="text-primary">Application Submitted</strong>
|
||||
<small class="text-muted">16th May, 2026 - 2:15 PM</small>
|
||||
</div>
|
||||
<p class="mb-0 small text-muted">by Applicant Olivia Sampson</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon bg-secondary text-white shadow-sm">
|
||||
<i class="bi bi-chat-text"></i>
|
||||
</div>
|
||||
<div class="timeline-content border rounded p-3 shadow-sm">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<strong>Applicatant to review</strong>
|
||||
<small class="text-muted">17th May, 2026 - 3:15 PM</small>
|
||||
</div>
|
||||
<p class="mb-0 text-dark small">Please ensure the architectural drawings include the updated drainage plan for the region.</p>
|
||||
<p class="mb-0 small text-muted">Updated by Kafui Amuzu (Works, Head )</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon bg-success text-white shadow-sm">
|
||||
<i class="bi bi-check-lg"></i>
|
||||
</div>
|
||||
<div class="timeline-content border rounded p-3 shadow-sm">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<strong class="text-success">Application Received</strong>
|
||||
<small class="text-muted">18th May, 2026 - 10:30 AM</small>
|
||||
</div>
|
||||
<p class="mb-2 small">All documents are in order</p>
|
||||
<div>
|
||||
<p class="mb-0 small text-muted">Updated by Luke Okyere (PPD Head)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="offcanvas-footer border-top p-3 bg-light z-3">
|
||||
|
||||
<textarea class="form-control mb-2 commentBody" rows="2" placeholder="Write a comment..."></textarea>
|
||||
<button class="btn btn-primary w-100 btn-sm submitPermitCommentBtn" type="button"><i class="bi bi-send"></i> Submit Comment
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -0,0 +1,118 @@
|
||||
<div class="modal fade" id="inspectionReportModal" tabindex="-1" aria-labelledby="inspectionReportModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header bg-light border-bottom">
|
||||
<h5 class="modal-title fw-bold" id="inspectionReportModalLabel">Site Inspection Report</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body p-4" id="report-pdf-content">
|
||||
<h6 class="text-primary fw-bold border-bottom pb-2 mb-3">1. Project Information</h6>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted small mb-1">Applicant Name</label> <input type="text" class="form-control form-control-sm" value="Jedidiah Madjanor">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted small mb-1">Site Location</label> <input type="text" class="form-control form-control-sm" value="2nd 3rdlink Fertilizer Road, Accra">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Plot/House Number (Ghana Post GPS)</label> <input type="text" class="form-control form-control-sm">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Proposed Development</label> <input type="text" class="form-control form-control-sm" value="Development Permit">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Date of Inspection</label> <input type="date" class="form-control form-control-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="text-primary fw-bold border-bottom pb-2 mb-3">2. Site Identification & Boundaries</h6>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Boundary Verification (Clear/Match Plan?)</label> <select class="form-select form-select-sm"><option>Yes</option><option>No</option></select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Beacon Check (Fixed and visible?)</label> <select class="form-select form-select-sm"><option>Yes</option><option>No</option></select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Land Ownership Document Verified?</label> <select class="form-select form-select-sm"><option>Yes</option><option>No</option></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="text-primary fw-bold border-bottom pb-2 mb-3">3. Zoning & Planning Compliance</h6>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Zoning Status Conformity</label> <input type="text" class="form-control form-control-sm" placeholder="e.g., residential, commercial">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Setbacks Met?</label> <input type="text" class="form-control form-control-sm" placeholder="Distance from roads, etc.">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Access to Site (Right of way)</label> <input type="text" class="form-control form-control-sm" placeholder="Is there an accessible road?">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="text-primary fw-bold border-bottom pb-2 mb-3">4. Site Suitability & Environmental Factors</h6>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Topography (Flooding/Erosion)</label> <input type="text" class="form-control form-control-sm">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Proximity to Hazards</label> <input type="text" class="form-control form-control-sm" placeholder="Distance to high-tension, gutters">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Drainage/Waste Management</label> <input type="text" class="form-control form-control-sm" placeholder="Stormwater plan">
|
||||
</div>
|
||||
</div>
|
||||
<div class="html2pdf__page-break"></div>
|
||||
<h6 class="text-primary fw-bold border-bottom pb-2 mb-3">5. Technical & Structural Observations</h6>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Foundation Status</label> <input type="text" class="form-control form-control-sm" placeholder="Suitability of soil capacity">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Structural Integrity</label> <input type="text" class="form-control form-control-sm" placeholder="Quality of materials">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label text-muted small mb-1">Workmanship Supervised?</label> <select class="form-select form-select-sm"><option>N/A</option><option>Yes</option><option>No</option></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="text-primary fw-bold border-bottom pb-2 mb-3">6. Official Recommendations & Sign-Off</h6>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<label class="form-label text-muted small mb-1">Inspector's Findings</label> <textarea class="form-control form-control-sm" rows="2" placeholder="e.g., Site boundaries are correct..."></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label text-muted small mb-1">Recommendations</label> <select class="form-select form-select-sm w-50">
|
||||
<option>Approve</option>
|
||||
<option>Query</option>
|
||||
<option>Reject</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mt-4">
|
||||
<label class="form-label text-muted small mb-1">Physical Planning Officer</label> <input type="text" class="form-control form-control-sm mb-2" placeholder="Name">
|
||||
<input type="text" class="form-control form-control-sm" placeholder="Signature (Digital)">
|
||||
</div>
|
||||
<div class="col-md-6 mt-4">
|
||||
<label class="form-label text-muted small mb-1">Works Department Engineer</label> <input type="text" class="form-control form-control-sm mb-2" placeholder="Name">
|
||||
<input type="text" class="form-control form-control-sm" placeholder="Signature (Digital)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer bg-light border-top">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<!-- <button type="button" class="btn btn-success" id="downloadPdfBtn"><i class="bi bi-download"></i> Download PDF
|
||||
</button> -->
|
||||
<button type="button" class="btn btn-success" onclick="generateReportPDF()">
|
||||
<i class="bi bi-download"></i> Download PDF
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary">Save Report</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,466 @@
|
||||
@extends('layouts.master')
|
||||
@section('page-title')
|
||||
Permits | {{ $page_title }}
|
||||
@endsection
|
||||
@section('page-css')
|
||||
<link rel="stylesheet" type="text/css" href="{{ url('public/assets/css/permit_show.css') }}">
|
||||
|
||||
@endsection
|
||||
@section('page-content')
|
||||
@include('permits.partials.pdf-modal')
|
||||
@include('layouts.partials.permits-navbar')
|
||||
@include('permits.partials.site-inspection')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 mt-2">
|
||||
<h1 class="display-6 fw-bold text-dark">{{ $permit_arr['application_code'] }} <span class="badge bg-warning text-dark">{{ $permit_arr['status'] }}</span></h1>
|
||||
</div>
|
||||
|
||||
<!-- Map Area -->
|
||||
<!-- <div class="map-container mb-4"> -->
|
||||
<!-- <div id="applicationmap" class="applicationmap" style="width: 100%; height: 100vH;"></div> -->
|
||||
<!-- <div><img src="{{ url('public/assets/images/grey-map-with-location-pin.jpg') }}" height="500px" width="801px"></div> -->
|
||||
<!-- <img src="https://placeholdit.com/900x200/dddddd/999999?text=Project+Location" alt="Project Location" class="img-fluid" /> -->
|
||||
<!-- </div> -->
|
||||
<div class="map-container mb-4">
|
||||
<iframe id="permit-map"
|
||||
src="https://pwa.lupmis4luspa.org/embed.php?mode=permit&application_code={{ urlencode($permit_arr['application_code']) }}{{ !empty($permit_arr['lon']) ? '&lon='.urlencode($permit_arr['lon']).'&lat='.urlencode($permit_arr['lat']) : '' }}{{ !empty($permit_arr['upn']) ? '&upn='.urlencode($permit_arr['upn']) : '' }}"
|
||||
style="width:100%;height:500px;border:0;border-radius:8px;"
|
||||
allow="geolocation; clipboard-write"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
loading="lazy"
|
||||
title="Permit location map">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Applicant & Property Details -->
|
||||
<div class="remarks-section">
|
||||
|
||||
|
||||
<h3 class="section-title">Actual Project Location</h3>
|
||||
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="permitLocation" class="form-label fw-bold text-primary">Coordinates *</label>
|
||||
<input type="text" name="permit_location" id="permitLocation" class="form-control">
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="permitUPN" class="form-label fw-bold text-primary">UPN</label>
|
||||
<input type="text" name="permit_upn" id="permitUPN" class="form-control">
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-warning btn-sm flex-fill" id="checkComplianceBtn"><i class="fa-solid fa-map"></i> Check Compliance</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="processingFee" class="form-label fw-bold text-primary">Permit Fee *</label>
|
||||
<input type="text" name="processing_fees" id="processingFee" class="form-control">
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-primary flex-fill"><i class="fa-solid fa-paper-plane"></i> Submit</button>
|
||||
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#inspectionReportModal">
|
||||
<i class="bi bi-file-earmark-text"></i> Site Inspection Report
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
<!-- Remarks -->
|
||||
<div class="details-section mt-2">
|
||||
<h2 class="section-title text-primary">
|
||||
Applicant & Property Details
|
||||
</h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<!-- <h6 class="detail-label">Project Description</h6> -->
|
||||
<p class="detail-value"></p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Name</h6>
|
||||
<p class="detail-value">{{ $permit_arr['applicant_name'] }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Nationality</h6>
|
||||
<p class="detail-value">{{ $permit_arr['nationality'] }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Email</h6>
|
||||
<p class="detail-value">{{ $permit_arr['email'] }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Telephone Number</h6>
|
||||
<p class="detail-value">{{ $permit_arr['phone'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h6 class="detail-label">Address</h6>
|
||||
<p class="detail-value">{{ $permit_arr['address'] }}, {{ $permit_arr['city'] }}, {{ $permit_arr['region'] }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Project Location</h6>
|
||||
<p class="detail-value">{{ $permit_arr['project_location'] }}</p>
|
||||
</div>
|
||||
<!-- <div class="col-md-4">
|
||||
<h6 class="detail-label">Parcel Size</h6>
|
||||
<p class="detail-value">0.25 Acre</p>
|
||||
</div> -->
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Permit Type</h6>
|
||||
<p class="detail-value">{{ $permit_arr['permit_type'] }}</p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<h6 class="detail-label">Description</h6>
|
||||
<p class="detail-value">{{ $permit_arr['project_description'] }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Purpose</h6>
|
||||
<p class="detail-value">{{ $permit_arr['land_request_use'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Planning Permit Application -->
|
||||
<div class="details-section mt-4">
|
||||
<h2 class="section-title text-primary">Documents</h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<!-- <h6 class="detail-label">Architectural Drawings</h6> -->
|
||||
<p class="detail-value">
|
||||
<a href="#" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#pdfModal" data-pdf-url="{{ route('view.pdf', ['filename' => 'site_plan.pdf']) }}">
|
||||
<i class="fa-solid fa-paperclip"></i> Site Plan
|
||||
</a>
|
||||
<!-- <a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Site Plan</a> -->
|
||||
</p>
|
||||
<p class="detail-value">
|
||||
<!-- <a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Architectural Drawings</a> -->
|
||||
<a href="#" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#pdfModal" data-pdf-url="{{ route('view.pdf', ['filename' => 'architectural_drawing.pdf']) }}">
|
||||
<i class="fa-solid fa-paperclip"></i> Architectural Drawings
|
||||
</a>
|
||||
</p>
|
||||
<!-- <p class="detail-value"><a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Working Drawing</a></p> -->
|
||||
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<!-- <h6 class="detail-label">Others</h6> -->
|
||||
|
||||
<p class="detail-value">
|
||||
<!-- <a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Block Plan</a> -->
|
||||
<a href="#" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#pdfModal" data-pdf-url="{{ route('view.pdf', ['filename' => 'block_plan.pdf']) }}">
|
||||
<i class="fa-solid fa-paperclip"></i> Block Plan
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<!-- <h6 class="detail-label">Architectural Drawings</h6> -->
|
||||
<!-- <p class="detail-value"><a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Zoning Report</a></p> -->
|
||||
<!-- <p class="detail-value"><a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Working Drawing</a></p> -->
|
||||
|
||||
</div>
|
||||
<!-- <div class="col-md-6">
|
||||
<p class="detail-value"><a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Site Plan</a></p>
|
||||
<p class="detail-value"><a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Title Certificate</a></p>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="details-section mt-4">
|
||||
<h2 class="section-title text-primary">Duration</h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Date of Application</h6>
|
||||
<p class="detail-value">
|
||||
<?php
|
||||
$originalDate = "2023-05-31 01:16:06";
|
||||
$unixTime = strtotime($permit_arr['submitted_at']);
|
||||
$normal_datetime = date("jS F, Y @ g:i A", $unixTime);
|
||||
|
||||
echo $normal_datetime;
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Processing Days Remaining</h6>
|
||||
<p class="detail-value">
|
||||
<span class="badge bg-warning text-dark">
|
||||
<?php
|
||||
if ($permit_arr['status'] == 'submitted') {
|
||||
echo "N/A";
|
||||
}
|
||||
?>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="remarks-section">
|
||||
<h3 class="section-title">Status & Comments</h3>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
@include('permits.partials.comments-timeline')
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<label for="permitCurrentStatus" class="form-label fw-bold text-primary"><i class="fa-solid fa-temperature-three-quarters"></i> Status Update</label>
|
||||
{{-- @if(in_array(session('current_user.ua_position'), $allowed_users_to_comment)) --}}
|
||||
<select class="form-select form-select-lgdd" id="permitCurrentStatus">
|
||||
<option value="" selected disabled>Select status ...</option>
|
||||
<option value="status_five">Applicant to Review</option>
|
||||
<option value="status_one">Application Received</option>
|
||||
<option value="status_two" disabled>Application Accepted</option>
|
||||
<option value="status_three" disabled>Permit Pending TSC Review</option>
|
||||
<option value="status_four" disabled>Permit Pending SPC Decision</option>
|
||||
<option value="status_five" disabled>Permit Approved (In Principle)</option>
|
||||
<option value="status_size" disabled>Permit Approved</option>
|
||||
<option value="status_seven" disabled>Permit Refused</option>
|
||||
<option value="status_eight" disabled>Permit Deferred</option>
|
||||
</select>
|
||||
{{-- @else --}}
|
||||
<!-- <p class="badge bg-warning text-dark">You don't have the right permission to update the status.</p> -->
|
||||
{{-- @endif-- }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
|
||||
{{-- @if(in_array(session('current_user.ua_position'), $allowed_users_to_comment)) --}}
|
||||
<textarea class="form-control mb-3 commentBody" rows="4" name="comment_body" placeholder="Enter new comment here" ></textarea>
|
||||
<input type="hidden" class="applicationCode" name="application_code" value="{{ $permit_arr['application_code'] }}">
|
||||
{{-- @else --}}
|
||||
<!-- <p class="badge bg-warning text-dark">You don't have the right permission to add comments.</p> -->
|
||||
{{-- @endif --}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-primary flex-fill submitPermitCommentBtn"><i class="fa-solid fa-paper-plane"></i> Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
@endsection
|
||||
@section('page-js')
|
||||
<!-- <script src="{{ url('public/assets/libs/select2/dist/js/select2.full.min.js') }}" type="text/javascript" ></script> -->
|
||||
|
||||
|
||||
<!-- <script src="{{ url('public/assets/js/permit_tools.js') }}" type="text/javascript" ></script> -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
||||
<script src="{{ url('public/assets/js/permit_comments.js') }}" type="text/javascript" ></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
|
||||
// Initial load
|
||||
|
||||
var pdfModal = document.getElementById('pdfModal');
|
||||
pdfModal.addEventListener('show.bs.modal', function (event) {
|
||||
// Button that triggered the modal
|
||||
var button = event.relatedTarget;
|
||||
// Extract info from data-bs-* attributes
|
||||
var pdfUrl = button.getAttribute('data-pdf-url');
|
||||
// Update the modal's iframe src
|
||||
var pdfIframe = document.getElementById('pdfIframe');
|
||||
pdfIframe.src = pdfUrl;
|
||||
});
|
||||
|
||||
// Optional: Clear the iframe src when the modal is closed to stop the stream
|
||||
pdfModal.addEventListener('hidden.bs.modal', function () {
|
||||
var pdfIframe = document.getElementById('pdfIframe');
|
||||
pdfIframe.src = '';
|
||||
});
|
||||
|
||||
const MAP_ORIGIN = 'https://pwa.lupmis4luspa.org';
|
||||
const iframe = document.getElementById('permit-map');
|
||||
const upnInput = document.getElementById('permitUPN');
|
||||
const locInput = document.getElementById('permitLocation');
|
||||
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.origin !== MAP_ORIGIN) return;
|
||||
const msg = event.data;
|
||||
if (!msg || typeof msg !== 'object') return;
|
||||
|
||||
switch (msg.type) {
|
||||
case 'ready':
|
||||
@if(!empty($permit_arr['upn']))
|
||||
iframe.contentWindow.postMessage(
|
||||
{ type: 'set:selected', upn: @json($permit_arr['upn']) },
|
||||
MAP_ORIGIN);
|
||||
@endif
|
||||
break;
|
||||
|
||||
case 'parcel:select':
|
||||
upnInput.value = msg.upn || '';
|
||||
locInput.value = (msg.lon != null && msg.lat != null)
|
||||
? msg.lat.toFixed(6) + ', ' + msg.lon.toFixed(6)
|
||||
: '';
|
||||
upnInput.dataset.parcelId = msg.parcel_id || '';
|
||||
upnInput.dataset.zoneCode = msg.zone_code || '';
|
||||
break;
|
||||
|
||||
case 'parcel:cleared':
|
||||
upnInput.value = '';
|
||||
locInput.value = '';
|
||||
delete upnInput.dataset.parcelId;
|
||||
delete upnInput.dataset.zoneCode;
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
console.warn('[permit-map]', msg.code, msg.message);
|
||||
break;
|
||||
}
|
||||
|
||||
$('#checkComplianceBtn').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
const payload = {
|
||||
application_code: @json($permit_arr['application_code']),
|
||||
upn: upnInput.value,
|
||||
parcel_id: upnInput.dataset.parcelId || null,
|
||||
coordinates: locInput.value,
|
||||
zone_code: upnInput.dataset.zoneCode || null,
|
||||
};
|
||||
$.ajax({ url: '{{ route("permits.checkCompliance") }}',
|
||||
method: 'POST', data: payload })
|
||||
.done(function (res) { /* render compliance result */ });
|
||||
});
|
||||
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const downloadBtn = document.getElementById('downloadPdfBtn');
|
||||
|
||||
if(downloadBtn) {
|
||||
downloadBtn.addEventListener('click', function() {
|
||||
const element = document.getElementById('report-pdf-content');
|
||||
const inputs = element.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
if (input.tagName === 'TEXTAREA') {
|
||||
input.innerHTML = input.value;
|
||||
} else if (input.tagName === 'SELECT') {
|
||||
const selectedOption = input.options[input.selectedIndex];
|
||||
if (selectedOption) selectedOption.setAttribute('selected', 'selected');
|
||||
} else {
|
||||
input.setAttribute('value', input.value);
|
||||
}
|
||||
});
|
||||
const originalText = downloadBtn.innerHTML;
|
||||
downloadBtn.innerHTML = '<i class="bi bi-hourglass-split"></i> Generating...';
|
||||
downloadBtn.disabled = true;
|
||||
|
||||
// Configure and generate PDF
|
||||
const opt = {
|
||||
margin: 0.5,
|
||||
filename: 'Site_Inspection_Report.pdf',
|
||||
image: { type: 'jpeg', quality: 0.98 },
|
||||
html2canvas: { scale: 2, useCORS: true, logging: false },
|
||||
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' }
|
||||
};
|
||||
|
||||
html2pdf().set(opt).from(element).save().then(() => {
|
||||
downloadBtn.innerHTML = originalText;
|
||||
downloadBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
window.generateReportPDF = function() {
|
||||
console.log("1. Button clicked. Starting PDF process...");
|
||||
|
||||
if (typeof html2pdf === 'undefined') {
|
||||
alert("ERROR: The html2pdf library is not loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
const element = document.getElementById('report-pdf-content');
|
||||
if (!element) {
|
||||
alert("ERROR: Could not find the div with id='report-pdf-content'.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const inputs = element.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
if (input.tagName === 'TEXTAREA') {
|
||||
input.innerHTML = input.value;
|
||||
} else if (input.tagName === 'SELECT') {
|
||||
const selectedOption = input.options[input.selectedIndex];
|
||||
if (selectedOption) selectedOption.setAttribute('selected', 'selected');
|
||||
input.style.backgroundImage = 'none';
|
||||
input.style.appearance = 'none'; // Removes native browser arrows
|
||||
} else {
|
||||
input.setAttribute('value', input.value);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("3. Form data locked and SVGs removed. Generating file...");
|
||||
const opt = {
|
||||
margin: 0.5,
|
||||
filename: 'Site_Inspection_Report.pdf',
|
||||
image: { type: 'jpeg', quality: 0.98 },
|
||||
html2canvas: {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
logging: false,
|
||||
scrollY: 0,
|
||||
windowY: 0
|
||||
},
|
||||
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' },
|
||||
|
||||
pagebreak: {
|
||||
mode: 'css',
|
||||
avoid: ['.row', 'h6']
|
||||
}
|
||||
};
|
||||
|
||||
// Generate PDF
|
||||
html2pdf().set(opt).from(element).save().then(() => {
|
||||
console.log("Success: PDF downloaded.");
|
||||
|
||||
// THE RESTORE: Put the arrows back on the screen after download finishes
|
||||
inputs.forEach(input => {
|
||||
if (input.tagName === 'SELECT') {
|
||||
input.style.backgroundImage = '';
|
||||
input.style.appearance = '';
|
||||
}
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error("PDF Generation Error:", err);
|
||||
alert("Failed to build the PDF. Check the browser console.");
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Critical Execution Error:", error);
|
||||
alert("A javascript error occurred: " + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
545
php_code/resources/views/permits/show.blade 21-06-2026.php
Normal file
545
php_code/resources/views/permits/show.blade 21-06-2026.php
Normal file
@ -0,0 +1,545 @@
|
||||
@extends('layouts.master')
|
||||
@section('page-title')
|
||||
Permits | {{ $page_title }}
|
||||
@endsection
|
||||
@section('page-css')
|
||||
<link rel="stylesheet" type="text/css" href="{{ url('public/assets/css/permit_show.css') }}">
|
||||
|
||||
@endsection
|
||||
@section('page-content')
|
||||
@include('permits.partials.pdf-modal')
|
||||
@include('layouts.partials.permits-navbar')
|
||||
@include('permits.partials.site-inspection')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 mt-2">
|
||||
<h1 class="display-6 fw-bold text-dark">{{ $permit_arr['application_code'] }} <span class="badge bg-warning text-dark">{{ $permit_arr['status'] }}</span></h1>
|
||||
</div>
|
||||
|
||||
<!-- Map Area -->
|
||||
<!-- <div class="map-container mb-4"> -->
|
||||
<!-- <div id="applicationmap" class="applicationmap" style="width: 100%; height: 100vH;"></div> -->
|
||||
<!-- <div><img src="{{ url('public/assets/images/grey-map-with-location-pin.jpg') }}" height="500px" width="801px"></div> -->
|
||||
<!-- <img src="https://placeholdit.com/900x200/dddddd/999999?text=Project+Location" alt="Project Location" class="img-fluid" /> -->
|
||||
<!-- </div> -->
|
||||
<div class="map-container mb-4">
|
||||
<iframe id="permit-map"
|
||||
src="https://pwa.lupmis4luspa.org/embed.php?mode=permit&application_code={{ urlencode($permit_arr['application_code']) }}{{ !empty($permit_arr['lon']) ? '&lon='.urlencode($permit_arr['lon']).'&lat='.urlencode($permit_arr['lat']) : '' }}{{ !empty($permit_arr['upn']) ? '&upn='.urlencode($permit_arr['upn']) : '' }}"
|
||||
style="width:100%;height:500px;border:0;border-radius:8px;"
|
||||
allow="geolocation; clipboard-write"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
loading="lazy"
|
||||
title="Permit location map">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Applicant & Property Details -->
|
||||
<div class="remarks-section">
|
||||
<h3 class="section-title">Actual Project Location & Fees</h3>
|
||||
<div class="card border border-light-subtle shadow-sm mb-4" style="background-color: #f8f9fc;">
|
||||
<div class="card-body p-4">
|
||||
|
||||
<h6 class="fw-bold text-uppercase text-secondary mb-4" style="letter-spacing: 0.5px; font-size: 0.85rem;">
|
||||
<!-- Actual Project Location -->
|
||||
</h6>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold text-secondary small mb-1">
|
||||
Coordinates <br class="d-none d-md-block"> (Latitude, Longitude) <span class="text-danger">*</span>
|
||||
</label>
|
||||
<div class="input-group shadow-sm">
|
||||
<span class="input-group-text bg-white border-end-0 text-muted font-monospace small pe-1"></span>
|
||||
<input type="text" class="form-control border-start-0 ps-1 font-monospace" placeholder="" value="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold text-secondary small mb-1">
|
||||
<br class="d-none d-md-block">UPN (Unique Parcel Number)
|
||||
</label>
|
||||
<input type="text" class="form-control shadow-sm" value="">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold text-secondary small mb-1">
|
||||
Permit <br class="d-none d-md-block"> Fee (GH₵) <span class="text-danger"></span>
|
||||
</label>
|
||||
<input type="number" class="form-control shadow-sm fw-bold" value="1250">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<!-- <button type="button" class="btn fw-bold px-4 text-dark shadow-sm" style="background-color: #ffb400; border-color: #ffb400;">
|
||||
Submit
|
||||
</button> -->
|
||||
|
||||
<button type="button" class="btn btn-primary fw-bold px-4 shadow-sm">
|
||||
<i class="bi bi-map me-1"></i> Submit
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="remarks-section card border border-light-subtle shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
|
||||
<div class="d-flex align-items-center border-bottom pb-3 mb-4">
|
||||
<i class="fa-solid fa-comments text-primary fs-5 me-2"></i>
|
||||
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Status & Comments</h6>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<button class="btn btn-outline-secondary w-100" type="button" data-bs-toggle="offcanvas" data-bs-target="#historyOffcanvas" aria-controls="historyOffcanvas">
|
||||
<i class="bi bi-clock-history"></i> View Existing Comments
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="permitCurrentStatus" class="form-label fw-bold text-secondary small mb-2" style="letter-spacing: 0.5px;">
|
||||
<i class="bi bi-option text-primary me-1"></i> Update Status Here
|
||||
</label>
|
||||
<select class="form-select shadow-sm" id="permitCurrentStatus">
|
||||
<option value="" selected disabled>Select status ...</option>
|
||||
<option value="status_zero">Applicant to Review</option>
|
||||
<option value="status_one">Application Received</option>
|
||||
<option value="status_two" disabled>Application Accepted</option>
|
||||
<option value="status_three" disabled>Permit Pending TSC Review</option>
|
||||
<option value="status_four" disabled>Permit Pending SPC Decision</option>
|
||||
<option value="status_five" disabled>Permit Approved (In Principle)</option>
|
||||
<option value="status_six" disabled>Permit Approved</option>
|
||||
<option value="status_seven" disabled>Permit Refused</option>
|
||||
<option value="status_eight" disabled>Permit Deferred</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<textarea class="form-control shadow-sm commentBody" rows="4" name="comment_body" placeholder="Enter new comment here"></textarea>
|
||||
<input type="hidden" class="applicationCode" name="application_code" value="{{ $permit_arr['application_code'] }}">
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary w-100 fw-bold shadow-sm submitPermitCommentBtn" type="button">
|
||||
<i class="fa-solid fa-paper-plane me-1"></i> Submit
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
<!-- Remarks -->
|
||||
<div class="details-section mt-2">
|
||||
<div class="card border border-light-subtle shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
|
||||
<div class="d-flex align-items-center border-bottom pb-3 mb-4">
|
||||
<i class="bi bi-person text-primary fs-5 me-2"></i>
|
||||
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Applicant & Property Details</h6>
|
||||
</div>
|
||||
|
||||
<div class="row gy-4">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
Applicant Name:
|
||||
</div>
|
||||
<div class="fw-bold text-dark fs-5">{{ $permit_arr['applicant_name'] }}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
Nationality:
|
||||
</div>
|
||||
<div class="text-dark fw-medium">{{ $permit_arr['nationality'] }}</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
Email Address:
|
||||
</div>
|
||||
<div>
|
||||
<a href="" class="text-decoration-none text-primary">
|
||||
{{ $permit_arr['email'] }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
Telephone Number:
|
||||
</div>
|
||||
<div class="text-dark">{{ $permit_arr['phone'] }}</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
Project Location:
|
||||
</div>
|
||||
<div class="text-dark fw-bold">{{ $permit_arr['project_location'] }}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
Mailing Address:
|
||||
</div>
|
||||
<div class="text-dark">{{ $permit_arr['address'] }}, {{ $permit_arr['city'] }}, {{ $permit_arr['region'] }}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
Permit Type Requested
|
||||
</div>
|
||||
<div class="text-dark fw-bold">{{ $permit_arr['permit_type'] }}</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
Purpose
|
||||
</div>
|
||||
<div class="fw-bold" style="color: #e67e22;">{{ $permit_arr['land_request_use'] }}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mt-2">
|
||||
<div class="border border-dark-subtle rounded p-3">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
Project Scope:
|
||||
</div>
|
||||
<div class="text-dark fst-italic" style="font-size: 0.95rem;">{{ $permit_arr['project_description'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border border-light-subtle shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
|
||||
<div class="d-flex align-items-center border-bottom pb-3 mb-4">
|
||||
<i class="bi bi-file-earmark-text text-primary fs-5 me-2"></i>
|
||||
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Attached CAD Drawings & Documents</h6>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column gap-3">
|
||||
|
||||
<div class="border border-light-subtle rounded p-3 d-flex align-items-center justify-content-between bg-light shadow-sm transition-all">
|
||||
<div class="d-flex align-items-center text-truncate pe-3">
|
||||
<i class="bi bi-file-earmark text-primary fs-4 me-3"></i>
|
||||
<div class="text-truncate">
|
||||
<div class="text-dark fw-medium" style="font-size: 0.95rem;">Site Plan Drawing</div>
|
||||
<div class="text-secondary font-monospace text-uppercase" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
LUSPA_TAIFA_SP3_DRAFT.jpg
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="text-decoration-none fw-semibold text-nowrap" style="font-size: 0.85rem;">
|
||||
Inspect CAD <i class="bi bi-arrow-up-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="border border-light-subtle rounded p-3 d-flex align-items-center justify-content-between bg-light shadow-sm transition-all">
|
||||
<div class="d-flex align-items-center text-truncate pe-3">
|
||||
<i class="bi bi-file-earmark text-primary fs-4 me-3"></i>
|
||||
<div class="text-truncate">
|
||||
<div class="text-dark fw-medium" style="font-size: 0.95rem;">Block Plan Drawing</div>
|
||||
<div class="text-secondary font-monospace text-uppercase" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
LUSPA_TAIFA_BLOCK2_A.jpg
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="text-decoration-none fw-semibold text-nowrap" style="font-size: 0.85rem;">Inspect CAD <i class="bi bi-arrow-up-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="border border-light-subtle rounded p-3 d-flex align-items-center justify-content-between bg-light shadow-sm transition-all">
|
||||
<div class="d-flex align-items-center text-truncate pe-3">
|
||||
<i class="bi bi-file-earmark text-primary fs-4 me-3"></i>
|
||||
<div class="text-truncate">
|
||||
<div class="text-dark fw-medium" style="font-size: 0.95rem;">Architectural Drawings</div>
|
||||
<div class="text-secondary font-monospace text-uppercase" style="font-size: 0.75rem; letter-spacing: 0.5px;">
|
||||
TAIFA_RESID_ELEVATION.jpg
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="text-decoration-none fw-semibold text-nowrap" style="font-size: 0.85rem;">
|
||||
Inspect CAD <i class="bi bi-arrow-up-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border border-light-subtle shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
|
||||
<div class="d-flex justify-content-between align-items-start border-bottom pb-3 mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-clock text-primary fs-5 me-2"></i>
|
||||
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Processing & Duration</h6>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">
|
||||
Remaining Days
|
||||
</div>
|
||||
<span class="badge text-dark fs-6 px-3 py-1" style="background-color: #fce4b4;">N/A</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-auto pe-2" style="max-height: 450px;">
|
||||
<div class="planning-stepper">
|
||||
<div class="stepper-item">
|
||||
<div class="stepper-dot active"></div>
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">
|
||||
Applicant to Review
|
||||
</div>
|
||||
<h6 class="fw-bold mb-1" style="color: #e67e22;">Queried</h6>
|
||||
<p class="mb-2 small text-secondary">
|
||||
Applicant was prompted to review their application and update.
|
||||
</p>
|
||||
<span class="badge bg-light text-secondary border fw-medium px-2 py-1">Updated by: PPD Head</span>
|
||||
</div>
|
||||
|
||||
<div class="stepper-item">
|
||||
<div class="stepper-dot completed"></div>
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">
|
||||
<?php
|
||||
$unixTime = strtotime($permit_arr['submitted_at']);
|
||||
$normal_datetime = date("jS F, Y @ g:i A", $unixTime);
|
||||
?>
|
||||
</div>
|
||||
<h6 class="fw-bold mb-1 text-dark">Application Submitted</h6>
|
||||
<p class="mb-2 small text-secondary">
|
||||
Application successfully submitted on LUPMIS Portal
|
||||
</p>
|
||||
<!-- <span class="badge bg-light text-secondary border fw-medium px-2 py-1">Verified by: Applicant Portal</span> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@include('permits.partials.comments-timeline')
|
||||
@endsection
|
||||
|
||||
@section('page-js')
|
||||
<!-- <script src="{{ url('public/assets/libs/select2/dist/js/select2.full.min.js') }}" type="text/javascript" ></script> -->
|
||||
|
||||
|
||||
<!-- <script src="{{ url('public/assets/js/permit_tools.js') }}" type="text/javascript" ></script> -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
||||
<script src="{{ url('public/assets/js/permit_comments.js') }}" type="text/javascript" ></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
|
||||
// Initial load
|
||||
|
||||
var pdfModal = document.getElementById('pdfModal');
|
||||
pdfModal.addEventListener('show.bs.modal', function (event) {
|
||||
// Button that triggered the modal
|
||||
var button = event.relatedTarget;
|
||||
// Extract info from data-bs-* attributes
|
||||
var pdfUrl = button.getAttribute('data-pdf-url');
|
||||
// Update the modal's iframe src
|
||||
var pdfIframe = document.getElementById('pdfIframe');
|
||||
pdfIframe.src = pdfUrl;
|
||||
});
|
||||
|
||||
// Optional: Clear the iframe src when the modal is closed to stop the stream
|
||||
pdfModal.addEventListener('hidden.bs.modal', function () {
|
||||
var pdfIframe = document.getElementById('pdfIframe');
|
||||
pdfIframe.src = '';
|
||||
});
|
||||
|
||||
const MAP_ORIGIN = 'https://pwa.lupmis4luspa.org';
|
||||
const iframe = document.getElementById('permit-map');
|
||||
const upnInput = document.getElementById('permitUPN');
|
||||
const locInput = document.getElementById('permitLocation');
|
||||
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.origin !== MAP_ORIGIN) return;
|
||||
const msg = event.data;
|
||||
if (!msg || typeof msg !== 'object') return;
|
||||
|
||||
switch (msg.type) {
|
||||
case 'ready':
|
||||
@if(!empty($permit_arr['upn']))
|
||||
iframe.contentWindow.postMessage(
|
||||
{ type: 'set:selected', upn: @json($permit_arr['upn']) },
|
||||
MAP_ORIGIN);
|
||||
@endif
|
||||
break;
|
||||
|
||||
case 'parcel:select':
|
||||
upnInput.value = msg.upn || '';
|
||||
locInput.value = (msg.lon != null && msg.lat != null)
|
||||
? msg.lat.toFixed(6) + ', ' + msg.lon.toFixed(6)
|
||||
: '';
|
||||
upnInput.dataset.parcelId = msg.parcel_id || '';
|
||||
upnInput.dataset.zoneCode = msg.zone_code || '';
|
||||
break;
|
||||
|
||||
case 'parcel:cleared':
|
||||
upnInput.value = '';
|
||||
locInput.value = '';
|
||||
delete upnInput.dataset.parcelId;
|
||||
delete upnInput.dataset.zoneCode;
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
console.warn('[permit-map]', msg.code, msg.message);
|
||||
break;
|
||||
}
|
||||
|
||||
$('#checkComplianceBtn').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
const payload = {
|
||||
application_code: @json($permit_arr['application_code']),
|
||||
upn: upnInput.value,
|
||||
parcel_id: upnInput.dataset.parcelId || null,
|
||||
coordinates: locInput.value,
|
||||
zone_code: upnInput.dataset.zoneCode || null,
|
||||
};
|
||||
$.ajax({ url: '{{ route("permits.checkCompliance") }}',
|
||||
method: 'POST', data: payload })
|
||||
.done(function (res) { /* render compliance result */ });
|
||||
});
|
||||
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const downloadBtn = document.getElementById('downloadPdfBtn');
|
||||
|
||||
if(downloadBtn) {
|
||||
downloadBtn.addEventListener('click', function() {
|
||||
const element = document.getElementById('report-pdf-content');
|
||||
const inputs = element.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
if (input.tagName === 'TEXTAREA') {
|
||||
input.innerHTML = input.value;
|
||||
} else if (input.tagName === 'SELECT') {
|
||||
const selectedOption = input.options[input.selectedIndex];
|
||||
if (selectedOption) selectedOption.setAttribute('selected', 'selected');
|
||||
} else {
|
||||
input.setAttribute('value', input.value);
|
||||
}
|
||||
});
|
||||
const originalText = downloadBtn.innerHTML;
|
||||
downloadBtn.innerHTML = '<i class="bi bi-hourglass-split"></i> Generating...';
|
||||
downloadBtn.disabled = true;
|
||||
|
||||
// Configure and generate PDF
|
||||
const opt = {
|
||||
margin: 0.5,
|
||||
filename: 'Site_Inspection_Report.pdf',
|
||||
image: { type: 'jpeg', quality: 0.98 },
|
||||
html2canvas: { scale: 2, useCORS: true, logging: false },
|
||||
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' }
|
||||
};
|
||||
|
||||
html2pdf().set(opt).from(element).save().then(() => {
|
||||
downloadBtn.innerHTML = originalText;
|
||||
downloadBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
window.generateReportPDF = function() {
|
||||
console.log("1. Button clicked. Starting PDF process...");
|
||||
|
||||
if (typeof html2pdf === 'undefined') {
|
||||
alert("ERROR: The html2pdf library is not loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
const element = document.getElementById('report-pdf-content');
|
||||
if (!element) {
|
||||
alert("ERROR: Could not find the div with id='report-pdf-content'.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const inputs = element.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
if (input.tagName === 'TEXTAREA') {
|
||||
input.innerHTML = input.value;
|
||||
} else if (input.tagName === 'SELECT') {
|
||||
const selectedOption = input.options[input.selectedIndex];
|
||||
if (selectedOption) selectedOption.setAttribute('selected', 'selected');
|
||||
input.style.backgroundImage = 'none';
|
||||
input.style.appearance = 'none'; // Removes native browser arrows
|
||||
} else {
|
||||
input.setAttribute('value', input.value);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("3. Form data locked and SVGs removed. Generating file...");
|
||||
const opt = {
|
||||
margin: 0.5,
|
||||
filename: 'Site_Inspection_Report.pdf',
|
||||
image: { type: 'jpeg', quality: 0.98 },
|
||||
html2canvas: {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
logging: false,
|
||||
scrollY: 0,
|
||||
windowY: 0
|
||||
},
|
||||
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' },
|
||||
|
||||
pagebreak: {
|
||||
mode: 'css',
|
||||
avoid: ['.row', 'h6']
|
||||
}
|
||||
};
|
||||
|
||||
// Generate PDF
|
||||
html2pdf().set(opt).from(element).save().then(() => {
|
||||
console.log("Success: PDF downloaded.");
|
||||
|
||||
// THE RESTORE: Put the arrows back on the screen after download finishes
|
||||
inputs.forEach(input => {
|
||||
if (input.tagName === 'SELECT') {
|
||||
input.style.backgroundImage = '';
|
||||
input.style.appearance = '';
|
||||
}
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error("PDF Generation Error:", err);
|
||||
alert("Failed to build the PDF. Check the browser console.");
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Critical Execution Error:", error);
|
||||
alert("A javascript error occurred: " + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,336 +1,439 @@
|
||||
@extends('layouts.master')
|
||||
|
||||
@section('page-title')
|
||||
Permits | {{ $page_title }}
|
||||
@endsection
|
||||
@section('page-css')
|
||||
<style>
|
||||
@media (max-width: 768px) {
|
||||
#permit-map { height: 350px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@section('page-css')
|
||||
<link rel="stylesheet" type="text/css" href="{{ url('public/assets/css/permit_show.css') }}">
|
||||
<style>
|
||||
/* TIMELINE & STEPPER CSS */
|
||||
/* Vertical Stepper Line */
|
||||
.planning-stepper { position: relative; padding-left: 1.25rem; }
|
||||
.planning-stepper::before { content: ''; position: absolute; top: 6px; bottom: 0; left: 4px; width: 2px; background-color: #e9ecef; z-index: 0; }
|
||||
.stepper-item { position: relative; padding-bottom: 1.75rem; }
|
||||
.stepper-item:last-child { padding-bottom: 0; }
|
||||
.stepper-dot { position: absolute; left: -1.25rem; top: 0.25rem; width: 10px; height: 10px; border-radius: 50%; z-index: 1; background-color: #dee2e6; }
|
||||
.stepper-dot.completed { background-color: #20c997; }
|
||||
.stepper-dot.active { background-color: #fd7e14; box-shadow: 0 0 0 5px rgba(253, 126, 20, 0.15); }
|
||||
.stepper-dot.pending { background-color: #e2e8f0; }
|
||||
.stepper-item.pending .step-title, .stepper-item.pending .step-desc { opacity: 0.6; }
|
||||
|
||||
/* Offcanvas Timeline CSS */
|
||||
.timeline { position: relative; padding-left: 2rem; margin-top: 1rem; }
|
||||
.timeline::before { content: ''; position: absolute; top: 0; bottom: 0; left: 0.85rem; width: 2px; background-color: #dee2e6; z-index: 0; }
|
||||
.timeline-item { position: relative; margin-bottom: 1.5rem; }
|
||||
.timeline-icon { position: absolute; top: 0; left: -2rem; width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1rem; z-index: 1; }
|
||||
.timeline-content { margin-left: 0.5rem; background-color: #fff; transition: all 0.2s ease; }
|
||||
.timeline-content:hover { box-shadow: 0 .5rem 1rem rgba(0,0,0,.08)!important; }
|
||||
</style>
|
||||
@endsection
|
||||
|
||||
@section('page-content')
|
||||
|
||||
@include('permits.partials.pdf-modal')
|
||||
@include('layouts.partials.permits-navbar')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 mt-2">
|
||||
<h1 class="display-6 fw-bold text-dark">{{ $permit_arr['application_code'] }} <span class="badge bg-warning text-dark">{{ $permit_arr['status'] }}</span></h1>
|
||||
@include('permits.partials.site-inspection')
|
||||
|
||||
<div class="container py-4">
|
||||
<div class="row g-4">
|
||||
|
||||
<div class="col-lg-7 col-md-12">
|
||||
|
||||
<div class="d-flex align-items-center mb-4 mt-2 gap-3">
|
||||
<h1 class="display-6 fw-bold text-dark mb-0">{{ $permit_arr['application_code'] }}</h1>
|
||||
<span class="badge bg-warning text-dark fs-5 shadow-sm align-middle">{{ $permit_arr['status'] }}</span>
|
||||
</div>
|
||||
<div class="map-container mb-4 border border-light-subtle shadow-sm rounded overflow-hidden" style="height: 450px; background-color: #f8f9fc;">
|
||||
<iframe id="permit-map"
|
||||
src="https://pwa.lupmis4luspa.org/embed.php?mode=permit&application_code={{ urlencode($permit_arr['application_code']) }}{{ !empty($permit_arr['lon']) ? '&lon='.urlencode($permit_arr['lon']).'&lat='.urlencode($permit_arr['lat']) : '' }}{{ !empty($permit_arr['upn']) ? '&upn='.urlencode($permit_arr['upn']) : '' }}"
|
||||
style="width:100%;height:500px;border:0;border-radius:8px;"
|
||||
allow="geolocation; clipboard-write"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
loading="lazy"
|
||||
title="Permit location map">
|
||||
</iframe>
|
||||
<!-- <div class="map-container mb-4 border border-light-subtle shadow-sm rounded p-5 text-center" style="background-color: #f8f9fc;">
|
||||
<i class="bi bi-map text-secondary" style="font-size: 3rem;"></i>
|
||||
<h5 class="mt-3 text-dark">Map View Available</h5>
|
||||
<p class="text-muted">For security reasons, the interactive map must be opened in a secure window.</p>
|
||||
<a href="https://pwa.lupmis4luspa.org/embed.php?mode=permit&application_code={{ urlencode($permit_arr['application_code']) }}..." target="_blank" class="btn btn-primary mt-2">
|
||||
<i class="bi bi-box-arrow-up-right me-1"></i> Open Spatial Map
|
||||
</a>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- Map Area -->
|
||||
<!-- <div class="map-container mb-4"> -->
|
||||
<!-- <div id="applicationmap" class="applicationmap" style="width: 100%; height: 100vH;"></div> -->
|
||||
<!-- <div><img src="{{ url('public/assets/images/grey-map-with-location-pin.jpg') }}" height="500px" width="801px"></div> -->
|
||||
<!-- <img src="https://placeholdit.com/900x200/dddddd/999999?text=Project+Location" alt="Project Location" class="img-fluid" /> -->
|
||||
<!-- </div> -->
|
||||
<div class="map-container mb-4">
|
||||
<iframe id="permit-map"
|
||||
src="https://pwa.lupmis4luspa.org/embed?mode=permit&application_code={{ urlencode($permit_arr['application_code']) }}{{ !empty($permit_arr['lon']) ? '&lon='.urlencode($permit_arr['lon']).'&lat='.urlencode($permit_arr['lat']) : '' }}{{ !empty($permit_arr['upn']) ? '&upn='.urlencode($permit_arr['upn']) : '' }}"
|
||||
style="width:100%;height:500px;border:0;border-radius:8px;"
|
||||
allow="geolocation; clipboard-write"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
loading="lazy"
|
||||
title="Permit location map"></iframe>
|
||||
<div class="card border border-light-subtle shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center border-bottom pb-3 mb-4">
|
||||
<i class="bi bi-person text-primary fs-5 me-2"></i>
|
||||
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Applicant & Property Details</h6>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Applicant & Property Details -->
|
||||
<div class="remarks-section">
|
||||
|
||||
|
||||
<h3 class="section-title">Actual Project Location</h3>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="permitLocation" class="form-label fw-bold text-primary">Coordinates *</label>
|
||||
<input type="text" name="permit_location" id="permitLocation" class="form-control">
|
||||
<div class="row gy-4">
|
||||
<div class="col-12">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Applicant Name:</div>
|
||||
<div class="fw-bold text-dark fs-5">FRANCIS BULLEN GAVOR</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Nationality:</div>
|
||||
<div class="text-dark fw-medium">Motswana</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Email Address:</div>
|
||||
<div><a href="mailto:jmadjanor6@gmail.com" class="text-decoration-none text-primary">jmadjanor6@gmail.com</a></div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Telephone Number:</div>
|
||||
<div class="text-dark">0554116836</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Zonal Location:</div>
|
||||
<div class="text-dark fw-bold">TAIFA</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Mailing Address:</div>
|
||||
<div class="text-dark">1 Tomatoes St, Hse 404/12, Accra, EASTERN REGION</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Permit Type Requested</div>
|
||||
<div class="text-dark fw-bold">Development</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Zoned Classification</div>
|
||||
<div class="fw-bold" style="color: #e67e22;">Mixed-Use Low Density Residential</div>
|
||||
</div>
|
||||
<div class="col-12 mt-2">
|
||||
<div class="border border-dark-subtle rounded p-3">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Project Scope:</div>
|
||||
<div class="text-dark fst-italic" style="font-size: 0.95rem;">Proposed construction of multi-family residential building.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="permitUPN" class="form-label fw-bold text-primary">UPN</label>
|
||||
<input type="text" name="permit_upn" id="permitUPN" class="form-control">
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-warning btn-sm flex-fill" id="checkComplianceBtn"><i class="fa-solid fa-map"></i> Check Compliance</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border border-light-subtle shadow-sm mb-4" style="background-color: #f8f9fc;">
|
||||
<div class="card-body p-4">
|
||||
<h6 class="fw-bold text-uppercase text-secondary mb-4" style="letter-spacing: 0.5px; font-size: 0.85rem;">
|
||||
Actual Project Location & Cadastral Registration
|
||||
</h6>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold text-secondary small mb-1">
|
||||
Coordinates <br class="d-none d-md-block"> (Latitude, Longitude) <span class="text-danger">*</span>
|
||||
</label>
|
||||
<div class="input-group shadow-sm">
|
||||
<span class="input-group-text bg-white border-end-0 text-muted font-monospace small pe-1">GPS:</span>
|
||||
<input type="text" class="form-control border-start-0 ps-1 font-monospace" placeholder="e.g., 5.66240, -0.18055" value="5.66240,">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold text-secondary small mb-1">
|
||||
<br class="d-none d-md-block">UPN (Unique Parcel Number)
|
||||
</label>
|
||||
<input type="text" class="form-control shadow-sm" value="04-098-032-012">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold text-secondary small mb-1">
|
||||
Permit Municipal <br class="d-none d-md-block">Review Fee (GH₵) <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input type="number" class="form-control shadow-sm fw-bold" value="1250">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="processingFee" class="form-label fw-bold text-primary">Permit Fee *</label>
|
||||
<input type="text" name="processing_fees" id="processingFee" class="form-control">
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-primary flex-fill"><i class="fa-solid fa-paper-plane"></i> Submit</button>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button type="button" class="btn fw-bold px-4 text-dark shadow-sm" style="background-color: #ffb400; border-color: #ffb400;">
|
||||
Check Spatial Buffers
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary fw-bold px-4 shadow-sm">
|
||||
<i class="bi bi-map me-1"></i> Save Geospatial Bounds
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border border-light-subtle shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center border-bottom pb-3 mb-4">
|
||||
<i class="bi bi-file-earmark-text text-primary fs-5 me-2"></i>
|
||||
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Attached CAD Drawings & Documents</h6>
|
||||
</div>
|
||||
<div class="d-flex flex-column gap-3">
|
||||
<div class="border border-light-subtle rounded p-3 d-flex align-items-center justify-content-between bg-light shadow-sm transition-all">
|
||||
<div class="d-flex align-items-center text-truncate pe-3">
|
||||
<i class="bi bi-file-earmark text-primary fs-4 me-3"></i>
|
||||
<div class="text-truncate">
|
||||
<div class="text-dark fw-medium" style="font-size: 0.95rem;">Site Plan Drawing</div>
|
||||
<div class="text-secondary font-monospace text-uppercase" style="font-size: 0.75rem; letter-spacing: 0.5px;">LUSPA_TAIFA_SP3_DRAFT.jpg</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="text-decoration-none fw-semibold text-nowrap" style="font-size: 0.85rem;">
|
||||
Inspect CAD <i class="bi bi-arrow-up-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="border border-light-subtle rounded p-3 d-flex align-items-center justify-content-between bg-light shadow-sm transition-all">
|
||||
<div class="d-flex align-items-center text-truncate pe-3">
|
||||
<i class="bi bi-file-earmark text-primary fs-4 me-3"></i>
|
||||
<div class="text-truncate">
|
||||
<div class="text-dark fw-medium" style="font-size: 0.95rem;">Block Plan Drawing</div>
|
||||
<div class="text-secondary font-monospace text-uppercase" style="font-size: 0.75rem; letter-spacing: 0.5px;">LUSPA_TAIFA_BLOCK2_A.jpg</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="text-decoration-none fw-semibold text-nowrap" style="font-size: 0.85rem;">
|
||||
Inspect CAD <i class="bi bi-arrow-up-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="border border-light-subtle rounded p-3 d-flex align-items-center justify-content-between bg-light shadow-sm transition-all">
|
||||
<div class="d-flex align-items-center text-truncate pe-3">
|
||||
<i class="bi bi-file-earmark text-primary fs-4 me-3"></i>
|
||||
<div class="text-truncate">
|
||||
<div class="text-dark fw-medium" style="font-size: 0.95rem;">Architectural Drawings</div>
|
||||
<div class="text-secondary font-monospace text-uppercase" style="font-size: 0.75rem; letter-spacing: 0.5px;">TAIFA_RESID_ELEVATION.jpg</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="text-decoration-none fw-semibold text-nowrap" style="font-size: 0.85rem;">
|
||||
Inspect CAD <i class="bi bi-arrow-up-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-lg-5 col-md-12">
|
||||
<div class="sticky-top" style="top: 1.5rem; z-index: 1020;">
|
||||
|
||||
<div class="card border border-light-subtle shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex justify-content-between align-items-start border-bottom pb-3 mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-clock text-primary fs-5 me-2"></i>
|
||||
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Planning Stepper & Duration</h6>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">Remaining Days</div>
|
||||
<span class="badge text-dark fs-6 px-3 py-1" style="background-color: #fce4b4;">N/A</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-auto pe-2" style="max-height: 450px;">
|
||||
<div class="planning-stepper">
|
||||
<div class="stepper-item">
|
||||
<div class="stepper-dot completed"></div>
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">28th Feb 2026, 11:59 PM • LUPMIS ENGINE</div>
|
||||
<h6 class="fw-bold mb-1 text-dark">Submission Received</h6>
|
||||
<p class="mb-2 small text-secondary">Application successfully uploaded to LUPMIS database by applicant.</p>
|
||||
<span class="badge bg-light text-secondary border fw-medium px-2 py-1">Verified by: Applicant Portal</span>
|
||||
</div>
|
||||
<div class="stepper-item">
|
||||
<div class="stepper-dot active"></div>
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">PENDING PROCESS SCHEDULE • LUPMIS ENGINE</div>
|
||||
<h6 class="fw-bold mb-1" style="color: #e67e22;">Document Verification</h6>
|
||||
<p class="mb-2 small text-secondary">Reviewing attached Site Plan, Block Plan, and Structural drawings consistency.</p>
|
||||
<span class="badge bg-light text-secondary border fw-medium px-2 py-1">Verified by: Planning Officer</span>
|
||||
</div>
|
||||
<div class="stepper-item pending">
|
||||
<div class="stepper-dot pending"></div>
|
||||
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.7rem; letter-spacing: 0.5px;">PENDING PROCESS SCHEDULE • LUPMIS ENGINE</div>
|
||||
<h6 class="fw-bold mb-1 step-title text-secondary">Spatial GIS Alignment</h6>
|
||||
<p class="mb-2 small text-secondary step-desc">Verification of parcel boundaries against Taifa master plan & greenbelt buffers.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
<!-- Remarks -->
|
||||
<div class="details-section mt-2">
|
||||
<h2 class="section-title text-primary">
|
||||
Applicant & Property Details
|
||||
</h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<!-- <h6 class="detail-label">Project Description</h6> -->
|
||||
<p class="detail-value"></p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Name</h6>
|
||||
<p class="detail-value">{{ $permit_arr['applicant_name'] }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Nationality</h6>
|
||||
<p class="detail-value">{{ $permit_arr['nationality'] }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Email</h6>
|
||||
<p class="detail-value">{{ $permit_arr['email'] }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Telephone Number</h6>
|
||||
<p class="detail-value">{{ $permit_arr['phone'] }}</p>
|
||||
</div>
|
||||
<div class="remarks-section card border border-light-subtle shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center border-bottom pb-3 mb-4">
|
||||
<i class="fa-solid fa-comments text-primary fs-5 me-2"></i>
|
||||
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Status & Comments</h6>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h6 class="detail-label">Address</h6>
|
||||
<p class="detail-value">{{ $permit_arr['address'] }}, {{ $permit_arr['city'] }}, {{ $permit_arr['region'] }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Project Location</h6>
|
||||
<p class="detail-value">{{ $permit_arr['project_location'] }}</p>
|
||||
</div>
|
||||
<!-- <div class="col-md-4">
|
||||
<h6 class="detail-label">Parcel Size</h6>
|
||||
<p class="detail-value">0.25 Acre</p>
|
||||
</div> -->
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Permit Type</h6>
|
||||
<p class="detail-value">{{ $permit_arr['permit_type'] }}</p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<h6 class="detail-label">Description</h6>
|
||||
<p class="detail-value">{{ $permit_arr['project_description'] }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Purpose</h6>
|
||||
<p class="detail-value">{{ $permit_arr['land_request_use'] }}</p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<button class="btn btn-outline-secondary w-100 fw-bold" type="button" data-bs-toggle="offcanvas" data-bs-target="#historyOffcanvas" aria-controls="historyOffcanvas">
|
||||
<i class="bi bi-clock-history me-1"></i> View Full History & Comments
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Planning Permit Application -->
|
||||
<div class="details-section mt-4">
|
||||
<h2 class="section-title text-primary">Documents</h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<!-- <h6 class="detail-label">Architectural Drawings</h6> -->
|
||||
<p class="detail-value">
|
||||
<a href="#" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#pdfModal" data-pdf-url="{{ route('view.pdf', ['filename' => 'site_plan.pdf']) }}">
|
||||
<i class="fa-solid fa-paperclip"></i> Site Plan
|
||||
</a>
|
||||
<!-- <a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Site Plan</a> -->
|
||||
</p>
|
||||
<p class="detail-value">
|
||||
<!-- <a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Architectural Drawings</a> -->
|
||||
<a href="#" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#pdfModal" data-pdf-url="{{ route('view.pdf', ['filename' => 'architectural_drawing.pdf']) }}">
|
||||
<i class="fa-solid fa-paperclip"></i> Architectural Drawings
|
||||
</a>
|
||||
</p>
|
||||
<!-- <p class="detail-value"><a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Working Drawing</a></p> -->
|
||||
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<!-- <h6 class="detail-label">Others</h6> -->
|
||||
|
||||
<p class="detail-value">
|
||||
<!-- <a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Block Plan</a> -->
|
||||
<a href="#" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#pdfModal" data-pdf-url="{{ route('view.pdf', ['filename' => 'block_plan.pdf']) }}">
|
||||
<i class="fa-solid fa-paperclip"></i> Block Plan
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<!-- <h6 class="detail-label">Architectural Drawings</h6> -->
|
||||
<!-- <p class="detail-value"><a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Zoning Report</a></p> -->
|
||||
<!-- <p class="detail-value"><a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Working Drawing</a></p> -->
|
||||
|
||||
</div>
|
||||
<!-- <div class="col-md-6">
|
||||
<p class="detail-value"><a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Site Plan</a></p>
|
||||
<p class="detail-value"><a href="" class="btn btn-link"><i class="fa-solid fa-paperclip"></i> Title Certificate</a></p>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="details-section mt-4">
|
||||
<h2 class="section-title text-primary">Duration</h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Date of Application</h6>
|
||||
<p class="detail-value">
|
||||
<?php
|
||||
$originalDate = "2023-05-31 01:16:06";
|
||||
$unixTime = strtotime($permit_arr['submitted_at']);
|
||||
$normal_datetime = date("jS F, Y @ g:i A", $unixTime);
|
||||
|
||||
echo $normal_datetime;
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="detail-label">Processing Days Remaining</h6>
|
||||
<p class="detail-value">
|
||||
<span class="badge bg-warning text-dark">
|
||||
<?php
|
||||
if ($permit_arr['status'] == 'submitted') {
|
||||
echo "N/A";
|
||||
}
|
||||
?>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="remarks-section">
|
||||
<h3 class="section-title">Remarks</h3>
|
||||
<div class="row mb-3">
|
||||
<label for="permitCurrentStatus" class="form-label fw-bold text-primary"><i class="fa-solid fa-temperature-three-quarters"></i> Status Update</label>
|
||||
@if(in_array(session('current_user.ua_position'), $allowed_users_to_comment))
|
||||
<select class="form-select form-select-lg" id="permitCurrentStatus">
|
||||
<option value="" selected disabled>First select status ...</option>
|
||||
<option value="status_five">Applicant to Review</option>
|
||||
<div class="mb-4">
|
||||
<label for="permitCurrentStatus" class="form-label fw-bold text-secondary small mb-2" style="letter-spacing: 0.5px;">
|
||||
<i class="fa-solid fa-temperature-three-quarters text-primary me-1"></i> Status Update
|
||||
</label>
|
||||
<select class="form-select shadow-sm" id="permitCurrentStatus">
|
||||
<option value="" selected disabled>Select status ...</option>
|
||||
<option value="status_zero">Applicant to Review</option>
|
||||
<option value="status_one">Application Received</option>
|
||||
<option value="status_two" disabled>Application Accepted</option>
|
||||
<option value="status_three" disabled>Permit Pending TSC Review</option>
|
||||
<option value="status_four" disabled>Permit Pending SPC Decision</option>
|
||||
<option value="status_five" disabled>Permit Approved (In Principle)</option>
|
||||
<option value="status_size" disabled>Permit Approved</option>
|
||||
<option value="status_seven" disabled>Permit Refused</option>
|
||||
<option value="status_eight" disabled>Permit Deferred</option>
|
||||
</select>
|
||||
@else
|
||||
<p class="badge bg-success text-dark">You don't have the right permission to update the status.</p>
|
||||
@endif
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
@if(in_array(session('current_user.ua_position'), $allowed_users_to_comment))
|
||||
<textarea class="form-control mb-3" rows="4" placeholder="Write a message" ></textarea>
|
||||
@else
|
||||
<p class="badge bg-info text-dark">You don't have the right permission to add comments.</p>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-primary flex-fill"><i class="fa-solid fa-paper-plane"></i> Submit</button>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<textarea class="form-control shadow-sm commentBody" rows="4" name="comment_body" placeholder="Enter new comment here"></textarea>
|
||||
<input type="hidden" class="applicationCode" name="application_code" value="{{ $permit_arr['application_code'] }}">
|
||||
</div>
|
||||
<button class="btn btn-primary w-100 fw-bold shadow-sm submitPermitCommentBtn" type="button">
|
||||
<i class="fa-solid fa-paper-plane me-1"></i> Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> </div> </div>
|
||||
</div>
|
||||
|
||||
<div class="offcanvas offcanvas-end shadow-lg" tabindex="-1" id="historyOffcanvas" aria-labelledby="historyOffcanvasLabel" style="width: 450px;">
|
||||
<div class="offcanvas-header border-bottom bg-light sticky-top z-3">
|
||||
<h5 class="offcanvas-title fw-bold" id="historyOffcanvasLabel">Application History</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<div class="timeline" id="historyTimeline">
|
||||
<div class="text-center text-muted mt-5">
|
||||
<i class="bi bi-arrow-clockwise fs-1"></i>
|
||||
<p class="mt-2">Loading application history...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
@section('page-js')
|
||||
<!-- <script src="{{ url('public/assets/libs/select2/dist/js/select2.full.min.js') }}" type="text/javascript" ></script> -->
|
||||
|
||||
@section('page-scripts')
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
|
||||
<!-- <script src="{{ url('public/assets/js/permit_tools.js') }}" type="text/javascript" ></script> -->
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
|
||||
var pdfModal = document.getElementById('pdfModal');
|
||||
pdfModal.addEventListener('show.bs.modal', function (event) {
|
||||
// Button that triggered the modal
|
||||
var button = event.relatedTarget;
|
||||
// Extract info from data-bs-* attributes
|
||||
var pdfUrl = button.getAttribute('data-pdf-url');
|
||||
// Update the modal's iframe src
|
||||
var pdfIframe = document.getElementById('pdfIframe');
|
||||
pdfIframe.src = pdfUrl;
|
||||
const MAP_ORIGIN = 'https://pwa.lupmis4luspa.org';
|
||||
const iframe = document.getElementById('permit-map');
|
||||
const upnInput = document.getElementById('permitUPN');
|
||||
const locInput = document.getElementById('permitLocation');
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.origin !== MAP_ORIGIN) return;
|
||||
const msg = event.data;
|
||||
if (!msg || typeof msg !== 'object') return;
|
||||
|
||||
switch (msg.type) {
|
||||
case 'ready':
|
||||
@if(!empty($permit_arr['upn']))
|
||||
iframe.contentWindow.postMessage(
|
||||
{ type: 'set:selected', upn: @json($permit_arr['upn']) },
|
||||
MAP_ORIGIN);
|
||||
@endif
|
||||
break;
|
||||
|
||||
case 'parcel:select':
|
||||
upnInput.value = msg.upn || '';
|
||||
locInput.value = (msg.lon != null && msg.lat != null)
|
||||
? msg.lat.toFixed(6) + ', ' + msg.lon.toFixed(6)
|
||||
: '';
|
||||
upnInput.dataset.parcelId = msg.parcel_id || '';
|
||||
upnInput.dataset.zoneCode = msg.zone_code || '';
|
||||
break;
|
||||
|
||||
case 'parcel:cleared':
|
||||
upnInput.value = '';
|
||||
locInput.value = '';
|
||||
delete upnInput.dataset.parcelId;
|
||||
delete upnInput.dataset.zoneCode;
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
console.warn('[permit-map]', msg.code, msg.message);
|
||||
break;
|
||||
}
|
||||
|
||||
$('#checkComplianceBtn').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
const payload = {
|
||||
application_code: @json($permit_arr['application_code']),
|
||||
upn: upnInput.value,
|
||||
parcel_id: upnInput.dataset.parcelId || null,
|
||||
coordinates: locInput.value,
|
||||
zone_code: upnInput.dataset.zoneCode || null,
|
||||
};
|
||||
$.ajax({ url: '{{ route("permits.checkCompliance") }}',
|
||||
method: 'POST', data: payload })
|
||||
.done(function (res) { /* render compliance result */ });
|
||||
});
|
||||
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// === PDF GENERATION LOGIC ===
|
||||
window.generateReportPDF = function() {
|
||||
console.log("1. Button clicked. Starting PDF process...");
|
||||
|
||||
// Optional: Clear the iframe src when the modal is closed to stop the stream
|
||||
pdfModal.addEventListener('hidden.bs.modal', function () {
|
||||
var pdfIframe = document.getElementById('pdfIframe');
|
||||
pdfIframe.src = '';
|
||||
});
|
||||
|
||||
const MAP_ORIGIN = 'https://pwa.lupmis4luspa.org';
|
||||
const iframe = document.getElementById('permit-map');
|
||||
const upnInput = document.getElementById('permitUPN');
|
||||
const locInput = document.getElementById('permitLocation');
|
||||
|
||||
// --- Receive events FROM the map ---
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.origin !== MAP_ORIGIN) return; // SECURITY
|
||||
const msg = event.data;
|
||||
if (!msg || typeof msg !== 'object') return;
|
||||
|
||||
switch (msg.type) {
|
||||
case 'ready':
|
||||
@if(!empty($permit_arr['upn']))
|
||||
iframe.contentWindow.postMessage(
|
||||
{ type: 'set:selected', upn: @json($permit_arr['upn']) },
|
||||
MAP_ORIGIN);
|
||||
@endif
|
||||
break;
|
||||
|
||||
case 'parcel:select':
|
||||
upnInput.value = msg.upn || '';
|
||||
locInput.value = (msg.lon != null && msg.lat != null)
|
||||
? msg.lat.toFixed(6) + ', ' + msg.lon.toFixed(6)
|
||||
: '';
|
||||
upnInput.dataset.parcelId = msg.parcel_id || '';
|
||||
upnInput.dataset.zoneCode = msg.zone_code || '';
|
||||
break;
|
||||
|
||||
case 'parcel:cleared':
|
||||
upnInput.value = '';
|
||||
locInput.value = '';
|
||||
delete upnInput.dataset.parcelId;
|
||||
delete upnInput.dataset.zoneCode;
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
console.warn('[permit-map]', msg.code, msg.message);
|
||||
break;
|
||||
if (typeof html2pdf === 'undefined') {
|
||||
alert("ERROR: The html2pdf library is not loaded.");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// --- Wire "Check Compliance" to use what the map gave us ---
|
||||
// $('button:contains("Check Compliance")').on('click', function (e) {
|
||||
|
||||
$('#checkComplianceBtn').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
const payload = {
|
||||
application_code: @json($permit_arr['application_code']),
|
||||
upn: upnInput.value,
|
||||
parcel_id: upnInput.dataset.parcelId || null,
|
||||
coordinates: locInput.value,
|
||||
zone_code: upnInput.dataset.zoneCode || null,
|
||||
};
|
||||
$.ajax({ url: '{{ route("permits.checkCompliance") }}',
|
||||
method: 'POST', data: payload })
|
||||
.done(function (res) { /* render compliance result */ });
|
||||
});
|
||||
|
||||
const element = document.getElementById('report-pdf-content');
|
||||
if (!element) {
|
||||
alert("ERROR: Could not find the div with id='report-pdf-content'.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Lock in the form values AND remove problem images
|
||||
const inputs = element.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
if (input.tagName === 'TEXTAREA') {
|
||||
input.innerHTML = input.value;
|
||||
} else if (input.tagName === 'SELECT') {
|
||||
// Lock the selected value
|
||||
const selectedOption = input.options[input.selectedIndex];
|
||||
if (selectedOption) selectedOption.setAttribute('selected', 'selected');
|
||||
|
||||
// THE FIX: Remove the SVG background arrow that crashes html2canvas
|
||||
input.style.backgroundImage = 'none';
|
||||
input.style.appearance = 'none'; // Removes native browser arrows
|
||||
} else {
|
||||
input.setAttribute('value', input.value);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("3. Form data locked and SVGs removed. Generating file...");
|
||||
const opt = {
|
||||
margin: 0.5,
|
||||
filename: 'Site_Inspection_Report.pdf',
|
||||
image: { type: 'jpeg', quality: 0.98 },
|
||||
html2canvas: {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
logging: false,
|
||||
scrollY: 0,
|
||||
windowY: 0
|
||||
},
|
||||
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' },
|
||||
|
||||
pagebreak: {
|
||||
mode: 'css',
|
||||
avoid: ['.row', 'h6']
|
||||
}
|
||||
};
|
||||
|
||||
// Generate PDF
|
||||
html2pdf().set(opt).from(element).save().then(() => {
|
||||
console.log("Success: PDF downloaded.");
|
||||
|
||||
// THE RESTORE: Put the arrows back on the screen after download finishes
|
||||
inputs.forEach(input => {
|
||||
if (input.tagName === 'SELECT') {
|
||||
input.style.backgroundImage = '';
|
||||
input.style.appearance = '';
|
||||
}
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error("PDF Generation Error:", err);
|
||||
alert("Failed to build the PDF. Check the browser console.");
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Critical Execution Error:", error);
|
||||
alert("A javascript error occurred: " + error.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
|
||||
|
||||
@endsection
|
||||
176
php_code/resources/views/test.blade.php
Normal file
176
php_code/resources/views/test.blade.php
Normal file
File diff suppressed because one or more lines are too long
@ -4,13 +4,18 @@ use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Middleware\CheckBackendSession;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Mail\UserAccountsMail;
|
||||
|
||||
use App\Http\Controllers\Admin\UserController;
|
||||
Route::get('/send-test-email', function () {
|
||||
$recipientEmail = 'recipient@example.com';
|
||||
Mail::to($recipientEmail)->send(new UserAccountsMail());
|
||||
dd('Email sent!');
|
||||
});
|
||||
|
||||
Route::get('/test', function () {
|
||||
// return view('admin.hometwo');
|
||||
return view('admin.home_new');
|
||||
});
|
||||
|
||||
|
||||
#Auth::routes();
|
||||
Auth::routes([
|
||||
@ -19,6 +24,21 @@ Auth::routes([
|
||||
'verify' => false, // Disables the email verification routes
|
||||
]);
|
||||
|
||||
|
||||
|
||||
|
||||
Route::middleware(['auth','can:manage-users'])->prefix('admin')->name('admin.')->group(function(){
|
||||
Route::get('users', [UserController::class,'index'])->name('users.index'); // shows Blade
|
||||
Route::get('users/list', [UserController::class,'list'])->name('users.list'); // returns JSON filtered list
|
||||
Route::get('users/{id}', [UserController::class,'show'])->name('users.show'); // user details
|
||||
Route::post('users', [UserController::class,'store'])->name('users.store'); // create
|
||||
Route::put('users/{id}', [UserController::class,'update'])->name('users.update'); // update
|
||||
Route::post('users/move', [UserController::class,'move'])->name('users.move'); // move multiple
|
||||
Route::post('users/deactivate', [UserController::class,'deactivate'])->name('users.deactivate'); // bulk deactivate
|
||||
Route::delete('users/{id}', [UserController::class,'destroy'])->name('users.destroy'); // delete
|
||||
});
|
||||
|
||||
|
||||
Route::get('/sso/validate', [App\Http\Controllers\SsoController::class, 'validateToken']);
|
||||
|
||||
|
||||
@ -71,15 +91,21 @@ Route::middleware([CheckBackendSession::class])->group(function () {
|
||||
|
||||
|
||||
Route::get('/users/edit/{user_id}', [App\Http\Controllers\UsersController::class, 'edit']);
|
||||
|
||||
|
||||
Route::post('/permits/addcomment', [App\Http\Controllers\PermitsController::class, 'addComment']);
|
||||
Route::post('/permits/getcomments', [App\Http\Controllers\PermitsController::class, 'getComments']);
|
||||
|
||||
Route::get('/permits/home', [App\Http\Controllers\PermitsController::class, 'index']);
|
||||
Route::get('/permits/reports', [App\Http\Controllers\PermitsController::class, 'reports']);
|
||||
Route::post('/permits/checkcompliance', [App\Http\Controllers\PermitsController::class, 'checkCompliance'])->name('permits.checkCompliance');
|
||||
Route::get('/permits/districtsettings', [App\Http\Controllers\PermitsController::class, 'settings']);
|
||||
|
||||
|
||||
Route::get('/permits/viewapplication/{id}', [App\Http\Controllers\PermitsController::class, 'show']);
|
||||
Route::get('/permits/status/{name}', [App\Http\Controllers\PermitsController::class, 'statusIndex']);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
50
php_code/user-transfer-logic.md
Normal file
50
php_code/user-transfer-logic.md
Normal file
@ -0,0 +1,50 @@
|
||||
Great—time-accurate historical data is exactly what the “effective-dated assignment” approach is for.
|
||||
|
||||
## Recommended flow (historical accuracy)
|
||||
### 1) Model “which branch the user belonged to when”
|
||||
Use a history table, e.g. `user_branch_assignments`:
|
||||
|
||||
- `user_id`
|
||||
- `branch_id`
|
||||
- `starts_at`
|
||||
- `ends_at` (nullable; `null` = current)
|
||||
- timestamps
|
||||
|
||||
Your rule for history:
|
||||
- For any `user_id`, at a given timestamp, exactly one assignment row is “active” (`starts_at <= t` and (`ends_at` is null or `t < ends_at`)).
|
||||
|
||||
### 2) Model branch-specific activities against the assignment
|
||||
For any activity that should be tied to the user’s branch “at the time it happened”, store:
|
||||
|
||||
- `user_branch_assignment_id` (FK)
|
||||
- `occurred_at` (the activity time)
|
||||
- activity fields
|
||||
|
||||
When creating an activity at time `occurred_at`, you must pick the assignment row that was active at that time.
|
||||
|
||||
## Transfer logic (what happens at transfer time)
|
||||
At transfer time `T`:
|
||||
1. Close current assignment (branch A):
|
||||
- set `ends_at = T`
|
||||
2. Create new assignment (branch B):
|
||||
- set `starts_at = T`
|
||||
- `ends_at = null`
|
||||
|
||||
From then on:
|
||||
- old activities remain linked to the old assignment
|
||||
- new activities link to the new assignment
|
||||
|
||||
## Query pattern you’ll use
|
||||
- “Current branch for a user”:
|
||||
- assignment where `ends_at is null`
|
||||
- “Activities for a user in branch X at time range” (historical):
|
||||
- join activities → `user_branch_assignments` and filter by `branch_id`
|
||||
|
||||
## Laravel-specific implementation note (important)
|
||||
When inserting an activity with `occurred_at`, do this in a transaction:
|
||||
- read the assignment active at `occurred_at`
|
||||
- create the activity pointing to `user_branch_assignment_id`
|
||||
|
||||
Optionally lock the user’s assignment rows to avoid edge-case races around the transfer timestamp.
|
||||
|
||||
If you share your current activity table name + the column you use for the activity time (e.g. `created_at` vs `occurred_at`), I can sketch the exact Laravel query/transaction structure.
|
||||
Loading…
x
Reference in New Issue
Block a user