maj via codex

This commit is contained in:
2026-02-22 18:34:50 +01:00
parent 20af00d653
commit 55387f4b0e
90 changed files with 9902 additions and 1251 deletions

View File

@@ -1,4 +1,14 @@
<template>
<div
v-if="debugMode"
class="fixed top-2 right-2 z-[70] bg-bg-hard/95 border border-bg-soft rounded-lg px-3 py-1.5 text-[11px] text-text-muted shadow-lg"
>
<span class="text-aqua font-semibold mr-2">DEBUG</span>
<span class="mr-2">CPU {{ debugCpuLabel }}</span>
<span class="mr-2">RAM {{ debugMemLabel }}</span>
<span>Disk {{ debugDiskLabel }}</span>
</div>
<!-- Mobile: header + drawer -->
<AppHeader class="lg:hidden" @toggle-drawer="drawerOpen = !drawerOpen" />
<AppDrawer :open="drawerOpen" @close="drawerOpen = false" />
@@ -35,12 +45,132 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { RouterLink, RouterView } from 'vue-router'
import AppHeader from '@/components/AppHeader.vue'
import AppDrawer from '@/components/AppDrawer.vue'
import { meteoApi } from '@/api/meteo'
import { settingsApi, type DebugSystemStats } from '@/api/settings'
const drawerOpen = ref(false)
const debugMode = ref(localStorage.getItem('debug_mode') === '1')
const debugStats = ref<DebugSystemStats | null>(null)
let debugTimer: number | null = null
function prefetchMeteoInBackground() {
// Préchargement du chunk route + données pour accélérer l'ouverture de /meteo
void import('@/views/CalendrierView.vue')
void meteoApi.preloadForMeteoView()
}
function toBool(value: unknown): boolean {
if (typeof value === 'boolean') return value
const s = String(value ?? '').toLowerCase().trim()
return s === '1' || s === 'true' || s === 'yes' || s === 'on'
}
function formatBytes(value?: number | null): string {
if (value == null || Number.isNaN(value)) return '—'
const units = ['B', 'KB', 'MB', 'GB', 'TB']
let n = value
let i = 0
while (n >= 1024 && i < units.length - 1) {
n /= 1024
i += 1
}
return `${n.toFixed(n >= 100 ? 0 : n >= 10 ? 1 : 2)}${units[i]}`
}
const debugCpuLabel = computed(() => {
const pct = debugStats.value?.cpu?.used_pct
return pct != null ? `${pct.toFixed(1)}%` : '—'
})
const debugMemLabel = computed(() => {
const used = debugStats.value?.memory?.used_bytes
const pct = debugStats.value?.memory?.used_pct
if (used == null) return '—'
return pct != null ? `${formatBytes(used)} (${pct.toFixed(1)}%)` : formatBytes(used)
})
const debugDiskLabel = computed(() => {
const used = debugStats.value?.disk?.used_bytes
const pct = debugStats.value?.disk?.used_pct
if (used == null) return '—'
return pct != null ? `${formatBytes(used)} (${pct.toFixed(1)}%)` : formatBytes(used)
})
async function fetchDebugStats() {
try {
debugStats.value = await settingsApi.getDebugSystemStats()
} catch {
debugStats.value = null
}
}
function stopDebugPolling() {
if (debugTimer != null) {
window.clearInterval(debugTimer)
debugTimer = null
}
}
function startDebugPolling() {
stopDebugPolling()
void fetchDebugStats()
debugTimer = window.setInterval(() => {
void fetchDebugStats()
}, 10000)
}
async function loadDebugModeFromApi() {
try {
const data = await settingsApi.get()
debugMode.value = toBool(data.debug_mode)
localStorage.setItem('debug_mode', debugMode.value ? '1' : '0')
} catch {
// On garde la valeur locale.
}
}
function handleSettingsUpdated(event: Event) {
const ce = event as CustomEvent<{ debug_mode?: boolean | string }>
if (!ce.detail || ce.detail.debug_mode == null) return
debugMode.value = toBool(ce.detail.debug_mode)
localStorage.setItem('debug_mode', debugMode.value ? '1' : '0')
}
function handleStorage(event: StorageEvent) {
if (event.key !== 'debug_mode') return
debugMode.value = event.newValue === '1'
}
onMounted(() => {
const ric = (window as Window & { requestIdleCallback?: (cb: () => void, opts?: { timeout: number }) => number }).requestIdleCallback
if (ric) {
ric(() => prefetchMeteoInBackground(), { timeout: 1500 })
} else {
window.setTimeout(prefetchMeteoInBackground, 500)
}
void loadDebugModeFromApi()
window.addEventListener('settings-updated', handleSettingsUpdated as EventListener)
window.addEventListener('storage', handleStorage)
})
watch(debugMode, (enabled) => {
if (enabled) startDebugPolling()
else {
stopDebugPolling()
debugStats.value = null
}
}, { immediate: true })
onBeforeUnmount(() => {
stopDebugPolling()
window.removeEventListener('settings-updated', handleSettingsUpdated as EventListener)
window.removeEventListener('storage', handleStorage)
})
const links = [
{ to: '/', label: 'Dashboard', icon: '🏠' },
@@ -51,7 +181,8 @@ const links = [
{ to: '/plantations', label: 'Plantations', icon: '🥕' },
{ to: '/taches', label: 'Tâches', icon: '✅' },
{ to: '/planning', label: 'Planning', icon: '📆' },
{ to: '/calendrier', label: 'Calendrier', icon: '🌙' },
{ to: '/meteo', label: 'Météo', icon: '🌦️' },
{ to: '/astuces', label: 'Astuces', icon: '💡' },
{ to: '/reglages', label: 'Réglages', icon: '⚙️' },
]
</script>

View File

@@ -1,24 +1,158 @@
/// <reference types="../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { ref } from 'vue';
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { RouterLink, RouterView } from 'vue-router';
import AppHeader from '@/components/AppHeader.vue';
import AppDrawer from '@/components/AppDrawer.vue';
import { meteoApi } from '@/api/meteo';
import { settingsApi } from '@/api/settings';
const drawerOpen = ref(false);
const debugMode = ref(localStorage.getItem('debug_mode') === '1');
const debugStats = ref(null);
let debugTimer = null;
function prefetchMeteoInBackground() {
// Préchargement du chunk route + données pour accélérer l'ouverture de /meteo
void import('@/views/CalendrierView.vue');
void meteoApi.preloadForMeteoView();
}
function toBool(value) {
if (typeof value === 'boolean')
return value;
const s = String(value ?? '').toLowerCase().trim();
return s === '1' || s === 'true' || s === 'yes' || s === 'on';
}
function formatBytes(value) {
if (value == null || Number.isNaN(value))
return '—';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let n = value;
let i = 0;
while (n >= 1024 && i < units.length - 1) {
n /= 1024;
i += 1;
}
return `${n.toFixed(n >= 100 ? 0 : n >= 10 ? 1 : 2)}${units[i]}`;
}
const debugCpuLabel = computed(() => {
const pct = debugStats.value?.cpu?.used_pct;
return pct != null ? `${pct.toFixed(1)}%` : '—';
});
const debugMemLabel = computed(() => {
const used = debugStats.value?.memory?.used_bytes;
const pct = debugStats.value?.memory?.used_pct;
if (used == null)
return '—';
return pct != null ? `${formatBytes(used)} (${pct.toFixed(1)}%)` : formatBytes(used);
});
const debugDiskLabel = computed(() => {
const used = debugStats.value?.disk?.used_bytes;
const pct = debugStats.value?.disk?.used_pct;
if (used == null)
return '—';
return pct != null ? `${formatBytes(used)} (${pct.toFixed(1)}%)` : formatBytes(used);
});
async function fetchDebugStats() {
try {
debugStats.value = await settingsApi.getDebugSystemStats();
}
catch {
debugStats.value = null;
}
}
function stopDebugPolling() {
if (debugTimer != null) {
window.clearInterval(debugTimer);
debugTimer = null;
}
}
function startDebugPolling() {
stopDebugPolling();
void fetchDebugStats();
debugTimer = window.setInterval(() => {
void fetchDebugStats();
}, 10000);
}
async function loadDebugModeFromApi() {
try {
const data = await settingsApi.get();
debugMode.value = toBool(data.debug_mode);
localStorage.setItem('debug_mode', debugMode.value ? '1' : '0');
}
catch {
// On garde la valeur locale.
}
}
function handleSettingsUpdated(event) {
const ce = event;
if (!ce.detail || ce.detail.debug_mode == null)
return;
debugMode.value = toBool(ce.detail.debug_mode);
localStorage.setItem('debug_mode', debugMode.value ? '1' : '0');
}
function handleStorage(event) {
if (event.key !== 'debug_mode')
return;
debugMode.value = event.newValue === '1';
}
onMounted(() => {
const ric = window.requestIdleCallback;
if (ric) {
ric(() => prefetchMeteoInBackground(), { timeout: 1500 });
}
else {
window.setTimeout(prefetchMeteoInBackground, 500);
}
void loadDebugModeFromApi();
window.addEventListener('settings-updated', handleSettingsUpdated);
window.addEventListener('storage', handleStorage);
});
watch(debugMode, (enabled) => {
if (enabled)
startDebugPolling();
else {
stopDebugPolling();
debugStats.value = null;
}
}, { immediate: true });
onBeforeUnmount(() => {
stopDebugPolling();
window.removeEventListener('settings-updated', handleSettingsUpdated);
window.removeEventListener('storage', handleStorage);
});
const links = [
{ to: '/', label: 'Dashboard', icon: '🏠' },
{ to: '/jardins', label: 'Jardins', icon: '🪴' },
{ to: '/plantes', label: 'Plantes', icon: '🌱' },
{ to: '/bibliotheque', label: 'Bibliothèque', 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: '/meteo', label: 'Météo', icon: '🌦️' },
{ to: '/astuces', label: 'Astuces', icon: '💡' },
{ to: '/reglages', label: 'Réglages', icon: '⚙️' },
];
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
if (__VLS_ctx.debugMode) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "fixed top-2 right-2 z-[70] bg-bg-hard/95 border border-bg-soft rounded-lg px-3 py-1.5 text-[11px] text-text-muted shadow-lg" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-aqua font-semibold mr-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "mr-2" },
});
(__VLS_ctx.debugCpuLabel);
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "mr-2" },
});
(__VLS_ctx.debugMemLabel);
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(__VLS_ctx.debugDiskLabel);
}
/** @type {[typeof AppHeader, ]} */ ;
// @ts-ignore
const __VLS_0 = __VLS_asFunctionalComponent(AppHeader, new AppHeader({
@@ -119,6 +253,24 @@ const __VLS_22 = {}.RouterView;
// @ts-ignore
const __VLS_23 = __VLS_asFunctionalComponent(__VLS_22, new __VLS_22({}));
const __VLS_24 = __VLS_23({}, ...__VLS_functionalComponentArgsRest(__VLS_23));
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
/** @type {__VLS_StyleScopedClasses['top-2']} */ ;
/** @type {__VLS_StyleScopedClasses['right-2']} */ ;
/** @type {__VLS_StyleScopedClasses['z-[70]']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-hard/95']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1.5']} */ ;
/** @type {__VLS_StyleScopedClasses['text-[11px]']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['shadow-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-aqua']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['mr-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mr-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mr-2']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:flex']} */ ;
/** @type {__VLS_StyleScopedClasses['hidden']} */ ;
@@ -183,6 +335,10 @@ const __VLS_self = (await import('vue')).defineComponent({
AppHeader: AppHeader,
AppDrawer: AppDrawer,
drawerOpen: drawerOpen,
debugMode: debugMode,
debugCpuLabel: debugCpuLabel,
debugMemLabel: debugMemLabel,
debugDiskLabel: debugDiskLabel,
links: links,
};
},

View File

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

View File

@@ -0,0 +1,31 @@
import client from './client'
export interface Astuce {
id?: number
titre: string
contenu: string
categorie?: string
tags?: string
mois?: string
photos?: string
videos?: string
source?: string
created_at?: string
}
export const astucesApi = {
list: (params?: { categorie?: string; mois?: number; tag?: string }) =>
client.get<Astuce[]>('/api/astuces', { params }).then(r => r.data),
get: (id: number) =>
client.get<Astuce>(`/api/astuces/${id}`).then(r => r.data),
create: (a: Omit<Astuce, 'id' | 'created_at'>) =>
client.post<Astuce>('/api/astuces', a).then(r => r.data),
update: (id: number, a: Partial<Astuce>) =>
client.put<Astuce>(`/api/astuces/${id}`, a).then(r => r.data),
remove: (id: number) =>
client.delete(`/api/astuces/${id}`),
}

View File

@@ -4,6 +4,11 @@ export const gardensApi = {
get: (id) => client.get(`/api/gardens/${id}`).then(r => r.data),
create: (g) => client.post('/api/gardens', g).then(r => r.data),
update: (id, g) => client.put(`/api/gardens/${id}`, g).then(r => r.data),
uploadPhoto: (id, file) => {
const fd = new FormData();
fd.append('file', file);
return client.post(`/api/gardens/${id}/photo`, fd).then(r => r.data);
},
delete: (id) => client.delete(`/api/gardens/${id}`),
cells: (id) => client.get(`/api/gardens/${id}/cells`).then(r => r.data),
measurements: (id) => client.get(`/api/gardens/${id}/measurements`).then(r => r.data),

View File

@@ -5,8 +5,16 @@ export interface Garden {
nom: string
description?: string
type: string
longueur_m?: number
largeur_m?: number
surface_m2?: number
carre_potager?: boolean
carre_x_cm?: number
carre_y_cm?: number
photo_parcelle?: string
latitude?: number
longitude?: number
altitude?: number
adresse?: string
exposition?: string
ombre?: string
@@ -41,6 +49,11 @@ export const gardensApi = {
get: (id: number) => client.get<Garden>(`/api/gardens/${id}`).then(r => r.data),
create: (g: Partial<Garden>) => client.post<Garden>('/api/gardens', g).then(r => r.data),
update: (id: number, g: Partial<Garden>) => client.put<Garden>(`/api/gardens/${id}`, g).then(r => r.data),
uploadPhoto: (id: number, file: File) => {
const fd = new FormData()
fd.append('file', file)
return client.post<Garden>(`/api/gardens/${id}/photo`, fd).then(r => r.data)
},
delete: (id: number) => client.delete(`/api/gardens/${id}`),
cells: (id: number) => client.get<GardenCell[]>(`/api/gardens/${id}/cells`).then(r => r.data),
measurements: (id: number) => client.get<Measurement[]>(`/api/gardens/${id}/measurements`).then(r => r.data),

View File

@@ -8,6 +8,7 @@ export interface LunarDay {
montante_descendante: string
signe: string
type_jour: string
saint_du_jour?: string
perigee: boolean
apogee: boolean
noeud_lunaire: boolean

View File

@@ -1,4 +1,60 @@
import client from './client';
const CACHE_TTL_MS = 5 * 60 * 1000;
const cache = new Map();
const inflight = new Map();
function todayIso() {
const d = new Date();
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}
function cacheKeyTableau(params) {
const center = params?.center_date || '';
const span = params?.span ?? '';
return `tableau:${center}:${span}`;
}
function getCached(key) {
const entry = cache.get(key);
if (!entry)
return null;
if (Date.now() > entry.expires_at) {
cache.delete(key);
return null;
}
return entry.value;
}
function setCached(key, value) {
cache.set(key, { value, expires_at: Date.now() + CACHE_TTL_MS });
}
function fetchWithCache(key, loader) {
const cached = getCached(key);
if (cached != null)
return Promise.resolve(cached);
const pending = inflight.get(key);
if (pending)
return pending;
const p = loader()
.then((value) => {
setCached(key, value);
return value;
})
.finally(() => {
inflight.delete(key);
});
inflight.set(key, p);
return p;
}
export const meteoApi = {
getForecast: (days = 14) => client.get('/api/meteo', { params: { days } }).then(r => r.data),
getForecast: (days = 14) => fetchWithCache(`forecast:${days}`, () => client.get('/api/meteo', { params: { days } }).then(r => r.data)),
getTableau: (params) => fetchWithCache(cacheKeyTableau(params), () => client.get('/api/meteo/tableau', { params }).then(r => r.data)),
getStationCurrent: () => fetchWithCache('station-current', () => client.get('/api/meteo/station/current').then(r => r.data)),
getPrevisions: (days = 7) => fetchWithCache(`previsions:${days}`, () => client.get('/api/meteo/previsions', { params: { days } }).then(r => r.data)),
preloadForMeteoView: (params) => Promise.all([
meteoApi.getTableau(params ?? { center_date: todayIso(), span: 15 }),
meteoApi.getStationCurrent(),
meteoApi.getPrevisions(7),
]).then(() => undefined),
clearCache: () => {
cache.clear();
inflight.clear();
},
refresh: () => client.post('/api/meteo/refresh').then(r => r.data),
};

View File

@@ -11,6 +11,134 @@ export interface MeteoDay {
icone: string
}
export const meteoApi = {
getForecast: (days = 14) => client.get<{ days: MeteoDay[] }>('/api/meteo', { params: { days } }).then(r => r.data),
export interface StationCurrent {
temp_ext?: number
humidite?: number
pression?: number
pluie_mm?: number
vent_kmh?: number
vent_dir?: string
uv?: number
solaire?: number
date_heure?: string
}
export interface StationDay {
t_min?: number
t_max?: number
pluie_mm?: number
vent_kmh?: number
humidite?: number
}
export interface OpenMeteoDay {
date?: string
t_min?: number
t_max?: number
pluie_mm?: number
vent_kmh?: number
wmo?: number
label?: string
humidite_moy?: number
sol_0cm?: number
etp_mm?: number
}
export interface TableauRow {
date: string
type: 'passe' | 'aujourd_hui' | 'futur'
station: StationDay | StationCurrent | null
open_meteo: OpenMeteoDay | null
}
const CACHE_TTL_MS = 5 * 60 * 1000
type CacheEntry<T> = {
value: T
expires_at: number
}
const cache = new Map<string, CacheEntry<unknown>>()
const inflight = new Map<string, Promise<unknown>>()
function todayIso(): string {
const d = new Date()
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}
function cacheKeyTableau(params?: { center_date?: string; span?: number }): string {
const center = params?.center_date || ''
const span = params?.span ?? ''
return `tableau:${center}:${span}`
}
function getCached<T>(key: string): T | null {
const entry = cache.get(key)
if (!entry) return null
if (Date.now() > entry.expires_at) {
cache.delete(key)
return null
}
return entry.value as T
}
function setCached<T>(key: string, value: T): void {
cache.set(key, { value, expires_at: Date.now() + CACHE_TTL_MS })
}
function fetchWithCache<T>(key: string, loader: () => Promise<T>): Promise<T> {
const cached = getCached<T>(key)
if (cached != null) return Promise.resolve(cached)
const pending = inflight.get(key)
if (pending) return pending as Promise<T>
const p = loader()
.then((value) => {
setCached(key, value)
return value
})
.finally(() => {
inflight.delete(key)
})
inflight.set(key, p as Promise<unknown>)
return p
}
export const meteoApi = {
getForecast: (days = 14) =>
fetchWithCache(`forecast:${days}`, () =>
client.get<{ days: MeteoDay[] }>('/api/meteo', { params: { days } }).then(r => r.data),
),
getTableau: (params?: { center_date?: string; span?: number }) =>
fetchWithCache(cacheKeyTableau(params), () =>
client.get<{ rows: TableauRow[] }>('/api/meteo/tableau', { params }).then(r => r.data),
),
getStationCurrent: () =>
fetchWithCache('station-current', () =>
client.get<StationCurrent | null>('/api/meteo/station/current').then(r => r.data),
),
getPrevisions: (days = 7) =>
fetchWithCache(`previsions:${days}`, () =>
client.get<{ days: OpenMeteoDay[] }>('/api/meteo/previsions', { params: { days } }).then(r => r.data),
),
preloadForMeteoView: (params?: { center_date?: string; span?: number }) =>
Promise.all([
meteoApi.getTableau(params ?? { center_date: todayIso(), span: 15 }),
meteoApi.getStationCurrent(),
meteoApi.getPrevisions(7),
]).then(() => undefined),
clearCache: () => {
cache.clear()
inflight.clear()
},
refresh: () =>
client.post('/api/meteo/refresh').then(r => r.data),
}

View File

@@ -8,6 +8,10 @@ export interface Planting {
date_plantation?: string
quantite: number
statut: string
boutique_nom?: string
boutique_url?: string
tarif_achat?: number
date_achat?: string
notes?: string
}

View File

@@ -0,0 +1,6 @@
import client from './client';
export const settingsApi = {
get: () => client.get('/api/settings').then(r => r.data),
update: (settings) => client.put('/api/settings', settings).then(r => r.data),
getDebugSystemStats: () => client.get('/api/settings/debug/system').then(r => r.data),
};

View File

@@ -0,0 +1,33 @@
import client from './client'
export type SettingsMap = Record<string, string>
export interface DebugSystemStats {
source: string
cpu: {
usage_usec_total?: number | null
quota_cores?: number | null
used_pct?: number | null
}
memory: {
used_bytes?: number | null
limit_bytes?: number | null
used_pct?: number | null
}
disk: {
path?: string
total_bytes?: number | null
used_bytes?: number | null
free_bytes?: number | null
used_pct?: number | null
uploads_bytes?: number | null
}
}
export const settingsApi = {
get: () => client.get<SettingsMap>('/api/settings').then(r => r.data),
update: (settings: Record<string, string | number | boolean>) =>
client.put<{ ok: boolean }>('/api/settings', settings).then(r => r.data),
getDebugSystemStats: () =>
client.get<DebugSystemStats>('/api/settings/debug/system').then(r => r.data),
}

View File

@@ -7,6 +7,9 @@ export interface Task {
garden_id?: number
priorite: string
echeance?: string
recurrence?: string | null
frequence_jours?: number | null
date_prochaine?: string | null
statut: string
}

View File

@@ -6,6 +6,11 @@ export interface Tool {
description?: string
categorie?: string
photo_url?: string
video_url?: string
notice_fichier_url?: string
boutique_nom?: string
boutique_url?: string
prix_achat?: number
}
export const toolsApi = {

View File

@@ -1,6 +1,6 @@
<template>
<Transition name="slide">
<div v-if="open" class="fixed inset-0 z-40 flex md:hidden" @click.self="$emit('close')">
<div v-if="open" class="fixed inset-0 z-40 flex lg:hidden" @click.self="$emit('close')">
<nav class="bg-bg-hard w-64 h-full p-6 flex flex-col gap-1 border-r border-bg-soft shadow-2xl">
<span class="text-green font-bold text-xl mb-6">🌿 Jardin</span>
<RouterLink
@@ -27,7 +27,8 @@ const links = [
{ to: '/plantations', label: 'Plantations' },
{ to: '/taches', label: 'Tâches' },
{ to: '/planning', label: 'Planning' },
{ to: '/calendrier', label: 'Calendrier' },
{ to: '/meteo', label: 'Météo' },
{ to: '/astuces', label: 'Astuces' },
{ to: '/reglages', label: 'Réglages' },
]
</script>

View File

@@ -5,11 +5,13 @@ const links = [
{ to: '/', label: 'Dashboard' },
{ to: '/jardins', label: 'Jardins' },
{ to: '/plantes', label: 'Plantes' },
{ to: '/bibliotheque', label: '📷 Bibliothèque' },
{ to: '/outils', label: 'Outils' },
{ to: '/plantations', label: 'Plantations' },
{ to: '/taches', label: 'Tâches' },
{ to: '/planning', label: 'Planning' },
{ to: '/calendrier', label: 'Calendrier' },
{ to: '/meteo', label: 'Météo' },
{ to: '/astuces', label: 'Astuces' },
{ to: '/reglages', label: 'Réglages' },
];
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
@@ -35,7 +37,7 @@ if (__VLS_ctx.open) {
return;
__VLS_ctx.$emit('close');
} },
...{ class: "fixed inset-0 z-40 flex md:hidden" },
...{ class: "fixed inset-0 z-40 flex lg:hidden" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.nav, __VLS_intrinsicElements.nav)({
...{ class: "bg-bg-hard w-64 h-full p-6 flex flex-col gap-1 border-r border-bg-soft shadow-2xl" },
@@ -81,7 +83,7 @@ var __VLS_3;
/** @type {__VLS_StyleScopedClasses['inset-0']} */ ;
/** @type {__VLS_StyleScopedClasses['z-40']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['md:hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['w-64']} */ ;
/** @type {__VLS_StyleScopedClasses['h-full']} */ ;

View File

@@ -0,0 +1,240 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { ref, onMounted } from 'vue';
import axios from 'axios';
const props = defineProps();
const medias = ref([]);
const loading = ref(false);
const lightbox = ref(null);
async function fetchMedias() {
loading.value = true;
try {
const r = await axios.get('/api/media', {
params: { entity_type: props.entityType, entity_id: props.entityId },
});
medias.value = r.data;
}
finally {
loading.value = false;
}
}
async function onUpload(e) {
const file = e.target.files?.[0];
if (!file)
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: props.entityType,
entity_id: props.entityId,
url: uploaded.url,
thumbnail_url: uploaded.thumbnail_url,
});
await fetchMedias();
}
async function deleteMedia(id) {
if (!confirm('Supprimer cette photo ?'))
return;
await axios.delete(`/api/media/${id}`);
medias.value = medias.value.filter((m) => m.id !== id);
}
onMounted(fetchMedias);
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center justify-between mb-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text-muted text-sm" },
});
(__VLS_ctx.medias.length);
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "cursor-pointer bg-bg-soft text-text-muted hover:text-text px-3 py-1 rounded-lg text-xs border border-bg-hard transition-colors" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
...{ onChange: (__VLS_ctx.onUpload) },
type: "file",
accept: "image/*",
capture: "environment",
...{ class: "hidden" },
});
if (__VLS_ctx.loading) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs" },
});
}
else if (!__VLS_ctx.medias.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs italic py-2" },
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-3 gap-2" },
});
for (const [m] of __VLS_getVForSourceType((__VLS_ctx.medias))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
__VLS_ctx.lightbox = m;
} },
key: (m.id),
...{ class: "aspect-square rounded-lg overflow-hidden bg-bg-hard relative group cursor-pointer" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (m.thumbnail_url || m.url),
alt: (m.titre || ''),
...{ class: "w-full h-full object-cover" },
});
if (m.identified_common) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "absolute bottom-0 left-0 right-0 bg-black/60 text-xs text-green px-1 py-0.5 truncate" },
});
(m.identified_common);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.deleteMedia(m.id);
} },
...{ class: "absolute top-1 right-1 bg-black/60 text-red text-xs px-1 rounded opacity-0 group-hover:opacity-100 transition-opacity" },
});
}
if (__VLS_ctx.lightbox) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.lightbox))
return;
__VLS_ctx.lightbox = null;
} },
...{ class: "fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "max-w-lg w-full" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (__VLS_ctx.lightbox.url),
...{ class: "w-full rounded-xl" },
});
if (__VLS_ctx.lightbox.identified_species) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-center mt-2 text-text-muted text-sm" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-green font-medium" },
});
(__VLS_ctx.lightbox.identified_common);
__VLS_asFunctionalElement(__VLS_intrinsicElements.em, __VLS_intrinsicElements.em)({});
(__VLS_ctx.lightbox.identified_species);
(Math.round((__VLS_ctx.lightbox.identified_confidence || 0) * 100));
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.lightbox))
return;
__VLS_ctx.lightbox = null;
} },
...{ class: "mt-3 w-full text-text-muted text-sm hover:text-text" },
});
}
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['cursor-pointer']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['hidden']} */ ;
/** @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['italic']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-3']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['aspect-square']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['overflow-hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['relative']} */ ;
/** @type {__VLS_StyleScopedClasses['group']} */ ;
/** @type {__VLS_StyleScopedClasses['cursor-pointer']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['h-full']} */ ;
/** @type {__VLS_StyleScopedClasses['object-cover']} */ ;
/** @type {__VLS_StyleScopedClasses['absolute']} */ ;
/** @type {__VLS_StyleScopedClasses['bottom-0']} */ ;
/** @type {__VLS_StyleScopedClasses['left-0']} */ ;
/** @type {__VLS_StyleScopedClasses['right-0']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/60']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['px-1']} */ ;
/** @type {__VLS_StyleScopedClasses['py-0.5']} */ ;
/** @type {__VLS_StyleScopedClasses['truncate']} */ ;
/** @type {__VLS_StyleScopedClasses['absolute']} */ ;
/** @type {__VLS_StyleScopedClasses['top-1']} */ ;
/** @type {__VLS_StyleScopedClasses['right-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/60']} */ ;
/** @type {__VLS_StyleScopedClasses['text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['px-1']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['opacity-0']} */ ;
/** @type {__VLS_StyleScopedClasses['group-hover:opacity-100']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-opacity']} */ ;
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
/** @type {__VLS_StyleScopedClasses['inset-0']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/80']} */ ;
/** @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['max-w-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-3']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {
medias: medias,
loading: loading,
lightbox: lightbox,
onUpload: onUpload,
deleteMedia: deleteMedia,
};
},
__typeProps: {},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
__typeProps: {},
});
; /* PartiallyEnd: #4569/main.vue */

