615 lines
25 KiB
PHP
615 lines
25 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('content')
|
|
<div class="container py-4">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h3 class="page-title m-0">
|
|
<i class="fas fa-list-alt me-2 text-danger"></i>All Users
|
|
</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Filter Controls -->
|
|
<div class="row mb-4 align-items-center">
|
|
<div class="col-md-4">
|
|
<div class="input-group">
|
|
<span class="input-group-text bg-light">Department</span>
|
|
<select id="department-filter" class="form-select">
|
|
<option value="">All Departments</option>
|
|
@foreach($departments as $department)
|
|
<option value="{{ $department->id }}">{{ $department->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<div class="input-group">
|
|
<span class="input-group-text bg-light">Role</span>
|
|
<select id="role-filter" class="form-select">
|
|
<option value="">All Roles</option>
|
|
<!-- Category options would be populated dynamically -->
|
|
@foreach($roles as $role)
|
|
<option value="{{ $role->id }}">{{ $role->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4 d-flex justify-content-end gap-2">
|
|
<!-- Add User Button -->
|
|
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
|
<i class="fas fa-plus me-1"></i> Add User
|
|
</button>
|
|
|
|
<!-- Column Selector -->
|
|
<div id="columnsSelector" class="">
|
|
@php
|
|
use Illuminate\Support\Str;
|
|
$labels = [
|
|
'ID',
|
|
'Name',
|
|
'Email',
|
|
'Department Name',
|
|
'Orcid Id',
|
|
'Scopus Id',
|
|
'Mobile No',
|
|
'Extension',
|
|
'Role Name',
|
|
'Actions',
|
|
];
|
|
$columns = [];
|
|
foreach ($labels as $i => $label) {
|
|
$columns[] = [
|
|
'label' => $label,
|
|
'id' => 'column-' . Str::slug($label, '-'),
|
|
'value' => $i, // 0-based index
|
|
'checked' => true,
|
|
];
|
|
}
|
|
@endphp
|
|
<x-column-selector :columns="$columns" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Table -->
|
|
<div class="table-responsive">
|
|
<table id="responses-table" class="table table-striped table-hover"> <thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Name</th>
|
|
<th>Email</th>
|
|
<th>Department Name</th>
|
|
<th>Orcid Id</th>
|
|
<th>Scopus Id</th>
|
|
<th>Mobile No</th>
|
|
<th>Extension</th>
|
|
<th>Role Name</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- Data will be loaded via AJAX -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add User Modal -->
|
|
<div class="modal fade" id="addUserModal" tabindex="-1" aria-labelledby="addUserModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-danger text-white">
|
|
<h5 class="modal-title" id="addUserModalLabel">Add New User</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form id="addUserForm">
|
|
<div class="modal-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label for="name" class="form-label">Name <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" id="name" name="name" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="email" class="form-label">Email <span class="text-danger">*</span></label>
|
|
<input type="email" class="form-control" id="email" name="email" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="password" class="form-label">Password <span class="text-danger">*</span></label>
|
|
<input type="password" class="form-control" id="password" name="password" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="role_id" class="form-label">Role <span class="text-danger">*</span></label>
|
|
<select class="form-select" id="role_id" name="role_id" required>
|
|
<option value="" selected disabled>Select Role</option>
|
|
<option value="1">Admin</option>
|
|
<option value="2">HOD</option>
|
|
<option value="3">Faculty</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="department_id" class="form-label">Department</label>
|
|
<select class="form-select" id="department_id" name="department_id">
|
|
<option value="" selected disabled>Select Department</option>
|
|
@foreach($departments as $department)
|
|
<option value="{{ $department->id }}">{{ $department->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="mobile_no" class="form-label">Mobile No</label>
|
|
<input type="text" class="form-control" id="mobile_no" name="mobile_no">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="extension" class="form-label">Extension</label>
|
|
<input type="text" class="form-control" id="extension" name="extension">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="orcid_id" class="form-label">ORCID ID</label>
|
|
<input type="text" class="form-control" id="orcid_id" name="orcid_id">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="scopus_id" class="form-label">Scopus ID</label>
|
|
<input type="text" class="form-control" id="scopus_id" name="scopus_id">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
<button type="submit" class="btn btn-danger">Save User</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@endsection
|
|
|
|
@section('scripts')
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/js/all.min.js" integrity="sha512-Tn2m0TIpgVyTzzvmxLNuqbSJH3JP8jm+Cy3hvHrW7ndTDcJ1w5mBiksqDBb8GpE2ksktFvDB/ykZ0mDpsZj20w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
<script>
|
|
const downloadProofsRoute = "{{ route('admin.downloadProofs') }}";
|
|
const currentModel = "{{ isset($model) ? $model : 'ActivitiesAttended' }}";
|
|
const csrf_token = "{{ csrf_token() }}";
|
|
$(document).ready(function() {
|
|
|
|
const sheetName = "User Data"; // Default sheet name
|
|
let table; // Declare table variable in the outer scope
|
|
|
|
function exportOptions() {
|
|
return {
|
|
columns: ':visible',
|
|
format: {
|
|
body: function(data, row, column, node) {
|
|
if ($(node).find('select').length) {
|
|
return $(node).find("select option:selected").text();
|
|
}
|
|
return $(node).text();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// Function to toggle column visibility - FIXED to check if responsive is initialized
|
|
function toggleColumnVisibility() {
|
|
$('.column-checkbox').each(function() {
|
|
const columnIndex = $(this).val();
|
|
const isChecked = $(this).is(':checked');
|
|
|
|
// Show or hide the column based on checkbox state
|
|
if (table) {
|
|
table.column(columnIndex).visible(isChecked);
|
|
}
|
|
});
|
|
|
|
// Adjust table layout only if table and responsive are initialized
|
|
if (table && table.responsive) {
|
|
table.columns.adjust().responsive.recalc();
|
|
} else if (table) {
|
|
table.columns.adjust();
|
|
}
|
|
}
|
|
|
|
var initAjaxRoute = function(route) {
|
|
// If table already exists, destroy it before re-initializing
|
|
if ($.fn.DataTable.isDataTable('#responses-table')) {
|
|
$('#responses-table').DataTable().destroy();
|
|
}
|
|
|
|
table = $("#responses-table").DataTable({
|
|
processing: true,
|
|
serverSide: true,
|
|
responsive: true,
|
|
ajax: {
|
|
url: route,
|
|
data: function(d) { d.department = $('#department-filter').val();
|
|
d.role = $('#role-filter').val();
|
|
}
|
|
}, columns: [
|
|
{ data: 'id', name: 'id', searchable: false },
|
|
{ data: 'name', name: 'name' },
|
|
{ data: 'email', name: 'email' },
|
|
{ data: 'department_name', name: 'department_name' },
|
|
{ data: 'orcid_id', name: 'orcid_id' },
|
|
{ data: 'scopus_id', name: 'scopus_id', searchable: false },
|
|
{ data: 'mobile_no', name: 'mobile_no' },
|
|
{ data: 'extension', name: 'extension' },
|
|
{ data: 'role_name', name: 'role_name' },
|
|
{
|
|
data: 'action',
|
|
name: 'action',
|
|
orderable: false,
|
|
searchable: false
|
|
}
|
|
],
|
|
columnDefs: [
|
|
{
|
|
targets: '_all',
|
|
className: 'text-center wrap-text'
|
|
},
|
|
{
|
|
targets: 9,
|
|
render: function(data, type, row) {
|
|
return '<div class="btn-group" role="group">' +
|
|
data +
|
|
'</div>';
|
|
}
|
|
}
|
|
],
|
|
dom: '<"d-flex justify-content-between align-items-center mb-3"<"d-flex align-items-center"l><"d-flex"f<"ms-2"B>>>rtip',
|
|
buttons: [{
|
|
extend: 'copy',
|
|
text: '<i class="fas fa-copy me-1"></i> Copy',
|
|
className: 'btn btn-sm btn-outline-white',
|
|
title: sheetName,
|
|
exportOptions: exportOptions()
|
|
},
|
|
{
|
|
extend: 'csv',
|
|
text: '<i class="fas fa-file-csv me-1"></i> CSV',
|
|
className: 'btn btn-sm btn-outline-white',
|
|
title: sheetName,
|
|
exportOptions: exportOptions()
|
|
},
|
|
{
|
|
extend: 'excel',
|
|
text: '<i class="fas fa-file-excel me-1"></i> Excel',
|
|
className: 'btn btn-sm btn-outline-white',
|
|
title: sheetName,
|
|
exportOptions: exportOptions()
|
|
},
|
|
{
|
|
extend: 'pdf',
|
|
text: '<i class="fas fa-file-pdf me-1"></i> PDF',
|
|
className: 'btn btn-sm btn-outline-white',
|
|
title: sheetName,
|
|
exportOptions: exportOptions()
|
|
},
|
|
{
|
|
extend: 'print',
|
|
text: '<i class="fas fa-print me-1"></i> Print',
|
|
className: 'btn btn-sm btn-outline-white',
|
|
title: sheetName,
|
|
exportOptions: exportOptions()
|
|
}
|
|
],
|
|
language: {
|
|
search: "<i class='fas fa-search'></i> _INPUT_",
|
|
searchPlaceholder: "Search records...",
|
|
lengthMenu: "<i class='fas fa-list me-1'></i> _MENU_ records per page",
|
|
info: "Showing _START_ to _END_ of _TOTAL_ entries",
|
|
paginate: {
|
|
first: "<i class='fas fa-angle-double-left'></i>",
|
|
last: "<i class='fas fa-angle-double-right'></i>",
|
|
next: "<i class='fas fa-angle-right'></i>",
|
|
previous: "<i class='fas fa-angle-left'></i>"
|
|
}
|
|
},
|
|
// Important: Initialize the column visibility after table is drawn
|
|
initComplete: function() {
|
|
// Now it's safe to set column visibility
|
|
toggleColumnVisibility();
|
|
},
|
|
});
|
|
|
|
// Apply filters when they change
|
|
$('#department-filter, #role-filter').change(function() {
|
|
table.ajax.reload();
|
|
});
|
|
|
|
return table;
|
|
};
|
|
|
|
// Delete button handler
|
|
$('#responses-table').on('click', '.delete-btn', function() {
|
|
if (confirm('Are you sure you want to delete this record?')) {
|
|
const id = $(this).data('id');
|
|
const url = $(this).data('url');
|
|
|
|
$.ajax({
|
|
url: url,
|
|
type: 'DELETE',
|
|
data: {
|
|
"_token": "{{ csrf_token() }}"
|
|
},
|
|
success: function(result) {
|
|
table.ajax.reload();
|
|
showToast('Record deleted successfully', 'success');
|
|
},
|
|
error: function(error) {
|
|
console.error(error);
|
|
showToast('Error deleting record', 'danger');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Handle inline role change
|
|
$('#responses-table').on('change', '.user-role-dropdown', function() {
|
|
const userId = $(this).data('user-id');
|
|
const newRoleId = $(this).val();
|
|
$.ajax({
|
|
url: `/users/${userId}`,
|
|
type: 'PATCH',
|
|
data: {
|
|
_token: csrf_token,
|
|
role_id: newRoleId
|
|
},
|
|
success: function() {
|
|
table.ajax.reload(null, false);
|
|
showToast('Role updated successfully', 'success');
|
|
},
|
|
error: function() {
|
|
showToast('Error updating role', 'danger');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Toast notification function
|
|
function showToast(message, type) {
|
|
const toastContainer = document.createElement('div');
|
|
toastContainer.className = 'position-fixed bottom-0 start-0 p-3';
|
|
toastContainer.style.zIndex = '1050';
|
|
|
|
const toastEl = document.createElement('div');
|
|
toastEl.className = `toast align-items-center text-white bg-${type} border-0`;
|
|
toastEl.setAttribute('role', 'alert');
|
|
toastEl.setAttribute('aria-live', 'assertive');
|
|
toastEl.setAttribute('aria-atomic', 'true');
|
|
|
|
toastEl.innerHTML = `
|
|
<div class="d-flex">
|
|
<div class="toast-body">
|
|
${message}
|
|
</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
</div>
|
|
`;
|
|
|
|
toastContainer.appendChild(toastEl);
|
|
document.body.appendChild(toastContainer);
|
|
|
|
const toast = new bootstrap.Toast(toastEl, {
|
|
autohide: true,
|
|
delay: 3000
|
|
});
|
|
|
|
toast.show();
|
|
|
|
toastEl.addEventListener('hidden.bs.toast', function() {
|
|
document.body.removeChild(toastContainer);
|
|
});
|
|
}
|
|
|
|
// Set the appropriate data route based on user role
|
|
const userRole = "{{ auth()->user()->role->name }}";
|
|
let dataRoute = "{{ route('admin.users.data') }}";
|
|
|
|
|
|
// Initialize the data table
|
|
table = initAjaxRoute(dataRoute);
|
|
|
|
// Attach change event listener to column visibility checkboxes AFTER table is initialized
|
|
$('.column-checkbox').on('change', function() {
|
|
toggleColumnVisibility();
|
|
});
|
|
|
|
// Ensure "Actions" column is always visible
|
|
$('#column-actions').prop('disabled', true);
|
|
|
|
|
|
// Set department filter from query string if present
|
|
$(document).ready(function() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const departmentId = urlParams.get('department_id');
|
|
if (departmentId) {
|
|
$('#department-filter').val(departmentId).trigger('change');
|
|
}
|
|
});
|
|
|
|
// Handle Add User Form Submission
|
|
$('#addUserForm').on('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
// Clear previous errors
|
|
$('.is-invalid').removeClass('is-invalid');
|
|
$('.invalid-feedback').remove();
|
|
|
|
const formData = new FormData(this);
|
|
formData.append('_token', csrf_token);
|
|
|
|
$.ajax({
|
|
url: "{{ route('users.store') }}",
|
|
type: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
success: function(response) {
|
|
$('#addUserModal').modal('hide');
|
|
$('#addUserForm')[0].reset();
|
|
table.ajax.reload();
|
|
showToast('User added successfully', 'success');
|
|
},
|
|
error: function(xhr) {
|
|
if (xhr.status === 422) {
|
|
const errors = xhr.responseJSON.errors;
|
|
Object.keys(errors).forEach(function(key) {
|
|
const input = $(`#${key}`);
|
|
input.addClass('is-invalid');
|
|
input.after(`<div class="invalid-feedback">${errors[key][0]}</div>`);
|
|
});
|
|
} else {
|
|
showToast('Error adding user', 'danger');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
<style>
|
|
.form-check-input:checked {
|
|
background-color: #b7202e;
|
|
border-color: #ed1c24;
|
|
}
|
|
|
|
|
|
.dropdown-menu {
|
|
background-color: #fff;
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:checked {
|
|
background-color: #b7202e;
|
|
border-color: #ed1c24;
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:focus {
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:checked:focus {
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:focus-visible {
|
|
outline: none;
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:checked:focus-visible {
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:focus-visible:checked {
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:focus-visible:not(:checked) {
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:focus-visible:not(:checked):checked {
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:focus-visible:not(:checked):not(:checked) {
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:focus-visible:not(:checked):not(:checked):checked {
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:focus-visible:not(:checked):not(:checked):not(:checked) {
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:focus-visible:not(:checked):not(:checked):not(:checked):checked {
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.dropdown-menu .form-check-input:focus-visible:not(:checked):not(:checked):not(:checked):not(:checked) {
|
|
box-shadow: 0 0 0 0.2rem rgba(189, 72, 46, 0.25);
|
|
}
|
|
|
|
.form-switch .form-check-input {
|
|
width: 2.5em;
|
|
margin-left: -2.8em;
|
|
position: relative;
|
|
}
|
|
|
|
.form-switch .form-check-input:focus {
|
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");
|
|
}
|
|
|
|
.form-switch .form-check-input:checked {
|
|
background-position: right center;
|
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
|
|
transition: background-position 0.15s ease-in-out;
|
|
}
|
|
|
|
/* Enhance the toggle appearance */
|
|
.form-switch .form-check-input {
|
|
background-size: contain;
|
|
transition: 0.2s;
|
|
}
|
|
|
|
.dropdown-menu {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* Ensure the table container is responsive */
|
|
.table-responsive {
|
|
overflow-x: auto;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Ensure the table fits within the container */
|
|
#responses-table {
|
|
width: 100% !important;
|
|
}
|
|
|
|
|
|
.select-checkbox {
|
|
width: 30px;
|
|
text-align: center;
|
|
}
|
|
|
|
.select-checkbox input[type="checkbox"] {
|
|
width: 18px;
|
|
height: 18px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.select-checkbox input[type="checkbox"]:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* Style the Download button */
|
|
#download-proofs {
|
|
background-color: #28a745;
|
|
border-color: #28a745;
|
|
}
|
|
|
|
#download-proofs:hover:not(:disabled) {
|
|
background-color: #218838;
|
|
border-color: #218838;
|
|
}
|
|
|
|
#download-proofs:disabled {
|
|
opacity: 0.65;
|
|
cursor: not-allowed;
|
|
}
|
|
</style>
|
|
@endsection |