This commit is contained in:
pbao9
2026-02-02 17:27:33 +07:00
parent 17b02631e4
commit 8125804bac
22 changed files with 1047 additions and 379 deletions
+241
View File
@@ -0,0 +1,241 @@
/* MicroModal Global Styles */
.modal {
display: none;
}
.modal.is-open {
display: block;
}
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 1050;
backdrop-filter: blur(2px);
}
.modal__container {
background-color: #fff;
border-radius: 0.75rem;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
position: relative;
margin: 1rem;
}
/* Modal Sizes */
.modal--xs {
max-width: 300px;
width: 90%;
}
.modal--sm {
max-width: 400px;
width: 90%;
}
.modal--md {
max-width: 600px;
width: 90%;
}
.modal--lg {
max-width: 800px;
width: 90%;
}
.modal--xl {
max-width: 1000px;
width: 90%;
}
/* Modal Header */
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.25rem;
color: white;
}
.modal__title {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
display: flex;
align-items: center;
}
.modal__close {
background: transparent;
border: none;
color: inherit;
cursor: pointer;
padding: 0.5rem;
border-radius: 0.375rem;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
}
.modal__close:focus {
outline: 2px solid rgba(255, 255, 255, 0.5);
outline-offset: 2px;
}
/* Modal Content */
.modal__content {
padding: 1.5rem;
max-height: 60vh;
overflow-y: auto;
}
.modal__content::-webkit-scrollbar {
width: 6px;
}
.modal__content::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.modal__content::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.modal__content::-webkit-scrollbar-thumb:hover {
background: #a1a1a1;
}
/* Modal Footer */
.modal__footer {
padding: 1rem 1.25rem;
border-top: 1px solid #dee2e6;
background-color: #f8f9fa;
border-radius: 0 0 0.75rem 0.75rem;
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
.modal__footer.justify-content-center {
justify-content: center;
}
.modal__footer.justify-content-start {
justify-content: flex-start;
}
/* Animations */
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide .modal__overlay {
animation: mmfadeIn 0.3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide .modal__container {
animation: mmslideIn 0.3s cubic-bezier(0, 0, 0.2, 1);
}
.micromodal-slide.is-closing .modal__overlay {
animation: mmfadeOut 0.3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide.is-closing .modal__container {
animation: mmslideOut 0.3s cubic-bezier(0, 0, 0.2, 1);
}
/* Keyframes */
@keyframes mmfadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes mmfadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes mmslideIn {
from {
transform: translateY(-15%) scale(0.95);
opacity: 0;
}
to {
transform: translateY(0) scale(1);
opacity: 1;
}
}
@keyframes mmslideOut {
from {
transform: translateY(0) scale(1);
opacity: 1;
}
to {
transform: translateY(-15%) scale(0.95);
opacity: 0;
}
}
/* Responsive */
@media (max-width: 768px) {
.modal__container {
margin: 0.5rem;
max-height: 95vh;
}
.modal__header {
padding: 0.75rem 1rem;
}
.modal__content {
padding: 1rem;
}
.modal__footer {
padding: 0.75rem 1rem;
flex-direction: column;
}
.modal__footer .btn {
width: 100%;
margin-bottom: 0.5rem;
}
.modal__footer .btn:last-child {
margin-bottom: 0;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
.modal__container {
background-color: #2d3748;
color: #f7fafc;;
}
.modal__footer {
background-color: #1a202c;
border-color: #4a5568;
}
}
+4
View File
@@ -6,6 +6,10 @@ window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// Import MicroModal
import MicroModal from 'micromodal';
window.MicroModal = MicroModal;
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
+300
View File
@@ -0,0 +1,300 @@
/**
* MicroModal Global Utilities
* Provides helper functions for MicroModal management
*/
class ModalManager {
constructor() {
this.initialized = false;
this.modals = new Map();
this.init();
}
/**
* Initialize MicroModal with default settings
*/
init() {
if (typeof MicroModal === 'undefined') {
console.warn('MicroModal is not loaded');
return;
}
if (this.initialized) {
return;
}
MicroModal.init({
onShow: (modal) => {
this.onModalShow(modal);
},
onClose: (modal) => {
this.onModalClose(modal);
},
openTrigger: 'data-micromodal-trigger',
closeTrigger: 'data-micromodal-close',
openClass: 'is-open',
disableScroll: true,
disableFocus: false,
awaitOpenAnimation: false,
awaitCloseAnimation: false,
debugMode: false
});
this.initialized = true;
console.log('MicroModal initialized');
}
/**
* Called when modal is shown
*/
onModalShow(modal) {
// Re-initialize Lucide icons when modal opens
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
// Add body class to prevent scrolling
document.body.classList.add('modal-open');
// Execute custom show callback if exists
if (modal && this.modals.has(modal.id)) {
const config = this.modals.get(modal.id);
if (config.onShow && typeof config.onShow === 'function') {
config.onShow(modal);
}
}
}
/**
* Called when modal is closed
*/
onModalClose(modal) {
// Remove body class
document.body.classList.remove('modal-open');
// Execute custom close callback if exists
if (modal && this.modals.has(modal.id)) {
const config = this.modals.get(modal.id);
if (config.onClose && typeof config.onClose === 'function') {
config.onClose(modal);
}
}
}
/**
* Show modal with optional data
*/
show(modalId, data = {}) {
if (typeof MicroModal === 'undefined') {
console.error('MicroModal is not available');
return;
}
// Update modal content if data is provided
if (Object.keys(data).length > 0) {
this.updateModalContent(modalId, data);
}
MicroModal.show(modalId);
}
/**
* Close modal
*/
close(modalId) {
if (typeof MicroModal === 'undefined') {
console.error('MicroModal is not available');
return;
}
MicroModal.close(modalId);
}
/**
* Register a modal with custom configuration
*/
register(modalId, config = {}) {
this.modals.set(modalId, config);
}
/**
* Update modal content dynamically
*/
updateModalContent(modalId, data) {
const modal = document.getElementById(modalId);
if (!modal) {
console.error(`Modal ${modalId} not found`);
return;
}
// Update text content for elements with data-modal-* attributes
Object.keys(data).forEach(key => {
const element = modal.querySelector(`[data-modal-${key}]`);
if (element) {
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
element.value = data[key];
} else {
element.textContent = data[key];
}
}
// Also support getElementById for backward compatibility
const elementById = modal.querySelector(`#${key}`);
if (elementById) {
if (elementById.tagName === 'INPUT' || elementById.tagName === 'TEXTAREA') {
elementById.value = data[key];
} else {
elementById.textContent = data[key];
}
}
});
// Update form action if provided
if (data.formAction) {
const form = modal.querySelector('form');
if (form) {
form.action = data.formAction;
}
}
}
/**
* Create a confirmation modal dynamically
*/
showConfirmation(options = {}) {
const defaults = {
modalId: 'confirmationModal',
title: 'Confirm Action',
message: 'Are you sure?',
confirmText: 'Confirm',
cancelText: 'Cancel',
confirmClass: 'btn btn-primary btn-sm',
cancelClass: 'btn btn-outline-secondary btn-sm',
onConfirm: null,
onCancel: null
};
const config = { ...defaults, ...options };
// Create modal HTML if it doesn't exist
let modal = document.getElementById(config.modalId);
if (!modal) {
modal = this.createConfirmationModal(config);
document.body.appendChild(modal);
} else {
// Update existing modal
this.updateConfirmationModal(modal, config);
}
// Register callbacks
this.register(config.modalId, {
onShow: () => {
const confirmBtn = modal.querySelector('.modal__confirm');
const cancelBtn = modal.querySelector('.modal__cancel');
// Remove existing event listeners
confirmBtn.replaceWith(confirmBtn.cloneNode(true));
cancelBtn.replaceWith(cancelBtn.cloneNode(true));
// Add new event listeners
modal.querySelector('.modal__confirm').addEventListener('click', (e) => {
if (config.onConfirm && typeof config.onConfirm === 'function') {
config.onConfirm(e);
}
this.close(config.modalId);
});
modal.querySelector('.modal__cancel').addEventListener('click', (e) => {
if (config.onCancel && typeof config.onCancel === 'function') {
config.onCancel(e);
}
this.close(config.modalId);
});
}
});
this.show(config.modalId);
}
/**
* Create confirmation modal HTML
*/
createConfirmationModal(config) {
const modalHtml = `
<div class="modal micromodal-slide" id="${config.modalId}" aria-hidden="true">
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
<div class="modal__container modal--sm" role="dialog" aria-modal="true">
<header class="modal__header bg-primary text-white">
<h6 class="modal__title">${config.title}</h6>
<button class="modal__close" aria-label="Close modal" data-micromodal-close>
<i data-lucide="x"></i>
</button>
</header>
<main class="modal__content">
<div class="text-center">
<p class="mb-0">${config.message}</p>
</div>
</main>
<footer class="modal__footer justify-content-center">
<button type="button" class="modal__cancel ${config.cancelClass}" data-micromodal-close>
${config.cancelText}
</button>
<button type="button" class="modal__confirm ${config.confirmClass}">
${config.confirmText}
</button>
</footer>
</div>
</div>
</div>
`;
const div = document.createElement('div');
div.innerHTML = modalHtml;
return div.firstElementChild;
}
/**
* Update existing confirmation modal
*/
updateConfirmationModal(modal, config) {
modal.querySelector('.modal__title').textContent = config.title;
modal.querySelector('.modal__content p').textContent = config.message;
modal.querySelector('.modal__confirm').textContent = config.confirmText;
modal.querySelector('.modal__cancel').textContent = config.cancelText;
// Update button classes
modal.querySelector('.modal__confirm').className = `modal__confirm ${config.confirmClass}`;
modal.querySelector('.modal__cancel').className = `modal__cancel ${config.cancelClass}`;
}
}
// Global instance
window.ModalManager = new ModalManager();
// Helper functions for backward compatibility
window.showModal = (modalId, data = {}) => {
window.ModalManager.show(modalId, data);
};
window.closeModal = (modalId) => {
window.ModalManager.close(modalId);
};
window.showConfirmation = (options) => {
window.ModalManager.showConfirmation(options);
};
// Auto-initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
console.log('Modal utilities loaded');
});
// Export for ES modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = ModalManager;
}
@@ -44,6 +44,9 @@
<!-- CSS for LTR layout-->
@vite(['resources/sass/app.scss'])
<!-- Global Modal CSS -->
@vite(['resources/css/components/micromodal.css'])
<!-- CSS for RTL layout-->
@@ -77,6 +80,9 @@
<!-- common js -->
@vite(['resources/js/pages/template.js'])
<!-- Global Modal JS -->
@vite(['resources/js/components/micromodal.js'])
<!-- end common js -->
<!-- Global Select2 initialization -->
@@ -88,6 +94,16 @@
height: "100%",
});
};
// Global ModalManager initialization
document.addEventListener('DOMContentLoaded', function() {
if (typeof ModalManager !== 'undefined') {
ModalManager.init();
console.log('ModalManager initialized globally');
} else {
console.warn('ModalManager not loaded');
}
});
</script>
@include('administrator.layout.partials.toast')
@@ -13,61 +13,64 @@
<h4 class="title">Clients Information</h4>
<!--Box info-->
<div class="summary__box">
<table class="table table-borderless">
<tbody>
<tr>
<th scope="row">First Name</th>
<td>{{ $customer->firstname }}</td>
<div class="table-responsive">
<table class="table table-borderless table-sm">
<tbody>
<tr>
<th scope="row">First Name</th>
<td>{{ $customer->firstname }}</td>
</tr>
<tr>
<th scope="row">Last Name</th>
<td>{{ $customer->lastname }}</td>
</tr>
<tr>
<th scope="row">Company</th>
<td>{{ $customer->company }}</td>
</tr>
<tr>
<th scope="row">Email</th>
<td>{{ $customer->email }}</td>
</tr>
<tr>
<th scope="row">Address</th>
<td>{{ $customer->address }}</td>
</tr>
<tr>
<th scope="row">City</th>
<td>{{ $customer->city }}</td>
</tr>
<tr>
<th scope="row">Country</th>
<td>{{ $customer->country }}</td>
</tr>
<tr>
<th scope="row">Phone Number</th>
<td>{{ $customer->phone }}</td>
</tr>
</tbody>
</table>
</tr>
<tr>
<th scope="row">Last Name</th>
<td>{{ $customer->lastname }}</td>
</tr>
<tr>
<th scope="row">Company</th>
<td>{{ $customer->company }}</td>
</tr>
<tr>
<th scope="row">Email</th>
<td>{{ $customer->email }}</td>
</tr>
<tr>
<th scope="row">Address</th>
<td>{{ $customer->address }}</td>
</tr>
<tr>
<th scope="row">City</th>
<td>{{ $customer->city }}</td>
</tr>
<tr>
<th scope="row">Country</th>
<td>{{ $customer->country }}</td>
</tr>
<tr>
<th scope="row">Phone Number</th>
<td>{{ $customer->phone }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!--Box Action-->
<div class="summary--box__action mt-2">
<ul class="list-unstyled">
<li>
<a id="summary-reset-password"
<div class="summary--box__action mt-3">
<div class="list-group list-group-flush">
<div class="list-group-item border-0 px-0">
<a class="btn btn-outline-warning btn-sm btn-block" id="summary-reset-password"
href="{{ route('customer.PasswordReset', ['userid' => $customer->id]) }}">
<img src="{{ asset('images/resetpw.png') }}">
<i data-lucide="key" class="mr-2"></i>
Reset & Send Password
</a>
</li>
<li>
<a target="_blank" id="summary-login-as-client"
</div>
<div class="list-group-item border-0 px-0">
<a class="btn btn-outline-info btn-sm btn-block" target="_blank" id="summary-login-as-client"
href="{{ route('customer.Login', ['userid' => $customer->id]) }}">
<img src="{{ asset('images/clientlogin.png') }}"> Login as Client
<i data-lucide="log-in" class="mr-2"></i>
Login as Client
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
@@ -77,50 +80,61 @@
<h4 class="title">Invoices/Billing</h4>
<!--Box info-->
<div class="summary__box">
<table class="table table-borderless">
<tbody>
<tr>
<th scope="row">Invoice Paid</th>
<td>{{ $customer->invoices_status_count_1 }}
(@money($customer->invoices_status_1)
đ)
</td>
<div class="table-responsive">
<table class="table table-borderless table-sm">
<tbody>
<tr>
<th scope="row">Invoice Paid</th>
<td class="text-success font-weight-bold">
<span class="badge badge-success">{{ $customer->invoices_status_count_1 }}</span>
(@money($customer->invoices_status_1) đ)
</td>
</tr>
<tr>
<th scope="row">Invoice UnPaid</th>
<td>{{ $customer->invoices_status_count_0 }}
(@money($customer->invoices_status_0)
đ)
</td>
</tr>
<tr>
<th scope="row">Total</th>
<td>@money($customer->invoices_status_0 + $customer->invoices_status_1) đ</td>
</tr>
</tbody>
</table>
</tr>
<tr>
<th scope="row">Invoice UnPaid</th>
<td class="text-warning font-weight-bold">
<span class="badge badge-warning">{{ $customer->invoices_status_count_0 }}</span>
(@money($customer->invoices_status_0) đ)
</td>
</tr>
<tr class="border-top">
<th scope="row" class="font-weight-bold">Total</th>
<td class="font-weight-bold text-primary">@money($customer->invoices_status_0 + $customer->invoices_status_1) đ</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="clients-summary__box">
<h4 class="title">Recent Emails</h4>
<div class="summary__box">
@if($customer->mail->count())
<table class="table table-borderless">
<tbody>
@foreach($customer->mail->take(4)->all() as $item)
<tr>
<td>
<a href="{{ route('customer.email', ['userid' => $userid, 'email_id' => $item->id]) }}"
target="_blank">{{ $item->created_at }}
- {{ $item->subject }}</a>
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="table-responsive">
<table class="table table-borderless table-sm table-hover">
<tbody>
@foreach($customer->mail->take(4)->all() as $item)
<tr>
<td>
<a class="text-decoration-none" href="{{ route('customer.email', ['userid' => $userid, 'email_id' => $item->id]) }}"
target="_blank">
<div class="d-flex flex-column">
<small class="text-muted">{{ $item->created_at }}</small>
<span class="text-dark">{{ $item->subject }}</span>
</div>
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<p class="text-center m-0">No recent Email</p>
<div class="text-center py-3">
<i data-lucide="mail-open" class="text-muted mb-2" style="width: 48px; height: 48px;"></i>
<p class="text-muted mb-0">No recent Email</p>
</div>
@endif
</div>
</div>
@@ -132,26 +146,36 @@
<h4 class="title">Products/Services</h4>
<!--Box info-->
<div class="summary__box">
<table class="table table-borderless">
<tbody>
<tr>
<th scope="row">VPS/Server</th>
<td>{{ $customer->vps_count}}</td>
</tr>
<tr>
<th scope="row">Hosting</th>
<td>{{ $customer->hosting_count }}</td>
</tr>
<tr>
<th scope="row">Domain</th>
<td>{{ $customer->domain_count }}</td>
</tr>
<tr>
<th scope="row">Support Tickets</th>
<td>{{ $customer->ticket_count }}</td>
</tr>
</tbody>
</table>
<div class="table-responsive">
<table class="table table-borderless table-sm">
<tbody>
<tr>
<th scope="row">VPS/Server</th>
<td>
<span class="badge badge-primary">{{ $customer->vps_count}}</span>
</td>
</tr>
<tr>
<th scope="row">Hosting</th>
<td>
<span class="badge badge-info">{{ $customer->hosting_count }}</span>
</td>
</tr>
<tr>
<th scope="row">Domain</th>
<td>
<span class="badge badge-success">{{ $customer->domain_count }}</span>
</td>
</tr>
<tr>
<th scope="row">Support Tickets</th>
<td>
<span class="badge badge-warning">{{ $customer->ticket_count }}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!--File-->
@@ -159,46 +183,58 @@
<h4 class="title">Files</h4>
<div class="summary__box">
@if($customer->file->count())
<table class="table table-borderless">
<tbody>
@foreach($customer->file->all() as $item)
<tr>
<td>
<a href="{{ $item->file_url }}" download
target="_blank">{{ $item->filename }}</a>
</td>
<td class="text-right">
<button data-id="{{ $item->id }}"
class="btn btn-outline-secondary p-1 border-0 btn-remove__file">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="table-responsive">
<table class="table table-borderless table-sm table-hover">
<tbody>
@foreach($customer->file->all() as $item)
<tr>
<td>
<a class="text-decoration-none d-flex align-items-center" href="{{ $item->file_url }}" download target="_blank">
<i data-lucide="file" class="mr-2 text-primary"></i>
<span>{{ $item->filename }}</span>
</a>
</td>
<td class="text-right">
<button data-id="{{ $item->id }}"
class="btn btn-outline-danger btn-sm p-1 btn-remove__file">
<i data-lucide="trash-2"></i>
</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<p class="text-center m-0">No files uploaded</p>
<div class="text-center py-3">
<i data-lucide="file-text" class="text-muted mb-2" style="width: 48px; height: 48px;"></i>
<p class="text-muted mb-0">No files uploaded</p>
</div>
@endif
</div>
<div class="box-add__file mt-2">
<a onclick="$('.box-add__input').removeClass('d-none')" class="cursor-point pointer-event">
<img alt="Add File" src="{{ asset('images/add.png') }}"> Add File
</a>
<div class="box-add__input d-none mt-2">
<div class="summary__box p-2">
<div class="row">
<div class="col-md-12">
<label for="input-file">File:</label>
<input type="text" class="form-control mt-2" id="input-file" name="file">
<div class="box-add__file mt-3">
<button type="button" class="btn btn-outline-success btn-sm" onclick="$('.box-add__input').toggleClass('d-none')">
<i data-lucide="plus" class="mr-2"></i>
Add File
</button>
<div class="box-add__input d-none mt-3">
<div class="card border-light">
<div class="card-body p-3">
<div class="form-group mb-3">
<label for="input-file" class="form-label">File:</label>
<input type="text" class="form-control form-control-sm" id="input-file" name="file" placeholder="Select a file...">
</div>
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-primary btn-open__ckfinder">
<i data-lucide="folder-open" class="mr-1"></i>
Choose File
</button>
<button type="button" class="btn btn-primary btn-save__data">
<i data-lucide="save" class="mr-1"></i>
Save
</button>
</div>
</div>
<button class="btn mt-2 btn-open__ckfinder btn-outline-secondary">
Choose File
</button>
<button class="btn mt-2 btn-save__data btn-outline-secondary">
Save
</button>
</div>
</div>
</div>
@@ -213,13 +249,20 @@
</div>
@else
<div class="alert alert-danger alert-dismissible">
<h5 class="mb-0"><i class="icon fas fa-ban"></i> Access Denied - You are not assigned to any Customer
<h5 class="mb-0"><i data-lucide="ban" class="icon mr-2"></i> Access Denied - You are not assigned to any Customer
management department</h5>
</div>
@endif
@endsection
@push('scripts')
<script type="text/javascript">
// Initialize Lucide icons
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});
$('.btn-open__ckfinder').click(function () {
CKFinder.popup({
chooseFiles: true,
@@ -18,7 +18,7 @@
<div class="card card-primary card-outline">
<div class="card-header">
@include("administrator.pages.s.block.Sync.Folder")
@include("administrator.pages.block.Sync.Folder")
</div>
<div class="card-body">
<x-datatable id="folderTable" ajax="{{ route('package.folder.datatable') }}" :columns="[
@@ -17,7 +17,7 @@
@if (auth()->guard('admins')->user()->can('manager package module'))
<div class="card card-primary card-outline">
<div class="card-header">
@include('administrator.pages.s.block.Group')
@include('administrator.pages.block.Group')
</div>
<div class="card-body">
<x-datatable id="groupTable" ajax="{{ route('package.group.datatable') }}" :columns="[
@@ -17,7 +17,7 @@
@if(auth()->guard('admins')->user()->can("manager package module"))
<div class="card card-primary card-outline">
<div class="card-header">
@include("administrator.pages.s.block.Sync.Network")
@include("administrator.pages.block.Sync.Network")
</div>
<div class="card-body">
<x-datatable id="networkTable" ajax="{{ route('package.network.datatable') }}" :columns="[
@@ -0,0 +1,69 @@
<!-- Delete Confirmation Modal -->
<div class="modal micromodal-slide" id="deleteModal" aria-hidden="true">
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
<div class="modal__container modal--sm" role="dialog" aria-modal="true" aria-labelledby="deleteModalLabel">
<header class="modal__header bg-primary text-white">
<h6 class="modal__title" id="deleteModalLabel">
<i data-lucide="trash-2" class="w-15px h-15px me-2"></i>
Confirm Delete
</h6>
<button class="modal__close" aria-label="Close modal" data-micromodal-close>
<i data-lucide="x"></i>
</button>
</header>
<main class="modal__content">
<div class="text-center">
<i data-lucide="alert-triangle" class="text-warning mb-3" style="width: 48px; height: 48px;"></i>
<h6 class="mb-2">Are you sure?</h6>
<p class="text-muted small mb-1">Delete OS Version:</p>
<p class="fw-bold mb-2"><span id="deleteItemName"></span></p>
<p class="text-danger small mb-0">This action cannot be undone!</p>
</div>
</main>
<footer class="modal__footer">
<button type="button" class="btn btn-outline-secondary btn-sm me-2" data-micromodal-close>
<i data-lucide="x" class="me-1"></i>
Cancel
</button>
<form id="deleteForm" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-primary btn-sm">
<i data-lucide="trash-2" class="me-1"></i>
Delete
</button>
</form>
</footer>
</div>
</div>
</div>
@push('custom-scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
function initializeModal() {
if (typeof ModalManager !== 'undefined') {
window.showDeleteModal = function(id, name) {
// Update modal content
const nameElement = document.getElementById('deleteItemName');
const formElement = document.getElementById('deleteForm');
if (nameElement && formElement) {
nameElement.textContent = name;
formElement.action = `/administrator/package/version/remove-os-version/${id}`;
ModalManager.show('deleteModal');
} else {
console.error('Modal elements not found');
}
};
} else {
console.warn('ModalManager not available, retrying...');
setTimeout(initializeModal, 100);
}
}
setTimeout(initializeModal, 50);
});
</script>
@endpush
@@ -17,14 +17,15 @@
@if (auth()->guard('admins')->user()->can('manager package module'))
<div class="card card-primary card-outline">
<div class="card-header">
<a href="{{ route('package.version.create') }}" class="btn btn-outline-secondary">
<i class="fas fa-plus"></i> Add New Version OS
<a href="{{ route('package.version.create') }}" class="btn btn-outline-primary">
<i data-lucide="plus" class="mr-2"></i> Add New Version OS
</a>
</div>
<div class="card-body">
<x-datatable id="osVerisionTable" ajax="{{ route('package.version.datatable') }}" :columns="[
['key' => 'version', 'label' => 'Version'],
['key' => 'os_group', 'label' => 'OS Group'],
['key' => 'template_id', 'label' => 'Template'],
['key' => 'status', 'label' => 'Status'],
['key' => 'action', 'label' => 'Action'],
]" />
@@ -33,12 +34,33 @@
</div>
@else
<div class="alert alert-danger alert-dismissible">
<h5 class="mb-0"><i class="icon fas fa-ban"></i> Access Denied - You are not assigned to any Module Package
<h5 class="mb-0"><i data-lucide="ban" class="icon mr-2"></i> Access Denied - You are not assigned to any Module Package
management department</h5>
</div>
@endif
<!-- Include Delete Modal -->
@include('administrator.pages.package.version.datatable.modal')
@endsection
@push('scripts')
<script type="text/javascript">
$(document).ready(function() {
// Initialize Lucide icons
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
// Re-initialize icons when DataTable draws
$('#osVerisionTable').on('draw.dt', function() {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});
});
</script>
@endpush
@push('plugin-scripts')
<script src="{{ asset('build/plugins/jquery/jquery.min.js') }}"></script>
<script src="{{ asset('build/plugins/select2/select2.min.js') }}"></script>