View File

@@ -0,0 +1,369 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { ref, onMounted } from 'vue';
import axios from 'axios';
const emit = defineEmits();
const previewUrl = ref(null);
const imageFile = ref(null);
const loading = ref(false);
const saving = ref(false);
const results = ref([]);
const source = ref('');
const selected = ref(null);
const plants = ref([]);
const linkPlantId = ref(null);
onMounted(async () => {
const { data } = await axios.get('/api/plants');
plants.value = data;
});
async function onFileSelect(e) {
const file = e.target.files?.[0];
if (!file)
return;
imageFile.value = file;
previewUrl.value = URL.createObjectURL(file);
await identify();
}
async function identify() {
loading.value = true;
results.value = [];
selected.value = null;
try {
const fd = new FormData();
fd.append('file', imageFile.value);
const { data } = await axios.post('/api/identify', fd);
results.value = data.results;
source.value = data.source;
if (results.value.length)
selected.value = 0;
}
catch {
results.value = [];
}
finally {
loading.value = false;
}
}
async function saveAndLink() {
if (imageFile.value === null || selected.value === null)
return;
const r = results.value[selected.value];
saving.value = true;
try {
const fd = new FormData();
fd.append('file', imageFile.value);
const { data: uploaded } = await axios.post('/api/upload', fd);
// Toujours sauvegarder : lié à une plante si choisie, sinon dans la bibliothèque générale
await axios.post('/api/media', {
entity_type: linkPlantId.value !== null ? 'plante' : 'bibliotheque',
entity_id: linkPlantId.value ?? 0,
url: uploaded.url,
thumbnail_url: uploaded.thumbnail_url,
identified_species: r.species,
identified_common: r.common_name,
identified_confidence: r.confidence,
identified_source: source.value,
});
emit('identified', { ...r, imageUrl: uploaded.url, plantId: linkPlantId.value });
emit('close');
}
finally {
saving.value = false;
}
}
function reset() {
previewUrl.value = null;
imageFile.value = null;
results.value = [];
selected.value = null;
}
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
let __VLS_directives;
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
__VLS_ctx.$emit('close');
} },
...{ class: "fixed inset-0 bg-black/70 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" },
});
if (!__VLS_ctx.previewUrl) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "border-2 border-dashed border-bg-soft rounded-xl p-8 text-center mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-sm mb-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "cursor-pointer bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
...{ onChange: (__VLS_ctx.onFileSelect) },
type: "file",
accept: "image/*",
capture: "environment",
...{ class: "hidden" },
});
}
else {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (__VLS_ctx.previewUrl),
...{ class: "w-full rounded-lg mb-4 max-h-48 object-cover" },
});
if (__VLS_ctx.loading) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm text-center py-4" },
});
}
else if (__VLS_ctx.results.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-xs mb-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-yellow font-mono" },
});
(__VLS_ctx.source);
for (const [r, i] of __VLS_getVForSourceType((__VLS_ctx.results))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
if (!!(!__VLS_ctx.previewUrl))
return;
if (!!(__VLS_ctx.loading))
return;
if (!(__VLS_ctx.results.length))
return;
__VLS_ctx.selected = i;
} },
key: (i),
...{ class: "mb-2 p-3 rounded-lg border cursor-pointer transition-colors" },
...{ class: (__VLS_ctx.selected === i
? 'border-green bg-green/10'
: 'border-bg-soft bg-bg hover:border-green/50') },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center justify-between" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text font-medium text-sm" },
});
(r.common_name || r.species);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs italic" },
});
(r.species);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-green text-sm font-bold" },
});
(Math.round(r.confidence * 100));
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-1 h-1 bg-bg-soft rounded-full overflow-hidden" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div)({
...{ class: "h-full bg-green rounded-full transition-all" },
...{ style: ({ width: `${r.confidence * 100}%` }) },
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-4 flex flex-col gap-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.linkPlantId),
...{ class: "w-full bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm outline-none focus:border-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: (null),
});
for (const [p] of __VLS_getVForSourceType((__VLS_ctx.plants))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
key: (p.id),
value: (p.id),
});
(p.nom_commun);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.saveAndLink) },
disabled: (__VLS_ctx.selected === null || __VLS_ctx.saving),
...{ class: "flex-1 bg-green text-bg py-2 rounded-lg text-sm font-semibold hover:opacity-90 disabled:opacity-40" },
});
(__VLS_ctx.saving ? 'Enregistrement...' : 'Enregistrer');
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!!(!__VLS_ctx.previewUrl))
return;
if (!!(__VLS_ctx.loading))
return;
if (!(__VLS_ctx.results.length))
return;
__VLS_ctx.$emit('close');
} },
...{ class: "px-4 py-2 text-text-muted hover:text-text text-sm" },
});
}
else {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm text-center py-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.reset) },
...{ class: "block mt-2 mx-auto text-green hover:underline text-xs" },
});
}
}
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
/** @type {__VLS_StyleScopedClasses['inset-0']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/70']} */ ;
/** @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['border-2']} */ ;
/** @type {__VLS_StyleScopedClasses['border-dashed']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['p-8']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['cursor-pointer']} */ ;
/** @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['hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-h-48']} */ ;
/** @type {__VLS_StyleScopedClasses['object-cover']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['font-mono']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['p-3']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['cursor-pointer']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['italic']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
/** @type {__VLS_StyleScopedClasses['h-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-full']} */ ;
/** @type {__VLS_StyleScopedClasses['overflow-hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['h-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-full']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-all']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-4']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-col']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @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['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @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['disabled:opacity-40']} */ ;
/** @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['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
/** @type {__VLS_StyleScopedClasses['block']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {
previewUrl: previewUrl,
loading: loading,
saving: saving,
results: results,
source: source,
selected: selected,
plants: plants,
linkPlantId: linkPlantId,
onFileSelect: onFileSelect,
saveAndLink: saveAndLink,
reset: reset,
};
},
__typeEmits: {},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
__typeEmits: {},
});
; /* PartiallyEnd: #4569/main.vue */

View File

@@ -6,14 +6,17 @@ export default createRouter({
{ path: '/jardins', component: () => import('@/views/JardinsView.vue') },
{ path: '/jardins/:id', component: () => import('@/views/JardinDetailView.vue') },
{ path: '/plantes', component: () => import('@/views/PlantesView.vue') },
{ path: '/bibliotheque', component: () => import('@/views/BibliothequeView.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: '/calendrier', component: () => import('@/views/CalendrierView.vue') },
{ path: '/meteo', component: () => import('@/views/CalendrierView.vue') },
{ path: '/astuces', component: () => import('@/views/AstucesView.vue') },
{ path: '/reglages', component: () => import('@/views/ReglagesView.vue') },
// Redirect des anciens liens
{ path: '/varietes', redirect: '/plantes' },
{ path: '/lunaire', redirect: '/calendrier' },
{ path: '/calendrier', redirect: '/meteo' },
{ path: '/lunaire', redirect: '/meteo' },
],
});

View File

@@ -12,10 +12,12 @@ export default createRouter({
{ path: '/plantations', component: () => import('@/views/PlantationsView.vue') },
{ path: '/planning', component: () => import('@/views/PlanningView.vue') },
{ path: '/taches', component: () => import('@/views/TachesView.vue') },
{ path: '/calendrier', component: () => import('@/views/CalendrierView.vue') },
{ path: '/meteo', component: () => import('@/views/CalendrierView.vue') },
{ path: '/astuces', component: () => import('@/views/AstucesView.vue') },
{ path: '/reglages', component: () => import('@/views/ReglagesView.vue') },
// Redirect des anciens liens
{ path: '/varietes', redirect: '/plantes' },
{ path: '/lunaire', redirect: '/calendrier' },
{ path: '/calendrier', redirect: '/meteo' },
{ path: '/lunaire', redirect: '/meteo' },
],
})

View File

@@ -0,0 +1,33 @@
import { ref } from 'vue';
import { defineStore } from 'pinia';
import { astucesApi } from '@/api/astuces';
export const useAstucesStore = defineStore('astuces', () => {
const astuces = ref([]);
const loading = ref(false);
async function fetchAll(params) {
loading.value = true;
try {
astuces.value = await astucesApi.list(params);
}
finally {
loading.value = false;
}
}
async function create(a) {
const created = await astucesApi.create(a);
astuces.value.unshift(created);
return created;
}
async function update(id, data) {
const updated = await astucesApi.update(id, data);
const idx = astuces.value.findIndex(x => x.id === id);
if (idx !== -1)
astuces.value[idx] = updated;
return updated;
}
async function remove(id) {
await astucesApi.remove(id);
astuces.value = astuces.value.filter(a => a.id !== id);
}
return { astuces, loading, fetchAll, create, update, remove };
});

View File

@@ -0,0 +1,37 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { astucesApi, type Astuce } from '@/api/astuces'
export const useAstucesStore = defineStore('astuces', () => {
const astuces = ref<Astuce[]>([])
const loading = ref(false)
async function fetchAll(params?: { categorie?: string; mois?: number; tag?: string }) {
loading.value = true
try {
astuces.value = await astucesApi.list(params)
} finally {
loading.value = false
}
}
async function create(a: Omit<Astuce, 'id' | 'created_at'>) {
const created = await astucesApi.create(a)
astuces.value.unshift(created)
return created
}
async function update(id: number, data: Partial<Astuce>) {
const updated = await astucesApi.update(id, data)
const idx = astuces.value.findIndex(x => x.id === id)
if (idx !== -1) astuces.value[idx] = updated
return updated
}
async function remove(id: number) {
await astucesApi.remove(id)
astuces.value = astuces.value.filter(a => a.id !== id)
}
return { astuces, loading, fetchAll, create, update, remove }
})

View File

