diff --git a/.gitignore b/.gitignore index 3f48653..76880a0 100644 --- a/.gitignore +++ b/.gitignore @@ -34,10 +34,6 @@ FEATURE_IDEAS.md bin/.phpunit.result.cache ###< temp files ### -###> frontend ### -/frontend/ -###< frontend ### - ###> ide ### /.idea/ ###< ide ### diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..1f6f542 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,29 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example + +# Playwright +e2e/.auth/ +playwright-report/ +test-results/ diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..8512a37 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,155 @@ +# Inventory Frontend + +Interface web de gestion d'inventaire industriel pour **Malio**. Application SPA complète permettant la gestion du parc machines, des pièces, composants, produits, fournisseurs et documents associés. + +## Stack technique + +| Technologie | Version | Rôle | +|-------------|---------|------| +| [Nuxt](https://nuxt.com) | 4 | Framework (SPA, SSR désactivé) | +| [Vue 3](https://vuejs.org) | 3.5 | Composition API + ` diff --git a/frontend/app/assets/LOGO_CARRE_BLANC.png b/frontend/app/assets/LOGO_CARRE_BLANC.png new file mode 100644 index 0000000..4192a3d Binary files /dev/null and b/frontend/app/assets/LOGO_CARRE_BLANC.png differ diff --git a/frontend/app/assets/app.css b/frontend/app/assets/app.css new file mode 100644 index 0000000..042b65d --- /dev/null +++ b/frontend/app/assets/app.css @@ -0,0 +1,381 @@ +/* ─── Fonts ─── */ +@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&display=swap'); + +@import "tailwindcss"; +@plugin "daisyui"; + +/* ─── Theme ─── */ +@plugin "daisyui/theme" { + name: "mytheme"; + default: true; + prefersdark: false; + color-scheme: light; + + /* Surfaces — warm gray with a hint of blue */ + --color-base-100: oklch(98.5% 0.004 260); + --color-base-200: oklch(95% 0.008 260); + --color-base-300: oklch(91% 0.015 260); + --color-base-content: oklch(22% 0.025 260); + + /* Primary — Malio blue, slightly richer */ + --color-primary: oklch(40% 0.16 262); + --color-primary-content: oklch(98% 0.005 262); + + /* Secondary — refined lavender */ + --color-secondary: oklch(72% 0.06 275); + --color-secondary-content: oklch(22% 0.03 275); + + /* Accent — warm amber-orange */ + --color-accent: oklch(72% 0.17 55); + --color-accent-content: oklch(20% 0.04 55); + + /* Neutral — deep slate */ + --color-neutral: oklch(28% 0.04 260); + --color-neutral-content: oklch(95% 0.005 260); + + /* Semantic */ + --color-info: oklch(58% 0.14 255); + --color-info-content: oklch(98% 0.005 255); + --color-success: oklch(62% 0.19 150); + --color-success-content: oklch(98% 0.005 150); + --color-warning: oklch(78% 0.15 70); + --color-warning-content: oklch(22% 0.05 70); + --color-error: oklch(58% 0.22 25); + --color-error-content: oklch(98% 0.005 25); + + /* Geometry */ + --radius-selector: 0.75rem; + --radius-field: 0.375rem; + --radius-box: 0.625rem; + + --size-selector: 0.25rem; + --size-field: 0.25rem; + + --border: 1px; + --depth: 1; + --noise: 0; +} + +@plugin "daisyui/theme" { + name: "mytheme-dark"; + default: false; + prefersdark: true; + color-scheme: dark; + + /* Surfaces — dark blue-gray */ + --color-base-100: oklch(22% 0.015 260); + --color-base-200: oklch(18% 0.012 260); + --color-base-300: oklch(28% 0.018 260); + --color-base-content: oklch(92% 0.005 260); + + /* Primary — Malio blue, brighter for dark */ + --color-primary: oklch(55% 0.18 262); + --color-primary-content: oklch(98% 0.005 262); + + /* Secondary — refined lavender */ + --color-secondary: oklch(72% 0.06 275); + --color-secondary-content: oklch(22% 0.03 275); + + /* Accent — warm amber-orange */ + --color-accent: oklch(72% 0.17 55); + --color-accent-content: oklch(20% 0.04 55); + + /* Neutral — lighter slate for dark mode */ + --color-neutral: oklch(75% 0.02 260); + --color-neutral-content: oklch(18% 0.01 260); + + /* Semantic */ + --color-info: oklch(62% 0.14 255); + --color-info-content: oklch(98% 0.005 255); + --color-success: oklch(65% 0.19 150); + --color-success-content: oklch(98% 0.005 150); + --color-warning: oklch(78% 0.15 70); + --color-warning-content: oklch(22% 0.05 70); + --color-error: oklch(62% 0.22 25); + --color-error-content: oklch(98% 0.005 25); + + /* Geometry — same as light */ + --radius-selector: 0.75rem; + --radius-field: 0.375rem; + --radius-box: 0.625rem; + + --size-selector: 0.25rem; + --size-field: 0.25rem; + + --border: 1px; + --depth: 1; + --noise: 0; +} + +/* ─── Typography ─── */ +:root { + --font-heading: 'Outfit', system-ui, sans-serif; + --font-body: 'DM Sans', system-ui, sans-serif; +} + +body { + font-family: var(--font-body); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + letter-spacing: -0.01em; +} + +h1, h2, h3, h4, h5, h6, +.card-title, +.stat-value, +.text-2xl, +.text-3xl, +.text-4xl { + font-family: var(--font-heading); + letter-spacing: -0.025em; +} + +/* ─── Density variables ─── */ +:root { + --spacing-xs: 0.5rem; + --spacing-sm: 0.75rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; +} + +.density-compact { + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 0.75rem; + --spacing-lg: 1rem; + --spacing-xl: 1.25rem; +} + +.density-comfortable { + --spacing-xs: 0.5rem; + --spacing-sm: 0.75rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; +} + +.density-spacious { + --spacing-xs: 0.75rem; + --spacing-sm: 1rem; + --spacing-md: 1.5rem; + --spacing-lg: 2rem; + --spacing-xl: 3rem; +} + +/* ─── High contrast mode ─── */ +.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; } + +/* ─── Accessibility ─── */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +*:focus-visible { + outline: 2px solid oklch(40% 0.16 262); + outline-offset: 2px; +} + +/* ─── Cards ─── */ +.card { + border: 1px solid oklch(91% 0.015 260 / 0.6); + transition: box-shadow 0.2s ease, transform 0.2s ease; +} + +.site-card { + background-color: oklch(100% 0 0); +} + +[data-theme="mytheme-dark"] .site-card { + background-color: oklch(24% 0.015 260); +} + +[data-theme="mytheme-dark"] .card { + border-color: oklch(30% 0.02 260 / 0.6); +} + +.card:hover { + box-shadow: + 0 4px 6px -1px oklch(22% 0.025 260 / 0.06), + 0 2px 4px -2px oklch(22% 0.025 260 / 0.04); +} + +/* ─── Navbar glass effect ─── */ +.navbar-glass { + background: oklch(98.5% 0.004 260 / 0.82); + backdrop-filter: blur(12px) saturate(1.5); + -webkit-backdrop-filter: blur(12px) saturate(1.5); + border-bottom: 1px solid oklch(91% 0.015 260 / 0.5); +} + +[data-theme="mytheme-dark"] .navbar-glass { + background: oklch(22% 0.015 260 / 0.85); + border-bottom-color: oklch(30% 0.02 260 / 0.5); +} + +/* ─── Buttons ─── */ +.btn { + font-family: var(--font-heading); + font-weight: 500; + letter-spacing: -0.01em; + transition: all 0.15s ease; +} + +.btn-circle { + transition: all 0.2s ease-in-out; +} +.btn-circle:hover { + transform: scale(1.05); +} +.btn-circle:active { + transform: scale(0.95); +} + +/* ─── Inputs ─── */ +.input, .select, .textarea { + font-family: var(--font-body); + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.input:focus, .select:focus, .textarea:focus { + box-shadow: 0 0 0 3px oklch(40% 0.16 262 / 0.1); +} + +/* ─── Tables ─── */ +.table thead th { + font-family: var(--font-heading); + font-weight: 600; + letter-spacing: 0.01em; + text-transform: uppercase; + font-size: 0.7rem; + color: oklch(45% 0.03 260); +} + +.table tbody tr { + transition: background-color 0.1s ease; +} + +/* ─── Badges ─── */ +.badge { + font-family: var(--font-heading); + font-weight: 500; + letter-spacing: 0; +} + +/* ─── Stats ─── */ +.stat-title { + font-family: var(--font-body); + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: 0.7rem; + font-weight: 500; + opacity: 0.6; +} + +.stat-value { + font-weight: 700; +} + +/* ─── Modals ─── */ +.modal { + transition: opacity 0.25s ease; +} + +.modal-box { + font-family: var(--font-body); + border-radius: 0.75rem; + border: 1px solid oklch(91% 0.015 260 / 0.5); +} + +@keyframes modalSlideUp { + from { opacity: 0; transform: translateY(0.5rem); } + to { opacity: 1; transform: translateY(0); } +} + +.modal.modal-open .modal-box { + animation: modalSlideUp 0.25s ease-out; +} + +/* ─── Page transitions ─── */ +.page-enter-active { + transition: opacity 0.2s ease, transform 0.2s ease; +} +.page-leave-active { + transition: opacity 0.15s ease; +} +.page-enter-from { + opacity: 0; + transform: translateY(4px); +} +.page-leave-to { + opacity: 0; +} + +/* ─── Scrollbar styling ─── */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: oklch(75% 0.02 260); + border-radius: 3px; +} +::-webkit-scrollbar-thumb:hover { + background: oklch(60% 0.03 260); +} + +/* ─── Readability ─── */ +.text-sm { line-height: 1.5; } +.text-xs { line-height: 1.4; } + +/* ─── Adaptive spacing ─── */ +.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); } + +@layer components { + .form-control .label { + @apply mb-2; + padding-bottom: 0; + margin-right: 15px; + } + .form-control .label + * { + margin-top: var(--spacing-xs); + } +} + +@layer base { + label + input, + label + select, + label + textarea, + label + .input, + label + .select, + label + .textarea { + margin-top: var(--spacing-xs); + } +} diff --git a/frontend/app/components/CommentDocumentList.vue b/frontend/app/components/CommentDocumentList.vue new file mode 100644 index 0000000..38d300e --- /dev/null +++ b/frontend/app/components/CommentDocumentList.vue @@ -0,0 +1,55 @@ + + + diff --git a/frontend/app/components/CommentSection.vue b/frontend/app/components/CommentSection.vue new file mode 100644 index 0000000..7c483a4 --- /dev/null +++ b/frontend/app/components/CommentSection.vue @@ -0,0 +1,270 @@ + + + diff --git a/frontend/app/components/SyncConfirmationModal.vue b/frontend/app/components/SyncConfirmationModal.vue new file mode 100644 index 0000000..96614af --- /dev/null +++ b/frontend/app/components/SyncConfirmationModal.vue @@ -0,0 +1,112 @@ + + + diff --git a/frontend/app/components/ToastContainer.vue b/frontend/app/components/ToastContainer.vue new file mode 100644 index 0000000..cfa6837 --- /dev/null +++ b/frontend/app/components/ToastContainer.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/frontend/app/components/common/ConfirmModal.vue b/frontend/app/components/common/ConfirmModal.vue new file mode 100644 index 0000000..4702bda --- /dev/null +++ b/frontend/app/components/common/ConfirmModal.vue @@ -0,0 +1,41 @@ + + + diff --git a/frontend/app/components/common/CustomFieldDisplay.vue b/frontend/app/components/common/CustomFieldDisplay.vue new file mode 100644 index 0000000..30e04c7 --- /dev/null +++ b/frontend/app/components/common/CustomFieldDisplay.vue @@ -0,0 +1,173 @@ + + + diff --git a/frontend/app/components/machine/MachineDetailHeader.vue b/frontend/app/components/machine/MachineDetailHeader.vue new file mode 100644 index 0000000..2613a96 --- /dev/null +++ b/frontend/app/components/machine/MachineDetailHeader.vue @@ -0,0 +1,67 @@ + + + diff --git a/frontend/app/components/machine/MachineDocumentsCard.vue b/frontend/app/components/machine/MachineDocumentsCard.vue new file mode 100644 index 0000000..4986cb2 --- /dev/null +++ b/frontend/app/components/machine/MachineDocumentsCard.vue @@ -0,0 +1,116 @@ +