avant codex

This commit is contained in:
2026-02-22 15:05:40 +01:00
parent fed449c784
commit 20af00d653
291 changed files with 51868 additions and 424 deletions

View File

@@ -8,4 +8,4 @@ RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
EXPOSE 8061

View File

@@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Lune - Premier quartier">
<title>Lune - Premier quartier</title>
<defs>
<linearGradient id="nightSkyFQ" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0F2027"/>
<stop offset="100%" stop-color="#203A43"/>
</linearGradient>
<radialGradient id="moonLightFQ" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#F5F3E7"/>
<stop offset="100%" stop-color="#D9D6C8"/>
</radialGradient>
</defs>
<!-- Night sky -->
<rect width="128" height="128" rx="20" fill="url(#nightSkyFQ)"/>
<!-- Stars -->
<g fill="#FFFFFF" opacity="0.8">
<circle cx="24" cy="28" r="2"/>
<circle cx="98" cy="22" r="1.8"/>
<circle cx="110" cy="46" r="1.5"/>
<circle cx="18" cy="60" r="1.5"/>
<circle cx="90" cy="78" r="2"/>
</g>
<!-- Moon base (dark disc) -->
<circle cx="64" cy="64" r="36" fill="#2C3E50"/>
<!-- Illuminated half (right side) -->
<path d="M64 28
A36 36 0 0 1 64 100
L64 28 Z"
fill="url(#moonLightFQ)"/>
<!-- Subtle craters on lit side -->
<g fill="#CFCBB8" opacity="0.5">
<circle cx="74" cy="52" r="4"/>
<circle cx="82" cy="70" r="3"/>
<circle cx="70" cy="78" r="2.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,39 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Lune - Pleine lune">
<title>Pleine lune</title>
<defs>
<linearGradient id="nightSkyFM" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0B1D2A"/>
<stop offset="100%" stop-color="#1C2F3F"/>
</linearGradient>
<radialGradient id="moonFull" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#F8F6EC"/>
<stop offset="70%" stop-color="#E2DECF"/>
<stop offset="100%" stop-color="#CFCBB8"/>
</radialGradient>
</defs>
<!-- Night sky -->
<rect width="128" height="128" rx="20" fill="url(#nightSkyFM)"/>
<!-- Stars -->
<g fill="#FFFFFF" opacity="0.85">
<circle cx="22" cy="24" r="2"/>
<circle cx="104" cy="20" r="1.8"/>
<circle cx="112" cy="48" r="1.5"/>
<circle cx="18" cy="68" r="1.6"/>
<circle cx="96" cy="86" r="2"/>
<circle cx="40" cy="18" r="1.4"/>
</g>
<!-- Full moon -->
<circle cx="64" cy="64" r="38" fill="url(#moonFull)"/>
<!-- Craters -->
<g fill="#BEB9A6" opacity="0.6">
<circle cx="52" cy="50" r="6"/>
<circle cx="76" cy="60" r="5"/>
<circle cx="68" cy="82" r="4"/>
<circle cx="48" cy="72" r="3.5"/>
<circle cx="84" cy="44" r="3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Lune - Dernier quartier">
<title>Lune - Dernier quartier</title>
<defs>
<linearGradient id="nightSkyLQ" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0F2027"/>
<stop offset="100%" stop-color="#203A43"/>
</linearGradient>
<radialGradient id="moonLightLQ" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#F5F3E7"/>
<stop offset="100%" stop-color="#D9D6C8"/>
</radialGradient>
</defs>
<!-- Night sky -->
<rect width="128" height="128" rx="20" fill="url(#nightSkyLQ)"/>
<!-- Stars -->
<g fill="#FFFFFF" opacity="0.8">
<circle cx="24" cy="28" r="2"/>
<circle cx="98" cy="22" r="1.8"/>
<circle cx="110" cy="46" r="1.5"/>
<circle cx="18" cy="60" r="1.5"/>
<circle cx="90" cy="78" r="2"/>
</g>
<!-- Moon base (dark disc) -->
<circle cx="64" cy="64" r="36" fill="#2C3E50"/>
<!-- Illuminated half (left side) -->
<path d="M64 28
A36 36 0 0 0 64 100
L64 28 Z"
fill="url(#moonLightLQ)"/>
<!-- Subtle craters on lit side -->
<g fill="#CFCBB8" opacity="0.5">
<circle cx="54" cy="52" r="4"/>
<circle cx="46" cy="70" r="3"/>
<circle cx="58" cy="78" r="2.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,37 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Lune - Nouvelle lune">
<title>Lune - Nouvelle lune</title>
<defs>
<linearGradient id="nightSkyNM" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#081821"/>
<stop offset="100%" stop-color="#132A35"/>
</linearGradient>
<radialGradient id="haloNM" cx="50%" cy="50%" r="60%">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.18"/>
<stop offset="70%" stop-color="#FFFFFF" stop-opacity="0.06"/>
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0"/>
</radialGradient>
</defs>
<!-- Night sky -->
<rect width="128" height="128" rx="20" fill="url(#nightSkyNM)"/>
<!-- Stars -->
<g fill="#FFFFFF" opacity="0.85">
<circle cx="22" cy="26" r="2"/>
<circle cx="46" cy="18" r="1.4"/>
<circle cx="104" cy="22" r="1.8"/>
<circle cx="112" cy="48" r="1.5"/>
<circle cx="18" cy="70" r="1.6"/>
<circle cx="94" cy="88" r="2"/>
<circle cx="34" cy="46" r="1.2"/>
</g>
<!-- Very dark moon disc -->
<circle cx="64" cy="64" r="38" fill="#0B0F14"/>
<!-- Subtle rim/earthshine hint -->
<circle cx="64" cy="64" r="40" fill="none" stroke="#FFFFFF" stroke-width="2" opacity="0.12"/>
<!-- Soft halo -->
<circle cx="64" cy="64" r="46" fill="url(#haloNM)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Lune - Dernier croissant">
<title>Dernier croissant</title>
<defs>
<linearGradient id="nightSkyWC" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0B1D2A"/>
<stop offset="100%" stop-color="#1C2F3F"/>
</linearGradient>
<radialGradient id="moonLightWC" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#F6F4E8"/>
<stop offset="100%" stop-color="#D9D6C8"/>
</radialGradient>
</defs>
<!-- Night sky -->
<rect width="128" height="128" rx="20" fill="url(#nightSkyWC)"/>
<!-- Stars -->
<g fill="#FFFFFF" opacity="0.85">
<circle cx="24" cy="26" r="2"/>
<circle cx="46" cy="18" r="1.4"/>
<circle cx="104" cy="22" r="1.8"/>
<circle cx="112" cy="48" r="1.5"/>
<circle cx="18" cy="70" r="1.6"/>
<circle cx="94" cy="88" r="2"/>
</g>
<!-- Dark moon base -->
<circle cx="64" cy="64" r="36" fill="#2C3E50"/>
<!-- Illuminated crescent (left side, waning) -->
<path d="
M64 28
A36 36 0 0 0 64 100
A24 36 0 0 1 64 28
Z"
fill="url(#moonLightWC)"/>
<!-- Subtle craters on lit crescent -->
<g fill="#CFCBB8" opacity="0.5">
<circle cx="52" cy="56" r="3.5"/>
<circle cx="54" cy="72" r="2.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Lune - Gibbeuse décroissante">
<title>Waning Gibbous</title>
<defs>
<linearGradient id="nightSkyWG" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0B1D2A"/>
<stop offset="100%" stop-color="#1C2F3F"/>
</linearGradient>
<radialGradient id="moonLightWG" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#F6F4E8"/>
<stop offset="100%" stop-color="#D9D6C8"/>
</radialGradient>
</defs>
<!-- Night sky -->
<rect width="128" height="128" rx="20" fill="url(#nightSkyWG)"/>
<!-- Stars -->
<g fill="#FFFFFF" opacity="0.85">
<circle cx="22" cy="24" r="2"/>
<circle cx="44" cy="18" r="1.4"/>
<circle cx="104" cy="22" r="1.8"/>
<circle cx="112" cy="46" r="1.5"/>
<circle cx="18" cy="70" r="1.6"/>
<circle cx="94" cy="88" r="2"/>
</g>
<!-- Dark moon base -->
<circle cx="64" cy="64" r="36" fill="#2C3E50"/>
<!-- Illuminated gibbous (mostly lit, shadow on right) -->
<path d="
M64 28
A36 36 0 1 0 64 100
A26 36 0 1 1 64 28
Z"
fill="url(#moonLightWG)"/>
<!-- Subtle craters -->
<g fill="#CFCBB8" opacity="0.5">
<circle cx="54" cy="50" r="5"/>
<circle cx="68" cy="70" r="4"/>
<circle cx="52" cy="78" r="3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Lune - Premier croissant">
<title>Waxing Crescent</title>
<defs>
<linearGradient id="nightSkyWCX" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0B1D2A"/>
<stop offset="100%" stop-color="#1C2F3F"/>
</linearGradient>
<radialGradient id="moonLightWCX" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#F6F4E8"/>
<stop offset="100%" stop-color="#D9D6C8"/>
</radialGradient>
</defs>
<!-- Night sky -->
<rect width="128" height="128" rx="20" fill="url(#nightSkyWCX)"/>
<!-- Stars -->
<g fill="#FFFFFF" opacity="0.85">
<circle cx="22" cy="24" r="2"/>
<circle cx="46" cy="18" r="1.4"/>
<circle cx="104" cy="22" r="1.8"/>
<circle cx="112" cy="48" r="1.5"/>
<circle cx="18" cy="70" r="1.6"/>
<circle cx="94" cy="88" r="2"/>
</g>
<!-- Dark moon base -->
<circle cx="64" cy="64" r="36" fill="#2C3E50"/>
<!-- Illuminated crescent (right side, waxing) -->
<path d="
M64 28
A36 36 0 0 1 64 100
A24 36 0 0 0 64 28
Z"
fill="url(#moonLightWCX)"/>
<!-- Subtle craters on lit crescent -->
<g fill="#CFCBB8" opacity="0.5">
<circle cx="74" cy="56" r="3.5"/>
<circle cx="72" cy="72" r="2.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Lune - Gibbeuse croissante">
<title>Waxing Gibbous</title>
<defs>
<linearGradient id="nightSkyWXG" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0B1D2A"/>
<stop offset="100%" stop-color="#1C2F3F"/>
</linearGradient>
<radialGradient id="moonLightWXG" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#F6F4E8"/>
<stop offset="100%" stop-color="#D9D6C8"/>
</radialGradient>
</defs>
<!-- Night sky -->
<rect width="128" height="128" rx="20" fill="url(#nightSkyWXG)"/>
<!-- Stars -->
<g fill="#FFFFFF" opacity="0.85">
<circle cx="22" cy="24" r="2"/>
<circle cx="44" cy="18" r="1.4"/>
<circle cx="104" cy="22" r="1.8"/>
<circle cx="112" cy="46" r="1.5"/>
<circle cx="18" cy="70" r="1.6"/>
<circle cx="94" cy="88" r="2"/>
</g>
<!-- Dark moon base -->
<circle cx="64" cy="64" r="36" fill="#2C3E50"/>
<!-- Illuminated gibbous (mostly lit, shadow on left) -->
<path d="
M64 28
A36 36 0 1 1 64 100
A26 36 0 1 0 64 28
Z"
fill="url(#moonLightWXG)"/>
<!-- Subtle craters -->
<g fill="#CFCBB8" opacity="0.5">
<circle cx="74" cy="50" r="5"/>
<circle cx="62" cy="70" r="4"/>
<circle cx="76" cy="78" r="3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,47 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Soleil - ciel clair">
<title>Soleil - ciel clair</title>
<defs>
<radialGradient id="sunCore" cx="50%" cy="50%" r="60%">
<stop offset="0%" stop-color="#FFE68A"/>
<stop offset="55%" stop-color="#FFC93C"/>
<stop offset="100%" stop-color="#FFB300"/>
</radialGradient>
<filter id="softGlow" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="2.2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Clear sky background -->
<rect x="0" y="0" width="128" height="128" rx="20" fill="#6EC6FF"/>
<!-- Subtle sky highlight -->
<path d="M0,20 C30,6 72,6 128,26 L128,0 L0,0 Z" fill="#8ED7FF" opacity="0.65"/>
<!-- Sun -->
<g transform="translate(64 58)" filter="url(#softGlow)">
<!-- Rays -->
<g stroke="#FFD25A" stroke-width="6" stroke-linecap="round" opacity="0.95">
<line x1="0" y1="-44" x2="0" y2="-60"/>
<line x1="0" y1="44" x2="0" y2="60"/>
<line x1="-44" y1="0" x2="-60" y2="0"/>
<line x1="44" y1="0" x2="60" y2="0"/>
<line x1="31" y1="-31" x2="44" y2="-44"/>
<line x1="-31" y1="-31" x2="-44" y2="-44"/>
<line x1="31" y1="31" x2="44" y2="44"/>
<line x1="-31" y1="31" x2="-44" y2="44"/>
</g>
<!-- Core -->
<circle r="26" fill="url(#sunCore)"/>
<circle r="30" fill="#FFD45C" opacity="0.18"/>
</g>
<!-- Horizon hint -->
<path d="M0,104 C26,96 46,94 64,94 C82,94 102,96 128,104 L128,128 L0,128 Z" fill="#49B36B"/>
<path d="M0,110 C26,102 46,100 64,100 C82,100 102,102 128,110 L128,128 L0,128 Z" fill="#3AA65E" opacity="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Principalement clair">
<title>Principalement clair</title>
<defs>
<radialGradient id="sunGrad" cx="50%" cy="50%" r="60%">
<stop offset="0%" stop-color="#FFE68A"/>
<stop offset="60%" stop-color="#FFC93C"/>
<stop offset="100%" stop-color="#FFB300"/>
</radialGradient>
<filter id="glow" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="2"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Sky background -->
<rect width="128" height="128" rx="20" fill="#6EC6FF"/>
<!-- Sun (partially visible) -->
<g transform="translate(48 48)" filter="url(#glow)">
<g stroke="#FFD25A" stroke-width="5" stroke-linecap="round">
<line x1="0" y1="-34" x2="0" y2="-48"/>
<line x1="0" y1="34" x2="0" y2="48"/>
<line x1="-34" y1="0" x2="-48" y2="0"/>
<line x1="34" y1="0" x2="48" y2="0"/>
<line x1="24" y1="-24" x2="34" y2="-34"/>
<line x1="-24" y1="-24" x2="-34" y2="-34"/>
<line x1="24" y1="24" x2="34" y2="34"/>
<line x1="-24" y1="24" x2="-34" y2="34"/>
</g>
<circle r="22" fill="url(#sunGrad)"/>
</g>
<!-- Small cloud (mostly clear) -->
<g transform="translate(70 72)">
<ellipse cx="0" cy="10" rx="26" ry="14" fill="#FFFFFF"/>
<circle cx="-14" cy="6" r="10" fill="#FFFFFF"/>
<circle cx="2" cy="2" r="12" fill="#FFFFFF"/>
<circle cx="18" cy="8" r="9" fill="#FFFFFF"/>
<ellipse cx="0" cy="14" rx="26" ry="8" fill="#E6F3FF" opacity="0.6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,50 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Partiellement nuageux">
<title>Partiellement nuageux</title>
<defs>
<radialGradient id="sunGradPN" cx="50%" cy="50%" r="60%">
<stop offset="0%" stop-color="#FFE68A"/>
<stop offset="60%" stop-color="#FFC93C"/>
<stop offset="100%" stop-color="#FFB300"/>
</radialGradient>
<filter id="sunGlowPN" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur stdDeviation="2.5"/>
</filter>
<linearGradient id="cloudShadePN" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFFFFF"/>
<stop offset="100%" stop-color="#E6EEF7"/>
</linearGradient>
</defs>
<!-- Sky -->
<rect width="128" height="128" rx="20" fill="#7EC8FF"/>
<!-- Sun behind clouds -->
<g transform="translate(44 44)">
<circle r="24" fill="url(#sunGradPN)" filter="url(#sunGlowPN)"/>
<g stroke="#FFD25A" stroke-width="4" stroke-linecap="round" opacity="0.9">
<line x1="0" y1="-36" x2="0" y2="-50"/>
<line x1="0" y1="36" x2="0" y2="50"/>
<line x1="-36" y1="0" x2="-50" y2="0"/>
<line x1="36" y1="0" x2="50" y2="0"/>
<line x1="26" y1="-26" x2="38" y2="-38"/>
<line x1="-26" y1="-26" x2="-38" y2="-38"/>
<line x1="26" y1="26" x2="38" y2="38"/>
<line x1="-26" y1="26" x2="-38" y2="38"/>
</g>
</g>
<!-- Main cloud -->
<g transform="translate(70 78)">
<ellipse cx="0" cy="12" rx="34" ry="18" fill="url(#cloudShadePN)"/>
<circle cx="-20" cy="8" r="14" fill="url(#cloudShadePN)"/>
<circle cx="6" cy="4" r="18" fill="url(#cloudShadePN)"/>
<circle cx="26" cy="10" r="12" fill="url(#cloudShadePN)"/>
</g>
<!-- Secondary smaller cloud -->
<g transform="translate(40 86)" opacity="0.9">
<ellipse cx="0" cy="8" rx="20" ry="12" fill="#F4F8FC"/>
<circle cx="-10" cy="6" r="8" fill="#F4F8FC"/>
<circle cx="8" cy="4" r="10" fill="#F4F8FC"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Couvert">
<title>Couvert</title>
<defs>
<linearGradient id="skyOvercast" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C9D3DD"/>
<stop offset="100%" stop-color="#9FAAB5"/>
</linearGradient>
<linearGradient id="cloudMain" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F1F4F7"/>
<stop offset="100%" stop-color="#D5DBE1"/>
</linearGradient>
<linearGradient id="cloudShadow" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E2E7EC"/>
<stop offset="100%" stop-color="#C3CBD3"/>
</linearGradient>
</defs>
<!-- Grey sky -->
<rect width="128" height="128" rx="20" fill="url(#skyOvercast)"/>
<!-- Large cloud layer -->
<g transform="translate(64 74)">
<ellipse cx="0" cy="16" rx="46" ry="24" fill="url(#cloudShadow)"/>
<circle cx="-28" cy="10" r="18" fill="url(#cloudShadow)"/>
<circle cx="0" cy="2" r="22" fill="url(#cloudShadow)"/>
<circle cx="30" cy="12" r="16" fill="url(#cloudShadow)"/>
</g>
<!-- Upper cloud layer -->
<g transform="translate(64 56)">
<ellipse cx="0" cy="14" rx="40" ry="20" fill="url(#cloudMain)"/>
<circle cx="-22" cy="10" r="16" fill="url(#cloudMain)"/>
<circle cx="6" cy="4" r="20" fill="url(#cloudMain)"/>
<circle cx="26" cy="12" r="14" fill="url(#cloudMain)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Brouillard">
<title>Brouillard</title>
<defs>
<linearGradient id="fogSky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#DCE3E9"/>
<stop offset="100%" stop-color="#B8C2CC"/>
</linearGradient>
<linearGradient id="fogCloud" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F4F7FA"/>
<stop offset="100%" stop-color="#D9E0E6"/>
</linearGradient>
</defs>
<!-- Sky background -->
<rect width="128" height="128" rx="20" fill="url(#fogSky)"/>
<!-- Diffuse sun (very muted) -->
<circle cx="40" cy="40" r="18" fill="#FFFFFF" opacity="0.25"/>
<!-- Main cloud mass -->
<g transform="translate(64 62)">
<ellipse cx="0" cy="12" rx="42" ry="20" fill="url(#fogCloud)" opacity="0.9"/>
<circle cx="-22" cy="8" r="14" fill="url(#fogCloud)" opacity="0.9"/>
<circle cx="8" cy="4" r="18" fill="url(#fogCloud)" opacity="0.9"/>
<circle cx="28" cy="10" r="12" fill="url(#fogCloud)" opacity="0.9"/>
</g>
<!-- Fog horizontal layers -->
<g stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" opacity="0.7">
<line x1="24" y1="84" x2="104" y2="84"/>
<line x1="20" y1="96" x2="108" y2="96"/>
<line x1="28" y1="108" x2="100" y2="108"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,56 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Brouillard givrant">
<title>Brouillard givrant</title>
<defs>
<linearGradient id="ffSky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#D7E6F5"/>
<stop offset="100%" stop-color="#A9B9C9"/>
</linearGradient>
<linearGradient id="ffCloud" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F7FBFF"/>
<stop offset="100%" stop-color="#D7E3EE"/>
</linearGradient>
<filter id="ffSoft" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="1.6"/>
</filter>
</defs>
<!-- Cold sky -->
<rect width="128" height="128" rx="20" fill="url(#ffSky)"/>
<!-- Muted sun halo -->
<circle cx="40" cy="38" r="18" fill="#FFFFFF" opacity="0.22" filter="url(#ffSoft)"/>
<!-- Cloud mass -->
<g transform="translate(64 60)">
<ellipse cx="0" cy="12" rx="44" ry="22" fill="url(#ffCloud)" opacity="0.92"/>
<circle cx="-24" cy="8" r="15" fill="url(#ffCloud)" opacity="0.92"/>
<circle cx="8" cy="4" r="19" fill="url(#ffCloud)" opacity="0.92"/>
<circle cx="30" cy="10" r="13" fill="url(#ffCloud)" opacity="0.92"/>
</g>
<!-- Fog layers -->
<g stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" opacity="0.68">
<line x1="22" y1="82" x2="106" y2="82"/>
<line x1="18" y1="94" x2="110" y2="94"/>
<line x1="26" y1="106" x2="102" y2="106"/>
</g>
<!-- Snowflake / ice marker -->
<g transform="translate(96 96)">
<circle cx="0" cy="0" r="14" fill="#FFFFFF" opacity="0.22"/>
<g stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" opacity="0.9">
<line x1="0" y1="-9" x2="0" y2="9"/>
<line x1="-9" y1="0" x2="9" y2="0"/>
<line x1="-7" y1="-7" x2="7" y2="7"/>
<line x1="-7" y1="7" x2="7" y2="-7"/>
<line x1="0" y1="-9" x2="3" y2="-6"/>
<line x1="0" y1="-9" x2="-3" y2="-6"/>
<line x1="0" y1="9" x2="3" y2="6"/>
<line x1="0" y1="9" x2="-3" y2="6"/>
<line x1="-9" y1="0" x2="-6" y2="3"/>
<line x1="-9" y1="0" x2="-6" y2="-3"/>
<line x1="9" y1="0" x2="6" y2="3"/>
<line x1="9" y1="0" x2="6" y2="-3"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,32 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Bruine légère">
<title>Bruine légère</title>
<defs>
<linearGradient id="drizzleSky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#9EC9F5"/>
<stop offset="100%" stop-color="#6FA8DC"/>
</linearGradient>
<linearGradient id="drizzleCloud" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F2F5F8"/>
<stop offset="100%" stop-color="#D6DEE6"/>
</linearGradient>
</defs>
<!-- Sky -->
<rect width="128" height="128" rx="20" fill="url(#drizzleSky)"/>
<!-- Cloud -->
<g transform="translate(64 58)">
<ellipse cx="0" cy="14" rx="40" ry="20" fill="url(#drizzleCloud)"/>
<circle cx="-22" cy="10" r="15" fill="url(#drizzleCloud)"/>
<circle cx="8" cy="4" r="18" fill="url(#drizzleCloud)"/>
<circle cx="28" cy="12" r="13" fill="url(#drizzleCloud)"/>
</g>
<!-- Light drizzle (fine drops) -->
<g stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" opacity="0.8">
<line x1="40" y1="88" x2="40" y2="96"/>
<line x1="56" y1="92" x2="56" y2="100"/>
<line x1="72" y1="88" x2="72" y2="96"/>
<line x1="88" y1="92" x2="88" y2="100"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Bruine modérée">
<title>Bruine modérée</title>
<defs>
<linearGradient id="skyDrizzleMod" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#8FBDEB"/>
<stop offset="100%" stop-color="#5F95C9"/>
</linearGradient>
<linearGradient id="cloudDrizzleMod" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#EEF2F6"/>
<stop offset="100%" stop-color="#CBD5DF"/>
</linearGradient>
</defs>
<!-- Sky -->
<rect width="128" height="128" rx="20" fill="url(#skyDrizzleMod)"/>
<!-- Cloud -->
<g transform="translate(64 56)">
<ellipse cx="0" cy="16" rx="42" ry="22" fill="url(#cloudDrizzleMod)"/>
<circle cx="-24" cy="10" r="16" fill="url(#cloudDrizzleMod)"/>
<circle cx="10" cy="4" r="20" fill="url(#cloudDrizzleMod)"/>
<circle cx="30" cy="12" r="14" fill="url(#cloudDrizzleMod)"/>
</g>
<!-- Moderate drizzle (more frequent drops) -->
<g stroke="#FFFFFF" stroke-width="4" stroke-linecap="round" opacity="0.9">
<line x1="34" y1="86" x2="34" y2="100"/>
<line x1="50" y1="92" x2="50" y2="106"/>
<line x1="66" y1="86" x2="66" y2="100"/>
<line x1="82" y1="92" x2="82" y2="106"/>
<line x1="98" y1="86" x2="98" y2="100"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Bruine dense">
<title>Bruine dense</title>
<defs>
<linearGradient id="skyDrizzleDense" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#7EA9D6"/>
<stop offset="100%" stop-color="#4F7FAF"/>
</linearGradient>
<linearGradient id="cloudDrizzleDense" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E8EDF2"/>
<stop offset="100%" stop-color="#B9C6D3"/>
</linearGradient>
</defs>
<!-- Darker sky -->
<rect width="128" height="128" rx="20" fill="url(#skyDrizzleDense)"/>
<!-- Thick cloud -->
<g transform="translate(64 54)">
<ellipse cx="0" cy="18" rx="44" ry="24" fill="url(#cloudDrizzleDense)"/>
<circle cx="-26" cy="12" r="18" fill="url(#cloudDrizzleDense)"/>
<circle cx="12" cy="4" r="22" fill="url(#cloudDrizzleDense)"/>
<circle cx="34" cy="14" r="16" fill="url(#cloudDrizzleDense)"/>
</g>
<!-- Dense drizzle (many close drops) -->
<g stroke="#FFFFFF" stroke-width="4.5" stroke-linecap="round" opacity="0.95">
<line x1="26" y1="84" x2="26" y2="104"/>
<line x1="38" y1="90" x2="38" y2="110"/>
<line x1="50" y1="84" x2="50" y2="104"/>
<line x1="62" y1="90" x2="62" y2="110"/>
<line x1="74" y1="84" x2="74" y2="104"/>
<line x1="86" y1="90" x2="86" y2="110"/>
<line x1="98" y1="84" x2="98" y2="104"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Bruine verglaçante">
<title>Bruine verglaçante</title>
<defs>
<linearGradient id="skyFreezingDrizzle" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#9FD0F7"/>
<stop offset="100%" stop-color="#6E9FC8"/>
</linearGradient>
<linearGradient id="cloudFreezingDrizzle" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F4F9FF"/>
<stop offset="100%" stop-color="#D2DEE9"/>
</linearGradient>
</defs>
<!-- Cold sky -->
<rect width="128" height="128" rx="20" fill="url(#skyFreezingDrizzle)"/>
<!-- Cloud -->
<g transform="translate(64 54)">
<ellipse cx="0" cy="18" rx="44" ry="24" fill="url(#cloudFreezingDrizzle)"/>
<circle cx="-26" cy="12" r="18" fill="url(#cloudFreezingDrizzle)"/>
<circle cx="12" cy="4" r="22" fill="url(#cloudFreezingDrizzle)"/>
<circle cx="34" cy="14" r="16" fill="url(#cloudFreezingDrizzle)"/>
</g>
<!-- Freezing drizzle (thin icy drops) -->
<g stroke="#FFFFFF" stroke-width="4" stroke-linecap="round" opacity="0.95">
<line x1="34" y1="86" x2="34" y2="102"/>
<line x1="50" y1="92" x2="50" y2="108"/>
<line x1="66" y1="86" x2="66" y2="102"/>
<line x1="82" y1="92" x2="82" y2="108"/>
<line x1="98" y1="86" x2="98" y2="102"/>
</g>
<!-- Ice indicator (snowflake accent) -->
<g transform="translate(98 100)">
<circle cx="0" cy="0" r="12" fill="#FFFFFF" opacity="0.25"/>
<g stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round">
<line x1="0" y1="-7" x2="0" y2="7"/>
<line x1="-7" y1="0" x2="7" y2="0"/>
<line x1="-5" y1="-5" x2="5" y2="5"/>
<line x1="-5" y1="5" x2="5" y2="-5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,47 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Bruine verglaçante dense">
<title>Bruine verglaçante dense</title>
<defs>
<linearGradient id="skyFZD" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#7FB4E3"/>
<stop offset="100%" stop-color="#4E7EA8"/>
</linearGradient>
<linearGradient id="cloudFZD" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#EEF6FF"/>
<stop offset="100%" stop-color="#C7D6E6"/>
</linearGradient>
</defs>
<!-- Cold dense sky -->
<rect width="128" height="128" rx="20" fill="url(#skyFZD)"/>
<!-- Thick cloud -->
<g transform="translate(64 52)">
<ellipse cx="0" cy="20" rx="46" ry="26" fill="url(#cloudFZD)"/>
<circle cx="-28" cy="14" r="20" fill="url(#cloudFZD)"/>
<circle cx="14" cy="6" r="24" fill="url(#cloudFZD)"/>
<circle cx="36" cy="16" r="18" fill="url(#cloudFZD)"/>
</g>
<!-- Dense freezing drizzle -->
<g stroke="#FFFFFF" stroke-width="4.5" stroke-linecap="round" opacity="0.95">
<line x1="24" y1="84" x2="24" y2="110"/>
<line x1="36" y1="90" x2="36" y2="116"/>
<line x1="48" y1="84" x2="48" y2="110"/>
<line x1="60" y1="90" x2="60" y2="116"/>
<line x1="72" y1="84" x2="72" y2="110"/>
<line x1="84" y1="90" x2="84" y2="116"/>
<line x1="96" y1="84" x2="96" y2="110"/>
<line x1="108" y1="90" x2="108" y2="116"/>
</g>
<!-- Ice symbol -->
<g transform="translate(100 98)">
<circle cx="0" cy="0" r="14" fill="#FFFFFF" opacity="0.25"/>
<g stroke="#FFFFFF" stroke-width="3" stroke-linecap="round">
<line x1="0" y1="-8" x2="0" y2="8"/>
<line x1="-8" y1="0" x2="8" y2="0"/>
<line x1="-6" y1="-6" x2="6" y2="6"/>
<line x1="-6" y1="6" x2="6" y2="-6"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Pluie légère">
<title>Pluie légère</title>
<defs>
<linearGradient id="skyLightRain" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#8FBCEB"/>
<stop offset="100%" stop-color="#5F8FC2"/>
</linearGradient>
<linearGradient id="cloudLightRain" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F1F4F8"/>
<stop offset="100%" stop-color="#D3DAE2"/>
</linearGradient>
<linearGradient id="dropLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#BFE4FF"/>
<stop offset="100%" stop-color="#5DA9E9"/>
</linearGradient>
</defs>
<!-- Sky -->
<rect width="128" height="128" rx="20" fill="url(#skyLightRain)"/>
<!-- Cloud -->
<g transform="translate(64 54)">
<ellipse cx="0" cy="18" rx="42" ry="22" fill="url(#cloudLightRain)"/>
<circle cx="-24" cy="12" r="16" fill="url(#cloudLightRain)"/>
<circle cx="10" cy="4" r="20" fill="url(#cloudLightRain)"/>
<circle cx="30" cy="14" r="14" fill="url(#cloudLightRain)"/>
</g>
<!-- Light rain drops -->
<g fill="url(#dropLight)">
<path d="M40 86 C40 82 44 78 44 78 C44 78 48 82 48 86 A4 4 0 1 1 40 86 Z"/>
<path d="M60 92 C60 88 64 84 64 84 C64 84 68 88 68 92 A4 4 0 1 1 60 92 Z"/>
<path d="M80 86 C80 82 84 78 84 78 C84 78 88 82 88 86 A4 4 0 1 1 80 86 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Pluie modérée">
<title>Pluie modérée</title>
<defs>
<linearGradient id="skyModerateRain" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#7EA9D6"/>
<stop offset="100%" stop-color="#4F7FAF"/>
</linearGradient>
<linearGradient id="cloudModerateRain" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E8EDF2"/>
<stop offset="100%" stop-color="#B9C6D3"/>
</linearGradient>
<linearGradient id="dropModerate" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#CFE9FF"/>
<stop offset="100%" stop-color="#3D8EDB"/>
</linearGradient>
</defs>
<!-- Sky -->
<rect width="128" height="128" rx="20" fill="url(#skyModerateRain)"/>
<!-- Cloud -->
<g transform="translate(64 52)">
<ellipse cx="0" cy="20" rx="44" ry="24" fill="url(#cloudModerateRain)"/>
<circle cx="-26" cy="14" r="18" fill="url(#cloudModerateRain)"/>
<circle cx="12" cy="6" r="22" fill="url(#cloudModerateRain)"/>
<circle cx="34" cy="16" r="16" fill="url(#cloudModerateRain)"/>
</g>
<!-- Moderate rain drops -->
<g fill="url(#dropModerate)">
<path d="M30 84 C30 78 36 72 36 72 C36 72 42 78 42 84 A6 6 0 1 1 30 84 Z"/>
<path d="M50 94 C50 88 56 82 56 82 C56 82 62 88 62 94 A6 6 0 1 1 50 94 Z"/>
<path d="M70 84 C70 78 76 72 76 72 C76 72 82 78 82 84 A6 6 0 1 1 70 84 Z"/>
<path d="M90 94 C90 88 96 82 96 82 C96 82 102 88 102 94 A6 6 0 1 1 90 94 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Pluie forte">
<title>Pluie forte</title>
<defs>
<linearGradient id="skyHeavyRain" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#5F8FC2"/>
<stop offset="100%" stop-color="#2F5E8F"/>
</linearGradient>
<linearGradient id="cloudHeavyRain" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#DDE3EA"/>
<stop offset="100%" stop-color="#AEBBC8"/>
</linearGradient>
<linearGradient id="dropHeavy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#BFE4FF"/>
<stop offset="100%" stop-color="#1E6FBF"/>
</linearGradient>
</defs>
<!-- Dark sky -->
<rect width="128" height="128" rx="20" fill="url(#skyHeavyRain)"/>
<!-- Large dense cloud -->
<g transform="translate(64 50)">
<ellipse cx="0" cy="22" rx="46" ry="26" fill="url(#cloudHeavyRain)"/>
<circle cx="-28" cy="16" r="20" fill="url(#cloudHeavyRain)"/>
<circle cx="14" cy="6" r="24" fill="url(#cloudHeavyRain)"/>
<circle cx="38" cy="18" r="18" fill="url(#cloudHeavyRain)"/>
</g>
<!-- Heavy rain drops -->
<g fill="url(#dropHeavy)">
<path d="M22 84 C22 74 32 64 32 64 C32 64 42 74 42 84 A10 10 0 1 1 22 84 Z"/>
<path d="M44 98 C44 88 54 78 54 78 C54 78 64 88 64 98 A10 10 0 1 1 44 98 Z"/>
<path d="M66 84 C66 74 76 64 76 64 C76 64 86 74 86 84 A10 10 0 1 1 66 84 Z"/>
<path d="M88 98 C88 88 98 78 98 78 C98 78 108 88 108 98 A10 10 0 1 1 88 98 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,45 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Pluie verglaçante légère">
<title>Pluie verglaçante légère</title>
<defs>
<linearGradient id="skyFreezingRainLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#9FD0F7"/>
<stop offset="100%" stop-color="#6E9FC8"/>
</linearGradient>
<linearGradient id="cloudFreezingRainLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F4F9FF"/>
<stop offset="100%" stop-color="#D2DEE9"/>
</linearGradient>
<linearGradient id="dropIceLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E6F7FF"/>
<stop offset="100%" stop-color="#4FA3E3"/>
</linearGradient>
</defs>
<!-- Cold sky -->
<rect width="128" height="128" rx="20" fill="url(#skyFreezingRainLight)"/>
<!-- Cloud -->
<g transform="translate(64 54)">
<ellipse cx="0" cy="18" rx="42" ry="22" fill="url(#cloudFreezingRainLight)"/>
<circle cx="-24" cy="12" r="16" fill="url(#cloudFreezingRainLight)"/>
<circle cx="10" cy="4" r="20" fill="url(#cloudFreezingRainLight)"/>
<circle cx="30" cy="14" r="14" fill="url(#cloudFreezingRainLight)"/>
</g>
<!-- Light freezing rain drops -->
<g fill="url(#dropIceLight)">
<path d="M42 88 C42 82 48 76 48 76 C48 76 54 82 54 88 A6 6 0 1 1 42 88 Z"/>
<path d="M70 94 C70 88 76 82 76 82 C76 82 82 88 82 94 A6 6 0 1 1 70 94 Z"/>
</g>
<!-- Ice indicator -->
<g transform="translate(98 100)">
<circle cx="0" cy="0" r="12" fill="#FFFFFF" opacity="0.25"/>
<g stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round">
<line x1="0" y1="-7" x2="0" y2="7"/>
<line x1="-7" y1="0" x2="7" y2="0"/>
<line x1="-5" y1="-5" x2="5" y2="5"/>
<line x1="-5" y1="5" x2="5" y2="-5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,47 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Pluie verglaçante forte">
<title>Pluie verglaçante forte</title>
<defs>
<linearGradient id="skyFZRHeavy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6FA8D6"/>
<stop offset="100%" stop-color="#2E5E8C"/>
</linearGradient>
<linearGradient id="cloudFZRHeavy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#EAF2F9"/>
<stop offset="100%" stop-color="#B7C6D6"/>
</linearGradient>
<linearGradient id="dropFZRHeavy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E6F7FF"/>
<stop offset="100%" stop-color="#1E78C8"/>
</linearGradient>
</defs>
<!-- Cold dark sky -->
<rect width="128" height="128" rx="20" fill="url(#skyFZRHeavy)"/>
<!-- Large dense cloud -->
<g transform="translate(64 50)">
<ellipse cx="0" cy="22" rx="46" ry="26" fill="url(#cloudFZRHeavy)"/>
<circle cx="-28" cy="16" r="20" fill="url(#cloudFZRHeavy)"/>
<circle cx="14" cy="6" r="24" fill="url(#cloudFZRHeavy)"/>
<circle cx="38" cy="18" r="18" fill="url(#cloudFZRHeavy)"/>
</g>
<!-- Heavy freezing rain drops -->
<g fill="url(#dropFZRHeavy)">
<path d="M20 84 C20 72 34 60 34 60 C34 60 48 72 48 84 A12 12 0 1 1 20 84 Z"/>
<path d="M44 100 C44 88 58 76 58 76 C58 76 72 88 72 100 A12 12 0 1 1 44 100 Z"/>
<path d="M68 84 C68 72 82 60 82 60 C82 60 96 72 96 84 A12 12 0 1 1 68 84 Z"/>
<path d="M92 100 C92 88 106 76 106 76 C106 76 120 88 120 100 A12 12 0 1 1 92 100 Z"/>
</g>
<!-- Ice symbol -->
<g transform="translate(96 92)">
<circle cx="0" cy="0" r="14" fill="#FFFFFF" opacity="0.25"/>
<g stroke="#FFFFFF" stroke-width="3" stroke-linecap="round">
<line x1="0" y1="-8" x2="0" y2="8"/>
<line x1="-8" y1="0" x2="8" y2="0"/>
<line x1="-6" y1="-6" x2="6" y2="6"/>
<line x1="-6" y1="6" x2="6" y2="-6"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,49 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Chute de neige faible">
<title>Chute de neige faible</title>
<defs>
<linearGradient id="skyLightSnow" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#BFD9F2"/>
<stop offset="100%" stop-color="#8FB3D1"/>
</linearGradient>
<linearGradient id="cloudLightSnow" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5F8FB"/>
<stop offset="100%" stop-color="#D8E0E8"/>
</linearGradient>
</defs>
<!-- Sky -->
<rect width="128" height="128" rx="20" fill="url(#skyLightSnow)"/>
<!-- Cloud -->
<g transform="translate(64 54)">
<ellipse cx="0" cy="18" rx="42" ry="22" fill="url(#cloudLightSnow)"/>
<circle cx="-24" cy="12" r="16" fill="url(#cloudLightSnow)"/>
<circle cx="10" cy="4" r="20" fill="url(#cloudLightSnow)"/>
<circle cx="30" cy="14" r="14" fill="url(#cloudLightSnow)"/>
</g>
<!-- Light snowflakes -->
<g stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round">
<!-- Flake 1 -->
<g transform="translate(44 92)">
<line x1="0" y1="-6" x2="0" y2="6"/>
<line x1="-6" y1="0" x2="6" y2="0"/>
<line x1="-4" y1="-4" x2="4" y2="4"/>
<line x1="-4" y1="4" x2="4" y2="-4"/>
</g>
<!-- Flake 2 -->
<g transform="translate(64 100)">
<line x1="0" y1="-6" x2="0" y2="6"/>
<line x1="-6" y1="0" x2="6" y2="0"/>
<line x1="-4" y1="-4" x2="4" y2="4"/>
<line x1="-4" y1="4" x2="4" y2="-4"/>
</g>
<!-- Flake 3 -->
<g transform="translate(84 92)">
<line x1="0" y1="-6" x2="0" y2="6"/>
<line x1="-6" y1="0" x2="6" y2="0"/>
<line x1="-4" y1="-4" x2="4" y2="4"/>
<line x1="-4" y1="4" x2="4" y2="-4"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,56 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Chute de neige modérée">
<title>Chute de neige modérée</title>
<defs>
<linearGradient id="skySnowMod" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#AFCDE8"/>
<stop offset="100%" stop-color="#6F9BC2"/>
</linearGradient>
<linearGradient id="cloudSnowMod" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#EEF3F8"/>
<stop offset="100%" stop-color="#C9D5E0"/>
</linearGradient>
</defs>
<!-- Sky -->
<rect width="128" height="128" rx="20" fill="url(#skySnowMod)"/>
<!-- Cloud -->
<g transform="translate(64 52)">
<ellipse cx="0" cy="20" rx="44" ry="24" fill="url(#cloudSnowMod)"/>
<circle cx="-26" cy="14" r="18" fill="url(#cloudSnowMod)"/>
<circle cx="12" cy="6" r="22" fill="url(#cloudSnowMod)"/>
<circle cx="34" cy="16" r="16" fill="url(#cloudSnowMod)"/>
</g>
<!-- Moderate snowflakes -->
<g stroke="#FFFFFF" stroke-width="3" stroke-linecap="round">
<!-- Flake 1 -->
<g transform="translate(32 88)">
<line x1="0" y1="-7" x2="0" y2="7"/>
<line x1="-7" y1="0" x2="7" y2="0"/>
<line x1="-5" y1="-5" x2="5" y2="5"/>
<line x1="-5" y1="5" x2="5" y2="-5"/>
</g>
<!-- Flake 2 -->
<g transform="translate(52 98)">
<line x1="0" y1="-7" x2="0" y2="7"/>
<line x1="-7" y1="0" x2="7" y2="0"/>
<line x1="-5" y1="-5" x2="5" y2="5"/>
<line x1="-5" y1="5" x2="5" y2="-5"/>
</g>
<!-- Flake 3 -->
<g transform="translate(72 88)">
<line x1="0" y1="-7" x2="0" y2="7"/>
<line x1="-7" y1="0" x2="7" y2="0"/>
<line x1="-5" y1="-5" x2="5" y2="5"/>
<line x1="-5" y1="5" x2="5" y2="-5"/>
</g>
<!-- Flake 4 -->
<g transform="translate(92 98)">
<line x1="0" y1="-7" x2="0" y2="7"/>
<line x1="-7" y1="0" x2="7" y2="0"/>
<line x1="-5" y1="-5" x2="5" y2="5"/>
<line x1="-5" y1="5" x2="5" y2="-5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,63 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Chute de neige forte">
<title>Chute de neige forte</title>
<defs>
<linearGradient id="skySnowHeavy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#8FB3D1"/>
<stop offset="100%" stop-color="#4E7EA8"/>
</linearGradient>
<linearGradient id="cloudSnowHeavy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E6EDF4"/>
<stop offset="100%" stop-color="#B7C6D6"/>
</linearGradient>
</defs>
<!-- Darker snowy sky -->
<rect width="128" height="128" rx="20" fill="url(#skySnowHeavy)"/>
<!-- Large dense cloud -->
<g transform="translate(64 50)">
<ellipse cx="0" cy="22" rx="46" ry="26" fill="url(#cloudSnowHeavy)"/>
<circle cx="-28" cy="16" r="20" fill="url(#cloudSnowHeavy)"/>
<circle cx="14" cy="6" r="24" fill="url(#cloudSnowHeavy)"/>
<circle cx="38" cy="18" r="18" fill="url(#cloudSnowHeavy)"/>
</g>
<!-- Heavy snowflakes -->
<g stroke="#FFFFFF" stroke-width="3.5" stroke-linecap="round">
<!-- Flake 1 -->
<g transform="translate(24 86)">
<line x1="0" y1="-9" x2="0" y2="9"/>
<line x1="-9" y1="0" x2="9" y2="0"/>
<line x1="-6" y1="-6" x2="6" y2="6"/>
<line x1="-6" y1="6" x2="6" y2="-6"/>
</g>
<!-- Flake 2 -->
<g transform="translate(44 100)">
<line x1="0" y1="-9" x2="0" y2="9"/>
<line x1="-9" y1="0" x2="9" y2="0"/>
<line x1="-6" y1="-6" x2="6" y2="6"/>
<line x1="-6" y1="6" x2="6" y2="-6"/>
</g>
<!-- Flake 3 -->
<g transform="translate(64 86)">
<line x1="0" y1="-9" x2="0" y2="9"/>
<line x1="-9" y1="0" x2="9" y2="0"/>
<line x1="-6" y1="-6" x2="6" y2="6"/>
<line x1="-6" y1="6" x2="6" y2="-6"/>
</g>
<!-- Flake 4 -->
<g transform="translate(84 100)">
<line x1="0" y1="-9" x2="0" y2="9"/>
<line x1="-9" y1="0" x2="9" y2="0"/>
<line x1="-6" y1="-6" x2="6" y2="6"/>
<line x1="-6" y1="6" x2="6" y2="-6"/>
</g>
<!-- Flake 5 -->
<g transform="translate(104 86)">
<line x1="0" y1="-9" x2="0" y2="9"/>
<line x1="-9" y1="0" x2="9" y2="0"/>
<line x1="-6" y1="-6" x2="6" y2="6"/>
<line x1="-6" y1="6" x2="6" y2="-6"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Neige en grains">
<title>Neige en grains</title>
<defs>
<linearGradient id="skySnowGrain" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#AFCDE8"/>
<stop offset="100%" stop-color="#6F9BC2"/>
</linearGradient>
<linearGradient id="cloudSnowGrain" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#EEF3F8"/>
<stop offset="100%" stop-color="#C9D5E0"/>
</linearGradient>
</defs>
<!-- Sky -->
<rect width="128" height="128" rx="20" fill="url(#skySnowGrain)"/>
<!-- Cloud -->
<g transform="translate(64 52)">
<ellipse cx="0" cy="20" rx="44" ry="24" fill="url(#cloudSnowGrain)"/>
<circle cx="-26" cy="14" r="18" fill="url(#cloudSnowGrain)"/>
<circle cx="12" cy="6" r="22" fill="url(#cloudSnowGrain)"/>
<circle cx="34" cy="16" r="16" fill="url(#cloudSnowGrain)"/>
</g>
<!-- Snow grains (small pellets) -->
<g fill="#FFFFFF">
<circle cx="28" cy="88" r="3"/>
<circle cx="40" cy="96" r="3"/>
<circle cx="52" cy="88" r="3"/>
<circle cx="64" cy="100" r="3"/>
<circle cx="76" cy="88" r="3"/>
<circle cx="88" cy="96" r="3"/>
<circle cx="100" cy="88" r="3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Averse de pluie légère">
<title>Averse de pluie légère</title>
<defs>
<linearGradient id="skyShowerLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#7EA9D6"/>
<stop offset="100%" stop-color="#3E6E9E"/>
</linearGradient>
<linearGradient id="cloudShowerLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E8EDF2"/>
<stop offset="100%" stop-color="#B9C6D3"/>
</linearGradient>
<linearGradient id="dropShowerLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#CFE9FF"/>
<stop offset="100%" stop-color="#2F86D6"/>
</linearGradient>
</defs>
<!-- Sky -->
<rect width="128" height="128" rx="20" fill="url(#skyShowerLight)"/>
<!-- Darker cloud (shower type) -->
<g transform="translate(64 50)">
<ellipse cx="0" cy="22" rx="46" ry="26" fill="url(#cloudShowerLight)"/>
<circle cx="-28" cy="16" r="20" fill="url(#cloudShowerLight)"/>
<circle cx="14" cy="6" r="24" fill="url(#cloudShowerLight)"/>
<circle cx="38" cy="18" r="18" fill="url(#cloudShowerLight)"/>
</g>
<!-- Light shower drops (slanted to show burst effect) -->
<g fill="url(#dropShowerLight)" transform="rotate(-15 64 90)">
<path d="M38 88 C38 82 44 76 44 76 C44 76 50 82 50 88 A6 6 0 1 1 38 88 Z"/>
<path d="M62 96 C62 90 68 84 68 84 C68 84 74 90 74 96 A6 6 0 1 1 62 96 Z"/>
<path d="M86 88 C86 82 92 76 92 76 C92 76 98 82 98 88 A6 6 0 1 1 86 88 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Averse de pluie modérée">
<title>Averse de pluie modérée</title>
<defs>
<linearGradient id="skyShowerModerate" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6F9BC2"/>
<stop offset="100%" stop-color="#2E5E8C"/>
</linearGradient>
<linearGradient id="cloudShowerModerate" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#DDE3EA"/>
<stop offset="100%" stop-color="#AEBBC8"/>
</linearGradient>
<linearGradient id="dropShowerModerate" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#CFE9FF"/>
<stop offset="100%" stop-color="#1E6FBF"/>
</linearGradient>
</defs>
<!-- Dark sky -->
<rect width="128" height="128" rx="20" fill="url(#skyShowerModerate)"/>
<!-- Dense cloud -->
<g transform="translate(64 48)">
<ellipse cx="0" cy="24" rx="48" ry="28" fill="url(#cloudShowerModerate)"/>
<circle cx="-30" cy="18" r="22" fill="url(#cloudShowerModerate)"/>
<circle cx="16" cy="8" r="26" fill="url(#cloudShowerModerate)"/>
<circle cx="42" cy="20" r="20" fill="url(#cloudShowerModerate)"/>
</g>
<!-- Moderate shower drops (slanted, more numerous) -->
<g fill="url(#dropShowerModerate)" transform="rotate(-18 64 92)">
<path d="M26 86 C26 76 38 64 38 64 C38 64 50 76 50 86 A10 10 0 1 1 26 86 Z"/>
<path d="M52 100 C52 90 64 78 64 78 C64 78 76 90 76 100 A10 10 0 1 1 52 100 Z"/>
<path d="M78 86 C78 76 90 64 90 64 C90 64 102 76 102 86 A10 10 0 1 1 78 86 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Averse de pluie violente">
<title>Averse de pluie violente</title>
<defs>
<linearGradient id="skyStormShower" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3E6E9E"/>
<stop offset="100%" stop-color="#1E3F63"/>
</linearGradient>
<linearGradient id="cloudStormShower" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C9D3DD"/>
<stop offset="100%" stop-color="#8FA0B2"/>
</linearGradient>
<linearGradient id="dropStorm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#BFE4FF"/>
<stop offset="100%" stop-color="#0F5EA8"/>
</linearGradient>
</defs>
<!-- Dark storm sky -->
<rect width="128" height="128" rx="20" fill="url(#skyStormShower)"/>
<!-- Massive storm cloud -->
<g transform="translate(64 46)">
<ellipse cx="0" cy="26" rx="50" ry="30" fill="url(#cloudStormShower)"/>
<circle cx="-32" cy="20" r="24" fill="url(#cloudStormShower)"/>
<circle cx="18" cy="8" r="28" fill="url(#cloudStormShower)"/>
<circle cx="46" cy="22" r="22" fill="url(#cloudStormShower)"/>
</g>
<!-- Violent shower drops (large, angled, numerous) -->
<g fill="url(#dropStorm)" transform="rotate(-22 64 92)">
<path d="M16 84 C16 70 34 52 34 52 C34 52 52 70 52 84 A14 14 0 1 1 16 84 Z"/>
<path d="M44 104 C44 90 62 72 62 72 C62 72 80 90 80 104 A14 14 0 1 1 44 104 Z"/>
<path d="M72 84 C72 70 90 52 90 52 C90 52 108 70 108 84 A14 14 0 1 1 72 84 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,49 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Averse de neige légère">
<title>Averse de neige légère</title>
<defs>
<linearGradient id="skySnowShowerLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#AFCDE8"/>
<stop offset="100%" stop-color="#6F9BC2"/>
</linearGradient>
<linearGradient id="cloudSnowShowerLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#EEF3F8"/>
<stop offset="100%" stop-color="#C9D5E0"/>
</linearGradient>
</defs>
<!-- Sky -->
<rect width="128" height="128" rx="20" fill="url(#skySnowShowerLight)"/>
<!-- Cloud -->
<g transform="translate(64 50)">
<ellipse cx="0" cy="22" rx="46" ry="26" fill="url(#cloudSnowShowerLight)"/>
<circle cx="-28" cy="16" r="20" fill="url(#cloudSnowShowerLight)"/>
<circle cx="14" cy="6" r="24" fill="url(#cloudSnowShowerLight)"/>
<circle cx="38" cy="18" r="18" fill="url(#cloudSnowShowerLight)"/>
</g>
<!-- Light snow shower (slightly angled flakes) -->
<g stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round" transform="rotate(-15 64 92)">
<!-- Flake 1 -->
<g transform="translate(44 92)">
<line x1="0" y1="-6" x2="0" y2="6"/>
<line x1="-6" y1="0" x2="6" y2="0"/>
<line x1="-4" y1="-4" x2="4" y2="4"/>
<line x1="-4" y1="4" x2="4" y2="-4"/>
</g>
<!-- Flake 2 -->
<g transform="translate(64 100)">
<line x1="0" y1="-6" x2="0" y2="6"/>
<line x1="-6" y1="0" x2="6" y2="0"/>
<line x1="-4" y1="-4" x2="4" y2="4"/>
<line x1="-4" y1="4" x2="4" y2="-4"/>
</g>
<!-- Flake 3 -->
<g transform="translate(84 92)">
<line x1="0" y1="-6" x2="0" y2="6"/>
<line x1="-6" y1="0" x2="6" y2="0"/>
<line x1="-4" y1="-4" x2="4" y2="4"/>
<line x1="-4" y1="4" x2="4" y2="-4"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,56 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Averse de neige forte">
<title>Averse de neige forte</title>
<defs>
<linearGradient id="skySnowShowerHeavy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#7EA9D6"/>
<stop offset="100%" stop-color="#3E6E9E"/>
</linearGradient>
<linearGradient id="cloudSnowShowerHeavy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E6EDF4"/>
<stop offset="100%" stop-color="#B7C6D6"/>
</linearGradient>
</defs>
<!-- Dark snowy sky -->
<rect width="128" height="128" rx="20" fill="url(#skySnowShowerHeavy)"/>
<!-- Massive cloud -->
<g transform="translate(64 46)">
<ellipse cx="0" cy="26" rx="50" ry="30" fill="url(#cloudSnowShowerHeavy)"/>
<circle cx="-32" cy="20" r="24" fill="url(#cloudSnowShowerHeavy)"/>
<circle cx="18" cy="8" r="28" fill="url(#cloudSnowShowerHeavy)"/>
<circle cx="46" cy="22" r="22" fill="url(#cloudSnowShowerHeavy)"/>
</g>
<!-- Heavy snow shower (angled, larger flakes) -->
<g stroke="#FFFFFF" stroke-width="3.5" stroke-linecap="round" transform="rotate(-18 64 92)">
<!-- Flake 1 -->
<g transform="translate(28 88)">
<line x1="0" y1="-9" x2="0" y2="9"/>
<line x1="-9" y1="0" x2="9" y2="0"/>
<line x1="-6" y1="-6" x2="6" y2="6"/>
<line x1="-6" y1="6" x2="6" y2="-6"/>
</g>
<!-- Flake 2 -->
<g transform="translate(52 104)">
<line x1="0" y1="-9" x2="0" y2="9"/>
<line x1="-9" y1="0" x2="9" y2="0"/>
<line x1="-6" y1="-6" x2="6" y2="6"/>
<line x1="-6" y1="6" x2="6" y2="-6"/>
</g>
<!-- Flake 3 -->
<g transform="translate(76 88)">
<line x1="0" y1="-9" x2="0" y2="9"/>
<line x1="-9" y1="0" x2="9" y2="0"/>
<line x1="-6" y1="-6" x2="6" y2="6"/>
<line x1="-6" y1="6" x2="6" y2="-6"/>
</g>
<!-- Flake 4 -->
<g transform="translate(100 104)">
<line x1="0" y1="-9" x2="0" y2="9"/>
<line x1="-9" y1="0" x2="9" y2="0"/>
<line x1="-6" y1="-6" x2="6" y2="6"/>
<line x1="-6" y1="6" x2="6" y2="-6"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,42 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Orage léger">
<title>Orage léger</title>
<defs>
<linearGradient id="skyLightStorm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6F8FB0"/>
<stop offset="100%" stop-color="#2E4F6E"/>
</linearGradient>
<linearGradient id="cloudLightStorm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#DDE3EA"/>
<stop offset="100%" stop-color="#AAB7C4"/>
</linearGradient>
<linearGradient id="lightningLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFF7A8"/>
<stop offset="100%" stop-color="#FFC400"/>
</linearGradient>
<linearGradient id="rainLightStorm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#CFE9FF"/>
<stop offset="100%" stop-color="#2F86D6"/>
</linearGradient>
</defs>
<!-- Storm sky -->
<rect width="128" height="128" rx="20" fill="url(#skyLightStorm)"/>
<!-- Cloud -->
<g transform="translate(64 48)">
<ellipse cx="0" cy="24" rx="48" ry="28" fill="url(#cloudLightStorm)"/>
<circle cx="-30" cy="18" r="22" fill="url(#cloudLightStorm)"/>
<circle cx="16" cy="8" r="26" fill="url(#cloudLightStorm)"/>
<circle cx="42" cy="20" r="20" fill="url(#cloudLightStorm)"/>
</g>
<!-- Lightning bolt (small) -->
<polygon points="66,70 54,92 66,92 58,112 82,82 68,82"
fill="url(#lightningLight)"/>
<!-- Light rain -->
<g fill="url(#rainLightStorm)" opacity="0.9">
<path d="M34 94 C34 88 40 82 40 82 C40 82 46 88 46 94 A6 6 0 1 1 34 94 Z"/>
<path d="M86 98 C86 92 92 86 92 86 C92 86 98 92 98 98 A6 6 0 1 1 86 98 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Orage avec grêle légère">
<title>Orage avec grêle légère</title>
<defs>
<linearGradient id="skyStormHailLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#4E7EA8"/>
<stop offset="100%" stop-color="#1E3F63"/>
</linearGradient>
<linearGradient id="cloudStormHailLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#D1DAE3"/>
<stop offset="100%" stop-color="#98A8B8"/>
</linearGradient>
<linearGradient id="boltHailLight" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFF7A8"/>
<stop offset="100%" stop-color="#FFB300"/>
</linearGradient>
</defs>
<!-- Storm sky -->
<rect width="128" height="128" rx="20" fill="url(#skyStormHailLight)"/>
<!-- Cloud -->
<g transform="translate(64 46)">
<ellipse cx="0" cy="26" rx="50" ry="30" fill="url(#cloudStormHailLight)"/>
<circle cx="-32" cy="20" r="24" fill="url(#cloudStormHailLight)"/>
<circle cx="18" cy="8" r="28" fill="url(#cloudStormHailLight)"/>
<circle cx="46" cy="22" r="22" fill="url(#cloudStormHailLight)"/>
</g>
<!-- Lightning bolt -->
<polygon points="68,66 52,96 66,96 56,120 90,84 72,84"
fill="url(#boltHailLight)"/>
<!-- Light hail (small pellets) -->
<g fill="#FFFFFF" opacity="0.95">
<circle cx="34" cy="92" r="4"/>
<circle cx="52" cy="104" r="4"/>
<circle cx="70" cy="92" r="4"/>
<circle cx="88" cy="104" r="4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,42 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Orage avec grêle forte">
<title>Orage avec grêle forte</title>
<defs>
<linearGradient id="skyStormHailHeavy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3E6E9E"/>
<stop offset="100%" stop-color="#162F47"/>
</linearGradient>
<linearGradient id="cloudStormHailHeavy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#C9D3DD"/>
<stop offset="100%" stop-color="#7F93A6"/>
</linearGradient>
<linearGradient id="boltHailHeavy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFF7A8"/>
<stop offset="100%" stop-color="#FF9F00"/>
</linearGradient>
</defs>
<!-- Dark storm sky -->
<rect width="128" height="128" rx="20" fill="url(#skyStormHailHeavy)"/>
<!-- Massive cloud -->
<g transform="translate(64 44)">
<ellipse cx="0" cy="28" rx="52" ry="32" fill="url(#cloudStormHailHeavy)"/>
<circle cx="-34" cy="22" r="26" fill="url(#cloudStormHailHeavy)"/>
<circle cx="20" cy="10" r="30" fill="url(#cloudStormHailHeavy)"/>
<circle cx="48" cy="24" r="24" fill="url(#cloudStormHailHeavy)"/>
</g>
<!-- Large lightning bolt -->
<polygon points="70,60 50,100 66,100 54,126 96,82 74,82"
fill="url(#boltHailHeavy)"/>
<!-- Heavy hail (larger pellets, more numerous) -->
<g fill="#FFFFFF" opacity="0.95">
<circle cx="24" cy="92" r="6"/>
<circle cx="40" cy="108" r="6"/>
<circle cx="58" cy="92" r="6"/>
<circle cx="76" cy="108" r="6"/>
<circle cx="94" cy="92" r="6"/>
<circle cx="110" cy="108" r="6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,46 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Risque de canicule">
<title>Risque de canicule</title>
<defs>
<linearGradient id="skyHeatRisk" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFD27A"/>
<stop offset="100%" stop-color="#FF8C42"/>
</linearGradient>
<radialGradient id="sunHeat" cx="50%" cy="50%" r="60%">
<stop offset="0%" stop-color="#FFF2A6"/>
<stop offset="60%" stop-color="#FFC93C"/>
<stop offset="100%" stop-color="#FF9F00"/>
</radialGradient>
</defs>
<!-- Hot sky -->
<rect width="128" height="128" rx="20" fill="url(#skyHeatRisk)"/>
<!-- Intense sun -->
<g transform="translate(64 48)">
<circle r="26" fill="url(#sunHeat)"/>
<g stroke="#FFB300" stroke-width="6" stroke-linecap="round">
<line x1="0" y1="-44" x2="0" y2="-60"/>
<line x1="0" y1="44" x2="0" y2="60"/>
<line x1="-44" y1="0" x2="-60" y2="0"/>
<line x1="44" y1="0" x2="60" y2="0"/>
<line x1="31" y1="-31" x2="44" y2="-44"/>
<line x1="-31" y1="-31" x2="-44" y2="-44"/>
<line x1="31" y1="31" x2="44" y2="44"/>
<line x1="-31" y1="31" x2="-44" y2="44"/>
</g>
</g>
<!-- Thermometer (high temperature) -->
<g transform="translate(96 68)">
<rect x="-6" y="-28" width="12" height="40" rx="6" fill="#FFFFFF" stroke="#E53935" stroke-width="3"/>
<circle cx="0" cy="20" r="14" fill="#E53935"/>
<rect x="-2" y="-8" width="4" height="20" fill="#E53935"/>
</g>
<!-- Heat waves -->
<g stroke="#FF6F00" stroke-width="4" stroke-linecap="round" opacity="0.8">
<path d="M24 96 C28 88 20 84 24 76"/>
<path d="M44 102 C48 94 40 90 44 82"/>
<path d="M64 96 C68 88 60 84 64 76"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Risque de gel">
<title>Risque de gel</title>
<defs>
<linearGradient id="skyFrostRisk" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#BFE4FF"/>
<stop offset="100%" stop-color="#7FAFD6"/>
</linearGradient>
<linearGradient id="groundFrost" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#EAF4FF"/>
<stop offset="100%" stop-color="#C8DDF0"/>
</linearGradient>
</defs>
<!-- Cold sky -->
<rect width="128" height="128" rx="20" fill="url(#skyFrostRisk)"/>
<!-- Ground frost layer -->
<rect x="0" y="88" width="128" height="40" fill="url(#groundFrost)"/>
<!-- Thermometer (cold) -->
<g transform="translate(40 40)">
<rect x="10" y="0" width="12" height="46" rx="6" fill="#FFFFFF" stroke="#5FA8E3" stroke-width="3"/>
<circle cx="16" cy="56" r="14" fill="#5FA8E3"/>
<rect x="14" y="24" width="4" height="20" fill="#5FA8E3"/>
</g>
<!-- Snowflake warning symbol -->
<g transform="translate(88 54)" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round">
<circle cx="0" cy="0" r="18" fill="#FFFFFF" opacity="0.2"/>
<line x1="0" y1="-10" x2="0" y2="10"/>
<line x1="-10" y1="0" x2="10" y2="0"/>
<line x1="-7" y1="-7" x2="7" y2="7"/>
<line x1="-7" y1="7" x2="7" y2="-7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,9 +1,20 @@
/// <reference types="../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { ref } from 'vue';
import { RouterView } from 'vue-router';
import { RouterLink, RouterView } from 'vue-router';
import AppHeader from '@/components/AppHeader.vue';
import AppDrawer from '@/components/AppDrawer.vue';
const drawerOpen = ref(false);
const links = [
{ to: '/', label: 'Dashboard', icon: '🏠' },
{ to: '/jardins', label: 'Jardins', icon: '🪴' },
{ to: '/plantes', label: 'Plantes', icon: '🌱' },
{ to: '/outils', label: 'Outils', icon: '🔧' },
{ to: '/plantations', label: 'Plantations', icon: '🥕' },
{ to: '/taches', label: 'Tâches', icon: '✅' },
{ to: '/planning', label: 'Planning', icon: '📆' },
{ to: '/calendrier', label: 'Calendrier', icon: '🌙' },
{ to: '/reglages', label: 'Réglages', icon: '⚙️' },
];
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
@@ -12,9 +23,11 @@ let __VLS_directives;
// @ts-ignore
const __VLS_0 = __VLS_asFunctionalComponent(AppHeader, new AppHeader({
...{ 'onToggleDrawer': {} },
...{ class: "lg:hidden" },
}));
const __VLS_1 = __VLS_0({
...{ 'onToggleDrawer': {} },
...{ class: "lg:hidden" },
}, ...__VLS_functionalComponentArgsRest(__VLS_0));
let __VLS_3;
let __VLS_4;
@@ -44,24 +57,133 @@ const __VLS_13 = {
}
};
var __VLS_9;
__VLS_asFunctionalElement(__VLS_intrinsicElements.main, __VLS_intrinsicElements.main)({
...{ class: "pt-14 min-h-screen" },
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "lg:flex" },
});
const __VLS_14 = {}.RouterView;
__VLS_asFunctionalElement(__VLS_intrinsicElements.aside, __VLS_intrinsicElements.aside)({
...{ class: "hidden lg:flex lg:flex-col lg:fixed lg:inset-y-0 lg:w-60 bg-bg-hard border-r border-bg-soft z-30" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "px-5 pt-6 pb-4 border-b border-bg-soft" },
});
const __VLS_14 = {}.RouterLink;
/** @type {[typeof __VLS_components.RouterLink, typeof __VLS_components.RouterLink, ]} */ ;
// @ts-ignore
const __VLS_15 = __VLS_asFunctionalComponent(__VLS_14, new __VLS_14({
to: "/",
...{ class: "text-green font-bold text-xl tracking-wide flex items-center gap-2" },
}));
const __VLS_16 = __VLS_15({
to: "/",
...{ class: "text-green font-bold text-xl tracking-wide flex items-center gap-2" },
}, ...__VLS_functionalComponentArgsRest(__VLS_15));
__VLS_17.slots.default;
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
var __VLS_17;
__VLS_asFunctionalElement(__VLS_intrinsicElements.nav, __VLS_intrinsicElements.nav)({
...{ class: "flex-1 py-4 px-3 flex flex-col gap-0.5 overflow-y-auto" },
});
for (const [l] of __VLS_getVForSourceType((__VLS_ctx.links))) {
const __VLS_18 = {}.RouterLink;
/** @type {[typeof __VLS_components.RouterLink, typeof __VLS_components.RouterLink, ]} */ ;
// @ts-ignore
const __VLS_19 = __VLS_asFunctionalComponent(__VLS_18, new __VLS_18({
key: (l.to),
to: (l.to),
...{ class: "flex items-center gap-3 text-text-muted hover:text-text py-2 px-3 rounded-lg text-sm transition-colors group" },
activeClass: "bg-bg-soft text-green font-medium",
}));
const __VLS_20 = __VLS_19({
key: (l.to),
to: (l.to),
...{ class: "flex items-center gap-3 text-text-muted hover:text-text py-2 px-3 rounded-lg text-sm transition-colors group" },
activeClass: "bg-bg-soft text-green font-medium",
}, ...__VLS_functionalComponentArgsRest(__VLS_19));
__VLS_21.slots.default;
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-base leading-none" },
});
(l.icon);
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(l.label);
var __VLS_21;
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "px-4 py-4 border-t border-bg-soft text-text-muted text-xs" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.main, __VLS_intrinsicElements.main)({
...{ class: "pt-14 lg:pt-0 lg:pl-60 min-h-screen w-full" },
});
const __VLS_22 = {}.RouterView;
/** @type {[typeof __VLS_components.RouterView, ]} */ ;
// @ts-ignore
const __VLS_15 = __VLS_asFunctionalComponent(__VLS_14, new __VLS_14({}));
const __VLS_16 = __VLS_15({}, ...__VLS_functionalComponentArgsRest(__VLS_15));
const __VLS_23 = __VLS_asFunctionalComponent(__VLS_22, new __VLS_22({}));
const __VLS_24 = __VLS_23({}, ...__VLS_functionalComponentArgsRest(__VLS_23));
/** @type {__VLS_StyleScopedClasses['lg:hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:flex']} */ ;
/** @type {__VLS_StyleScopedClasses['hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:flex']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:flex-col']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:fixed']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:inset-y-0']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:w-60']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['border-r']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['z-30']} */ ;
/** @type {__VLS_StyleScopedClasses['px-5']} */ ;
/** @type {__VLS_StyleScopedClasses['pt-6']} */ ;
/** @type {__VLS_StyleScopedClasses['pb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['border-b']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['tracking-wide']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-col']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-0.5']} */ ;
/** @type {__VLS_StyleScopedClasses['overflow-y-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['group']} */ ;
/** @type {__VLS_StyleScopedClasses['text-base']} */ ;
/** @type {__VLS_StyleScopedClasses['leading-none']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['border-t']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['pt-14']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:pt-0']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:pl-60']} */ ;
/** @type {__VLS_StyleScopedClasses['min-h-screen']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {
RouterLink: RouterLink,
RouterView: RouterView,
AppHeader: AppHeader,
AppDrawer: AppDrawer,
drawerOpen: drawerOpen,
links: links,
};
},
});

View File

@@ -0,0 +1,5 @@
import client from './client';
export const lunarApi = {
getMonth: (month) => client.get('/api/lunar', { params: { month } }).then(r => r.data),
getDictons: (mois) => client.get('/api/dictons', { params: { mois } }).then(r => r.data),
};

27
frontend/src/api/lunar.ts Normal file
View File

@@ -0,0 +1,27 @@
import client from './client'
export interface LunarDay {
date: string
phase: string
illumination: number
croissante_decroissante: string
montante_descendante: string
signe: string
type_jour: string
perigee: boolean
apogee: boolean
noeud_lunaire: boolean
}
export interface Dicton {
id: number
mois: number
jour?: number
texte: string
region?: string
}
export const lunarApi = {
getMonth: (month: string) => client.get<LunarDay[]>('/api/lunar', { params: { month } }).then(r => r.data),
getDictons: (mois: number) => client.get<Dicton[]>('/api/dictons', { params: { mois } }).then(r => r.data),
}

View File

@@ -0,0 +1,4 @@
import client from './client';
export const meteoApi = {
getForecast: (days = 14) => client.get('/api/meteo', { params: { days } }).then(r => r.data),
};

16
frontend/src/api/meteo.ts Normal file
View File

@@ -0,0 +1,16 @@
import client from './client'
export interface MeteoDay {
date: string
t_max?: number
t_min?: number
pluie_mm: number
vent_kmh: number
code: number
label: string
icone: string
}
export const meteoApi = {
getForecast: (days = 14) => client.get<{ days: MeteoDay[] }>('/api/meteo', { params: { days } }).then(r => r.data),
}

View File

@@ -0,0 +1,8 @@
import client from './client';
export const plantsApi = {
list: (categorie) => client.get('/api/plants', { params: categorie ? { categorie } : {} }).then(r => r.data),
get: (id) => client.get(`/api/plants/${id}`).then(r => r.data),
create: (p) => client.post('/api/plants', p).then(r => r.data),
update: (id, p) => client.put(`/api/plants/${id}`, p).then(r => r.data),
delete: (id) => client.delete(`/api/plants/${id}`),
};

View File

@@ -0,0 +1,33 @@
import client from './client'
export interface Plant {
id?: number
nom_commun: string
nom_botanique?: string
variete?: string
famille?: string
categorie?: string // potager|fleur|arbre|arbuste
tags?: string
type_plante?: string
besoin_eau?: string
besoin_soleil?: string
espacement_cm?: number
temp_min_c?: number
hauteur_cm?: number
plantation_mois?: string
recolte_mois?: string
semis_interieur_mois?: string
semis_exterieur_mois?: string
maladies_courantes?: string
astuces_culture?: string
url_reference?: string
notes?: string
}
export const plantsApi = {
list: (categorie?: string) => client.get<Plant[]>('/api/plants', { params: categorie ? { categorie } : {} }).then(r => r.data),
get: (id: number) => client.get<Plant>(`/api/plants/${id}`).then(r => r.data),
create: (p: Partial<Plant>) => client.post<Plant>('/api/plants', p).then(r => r.data),
update: (id: number, p: Partial<Plant>) => client.put<Plant>(`/api/plants/${id}`, p).then(r => r.data),
delete: (id: number) => client.delete(`/api/plants/${id}`),
}

View File

@@ -0,0 +1,6 @@
import client from './client';
export const recoltesApi = {
list: (plantingId) => client.get(`/api/plantings/${plantingId}/recoltes`).then(r => r.data),
create: (plantingId, data) => client.post(`/api/plantings/${plantingId}/recoltes`, data).then(r => r.data),
delete: (id) => client.delete(`/api/recoltes/${id}`),
};

View File

@@ -0,0 +1,21 @@
import client from './client'
export interface Recolte {
id?: number
plantation_id?: number
quantite: number
unite: string // kg|g|unites|litres|bottes
date_recolte: string
notes?: string
created_at?: string
}
export const recoltesApi = {
list: (plantingId: number) =>
client.get<Recolte[]>(`/api/plantings/${plantingId}/recoltes`).then(r => r.data),
create: (plantingId: number, data: Omit<Recolte, 'id' | 'plantation_id' | 'created_at'>) =>
client.post<Recolte>(`/api/plantings/${plantingId}/recoltes`, data).then(r => r.data),
delete: (id: number) => client.delete(`/api/recoltes/${id}`),
}

View File

@@ -0,0 +1,8 @@
import client from './client';
export const toolsApi = {
list: () => client.get('/api/tools').then(r => r.data),
get: (id) => client.get(`/api/tools/${id}`).then(r => r.data),
create: (t) => client.post('/api/tools', t).then(r => r.data),
update: (id, t) => client.put(`/api/tools/${id}`, t).then(r => r.data),
delete: (id) => client.delete(`/api/tools/${id}`),
};

17
frontend/src/api/tools.ts Normal file
View File

@@ -0,0 +1,17 @@
import client from './client'
export interface Tool {
id?: number
nom: string
description?: string
categorie?: string
photo_url?: string
}
export const toolsApi = {
list: () => client.get<Tool[]>('/api/tools').then(r => r.data),
get: (id: number) => client.get<Tool>(`/api/tools/${id}`).then(r => r.data),
create: (t: Partial<Tool>) => client.post<Tool>('/api/tools', t).then(r => r.data),
update: (id: number, t: Partial<Tool>) => client.put<Tool>(`/api/tools/${id}`, t).then(r => r.data),
delete: (id: number) => client.delete(`/api/tools/${id}`),
}

View File

@@ -4,11 +4,12 @@ const __VLS_emit = defineEmits(['close']);
const links = [
{ to: '/', label: 'Dashboard' },
{ to: '/jardins', label: 'Jardins' },
{ to: '/varietes', label: 'Variétés' },
{ to: '/plantes', label: 'Plantes' },
{ to: '/outils', label: 'Outils' },
{ to: '/plantations', label: 'Plantations' },
{ to: '/taches', label: 'Tâches' },
{ to: '/planning', label: 'Planning' },
{ to: '/lunaire', label: 'Calendrier lunaire' },
{ to: '/calendrier', label: 'Calendrier' },
{ to: '/reglages', label: 'Réglages' },
];
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */

