Refonte design V1

This commit is contained in:
mrKamoo
2026-10-02 13:06:14 +02:00
parent 429dd9de5e
commit 83e1ce00a9
20 changed files with 1403 additions and 531 deletions
+114 -78
View File
@@ -1,93 +1,129 @@
@php
$user = auth()->user();
$hasNav = (bool) $user?->role;
$isAdmin = $hasNav && $user->hasRole('admin');
$org = $user?->tenant?->name ?? config('signature.default_tenant.name');
$roleLabel = ['superadmin' => 'Super-administrateur', 'admin' => 'Administrateur', 'editor' => 'Éditeur'][$user?->role] ?? 'Agent';
$initials = $user ? mb_strtoupper(collect(preg_split('/[\s\-]+/u', trim($user->name)))->filter()->take(2)->map(fn ($w) => mb_substr($w, 0, 1))->join('')) : '';
// Version des fichiers statiques = date de modification : le navigateur recharge après chaque mise à jour.
$asset = fn (string $path) => asset($path).'?v='.(@filemtime(public_path($path)) ?: '1');
$nav = fn (string $route, string $pattern, string $icon, string $label) => compact('route', 'pattern', 'icon', 'label');
$links = $hasNav ? array_filter([
['group' => null, 'items' => [$nav('admin.dashboard', 'admin.dashboard', 'layout-dashboard', 'Tableau de bord')]],
['group' => 'Contenu', 'items' => [
$nav('admin.templates.index', 'admin.templates.*', 'file-code', 'Templates'),
$nav('admin.images.index', 'admin.images.*', 'images', 'Images'),
$nav('admin.campaigns.index', 'admin.campaigns.*', 'megaphone', 'Campagnes'),
]],
['group' => 'Diffusion', 'items' => array_values(array_filter([
$isAdmin ? $nav('admin.assignments.index', 'admin.assignments.*', 'users', 'Affectations') : null,
$isAdmin ? $nav('admin.fields.index', 'admin.fields.*', 'sliders-horizontal', 'Champs agents') : null,
$nav('admin.agents.index', 'admin.agents.*', 'contact', 'Agents'),
]))],
]) : [];
@endphp
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title', 'Signatures CABM')</title>
<style>
:root { --ink:#1b1b1b; --muted:#5c5c5c; --line:#dcdcdc; --accent:#0b5cad; --accent-soft:#e7f0fa; --bg:#f6f7f9; --ok:#1f7a3d; --warn:#9a5b00; --danger:#b3261e; }
* { box-sizing: border-box; }
body { margin:0; font-family:"Segoe UI", system-ui, sans-serif; color:var(--ink); background:var(--bg); font-size:15px; }
header { background:#fff; border-bottom:1px solid var(--line); padding:0 24px; display:flex; justify-content:space-between; align-items:center; gap:16px; flex-wrap:wrap; }
header .brand { color:var(--accent); font-weight:600; padding:14px 0; }
nav a { display:inline-block; padding:14px 10px; color:var(--muted); text-decoration:none; border-bottom:2px solid transparent; }
nav a.active, nav a:hover { color:var(--ink); border-bottom-color:var(--accent); }
main { max-width:1200px; margin:24px auto; padding:0 16px; }
h1 { font-size:22px; margin:0 0 16px; }
h2 { font-size:17px; margin:0 0 12px; }
.card { background:#fff; border:1px solid var(--line); border-radius:8px; padding:20px; margin-bottom:16px; }
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:12px; }
.split { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:16px; align-items:start; }
@media (max-width: 900px) { .split { grid-template-columns:1fr; } }
.stat { font-size:28px; font-weight:600; }
.muted { color:var(--muted); font-size:13px; }
.row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.spacer { flex:1; }
table.list { width:100%; border-collapse:collapse; }
table.list th, table.list td { text-align:left; padding:8px; border-bottom:1px solid var(--line); vertical-align:middle; }
table.list th { font-size:13px; color:var(--muted); font-weight:600; }
label { display:block; font-weight:600; font-size:13px; margin:12px 0 4px; }
input[type=text], input[type=number], input[type=search], input[type=url], input[type=datetime-local], select, textarea { width:100%; padding:8px 10px; border:1px solid var(--line); border-radius:6px; font:inherit; background:#fff; }
textarea.code { font-family:Consolas, "Cascadia Mono", monospace; font-size:13px; line-height:1.45; min-height:260px; resize:vertical; tab-size:2; }
input.small { width:90px; }
.btn { display:inline-block; background:var(--accent); color:#fff; border:1px solid var(--accent); border-radius:6px; padding:7px 14px; font:inherit; font-size:14px; cursor:pointer; text-decoration:none; }
.btn.secondary { background:#fff; color:var(--accent); }
.btn.danger { background:#fff; color:var(--danger); border-color:var(--danger); }
.btn.small { padding:4px 10px; font-size:13px; }
.btn:disabled { opacity:.5; cursor:default; }
button.link { background:none; border:0; color:var(--accent); cursor:pointer; font:inherit; padding:0; }
.badge { display:inline-block; padding:2px 8px; border-radius:10px; font-size:12px; background:var(--accent-soft); color:var(--accent); }
.badge.ok { background:#e5f4ea; color:var(--ok); }
.badge.warn { background:#fdf1dc; color:var(--warn); }
.flash { padding:10px 14px; border-radius:6px; margin-bottom:16px; background:#e5f4ea; color:var(--ok); border:1px solid #bfe3cb; }
.errors { padding:10px 14px; border-radius:6px; margin-bottom:16px; background:#fde8e6; color:var(--danger); border:1px solid #f5c2bd; }
.notice { padding:10px 14px; border-radius:6px; margin-bottom:16px; background:#fdf1dc; color:var(--warn); border:1px solid #f3d9a4; }
label.check { font-weight:400; font-size:14px; margin:4px 0; }
label.check input { margin-right:6px; }
fieldset { border:1px solid var(--line); border-radius:6px; margin:12px 0 0; padding:8px 12px 12px; }
legend { font-weight:600; font-size:13px; padding:0 4px; }
.scroll { max-height:220px; overflow:auto; }
.chips button { background:var(--accent-soft); color:var(--accent); border:0; border-radius:12px; padding:3px 9px; margin:2px; font:12px Consolas, monospace; cursor:pointer; }
iframe.sig { width:100%; min-height:220px; border:1px solid var(--line); border-radius:6px; background:#fff; resize:vertical; }
form.inline { display:inline; }
</style>
<title>@yield('title', 'Signatures')</title>
<script>document.documentElement.classList.add('js');</script>
<link rel="preload" href="{{ asset('fonts/atkinson-next.woff2') }}" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="{{ $asset('css/app.css') }}">
</head>
<body>
<header>
<span class="brand">Signatures CABM</span>
<a class="skip" href="#main">Aller au contenu</a>
<div @class(['app', 'app--nav' => $hasNav])>
@auth
@if (auth()->user()->role)
<nav>
<a href="{{ route('admin.dashboard') }}" @class(['active' => request()->routeIs('admin.dashboard')])>Tableau de bord</a>
<a href="{{ route('admin.templates.index') }}" @class(['active' => request()->routeIs('admin.templates.*')])>Templates</a>
<a href="{{ route('admin.images.index') }}" @class(['active' => request()->routeIs('admin.images.*')])>Images</a>
<a href="{{ route('admin.campaigns.index') }}" @class(['active' => request()->routeIs('admin.campaigns.*')])>Campagnes</a>
@if (auth()->user()->hasRole('admin'))
<a href="{{ route('admin.assignments.index') }}" @class(['active' => request()->routeIs('admin.assignments.*')])>Affectations</a>
<a href="{{ route('admin.fields.index') }}" @class(['active' => request()->routeIs('admin.fields.*')])>Champs agents</a>
@endif
<a href="{{ route('admin.agents.index') }}" @class(['active' => request()->routeIs('admin.agents.*')])>Agents</a>
<a href="{{ route('portal.home') }}" @class(['active' => request()->routeIs('portal.*')])>Ma signature</a>
</nav>
@if ($hasNav)
<aside class="sidebar">
<a class="brand" href="{{ route('admin.dashboard') }}">
<span class="brand-name">Signatures</span>
<svg class="brand-stroke" viewBox="0 0 104 12" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true">
<path d="M2 8c9-6 15-6 20-1s9 4 14-1 10-5 15 0 9 4 14 0 11-5 17-1 12 4 20-1"/>
</svg>
<span class="brand-org">{{ $org }}</span>
</a>
<nav class="nav" aria-label="Navigation principale">
@foreach ($links as $section)
@if ($section['group'])<p class="nav-group">{{ $section['group'] }}</p>@endif
@foreach ($section['items'] as $item)
<a href="{{ route($item['route']) }}" @class(['nav-link', 'active' => request()->routeIs($item['pattern'])])
@if (request()->routeIs($item['pattern'])) aria-current="page" @endif>
<x-icon :name="$item['icon']" />{{ $item['label'] }}
</a>
@endforeach
@endforeach
</nav>
<div class="sidebar-foot">
<a href="{{ route('portal.home') }}" @class(['nav-link', 'active' => request()->routeIs('portal.*')])>
<x-icon name="signature" />Ma signature
</a>
<div class="user">
<span class="avatar" aria-hidden="true">{{ $initials }}</span>
<span>
<span class="user-name">{{ $user->name }}</span>
<span class="user-role">{{ $roleLabel }}</span>
</span>
<form method="post" action="{{ route('logout') }}">@csrf
<button class="icon-btn" type="submit" title="Se déconnecter" aria-label="Se déconnecter"><x-icon name="log-out" /></button>
</form>
</div>
</div>
</aside>
@else
<header class="topbar">
<a class="brand" href="{{ route('portal.home') }}"><span class="brand-name">Signatures</span></a>
<span class="muted" style="color:inherit">{{ $org }}</span>
<div class="user">
<span class="avatar" aria-hidden="true">{{ $initials }}</span>
<span class="user-name">{{ $user->name }}</span>
<form method="post" action="{{ route('logout') }}">@csrf
<button class="icon-btn" type="submit" title="Se déconnecter" aria-label="Se déconnecter"><x-icon name="log-out" /></button>
</form>
</div>
</header>
@endif
<form method="post" action="{{ route('logout') }}" class="row">@csrf
<span class="muted">{{ auth()->user()->name }}@if (auth()->user()->role) · {{ auth()->user()->role }}@endif</span>
<button class="link" type="submit">Se déconnecter</button>
</form>
@endauth
</header>
<main>
@if (session('status'))
<div class="flash" role="status">{{ session('status') }}</div>
@endif
@if ($errors->any())
<div class="errors" role="alert">
@foreach ($errors->all() as $error)<div>{{ $error }}</div>@endforeach
<div class="content">
<main id="main">
@if (session('status'))
<div class="toasts">
<div class="toast" role="status" data-toast>
<x-icon name="circle-check" />
<span>{{ session('status') }}</span>
<button class="icon-btn" type="button" data-dismiss aria-label="Fermer"><x-icon name="x" :size="16" /></button>
</div>
</div>
@endif
@if ($errors->any())
<div class="alert errors" role="alert">
<x-icon name="triangle-alert" />
<div>@foreach ($errors->all() as $error)<div>{{ $error }}</div>@endforeach</div>
</div>
@endif
@yield('content')
</main>
</div>
</div>
<dialog class="dialog" id="confirm-dialog" aria-labelledby="confirm-title">
<form method="dialog">
<div class="dialog-body">
<h2 id="confirm-title">Confirmer l'action</h2>
<p data-message></p>
</div>
@endif
@yield('content')
</main>
<div class="dialog-actions">
<button class="btn secondary" value="cancel" autofocus>Annuler</button>
<button class="btn" value="ok" data-ok>Confirmer</button>
</div>
</form>
</dialog>
<script src="{{ $asset('js/app.js') }}" defer></script>
@stack('scripts')
</body>
</html>