add img preview + fix navbar
This commit is contained in:
648
app/app.vue
648
app/app.vue
@@ -59,132 +59,120 @@
|
|||||||
Squelettes de machine
|
Squelettes de machine
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li class="mt-1 border-t border-base-200 pt-2">
|
||||||
class="dropdown"
|
<button
|
||||||
:class="{ 'dropdown-open': openDropdown === 'pieces-mobile' }"
|
type="button"
|
||||||
@mouseenter="setDropdown('pieces-mobile')"
|
class="flex w-full items-center justify-between rounded-md px-2 py-1 text-left transition-colors"
|
||||||
@mouseleave="scheduleDropdownClose('pieces-mobile')"
|
|
||||||
@focusin="setDropdown('pieces-mobile')"
|
|
||||||
@focusout="scheduleDropdownClose('pieces-mobile')"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
|
|
||||||
@click="toggleDropdown('pieces-mobile')"
|
|
||||||
@keydown.enter.prevent="toggleDropdown('pieces-mobile')"
|
|
||||||
@keydown.space.prevent="toggleDropdown('pieces-mobile')"
|
|
||||||
:class="
|
:class="
|
||||||
isActive('/piece-category') || isActive('/pieces-catalog')
|
isActive('/piece-category') || isActive('/pieces-catalog')
|
||||||
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
"
|
"
|
||||||
|
@click="toggleDropdown('pieces-mobile')"
|
||||||
|
@keydown.enter.prevent="toggleDropdown('pieces-mobile')"
|
||||||
|
@keydown.space.prevent="toggleDropdown('pieces-mobile')"
|
||||||
|
:aria-expanded="openDropdown === 'pieces-mobile'"
|
||||||
>
|
>
|
||||||
Pièces
|
<span>Pièces</span>
|
||||||
</div>
|
<IconLucideChevronRight
|
||||||
<ul
|
class="h-4 w-4 transition-transform"
|
||||||
tabindex="0"
|
:class="openDropdown === 'pieces-mobile' ? 'rotate-90' : ''"
|
||||||
class="dropdown-content z-[1] menu menu-sm bg-base-100 rounded-box p-2 shadow space-y-1"
|
aria-hidden="true"
|
||||||
>
|
/>
|
||||||
<li>
|
</button>
|
||||||
<NuxtLink
|
<Transition name="nav-dropdown-mobile">
|
||||||
to="/pieces-catalog"
|
<ul
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
v-if="openDropdown === 'pieces-mobile'"
|
||||||
:class="
|
class="mt-2 space-y-1 rounded-md border border-base-200 bg-base-100 p-2 shadow-sm overflow-hidden"
|
||||||
isActive('/pieces-catalog')
|
>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/pieces-catalog"
|
||||||
>
|
class="rounded-md px-2 py-1 transition-colors block"
|
||||||
Catalogue des pièces
|
:class="
|
||||||
</NuxtLink>
|
isActive('/pieces-catalog')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
<li>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
<NuxtLink
|
"
|
||||||
to="/piece-category"
|
>
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
Catalogue des pièces
|
||||||
:class="
|
</NuxtLink>
|
||||||
isActive('/piece-category')
|
</li>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/piece-category"
|
||||||
>
|
class="rounded-md px-2 py-1 transition-colors block"
|
||||||
Catégorie de pièce
|
:class="
|
||||||
</NuxtLink>
|
isActive('/piece-category')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
</ul>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Catégorie de pièce
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Transition>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li class="mt-1 border-t border-base-200 pt-2">
|
||||||
class="dropdown"
|
<button
|
||||||
:class="{ 'dropdown-open': openDropdown === 'component-mobile' }"
|
type="button"
|
||||||
@mouseenter="setDropdown('component-mobile')"
|
class="flex w-full items-center justify-between rounded-md px-2 py-1 text-left transition-colors"
|
||||||
@mouseleave="scheduleDropdownClose('component-mobile')"
|
|
||||||
@focusin="setDropdown('component-mobile')"
|
|
||||||
@focusout="scheduleDropdownClose('component-mobile')"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
|
|
||||||
@click="toggleDropdown('component-mobile')"
|
|
||||||
@keydown.enter.prevent="toggleDropdown('component-mobile')"
|
|
||||||
@keydown.space.prevent="toggleDropdown('component-mobile')"
|
|
||||||
:class="
|
:class="
|
||||||
isActive('/component-category') ||
|
isActive('/component-category') || isActive('/component-catalog')
|
||||||
isActive('/component-catalog')
|
|
||||||
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
"
|
"
|
||||||
|
@click="toggleDropdown('component-mobile')"
|
||||||
|
@keydown.enter.prevent="toggleDropdown('component-mobile')"
|
||||||
|
@keydown.space.prevent="toggleDropdown('component-mobile')"
|
||||||
|
:aria-expanded="openDropdown === 'component-mobile'"
|
||||||
>
|
>
|
||||||
Composant
|
<span>Composant</span>
|
||||||
</div>
|
<IconLucideChevronRight
|
||||||
<ul
|
class="h-4 w-4 transition-transform"
|
||||||
tabindex="0"
|
:class="openDropdown === 'component-mobile' ? 'rotate-90' : ''"
|
||||||
class="dropdown-content z-[1] menu menu-sm bg-base-100 rounded-box p-2 shadow space-y-1"
|
aria-hidden="true"
|
||||||
>
|
/>
|
||||||
<li>
|
</button>
|
||||||
<NuxtLink
|
<Transition name="nav-dropdown-mobile">
|
||||||
to="/component-catalog"
|
<ul
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
v-if="openDropdown === 'component-mobile'"
|
||||||
:class="
|
class="mt-2 space-y-1 rounded-md border border-base-200 bg-base-100 p-2 shadow-sm overflow-hidden"
|
||||||
isActive('/component-catalog')
|
>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/component-catalog"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Catalogue des composants
|
:class="
|
||||||
</NuxtLink>
|
isActive('/component-catalog')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
<li>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
<NuxtLink
|
"
|
||||||
to="/component-category"
|
>
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
Catalogue des composants
|
||||||
:class="
|
</NuxtLink>
|
||||||
isActive('/component-category')
|
</li>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/component-category"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Catégorie de composant
|
:class="
|
||||||
</NuxtLink>
|
isActive('/component-category')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
</ul>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Catégorie de composant
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Transition>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li class="mt-1 border-t border-base-200 pt-2">
|
||||||
class="dropdown"
|
<button
|
||||||
:class="{ 'dropdown-open': openDropdown === 'resources-mobile' }"
|
type="button"
|
||||||
@mouseenter="setDropdown('resources-mobile')"
|
class="flex w-full items-center justify-between rounded-md px-2 py-1 text-left transition-colors"
|
||||||
@mouseleave="scheduleDropdownClose('resources-mobile')"
|
|
||||||
@focusin="setDropdown('resources-mobile')"
|
|
||||||
@focusout="scheduleDropdownClose('resources-mobile')"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
|
|
||||||
@click="toggleDropdown('resources-mobile')"
|
|
||||||
@keydown.enter.prevent="toggleDropdown('resources-mobile')"
|
|
||||||
@keydown.space.prevent="toggleDropdown('resources-mobile')"
|
|
||||||
:class="
|
:class="
|
||||||
isActive('/sites') ||
|
isActive('/sites') ||
|
||||||
isActive('/documents') ||
|
isActive('/documents') ||
|
||||||
@@ -192,53 +180,64 @@
|
|||||||
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
"
|
"
|
||||||
|
@click="toggleDropdown('resources-mobile')"
|
||||||
|
@keydown.enter.prevent="toggleDropdown('resources-mobile')"
|
||||||
|
@keydown.space.prevent="toggleDropdown('resources-mobile')"
|
||||||
|
:aria-expanded="openDropdown === 'resources-mobile'"
|
||||||
>
|
>
|
||||||
Ressources liées
|
<span>Ressources liées</span>
|
||||||
</div>
|
<IconLucideChevronRight
|
||||||
<ul
|
class="h-4 w-4 transition-transform"
|
||||||
tabindex="0"
|
:class="openDropdown === 'resources-mobile' ? 'rotate-90' : ''"
|
||||||
class="dropdown-content z-[1] menu menu-sm bg-base-100 rounded-box p-2 shadow space-y-1"
|
aria-hidden="true"
|
||||||
>
|
/>
|
||||||
<li>
|
</button>
|
||||||
<NuxtLink
|
<Transition name="nav-dropdown-mobile">
|
||||||
to="/sites"
|
<ul
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
v-if="openDropdown === 'resources-mobile'"
|
||||||
:class="
|
class="mt-2 space-y-1 rounded-md border border-base-200 bg-base-100 p-2 shadow-sm overflow-hidden"
|
||||||
isActive('/sites')
|
>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/sites"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Sites
|
:class="
|
||||||
</NuxtLink>
|
isActive('/sites')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
<li>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
<NuxtLink
|
"
|
||||||
to="/documents"
|
>
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
Sites
|
||||||
:class="
|
</NuxtLink>
|
||||||
isActive('/documents')
|
</li>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/documents"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Documents
|
:class="
|
||||||
</NuxtLink>
|
isActive('/documents')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
<li>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
<NuxtLink
|
"
|
||||||
to="/constructeurs"
|
>
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
Documents
|
||||||
:class="
|
</NuxtLink>
|
||||||
isActive('/constructeurs')
|
</li>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/constructeurs"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Constructeurs
|
:class="
|
||||||
</NuxtLink>
|
isActive('/constructeurs')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
</ul>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Constructeurs
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Transition>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -297,131 +296,138 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="dropdown"
|
class="relative"
|
||||||
:class="{ 'dropdown-open': openDropdown === 'pieces-desktop' }"
|
|
||||||
@mouseenter="setDropdown('pieces-desktop')"
|
@mouseenter="setDropdown('pieces-desktop')"
|
||||||
@mouseleave="scheduleDropdownClose('pieces-desktop')"
|
@mouseleave="scheduleDropdownClose('pieces-desktop')"
|
||||||
@focusin="setDropdown('pieces-desktop')"
|
@focusin="setDropdown('pieces-desktop')"
|
||||||
@focusout="scheduleDropdownClose('pieces-desktop')"
|
@focusout="scheduleDropdownClose('pieces-desktop')"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
tabindex="0"
|
type="button"
|
||||||
role="button"
|
class="inline-flex items-center gap-1 rounded-md px-3 py-2 transition-colors"
|
||||||
class="transition-colors px-3 py-2 rounded-md inline-flex items-center gap-1 cursor-pointer"
|
|
||||||
@click="toggleDropdown('pieces-desktop')"
|
|
||||||
@keydown.enter.prevent="toggleDropdown('pieces-desktop')"
|
|
||||||
@keydown.space.prevent="toggleDropdown('pieces-desktop')"
|
|
||||||
:class="
|
:class="
|
||||||
isActive('/piece-category') || isActive('/pieces-catalog')
|
isActive('/piece-category') || isActive('/pieces-catalog')
|
||||||
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
"
|
"
|
||||||
|
@click="toggleDropdown('pieces-desktop')"
|
||||||
|
@keydown.enter.prevent="toggleDropdown('pieces-desktop')"
|
||||||
|
@keydown.space.prevent="toggleDropdown('pieces-desktop')"
|
||||||
|
:aria-expanded="openDropdown === 'pieces-desktop'"
|
||||||
>
|
>
|
||||||
Pièces
|
Pièces
|
||||||
</div>
|
<IconLucideChevronRight
|
||||||
<ul
|
class="h-4 w-4 transition-transform"
|
||||||
tabindex="0"
|
:class="openDropdown === 'pieces-desktop' ? 'rotate-90' : ''"
|
||||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-60"
|
aria-hidden="true"
|
||||||
>
|
/>
|
||||||
<li>
|
</button>
|
||||||
<NuxtLink
|
<Transition name="nav-dropdown-desktop">
|
||||||
to="/piece-category"
|
<ul
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
v-if="openDropdown === 'pieces-desktop'"
|
||||||
:class="
|
class="absolute left-0 top-full mt-2 w-60 rounded-lg border border-base-200 bg-base-100 p-2 shadow-lg z-50"
|
||||||
isActive('/piece-category')
|
>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/piece-category"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Catégorie de pièce
|
:class="
|
||||||
</NuxtLink>
|
isActive('/piece-category')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
<li>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
<NuxtLink
|
"
|
||||||
to="/pieces-catalog"
|
>
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
Catégorie de pièce
|
||||||
:class="
|
</NuxtLink>
|
||||||
isActive('/pieces-catalog')
|
</li>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/pieces-catalog"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Catalogue des pièces
|
:class="
|
||||||
</NuxtLink>
|
isActive('/pieces-catalog')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
</ul>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Catalogue des pièces
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Transition>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="dropdown"
|
class="relative"
|
||||||
:class="{ 'dropdown-open': openDropdown === 'component-desktop' }"
|
|
||||||
@mouseenter="setDropdown('component-desktop')"
|
@mouseenter="setDropdown('component-desktop')"
|
||||||
@mouseleave="scheduleDropdownClose('component-desktop')"
|
@mouseleave="scheduleDropdownClose('component-desktop')"
|
||||||
@focusin="setDropdown('component-desktop')"
|
@focusin="setDropdown('component-desktop')"
|
||||||
@focusout="scheduleDropdownClose('component-desktop')"
|
@focusout="scheduleDropdownClose('component-desktop')"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
tabindex="0"
|
type="button"
|
||||||
role="button"
|
class="inline-flex items-center gap-1 rounded-md px-3 py-2 transition-colors"
|
||||||
class="transition-colors px-3 py-2 rounded-md inline-flex items-center gap-1 cursor-pointer"
|
|
||||||
@click="toggleDropdown('component-desktop')"
|
|
||||||
@keydown.enter.prevent="toggleDropdown('component-desktop')"
|
|
||||||
@keydown.space.prevent="toggleDropdown('component-desktop')"
|
|
||||||
:class="
|
:class="
|
||||||
isActive('/component-category') ||
|
isActive('/component-category') ||
|
||||||
isActive('/component-catalog')
|
isActive('/component-catalog')
|
||||||
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
"
|
"
|
||||||
|
@click="toggleDropdown('component-desktop')"
|
||||||
|
@keydown.enter.prevent="toggleDropdown('component-desktop')"
|
||||||
|
@keydown.space.prevent="toggleDropdown('component-desktop')"
|
||||||
|
:aria-expanded="openDropdown === 'component-desktop'"
|
||||||
>
|
>
|
||||||
Composant
|
Composant
|
||||||
</div>
|
<IconLucideChevronRight
|
||||||
<ul
|
class="h-4 w-4 transition-transform"
|
||||||
tabindex="0"
|
:class="openDropdown === 'component-desktop' ? 'rotate-90' : ''"
|
||||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-64"
|
aria-hidden="true"
|
||||||
>
|
/>
|
||||||
<li>
|
</button>
|
||||||
<NuxtLink
|
<Transition name="nav-dropdown-desktop">
|
||||||
to="/component-category"
|
<ul
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
v-if="openDropdown === 'component-desktop'"
|
||||||
:class="
|
class="absolute left-0 top-full mt-2 w-64 rounded-lg border border-base-200 bg-base-100 p-2 shadow-lg z-50"
|
||||||
isActive('/component-category')
|
>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/component-category"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Catégorie de composant
|
:class="
|
||||||
</NuxtLink>
|
isActive('/component-category')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
<li>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
<NuxtLink
|
"
|
||||||
to="/component-catalog"
|
>
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
Catégorie de composant
|
||||||
:class="
|
</NuxtLink>
|
||||||
isActive('/component-catalog')
|
</li>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/component-catalog"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Catalogue des composants
|
:class="
|
||||||
</NuxtLink>
|
isActive('/component-catalog')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
</ul>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Catalogue des composants
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Transition>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="dropdown"
|
class="relative"
|
||||||
:class="{ 'dropdown-open': openDropdown === 'resources-desktop' }"
|
|
||||||
@mouseenter="setDropdown('resources-desktop')"
|
@mouseenter="setDropdown('resources-desktop')"
|
||||||
@mouseleave="scheduleDropdownClose('resources-desktop')"
|
@mouseleave="scheduleDropdownClose('resources-desktop')"
|
||||||
@focusin="setDropdown('resources-desktop')"
|
@focusin="setDropdown('resources-desktop')"
|
||||||
@focusout="scheduleDropdownClose('resources-desktop')"
|
@focusout="scheduleDropdownClose('resources-desktop')"
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
tabindex="0"
|
type="button"
|
||||||
role="button"
|
class="inline-flex items-center gap-1 rounded-md px-3 py-2 transition-colors"
|
||||||
class="transition-colors px-3 py-2 rounded-md inline-flex items-center gap-1 cursor-pointer"
|
|
||||||
@click="toggleDropdown('resources-desktop')"
|
|
||||||
@keydown.enter.prevent="toggleDropdown('resources-desktop')"
|
|
||||||
@keydown.space.prevent="toggleDropdown('resources-desktop')"
|
|
||||||
:class="
|
:class="
|
||||||
isActive('/sites') ||
|
isActive('/sites') ||
|
||||||
isActive('/documents') ||
|
isActive('/documents') ||
|
||||||
@@ -429,53 +435,64 @@
|
|||||||
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
"
|
"
|
||||||
|
@click="toggleDropdown('resources-desktop')"
|
||||||
|
@keydown.enter.prevent="toggleDropdown('resources-desktop')"
|
||||||
|
@keydown.space.prevent="toggleDropdown('resources-desktop')"
|
||||||
|
:aria-expanded="openDropdown === 'resources-desktop'"
|
||||||
>
|
>
|
||||||
Ressources liées
|
Ressources liées
|
||||||
</div>
|
<IconLucideChevronRight
|
||||||
<ul
|
class="h-4 w-4 transition-transform"
|
||||||
tabindex="0"
|
:class="openDropdown === 'resources-desktop' ? 'rotate-90' : ''"
|
||||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-60"
|
aria-hidden="true"
|
||||||
>
|
/>
|
||||||
<li>
|
</button>
|
||||||
<NuxtLink
|
<Transition name="nav-dropdown-desktop">
|
||||||
to="/sites"
|
<ul
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
v-if="openDropdown === 'resources-desktop'"
|
||||||
:class="
|
class="absolute left-0 top-full mt-2 w-60 rounded-lg border border-base-200 bg-base-100 p-2 shadow-lg z-50"
|
||||||
isActive('/sites')
|
>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/sites"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Sites
|
:class="
|
||||||
</NuxtLink>
|
isActive('/sites')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
<li>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
<NuxtLink
|
"
|
||||||
to="/documents"
|
>
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
Sites
|
||||||
:class="
|
</NuxtLink>
|
||||||
isActive('/documents')
|
</li>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/documents"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Documents
|
:class="
|
||||||
</NuxtLink>
|
isActive('/documents')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
<li>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
<NuxtLink
|
"
|
||||||
to="/constructeurs"
|
>
|
||||||
class="rounded-md px-2 py-1 transition-colors"
|
Documents
|
||||||
:class="
|
</NuxtLink>
|
||||||
isActive('/constructeurs')
|
</li>
|
||||||
? 'bg-primary/10 text-primary font-semibold'
|
<li>
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
<NuxtLink
|
||||||
"
|
to="/constructeurs"
|
||||||
>
|
class="block rounded-md px-2 py-1 transition-colors"
|
||||||
Constructeurs
|
:class="
|
||||||
</NuxtLink>
|
isActive('/constructeurs')
|
||||||
</li>
|
? 'bg-primary/10 text-primary font-semibold'
|
||||||
</ul>
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Constructeurs
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Transition>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -567,7 +584,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
|
||||||
import { useRoute, navigateTo, useRuntimeConfig } from "#imports";
|
import { useRoute, navigateTo, useRuntimeConfig } from "#imports";
|
||||||
import { useProfileSession } from "~/composables/useProfileSession";
|
import { useProfileSession } from "~/composables/useProfileSession";
|
||||||
import IconLucideMenu from "~icons/lucide/menu";
|
import IconLucideMenu from "~icons/lucide/menu";
|
||||||
@@ -656,6 +673,13 @@ const toggleDropdown = (name) => {
|
|||||||
setDropdown(name);
|
setDropdown(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.fullPath,
|
||||||
|
() => {
|
||||||
|
closeDropdownNow();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const activeProfileLabel = computed(() => {
|
const activeProfileLabel = computed(() => {
|
||||||
if (!activeProfile.value) {
|
if (!activeProfile.value) {
|
||||||
return "Profil inconnu";
|
return "Profil inconnu";
|
||||||
@@ -685,3 +709,35 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.nav-dropdown-desktop-enter-active,
|
||||||
|
.nav-dropdown-desktop-leave-active {
|
||||||
|
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||||
|
}
|
||||||
|
.nav-dropdown-desktop-enter-from,
|
||||||
|
.nav-dropdown-desktop-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(0.25rem);
|
||||||
|
}
|
||||||
|
.nav-dropdown-desktop-enter-to,
|
||||||
|
.nav-dropdown-desktop-leave-from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-dropdown-mobile-enter-active,
|
||||||
|
.nav-dropdown-mobile-leave-active {
|
||||||
|
transition: max-height 0.2s ease, opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
.nav-dropdown-mobile-enter-from,
|
||||||
|
.nav-dropdown-mobile-leave-to {
|
||||||
|
max-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.nav-dropdown-mobile-enter-to,
|
||||||
|
.nav-dropdown-mobile-leave-from {
|
||||||
|
max-height: 12rem;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -214,13 +214,21 @@
|
|||||||
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3 text-sm">
|
<div class="flex items-center gap-3 text-sm">
|
||||||
<span class="text-xl" :class="documentIcon(document).colorClass">
|
<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-md border border-base-200 bg-base-200/70 flex items-center justify-center">
|
||||||
|
<img
|
||||||
|
v-if="isImageDocument(document) && document.path"
|
||||||
|
:src="document.path"
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
:alt="`Aperçu de ${document.name}`"
|
||||||
|
>
|
||||||
<component
|
<component
|
||||||
|
v-else
|
||||||
:is="documentIcon(document).component"
|
:is="documentIcon(document).component"
|
||||||
class="h-6 w-6"
|
class="h-6 w-6"
|
||||||
|
:class="documentIcon(document).colorClass"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="font-medium">
|
<div class="font-medium">
|
||||||
{{ document.name }}
|
{{ document.name }}
|
||||||
@@ -309,7 +317,7 @@ import DocumentUpload from './DocumentUpload.vue'
|
|||||||
import ConstructeurSelect from './ConstructeurSelect.vue'
|
import ConstructeurSelect from './ConstructeurSelect.vue'
|
||||||
import { useDocuments } from '~/composables/useDocuments'
|
import { useDocuments } from '~/composables/useDocuments'
|
||||||
import { getFileIcon } from '~/utils/fileIcons'
|
import { getFileIcon } from '~/utils/fileIcons'
|
||||||
import { canPreviewDocument } from '~/utils/documentPreview'
|
import { canPreviewDocument, isImageDocument } from '~/utils/documentPreview'
|
||||||
import DocumentPreviewModal from '~/components/DocumentPreviewModal.vue'
|
import DocumentPreviewModal from '~/components/DocumentPreviewModal.vue'
|
||||||
import IconLucideChevronRight from '~icons/lucide/chevron-right'
|
import IconLucideChevronRight from '~icons/lucide/chevron-right'
|
||||||
import { useCustomFields } from '~/composables/useCustomFields'
|
import { useCustomFields } from '~/composables/useCustomFields'
|
||||||
|
|||||||
@@ -37,12 +37,21 @@
|
|||||||
<ul v-if="selectedFiles.length" class="mt-4 w-full space-y-2 text-left">
|
<ul v-if="selectedFiles.length" class="mt-4 w-full space-y-2 text-left">
|
||||||
<li v-for="file in selectedFiles" :key="file.name" class="flex items-center justify-between text-sm">
|
<li v-for="file in selectedFiles" :key="file.name" class="flex items-center justify-between text-sm">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<component
|
<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-md border border-base-300 bg-base-200/70 flex items-center justify-center">
|
||||||
:is="getIcon(file).component"
|
<img
|
||||||
class="h-6 w-6"
|
v-if="isImageFile(file)"
|
||||||
:class="getIcon(file).colorClass"
|
:src="getFilePreview(file)"
|
||||||
aria-hidden="true"
|
class="h-full w-full object-cover"
|
||||||
/>
|
:alt="`Aperçu de ${file.name}`"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
v-else
|
||||||
|
:is="getIcon(file).component"
|
||||||
|
class="h-6 w-6"
|
||||||
|
:class="getIcon(file).colorClass"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="font-medium">{{ file.name }}</span>
|
<span class="font-medium">{{ file.name }}</span>
|
||||||
<span class="text-xs text-gray-500">{{ formatSize(file.size) }} • {{ file.type || 'Type inconnu' }}</span>
|
<span class="text-xs text-gray-500">{{ formatSize(file.size) }} • {{ file.type || 'Type inconnu' }}</span>
|
||||||
@@ -58,7 +67,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch, onBeforeUnmount } from 'vue'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
import { getFileIcon } from '~/utils/fileIcons'
|
import { getFileIcon } from '~/utils/fileIcons'
|
||||||
import IconLucideCloudUpload from '~icons/lucide/cloud-upload'
|
import IconLucideCloudUpload from '~icons/lucide/cloud-upload'
|
||||||
@@ -96,6 +105,30 @@ const dragActive = ref(false)
|
|||||||
const fileInput = ref(null)
|
const fileInput = ref(null)
|
||||||
const internalFiles = ref([])
|
const internalFiles = ref([])
|
||||||
const { showError } = useToast()
|
const { showError } = useToast()
|
||||||
|
const previewUrls = new Map()
|
||||||
|
|
||||||
|
const isImageFile = (file) => (file?.type || '').startsWith('image/')
|
||||||
|
|
||||||
|
const getFilePreview = (file) => {
|
||||||
|
if (!isImageFile(file)) { return null }
|
||||||
|
if (!previewUrls.has(file)) {
|
||||||
|
previewUrls.set(file, URL.createObjectURL(file))
|
||||||
|
}
|
||||||
|
return previewUrls.get(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanupRemovedPreviews = (previousFiles = [], nextFiles = []) => {
|
||||||
|
const nextSet = new Set(nextFiles)
|
||||||
|
previousFiles.forEach((file) => {
|
||||||
|
if (!nextSet.has(file)) {
|
||||||
|
const url = previewUrls.get(file)
|
||||||
|
if (url) {
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
previewUrls.delete(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const selectedFiles = computed(() => internalFiles.value)
|
const selectedFiles = computed(() => internalFiles.value)
|
||||||
|
|
||||||
@@ -103,6 +136,7 @@ watch(
|
|||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (Array.isArray(newValue)) {
|
if (Array.isArray(newValue)) {
|
||||||
|
cleanupRemovedPreviews(internalFiles.value, newValue)
|
||||||
internalFiles.value = [...newValue]
|
internalFiles.value = [...newValue]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -114,6 +148,7 @@ const triggerFileDialog = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const emitFiles = (files) => {
|
const emitFiles = (files) => {
|
||||||
|
cleanupRemovedPreviews(internalFiles.value, files)
|
||||||
internalFiles.value = files
|
internalFiles.value = files
|
||||||
emit('update:modelValue', files)
|
emit('update:modelValue', files)
|
||||||
emit('files-added', files)
|
emit('files-added', files)
|
||||||
@@ -180,4 +215,11 @@ const formatSize = (size) => {
|
|||||||
const getIcon = (file) => {
|
const getIcon = (file) => {
|
||||||
return getFileIcon({ name: file.name, mime: file.type })
|
return getFileIcon({ name: file.name, mime: file.type })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
previewUrls.forEach((url) => {
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
})
|
||||||
|
previewUrls.clear()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -276,13 +276,21 @@
|
|||||||
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3 text-sm">
|
<div class="flex items-center gap-3 text-sm">
|
||||||
<span class="text-xl" :class="documentIcon(document).colorClass">
|
<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-md border border-base-200 bg-base-200/70 flex items-center justify-center">
|
||||||
|
<img
|
||||||
|
v-if="isImageDocument(document) && document.path"
|
||||||
|
:src="document.path"
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
:alt="`Aperçu de ${document.name}`"
|
||||||
|
>
|
||||||
<component
|
<component
|
||||||
|
v-else
|
||||||
:is="documentIcon(document).component"
|
:is="documentIcon(document).component"
|
||||||
class="h-6 w-6"
|
class="h-6 w-6"
|
||||||
|
:class="documentIcon(document).colorClass"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="font-medium">
|
<div class="font-medium">
|
||||||
{{ document.name }}
|
{{ document.name }}
|
||||||
@@ -340,7 +348,7 @@ import { useCustomFields } from "~/composables/useCustomFields";
|
|||||||
import { useToast } from "~/composables/useToast";
|
import { useToast } from "~/composables/useToast";
|
||||||
import { useDocuments } from "~/composables/useDocuments";
|
import { useDocuments } from "~/composables/useDocuments";
|
||||||
import { getFileIcon } from "~/utils/fileIcons";
|
import { getFileIcon } from "~/utils/fileIcons";
|
||||||
import { canPreviewDocument } from "~/utils/documentPreview";
|
import { canPreviewDocument, isImageDocument } from "~/utils/documentPreview";
|
||||||
import DocumentUpload from "~/components/DocumentUpload.vue";
|
import DocumentUpload from "~/components/DocumentUpload.vue";
|
||||||
import DocumentPreviewModal from "~/components/DocumentPreviewModal.vue";
|
import DocumentPreviewModal from "~/components/DocumentPreviewModal.vue";
|
||||||
import IconLucidePackage from "~icons/lucide/package";
|
import IconLucidePackage from "~icons/lucide/package";
|
||||||
|
|||||||
@@ -53,9 +53,21 @@
|
|||||||
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3 text-sm">
|
<div class="flex items-center gap-3 text-sm">
|
||||||
<span class="text-xl" :class="documentIcon(document).colorClass">
|
<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-md border border-base-200 bg-base-200/70 flex items-center justify-center">
|
||||||
<component :is="documentIcon(document).component" class="h-6 w-6" aria-hidden="true" />
|
<img
|
||||||
</span>
|
v-if="isImageDocument(document) && document.path"
|
||||||
|
:src="document.path"
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
:alt="`Aperçu de ${document.name}`"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
v-else
|
||||||
|
:is="documentIcon(document).component"
|
||||||
|
class="h-6 w-6"
|
||||||
|
:class="documentIcon(document).colorClass"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="font-medium">
|
<div class="font-medium">
|
||||||
{{ document.name }}
|
{{ document.name }}
|
||||||
@@ -103,6 +115,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, toRefs } from 'vue'
|
import { computed, toRefs } from 'vue'
|
||||||
|
import { isImageDocument } from '~/utils/documentPreview'
|
||||||
import DocumentUpload from '~/components/DocumentUpload.vue'
|
import DocumentUpload from '~/components/DocumentUpload.vue'
|
||||||
import SiteContactFormFields from '~/components/sites/SiteContactFormFields.vue'
|
import SiteContactFormFields from '~/components/sites/SiteContactFormFields.vue'
|
||||||
|
|
||||||
|
|||||||
@@ -300,13 +300,21 @@
|
|||||||
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3 text-sm">
|
<div class="flex items-center gap-3 text-sm">
|
||||||
<span class="text-xl" :class="documentIcon(document).colorClass">
|
<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-md border border-base-200 bg-base-200/70 flex items-center justify-center">
|
||||||
|
<img
|
||||||
|
v-if="isImageDocument(document) && document.path"
|
||||||
|
:src="document.path"
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
:alt="`Aperçu de ${document.name}`"
|
||||||
|
>
|
||||||
<component
|
<component
|
||||||
|
v-else
|
||||||
:is="documentIcon(document).component"
|
:is="documentIcon(document).component"
|
||||||
class="h-6 w-6"
|
class="h-6 w-6"
|
||||||
|
:class="documentIcon(document).colorClass"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="font-medium">
|
<div class="font-medium">
|
||||||
{{ document.name }}
|
{{ document.name }}
|
||||||
@@ -379,7 +387,7 @@ import { formatStructurePreview } from '~/shared/modelUtils'
|
|||||||
import type { ComponentModelStructure } from '~/shared/types/inventory'
|
import type { ComponentModelStructure } from '~/shared/types/inventory'
|
||||||
import type { ModelType } from '~/services/modelTypes'
|
import type { ModelType } from '~/services/modelTypes'
|
||||||
import { getFileIcon } from '~/utils/fileIcons'
|
import { getFileIcon } from '~/utils/fileIcons'
|
||||||
import { canPreviewDocument } from '~/utils/documentPreview'
|
import { canPreviewDocument, isImageDocument } from '~/utils/documentPreview'
|
||||||
|
|
||||||
interface ComponentCatalogType extends ModelType {
|
interface ComponentCatalogType extends ModelType {
|
||||||
structure: ComponentModelStructure | null
|
structure: ComponentModelStructure | null
|
||||||
|
|||||||
@@ -265,13 +265,21 @@
|
|||||||
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3 text-sm">
|
<div class="flex items-center gap-3 text-sm">
|
||||||
<span class="text-xl" :class="documentIcon(document).colorClass">
|
<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-md border border-base-200 bg-base-200/70 flex items-center justify-center">
|
||||||
|
<img
|
||||||
|
v-if="isImageDocument(document) && document.path"
|
||||||
|
:src="document.path"
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
:alt="`Aperçu de ${document.name}`"
|
||||||
|
>
|
||||||
<component
|
<component
|
||||||
|
v-else
|
||||||
:is="documentIcon(document).component"
|
:is="documentIcon(document).component"
|
||||||
class="h-6 w-6"
|
class="h-6 w-6"
|
||||||
|
:class="documentIcon(document).colorClass"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="font-medium">{{ document.name }}</div>
|
<div class="font-medium">{{ document.name }}</div>
|
||||||
<div class="text-xs text-gray-500">
|
<div class="text-xs text-gray-500">
|
||||||
@@ -517,7 +525,7 @@ import { useToast } from '~/composables/useToast'
|
|||||||
import { useDocuments } from '~/composables/useDocuments'
|
import { useDocuments } from '~/composables/useDocuments'
|
||||||
import { getFileIcon } from '~/utils/fileIcons'
|
import { getFileIcon } from '~/utils/fileIcons'
|
||||||
import { sanitizeDefinitionOverrides } from '~/shared/modelUtils'
|
import { sanitizeDefinitionOverrides } from '~/shared/modelUtils'
|
||||||
import { canPreviewDocument } from '~/utils/documentPreview'
|
import { canPreviewDocument, isImageDocument } from '~/utils/documentPreview'
|
||||||
import ComponentHierarchy from '~/components/ComponentHierarchy.vue'
|
import ComponentHierarchy from '~/components/ComponentHierarchy.vue'
|
||||||
import DocumentUpload from '~/components/DocumentUpload.vue'
|
import DocumentUpload from '~/components/DocumentUpload.vue'
|
||||||
import ConstructeurSelect from '~/components/ConstructeurSelect.vue'
|
import ConstructeurSelect from '~/components/ConstructeurSelect.vue'
|
||||||
|
|||||||
@@ -840,18 +840,6 @@ const formatAssignmentList = (assignments) => {
|
|||||||
.join(', ')
|
.join(', ')
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedComponentIds = computed(() => {
|
|
||||||
const ids = []
|
|
||||||
Object.values(componentRequirementSelections).forEach((entries) => {
|
|
||||||
;(entries || []).forEach((entry) => {
|
|
||||||
if (entry?.composantId) {
|
|
||||||
ids.push(entry.composantId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return ids
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectedPieceIds = computed(() => {
|
const selectedPieceIds = computed(() => {
|
||||||
const ids = []
|
const ids = []
|
||||||
Object.values(pieceRequirementSelections).forEach((entries) => {
|
Object.values(pieceRequirementSelections).forEach((entries) => {
|
||||||
@@ -866,21 +854,15 @@ const selectedPieceIds = computed(() => {
|
|||||||
|
|
||||||
const getComponentOptions = (requirement, currentEntry) => {
|
const getComponentOptions = (requirement, currentEntry) => {
|
||||||
const requirementTypeId = requirement?.typeComposantId || requirement?.typeComposant?.id || null
|
const requirementTypeId = requirement?.typeComposantId || requirement?.typeComposant?.id || null
|
||||||
const usedIds = new Set(
|
|
||||||
selectedComponentIds.value.filter((id) => id && (!currentEntry || id !== currentEntry.composantId)),
|
|
||||||
)
|
|
||||||
|
|
||||||
return componentInventory.value.filter((component) => {
|
return componentInventory.value.filter((component) => {
|
||||||
|
if (!component?.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if (requirementTypeId && component.typeComposantId !== requirementTypeId) {
|
if (requirementTypeId && component.typeComposantId !== requirementTypeId) {
|
||||||
return false
|
return currentEntry?.composantId === component.id
|
||||||
}
|
}
|
||||||
if (!component.id) {
|
return true
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (currentEntry?.composantId === component.id) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return !usedIds.has(component.id)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -270,13 +270,21 @@
|
|||||||
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3 text-sm">
|
<div class="flex items-center gap-3 text-sm">
|
||||||
<span class="text-xl" :class="documentIcon(document).colorClass">
|
<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-md border border-base-200 bg-base-200/70 flex items-center justify-center">
|
||||||
|
<img
|
||||||
|
v-if="isImageDocument(document) && document.path"
|
||||||
|
:src="document.path"
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
:alt="`Aperçu de ${document.name}`"
|
||||||
|
>
|
||||||
<component
|
<component
|
||||||
|
v-else
|
||||||
:is="documentIcon(document).component"
|
:is="documentIcon(document).component"
|
||||||
class="h-6 w-6"
|
class="h-6 w-6"
|
||||||
|
:class="documentIcon(document).colorClass"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="font-medium">
|
<div class="font-medium">
|
||||||
{{ document.name }}
|
{{ document.name }}
|
||||||
@@ -346,7 +354,7 @@ import { useApi } from '~/composables/useApi'
|
|||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
import { useDocuments } from '~/composables/useDocuments'
|
import { useDocuments } from '~/composables/useDocuments'
|
||||||
import { getFileIcon } from '~/utils/fileIcons'
|
import { getFileIcon } from '~/utils/fileIcons'
|
||||||
import { canPreviewDocument } from '~/utils/documentPreview'
|
import { canPreviewDocument, isImageDocument } from '~/utils/documentPreview'
|
||||||
import { formatPieceStructurePreview } from '~/shared/modelUtils'
|
import { formatPieceStructurePreview } from '~/shared/modelUtils'
|
||||||
import type { PieceModelStructure } from '~/shared/types/inventory'
|
import type { PieceModelStructure } from '~/shared/types/inventory'
|
||||||
import type { ModelType } from '~/services/modelTypes'
|
import type { ModelType } from '~/services/modelTypes'
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export const getPreviewType = (document) => {
|
|||||||
|
|
||||||
export const canPreviewDocument = (document = {}) => !!getPreviewType(document)
|
export const canPreviewDocument = (document = {}) => !!getPreviewType(document)
|
||||||
|
|
||||||
|
export const isImageDocument = (document = {}) => getPreviewType(document) === 'image'
|
||||||
|
|
||||||
export const describeDocument = (document) => {
|
export const describeDocument = (document) => {
|
||||||
if (!document) { return '' }
|
if (!document) { return '' }
|
||||||
const name = document.filename || document.name || ''
|
const name = document.filename || document.name || ''
|
||||||
|
|||||||
Reference in New Issue
Block a user