@@ -14,9 +14,16 @@ export const useGardensStore = defineStore('gardens', () => {
gardens.value.push(created);
return created;
}
async function update(id, g) {
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) {
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

@@ -14,9 +14,16 @@ export const usePlantingsStore = defineStore('plantings', () => {
plantings.value.push(created);
return created;
}
async function update(id, p) {
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) {
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

@@ -14,6 +14,13 @@ export const useTasksStore = defineStore('tasks', () => {
tasks.value.push(created);
return created;
}
async function update(id, data) {
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, statut) {
const t = tasks.value.find(t => t.id === id);
if (!t)
@@ -25,5 +32,5 @@ export const useTasksStore = defineStore('tasks', () => {
await tasksApi.delete(id);
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

@@ -18,9 +18,16 @@ export const useToolsStore = defineStore('tools', () => {
tools.value.push(created);
return created;
}
async function update(id, t) {
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) {
await toolsApi.delete(id);
tools.value = tools.value.filter(t => t.id !== id);
}
return { tools, loading, fetchAll, create, remove };
return { tools, loading, fetchAll, create, update, remove };
});

View File

@@ -0,0 +1,405 @@
<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">💡 Astuces</h1>
<button @click="openCreate" class="bg-yellow text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">
+ Ajouter
</button>
</div>
<div class="flex flex-wrap items-center gap-2 mb-4">
<select
v-model="filterCategorie"
class="bg-bg border border-bg-hard rounded px-3 py-1 text-text text-xs outline-none focus:border-yellow"
>
<option value="">Toutes catégories</option>
<option value="plante">Plante</option>
<option value="jardin">Jardin</option>
<option value="tache">Tâche</option>
<option value="general">Général</option>
<option value="ravageur">Ravageur</option>
<option value="maladie">Maladie</option>
</select>
<input
v-model="filterTag"
placeholder="Filtrer par tag..."
class="bg-bg border border-bg-hard rounded px-3 py-1 text-text text-xs outline-none focus:border-yellow w-44"
/>
<button
@click="filterMoisActuel = !filterMoisActuel"
:class="[
'px-3 py-1 rounded-full text-xs font-medium transition-colors border',
filterMoisActuel ? 'bg-green/20 text-green border-green/40' : 'border-bg-hard text-text-muted',
]"
>
📅 Ce mois
</button>
<button @click="refresh" class="text-xs text-text-muted hover:text-text underline ml-auto">Rafraîchir</button>
</div>
<div v-if="store.loading" class="text-text-muted text-sm">Chargement...</div>
<div v-else-if="!store.astuces.length" class="text-text-muted text-sm py-6">Aucune astuce pour ce filtre.</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div
v-for="a in store.astuces"
:key="a.id"
class="bg-bg-soft rounded-xl p-4 border border-bg-hard"
>
<div class="flex items-start justify-between gap-2 mb-2">
<h2 class="text-text font-semibold leading-tight">{{ a.titre }}</h2>
<div class="flex gap-2 shrink-0">
<button @click="openEdit(a)" class="text-yellow text-xs hover:underline">Édit.</button>
<button @click="removeAstuce(a.id)" class="text-red text-xs hover:underline">Suppr.</button>
</div>
</div>
<p class="text-text-muted text-sm whitespace-pre-line">{{ a.contenu }}</p>
<div v-if="parseMediaUrls(a.photos).length" class="mt-3 grid grid-cols-3 gap-2">
<img
v-for="(url, idx) in parseMediaUrls(a.photos)"
:key="`astuce-photo-${a.id}-${idx}`"
:src="url"
alt="photo astuce"
class="w-full h-20 object-cover rounded-md border border-bg-hard"
/>
</div>
<div v-if="parseMediaUrls(a.videos).length" class="mt-3 space-y-2">
<video
v-for="(url, idx) in parseMediaUrls(a.videos)"
:key="`astuce-video-${a.id}-${idx}`"
:src="url"
controls
muted
class="w-full rounded-md border border-bg-hard bg-black/40 max-h-52"
/>
</div>
<div class="mt-3 flex flex-wrap gap-1">
<span v-if="a.categorie" class="text-[11px] bg-yellow/15 text-yellow rounded-full px-2 py-0.5">{{ a.categorie }}</span>
<span v-for="t in parseTags(a.tags)" :key="`${a.id}-t-${t}`" class="text-[11px] bg-blue/15 text-blue rounded-full px-2 py-0.5">#{{ t }}</span>
<span v-if="parseMois(a.mois).length" class="text-[11px] bg-green/15 text-green rounded-full px-2 py-0.5">mois: {{ parseMois(a.mois).join(',') }}</span>
</div>
</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-lg border border-bg-soft">
<h2 class="text-text font-bold text-lg mb-4">{{ editId ? 'Modifier astuce' : 'Nouvelle astuce' }}</h2>
<form @submit.prevent="submitAstuce" class="flex flex-col gap-3">
<input
v-model="form.titre"
placeholder="Titre *"
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"
/>
<textarea
v-model="form.contenu"
placeholder="Contenu *"
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 resize-none h-28"
/>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<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="plante">Plante</option>
<option value="jardin">Jardin</option>
<option value="tache">Tâche</option>
<option value="general">Général</option>
<option value="ravageur">Ravageur</option>
<option value="maladie">Maladie</option>
</select>
<input
v-model="form.source"
placeholder="Source"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow"
/>
</div>
<input
v-model="form.tagsInput"
placeholder="Tags (ex: tomate, semis, mildiou)"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow"
/>
<input
v-model="form.moisInput"
placeholder="Mois (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-yellow"
/>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<label class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm cursor-pointer text-center hover:border-yellow">
{{ uploadingPhotos ? 'Upload photos...' : 'Ajouter photo(s)' }}
<input
type="file"
accept="image/*"
multiple
class="hidden"
@change="uploadFiles($event, 'photo')"
/>
</label>
<label class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm cursor-pointer text-center hover:border-yellow">
{{ uploadingVideos ? 'Upload vidéos...' : 'Ajouter vidéo(s)' }}
<input
type="file"
accept="video/*"
multiple
class="hidden"
@change="uploadFiles($event, 'video')"
/>
</label>
</div>
<div v-if="form.photos.length" class="bg-bg border border-bg-soft rounded-lg p-2">
<div class="text-xs text-text-muted mb-1">Photos jointes</div>
<div class="grid grid-cols-3 gap-2">
<div v-for="(url, idx) in form.photos" :key="`form-photo-${idx}`" class="relative group">
<img :src="url" alt="photo astuce" class="w-full h-16 object-cover rounded border border-bg-hard" />
<button
type="button"
class="absolute top-1 right-1 hidden group-hover:block bg-red/80 text-white text-[10px] rounded px-1"
@click="removeMedia('photo', idx)"
>
</button>
</div>
</div>
</div>
<div v-if="form.videos.length" class="bg-bg border border-bg-soft rounded-lg p-2">
<div class="text-xs text-text-muted mb-1">Vidéos jointes</div>
<div class="space-y-2">
<div v-for="(url, idx) in form.videos" :key="`form-video-${idx}`" class="relative group">
<video :src="url" controls muted class="w-full max-h-36 rounded border border-bg-hard bg-black/40" />
<button
type="button"
class="absolute top-1 right-1 hidden group-hover:block bg-red/80 text-white text-[10px] rounded px-1"
@click="removeMedia('video', idx)"
>
</button>
</div>
</div>
</div>
<div class="flex gap-2 justify-end mt-1">
<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 { computed, onMounted, reactive, ref, watch } from 'vue'
import axios from 'axios'
import { useAstucesStore } from '@/stores/astuces'
import type { Astuce } from '@/api/astuces'
const store = useAstucesStore()
const filterCategorie = ref('')
const filterTag = ref('')
const filterMoisActuel = ref(false)
const showForm = ref(false)
const editId = ref<number | null>(null)
const form = reactive({
titre: '',
contenu: '',
categorie: '',
source: '',
tagsInput: '',
moisInput: '',
photos: [] as string[],
videos: [] as string[],
})
const uploadingPhotos = ref(false)
const uploadingVideos = ref(false)
const currentMonth = computed(() => new Date().getMonth() + 1)
function parseTags(raw?: string): string[] {
if (!raw) return []
try {
const arr = JSON.parse(raw)
if (Array.isArray(arr)) return arr.map((x) => String(x).trim()).filter(Boolean)
} catch {
return raw.split(',').map((x) => x.trim()).filter(Boolean)
}
return []
}
function parseMois(raw?: string): number[] {
if (!raw) return []
try {
const arr = JSON.parse(raw)
if (Array.isArray(arr)) {
return arr
.map((x) => Number(x))
.filter((x) => Number.isInteger(x) && x >= 1 && x <= 12)
}
} catch {
return raw
.split(',')
.map((x) => Number(x.trim()))
.filter((x) => Number.isInteger(x) && x >= 1 && x <= 12)
}
return []
}
function parseMediaUrls(raw?: string): string[] {
if (!raw) return []
try {
const arr = JSON.parse(raw)
if (Array.isArray(arr)) return arr.map((x) => String(x).trim()).filter(Boolean)
} catch {
return raw
.split(',')
.map((x) => x.trim())
.filter(Boolean)
}
return []
}
function toJsonTags(input: string): string | undefined {
const tags = input
.split(',')
.map((x) => x.trim().toLowerCase())
.filter(Boolean)
return tags.length ? JSON.stringify(tags) : undefined
}
function toJsonMois(input: string): string | undefined {
const months = input
.split(',')
.map((x) => Number(x.trim()))
.filter((x) => Number.isInteger(x) && x >= 1 && x <= 12)
return months.length ? JSON.stringify(months) : undefined
}
function toJsonArray(values: string[]): string | undefined {
const clean = values.map((x) => x.trim()).filter(Boolean)
return clean.length ? JSON.stringify(clean) : undefined
}
async function uploadFiles(event: Event, kind: 'photo' | 'video') {
const files = (event.target as HTMLInputElement).files
if (!files?.length) return
if (kind === 'photo') uploadingPhotos.value = true
else uploadingVideos.value = true
try {
for (const file of Array.from(files)) {
const fd = new FormData()
fd.append('file', file)
const { data } = await axios.post<{ url: string }>('/api/upload', fd)
if (!data?.url) continue
if (kind === 'photo') form.photos.push(data.url)
else form.videos.push(data.url)
}
} finally {
if (kind === 'photo') uploadingPhotos.value = false
else uploadingVideos.value = false
;(event.target as HTMLInputElement).value = ''
}
}
function removeMedia(kind: 'photo' | 'video', idx: number) {
if (kind === 'photo') form.photos.splice(idx, 1)
else form.videos.splice(idx, 1)
}
async function refresh() {
await store.fetchAll({
categorie: filterCategorie.value || undefined,
tag: filterTag.value || undefined,
mois: filterMoisActuel.value ? currentMonth.value : undefined,
})
}
function openCreate() {
editId.value = null
Object.assign(form, {
titre: '',
contenu: '',
categorie: '',
source: '',
tagsInput: '',
moisInput: '',
photos: [],
videos: [],
})
showForm.value = true
}
function openEdit(a: Astuce) {
editId.value = a.id || null
Object.assign(form, {
titre: a.titre,
contenu: a.contenu,
categorie: a.categorie || '',
source: a.source || '',
tagsInput: parseTags(a.tags).join(', '),
moisInput: parseMois(a.mois).join(','),
photos: parseMediaUrls(a.photos),
videos: parseMediaUrls(a.videos),
})
showForm.value = true
}
function closeForm() {
showForm.value = false
}
async function submitAstuce() {
const payload = {
titre: form.titre.trim(),
contenu: form.contenu.trim(),
categorie: form.categorie || undefined,
source: form.source.trim() || undefined,
tags: toJsonTags(form.tagsInput),
mois: toJsonMois(form.moisInput),
photos: toJsonArray(form.photos),
videos: toJsonArray(form.videos),
}
if (editId.value) {
await store.update(editId.value, payload)
} else {
await store.create(payload)
}
closeForm()
}
async function removeAstuce(id?: number) {
if (!id) return
if (confirm('Supprimer cette astuce ?')) {
await store.remove(id)
}
}
watch([filterCategorie, filterTag, filterMoisActuel], refresh)
onMounted(refresh)
</script>

View File

@@ -0,0 +1,883 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { computed, onMounted, reactive, ref, watch } from 'vue';
import axios from 'axios';
import { useAstucesStore } from '@/stores/astuces';
const store = useAstucesStore();
const filterCategorie = ref('');
const filterTag = ref('');
const filterMoisActuel = ref(false);
const showForm = ref(false);
const editId = ref(null);
const form = reactive({
titre: '',
contenu: '',
categorie: '',
source: '',
tagsInput: '',
moisInput: '',
photos: [],
videos: [],
});
const uploadingPhotos = ref(false);
const uploadingVideos = ref(false);
const currentMonth = computed(() => new Date().getMonth() + 1);
function parseTags(raw) {
if (!raw)
return [];
try {
const arr = JSON.parse(raw);
if (Array.isArray(arr))
return arr.map((x) => String(x).trim()).filter(Boolean);
}
catch {
return raw.split(',').map((x) => x.trim()).filter(Boolean);
}
return [];
}
function parseMois(raw) {
if (!raw)
return [];
try {
const arr = JSON.parse(raw);
if (Array.isArray(arr)) {
return arr
.map((x) => Number(x))
.filter((x) => Number.isInteger(x) && x >= 1 && x <= 12);
}
}
catch {
return raw
.split(',')
.map((x) => Number(x.trim()))
.filter((x) => Number.isInteger(x) && x >= 1 && x <= 12);
}
return [];
}
function parseMediaUrls(raw) {
if (!raw)
return [];
try {
const arr = JSON.parse(raw);
if (Array.isArray(arr))
return arr.map((x) => String(x).trim()).filter(Boolean);
}
catch {
return raw
.split(',')
.map((x) => x.trim())
.filter(Boolean);
}
return [];
}
function toJsonTags(input) {
const tags = input
.split(',')
.map((x) => x.trim().toLowerCase())
.filter(Boolean);
return tags.length ? JSON.stringify(tags) : undefined;
}
function toJsonMois(input) {
const months = input
.split(',')
.map((x) => Number(x.trim()))
.filter((x) => Number.isInteger(x) && x >= 1 && x <= 12);
return months.length ? JSON.stringify(months) : undefined;
}
function toJsonArray(values) {
const clean = values.map((x) => x.trim()).filter(Boolean);
return clean.length ? JSON.stringify(clean) : undefined;
}
async function uploadFiles(event, kind) {
const files = event.target.files;
if (!files?.length)
return;
if (kind === 'photo')
uploadingPhotos.value = true;
else
uploadingVideos.value = true;
try {
for (const file of Array.from(files)) {
const fd = new FormData();
fd.append('file', file);
const { data } = await axios.post('/api/upload', fd);
if (!data?.url)
continue;
if (kind === 'photo')
form.photos.push(data.url);
else
form.videos.push(data.url);
}
}
finally {
if (kind === 'photo')
uploadingPhotos.value = false;
else
uploadingVideos.value = false;
event.target.value = '';
}
}
function removeMedia(kind, idx) {
if (kind === 'photo')
form.photos.splice(idx, 1);
else
form.videos.splice(idx, 1);
}
async function refresh() {
await store.fetchAll({
categorie: filterCategorie.value || undefined,
tag: filterTag.value || undefined,
mois: filterMoisActuel.value ? currentMonth.value : undefined,
});
}
function openCreate() {
editId.value = null;
Object.assign(form, {
titre: '',
contenu: '',
categorie: '',
source: '',
tagsInput: '',
moisInput: '',
photos: [],
videos: [],
});
showForm.value = true;
}
function openEdit(a) {
editId.value = a.id || null;
Object.assign(form, {
titre: a.titre,
contenu: a.contenu,
categorie: a.categorie || '',
source: a.source || '',
tagsInput: parseTags(a.tags).join(', '),
moisInput: parseMois(a.mois).join(','),
photos: parseMediaUrls(a.photos),
videos: parseMediaUrls(a.videos),
});
showForm.value = true;
}
function closeForm() {
showForm.value = false;
}
async function submitAstuce() {
const payload = {
titre: form.titre.trim(),
contenu: form.contenu.trim(),
categorie: form.categorie || undefined,
source: form.source.trim() || undefined,
tags: toJsonTags(form.tagsInput),
mois: toJsonMois(form.moisInput),
photos: toJsonArray(form.photos),
videos: toJsonArray(form.videos),
};
if (editId.value) {
await store.update(editId.value, payload);
}
else {
await store.create(payload);
}
closeForm();
}
async function removeAstuce(id) {
if (!id)
return;
if (confirm('Supprimer cette astuce ?')) {
await store.remove(id);
}
}
watch([filterCategorie, filterTag, filterMoisActuel], refresh);
onMounted(refresh);
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: (__VLS_ctx.openCreate) },
...{ class: "bg-yellow 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 flex-wrap items-center gap-2 mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.filterCategorie),
...{ class: "bg-bg border border-bg-hard rounded px-3 py-1 text-text text-xs outline-none focus:border-yellow" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "plante",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "jardin",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "tache",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "general",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "ravageur",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "maladie",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
placeholder: "Filtrer par tag...",
...{ class: "bg-bg border border-bg-hard rounded px-3 py-1 text-text text-xs outline-none focus:border-yellow w-44" },
});
(__VLS_ctx.filterTag);
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.filterMoisActuel = !__VLS_ctx.filterMoisActuel;
} },
...{ class: ([
'px-3 py-1 rounded-full text-xs font-medium transition-colors border',
__VLS_ctx.filterMoisActuel ? 'bg-green/20 text-green border-green/40' : 'border-bg-hard text-text-muted',
]) },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.refresh) },
...{ class: "text-xs text-text-muted hover:text-text underline ml-auto" },
});
if (__VLS_ctx.store.loading) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm" },
});
}
else if (!__VLS_ctx.store.astuces.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-sm py-6" },
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-1 md:grid-cols-2 gap-3" },
});
for (const [a] of __VLS_getVForSourceType((__VLS_ctx.store.astuces))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (a.id),
...{ class: "bg-bg-soft rounded-xl p-4 border border-bg-hard" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-start justify-between gap-2 mb-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text font-semibold leading-tight" },
});
(a.titre);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 shrink-0" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.openEdit(a);
} },
...{ class: "text-yellow text-xs hover:underline" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.removeAstuce(a.id);
} },
...{ class: "text-red text-xs hover:underline" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-sm whitespace-pre-line" },
});
(a.contenu);
if (__VLS_ctx.parseMediaUrls(a.photos).length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-3 grid grid-cols-3 gap-2" },
});
for (const [url, idx] of __VLS_getVForSourceType((__VLS_ctx.parseMediaUrls(a.photos)))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
key: (`astuce-photo-${a.id}-${idx}`),
src: (url),
alt: "photo astuce",
...{ class: "w-full h-20 object-cover rounded-md border border-bg-hard" },
});
}
}
if (__VLS_ctx.parseMediaUrls(a.videos).length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-3 space-y-2" },
});
for (const [url, idx] of __VLS_getVForSourceType((__VLS_ctx.parseMediaUrls(a.videos)))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.video)({
key: (`astuce-video-${a.id}-${idx}`),
src: (url),
controls: true,
muted: true,
...{ class: "w-full rounded-md border border-bg-hard bg-black/40 max-h-52" },
});
}
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-3 flex flex-wrap gap-1" },
});
if (a.categorie) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-[11px] bg-yellow/15 text-yellow rounded-full px-2 py-0.5" },
});
(a.categorie);
}
for (const [t] of __VLS_getVForSourceType((__VLS_ctx.parseTags(a.tags)))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
key: (`${a.id}-t-${t}`),
...{ class: "text-[11px] bg-blue/15 text-blue rounded-full px-2 py-0.5" },
});
(t);
}
if (__VLS_ctx.parseMois(a.mois).length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-[11px] bg-green/15 text-green rounded-full px-2 py-0.5" },
});
(__VLS_ctx.parseMois(a.mois).join(','));
}
}
if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (__VLS_ctx.closeForm) },
...{ 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-lg border border-bg-soft" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text font-bold text-lg mb-4" },
});
(__VLS_ctx.editId ? 'Modifier astuce' : 'Nouvelle astuce');
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (__VLS_ctx.submitAstuce) },
...{ class: "flex flex-col gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
placeholder: "Titre *",
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.titre);
__VLS_asFunctionalElement(__VLS_intrinsicElements.textarea)({
value: (__VLS_ctx.form.contenu),
placeholder: "Contenu *",
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 resize-none h-28" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-1 sm:grid-cols-2 gap-2" },
});
__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: "plante",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "jardin",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "tache",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "general",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "ravageur",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "maladie",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
placeholder: "Source",
...{ 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.source);
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
placeholder: "Tags (ex: tomate, semis, mildiou)",
...{ 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.tagsInput);
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
placeholder: "Mois (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-yellow" },
});
(__VLS_ctx.form.moisInput);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-1 sm:grid-cols-2 gap-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm cursor-pointer text-center hover:border-yellow" },
});
(__VLS_ctx.uploadingPhotos ? 'Upload photos...' : 'Ajouter photo(s)');
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
...{ onChange: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
__VLS_ctx.uploadFiles($event, 'photo');
} },
type: "file",
accept: "image/*",
multiple: true,
...{ class: "hidden" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm cursor-pointer text-center hover:border-yellow" },
});
(__VLS_ctx.uploadingVideos ? 'Upload vidéos...' : 'Ajouter vidéo(s)');
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
...{ onChange: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
__VLS_ctx.uploadFiles($event, 'video');
} },
type: "file",
accept: "video/*",
multiple: true,
...{ class: "hidden" },
});
if (__VLS_ctx.form.photos.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg border border-bg-soft rounded-lg p-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-xs text-text-muted mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-3 gap-2" },
});
for (const [url, idx] of __VLS_getVForSourceType((__VLS_ctx.form.photos))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (`form-photo-${idx}`),
...{ class: "relative group" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (url),
alt: "photo astuce",
...{ class: "w-full h-16 object-cover rounded border border-bg-hard" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
if (!(__VLS_ctx.form.photos.length))
return;
__VLS_ctx.removeMedia('photo', idx);
} },
type: "button",
...{ class: "absolute top-1 right-1 hidden group-hover:block bg-red/80 text-white text-[10px] rounded px-1" },
});
}
}
if (__VLS_ctx.form.videos.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg border border-bg-soft rounded-lg p-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-xs text-text-muted mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "space-y-2" },
});
for (const [url, idx] of __VLS_getVForSourceType((__VLS_ctx.form.videos))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (`form-video-${idx}`),
...{ class: "relative group" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.video)({
src: (url),
controls: true,
muted: true,
...{ class: "w-full max-h-36 rounded border border-bg-hard bg-black/40" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
if (!(__VLS_ctx.form.videos.length))
return;
__VLS_ctx.removeMedia('video', idx);
} },
type: "button",
...{ class: "absolute top-1 right-1 hidden group-hover:block bg-red/80 text-white text-[10px] rounded px-1" },
});
}
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 justify-end mt-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.closeForm) },
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" },
});
(__VLS_ctx.editId ? 'Enregistrer' : 'Créer');
}
/** @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['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @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-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @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-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['w-44']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['underline']} */ ;
/** @type {__VLS_StyleScopedClasses['ml-auto']} */ ;
/** @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-6']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-1']} */ ;
/** @type {__VLS_StyleScopedClasses['md:grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @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-start']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['leading-tight']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['shrink-0']} */ ;
/** @type {__VLS_StyleScopedClasses['text-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['whitespace-pre-line']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-3']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-3']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['h-20']} */ ;
/** @type {__VLS_StyleScopedClasses['object-cover']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-md']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-3']} */ ;
/** @type {__VLS_StyleScopedClasses['space-y-2']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-md']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/40']} */ ;
/** @type {__VLS_StyleScopedClasses['max-h-52']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-3']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-[11px]']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-yellow/15']} */ ;
/** @type {__VLS_StyleScopedClasses['text-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-full']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-0.5']} */ ;
/** @type {__VLS_StyleScopedClasses['text-[11px]']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-blue/15']} */ ;
/** @type {__VLS_StyleScopedClasses['text-blue']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-full']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-0.5']} */ ;
/** @type {__VLS_StyleScopedClasses['text-[11px]']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green/15']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-full']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-0.5']} */ ;
/** @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-lg']} */ ;
/** @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['resize-none']} */ ;
/** @type {__VLS_StyleScopedClasses['h-28']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-1']} */ ;
/** @type {__VLS_StyleScopedClasses['sm:grid-cols-2']} */ ;
/** @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['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['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['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-1']} */ ;
/** @type {__VLS_StyleScopedClasses['sm:grid-cols-2']} */ ;
/** @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['cursor-pointer']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:border-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['hidden']} */ ;
/** @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['cursor-pointer']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:border-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-3']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['relative']} */ ;
/** @type {__VLS_StyleScopedClasses['group']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['h-16']} */ ;
/** @type {__VLS_StyleScopedClasses['object-cover']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['absolute']} */ ;
/** @type {__VLS_StyleScopedClasses['top-1']} */ ;
/** @type {__VLS_StyleScopedClasses['right-1']} */ ;
/** @type {__VLS_StyleScopedClasses['hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['group-hover:block']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-red/80']} */ ;
/** @type {__VLS_StyleScopedClasses['text-white']} */ ;
/** @type {__VLS_StyleScopedClasses['text-[10px]']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['space-y-2']} */ ;
/** @type {__VLS_StyleScopedClasses['relative']} */ ;
/** @type {__VLS_StyleScopedClasses['group']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['max-h-36']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/40']} */ ;
/** @type {__VLS_StyleScopedClasses['absolute']} */ ;
/** @type {__VLS_StyleScopedClasses['top-1']} */ ;
/** @type {__VLS_StyleScopedClasses['right-1']} */ ;
/** @type {__VLS_StyleScopedClasses['hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['group-hover:block']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-red/80']} */ ;
/** @type {__VLS_StyleScopedClasses['text-white']} */ ;
/** @type {__VLS_StyleScopedClasses['text-[10px]']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-1']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-end']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
/** @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 {
store: store,
filterCategorie: filterCategorie,
filterTag: filterTag,
filterMoisActuel: filterMoisActuel,
showForm: showForm,
editId: editId,
form: form,
uploadingPhotos: uploadingPhotos,
uploadingVideos: uploadingVideos,
parseTags: parseTags,
parseMois: parseMois,
parseMediaUrls: parseMediaUrls,
uploadFiles: uploadFiles,
removeMedia: removeMedia,
refresh: refresh,
openCreate: openCreate,
openEdit: openEdit,
closeForm: closeForm,
submitAstuce: submitAstuce,
removeAstuce: removeAstuce,
};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */

