added document vault and bug fixes on profile and staff members controllers

This commit is contained in:
Kwesi Banson Jnr
2026-08-20 19:08:31 +00:00
parent 81d26dc138
commit cc129bccac
7 changed files with 456 additions and 261 deletions

View File

@@ -1,283 +1,259 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Click ERP</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<style>
body { background-color: #f8f9fc; }
/* Sidebar & Topbar (Inherited from Base Design) */
#sidebar { width: 260px; background-color: #111425; position: fixed; height: 100vh; overflow-y: auto; z-index: 1000; }
.sidebar-brand { color: #fff; padding: 1.5rem 1.25rem; font-weight: 700; }
.sidebar-brand-icon { background: #5c4df0; padding: 0.5rem; border-radius: 8px; margin-right: 10px; }
.sidebar-nav-item { color: #8a90a5; text-decoration: none; padding: 0.75rem 1.25rem; display: block; border-radius: 8px; margin: 0.2rem 1rem; font-size: 0.9rem; }
.sidebar-nav-item:hover, .sidebar-nav-item.active { background-color: #5c4df0; color: #fff; }
.sidebar-heading { color: #5a617a; font-size: 0.75rem; text-transform: uppercase; padding: 1rem 1.25rem 0.5rem; font-weight: 600; }
#main-content { margin-left: 260px; min-height: 100vh; }
.topbar { background: #fff; height: 70px; border-bottom: 1px solid #eaedf1; padding: 0 1.5rem; }
/* Cards & Tables */
.content-card { background: #fff; border: 1px solid #eaedf1; border-radius: 12px; box-shadow: 0 2px 4px rgba(0,0,0,0.02); }
.table-custom th { background-color: #f8f9fc; color: #5a617a; font-weight: 600; font-size: 0.8rem; text-transform: uppercase; border-bottom: 2px solid #eaedf1; padding: 1rem; }
.table-custom td { padding: 1rem; vertical-align: middle; font-size: 0.9rem; border-bottom: 1px solid #eaedf1; }
/* Modal Customization */
.modal-content { border-radius: 12px; border: none; box-shadow: 0 10px 30px rgba(0,0,0,0.1); }
.modal-header { border-bottom: 1px solid #eaedf1; background-color: #f8f9fc; border-radius: 12px 12px 0 0; }
.modal-footer { border-top: 1px solid #eaedf1; }
.form-control:focus, .form-select:focus { border-color: #5c4df0; box-shadow: 0 0 0 0.25rem rgba(92, 77, 240, 0.25); }
</style>
</head>
<body>
@extends('layouts.masterbeta')
<aside id="sidebar">
<div class="sidebar-brand d-flex align-items-center">
<span class="sidebar-brand-icon"><i class="bi bi-buildings"></i></span>
<div>
<div class="mb-0 fs-6">NEXUS <span class="badge bg-secondary ms-1" style="font-size: 0.6rem;">ERP</span></div>
</div>
@section('title', 'Click ERP - Document Vault')
@section('breadcrumbs')
<a href="{{ url('/') }}" class="text-secondary text-decoration-none"><i class="bi bi-house me-2"></i></a>
<i class="bi bi-chevron-right text-secondary me-2" style="font-size: 0.8rem;"></i>
<span class="fw-bold" style="font-size: 0.95rem;">Document Vault</span>
@endsection
@section('content')
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h4 class="fw-bold mb-1">Document Vault</h4>
<p class="text-secondary mb-0" style="font-size: 0.9rem;">Store, manage, and share company or staff documents securely.</p>
</div>
<div class="sidebar-heading mt-2">Core Entities</div>
<a href="#" class="sidebar-nav-item"><i class="bi bi-person-badge me-2"></i> Staff Directory</a>
<a href="#" class="sidebar-nav-item"><i class="bi bi-people me-2"></i> Clients</a>
<a href="#" class="sidebar-nav-item"><i class="bi bi-broadcast-pin me-2"></i> MNO Partners</a>
<div class="sidebar-heading mt-2">Resources</div>
<a href="#" class="sidebar-nav-item active"><i class="bi bi-folder2-open me-2"></i> Document Vault</a>
</aside>
<button class="btn text-white fw-bold d-flex align-items-center" style="background-color: #5c4df0; border-radius: 8px;" id="btnOpenUploadModal">
<i class="bi bi-upload me-2"></i> Upload Document
</button>
</div>
<main id="main-content">
<header class="topbar d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<span class="fw-bold" style="font-size: 0.95rem;">Document Vault</span>
</div>
</header>
<div class="container-fluid p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h4 class="fw-bold mb-1">Corporate Documents</h4>
<p class="text-secondary mb-0" style="font-size: 0.9rem;">Manage compliance files, MNO agreements, and client contracts.</p>
<!-- Filters & Search -->
<div class="content-card mb-4 p-3 shadow-sm">
<form action="{{ route('documents.index') }}" method="GET">
<div class="row g-3">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text bg-white border-end-0 text-secondary"><i class="bi bi-search"></i></span>
<input type="text" name="search" value="{{ request('search') }}" class="form-control bg-white border-start-0 ps-0" placeholder="Search by document name or description...">
</div>
</div>
<button class="btn text-white fw-bold d-flex align-items-center" style="background-color: #5c4df0; border-radius: 8px;" id="btnOpenUploadModal">
<i class="bi bi-cloud-upload me-2"></i> Upload Document
</button>
</div>
<div class="content-card">
<div class="table-responsive">
<table class="table table-custom table-hover mb-0">
<thead>
<tr>
<th>File Name</th>
<th>Related Entity</th>
<th>Type</th>
<th>Size</th>
<th>Uploaded</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i class="bi bi-file-earmark-pdf text-danger me-2"></i>
<span class="fw-bold text-dark">MTN_SIP_Trunk_SLA_2026.pdf</span>
</td>
<td><span class="badge bg-dark bg-opacity-10 text-dark">MNO: MTN</span></td>
<td>Contract</td>
<td class="text-secondary">2.4 MB</td>
<td class="text-secondary">Jun 15, 2026</td>
<td class="text-end">
<button class="btn btn-sm btn-light text-primary me-1"><i class="bi bi-download"></i></button>
<button class="btn btn-sm btn-light text-secondary me-1 btn-edit-doc" data-id="1" data-filename="MTN_SIP_Trunk_SLA_2026.pdf" data-entity="mno_mtn" data-type="contract"><i class="bi bi-pencil"></i></button>
<button class="btn btn-sm btn-light text-danger"><i class="bi bi-trash"></i></button>
</td>
</tr>
<tr>
<td>
<i class="bi bi-file-earmark-word text-primary me-2"></i>
<span class="fw-bold text-dark">Telecel_Interconnect_Draft.docx</span>
</td>
<td><span class="badge bg-dark bg-opacity-10 text-dark">MNO: Telecel</span></td>
<td>Draft</td>
<td class="text-secondary">850 KB</td>
<td class="text-secondary">Jun 12, 2026</td>
<td class="text-end">
<button class="btn btn-sm btn-light text-primary me-1"><i class="bi bi-download"></i></button>
<button class="btn btn-sm btn-light text-secondary me-1 btn-edit-doc" data-id="2" data-filename="Telecel_Interconnect_Draft.docx" data-entity="mno_telecel" data-type="draft"><i class="bi bi-pencil"></i></button>
<button class="btn btn-sm btn-light text-danger"><i class="bi bi-trash"></i></button>
</td>
</tr>
</tbody>
</table>
<div class="col-md-4">
<select name="category" class="form-select bg-white" onchange="this.form.submit()">
<option value="">Category: All</option>
@foreach($categories as $cat)
<option value="{{ $cat }}" {{ request('category') == $cat ? 'selected' : '' }}>{{ $cat }}</option>
@endforeach
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn text-white w-100" style="background-color: #5c4df0;">
<i class="bi bi-funnel me-1"></i> Filter
</button>
</div>
</div>
</div>
</main>
</form>
</div>
<div class="modal fade" id="documentModal" tabindex="-1" aria-hidden="true">
<!-- Documents Grid / Table -->
<div class="content-card shadow-sm">
<div class="table-responsive">
<table class="table table-custom table-hover mb-0 align-middle">
<thead>
<tr>
<th>Document Name</th>
<th>Category</th>
<th>Description</th>
<th>Format</th>
<th>Uploaded</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
@forelse($documents as $doc)
<tr>
<td>
<div class="fw-bold text-dark d-flex align-items-center">
<i class="bi bi-file-earmark-text text-primary me-2 fs-5"></i>
{{ $doc->name }}
</div>
</td>
<td>
@if($doc->category)
<span class="badge bg-secondary bg-opacity-10 text-dark border">{{ $doc->category }}</span>
@else
<span class="text-secondary small">Uncategorized</span>
@endif
</td>
<td>
<span class="text-secondary small text-truncate d-inline-block" style="max-width: 250px;" title="{{ $doc->description }}">
{{ $doc->description ?? 'No description provided.' }}
</span>
</td>
<td>
<span class="text-uppercase fw-bold text-secondary" style="font-size: 0.75rem;">{{ $doc->file_extension }}</span>
</td>
<td class="text-secondary small">
<div>{{ $doc->created_at ? $doc->created_at->format('M d, Y') : 'N/A' }}</div>
</td>
<td class="text-end">
<a href="{{ asset('public/storage/' . $doc->filename) }}" target="_blank" class="btn btn-sm btn-light text-primary me-1" title="View / Download">
<i class="bi bi-download"></i>
</a>
{{--
<button class="btn btn-sm btn-light text-danger btn-delete-doc" data-id="{{ $doc->id }}" title="Delete">
<i class="bi bi-trash"></i>
</button>
--}}
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center py-4 text-secondary">No documents found in the Vault.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="p-3 border-top">
{{ $documents->links('pagination::bootstrap-5') }}
</div>
</div>
@endsection
@push('modals')
<!-- UPLOAD DOCUMENT MODAL -->
<div class="modal fade" id="uploadDocModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="documentModalTitle">Upload New Document</h5>
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold">Upload Document</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="documentForm" enctype="multipart/form-data">
<form id="uploadDocForm" enctype="multipart/form-data">
@csrf
<div class="modal-body p-4">
<input type="hidden" id="docId" name="id">
<input type="hidden" id="docMethod" name="_method" value="POST">
<div id="uploadModalAlert"></div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold" style="font-size: 0.85rem;">Select File</label>
<input class="form-control" type="file" id="docFile" name="file">
<div class="form-text" id="fileHelpText">Max size: 10MB. Allowed: PDF, DOCX, PNG, JPG.</div>
<label class="form-label text-secondary fw-semibold small">Document Name *</label>
<input type="text" class="form-control" name="name" required placeholder="e.g. Employee Handbook 2026">
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold" style="font-size: 0.85rem;">Document Type</label>
<select class="form-select" id="docType" name="type" required>
<option value="" selected disabled>Select...</option>
<option value="contract">SLA / Contract</option>
<option value="draft">Draft Proposal</option>
<option value="identity">KYC / Identity</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold" style="font-size: 0.85rem;">Related Entity</label>
<select class="form-select" id="docEntity" name="entity_id">
<option value="">General (None)</option>
<optgroup label="Mobile Network Operators">
<option value="mno_mtn">MTN</option>
<option value="mno_telecel">Telecel</option>
<option value="mno_at">AT</option>
</optgroup>
<optgroup label="Clients">
<option value="client_1">Click Tech Corp</option>
</optgroup>
</select>
</div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Category</label>
<!-- <input type="text" class="form-control" name="category" placeholder="e.g. Policy, Contract, Template"> -->
<select name="client_id" id="modal_client_id" class="form-select select2-field">
<option value="">Category</option>
@foreach($document_types as $type)
<option value="{{ $type }}">{{ $type }}</option>
@endforeach
</select>
</div>
<div class="mb-2">
<label class="form-label text-secondary fw-semibold" style="font-size: 0.85rem;">Description</label>
<textarea class="form-control" id="docNotes" name="notes" rows="2" placeholder="Optional notes..."></textarea>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Description</label>
<textarea class="form-control" name="description" rows="2" placeholder="Brief details about this file..."></textarea>
</div>
<div class="mb-3">
<label class="form-label text-secondary fw-semibold small">Select File * (Max: 10MB)</label>
<input type="file" class="form-control" name="file" required accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.xls,.xlsx">
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn text-white fw-bold px-4" style="background-color: #5c4df0;" id="btnSubmitDoc">
Upload File
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" id="btnSubmitDoc" class="btn text-white btn-sm fw-bold px-4" style="background-color: #5c4df0;">Upload File</button>
</div>
</form>
</div>
</div>
</div>
@endpush
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
@push('scripts')
<script>
$(document).ready(function() {
// Setup CSRF token for all AJAX requests
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
const uploadModal = new bootstrap.Modal(document.getElementById('uploadDocModal'));
const $form = $('#uploadDocForm');
const docModal = new bootstrap.Modal(document.getElementById('documentModal'));
const $form = $('#documentForm');
// --- OPEN MODAL FOR CREATE ---
$('#btnOpenUploadModal').on('click', function() {
$form[0].reset(); // Clear previous data
$('#docId').val('');
$('#docMethod').val('POST');
$('#documentModalTitle').text('Upload New Document');
$('#btnSubmitDoc').html('<i class="bi bi-cloud-upload me-2"></i>Upload File');
$('#docFile').prop('required', true); // File is mandatory for upload
$('#fileHelpText').text('Max size: 10MB. Allowed: PDF, DOCX, PNG, JPG.');
docModal.show();
});
// --- OPEN MODAL FOR EDIT ---
$('.btn-edit-doc').on('click', function() {
const id = $(this).data('id');
const filename = $(this).data('filename');
const entity = $(this).data('entity');
const type = $(this).data('type');
$form[0].reset();
$('#docId').val(id);
$('#docMethod').val('PUT'); // Set to PUT for backend routing
// Populate existing data
$('#docType').val(type);
$('#docEntity').val(entity);
$('#documentModalTitle').text('Edit Document Info');
$('#btnSubmitDoc').html('<i class="bi bi-save me-2"></i>Save Changes');
$('#docFile').prop('required', false); // File is optional when editing
$('#fileHelpText').text('Leave file input blank to keep existing file: ' + filename);
docModal.show();
$('#uploadModalAlert').html('');
uploadModal.show();
});
// --- HANDLE FORM SUBMISSION VIA AJAX ---
// Handle AJAX Upload
$form.on('submit', function(e) {
e.preventDefault();
const $btn = $('#btnSubmitDoc');
const originalText = $btn.html();
$btn.html('<span class="spinner-border spinner-border-sm me-2"></span> Processing...');
$btn.html('<span class="spinner-border spinner-border-sm me-2"></span> Uploading...');
$btn.prop('disabled', true);
// Use FormData to handle the multipart/form-data payload natively
let formData = new FormData(this);
let isEdit = $('#docId').val() !== '';
let ajaxUrl = isEdit ? '/api/documents/' + $('#docId').val() : '/api/documents';
// Simulating the AJAX call
setTimeout(() => {
console.log('Submitted Payload:', Object.fromEntries(formData));
$btn.html(originalText);
$btn.prop('disabled', false);
docModal.hide();
// Here you would normally trigger a toast notification or reload the data table
alert(isEdit ? 'Document metadata updated successfully!' : 'File uploaded successfully!');
}, 1200);
/* // Actual AJAX Call Structure:
$.ajax({
url: ajaxUrl,
type: 'POST', // Always POST for FormData, pass _method=PUT in data for edits
url: "{{ route('documents.store') }}",
type: 'POST',
data: formData,
processData: false, // Prevent jQuery from processing the data
contentType: false, // Prevent jQuery from setting contentType
processData: false,
contentType: false,
success: function(response) {
docModal.hide();
// Refresh DataTables or UI
if (response.success) {
uploadModal.hide();
Swal.fire({
icon: 'success',
title: 'Uploaded!',
text: response.message,
confirmButtonColor: '#5c4df0'
}).then(() => location.reload());
}
},
error: function(xhr) {
let msg = xhr.responseJSON?.message || 'Failed to upload document. Check file size/type.';
$('#uploadModalAlert').html(`
<div class="alert alert-danger py-2 px-3 small" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i> ${msg}
</div>
`);
},
complete: function() {
$btn.html(originalText).prop('disabled', false);
// Handle validation errors
}
});
*/
});
// Handle Delete Document
$('.btn-delete-doc').on('click', function() {
const docId = $(this).data('id');
Swal.fire({
title: 'Delete Document?',
text: "This file will be permanently removed from storage.",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#ef4444',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Yes, delete it!'
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: "{{ url('documents') }}/" + docId,
type: 'POST',
data: {
_token: '{{ csrf_token() }}',
_method: 'DELETE'
},
success: function(response) {
if (response.success) {
Swal.fire({
icon: 'success',
title: 'Deleted!',
text: response.message,
confirmButtonColor: '#5c4df0'
}).then(() => location.reload());
}
}
});
}
});
});
});
</script>
</body>
</html>
@endpush