Fix : permit and admin page modifications

This commit is contained in:
Kwesi Banson Jnr 2026-07-02 11:39:14 +00:00
parent 0a60c268af
commit 80f0bf24d7
29 changed files with 1793 additions and 277 deletions

View File

@ -30,6 +30,7 @@ class AdminController extends Controller
$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");
return redirect()->back()->withErrors('Your request cannot be handled at this time. Try again later');
}
@ -39,6 +40,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");
return redirect()->back()->withErrors('Your request cannot be handled at this time. Try again later');
}
@ -234,11 +236,44 @@ class AdminController extends Controller
$result = json_decode($result, true);
dd($result);
*/
$data = [
'page_title' => "District Parameters"
];
return view('admin.district_params', $data);
}
public function getDistrictSettingsJson()
{
$districtid = session('current_user.district_id');
$settings = Models\DistrictSetting::where('district_id', $districtid)->first();
return response()->json([
'success' => true,
'data' => $settings
]);
}
public function updateDistrictSettings(Request $request)
{
$validated = $request->validate([
'region_name' => 'required|string',
'district_name' => 'required|string',
'assembly_type' => 'required|in:district,municipal,metropolitan',
'abbreviation' => 'required|string|max:20',
'contact_phone' => 'required|string|max:20',
'contact_email' => 'required|email',
'outgoing_email' => 'required|email',
'display_name' => 'required|string',
'sms_sender_name' => 'required|string|max:20',
]);
$districtid = session('current_user.district_id');
Models\DistrictSetting::updateOrCreate(['district_id' => $districtid], $validated);
return response()->json([
'success' => true,
'message' => 'Settings saved successfully'
]);
}
public function luspaparams(){
$data = [
'page_title' => "Page Under Development"
@ -252,6 +287,7 @@ class AdminController extends Controller
return view('common.notready', $data);
}
public function districtsettings(){
$data = [
'page_title' => "District Settings"
];

View File

@ -103,7 +103,8 @@ class PermitsController extends Controller
'allowed_users_to_comment' => $allowed_users_to_comment
];
// return view('permits.show_20-06-2026-6-56pm', $data);
return view('permits.show', $data);
// return view('permits.show', $data);
return view('permits.show-tabbed', $data);
}
public function statusUpdate($id){

View File

@ -29,14 +29,13 @@ 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);
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();
return redirect("user-login")->withErrors(array("Incorrect Username/Password. Check and try again!"))->withInput();
}
if($result['data']['is_disabled'] == true){
##return redirect("user-login")->withErrors(array("Your Account has been disabled. Contact your administrator!"))->withInput();

View File

@ -11,7 +11,7 @@ use App\Mail\PasswordResetMail;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use App\Rules\GhanaPhoneRule;
use Illuminate\Support\Facades\Hash;
class UsersController extends Controller
{
@ -28,7 +28,13 @@ class UsersController extends Controller
return view('user-auth.reset', $data);
}
public function check_reset_email(Request $request){
$url = "user_mgt/get_user_by_user_id.php";
dd($request->all());
// array:2 [▼ // app/Http/Controllers/UsersController.php:31
// "_token" => "ihqMhlBqQp6WP0qNr6mx9ddqF1szemeyHbjcsFtS"
// "email" => "kwesi_banson@hotmail.com"
// ]
$url = "user_mgt/get_user_by_email.php";
$user_id = "34ba702b-18f8-4d85-948d-8c55e8500f32";
$data = json_encode([
'user_id' => "34ba702b-18f8-4d85-948d-8c55e8500f32",// $id,
@ -59,7 +65,6 @@ class UsersController extends Controller
public function show_new_passform(Request $request){
// code...show_new_passform
// dd('foo bar show new pass');
dump($request->all());
$data = [
'page_title' => 'New Password Form'
];
@ -69,12 +74,34 @@ 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 = [
'user_id' => $request['user_id'],
'api_token' => env('LUPMISAPIKEY'),
];
$data = json_encode($user_data);
$result = ApiCalls::CurlPost($data, $url);
$result = json_decode($result, true);
// dd($result);
// return response()->json($result);
if ($request->expectsJson()) {
return response()->json($result);
}
return redirect(url('reset-success')); //get
}
public function reset_success(Request $request){
// code...show_new_passform
// dd('foo bar handle reset');
$data = [
'page_title' => 'New Password Success'
];
@ -151,15 +178,15 @@ class UsersController extends Controller
'allowed_apps'=> 'required|array',
'user_status' => 'required|string',
'gender' => 'required|in:male,female',
'districtid' => [
'district_id' => [
Rule::requiredIf(function () use ($request) {
return in_array($request->user_type, ['district_user', 'regional_luspa']);
return in_array($request->user_type, ['district_user']);
}),
'integer'
],
'region_id' => [
Rule::requiredIf(function () use ($request) {
return in_array($request->user_type, ['regional_luspa']);
return in_array($request->user_type, ['regional_luspa', 'national_luspa']);
}),
'integer'
],
@ -200,7 +227,7 @@ class UsersController extends Controller
'pass' => $password,
'is_disabled' => false,
'region_id' => $request['region_id'],
'district_id' => $request['districtid'],
'district_id' => $request['district_id'],
'api_token' => env('LUPMISAPIKEY'),
]);
// dd($data);
@ -234,21 +261,21 @@ class UsersController extends Controller
}
}
public function update(Request $request){
$url = "user_mgt/update_usr_user.php";
// return ['success' => true];
// dd($request->all());
$url = "user_mgt/update_usr_user.php";
$userId = $request->input('user_id');
$this->validate($request, [
'full_name' => 'required|string|max:255',
'username' => 'required|string|max:255|unique:users,username',
'username' => 'required|string|max:20|',
'ua_position' => 'required|string',
'allowed_apps'=> 'required|array',
'user_status' => 'required|string',
'user_status' => 'required|string',
'district_id' => 'required|integer',
'region_id' => 'required|integer',
'gender' => 'required|in:male,female',
// 'districtid' => 'required|integer',
'districtid' => [
/*
'district_id' => [
Rule::requiredIf(function () use ($request) {
return in_array($request->user_type, ['district_user']);
}),
@ -260,60 +287,71 @@ class UsersController extends Controller
}),
'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.',
'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'],
'ua_position' => $request['ua_position'],
'user_id' => $request['user_id'],
'email' => $request['email'],
'title' => $request['title'],
'allowed_apps' => implode(", ", $request['allowed_apps']),
'phone' => str_replace('+', '',$request['phone']),
'gender' => $request['gender'],
'user_type' => $request['user_type'],
'api_token' => env('LUPMISAPIKEY'),
$userData = [
'user_id' => $request->user_id,
'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),
'phone' => str_replace('+', '', $request->phone),
'gender' => $request->gender,
'user_type' => $request->user_type,
'is_disabled' => $is_disabled,
// 'region_id' => $request['region_id'],
'district_id' => $request['districtid'],
'district_id' => $request->district_id,
'region_id' => $request->region_id,
'api_token' => config('services.lupmis.api_key'), //env('LUPMISAPIKEY'),
];
if ($request->has('expire_password')) {
$user_data['is_password_changed'] = 'NO';
#$user_data['is_password_changed'] = 'YES';
#$reset_link = env('APP_URL') . "/reset-auth/" . $request->user_id;
#Mail::to($recipientEmail)->send(new PasswordResetMail($request->fullname, $reset_link));
}
$data = json_encode($user_data);
$result = ApiCalls::CurlPost($data, $url);
$result = json_decode($result, true);
// dd($result);
// return response()->json($result);
if ($request->expectsJson()) {
return response()->json($result);
if (config('app.env') === 'local' && $request->filled('reset_password')) {
$reset_url = "user_mgt/reset_user_password.php";
$reset_data["api_token"] = config('services.lupmis.api_key');
$reset_data['user_id'] = $request->user_id;
$reset_data['pass'] = $request->reset_password;
$data = json_encode($reset_data);
$retval = ApiCalls::CurlPost($data, $reset_url);
$retval = json_decode($retval, true);
}
$result = ApiCalls::CurlPost(json_encode($userData), $url);
$decodedResult = json_decode($result, true);
return response()->json($decodedResult ?? ['error' => 'Invalid API Response']);
}
public function profileupdate(Request $request){
$url = "user_mgt/update_usr_user.php";
// return ['success' => true];
// dd($request->all());
$user_data = [
'full_name' => $request['full_name'],
'username' => $request['username'],
@ -325,6 +363,13 @@ class UsersController extends Controller
];
if ($request->filled('password')) {
$user_data['password'] = $request->password;
$reset_url = "user_mgt/reset_user_password.php";
$reset_data["api_token"] = env('LUPMISAPIKEY');
$reset_data['user_id'] = $request->user_id;
$reset_data['pass'] = $request->password;
$data = json_encode($reset_data);
$retval = ApiCalls::CurlPost($reset_data, $reset_url);
$retval = json_decode($retval, true);
}
$data = json_encode($user_data);

View File

@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class DistrictSetting extends Model
{
use HasFactory;
// Explicitly define the table name (optional, but good practice)
protected $table = 'district_settings';
// Allow mass assignment for the form fields
protected $fillable = [
'region_name',
'district_id',
'district_name',
'assembly_type',
'abbreviation',
'contact_phone',
'contact_email',
'outgoing_email',
'display_name',
'sms_sender_name',
];
}

View File

@ -13,7 +13,7 @@ return [
|
*/
'name' => env('APP_NAME', 'Laravel'),
'name' => env('APP_NAME', 'LUPMIS'),
/*
|--------------------------------------------------------------------------

View File

@ -27,6 +27,9 @@ return [
'resend' => [
'key' => env('RESEND_KEY'),
],
'lupmis' => [
'api_key' => env('LUPMISAPIKEY'),
],
'slack' => [
'notifications' => [

View File

@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('district_settings', function (Blueprint $table) {
$table->id();
// Assembly Profile
$table->string('region_name');
$table->string('district_name');
$table->enum('assembly_type', ['district', 'municipal', 'metropolitan']);
$table->string('abbreviation', 20);
$table->string('contact_phone', 20);
$table->string('contact_email');
// Email Notifications
$table->string('outgoing_email');
$table->string('display_name');
// SMS Notifications
$table->string('sms_sender_name', 20);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('district_settings');
}
};

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('district_settings', function (Blueprint $table) {
// Adds the column right after the primary 'id' column.
// Nullable ensures it won't break if you already have existing rows.
$table->unsignedBigInteger('district_id')->nullable()->after('id');
// PRO-TIP: If you actually have a 'districts' table and want a strict foreign key relationship,
// comment out the line above and use this line instead:
// $table->foreignId('district_id')->nullable()->after('id')->constrained('districts')->cascadeOnDelete();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('district_settings', function (Blueprint $table) {
// Safely removes the column if you ever roll back this migration
$table->dropColumn('district_id');
});
}
};

8
php_code/env_parts.md Normal file
View File

@ -0,0 +1,8 @@
MAIL_MAILER=log
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_USERNAME=lupmisredevelopment@gmail.com
MAIL_PASSWORD=nemdlwqrkrnxavdw
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=lupmisredevelopment@gmail.com
MAIL_FROM_NAME=LUPMIS

View File

@ -1,6 +1,7 @@
# Acounts
- assiamah/win
- kwesilupmis/7aa0478bce
- kwesilupmis/7aa0478bce/virgin200
- kwesibanson/virgin200
Username: saxiquzipa/lwCDfAMaBy </li>
@ -76,5 +77,3 @@ add_header Content-Security-Policy "frame-ancestors 'self' https://lupmis.houseb
}
php artisan make:model UserGroupMatrix -m

Binary file not shown.

View File

@ -82,8 +82,8 @@ $(document).ready(function () {
let isDistrict = (userType === 'district_user');
let isRegional = (userType === 'regional_luspa');
$('#regionID, #editRegionID').prop('disabled', !isDistrict && !isRegional);
$('#districtID, #editdistrictID').prop('disabled', !isDistrict);
//$('#regionID, #editRegionID').prop('disabled', !isDistrict && !isRegional);
//$('#districtID, #editdistrictID').prop('disabled', !isDistrict);
populatePositions(userType, $("#uaPostionAdd"));
});
@ -128,10 +128,11 @@ $(document).ready(function () {
type: 'GET',
beforeSend: function() {
$('#editSuccessArea').addClass('d-none');
$('#editErrorArea').removeClass('d-none').text("Please wait ... loading user details!!");
$('#editInfoArea').removeClass('d-none').text("Please wait ... loading user details!!");
},
success: function(response) {
$('#editErrorArea').addClass('d-none').text('');
$('#editInfoArea').addClass('d-none').text('');
if (response.success) {
let user = response.data;

View File

@ -1,98 +1,202 @@
$(document).ready(function(){
$('.profileLink').click(function(evt){
evt.preventDefault();
$.ajax({
type: "GET",
url: base_url + '/users/getprofile',
processData: false,
contentType: false,
async: false,
success: function (data){
console.log(data);
if (data.code === 1) {
console.log(data['user_details']);
// $(document).ready(function(){
// $('.profileLinkXX').click(function(evt){
// evt.preventDefault();
// $.ajax({
// type: "GET",
// url: base_url + '/users/getprofile',
// processData: false,
// contentType: false,
// async: false,
// success: function (data){
// console.log(data);
// if (data.code === 1) {
// console.log(data['user_details']);
$('.userId').val(data['user_details']['user_id']);
$('.userName').val(data['user_details']['username']);
$('.userFullName').val(data['user_details']['full_name']);
$('.userEmail').val(data['user_details']['email']);
$('.userPhone').val(data['user_details']['phone']);
$('.userDesignation').val(data['user_details']['ua_position']);
$('.userDateAdded').val(data['user_details']['created_date']);
// $('.userEmail').val(data['user_details']['email']);
$('#profileModal').modal('show');
// $('.userId').val(data['user_details']['user_id']);
// $('.userName').val(data['user_details']['username']);
// $('.userFullName').val(data['user_details']['full_name']);
// $('.userEmail').val(data['user_details']['email']);
// $('.userPhone').val(data['user_details']['phone']);
// $('.userDesignation').val(data['user_details']['ua_position']);
// $('.userDateAdded').val(data['user_details']['created_date']);
// $('#profileModal').modal('show');
// }
// else if (data.code > 1) {
// $.alert({
// title: 'Alert!',
// content: data.msg,
// });
// }
// else {
// $.alert({
// title: 'Alert!',
// content: 'Your request could not be handled. Try again !',
// });
// }
// }
// });
}
else if (data.code > 1) {
// });
// $('#profileModalSubmitBtnXX').click(function(evt){
// evt.preventDefault(evt);
// $('#successArea').addClass('d-none');
// $('#errorsArea').removeClass('d-none');
// var formData = new FormData($('#userProfileForm')[0]);
// $.ajax({
// url: base_url + '/profileupdate',
// type: 'POST',
// data: formData,
// processData: false,
// contentType: false,
// beforeSend: function() {
// $('#successArea').text("");
// $('#successArea').text("Please wait ... profile update in progress!");
// },
// success: function(data) {
// if (data['success'] == true) {
// $('#successArea').removeClass('d-none');
// $('#errorsArea').addClass('d-none');
// $('#successArea').text("");
// $('#successArea').text("Profile successfully updated!");
// // location.reload();
// $.alert({
// title: 'Alert!',
// content: 'Profile successfully updated!',
// });
// setTimeout(function() {
// }, 2000);
// }
// else{
// $('#successArea').addClass('d-none');
// $('#errorArea').removeClass('d-none');
// $('#errorArea').text("");
// $('#errorArea').text("Profile could not be updated!");
// $.alert({
// title: 'Alert!',
// content: 'Profile could not updated!',
// });
// }
// },
// error: function(xhr, status, error) {
// console.error('Error:', error);
// $('#successArea').text(error);
// $('#successArea').text(error);
// $.alert({
// title: 'Alert!',
// content: error,
// });
// }
// });
// });
// });
$(document).ready(function() {
// Cache DOM elements that are manipulated frequently
const $successArea = $('#successArea');
const $errorArea = $('#errorArea'); // Standardized ID to avoid the 'errorsArea' vs 'errorArea' typo
const $submitBtn = $('#profileModalSubmitBtn');
// GET: Fetch Profile
$('.profileLink').click(function(evt) {
evt.preventDefault();
$.ajax({
type: "GET",
url: base_url + '/users/getprofile',
// Removed processData, contentType, and async: false
success: function(data) {
if (data.code === 1) {
const user = data.user_details;
$('.userId').val(user.user_id);
$('.userName').val(user.username);
$('.userFullName').val(user.full_name);
$('.userEmail').val(user.email);
$('.userPhone').val(user.phone);
$('.userDesignation').val(user.ua_position);
$('.userDateAdded').val(user.created_date);
$('#profileModal').modal('show');
} else if (data.code > 1) {
$.alert({
title: 'Alert!',
content: data.msg,
});
}
else {
} else {
$.alert({
title: 'Alert!',
content: 'Your request could not be handled. Try again !',
});
}
}
});
});
$('#profileModalSubmitBtn').click(function(evt){
evt.preventDefault(evt);
console.log('info');
$('#successArea').addClass('d-none');
$('#errorsArea').removeClass('d-none');
var formData = new FormData($('#userProfileForm')[0]);
$.ajax({
url: base_url + '/profileupdate',
type: 'POST',
data: formData,
processData: false,
contentType: false,
beforeSend: function() {
$('#successArea').text("");
$('#successArea').text("Please wait ... profile update in progress!");
},
success: function(data) {
if (data['success'] == true) {
$('#successArea').removeClass('d-none');
$('#errorsArea').addClass('d-none');
$('#successArea').text("");
$('#successArea').text("Profile successfully updated!");
// location.reload();
$.alert({
title: 'Alert!',
content: 'Profile successfully updated!',
});
setTimeout(function() {
//location.reload(); // Reloads the current page
}, 2000);
}
else{
$('#successArea').addClass('d-none');
$('#errorArea').removeClass('d-none');
$('#errorArea').text("");
$('#errorArea').text("Profile could not be updated!");
$.alert({
title: 'Alert!',
content: 'Profile could not updated!',
});
}
},
error: function(xhr, status, error) {
console.error('Error:', error);
$('#successArea').text(error);
$('#successArea').text(error);
$.alert({
title: 'Alert!',
content: error,
content: 'Your request could not be handled. Try again!',
});
}
});
},
error: function() {
$.alert({
title: 'Error!',
content: 'Failed to connect to the server.',
});
}
});
});
// POST: Update Profile
$submitBtn.click(function(evt) {
evt.preventDefault(); // Fixed: evt.preventDefault(evt) -> evt.preventDefault()
// Reset message areas
$successArea.addClass('d-none').text("");
$errorArea.addClass('d-none').text("");
// UI UX: Disable button to prevent double-clicks during AJAX
const originalBtnText = $submitBtn.text();
$submitBtn.prop('disabled', true).text('Updating...');
let formData = new FormData($('#userProfileForm')[0]);
$.ajax({
url: base_url + '/profileupdate',
type: 'POST',
data: formData,
processData: false,
contentType: false,
// Assuming CSRF token is handled globally in $.ajaxSetup for Laravel
success: function(data) {
if (data.success == true) {
$successArea.removeClass('d-none').text("Profile successfully updated!");
$.alert({
title: 'Success!',
content: 'Profile successfully updated!',
});
setTimeout(function() {
// location.reload();
}, 2000);
} else {
$errorArea.removeClass('d-none').text("Profile could not be updated!");
$.alert({
title: 'Alert!',
content: 'Profile could not be updated!',
});
}
},
error: function(xhr, status, error) {
console.error('Error:', error);
// Fixed: The error message was previously being sent to the successArea
$errorArea.removeClass('d-none').text("An error occurred: " + error);
$.alert({
title: 'Alert!',
content: 'An error occurred: ' + error,
});
},
complete: function() {
// UI UX: Re-enable the button regardless of success or failure
$submitBtn.prop('disabled', false).text(originalBtnText);
}
});
});
});

View File

@ -1,11 +1,12 @@
$(document).ready(function(){
console.log('inside comments');
const baseUrl = base_url + "/permits/getcomments";
fetchPermitApplicationComments();
$(".submitPermitCommentBtn").click(function (e) {
e.preventDefault();
// let formData = $(this).serialize();
console.log('heere in permit comments');
var currentStatus = $('permitCurrentStatus').val();
var commentBody = $('.commentBody').val();

View File

@ -1,4 +1,5 @@
(function(){
console.log('foo bar in new')
$(document).on('keydown', function(e){
if (e.key === '/' && !$(e.target).is('input, textarea')) {
e.preventDefault();

View File

@ -1,5 +1,5 @@
$(document).ready(function () {
console.log('old js');
let iti;
$('#addUserModal').on('shown.bs.modal', function () {
@ -163,35 +163,35 @@ $(document).ready(function () {
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"]
const optionsMap = {
district_user: [
"PPD Head", "Works Head", "MIS",
"PPD Staff", "Works Staff",
"Devt Planning Officer",
"MCE", "DCE",
"Urban Roads Department Head",
"District Fire Officer",
"District Disaster Prevention Department",
"Head of District Health Department",
"Representative of the Lands Commission",
"Representative of the Environmental Protection Agency",
"Rep from Traditional Council",
"Chairman of the Works Sub Committee",
"Chairman of Development Sub Planning Committee",
"Nominated Elected Assembly Memebers"
],
national_luspa: ["Director", "IT Head", "Staff"],
regional_luspa: ["Director", "Staff"]
};
$dropdown.empty();
$dropdown.append('<option value="">-- Select an option --</option>');
};
$dropdown.empty();
$dropdown.append('<option value="">-- Select an option --</option>');
if (optionsMap['district_user']) {
$.each(optionsMap['district_user'], function(index, value) {
$dropdown.append($("<option></option>").attr("value", value.toLowerCase()).text(value));
});
}
if (optionsMap['district_user']) {
$.each(optionsMap['district_user'], function(index, value) {
$dropdown.append($("<option></option>").attr("value", value.toLowerCase()).text(value));
});
}
$("input[name='user_type']").change(function() {
var userValue = $(this).val();
@ -357,8 +357,9 @@ $(document).ready(function () {
});
$("#editForm").submit(function(evt){
$("#editFormPP").submit(function(evt){
evt.preventDefault();
console.log('point in edit');
const $successArea = $("#editSuccessArea");
const $errorArea = $("#editErrorArea");
$('#editSuccessArea').addClass('d-none');

View File

@ -18,8 +18,9 @@
<p class="text-muted">Update these settings as needed</p>
</div>
<form action="permit/districtsettings" method="POST">
@csrf <div class="card shadow-sm mb-4">
<form id="districtSettingsForm" action="updatedistrictsettings" method="POST">
@csrf
<div class="card shadow-sm mb-4">
<div class="card-header bg-white py-3">
<h5 class="card-title mb-0 text-dark">
<i class="bi bi-building me-2 text-secondary"></i>Assembly Profile
@ -29,7 +30,7 @@
<div class="row mb-3 g-3">
<div class="col-md-6">
<label for="regionName" class="form-label fw-bold">Region Name</label>
<input type="text" class="form-control" id="regionName" name="region_name" value="" placeholder="e.g. Western Region" required>
<input type="text" class="form-control" id="regionName" name="region_name" value="" placeholder="e.g. Western Region" required readonly>
</div>
<div class="col-md-6">
<label for="districtName" class="form-label fw-bold">District Name</label>
@ -99,8 +100,8 @@
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end mb-5">
<button type="button" class="btn btn-outline-secondary px-4">Cancel</button>
<button type="submit" class="btn btn-primary px-4">Save Changes</button>
<button type="button" class="btn btn-outline-secondary px-4" id="cancelBtn">Cancel</button>
<button type="submit" class="btn btn-primary px-4" id="saveSettingsBtn">Save Changes</button>
</div>
</form>
</div>
@ -113,7 +114,76 @@
@section('page-js')
<script type="text/javascript">
$(document).ready(function(){
// Form validation or dynamic selection logic can go here
// 1. Load existing settings on page load
fetchSettings();
function fetchSettings() {
$.ajax({
// Ensure this route exists in your web.php
url: '{{ url("admin/getdistrictsettingsjson") }}',
method: 'GET',
success: function(response) {
if(response.success && response.data) {
const data = response.data;
$('#regionName').val(data.region_name);
$('#districtName').val(data.district_name);
$('#assemblyType').val(data.assembly_type);
$('#abbreviation').val(data.abbreviation);
$('#contactPhone').val(data.contact_phone);
$('#contactEmail').val(data.contact_email);
$('#outgoingEmail').val(data.outgoing_email);
$('#displayName').val(data.display_name);
$('#senderName').val(data.sms_sender_name);
}
},
error: function() {
console.error("Could not load district settings.");
}
});
}
// 2. Handle form submission via AJAX
$('#districtSettingsForm').on('submit', function(e) {
e.preventDefault(); // Prevent standard page reload
let $btn = $('#saveSettingsBtn');
let originalText = $btn.html();
// Show loading state on button
$btn.html('<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Saving...').prop('disabled', true);
$.ajax({
url: $(this).attr('action'),
method: 'POST',
data: $(this).serialize(), // Automatically grabs all inputs and the @csrf token
success: function(response) {
if(response.success) {
// You can replace this with a Toastr or SweetAlert notification
$.alert('Settings updated successfully!');
} else {
$.alert('Failed to update settings. Please try again.');
}
},
error: function(xhr) {
let errorMessage = 'An error occurred while saving.';
// Optional: Handle Laravel validation errors returned as JSON
if(xhr.responseJSON && xhr.responseJSON.errors) {
errorMessage = Object.values(xhr.responseJSON.errors)[0][0];
}
$.alert(errorMessage);
},
complete: function() {
// Restore button state
$btn.html(originalText).prop('disabled', false);
}
});
});
// Optional: Cancel button resets form to last saved database state
$('#cancelBtn').on('click', function() {
fetchSettings();
});
});
</script>
@endsection

View File

@ -30,7 +30,6 @@
@section('page-content')
@include('admin.partials.create-user')
@include('admin.partials.edit-user')
@include('admin.partials.edit-modal')
@include('admin.partials.move-user')
@include('admin.partials.view-user')
@include('layouts.partials.navbar')
@ -87,7 +86,7 @@
</ul>
</div>
<!-- <button id="bulkMoveBtn" class="btn btn-primary" disabled data-bs-toggle="modal" data-bs-target="#moveModal">Move</button> -->
<button id="bulkMoveBtn" class="btn btn-primary" disabled data-bs-toggle="modal" data-bs-target="#moveModal">Move</button>
<button id="bulkDeactivate" class="btn btn-outline-danger" disabled>Disable</button>
</div>
</div>
@ -127,7 +126,7 @@
</div>
</td>
<td class="align-middle">{{ $user['email'] }}</td>
<td class="align-middle"><span class="category-badge badge-district">{{ ucwords(str_replace('-', ' ', $user['ua_position'])) }}</span></td>
<td class="align-middle"><span class="category-badge badge-district">{{ strtoupper(str_replace('-', ' ', $user['ua_position'])) }}</span></td>
<td class="align-middle">
<span class="status-dot {{ $status === 'Active' ? 'status-active' : 'status-suspended' }}"></span> {{ $status }}
</td>
@ -185,7 +184,7 @@
</div>
</td>
<td class="align-middle">{{ $user['email'] }}</td>
<td class="align-middle"><span class="category-badge badge-regional">{{ ucwords(str_replace('-', ' ', $user['ua_position'])) }}</span></td>
<td class="align-middle"><span class="category-badge badge-regional">{{ strtoupper(str_replace('-', ' ', $user['ua_position'])) }}</span></td>
<td class="align-middle">
<span class="status-dot {{ $status === 'Active' ? 'status-active' : 'status-suspended' }}"></span> {{ $status }}
</td>
@ -242,7 +241,7 @@
</div>
</td>
<td class="align-middle">{{ $user['email'] }}</td>
<td class="align-middle"><span class="category-badge badge-national">{{ ucwords(str_replace('-', ' ', $user['ua_position'])) }}</span></td>
<td class="align-middle"><span class="category-badge badge-national">{{ strtoupper(str_replace('-', ' ', $user['ua_position'])) }}</span></td>
<td class="align-middle">
<span class="status-dot {{ $status === 'Active' ? 'status-active' : 'status-suspended' }}"></span> {{ $status }}
</td>
@ -270,13 +269,7 @@
</div>
</div>
@endsection
@section('page-js')
<script src="{{ url('public/assets/libs/select2/dist/js/select2.full.min.js') }}" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/intlTelInput.min.js"></script>

View File

@ -15,22 +15,7 @@
@csrf
<input type="hidden" name="action" value="newuser">
<div class="col-md-12">
@if (session('current_user.user_type') == 'district_user')
<div class="form-check">
<input class="form-check-input" type="radio" name="user_type" value="district_user" id="userTypeDistrict" checked>
<label class="form-check-label" for="userTypeDistrict">District User</label>
</div>
@elseif(session('current_user.user_type') == 'regional_user')
<div class="form-check">
<input class="form-check-input" type="radio" name="user_type" value="district_user" id="userTypeDistrict" checked>
<label class="form-check-label" for="userTypeDistrict">District User</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="user_type" value="regional_luspa" id="userTypeLuspaRegion">
<label class="form-check-label" for="userTypeLuspaRegion">Regional LUSPA</label>
</div>
@else
<div class="form-check">
<div class="form-check">
<input class="form-check-input" type="radio" name="user_type" value="district_user" id="userTypeDistrict" checked>
<label class="form-check-label" for="userTypeDistrict">District User</label>
</div>
@ -42,6 +27,11 @@
<input class="form-check-input" type="radio" name="user_type" value="national_luspa" id="userTypeLuspaNational">
<label class="form-check-label" for="userTypeLuspaNational">National LUSPA</label>
</div>
@if(session('current_user.user_type') == 'admin_user')
<div class="form-check">
<input class="form-check-input" type="radio" name="user_type" value="admin_user" id="userTypeAdminUser">
<label class="form-check-label" for="userTypeAdminUser">Admin User</label>
</div>
@endif
</div>
<div class="col-md-2">
@ -94,7 +84,7 @@
</select>
</div>
@if (session('current_user.user_type') == 'district_user' )
<input type="hidden" name="districtid" value="{{ session('current_user.district_id') }}">
<input type="hidden" name="district_id" value="{{ session('current_user.district_id') }}">
@elseif(session('current_user.user_type') == 'regional_user')
<div class="col-md-4">
<label for="regionID" class="form-label">Region</label>
@ -118,7 +108,7 @@
</div>
<div class="col-md-4">
<label for="districtID" class="form-label">District*</label>
<select id="districtID" name="districtid" class="form-select districtIDD">
<select id="districtID" name="district_id" class="form-select districtIDD">
<option selected disabled>Choose...</option>
</select>
</div>

View File

@ -9,13 +9,14 @@
<div class="modal-body">
<div class="alert alert-success d-none" id="editSuccessArea">Success</div>
<div class="alert alert-danger d-none" id="editErrorArea">Failed</div>
<div class="alert alert-info d-none" id="editInfoArea"></div>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<form class="row g-3" action="" method="POST" id="editUserForm">
@csrf
<input type="hidden" name="user_id" value="">
<input type="hidden" name="districtid" value="{{ session('current_user.district_id') }}">
<input type="hidden" name="district_id" value="{{ session('current_user.district_id') }}">
<!-- <input type="hidden" name="user_type" value="district_user"> -->
<div class="col-md-12">
@if (session('current_user.user_type') == 'district_user')
@ -94,7 +95,7 @@
</select>
</div>
@if (session('current_user.user_type') == 'district_user' )
<input type="hidden" name="districtid" value="{{ session('current_user.district_id') }}">
<input type="hidden" name="district_id" value="{{ session('current_user.district_id') }}">
@elseif(session('current_user.user_type') == 'regional_user')
<div class="col-md-4">
<label for="editRegionID" class="form-label">Region *</label>
@ -107,7 +108,7 @@
</div>
<div class="col-md-4">
<label for="editDistrictID" class="form-label">District *</label>
<select id="editDistrictID" name="districtid" class="form-select">
<select id="editDistrictID" name="district_id" class="form-select">
<option selected disabled>Choose...</option>
</select>
</div>
@ -123,30 +124,37 @@
</div>
<div class="col-md-4">
<label for="editDistrictID" class="form-label">District *</label>
<select id="editDistrictID" name="districtid" class="form-select districtIDD">
<select id="editDistrictID" name="district_id" class="form-select districtIDD">
<option selected disabled>Choose...</option>
</select>
</div>
@endif
<div class="col-md-4">
<label for="userStatus" class="form-label">User Status</label>
<select id="userStatus" name="user_status" class="form-select"required style="width:100%">
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
<div class="col-md-4">
<label for="editAllowedApps" class="form-label">Allowed Apps *</label>
<select id="editAllowedApps" name="allowed_apps[]" class="form-select" multiple required style="width:100%">
<!-- <option selected >Choose...</option> -->
<option value="drawing-tools">Drawing Tools</option>
<option value="permit-tools">Permit Tools</option>
<option value="admin-gui">Admin GUI</option>
</select>
</div>
@if(env('APP_ENV') == 'local')
<div class="col-md-6">
<label for="editAllowedApps" class="form-label">Allowed Apps *</label>
<select id="editAllowedApps" name="allowed_apps[]" class="form-select" multiple required style="width:100%">
<!-- <option selected >Choose...</option> -->
<option value="drawing-tools">Drawing Tools</option>
<option value="permit-tools">Permit Tools</option>
<option value="admin-gui">Admin GUI</option>
</select>
</div>
<div class="col-md-6">
<label for="userStatus" class="form-label">Status *</label>
<select id="userStatus" name="user_status" class="form-select"required style="width:100%">
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
<label for="resetPassword" class="form-label">Reset Password</label>
<input type="text" name="reset_password" value="" class="form-control" id="resetPassword">
</div>
@endif
<div class="col-md-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="expire_password" value="yes" id="expirePassword" checked>
<input class="form-check-input" type="checkbox" name="expire_password" value="yes" id="expirePassword">
<label class="form-check-label" for="expirePassword">Expire Password</label>
</div>
</div>

View File

@ -17,7 +17,7 @@
<input type="text" class="form-control userFullName" name="full_name" id="userName" value="">
</div>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<label for="name" class="form-label">Username</label>
<input type="text" class="form-control userName" name="username" id="username" value="">
</div>
<div class="mb-3">
@ -34,11 +34,11 @@
</div>
<div class="mb-3">
<label for="designation" class="form-label">Password</label>
<input type="text" class="form-control userPassword" name="password" id="password" value="" placeholder="Enter a new password to reset">
<input type="password" class="form-control userPassword" name="password" id="password" value="" placeholder="Enter a new password to reset">
</div>
<div class="mb-3">
<label for="dateAdded" class="form-label">Date Added</label>
<input type="text" class="form-control userDateAdded" id="dateAdded" value="" readonly>
<input type="text" class="form-control userDateAdded" id="dateAdded" value="" readonly disabled>
</div>
<button type="button" class="btn btn-primary w-100" id="profileModalSubmitBtn">Update</button>

View File

@ -6,13 +6,14 @@
<body>
<h2>Account Reset Request</h2>
<p>Hello {{ $fullname }} </p>
<p>Your have requested to reset your LUPMIS Account. Use the link below to complete the process.</p>
<p>Your have requested to reset your LUPMIS Account password or the admin has expired your password.
Use the link below to complete the process of setting a new one</p>
<ol>
<li><a href="{{ $reset_url }}">Reset Link</a></li>
</ol>
<p>The reset link will expire in 15 mins</p>
<p>If you did not make this request, no further action is needed.</p>
<p>The reset link will expire in 30 mins</p>
<!-- <p>If you did not make this request, no further action is needed.</p> -->

View File

@ -44,11 +44,11 @@
</div>
</div>
</div>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary shadow-sm" style="z-index: 2000;">
<nav class="navbar navbar-expand-lg navbar-dark bg-primary shadow-sm" >
<div class="container">
<a class="navbar-brand" href="/landing">LUPMIS4LUSPA</a>
<div class="ms-auto">
<div class="dropdown">
<div class="dropdown" style="z-index: 1080;">
<button class="btn btn-light dropdown-toggle" type="button" id="userDropdown" data-bs-toggle="dropdown">
<span class="me-2"><?php echo ucfirst(session('current_user.username')); ?></span>
<!-- <small class="text-muted">Municipality</small> -->

View File

@ -0,0 +1,538 @@
@extends('layouts.master')
@section('page-title')
Permits | {{ $page_title ?? 'Details' }}
@endsection
@section('page-css')
<link rel="stylesheet" type="text/css" href="{{ url('public/assets/css/permit_show.css') }}">
<style>
/* TIMELINE & STEPPER CSS */
.planning-stepper { position: relative; padding-left: 1.25rem; }
.planning-stepper::before { content: ''; position: absolute; top: 6px; bottom: 0; left: 4px; width: 2px; background-color: #e9ecef; z-index: 0; }
.stepper-item { position: relative; padding-bottom: 1.75rem; }
.stepper-item:last-child { padding-bottom: 0; }
.stepper-dot { position: absolute; left: -1.25rem; top: 0.25rem; width: 10px; height: 10px; border-radius: 50%; z-index: 1; background-color: #dee2e6; }
.stepper-dot.completed { background-color: #20c997; }
.stepper-dot.active { background-color: #fd7e14; box-shadow: 0 0 0 5px rgba(253, 126, 20, 0.15); }
.stepper-dot.pending { background-color: #e2e8f0; }
/* Offcanvas Timeline CSS */
.timeline { position: relative; padding-left: 2rem; margin-top: 1rem; }
.timeline::before { content: ''; position: absolute; top: 0; bottom: 0; left: 0.85rem; width: 2px; background-color: #dee2e6; z-index: 0; }
/* Custom Tab Styling to match mockup */
.nav-tabs .nav-link { color: #6c757d; font-weight: 500; border: none; padding: 1rem 1.5rem; margin-bottom: -1px; }
.nav-tabs .nav-link:hover { color: #0d6efd; border-color: transparent; }
.nav-tabs .nav-link.active { color: #0d6efd; background-color: transparent; border-bottom: 2px solid #0d6efd; font-weight: 600; }
body { background-color: #f4f5f7; } /* Slightly darker background to make white cards pop like the image */
</style>
@endsection
@section('page-content')
@include('permits.partials.pdf-modal')
@include('layouts.partials.permits-navbar')
@include('permits.partials.site-inspection')
<div class="container py-4">
<div class="row g-4">
<!-- LEFT COLUMN (Main Content) -->
<div class="col-lg-8 col-md-12">
<!-- Title & Badge -->
<div class="d-flex align-items-center mb-3 mt-1 gap-3">
<h1 class="display-6 fw-bold text-dark mb-0 fs-2">{{ $permit_arr['application_code'] ?? 'REQ-20260312-22AL' }}</h1>
<span class="badge rounded-pill shadow-sm align-middle fs-6 px-3 py-2" style="background-color: #f39c12; color: #fff;">{{ $permit_arr['status'] ?? 'SUBMITTED' }}</span>
</div>
<!-- Map Container -->
<div class="map-container mb-4 border border-light-subtle shadow-sm rounded overflow-hidden" style="height: 400px; background-color: #e9ecef; position: relative;">
<!-- Map Iframe -->
<iframe id="permit-map"
src="https://pwa.lupmis4luspa.org/embed.php?mode=permit&application_code={{ urlencode($permit_arr['application_code'] ?? '') }}{{ !empty($permit_arr['lon']) ? '&lon='.urlencode($permit_arr['lon']).'&lat='.urlencode($permit_arr['lat']) : '' }}{{ !empty($permit_arr['upn']) ? '&upn='.urlencode($permit_arr['upn']) : '' }}"
style="width:100%;height:100%;border:0;"
allow="geolocation; clipboard-write"
referrerpolicy="strict-origin-when-cross-origin"
loading="lazy"
title="Permit location map">
</iframe>
</div>
<!-- Tabs Section (Matches Image Layout) -->
<div class="card border border-light-subtle shadow-sm mb-4 bg-white">
<div class="card-header bg-white border-bottom-0 p-0 pt-2 px-3">
<ul class="nav nav-tabs border-bottom" id="permitDetailTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="applicant-tab" data-bs-toggle="tab" data-bs-target="#applicant-tab-pane" type="button" role="tab" aria-controls="applicant-tab-pane" aria-selected="true">Applicant Details</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="location-tab" data-bs-toggle="tab" data-bs-target="#location-tab-pane" type="button" role="tab" aria-controls="location-tab-pane" aria-selected="false">Project Location</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="documents-tab" data-bs-toggle="tab" data-bs-target="#documents-tab-pane" type="button" role="tab" aria-controls="documents-tab-pane" aria-selected="false">Documents</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="inspection-tab" data-bs-toggle="tab" data-bs-target="#inspection-tab-pane" type="button" role="tab" aria-controls="inspection-tab-pane" aria-selected="false">Site Inspection</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="fees-tab" data-bs-toggle="tab" data-bs-target="#fees-tab-pane" type="button" role="tab" aria-controls="fees-tab-pane" aria-selected="false">Payments</button>
</li>
</ul>
</div>
<div class="card-body p-4">
<div class="tab-content" id="permitDetailTabsContent">
<!-- TAB 1: Applicant Details -->
<div class="tab-pane fade show active" id="applicant-tab-pane" role="tabpanel" aria-labelledby="applicant-tab" tabindex="0">
<div class="d-flex align-items-center mb-4">
<i class="bi bi-person text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Applicant & Property Profile</h6>
</div>
<div class="row gy-4">
<div class="col-12">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Applicant Name:</div>
<div class="fw-bold text-dark fs-5">{{ $permit_arr['applicant_name'] ?? 'N/A' }}</div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Nationality:</div>
<div class="text-dark fw-medium">{{ $permit_arr['nationality'] ?? 'N/A' }}</div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Email Address:</div>
<div><a href="mailto:{{ $permit_arr['email'] ?? '' }}" class="text-decoration-none text-primary">{{ $permit_arr['email'] ?? 'N/A' }}</a></div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Telephone Number:</div>
<div class="text-dark">{{ $permit_arr['phone'] ?? 'N/A' }}</div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Project Location:</div>
<div class="text-dark fw-bold">{{ $permit_arr['project_location'] ?? 'N/A' }}</div>
</div>
<div class="col-12">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Mailing Address:</div>
<div class="text-dark">{{ $permit_arr['address'] ?? 'N/A' }}</div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Permit Type Requested</div>
<div class="text-dark fw-bold">{{ $permit_arr['permit_type'] ?? 'N/A' }}</div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Purpose</div>
<div class="fw-bold" style="color: #e67e22;">{{ $permit_arr['land_request_use'] ?? 'N/A' }}</div>
</div>
<div class="col-12 mt-2">
<div class="border border-dark-subtle rounded p-3 bg-light">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Project Scope:</div>
<div class="text-dark fst-italic" style="font-size: 0.95rem;">{{ $permit_arr['project_description'] ?? 'No scope provided.' }}</div>
</div>
</div>
</div>
</div>
<!-- TAB 2: Project Location -->
<div class="tab-pane fade" id="location-tab-pane" role="tabpanel" aria-labelledby="location-tab" tabindex="0">
<div class="d-flex align-items-center mb-4">
<i class="bi bi-geo-alt-fill text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-primary" style="font-size: 1.1rem;">Actual Project Location</h6>
</div>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label fw-bold text-secondary mb-1" style="font-size: 0.75rem;">
COORDINATES (LATITUDE, LONGITUDE) <span class="text-danger">*</span>
</label>
<div class="input-group">
<span class="input-group-text bg-white text-muted">GPS:</span>
<input type="text" class="form-control ps-1" id="permitLocation" placeholder="e.g., 5.66240, -0.18055" value="">
</div>
</div>
<div class="col-md-6">
<label class="form-label fw-bold text-secondary mb-1" style="font-size: 0.75rem;">
UPN (UNIQUE PARCEL NUMBER)
</label>
<input type="text" class="form-control" id="permitUPN" value="">
</div>
</div>
<div class="d-flex gap-3">
<button type="button" id="checkComplianceBtn" class="btn fw-bold px-4 text-white shadow-sm" style="background-color: #f39c12; border: none;">
Check Zone Compliance
</button>
<button type="button" class="btn btn-primary fw-bold px-4 shadow-sm" style="background-color: #3b82f6; border: none;">
<i class="bi bi-download me-1"></i> Save Physical Location
</button>
</div>
</div>
<!-- TAB 5: Documents -->
<div class="tab-pane fade" id="documents-tab-pane" role="tabpanel" aria-labelledby="documents-tab" tabindex="0">
<div class="d-flex align-items-center mb-4">
<i class="bi bi-file-earmark-pdf text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Uploaded Documents</h6>
</div>
<div class="d-flex flex-column gap-3">
<div class="border rounded p-3 d-flex align-items-center justify-content-between bg-light">
<div class="d-flex align-items-center">
<i class="bi bi-file-earmark-text text-primary fs-3 me-3"></i>
<div>
<div class="text-dark fw-medium">Site Plan Drawing</div>
<div class="text-secondary font-monospace" style="font-size: 0.75rem;">site_plan.pdf</div>
</div>
</div>
<a href="#" class="btn btn-sm btn-outline-secondary fw-semibold">Open File <i class="bi bi-box-arrow-up-right ms-1"></i></a>
</div>
<div class="border rounded p-3 d-flex align-items-center justify-content-between bg-light">
<div class="d-flex align-items-center">
<i class="bi bi-file-earmark-text text-primary fs-3 me-3"></i>
<div>
<div class="text-dark fw-medium">Architectural Drawings</div>
<div class="text-secondary font-monospace" style="font-size: 0.75rem;">architectural.pdf</div>
</div>
</div>
<a href="#" class="btn btn-sm btn-outline-secondary fw-semibold">Open File <i class="bi bi-box-arrow-up-right ms-1"></i></a>
</div>
</div>
</div>
<!-- TAB 3: Site Inspection -->
<div class="tab-pane fade" id="inspection-tab-pane" role="tabpanel" aria-labelledby="inspection-tab" tabindex="0">
<div class="d-flex align-items-center mb-4">
<i class="bi bi-clipboard-check text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Site Inspection</h6>
</div>
<p class="text-muted mb-4">Generate and manage the official site inspection report for this application.</p>
<button type="button" class="btn btn-outline-primary px-4 py-2" data-bs-toggle="modal" data-bs-target="#inspectionReportModal">
<i class="bi bi-file-earmark-text me-2"></i> Open Site Inspection Report
</button>
</div>
<!-- TAB 4: Permit Fees -->
<div class="tab-pane fade" id="fees-tab-pane" role="tabpanel" aria-labelledby="fees-tab" tabindex="0">
<div class="d-flex align-items-center mb-4">
<i class="bi bi-cash-stack text-success fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Payments</h6>
</div>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label fw-bold text-secondary mb-1" style="font-size: 0.75rem;">
PERMIT FEE (GH₵) <span class="text-danger">*</span>
</label>
<input type="number" class="form-control fw-bold" placeholder="0.00">
</div>
</div>
<button type="button" class="btn btn-success fw-bold px-4 shadow-sm">
Submit Fee
</button>
</div>
</div>
</div>
</div>
</div>
<!-- RIGHT COLUMN (Sidebar Layout exactly like image) -->
<div class="col-lg-4 col-md-12">
<div class="sticky-top" style="top: 1.5rem;">
<!-- Processing & Duration Card -->
<div class="card border border-light-subtle shadow-sm mb-4">
<div class="card-body p-4">
<div class="d-flex justify-content-between align-items-start border-bottom pb-3 mb-4">
<div class="d-flex align-items-center">
<i class="bi bi-clock text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark">Processing & Duration</h6>
</div>
<div class="text-end">
<div class="text-uppercase text-secondary fw-bold mb-1" style="font-size: 0.65rem; letter-spacing: 0.5px;">REMAINING DAYS</div>
<span class="badge text-dark fs-6 px-3 py-1 bg-light border">N/A</span>
</div>
</div>
<div class="overflow-auto pe-2" style="max-height: 450px;">
<div class="planning-stepper">
<div class="stepper-item">
<div class="stepper-dot completed" style="background-color: #2ecc71;"></div>
<div class="text-uppercase text-secondary fw-bold mb-1" style="font-size: 0.65rem; letter-spacing: 0.5px;">28TH FEB 2026, 11:58 PM - LUPMIS ENGINE</div>
<h6 class="fw-bold mb-1 text-dark">Submission Received</h6>
<p class="mb-2 small text-secondary">Application successfully uploaded to the Portal by applicant.</p>
<span class="badge bg-light text-secondary border fw-medium px-2 py-1">Verified by: Applicant Portal</span>
</div>
<div class="stepper-item">
<div class="stepper-dot active" style="background-color: #e67e22; box-shadow: none;"></div>
<div class="text-uppercase text-secondary fw-bold mb-1" style="font-size: 0.65rem; letter-spacing: 0.5px;">PENDING PROCESS SCHEDULE - LUPMIS ENGINE</div>
<h6 class="fw-bold mb-1" style="color: #e67e22;">Applicant to Review</h6>
<p class="mb-2 small text-secondary">Applicant asked to review attached documents.</p>
<span class="badge bg-light text-secondary border fw-medium px-2 py-1">Updated by: Luke Eshun</span>
</div>
</div>
</div>
</div>
</div>
<!-- Status & Comments Card -->
<div class="card border border-light-subtle shadow-sm mb-4">
<div class="card-body p-4">
<div class="d-flex align-items-center border-bottom pb-3 mb-4">
<i class="bi bi-chat-dots-fill text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark">Status & Comments</h6>
</div>
<div class="mb-4">
<button class="btn btn-outline-secondary w-100 fw-bold py-2" type="button" data-bs-toggle="offcanvas" data-bs-target="#historyOffcanvas" aria-controls="historyOffcanvas">
<i class="bi bi-clock-history me-2"></i> View Full History & Comments
</button>
</div>
<div class="mb-3">
<label for="permitCurrentStatus" class="form-label fw-bold text-secondary mb-2" style="font-size: 0.75rem; letter-spacing: 0.5px;">
<i class="bi bi-info-circle text-primary me-1"></i> STATUS UPDATE
</label>
<select class="form-select py-2" id="permitCurrentStatus">
<option value="" selected disabled>Select status ...</option>
<option value="status_zero">Applicant to Review</option>
<option value="status_one">Application Received</option>
<option value="status_two" disabled>Application Accepted</option>
<option value="status_three" disabled>Permit Pending TSC Review</option>
<option value="status_four" disabled>Permit Pending SPC Decision</option>
<option value="status_five" disabled>Permit Approved (In Principle)</option>
<option value="status_six" disabled>Permit Approved</option>
<option value="status_seven" disabled>Permit Refused</option>
<option value="status_eight" disabled>Permit Deferred</option>
</select>
</div>
<div class="mb-4">
<textarea class="form-control commentBody py-2" rows="4" name="comment_body" placeholder="Enter new comment here" style="resize: none; font-style: italic;"></textarea>
<input type="hidden" class="applicationCode" name="application_code" value="{{ $permit_arr['application_code'] ?? '' }}">
</div>
<button class="btn w-100 fw-bold shadow-sm submitPermitCommentBtn py-2 text-white" type="button" style="background-color: #3b82f6;">
<i class="bi bi-send-fill me-2"></i> Submit
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="offcanvas offcanvas-end shadow-lg" tabindex="-1" id="historyOffcanvas" aria-labelledby="historyOffcanvasLabel" style="width: 450px;">
<div class="offcanvas-header border-bottom bg-light sticky-top z-3">
<h5 class="offcanvas-title fw-bold" id="historyOffcanvasLabel">Application History</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<div class="timeline" id="historyTimeline">
<div class="text-center text-muted mt-5">
<i class="bi bi-arrow-clockwise fs-1"></i>
<p class="mt-2">Loading application history...</p>
</div>
</div>
</div>
</div>
@endsection
@section('page-scripts')
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<script src="{{ url('public/assets/js/permit_comments.js') }}" type="text/javascript"></script>
<script type="text/javascript">
window.generateReportPDF = function() {
console.log("1. Button clicked. Starting PDF process...");
if (typeof html2pdf === 'undefined') {
alert("ERROR: The html2pdf library is not loaded.");
return;
}
const element = document.getElementById('report-pdf-content');
if (!element) {
alert("ERROR: Could not find the div with id='report-pdf-content'.");
return;
}
try {
const inputs = element.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
if (input.tagName === 'TEXTAREA') {
input.innerHTML = input.value;
} else if (input.tagName === 'SELECT') {
const selectedOption = input.options[input.selectedIndex];
if (selectedOption) selectedOption.setAttribute('selected', 'selected');
input.style.backgroundImage = 'none';
input.style.appearance = 'none';
} else {
input.setAttribute('value', input.value);
}
});
console.log("3. Form data locked and SVGs removed. Generating file...");
const opt = {
margin: 0.5,
filename: 'Site_Inspection_Report.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: {
scale: 2,
useCORS: true,
logging: false,
scrollY: 0,
windowY: 0
},
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' },
pagebreak: {
mode: 'css',
avoid: ['.row', 'h6']
}
};
html2pdf().set(opt).from(element).save().then(() => {
console.log("Success: PDF downloaded.");
inputs.forEach(input => {
if (input.tagName === 'SELECT') {
input.style.backgroundImage = '';
input.style.appearance = '';
}
});
}).catch(err => {
console.error("PDF Generation Error:", err);
alert("Failed to build the PDF. Check the browser console.");
});
} catch (error) {
console.error("Critical Execution Error:", error);
alert("A javascript error occurred: " + error.message);
}
};
$(document).ready(function(){
var pdfModal = document.getElementById('pdfModal');
if(pdfModal) {
pdfModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget;
var pdfUrl = button.getAttribute('data-pdf-url');
var pdfIframe = document.getElementById('pdfIframe');
if(pdfIframe) pdfIframe.src = pdfUrl;
});
pdfModal.addEventListener('hidden.bs.modal', function () {
var pdfIframe = document.getElementById('pdfIframe');
if(pdfIframe) pdfIframe.src = '';
});
}
const MAP_ORIGIN = 'https://pwa.lupmis4luspa.org';
const iframe = document.getElementById('permit-map');
const upnInput = document.getElementById('permitUPN');
const locInput = document.getElementById('permitLocation');
window.addEventListener('message', function (event) {
if (event.origin !== MAP_ORIGIN) return;
const msg = event.data;
if (!msg || typeof msg !== 'object') return;
switch (msg.type) {
case 'ready':
@if(!empty($permit_arr['upn']))
if(iframe) {
iframe.contentWindow.postMessage(
{ type: 'set:selected', upn: @json($permit_arr['upn']) },
MAP_ORIGIN);
}
@endif
break;
case 'parcel:select':
if(upnInput) upnInput.value = msg.upn || '';
if(locInput) {
locInput.value = (msg.lon != null && msg.lat != null)
? msg.lat.toFixed(6) + ', ' + msg.lon.toFixed(6)
: '';
}
if(upnInput) {
upnInput.dataset.parcelId = msg.parcel_id || '';
upnInput.dataset.zoneCode = msg.zone_code || '';
}
break;
case 'parcel:cleared':
if(upnInput) {
upnInput.value = '';
delete upnInput.dataset.parcelId;
delete upnInput.dataset.zoneCode;
}
if(locInput) locInput.value = '';
break;
case 'error':
console.warn('[permit-map]', msg.code, msg.message);
break;
}
});
$('#checkComplianceBtn').on('click', function (e) {
e.preventDefault();
const payload = {
application_code: @json($permit_arr['application_code'] ?? ''),
upn: upnInput ? upnInput.value : '',
parcel_id: upnInput ? (upnInput.dataset.parcelId || null) : null,
coordinates: locInput ? locInput.value : '',
zone_code: upnInput ? (upnInput.dataset.zoneCode || null) : null,
};
$.ajax({
url: '{{ route("permits.checkCompliance") }}',
method: 'POST',
data: payload
}).done(function (res) {
/* render compliance result */
});
});
const downloadBtn = document.getElementById('downloadPdfBtn');
if(downloadBtn) {
downloadBtn.addEventListener('click', function() {
const element = document.getElementById('report-pdf-content');
if(!element) return;
const inputs = element.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
if (input.tagName === 'TEXTAREA') {
input.innerHTML = input.value;
} else if (input.tagName === 'SELECT') {
const selectedOption = input.options[input.selectedIndex];
if (selectedOption) selectedOption.setAttribute('selected', 'selected');
} else {
input.setAttribute('value', input.value);
}
});
const originalText = downloadBtn.innerHTML;
downloadBtn.innerHTML = '<i class="bi bi-hourglass-split"></i> Generating...';
downloadBtn.disabled = true;
const opt = {
margin: 0.5,
filename: 'Site_Inspection_Report.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true, logging: false },
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' }
};
html2pdf().set(opt).from(element).save().then(() => {
downloadBtn.innerHTML = originalText;
downloadBtn.disabled = false;
});
});
}
});
</script>
@endsection

View File

@ -0,0 +1,607 @@
@extends('layouts.master')
@section('page-title')
Permits | {{ $page_title ?? 'Details' }}
@endsection
@section('page-css')
<link rel="stylesheet" type="text/css" href="{{ url('public/assets/css/permit_show.css') }}">
<style>
/* TIMELINE & STEPPER CSS */
.planning-stepper { position: relative; padding-left: 1.25rem; }
.planning-stepper::before { content: ''; position: absolute; top: 6px; bottom: 0; left: 4px; width: 2px; background-color: #e9ecef; z-index: 0; }
.stepper-item { position: relative; padding-bottom: 1.75rem; }
.stepper-item:last-child { padding-bottom: 0; }
.stepper-dot { position: absolute; left: -1.25rem; top: 0.25rem; width: 10px; height: 10px; border-radius: 50%; z-index: 1; background-color: #dee2e6; }
.stepper-dot.completed { background-color: #20c997; }
.stepper-dot.active { background-color: #fd7e14; box-shadow: 0 0 0 5px rgba(253, 126, 20, 0.15); }
.stepper-dot.pending { background-color: #e2e8f0; }
/* Offcanvas Timeline CSS */
.timeline { position: relative; padding-left: 2rem; margin-top: 1rem; }
.timeline::before { content: ''; position: absolute; top: 0; bottom: 0; left: 0.85rem; width: 2px; background-color: #dee2e6; z-index: 0; }
/* Custom Tab Styling to match mockup */
.nav-tabs .nav-link { color: #6c757d; font-weight: 500; border: none; padding: 1rem 1.5rem; margin-bottom: -1px; }
.nav-tabs .nav-link:hover { color: #0d6efd; border-color: transparent; }
.nav-tabs .nav-link.active { color: #0d6efd; background-color: transparent; border-bottom: 2px solid #0d6efd; font-weight: 600; }
body { background-color: #f4f5f7; } /* Slightly darker background to make white cards pop like the image */
</style>
@endsection
@section('page-content')
@include('permits.partials.pdf-modal')
@include('layouts.partials.permits-navbar')
@include('permits.partials.site-inspection')
<div class="container py-4">
<div class="row g-4">
<!-- LEFT COLUMN (Main Content) -->
<div class="col-lg-8 col-md-12">
<!-- Title & Badge -->
<div class="d-flex align-items-center mb-3 mt-1 gap-3">
<h1 class="display-6 fw-bold text-dark mb-0 fs-2">{{ $permit_arr['application_code'] ?? 'REQ-20260312-22AL' }}</h1>
<span class="badge rounded-pill shadow-sm align-middle fs-6 px-3 py-2" style="background-color: #f39c12; color: #fff;">{{ $permit_arr['status'] ?? 'SUBMITTED' }}</span>
</div>
<!-- Map Container -->
<div class="map-container mb-4 border border-light-subtle shadow-sm rounded overflow-hidden" style="height: 400px; background-color: #e9ecef; position: relative;">
<!-- Map Iframe -->
<!-- <iframe id="permit-map"
src="https://pwa.lupmis4luspa.org/embed.php?mode=permit&application_code={{ urlencode($permit_arr['application_code'] ?? '') }}{{ !empty($permit_arr['lon']) ? '&lon='.urlencode($permit_arr['lon']).'&lat='.urlencode($permit_arr['lat']) : '' }}{{ !empty($permit_arr['upn']) ? '&upn='.urlencode($permit_arr['upn']) : '' }}"
style="width:100%;height:100%;border:0;"
allow="geolocation; clipboard-write"
referrerpolicy="strict-origin-when-cross-origin"
loading="lazy"
title="Permit location map">
</iframe> -->
</div>
<!-- Tabs Section (Matches Image Layout) -->
<div class="card border border-light-subtle shadow-sm mb-4 bg-white">
<div class="card-header bg-white border-bottom-0 p-0 pt-2 px-3">
<ul class="nav nav-tabs border-bottom" id="permitDetailTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="applicant-tab" data-bs-toggle="tab" data-bs-target="#applicant-tab-pane" type="button" role="tab" aria-controls="applicant-tab-pane" aria-selected="true">Applicant Details</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="location-tab" data-bs-toggle="tab" data-bs-target="#location-tab-pane" type="button" role="tab" aria-controls="location-tab-pane" aria-selected="false">Project Location</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="documents-tab" data-bs-toggle="tab" data-bs-target="#documents-tab-pane" type="button" role="tab" aria-controls="documents-tab-pane" aria-selected="false">Documents</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="inspection-tab" data-bs-toggle="tab" data-bs-target="#inspection-tab-pane" type="button" role="tab" aria-controls="inspection-tab-pane" aria-selected="false">Site Inspection</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="fees-tab" data-bs-toggle="tab" data-bs-target="#fees-tab-pane" type="button" role="tab" aria-controls="fees-tab-pane" aria-selected="false">Payments</button>
</li>
</ul>
</div>
<div class="card-body p-4">
<div class="tab-content" id="permitDetailTabsContent">
<!-- TAB 1: Applicant Details -->
<div class="tab-pane fade show active" id="applicant-tab-pane" role="tabpanel" aria-labelledby="applicant-tab" tabindex="0">
<div class="d-flex align-items-center mb-4">
<i class="bi bi-person text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Applicant & Property Profile</h6>
</div>
<div class="row gy-4">
<div class="col-12">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Applicant Name:</div>
<div class="fw-bold text-dark fs-5">{{ $permit_arr['applicant_name'] ?? 'N/A' }}</div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Nationality:</div>
<div class="text-dark fw-medium">{{ $permit_arr['nationality'] ?? 'N/A' }}</div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Email Address:</div>
<div><a href="mailto:{{ $permit_arr['email'] ?? '' }}" class="text-decoration-none text-primary">{{ $permit_arr['email'] ?? 'N/A' }}</a></div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Telephone Number:</div>
<div class="text-dark">{{ $permit_arr['phone'] ?? 'N/A' }}</div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Project Location:</div>
<div class="text-dark fw-bold">{{ $permit_arr['project_location'] ?? 'N/A' }}</div>
</div>
<div class="col-12">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Mailing Address:</div>
<div class="text-dark">{{ $permit_arr['address'] ?? 'N/A' }}</div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Permit Type Requested</div>
<div class="text-dark fw-bold">{{ $permit_arr['permit_type'] ?? 'N/A' }}</div>
</div>
<div class="col-md-6">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Purpose</div>
<div class="fw-bold" style="color: #e67e22;">{{ $permit_arr['land_request_use'] ?? 'N/A' }}</div>
</div>
<div class="col-12 mt-2">
<div class="border border-dark-subtle rounded p-3 bg-light">
<div class="text-uppercase text-secondary fw-semibold mb-1" style="font-size: 0.75rem; letter-spacing: 0.5px;">Project Scope:</div>
<div class="text-dark fst-italic" style="font-size: 0.95rem;">{{ $permit_arr['project_description'] ?? 'No scope provided.' }}</div>
</div>
</div>
</div>
</div>
<!-- TAB 2: Project Location -->
<div class="tab-pane fade" id="location-tab-pane" role="tabpanel" aria-labelledby="location-tab" tabindex="0">
<div class="d-flex align-items-center mb-4">
<i class="bi bi-geo-alt-fill text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-primary" style="font-size: 1.1rem;">Actual Project Location</h6>
</div>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label fw-bold text-secondary mb-1" style="font-size: 0.75rem;">
COORDINATES (LATITUDE, LONGITUDE) <span class="text-danger">*</span>
</label>
<div class="input-group">
<span class="input-group-text bg-white text-muted">GPS:</span>
<input type="text" class="form-control ps-1" id="permitLocation" placeholder="e.g., 5.66240, -0.18055" value="">
</div>
</div>
<div class="col-md-6">
<label class="form-label fw-bold text-secondary mb-1" style="font-size: 0.75rem;">
UPN (UNIQUE PARCEL NUMBER)
</label>
<input type="text" class="form-control" id="permitUPN" value="">
</div>
</div>
<div class="d-flex gap-3">
<button type="button" id="checkComplianceBtn" class="btn fw-bold px-4 text-white shadow-sm" style="background-color: #f39c12; border: none;">
Check Zone Compliance
</button>
<button type="button" class="btn btn-primary fw-bold px-4 shadow-sm" style="background-color: #3b82f6; border: none;">
<i class="bi bi-download me-1"></i> Save Physical Location
</button>
</div>
</div>
<!-- TAB 5: Documents -->
<div class="tab-pane fade" id="documents-tab-pane" role="tabpanel" aria-labelledby="documents-tab" tabindex="0">
<div class="d-flex align-items-center mb-4">
<i class="bi bi-file-earmark-pdf text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Uploaded Documents</h6>
</div>
<div class="d-flex flex-column gap-3">
<div class="border rounded p-3 d-flex align-items-center justify-content-between bg-light">
<div class="d-flex align-items-center">
<i class="bi bi-file-earmark-text text-primary fs-3 me-3"></i>
<div>
<div class="text-dark fw-medium">Site Plan Drawing</div>
<div class="text-secondary font-monospace" style="font-size: 0.75rem;">site_plan.pdf</div>
</div>
</div>
<a href="#" class="btn btn-sm btn-outline-secondary fw-semibold">Open File <i class="bi bi-box-arrow-up-right ms-1"></i></a>
</div>
<div class="border rounded p-3 d-flex align-items-center justify-content-between bg-light">
<div class="d-flex align-items-center">
<i class="bi bi-file-earmark-text text-primary fs-3 me-3"></i>
<div>
<div class="text-dark fw-medium">Architectural Drawings</div>
<div class="text-secondary font-monospace" style="font-size: 0.75rem;">architectural.pdf</div>
</div>
</div>
<a href="#" class="btn btn-sm btn-outline-secondary fw-semibold">Open File <i class="bi bi-box-arrow-up-right ms-1"></i></a>
</div>
</div>
<hr class="my-4 text-secondary">
<div class="d-flex align-items-center mb-3">
<i class="bi bi-plus-square-dotted text-warning fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Request Required Documents</h6>
</div>
<div class="card border border-light-subtle shadow-sm bg-white">
<div class="card-body p-4">
<p class="text-muted small mb-3">Select the missing documents the applicant must upload before processing can continue.</p>
<form id="requestDocumentsForm">
<div class="row g-3 mb-4">
<div class="col-md-6">
<div class="form-check mb-2">
<input class="form-check-input req-doc-checkbox" type="checkbox" name="requested_docs[]" value="structural_drawings" id="doc1">
<label class="form-check-label fw-medium" for="doc1">Structural Drawings</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input req-doc-checkbox" type="checkbox" name="requested_docs[]" value="fire_report" id="doc2">
<label class="form-check-label fw-medium" for="doc2">Fire Service Report</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input req-doc-checkbox" type="checkbox" name="requested_docs[]" value="epa_certificate" id="doc3">
<label class="form-check-label fw-medium" for="doc3">EPA Certificate</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check mb-2">
<input class="form-check-input req-doc-checkbox" type="checkbox" name="requested_docs[]" value="title_certificate" id="doc4">
<label class="form-check-label fw-medium" for="doc4">Land Title Certificate</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input req-doc-checkbox" type="checkbox" name="requested_docs[]" value="soil_test" id="doc5">
<label class="form-check-label fw-medium" for="doc5">Soil Test Report</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input req-doc-checkbox" type="checkbox" name="requested_docs[]" value="block_plan" id="doc6">
<label class="form-check-label fw-medium" for="doc6">Block Plan</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold text-secondary small" style="font-size: 0.75rem;">ADDITIONAL NOTES (OPTIONAL)</label>
<textarea class="form-control" id="docRequestNotes" rows="2" placeholder="e.g., Please ensure the EPA certificate is dated this year..."></textarea>
</div>
<div class="d-flex align-items-center gap-3">
<button type="button" class="btn fw-bold px-4 text-dark shadow-sm" id="sendDocRequestBtn" style="background-color: #f39c12; border: none;">
<i class="bi bi-send me-1"></i> Request from Applicant
</button>
<span class="text-success small fw-bold d-none" id="docRequestSuccessMsg">
<i class="bi bi-check-circle-fill me-1"></i> Request Sent!
</span>
</div>
</form>
</div>
</div>
</div>
<!-- TAB 3: Site Inspection -->
<div class="tab-pane fade" id="inspection-tab-pane" role="tabpanel" aria-labelledby="inspection-tab" tabindex="0">
<div class="d-flex align-items-center mb-4">
<i class="bi bi-clipboard-check text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Site Inspection</h6>
</div>
<p class="text-muted mb-4">Generate and manage the official site inspection report for this application.</p>
<button type="button" class="btn btn-outline-primary px-4 py-2" data-bs-toggle="modal" data-bs-target="#inspectionReportModal">
<i class="bi bi-file-earmark-text me-2"></i> Open Site Inspection Report
</button>
</div>
<!-- TAB 4: Permit Fees -->
<div class="tab-pane fade" id="fees-tab-pane" role="tabpanel" aria-labelledby="fees-tab" tabindex="0">
<div class="d-flex align-items-center mb-4">
<i class="bi bi-cash-stack text-success fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark" style="font-size: 1.1rem;">Payments</h6>
</div>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label fw-bold text-secondary mb-1" style="font-size: 0.75rem;">
PERMIT FEE (GH₵) <span class="text-danger">*</span>
</label>
<input type="number" class="form-control fw-bold" placeholder="0.00">
</div>
</div>
<button type="button" class="btn btn-success fw-bold px-4 shadow-sm">
Apply Fee
</button>
</div>
</div>
</div>
</div>
</div>
<!-- RIGHT COLUMN (Sidebar Layout exactly like image) -->
<div class="col-lg-4 col-md-12">
<div class="sticky-top" style="top: 1.5rem;">
<!-- Processing & Duration Card -->
<div class="card border border-light-subtle shadow-sm mb-4">
<div class="card-body p-4">
<div class="d-flex justify-content-between align-items-start border-bottom pb-3 mb-4">
<div class="d-flex align-items-center">
<i class="bi bi-clock text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark">Processing & Duration</h6>
</div>
<div class="text-end">
<div class="text-uppercase text-secondary fw-bold mb-1" style="font-size: 0.65rem; letter-spacing: 0.5px;">REMAINING DAYS</div>
<span class="badge text-dark fs-6 px-3 py-1 bg-light border">N/A</span>
</div>
</div>
<div class="overflow-auto pe-2" style="max-height: 450px;">
<div class="planning-stepper">
<div class="stepper-item">
<div class="stepper-dot completed" style="background-color: #2ecc71;"></div>
<div class="text-uppercase text-secondary fw-bold mb-1" style="font-size: 0.65rem; letter-spacing: 0.5px;">28TH FEB 2026, 11:58 PM - LUPMIS ENGINE</div>
<h6 class="fw-bold mb-1 text-dark">Submission Received</h6>
<p class="mb-2 small text-secondary">Application successfully uploaded to the Portal by applicant.</p>
<span class="badge bg-light text-secondary border fw-medium px-2 py-1">Verified by: Applicant Portal</span>
</div>
<div class="stepper-item">
<div class="stepper-dot active" style="background-color: #e67e22; box-shadow: none;"></div>
<div class="text-uppercase text-secondary fw-bold mb-1" style="font-size: 0.65rem; letter-spacing: 0.5px;">PENDING PROCESS SCHEDULE - LUPMIS ENGINE</div>
<h6 class="fw-bold mb-1" style="color: #e67e22;">Applicant to Review</h6>
<p class="mb-2 small text-secondary">Applicant asked to review attached documents.</p>
<span class="badge bg-light text-secondary border fw-medium px-2 py-1">Updated by: Luke Eshun</span>
</div>
</div>
</div>
</div>
</div>
<!-- Status & Comments Card -->
<div class="card border border-light-subtle shadow-sm mb-4">
<div class="card-body p-4">
<div class="d-flex align-items-center border-bottom pb-3 mb-4">
<i class="bi bi-chat-dots-fill text-primary fs-5 me-2"></i>
<h6 class="fw-bold mb-0 text-dark">Status & Comments</h6>
</div>
<div class="mb-4">
<button class="btn btn-outline-secondary w-100 fw-bold py-2" type="button" data-bs-toggle="offcanvas" data-bs-target="#historyOffcanvas" aria-controls="historyOffcanvas">
<i class="bi bi-clock-history me-2"></i> View Full History & Comments
</button>
</div>
<div class="mb-3">
<label for="permitCurrentStatus" class="form-label fw-bold text-secondary mb-2" style="font-size: 0.75rem; letter-spacing: 0.5px;">
<i class="bi bi-info-circle text-primary me-1"></i> STATUS UPDATE
</label>
<select class="form-select py-2" id="permitCurrentStatus">
<option value="" selected disabled>Select status ...</option>
<option value="status_zero">Applicant to Review</option>
<option value="status_one">Application Received</option>
<option value="status_two" disabled>Application Accepted</option>
<option value="status_three" disabled>Permit Pending TSC Review</option>
<option value="status_four" disabled>Permit Pending SPC Decision</option>
<option value="status_five" disabled>Permit Approved (In Principle)</option>
<option value="status_six" disabled>Permit Approved</option>
<option value="status_seven" disabled>Permit Refused</option>
<option value="status_eight" disabled>Permit Deferred</option>
</select>
</div>
<div class="mb-4">
<textarea class="form-control commentBody py-2" rows="4" name="comment_body" placeholder="Enter new comment here" style="resize: none; font-style: italic;"></textarea>
<input type="hidden" class="applicationCode" name="application_code" value="{{ $permit_arr['application_code'] ?? '' }}">
</div>
<button class="btn w-100 fw-bold shadow-sm submitPermitCommentBtn py-2 text-white" type="button" style="background-color: #3b82f6;">
<i class="bi bi-send-fill me-2"></i> Submit
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="offcanvas offcanvas-end shadow-lg" tabindex="-1" id="historyOffcanvas" aria-labelledby="historyOffcanvasLabel" style="width: 450px;">
<div class="offcanvas-header border-bottom bg-light sticky-top z-3">
<h5 class="offcanvas-title fw-bold" id="historyOffcanvasLabel">Application History</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<div class="timeline" id="commentsTimeline">
<div class="text-center text-muted mt-5">
<i class="bi bi-arrow-clockwise fs-1"></i>
<p class="mt-2">Loading application history...</p>
</div>
</div>
</div>
</div>
@endsection
@section('page-js')
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<script src="{{ url('public/assets/js/permit_comments.js') }}" type="text/javascript"></script>
<script type="text/javascript">
// 1. Global Functions (Keep OUTSIDE document.ready so buttons can trigger them)
window.generateReportPDF = function() {
console.log("Button clicked. Starting PDF process...");
if (typeof html2pdf === 'undefined') {
$.alert("ERROR: The html2pdf library is not loaded.");
return;
}
const element = document.getElementById('report-pdf-content');
if (!element) {
$.alert("ERROR: Could not find the div with id='report-pdf-content'.");
return;
}
try {
const inputs = element.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
if (input.tagName === 'TEXTAREA') {
input.innerHTML = input.value;
} else if (input.tagName === 'SELECT') {
const selectedOption = input.options[input.selectedIndex];
if (selectedOption) selectedOption.setAttribute('selected', 'selected');
input.style.backgroundImage = 'none';
input.style.appearance = 'none';
} else {
input.setAttribute('value', input.value);
}
});
const opt = {
margin: 0.5,
filename: 'Site_Inspection_Report.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: {
scale: 2,
useCORS: true,
logging: false,
scrollY: 0,
windowY: 0
},
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' },
pagebreak: {
mode: 'css',
avoid: ['.row', 'h6']
}
};
html2pdf().set(opt).from(element).save().then(() => {
console.log("Success: PDF downloaded.");
inputs.forEach(input => {
if (input.tagName === 'SELECT') {
input.style.backgroundImage = '';
input.style.appearance = '';
}
});
}).catch(err => {
console.error("PDF Generation Error:", err);
alert("Failed to build the PDF. Check the browser console.");
});
} catch (error) {
console.error("Critical Execution Error:", error);
alert("A javascript error occurred: " + error.message);
}
};
// 2. DOM Dependencies and Listeners (Keep INSIDE document.ready)
$(document).ready(function(){
// Define all DOM elements first
const MAP_ORIGIN = 'https://pwa.lupmis4luspa.org';
const iframe = document.getElementById('permit-map');
const upnInput = document.getElementById('permitUPN');
const locInput = document.getElementById('permitLocation');
var pdfModal = document.getElementById('pdfModal');
// PDF Modal Logic
if(pdfModal) {
pdfModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget;
var pdfUrl = button.getAttribute('data-pdf-url');
var pdfIframe = document.getElementById('pdfIframe');
if(pdfIframe) pdfIframe.src = pdfUrl;
});
pdfModal.addEventListener('hidden.bs.modal', function () {
var pdfIframe = document.getElementById('pdfIframe');
if(pdfIframe) pdfIframe.src = '';
});
}
// Map Iframe Logic
window.addEventListener('message', function (event) {
if (event.origin !== MAP_ORIGIN) return;
const msg = event.data;
if (!msg || typeof msg !== 'object') return;
switch (msg.type) {
case 'ready':
@if(!empty($permit_arr['upn']))
if(iframe) {
iframe.contentWindow.postMessage(
{ type: 'set:selected', upn: @json($permit_arr['upn']) },
MAP_ORIGIN);
}
@endif
break;
case 'parcel:select':
if(upnInput) upnInput.value = msg.upn || '';
if(locInput) {
locInput.value = (msg.lon != null && msg.lat != null)
? msg.lat.toFixed(6) + ', ' + msg.lon.toFixed(6)
: '';
}
if(upnInput) {
upnInput.dataset.parcelId = msg.parcel_id || '';
upnInput.dataset.zoneCode = msg.zone_code || '';
}
break;
case 'parcel:cleared':
if(upnInput) {
upnInput.value = '';
delete upnInput.dataset.parcelId;
delete upnInput.dataset.zoneCode;
}
if(locInput) locInput.value = '';
break;
case 'error':
console.warn('[permit-map]', msg.code, msg.message);
break;
}
});
// Check Compliance Logic
$('#checkComplianceBtn').on('click', function (e) {
e.preventDefault();
const payload = {
application_code: @json($permit_arr['application_code'] ?? ''),
upn: upnInput ? upnInput.value : '',
parcel_id: upnInput ? (upnInput.dataset.parcelId || null) : null,
coordinates: locInput ? locInput.value : '',
zone_code: upnInput ? (upnInput.dataset.zoneCode || null) : null,
};
$.ajax({
url: '{{ route("permits.checkCompliance") }}',
method: 'POST',
data: payload
}).done(function (res) {
/* render compliance result */
});
});
// Download PDF Button Logic
const downloadBtn = document.getElementById('downloadPdfBtn');
if(downloadBtn) {
downloadBtn.addEventListener('click', function() {
const element = document.getElementById('report-pdf-content');
if(!element) return;
const inputs = element.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
if (input.tagName === 'TEXTAREA') {
input.innerHTML = input.value;
} else if (input.tagName === 'SELECT') {
const selectedOption = input.options[input.selectedIndex];
if (selectedOption) selectedOption.setAttribute('selected', 'selected');
} else {
input.setAttribute('value', input.value);
}
});
const originalText = downloadBtn.innerHTML;
downloadBtn.innerHTML = '<i class="bi bi-hourglass-split"></i> Generating...';
downloadBtn.disabled = true;
const opt = {
margin: 0.5,
filename: 'Site_Inspection_Report.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true, logging: false },
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' }
};
html2pdf().set(opt).from(element).save().then(() => {
downloadBtn.innerHTML = originalText;
downloadBtn.disabled = false;
});
});
}
});
</script>
@endsection

View File

@ -292,55 +292,55 @@
const locInput = document.getElementById('permitLocation');
window.addEventListener('message', function (event) {
if (event.origin !== MAP_ORIGIN) return;
const msg = event.data;
if (!msg || typeof msg !== 'object') return;
if (event.origin !== MAP_ORIGIN) return;
const msg = event.data;
if (!msg || typeof msg !== 'object') return;
switch (msg.type) {
case 'ready':
@if(!empty($permit_arr['upn']))
iframe.contentWindow.postMessage(
{ type: 'set:selected', upn: @json($permit_arr['upn']) },
MAP_ORIGIN);
@endif
break;
switch (msg.type) {
case 'ready':
@if(!empty($permit_arr['upn']))
iframe.contentWindow.postMessage(
{ type: 'set:selected', upn: @json($permit_arr['upn']) },
MAP_ORIGIN);
@endif
break;
case 'parcel:select':
upnInput.value = msg.upn || '';
locInput.value = (msg.lon != null && msg.lat != null)
? msg.lat.toFixed(6) + ', ' + msg.lon.toFixed(6)
: '';
upnInput.dataset.parcelId = msg.parcel_id || '';
upnInput.dataset.zoneCode = msg.zone_code || '';
break;
case 'parcel:select':
upnInput.value = msg.upn || '';
locInput.value = (msg.lon != null && msg.lat != null)
? msg.lat.toFixed(6) + ', ' + msg.lon.toFixed(6)
: '';
upnInput.dataset.parcelId = msg.parcel_id || '';
upnInput.dataset.zoneCode = msg.zone_code || '';
break;
case 'parcel:cleared':
upnInput.value = '';
locInput.value = '';
delete upnInput.dataset.parcelId;
delete upnInput.dataset.zoneCode;
break;
case 'parcel:cleared':
upnInput.value = '';
locInput.value = '';
delete upnInput.dataset.parcelId;
delete upnInput.dataset.zoneCode;
break;
case 'error':
console.warn('[permit-map]', msg.code, msg.message);
break;
}
case 'error':
console.warn('[permit-map]', msg.code, msg.message);
break;
}
$('#checkComplianceBtn').on('click', function (e) {
e.preventDefault();
const payload = {
application_code: @json($permit_arr['application_code']),
upn: upnInput.value,
parcel_id: upnInput.dataset.parcelId || null,
coordinates: locInput.value,
zone_code: upnInput.dataset.zoneCode || null,
};
$.ajax({ url: '{{ route("permits.checkCompliance") }}',
method: 'POST', data: payload })
.done(function (res) { /* render compliance result */ });
});
$('#checkComplianceBtn').on('click', function (e) {
e.preventDefault();
const payload = {
application_code: @json($permit_arr['application_code']),
upn: upnInput.value,
parcel_id: upnInput.dataset.parcelId || null,
coordinates: locInput.value,
zone_code: upnInput.dataset.zoneCode || null,
};
$.ajax({ url: '{{ route("permits.checkCompliance") }}',
method: 'POST', data: payload })
.done(function (res) { /* render compliance result */ });
});
});
document.addEventListener('DOMContentLoaded', function() {
const downloadBtn = document.getElementById('downloadPdfBtn');

View File

@ -90,6 +90,10 @@ Route::middleware([CheckBackendSession::class])->group(function () {
Route::get('/admin/districtparams', [App\Http\Controllers\AdminController::class, 'districtparams']);
Route::get('/admin/feefixing', [App\Http\Controllers\AdminController::class, 'feefixing']);
Route::get('/admin/districtsettings', [App\Http\Controllers\AdminController::class, 'districtsettings']);
Route::get('/admin/getdistrictsettingsjson', [App\Http\Controllers\AdminController::class, 'getDistrictSettingsJson']);
Route::post('/admin/updatedistrictsettings', [App\Http\Controllers\AdminController::class, 'updateDistrictSettings']);
Route::get('/admin/luspaparams', [App\Http\Controllers\AdminController::class, 'luspaparams']);
Route::get('/admin/reports', [App\Http\Controllers\AdminController::class, 'reports']);
Route::get('/admin/districts/{region_id}', [App\Http\Controllers\AdminController::class, 'districts']);