feat: Layout principal et composants de base

- app.vue : Layout principal avec navbar et navigation
- ToastContainer.vue : Système de notifications toast
- app.css : Styles globaux avec DaisyUI et accessibilité
- Configuration des paramètres d'affichage (zoom, densité, contraste)
- Navigation responsive avec menu déroulant
- Bouton 'Nouveau' avec actions rapides
This commit is contained in:
Matthieu
2025-07-30 08:16:33 +02:00
parent 1ca5c347b7
commit 36b7ce93ec
3 changed files with 530 additions and 0 deletions

138
app/app.vue Normal file
View File

@@ -0,0 +1,138 @@
<template>
<div class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
<!-- Navbar -->
<div class="navbar bg-base-100 shadow-lg">
<div class="navbar-start">
<div class="dropdown">
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16"></path>
</svg>
</div>
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
<li><NuxtLink to="/">Dashboard</NuxtLink></li>
<li><NuxtLink to="/machines">Machines</NuxtLink></li>
<li><NuxtLink to="/types">Types de Machines</NuxtLink></li>
<li><NuxtLink to="/sites">Sites</NuxtLink></li>
<li><NuxtLink to="/generator">Générateur</NuxtLink></li>
<li><NuxtLink to="/documents">Documents</NuxtLink></li>
</ul>
</div>
<div class="flex items-center space-x-3">
<div class="avatar placeholder">
<div class="bg-primary text-primary-content rounded-lg w-10">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path>
</svg>
</div>
</div>
<NuxtLink to="/" class="btn btn-ghost text-xl">Inventaire Pro</NuxtLink>
</div>
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal px-1">
<li><NuxtLink to="/" class="link link-hover">Dashboard</NuxtLink></li>
<li><NuxtLink to="/machines" class="link link-hover">Machines</NuxtLink></li>
<li><NuxtLink to="/types" class="link link-hover">Types de Machines</NuxtLink></li>
<li><NuxtLink to="/sites" class="link link-hover">Sites</NuxtLink></li>
<li><NuxtLink to="/generator" class="link link-hover">Générateur</NuxtLink></li>
<li><NuxtLink to="/documents" class="link link-hover">Documents</NuxtLink></li>
</ul>
</div>
<div class="navbar-end">
<div class="flex items-center gap-2">
<!-- Bouton paramètres d'affichage -->
<button
@click="openDisplaySettings"
class="btn btn-ghost btn-circle"
title="Paramètres d'affichage"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
</button>
<!-- Menu Nouveau -->
<div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-primary">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
Nouveau
</div>
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
<li>
<NuxtLink to="/machines?add=true" class="flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
</svg>
Nouvelle Machine
</NuxtLink>
</li>
<li>
<NuxtLink to="/generator" class="flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
Nouveau Type
</NuxtLink>
</li>
<li>
<NuxtLink to="/sites?add=true" class="flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
Nouveau Site
</NuxtLink>
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Page Content -->
<NuxtPage />
<!-- Toast Notifications -->
<ToastContainer />
<!-- Paramètres d'affichage -->
<DisplaySettings
:is-open="displaySettingsOpen"
@close="closeDisplaySettings"
@update-settings="handleSettingsUpdate"
/>
<!-- Footer -->
<footer class="footer p-4 bg-neutral text-neutral-content">
<div class="items-center grid-flow-col">
<p>@Malio 2025</p>
</div>
</footer>
</div>
</template>
<script setup>
import { ref } from 'vue'
// État du modal des paramètres d'affichage
const displaySettingsOpen = ref(false)
// Ouvrir les paramètres d'affichage
const openDisplaySettings = () => {
displaySettingsOpen.value = true
}
// Fermer les paramètres d'affichage
const closeDisplaySettings = () => {
displaySettingsOpen.value = false
}
// Gérer les mises à jour des paramètres
const handleSettingsUpdate = (settings) => {
console.log('Paramètres d\'affichage mis à jour:', settings)
}
</script>

297
app/assets/app.css Normal file
View File