View File

@@ -1,28 +1,11 @@
<template>
<header class="fixed top-0 left-0 right-0 z-50 bg-bg-hard border-b border-bg-soft h-14 flex items-center px-4 gap-4">
<button class="md:hidden text-text-muted hover:text-text text-xl leading-none" @click="$emit('toggle-drawer')"></button>
<button class="text-text-muted hover:text-text text-xl leading-none" @click="$emit('toggle-drawer')"></button>
<RouterLink to="/" class="text-green font-bold text-lg tracking-wide">🌿 Jardin</RouterLink>
<nav class="hidden md:flex gap-5 ml-4">
<RouterLink
v-for="l in links" :key="l.to" :to="l.to"
class="text-text-muted hover:text-text transition-colors text-sm"
active-class="text-green font-semibold"
>{{ l.label }}</RouterLink>
</nav>
</header>
</template>
<script setup lang="ts">
import { RouterLink } from 'vue-router'
defineEmits(['toggle-drawer'])
const links = [
{ to: '/', label: 'Dashboard' },
{ to: '/jardins', label: 'Jardins' },
{ to: '/varietes', label: 'Variétés' },
{ to: '/plantations', label: 'Plantations' },
{ to: '/taches', label: 'Tâches' },
{ to: '/planning', label: 'Planning' },
{ to: '/lunaire', label: 'Lunaire' },
{ to: '/reglages', label: 'Réglages' },
]
</script>

