Feat: User Management

This commit is contained in:
Sallu9007
2025-05-13 23:05:24 +05:30
parent 78e4207e65
commit 7ead83e69f
4 changed files with 582 additions and 10 deletions

View File

@@ -0,0 +1,501 @@
@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 -->
</select>
</div>
</div>
<div class="col-md-4 d-flex justify-content-end">
<!-- 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>
@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');
}
});
});
</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