@@ -0,0 +1,297 @@
@import "tailwindcss";
@plugin "daisyui";
@plugin "daisyui/theme" {
name: "mytheme";
default: true; /* set as default */
prefersdark: false; /* set as default dark mode (prefers-color-scheme:dark) */
color-scheme: light; /* color of browser-provided UI */
--color-base-100: oklch(98% 0.02 240);
--color-base-200: oklch(95% 0.03 240);
--color-base-300: oklch(92% 0.04 240);
--color-base-content: oklch(20% 0.05 240);
--color-primary: oklch(55% 0.3 240);
--color-primary-content: oklch(98% 0.01 240);
--color-secondary: oklch(70% 0.25 200);
--color-secondary-content: oklch(98% 0.01 200);
--color-accent: oklch(65% 0.25 160);
--color-accent-content: oklch(98% 0.01 160);
--color-neutral: oklch(50% 0.05 240);
--color-neutral-content: oklch(98% 0.01 240);
--color-info: oklch(70% 0.2 220);
--color-info-content: oklch(98% 0.01 220);
--color-success: oklch(65% 0.25 140);
--color-success-content: oklch(98% 0.01 140);
--color-warning: oklch(80% 0.25 80);
--color-warning-content: oklch(20% 0.05 80);
--color-error: oklch(65% 0.3 30);
--color-error-content: oklch(98% 0.01 30);
/* border radius */
--radius-selector: 1rem;
--radius-field: 0.25rem;
--radius-box: 0.5rem;
/* base sizes */
--size-selector: 0.25rem;
--size-field: 0.25rem;
/* border size */
--border: 1px;
/* effects */
--depth: 1;
--noise: 0;
}
/* Styles pour l'accessibilité et les paramètres d'affichage */
:root {
--spacing-xs: 0.5rem;
--spacing-sm: 0.75rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
}
/* Densité compacte */
.density-compact {
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 0.75rem;
--spacing-lg: 1rem;
--spacing-xl: 1.25rem;
}
/* Densité confortable (défaut) */
.density-comfortable {
--spacing-xs: 0.5rem;
--spacing-sm: 0.75rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
}
/* Densité espacée */
.density-spacious {
--spacing-xs: 0.75rem;
--spacing-sm: 1rem;
--spacing-md: 1.5rem;
--spacing-lg: 2rem;
--spacing-xl: 3rem;
}
/* Contraste élevé avec DaisyUI */
.contrast-high .btn {
@apply border-2;
}
.contrast-high .input {
@apply border-2;
}
.contrast-high .select {
@apply border-2;
}
.contrast-high .textarea {
@apply border-2;
}
.contrast-high .modal-box {
@apply border-2 border-base-content;
}
/* Amélioration de l'accessibilité */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Focus visible pour l'accessibilité */
*:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* Styles pour les boutons de paramètres */
.btn-circle {
transition: all 0.2s ease-in-out;
}
.btn-circle:hover {
transform: scale(1.05);
}
.btn-circle:active {
transform: scale(0.95);
}
/* Animation pour le modal */
.modal {
transition: opacity 0.3s ease-in-out;
font-size: 100% !important; /* Force la taille normale pour le modal */
transform: none !important; /* Empêche les transformations */
scale: 1 !important; /* Force l'échelle à 1 */
}
.modal.modal-open {
animation: modalFadeIn 0.3s ease-in-out;
}
/* S'assurer que le contenu du modal garde une taille normale */
.modal-box {
font-size: 100% !important;
transform: none !important;
scale: 1 !important;
width: auto !important;
max-width: 500px !important;
}
.modal .form-control {
font-size: 100% !important;
transform: none !important;
}
.modal .btn {
font-size: 100% !important;
transform: none !important;
padding: 0.5rem 1rem !important;
height: auto !important;
min-height: 2.5rem !important;
}
.modal .input {
font-size: 100% !important;
transform: none !important;
height: auto !important;
min-height: 2.5rem !important;
}
.modal .select {
font-size: 100% !important;
transform: none !important;
height: auto !important;
min-height: 2.5rem !important;
}
.modal .textarea {
font-size: 100% !important;
transform: none !important;
min-height: 4rem !important;
}
.modal .range {
font-size: 100% !important;
transform: none !important;
height: auto !important;
min-height: 1.5rem !important;
}
.modal .label {
font-size: 100% !important;
transform: none !important;
}
.modal .label-text {
font-size: 100% !important;
transform: none !important;
}
.modal .label-text-alt {
font-size: 100% !important;
transform: none !important;
}
.modal .checkbox {
font-size: 100% !important;
transform: none !important;
width: 1rem !important;
height: 1rem !important;
}
.modal .text-xs {
font-size: 0.75rem !important;
transform: none !important;
}
.modal .text-sm {
font-size: 0.875rem !important;
transform: none !important;
}
.modal .text-lg {
font-size: 1.125rem !important;
transform: none !important;
}
.modal .font-bold {
font-weight: 700 !important;
transform: none !important;
}
.modal .font-medium {
font-weight: 500 !important;
transform: none !important;
}
/* Empêcher les héritages de taille */
.modal * {
font-size: inherit !important;
transform: none !important;
scale: 1 !important;
}
@keyframes modalFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* Styles pour les contrôles de zoom */
.range {
transition: all 0.2s ease-in-out;
}
.range::-webkit-slider-thumb {
transition: all 0.2s ease-in-out;
}
.range::-webkit-slider-thumb:hover {
transform: scale(1.1);
}
/* Amélioration de la lisibilité */
.text-sm {
line-height: 1.5;
}
.text-xs {
line-height: 1.4;
}
/* Espacement adaptatif */
.p-1 { padding: var(--spacing-xs); }
.p-2 { padding: var(--spacing-sm); }
.p-3 { padding: var(--spacing-md); }
.p-4 { padding: var(--spacing-lg); }
.p-5 { padding: var(--spacing-xl); }
.m-1 { margin: var(--spacing-xs); }
.m-2 { margin: var(--spacing-sm); }
.m-3 { margin: var(--spacing-md); }
.m-4 { margin: var(--spacing-lg); }
.m-5 { margin: var(--spacing-xl); }
.gap-1 { gap: var(--spacing-xs); }
.gap-2 { gap: var(--spacing-sm); }
.gap-3 { gap: var(--spacing-md); }
.gap-4 { gap: var(--spacing-lg); }
.gap-5 { gap: var(--spacing-xl); }