View File

@@ -1,16 +1,6 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { RouterLink } from 'vue-router';
const __VLS_emit = defineEmits(['toggle-drawer']);
const links = [
{ to: '/', label: 'Dashboard' },
{ to: '/jardins', label: 'Jardins' },
{ to: '/varietes', label: 'Variétés' },
{ to: '/plantations', label: 'Plantations' },
{ to: '/taches', label: 'Tâches' },
{ to: '/planning', label: 'Planning' },
{ to: '/lunaire', label: 'Lunaire' },
{ to: '/reglages', label: 'Réglages' },
];
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
@@ -22,7 +12,7 @@ __VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElement
...{ onClick: (...[$event]) => {
__VLS_ctx.$emit('toggle-drawer');
} },
...{ class: "md:hidden text-text-muted hover:text-text text-xl leading-none" },
...{ class: "text-text-muted hover:text-text text-xl leading-none" },
});
const __VLS_0 = {}.RouterLink;
/** @type {[typeof __VLS_components.RouterLink, typeof __VLS_components.RouterLink, ]} */ ;
@@ -37,29 +27,6 @@ const __VLS_2 = __VLS_1({
}, ...__VLS_functionalComponentArgsRest(__VLS_1));
__VLS_3.slots.default;
var __VLS_3;
__VLS_asFunctionalElement(__VLS_intrinsicElements.nav, __VLS_intrinsicElements.nav)({
...{ class: "hidden md:flex gap-5 ml-4" },
});
for (const [l] of __VLS_getVForSourceType((__VLS_ctx.links))) {
const __VLS_4 = {}.RouterLink;
/** @type {[typeof __VLS_components.RouterLink, typeof __VLS_components.RouterLink, ]} */ ;
// @ts-ignore
const __VLS_5 = __VLS_asFunctionalComponent(__VLS_4, new __VLS_4({
key: (l.to),
to: (l.to),
...{ class: "text-text-muted hover:text-text transition-colors text-sm" },
activeClass: "text-green font-semibold",
}));
const __VLS_6 = __VLS_5({
key: (l.to),
to: (l.to),
...{ class: "text-text-muted hover:text-text transition-colors text-sm" },
activeClass: "text-green font-semibold",
}, ...__VLS_functionalComponentArgsRest(__VLS_5));
__VLS_7.slots.default;
(l.label);
var __VLS_7;
}
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
/** @type {__VLS_StyleScopedClasses['top-0']} */ ;
/** @type {__VLS_StyleScopedClasses['left-0']} */ ;
@@ -73,7 +40,6 @@ for (const [l] of __VLS_getVForSourceType((__VLS_ctx.links))) {
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-4']} */ ;
/** @type {__VLS_StyleScopedClasses['md:hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xl']} */ ;
@@ -82,20 +48,11 @@ for (const [l] of __VLS_getVForSourceType((__VLS_ctx.links))) {
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['tracking-wide']} */ ;
/** @type {__VLS_StyleScopedClasses['hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['md:flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-5']} */ ;
/** @type {__VLS_StyleScopedClasses['ml-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {
RouterLink: RouterLink,
links: links,
};
},
emits: {},

View File

@@ -5,11 +5,15 @@ export default createRouter({
{ path: '/', component: () => import('@/views/DashboardView.vue') },
{ path: '/jardins', component: () => import('@/views/JardinsView.vue') },
{ path: '/jardins/:id', component: () => import('@/views/JardinDetailView.vue') },
{ path: '/varietes', component: () => import('@/views/VarietesView.vue') },
{ path: '/plantes', component: () => import('@/views/PlantesView.vue') },
{ path: '/outils', component: () => import('@/views/OutilsView.vue') },
{ path: '/plantations', component: () => import('@/views/PlantationsView.vue') },
{ path: '/planning', component: () => import('@/views/PlanningView.vue') },
{ path: '/taches', component: () => import('@/views/TachesView.vue') },
{ path: '/lunaire', component: () => import('@/views/LunaireView.vue') },
{ path: '/calendrier', component: () => import('@/views/CalendrierView.vue') },
{ path: '/reglages', component: () => import('@/views/ReglagesView.vue') },
// Redirect des anciens liens
{ path: '/varietes', redirect: '/plantes' },
{ path: '/lunaire', redirect: '/calendrier' },
],
});

View File

@@ -18,10 +18,17 @@ export const useGardensStore = defineStore('gardens', () => {
return created
}
async function update(id: number, g: Partial<Garden>) {
const updated = await gardensApi.update(id, g)
const idx = gardens.value.findIndex(x => x.id === id)
if (idx !== -1) gardens.value[idx] = updated
return updated
}
async function remove(id: number) {
await gardensApi.delete(id)
gardens.value = gardens.value.filter(g => g.id !== id)
}
return { gardens, loading, fetchAll, create, remove }
return { gardens, loading, fetchAll, create, update, remove }
})

View File

@@ -18,10 +18,17 @@ export const usePlantingsStore = defineStore('plantings', () => {
return created
}
async function update(id: number, p: Partial<Planting>) {
const updated = await plantingsApi.update(id, p)
const idx = plantings.value.findIndex(x => x.id === id)
if (idx !== -1) plantings.value[idx] = updated
return updated
}
async function remove(id: number) {
await plantingsApi.delete(id)
plantings.value = plantings.value.filter(p => p.id !== id)
}
return { plantings, loading, fetchAll, create, remove }
return { plantings, loading, fetchAll, create, update, remove }
})

View File

@@ -0,0 +1,26 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { plantsApi } from '@/api/plants';
export const usePlantsStore = defineStore('plants', () => {
const plants = ref([]);
const loading = ref(false);
async function fetchAll(categorie) {
loading.value = true;
try {
plants.value = await plantsApi.list(categorie);
}
finally {
loading.value = false;
}
}
async function create(p) {
const created = await plantsApi.create(p);
plants.value.push(created);
return created;
}
async function remove(id) {
await plantsApi.delete(id);
plants.value = plants.value.filter(p => p.id !== id);
}
return { plants, loading, fetchAll, create, remove };
});

View File

@@ -0,0 +1,27 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { plantsApi, type Plant } from '@/api/plants'
export const usePlantsStore = defineStore('plants', () => {
const plants = ref<Plant[]>([])
const loading = ref(false)
async function fetchAll(categorie?: string) {
loading.value = true
try { plants.value = await plantsApi.list(categorie) }
finally { loading.value = false }
}
async function create(p: Partial<Plant>) {
const created = await plantsApi.create(p)
plants.value.push(created)
return created
}
async function remove(id: number) {
await plantsApi.delete(id)
plants.value = plants.value.filter(p => p.id !== id)
}
return { plants, loading, fetchAll, create, remove }
})

View File

@@ -18,6 +18,13 @@ export const useTasksStore = defineStore('tasks', () => {
return created
}
async function update(id: number, data: Partial<Task>) {
const updated = await tasksApi.update(id, data)
const idx = tasks.value.findIndex(t => t.id === id)
if (idx !== -1) tasks.value[idx] = updated
return updated
}
async function updateStatut(id: number, statut: string) {
const t = tasks.value.find(t => t.id === id)
if (!t) return
@@ -30,5 +37,5 @@ export const useTasksStore = defineStore('tasks', () => {
tasks.value = tasks.value.filter(t => t.id !== id)
}
return { tasks, loading, fetchAll, create, updateStatut, remove }
return { tasks, loading, fetchAll, create, update, updateStatut, remove }
})

View File

@@ -0,0 +1,26 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { toolsApi } from '@/api/tools';
export const useToolsStore = defineStore('tools', () => {
const tools = ref([]);
const loading = ref(false);
async function fetchAll() {
loading.value = true;
try {
tools.value = await toolsApi.list();
}
finally {
loading.value = false;
}
}
async function create(t) {
const created = await toolsApi.create(t);
tools.value.push(created);
return created;
}
async function remove(id) {
await toolsApi.delete(id);
tools.value = tools.value.filter(t => t.id !== id);
}
return { tools, loading, fetchAll, create, remove };
});

View File

@@ -0,0 +1,34 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { toolsApi, type Tool } from '@/api/tools'
export const useToolsStore = defineStore('tools', () => {
const tools = ref<Tool[]>([])
const loading = ref(false)
async function fetchAll() {
loading.value = true
try { tools.value = await toolsApi.list() }
finally { loading.value = false }
}
async function create(t: Partial<Tool>) {
const created = await toolsApi.create(t)
tools.value.push(created)
return created
}
async function update(id: number, t: Partial<Tool>) {
const updated = await toolsApi.update(id, t)
const idx = tools.value.findIndex(x => x.id === id)
if (idx !== -1) tools.value[idx] = updated
return updated
}
async function remove(id: number) {
await toolsApi.delete(id)
tools.value = tools.value.filter(t => t.id !== id)
}
return { tools, loading, fetchAll, create, update, remove }
})

View File

@@ -2,96 +2,100 @@
<div class="p-4 max-w-4xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold text-green">📷 Bibliothèque</h1>
<button
@click="showIdentify = true"
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
>
<button @click="showIdentify = true"
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">
Identifier une plante
</button>
</div>
<!-- Filtres -->
<div class="flex gap-2 mb-4 flex-wrap">
<button
v-for="f in filters"
:key="f.val"
@click="activeFilter = f.val"
:class="[
'px-3 py-1 rounded-full text-xs font-medium transition-colors',
activeFilter === f.val
? 'bg-green text-bg'
: 'bg-bg-soft text-text-muted hover:text-text',
]"
>
<button v-for="f in filters" :key="f.val" @click="activeFilter = f.val"
:class="['px-3 py-1 rounded-full text-xs font-medium transition-colors',
activeFilter === f.val ? 'bg-green text-bg' : 'bg-bg-soft text-text-muted hover:text-text']">
{{ f.label }}
</button>
</div>
<!-- Grille -->
<div v-if="loading" class="text-text-muted text-sm">Chargement...</div>
<div v-else-if="!filtered.length" class="text-text-muted text-sm py-4">
Aucune photo.
</div>
<div v-else-if="!filtered.length" class="text-text-muted text-sm py-4">Aucune photo.</div>
<div v-else class="grid grid-cols-3 md:grid-cols-4 gap-2">
<div
v-for="m in filtered"
:key="m.id"
<div v-for="m in filtered" :key="m.id"
class="aspect-square rounded-lg overflow-hidden bg-bg-hard relative group cursor-pointer"
@click="lightbox = m"
>
<img
:src="m.thumbnail_url || m.url"
:alt="m.titre || ''"
class="w-full h-full object-cover"
/>
<div
v-if="m.identified_common"
class="absolute bottom-0 left-0 right-0 bg-black/70 text-xs text-green px-1 py-0.5 truncate"
>
@click="openLightbox(m)">
<img :src="m.thumbnail_url || m.url" :alt="m.titre || ''" class="w-full h-full object-cover" />
<div v-if="m.identified_common"
class="absolute bottom-0 left-0 right-0 bg-black/70 text-xs text-green px-1 py-0.5 truncate">
{{ m.identified_common }}
</div>
<div class="absolute top-1 left-1 bg-black/60 text-text-muted text-xs px-1 rounded">
{{ labelFor(m.entity_type) }}
</div>
<button @click.stop="deleteMedia(m)"
class="hidden group-hover:flex absolute top-1 right-1 bg-red/80 text-white text-xs rounded px-1"></button>
</div>
</div>
<!-- Lightbox -->
<div
v-if="lightbox"
class="fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4"
@click.self="lightbox = null"
>
<div class="max-w-lg w-full">
<img :src="lightbox.url" class="w-full rounded-xl" />
<div
v-if="lightbox.identified_species"
class="text-center mt-3 text-text-muted text-sm"
>
<div class="text-green font-semibold text-base">
{{ lightbox.identified_common }}
<div v-if="lightbox" class="fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4" @click.self="lightbox = null">
<div class="max-w-lg w-full bg-bg-hard rounded-xl overflow-hidden border border-bg-soft">
<img :src="lightbox.url" class="w-full" />
<div class="p-4">
<!-- Infos identification -->
<div v-if="lightbox.identified_species" class="text-center mb-3">
<div class="text-green font-semibold text-base">{{ lightbox.identified_common }}</div>
<div class="italic text-text-muted text-sm">{{ lightbox.identified_species }}</div>
<div class="text-xs text-text-muted mt-1">
Confiance : {{ Math.round((lightbox.identified_confidence || 0) * 100) }}% via {{ lightbox.identified_source }}
</div>
</div>
<div class="italic">{{ lightbox.identified_species }}</div>
<div class="text-xs mt-1">
Confiance : {{ Math.round((lightbox.identified_confidence || 0) * 100) }}%
via {{ lightbox.identified_source }}
<!-- Lien actuel -->
<div class="text-xs text-text-muted mb-3 text-center">
{{ labelFor(lightbox.entity_type) }}
<span v-if="lightbox.entity_type === 'plante' && plantName(lightbox.entity_id)">
: <span class="text-green font-medium">{{ plantName(lightbox.entity_id) }}</span>
</span>
</div>
<!-- Actions -->
<div class="flex gap-2 flex-wrap">
<button @click="startLink(lightbox!)"
class="flex-1 bg-blue/20 text-blue hover:bg-blue/30 px-3 py-2 rounded-lg text-xs font-medium transition-colors">
🔗 Associer à une plante
</button>
<button @click="deleteMedia(lightbox!); lightbox = null"
class="bg-red/20 text-red hover:bg-red/30 px-3 py-2 rounded-lg text-xs font-medium transition-colors">
🗑 Supprimer
</button>
</div>
<button class="mt-3 w-full text-text-muted hover:text-text text-sm" @click="lightbox = null">Fermer</button>
</div>
</div>
</div>
<!-- Modal associer à une plante -->
<div v-if="linkMedia" class="fixed inset-0 bg-black/70 z-50 flex items-center justify-center p-4" @click.self="linkMedia = null">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-sm border border-bg-soft">
<h3 class="text-text font-bold mb-4">Associer à une plante</h3>
<select v-model="linkPlantId"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green mb-4">
<option :value="null">-- Choisir une plante --</option>
<option v-for="p in plantsStore.plants" :key="p.id" :value="p.id">
{{ p.nom_commun }}{{ p.variete ? ' ' + p.variete : '' }}
</option>
</select>
<div class="flex gap-2 justify-end">
<button @click="linkMedia = null" class="px-4 py-2 text-text-muted hover:text-text text-sm">Annuler</button>
<button @click="confirmLink" :disabled="!linkPlantId"
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90 disabled:opacity-40">
Associer
</button>
</div>
<button
class="mt-4 w-full text-text-muted hover:text-text text-sm"
@click="lightbox = null"
>
Fermer
</button>
</div>
</div>
<!-- Modal identification -->
<PhotoIdentifyModal
v-if="showIdentify"
@close="showIdentify = false"
@identified="onIdentified"
/>
<PhotoIdentifyModal v-if="showIdentify" @close="showIdentify = false" @identified="onIdentified" />
</div>
</template>
@@ -99,18 +103,13 @@
import { computed, onMounted, ref } from 'vue'
import axios from 'axios'
import PhotoIdentifyModal from '@/components/PhotoIdentifyModal.vue'
import { usePlantsStore } from '@/stores/plants'
interface Media {
id: number
entity_type: string
entity_id: number
url: string
thumbnail_url?: string
titre?: string
identified_species?: string
identified_common?: string
identified_confidence?: number
identified_source?: string
id: number; entity_type: string; entity_id: number
url: string; thumbnail_url?: string; titre?: string
identified_species?: string; identified_common?: string
identified_confidence?: number; identified_source?: string
}
const medias = ref<Media[]>([])
@@ -118,6 +117,10 @@ const loading = ref(false)
const lightbox = ref<Media | null>(null)
const showIdentify = ref(false)
const activeFilter = ref('')
const linkMedia = ref<Media | null>(null)
const linkPlantId = ref<number | null>(null)
const plantsStore = usePlantsStore()
const filters = [
{ val: '', label: 'Toutes' },
@@ -125,24 +128,53 @@ const filters = [
{ val: 'jardin', label: '🏡 Jardins' },
{ val: 'plantation', label: '🥕 Plantations' },
{ val: 'outil', label: '🔧 Outils' },
{ val: 'bibliotheque', label: '📷 Sans lien' },
]
const filtered = computed(() =>
activeFilter.value
? medias.value.filter((m) => m.entity_type === activeFilter.value)
: medias.value,
activeFilter.value ? medias.value.filter(m => m.entity_type === activeFilter.value) : medias.value
)
function labelFor(type: string) {
const map: Record<string, string> = {
plante: '🌱',
jardin: '🏡',
plantation: '🥕',
outil: '🔧',
plante: '🌱 Plante', jardin: '🏡 Jardin',
plantation: '🥕 Plantation', outil: '🔧 Outil', bibliotheque: '📷'
}
return map[type] ?? '📷'
}
function plantName(id: number) {
return plantsStore.plants.find(p => p.id === id)?.nom_commun ?? ''
}
function openLightbox(m: Media) { lightbox.value = m }
function startLink(m: Media) {
linkMedia.value = m
linkPlantId.value = m.entity_type === 'plante' ? m.entity_id : null
}
async function confirmLink() {
if (!linkMedia.value || !linkPlantId.value) return
await axios.patch(`/api/media/${linkMedia.value.id}`, {
entity_type: 'plante', entity_id: linkPlantId.value,
})
const m = medias.value.find(x => x.id === linkMedia.value!.id)
if (m) { m.entity_type = 'plante'; m.entity_id = linkPlantId.value! }
if (lightbox.value?.id === linkMedia.value.id) {
lightbox.value = { ...lightbox.value, entity_type: 'plante', entity_id: linkPlantId.value! }
}
linkMedia.value = null
linkPlantId.value = null
}
async function deleteMedia(m: Media) {
if (!confirm('Supprimer cette photo ?')) return
await axios.delete(`/api/media/${m.id}`)
medias.value = medias.value.filter(x => x.id !== m.id)
if (lightbox.value?.id === m.id) lightbox.value = null
}
async function fetchAll() {
loading.value = true
try {
@@ -153,9 +185,10 @@ async function fetchAll() {
}
}
function onIdentified() {
fetchAll()
}
function onIdentified() { fetchAll() }
onMounted(fetchAll)
onMounted(() => {
fetchAll()
plantsStore.fetchAll()
})
</script>

View File

@@ -0,0 +1,247 @@
<template>
<div class="p-4 max-w-4xl mx-auto">
<h1 class="text-2xl font-bold text-blue mb-4">📅 Calendrier</h1>
<!-- Onglets -->
<div class="flex gap-1 mb-6 bg-bg-soft rounded-lg p-1 w-fit">
<button v-for="tab in tabs" :key="tab.id" @click="activeTab = tab.id"
:class="['px-4 py-2 rounded-md text-sm font-medium transition-colors',
activeTab === tab.id ? 'bg-blue text-bg' : 'text-text-muted hover:text-text']">
{{ tab.label }}
</button>
</div>
<!-- Sélecteur mois (onglets lunaire/dictons) -->
<div v-if="activeTab === 'lunaire' || activeTab === 'dictons'" class="flex items-center gap-3 mb-4">
<button @click="prevMonth" class="text-text-muted hover:text-text text-lg"></button>
<span class="text-text font-semibold">{{ monthLabel }}</span>
<button @click="nextMonth" class="text-text-muted hover:text-text text-lg"></button>
</div>
<!-- === LUNAIRE === -->
<div v-if="activeTab === 'lunaire'">
<div v-if="loadingLunar" class="text-text-muted text-sm py-4">Calcul en cours (skyfield)...</div>
<div v-else-if="errorLunar" class="bg-red/10 border border-red rounded-lg p-4 text-red text-sm">
{{ errorLunar }}
</div>
<div v-else-if="!lunarDays.length" class="text-text-muted text-sm py-4">Aucune donnée lunaire.</div>
<div v-else>
<!-- Grille calendrier -->
<div class="grid grid-cols-7 gap-1 mb-2">
<div v-for="d in ['Lun','Mar','Mer','Jeu','Ven','Sam','Dim']" :key="d"
class="text-center text-text-muted text-xs py-1">{{ d }}</div>
</div>
<div class="grid grid-cols-7 gap-1">
<div v-for="_ in firstDayOffset" :key="'empty-'+_" class="h-16"></div>
<div v-for="day in lunarDays" :key="day.date"
@click="selectedDay = day"
:class="['h-16 bg-bg-soft rounded-lg p-1 cursor-pointer hover:border hover:border-blue transition-colors flex flex-col items-center justify-center gap-0.5',
selectedDay?.date === day.date ? 'border border-blue' : 'border border-transparent']">
<span class="text-text-muted text-xs">{{ new Date(day.date+'T12:00:00').getDate() }}</span>
<img :src="moonIcon(day.illumination, day.croissante_decroissante)" class="w-6 h-6 opacity-90" alt="phase" />
<span class="text-xs leading-none" :class="typeColor(day.type_jour)">{{ typeEmoji(day.type_jour) }}</span>
</div>
</div>
<!-- Détail jour sélectionné -->
<div v-if="selectedDay" class="mt-4 bg-bg-soft rounded-xl p-4 border border-bg-hard">
<div class="flex items-center gap-3 mb-3">
<img :src="moonIcon(selectedDay.illumination, selectedDay.croissante_decroissante)" class="w-10 h-10" alt="phase" />
<div>
<div class="text-text font-bold">{{ formatDate(selectedDay.date) }}</div>
<div class="text-text-muted text-sm">{{ selectedDay.phase || 'Pas de phase particulière' }}</div>
</div>
</div>
<div class="grid grid-cols-2 gap-2 text-sm">
<div class="bg-bg rounded-lg p-2">
<div class="text-text-muted text-xs mb-1">Illumination</div>
<div class="text-text">{{ selectedDay.illumination }}%</div>
</div>
<div class="bg-bg rounded-lg p-2">
<div class="text-text-muted text-xs mb-1">Tendance</div>
<div class="text-text">{{ selectedDay.croissante_decroissante }}</div>
</div>
<div class="bg-bg rounded-lg p-2">
<div class="text-text-muted text-xs mb-1">Lune</div>
<div class="text-text">{{ selectedDay.montante_descendante }}</div>
</div>
<div class="bg-bg rounded-lg p-2">
<div class="text-text-muted text-xs mb-1">Signe</div>
<div class="text-text">{{ selectedDay.signe }}</div>
</div>
<div class="bg-bg rounded-lg p-2 col-span-2">
<div class="text-text-muted text-xs mb-1">Type de jour</div>
<div :class="['font-semibold', typeColor(selectedDay.type_jour)]">
{{ typeEmoji(selectedDay.type_jour) }} {{ selectedDay.type_jour }}
</div>
</div>
</div>
<div v-if="selectedDay.perigee" class="mt-2 text-xs text-orange bg-orange/10 rounded px-2 py-1"> Périgée (lune proche)</div>
<div v-if="selectedDay.apogee" class="mt-2 text-xs text-blue bg-blue/10 rounded px-2 py-1">🌌 Apogée (lune éloignée)</div>
<div v-if="selectedDay.noeud_lunaire" class="mt-2 text-xs text-yellow bg-yellow/10 rounded px-2 py-1"> Nœud lunaire</div>
</div>
</div>
</div>
<!-- === MÉTÉO === -->
<div v-if="activeTab === 'meteo'">
<div v-if="loadingMeteo" class="text-text-muted text-sm py-4">Chargement météo...</div>
<div v-else-if="!meteoData.length" class="text-text-muted text-sm py-4">Données météo non disponibles.</div>
<div v-else class="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div v-for="day in meteoData" :key="day.date"
class="bg-bg-soft rounded-xl p-3 border border-bg-hard flex flex-col items-center gap-1">
<div class="text-text-muted text-xs">{{ formatDate(day.date) }}</div>
<img :src="weatherIcon(day.code)" class="w-10 h-10" :alt="day.label" />
<div class="text-text text-xs font-medium text-center">{{ day.label }}</div>
<div class="flex gap-2 text-xs mt-1">
<span class="text-orange">{{ day.t_max?.toFixed(0) }}°</span>
<span class="text-blue">{{ day.t_min?.toFixed(0) }}°</span>
</div>
<div v-if="day.pluie_mm > 0" class="text-xs text-blue">💧 {{ day.pluie_mm }}mm</div>
</div>
</div>
</div>
<!-- === TÂCHES === -->
<div v-if="activeTab === 'taches'">
<p class="text-text-muted text-sm py-4">Les tâches planifiées s'afficheront ici.</p>
</div>
<!-- === DICTONS === -->
<div v-if="activeTab === 'dictons'">
<div v-if="!dictons.length" class="text-text-muted text-sm py-4">Aucun dicton pour ce mois.</div>
<div v-for="d in dictons" :key="d.id" class="bg-bg-soft rounded-lg p-4 mb-2 border border-bg-hard">
<p class="text-text italic text-sm">"{{ d.texte }}"</p>
<p v-if="d.region" class="text-text-muted text-xs mt-1">— {{ d.region }}</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { lunarApi, type LunarDay, type Dicton } from '@/api/lunar'
import { meteoApi, type MeteoDay } from '@/api/meteo'
const activeTab = ref('lunaire')
const tabs = [
{ id: 'lunaire', label: '🌙 Lunaire' },
{ id: 'meteo', label: ' Météo' },
{ id: 'taches', label: ' Tâches' },
{ id: 'dictons', label: '📜 Dictons' },
]
const now = new Date()
const currentYear = ref(now.getFullYear())
const currentMonth = ref(now.getMonth() + 1)
const monthLabel = computed(() => {
const d = new Date(currentYear.value, currentMonth.value - 1, 1)
return d.toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' })
})
const monthStr = computed(() => `${currentYear.value}-${String(currentMonth.value).padStart(2, '0')}`)
function prevMonth() {
if (currentMonth.value === 1) { currentMonth.value = 12; currentYear.value-- }
else currentMonth.value--
}
function nextMonth() {
if (currentMonth.value === 12) { currentMonth.value = 1; currentYear.value++ }
else currentMonth.value++
}
// Lunaire
const lunarDays = ref<LunarDay[]>([])
const loadingLunar = ref(false)
const errorLunar = ref('')
const selectedDay = ref<LunarDay | null>(null)
const firstDayOffset = computed(() => {
if (!lunarDays.value.length) return 0
const d = new Date(lunarDays.value[0].date + 'T12:00:00')
return (d.getDay() + 6) % 7 // Lundi=0
})
async function loadLunar() {
loadingLunar.value = true; errorLunar.value = ''; selectedDay.value = null
try {
lunarDays.value = await lunarApi.getMonth(monthStr.value)
} catch (e: unknown) {
const err = e as { response?: { data?: { detail?: string } } }
errorLunar.value = err?.response?.data?.detail || 'Erreur lors du chargement du calendrier lunaire.'
} finally {
loadingLunar.value = false
}
}
// Météo
const meteoData = ref<MeteoDay[]>([])
const loadingMeteo = ref(false)
async function loadMeteo() {
loadingMeteo.value = true
try {
const res = await meteoApi.getForecast(14)
meteoData.value = res.days || []
} catch { meteoData.value = [] }
finally { loadingMeteo.value = false }
}
// Dictons
const dictons = ref<Dicton[]>([])
async function loadDictons() {
try { dictons.value = await lunarApi.getDictons(currentMonth.value) }
catch { dictons.value = [] }
}
// Helpers affichage
function moonIcon(illum: number, tendance: string): string {
const i = illum / 100
let name: string
if (i < 0.05) name = 'new_moon'
else if (tendance === 'Croissante') {
if (i < 0.35) name = 'waxing_crescent'
else if (i < 0.65) name = 'first_quarter'
else if (i < 0.95) name = 'waxing_gibbous'
else name = 'full_moon'
} else {
if (i > 0.95) name = 'full_moon'
else if (i > 0.65) name = 'waning_gibbous'
else if (i > 0.35) name = 'last_quarter'
else name = 'waning_crescent'
}
return `/icons/moon/${name}.svg`
}
function weatherIcon(code: number): string {
// WMO code → fichier SVG disponible
const available = [0, 1, 2, 3, 45, 48, 51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 71, 73, 75, 77, 80, 81, 82, 85, 86, 95, 96, 99]
const closest = available.reduce((prev, curr) =>
Math.abs(curr - code) < Math.abs(prev - code) ? curr : prev
)
return `/icons/weather/${closest}.svg`
}
function typeEmoji(type: string): string {
return ({ Racine: '🌱', Feuille: '🌿', Fleur: '🌸', Fruit: '🍅' } as Record<string, string>)[type] || ''
}
function typeColor(type: string): string {
return ({ Racine: 'text-yellow', Feuille: 'text-green', Fleur: 'text-orange', Fruit: 'text-red' } as Record<string, string>)[type] || 'text-text-muted'
}
function formatDate(dateStr: string): string {
return new Date(dateStr + 'T12:00:00').toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' })
}
watch(monthStr, () => {
if (activeTab.value === 'lunaire') loadLunar()
if (activeTab.value === 'dictons') loadDictons()
})
watch(activeTab, (tab) => {
if (tab === 'lunaire' && !lunarDays.value.length) loadLunar()
if (tab === 'meteo' && !meteoData.value.length) loadMeteo()
if (tab === 'dictons' && !dictons.value.length) loadDictons()
})
onMounted(() => { loadLunar(); loadMeteo() })
</script>

View File

@@ -0,0 +1,635 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { computed, onMounted, ref, watch } from 'vue';
import { lunarApi } from '@/api/lunar';
import { meteoApi } from '@/api/meteo';
const activeTab = ref('lunaire');
const tabs = [
{ id: 'lunaire', label: '🌙 Lunaire' },
{ id: 'meteo', label: '☀️ Météo' },
{ id: 'taches', label: '✅ Tâches' },
{ id: 'dictons', label: '📜 Dictons' },
];
const now = new Date();
const currentYear = ref(now.getFullYear());
const currentMonth = ref(now.getMonth() + 1);
const monthLabel = computed(() => {
const d = new Date(currentYear.value, currentMonth.value - 1, 1);
return d.toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
});
const monthStr = computed(() => `${currentYear.value}-${String(currentMonth.value).padStart(2, '0')}`);
function prevMonth() {
if (currentMonth.value === 1) {
currentMonth.value = 12;
currentYear.value--;
}
else
currentMonth.value--;
}
function nextMonth() {
if (currentMonth.value === 12) {
currentMonth.value = 1;
currentYear.value++;
}
else
currentMonth.value++;
}
// Lunaire
const lunarDays = ref([]);
const loadingLunar = ref(false);
const errorLunar = ref('');
const selectedDay = ref(null);
const firstDayOffset = computed(() => {
if (!lunarDays.value.length)
return 0;
const d = new Date(lunarDays.value[0].date + 'T12:00:00');
return (d.getDay() + 6) % 7; // Lundi=0
});
async function loadLunar() {
loadingLunar.value = true;
errorLunar.value = '';
selectedDay.value = null;
try {
lunarDays.value = await lunarApi.getMonth(monthStr.value);
}
catch (e) {
const err = e;
errorLunar.value = err?.response?.data?.detail || 'Erreur lors du chargement du calendrier lunaire.';
}
finally {
loadingLunar.value = false;
}
}
// Météo
const meteoData = ref([]);
const loadingMeteo = ref(false);
async function loadMeteo() {
loadingMeteo.value = true;
try {
const res = await meteoApi.getForecast(14);
meteoData.value = res.days || [];
}
catch {
meteoData.value = [];
}
finally {
loadingMeteo.value = false;
}
}
// Dictons
const dictons = ref([]);
async function loadDictons() {
try {
dictons.value = await lunarApi.getDictons(currentMonth.value);
}
catch {
dictons.value = [];
}
}
// Helpers affichage
function moonEmoji(illum, tendance) {
const i = illum / 100;
if (i < 0.05)
return '🌑';
if (tendance === 'Croissante') {
if (i < 0.25)
return '🌒';
if (i < 0.5)
return '🌓';
if (i < 0.75)
return '🌔';
return '🌕';
}
else {
if (i > 0.75)
return '🌕';
if (i > 0.5)
return '🌖';
if (i > 0.25)
return '🌗';
return '🌘';
}
}
function typeEmoji(type) {
return { Racine: '🌱', Feuille: '🌿', Fleur: '🌸', Fruit: '🍅' }[type] || '●';
}
function typeColor(type) {
return { Racine: 'text-yellow', Feuille: 'text-green', Fleur: 'text-orange', Fruit: 'text-red' }[type] || 'text-text-muted';
}
function formatDate(dateStr) {
return new Date(dateStr + 'T12:00:00').toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
}
watch(monthStr, () => {
if (activeTab.value === 'lunaire')
loadLunar();
if (activeTab.value === 'dictons')
loadDictons();
});
watch(activeTab, (tab) => {
if (tab === 'lunaire' && !lunarDays.value.length)
loadLunar();
if (tab === 'meteo' && !meteoData.value.length)
loadMeteo();
if (tab === 'dictons' && !dictons.value.length)
loadDictons();
});
onMounted(() => { loadLunar(); loadMeteo(); });
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4 max-w-4xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-blue mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-1 mb-6 bg-bg-soft rounded-lg p-1 w-fit" },
});
for (const [tab] of __VLS_getVForSourceType((__VLS_ctx.tabs))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.activeTab = tab.id;
} },
key: (tab.id),
...{ class: (['px-4 py-2 rounded-md text-sm font-medium transition-colors',
__VLS_ctx.activeTab === tab.id ? 'bg-blue text-bg' : 'text-text-muted hover:text-text']) },
});
(tab.label);
}
if (__VLS_ctx.activeTab === 'lunaire' || __VLS_ctx.activeTab === 'dictons') {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center gap-3 mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.prevMonth) },
...{ class: "text-text-muted hover:text-text text-lg" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text font-semibold" },
});
(__VLS_ctx.monthLabel);
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.nextMonth) },
...{ class: "text-text-muted hover:text-text text-lg" },
});
}
if (__VLS_ctx.activeTab === 'lunaire') {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
if (__VLS_ctx.loadingLunar) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm py-4" },
});
}
else if (__VLS_ctx.errorLunar) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-red/10 border border-red rounded-lg p-4 text-red text-sm" },
});
(__VLS_ctx.errorLunar);
}
else if (!__VLS_ctx.lunarDays.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm py-4" },
});
}
else {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-7 gap-1 mb-2" },
});
for (const [d] of __VLS_getVForSourceType((['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (d),
...{ class: "text-center text-text-muted text-xs py-1" },
});
(d);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-7 gap-1" },
});
for (const [_] of __VLS_getVForSourceType((__VLS_ctx.firstDayOffset))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: ('empty-' + _),
...{ class: "h-16" },
});
}
for (const [day] of __VLS_getVForSourceType((__VLS_ctx.lunarDays))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.activeTab === 'lunaire'))
return;
if (!!(__VLS_ctx.loadingLunar))
return;
if (!!(__VLS_ctx.errorLunar))
return;
if (!!(!__VLS_ctx.lunarDays.length))
return;
__VLS_ctx.selectedDay = day;
} },
key: (day.date),
...{ class: (['h-16 bg-bg-soft rounded-lg p-1 cursor-pointer hover:border hover:border-blue transition-colors flex flex-col items-center justify-center gap-0.5',
__VLS_ctx.selectedDay?.date === day.date ? 'border border-blue' : 'border border-transparent']) },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text-muted text-xs" },
});
(new Date(day.date + 'T12:00:00').getDate());
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-base leading-none" },
});
(__VLS_ctx.moonEmoji(day.illumination, day.croissante_decroissante));
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-xs leading-none" },
...{ class: (__VLS_ctx.typeColor(day.type_jour)) },
});
(__VLS_ctx.typeEmoji(day.type_jour));
}
if (__VLS_ctx.selectedDay) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-4 bg-bg-soft rounded-xl p-4 border border-bg-hard" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center gap-3 mb-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-3xl" },
});
(__VLS_ctx.moonEmoji(__VLS_ctx.selectedDay.illumination, __VLS_ctx.selectedDay.croissante_decroissante));
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text font-bold" },
});
(__VLS_ctx.formatDate(__VLS_ctx.selectedDay.date));
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm" },
});
(__VLS_ctx.selectedDay.phase || 'Pas de phase particulière');
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-2 gap-2 text-sm" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg rounded-lg p-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text" },
});
(__VLS_ctx.selectedDay.illumination);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg rounded-lg p-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text" },
});
(__VLS_ctx.selectedDay.croissante_decroissante);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg rounded-lg p-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text" },
});
(__VLS_ctx.selectedDay.montante_descendante);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg rounded-lg p-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text" },
});
(__VLS_ctx.selectedDay.signe);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg rounded-lg p-2 col-span-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: (['font-semibold', __VLS_ctx.typeColor(__VLS_ctx.selectedDay.type_jour)]) },
});
(__VLS_ctx.typeEmoji(__VLS_ctx.selectedDay.type_jour));
(__VLS_ctx.selectedDay.type_jour);
if (__VLS_ctx.selectedDay.perigee) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-2 text-xs text-orange bg-orange/10 rounded px-2 py-1" },
});
}
if (__VLS_ctx.selectedDay.apogee) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-2 text-xs text-blue bg-blue/10 rounded px-2 py-1" },
});
}
if (__VLS_ctx.selectedDay.noeud_lunaire) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-2 text-xs text-yellow bg-yellow/10 rounded px-2 py-1" },
});
}
}
}
}
if (__VLS_ctx.activeTab === 'meteo') {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
if (__VLS_ctx.loadingMeteo) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm py-4" },
});
}
else if (!__VLS_ctx.meteoData.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm py-4" },
});
}
else {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-2 sm:grid-cols-4 gap-3" },
});
for (const [day] of __VLS_getVForSourceType((__VLS_ctx.meteoData))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (day.date),
...{ class: "bg-bg-soft rounded-xl p-3 border border-bg-hard flex flex-col items-center gap-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs" },
});
(__VLS_ctx.formatDate(day.date));
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-3xl" },
});
(day.icone);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text text-xs font-medium text-center" },
});
(day.label);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 text-xs mt-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-orange" },
});
(day.t_max?.toFixed(0));
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-blue" },
});
(day.t_min?.toFixed(0));
if (day.pluie_mm > 0) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-xs text-blue" },
});
(day.pluie_mm);
}
}
}
}
if (__VLS_ctx.activeTab === 'taches') {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-sm py-4" },
});
}
if (__VLS_ctx.activeTab === 'dictons') {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
if (!__VLS_ctx.dictons.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm py-4" },
});
}
for (const [d] of __VLS_getVForSourceType((__VLS_ctx.dictons))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (d.id),
...{ class: "bg-bg-soft rounded-lg p-4 mb-2 border border-bg-hard" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text italic text-sm" },
});
(d.texte);
if (d.region) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-xs mt-1" },
});
(d.region);
}
}
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-4xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-blue']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-fit']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-red/10']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-red']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-7']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-7']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['h-16']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-base']} */ ;
/** @type {__VLS_StyleScopedClasses['leading-none']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['leading-none']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-4']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-3xl']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-2']} */ ;
/** @type {__VLS_StyleScopedClasses['col-span-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-orange']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-orange/10']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-blue']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-blue/10']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-yellow/10']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['sm:grid-cols-4']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['p-3']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-col']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-3xl']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-orange']} */ ;
/** @type {__VLS_StyleScopedClasses['text-blue']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-blue']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['italic']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {
activeTab: activeTab,
tabs: tabs,
monthLabel: monthLabel,
prevMonth: prevMonth,
nextMonth: nextMonth,
lunarDays: lunarDays,
loadingLunar: loadingLunar,
errorLunar: errorLunar,
selectedDay: selectedDay,
firstDayOffset: firstDayOffset,
meteoData: meteoData,
loadingMeteo: loadingMeteo,
dictons: dictons,
moonEmoji: moonEmoji,
typeEmoji: typeEmoji,
typeColor: typeColor,
formatDate: formatDate,
};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */

View File

@@ -23,6 +23,22 @@
</div>
</section>
<section class="mb-6">
<h2 class="text-text-muted text-xs uppercase tracking-widest mb-3">Météo (3 jours)</h2>
<div class="flex gap-2 overflow-x-auto">
<div v-for="day in meteo3j" :key="day.date"
class="bg-bg-soft rounded-xl p-3 border border-bg-hard flex flex-col items-center gap-1 min-w-[90px]">
<div class="text-text-muted text-xs">{{ formatDate(day.date) }}</div>
<div class="text-2xl">{{ day.icone }}</div>
<div class="text-xs text-center text-text-muted">{{ day.label }}</div>
<div class="flex gap-1 text-xs">
<span class="text-orange">{{ day.t_max?.toFixed(0) }}°</span>
<span class="text-blue">{{ day.t_min?.toFixed(0) }}°</span>
</div>
</div>
</div>
</section>
<section>
<h2 class="text-text-muted text-xs uppercase tracking-widest mb-3">Jardins</h2>
<div v-if="gardensStore.loading" class="text-text-muted text-sm">Chargement...</div>
@@ -40,15 +56,26 @@
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useGardensStore } from '@/stores/gardens'
import { useTasksStore } from '@/stores/tasks'
import { meteoApi, type MeteoDay } from '@/api/meteo'
const router = useRouter()
const gardensStore = useGardensStore()
const tasksStore = useTasksStore()
const pendingTasks = computed(() => tasksStore.tasks.filter(t => t.statut === 'a_faire').slice(0, 5))
onMounted(() => { gardensStore.fetchAll(); tasksStore.fetchAll() })
const meteo3j = ref<MeteoDay[]>([])
function formatDate(s: string) {
return new Date(s + 'T12:00:00').toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' })
}
onMounted(async () => {
gardensStore.fetchAll()
tasksStore.fetchAll()
try { const r = await meteoApi.getForecast(3); meteo3j.value = r.days.slice(0, 3) } catch {}
})
</script>

