From 066b9fd2d8875d40aa783e527b147953ba7d5ddd Mon Sep 17 00:00:00 2001 From: Kwesi Banson Jnr Date: Sun, 23 Aug 2026 22:15:17 +0000 Subject: [PATCH] Bug fixes in User Mgt and Permit --- info.md | 52 +++ lupmis.sqlite | Bin 0 -> 188416 bytes .../Http/Controllers/Admin/UserController.php | 4 +- .../app/Http/Controllers/AdminController.php | 84 +++-- .../Http/Controllers/PermitsController.php | 78 ++++- .../app/Http/Controllers/UsersController.php | 126 +++++-- .../Http/Controllers/UtilityController.php | 12 + php_code/app/Models/User.php | 19 + .../app/Services/MicrosoftMailService.php | 65 ++++ php_code/config/usergroupmatrix.php | 326 ++++++++++++++++++ php_code/info.md | 41 +-- php_code/public/assets/js/add_edit_user.js | 94 +++-- php_code/public/assets/js/permits_index.js | 30 ++ .../admin/partials/create-user.blade.php | 13 +- .../views/admin/partials/edit-user.blade.php | 6 +- .../views/layouts/partials/navbar.blade.php | 7 +- .../views/permits/dashboard.blade.php | 224 ++++++------ .../views/permits/dashboard_old.blade.php | 104 ++++++ .../resources/views/permits/index.blade.php | 4 +- php_code/routes/web.php | 25 +- workshop-koforidua-2026.md | 63 ++++ workshop-tamale-2026.md | 80 +++++ 22 files changed, 1186 insertions(+), 271 deletions(-) create mode 100644 info.md create mode 100644 lupmis.sqlite create mode 100644 php_code/app/Services/MicrosoftMailService.php create mode 100644 php_code/config/usergroupmatrix.php create mode 100644 php_code/public/assets/js/permits_index.js create mode 100644 php_code/resources/views/permits/dashboard_old.blade.php create mode 100644 workshop-koforidua-2026.md create mode 100644 workshop-tamale-2026.md diff --git a/info.md b/info.md new file mode 100644 index 00000000..07c2847f --- /dev/null +++ b/info.md @@ -0,0 +1,52 @@ +# Tit Bits +## API Base URL +- https://api.lupmis4luspa.org/ + + +```` +- g!_ArOqhC$Gs9-u2 +```` + +## API Testing Tool +- https://restful-api.dev/ + +git remote add https://gitea.lupmis4luspa.org/kwesigitea/main-backend.git + +title - add N/A + +from the server 29-10-2025 +services: + l4lauthentication-docker: + container_name: l4lauthentication + build: . + volumes: + - ./php_code:/var/www/html + #ports: + #- 8831:80 + restart: unless-stopped + networks: + - nginx_proxy_manager_default +networks: + nginx_proxy_manager_default: + external: true + + + +curl -X 'POST' \ + 'https://api.lupmis4luspa.org/api/user_mgt/get_district_by_region_id.php' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "region_id": "3", + "api_token": "1c46538c712e9b5b" +}' + + + + +permit application comments +id, user_id, application_code, comment_body, created_at, updated_at + +php artisan make:model PermitApplicationComments -m + + diff --git a/lupmis.sqlite b/lupmis.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..3da265b091de119d315e86cfce9b7fedbe2750d4 GIT binary patch literal 188416 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 literal 0 HcmV?d00001 diff --git a/php_code/app/Http/Controllers/Admin/UserController.php b/php_code/app/Http/Controllers/Admin/UserController.php index 37b9250d..c990c64c 100644 --- a/php_code/app/Http/Controllers/Admin/UserController.php +++ b/php_code/app/Http/Controllers/Admin/UserController.php @@ -4,7 +4,7 @@ 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 App\Models\User; use Illuminate\Validation\Rule; use Illuminate\Support\Facades\Validator; @@ -13,6 +13,7 @@ class UserController extends Controller public function index() { + dd(session('current_user')); return view('admin.users'); // Blade file below } @@ -102,4 +103,5 @@ class UserController extends Controller $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 110b7c4f..1b3eda81 100644 --- a/php_code/app/Http/Controllers/AdminController.php +++ b/php_code/app/Http/Controllers/AdminController.php @@ -7,11 +7,15 @@ use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use App\Models; +use Illuminate\Support\Facades\Log; + class AdminController extends Controller { public function index() { + //dd(session('current_user')); //"ua_position" => "district-ppd-head" "user_type" => "district_user" + if (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')]; @@ -20,19 +24,47 @@ class AdminController extends Controller $users_url = "user_mgt/get_all_users_by_district.php"; $data = ['region_id' => session('region_id'), 'api_token' => env('LUPMISAPIKEY')]; } + elseif(session('current_user.user_type') == 'national_luspa'){ + $users_url = "user_mgt/get_all_users.php"; + $data = ['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); - - if ($users_arr == null || $users_arr['success'] == false) { - \Log::info(session('current_user.username') . " tried accessing Admin/home and got empty or no success users_arr"); + dump($users_arr); + if ($users_arr == null || $users_arr['success'] == false || $users_arr['status'] == 'error') { + Log::info(session('current_user.username') . " tried accessing Admin/home and got empty or no success users_arr"); return redirect()->back()->withErrors('Your request cannot be handled at this time. Try again later'); } + */ + + try { + $usersArr = json_decode($result, true, 512, JSON_THROW_ON_ERROR); + } + catch (\JsonException $e) { + Log::warning('Admin/home API returned invalid JSON', [ + 'user' => session('current_user.username'), + 'json_error' => $e->getMessage(), + ]); + + return redirect()->back() + ->withErrors(['api' => 'Your request cannot be handled at this time. Try again later.']); + } + + if (!is_array($usersArr) || ($usersArr['success'] ?? null) === false || strtolower((string) ($usersArr['status'] ?? '')) === 'error') { + Log::warning('Admin/home API returned an unsuccessful response', [ + 'user' => session('current_user.username'), + 'success' => $usersArr['success'] ?? 'missing', + 'status' => $usersArr['status'] ?? 'missing', + ]); + return redirect()->back() + ->withErrors(['api' => 'Your request cannot be handled at this time. Try again later.']); + } $regions_url = "user_mgt/get_all_regions.php"; @@ -40,7 +72,7 @@ class AdminController extends Controller $result = ApiCalls::CurlPost(json_encode($data), $regions_url); $regions_arr = json_decode($result, true); if ($regions_arr == null || $regions_arr['success'] == false) { - \Log::info(session('current_user.username') . " tried accessing Admin/home and got empty or no success region"); + Log::info(session('current_user.username') . " tried accessing Admin/home and got empty or no success region"); return redirect()->back()->withErrors('Your request cannot be handled at this time. Try again later'); } @@ -98,15 +130,7 @@ class AdminController extends Controller $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'], @@ -194,37 +218,7 @@ class AdminController extends Controller // return view('admin.home', $data); } - public function indexOld(){ - $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); - if ($users_arr['success'] == false) { - return redirect->back(); - } - - $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['success'] == false) { - return redirect->back(); - } - - $user_type_arr = [ - 'district_user' => 'District User', - 'regional_user' => 'Regional User', - 'national_user' => 'National User' - ]; - - $data = [ - 'page_title' => 'User Admin', - 'users_arr' => $users_arr['data'], - 'regions_arr' => $regions_arr['data'], - 'user_type_arr' => $user_type_arr - ]; - return view('admin.home', $data); - } + public function districtparams(){ /* $url = "user_mgt/get_district_parameters.php"; diff --git a/php_code/app/Http/Controllers/PermitsController.php b/php_code/app/Http/Controllers/PermitsController.php index bbab71ca..4e823f21 100644 --- a/php_code/app/Http/Controllers/PermitsController.php +++ b/php_code/app/Http/Controllers/PermitsController.php @@ -6,35 +6,86 @@ use Session; use Illuminate\Http\Request; use Storage; use Response; +use Illuminate\Support\Collection; +use Illuminate\Pagination\LengthAwarePaginator; +use Carbon\Carbon; class PermitsController extends Controller { - public function index(){ - // dd(session('current_user.district_id')); + public function indexOld(){ $url = "permit/get_applications_by_district.php"; $data = json_encode([ 'district_id' => session('current_user.district_id'), 'api_token' => env('LUPMISAPIKEY') ]); $result = ApiCalls::CurlPost($data, $url); - // dd($result); - - - // { "id":163,"applicant_id":376,"client_generated_id":"7d707dc2-938f-47e7-8979-2def086d4a8d","application_code":"REQ-20260310-2ZK4","permit_type":"Development Permit","permit_form_type":"structural_alteration","project_description":"jkhghhjghjgh","project_location":"mbvmbnv","project_region_id":10,"project_district_id":122,"estimated_cost":0.00,"status":"submitted","property_size":null,"expected_completion_date":null,"is_paid":null,"submitted_at":"2026-03-10T09:18:04.224672","updated_at":"2026-03-10T09:18:04.224672"}, - // {"id":313,"applicant_id":482,"client_generated_id":"34ceb996-c809-4dbe-ba27-df1527e75538","application_code":"REQ-20260312-22AL","permit_type":"Development Permit","permit_form_type":"erection_of_building","project_description":"Two floor residence","project_location":"Butumagyabu","project_region_id":10,"project_district_id":122,"estimated_cost":0.00,"status":"submitted","property_size":null,"expected_completion_date":null,"is_paid":null,"submitted_at":"2026-03-12T13:14:04.420739","updated_at":"2026-03-12T13:14:04.420739"}]} - - - - $result = json_decode($result, true); - // dd(is_array($result['data'])); - + dd($result); $data = [ 'page_title' => 'Permits Dashboard', 'permits_arr' => $result ]; return view('permits.dashboard', $data); } + public function index(Request $request) + { + $url = "permit/get_applications_by_district.php"; + $data = json_encode([ + 'district_id' => session('current_user.district_id'), + 'api_token' => env('LUPMISAPIKEY') + ]); + $result = ApiCalls::CurlPost($data, $url); + $response = json_decode($result, true); + // dd($response); + $permits = collect($response['data'] ?? []); + + + $summaries = [ + 'total' => $permits->count(), + 'submitted' => $permits->filter(fn($p) => strtolower($p['status'] ?? '') === 'submitted')->count(), + 'development' => $permits->filter(fn($p) => str_contains(strtolower($p['permit_type'] ?? ''), 'development'))->count(), + 'planning' => $permits->filter(fn($p) => str_contains(strtolower($p['permit_type'] ?? ''), 'planning'))->count(), + ]; + if ($request->has('search') && $request->search != '') { + $search = strtolower($request->search); + $permits = $permits->filter(function($p) use ($search) { + $code = strtolower($p['application_code'] ?? ''); + $location = strtolower($p['project_location'] ?? ''); + $type = strtolower($p['permit_type'] ?? ''); + $form_type = strtolower($p['permit_form_type'] ?? ''); + + return str_contains($code, $search) || + str_contains($location, $search) || + str_contains($form_type, $search) || + str_contains($type, $search); + }); + } + $paginatedPermits = $this->paginateCollection($permits, 15, 'page'); + + return view('permits.dashboard', [ + 'page_title' => 'Permits Management', + 'permits' => $paginatedPermits, + 'summaries' => $summaries + ]); + } + private function paginateCollection(Collection $items, $perPage = 15, $pageName = 'page') + { + $page = LengthAwarePaginator::resolveCurrentPage($pageName); + $currentPageItems = $items->slice(($page - 1) * $perPage, $perPage)->values(); + + $paginator = new LengthAwarePaginator( + $currentPageItems, + $items->count(), + $perPage, + $page, + [ + 'path' => LengthAwarePaginator::resolveCurrentPath(), + 'pageName' => $pageName, + ] + ); + + return $paginator->withQueryString(); + } public function settings(){ //pull email address, sms configs, /* @@ -74,6 +125,7 @@ class PermitsController extends Controller return view('permits.index', $data); } public function show($id){ + // dd($id); // dump(session('current_user')); $url = "permit/get_applications_with_applicant_by_application_code.php"; diff --git a/php_code/app/Http/Controllers/UsersController.php b/php_code/app/Http/Controllers/UsersController.php index 07e52377..622d5c79 100644 --- a/php_code/app/Http/Controllers/UsersController.php +++ b/php_code/app/Http/Controllers/UsersController.php @@ -50,8 +50,9 @@ class UsersController extends Controller //send reset link email //baduhupir@mailinator.com $reset_link = env('APP_URL') . "/reset-auth/" . $user_id; - Mail::to($recipientEmail)->send(new PasswordResetMail($fullname, $reset_link)); - + if(env('APP_ENV') == 'production'){ + Mail::to($recipientEmail)->send(new PasswordResetMail($fullname, $reset_link)); + } return redirect(url('reset-notice')); //get // return redirect(url('new-password-form')); @@ -72,16 +73,6 @@ class UsersController extends Controller } public function handle_reset(Request $request){ - // code...show_new_passform - // dd('foo bar handle reset'); - /* - { - "username": "string", - "pass": "string", - "user_id": "string", - "api_token": "string" - } - */ $url = "user_mgt/update_usr_user.php"; $user_data = [ @@ -167,7 +158,6 @@ class UsersController extends Controller } - public function store(Request $request){ $url = "user_mgt/add_usr_user.php"; @@ -175,7 +165,7 @@ class UsersController extends Controller 'full_name' => 'required|string|max:255', 'username' => 'required|string|max:255|unique:users,username', 'ua_position' => 'required|string', - 'allowed_apps'=> 'required|array', + // 'allowed_apps'=> 'required|array', 'user_status' => 'required|string', 'gender' => 'required|in:male,female', 'district_id' => [ @@ -198,7 +188,7 @@ class UsersController extends Controller '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.', + // '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', @@ -210,15 +200,29 @@ class UsersController extends Controller 'phone.required' => 'Phone number is required.', ]); - // Generate random password + // dd($request->ua_position); $password = Str::random(10); + $user_group_matrix = Config('usergroupmatrix'); + //$allowed_apps_all = $user_group_matrix[ucwords($request->ua_position)]; + $allowed_apps_all = collect($user_group_matrix) + ->filter( + fn (array $user_group_matrix, string $groupName) => + str_contains(strtolower($groupName), strtolower($request->ua_position)) + )->all(); + + $allowed_apps = collect($allowed_apps_all) + ->filter(fn ($allowed) => $allowed === true) + ->keys() + ->values() + ->all(); + $data = json_encode([ 'full_name' => $request['full_name'], 'username' => $request['username'], 'ua_position' => $request['ua_position'], 'email' => $request['email'], 'title' => $request['title'], - 'allowed_apps' => implode(", ", $request['allowed_apps']), + 'allowed_apps' => implode(", ", $allowed_apps), 'is_password_changed' => false, 'password_hint' => 'none', 'phone' => str_replace('+', '',$request['phone']), @@ -230,7 +234,33 @@ class UsersController extends Controller 'district_id' => $request['district_id'], 'api_token' => env('LUPMISAPIKEY'), ]); + + // dd($data); + //todo determine the allowed_apps user + //user_type and ua_position + if ($request->user_type == 'district_user') { + // code... + } + elseif ($request->user_type == 'regional_luspa') { + // code... + } + elseif ($request->user_type == 'national_luspa') { + // code... + } + /* + 'permit_tools_backend', + 'update_permits', + 'drawing_tools', + 'view_drawing_tools', + 'admin_gui', + + */ + // "drawing-tools" + // "permit-tools" + // "admin-gui" + // "" + $result = ApiCalls::CurlPost($data, $url); $result = json_decode($result, true); @@ -239,9 +269,9 @@ class UsersController extends Controller } \Log::info("Your Password is $password"); - - Mail::to($request->email)->send(new UserAccountsMail($password, $request->username)); - + if(env('APP_ENV') == 'production'){ + Mail::to($request->email)->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"; @@ -251,9 +281,10 @@ class UsersController extends Controller 'recipient' => $request['phone'], 'message' => $sms_message ]; - $sms = new SmsLibrary; - $retval = $sms->SendMnotitySms($sms_data); - + if(env('APP_ENV') == 'production'){ + $sms = new SmsLibrary; + $retval = $sms->SendMnotitySms($sms_data); + } \Log::info("SMS Body : $sms_message"); if ($request->expectsJson()) { @@ -269,10 +300,10 @@ class UsersController extends Controller 'full_name' => 'required|string|max:255', 'username' => 'required|string|max:20|', 'ua_position' => 'required|string', - 'allowed_apps'=> 'required|array', + // 'allowed_apps'=> 'required|array', 'user_status' => 'required|string', 'district_id' => 'required|integer', - 'region_id' => 'required|integer', + 'region_id' => 'sometimes|integer', 'gender' => 'required|in:male,female', /* 'district_id' => [ @@ -295,7 +326,7 @@ class UsersController extends Controller 'full_name.required' => 'Please provide the full name.', 'username.required' => 'A username is required.', 'ua_position.required' => 'Position is mandatory.', - 'allowed_apps.required'=> 'Select at least one application.', + // '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', @@ -307,6 +338,20 @@ class UsersController extends Controller 'phone.required' => 'Phone number is required.', ]); $is_disabled = ($request->user_status == 'active') ? 'false' : 'true'; + $user_group_matrix = Config('usergroupmatrix'); + //$allowed_apps_all = $user_group_matrix[ucwords($request->ua_position)]; + $allowed_apps_all = collect($user_group_matrix) + ->filter( + fn (array $user_group_matrix, string $groupName) => + str_contains(strtolower($groupName), strtolower($request->ua_position)) + )->all(); + + $allowed_apps = collect($allowed_apps_all) + ->filter(fn ($allowed) => $allowed === true) + ->keys() + ->values() + ->all(); + $userData = [ 'user_id' => $request->user_id, 'full_name' => $request->full_name, @@ -314,8 +359,8 @@ class UsersController extends Controller 'ua_position' => $request->ua_position, 'email' => $request->email, 'title' => $request->title, - - 'allowed_apps' => implode(", ", $request->allowed_apps), + // 'allowed_apps' => implode(", ", $request->allowed_apps), + 'allowed_apps' => implode(", ", $allowed_apps), 'phone' => str_replace('+', '', $request->phone), 'gender' => $request->gender, @@ -379,4 +424,29 @@ class UsersController extends Controller return response()->json($result); } } + public function getPositionsMap() + { + $district_groups = array_keys(Config('usergroupmatrix')); + $map = [ + 'district_user' => $district_groups, + /* + '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"] + ]; + + return response()->json([ + 'success' => true, + 'data' => $map + ]); + } } diff --git a/php_code/app/Http/Controllers/UtilityController.php b/php_code/app/Http/Controllers/UtilityController.php index a2ea9dc8..19e1efd7 100644 --- a/php_code/app/Http/Controllers/UtilityController.php +++ b/php_code/app/Http/Controllers/UtilityController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Utilities\ApiCalls; use App\Utilities\SmsLibrary; +use App\Services\MicrosoftMailService; class UtilityController extends Controller { @@ -23,4 +24,15 @@ class UtilityController extends Controller \Log::info("SMS API Response : $sms_result"); } + + public function testMSEmail(MicrosoftMailService $mailService) + { + $mailService->sendEmail( + 'kwesilabs@outlook.com', + 'Test from LUPMIS', + '