View File

@@ -63,6 +63,12 @@
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="markAsAdventice(lightbox!)"
class="bg-green/20 text-green hover:bg-green/30 px-3 py-2 rounded-lg text-xs font-medium transition-colors"
>
🌾 Marquer adventice
</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
@@ -125,6 +131,7 @@ const plantsStore = usePlantsStore()
const filters = [
{ val: '', label: 'Toutes' },
{ val: 'plante', label: '🌱 Plantes' },
{ val: 'adventice', label: '🌾 Adventices' },
{ val: 'jardin', label: '🏡 Jardins' },
{ val: 'plantation', label: '🥕 Plantations' },
{ val: 'outil', label: '🔧 Outils' },
@@ -137,6 +144,7 @@ const filtered = computed(() =>
function labelFor(type: string) {
const map: Record<string, string> = {
adventice: '🌾 Adventice',
plante: '🌱 Plante', jardin: '🏡 Jardin',
plantation: '🥕 Plantation', outil: '🔧 Outil', bibliotheque: '📷'
}
@@ -168,6 +176,21 @@ async function confirmLink() {
linkPlantId.value = null
}
async function markAsAdventice(m: Media) {
await axios.patch(`/api/media/${m.id}`, {
entity_type: 'adventice',
entity_id: 0,
})
const target = medias.value.find(x => x.id === m.id)
if (target) {
target.entity_type = 'adventice'
target.entity_id = 0
}
if (lightbox.value?.id === m.id) {
lightbox.value = { ...lightbox.value, entity_type: 'adventice', entity_id: 0 }
}
}
async function deleteMedia(m: Media) {
if (!confirm('Supprimer cette photo ?')) return
await axios.delete(`/api/media/${m.id}`)

View File

@@ -0,0 +1,554 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { computed, onMounted, ref } from 'vue';
import axios from 'axios';
import PhotoIdentifyModal from '@/components/PhotoIdentifyModal.vue';
import { usePlantsStore } from '@/stores/plants';
const medias = ref([]);
const loading = ref(false);
const lightbox = ref(null);
const showIdentify = ref(false);
const activeFilter = ref('');
const linkMedia = ref(null);
const linkPlantId = ref(null);
const plantsStore = usePlantsStore();
const filters = [
{ val: '', label: 'Toutes' },
{ val: 'plante', label: '🌱 Plantes' },
{ val: 'adventice', label: '🌾 Adventices' },
{ 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);
function labelFor(type) {
const map = {
adventice: '🌾 Adventice',
plante: '🌱 Plante', jardin: '🏡 Jardin',
plantation: '🥕 Plantation', outil: '🔧 Outil', bibliotheque: '📷'
};
return map[type] ?? '📷';
}
function plantName(id) {
return plantsStore.plants.find(p => p.id === id)?.nom_commun ?? '';
}
function openLightbox(m) { lightbox.value = m; }
function startLink(m) {
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 markAsAdventice(m) {
await axios.patch(`/api/media/${m.id}`, {
entity_type: 'adventice',
entity_id: 0,
});
const target = medias.value.find(x => x.id === m.id);
if (target) {
target.entity_type = 'adventice';
target.entity_id = 0;
}
if (lightbox.value?.id === m.id) {
lightbox.value = { ...lightbox.value, entity_type: 'adventice', entity_id: 0 };
}
}
async function deleteMedia(m) {
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 {
const { data } = await axios.get('/api/media/all');
medias.value = data;
}
finally {
loading.value = false;
}
}
function onIdentified() { fetchAll(); }
onMounted(() => {
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 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.showIdentify = 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 [f] of __VLS_getVForSourceType((__VLS_ctx.filters))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.activeFilter = f.val;
} },
key: (f.val),
...{ class: (['px-3 py-1 rounded-full text-xs font-medium transition-colors',
__VLS_ctx.activeFilter === f.val ? 'bg-green text-bg' : 'bg-bg-soft text-text-muted hover:text-text']) },
});
(f.label);
}
if (__VLS_ctx.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 py-4" },
});
}
else {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-3 md:grid-cols-4 gap-2" },
});
for (const [m] of __VLS_getVForSourceType((__VLS_ctx.filtered))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
if (!!(__VLS_ctx.loading))
return;
if (!!(!__VLS_ctx.filtered.length))
return;
__VLS_ctx.openLightbox(m);
} },
key: (m.id),
...{ class: "aspect-square rounded-lg overflow-hidden bg-bg-hard relative group cursor-pointer" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (m.thumbnail_url || m.url),
alt: (m.titre || ''),
...{ class: "w-full h-full object-cover" },
});
if (m.identified_common) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "absolute bottom-0 left-0 right-0 bg-black/70 text-xs text-green px-1 py-0.5 truncate" },
});
(m.identified_common);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "absolute top-1 left-1 bg-black/60 text-text-muted text-xs px-1 rounded" },
});
(__VLS_ctx.labelFor(m.entity_type));
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!!(__VLS_ctx.loading))
return;
if (!!(!__VLS_ctx.filtered.length))
return;
__VLS_ctx.deleteMedia(m);
} },
...{ class: "hidden group-hover:flex absolute top-1 right-1 bg-red/80 text-white text-xs rounded px-1" },
});
}
}
if (__VLS_ctx.lightbox) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.lightbox))
return;
__VLS_ctx.lightbox = null;
} },
...{ class: "fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "max-w-lg w-full bg-bg-hard rounded-xl overflow-hidden border border-bg-soft" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (__VLS_ctx.lightbox.url),
...{ class: "w-full" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "p-4" },
});
if (__VLS_ctx.lightbox.identified_species) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-center mb-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-green font-semibold text-base" },
});
(__VLS_ctx.lightbox.identified_common);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "italic text-text-muted text-sm" },
});
(__VLS_ctx.lightbox.identified_species);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-xs text-text-muted mt-1" },
});
(Math.round((__VLS_ctx.lightbox.identified_confidence || 0) * 100));
(__VLS_ctx.lightbox.identified_source);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-xs text-text-muted mb-3 text-center" },
});
(__VLS_ctx.labelFor(__VLS_ctx.lightbox.entity_type));
if (__VLS_ctx.lightbox.entity_type === 'plante' && __VLS_ctx.plantName(__VLS_ctx.lightbox.entity_id)) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-green font-medium" },
});
(__VLS_ctx.plantName(__VLS_ctx.lightbox.entity_id));
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 flex-wrap" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.lightbox))
return;
__VLS_ctx.startLink(__VLS_ctx.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" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.lightbox))
return;
__VLS_ctx.markAsAdventice(__VLS_ctx.lightbox);
} },
...{ class: "bg-green/20 text-green hover:bg-green/30 px-3 py-2 rounded-lg text-xs font-medium transition-colors" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.lightbox))
return;
__VLS_ctx.deleteMedia(__VLS_ctx.lightbox);
__VLS_ctx.lightbox = null;
} },
...{ class: "bg-red/20 text-red hover:bg-red/30 px-3 py-2 rounded-lg text-xs font-medium transition-colors" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.lightbox))
return;
__VLS_ctx.lightbox = null;
} },
...{ class: "mt-3 w-full text-text-muted hover:text-text text-sm" },
});
}
if (__VLS_ctx.linkMedia) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.linkMedia))
return;
__VLS_ctx.linkMedia = null;
} },
...{ class: "fixed inset-0 bg-black/70 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.h3, __VLS_intrinsicElements.h3)({
...{ class: "text-text font-bold mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.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" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: (null),
});
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: "flex gap-2 justify-end" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.linkMedia))
return;
__VLS_ctx.linkMedia = null;
} },
...{ class: "px-4 py-2 text-text-muted hover:text-text text-sm" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.confirmLink) },
disabled: (!__VLS_ctx.linkPlantId),
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90 disabled:opacity-40" },
});
}
if (__VLS_ctx.showIdentify) {
/** @type {[typeof PhotoIdentifyModal, ]} */ ;
// @ts-ignore
const __VLS_0 = __VLS_asFunctionalComponent(PhotoIdentifyModal, new PhotoIdentifyModal({
...{ 'onClose': {} },
...{ 'onIdentified': {} },
}));
const __VLS_1 = __VLS_0({
...{ 'onClose': {} },
...{ 'onIdentified': {} },
}, ...__VLS_functionalComponentArgsRest(__VLS_0));
let __VLS_3;
let __VLS_4;
let __VLS_5;
const __VLS_6 = {
onClose: (...[$event]) => {
if (!(__VLS_ctx.showIdentify))
return;
__VLS_ctx.showIdentify = false;
}
};
const __VLS_7 = {
onIdentified: (__VLS_ctx.onIdentified)
};
var __VLS_2;
}
/** @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['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-3']} */ ;
/** @type {__VLS_StyleScopedClasses['md:grid-cols-4']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['aspect-square']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['overflow-hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['relative']} */ ;
/** @type {__VLS_StyleScopedClasses['group']} */ ;
/** @type {__VLS_StyleScopedClasses['cursor-pointer']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['h-full']} */ ;
/** @type {__VLS_StyleScopedClasses['object-cover']} */ ;
/** @type {__VLS_StyleScopedClasses['absolute']} */ ;
/** @type {__VLS_StyleScopedClasses['bottom-0']} */ ;
/** @type {__VLS_StyleScopedClasses['left-0']} */ ;
/** @type {__VLS_StyleScopedClasses['right-0']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/70']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['px-1']} */ ;
/** @type {__VLS_StyleScopedClasses['py-0.5']} */ ;
/** @type {__VLS_StyleScopedClasses['truncate']} */ ;
/** @type {__VLS_StyleScopedClasses['absolute']} */ ;
/** @type {__VLS_StyleScopedClasses['top-1']} */ ;
/** @type {__VLS_StyleScopedClasses['left-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/60']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['px-1']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['group-hover:flex']} */ ;
/** @type {__VLS_StyleScopedClasses['absolute']} */ ;
/** @type {__VLS_StyleScopedClasses['top-1']} */ ;
/** @type {__VLS_StyleScopedClasses['right-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-red/80']} */ ;
/** @type {__VLS_StyleScopedClasses['text-white']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-1']} */ ;
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
/** @type {__VLS_StyleScopedClasses['inset-0']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/80']} */ ;
/** @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['max-w-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['overflow-hidden']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-base']} */ ;
/** @type {__VLS_StyleScopedClasses['italic']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-blue/20']} */ ;
/** @type {__VLS_StyleScopedClasses['text-blue']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:bg-blue/30']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green/20']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:bg-green/30']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-red/20']} */ ;
/** @type {__VLS_StyleScopedClasses['text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:bg-red/30']} */ ;
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-3']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
/** @type {__VLS_StyleScopedClasses['inset-0']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/70']} */ ;
/** @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['mb-4']} */ ;
/** @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['mb-4']} */ ;
/** @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']} */ ;
/** @type {__VLS_StyleScopedClasses['disabled:opacity-40']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {
PhotoIdentifyModal: PhotoIdentifyModal,
loading: loading,
lightbox: lightbox,
showIdentify: showIdentify,
activeFilter: activeFilter,
linkMedia: linkMedia,
linkPlantId: linkPlantId,
plantsStore: plantsStore,
filters: filters,
filtered: filtered,
labelFor: labelFor,
plantName: plantName,
openLightbox: openLightbox,
startLink: startLink,
confirmLink: confirmLink,
markAsAdventice: markAsAdventice,
deleteMedia: deleteMedia,
onIdentified: onIdentified,
};
},
});
export default (await import('vue')).defineComponent({
setup() {
return {};
},
});
; /* PartiallyEnd: #4569/main.vue */

View File