View File

@@ -1,22 +1,232 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { computed, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useGardensStore } from '@/stores/gardens';
import { useTasksStore } from '@/stores/tasks';
import { meteoApi } from '@/api/meteo';
const router = useRouter();
const gardensStore = useGardensStore();
const tasksStore = useTasksStore();
const pendingTasks = computed(() => tasksStore.tasks.filter(t => t.statut === 'a_faire').slice(0, 5));
const meteo3j = ref([]);
function formatDate(s) {
return new Date(s + 'T12:00:00').toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
}
onMounted(async () => {
gardensStore.fetchAll();
tasksStore.fetchAll();
try {
const r = await meteoApi.getForecast(3);
meteo3j.value = r.days.slice(0, 3);
}
catch { }
});
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4" },
...{ class: "p-4 max-w-2xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green" },
...{ class: "text-2xl font-bold text-green mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.section, __VLS_intrinsicElements.section)({
...{ class: "mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text-muted text-xs uppercase tracking-widest mb-3" },
});
if (!__VLS_ctx.pendingTasks.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm py-2" },
});
}
for (const [t] of __VLS_getVForSourceType((__VLS_ctx.pendingTasks))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (t.id),
...{ class: "bg-bg-soft rounded-lg p-3 mb-2 flex items-center gap-3 border border-bg-hard" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: ({
'text-red': t.priorite === 'haute',
'text-yellow': t.priorite === 'normale',
'text-text-muted': t.priorite === 'basse'
}) },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text text-sm flex-1" },
});
(t.titre);
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.tasksStore.updateStatut(t.id, 'fait');
} },
...{ class: "text-xs text-green hover:underline px-2" },
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.section, __VLS_intrinsicElements.section)({
...{ class: "mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text-muted text-xs uppercase tracking-widest mb-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 overflow-x-auto" },
});
for (const [day] of __VLS_getVForSourceType((__VLS_ctx.meteo3j))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (day.date),
...{ class: "bg-bg-soft rounded-xl p-3 border border-bg-hard flex flex-col items-center gap-1 min-w-[90px]" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs" },
});
(__VLS_ctx.formatDate(day.date));
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-2xl" },
});
(day.icone);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-xs text-center text-text-muted" },
});
(day.label);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-1 text-xs" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-orange" },
});
(day.t_max?.toFixed(0));
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-blue" },
});
(day.t_min?.toFixed(0));
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.section, __VLS_intrinsicElements.section)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text-muted text-xs uppercase tracking-widest mb-3" },
});
if (__VLS_ctx.gardensStore.loading) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm" },
});
}
for (const [g] of __VLS_getVForSourceType((__VLS_ctx.gardensStore.gardens))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
__VLS_ctx.router.push(`/jardins/${g.id}`);
} },
key: (g.id),
...{ class: "bg-bg-soft rounded-lg p-4 mb-2 border border-bg-hard cursor-pointer hover:border-green transition-colors" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text font-medium" },
});
(g.nom);
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "ml-2 text-xs text-text-muted px-2 py-0.5 bg-bg rounded" },
});
(g.type);
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['uppercase']} */ ;
/** @type {__VLS_StyleScopedClasses['tracking-widest']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-3']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['uppercase']} */ ;
/** @type {__VLS_StyleScopedClasses['tracking-widest']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['overflow-x-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['p-3']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-col']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['min-w-[90px]']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-orange']} */ ;
/** @type {__VLS_StyleScopedClasses['text-blue']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['uppercase']} */ ;
/** @type {__VLS_StyleScopedClasses['tracking-widest']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['cursor-pointer']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
/** @type {__VLS_StyleScopedClasses['ml-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-0.5']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
return {
router: router,
gardensStore: gardensStore,
tasksStore: tasksStore,
pendingTasks: pendingTasks,
meteo3j: meteo3j,
formatDate: formatDate,
};
},
});
export default (await import('vue')).defineComponent({

View File

@@ -1,22 +1,136 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { computed, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { gardensApi } from '@/api/gardens';
const route = useRoute();
const router = useRouter();
const garden = ref(null);
const cells = ref([]);
const displayCells = computed(() => {
if (!garden.value)
return [];
const map = new Map(cells.value.map(c => [`${c.row}-${c.col}`, c]));
const result = [];
for (let row = 0; row < garden.value.grille_hauteur; row++) {
for (let col = 0; col < garden.value.grille_largeur; col++) {
result.push(map.get(`${row}-${col}`) ?? {
col, row,
libelle: `${String.fromCharCode(65 + row)}${col + 1}`,
etat: 'libre',
});
}
}
return result;
});
onMounted(async () => {
const id = Number(route.params.id);
garden.value = await gardensApi.get(id);
cells.value = await gardensApi.cells(id);
});
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4" },
...{ class: "p-4 max-w-3xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green" },
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.router.back();
} },
...{ class: "text-text-muted text-sm mb-4 hover:text-text" },
});
if (__VLS_ctx.garden) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green mb-1" },
});
(__VLS_ctx.garden.nom);
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-sm mb-6" },
});
(__VLS_ctx.garden.type);
(__VLS_ctx.garden.exposition ?? 'exposition non définie');
if (__VLS_ctx.garden.sol_type) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(__VLS_ctx.garden.sol_type);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text-muted text-xs uppercase tracking-widest mb-3" },
});
(__VLS_ctx.garden.grille_largeur);
(__VLS_ctx.garden.grille_hauteur);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "overflow-x-auto pb-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid gap-1 w-max" },
...{ style: (`grid-template-columns: repeat(${__VLS_ctx.garden.grille_largeur}, 52px)`) },
});
for (const [cell] of __VLS_getVForSourceType((__VLS_ctx.displayCells))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (`${cell.row}-${cell.col}`),
...{ class: "w-[52px] h-[52px] bg-bg-soft border border-bg-hard rounded-md flex items-center justify-center text-xs text-text-muted cursor-pointer hover:border-green transition-colors select-none" },
...{ class: ({ 'border-orange/60 bg-orange/10 text-orange': cell.etat === 'occupe' }) },
title: (cell.libelle),
});
(cell.libelle);
}
}
else {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm" },
});
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-3xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['uppercase']} */ ;
/** @type {__VLS_StyleScopedClasses['tracking-widest']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['overflow-x-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['pb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-max']} */ ;
/** @type {__VLS_StyleScopedClasses['w-[52px]']} */ ;
/** @type {__VLS_StyleScopedClasses['h-[52px]']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-md']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['cursor-pointer']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['select-none']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
return {
router: router,
garden: garden,
displayCells: displayCells,
};
},
});
export default (await import('vue')).defineComponent({

View File

@@ -1,66 +1,100 @@
<template>
<div class="p-4 max-w-2xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold text-green">Jardins</h1>
<button
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90 transition-opacity"
@click="showForm = !showForm"
>+ Nouveau</button>
<h1 class="text-2xl font-bold text-green">🪴 Jardins</h1>
<button class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
@click="openCreate">+ Nouveau</button>
</div>
<form v-if="showForm" class="bg-bg-soft rounded-lg p-4 mb-6 border border-green/30" @submit.prevent="submit">
<div class="grid gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Nom *</label>
<input v-model="form.nom" required
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Type</label>
<select v-model="form.type" class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm">
<option value="plein_air">Plein air</option>
<option value="serre">Serre</option>
<option value="tunnel">Tunnel</option>
</select>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Largeur grille</label>
<input v-model.number="form.grille_largeur" type="number" min="1" max="20"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Hauteur grille</label>
<input v-model.number="form.grille_hauteur" type="number" min="1" max="20"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" />
</div>
</div>
</div>
<div class="flex gap-2 mt-4">
<button type="submit" class="bg-green text-bg px-4 py-2 rounded text-sm font-semibold">Créer</button>
<button type="button" class="text-text-muted text-sm px-4 py-2 hover:text-text" @click="showForm = false">Annuler</button>
</div>
</form>
<div v-if="store.loading" class="text-text-muted text-sm">Chargement...</div>
<div
v-for="g in store.gardens" :key="g.id"
class="bg-bg-soft rounded-lg p-4 mb-3 border border-bg-hard flex items-center gap-3 cursor-pointer hover:border-green transition-colors group"
@click="router.push(`/jardins/${g.id}`)"
>
<div class="flex-1">
<div v-for="g in store.gardens" :key="g.id"
class="bg-bg-soft rounded-lg p-4 mb-3 border border-bg-hard flex items-center gap-3 group">
<div class="flex-1 cursor-pointer" @click="router.push(`/jardins/${g.id}`)">
<div class="text-text font-medium group-hover:text-green transition-colors">{{ g.nom }}</div>
<div class="text-text-muted text-xs mt-1">{{ g.type }} · {{ g.grille_largeur }}×{{ g.grille_hauteur }} cases</div>
<div class="text-text-muted text-xs mt-1">
{{ typeLabel(g.type) }} · {{ g.grille_largeur }}×{{ g.grille_hauteur }} cases
<span v-if="g.exposition"> · {{ g.exposition }}</span>
</div>
</div>
<button
class="text-text-muted hover:text-red text-sm px-2 py-1 rounded hover:bg-bg transition-colors"
@click.stop="store.remove(g.id!)"
></button>
<button @click="startEdit(g)" class="text-yellow text-xs hover:underline px-2">Édit.</button>
<button @click="store.remove(g.id!)" class="text-text-muted hover:text-red text-sm px-2"></button>
</div>
<div v-if="!store.loading && !store.gardens.length" class="text-text-muted text-sm text-center py-8">
Aucun jardin. Créez-en un !
</div>
<!-- Modal création / édition -->
<div v-if="showForm" class="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4" @click.self="closeForm">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-md border border-bg-soft max-h-[90vh] overflow-y-auto">
<h2 class="text-text font-bold text-lg mb-4">{{ editId ? 'Modifier le jardin' : 'Nouveau jardin' }}</h2>
<form @submit.prevent="submit" class="grid gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Nom *</label>
<input v-model="form.nom" required
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Description</label>
<textarea v-model="form.description" rows="2"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none resize-none" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Type</label>
<select v-model="form.type" class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm">
<option value="plein_air">Plein air</option>
<option value="serre">Serre</option>
<option value="tunnel">Tunnel</option>
<option value="bac">Bac / Pot</option>
</select>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Largeur grille</label>
<input v-model.number="form.grille_largeur" type="number" min="1" max="30"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Hauteur grille</label>
<input v-model.number="form.grille_hauteur" type="number" min="1" max="30"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" />
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Exposition</label>
<select v-model="form.exposition" class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm">
<option value=""></option>
<option value="Nord">Nord</option>
<option value="Est">Est</option>
<option value="Sud">Sud</option>
<option value="Ouest">Ouest</option>
<option value="Sud-Est">Sud-Est</option>
<option value="Sud-Ouest">Sud-Ouest</option>
</select>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Type de sol</label>
<select v-model="form.sol_type" class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm">
<option value=""></option>
<option value="argileux">Argileux</option>
<option value="limoneux">Limoneux</option>
<option value="sableux">Sableux</option>
<option value="calcaire">Calcaire</option>
<option value="humifère">Humifère</option>
<option value="mixte">Mixte</option>
</select>
</div>
</div>
<div class="flex gap-2 mt-2">
<button type="submit" class="bg-green text-bg px-4 py-2 rounded text-sm font-semibold">
{{ editId ? 'Enregistrer' : 'Créer' }}
</button>
<button type="button" class="text-text-muted text-sm px-4 py-2 hover:text-text" @click="closeForm">Annuler</button>
</div>
</form>
</div>
</div>
</div>
</template>
@@ -68,17 +102,49 @@
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useGardensStore } from '@/stores/gardens'
import type { Garden } from '@/api/gardens'
const router = useRouter()
const store = useGardensStore()
const showForm = ref(false)
const form = reactive({ nom: '', type: 'plein_air', grille_largeur: 6, grille_hauteur: 4 })
const editId = ref<number | null>(null)
onMounted(() => store.fetchAll())
const form = reactive({
nom: '', description: '', type: 'plein_air',
grille_largeur: 6, grille_hauteur: 4,
exposition: '', sol_type: '',
})
function typeLabel(t: string) {
return ({ plein_air: 'Plein air', serre: 'Serre', tunnel: 'Tunnel', bac: 'Bac/Pot' } as Record<string, string>)[t] ?? t
}
function openCreate() {
editId.value = null
Object.assign(form, { nom: '', description: '', type: 'plein_air', grille_largeur: 6, grille_hauteur: 4, exposition: '', sol_type: '' })
showForm.value = true
}
function startEdit(g: Garden) {
editId.value = g.id!
Object.assign(form, {
nom: g.nom, description: g.description || '',
type: g.type, grille_largeur: g.grille_largeur, grille_hauteur: g.grille_hauteur,
exposition: g.exposition || '', sol_type: g.sol_type || '',
})
showForm.value = true
}
function closeForm() { showForm.value = false; editId.value = null }
async function submit() {
await store.create({ ...form })
showForm.value = false
Object.assign(form, { nom: '', type: 'plein_air', grille_largeur: 6, grille_hauteur: 4 })
if (editId.value) {
await store.update(editId.value, { ...form })
} else {
await store.create({ ...form })
}
closeForm()
}
onMounted(() => store.fetchAll())
</script>

View File

@@ -1,22 +1,294 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useGardensStore } from '@/stores/gardens';
const router = useRouter();
const store = useGardensStore();
const showForm = ref(false);
const form = reactive({ nom: '', type: 'plein_air', grille_largeur: 6, grille_hauteur: 4 });
onMounted(() => store.fetchAll());
async function submit() {
await store.create({ ...form });
showForm.value = false;
Object.assign(form, { nom: '', type: 'plein_air', grille_largeur: 6, grille_hauteur: 4 });
}
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4" },
...{ class: "p-4 max-w-2xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center justify-between mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.showForm = !__VLS_ctx.showForm;
} },
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90 transition-opacity" },
});
if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (__VLS_ctx.submit) },
...{ class: "bg-bg-soft rounded-lg p-4 mb-6 border border-green/30" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
required: true,
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" },
});
(__VLS_ctx.form.nom);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.form.type),
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "plein_air",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "serre",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "tunnel",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-2 gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "number",
min: "1",
max: "20",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
(__VLS_ctx.form.grille_largeur);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "number",
min: "1",
max: "20",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
(__VLS_ctx.form.grille_hauteur);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 mt-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
type: "submit",
...{ class: "bg-green text-bg px-4 py-2 rounded text-sm font-semibold" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
__VLS_ctx.showForm = false;
} },
type: "button",
...{ class: "text-text-muted text-sm px-4 py-2 hover:text-text" },
});
}
if (__VLS_ctx.store.loading) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm" },
});
}
for (const [g] of __VLS_getVForSourceType((__VLS_ctx.store.gardens))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
__VLS_ctx.router.push(`/jardins/${g.id}`);
} },
key: (g.id),
...{ class: "bg-bg-soft rounded-lg p-4 mb-3 border border-bg-hard flex items-center gap-3 cursor-pointer hover:border-green transition-colors group" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text font-medium group-hover:text-green transition-colors" },
});
(g.nom);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs mt-1" },
});
(g.type);
(g.grille_largeur);
(g.grille_hauteur);
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.store.remove(g.id);
} },
...{ class: "text-text-muted hover:text-red text-sm px-2 py-1 rounded hover:bg-bg transition-colors" },
});
}
if (!__VLS_ctx.store.loading && !__VLS_ctx.store.gardens.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm text-center py-8" },
});
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-opacity']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-green/30']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-4']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['cursor-pointer']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['group']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
/** @type {__VLS_StyleScopedClasses['group-hover:text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['py-8']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
return {
router: router,
store: store,
showForm: showForm,
form: form,
submit: submit,
};
},
});
export default (await import('vue')).defineComponent({

View File

@@ -4,15 +4,23 @@ const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4" },
...{ class: "p-4 max-w-2xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green" },
...{ class: "text-2xl font-bold text-green mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-sm" },
});
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {

View File

@@ -0,0 +1,88 @@
<template>
<div class="p-4 max-w-4xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold text-yellow">🔧 Outils</h1>
<button @click="showForm = true" class="bg-yellow text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">+ Ajouter</button>
</div>
<div v-if="toolsStore.loading" class="text-text-muted text-sm">Chargement...</div>
<div v-else-if="!toolsStore.tools.length" class="text-text-muted text-sm py-4">Aucun outil enregistré.</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
<div v-for="t in toolsStore.tools" :key="t.id"
class="bg-bg-soft rounded-lg p-4 border border-bg-hard flex flex-col gap-2">
<div class="flex items-center justify-between">
<span class="text-text font-semibold">{{ t.nom }}</span>
<div class="flex gap-2">
<button @click="startEdit(t)" class="text-yellow text-xs hover:underline">Édit.</button>
<button @click="removeTool(t.id!)" class="text-red text-xs hover:underline">Suppr.</button>
</div>
</div>
<span v-if="t.categorie" class="text-xs text-yellow bg-yellow/10 rounded-full px-2 py-0.5 w-fit">{{ t.categorie }}</span>
<p v-if="t.description" class="text-text-muted text-xs">{{ t.description }}</p>
</div>
</div>
<div v-if="showForm" class="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4" @click.self="closeForm">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-sm border border-bg-soft">
<h2 class="text-text font-bold text-lg mb-4">{{ editId ? 'Modifier l\'outil' : 'Nouvel outil' }}</h2>
<form @submit.prevent="submitTool" class="flex flex-col gap-3">
<input v-model="form.nom" placeholder="Nom de l'outil *" required
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" />
<select v-model="form.categorie"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow">
<option value="">Catégorie</option>
<option value="beche">Bêche</option>
<option value="fourche">Fourche</option>
<option value="griffe">Griffe/Grelinette</option>
<option value="arrosage">Arrosage</option>
<option value="taille">Taille</option>
<option value="autre">Autre</option>
</select>
<textarea v-model="form.description" placeholder="Description..."
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow resize-none h-16" />
<div class="flex gap-2 justify-end">
<button type="button" @click="closeForm" class="px-4 py-2 text-text-muted hover:text-text text-sm">Annuler</button>
<button type="submit" class="bg-yellow text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">
{{ editId ? 'Enregistrer' : 'Créer' }}
</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { useToolsStore } from '@/stores/tools'
import type { Tool } from '@/api/tools'
const toolsStore = useToolsStore()
const showForm = ref(false)
const editId = ref<number | null>(null)
const form = reactive({ nom: '', categorie: '', description: '' })
function startEdit(t: Tool) {
editId.value = t.id!
Object.assign(form, { nom: t.nom, categorie: t.categorie || '', description: t.description || '' })
showForm.value = true
}
function closeForm() { showForm.value = false; editId.value = null }
async function submitTool() {
if (editId.value) {
await toolsStore.update(editId.value, { ...form })
} else {
await toolsStore.create({ ...form })
}
Object.assign(form, { nom: '', categorie: '', description: '' })
closeForm()
}
async function removeTool(id: number) {
if (confirm('Supprimer cet outil ?')) await toolsStore.remove(id)
}
onMounted(() => toolsStore.fetchAll())
</script>

View File

@@ -0,0 +1,295 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { onMounted, reactive, ref } from 'vue';
import { useToolsStore } from '@/stores/tools';
const toolsStore = useToolsStore();
const showForm = ref(false);
const form = reactive({ nom: '', categorie: '', description: '' });
async function submitTool() {
await toolsStore.create({ ...form });
Object.assign(form, { nom: '', categorie: '', description: '' });
showForm.value = false;
}
async function removeTool(id) {
if (confirm('Supprimer cet outil ?'))
await toolsStore.remove(id);
}
onMounted(() => toolsStore.fetchAll());
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4 max-w-4xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center justify-between mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-yellow" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.showForm = true;
} },
...{ class: "bg-yellow text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
if (__VLS_ctx.toolsStore.loading) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm" },
});
}
else if (!__VLS_ctx.toolsStore.tools.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm py-4" },
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3" },
});
for (const [t] of __VLS_getVForSourceType((__VLS_ctx.toolsStore.tools))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (t.id),
...{ class: "bg-bg-soft rounded-lg p-4 border border-bg-hard flex flex-col gap-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center justify-between" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text font-semibold" },
});
(t.nom);
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.removeTool(t.id);
} },
...{ class: "text-red text-xs hover:underline" },
});
if (t.categorie) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-xs text-yellow bg-yellow/10 rounded-full px-2 py-0.5 w-fit" },
});
(t.categorie);
}
if (t.description) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-xs" },
});
(t.description);
}
}
if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
__VLS_ctx.showForm = false;
} },
...{ class: "fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg-hard rounded-xl p-6 w-full max-w-sm border border-bg-soft" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text font-bold text-lg mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (__VLS_ctx.submitTool) },
...{ class: "flex flex-col gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
placeholder: "Nom de l'outil *",
required: true,
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" },
});
(__VLS_ctx.form.nom);
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.form.categorie),
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "beche",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "fourche",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "griffe",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "arrosage",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "taille",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "autre",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.textarea)({
value: (__VLS_ctx.form.description),
placeholder: "Description...",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow resize-none h-16" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 justify-end" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
__VLS_ctx.showForm = false;
} },
type: "button",
...{ class: "px-4 py-2 text-text-muted hover:text-text text-sm" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
type: "submit",
...{ class: "bg-yellow text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-4xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-1']} */ ;
/** @type {__VLS_StyleScopedClasses['sm:grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:grid-cols-3']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-col']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-yellow/10']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-full']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-0.5']} */ ;
/** @type {__VLS_StyleScopedClasses['w-fit']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
/** @type {__VLS_StyleScopedClasses['inset-0']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/60']} */ ;
/** @type {__VLS_StyleScopedClasses['z-50']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['p-6']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-col']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['resize-none']} */ ;
/** @type {__VLS_StyleScopedClasses['h-16']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-end']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {
toolsStore: toolsStore,
showForm: showForm,
form: form,
submitTool: submitTool,
removeTool: removeTool,
};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */

