Updated ERP with new modules and features

This commit is contained in:
Kwesi Banson
2026-09-22 08:06:31 +00:00
parent 71bc103fee
commit 5174e82e51
53 changed files with 3706 additions and 203 deletions

View File

@@ -66,9 +66,10 @@
<div class="col-md-3">
<select name="connection_status" class="form-select bg-white" onchange="this.form.submit()">
<option value="">Any Status</option>
<option value="Active" {{ request('link_type') == 'vpn' ? 'selected' : '' }}>Active</option>
<option value="Inactive" {{ request('link_type') == 'direct' ? 'selected' : '' }}>Inactive</option>
<option value="Pending" {{ request('link_type') == 'direct' ? 'selected' : '' }}>Pending</option>
<option value="Active" {{ request('connection_status') == 'Active' ? 'selected' : '' }}>Active</option>
<option value="Inactive" {{ request('connection_status') == 'Inactive' ? 'selected' : '' }}>Inactive</option>
<option value="Pending" {{ request('connection_status') == 'Pending' ? 'selected' : '' }}>Pending</option>
<option value="Testing" {{ request('connection_status') == 'Testing' ? 'selected' : '' }}>Testing</option>
</select>
</div>
</form>

View File

@@ -0,0 +1,258 @@
<!-- Add Connection Modal -->
<!-- Add Connection Modal -->
<div class="modal fade" id="addConnectionModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold">Add Interconnect Detail</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- Added ID to the form -->
<form id="addConnectionForm" action="{{ route('mnos.connections.store', $operator->id) }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="modal-body p-4">
<!-- Alert container for AJAX errors -->
<div id="connectionModalAlert"></div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Connection Mode *</label>
<select name="connection_mode" id="connectionModeSelect" class="form-select" required>
<option value="Whitelisted IP">Whitelisted IP Address</option>
<option value="Domain">Domain / Hostname</option>
<option value="VPN Tunnel">VPN Tunnel Endpoint</option>
</select>
</div>
<div class="mb-3" id="vpnFormContainer" style="display: none;">
<label class="form-label text-secondary fw-semibold small">Upload VPN Form (PDF, DOCX)</label>
<input type="file" name="vpn_file" id="vpnFileInput" class="form-control" accept=".pdf,.doc,.docx,.xlsx,.jpg,.png">
</div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Identifier (IP / Domain / VPN Peer) *</label>
<input type="text" name="identifier" class="form-control" required placeholder="e.g. 196.200.15.22 or smpp.operator.com">
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Port</label>
<!-- Added type="number", min, and max -->
<input type="number" name="port" class="form-control" placeholder="e.g. 2775" min="1" max="65535">
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Status</label>
<select name="status" class="form-select">
<option value="Active">Active</option>
<option value="Testing">Testing</option>
<option value="Disabled">Disabled</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Notes / Description</label>
<textarea name="notes" class="form-control" rows="2" placeholder="Optional context or routing details..."></textarea>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" id="btnSubmitConnection" class="btn text-white btn-sm fw-bold px-4" style="background-color: #5c4df0;">Save Endpoint</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Document Modal -->
<div class="modal fade" id="addDocumentModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold">Upload Documents</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="addDocumentForm" action="{{ route('mnos.documents.store', $operator->id) }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="modal-body p-4">
<div id="documentModalAlert"></div>
<div id="documentRowsContainer">
<!-- Initial Row -->
<div class="row g-2 mb-3 document-row">
<div class="col-md-5">
<label class="form-label text-secondary fw-semibold small">Document Name *</label>
<input type="text" name="document_names[]" class="form-control" placeholder="e.g. Signed NDA" required>
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">File *</label>
<input type="file" name="documents[]" class="form-control" required>
</div>
<div class="col-md-1 d-flex align-items-end">
<button type="button" class="btn btn-outline-danger w-100 remove-doc-row" disabled>
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
<button type="button" id="addDocRowBtn" class="btn btn-sm btn-light text-primary border fw-bold mt-2">
<i class="bi bi-plus-lg me-1"></i> Add Another File
</button>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" id="btnSubmitDocuments" class="btn text-white btn-sm fw-bold px-4" style="background-color: #5c4df0;">Upload All</button>
</div>
</form>
</div>
</div>
</div>
<!-- EDIT MNO MODAL -->
<div class="modal fade" id="editOperatorModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-light border-bottom-0">
<h5 class="modal-title fw-bold">Edit MNO Partner</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="editOperatorForm" novalidate>
@csrf
@method('PUT')
<input type="hidden" id="edit_mno_id">
<div class="modal-body p-0">
<ul class="nav nav-tabs px-4 pt-3 bg-light" role="tablist">
<li class="nav-item"><button class="nav-link active fw-bold" data-bs-toggle="tab" data-bs-target="#edit-tab-basic" type="button">Basic & Tech</button></li>
<li class="nav-item"><button class="nav-link fw-bold text-dark" data-bs-toggle="tab" data-bs-target="#edit-tab-contacts" type="button">Contacts</button></li>
<li class="nav-item"><button class="nav-link fw-bold text-dark" data-bs-toggle="tab" data-bs-target="#edit-tab-billing" type="button">Billing</button></li>
</ul>
<div class="tab-content p-4">
<!-- TAB 1: BASIC INFO -->
<div class="tab-pane fade show active" id="edit-tab-basic">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Name *</label>
<input type="text" class="form-control" name="name" id="edit_name" required>
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Country *</label>
<input type="text" class="form-control" name="country" id="edit_country" required>
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Connection Status</label>
<select class="form-select" name="connection_status" id="edit_connection_status">
<option value="Active">Active</option>
<option value="Inactive">Inactive</option>
<option value="Testing">Testing</option>
<option value="Pending">Pending</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Connection Type</label>
<select class="form-select" name="connection_type" id="edit_connection_type">
<option value="DIRECT">Direct</option>
<option value="VPN">VPN</option>
</select>
</div>
</div>
</div>
<!-- TAB 2: CONTACTS -->
<div class="tab-pane fade" id="edit-tab-contacts">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Account Manager</label>
<select class="form-select" name="mno_account_manager" id="edit_mno_account_manager">
<option value="">Select Account Manager</option>
@foreach($staffMembers as $staff)
<option value="{{ $staff->name }}">{{ $staff->name }}</option>
@endforeach
</select>
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Technical Support Lead</label>
<input type="text" class="form-control" name="technical_support_person" id="edit_technical_support_person">
</div>
<div class="col-md-4">
<label class="form-label text-secondary fw-semibold small">Contact Person</label>
<input type="text" class="form-control" name="contact_person" id="edit_contact_person">
</div>
<div class="col-md-4">
<label class="form-label text-secondary fw-semibold small">Contact Phone</label>
<input type="text" class="form-control" name="contact_person_phone" id="edit_contact_person_phone">
</div>
<div class="col-md-4">
<label class="form-label text-secondary fw-semibold small">Contact Email</label>
<input type="email" class="form-control" name="contact_person_email" id="edit_contact_person_email">
</div>
<div class="col-12">
<label class="form-label text-secondary fw-semibold small">Support MS Teams / Skype</label>
<input type="text" class="form-control" name="support_skype" id="edit_support_skype">
</div>
</div>
</div>
<!-- TAB 3: BILLING -->
<div class="tab-pane fade" id="edit-tab-billing">
<div class="row g-3">
<div class="col-12 mt-3 mb-2 border-bottom pb-2">
<h6 class="fw-bold text-dark mb-0"><i class="bi bi-file-earmark-text me-2"></i>Contract Details</h6>
</div>
<div class="col-md-4">
<label class="form-label text-secondary fw-semibold small">Contract Type</label>
<select class="form-select" name="contract_type" id="edit_contract_type">
<option value="">Select Type...</option>
<option value="Bilateral">Bilateral</option>
<option value="Unilateral">Unilateral</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label text-secondary fw-semibold small">Contract Validity</label>
<select class="form-select" name="contract_validity" id="edit_contract_validity">
<option value="">Select Validity...</option>
<option value="Quarterly">Quarterly</option>
<option value="Biannual">Biannual</option>
<option value="Yearly">Yearly</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label text-secondary fw-semibold small">Auto Renew?</label>
<select class="form-select" name="contract_auto_renew" id="edit_contract_auto_renew">
<option value="1">Yes</option>
<option value="0">No</option>
</select>
</div>
<div class="col-md-4" id="renewal_date_container" style="display: none;">
<label class="form-label text-secondary fw-semibold small">Next Renewal Date *</label>
<input type="date" class="form-control" name="next_renewal_date" id="edit_next_renewal_date">
</div>
<div class="col-md-4">
<label class="form-label text-secondary fw-semibold small">Buying Rate</label>
<input type="number" step="0.0001" class="form-control" name="buying_rate" id="edit_buying_rate">
</div>
<div class="col-md-4">
<label class="form-label text-secondary fw-semibold small">Rate Type</label>
<input type="text" class="form-control" name="rate_type" id="edit_rate_type" placeholder="e.g. Flat, Tiered">
</div>
<div class="col-md-4">
<label class="form-label text-secondary fw-semibold small">Payment Terms</label>
<input type="text" class="form-control" name="payment_terms" id="edit_payment_terms" placeholder="">
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer bg-light border-top-0">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" id="btnUpdateOperator" class="btn text-white btn-sm fw-bold px-4" style="background-color: #5c4df0;">
Update Partner
</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -21,13 +21,16 @@
<h3 class="fw-bold mb-1">{{ $operator->name }}</h3>
<p class="text-secondary mb-0" style="font-size: 0.9rem;">
<i class="bi bi-geo-alt me-1"></i> {{ $operator->country }} &bull;
<span class="badge {{ $operator->connection_status == 'Online' ? 'bg-success' : 'bg-danger' }} ms-1">
{{ $operator->connection_status ?? 'Offline' }}
<span class="badge {{ $operator->connection_status == 'Active' ? 'bg-success' : 'bg-danger' }} ms-1">
{{ $operator->connection_status ?? 'Inactive' }}
</span>
</p>
</div>
</div>
<div>
<button class="btn btn-primary btn-sm fw-bold btn-edit-operator" data-id="{{ $operator->id }}">
<i class="bi bi-pencil me-1"></i> Edit MNO
</button>
<a href="{{ route('mnos.index') }}" class="btn btn-outline-secondary btn-sm fw-bold">
<i class="bi bi-arrow-left me-1"></i> Back to List
</a>
@@ -66,6 +69,7 @@
</h5>
<hr class="text-muted opacity-25">
<div class="row g-3">
<div class="col-md-6">
<span class="text-muted small d-block">Operating Country</span>
<strong class="text-dark">{{ $operator->country }}</strong>
@@ -84,6 +88,36 @@
</div>
</div>
</div>
<!-- Contract Details -->
<div class="content-card p-4 mb-4">
<h5 class="fw-bold mb-3 text-secondary" style="font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.5px;">
<i class="bi bi-file-earmark-text me-2 text-primary"></i> Contract Details
</h5>
<hr class="text-muted opacity-25">
<div class="row g-3">
<div class="col-md-4">
<span class="text-muted small d-block">Contract Type</span>
<strong class="text-dark">{{ $operator->contract_type ?? 'N/A' }}</strong>
</div>
<div class="col-md-4">
<span class="text-muted small d-block">Contract Validity</span>
<strong class="text-dark">{{ $operator->contract_validity ?? 'N/A' }}</strong>
</div>
<div class="col-md-4">
<span class="text-muted small d-block">Auto Renew</span>
@if(is_null($operator->contract_auto_renew))
<strong class="text-dark">N/A</strong>
@elseif($operator->contract_auto_renew)
<span class="badge bg-success bg-opacity-10 text-success border border-success">Yes</span>
@else
<span class="badge bg-danger bg-opacity-10 text-danger border border-danger mb-1">No</span>
<div class="text-muted" style="font-size: 0.75rem;">
Renewal Date: <strong class="text-dark">{{ $operator->next_renewal_date ? $operator->next_renewal_date->format('d M Y') : 'Not Set' }}</strong>
</div>
@endif
</div>
</div>
</div>
<!-- Provisioned Services Badges -->
<div class="content-card p-4">
@@ -220,182 +254,70 @@
</table>
</div>
</div>
<!-- Documents Section -->
<div class="content-card p-4 mb-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="fw-bold mb-0 text-secondary" style="font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.5px;">
<i class="bi bi-folder me-2 text-primary"></i> Operator Documents
</h5>
<button type="button" class="btn btn-sm text-white fw-bold" style="background-color: #5c4df0;" data-bs-toggle="modal" data-bs-target="#addDocumentModal">
<i class="bi bi-cloud-upload me-1"></i> Upload Documents
</button>
</div>
<hr class="text-muted opacity-25">
<div class="table-responsive">
<table class="table table-custom align-middle mb-0">
<thead>
<tr>
<th>Document Name</th>
<th>File Type</th>
<th>Uploaded On</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
@forelse($operator->documents as $doc)
<tr>
<td class="fw-bold">{{ $doc->name }}</td>
<td><span class="badge bg-light text-dark border">{{ strtoupper(pathinfo($doc->file_path, PATHINFO_EXTENSION)) }}</span></td>
<td>{{ $doc->created_at->format('d M Y') }}</td>
<td class="text-end">
<a href="{{ asset('storage/' . $doc->file_path) }}" target="_blank" class="btn btn-sm btn-light text-primary me-2">
<i class="bi bi-eye"></i> View
</a>
<form action="{{ route('mnos.documents.destroy', $doc->id) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-light text-danger" onclick="return confirm('Delete this document?')">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center text-secondary py-4">No documents uploaded yet.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('modals')
<!-- Add Connection Modal -->
<!-- Add Connection Modal -->
<div class="modal fade" id="addConnectionModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold">Add Interconnect Detail</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- Added ID to the form -->
<form id="addConnectionForm" action="{{ route('mnos.connections.store', $operator->id) }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="modal-body p-4">
<!-- Alert container for AJAX errors -->
<div id="connectionModalAlert"></div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Connection Mode *</label>
<select name="connection_mode" id="connectionModeSelect" class="form-select" required>
<option value="Whitelisted IP">Whitelisted IP Address</option>
<option value="Domain">Domain / Hostname</option>
<option value="VPN Tunnel">VPN Tunnel Endpoint</option>
</select>
</div>
<div class="mb-3" id="vpnFormContainer" style="display: none;">
<label class="form-label text-secondary fw-semibold small">Upload VPN Form (PDF, DOCX)</label>
<input type="file" name="vpn_file" id="vpnFileInput" class="form-control" accept=".pdf,.doc,.docx,.xlsx,.jpg,.png">
</div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Identifier (IP / Domain / VPN Peer) *</label>
<input type="text" name="identifier" class="form-control" required placeholder="e.g. 196.200.15.22 or smpp.operator.com">
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Port</label>
<!-- Added type="number", min, and max -->
<input type="number" name="port" class="form-control" placeholder="e.g. 2775" min="1" max="65535">
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Status</label>
<select name="status" class="form-select">
<option value="Active">Active</option>
<option value="Testing">Testing</option>
<option value="Disabled">Disabled</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Notes / Description</label>
<textarea name="notes" class="form-control" rows="2" placeholder="Optional context or routing details..."></textarea>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" id="btnSubmitConnection" class="btn text-white btn-sm fw-bold px-4" style="background-color: #5c4df0;">Save Endpoint</button>
</div>
</form>
</div>
</div>
</div>
@include('network_operators.partials.modals')
@endpush
@push('scripts')
<script>
$(document).ready(function() {
console.log('foo bar')
$('#connectionModeSelect').on('change', function() {
if ($(this).val() === 'VPN Tunnel') {
$('#vpnFormContainer').slideDown();
} else {
$('#vpnFormContainer').slideUp();
$('#vpnFileInput').val(''); // Clear the file input if they switch away
}
});
});
$(document).ready(function() {
// Toggle VPN File input visibility
$('#connectionModeSelect').on('change', function() {
if ($(this).val() === 'VPN Tunnel') {
$('#vpnFormContainer').slideDown();
} else {
$('#vpnFormContainer').slideUp();
$('#vpnFileInput').val('');
}
});
// Handle AJAX Form Submission
$('#addConnectionForm').on('submit', function(e) {
e.preventDefault();
let $form = $(this);
let $submitBtn = $('#btnSubmitConnection');
let originalText = $submitBtn.html();
let $alertBox = $('#connectionModalAlert');
// Use FormData to handle the file upload payload
let formData = new FormData(this);
$submitBtn.html('<span class="spinner-border spinner-border-sm me-2"></span>Saving...').prop('disabled', true);
$alertBox.html('');
$form.find('.is-invalid').removeClass('is-invalid');
$form.find('.invalid-feedback').remove();
$.ajax({
url: $form.attr('action'),
type: 'POST',
data: formData,
processData: false, // Prevent jQuery from converting the data into a query string
contentType: false, // Prevent jQuery from overriding the multipart header
success: function(response) {
if (response.success) {
$('#addConnectionModal').modal('hide');
Swal.fire({
icon: 'success',
title: 'Success!',
text: response.message,
confirmButtonColor: '#5c4df0'
}).then(() => {
// Reload the page to reflect the new connection in the table
location.reload();
});
}
},
error: function(xhr) {
if (xhr.status === 422) {
let errors = xhr.responseJSON.errors;
$alertBox.html(`
<div class="alert alert-danger d-flex align-items-center py-2 px-3 small" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<div>Please check the highlighted fields below.</div>
</div>
`);
// Highlight specific fields
$.each(errors, function(key, value) {
let $input = $form.find('[name="' + key + '"]');
if ($input.length) {
$input.addClass('is-invalid');
$input.parent().append('<div class="invalid-feedback d-block fw-medium small">' + value[0] + '</div>');
}
});
} else {
$alertBox.html(`
<div class="alert alert-danger d-flex align-items-center py-2 px-3 small" role="alert">
<i class="bi bi-x-circle-fill me-2"></i>
<div>An error occurred. Please try again.</div>
</div>
`);
}
},
complete: function() {
$submitBtn.html(originalText).prop('disabled', false);
}
});
});
// Reset the form when the modal is closed
$('#addConnectionModal').on('hidden.bs.modal', function () {
$('#addConnectionForm')[0].reset();
$('#vpnFormContainer').hide();
$('#connectionModalAlert').html('');
$('#addConnectionForm').find('.is-invalid').removeClass('is-invalid');
$('#addConnectionForm').find('.invalid-feedback').remove();
});
});
const baseUrl = "{{ url('/') }}";
const operatorId = "{{ $operator->id }}";
</script>
<script src="{{ asset('public/assets/js/mno-show.js') }}"></script>
@endpush