diff --git a/php_code/app/Http/Controllers/Admin/UserController.php b/php_code/app/Http/Controllers/Admin/UserController.php
new file mode 100644
index 00000000..37b9250d
--- /dev/null
+++ b/php_code/app/Http/Controllers/Admin/UserController.php
@@ -0,0 +1,105 @@
+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]);
+ }
+}
diff --git a/php_code/app/Http/Controllers/AdminController.php b/php_code/app/Http/Controllers/AdminController.php
index 9f96d031..a2350d5f 100644
--- a/php_code/app/Http/Controllers/AdminController.php
+++ b/php_code/app/Http/Controllers/AdminController.php
@@ -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();
+ }
}
diff --git a/php_code/app/Http/Controllers/Controller.php b/php_code/app/Http/Controllers/Controller.php
index 77ec359a..250c2bc2 100644
--- a/php_code/app/Http/Controllers/Controller.php
+++ b/php_code/app/Http/Controllers/Controller.php
@@ -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
+ }
+}
+
}
diff --git a/php_code/app/Http/Controllers/PermitsController.php b/php_code/app/Http/Controllers/PermitsController.php
index 1a11fde7..b7e0c3a7 100644
--- a/php_code/app/Http/Controllers/PermitsController.php
+++ b/php_code/app/Http/Controllers/PermitsController.php
@@ -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);
diff --git a/php_code/app/Http/Controllers/UserloginController.php b/php_code/app/Http/Controllers/UserloginController.php
index 05675074..3e2e6853 100644
--- a/php_code/app/Http/Controllers/UserloginController.php
+++ b/php_code/app/Http/Controllers/UserloginController.php
@@ -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);
diff --git a/php_code/app/Http/Controllers/UsersController.php b/php_code/app/Http/Controllers/UsersController.php
index 4f88a298..640d8c9f 100644
--- a/php_code/app/Http/Controllers/UsersController.php
+++ b/php_code/app/Http/Controllers/UsersController.php
@@ -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';
}
diff --git a/php_code/app/Models/PermitApplicationComments.php b/php_code/app/Models/PermitApplicationComments.php
new file mode 100644
index 00000000..ca038e22
--- /dev/null
+++ b/php_code/app/Models/PermitApplicationComments.php
@@ -0,0 +1,10 @@
+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');
+ }
+};
diff --git a/php_code/info.md b/php_code/info.md
index 53f5aee6..a21b6135 100644
--- a/php_code/info.md
+++ b/php_code/info.md
@@ -2,6 +2,9 @@
- assiamah/win
- kwesilupmis/7aa0478bce
+Username: saxiquzipa/lwCDfAMaBy
+
+
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"
\ No newline at end of file
+ "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;
+
+
diff --git a/php_code/live.env b/php_code/live.env
new file mode 100644
index 00000000..918fd6eb
--- /dev/null
+++ b/php_code/live.env
@@ -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}"
\ No newline at end of file
diff --git a/php_code/public/assets/css/permit_show.css b/php_code/public/assets/css/permit_show.css
new file mode 100644
index 00000000..1a40f523
--- /dev/null
+++ b/php_code/public/assets/css/permit_show.css
@@ -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;
+}
\ No newline at end of file
diff --git a/php_code/public/assets/js/add_edit_user copy 2.js b/php_code/public/assets/js/add_edit_user copy 2.js
new file mode 100644
index 00000000..f2054b9b
--- /dev/null
+++ b/php_code/public/assets/js/add_edit_user copy 2.js
@@ -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('');
+
+ if (optionsMap['district_user']) {
+ $.each(optionsMap['district_user'], function(index, value) {
+ $dropdown.append($("").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('');
+
+ if (optionsMap[userValue]) {
+ $.each(optionsMap[userValue], function(index, value) {
+ $dropdown.append($("")
+ .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("
"));
+ 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("
"));
+ });
+
+ $('#editErrorArea').removeClass("d-none").html(messages.join("
"));
+
+ $.alert({
+ title: "Validation Error",
+ content: messages.join("
"),
+ });
+ } 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($("").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("
"));
+ 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("
"));
+ });
+
+ $errorArea.removeClass("d-none").html(messages.join("
"));
+
+ $.alert({
+ title: "Validation Error",
+ content: messages.join("
"),
+ });
+ } else {
+ // Other errors
+ $errorArea.removeClass("d-none").text("Request failed: " + error);
+
+ $.alert({
+ title: "Error",
+ content: "Request failed: " + error,
+ });
+ }
+ },
+ });
+
+ });
+});
\ No newline at end of file
diff --git a/php_code/public/assets/js/add_edit_user copy.js b/php_code/public/assets/js/add_edit_user copy.js
new file mode 100644
index 00000000..6933613c
--- /dev/null
+++ b/php_code/public/assets/js/add_edit_user copy.js
@@ -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('');
+
+ if (optionsMap['district_user']) {
+ $.each(optionsMap['district_user'], function(index, value) {
+ $dropdown.append($("").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('');
+
+ if (optionsMap[userValue]) {
+ $.each(optionsMap[userValue], function(index, value) {
+ $dropdown.append($("")
+ .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("
"));
+ 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("
"));
+ });
+
+ $('#editErrorArea').removeClass("d-none").html(messages.join("
"));
+
+ $.alert({
+ title: "Validation Error",
+ content: messages.join("
"),
+ });
+ } 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($("").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("
"));
+ 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("
"));
+ });
+
+ $errorArea.removeClass("d-none").html(messages.join("
"));
+
+ $.alert({
+ title: "Validation Error",
+ content: messages.join("
"),
+ });
+ } else {
+ // Other errors
+ $errorArea.removeClass("d-none").text("Request failed: " + error);
+
+ $.alert({
+ title: "Error",
+ content: "Request failed: " + error,
+ });
+ }
+ },
+ });
+
+ });
+});
\ No newline at end of file
diff --git a/php_code/public/assets/js/add_edit_user.js b/php_code/public/assets/js/add_edit_user.js
new file mode 100644
index 00000000..d5ab8407
--- /dev/null
+++ b/php_code/public/assets/js/add_edit_user.js
@@ -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('');
+ if (optionsMap[userType]) {
+ $.each(optionsMap[userType], function(index, value) {
+ targetDropdown.append($("").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('');
+
+ 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($("").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("
"));
+ 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("
"));
+ });
+ $errorArea.removeClass("d-none").html(messages.join("
"));
+ $.alert({ title: "Validation Error", content: messages.join("
") });
+ } 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");
+ });
+
+});
\ No newline at end of file
diff --git a/php_code/public/assets/js/add_edit_user.js.bak b/php_code/public/assets/js/add_edit_user.js.bak
new file mode 100644
index 00000000..f2054b9b
--- /dev/null
+++ b/php_code/public/assets/js/add_edit_user.js.bak
@@ -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('');
+
+ if (optionsMap['district_user']) {
+ $.each(optionsMap['district_user'], function(index, value) {
+ $dropdown.append($("").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('');
+
+ if (optionsMap[userValue]) {
+ $.each(optionsMap[userValue], function(index, value) {
+ $dropdown.append($("")
+ .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("
"));
+ 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("
"));
+ });
+
+ $('#editErrorArea').removeClass("d-none").html(messages.join("
"));
+
+ $.alert({
+ title: "Validation Error",
+ content: messages.join("
"),
+ });
+ } 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($("").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("
"));
+ 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("
"));
+ });
+
+ $errorArea.removeClass("d-none").html(messages.join("
"));
+
+ $.alert({
+ title: "Validation Error",
+ content: messages.join("
"),
+ });
+ } else {
+ // Other errors
+ $errorArea.removeClass("d-none").text("Request failed: " + error);
+
+ $.alert({
+ title: "Error",
+ content: "Request failed: " + error,
+ });
+ }
+ },
+ });
+
+ });
+});
\ No newline at end of file
diff --git a/php_code/public/assets/js/permit_backend.js b/php_code/public/assets/js/permit_backend.js
new file mode 100644
index 00000000..e69de29b
diff --git a/php_code/public/assets/js/permit_comments.js b/php_code/public/assets/js/permit_comments.js
new file mode 100644
index 00000000..c1fa7ecc
--- /dev/null
+++ b/php_code/public/assets/js/permit_comments.js
@@ -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(`
+ //
${item.app_comments}
+No history or comments found for this application.
+| + | Name | +Role | +Status | +Allowed Apps | +Actions | +|
|---|---|---|---|---|---|---|
| + |
+
+
+
+
+ {{ $user['title'] }} {{ $user['full_name'] }}
+ Username: {{ $user['username'] }}
+ |
+ {{ $user['email'] }} | +{{ ucwords(str_replace('-', ' ', $user['ua_position'])) }} | ++ {{ $status }} + | +{{ $user['allowed_apps'] }} | +
+
+
+
+
+ |
+
| No district users found. | ||||||
| + | Person | +Role | +Status | +Allowed Apps | +Actions | +|
|---|---|---|---|---|---|---|
| + |
+
+
+
+
+ {{ $user['title'] }} {{ $user['full_name'] }}
+ Username: {{ $user['username'] }}
+ |
+ {{ $user['email'] }} | +{{ ucwords(str_replace('-', ' ', $user['ua_position'])) }} | ++ {{ $status }} + | +{{ $user['allowed_apps'] }} | +
+
+
+
+
+ |
+
| No regional users found. | ||||||
| + | Person | +Role | +Status | +Allowed Apps | +Actions | +|
|---|---|---|---|---|---|---|
| + |
+
+
+
+
+ {{ $user['title'] }} {{ $user['full_name'] }}
+ Username: {{ $user['username'] }}
+ |
+ {{ $user['email'] }} | +{{ ucwords(str_replace('-', ' ', $user['ua_position'])) }} | ++ {{ $status }} + | +{{ $user['allowed_apps'] }} | +
+
+
+
+
+ |
+
| No national users found. | ||||||
| + | Person | +Role | +Last activity | +Actions | +|
|---|---|---|---|---|---|
| + |
+
+
+
+
+ Alice Doe
+ ID: D001
+ |
+ alice@example.com | +District · Manager | +2026-06-10 | +
+
+
+
+
+ |
+
| + |
+
+
+
+
+ Ben Kyle
+ ID: D002
+ |
+ ben@example.com | +District · Staff | +2026-06-12 | +
+
+
+
+
+ |
+
| + |
+
+
+
+
+ Clara Park
+ ID: D003
+ |
+ clara@example.com | +District · Supervisor | +2026-05-30 | +
+
+
+
+
+ |
+
| + | Person | +Role | +Last activity | +Actions | +|
|---|---|---|---|---|---|
| + |
+
+
+
+
+ Derek Holt
+ ID: R001
+ |
+ derek@example.com | +Regional · Lead | +2026-06-13 | +
+
+
+
+
+ |
+
| + |
+
+
+
+
+ Eva Stone
+ ID: R002
+ |
+ eva@example.com | +Regional · Coordinator | +2026-06-11 | +
+
+
+
+
+ |
+
| + | Person | +Role | +Last activity | +Actions | +|
|---|---|---|---|---|---|
| + |
+
+
+
+
+ Frank West
+ ID: N001
+ |
+ frank@example.com | +National · Director | +2026-06-05 | +
+
+
+
+
+ |
+
| + | Person | +Role | +Allowed Apps | +Actions | +|
|---|---|---|---|---|---|
| + |
+
+
+
+
+ Alice Doe
+ ID: D001
+ |
+ alice@example.com | +District · Manager | +2026-06-10 | +
+
+
+
+
+ |
+
| + |
+
+
+
+
+ Ben Kyle
+ ID: D002
+ |
+ ben@example.com | +District · Staff | +2026-06-12 | +
+
+
+
+
+ |
+
| + |
+
+
+
+
+ Clara Park
+ ID: D003
+ |
+ clara@example.com | +District · Supervisor | +2026-05-30 | +
+
+
+
+
+ |
+
| + | Person | +Role | +Allowed Apps | +Actions | +|
|---|---|---|---|---|---|
| + |
+
+
+
+
+ Derek Holt
+ ID: R001
+ |
+ derek@example.com | +Regional · Lead | +2026-06-13 | +
+
+
+
+
+ |
+
| + |
+
+
+
+
+ Eva Stone
+ ID: R002
+ |
+ eva@example.com | +Regional · Coordinator | +2026-06-11 | +
+
+
+
+
+ |
+
| + | Person | +Role | +Allowed Apps | +Actions | +|
|---|---|---|---|---|---|
| + |
+
+
+
+
+ Frank West
+ ID: N001
+ |
+ frank@example.com | +National · Director | +2026-06-05 | +
+
+
+
+
+ |
+
| Person | Role | Last activity | Actions |
|---|
| Person | Role | Last activity | Actions |
|---|
| Person | Role | Last activity | Actions |
|---|
| + | Person | +Role | +Last activity | +Actions | +
|---|
| + | Person | +Role | +Last activity | +Actions | +
|---|
| + | Person | +Role | +Last activity | +Actions | +
|---|