View File

@@ -1,6 +1,141 @@
<template>
<div class="p-4 max-w-2xl mx-auto">
<h1 class="text-2xl font-bold text-green mb-4">Planning</h1>
<p class="text-text-muted text-sm">Vue calendrier prochaine étape.</p>
<div class="p-4 max-w-3xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold text-green">📆 Planning</h1>
<!-- Navigateur semaine -->
<div class="flex items-center gap-3">
<button @click="prevWeek" class="text-text-muted hover:text-text text-lg"></button>
<span class="text-text text-sm font-medium">{{ weekLabel }}</span>
<button @click="nextWeek" class="text-text-muted hover:text-text text-lg"></button>
<button @click="goToday" class="text-xs text-green border border-green/30 rounded px-2 py-0.5 hover:bg-green/10">Auj.</button>
</div>
</div>
<!-- Grille semaine -->
<div class="grid grid-cols-7 gap-1 mb-2">
<div v-for="d in weekDays" :key="d.iso"
:class="['text-center text-xs py-1 rounded',
d.isToday ? 'text-green font-bold' : 'text-text-muted']">
<div>{{ d.dayShort }}</div>
<div :class="['text-sm font-semibold mt-0.5', d.isToday ? 'bg-green text-bg rounded-full w-6 h-6 flex items-center justify-center mx-auto' : '']">
{{ d.dayNum }}
</div>
</div>
</div>
<!-- Tâches par jour -->
<div class="grid grid-cols-7 gap-1">
<div v-for="d in weekDays" :key="d.iso"
:class="['min-h-24 rounded-lg p-1 border transition-colors',
d.isToday ? 'border-green/40 bg-green/5' : 'border-bg-hard bg-bg-soft']">
<div v-for="t in tasksByDay[d.iso] || []" :key="t.id"
:class="['text-xs rounded px-1 py-0.5 mb-0.5 cursor-pointer hover:opacity-80 truncate',
priorityClass(t.priorite)]"
:title="t.titre">
{{ t.titre }}
</div>
<!-- Zone drop-cible (vide) -->
<div v-if="!(tasksByDay[d.iso]?.length)" class="text-text-muted text-xs text-center pt-2 opacity-40"></div>
</div>
</div>
<!-- Tâches sans date -->
<div class="mt-6">
<h2 class="text-text-muted text-xs uppercase tracking-widest mb-2">Sans date</h2>
<div v-if="!unscheduled.length" class="text-text-muted text-xs pl-2">Toutes les tâches ont une échéance.</div>
<div v-for="t in unscheduled" :key="t.id"
class="bg-bg-soft rounded-lg p-2 mb-1 border border-bg-hard flex items-center gap-2">
<span :class="['text-xs w-2 h-2 rounded-full flex-shrink-0', dotClass(t.priorite)]"></span>
<span class="text-text text-sm flex-1 truncate">{{ t.titre }}</span>
<span :class="['text-xs px-1.5 py-0.5 rounded', statutClass(t.statut)]">{{ t.statut }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useTasksStore } from '@/stores/tasks'
const store = useTasksStore()
const today = new Date()
const weekStart = ref(getMonday(today))
function getMonday(d: Date) {
const day = d.getDay()
const diff = (day === 0 ? -6 : 1 - day)
const m = new Date(d)
m.setDate(d.getDate() + diff)
m.setHours(0, 0, 0, 0)
return m
}
function toIso(d: Date) {
return d.toISOString().slice(0, 10)
}
const weekDays = computed(() => {
const todayIso = toIso(today)
return Array.from({ length: 7 }, (_, i) => {
const d = new Date(weekStart.value)
d.setDate(d.getDate() + i)
return {
iso: toIso(d),
dayShort: d.toLocaleDateString('fr-FR', { weekday: 'short' }),
dayNum: d.getDate(),
isToday: toIso(d) === todayIso,
}
})
})
const weekLabel = computed(() => {
const start = weekDays.value[0]
const end = weekDays.value[6]
const s = new Date(start.iso + 'T12:00:00')
const e = new Date(end.iso + 'T12:00:00')
return `${s.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' })} ${e.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric' })}`
})
const tasksByDay = computed(() => {
const map: Record<string, typeof store.tasks> = {}
for (const t of store.tasks) {
if (!t.echeance) continue
const key = t.echeance.slice(0, 10)
if (!map[key]) map[key] = []
map[key].push(t)
}
return map
})
const unscheduled = computed(() => store.tasks.filter(t => !t.echeance && t.statut !== 'fait'))
function prevWeek() {
const d = new Date(weekStart.value)
d.setDate(d.getDate() - 7)
weekStart.value = d
}
function nextWeek() {
const d = new Date(weekStart.value)
d.setDate(d.getDate() + 7)
weekStart.value = d
}
function goToday() { weekStart.value = getMonday(today) }
const priorityClass = (p: string) => ({
haute: 'bg-red/20 text-red',
normale: 'bg-yellow/20 text-yellow',
basse: 'bg-bg-hard text-text-muted',
}[p] || 'bg-bg-hard text-text-muted')
const dotClass = (p: string) => ({
haute: 'bg-red', normale: 'bg-yellow', basse: 'bg-text-muted',
}[p] || 'bg-text-muted')
const statutClass = (s: string) => ({
a_faire: 'bg-blue/20 text-blue', en_cours: 'bg-green/20 text-green',
fait: 'bg-text-muted/20 text-text-muted',
}[s] || '')
onMounted(() => store.fetchAll())
</script>