View File

@@ -0,0 +1,95 @@
<template>
<div class="toast-container fixed top-4 right-4 z-50 space-y-2 pointer-events-none">
<TransitionGroup name="toast">
<div
v-for="toast in toasts"
:key="toast.id"
class="toast-item"
:class="[
'transform transition-all duration-300 ease-in-out',
toast.visible ? 'translate-x-0 opacity-100 pointer-events-auto' : 'translate-x-full opacity-0 pointer-events-none'
]"
>
<div
class="alert shadow-lg max-w-sm"
:class="getToastClasses(toast.type)"
>
<div class="flex items-center gap-2">
<!-- Icon -->
<div class="flex-shrink-0">
<svg v-if="toast.type === 'success'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<svg v-else-if="toast.type === 'error'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
<svg v-else-if="toast.type === 'warning'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
</svg>
<svg v-else class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<!-- Message -->
<div class="flex-1">
<span class="text-sm font-medium">{{ toast.message }}</span>
</div>
<!-- Close button -->
<button
@click="removeToast(toast.id)"
class="btn btn-ghost btn-xs"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
</div>
</div>
</TransitionGroup>
</div>
</template>
<script setup>
import { useToast } from '~/composables/useToast'
const { toasts, removeToast } = useToast()
const getToastClasses = (type) => {
switch (type) {
case 'success':
return 'alert-success'
case 'error':
return 'alert-error'
case 'warning':
return 'alert-warning'
case 'info':
return 'alert-info'
default:
return 'alert-info'
}
}
</script>
<style scoped>
.toast-enter-active,
.toast-leave-active {
transition: all 0.3s ease;
}
.toast-enter-from {
opacity: 0;
transform: translateX(100%);
}
.toast-leave-to {
opacity: 0;
transform: translateX(100%);
}
.toast-move {
transition: transform 0.3s ease;
}
</style>