Hello!

This is sent via Microsoft Graph API.

' + ); + + return 'Email sent!'; + } } diff --git a/php_code/app/Models/User.php b/php_code/app/Models/User.php index 65642024..af7514c6 100644 --- a/php_code/app/Models/User.php +++ b/php_code/app/Models/User.php @@ -47,4 +47,23 @@ class User extends Authenticatable 'password' => 'hashed', ]; } + /* + public function boot(): void + { + $matrix = [ + 'LUSPA National' => ['permit backend', 'drawing tools', 'view drawing tools', 'admin gui'], + 'District Chief Executive' => ['permit backend', 'view drawing tools'], + 'District Coordinating Director' => ['view drawing tools'], + 'Head of Physical Planning Department' => ['permit backend', 'update permits', 'drawing tools', 'view drawing tools', 'admin gui'], + ]; + + Gate::before(function (User $user, string $ability) use ($matrix) { + $role = $user->role; + + if (isset($matrix[$role]) && in_array($ability, $matrix[$role])) { + return true; + } + }); + } + */ } diff --git a/php_code/app/Services/MicrosoftMailService.php b/php_code/app/Services/MicrosoftMailService.php new file mode 100644 index 00000000..0a120e43 --- /dev/null +++ b/php_code/app/Services/MicrosoftMailService.php @@ -0,0 +1,65 @@ +post("https://login.microsoftonline.com/{$tenantId}/oauth2/v2.0/token", [ + 'client_id' => $clientId, + 'client_secret' => $clientSecret, + 'scope' => 'https://graph.microsoft.com/.default', + 'grant_type' => 'client_credentials', + ]); + + if ($response->failed()) { + throw new Exception('Failed to get Microsoft Graph token: ' . $response->body()); + } + // dd($response); + return $response->json('access_token'); + } + + /** + * Sends an email using the Microsoft Graph API. + */ + public function sendEmail($toEmail, $subject, $body) + { + $accessToken = $this->getAccessToken(); + $sender = env('MS_GRAPH_SENDER_EMAIL'); + + $response = Http::withToken($accessToken) + ->post("https://graph.microsoft.com/v1.0/users/{$sender}/sendMail", [ + 'message' => [ + 'subject' => $subject, + 'body' => [ + 'contentType' => 'HTML', // Or 'Text' + 'content' => $body + ], + 'toRecipients' => [ + [ + 'emailAddress' => [ + 'address' => $toEmail + ] + ] + ] + ], + 'saveToSentItems' => 'true' + ]); + + if ($response->failed()) { + throw new Exception('Failed to send email: ' . $response->body()); + } + + return true; + } +} \ No newline at end of file diff --git a/php_code/config/usergroupmatrix.php b/php_code/config/usergroupmatrix.php new file mode 100644 index 00000000..4e17c75f --- /dev/null +++ b/php_code/config/usergroupmatrix.php @@ -0,0 +1,326 @@ + [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => true, + ], + + 'luspa_regional' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => true, + ], + + 'district_chief_executive' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'district_coordinating_director' => [ + 'permit_backend' => false, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'district_mis' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => true, + ], + + 'head_of_physical_planning_department' => [ + 'permit_backend' => true, + 'update_permits' => true, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => true, + ], + + 'physical_planning_department_staff' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => true, + ], + + 'development_planning_officer' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'head_of_works_department' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'works_department_staff' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'head_of_the_roads_unit_or_urban_roads_department' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'district_fire_officer' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'district_disaster_prevention_department' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'head_of_district_health_department' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'representative_of_the_lands_commission' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'representative_of_the_environmental_protection_agency' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'rep_from_traditional_council' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'chairman_of_the_works_sub_committee' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'chairman_of_development_sub_planning_committee' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'nominated_elected_assembly_members' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], +]; + +/*return [ + 'LUSPA National' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => true, + ], + + 'LUSPA Regional' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => true, + ], + + 'District Chief Executive' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'District Coordinating Director' => [ + 'permit_backend' => false, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'District MIS' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => true, + ], + + 'Head of Physical Planning Department' => [ + 'permit_backend' => true, + 'update_permits' => true, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => true, + ], + 'Physical Planning Department Staff' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => true, + ], + + 'Development Planning Officer' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'Head of Works Department' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + 'Works Department Staff' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'Head of the Roads Unit or Urban Roads Department' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'District Fire Officer' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => true, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'District Disaster Prevention Department' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'Head of District Health Department' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'Representative of the Lands Commission' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'Representative of the Environmental Protection Agency' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'Rep from Traditional Council' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + + 'Chairman of the Works Sub Committee' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + 'Chairman of Development Sub Planning Committee' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], + 'Nominated Elected Assembly Members' => [ + 'permit_backend' => true, + 'update_permits' => false, + 'drawing_tools' => false, + 'view_drawing_tools' => true, + 'admin_gui' => false, + ], +]; + +*/ + + + ?> \ No newline at end of file diff --git a/php_code/info.md b/php_code/info.md index 4851e714..f9e47f25 100644 --- a/php_code/info.md +++ b/php_code/info.md @@ -1,9 +1,9 @@ # Acounts - assiamah/win - kwesilupmis/7aa0478bce/virgin200 -- kwesibanson/virgin200 +- kwesibanson/virgin200 : district user -Username: saxiquzipa/lwCDfAMaBy +Username: saxiquzipa/lwCDfAMaBy : national_luspa composer create-project laravel/laravel:^11.0 my-laravel-11-project @@ -11,6 +11,10 @@ chmod -R 0777 storage/ chmod -R 0777 bootstrap/ +https://lupmis4luspa.org/landing +https://lupmis4luspa.org/landing +afuaabasa / 2jcukPZGiCsf +
  • Username: kwesilupmis
  • Password: 7aa0478bce
  • @@ -35,28 +39,19 @@ add measurements to the backend for works department 216.55.185.131 -ua_id": 10, - "user_id": "34ba702b-18f8-4d85-948d-8c55e8500f32", - "username": "hyhix", - "title": "Mr", - "full_name": "Myles Benton", - "ua_position": "district-works-head", - "email": "baduhupir@mailinator.com", - "phone": "233244678092", - "gender": "male", - "is_disabled": "true", - "is_logged_in": null, - "is_password_changed": "false", - "password_hint": "none", - "created_by_id": null, - "created_date": "2025-09-08T20:06:11.290737", - "updated_by_id": null, - "updated_date": "2025-09-08T20:06:11.290737", - "allowed_apps": "permit-tools", - "region_id": 3, - "district_id": 180, - "user_type": "District User" +## Microsoft Email +Application (client) ID: +8e31f6b1-4a4f-4bb6-9019-aaf64e66143f + +Directory (tenant) ID: +afab14ca-ab60-42a6-8a29-ff101424f318 + +Secret ID: +bd797934-94be-4506-977c-cd65042c9667 + +Secret Value: +Obk8Q~6pnUQEIn3b9xBe~havTyRgn7u5~mZ7Ecan diff --git a/php_code/public/assets/js/add_edit_user.js b/php_code/public/assets/js/add_edit_user.js index f7c2fe66..5a704f48 100644 --- a/php_code/public/assets/js/add_edit_user.js +++ b/php_code/public/assets/js/add_edit_user.js @@ -7,27 +7,25 @@ $(document).ready(function () { } }); + // --- PHONE INPUT LOGIC --- 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 + nationalMode: true, 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()); } }); @@ -35,10 +33,10 @@ $(document).ready(function () { return phoneInstances[selector]; } - // Initialize inputs when their respective modals open $('#editUserModal').on('shown.bs.modal', () => initPhoneInput("#editPhone")); $('#addUserModal').on('shown.bs.modal', () => initPhoneInput("#addUserphone")); + // --- SELECT2 LOGIC --- $('#editAllowedApps, #inputPermissions').select2({ dropdownParent: $('#editUserModal'), placeholder: "Select options, multiple allowed" @@ -49,48 +47,76 @@ $(document).ready(function () { 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"] - }; + + // --- DYNAMIC POSITIONS MAP LOGIC --- + let optionsMap = {}; // Start empty function populatePositions(userType, targetDropdown) { targetDropdown.empty().append(''); if (optionsMap[userType]) { $.each(optionsMap[userType], function(index, value) { - targetDropdown.append($("").attr("value", value.toLowerCase()).text(value)); + targetDropdown.append($("").attr("value", value.toLowerCase()).text(toTitleCase(value))); }); } } + function toSentenceCase(value) { + value = value + .replace(/_+/g, ' ') + .replace(/\s+/g, ' ') + .trim() + .toLowerCase(); + + return value + ? value.charAt(0).toUpperCase() + value.slice(1) + : ''; + } + function toTitleCase(value) { + return value + .replace(/_+/g, ' ') + .replace(/\s+/g, ' ') + .trim() + .toLowerCase() + .replace(/\b\w/g, char => char.toUpperCase()); + } + + + // Fetch the map from the backend on load + $.ajax({ + url: base_url + '/users/positions-map', + type: 'GET', + success: function(response) { + let mapData = response.data || response; + if (mapData && Object.keys(mapData).length > 0) { + optionsMap = mapData; + // Populate if a radio is already checked + let initialUserType = $("input[name='user_type']:checked").val(); + if(initialUserType) { + populatePositions(initialUserType, $("#uaPostionAdd")); + } + } else { + console.warn("The map data is empty or formatted incorrectly."); + } + }, + error: function(xhr, status, error) { + console.error("Failed to load positions map:", error); + console.log(xhr.responseText); // Shows PHP errors if the route crashed + } + }); - 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")); }); + // --- DEPENDENT DROPDOWNS --- $('.regionIDD').change(function() { - let region_id = $(this).val(); + let region_id = $(this).val(); let $districtSelect = $('.districtIDD'); $districtSelect.empty().append(''); @@ -111,6 +137,7 @@ $(document).ready(function () { }); + // --- EDIT USER LOGIC --- let lastTrigger = null; $(document).on('click', '.editUserBtn', function(evnt) { @@ -120,7 +147,6 @@ $(document).ready(function () { 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({ @@ -143,12 +169,12 @@ $(document).ready(function () { $('#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); } @@ -159,12 +185,17 @@ $(document).ready(function () { $('#editUserTypeLuspaNational').prop('checked', true); } + // Populate positions based on the loaded user_type + if(user.user_type) { + populatePositions(user.user_type, $("#editUaPostion")); + } + $('#editUaPostion').val(user.ua_position); + if (phoneIti && user.phone) { phoneIti.setNumber(user.phone); } else { $('#editPhone').val(user.phone); } - // $('#editUserModal').modal('show') } }, error: function(xhr, status, error) { @@ -179,6 +210,7 @@ $(document).ready(function () { }); + // --- FORM SUBMISSION LOGIC --- function handleFormSubmission(formElement, url, successMessage, phoneInstanceSelector) { const $form = $(formElement); const isEdit = formElement.id === 'editUserForm'; @@ -194,17 +226,14 @@ $(document).ready(function () { 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(); @@ -260,7 +289,6 @@ $(document).ready(function () { }); } - $("#editUserForm").on("submit", function (evt) { evt.preventDefault(); handleFormSubmission(this, base_url + '/userupdate', "User details successfully updated!", "#editPhone"); diff --git a/php_code/public/assets/js/permits_index.js b/php_code/public/assets/js/permits_index.js new file mode 100644 index 00000000..365edba9 --- /dev/null +++ b/php_code/public/assets/js/permits_index.js @@ -0,0 +1,30 @@ +$(document).ready(function() { + let searchTimer; + + $('#permitSearch').on('keyup', function() { + clearTimeout(searchTimer); + let query = $(this).val(); + + $('#permitBody').css('opacity', '0.5'); + + searchTimer = setTimeout(function() { + $.ajax({ + url: window.location.pathname, + type: "GET", + data: { search: query }, + success: function(response) { + let html = $(response); + + $('#permitBody').html(html.find('#permitBody').html()); + + $('#permit-pagination').html(html.find('#permit-pagination').html()); + $('#permitBody').css('opacity', '1'); + }, + error: function() { + console.error("Live search failed"); + $('#permitBody').css('opacity', '1'); + } + }); + }, 500); // 500ms delay + }); +}); \ No newline at end of file diff --git a/php_code/resources/views/admin/partials/create-user.blade.php b/php_code/resources/views/admin/partials/create-user.blade.php index 61d8fc7a..3ab00c02 100644 --- a/php_code/resources/views/admin/partials/create-user.blade.php +++ b/php_code/resources/views/admin/partials/create-user.blade.php @@ -3,7 +3,7 @@