Intégration fonctionnalites V1
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
<script setup>
|
||||
import ApplicationLogo from '@/Components/ApplicationLogo.vue';
|
||||
import LaverieLogo from '@/Components/LaverieLogo.vue';
|
||||
import Dropdown from '@/Components/Dropdown.vue';
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
import { computed } from 'vue';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
defineProps({
|
||||
title: {
|
||||
@@ -10,120 +11,308 @@ defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const STORAGE_KEY = 'supervisor-sidebar-collapsed';
|
||||
|
||||
const page = usePage();
|
||||
const supervisor = computed(() => page.props.auth.supervisor);
|
||||
const sidebarCollapsed = ref(false);
|
||||
|
||||
const navItems = [
|
||||
{ label: 'Tableau de bord', route: 'supervisor.dashboard', icon: 'dashboard' },
|
||||
{ label: 'Machines', route: 'supervisor.machines.index', icon: 'machines' },
|
||||
{ label: 'Réservations', route: 'supervisor.bookings.index', icon: 'bookings' },
|
||||
{ label: 'Lavages', route: 'supervisor.washes.index', icon: 'washes' },
|
||||
{ label: 'Tarifs', route: 'supervisor.pricing.index', icon: 'pricing' },
|
||||
{ label: 'Promotions', route: 'supervisor.promotions.index', icon: 'promotions' },
|
||||
onMounted(() => {
|
||||
sidebarCollapsed.value = localStorage.getItem(STORAGE_KEY) === '1';
|
||||
});
|
||||
|
||||
watch(sidebarCollapsed, (value) => {
|
||||
localStorage.setItem(STORAGE_KEY, value ? '1' : '0');
|
||||
});
|
||||
|
||||
const toggleSidebar = () => {
|
||||
sidebarCollapsed.value = !sidebarCollapsed.value;
|
||||
};
|
||||
const navGroups = [
|
||||
{
|
||||
label: 'Vue d\'ensemble',
|
||||
items: [
|
||||
{ label: 'Tableau de bord', route: 'supervisor.dashboard', icon: 'dashboard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Exploitation',
|
||||
items: [
|
||||
{ label: 'Enseignes', route: 'supervisor.organizations.index', icon: 'organizations' },
|
||||
{ label: 'Machines', route: 'supervisor.machines.index', icon: 'machines' },
|
||||
{ label: 'Réservations', route: 'supervisor.bookings.index', icon: 'bookings' },
|
||||
{ label: 'Lavages', route: 'supervisor.washes.index', icon: 'washes' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Commercial',
|
||||
items: [
|
||||
{ label: 'Tarifs', route: 'supervisor.pricing.index', icon: 'pricing' },
|
||||
{ label: 'Promotions', route: 'supervisor.promotions.index', icon: 'promotions' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const roleLabels = {
|
||||
platform_admin: 'Administrateur',
|
||||
owner: 'Propriétaire',
|
||||
manager: 'Gestionnaire',
|
||||
viewer: 'Lecture seule',
|
||||
};
|
||||
|
||||
const initials = computed(() => {
|
||||
const name = supervisor.value?.name?.trim() ?? '';
|
||||
if (!name) return '?';
|
||||
const parts = name.split(/\s+/);
|
||||
if (parts.length >= 2) {
|
||||
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
||||
}
|
||||
return name.slice(0, 2).toUpperCase();
|
||||
});
|
||||
|
||||
const isActive = (routeName) => {
|
||||
if (routeName === 'supervisor.machines.index') {
|
||||
return route().current('supervisor.machines.*');
|
||||
}
|
||||
if (routeName === 'supervisor.organizations.index') {
|
||||
return route().current('supervisor.organizations.*');
|
||||
}
|
||||
return route().current(routeName);
|
||||
};
|
||||
|
||||
const iconPaths = {
|
||||
dashboard: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6',
|
||||
organizations: 'M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4',
|
||||
machines: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15',
|
||||
bookings: 'M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z',
|
||||
washes: 'M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z',
|
||||
pricing: 'M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
promotions: 'M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-100">
|
||||
<div class="min-h-screen bg-slate-50">
|
||||
<div class="flex min-h-screen">
|
||||
<!-- Sidebar -->
|
||||
<aside class="hidden w-64 flex-shrink-0 bg-indigo-900 lg:flex lg:flex-col">
|
||||
<div class="flex h-16 items-center px-6">
|
||||
<Link :href="route('supervisor.dashboard')" class="flex items-center gap-2">
|
||||
<ApplicationLogo class="h-8 w-auto fill-current text-white" />
|
||||
<span class="text-lg font-semibold text-white">Laverie</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav class="mt-4 flex-1 space-y-1 px-3">
|
||||
<Link
|
||||
v-for="item in navItems"
|
||||
:key="item.route"
|
||||
:href="route(item.route)"
|
||||
class="flex items-center rounded-md px-3 py-2 text-sm font-medium transition"
|
||||
:class="
|
||||
isActive(item.route)
|
||||
? 'bg-indigo-800 text-white'
|
||||
: 'text-indigo-100 hover:bg-indigo-800 hover:text-white'
|
||||
"
|
||||
<aside
|
||||
class="hidden flex-shrink-0 transition-[width] duration-300 ease-in-out lg:flex lg:flex-col"
|
||||
:class="sidebarCollapsed ? 'w-[4.75rem]' : 'w-72'"
|
||||
>
|
||||
<div class="flex h-full flex-col bg-gradient-to-b from-slate-900 via-slate-900 to-indigo-950 shadow-xl">
|
||||
<!-- Logo + toggle -->
|
||||
<div
|
||||
class="flex items-center border-b border-white/5"
|
||||
:class="sidebarCollapsed ? 'flex-col justify-center gap-2 py-3 px-2' : 'h-16 justify-between px-4'"
|
||||
>
|
||||
{{ item.label }}
|
||||
</Link>
|
||||
</nav>
|
||||
<button
|
||||
v-if="sidebarCollapsed"
|
||||
type="button"
|
||||
class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg text-slate-400 transition hover:bg-white/10 hover:text-white"
|
||||
title="Agrandir le menu"
|
||||
@click="toggleSidebar"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="border-t border-indigo-800 p-4">
|
||||
<p class="truncate text-sm font-medium text-white">
|
||||
{{ supervisor?.name }}
|
||||
</p>
|
||||
<p class="truncate text-xs text-indigo-300">
|
||||
{{ supervisor?.email }}
|
||||
</p>
|
||||
<Link
|
||||
:href="route('supervisor.dashboard')"
|
||||
class="flex items-center rounded-xl transition hover:opacity-90"
|
||||
:class="sidebarCollapsed ? 'justify-center' : 'gap-3'"
|
||||
:title="sidebarCollapsed ? 'Laverie' : undefined"
|
||||
>
|
||||
<div class="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-white/10 ring-1 ring-white/20">
|
||||
<LaverieLogo class="h-5 w-5 text-white" />
|
||||
</div>
|
||||
<div v-show="!sidebarCollapsed" class="min-w-0">
|
||||
<span class="text-base font-semibold tracking-tight text-white">Laverie</span>
|
||||
<p class="text-[10px] font-medium uppercase tracking-widest text-indigo-300/80">
|
||||
Back-office
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<button
|
||||
v-if="!sidebarCollapsed"
|
||||
type="button"
|
||||
class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg text-slate-400 transition hover:bg-white/10 hover:text-white"
|
||||
title="Réduire le menu"
|
||||
@click="toggleSidebar"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav
|
||||
class="mt-2 flex-1 space-y-4 overflow-y-auto pb-4"
|
||||
:class="sidebarCollapsed ? 'px-2' : 'px-3'"
|
||||
>
|
||||
<div v-for="(group, groupIndex) in navGroups" :key="group.label">
|
||||
<p
|
||||
v-show="!sidebarCollapsed"
|
||||
class="mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-slate-500"
|
||||
>
|
||||
{{ group.label }}
|
||||
</p>
|
||||
<div
|
||||
v-if="sidebarCollapsed && groupIndex > 0"
|
||||
class="mx-auto mb-2 h-px w-8 bg-white/10"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div class="space-y-0.5">
|
||||
<Link
|
||||
v-for="item in group.items"
|
||||
:key="item.route"
|
||||
:href="route(item.route)"
|
||||
class="group relative flex items-center rounded-xl text-sm font-medium transition-all duration-150"
|
||||
:class="[
|
||||
sidebarCollapsed ? 'justify-center px-0 py-2.5' : 'gap-3 px-3 py-2.5',
|
||||
isActive(item.route)
|
||||
? 'bg-white/10 text-white shadow-sm ring-1 ring-white/10'
|
||||
: 'text-slate-400 hover:bg-white/5 hover:text-white',
|
||||
]"
|
||||
:title="sidebarCollapsed ? item.label : undefined"
|
||||
>
|
||||
<span
|
||||
class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg transition-colors"
|
||||
:class="
|
||||
isActive(item.route)
|
||||
? 'bg-indigo-500/30 text-indigo-200'
|
||||
: 'bg-white/5 text-slate-500 group-hover:bg-white/10 group-hover:text-slate-300'
|
||||
"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
:d="iconPaths[item.icon]"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span v-show="!sidebarCollapsed">{{ item.label }}</span>
|
||||
<span
|
||||
v-if="isActive(item.route) && !sidebarCollapsed"
|
||||
class="ml-auto h-1.5 w-1.5 rounded-full bg-indigo-400"
|
||||
/>
|
||||
|
||||
<!-- Tooltip mode réduit -->
|
||||
<span
|
||||
v-if="sidebarCollapsed"
|
||||
class="pointer-events-none absolute left-full z-50 ml-3 hidden whitespace-nowrap rounded-lg bg-slate-800 px-2.5 py-1.5 text-xs font-medium text-white shadow-lg ring-1 ring-white/10 group-hover:block"
|
||||
>
|
||||
{{ item.label }}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main content -->
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div class="flex min-w-0 flex-1 flex-col">
|
||||
<!-- Header -->
|
||||
<header class="border-b border-gray-200 bg-white shadow-sm">
|
||||
<header class="sticky top-0 z-10 border-b border-slate-200/80 bg-white/80 backdrop-blur-md">
|
||||
<div class="flex h-16 items-center justify-between px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex min-w-0 items-center gap-4">
|
||||
<Link
|
||||
:href="route('supervisor.dashboard')"
|
||||
class="text-lg font-semibold text-gray-800 lg:hidden"
|
||||
class="text-lg font-semibold text-slate-800 lg:hidden"
|
||||
>
|
||||
Laverie
|
||||
</Link>
|
||||
<h1 v-if="title" class="text-lg font-semibold text-gray-800">
|
||||
{{ title }}
|
||||
</h1>
|
||||
<div v-if="title" class="min-w-0">
|
||||
<h1 class="truncate text-lg font-semibold text-slate-900">
|
||||
{{ title }}
|
||||
</h1>
|
||||
</div>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="hidden text-sm text-gray-600 sm:inline">
|
||||
{{ supervisor?.name }}
|
||||
</span>
|
||||
<Link
|
||||
:href="route('logout')"
|
||||
method="post"
|
||||
as="button"
|
||||
class="rounded-md bg-gray-100 px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-200"
|
||||
>
|
||||
Déconnexion
|
||||
</Link>
|
||||
<div class="flex items-center gap-3">
|
||||
<slot name="actions" />
|
||||
<Dropdown align="right" width="64" content-classes="overflow-hidden rounded-xl bg-white py-0 shadow-xl ring-1 ring-slate-200">
|
||||
<template #trigger>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-full bg-gradient-to-br from-indigo-500 to-violet-600 text-xs font-bold text-white shadow-sm ring-2 ring-white transition hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-indigo-500/30"
|
||||
:title="supervisor?.name"
|
||||
>
|
||||
{{ initials }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="border-b border-slate-100 px-5 py-4">
|
||||
<p class="truncate text-base font-semibold text-slate-900">
|
||||
{{ supervisor?.name }}
|
||||
</p>
|
||||
<p class="mt-0.5 truncate text-sm text-slate-500">
|
||||
{{ supervisor?.email }}
|
||||
</p>
|
||||
<p class="mt-2 inline-flex rounded-full bg-indigo-50 px-2.5 py-1 text-xs font-medium text-indigo-700">
|
||||
{{ roleLabels[supervisor?.role] ?? supervisor?.role }}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
:href="route('logout')"
|
||||
method="post"
|
||||
as="button"
|
||||
class="flex w-full items-center gap-2.5 px-5 py-3 text-sm font-medium text-red-600 transition hover:bg-red-50"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.75">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
/>
|
||||
</svg>
|
||||
Déconnexion
|
||||
</Link>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile nav -->
|
||||
<nav class="flex gap-1 overflow-x-auto border-t border-gray-100 px-4 py-2 lg:hidden">
|
||||
<Link
|
||||
v-for="item in navItems"
|
||||
:key="item.route"
|
||||
:href="route(item.route)"
|
||||
class="whitespace-nowrap rounded-md px-3 py-1.5 text-xs font-medium"
|
||||
:class="
|
||||
isActive(item.route)
|
||||
? 'bg-indigo-100 text-indigo-800'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
"
|
||||
>
|
||||
{{ item.label }}
|
||||
</Link>
|
||||
<nav class="flex gap-1 overflow-x-auto border-t border-slate-100 px-3 py-2 lg:hidden">
|
||||
<template v-for="group in navGroups" :key="group.label">
|
||||
<Link
|
||||
v-for="item in group.items"
|
||||
:key="item.route"
|
||||
:href="route(item.route)"
|
||||
class="whitespace-nowrap rounded-lg px-3 py-1.5 text-xs font-medium transition"
|
||||
:class="
|
||||
isActive(item.route)
|
||||
? 'bg-indigo-50 text-indigo-700 ring-1 ring-indigo-200'
|
||||
: 'text-slate-600 hover:bg-slate-100'
|
||||
"
|
||||
>
|
||||
{{ item.label }}
|
||||
</Link>
|
||||
</template>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- Flash message -->
|
||||
<div
|
||||
v-if="page.props.flash?.success"
|
||||
class="mx-4 mt-4 rounded-md bg-green-50 px-4 py-3 text-sm text-green-800 sm:mx-6 lg:mx-8"
|
||||
class="mx-4 mt-4 flex items-center gap-2 rounded-xl border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800 sm:mx-6 lg:mx-8"
|
||||
>
|
||||
<svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
{{ page.props.flash.success }}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user