@@ -1,247 +1,429 @@
<template>
<div class="p-4 max-w-4xl mx-auto">
<h1 class="text-2xl font-bold text-blue mb-4">📅 Calendrier</h1>
<div class="p-4 max-w-6xl mx-auto">
<h1 class="text-2xl font-bold text-blue mb-4">🌦 Météo</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 }}
<div class="flex flex-wrap items-center gap-2 mb-4">
<button
class="px-3 py-1.5 rounded-md text-xs font-medium bg-bg-soft text-text hover:text-blue border border-bg-hard"
@click="shiftWindow(-spanDays)"
>
Prev
</button>
<button
class="px-3 py-1.5 rounded-md text-xs font-medium bg-blue/20 text-blue border border-blue/30"
@click="goToday"
>
Today
</button>
<button
class="px-3 py-1.5 rounded-md text-xs font-medium bg-bg-soft text-text hover:text-blue border border-bg-hard"
@click="shiftWindow(spanDays)"
>
Next
</button>
<span class="text-text-muted text-xs ml-1">
Fenêtre: {{ formatDate(rangeStart) }} {{ formatDate(rangeEnd) }}
</span>
</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
v-if="stationCurrent"
class="bg-bg-soft rounded-xl p-4 border border-bg-hard mb-4 flex flex-wrap gap-4 items-center"
>
<div>
<div class="text-text-muted text-xs mb-1">Température extérieure</div>
<div class="text-text text-2xl font-bold">{{ stationCurrent.temp_ext?.toFixed(1) ?? '—' }}°C</div>
</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 class="flex gap-4 text-sm">
<span v-if="stationCurrent.humidite != null" class="text-blue">💧{{ stationCurrent.humidite }}%</span>
<span v-if="stationCurrent.vent_kmh != null" class="text-text">💨{{ stationCurrent.vent_kmh }} km/h {{ stationCurrent.vent_dir || '' }}</span>
<span v-if="stationCurrent.pression != null" class="text-text">🧭{{ stationCurrent.pression }} hPa</span>
</div>
<div class="flex items-center gap-2">
<img
v-if="currentOpenMeteo?.wmo != null"
:src="weatherIcon(currentOpenMeteo.wmo)"
class="w-6 h-6"
:alt="currentOpenMeteo.label || 'Météo'"
/>
<div>
<div class="text-text-muted text-xs mb-1">Condition actuelle</div>
<div class="text-text text-sm">{{ currentOpenMeteo?.label || '—' }}</div>
</div>
</div>
<div v-if="stationCurrent.date_heure" class="text-text-muted text-xs ml-auto">
Relevé {{ stationCurrent.date_heure.slice(11, 16) }}
</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 v-if="loadingTableau" class="text-text-muted text-sm py-4">Chargement météo...</div>
<div v-else-if="!tableauRows.length" class="text-text-muted text-sm py-4">Pas de données météo.</div>
<div v-else class="grid grid-cols-1 xl:grid-cols-[minmax(0,1fr)_340px] gap-4 items-start">
<div class="overflow-x-auto bg-bg-soft rounded-xl border border-bg-hard p-2">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="text-text-muted text-xs">
<th class="text-left py-2 px-2">Date</th>
<th class="text-center py-2 px-2 text-blue" colspan="3">📡 Station locale</th>
<th class="text-center py-2 px-2 text-green border-l-2 border-bg-hard" colspan="4">🌐 Open-Meteo</th>
<th class="text-center py-2 px-2 text-yellow border-l-2 border-bg-hard" colspan="3">🌙 Lunaire</th>
</tr>
<tr class="text-text-muted text-xs border-b border-bg-hard">
<th class="text-left py-1 px-2"></th>
<th class="text-right py-1 px-1">T°min</th>
<th class="text-right py-1 px-1">T°max</th>
<th class="text-right py-1 px-1">💧mm</th>
<th class="text-right py-1 px-1 border-l-2 border-bg-hard">T°min</th>
<th class="text-right py-1 px-1">T°max</th>
<th class="text-right py-1 px-1">💧mm</th>
<th class="text-left py-1 px-2">État</th>
<th class="text-left py-1 px-2 border-l-2 border-bg-hard">Type lune</th>
<th class="text-left py-1 px-2">Mont./Desc.</th>
<th class="text-left py-1 px-2">Type jour</th>
</tr>
</thead>
<tbody>
<tr
v-for="row in tableauRows"
:key="row.date"
@click="selectMeteoDate(row.date)"
:class="[
'border-b border-bg-hard transition-colors cursor-pointer',
row.type === 'passe' ? 'opacity-80' : '',
row.date === selectedMeteoDate ? 'bg-blue/10 border-blue' : '',
]"
>
<td class="py-2 px-2 text-text-muted text-xs whitespace-nowrap">
<span :class="row.type === 'aujourd_hui' ? 'text-green font-bold' : ''">
{{ formatDate(row.date) }}
</span>
</td>
<td class="text-right px-1 text-blue text-xs">{{ stationTMin(row) }}</td>
<td class="text-right px-1 text-orange text-xs">{{ stationTMax(row) }}</td>
<td class="text-right px-1 text-blue text-xs">{{ stationRain(row) }}</td>
<td class="text-right px-1 text-blue text-xs border-l-2 border-bg-hard">{{ omTMin(row) }}</td>
<td class="text-right px-1 text-orange text-xs">{{ omTMax(row) }}</td>
<td class="text-right px-1 text-blue text-xs">{{ omRain(row) }}</td>
<td class="px-2">
<div class="flex items-center gap-1">
<img
v-if="row.open_meteo?.wmo != null"
:src="weatherIcon(row.open_meteo.wmo)"
class="w-5 h-5"
:alt="row.open_meteo.label"
/>
<span class="text-text-muted text-xs">{{ row.open_meteo?.label || '—' }}</span>
</div>
</td>
<td class="px-2 text-xs border-l-2 border-bg-hard">
{{ lunarForDate(row.date)?.croissante_decroissante || '—' }}
</td>
<td class="px-2 text-xs">
{{ lunarForDate(row.date)?.montante_descendante || '—' }}
</td>
<td class="px-2 text-xs" :class="typeColor(lunarForDate(row.date)?.type_jour || '')">
{{ lunarForDate(row.date)?.type_jour || '—' }}
</td>
</tr>
</tbody>
</table>
</div>
<aside class="bg-bg-soft rounded-xl border border-bg-hard p-4">
<div v-if="!selectedMeteoRow" class="text-text-muted text-sm">Sélectionne un jour dans le tableau pour voir le détail.</div>
<div v-else class="space-y-3">
<div>
<h3 class="text-text font-semibold">{{ formatDateLong(selectedMeteoRow.date) }}</h3>
<p class="text-text-muted text-xs">
{{ selectedMeteoRow.type === 'passe' ? 'Historique' : selectedMeteoRow.type === 'aujourd_hui' ? 'Aujourd\'hui' : 'Prévision' }}
</p>
</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 class="pt-2 border-t border-bg-hard">
<div class="text-blue text-xs font-semibold mb-1">📡 Station locale</div>
<div class="text-xs text-text-muted space-y-1">
<div v-if="selectedMeteoRow.station && 'temp_ext' in selectedMeteoRow.station && selectedMeteoRow.station.temp_ext != null">
T° actuelle: <span class="text-text">{{ selectedMeteoRow.station.temp_ext.toFixed(1) }}°</span>
</div>
<div>T° min: <span class="text-text">{{ stationTMin(selectedMeteoRow) }}</span></div>
<div>T° max/actuelle: <span class="text-text">{{ stationTMax(selectedMeteoRow) }}</span></div>
<div>Pluie: <span class="text-text">{{ stationRain(selectedMeteoRow) }}</span></div>
<div v-if="selectedMeteoRow.station && 'vent_kmh' in selectedMeteoRow.station && selectedMeteoRow.station.vent_kmh != null">
Vent max: <span class="text-text">{{ selectedMeteoRow.station.vent_kmh }} km/h</span>
</div>
<div v-if="selectedMeteoRow.station && 'humidite' in selectedMeteoRow.station && selectedMeteoRow.station.humidite != null">
Humidité: <span class="text-text">{{ selectedMeteoRow.station.humidite }}%</span>
</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 class="pt-2 border-t border-bg-hard">
<div class="text-green text-xs font-semibold mb-1">🌐 Open-Meteo</div>
<div class="text-xs text-text-muted space-y-1">
<div>T° min: <span class="text-text">{{ omTMin(selectedMeteoRow) }}</span></div>
<div>T° max: <span class="text-text">{{ omTMax(selectedMeteoRow) }}</span></div>
<div>Pluie: <span class="text-text">{{ omRain(selectedMeteoRow) }}</span></div>
<div>État: <span class="text-text">{{ selectedMeteoRow.open_meteo?.label || '—' }}</span></div>
<div v-if="selectedMeteoRow.open_meteo?.vent_kmh != null">Vent: <span class="text-text">{{ selectedMeteoRow.open_meteo.vent_kmh }} km/h</span></div>
<div v-if="selectedMeteoRow.open_meteo?.sol_0cm != null">Sol 0cm: <span class="text-text">{{ selectedMeteoRow.open_meteo.sol_0cm }}°C</span></div>
<div v-if="selectedMeteoRow.open_meteo?.etp_mm != null">ETP: <span class="text-text">{{ selectedMeteoRow.open_meteo.etp_mm }} mm</span></div>
</div>
</div>
<div class="pt-2 border-t border-bg-hard">
<div class="text-yellow text-xs font-semibold mb-1">🌙 Lunaire</div>
<div v-if="selectedLunarDay" class="text-xs text-text-muted space-y-1">
<div>Type lune: <span class="text-text">{{ selectedLunarDay.croissante_decroissante }}</span></div>
<div>Montante/Descendante: <span class="text-text">{{ selectedLunarDay.montante_descendante }}</span></div>
<div>Type de jour: <span :class="['font-semibold', typeColor(selectedLunarDay.type_jour)]">{{ selectedLunarDay.type_jour }}</span></div>
<div>Signe: <span class="text-text">{{ selectedLunarDay.signe }}</span></div>
<div>Illumination: <span class="text-text">{{ selectedLunarDay.illumination }}%</span></div>
<div>Saint: <span class="text-text">{{ selectedSaint || '—' }}</span></div>
</div>
<div v-else class="text-xs text-text-muted">Donnée lunaire indisponible pour cette date.</div>
</div>
<div class="pt-2 border-t border-bg-hard">
<div class="text-orange text-xs font-semibold mb-1">📜 Dictons</div>
<div v-if="selectedDictons.length" class="space-y-2">
<p v-for="d in selectedDictons" :key="`detail-dicton-${d.id}`" class="text-xs text-text-muted italic">
"{{ d.texte }}"
</p>
</div>
<div v-else class="text-xs text-text-muted">Aucun dicton trouvé pour ce jour.</div>
</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>
</aside>
</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'
import { lunarApi, type Dicton, type LunarDay } from '@/api/lunar'
import { meteoApi, type StationCurrent, type TableauRow } 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 spanDays = 15
const now = new Date()
const currentYear = ref(now.getFullYear())
const currentMonth = ref(now.getMonth() + 1)
const tableauRows = ref<TableauRow[]>([])
const loadingTableau = ref(false)
const stationCurrent = ref<StationCurrent | null>(null)
const lunarByDate = ref<Record<string, LunarDay>>({})
const selectedMeteoDate = ref('')
const monthLabel = computed(() => {
const d = new Date(currentYear.value, currentMonth.value - 1, 1)
return d.toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' })
})
const dictonsByMonth = ref<Record<number, Dicton[]>>({})
const monthStr = computed(() => `${currentYear.value}-${String(currentMonth.value).padStart(2, '0')}`)
const centerDate = ref(todayIso())
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++
const rangeStart = computed(() => shiftIso(centerDate.value, -spanDays))
const rangeEnd = computed(() => shiftIso(centerDate.value, spanDays))
const saintsFallback: Record<string, string> = {
'04-23': 'Saint Georges',
'04-25': 'Saint Marc',
'05-11': 'Saint Mamert',
'05-12': 'Saint Pancrace',
'05-13': 'Saint Servais',
'05-14': 'Saint Boniface',
'05-19': 'Saint Yves',
'05-25': 'Saint Urbain',
}
// 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
const selectedMeteoRow = computed(() => tableauRows.value.find((r) => r.date === selectedMeteoDate.value) || null)
const selectedLunarDay = computed(() => lunarByDate.value[selectedMeteoDate.value] || null)
const currentOpenMeteo = computed(() => {
const today = tableauRows.value.find((r) => r.type === 'aujourd_hui')
return today?.open_meteo || null
})
async function loadLunar() {
loadingLunar.value = true; errorLunar.value = ''; selectedDay.value = null
const selectedSaint = computed(() => {
if (!selectedMeteoDate.value) return ''
if (selectedLunarDay.value?.saint_du_jour) return selectedLunarDay.value.saint_du_jour
const mmdd = selectedMeteoDate.value.slice(5)
return saintsFallback[mmdd] || ''
})
const selectedDictons = computed(() => {
if (!selectedMeteoDate.value) return []
const month = monthFromIso(selectedMeteoDate.value)
const day = dayFromIso(selectedMeteoDate.value)
const rows = dictonsByMonth.value[month] || []
const exact = rows.filter((d) => d.jour === day)
if (exact.length) return exact
return rows.filter((d) => d.jour == null).slice(0, 3)
})
function todayIso(): string {
const d = new Date()
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}
function shiftIso(isoDate: string, days: number): string {
const d = new Date(`${isoDate}T12:00:00`)
d.setDate(d.getDate() + days)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}
function shiftWindow(days: number) {
centerDate.value = shiftIso(centerDate.value, days)
}
function goToday() {
centerDate.value = todayIso()
}
function monthFromIso(isoDate: string): number {
return Number(isoDate.slice(5, 7))
}
function dayFromIso(isoDate: string): number {
return Number(isoDate.slice(8, 10))
}
function selectMeteoDate(isoDate: string) {
selectedMeteoDate.value = isoDate
void ensureDictonsMonth(monthFromIso(isoDate))
}
async function ensureDictonsMonth(month: number) {
if (!month || dictonsByMonth.value[month]) return
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.'
const data = await lunarApi.getDictons(month)
dictonsByMonth.value = { ...dictonsByMonth.value, [month]: data }
} catch {
dictonsByMonth.value = { ...dictonsByMonth.value, [month]: [] }
}
}
async function loadLunarForTableau() {
const months = Array.from(new Set(tableauRows.value.map((r) => r.date.slice(0, 7))))
const map: Record<string, LunarDay> = {}
for (const month of months) {
try {
const days = await lunarApi.getMonth(month)
for (const d of days) map[d.date] = d
} catch {
// Mois indisponible: on laisse les cellules lunaires vides
}
}
lunarByDate.value = map
}
async function loadTableau() {
loadingTableau.value = true
try {
const res = await meteoApi.getTableau({
center_date: centerDate.value,
span: spanDays,
})
tableauRows.value = res.rows || []
await loadLunarForTableau()
const selectedStillVisible = tableauRows.value.some((r) => r.date === selectedMeteoDate.value)
if (selectedStillVisible) return
const todayRow = tableauRows.value.find((r) => r.type === 'aujourd_hui')
if (todayRow) {
selectMeteoDate(todayRow.date)
} else if (tableauRows.value.length) {
selectMeteoDate(tableauRows.value[0].date)
}
} catch {
tableauRows.value = []
lunarByDate.value = {}
} finally {
loadingLunar.value = false
loadingTableau.value = false
}
}
// Météo
const meteoData = ref<MeteoDay[]>([])
const loadingMeteo = ref(false)
async function loadMeteo() {
loadingMeteo.value = true
async function loadStationCurrent() {
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'
stationCurrent.value = await meteoApi.getStationCurrent()
} catch {
stationCurrent.value = null
}
return `/icons/moon/${name}.svg`
}
function lunarForDate(isoDate: string): LunarDay | null {
return lunarByDate.value[isoDate] || null
}
function stationTMin(row: TableauRow): string {
if (row.station && 't_min' in row.station && row.station.t_min != null) return `${row.station.t_min.toFixed(1)}°`
return '—'
}
function stationTMax(row: TableauRow): string {
if (row.station && 't_max' in row.station && row.station.t_max != null) return `${row.station.t_max.toFixed(1)}°`
if (row.type === 'aujourd_hui' && row.station && 'temp_ext' in row.station && row.station.temp_ext != null) {
return `${row.station.temp_ext.toFixed(1)}° act.`
}
return '—'
}
function stationRain(row: TableauRow): string {
if (row.station && row.station.pluie_mm != null) return String(row.station.pluie_mm)
return '—'
}
function omTMin(row: TableauRow): string {
return row.open_meteo?.t_min != null ? `${row.open_meteo.t_min.toFixed(1)}°` : '—'
}
function omTMax(row: TableauRow): string {
return row.open_meteo?.t_max != null ? `${row.open_meteo.t_max.toFixed(1)}°` : '—'
}
function omRain(row: TableauRow): string {
return row.open_meteo?.pluie_mm != null ? String(row.open_meteo.pluie_mm) : '—'
}
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
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' })
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()
function formatDateLong(dateStr: string): string {
return new Date(`${dateStr}T12:00:00`).toLocaleDateString('fr-FR', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
})
}
watch(centerDate, () => {
void loadTableau()
})
watch(activeTab, (tab) => {
if (tab === 'lunaire' && !lunarDays.value.length) loadLunar()
if (tab === 'meteo' && !meteoData.value.length) loadMeteo()
if (tab === 'dictons' && !dictons.value.length) loadDictons()
watch(selectedMeteoDate, (iso) => {
if (!iso) return
void ensureDictonsMonth(monthFromIso(iso))
})
onMounted(() => { loadLunar(); loadMeteo() })
onMounted(() => {
void loadTableau()
void loadStationCurrent()
})
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<template>
<div class="p-4 max-w-2xl mx-auto">
<div class="p-4 max-w-6xl mx-auto">
<h1 class="text-2xl font-bold text-green mb-6">Tableau de bord</h1>
<section class="mb-6">
@@ -24,19 +24,46 @@
</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>
<h2 class="text-text-muted text-xs uppercase tracking-widest mb-3">Météo</h2>
<div v-if="stationCurrent || meteo7j.length" class="bg-bg-soft rounded-xl p-3 border border-bg-hard mb-3">
<div class="text-text-muted text-xs mb-1">Condition actuelle</div>
<div class="flex items-center gap-3">
<img
v-if="meteoCurrent?.wmo != null"
:src="weatherIcon(meteoCurrent.wmo)"
class="w-8 h-8"
:alt="meteoCurrent.label || 'Météo'"
/>
<div class="text-sm text-text">
<div>{{ meteoCurrent?.label || '—' }}</div>
<div class="text-text-muted text-xs">
{{ stationCurrent?.temp_ext != null ? `${stationCurrent.temp_ext.toFixed(1)}°C` : 'Temp. indisponible' }}
<span v-if="stationCurrent?.date_heure"> · relevé {{ stationCurrent.date_heure.slice(11, 16) }}</span>
</div>
</div>
</div>
</div>
<div v-if="meteo7j.length" class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-7 gap-2">
<div v-for="day in meteo7j" :key="day.date"
class="bg-bg-soft rounded-xl p-3 border border-bg-hard flex flex-col items-center gap-1 min-w-0">
<div class="text-text-muted text-xs">{{ formatDate(day.date || '') }}</div>
<img
v-if="day.wmo != null"
:src="weatherIcon(day.wmo)"
class="w-8 h-8"
:alt="day.label || 'Météo'"
/>
<div v-else class="text-2xl"></div>
<div class="text-[11px] text-center text-text-muted leading-tight min-h-[30px]">{{ day.label || '—' }}</div>
<div class="flex gap-1 text-xs">
<span class="text-orange">{{ day.t_max != null ? day.t_max.toFixed(0) : '—' }}°</span>
<span class="text-blue">{{ day.t_min != null ? day.t_min.toFixed(0) : '—' }}°</span>
</div>
</div>
</div>
<div v-else class="text-text-muted text-sm py-2">Prévisions indisponibles.</div>
</section>
<section>
@@ -60,22 +87,34 @@ 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'
import { meteoApi, type OpenMeteoDay, type StationCurrent } 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<MeteoDay[]>([])
const meteo7j = ref<OpenMeteoDay[]>([])
const stationCurrent = ref<StationCurrent | null>(null)
const meteoCurrent = computed(() => meteo7j.value[0] || null)
function formatDate(s: string) {
if (!s) return '—'
return new Date(s + 'T12:00:00').toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' })
}
function weatherIcon(code: number): string {
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`
}
onMounted(async () => {
gardensStore.fetchAll()
tasksStore.fetchAll()
try { const r = await meteoApi.getForecast(3); meteo3j.value = r.days.slice(0, 3) } catch {}
try { stationCurrent.value = await meteoApi.getStationCurrent() } catch { stationCurrent.value = null }
try { const r = await meteoApi.getPrevisions(7); meteo7j.value = r.days.slice(0, 7) } catch { meteo7j.value = [] }
})
</script>

View File

@@ -8,25 +8,42 @@ 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([]);
const meteo7j = ref([]);
const stationCurrent = ref(null);
const meteoCurrent = computed(() => meteo7j.value[0] || null);
function formatDate(s) {
if (!s)
return '—';
return new Date(s + 'T12:00:00').toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
}
function weatherIcon(code) {
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`;
}
onMounted(async () => {
gardensStore.fetchAll();
tasksStore.fetchAll();
try {
const r = await meteoApi.getForecast(3);
meteo3j.value = r.days.slice(0, 3);
stationCurrent.value = await meteoApi.getStationCurrent();
}
catch {
stationCurrent.value = null;
}
try {
const r = await meteoApi.getPrevisions(7);
meteo7j.value = r.days.slice(0, 7);
}
catch {
meteo7j.value = [];
}
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 max-w-2xl mx-auto" },
...{ class: "p-4 max-w-6xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green mb-6" },
@@ -71,37 +88,83 @@ __VLS_asFunctionalElement(__VLS_intrinsicElements.section, __VLS_intrinsicElemen
__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))) {
if (__VLS_ctx.stationCurrent || __VLS_ctx.meteo7j.length) {
__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]" },
...{ class: "bg-bg-soft rounded-xl p-3 border border-bg-hard mb-3" },
});
__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: "flex items-center gap-3" },
});
if (__VLS_ctx.meteoCurrent?.wmo != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (__VLS_ctx.weatherIcon(__VLS_ctx.meteoCurrent.wmo)),
...{ class: "w-8 h-8" },
alt: (__VLS_ctx.meteoCurrent.label || 'Météo'),
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-sm text-text" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
(__VLS_ctx.meteoCurrent?.label || '—');
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs" },
});
(__VLS_ctx.formatDate(day.date));
(__VLS_ctx.stationCurrent?.temp_ext != null ? `${__VLS_ctx.stationCurrent.temp_ext.toFixed(1)}°C` : 'Temp. indisponible');
if (__VLS_ctx.stationCurrent?.date_heure) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(__VLS_ctx.stationCurrent.date_heure.slice(11, 16));
}
}
if (__VLS_ctx.meteo7j.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-2xl" },
...{ class: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-7 gap-2" },
});
(day.icone);
for (const [day] of __VLS_getVForSourceType((__VLS_ctx.meteo7j))) {
__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-0" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs" },
});
(__VLS_ctx.formatDate(day.date || ''));
if (day.wmo != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (__VLS_ctx.weatherIcon(day.wmo)),
...{ class: "w-8 h-8" },
alt: (day.label || 'Météo'),
});
}
else {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-2xl" },
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-[11px] text-center text-text-muted leading-tight min-h-[30px]" },
});
(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 != null ? day.t_max.toFixed(0) : '—');
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-blue" },
});
(day.t_min != null ? day.t_min.toFixed(0) : '—');
}
}
else {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-xs text-center text-text-muted" },
...{ class: "text-text-muted text-sm py-2" },
});
(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)({
@@ -130,7 +193,7 @@ for (const [g] of __VLS_getVForSourceType((__VLS_ctx.gardensStore.gardens))) {
(g.type);
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-6xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
@@ -167,9 +230,29 @@ for (const [g] of __VLS_getVForSourceType((__VLS_ctx.gardensStore.gardens))) {
/** @type {__VLS_StyleScopedClasses['uppercase']} */ ;
/** @type {__VLS_StyleScopedClasses['tracking-widest']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-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['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['w-8']} */ ;
/** @type {__VLS_StyleScopedClasses['h-8']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['sm:grid-cols-3']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:grid-cols-7']} */ ;
/** @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']} */ ;
@@ -179,19 +262,26 @@ for (const [g] of __VLS_getVForSourceType((__VLS_ctx.gardensStore.gardens))) {
/** @type {__VLS_StyleScopedClasses['flex-col']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['min-w-[90px]']} */ ;
/** @type {__VLS_StyleScopedClasses['min-w-0']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['w-8']} */ ;
/** @type {__VLS_StyleScopedClasses['h-8']} */ ;
/** @type {__VLS_StyleScopedClasses['text-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-[11px]']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['leading-tight']} */ ;
/** @type {__VLS_StyleScopedClasses['min-h-[30px]']} */ ;
/** @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-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['uppercase']} */ ;
/** @type {__VLS_StyleScopedClasses['tracking-widest']} */ ;
@@ -224,8 +314,11 @@ const __VLS_self = (await import('vue')).defineComponent({
gardensStore: gardensStore,
tasksStore: tasksStore,
pendingTasks: pendingTasks,
meteo3j: meteo3j,
meteo7j: meteo7j,
stationCurrent: stationCurrent,
meteoCurrent: meteoCurrent,
formatDate: formatDate,
weatherIcon: weatherIcon,
};
},
});

View File

@@ -7,8 +7,38 @@
<p class="text-text-muted text-sm mb-6">
{{ garden.type }} · {{ garden.exposition ?? 'exposition non définie' }}
<span v-if="garden.sol_type"> · Sol : {{ garden.sol_type }}</span>
<span v-if="garden.surface_m2 != null"> · {{ garden.surface_m2 }} m²</span>
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-6">
<div class="bg-bg-soft border border-bg-hard rounded-lg p-3 text-sm">
<div class="text-text-muted text-xs mb-1">Dimensions</div>
<div class="text-text">
<span v-if="garden.longueur_m != null && garden.largeur_m != null">
{{ garden.longueur_m }} m × {{ garden.largeur_m }} m
</span>
<span v-else>Non renseignées</span>
</div>
</div>
<div class="bg-bg-soft border border-bg-hard rounded-lg p-3 text-sm">
<div class="text-text-muted text-xs mb-1">Géolocalisation</div>
<div class="text-text">
<span v-if="garden.latitude != null && garden.longitude != null">
{{ garden.latitude }}, {{ garden.longitude }}
<span v-if="garden.altitude != null"> · {{ garden.altitude }} m</span>
</span>
<span v-else>Non renseignée</span>
</div>
<div v-if="garden.adresse" class="text-text-muted text-xs mt-1">{{ garden.adresse }}</div>
</div>
</div>
<div v-if="garden.photo_parcelle" class="mb-6">
<div class="text-text-muted text-xs uppercase tracking-widest mb-2">Photo parcelle</div>
<img :src="garden.photo_parcelle" alt="Photo parcelle"
class="w-full max-h-72 object-cover rounded-lg border border-bg-hard bg-bg-soft" />
</div>
<h2 class="text-text-muted text-xs uppercase tracking-widest mb-3">
Grille {{ garden.grille_largeur }}×{{ garden.grille_hauteur }}
</h2>

View File

@@ -55,6 +55,70 @@ if (__VLS_ctx.garden) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(__VLS_ctx.garden.sol_type);
}
if (__VLS_ctx.garden.surface_m2 != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(__VLS_ctx.garden.surface_m2);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-1 md:grid-cols-2 gap-3 mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg-soft border border-bg-hard rounded-lg p-3 text-sm" },
});
__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" },
});
if (__VLS_ctx.garden.longueur_m != null && __VLS_ctx.garden.largeur_m != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(__VLS_ctx.garden.longueur_m);
(__VLS_ctx.garden.largeur_m);
}
else {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "bg-bg-soft border border-bg-hard rounded-lg p-3 text-sm" },
});
__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" },
});
if (__VLS_ctx.garden.latitude != null && __VLS_ctx.garden.longitude != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(__VLS_ctx.garden.latitude);
(__VLS_ctx.garden.longitude);
if (__VLS_ctx.garden.altitude != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(__VLS_ctx.garden.altitude);
}
}
else {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
}
if (__VLS_ctx.garden.adresse) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs mt-1" },
});
(__VLS_ctx.garden.adresse);
}
if (__VLS_ctx.garden.photo_parcelle) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mb-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs uppercase tracking-widest mb-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (__VLS_ctx.garden.photo_parcelle),
alt: "Photo parcelle",
...{ class: "w-full max-h-72 object-cover rounded-lg border border-bg-hard bg-bg-soft" },
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text-muted text-xs uppercase tracking-widest mb-3" },
});
@@ -96,6 +160,47 @@ else {
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-1']} */ ;
/** @type {__VLS_StyleScopedClasses['md:grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @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-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
/** @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['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['max-h-72']} */ ;
/** @type {__VLS_StyleScopedClasses['object-cover']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['uppercase']} */ ;

View File

@@ -14,6 +14,11 @@
<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>
<span v-if="g.carre_potager && g.carre_x_cm != null && g.carre_y_cm != null">
· Carré potager {{ g.carre_x_cm }}×{{ g.carre_y_cm }} cm
</span>
<span v-if="g.longueur_m != null && g.largeur_m != null"> · {{ g.longueur_m }}×{{ g.largeur_m }} m</span>
<span v-if="g.surface_m2 != null"> · {{ g.surface_m2 }} m²</span>
</div>
</div>
<button @click="startEdit(g)" class="text-yellow text-xs hover:underline px-2">Édit.</button>
@@ -26,15 +31,15 @@
<!-- 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">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-4xl 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">
<form @submit.prevent="submit" class="grid grid-cols-1 lg:grid-cols-2 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>
<div class="lg:col-span-2">
<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" />
@@ -48,7 +53,26 @@
<option value="bac">Bac / Pot</option>
</select>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="bg-bg rounded border border-bg-hard p-3 lg:col-span-2">
<label class="inline-flex items-center gap-2 text-sm text-text">
<input v-model="form.carre_potager" type="checkbox" class="accent-green" />
Carré potager
</label>
<p class="text-text-muted text-[11px] mt-1">Active les dimensions X/Y en centimètres pour un bac carré.</p>
</div>
<div v-if="form.carre_potager" class="grid grid-cols-1 sm:grid-cols-2 gap-3 lg:col-span-2">
<div>
<label class="text-text-muted text-xs block mb-1">Dimension X (cm)</label>
<input v-model.number="form.carre_x_cm" type="number" min="1" step="1"
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">Dimension Y (cm)</label>
<input v-model.number="form.carre_y_cm" type="number" min="1" step="1"
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-1 sm: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"
@@ -60,7 +84,55 @@
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 class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 lg:col-span-2">
<div>
<label class="text-text-muted text-xs block mb-1">Longueur (m)</label>
<input v-model.number="form.longueur_m" type="number" min="0" step="0.1"
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">Largeur (m)</label>
<input v-model.number="form.largeur_m" type="number" min="0" step="0.1"
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">Surface ()</label>
<input v-model.number="form.surface_m2" type="number" min="0" step="0.1"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" />
</div>
</div>
<div class="lg:col-span-2">
<label class="text-text-muted text-xs block mb-1">Photo parcelle (image)</label>
<input type="file" accept="image/*" @change="onPhotoSelected"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" />
<div v-if="photoPreview" class="mt-2">
<img :src="photoPreview" alt="Prévisualisation parcelle"
class="w-full max-h-44 object-cover rounded border border-bg-hard bg-bg-soft" />
</div>
</div>
<div class="lg:col-span-2">
<label class="text-text-muted text-xs block mb-1">Adresse / localisation</label>
<input v-model="form.adresse"
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-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 lg:col-span-2">
<div>
<label class="text-text-muted text-xs block mb-1">Latitude</label>
<input v-model.number="form.latitude" type="number" step="0.000001"
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">Longitude</label>
<input v-model.number="form.longitude" type="number" step="0.000001"
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">Altitude (m)</label>
<input v-model.number="form.altitude" type="number" step="1"
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-1 sm:grid-cols-2 gap-3 lg:col-span-2">
<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">
@@ -86,7 +158,7 @@
</select>
</div>
</div>
<div class="flex gap-2 mt-2">
<div class="flex gap-2 mt-2 lg:col-span-2">
<button type="submit" class="bg-green text-bg px-4 py-2 rounded text-sm font-semibold">
{{ editId ? 'Enregistrer' : 'Créer' }}
</button>
@@ -102,16 +174,28 @@
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useGardensStore } from '@/stores/gardens'
import type { Garden } from '@/api/gardens'
import { gardensApi, type Garden } from '@/api/gardens'
const router = useRouter()
const store = useGardensStore()
const showForm = ref(false)
const editId = ref<number | null>(null)
const photoFile = ref<File | null>(null)
const photoPreview = ref('')
const form = reactive({
nom: '', description: '', type: 'plein_air',
grille_largeur: 6, grille_hauteur: 4,
carre_potager: false,
carre_x_cm: undefined as number | undefined,
carre_y_cm: undefined as number | undefined,
longueur_m: undefined as number | undefined,
largeur_m: undefined as number | undefined,
surface_m2: undefined as number | undefined,
latitude: undefined as number | undefined,
longitude: undefined as number | undefined,
altitude: undefined as number | undefined,
adresse: '',
exposition: '', sol_type: '',
})
@@ -121,7 +205,16 @@ function typeLabel(t: string) {
function openCreate() {
editId.value = null
Object.assign(form, { nom: '', description: '', type: 'plein_air', grille_largeur: 6, grille_hauteur: 4, exposition: '', sol_type: '' })
Object.assign(form, {
nom: '', description: '', type: 'plein_air',
grille_largeur: 6, grille_hauteur: 4,
carre_potager: false, carre_x_cm: undefined, carre_y_cm: undefined,
longueur_m: undefined, largeur_m: undefined, surface_m2: undefined,
latitude: undefined, longitude: undefined, altitude: undefined, adresse: '',
exposition: '', sol_type: '',
})
photoFile.value = null
photoPreview.value = ''
showForm.value = true
}
@@ -130,18 +223,73 @@ function startEdit(g: Garden) {
Object.assign(form, {
nom: g.nom, description: g.description || '',
type: g.type, grille_largeur: g.grille_largeur, grille_hauteur: g.grille_hauteur,
carre_potager: !!g.carre_potager, carre_x_cm: g.carre_x_cm, carre_y_cm: g.carre_y_cm,
longueur_m: g.longueur_m, largeur_m: g.largeur_m, surface_m2: g.surface_m2,
latitude: g.latitude, longitude: g.longitude, altitude: g.altitude, adresse: g.adresse || '',
exposition: g.exposition || '', sol_type: g.sol_type || '',
})
photoFile.value = null
photoPreview.value = g.photo_parcelle || ''
showForm.value = true
}
function closeForm() { showForm.value = false; editId.value = null }
function onPhotoSelected(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0] || null
photoFile.value = file
if (file) {
photoPreview.value = URL.createObjectURL(file)
}
}
async function submit() {
const autoLongueur =
form.carre_potager && form.carre_x_cm != null
? Number((form.carre_x_cm / 100).toFixed(2))
: form.longueur_m
const autoLargeur =
form.carre_potager && form.carre_y_cm != null
? Number((form.carre_y_cm / 100).toFixed(2))
: form.largeur_m
const inferredSurface =
form.surface_m2 ??
((autoLongueur != null && autoLargeur != null)
? Number((autoLongueur * autoLargeur).toFixed(2))
: undefined)
const payload: Partial<Garden> = {
nom: form.nom,
description: form.description || undefined,
type: form.type,
grille_largeur: form.grille_largeur,
grille_hauteur: form.grille_hauteur,
carre_potager: form.carre_potager,
carre_x_cm: form.carre_potager ? form.carre_x_cm : undefined,
carre_y_cm: form.carre_potager ? form.carre_y_cm : undefined,
longueur_m: autoLongueur,
largeur_m: autoLargeur,
surface_m2: inferredSurface,
latitude: form.latitude,
longitude: form.longitude,
altitude: form.altitude,
adresse: form.adresse || undefined,
exposition: form.exposition || undefined,
sol_type: form.sol_type || undefined,
}
let saved: Garden
if (editId.value) {
await store.update(editId.value, { ...form })
saved = await store.update(editId.value, payload)
} else {
await store.create({ ...form })
saved = await store.create(payload)
}
if (photoFile.value && saved.id) {
await gardensApi.uploadPhoto(saved.id, photoFile.value)
await store.fetchAll()
}
closeForm()
}