View File

@@ -4,15 +4,23 @@ const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4" },
...{ class: "p-4 max-w-2xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green" },
...{ class: "text-2xl font-bold text-green mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-sm" },
});
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {

View File

@@ -1,35 +1,258 @@
<template>
<div class="p-4 max-w-2xl mx-auto">
<h1 class="text-2xl font-bold text-green mb-6">Plantations</h1>
<div class="p-4 max-w-3xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold text-green">🌱 Plantations</h1>
<button @click="showCreate = true"
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">
+ Nouvelle
</button>
</div>
<!-- Filtres statut -->
<div class="flex gap-2 mb-4 flex-wrap">
<button v-for="s in statuts" :key="s.val" @click="filterStatut = s.val"
:class="['px-3 py-1 rounded-full text-xs font-medium transition-colors',
filterStatut === s.val ? 'bg-blue text-bg' : 'bg-bg-soft text-text-muted hover:text-text']">
{{ s.label }}
</button>
</div>
<div v-if="store.loading" class="text-text-muted text-sm">Chargement...</div>
<div v-for="p in store.plantings" :key="p.id"
class="bg-bg-soft rounded-lg p-4 mb-3 border border-bg-hard">
<div class="flex items-start gap-3">
<div v-else-if="!filtered.length" class="text-text-muted text-sm text-center py-8">
Aucune plantation enregistrée.
</div>
<div v-for="p in filtered" :key="p.id"
class="bg-bg-soft rounded-xl mb-3 border border-bg-hard overflow-hidden">
<!-- En-tête plantation -->
<div class="p-4 flex items-start gap-3">
<div class="flex-1">
<div class="text-text font-medium">Plantation #{{ p.id }}</div>
<div class="text-text-muted text-xs mt-1">
Jardin {{ p.garden_id }} · Variété {{ p.variety_id }} · {{ p.quantite }} plant(s)
<div class="flex items-center gap-2 flex-wrap">
<span class="text-text font-semibold">{{ plantName(p.variety_id) }}</span>
<span class="text-text-muted text-xs"> {{ gardenName(p.garden_id) }}</span>
<span :class="['text-xs px-2 py-0.5 rounded-full font-medium', statutClass(p.statut)]">{{ p.statut }}</span>
</div>
<div class="text-text-muted text-xs mt-1 flex gap-3 flex-wrap">
<span>{{ p.quantite }} plant(s)</span>
<span v-if="p.date_plantation">🌱 {{ fmtDate(p.date_plantation) }}</span>
<span v-if="p.notes">📝 {{ p.notes }}</span>
</div>
<span class="inline-block mt-2 text-xs px-2 py-0.5 rounded" :class="{
'bg-blue/20 text-blue': p.statut === 'prevu',
'bg-green/20 text-green': p.statut === 'en_cours',
'bg-text-muted/20 text-text-muted': p.statut === 'termine',
'bg-red/20 text-red': p.statut === 'echoue',
}">{{ p.statut }}</span>
</div>
<button class="text-text-muted hover:text-red text-sm" @click="store.remove(p.id!)"></button>
<div class="flex gap-2 items-center">
<button @click="toggleRecoltes(p.id!)"
:class="['text-xs px-2 py-1 rounded transition-colors',
openRecoltes === p.id ? 'bg-aqua/20 text-aqua' : 'bg-bg-hard text-text-muted hover:text-aqua']">
🍅 Récoltes
</button>
<button @click="startEdit(p)" class="text-yellow text-xs hover:underline">Édit.</button>
<button @click="store.remove(p.id!)" class="text-text-muted hover:text-red text-sm ml-1"></button>
</div>
</div>
<!-- Section récoltes (dépliable) -->
<div v-if="openRecoltes === p.id" class="border-t border-bg-hard px-4 py-3 bg-bg/50">
<div v-if="loadingRecoltes" class="text-text-muted text-xs py-2">Chargement...</div>
<div v-else>
<div v-if="!recoltesList.length" class="text-text-muted text-xs mb-2">Aucune récolte enregistrée.</div>
<div v-for="r in recoltesList" :key="r.id"
class="flex items-center gap-3 text-sm py-1 border-b border-bg-hard last:border-0">
<span class="text-aqua font-mono">{{ r.quantite }} {{ r.unite }}</span>
<span class="text-text-muted text-xs">{{ fmtDate(r.date_recolte) }}</span>
<span v-if="r.notes" class="text-text-muted text-xs flex-1 truncate">{{ r.notes }}</span>
<button @click="deleteRecolte(r.id!, p.id!)" class="text-text-muted hover:text-red text-xs ml-auto"></button>
</div>
<!-- Formulaire ajout récolte -->
<form @submit.prevent="addRecolte(p.id!)" class="flex gap-2 mt-3 flex-wrap items-end">
<div>
<label class="text-text-muted text-xs block mb-1">Quantité *</label>
<input v-model.number="rForm.quantite" type="number" step="0.1" min="0" required
class="bg-bg border border-bg-hard rounded px-2 py-1 text-text text-xs w-20 focus:border-aqua outline-none" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Unité</label>
<select v-model="rForm.unite"
class="bg-bg border border-bg-hard rounded px-2 py-1 text-text text-xs focus:border-aqua outline-none">
<option>kg</option><option>g</option><option>unites</option><option>litres</option><option>bottes</option>
</select>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Date *</label>
<input v-model="rForm.date_recolte" type="date" required
class="bg-bg border border-bg-hard rounded px-2 py-1 text-text text-xs focus:border-aqua outline-none" />
</div>
<button type="submit"
class="bg-aqua text-bg px-3 py-1 rounded text-xs font-semibold hover:opacity-90 self-end">
+ Ajouter
</button>
</form>
</div>
</div>
</div>
<div v-if="!store.loading && !store.plantings.length" class="text-text-muted text-sm text-center py-8">
Aucune plantation enregistrée.
<!-- Modal création / édition plantation -->
<div v-if="showCreate" class="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4"
@click.self="closeCreate">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-md border border-bg-soft">
<h2 class="text-text font-bold text-lg mb-4">{{ editId ? 'Modifier la plantation' : 'Nouvelle plantation' }}</h2>
<form @submit.prevent="createPlanting" class="flex flex-col gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Jardin *</label>
<select v-model.number="cForm.garden_id" required
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green">
<option value="">Choisir un jardin</option>
<option v-for="g in gardensStore.gardens" :key="g.id" :value="g.id">{{ g.nom }}</option>
</select>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Plante *</label>
<select v-model.number="cForm.variety_id" required
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green">
<option value="">Choisir une plante</option>
<option v-for="p in plantsStore.plants" :key="p.id" :value="p.id">
{{ p.nom_commun }}{{ p.variete ? ' ' + p.variete : '' }}
</option>
</select>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Quantité</label>
<input v-model.number="cForm.quantite" type="number" min="1"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Date plantation</label>
<input v-model="cForm.date_plantation" type="date"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
</div>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Statut</label>
<select v-model="cForm.statut"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green">
<option value="prevu">Prévu</option>
<option value="en_cours">En cours</option>
<option value="termine">Terminé</option>
</select>
</div>
<textarea v-model="cForm.notes" placeholder="Notes..."
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green resize-none h-16" />
<div class="flex gap-2 justify-end">
<button type="button" @click="closeCreate" class="px-4 py-2 text-text-muted hover:text-text text-sm">Annuler</button>
<button type="submit" class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">
{{ editId ? 'Enregistrer' : 'Créer' }}
</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { computed, onMounted, reactive, ref } from 'vue'
import { usePlantingsStore } from '@/stores/plantings'
import { useGardensStore } from '@/stores/gardens'
import { usePlantsStore } from '@/stores/plants'
import { recoltesApi, type Recolte } from '@/api/recoltes'
const store = usePlantingsStore()
onMounted(() => store.fetchAll())
const gardensStore = useGardensStore()
const plantsStore = usePlantsStore()
const showCreate = ref(false)
const editId = ref<number | null>(null)
const filterStatut = ref('')
const openRecoltes = ref<number | null>(null)
const recoltesList = ref<Recolte[]>([])
const loadingRecoltes = ref(false)
const statuts = [
{ val: '', label: 'Toutes' },
{ val: 'prevu', label: '📋 Prévu' },
{ val: 'en_cours', label: '🌿 En cours' },
{ val: 'termine', label: '✅ Terminé' },
{ val: 'echoue', label: '❌ Échoué' },
]
const cForm = reactive({
garden_id: 0, variety_id: 0, quantite: 1,
date_plantation: '', statut: 'prevu', notes: ''
})
const rForm = reactive({
quantite: 1, unite: 'kg', date_recolte: new Date().toISOString().slice(0, 10)
})
const filtered = computed(() =>
filterStatut.value ? store.plantings.filter(p => p.statut === filterStatut.value) : store.plantings
)
function plantName(id: number) {
const p = plantsStore.plants.find(x => x.id === id)
return p ? (p.variete ? `${p.nom_commun} (${p.variete})` : p.nom_commun) : `Plante #${id}`
}
function gardenName(id: number) {
return gardensStore.gardens.find(g => g.id === id)?.nom ?? `Jardin #${id}`
}
function fmtDate(s: string) {
return new Date(s + (s.length === 10 ? 'T12:00:00' : '')).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric' })
}
const statutClass = (s: string) => ({
prevu: 'bg-blue/20 text-blue',
en_cours: 'bg-green/20 text-green',
termine: 'bg-text-muted/20 text-text-muted',
echoue: 'bg-red/20 text-red',
}[s] || 'bg-bg text-text-muted')
async function toggleRecoltes(id: number) {
if (openRecoltes.value === id) { openRecoltes.value = null; return }
openRecoltes.value = id
loadingRecoltes.value = true
try { recoltesList.value = await recoltesApi.list(id) } catch { recoltesList.value = [] }
finally { loadingRecoltes.value = false }
}
async function addRecolte(plantingId: number) {
const created = await recoltesApi.create(plantingId, { ...rForm })
recoltesList.value.push(created)
Object.assign(rForm, { quantite: 1, unite: 'kg', date_recolte: new Date().toISOString().slice(0, 10) })
}
async function deleteRecolte(id: number, plantingId: number) {
if (!confirm('Supprimer cette récolte ?')) return
await recoltesApi.delete(id)
recoltesList.value = recoltesList.value.filter(r => r.id !== id)
}
function startEdit(p: typeof store.plantings[0]) {
editId.value = p.id!
Object.assign(cForm, {
garden_id: p.garden_id, variety_id: p.variety_id,
quantite: p.quantite, date_plantation: p.date_plantation?.slice(0, 10) || '',
statut: p.statut, notes: p.notes || '',
})
showCreate.value = true
}
function closeCreate() { showCreate.value = false; editId.value = null }
async function createPlanting() {
if (editId.value) {
await store.update(editId.value, { ...cForm })
} else {
await store.create({ ...cForm })
}
closeCreate()
Object.assign(cForm, { garden_id: 0, variety_id: 0, quantite: 1, date_plantation: '', statut: 'prevu', notes: '' })
}
onMounted(() => {
store.fetchAll()
gardensStore.fetchAll()
plantsStore.fetchAll()
})
</script>

