new branding an dnow scroll prpotection
This commit is contained in:
@@ -44,6 +44,8 @@
|
|||||||
"spatie/laravel-activitylog": "^4.12",
|
"spatie/laravel-activitylog": "^4.12",
|
||||||
"spatie/laravel-medialibrary": "^11.23",
|
"spatie/laravel-medialibrary": "^11.23",
|
||||||
"spatie/laravel-permission": "^8.0",
|
"spatie/laravel-permission": "^8.0",
|
||||||
|
"spatie/laravel-webhook-client": "^3.6",
|
||||||
|
"spatie/laravel-webhook-server": "^3.10",
|
||||||
"spatie/pdf-to-text": "^1.54",
|
"spatie/pdf-to-text": "^1.54",
|
||||||
"stancl/tenancy": "^3.10",
|
"stancl/tenancy": "^3.10",
|
||||||
"yajra/laravel-datatables": "^13.0"
|
"yajra/laravel-datatables": "^13.0"
|
||||||
|
|||||||
@@ -1213,3 +1213,166 @@ html[data-wms-sidebar="collapsed"] .wms-sidebar-link.is-active::after {
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| WMS fixed sidebar / scrollable content correction
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The application shell must own the viewport. The browser document must not
|
||||||
|
| scroll; only the right-hand WMS content pane should scroll. Keep this block
|
||||||
|
| at the end of the stylesheet so it wins over earlier shell declarations.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
overflow: hidden !important;
|
||||||
|
overscroll-behavior: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wms-app-shell {
|
||||||
|
position: fixed !important;
|
||||||
|
inset: 0 !important;
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: var(--wms-sidebar-width) minmax(0, 1fr) !important;
|
||||||
|
width: 100vw !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
max-height: 100vh !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (height: 100dvh) {
|
||||||
|
.wms-app-shell {
|
||||||
|
height: 100dvh !important;
|
||||||
|
max-height: 100dvh !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-wms-sidebar="expanded"] .wms-app-shell {
|
||||||
|
grid-template-columns: var(--wms-sidebar-width) minmax(0, 1fr) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-wms-sidebar="collapsed"] .wms-app-shell {
|
||||||
|
grid-template-columns: var(--wms-sidebar-collapsed-width) minmax(0, 1fr) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wms-sidebar {
|
||||||
|
position: relative !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
width: var(--wms-sidebar-width) !important;
|
||||||
|
min-width: var(--wms-sidebar-width) !important;
|
||||||
|
max-width: var(--wms-sidebar-width) !important;
|
||||||
|
height: 100% !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
max-height: 100% !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-wms-sidebar="collapsed"] .wms-sidebar {
|
||||||
|
width: var(--wms-sidebar-collapsed-width) !important;
|
||||||
|
min-width: var(--wms-sidebar-collapsed-width) !important;
|
||||||
|
max-width: var(--wms-sidebar-collapsed-width) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wms-sidebar-header {
|
||||||
|
flex: 0 0 auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wms-sidebar-nav {
|
||||||
|
flex: 1 1 auto !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wms-main-panel {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
max-height: 100% !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wms-main-panel > header,
|
||||||
|
.wms-main-panel > .wms-topbar,
|
||||||
|
.wms-main-panel > [data-wms-topbar] {
|
||||||
|
position: relative !important;
|
||||||
|
top: auto !important;
|
||||||
|
flex: 0 0 auto !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wms-content {
|
||||||
|
display: block !important;
|
||||||
|
flex: 1 1 auto !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
max-height: none !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.wms-app-shell {
|
||||||
|
display: block !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
max-height: 100vh !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (height: 100dvh) {
|
||||||
|
.wms-app-shell {
|
||||||
|
height: 100dvh !important;
|
||||||
|
max-height: 100dvh !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wms-main-panel {
|
||||||
|
height: 100% !important;
|
||||||
|
max-height: 100% !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wms-sidebar,
|
||||||
|
html[data-wms-sidebar="collapsed"] .wms-sidebar {
|
||||||
|
position: fixed !important;
|
||||||
|
inset: 0 auto 0 0 !important;
|
||||||
|
z-index: 70 !important;
|
||||||
|
width: min(88vw, 22rem) !important;
|
||||||
|
min-width: min(88vw, 22rem) !important;
|
||||||
|
max-width: min(88vw, 22rem) !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
max-height: 100vh !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (height: 100dvh) {
|
||||||
|
.wms-sidebar,
|
||||||
|
html[data-wms-sidebar="collapsed"] .wms-sidebar {
|
||||||
|
height: 100dvh !important;
|
||||||
|
max-height: 100dvh !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<body class="min-h-screen bg-white font-sans text-zinc-950 antialiased dark:bg-zinc-950 dark:text-zinc-100">
|
<body class="h-screen min-h-screen overflow-hidden bg-white font-sans text-zinc-950 antialiased dark:bg-zinc-950 dark:text-zinc-100">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--wms-sidebar-width: 288px;
|
--wms-sidebar-width: 288px;
|
||||||
@@ -37,13 +37,38 @@
|
|||||||
--wms-active-child-gold-dark: #e7b72f;
|
--wms-active-child-gold-dark: #e7b72f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
overflow: hidden !important;
|
||||||
|
overscroll-behavior: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.wms-app-shell {
|
.wms-app-shell {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: var(--wms-sidebar-width) minmax(0, 1fr);
|
grid-template-columns: var(--wms-sidebar-width) minmax(0, 1fr);
|
||||||
min-height: 100vh;
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden !important;
|
||||||
transition-property: none;
|
transition-property: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@supports (height: 100dvh) {
|
||||||
|
.wms-app-shell {
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
html[data-wms-sidebar="expanded"] .wms-app-shell {
|
html[data-wms-sidebar="expanded"] .wms-app-shell {
|
||||||
grid-template-columns: var(--wms-sidebar-width) minmax(0, 1fr);
|
grid-template-columns: var(--wms-sidebar-width) minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
@@ -54,16 +79,49 @@
|
|||||||
|
|
||||||
.wms-sidebar {
|
.wms-sidebar {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
width: var(--wms-sidebar-width);
|
width: var(--wms-sidebar-width);
|
||||||
min-width: var(--wms-sidebar-width);
|
min-width: var(--wms-sidebar-width);
|
||||||
max-width: var(--wms-sidebar-width);
|
max-width: var(--wms-sidebar-width);
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: hidden !important;
|
||||||
transition-property: none;
|
transition-property: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wms-main-panel {
|
.wms-main-panel {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: hidden !important;
|
||||||
transition-property: none;
|
transition-property: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wms-main-panel > header,
|
||||||
|
.wms-main-panel > .wms-topbar,
|
||||||
|
.wms-main-panel > [data-wms-topbar] {
|
||||||
|
flex: 0 0 auto !important;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wms-content {
|
||||||
|
display: block;
|
||||||
|
flex: 1 1 auto !important;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0 !important;
|
||||||
|
height: auto;
|
||||||
|
max-height: none;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
html[data-wms-sidebar="collapsed"] .wms-sidebar {
|
html[data-wms-sidebar="collapsed"] .wms-sidebar {
|
||||||
width: var(--wms-sidebar-collapsed-width);
|
width: var(--wms-sidebar-collapsed-width);
|
||||||
min-width: var(--wms-sidebar-collapsed-width);
|
min-width: var(--wms-sidebar-collapsed-width);
|
||||||
@@ -108,7 +166,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wms-sidebar-nav {
|
.wms-sidebar-nav {
|
||||||
overflow-x: hidden;
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wms-sidebar-heading-button {
|
.wms-sidebar-heading-button {
|
||||||
@@ -166,7 +229,6 @@
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.wms-sidebar-primary-link {
|
.wms-sidebar-primary-link {
|
||||||
margin-bottom: 0.35rem;
|
margin-bottom: 0.35rem;
|
||||||
min-height: 3.25rem;
|
min-height: 3.25rem;
|
||||||
@@ -179,6 +241,7 @@
|
|||||||
.wms-sidebar-primary-link span {
|
.wms-sidebar-primary-link span {
|
||||||
letter-spacing: 0.16em;
|
letter-spacing: 0.16em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wms-sidebar-link.is-active {
|
.wms-sidebar-link.is-active {
|
||||||
position: relative;
|
position: relative;
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
@@ -240,6 +303,20 @@
|
|||||||
@media (max-width: 1023px) {
|
@media (max-width: 1023px) {
|
||||||
.wms-app-shell {
|
.wms-app-shell {
|
||||||
display: block;
|
display: block;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (height: 100dvh) {
|
||||||
|
.wms-app-shell {
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wms-main-panel {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wms-sidebar,
|
.wms-sidebar,
|
||||||
@@ -247,6 +324,8 @@
|
|||||||
width: min(88vw, 22rem);
|
width: min(88vw, 22rem);
|
||||||
min-width: min(88vw, 22rem);
|
min-width: min(88vw, 22rem);
|
||||||
max-width: min(88vw, 22rem);
|
max-width: min(88vw, 22rem);
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wms-sidebar-resizer {
|
.wms-sidebar-resizer {
|
||||||
@@ -284,7 +363,7 @@
|
|||||||
<div class="wms-app-shell" data-wms-shell data-wms-mobile-shell>
|
<div class="wms-app-shell" data-wms-shell data-wms-mobile-shell>
|
||||||
<button type="button" class="wms-mobile-overlay" data-wms-mobile-close aria-label="{{ __('Close navigation menu') }}"></button>
|
<button type="button" class="wms-mobile-overlay" data-wms-mobile-close aria-label="{{ __('Close navigation menu') }}"></button>
|
||||||
|
|
||||||
<aside id="wms-main-sidebar" class="wms-sidebar" data-wms-sidebar aria-label="{{ __('Main navigation') }}">
|
<aside id="wms-main-sidebar" class="wms-sidebar" data-wms-sidebar data-test="desktop-sidebar" aria-label="{{ __('Main navigation') }}">
|
||||||
<div class="wms-sidebar-header">
|
<div class="wms-sidebar-header">
|
||||||
<a href="{{ auth()->user()?->isMpbaPlatformAdmin() && Route::has('platform.dashboard') ? route('platform.dashboard') : route('dashboard') }}" wire:navigate class="wms-brand" aria-label="{{ __('Warehouse Management System') }}">
|
<a href="{{ auth()->user()?->isMpbaPlatformAdmin() && Route::has('platform.dashboard') ? route('platform.dashboard') : route('dashboard') }}" wire:navigate class="wms-brand" aria-label="{{ __('Warehouse Management System') }}">
|
||||||
<span class="wms-brand-mark" data-wms-brand-toggle title="{{ __('Expand sidebar') }}">
|
<span class="wms-brand-mark" data-wms-brand-toggle title="{{ __('Expand sidebar') }}">
|
||||||
@@ -325,6 +404,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="wms-sidebar-group" data-wms-menu-group="operations">
|
<div class="wms-sidebar-group" data-wms-menu-group="operations">
|
||||||
<button type="button" class="wms-sidebar-heading wms-sidebar-heading-button" data-wms-menu-toggle data-wms-sidebar-label aria-expanded="false">
|
<button type="button" class="wms-sidebar-heading wms-sidebar-heading-button" data-wms-menu-toggle data-wms-sidebar-label aria-expanded="false">
|
||||||
<span class="wms-sidebar-heading-content">
|
<span class="wms-sidebar-heading-content">
|
||||||
@@ -589,6 +669,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="wms-sidebar-resizer"
|
class="wms-sidebar-resizer"
|
||||||
@@ -598,10 +679,10 @@
|
|||||||
></button>
|
></button>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="wms-main-panel">
|
<div class="wms-main-panel" data-test="main-panel">
|
||||||
@include("layouts.partials.topbar")
|
@include("layouts.partials.topbar")
|
||||||
|
|
||||||
<main class="wms-content">
|
<main class="wms-content" data-test="main-content" tabindex="-1">
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,835 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
||||||
|
@include('partials.head')
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
const SIDEBAR_STATE_KEY = 'wms-sidebar-state';
|
||||||
|
const SIDEBAR_WIDTH_KEY = 'wms-sidebar-width';
|
||||||
|
|
||||||
|
const DEFAULT_WIDTH = 288;
|
||||||
|
const MIN_WIDTH = 224;
|
||||||
|
const MAX_WIDTH = 448;
|
||||||
|
|
||||||
|
function clamp(value, min, max) {
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedWidth = Number.parseInt(localStorage.getItem(SIDEBAR_WIDTH_KEY) || DEFAULT_WIDTH, 10);
|
||||||
|
const width = clamp(Number.isFinite(savedWidth) ? savedWidth : DEFAULT_WIDTH, MIN_WIDTH, MAX_WIDTH);
|
||||||
|
const savedState = localStorage.getItem(SIDEBAR_STATE_KEY) || 'expanded';
|
||||||
|
|
||||||
|
document.documentElement.style.setProperty('--wms-sidebar-width', `${width}px`);
|
||||||
|
document.documentElement.dataset.wmsSidebar = savedState === 'collapsed' ? 'collapsed' : 'expanded';
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<body class="h-screen min-h-screen overflow-hidden bg-white font-sans text-zinc-950 antialiased dark:bg-zinc-950 dark:text-zinc-100">
|
||||||
|
|
||||||
|
@vite(['resources/css/app.css'])
|
||||||
|
|
||||||
|
<div class="wms-app-shell" data-wms-shell data-wms-mobile-shell>
|
||||||
|
<button type="button" class="wms-mobile-overlay" data-wms-mobile-close aria-label="{{ __('Close navigation menu') }}"></button>
|
||||||
|
|
||||||
|
<aside id="wms-main-sidebar" class="wms-sidebar" data-wms-sidebar data-test="desktop-sidebar" aria-label="{{ __('Main navigation') }}">
|
||||||
|
<div class="wms-sidebar-header">
|
||||||
|
<a href="{{ auth()->user()?->isMpbaPlatformAdmin() && Route::has('platform.dashboard') ? route('platform.dashboard') : route('dashboard') }}" wire:navigate class="wms-brand" aria-label="{{ __('Warehouse Management System') }}">
|
||||||
|
<span class="wms-brand-mark" data-wms-brand-toggle title="{{ __('Expand sidebar') }}">
|
||||||
|
<span class="wms-brand-mark-inner">W</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="wms-brand-copy" data-wms-sidebar-label>
|
||||||
|
<span class="wms-brand-title">{{ __('Warehouse Management System') }}</span>
|
||||||
|
<span class="wms-brand-subtitle">MPBA</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button type="button" class="wms-sidebar-toggle" data-wms-sidebar-toggle aria-label="{{ __('Collapse sidebar') }}" aria-expanded="true">
|
||||||
|
<span class="wms-toggle-expanded">‹</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" class="wms-mobile-close" data-wms-mobile-close aria-label="{{ __('Close navigation') }}">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="wms-sidebar-nav" aria-label="{{ __('Main navigation') }}">
|
||||||
|
@php
|
||||||
|
$platformRoute = auth()->user()?->isMpbaPlatformAdmin() && Route::has('platform.dashboard')
|
||||||
|
? 'platform.dashboard'
|
||||||
|
: (Route::has('dashboard') ? 'dashboard' : null);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if($platformRoute)
|
||||||
|
<div
|
||||||
|
class="wms-sidebar-group"
|
||||||
|
data-wms-menu-group="platform"
|
||||||
|
@if(request()->routeIs('platform.dashboard') || request()->routeIs('dashboard')) data-wms-selected="true" data-wms-has-active="true" @endif
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="{{ route($platformRoute) }}"
|
||||||
|
wire:navigate
|
||||||
|
class="wms-sidebar-heading wms-sidebar-heading-button wms-sidebar-primary-link"
|
||||||
|
title="{{ __('Platform') }}"
|
||||||
|
data-wms-sidebar-label
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-screwdriver-wrench wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span>{{ __('Platform') }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="wms-sidebar-group" data-wms-menu-group="operations">
|
||||||
|
<button type="button" class="wms-sidebar-heading wms-sidebar-heading-button" data-wms-menu-toggle data-wms-sidebar-label aria-expanded="false">
|
||||||
|
<span class="wms-sidebar-heading-content">
|
||||||
|
<i class="fa-solid fa-chart-simple wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span>{{ __('Operations') }}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="wms-sidebar-group-items" data-wms-menu-items>
|
||||||
|
@if(Route::has('analytics.index'))
|
||||||
|
<a href="{{ route('analytics.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('analytics.*')) is-active @endif" title="{{ __('Analytics') }}">
|
||||||
|
<i class="fa-solid fa-chart-line wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Analytics') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('inventory-management.index'))
|
||||||
|
<a href="{{ route('inventory-management.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('inventory-management.*')) is-active @endif" title="{{ __('Inventory') }}">
|
||||||
|
<i class="fa-solid fa-boxes-stacked wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Inventory') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('replenishment.index'))
|
||||||
|
<a href="{{ route('replenishment.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('replenishment.*')) is-active @endif" title="{{ __('Replenishment') }}">
|
||||||
|
<i class="fa-solid fa-arrows-rotate wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Replenishment') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('shipping.index'))
|
||||||
|
<a href="{{ route('shipping.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('shipping.*')) is-active @endif" title="{{ __('Shipping') }}">
|
||||||
|
<i class="fa-solid fa-truck-fast wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Shipping') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('transportation.index'))
|
||||||
|
<a href="{{ route('transportation.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('transportation.*')) is-active @endif" title="{{ __('Transportation') }}">
|
||||||
|
<i class="fa-solid fa-truck-ramp-box wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Transportation') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('carrier-management.index'))
|
||||||
|
<a href="{{ route('carrier-management.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('carrier-management.*')) is-active @endif" title="{{ __('Carriers') }}">
|
||||||
|
<i class="fa-solid fa-route wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Carriers') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@php
|
||||||
|
$helpdeskEnabled = Route::has('helpdesk.index') || Route::has('helpdesk.tickets.index');
|
||||||
|
|
||||||
|
$helpdeskSettingsRoute = Route::has('helpdesk.settings')
|
||||||
|
? 'helpdesk.settings'
|
||||||
|
: (Route::has('helpdesk.settings.index') ? 'helpdesk.settings.index' : null);
|
||||||
|
|
||||||
|
$helpdeskGitRoute = Route::has('helpdesk.git.index')
|
||||||
|
? 'helpdesk.git.index'
|
||||||
|
: null;
|
||||||
|
|
||||||
|
$helpdeskWorkflowRoute = Route::has('helpdesk.workflow-templates.index')
|
||||||
|
? 'helpdesk.workflow-templates.index'
|
||||||
|
: null;
|
||||||
|
|
||||||
|
$documentationEnabled = Route::has('documentation.index') || Route::has('documentation.documents.index');
|
||||||
|
|
||||||
|
$documentationRoute = Route::has('documentation.index')
|
||||||
|
? 'documentation.index'
|
||||||
|
: (Route::has('documentation.documents.index') ? 'documentation.documents.index' : null);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if($helpdeskEnabled || $documentationEnabled)
|
||||||
|
<div class="wms-sidebar-group" data-wms-menu-group="support">
|
||||||
|
@if(Route::has('helpdesk.index'))
|
||||||
|
<a href="{{ route('helpdesk.index') }}" wire:navigate class="wms-sidebar-heading wms-sidebar-heading-button @if(request()->routeIs('helpdesk.*')) is-active @endif" data-wms-sidebar-label title="{{ __('Support') }}">
|
||||||
|
<span class="wms-sidebar-heading-content">
|
||||||
|
<i class="fa-solid fa-headset wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span>{{ __('Support') }}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<button type="button" class="wms-sidebar-heading wms-sidebar-heading-button" data-wms-menu-toggle data-wms-sidebar-label aria-expanded="false">
|
||||||
|
<span class="wms-sidebar-heading-content">
|
||||||
|
<i class="fa-solid fa-headset wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span>{{ __('Support') }}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="wms-sidebar-group-items mt-2 space-y-1" data-wms-menu-items>
|
||||||
|
@if($helpdeskGitRoute)
|
||||||
|
<a href="{{ route($helpdeskGitRoute) }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('helpdesk.git.*')) is-active @endif" title="{{ __('Git Tracking') }}">
|
||||||
|
<i class="fa-solid fa-code-commit wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Git Tracking') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($helpdeskWorkflowRoute)
|
||||||
|
<a href="{{ route($helpdeskWorkflowRoute) }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('helpdesk.workflow-templates.*')) is-active @endif" title="{{ __('Workflow Emails') }}">
|
||||||
|
<i class="fa-solid fa-envelope-circle-check wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Workflow Emails') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($documentationRoute)
|
||||||
|
<a href="{{ route($documentationRoute) }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('documentation.*')) is-active @endif" title="{{ __('Documentation') }}">
|
||||||
|
<i class="fa-solid fa-book-open wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Documentation') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($helpdeskSettingsRoute)
|
||||||
|
<a href="{{ route($helpdeskSettingsRoute) }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('helpdesk.settings') || request()->routeIs('helpdesk.settings.index')) is-active @endif" title="{{ __('Helpdesk Settings') }}">
|
||||||
|
<i class="fa-solid fa-gears wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Settings') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="wms-sidebar-group" data-wms-menu-group="integration-reporting">
|
||||||
|
<button type="button" class="wms-sidebar-heading wms-sidebar-heading-button" data-wms-menu-toggle data-wms-sidebar-label aria-expanded="false">
|
||||||
|
<span class="wms-sidebar-heading-content">
|
||||||
|
<i class="fa-solid fa-plug-circle-bolt wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span>{{ __('Integration & Reporting') }}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="wms-sidebar-group-items" data-wms-menu-items>
|
||||||
|
@if(Route::has('m3-integration.index'))
|
||||||
|
<a href="{{ route('m3-integration.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('m3-integration.*')) is-active @endif" title="{{ __('M3 Integration') }}">
|
||||||
|
<i class="fa-solid fa-plug-circle-bolt wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('M3 Integration') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('sentinel-integration.index'))
|
||||||
|
<a href="{{ route('sentinel-integration.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('sentinel-integration.*')) is-active @endif" title="{{ __('Sentinel') }}">
|
||||||
|
<i class="fa-solid fa-satellite-dish wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Sentinel') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('document-archive.index'))
|
||||||
|
<a href="{{ route('document-archive.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('document-archive.*')) is-active @endif" title="{{ __('Documents') }}">
|
||||||
|
<i class="fa-solid fa-box-archive wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Documents') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('reporting.index'))
|
||||||
|
<a href="{{ route('reporting.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('reporting.*')) is-active @endif" title="{{ __('Reporting') }}">
|
||||||
|
<i class="fa-solid fa-file-lines wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Reporting') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wms-sidebar-group" data-wms-menu-group="administration">
|
||||||
|
<button type="button" class="wms-sidebar-heading wms-sidebar-heading-button" data-wms-menu-toggle data-wms-sidebar-label aria-expanded="false">
|
||||||
|
<span class="wms-sidebar-heading-content">
|
||||||
|
<i class="fa-solid fa-sliders wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span>{{ __('Administration') }}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="wms-sidebar-group-items" data-wms-menu-items>
|
||||||
|
@if(Route::has('warehouses.index'))
|
||||||
|
<a href="{{ route('warehouses.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('warehouses.*')) is-active @endif" title="{{ __('Warehouses') }}">
|
||||||
|
<i class="fa-solid fa-warehouse wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Warehouses') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('warehouse-rules.index'))
|
||||||
|
<a href="{{ route('warehouse-rules.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('warehouse-rules.*')) is-active @endif" title="{{ __('Validation Rules') }}">
|
||||||
|
<i class="fa-solid fa-shield-halved wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Validation Rules') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(auth()->user()?->isMpbaPlatformAdmin() && Route::has('platform.prices.index'))
|
||||||
|
<a href="{{ route('platform.prices.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('platform.prices.*')) is-active @endif" title="{{ __('Prices') }}">
|
||||||
|
<i class="fa-solid fa-tags wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Prices') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('configuration.index'))
|
||||||
|
<a href="{{ route('configuration.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('configuration.*')) is-active @endif" title="{{ __('Configuration') }}">
|
||||||
|
<i class="fa-solid fa-sliders wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Configuration') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('security.index'))
|
||||||
|
<a href="{{ route('security.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('security.*')) is-active @endif" title="{{ __('Security') }}">
|
||||||
|
<i class="fa-solid fa-user-shield wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Security') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('audit.index'))
|
||||||
|
<a href="{{ route('audit.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('audit.*')) is-active @endif" title="{{ __('Audit') }}">
|
||||||
|
<i class="fa-solid fa-clipboard-check wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Audit') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if(auth()->user()?->isMpbaPlatformAdmin())
|
||||||
|
<div class="wms-sidebar-group" data-wms-menu-group="platform-administration">
|
||||||
|
<button type="button" class="wms-sidebar-heading wms-sidebar-heading-button" data-wms-menu-toggle data-wms-sidebar-label aria-expanded="false">
|
||||||
|
<span class="wms-sidebar-heading-content">
|
||||||
|
<i class="fa-solid fa-building-shield wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span>{{ __('Platform Administration') }}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="wms-sidebar-group-items" data-wms-menu-items>
|
||||||
|
@if(Route::has('platform.companies.index'))
|
||||||
|
<a href="{{ route('platform.companies.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('platform.companies.*')) is-active @endif" title="{{ __('Companies') }}">
|
||||||
|
<i class="fa-solid fa-building wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Companies') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('platform.licenses.index'))
|
||||||
|
<a href="{{ route('platform.licenses.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('platform.licenses.*')) is-active @endif" title="{{ __('Licences') }}">
|
||||||
|
<i class="fa-solid fa-key wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Licences') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('platform.payments.index'))
|
||||||
|
<a href="{{ route('platform.payments.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('platform.payments.*')) is-active @endif" title="{{ __('Payments') }}">
|
||||||
|
<i class="fa-solid fa-credit-card wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Payments') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(Route::has('countries.index'))
|
||||||
|
<a href="{{ route('countries.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('countries.*')) is-active @endif" title="{{ __('Countries') }}">
|
||||||
|
<i class="fa-solid fa-globe wms-sidebar-icon" aria-hidden="true"></i>
|
||||||
|
<span data-wms-sidebar-label>{{ __('Countries') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</nav>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="wms-sidebar-resizer"
|
||||||
|
data-wms-sidebar-resizer
|
||||||
|
aria-label="{{ __('Resize sidebar') }}"
|
||||||
|
title="{{ __('Drag to resize sidebar') }}"
|
||||||
|
></button>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div class="wms-main-panel" data-test="main-panel">
|
||||||
|
@include("layouts.partials.topbar")
|
||||||
|
|
||||||
|
<main class="wms-content" data-test="main-content" tabindex="-1">
|
||||||
|
{{ $slot }}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="wms-toast-stack"
|
||||||
|
class="fixed right-4 top-4 z-[9999] flex w-[min(24rem,calc(100vw-2rem))] flex-col gap-3 font-sans"
|
||||||
|
aria-live="polite"
|
||||||
|
aria-atomic="true"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.wmsToast = function (options) {
|
||||||
|
const stack = document.getElementById('wms-toast-stack');
|
||||||
|
|
||||||
|
if (!stack) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
type: options.type || 'info',
|
||||||
|
title: options.title || '',
|
||||||
|
message: options.message || '',
|
||||||
|
timeout: Number(options.timeout || 4500),
|
||||||
|
};
|
||||||
|
|
||||||
|
const palette = {
|
||||||
|
success: 'border-emerald-400/40 bg-emerald-950/95 text-emerald-50',
|
||||||
|
error: 'border-red-400/40 bg-red-950/95 text-red-50',
|
||||||
|
warning: 'border-amber-400/40 bg-amber-950/95 text-amber-50',
|
||||||
|
info: 'border-blue-400/40 bg-blue-950/95 text-blue-50',
|
||||||
|
};
|
||||||
|
|
||||||
|
const icon = {
|
||||||
|
success: 'fa-circle-check',
|
||||||
|
error: 'fa-circle-xmark',
|
||||||
|
warning: 'fa-triangle-exclamation',
|
||||||
|
info: 'fa-circle-info',
|
||||||
|
};
|
||||||
|
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
|
||||||
|
toast.className = [
|
||||||
|
'wms-toast pointer-events-auto select-none rounded-2xl border px-4 py-3 font-sans shadow-2xl backdrop-blur',
|
||||||
|
'transition duration-200 ease-out translate-y-0 opacity-100',
|
||||||
|
palette[config.type] || palette.info,
|
||||||
|
].join(' ');
|
||||||
|
|
||||||
|
toast.innerHTML = `
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<i class="fa-solid ${icon[config.type] || icon.info} mt-0.5 text-lg" aria-hidden="true"></i>
|
||||||
|
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
${config.title ? `<div class="text-sm font-bold">${config.title}</div>` : ''}
|
||||||
|
<div class="text-sm leading-5">${config.message}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="ml-2 text-white/70 hover:text-white" aria-label="Close notification">
|
||||||
|
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const close = function () {
|
||||||
|
toast.classList.add('translate-y-[-0.35rem]', 'opacity-0');
|
||||||
|
|
||||||
|
window.setTimeout(function () {
|
||||||
|
toast.remove();
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
toast.querySelector('button').addEventListener('click', close);
|
||||||
|
|
||||||
|
stack.appendChild(toast);
|
||||||
|
|
||||||
|
if (config.timeout > 0) {
|
||||||
|
window.setTimeout(close, config.timeout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('wms:toast', function (event) {
|
||||||
|
window.wmsToast(event.detail || {});
|
||||||
|
});
|
||||||
|
|
||||||
|
function showSessionToasts() {
|
||||||
|
@if(session('success'))
|
||||||
|
window.wmsToast({
|
||||||
|
type: 'success',
|
||||||
|
title: @json(__('Success')),
|
||||||
|
message: @json(session('success')),
|
||||||
|
});
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(session('error'))
|
||||||
|
window.wmsToast({
|
||||||
|
type: 'error',
|
||||||
|
title: @json(__('Error')),
|
||||||
|
message: @json(session('error')),
|
||||||
|
});
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(session('warning'))
|
||||||
|
window.wmsToast({
|
||||||
|
type: 'warning',
|
||||||
|
title: @json(__('Warning')),
|
||||||
|
message: @json(session('warning')),
|
||||||
|
});
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(session('info'))
|
||||||
|
window.wmsToast({
|
||||||
|
type: 'info',
|
||||||
|
title: @json(__('Information')),
|
||||||
|
message: @json(session('info')),
|
||||||
|
});
|
||||||
|
@endif
|
||||||
|
}
|
||||||
|
|
||||||
|
let wmsSessionToastsShown = false;
|
||||||
|
|
||||||
|
function showSessionToastsOnce() {
|
||||||
|
if (wmsSessionToastsShown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wmsSessionToastsShown = true;
|
||||||
|
showSessionToasts();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', showSessionToastsOnce);
|
||||||
|
document.addEventListener('livewire:navigated', showSessionToastsOnce);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@fluxScripts
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
const SIDEBAR_STATE_KEY = 'wms-sidebar-state';
|
||||||
|
const SIDEBAR_WIDTH_KEY = 'wms-sidebar-width';
|
||||||
|
const PLATFORM_FIRST_OPEN_KEY = 'wms-platform-first-opened';
|
||||||
|
|
||||||
|
const DEFAULT_WIDTH = 288;
|
||||||
|
const MIN_WIDTH = 224;
|
||||||
|
const MAX_WIDTH = 448;
|
||||||
|
|
||||||
|
function clamp(value, min, max) {
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSavedSidebarWidth() {
|
||||||
|
const savedWidth = Number.parseInt(localStorage.getItem(SIDEBAR_WIDTH_KEY) || DEFAULT_WIDTH, 10);
|
||||||
|
|
||||||
|
return clamp(Number.isFinite(savedWidth) ? savedWidth : DEFAULT_WIDTH, MIN_WIDTH, MAX_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySidebarWidth() {
|
||||||
|
const nextWidth = `${getSavedSidebarWidth()}px`;
|
||||||
|
|
||||||
|
document.documentElement.style.setProperty('--wms-sidebar-width', nextWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySidebarState() {
|
||||||
|
const root = document.documentElement;
|
||||||
|
const saved = localStorage.getItem(SIDEBAR_STATE_KEY) || 'expanded';
|
||||||
|
|
||||||
|
root.dataset.wmsSidebar = saved === 'collapsed' ? 'collapsed' : 'expanded';
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-wms-sidebar-toggle]').forEach((button) => {
|
||||||
|
const expanded = root.dataset.wmsSidebar !== 'collapsed';
|
||||||
|
|
||||||
|
button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
||||||
|
button.setAttribute('aria-label', expanded ? '{{ __('Collapse sidebar') }}' : '{{ __('Expand sidebar') }}');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMenuItems(group) {
|
||||||
|
return Array.from(group.children).find((child) => child.matches('[data-wms-menu-items]')) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMenuToggle(group) {
|
||||||
|
return Array.from(group.children).find((child) => child.matches('[data-wms-menu-toggle]')) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupHasActiveLink(group) {
|
||||||
|
const items = getMenuItems(group);
|
||||||
|
const activeHeading = Array.from(group.children).some((child) => {
|
||||||
|
return child.matches('.wms-sidebar-heading-button.is-active');
|
||||||
|
});
|
||||||
|
|
||||||
|
const routeSelectedGroup = group.dataset.wmsSelected === 'true' && group.dataset.wmsHasActive === 'true';
|
||||||
|
|
||||||
|
return routeSelectedGroup || activeHeading || (items ? items.querySelector('.wms-sidebar-link.is-active') !== null : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMenuGroupOpen(group, isOpen) {
|
||||||
|
group.dataset.wmsMenuOpen = isOpen ? 'true' : 'false';
|
||||||
|
|
||||||
|
const toggle = getMenuToggle(group);
|
||||||
|
|
||||||
|
if (toggle) {
|
||||||
|
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMenuGroupSelected(group, isSelected) {
|
||||||
|
group.dataset.wmsSelected = isSelected ? 'true' : 'false';
|
||||||
|
group.dataset.wmsHasActive = isSelected ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAllMenuGroups() {
|
||||||
|
document.querySelectorAll('[data-wms-menu-group]').forEach((group) => {
|
||||||
|
setMenuGroupOpen(group, false);
|
||||||
|
setMenuGroupSelected(group, false);
|
||||||
|
group.dataset.wmsManualActive = 'false';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectMenuGroup(group, openGroup = true) {
|
||||||
|
resetAllMenuGroups();
|
||||||
|
setMenuGroupSelected(group, true);
|
||||||
|
setMenuGroupOpen(group, openGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyMenuState() {
|
||||||
|
const groups = Array.from(document.querySelectorAll('[data-wms-menu-group]'));
|
||||||
|
const selectedByRoute = groups.find((group) => groupHasActiveLink(group)) || null;
|
||||||
|
const selectedByManualClick = groups.find((group) => group.dataset.wmsManualActive === 'true') || null;
|
||||||
|
const selectedGroup = selectedByRoute || selectedByManualClick;
|
||||||
|
const shouldOpenPlatformForFirstVisit = !selectedGroup && localStorage.getItem(PLATFORM_FIRST_OPEN_KEY) !== '1';
|
||||||
|
|
||||||
|
resetAllMenuGroups();
|
||||||
|
|
||||||
|
if (selectedGroup) {
|
||||||
|
setMenuGroupSelected(selectedGroup, true);
|
||||||
|
setMenuGroupOpen(selectedGroup, true);
|
||||||
|
} else if (shouldOpenPlatformForFirstVisit) {
|
||||||
|
const platformGroup = groups.find((group) => group.dataset.wmsMenuGroup === 'platform');
|
||||||
|
|
||||||
|
if (platformGroup) {
|
||||||
|
setMenuGroupOpen(platformGroup, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem(PLATFORM_FIRST_OPEN_KEY, '1');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindMenuControls() {
|
||||||
|
document.querySelectorAll('[data-wms-menu-toggle]').forEach((button) => {
|
||||||
|
if (button.dataset.wmsBound === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.dataset.wmsBound = '1';
|
||||||
|
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
const group = button.closest('[data-wms-menu-group]');
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextOpen = group.dataset.wmsMenuOpen !== 'true';
|
||||||
|
|
||||||
|
resetAllMenuGroups();
|
||||||
|
|
||||||
|
if (nextOpen) {
|
||||||
|
group.dataset.wmsManualActive = 'true';
|
||||||
|
setMenuGroupSelected(group, true);
|
||||||
|
setMenuGroupOpen(group, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindSidebarResize() {
|
||||||
|
document.querySelectorAll('[data-wms-sidebar-resizer]').forEach((handle) => {
|
||||||
|
if (handle.dataset.wmsBound === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.dataset.wmsBound = '1';
|
||||||
|
|
||||||
|
let startX = 0;
|
||||||
|
let startWidth = DEFAULT_WIDTH;
|
||||||
|
let isResizing = false;
|
||||||
|
|
||||||
|
function getClientX(event) {
|
||||||
|
if (event.touches && event.touches.length > 0) {
|
||||||
|
return event.touches[0].clientX;
|
||||||
|
}
|
||||||
|
|
||||||
|
return event.clientX;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startResize(event) {
|
||||||
|
if (document.documentElement.dataset.wmsSidebar === 'collapsed') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isResizing = true;
|
||||||
|
startX = getClientX(event);
|
||||||
|
|
||||||
|
const currentWidth = Number.parseInt(
|
||||||
|
getComputedStyle(document.documentElement).getPropertyValue('--wms-sidebar-width'),
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
|
startWidth = Number.isFinite(currentWidth) ? currentWidth : DEFAULT_WIDTH;
|
||||||
|
|
||||||
|
document.body.classList.add('wms-sidebar-is-resizing');
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveResize(event) {
|
||||||
|
if (!isResizing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = getClientX(event) - startX;
|
||||||
|
const nextWidth = clamp(startWidth + delta, MIN_WIDTH, MAX_WIDTH);
|
||||||
|
|
||||||
|
document.documentElement.style.setProperty('--wms-sidebar-width', `${nextWidth}px`);
|
||||||
|
localStorage.setItem(SIDEBAR_WIDTH_KEY, String(nextWidth));
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopResize() {
|
||||||
|
if (!isResizing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isResizing = false;
|
||||||
|
document.body.classList.remove('wms-sidebar-is-resizing');
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.addEventListener('mousedown', startResize);
|
||||||
|
handle.addEventListener('touchstart', startResize, { passive: false });
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', moveResize);
|
||||||
|
window.addEventListener('touchmove', moveResize, { passive: false });
|
||||||
|
|
||||||
|
window.addEventListener('mouseup', stopResize);
|
||||||
|
window.addEventListener('touchend', stopResize);
|
||||||
|
window.addEventListener('touchcancel', stopResize);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMobileToggleState(isOpen) {
|
||||||
|
document.querySelectorAll('[data-wms-mobile-open]').forEach((button) => {
|
||||||
|
button.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.classList.toggle('wms-mobile-nav-open', isOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMobileSidebar() {
|
||||||
|
document.documentElement.dataset.wmsMobileSidebar = 'closed';
|
||||||
|
setMobileToggleState(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMobileSidebar() {
|
||||||
|
document.documentElement.dataset.wmsMobileSidebar = 'open';
|
||||||
|
setMobileToggleState(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMobileSidebar() {
|
||||||
|
if (document.documentElement.dataset.wmsMobileSidebar === 'open') {
|
||||||
|
closeMobileSidebar();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openMobileSidebar();
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindSidebarControls() {
|
||||||
|
applySidebarWidth();
|
||||||
|
applySidebarState();
|
||||||
|
applyMenuState();
|
||||||
|
bindMenuControls();
|
||||||
|
bindSidebarResize();
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-wms-sidebar-toggle]').forEach((button) => {
|
||||||
|
if (button.dataset.wmsBound === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.dataset.wmsBound = '1';
|
||||||
|
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
const root = document.documentElement;
|
||||||
|
const next = root.dataset.wmsSidebar === 'collapsed' ? 'expanded' : 'collapsed';
|
||||||
|
|
||||||
|
root.dataset.wmsSidebar = next;
|
||||||
|
localStorage.setItem(SIDEBAR_STATE_KEY, next);
|
||||||
|
|
||||||
|
applySidebarState();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-wms-brand-toggle]').forEach((button) => {
|
||||||
|
if (button.dataset.wmsBound === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.dataset.wmsBound = '1';
|
||||||
|
|
||||||
|
button.addEventListener('click', function (event) {
|
||||||
|
const root = document.documentElement;
|
||||||
|
|
||||||
|
if (root.dataset.wmsSidebar !== 'collapsed') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
root.dataset.wmsSidebar = 'expanded';
|
||||||
|
localStorage.setItem(SIDEBAR_STATE_KEY, 'expanded');
|
||||||
|
|
||||||
|
applySidebarState();
|
||||||
|
applyMenuState();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
closeMobileSidebar();
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-wms-mobile-open]').forEach((button) => {
|
||||||
|
if (button.dataset.wmsBound === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.dataset.wmsBound = '1';
|
||||||
|
|
||||||
|
button.addEventListener('click', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
toggleMobileSidebar();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-wms-mobile-close]').forEach((button) => {
|
||||||
|
if (button.dataset.wmsBound === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.dataset.wmsBound = '1';
|
||||||
|
button.addEventListener('click', closeMobileSidebar);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.wms-sidebar-link[wire\\:navigate], .wms-sidebar-heading-button[wire\\:navigate]').forEach((link) => {
|
||||||
|
if (link.dataset.wmsMobileBound === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
link.dataset.wmsMobileBound = '1';
|
||||||
|
|
||||||
|
link.addEventListener('click', function () {
|
||||||
|
document.querySelectorAll('.wms-sidebar-link.is-active, .wms-sidebar-heading-button.is-active').forEach((activeLink) => {
|
||||||
|
activeLink.classList.remove('is-active');
|
||||||
|
});
|
||||||
|
|
||||||
|
link.classList.add('is-active');
|
||||||
|
|
||||||
|
const selectedGroup = link.closest('[data-wms-menu-group]');
|
||||||
|
|
||||||
|
if (selectedGroup) {
|
||||||
|
selectMenuGroup(selectedGroup, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
applySidebarWidth();
|
||||||
|
closeMobileSidebar();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
applySidebarWidth();
|
||||||
|
applySidebarState();
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', bindSidebarControls);
|
||||||
|
document.addEventListener('livewire:navigated', bindSidebarControls);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Layout;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
|
class FixedSidebarMainScrollTest extends DuskTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The document must not scroll; only the right-hand main content area may scroll.
|
||||||
|
*/
|
||||||
|
public function test_document_does_not_scroll_when_main_content_scrolls(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->browse(function (Browser $browser) use ($user): void {
|
||||||
|
$browser
|
||||||
|
->loginAs($user)
|
||||||
|
->visit('/dashboard')
|
||||||
|
->waitFor('[data-test="desktop-sidebar"]')
|
||||||
|
->waitFor('[data-test="main-content"]');
|
||||||
|
|
||||||
|
$browser->script(<<<'JS'
|
||||||
|
const main = document.querySelector('[data-test="main-content"]');
|
||||||
|
const filler = document.createElement('div');
|
||||||
|
|
||||||
|
filler.setAttribute('data-test', 'scroll-filler');
|
||||||
|
filler.style.height = '2600px';
|
||||||
|
|
||||||
|
main.appendChild(filler);
|
||||||
|
|
||||||
|
window.scrollTo(0, 800);
|
||||||
|
document.documentElement.scrollTop = 800;
|
||||||
|
document.body.scrollTop = 800;
|
||||||
|
main.scrollTop = 900;
|
||||||
|
JS);
|
||||||
|
|
||||||
|
$documentScrollTop = $browser->script(
|
||||||
|
'return Math.round(window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0);'
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
$mainScrollTop = $browser->script(
|
||||||
|
'return Math.round(document.querySelector("[data-test=\"main-content\"]").scrollTop);'
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
$sidebarTop = $browser->script(
|
||||||
|
'return Math.round(document.querySelector("[data-test=\"desktop-sidebar\"]").getBoundingClientRect().top);'
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
$this->assertSame(0, $documentScrollTop);
|
||||||
|
$this->assertSame(0, $sidebarTop);
|
||||||
|
$this->assertGreaterThan(0, $mainScrollTop);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Layout;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
|
class FixedSidebarScrollTest extends DuskTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Ensure only the main content area scrolls while the desktop sidebar stays fixed.
|
||||||
|
*/
|
||||||
|
public function test_only_main_content_area_scrolls_while_sidebar_remains_fixed(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->browse(function (Browser $browser) use ($user): void {
|
||||||
|
$browser
|
||||||
|
->loginAs($user)
|
||||||
|
->visit('/dashboard')
|
||||||
|
->waitFor('[data-test="desktop-sidebar"]')
|
||||||
|
->waitFor('[data-test="main-content"]');
|
||||||
|
|
||||||
|
$browser->script(<<<'JS'
|
||||||
|
const main = document.querySelector('[data-test="main-content"]');
|
||||||
|
const filler = document.createElement('div');
|
||||||
|
|
||||||
|
filler.setAttribute('data-test', 'scroll-filler');
|
||||||
|
filler.style.height = '2200px';
|
||||||
|
|
||||||
|
main.appendChild(filler);
|
||||||
|
main.scrollTop = 900;
|
||||||
|
JS);
|
||||||
|
|
||||||
|
$sidebarTop = $browser->script(
|
||||||
|
'return Math.round(document.querySelector("[data-test=\"desktop-sidebar\"]").getBoundingClientRect().top);'
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
$bodyScrollTop = $browser->script(
|
||||||
|
'return Math.round(document.scrollingElement.scrollTop);'
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
$mainScrollTop = $browser->script(
|
||||||
|
'return Math.round(document.querySelector("[data-test=\"main-content\"]").scrollTop);'
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
$this->assertSame(0, $sidebarTop);
|
||||||
|
$this->assertSame(0, $bodyScrollTop);
|
||||||
|
$this->assertGreaterThan(0, $mainScrollTop);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user