View File

@@ -2,16 +2,112 @@
import { onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useGardensStore } from '@/stores/gardens';
import { gardensApi } 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 });
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 });
const editId = ref(null);
const photoFile = ref(null);
const photoPreview = ref('');
const form = reactive({
nom: '', description: '', type: 'plein_air',
grille_largeur: 6, grille_hauteur: 4,
carre_potager: false,
carre_x_cm: undefined,
carre_y_cm: undefined,
longueur_m: undefined,
largeur_m: undefined,
surface_m2: undefined,
latitude: undefined,
longitude: undefined,
altitude: undefined,
adresse: '',
exposition: '', sol_type: '',
});
function typeLabel(t) {
return { plein_air: 'Plein air', serre: 'Serre', tunnel: 'Tunnel', bac: 'Bac/Pot' }[t] ?? t;
}
function openCreate() {
editId.value = null;
Object.assign(form, {
nom: '', description: '', type: 'plein_air',
grille_largeur: 6, grille_hauteur: 4,
carre_potager: false, carre_x_cm: undefined, carre_y_cm: undefined,
longueur_m: undefined, largeur_m: undefined, surface_m2: undefined,
latitude: undefined, longitude: undefined, altitude: undefined, adresse: '',
exposition: '', sol_type: '',
});
photoFile.value = null;
photoPreview.value = '';
showForm.value = true;
}
function startEdit(g) {
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,
carre_potager: !!g.carre_potager, carre_x_cm: g.carre_x_cm, carre_y_cm: g.carre_y_cm,
longueur_m: g.longueur_m, largeur_m: g.largeur_m, surface_m2: g.surface_m2,
latitude: g.latitude, longitude: g.longitude, altitude: g.altitude, adresse: g.adresse || '',
exposition: g.exposition || '', sol_type: g.sol_type || '',
});
photoFile.value = null;
photoPreview.value = g.photo_parcelle || '';
showForm.value = true;
}
function closeForm() { showForm.value = false; editId.value = null; }
function onPhotoSelected(event) {
const input = event.target;
const file = input.files?.[0] || null;
photoFile.value = file;
if (file) {
photoPreview.value = URL.createObjectURL(file);
}
}
async function submit() {
const autoLongueur = form.carre_potager && form.carre_x_cm != null
? Number((form.carre_x_cm / 100).toFixed(2))
: form.longueur_m;
const autoLargeur = form.carre_potager && form.carre_y_cm != null
? Number((form.carre_y_cm / 100).toFixed(2))
: form.largeur_m;
const inferredSurface = form.surface_m2 ??
((autoLongueur != null && autoLargeur != null)
? Number((autoLongueur * autoLargeur).toFixed(2))
: undefined);
const payload = {
nom: form.nom,
description: form.description || undefined,
type: form.type,
grille_largeur: form.grille_largeur,
grille_hauteur: form.grille_hauteur,
carre_potager: form.carre_potager,
carre_x_cm: form.carre_potager ? form.carre_x_cm : undefined,
carre_y_cm: form.carre_potager ? form.carre_y_cm : undefined,
longueur_m: autoLongueur,
largeur_m: autoLargeur,
surface_m2: inferredSurface,
latitude: form.latitude,
longitude: form.longitude,
altitude: form.altitude,
adresse: form.adresse || undefined,
exposition: form.exposition || undefined,
sol_type: form.sol_type || undefined,
};
let saved;
if (editId.value) {
saved = await store.update(editId.value, payload);
}
else {
saved = await store.create(payload);
}
if (photoFile.value && saved.id) {
await gardensApi.uploadPhoto(saved.id, photoFile.value);
await store.fetchAll();
}
closeForm();
}
onMounted(() => store.fetchAll());
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
let __VLS_components;
@@ -26,18 +122,86 @@ __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" },
...{ onClick: (__VLS_ctx.openCreate) },
...{ 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" },
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)({
key: (g.id),
...{ class: "bg-bg-soft rounded-lg p-4 mb-3 border border-bg-hard flex items-center gap-3 group" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid gap-3" },
...{ onClick: (...[$event]) => {
__VLS_ctx.router.push(`/jardins/${g.id}`);
} },
...{ class: "flex-1 cursor-pointer" },
});
__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" },
});
(__VLS_ctx.typeLabel(g.type));
(g.grille_largeur);
(g.grille_hauteur);
if (g.exposition) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(g.exposition);
}
if (g.carre_potager && g.carre_x_cm != null && g.carre_y_cm != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(g.carre_x_cm);
(g.carre_y_cm);
}
if (g.longueur_m != null && g.largeur_m != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(g.longueur_m);
(g.largeur_m);
}
if (g.surface_m2 != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(g.surface_m2);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.startEdit(g);
} },
...{ class: "text-yellow text-xs hover:underline px-2" },
});
__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" },
});
}
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" },
});
}
if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (__VLS_ctx.closeForm) },
...{ 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-4xl border border-bg-soft max-h-[90vh] overflow-y-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text font-bold text-lg mb-4" },
});
(__VLS_ctx.editId ? 'Modifier le jardin' : 'Nouveau jardin');
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (__VLS_ctx.submit) },
...{ class: "grid grid-cols-1 lg:grid-cols-2 gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
@@ -48,6 +212,17 @@ if (__VLS_ctx.showForm) {
...{ 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)({
...{ class: "lg:col-span-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.textarea)({
value: (__VLS_ctx.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" },
});
__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" },
@@ -65,8 +240,52 @@ if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "tunnel",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "bac",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-2 gap-3" },
...{ class: "bg-bg rounded border border-bg-hard p-3 lg:col-span-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "inline-flex items-center gap-2 text-sm text-text" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "checkbox",
...{ class: "accent-green" },
});
(__VLS_ctx.form.carre_potager);
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-[11px] mt-1" },
});
if (__VLS_ctx.form.carre_potager) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-1 sm:grid-cols-2 gap-3 lg:col-span-2" },
});
__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",
step: "1",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
(__VLS_ctx.form.carre_x_cm);
__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",
step: "1",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
(__VLS_ctx.form.carre_y_cm);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-1 sm:grid-cols-2 gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
@@ -75,7 +294,7 @@ if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "number",
min: "1",
max: "20",
max: "30",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
(__VLS_ctx.form.grille_largeur);
@@ -86,65 +305,186 @@ if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "number",
min: "1",
max: "20",
max: "30",
...{ 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" },
...{ class: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 lg:col-span-2" },
});
__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: "0",
step: "0.1",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
(__VLS_ctx.form.longueur_m);
__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: "0",
step: "0.1",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
(__VLS_ctx.form.largeur_m);
__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: "0",
step: "0.1",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
(__VLS_ctx.form.surface_m2);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "lg:col-span-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
...{ onChange: (__VLS_ctx.onPhotoSelected) },
type: "file",
accept: "image/*",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" },
});
if (__VLS_ctx.photoPreview) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (__VLS_ctx.photoPreview),
alt: "Prévisualisation parcelle",
...{ class: "w-full max-h-44 object-cover rounded border border-bg-hard bg-bg-soft" },
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "lg:col-span-2" },
});
__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.adresse);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 lg:col-span-2" },
});
__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.000001",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
(__VLS_ctx.form.latitude);
__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.000001",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
(__VLS_ctx.form.longitude);
__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: "1",
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm" },
});
(__VLS_ctx.form.altitude);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-1 sm:grid-cols-2 gap-3 lg:col-span-2" },
});
__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.exposition),
...{ 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: "Nord",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "Est",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "Sud",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "Ouest",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "Sud-Est",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "Sud-Ouest",
});
__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.sol_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: "",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "argileux",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "limoneux",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "sableux",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "calcaire",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "humifère",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "mixte",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 mt-2 lg:col-span-2" },
});
__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_ctx.editId ? 'Enregistrer' : 'Créer');
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
__VLS_ctx.showForm = false;
} },
...{ onClick: (__VLS_ctx.closeForm) },
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']} */ ;
@@ -163,14 +503,63 @@ if (!__VLS_ctx.store.loading && !__VLS_ctx.store.gardens.length) {
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
/** @type {__VLS_StyleScopedClasses['transition-opacity']} */ ;
/** @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-6']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-green/30']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['group']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['cursor-pointer']} */ ;
/** @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-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @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['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['py-8']} */ ;
/** @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-4xl']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['max-h-[90vh]']} */ ;
/** @type {__VLS_StyleScopedClasses['overflow-y-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
/** @type {__VLS_StyleScopedClasses['text-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-1']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
@@ -187,6 +576,69 @@ if (!__VLS_ctx.store.loading && !__VLS_ctx.store.gardens.length) {
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:col-span-2']} */ ;
/** @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['resize-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['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['p-3']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:col-span-2']} */ ;
/** @type {__VLS_StyleScopedClasses['inline-flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['accent-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-[11px]']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-1']} */ ;
/** @type {__VLS_StyleScopedClasses['sm:grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:col-span-2']} */ ;
/** @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']} */ ;
@@ -201,7 +653,8 @@ if (!__VLS_ctx.store.loading && !__VLS_ctx.store.gardens.length) {
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-1']} */ ;
/** @type {__VLS_StyleScopedClasses['sm:grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
@@ -229,9 +682,171 @@ if (!__VLS_ctx.store.loading && !__VLS_ctx.store.gardens.length) {
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @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['lg:col-span-2']} */ ;
/** @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['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['lg:col-span-2']} */ ;
/** @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['mt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['max-h-44']} */ ;
/** @type {__VLS_StyleScopedClasses['object-cover']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:col-span-2']} */ ;
/** @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-1']} */ ;
/** @type {__VLS_StyleScopedClasses['sm:grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:grid-cols-3']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:col-span-2']} */ ;
/** @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['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-1']} */ ;
/** @type {__VLS_StyleScopedClasses['sm:grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:col-span-2']} */ ;
/** @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['mt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['lg:col-span-2']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
@@ -244,41 +859,6 @@ if (!__VLS_ctx.store.loading && !__VLS_ctx.store.gardens.length) {
/** @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() {
@@ -286,7 +866,14 @@ const __VLS_self = (await import('vue')).defineComponent({
router: router,
store: store,
showForm: showForm,
editId: editId,
photoPreview: photoPreview,
form: form,
typeLabel: typeLabel,
openCreate: openCreate,
startEdit: startEdit,
closeForm: closeForm,
onPhotoSelected: onPhotoSelected,
submit: submit,
};
},

View File

@@ -2,7 +2,7 @@
<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>
<button @click="openCreate" 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>
@@ -19,11 +19,28 @@
</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>
<p v-if="t.boutique_nom || t.prix_achat != null" class="text-text-muted text-xs">
<span v-if="t.boutique_nom">🛒 {{ t.boutique_nom }}</span>
<span v-if="t.prix_achat != null"> · 💶 {{ t.prix_achat }} </span>
</p>
<a v-if="t.boutique_url" :href="t.boutique_url" target="_blank" rel="noopener noreferrer"
class="text-blue text-xs hover:underline truncate">🔗 Boutique</a>
<a v-if="t.video_url" :href="t.video_url" target="_blank" rel="noopener noreferrer"
class="text-aqua text-xs hover:underline truncate">🎬 Vidéo</a>
<a v-if="t.notice_fichier_url" :href="t.notice_fichier_url" target="_blank" rel="noopener noreferrer"
class="text-aqua text-xs hover:underline truncate">📄 Notice</a>
<div v-if="t.photo_url || t.video_url" class="mt-auto pt-2 space-y-2">
<img v-if="t.photo_url" :src="t.photo_url" alt="photo outil"
class="w-full h-28 object-cover rounded border border-bg-hard bg-bg" />
<video v-if="t.video_url" :src="t.video_url" controls muted
class="w-full h-36 object-cover rounded border border-bg-hard bg-bg" />
</div>
</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">
<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 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
@@ -35,11 +52,41 @@
<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>
<option value="taille">Taille</option>
<option value="autre">Autre</option>
</select>
<div class="grid grid-cols-2 gap-3">
<input v-model="form.boutique_nom" placeholder="Nom boutique"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" />
<input v-model.number="form.prix_achat" type="number" min="0" step="0.01" placeholder="Prix achat (€)"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" />
</div>
<input v-model="form.boutique_url" type="url" placeholder="URL boutique (https://...)"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" />
<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>
<label class="text-text-muted text-xs block mb-1">Photo de l'outil</label>
<input type="file" accept="image/*" @change="onPhotoSelected"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" />
<img v-if="photoPreview" :src="photoPreview" alt="Prévisualisation photo"
class="mt-2 w-full h-28 object-cover rounded border border-bg-hard bg-bg" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Vidéo de l'outil</label>
<input type="file" accept="video/*" @change="onVideoSelected"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" />
<video v-if="videoPreview" :src="videoPreview" controls muted
class="mt-2 w-full h-36 object-cover rounded border border-bg-hard bg-bg" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Notice (fichier texte)</label>
<input type="file" accept=".txt,.md,text/plain" @change="onNoticeSelected"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" />
<div v-if="noticeFileName || form.notice_fichier_url" class="text-text-muted text-xs mt-1 truncate">
{{ noticeFileName || fileNameFromUrl(form.notice_fichier_url || '') }}
</div>
</div>
<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">
@@ -54,29 +101,152 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import axios from 'axios'
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: '' })
const photoFile = ref<File | null>(null)
const videoFile = ref<File | null>(null)
const noticeFile = ref<File | null>(null)
const photoPreview = ref('')
const videoPreview = ref('')
const noticeFileName = ref('')
const form = reactive({
nom: '',
categorie: '',
description: '',
boutique_nom: '',
boutique_url: '',
prix_achat: undefined as number | undefined,
photo_url: '',
video_url: '',
notice_fichier_url: '',
})
function startEdit(t: Tool) {
editId.value = t.id!
Object.assign(form, { nom: t.nom, categorie: t.categorie || '', description: t.description || '' })
function fileNameFromUrl(url: string) {
if (!url) return ''
return url.split('/').pop() || url
}
function resetForm() {
Object.assign(form, {
nom: '',
categorie: '',
description: '',
boutique_nom: '',
boutique_url: '',
prix_achat: undefined,
photo_url: '',
video_url: '',
notice_fichier_url: '',
})
}
function openCreate() {
editId.value = null
resetForm()
photoFile.value = null
videoFile.value = null
noticeFile.value = null
photoPreview.value = ''
videoPreview.value = ''
noticeFileName.value = ''
showForm.value = true
}
function closeForm() { showForm.value = false; editId.value = null }
function onPhotoSelected(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0] || null
photoFile.value = file
if (file) photoPreview.value = URL.createObjectURL(file)
}
function onVideoSelected(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0] || null
videoFile.value = file
if (file) videoPreview.value = URL.createObjectURL(file)
}
function onNoticeSelected(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0] || null
noticeFile.value = file
noticeFileName.value = file?.name || ''
}
function startEdit(t: Tool) {
editId.value = t.id!
Object.assign(form, {
nom: t.nom,
categorie: t.categorie || '',
description: t.description || '',
boutique_nom: t.boutique_nom || '',
boutique_url: t.boutique_url || '',
prix_achat: t.prix_achat,
photo_url: t.photo_url || '',
video_url: t.video_url || '',
notice_fichier_url: t.notice_fichier_url || '',
})
photoFile.value = null
videoFile.value = null
noticeFile.value = null
photoPreview.value = t.photo_url || ''
videoPreview.value = t.video_url || ''
noticeFileName.value = fileNameFromUrl(t.notice_fichier_url || '')
showForm.value = true
}
function closeForm() {
showForm.value = false
editId.value = null
photoFile.value = null
videoFile.value = null
noticeFile.value = null
photoPreview.value = ''
videoPreview.value = ''
noticeFileName.value = ''
}
async function uploadFile(file: File): Promise<string> {
const fd = new FormData()
fd.append('file', file)
const { data } = await axios.post('/api/upload', fd)
return data.url as string
}
async function submitTool() {
if (editId.value) {
await toolsStore.update(editId.value, { ...form })
} else {
await toolsStore.create({ ...form })
let saved: Tool
const payload: Partial<Tool> = {
nom: form.nom,
categorie: form.categorie || undefined,
description: form.description || undefined,
boutique_nom: form.boutique_nom || undefined,
boutique_url: form.boutique_url || undefined,
prix_achat: form.prix_achat,
photo_url: form.photo_url || undefined,
video_url: form.video_url || undefined,
notice_fichier_url: form.notice_fichier_url || undefined,
}
Object.assign(form, { nom: '', categorie: '', description: '' })
if (editId.value) {
saved = await toolsStore.update(editId.value, payload)
} else {
saved = await toolsStore.create(payload)
}
if (saved.id && (photoFile.value || videoFile.value || noticeFile.value)) {
const patch: Partial<Tool> = {}
if (photoFile.value) patch.photo_url = await uploadFile(photoFile.value)
if (videoFile.value) patch.video_url = await uploadFile(videoFile.value)
if (noticeFile.value) patch.notice_fichier_url = await uploadFile(noticeFile.value)
if (Object.keys(patch).length) await toolsStore.update(saved.id, patch)
}
resetForm()
closeForm()
}

View File

@@ -1,13 +1,145 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { onMounted, reactive, ref } from 'vue';
import axios from 'axios';
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: '' });
const editId = ref(null);
const photoFile = ref(null);
const videoFile = ref(null);
const noticeFile = ref(null);
const photoPreview = ref('');
const videoPreview = ref('');
const noticeFileName = ref('');
const form = reactive({
nom: '',
categorie: '',
description: '',
boutique_nom: '',
boutique_url: '',
prix_achat: undefined,
photo_url: '',
video_url: '',
notice_fichier_url: '',
});
function fileNameFromUrl(url) {
if (!url)
return '';
return url.split('/').pop() || url;
}
function resetForm() {
Object.assign(form, {
nom: '',
categorie: '',
description: '',
boutique_nom: '',
boutique_url: '',
prix_achat: undefined,
photo_url: '',
video_url: '',
notice_fichier_url: '',
});
}
function openCreate() {
editId.value = null;
resetForm();
photoFile.value = null;
videoFile.value = null;
noticeFile.value = null;
photoPreview.value = '';
videoPreview.value = '';
noticeFileName.value = '';
showForm.value = true;
}
function onPhotoSelected(event) {
const input = event.target;
const file = input.files?.[0] || null;
photoFile.value = file;
if (file)
photoPreview.value = URL.createObjectURL(file);
}
function onVideoSelected(event) {
const input = event.target;
const file = input.files?.[0] || null;
videoFile.value = file;
if (file)
videoPreview.value = URL.createObjectURL(file);
}
function onNoticeSelected(event) {
const input = event.target;
const file = input.files?.[0] || null;
noticeFile.value = file;
noticeFileName.value = file?.name || '';
}
function startEdit(t) {
editId.value = t.id;
Object.assign(form, {
nom: t.nom,
categorie: t.categorie || '',
description: t.description || '',
boutique_nom: t.boutique_nom || '',
boutique_url: t.boutique_url || '',
prix_achat: t.prix_achat,
photo_url: t.photo_url || '',
video_url: t.video_url || '',
notice_fichier_url: t.notice_fichier_url || '',
});
photoFile.value = null;
videoFile.value = null;
noticeFile.value = null;
photoPreview.value = t.photo_url || '';
videoPreview.value = t.video_url || '';
noticeFileName.value = fileNameFromUrl(t.notice_fichier_url || '');
showForm.value = true;
}
function closeForm() {
showForm.value = false;
editId.value = null;
photoFile.value = null;
videoFile.value = null;
noticeFile.value = null;
photoPreview.value = '';
videoPreview.value = '';
noticeFileName.value = '';
}
async function uploadFile(file) {
const fd = new FormData();
fd.append('file', file);
const { data } = await axios.post('/api/upload', fd);
return data.url;
}
async function submitTool() {
let saved;
const payload = {
nom: form.nom,
categorie: form.categorie || undefined,
description: form.description || undefined,
boutique_nom: form.boutique_nom || undefined,
boutique_url: form.boutique_url || undefined,
prix_achat: form.prix_achat,
photo_url: form.photo_url || undefined,
video_url: form.video_url || undefined,
notice_fichier_url: form.notice_fichier_url || undefined,
};
if (editId.value) {
saved = await toolsStore.update(editId.value, payload);
}
else {
saved = await toolsStore.create(payload);
}
if (saved.id && (photoFile.value || videoFile.value || noticeFile.value)) {
const patch = {};
if (photoFile.value)
patch.photo_url = await uploadFile(photoFile.value);
if (videoFile.value)
patch.video_url = await uploadFile(videoFile.value);
if (noticeFile.value)
patch.notice_fichier_url = await uploadFile(noticeFile.value);
if (Object.keys(patch).length)
await toolsStore.update(saved.id, patch);
}
resetForm();
closeForm();
}
async function removeTool(id) {
if (confirm('Supprimer cet outil ?'))
@@ -28,9 +160,7 @@ __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;
} },
...{ onClick: (__VLS_ctx.openCreate) },
...{ class: "bg-yellow text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
if (__VLS_ctx.toolsStore.loading) {
@@ -58,6 +188,15 @@ for (const [t] of __VLS_getVForSourceType((__VLS_ctx.toolsStore.tools))) {
...{ class: "text-text font-semibold" },
});
(t.nom);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.startEdit(t);
} },
...{ class: "text-yellow text-xs hover:underline" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.removeTool(t.id);
@@ -76,22 +215,76 @@ for (const [t] of __VLS_getVForSourceType((__VLS_ctx.toolsStore.tools))) {
});
(t.description);
}
if (t.boutique_nom || t.prix_achat != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-xs" },
});
if (t.boutique_nom) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(t.boutique_nom);
}
if (t.prix_achat != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(t.prix_achat);
}
}
if (t.boutique_url) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.a, __VLS_intrinsicElements.a)({
href: (t.boutique_url),
target: "_blank",
rel: "noopener noreferrer",
...{ class: "text-blue text-xs hover:underline truncate" },
});
}
if (t.video_url) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.a, __VLS_intrinsicElements.a)({
href: (t.video_url),
target: "_blank",
rel: "noopener noreferrer",
...{ class: "text-aqua text-xs hover:underline truncate" },
});
}
if (t.notice_fichier_url) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.a, __VLS_intrinsicElements.a)({
href: (t.notice_fichier_url),
target: "_blank",
rel: "noopener noreferrer",
...{ class: "text-aqua text-xs hover:underline truncate" },
});
}
if (t.photo_url || t.video_url) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-auto pt-2 space-y-2" },
});
if (t.photo_url) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (t.photo_url),
alt: "photo outil",
...{ class: "w-full h-28 object-cover rounded border border-bg-hard bg-bg" },
});
}
if (t.video_url) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.video)({
src: (t.video_url),
controls: true,
muted: true,
...{ class: "w-full h-36 object-cover rounded border border-bg-hard bg-bg" },
});
}
}
}
if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showForm))
return;
__VLS_ctx.showForm = false;
} },
...{ onClick: (__VLS_ctx.closeForm) },
...{ 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" },
...{ 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_ctx.editId ? 'Modifier l\'outil' : 'Nouvel outil');
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (__VLS_ctx.submitTool) },
...{ class: "flex flex-col gap-3" },
@@ -127,20 +320,89 @@ if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "autre",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-2 gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
placeholder: "Nom boutique",
...{ 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.boutique_nom);
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "number",
min: "0",
step: "0.01",
placeholder: "Prix achat (€)",
...{ 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.prix_achat);
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "url",
placeholder: "URL boutique (https://...)",
...{ 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.boutique_url);
__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)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
...{ onChange: (__VLS_ctx.onPhotoSelected) },
type: "file",
accept: "image/*",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" },
});
if (__VLS_ctx.photoPreview) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
src: (__VLS_ctx.photoPreview),
alt: "Prévisualisation photo",
...{ class: "mt-2 w-full h-28 object-cover rounded border border-bg-hard bg-bg" },
});
}
__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)({
...{ onChange: (__VLS_ctx.onVideoSelected) },
type: "file",
accept: "video/*",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" },
});
if (__VLS_ctx.videoPreview) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.video)({
src: (__VLS_ctx.videoPreview),
controls: true,
muted: true,
...{ class: "mt-2 w-full h-36 object-cover rounded border border-bg-hard bg-bg" },
});
}
__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)({
...{ onChange: (__VLS_ctx.onNoticeSelected) },
type: "file",
accept: ".txt,.md,text/plain",
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-yellow" },
});
if (__VLS_ctx.noticeFileName || __VLS_ctx.form.notice_fichier_url) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs mt-1 truncate" },
});
(__VLS_ctx.noticeFileName || __VLS_ctx.fileNameFromUrl(__VLS_ctx.form.notice_fichier_url || ''));
}
__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;
} },
...{ onClick: (__VLS_ctx.closeForm) },
type: "button",
...{ class: "px-4 py-2 text-text-muted hover:text-text text-sm" },
});
@@ -148,6 +410,7 @@ if (__VLS_ctx.showForm) {
type: "submit",
...{ class: "bg-yellow text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
(__VLS_ctx.editId ? 'Enregistrer' : 'Créer');
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-4xl']} */ ;
@@ -190,6 +453,11 @@ if (__VLS_ctx.showForm) {
/** @type {__VLS_StyleScopedClasses['justify-between']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
@@ -202,6 +470,37 @@ if (__VLS_ctx.showForm) {
/** @type {__VLS_StyleScopedClasses['w-fit']} */ ;
/** @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['text-blue']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['truncate']} */ ;
/** @type {__VLS_StyleScopedClasses['text-aqua']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['truncate']} */ ;
/** @type {__VLS_StyleScopedClasses['text-aqua']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['truncate']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-auto']} */ ;
/** @type {__VLS_StyleScopedClasses['pt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['space-y-2']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['h-28']} */ ;
/** @type {__VLS_StyleScopedClasses['object-cover']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['h-36']} */ ;
/** @type {__VLS_StyleScopedClasses['object-cover']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['fixed']} */ ;
/** @type {__VLS_StyleScopedClasses['inset-0']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-black/60']} */ ;
@@ -214,7 +513,7 @@ if (__VLS_ctx.showForm) {
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['p-6']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-md']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
@@ -246,6 +545,42 @@ if (__VLS_ctx.showForm) {
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
/** @type {__VLS_StyleScopedClasses['focus:border-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-2']} */ ;
/** @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['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
@@ -259,6 +594,71 @@ if (__VLS_ctx.showForm) {
/** @type {__VLS_StyleScopedClasses['focus:border-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['resize-none']} */ ;
/** @type {__VLS_StyleScopedClasses['h-16']} */ ;
/** @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-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['h-28']} */ ;
/** @type {__VLS_StyleScopedClasses['object-cover']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @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-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
/** @type {__VLS_StyleScopedClasses['h-36']} */ ;
/** @type {__VLS_StyleScopedClasses['object-cover']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
/** @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-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
/** @type {__VLS_StyleScopedClasses['truncate']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['justify-end']} */ ;
@@ -281,7 +681,18 @@ const __VLS_self = (await import('vue')).defineComponent({
return {
toolsStore: toolsStore,
showForm: showForm,
editId: editId,
photoPreview: photoPreview,
videoPreview: videoPreview,
noticeFileName: noticeFileName,
form: form,
fileNameFromUrl: fileNameFromUrl,
openCreate: openCreate,
onPhotoSelected: onPhotoSelected,
onVideoSelected: onVideoSelected,
onNoticeSelected: onNoticeSelected,
startEdit: startEdit,
closeForm: closeForm,
submitTool: submitTool,
removeTool: removeTool,
};

View File

@@ -1,30 +1,256 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.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) {
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) {
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 = {};
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) => ({
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) => ({
haute: 'bg-red', normale: 'bg-yellow', basse: 'bg-text-muted',
}[p] || 'bg-text-muted');
const statutClass = (s) => ({
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());
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-2xl mx-auto" },
...{ 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 mb-4" },
...{ class: "text-2xl font-bold text-green" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-sm" },
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex items-center gap-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.prevWeek) },
...{ class: "text-text-muted hover:text-text text-lg" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text text-sm font-medium" },
});
(__VLS_ctx.weekLabel);
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.nextWeek) },
...{ class: "text-text-muted hover:text-text text-lg" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.goToday) },
...{ class: "text-xs text-green border border-green/30 rounded px-2 py-0.5 hover:bg-green/10" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-7 gap-1 mb-2" },
});
for (const [d] of __VLS_getVForSourceType((__VLS_ctx.weekDays))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (d.iso),
...{ class: (['text-center text-xs py-1 rounded',
d.isToday ? 'text-green font-bold' : 'text-text-muted']) },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({});
(d.dayShort);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.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);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "grid grid-cols-7 gap-1" },
});
for (const [d] of __VLS_getVForSourceType((__VLS_ctx.weekDays))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
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']) },
});
for (const [t] of __VLS_getVForSourceType((__VLS_ctx.tasksByDay[d.iso] || []))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (t.id),
...{ class: (['text-xs rounded px-1 py-0.5 mb-0.5 cursor-pointer hover:opacity-80 truncate',
__VLS_ctx.priorityClass(t.priorite)]) },
title: (t.titre),
});
(t.titre);
}
if (!(__VLS_ctx.tasksByDay[d.iso]?.length)) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs text-center pt-2 opacity-40" },
});
}
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-6" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text-muted text-xs uppercase tracking-widest mb-2" },
});
if (!__VLS_ctx.unscheduled.length) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs pl-2" },
});
}
for (const [t] of __VLS_getVForSourceType((__VLS_ctx.unscheduled))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
key: (t.id),
...{ class: "bg-bg-soft rounded-lg p-2 mb-1 border border-bg-hard flex items-center gap-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: (['text-xs w-2 h-2 rounded-full flex-shrink-0', __VLS_ctx.dotClass(t.priorite)]) },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text text-sm flex-1 truncate" },
});
(t.titre);
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: (['text-xs px-1.5 py-0.5 rounded', __VLS_ctx.statutClass(t.statut)]) },
});
(t.statut);
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
/** @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['mb-4']} */ ;
/** @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['text-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-green/30']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
/** @type {__VLS_StyleScopedClasses['py-0.5']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:bg-green/10']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-7']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-7']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
/** @type {__VLS_StyleScopedClasses['pt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['opacity-40']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-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['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
/** @type {__VLS_StyleScopedClasses['p-2']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['truncate']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
return {
weekDays: weekDays,
weekLabel: weekLabel,
tasksByDay: tasksByDay,
unscheduled: unscheduled,
prevWeek: prevWeek,
nextWeek: nextWeek,
goToday: goToday,
priorityClass: priorityClass,
dotClass: dotClass,
statutClass: statutClass,
};
},
});
export default (await import('vue')).defineComponent({

View File

@@ -35,6 +35,9 @@
<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.boutique_nom">🛒 {{ p.boutique_nom }}</span>
<span v-if="p.tarif_achat != null">💶 {{ p.tarif_achat }} </span>
<span v-if="p.date_achat">🧾 {{ fmtDate(p.date_achat) }}</span>
<span v-if="p.notes">📝 {{ p.notes }}</span>
</div>
</div>
@@ -135,6 +138,30 @@
<option value="termine">Terminé</option>
</select>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Nom boutique</label>
<input v-model="cForm.boutique_nom" type="text" placeholder="Ex: Graines Bocquet"
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 achat</label>
<input v-model="cForm.date_achat" 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 class="grid grid-cols-2 gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Tarif achat ()</label>
<input v-model.number="cForm.tarif_achat" type="number" min="0" step="0.01" placeholder="0.00"
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">URL boutique</label>
<input v-model="cForm.boutique_url" type="url" placeholder="https://..."
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>
<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">
@@ -177,7 +204,9 @@ const statuts = [
const cForm = reactive({
garden_id: 0, variety_id: 0, quantite: 1,
date_plantation: '', statut: 'prevu', notes: ''
date_plantation: '', statut: 'prevu',
boutique_nom: '', boutique_url: '', tarif_achat: undefined as number | undefined, date_achat: '',
notes: ''
})
const rForm = reactive({
@@ -233,7 +262,12 @@ function startEdit(p: typeof store.plantings[0]) {
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 || '',
statut: p.statut,
boutique_nom: p.boutique_nom || '',
boutique_url: p.boutique_url || '',
tarif_achat: p.tarif_achat,
date_achat: p.date_achat?.slice(0, 10) || '',
notes: p.notes || '',
})
showCreate.value = true
}
@@ -247,7 +281,11 @@ async function createPlanting() {
await store.create({ ...cForm })
}
closeCreate()
Object.assign(cForm, { garden_id: 0, variety_id: 0, quantite: 1, date_plantation: '', statut: 'prevu', notes: '' })
Object.assign(cForm, {
garden_id: 0, variety_id: 0, quantite: 1, date_plantation: '', statut: 'prevu',
boutique_nom: '', boutique_url: '', tarif_achat: undefined, date_achat: '',
notes: '',
})
}
onMounted(() => {

View File

@@ -8,6 +8,7 @@ const store = usePlantingsStore();
const gardensStore = useGardensStore();
const plantsStore = usePlantsStore();
const showCreate = ref(false);
const editId = ref(null);
const filterStatut = ref('');
const openRecoltes = ref(null);
const recoltesList = ref([]);
@@ -21,7 +22,9 @@ const statuts = [
];
const cForm = reactive({
garden_id: 0, variety_id: 0, quantite: 1,
date_plantation: '', statut: 'prevu', notes: ''
date_plantation: '', statut: 'prevu',
boutique_nom: '', boutique_url: '', tarif_achat: undefined, date_achat: '',
notes: ''
});
const rForm = reactive({
quantite: 1, unite: 'kg', date_recolte: new Date().toISOString().slice(0, 10)
@@ -71,10 +74,34 @@ async function deleteRecolte(id, plantingId) {
await recoltesApi.delete(id);
recoltesList.value = recoltesList.value.filter(r => r.id !== id);
}
function startEdit(p) {
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,
boutique_nom: p.boutique_nom || '',
boutique_url: p.boutique_url || '',
tarif_achat: p.tarif_achat,
date_achat: p.date_achat?.slice(0, 10) || '',
notes: p.notes || '',
});
showCreate.value = true;
}
function closeCreate() { showCreate.value = false; editId.value = null; }
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: '' });
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',
boutique_nom: '', boutique_url: '', tarif_achat: undefined, date_achat: '',
notes: '',
});
}
onMounted(() => {
store.fetchAll();
@@ -159,6 +186,18 @@ for (const [p] of __VLS_getVForSourceType((__VLS_ctx.filtered))) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(__VLS_ctx.fmtDate(p.date_plantation));
}
if (p.boutique_nom) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(p.boutique_nom);
}
if (p.tarif_achat != null) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(p.tarif_achat);
}
if (p.date_achat) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(__VLS_ctx.fmtDate(p.date_achat));
}
if (p.notes) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
(p.notes);
@@ -173,6 +212,12 @@ for (const [p] of __VLS_getVForSourceType((__VLS_ctx.filtered))) {
...{ 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.startEdit(p);
} },
...{ class: "text-yellow text-xs hover:underline" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.store.remove(p.id);
@@ -280,11 +325,7 @@ for (const [p] of __VLS_getVForSourceType((__VLS_ctx.filtered))) {
}
if (__VLS_ctx.showCreate) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (...[$event]) => {
if (!(__VLS_ctx.showCreate))
return;
__VLS_ctx.showCreate = false;
} },
...{ onClick: (__VLS_ctx.closeCreate) },
...{ class: "fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
@@ -293,6 +334,7 @@ if (__VLS_ctx.showCreate) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text font-bold text-lg mb-4" },
});
(__VLS_ctx.editId ? 'Modifier la plantation' : 'Nouvelle plantation');
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (__VLS_ctx.createPlanting) },
...{ class: "flex flex-col gap-3" },
@@ -375,6 +417,53 @@ if (__VLS_ctx.showCreate) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "termine",
});
__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)({
value: (__VLS_ctx.cForm.boutique_nom),
type: "text",
placeholder: "Ex: Graines Bocquet",
...{ 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.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_achat);
__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: "0",
step: "0.01",
placeholder: "0.00",
...{ 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.tarif_achat);
__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: "url",
placeholder: "https://...",
...{ 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.boutique_url);
__VLS_asFunctionalElement(__VLS_intrinsicElements.textarea)({
value: (__VLS_ctx.cForm.notes),
placeholder: "Notes...",
@@ -384,11 +473,7 @@ if (__VLS_ctx.showCreate) {
...{ 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;
} },
...{ onClick: (__VLS_ctx.closeCreate) },
type: "button",
...{ class: "px-4 py-2 text-text-muted hover:text-text text-sm" },
});
@@ -396,6 +481,7 @@ if (__VLS_ctx.showCreate) {
type: "submit",
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
});
(__VLS_ctx.editId ? 'Enregistrer' : 'Créer');
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-3xl']} */ ;
@@ -453,6 +539,9 @@ if (__VLS_ctx.showCreate) {
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['text-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
@@ -645,6 +734,72 @@ if (__VLS_ctx.showCreate) {
/** @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['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['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
@@ -682,6 +837,7 @@ const __VLS_self = (await import('vue')).defineComponent({
gardensStore: gardensStore,
plantsStore: plantsStore,
showCreate: showCreate,
editId: editId,
filterStatut: filterStatut,
openRecoltes: openRecoltes,
recoltesList: recoltesList,
@@ -697,6 +853,8 @@ const __VLS_self = (await import('vue')).defineComponent({
toggleRecoltes: toggleRecoltes,
addRecolte: addRecolte,
deleteRecolte: deleteRecolte,
startEdit: startEdit,
closeCreate: closeCreate,
createPlanting: createPlanting,
};
},

View File

@@ -78,62 +78,107 @@
<!-- 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">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-4xl 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">
<form @submit.prevent="submitPlant" class="grid grid-cols-1 lg:grid-cols-2 gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Nom commun *</label>
<input v-model="form.nom_commun" placeholder="Ex: Tomate" 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" />
<p class="text-text-muted text-[11px] mt-1">Nom utilisé au jardin pour identifier rapidement la plante.</p>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Nom botanique</label>
<input v-model="form.nom_botanique" placeholder="Ex: Solanum lycopersicum"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
<p class="text-text-muted text-[11px] mt-1">Nom scientifique utile pour éviter les ambiguïtés.</p>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Variété</label>
<input v-model="form.variete" placeholder="Ex: Andine Cornue"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
<p class="text-text-muted text-[11px] mt-1">Cultivar précis (optionnel).</p>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Famille botanique</label>
<input v-model="form.famille" placeholder="Ex: Solanacées"
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" />
<p class="text-text-muted text-[11px] mt-1">Permet d'organiser la rotation des cultures.</p>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Catégorie</label>
<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>
<p class="text-text-muted text-[11px] mt-1">Classe principale pour filtrer la bibliothèque de plantes.</p>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Type de plante</label>
<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>
<p class="text-text-muted text-[11px] mt-1">Type d'usage de la plante (récolte, ornement, etc.).</p>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Besoin en eau</label>
<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>
<p class="text-text-muted text-[11px] mt-1">Aide à planifier l'arrosage.</p>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Ensoleillement</label>
<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>
<p class="text-text-muted text-[11px] mt-1">Exposition lumineuse idéale.</p>
</div>
<div class="lg:col-span-2 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">
<p class="lg:col-span-2 text-text-muted text-[11px] -mt-2">Espacement recommandé en cm et température minimale supportée (en °C).</p>
<div>
<label class="text-text-muted text-xs block mb-1">Mois de plantation</label>
<input v-model="form.plantation_mois" placeholder="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" />
<p class="text-text-muted text-[11px] mt-1">Liste des mois conseillés, séparés par des virgules.</p>
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Mois de récolte</label>
<input v-model="form.recolte_mois" placeholder="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" />
<p class="text-text-muted text-[11px] mt-1">Période habituelle de récolte.</p>
</div>
<div class="lg:col-span-2">
<label class="text-text-muted text-xs block mb-1">Notes</label>
<textarea v-model="form.notes" placeholder="Observations, maladies, astuces..."
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" />
<p class="text-text-muted text-[11px] mt-1">Commentaires libres visibles dans le détail de la plante.</p>
</div>
<div class="lg:col-span-2 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"

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,106 @@
<template>
<div class="p-4 max-w-2xl mx-auto">
<div class="p-4 max-w-3xl mx-auto">
<h1 class="text-2xl font-bold text-green mb-4">Réglages</h1>
<p class="text-text-muted text-sm">Paramètres et export/import prochaine étape.</p>
<section class="bg-bg-soft border border-bg-hard rounded-xl p-4 mb-4">
<h2 class="text-text font-semibold mb-2">Général</h2>
<p class="text-text-muted text-sm mb-3">Options globales de l'application.</p>
<label class="inline-flex items-center gap-2 text-sm text-text">
<input v-model="debugMode" type="checkbox" class="accent-green" />
Activer le mode debug (affichage CPU / RAM / disque en header)
</label>
<div class="mt-3 flex items-center gap-2">
<button
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
:disabled="saving"
@click="saveSettings"
>
{{ saving ? 'Enregistrement...' : 'Enregistrer' }}
</button>
<span v-if="savedMsg" class="text-xs text-aqua">{{ savedMsg }}</span>
</div>
</section>
<section class="bg-bg-soft border border-bg-hard rounded-xl p-4 mb-4">
<h2 class="text-text font-semibold mb-2">Maintenance météo</h2>
<p class="text-text-muted text-sm mb-3">Déclenche un rafraîchissement immédiat des jobs météo backend.</p>
<button
class="bg-blue text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
:disabled="refreshingMeteo"
@click="refreshMeteo"
>
{{ refreshingMeteo ? 'Rafraîchissement...' : 'Rafraîchir maintenant' }}
</button>
</section>
<section class="bg-bg-soft border border-bg-hard rounded-xl p-4">
<h2 class="text-text font-semibold mb-2">Idées utiles (prochaine étape)</h2>
<ul class="text-text-muted text-sm space-y-1">
<li>• Sauvegarde/restauration JSON de la base métier</li>
<li>• Rotation/nettoyage des médias anciens</li>
<li>• Choix des unités météo (°C, mm, km/h)</li>
<li>• Paramètres de seuils alertes (gel, pluie, vent)</li>
</ul>
</section>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { settingsApi } from '@/api/settings'
import { meteoApi } from '@/api/meteo'
const debugMode = ref(false)
const saving = ref(false)
const savedMsg = ref('')
const refreshingMeteo = ref(false)
function toBool(value: unknown): boolean {
if (typeof value === 'boolean') return value
const s = String(value ?? '').toLowerCase().trim()
return s === '1' || s === 'true' || s === 'yes' || s === 'on'
}
function notifyDebugChanged(enabled: boolean) {
localStorage.setItem('debug_mode', enabled ? '1' : '0')
window.dispatchEvent(new CustomEvent('settings-updated', { detail: { debug_mode: enabled } }))
}
async function loadSettings() {
try {
const data = await settingsApi.get()
debugMode.value = toBool(data.debug_mode)
notifyDebugChanged(debugMode.value)
} catch {
// Laisse la valeur locale si l'API n'est pas disponible.
}
}
async function saveSettings() {
saving.value = true
savedMsg.value = ''
try {
await settingsApi.update({ debug_mode: debugMode.value ? '1' : '0' })
notifyDebugChanged(debugMode.value)
savedMsg.value = 'Enregistré'
window.setTimeout(() => { savedMsg.value = '' }, 1800)
} finally {
saving.value = false
}
}
async function refreshMeteo() {
refreshingMeteo.value = true
try {
await meteoApi.refresh()
} finally {
refreshingMeteo.value = false
}
}
onMounted(() => {
void loadSettings()
})
</script>

View File

@@ -1,30 +1,207 @@
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
import { onMounted, ref } from 'vue';
import { settingsApi } from '@/api/settings';
import { meteoApi } from '@/api/meteo';
const debugMode = ref(false);
const saving = ref(false);
const savedMsg = ref('');
const refreshingMeteo = ref(false);
function toBool(value) {
if (typeof value === 'boolean')
return value;
const s = String(value ?? '').toLowerCase().trim();
return s === '1' || s === 'true' || s === 'yes' || s === 'on';
}
function notifyDebugChanged(enabled) {
localStorage.setItem('debug_mode', enabled ? '1' : '0');
window.dispatchEvent(new CustomEvent('settings-updated', { detail: { debug_mode: enabled } }));
}
async function loadSettings() {
try {
const data = await settingsApi.get();
debugMode.value = toBool(data.debug_mode);
notifyDebugChanged(debugMode.value);
}
catch {
// Laisse la valeur locale si l'API n'est pas disponible.
}
}
async function saveSettings() {
saving.value = true;
savedMsg.value = '';
try {
await settingsApi.update({ debug_mode: debugMode.value ? '1' : '0' });
notifyDebugChanged(debugMode.value);
savedMsg.value = 'Enregistré';
window.setTimeout(() => { savedMsg.value = ''; }, 1800);
}
finally {
saving.value = false;
}
}
async function refreshMeteo() {
refreshingMeteo.value = true;
try {
await meteoApi.refresh();
}
finally {
refreshingMeteo.value = false;
}
}
onMounted(() => {
void loadSettings();
});
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-2xl mx-auto" },
...{ class: "p-4 max-w-3xl mx-auto" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1)({
...{ class: "text-2xl font-bold text-green mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-sm" },
__VLS_asFunctionalElement(__VLS_intrinsicElements.section, __VLS_intrinsicElements.section)({
...{ class: "bg-bg-soft border border-bg-hard rounded-xl p-4 mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text font-semibold mb-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-sm mb-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "inline-flex items-center gap-2 text-sm text-text" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "checkbox",
...{ class: "accent-green" },
});
(__VLS_ctx.debugMode);
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "mt-3 flex items-center gap-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.saveSettings) },
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
disabled: (__VLS_ctx.saving),
});
(__VLS_ctx.saving ? 'Enregistrement...' : 'Enregistrer');
if (__VLS_ctx.savedMsg) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-xs text-aqua" },
});
(__VLS_ctx.savedMsg);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.section, __VLS_intrinsicElements.section)({
...{ class: "bg-bg-soft border border-bg-hard rounded-xl p-4 mb-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text font-semibold mb-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-sm mb-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.refreshMeteo) },
...{ class: "bg-blue text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
disabled: (__VLS_ctx.refreshingMeteo),
});
(__VLS_ctx.refreshingMeteo ? 'Rafraîchissement...' : 'Rafraîchir maintenant');
__VLS_asFunctionalElement(__VLS_intrinsicElements.section, __VLS_intrinsicElements.section)({
...{ class: "bg-bg-soft border border-bg-hard rounded-xl p-4" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.h2, __VLS_intrinsicElements.h2)({
...{ class: "text-text font-semibold mb-2" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.ul, __VLS_intrinsicElements.ul)({
...{ class: "text-text-muted text-sm space-y-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.li, __VLS_intrinsicElements.li)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.li, __VLS_intrinsicElements.li)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.li, __VLS_intrinsicElements.li)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.li, __VLS_intrinsicElements.li)({});
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-3xl']} */ ;
/** @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['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['inline-flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['accent-green']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-3']} */ ;
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @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['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-aqua']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-blue']} */ ;
/** @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['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['space-y-1']} */ ;
var __VLS_dollars;
const __VLS_self = (await import('vue')).defineComponent({
setup() {
return {};
return {
debugMode: debugMode,
saving: saving,
savedMsg: savedMsg,
refreshingMeteo: refreshingMeteo,
saveSettings: saveSettings,
refreshMeteo: refreshMeteo,
};
},
});
export default (await import('vue')).defineComponent({

View File

@@ -19,6 +19,9 @@
<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 v-if="t.frequence_jours != null && t.frequence_jours > 0" class="text-text-muted text-xs">
🔁 Tous les {{ t.frequence_jours }} jours
</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"
@@ -69,6 +72,25 @@
<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="bg-bg rounded border border-bg-hard p-3">
<label class="inline-flex items-center gap-2 text-sm text-text">
<input v-model="form.repetition" type="checkbox" class="accent-green" />
Répétition
</label>
<p class="text-text-muted text-[11px] mt-1">Active une tâche récurrente.</p>
</div>
<div v-if="form.repetition">
<label class="text-text-muted text-xs block mb-1">Fréquence (jours)</label>
<input
v-model.number="form.frequence_jours"
type="number"
min="1"
step="1"
required
placeholder="Ex: 7"
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' }}
@@ -89,7 +111,15 @@ import type { Task } from '@/api/tasks'
const store = useTasksStore()
const showForm = ref(false)
const editId = ref<number | null>(null)
const form = reactive({ titre: '', description: '', priorite: 'normale', statut: 'a_faire', echeance: '' })
const form = reactive({
titre: '',
description: '',
priorite: 'normale',
statut: 'a_faire',
echeance: '',
repetition: false,
frequence_jours: undefined as number | undefined,
})
const groupes: [string, string][] = [
['a_faire', 'À faire'],
@@ -105,7 +135,15 @@ function fmtDate(s: string) {
function openCreate() {
editId.value = null
Object.assign(form, { titre: '', description: '', priorite: 'normale', statut: 'a_faire', echeance: '' })
Object.assign(form, {
titre: '',
description: '',
priorite: 'normale',
statut: 'a_faire',
echeance: '',
repetition: false,
frequence_jours: undefined,
})
showForm.value = true
}
@@ -115,6 +153,8 @@ function startEdit(t: Task) {
titre: t.titre, description: (t as any).description || '',
priorite: t.priorite, statut: t.statut,
echeance: t.echeance ? t.echeance.slice(0, 10) : '',
repetition: Boolean((t as any).recurrence || (t as any).frequence_jours),
frequence_jours: (t as any).frequence_jours ?? undefined,
})
showForm.value = true
}
@@ -124,10 +164,19 @@ function closeForm() { showForm.value = false; editId.value = null }
onMounted(() => store.fetchAll())
async function submit() {
const payload = {
titre: form.titre,
description: form.description,
priorite: form.priorite,
statut: form.statut,
echeance: form.echeance || undefined,
recurrence: form.repetition ? 'jours' : null,
frequence_jours: form.repetition ? (form.frequence_jours ?? 7) : null,
}
if (editId.value) {
await store.update(editId.value, { ...form })
await store.update(editId.value, payload)
} else {
await store.create({ ...form })
await store.create(payload)
}
closeForm()
}

View File

@@ -3,18 +3,68 @@ 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 editId = ref(null);
const form = reactive({
titre: '',
description: '',
priorite: 'normale',
statut: 'a_faire',
echeance: '',
repetition: false,
frequence_jours: undefined,
});
const groupes = [
['a_faire', 'À faire'],
['en_cours', 'En cours'],
['fait', 'Terminé'],
];
const byStatut = (s) => store.tasks.filter(t => t.statut === s);
function fmtDate(s) {
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: '',
repetition: false,
frequence_jours: undefined,
});
showForm.value = true;
}
function startEdit(t) {
editId.value = t.id;
Object.assign(form, {
titre: t.titre, description: t.description || '',
priorite: t.priorite, statut: t.statut,
echeance: t.echeance ? t.echeance.slice(0, 10) : '',
repetition: Boolean(t.recurrence || t.frequence_jours),
frequence_jours: t.frequence_jours ?? undefined,
});
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: '' });
const payload = {
titre: form.titre,
description: form.description,
priorite: form.priorite,
statut: form.statut,
echeance: form.echeance || undefined,
recurrence: form.repetition ? 'jours' : null,
frequence_jours: form.repetition ? (form.frequence_jours ?? 7) : null,
};
if (editId.value) {
await store.update(editId.value, payload);
}
else {
await store.create(payload);
}
closeForm();
}
debugger; /* PartiallyEnd: #3632/scriptSetup.vue */
const __VLS_ctx = {};
@@ -30,74 +80,9 @@ __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;
} },
...{ onClick: (__VLS_ctx.openCreate) },
...{ 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),
@@ -124,12 +109,27 @@ for (const [[groupe, label]] of __VLS_getVForSourceType((__VLS_ctx.groupes))) {
'text-text-muted': t.priorite === 'basse'
}) },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
...{ class: "text-text text-sm flex-1" },
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex-1 min-w-0" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text text-sm" },
});
(t.titre);
if (t.echeance) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs" },
});
(__VLS_ctx.fmtDate(t.echeance));
}
if (t.frequence_jours != null && t.frequence_jours > 0) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "text-text-muted text-xs" },
});
(t.frequence_jours);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-1 items-center" },
...{ class: "flex gap-1 items-center shrink-0" },
});
if (t.statut === 'a_faire') {
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
@@ -151,14 +151,143 @@ for (const [[groupe, label]] of __VLS_getVForSourceType((__VLS_ctx.groupes))) {
...{ class: "text-xs text-green hover:underline" },
});
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (...[$event]) => {
__VLS_ctx.startEdit(t);
} },
...{ class: "text-xs text-yellow hover:underline ml-2" },
});
__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" },
...{ class: "text-xs text-text-muted hover:text-red ml-1" },
});
}
}
if (__VLS_ctx.showForm) {
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ onClick: (__VLS_ctx.closeForm) },
...{ 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_ctx.editId ? 'Modifier la tâche' : 'Nouvelle tâche');
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
...{ onSubmit: (__VLS_ctx.submit) },
...{ 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)({});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "text-text-muted text-xs block mb-1" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.textarea)({
value: (__VLS_ctx.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" },
});
__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.select, __VLS_intrinsicElements.select)({
value: (__VLS_ctx.form.statut),
...{ 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: "a_faire",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "en_cours",
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
value: "fait",
});
__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: "bg-bg rounded border border-bg-hard p-3" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
...{ class: "inline-flex items-center gap-2 text-sm text-text" },
});
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
type: "checkbox",
...{ class: "accent-green" },
});
(__VLS_ctx.form.repetition);
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
...{ class: "text-text-muted text-[11px] mt-1" },
});
if (__VLS_ctx.form.repetition) {
__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",
step: "1",
required: true,
placeholder: "Ex: 7",
...{ 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.frequence_jours);
}
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
...{ class: "flex gap-2 mt-2" },
});
__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_ctx.editId ? 'Enregistrer' : 'Créer');
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
...{ onClick: (__VLS_ctx.closeForm) },
type: "button",
...{ class: "text-text-muted text-sm px-4 py-2 hover:text-text" },
});
}
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
@@ -177,12 +306,70 @@ for (const [[groupe, label]] of __VLS_getVForSourceType((__VLS_ctx.groupes))) {
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
/** @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-4']} */ ;
/** @type {__VLS_StyleScopedClasses['mb-6']} */ ;
/** @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-green/30']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
/** @type {__VLS_StyleScopedClasses['min-w-0']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @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-1']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['shrink-0']} */ ;
/** @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-yellow']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
/** @type {__VLS_StyleScopedClasses['ml-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['hover:text-red']} */ ;
/** @type {__VLS_StyleScopedClasses['ml-1']} */ ;
/** @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['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
@@ -200,6 +387,22 @@ for (const [[groupe, label]] of __VLS_getVForSourceType((__VLS_ctx.groupes))) {
/** @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['resize-none']} */ ;
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
/** @type {__VLS_StyleScopedClasses['grid-cols-2']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
@@ -229,11 +432,53 @@ for (const [[groupe, label]] of __VLS_getVForSourceType((__VLS_ctx.groupes))) {
/** @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['bg-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
/** @type {__VLS_StyleScopedClasses['border']} */ ;
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
/** @type {__VLS_StyleScopedClasses['p-3']} */ ;
/** @type {__VLS_StyleScopedClasses['inline-flex']} */ ;
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
/** @type {__VLS_StyleScopedClasses['accent-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
/** @type {__VLS_StyleScopedClasses['text-[11px]']} */ ;
/** @type {__VLS_StyleScopedClasses['mt-1']} */ ;
/** @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['mt-2']} */ ;
/** @type {__VLS_StyleScopedClasses['bg-green']} */ ;
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
@@ -246,50 +491,20 @@ for (const [[groupe, label]] of __VLS_getVForSourceType((__VLS_ctx.groupes))) {
/** @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 {
store: store,
showForm: showForm,
editId: editId,
form: form,
groupes: groupes,
byStatut: byStatut,
fmtDate: fmtDate,
openCreate: openCreate,
startEdit: startEdit,
closeForm: closeForm,
submit: submit,
};
},