View File

@@ -1,22 +1,704 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { computed, onMounted, reactive, ref } from 'vue';
import { usePlantingsStore } from '@/stores/plantings';
import { useGardensStore } from '@/stores/gardens';
import { usePlantsStore } from '@/stores/plants';
import { recoltesApi } from '@/api/recoltes';
const store = usePlantingsStore();
const gardensStore = useGardensStore();
const plantsStore = usePlantsStore();
const showCreate = ref(false);
const filterStatut = ref('');
const openRecoltes = ref(null);
const recoltesList = ref([]);
const loadingRecoltes = ref(false);
const statuts = [
{ val: '', label: 'Toutes' },
{ val: 'prevu', label: '📋 Prévu' },
{ val: 'en_cours', label: '🌿 En cours' },
{ val: 'termine', label: '✅ Terminé' },
{ val: 'echoue', label: '❌ Échoué' },
];
const cForm = reactive({
garden_id: 0, variety_id: 0, quantite: 1,
date_plantation: '', statut: 'prevu', notes: ''
});
const rForm = reactive({
quantite: 1, unite: 'kg', date_recolte: new Date().toISOString().slice(0, 10)
});
const filtered = computed(() => filterStatut.value ? store.plantings.filter(p => p.statut === filterStatut.value) : store.plantings);
function plantName(id) {
const p = plantsStore.plants.find(x => x.id === id);
return p ? (p.variete ? `${p.nom_commun} (${p.variete})` : p.nom_commun) : `Plante #${id}`;
}
function gardenName(id) {
return gardensStore.gardens.find(g => g.id === id)?.nom ?? `Jardin #${id}`;
}
function fmtDate(s) {
return new Date(s + (s.length === 10 ? 'T12:00:00' : '')).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric' });
}
const statutClass = (s) => ({
prevu: 'bg-blue/20 text-blue',
en_cours: 'bg-green/20 text-green',
termine: 'bg-text-muted/20 text-text-muted',
echoue: 'bg-red/20 text-red',
}[s] || 'bg-bg text-text-muted');
async function toggleRecoltes(id) {
if (openRecoltes.value === id) {
openRecoltes.value = null;
return;
}
openRecoltes.value = id;
loadingRecoltes.value = true;
try {
recoltesList.value = await recoltesApi.list(id);
}
catch {
recoltesList.value = [];
}
finally {
loadingRecoltes.value = false;
}
}
async function addRecolte(plantingId) {
const created = await recoltesApi.create(plantingId, { ...rForm });
recoltesList.value.push(created);
Object.assign(rForm, { quantite: 1, unite: 'kg', date_recolte: new Date().toISOString().slice(0, 10) });
}
async function deleteRecolte(id, plantingId) {
if (!confirm('Supprimer cette récolte ?'))
return;
await recoltesApi.delete(id);
recoltesList.value = recoltesList.value.filter(r => r.id !== id);
}
async function createPlanting() {
await store.create({ ...cForm });
showCreate.value = false;
Object.assign(cForm, { garden_id: 0, variety_id: 0, quantite: 1, date_plantation: '', statut: 'prevu', notes: '' });
}
onMounted(() => {
store.fetchAll();
gardensStore.fetchAll();
plantsStore.fetchAll();
});
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4" },
...{ class: "p-4 max-w-3xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center justify-between mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.showCreate = true;
} },
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 mb-4 flex-wrap" },
});
for (const [s] of __VLS_getVForSourceType((__VLS_ctx.statuts))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.filterStatut = s.val;
} },
key: (s.val),
...{ class: (['px-3 py-1 rounded-full text-xs font-medium transition-colors',
__VLS_ctx.filterStatut === s.val ? 'bg-blue text-bg' : 'bg-bg-soft text-text-muted hover:text-text']) },
});
(s.label);
}
if (__VLS_ctx.store.loading) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm" },
});
}
else if (!__VLS_ctx.filtered.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm text-center py-8" },
});
}
for (const [p] of __VLS_getVForSourceType((__VLS_ctx.filtered))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (p.id),
...{ class: "bg-bg-soft rounded-xl mb-3 border border-bg-hard overflow-hidden" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4 flex items-start gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center gap-2 flex-wrap" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text font-semibold" },
});
(__VLS_ctx.plantName(p.variety_id));
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text-muted text-xs" },
});
(__VLS_ctx.gardenName(p.garden_id));
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: (['text-xs px-2 py-0.5 rounded-full font-medium', __VLS_ctx.statutClass(p.statut)]) },
});
(p.statut);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs mt-1 flex gap-3 flex-wrap" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(p.quantite);
if (p.date_plantation) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(__VLS_ctx.fmtDate(p.date_plantation));
}
if (p.notes) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(p.notes);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 items-center" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.toggleRecoltes(p.id);
} },
...{ class: (['text-xs px-2 py-1 rounded transition-colors',
__VLS_ctx.openRecoltes === p.id ? 'bg-aqua/20 text-aqua' : 'bg-bg-hard text-text-muted hover:text-aqua']) },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.store.remove(p.id);
} },
...{ class: "text-text-muted hover:text-red text-sm ml-1" },
});
if (__VLS_ctx.openRecoltes === p.id) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "border-t border-bg-hard px-4 py-3 bg-bg/50" },
});
if (__VLS_ctx.loadingRecoltes) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs py-2" },
});
}
else {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
if (!__VLS_ctx.recoltesList.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs mb-2" },
});
}
for (const [r] of __VLS_getVForSourceType((__VLS_ctx.recoltesList))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (r.id),
...{ class: "flex items-center gap-3 text-sm py-1 border-b border-bg-hard last:border-0" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-aqua font-mono" },
});
(r.quantite);
(r.unite);
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text-muted text-xs" },
});
(__VLS_ctx.fmtDate(r.date_recolte));
if (r.notes) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text-muted text-xs flex-1 truncate" },
});
(r.notes);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.openRecoltes === p.id))
return;
if (!!(__VLS_ctx.loadingRecoltes))
return;
__VLS_ctx.deleteRecolte(r.id, p.id);
} },
...{ class: "text-text-muted hover:text-red text-xs ml-auto" },
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (...[$event]) => {
if (!(__VLS_ctx.openRecoltes === p.id))
return;
if (!!(__VLS_ctx.loadingRecoltes))
return;
__VLS_ctx.addRecolte(p.id);
} },
...{ class: "flex gap-2 mt-3 flex-wrap items-end" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "number",
step: "0.1",
min: "0",
required: true,
...{ class: "bg-bg border border-bg-hard rounded px-2 py-1 text-text text-xs w-20 focus:border-aqua outline-none" },
});
(__VLS_ctx.rForm.quantite);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.rForm.unite),
...{ class: "bg-bg border border-bg-hard rounded px-2 py-1 text-text text-xs focus:border-aqua outline-none" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "date",
required: true,
...{ class: "bg-bg border border-bg-hard rounded px-2 py-1 text-text text-xs focus:border-aqua outline-none" },
});
(__VLS_ctx.rForm.date_recolte);
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
type: "submit",
...{ class: "bg-aqua text-bg px-3 py-1 rounded text-xs font-semibold hover:opacity-90 self-end" },
});
}
}
}
if (__VLS_ctx.showCreate) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showCreate))
return;
__VLS_ctx.showCreate = false;
} },
...{ class: "fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg-hard rounded-xl p-6 w-full max-w-md border border-bg-soft" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text font-bold text-lg mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (__VLS_ctx.createPlanting) },
...{ class: "flex flex-col gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.cForm.garden_id),
required: true,
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "",
});
for (const [g] of __VLS_getVForSourceType((__VLS_ctx.gardensStore.gardens))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
key: (g.id),
value: (g.id),
});
(g.nom);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.cForm.variety_id),
required: true,
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "",
});
for (const [p] of __VLS_getVForSourceType((__VLS_ctx.plantsStore.plants))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
key: (p.id),
value: (p.id),
});
(p.nom_commun);
(p.variete ? ' — ' + p.variete : '');
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-2 gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "number",
min: "1",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" },
});
(__VLS_ctx.cForm.quantite);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "date",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" },
});
(__VLS_ctx.cForm.date_plantation);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.cForm.statut),
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "prevu",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "en_cours",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "termine",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.textarea)({
value: (__VLS_ctx.cForm.notes),
placeholder: "Notes...",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green resize-none h-16" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 justify-end" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showCreate))
return;
__VLS_ctx.showCreate = false;
} },
type: "button",
...{ class: "px-4 py-2 text-text-muted hover:text-text text-sm" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
type: "submit",
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-3xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['py-8']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['overflow-hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-start']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['ml-1']} */ ;
/** @type {__VLS_StyleScopedClasses['border-t']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-3']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg/50']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['border-b']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['last:border-0']} */ ;
/** @type {__VLS_StyleScopedClasses['text-aqua']} */ ;
/** @type {__VLS_StyleScopedClasses['font-mono']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['truncate']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['ml-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-3']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
/** @type {__VLS_StyleScopedClasses['items-end']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['w-20']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-aqua']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-aqua']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-aqua']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-aqua']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
/** @type {__VLS_StyleScopedClasses['self-end']} */ ;
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
/** @type {__VLS_StyleScopedClasses['inset-0']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/60']} */ ;
/** @type {__VLS_StyleScopedClasses['z-50']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['p-6']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-md']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-col']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['resize-none']} */ ;
/** @type {__VLS_StyleScopedClasses['h-16']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-end']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
return {
store: store,
gardensStore: gardensStore,
plantsStore: plantsStore,
showCreate: showCreate,
filterStatut: filterStatut,
openRecoltes: openRecoltes,
recoltesList: recoltesList,
loadingRecoltes: loadingRecoltes,
statuts: statuts,
cForm: cForm,
rForm: rForm,
filtered: filtered,
plantName: plantName,
gardenName: gardenName,
fmtDate: fmtDate,
statutClass: statutClass,
toggleRecoltes: toggleRecoltes,
addRecolte: addRecolte,
deleteRecolte: deleteRecolte,
createPlanting: createPlanting,
};
},
});
export default (await import('vue')).defineComponent({

View File

@@ -0,0 +1,361 @@
<template>
<div class="p-4 max-w-4xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold text-green">🌱 Plantes</h1>
<button @click="showForm = true" class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">+ Ajouter</button>
</div>
<!-- Filtres catégorie -->
<div class="flex gap-2 mb-4 flex-wrap">
<button v-for="cat in categories" :key="cat.val"
@click="selectedCat = cat.val"
:class="['px-3 py-1 rounded-full text-xs font-medium transition-colors',
selectedCat === cat.val ? 'bg-green text-bg' : 'bg-bg-soft text-text-muted hover:text-text']">
{{ cat.label }}
</button>
</div>
<!-- Liste -->
<div v-if="plantsStore.loading" class="text-text-muted text-sm">Chargement...</div>
<div v-else-if="!filteredPlants.length" class="text-text-muted text-sm py-4">Aucune plante.</div>
<div v-for="p in filteredPlants" :key="p.id"
class="bg-bg-soft rounded-lg mb-2 border border-bg-hard overflow-hidden">
<!-- En-tête cliquable -->
<div class="p-4 flex items-start justify-between gap-4 cursor-pointer"
@click="toggleDetail(p.id!)">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1 flex-wrap">
<span class="text-text font-semibold">{{ p.nom_commun }}</span>
<span v-if="p.variete" class="text-text-muted text-xs"> {{ p.variete }}</span>
<span v-if="p.categorie" :class="['text-xs px-2 py-0.5 rounded-full font-medium', catClass(p.categorie)]">{{ catLabel(p.categorie) }}</span>
</div>
<div class="text-text-muted text-xs flex gap-3 flex-wrap">
<span v-if="p.famille">🌿 {{ p.famille }}</span>
<span v-if="p.espacement_cm"> {{ p.espacement_cm }}cm</span>
<span v-if="p.besoin_eau">💧 {{ p.besoin_eau }}</span>
<span v-if="p.plantation_mois">🌱 Plantation: mois {{ p.plantation_mois }}</span>
</div>
</div>
<div class="flex items-center gap-2 shrink-0">
<span class="text-text-muted text-xs">{{ openId === p.id ? '▲' : '▼' }}</span>
<button @click.stop="startEdit(p)" class="text-yellow text-xs hover:underline">Édit.</button>
<button @click.stop="removePlant(p.id!)" class="text-red text-xs hover:underline">Suppr.</button>
</div>
</div>
<!-- Panneau détail -->
<div v-if="openId === p.id" class="border-t border-bg-hard px-4 pb-4 pt-3">
<!-- Notes -->
<p v-if="p.notes" class="text-text-muted text-sm mb-3 italic">{{ p.notes }}</p>
<!-- Galerie photos -->
<div class="mb-2 flex items-center justify-between">
<span class="text-text-muted text-xs font-medium uppercase tracking-wide">Photos</span>
<button @click="openUpload(p)" class="text-green text-xs hover:underline">+ Ajouter une photo</button>
</div>
<div v-if="loadingPhotos" class="text-text-muted text-xs">Chargement...</div>
<div v-else-if="!plantPhotos.length" class="text-text-muted text-xs mb-3">Aucune photo pour cette plante.</div>
<div v-else class="grid grid-cols-4 gap-2 mb-3">
<div v-for="m in plantPhotos" :key="m.id"
class="aspect-square rounded overflow-hidden bg-bg-hard relative group cursor-pointer"
@click="lightbox = m">
<img :src="m.thumbnail_url || m.url" class="w-full h-full object-cover" />
<div v-if="m.identified_common"
class="absolute bottom-0 left-0 right-0 bg-black/70 text-xs text-green px-1 py-0.5 truncate">
{{ m.identified_common }}
</div>
<button @click.stop="deletePhoto(m)" class="hidden group-hover:flex absolute top-1 right-1 bg-red/80 text-white text-xs rounded px-1"></button>
</div>
</div>
<!-- Lier une photo existante de la bibliothèque -->
<button @click="openLinkPhoto(p)" class="text-blue text-xs hover:underline">
🔗 Lier une photo existante de la bibliothèque
</button>
</div>
</div>
<!-- Modal formulaire création / édition -->
<div v-if="showForm || editPlant" class="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4" @click.self="closeForm">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-md border border-bg-soft max-h-[90vh] overflow-y-auto">
<h2 class="text-text font-bold text-lg mb-4">{{ editPlant ? 'Modifier la plante' : 'Nouvelle plante' }}</h2>
<form @submit.prevent="submitPlant" class="flex flex-col gap-3">
<input v-model="form.nom_commun" placeholder="Nom commun *" required
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
<input v-model="form.nom_botanique" placeholder="Nom botanique"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
<input v-model="form.variete" placeholder="Variété"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
<input v-model="form.famille" placeholder="Famille botanique"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
<select v-model="form.categorie"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green">
<option value="">Catégorie</option>
<option value="potager">Potager</option>
<option value="fleur">Fleur</option>
<option value="arbre">Arbre</option>
<option value="arbuste">Arbuste</option>
<option value="adventice">Adventice (mauvaise herbe)</option>
</select>
<select v-model="form.type_plante"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green">
<option value="">Type</option>
<option value="legume">Légume</option>
<option value="fruit">Fruit</option>
<option value="aromatique">Aromatique</option>
<option value="fleur">Fleur</option>
<option value="adventice">Adventice</option>
</select>
<select v-model="form.besoin_eau"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green">
<option value="">Besoin en eau</option>
<option value="faible">Faible</option>
<option value="moyen">Moyen</option>
<option value="élevé">Élevé</option>
</select>
<select v-model="form.besoin_soleil"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green">
<option value="">Ensoleillement</option>
<option value="ombre">Ombre</option>
<option value="mi-ombre">Mi-ombre</option>
<option value="plein soleil">Plein soleil</option>
</select>
<div class="flex gap-2">
<input v-model.number="form.espacement_cm" type="number" placeholder="Espacement (cm)"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm flex-1 outline-none focus:border-green" />
<input v-model.number="form.temp_min_c" type="number" placeholder="T° min (°C)"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm flex-1 outline-none focus:border-green" />
</div>
<input v-model="form.plantation_mois" placeholder="Mois plantation (ex: 3,4,5)"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
<input v-model="form.recolte_mois" placeholder="Mois récolte (ex: 7,8,9)"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
<textarea v-model="form.notes" placeholder="Notes..."
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green resize-none h-20" />
<div class="flex gap-2 justify-end">
<button type="button" @click="closeForm"
class="px-4 py-2 text-text-muted hover:text-text text-sm">Annuler</button>
<button type="submit"
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">
{{ editPlant ? 'Enregistrer' : 'Créer' }}
</button>
</div>
</form>
</div>
</div>
<!-- Modal upload photo pour une plante -->
<div v-if="uploadTarget" class="fixed inset-0 bg-black/70 z-50 flex items-center justify-center p-4" @click.self="uploadTarget = null">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-sm border border-bg-soft">
<h3 class="text-text font-bold mb-4">Photo pour "{{ uploadTarget.nom_commun }}"</h3>
<label class="block border-2 border-dashed border-bg-soft rounded-lg p-6 text-center cursor-pointer hover:border-green transition-colors">
<input type="file" accept="image/*" class="hidden" @change="uploadPhoto" />
<div class="text-text-muted text-sm">📷 Choisir une image</div>
</label>
<button @click="uploadTarget = null" class="mt-3 w-full text-text-muted hover:text-text text-sm">Annuler</button>
</div>
</div>
<!-- Modal lier photo existante -->
<div v-if="linkTarget" class="fixed inset-0 bg-black/70 z-50 flex items-center justify-center p-4" @click.self="linkTarget = null">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-2xl border border-bg-soft max-h-[80vh] flex flex-col">
<h3 class="text-text font-bold mb-3">Lier une photo à "{{ linkTarget.nom_commun }}"</h3>
<p class="text-text-muted text-xs mb-3">Sélectionne une photo de la bibliothèque (non liée à une plante)</p>
<div v-if="!unlinkPhotos.length" class="text-text-muted text-sm py-4 text-center">Aucune photo disponible.</div>
<div v-else class="grid grid-cols-4 gap-2 overflow-y-auto flex-1">
<div v-for="m in unlinkPhotos" :key="m.id"
class="aspect-square rounded overflow-hidden bg-bg-hard relative cursor-pointer group border-2 transition-colors"
:class="selectedLinkPhoto === m.id ? 'border-green' : 'border-transparent'"
@click="selectedLinkPhoto = m.id">
<img :src="m.thumbnail_url || m.url" class="w-full h-full object-cover" />
<div v-if="m.identified_common" class="absolute bottom-0 left-0 right-0 bg-black/70 text-xs text-green px-1 py-0.5 truncate">{{ m.identified_common }}</div>
</div>
</div>
<div class="flex gap-2 justify-end mt-3">
<button @click="linkTarget = null" class="px-4 py-2 text-text-muted hover:text-text text-sm">Annuler</button>
<button @click="confirmLink" :disabled="!selectedLinkPhoto"
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90 disabled:opacity-40">
Lier la photo
</button>
</div>
</div>
</div>
<!-- Lightbox -->
<div v-if="lightbox" class="fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4" @click.self="lightbox = null">
<div class="max-w-lg w-full">
<img :src="lightbox.url" class="w-full rounded-xl" />
<div v-if="lightbox.identified_species" class="text-center mt-3 text-text-muted text-sm">
<div class="text-green font-semibold text-base">{{ lightbox.identified_common }}</div>
<div class="italic">{{ lightbox.identified_species }}</div>
<div class="text-xs mt-1">Confiance : {{ Math.round((lightbox.identified_confidence || 0) * 100) }}% via {{ lightbox.identified_source }}</div>
</div>
<button class="mt-4 w-full text-text-muted hover:text-text text-sm" @click="lightbox = null">Fermer</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import axios from 'axios'
import { usePlantsStore } from '@/stores/plants'
import type { Plant } from '@/api/plants'
const plantsStore = usePlantsStore()
const showForm = ref(false)
const editPlant = ref<Plant | null>(null)
const selectedCat = ref('')
const openId = ref<number | null>(null)
const plantPhotos = ref<Media[]>([])
const loadingPhotos = ref(false)
const uploadTarget = ref<Plant | null>(null)
const linkTarget = ref<Plant | null>(null)
const unlinkPhotos = ref<Media[]>([])
const selectedLinkPhoto = ref<number | null>(null)
const lightbox = ref<Media | null>(null)
interface Media {
id: number; entity_type: string; entity_id: number
url: string; thumbnail_url?: string; titre?: string
identified_species?: string; identified_common?: string
identified_confidence?: number; identified_source?: string
}
const categories = [
{ val: '', label: 'Toutes' },
{ val: 'potager', label: '🥕 Potager' },
{ val: 'fleur', label: '🌸 Fleur' },
{ val: 'arbre', label: '🌳 Arbre' },
{ val: 'arbuste', label: '🌿 Arbuste' },
{ val: 'adventice', label: '🌾 Adventices' },
]
const form = reactive({
nom_commun: '', nom_botanique: '', variete: '', famille: '',
categorie: '', type_plante: '', besoin_eau: '', besoin_soleil: '',
espacement_cm: undefined as number | undefined,
temp_min_c: undefined as number | undefined,
plantation_mois: '', recolte_mois: '', notes: '',
})
const filteredPlants = computed(() =>
selectedCat.value ? plantsStore.plants.filter(p => p.categorie === selectedCat.value) : plantsStore.plants
)
const catClass = (cat: string) => ({
potager: 'bg-green/20 text-green',
fleur: 'bg-orange/20 text-orange',
arbre: 'bg-blue/20 text-blue',
arbuste: 'bg-yellow/20 text-yellow',
adventice: 'bg-red/20 text-red',
}[cat] || 'bg-bg text-text-muted')
const catLabel = (cat: string) => ({
potager: '🥕 Potager', fleur: '🌸 Fleur', arbre: '🌳 Arbre',
arbuste: '🌿 Arbuste', adventice: '🌾 Adventice',
}[cat] || cat)
async function toggleDetail(id: number) {
if (openId.value === id) { openId.value = null; return }
openId.value = id
await fetchPhotos(id)
}
async function fetchPhotos(plantId: number) {
loadingPhotos.value = true
try {
const { data } = await axios.get<Media[]>('/api/media', {
params: { entity_type: 'plante', entity_id: plantId }
})
plantPhotos.value = data
} finally {
loadingPhotos.value = false
}
}
function startEdit(p: Plant) {
editPlant.value = p
Object.assign(form, {
nom_commun: p.nom_commun || '', nom_botanique: (p as any).nom_botanique || '',
variete: p.variete || '', famille: p.famille || '',
categorie: p.categorie || '', type_plante: p.type_plante || '',
besoin_eau: p.besoin_eau || '', besoin_soleil: p.besoin_soleil || '',
espacement_cm: p.espacement_cm, temp_min_c: p.temp_min_c,
plantation_mois: p.plantation_mois || '', recolte_mois: p.recolte_mois || '',
notes: p.notes || '',
})
}
function closeForm() {
showForm.value = false
editPlant.value = null
Object.assign(form, {
nom_commun: '', nom_botanique: '', variete: '', famille: '', categorie: '',
type_plante: '', besoin_eau: '', besoin_soleil: '',
espacement_cm: undefined, temp_min_c: undefined,
plantation_mois: '', recolte_mois: '', notes: '',
})
}
async function submitPlant() {
if (editPlant.value) {
await axios.put(`/api/plants/${editPlant.value.id}`, { ...form })
await plantsStore.fetchAll()
} else {
await plantsStore.create({ ...form })
}
closeForm()
}
async function removePlant(id: number) {
if (confirm('Supprimer cette plante ?')) {
await plantsStore.remove(id)
if (openId.value === id) openId.value = null
}
}
function openUpload(p: Plant) { uploadTarget.value = p }
async function uploadPhoto(e: Event) {
const file = (e.target as HTMLInputElement).files?.[0]
if (!file || !uploadTarget.value) return
const fd = new FormData()
fd.append('file', file)
const { data: uploaded } = await axios.post('/api/upload', fd)
await axios.post('/api/media', {
entity_type: 'plante', entity_id: uploadTarget.value.id,
url: uploaded.url, thumbnail_url: uploaded.thumbnail_url,
})
uploadTarget.value = null
if (openId.value) await fetchPhotos(openId.value)
}
async function deletePhoto(m: Media) {
if (!confirm('Supprimer cette photo ?')) return
await axios.delete(`/api/media/${m.id}`)
if (openId.value) await fetchPhotos(openId.value)
}
async function openLinkPhoto(p: Plant) {
linkTarget.value = p
selectedLinkPhoto.value = null
const { data } = await axios.get<Media[]>('/api/media/all')
// Photos non liées à une plante (bibliothèque ou autres)
unlinkPhotos.value = data.filter(m => m.entity_type !== 'plante')
}
async function confirmLink() {
if (!selectedLinkPhoto.value || !linkTarget.value) return
await axios.patch(`/api/media/${selectedLinkPhoto.value}`, {
entity_type: 'plante', entity_id: linkTarget.value.id,
})
const pid = linkTarget.value.id
linkTarget.value = null
selectedLinkPhoto.value = null
if (openId.value === pid) await fetchPhotos(pid)
}
onMounted(() => plantsStore.fetchAll())
</script>

View File

@@ -0,0 +1,446 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { computed, onMounted, reactive, ref } from 'vue';
import { usePlantsStore } from '@/stores/plants';
const plantsStore = usePlantsStore();
const showForm = ref(false);
const selectedCat = ref('');
const categories = [
{ val: '', label: 'Toutes' },
{ val: 'potager', label: '🥕 Potager' },
{ val: 'fleur', label: '🌸 Fleur' },
{ val: 'arbre', label: '🌳 Arbre' },
{ val: 'arbuste', label: '🌿 Arbuste' },
];
const form = reactive({
nom_commun: '', variete: '', famille: '', categorie: '', type_plante: '',
espacement_cm: undefined,
temp_min_c: undefined,
notes: '',
});
const filteredPlants = computed(() => selectedCat.value ? plantsStore.plants.filter(p => p.categorie === selectedCat.value) : plantsStore.plants);
const catClass = (cat) => ({
potager: 'bg-green/20 text-green',
fleur: 'bg-orange/20 text-orange',
arbre: 'bg-blue/20 text-blue',
arbuste: 'bg-yellow/20 text-yellow',
}[cat] || 'bg-bg text-text-muted');
async function submitPlant() {
await plantsStore.create({ ...form });
Object.assign(form, { nom_commun: '', variete: '', famille: '', categorie: '', type_plante: '', espacement_cm: undefined, temp_min_c: undefined, notes: '' });
showForm.value = false;
}
async function removePlant(id) {
if (confirm('Supprimer cette plante ?'))
await plantsStore.remove(id);
}
onMounted(() => plantsStore.fetchAll());
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4 max-w-4xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center justify-between mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.showForm = true;
} },
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 mb-4 flex-wrap" },
});
for (const [cat] of __VLS_getVForSourceType((__VLS_ctx.categories))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.selectedCat = cat.val;
} },
key: (cat.val),
...{ class: (['px-3 py-1 rounded-full text-xs font-medium transition-colors',
__VLS_ctx.selectedCat === cat.val ? 'bg-green text-bg' : 'bg-bg-soft text-text-muted hover:text-text']) },
});
(cat.label);
}
if (__VLS_ctx.plantsStore.loading) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm" },
});
}
else if (!__VLS_ctx.filteredPlants.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm py-4" },
});
}
for (const [p] of __VLS_getVForSourceType((__VLS_ctx.filteredPlants))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (p.id),
...{ class: "bg-bg-soft rounded-lg p-4 mb-2 border border-bg-hard flex items-start justify-between gap-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center gap-2 mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text font-semibold" },
});
(p.nom_commun);
if (p.variete) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text-muted text-xs" },
});
(p.variete);
}
if (p.categorie) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: (['text-xs px-2 py-0.5 rounded-full font-medium', __VLS_ctx.catClass(p.categorie)]) },
});
(p.categorie);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs flex gap-3 flex-wrap" },
});
if (p.famille) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(p.famille);
}
if (p.espacement_cm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(p.espacement_cm);
}
if (p.besoin_eau) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(p.besoin_eau);
}
if (p.plantation_mois) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(p.plantation_mois);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.removePlant(p.id);
} },
...{ class: "text-red text-xs hover:underline shrink-0" },
});
}
if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
__VLS_ctx.showForm = false;
} },
...{ class: "fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg-hard rounded-xl p-6 w-full max-w-md border border-bg-soft" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text font-bold text-lg mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (__VLS_ctx.submitPlant) },
...{ class: "flex flex-col gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
placeholder: "Nom commun *",
required: true,
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" },
});
(__VLS_ctx.form.nom_commun);
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
placeholder: "Variété",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" },
});
(__VLS_ctx.form.variete);
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
placeholder: "Famille botanique",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" },
});
(__VLS_ctx.form.famille);
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.form.categorie),
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "potager",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "fleur",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "arbre",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "arbuste",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.form.type_plante),
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "legume",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "fruit",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "aromatique",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "fleur",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "number",
placeholder: "Espacement (cm)",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm flex-1 outline-none focus:border-green" },
});
(__VLS_ctx.form.espacement_cm);
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "number",
placeholder: "T° min (°C)",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm flex-1 outline-none focus:border-green" },
});
(__VLS_ctx.form.temp_min_c);
__VLS_asFunctionalElement(__VLS_intrinsicElements.textarea)({
value: (__VLS_ctx.form.notes),
placeholder: "Notes...",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green resize-none h-20" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 justify-end" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
__VLS_ctx.showForm = false;
} },
type: "button",
...{ class: "px-4 py-2 text-text-muted hover:text-text text-sm" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
type: "submit",
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-4xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-start']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-4']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
/** @type {__VLS_StyleScopedClasses['text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['shrink-0']} */ ;
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
/** @type {__VLS_StyleScopedClasses['inset-0']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/60']} */ ;
/** @type {__VLS_StyleScopedClasses['z-50']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['p-6']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-md']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-col']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['resize-none']} */ ;
/** @type {__VLS_StyleScopedClasses['h-20']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-end']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {
plantsStore: plantsStore,
showForm: showForm,
selectedCat: selectedCat,
categories: categories,
form: form,
filteredPlants: filteredPlants,
catClass: catClass,
submitPlant: submitPlant,
removePlant: removePlant,
};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */

