fixed system

This commit is contained in:
Mark Gregory
2026-06-18 12:30:16 +01:00
parent 38525c8e9a
commit cf3dceb994
6 changed files with 107 additions and 971 deletions
@@ -1,944 +1,5 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
@include('partials.head')
@props(['title' => null])
<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="min-h-screen bg-white font-sans text-zinc-950 antialiased dark:bg-zinc-950 dark:text-zinc-100">
<style>
:root {
--wms-sidebar-width: 288px;
--wms-sidebar-min-width: 224px;
--wms-sidebar-max-width: 448px;
--wms-sidebar-collapsed-width: 76px;
--wms-active-parent-blue: #4164dc;
--wms-active-child-gold: #e7b72f;
--wms-active-child-gold-dark: #e7b72f;
}
.wms-app-shell {
display: grid;
grid-template-columns: var(--wms-sidebar-width) minmax(0, 1fr);
min-height: 100vh;
transition-property: none;
}
html[data-wms-sidebar="expanded"] .wms-app-shell {
grid-template-columns: var(--wms-sidebar-width) minmax(0, 1fr);
}
html[data-wms-sidebar="collapsed"] .wms-app-shell {
grid-template-columns: var(--wms-sidebar-collapsed-width) minmax(0, 1fr);
}
.wms-sidebar {
position: relative;
width: var(--wms-sidebar-width);
min-width: var(--wms-sidebar-width);
max-width: var(--wms-sidebar-width);
transition-property: none;
}
.wms-main-panel {
transition-property: none;
}
html[data-wms-sidebar="collapsed"] .wms-sidebar {
width: var(--wms-sidebar-collapsed-width);
min-width: var(--wms-sidebar-collapsed-width);
max-width: var(--wms-sidebar-collapsed-width);
}
.wms-sidebar-resizer {
position: absolute;
top: 0;
right: -4px;
z-index: 30;
width: 8px;
height: 100%;
cursor: col-resize;
border: 0;
background: transparent;
}
.wms-sidebar-resizer::after {
position: absolute;
top: 0;
right: 3px;
width: 1px;
height: 100%;
content: "";
background: rgba(148, 163, 184, 0.22);
transition: background 160ms ease;
}
.wms-sidebar-resizer:hover::after,
body.wms-sidebar-is-resizing .wms-sidebar-resizer::after {
background: rgba(59, 130, 246, 0.7);
}
body.wms-sidebar-is-resizing {
cursor: col-resize;
user-select: none;
}
html[data-wms-sidebar="collapsed"] .wms-sidebar-resizer {
display: none;
}
.wms-sidebar-nav {
overflow-x: hidden;
}
.wms-sidebar-heading-button {
display: flex;
width: 100%;
align-items: center;
justify-content: flex-start;
gap: 0.75rem;
border: 0;
background: transparent;
cursor: pointer;
text-align: left;
}
.wms-sidebar-heading-content {
display: inline-flex;
align-items: center;
gap: 0.7rem;
min-width: 0;
}
.wms-sidebar-heading-content > span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.wms-sidebar-heading-chevron {
display: none;
}
.wms-sidebar-group-items {
display: block;
}
.wms-sidebar-group[data-wms-menu-open="false"] > .wms-sidebar-group-items {
display: block;
}
.wms-sidebar-group[data-wms-menu-open="true"] > .wms-sidebar-group-items {
display: block;
}
.wms-sidebar-group:not([data-wms-menu-group="platform"])[data-wms-selected="true"] > .wms-sidebar-heading-button {
position: relative;
width: calc(100% - 1.25rem);
min-height: 2.9rem;
margin: 0.25rem 0.65rem 0.35rem;
padding: 0.6rem 0.85rem;
border-radius: 1.35rem;
background: linear-gradient(135deg, #4667df 0%, #3f5fd2 100%);
color: #ffffff;
font-weight: 700;
letter-spacing: 0.035em;
box-shadow: 0 8px 18px rgba(65, 100, 220, 0.22);
}
.wms-sidebar-group:not([data-wms-menu-group="platform"])[data-wms-selected="true"] > .wms-sidebar-heading-button .wms-sidebar-icon {
color: #ffffff;
}
.wms-sidebar-link.is-active {
position: relative;
isolation: isolate;
width: calc(100% - 2.05rem);
min-height: 2.85rem;
margin: 0.2rem 1.25rem 0.25rem 0.8rem;
padding: 0.6rem 0.95rem;
border-radius: 1.65rem 0.75rem 0.75rem 1.65rem;
background: var(--wms-active-child-gold);
color: #ffffff;
font-weight: 700;
box-shadow: 0 8px 18px rgba(231, 183, 47, 0.24);
}
.wms-sidebar-link.is-active::after {
position: absolute;
top: 0;
right: -1.05rem;
z-index: -1;
width: 1.75rem;
height: 100%;
content: "";
background: var(--wms-active-child-gold);
clip-path: polygon(0 0, 64% 0, 100% 50%, 64% 100%, 0 100%);
border-radius: 0 0.9rem 0.9rem 0;
}
.wms-sidebar-link.is-active .wms-sidebar-icon,
.wms-sidebar-link.is-active i,
.wms-sidebar-link.is-active span {
position: relative;
z-index: 1;
color: #ffffff;
}
html[data-wms-sidebar="collapsed"] .wms-sidebar-heading-chevron,
html[data-wms-sidebar="collapsed"] .wms-sidebar-group-items {
display: none;
}
html[data-wms-sidebar="collapsed"] .wms-sidebar-heading-button {
pointer-events: none;
}
html[data-wms-sidebar="collapsed"] .wms-sidebar-group:not([data-wms-menu-group="platform"])[data-wms-selected="true"] > .wms-sidebar-heading-button,
html[data-wms-sidebar="collapsed"] .wms-sidebar-link.is-active {
width: 2.75rem;
min-height: 2.75rem;
margin: 0.25rem auto;
justify-content: center;
border-radius: 9999px;
padding: 0.65rem;
}
html[data-wms-sidebar="collapsed"] .wms-sidebar-link.is-active::after {
display: none;
}
@media (max-width: 1023px) {
.wms-app-shell {
display: block;
}
.wms-sidebar,
html[data-wms-sidebar="collapsed"] .wms-sidebar {
width: min(88vw, 22rem);
min-width: min(88vw, 22rem);
max-width: min(88vw, 22rem);
}
.wms-sidebar-resizer {
display: none;
}
.wms-sidebar-group:not([data-wms-menu-group="platform"])[data-wms-selected="true"] > .wms-sidebar-heading-button {
width: calc(100% - 1.25rem);
min-height: 2.85rem;
margin: 0.25rem 0.65rem 0.35rem;
padding: 0.6rem 0.85rem;
}
.wms-sidebar-link.is-active {
width: calc(100% - 2.05rem);
min-height: 2.8rem;
margin: 0.2rem 1.25rem 0.25rem 0.8rem;
padding: 0.6rem 0.95rem;
}
html[data-wms-sidebar="collapsed"] .wms-sidebar-group-items,
html[data-wms-sidebar="collapsed"] .wms-sidebar-group[data-wms-menu-open="true"] > .wms-sidebar-group-items,
html[data-wms-sidebar="collapsed"] .wms-sidebar-group[data-wms-menu-open="false"] > .wms-sidebar-group-items {
display: block;
}
html[data-wms-sidebar="collapsed"] .wms-sidebar-heading-button {
pointer-events: auto;
}
}
</style>
<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 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') }}">
<div class="wms-sidebar-group" data-wms-menu-group="platform">
<button type="button" class="wms-sidebar-heading wms-sidebar-heading-button" data-wms-menu-toggle data-wms-sidebar-label aria-expanded="true">
<span class="wms-sidebar-heading-content">
<i class="fa-solid fa-screwdriver-wrench wms-sidebar-icon" aria-hidden="true"></i>
<span>{{ __('Platform') }}</span>
</span>
</button>
<div class="wms-sidebar-group-items" data-wms-menu-items>
@if(auth()->user()?->isMpbaPlatformAdmin() && Route::has('platform.dashboard'))
<a href="{{ route('platform.dashboard') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('platform.dashboard')) is-active @endif" title="{{ __('Platform') }}">
<i class="fa-solid fa-screwdriver-wrench wms-sidebar-icon" aria-hidden="true"></i>
<span data-wms-sidebar-label>{{ __('Platform') }}</span>
</a>
@elseif(Route::has('dashboard'))
<a href="{{ route('dashboard') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('dashboard')) is-active @endif" title="{{ __('Dashboard') }}">
<i class="fa-solid fa-gauge-high wms-sidebar-icon" aria-hidden="true"></i>
<span data-wms-sidebar-label>{{ __('Dashboard') }}</span>
</a>
@endif
</div>
</div>
<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="true">
<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">
<button type="button" class="wms-sidebar-heading wms-sidebar-heading-button" data-wms-menu-toggle data-wms-sidebar-label aria-expanded="true">
<span class="wms-sidebar-heading-content">
<i class="fa-solid fa-headset wms-sidebar-icon" aria-hidden="true"></i>
<span>{{ __('Support') }}</span>
</span>
</button>
<div class="wms-sidebar-group-items" data-wms-menu-items>
@if(Route::has('helpdesk.index'))
<a href="{{ route('helpdesk.index') }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('helpdesk.index')) is-active @endif" title="{{ __('Helpdesk Dashboard') }}">
<i class="fa-solid fa-headset wms-sidebar-icon" aria-hidden="true"></i>
<span data-wms-sidebar-label>{{ __('Dashboard') }}</span>
</a>
@endif
@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="true">
<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="true">
<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())
@php
$countriesRoute = Route::has('platform.countries.index')
? 'platform.countries.index'
: (Route::has('countries.index') ? 'countries.index' : null);
@endphp
<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="true">
<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($countriesRoute)
<a href="{{ route($countriesRoute) }}" wire:navigate class="wms-sidebar-link @if(request()->routeIs('platform.countries.*') || request()->routeIs('countries.*') || request()->routeIs('country.*')) 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">
("layouts.partials.topbar")
<main class="wms-content">
{{ $slot }}
</main>
</div>
</div>
@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);
return 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, true);
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();
groups.forEach((group) => setMenuGroupOpen(group, true));
if (selectedGroup) {
setMenuGroupSelected(selectedGroup, true);
setMenuGroupOpen(selectedGroup, true);
} else if (shouldOpenPlatformForFirstVisit) {
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';
if (nextOpen) {
group.dataset.wmsManualActive = 'true';
setMenuGroupSelected(group, true);
} else {
group.dataset.wmsManualActive = 'false';
setMenuGroupSelected(group, false);
}
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]').forEach((link) => {
if (link.dataset.wmsMobileBound === '1') {
return;
}
link.dataset.wmsMobileBound = '1';
link.addEventListener('click', function () {
document.querySelectorAll('.wms-sidebar-link.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>
<x-layouts.app :title="$title ?? __('Helpdesk')">
{{ $slot }}
</x-layouts.app>
+7
View File
@@ -380,3 +380,10 @@ versions:
- Added browser coverage to keep Helpdesk settings free of direct Tickets and Kanban shortcut links.
- Kept module.json version in sync with changelog.yaml.
- version: 1.9.25
date: 2026-06-18
changed:
- Replaced the duplicated Helpdesk application shell with the shared application layout so Helpdesk uses the same mobile navigation and top bar as the rest of WMS.
- Preserved the existing Helpdesk views while centralising the layout surface to reduce future mobile regressions.
- Kept module.json version in sync with changelog.yaml.
+1 -1
View File
@@ -22,5 +22,5 @@
"Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider"
],
"files": [],
"version": "1.9.24"
"version": "1.9.25"
}
@@ -1,5 +1,5 @@
<x-layouts::app :title="__('Platform')">
<div class="space-y-6">
<div class="min-w-0 space-y-6">
<x-wms.page-hero
eyebrow="Platform"
title="Platform Dashboard"
@@ -11,7 +11,7 @@
]"
/>
<section class="grid gap-4 sm:grid-cols-2 xl:grid-cols-5">
<section class="grid min-w-0 grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-5">
@foreach ([['Companies', $companiesCount], ['Active', $activeCompaniesCount], ['Warehouses', $warehousesCount], ['Prices', $pricesCount], ['Licences', $licensesCount]] as [$label, $value])
<article class="rounded-2xl border border-zinc-200 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<p class="text-sm text-zinc-500 dark:text-zinc-400">{{ $label }}</p>
@@ -20,8 +20,8 @@
@endforeach
</section>
<section class="grid gap-5 xl:grid-cols-3">
<article class="rounded-3xl border border-zinc-200 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<section class="grid min-w-0 grid-cols-1 gap-5 xl:grid-cols-3">
<article class="min-w-0 overflow-hidden rounded-3xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900 sm:p-5">
<div class="mb-5 flex items-start justify-between gap-4">
<div>
<p class="text-xs font-bold uppercase tracking-[0.28em] text-cyan-500">Tenant usage</p>
@@ -34,8 +34,9 @@
</span>
</div>
<div class="h-72">
<div class="relative h-64 w-full max-w-full sm:h-72">
<canvas
class="!h-full !w-full max-w-full"
data-wms-chart="bar"
data-labels='@json($companyUsageChartLabels)'
data-series='@json($companyUsageChartSeries)'
@@ -43,7 +44,7 @@
</div>
</article>
<article class="rounded-3xl border border-zinc-200 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<article class="min-w-0 overflow-hidden rounded-3xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900 sm:p-5">
<div class="mb-5 flex items-start justify-between gap-4">
<div>
<p class="text-xs font-bold uppercase tracking-[0.28em] text-cyan-500">Platform mix</p>
@@ -56,8 +57,9 @@
</span>
</div>
<div class="h-72">
<div class="relative h-64 w-full max-w-full sm:h-72">
<canvas
class="!h-full !w-full max-w-full"
data-wms-chart="doughnut"
data-labels='@json($platformMixChartLabels)'
data-values='@json($platformMixChartValues)'
@@ -65,7 +67,7 @@
</div>
</article>
<article class="rounded-3xl border border-zinc-200 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<article class="min-w-0 overflow-hidden rounded-3xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900 sm:p-5">
<div class="mb-5 flex items-start justify-between gap-4">
<div>
<p class="text-xs font-bold uppercase tracking-[0.28em] text-cyan-500">Company status</p>
@@ -78,8 +80,9 @@
</span>
</div>
<div class="h-72">
<div class="relative h-64 w-full max-w-full sm:h-72">
<canvas
class="!h-full !w-full max-w-full"
data-wms-chart="doughnut"
data-labels='@json($companyStatusChartLabels)'
data-values='@json($companyStatusChartValues)'
@@ -88,7 +91,7 @@
</article>
</section>
<section class="rounded-3xl border border-zinc-200 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<section class="min-w-0 overflow-hidden rounded-3xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900 sm:p-5">
<div class="mb-5 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div>
<p class="text-xs font-bold uppercase tracking-[0.28em] text-cyan-500">Pricing</p>
@@ -123,9 +126,9 @@
</div>
</div>
<div class="grid gap-4 md:grid-cols-3">
<div class="grid min-w-0 grid-cols-1 gap-4 md:grid-cols-3">
@foreach ($prices as $price)
<article class="rounded-2xl border border-zinc-200 p-5 dark:border-zinc-800">
<article class="min-w-0 rounded-2xl border border-zinc-200 p-4 dark:border-zinc-800 sm:p-5">
<div class="flex items-start justify-between gap-3">
<div>
<h3 class="text-lg font-bold text-zinc-900 dark:text-white">
@@ -164,8 +167,8 @@
</div>
</section>
<section class="grid gap-5 xl:grid-cols-[360px_minmax(0,1fr)]">
<article class="rounded-3xl border border-zinc-200 bg-white p-5 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
<section class="grid min-w-0 grid-cols-1 gap-5 xl:grid-cols-[360px_minmax(0,1fr)]">
<article class="min-w-0 overflow-hidden rounded-3xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900 sm:p-5">
<div class="mb-5 flex items-start justify-between gap-4">
<div>
<p class="text-xs font-bold uppercase tracking-[0.28em] text-cyan-500">Administration</p>
+29
View File
@@ -930,3 +930,32 @@ select:focus[data-flux-control] {
}
}
}
@layer components {
.wms-content,
.wms-main-panel,
.wms-app-shell {
min-width: 0;
}
.wms-content canvas {
max-width: 100%;
}
@media (max-width: 1023px) {
.wms-content {
width: 100%;
max-width: 100vw;
overflow-x: hidden;
padding-inline: 0.875rem;
}
.wms-page-hero {
max-width: 100%;
}
.wms-sidebar-nav {
-webkit-overflow-scrolling: touch;
}
}
}
@@ -1,4 +1,4 @@
{{-- Top Menu Bar --}}
{{-- Shared Application Top Bar --}}
@php
$user = auth()->user();
$dashboardRoute = Route::has('platform.dashboard') && method_exists($user, 'isMpbaPlatformAdmin') && $user?->isMpbaPlatformAdmin()
@@ -7,23 +7,58 @@
@endphp
<header class="sticky top-0 z-40 border-b border-white/10 bg-[#111118]/95 backdrop-blur" role="banner">
<div class="flex h-16 w-full items-center justify-end px-4 lg:px-6">
<div
id="userControls"
class="relative flex shrink-0 items-center gap-3"
x-data="{ open: false }"
x-on:keydown.escape.window="open = false">
<div class="flex h-16 w-full items-center justify-between gap-3 px-3 sm:px-4 lg:px-6">
<div class="flex min-w-0 items-center gap-3 lg:hidden">
<button
type="button"
class="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/5 px-3 py-2 text-left transition hover:bg-white/10 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2 focus:ring-offset-[#111118]"
class="inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-xl border border-white/10 bg-white/5 text-white transition hover:bg-white/10 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2 focus:ring-offset-[#111118] lg:hidden"
data-wms-mobile-open
aria-label="{{ __('Open navigation menu') }}"
aria-controls="wms-main-sidebar"
aria-expanded="false"
data-test="mobile-menu-button"
>
<i class="fa-solid fa-bars" aria-hidden="true"></i>
</button>
<a
href="{{ $dashboardRoute ? route($dashboardRoute) : '#' }}"
wire:navigate
class="flex min-w-0 items-center gap-3"
aria-label="{{ __('Warehouse Management System') }}"
>
<span class="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-cyan-600 text-sm font-bold text-white shadow-lg shadow-cyan-950/40">
W
</span>
<span class="hidden min-w-0 sm:block">
<span class="block truncate text-sm font-semibold text-white sm:text-base">
{{ __('Warehouse Management System') }}
</span>
<span class="block text-xs font-semibold uppercase tracking-wide text-cyan-400">
MPBA
</span>
</span>
</a>
</div>
<div
id="userControls"
class="relative ml-auto flex shrink-0 items-center gap-3"
x-data="{ open: false }"
x-on:keydown.escape.window="open = false"
>
<button
type="button"
class="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/5 px-2 py-2 text-left transition hover:bg-white/10 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2 focus:ring-offset-[#111118] sm:px-3"
x-on:click="open = ! open"
x-bind:aria-expanded="open.toString()"
aria-haspopup="menu"
data-test="top-user-menu-button"
>
<span class="hidden text-right sm:block">
<span class="block text-sm font-semibold text-white">{{ $user?->name ?? __('User') }}</span>
<span class="block text-xs text-white/50">{{ $user?->email ?? '' }}</span>
<span class="hidden text-right md:block">
<span class="block max-w-48 truncate text-sm font-semibold text-white">{{ $user?->name ?? __('User') }}</span>
<span class="block max-w-48 truncate text-xs text-white/50">{{ $user?->email ?? '' }}</span>
</span>
<span class="flex h-10 w-10 items-center justify-center rounded-full bg-blue-700 text-xs font-bold text-white shadow-lg shadow-blue-950/40">
@@ -32,16 +67,17 @@
<i class="fa-solid fa-chevron-down hidden text-white/50 sm:block" aria-hidden="true"></i>
</button>
<div
x-cloak
x-show="open"
x-transition.origin.top.right
x-on:click.outside="open = false"
class="absolute right-0 top-full mt-2 w-72 overflow-hidden rounded-2xl border border-white/10 bg-[#181820] shadow-2xl shadow-black/40"
class="absolute right-0 top-full mt-2 w-[calc(100vw-1.5rem)] max-w-72 overflow-hidden rounded-2xl border border-white/10 bg-[#181820] shadow-2xl shadow-black/40 sm:w-72"
role="menu"
>
<div class="border-b border-white/10 px-4 py-3">
<div class="text-sm font-semibold text-white">{{ $user?->name ?? __('User') }}</div>
<div class="truncate text-sm font-semibold text-white">{{ $user?->name ?? __('User') }}</div>
<div class="truncate text-xs text-white/50">{{ $user?->email ?? '' }}</div>
</div>