View File

@@ -4,15 +4,23 @@ const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4" },
...{ class: "p-4 max-w-2xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green" },
...{ class: "text-2xl font-bold text-green mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-sm" },
});
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {

View File

@@ -1,42 +1,11 @@
<template>
<div class="p-4 max-w-2xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold text-green">Tâches</h1>
<button
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
@click="showForm = !showForm"
>+ Nouvelle</button>
<h1 class="text-2xl font-bold text-green"> Tâches</h1>
<button class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
@click="openCreate">+ Nouvelle</button>
</div>
<form v-if="showForm" class="bg-bg-soft rounded-lg p-4 mb-6 border border-green/30" @submit.prevent="submit">
<div class="grid gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Titre *</label>
<input v-model="form.titre" required
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" />
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Priorité</label>
<select v-model="form.priorite" class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm">
<option value="basse">Basse</option>
<option value="normale">Normale</option>
<option value="haute">Haute</option>
</select>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Échéance</label>
<input v-model="form.echeance" type="date"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" />
</div>
</div>
</div>
<div class="flex gap-2 mt-4">
<button type="submit" class="bg-green text-bg px-4 py-2 rounded text-sm font-semibold">Créer</button>
<button type="button" class="text-text-muted text-sm px-4 py-2 hover:text-text" @click="showForm = false">Annuler</button>
</div>
</form>
<div v-for="[groupe, label] in groupes" :key="groupe" class="mb-6">
<h2 class="text-text-muted text-xs uppercase tracking-widest mb-2">{{ label }}</h2>
<div v-if="!byStatut(groupe).length" class="text-text-muted text-xs pl-2 mb-2"></div>
@@ -47,26 +16,80 @@
'text-yellow': t.priorite === 'normale',
'text-text-muted': t.priorite === 'basse'
}"></span>
<span class="text-text text-sm flex-1">{{ t.titre }}</span>
<div class="flex gap-1 items-center">
<div class="flex-1 min-w-0">
<div class="text-text text-sm">{{ t.titre }}</div>
<div v-if="t.echeance" class="text-text-muted text-xs">📅 {{ fmtDate(t.echeance) }}</div>
</div>
<div class="flex gap-1 items-center shrink-0">
<button v-if="t.statut === 'a_faire'" class="text-xs text-blue hover:underline"
@click="store.updateStatut(t.id!, 'en_cours')"> En cours</button>
<button v-if="t.statut === 'en_cours'" class="text-xs text-green hover:underline"
@click="store.updateStatut(t.id!, 'fait')"> Fait</button>
<button class="text-xs text-text-muted hover:text-red ml-2" @click="store.remove(t.id!)"></button>
<button @click="startEdit(t)" class="text-xs text-yellow hover:underline ml-2">Édit.</button>
<button class="text-xs text-text-muted hover:text-red ml-1" @click="store.remove(t.id!)"></button>
</div>
</div>
</div>
<!-- Modal création / édition -->
<div v-if="showForm" class="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4" @click.self="closeForm">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-md border border-bg-soft">
<h2 class="text-text font-bold text-lg mb-4">{{ editId ? 'Modifier la tâche' : 'Nouvelle tâche' }}</h2>
<form @submit.prevent="submit" class="grid gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Titre *</label>
<input v-model="form.titre" required
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Description</label>
<textarea v-model="form.description" rows="2"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none resize-none" />
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Priorité</label>
<select v-model="form.priorite" class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm">
<option value="basse">Basse</option>
<option value="normale">Normale</option>
<option value="haute">Haute</option>
</select>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Statut</label>
<select v-model="form.statut" class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm">
<option value="a_faire">À faire</option>
<option value="en_cours">En cours</option>
<option value="fait">Terminé</option>
</select>
</div>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Échéance</label>
<input v-model="form.echeance" type="date"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" />
</div>
<div class="flex gap-2 mt-2">
<button type="submit" class="bg-green text-bg px-4 py-2 rounded text-sm font-semibold">
{{ editId ? 'Enregistrer' : 'Créer' }}
</button>
<button type="button" class="text-text-muted text-sm px-4 py-2 hover:text-text" @click="closeForm">Annuler</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { useTasksStore } from '@/stores/tasks'
import type { Task } from '@/api/tasks'
const store = useTasksStore()
const showForm = ref(false)
const form = reactive({ titre: '', priorite: 'normale', statut: 'a_faire', echeance: '' })
const editId = ref<number | null>(null)
const form = reactive({ titre: '', description: '', priorite: 'normale', statut: 'a_faire', echeance: '' })
const groupes: [string, string][] = [
['a_faire', 'À faire'],
@@ -76,11 +99,36 @@ const groupes: [string, string][] = [
const byStatut = (s: string) => store.tasks.filter(t => t.statut === s)
function fmtDate(s: string) {
return new Date(s + 'T12:00:00').toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' })
}
function openCreate() {
editId.value = null
Object.assign(form, { titre: '', description: '', priorite: 'normale', statut: 'a_faire', echeance: '' })
showForm.value = true
}
function startEdit(t: Task) {
editId.value = t.id!
Object.assign(form, {
titre: t.titre, description: (t as any).description || '',
priorite: t.priorite, statut: t.statut,
echeance: t.echeance ? t.echeance.slice(0, 10) : '',
})
showForm.value = true
}
function closeForm() { showForm.value = false; editId.value = null }
onMounted(() => store.fetchAll())
async function submit() {
await store.create({ ...form })
showForm.value = false
Object.assign(form, { titre: '', priorite: 'normale', statut: 'a_faire', echeance: '' })
if (editId.value) {
await store.update(editId.value, { ...form })
} else {
await store.create({ ...form })
}
closeForm()
}
</script>

View File

@@ -1,22 +1,297 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { onMounted, reactive, ref } from 'vue';
import { useTasksStore } from '@/stores/tasks';
const store = useTasksStore();
const showForm = ref(false);
const form = reactive({ titre: '', priorite: 'normale', statut: 'a_faire', echeance: '' });
const groupes = [
['a_faire', 'À faire'],
['en_cours', 'En cours'],
['fait', 'Terminé'],
];
const byStatut = (s) => store.tasks.filter(t => t.statut === s);
onMounted(() => store.fetchAll());
async function submit() {
await store.create({ ...form });
showForm.value = false;
Object.assign(form, { titre: '', priorite: 'normale', statut: 'a_faire', echeance: '' });
}
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4" },
...{ class: "p-4 max-w-2xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center justify-between mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.showForm = !__VLS_ctx.showForm;
} },
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (__VLS_ctx.submit) },
...{ class: "bg-bg-soft rounded-lg p-4 mb-6 border border-green/30" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
required: true,
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" },
});
(__VLS_ctx.form.titre);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-2 gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.form.priorite),
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "basse",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "normale",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "haute",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "date",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" },
});
(__VLS_ctx.form.echeance);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 mt-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
type: "submit",
...{ class: "bg-green text-bg px-4 py-2 rounded text-sm font-semibold" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
__VLS_ctx.showForm = false;
} },
type: "button",
...{ class: "text-text-muted text-sm px-4 py-2 hover:text-text" },
});
}
for (const [[groupe, label]] of __VLS_getVForSourceType((__VLS_ctx.groupes))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (groupe),
...{ class: "mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text-muted text-xs uppercase tracking-widest mb-2" },
});
(label);
if (!__VLS_ctx.byStatut(groupe).length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs pl-2 mb-2" },
});
}
for (const [t] of __VLS_getVForSourceType((__VLS_ctx.byStatut(groupe)))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (t.id),
...{ class: "bg-bg-soft rounded-lg p-3 mb-2 flex items-center gap-3 border border-bg-hard" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: ({
'text-red': t.priorite === 'haute',
'text-yellow': t.priorite === 'normale',
'text-text-muted': t.priorite === 'basse'
}) },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text text-sm flex-1" },
});
(t.titre);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-1 items-center" },
});
if (t.statut === 'a_faire') {
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(t.statut === 'a_faire'))
return;
__VLS_ctx.store.updateStatut(t.id, 'en_cours');
} },
...{ class: "text-xs text-blue hover:underline" },
});
}
if (t.statut === 'en_cours') {
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(t.statut === 'en_cours'))
return;
__VLS_ctx.store.updateStatut(t.id, 'fait');
} },
...{ class: "text-xs text-green hover:underline" },
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.store.remove(t.id);
} },
...{ class: "text-xs text-text-muted hover:text-red ml-2" },
});
}
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-green/30']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-4']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['uppercase']} */ ;
/** @type {__VLS_StyleScopedClasses['tracking-widest']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['pl-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-3']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-blue']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['ml-2']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
return {
store: store,
showForm: showForm,
form: form,
groupes: groupes,
byStatut: byStatut,
submit: submit,
};
},
});
export default (await import('vue')).defineComponent({

View File

@@ -1,22 +1,322 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { onMounted, reactive, ref } from 'vue';
import { useVarietiesStore } from '@/stores/varieties';
const store = useVarietiesStore();
const showForm = ref(false);
const form = reactive({ nom_commun: '', variete: '', famille: '', besoin_eau: '' });
onMounted(() => store.fetchAll());
async function submit() {
await store.create({ ...form });
showForm.value = false;
Object.assign(form, { nom_commun: '', variete: '', famille: '', besoin_eau: '' });
}
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4" },
...{ class: "p-4 max-w-2xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center justify-between mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.showForm = !__VLS_ctx.showForm;
} },
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (__VLS_ctx.submit) },
...{ class: "bg-bg-soft rounded-lg p-4 mb-6 border border-green/30" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
required: true,
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" },
});
(__VLS_ctx.form.nom_commun);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-2 gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" },
});
(__VLS_ctx.form.variete);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" },
});
(__VLS_ctx.form.famille);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.form.besoin_eau),
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "faible",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "moyen",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "fort",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 mt-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
type: "submit",
...{ class: "bg-green text-bg px-4 py-2 rounded text-sm font-semibold" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
__VLS_ctx.showForm = false;
} },
type: "button",
...{ class: "text-text-muted text-sm px-4 py-2 hover:text-text" },
});
}
if (__VLS_ctx.store.loading) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm" },
});
}
for (const [v] of __VLS_getVForSourceType((__VLS_ctx.store.varieties))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (v.id),
...{ class: "bg-bg-soft rounded-lg p-4 mb-2 border border-bg-hard flex items-start gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text font-medium" },
});
(v.nom_commun);
if (v.variete) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text-muted text-xs" },
});
(v.variete);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs mt-1" },
});
(v.famille);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 mt-2 flex-wrap" },
});
if (v.besoin_eau) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-xs px-2 py-0.5 bg-bg rounded text-blue" },
});
(v.besoin_eau);
}
if (v.espacement_cm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-xs px-2 py-0.5 bg-bg rounded text-text-muted" },
});
(v.espacement_cm);
}
if (v.plantation_mois) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-xs px-2 py-0.5 bg-bg rounded text-green" },
});
(v.plantation_mois);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.store.remove(v.id);
} },
...{ class: "text-text-muted hover:text-red text-sm px-2 py-1 rounded hover:bg-bg transition-colors" },
});
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-green/30']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-4']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-start']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-0.5']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['text-blue']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-0.5']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-0.5']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
return {
store: store,
showForm: showForm,
form: form,
submit: submit,
};
},
});
export default (await import('vue')).defineComponent({

View File

@@ -9,8 +9,8 @@ export default defineConfig({
},
server: {
proxy: {
'/api': 'http://localhost:8000',
'/uploads': 'http://localhost:8000',
'/api': 'http://localhost:8060',
'/uploads': 'http://localhost:8060',
},
},
})