before gemiin
This commit is contained in:
4431
frontend/package-lock.json
generated
4431
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,18 +9,19 @@
|
||||
"lint": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0",
|
||||
"axios": "^1.7.9",
|
||||
"pinia": "^2.3.0",
|
||||
"axios": "^1.7.9"
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"vite": "^6.0.7",
|
||||
"typescript": "^5.7.3",
|
||||
"vue-tsc": "^2.2.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.5.1",
|
||||
"autoprefixer": "^10.4.20"
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.0.7",
|
||||
"vite-plugin-pwa": "^1.2.0",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</aside>
|
||||
|
||||
<!-- Main content -->
|
||||
<main class="pt-14 lg:pt-0 lg:pl-60 min-h-screen w-full" style="font-size: var(--ui-font-size, 14px)">
|
||||
<main class="pt-14 lg:pt-0 lg:pl-60 min-h-screen w-full bg-bg" style="font-size: var(--ui-font-size, 14px)">
|
||||
<RouterView />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import AppHeader from '@/components/AppHeader.vue';
|
||||
import AppDrawer from '@/components/AppDrawer.vue';
|
||||
import { meteoApi } from '@/api/meteo';
|
||||
import { settingsApi } from '@/api/settings';
|
||||
import { applyUiSizesToRoot } from '@/utils/uiSizeDefaults';
|
||||
const drawerOpen = ref(false);
|
||||
const debugMode = ref(localStorage.getItem('debug_mode') === '1');
|
||||
const debugStats = ref(null);
|
||||
@@ -71,11 +72,15 @@ function startDebugPolling() {
|
||||
void fetchDebugStats();
|
||||
}, 10000);
|
||||
}
|
||||
function applyUiSizesFromSettings(data) {
|
||||
applyUiSizesToRoot(data);
|
||||
}
|
||||
async function loadDebugModeFromApi() {
|
||||
try {
|
||||
const data = await settingsApi.get();
|
||||
debugMode.value = toBool(data.debug_mode);
|
||||
localStorage.setItem('debug_mode', debugMode.value ? '1' : '0');
|
||||
applyUiSizesFromSettings(data);
|
||||
}
|
||||
catch {
|
||||
// On garde la valeur locale.
|
||||
@@ -235,10 +240,12 @@ for (const [l] of __VLS_getVForSourceType((__VLS_ctx.links))) {
|
||||
}, ...__VLS_functionalComponentArgsRest(__VLS_19));
|
||||
__VLS_21.slots.default;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-base leading-none" },
|
||||
...{ style: (`font-size: var(--ui-menu-icon-size, 18px); line-height: 1`) },
|
||||
});
|
||||
(l.icon);
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ style: (`font-size: var(--ui-menu-font-size, 13px)`) },
|
||||
});
|
||||
(l.label);
|
||||
var __VLS_21;
|
||||
}
|
||||
@@ -246,7 +253,8 @@ __VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.d
|
||||
...{ class: "px-4 py-4 border-t border-bg-soft text-text-muted text-xs" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.main, __VLS_intrinsicElements.main)({
|
||||
...{ class: "pt-14 lg:pt-0 lg:pl-60 min-h-screen w-full" },
|
||||
...{ class: "pt-14 lg:pt-0 lg:pl-60 min-h-screen w-full bg-bg" },
|
||||
...{ style: {} },
|
||||
});
|
||||
const __VLS_22 = {}.RouterView;
|
||||
/** @type {[typeof __VLS_components.RouterView, ]} */ ;
|
||||
@@ -313,8 +321,6 @@ const __VLS_24 = __VLS_23({}, ...__VLS_functionalComponentArgsRest(__VLS_23));
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['group']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-base']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['leading-none']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-t']} */ ;
|
||||
@@ -326,6 +332,7 @@ const __VLS_24 = __VLS_23({}, ...__VLS_functionalComponentArgsRest(__VLS_23));
|
||||
/** @type {__VLS_StyleScopedClasses['lg:pl-60']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['min-h-screen']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
|
||||
var __VLS_dollars;
|
||||
const __VLS_self = (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
|
||||
@@ -3,4 +3,19 @@ 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),
|
||||
downloadBackup: () => client.get('/api/settings/backup/download', { responseType: 'blob' }).then(r => {
|
||||
let filename = 'jardin_backup.zip';
|
||||
const contentDisposition = r.headers?.['content-disposition'];
|
||||
if (typeof contentDisposition === 'string') {
|
||||
const utf8Match = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i);
|
||||
const classicMatch = contentDisposition.match(/filename="?([^\";]+)"?/i);
|
||||
if (utf8Match?.[1]) {
|
||||
filename = decodeURIComponent(utf8Match[1]);
|
||||
}
|
||||
else if (classicMatch?.[1]) {
|
||||
filename = classicMatch[1];
|
||||
}
|
||||
}
|
||||
return { blob: r.data, filename };
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -30,4 +30,19 @@ export const settingsApi = {
|
||||
client.put<{ ok: boolean }>('/api/settings', settings).then(r => r.data),
|
||||
getDebugSystemStats: () =>
|
||||
client.get<DebugSystemStats>('/api/settings/debug/system').then(r => r.data),
|
||||
downloadBackup: () =>
|
||||
client.get('/api/settings/backup/download', { responseType: 'blob' }).then(r => {
|
||||
let filename = 'jardin_backup.zip'
|
||||
const contentDisposition = r.headers?.['content-disposition']
|
||||
if (typeof contentDisposition === 'string') {
|
||||
const utf8Match = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i)
|
||||
const classicMatch = contentDisposition.match(/filename="?([^\";]+)"?/i)
|
||||
if (utf8Match?.[1]) {
|
||||
filename = decodeURIComponent(utf8Match[1])
|
||||
} else if (classicMatch?.[1]) {
|
||||
filename = classicMatch[1]
|
||||
}
|
||||
}
|
||||
return { blob: r.data as Blob, filename }
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ export interface Task {
|
||||
titre: string
|
||||
description?: string
|
||||
garden_id?: number
|
||||
planting_id?: number
|
||||
outil_id?: number
|
||||
priorite: string
|
||||
echeance?: string
|
||||
recurrence?: string | null
|
||||
@@ -14,7 +16,7 @@ export interface Task {
|
||||
}
|
||||
|
||||
export const tasksApi = {
|
||||
list: (params?: { statut?: string; garden_id?: number }) =>
|
||||
list: (params?: { statut?: string; garden_id?: number; planting_id?: number }) =>
|
||||
client.get<Task[]>('/api/tasks', { params }).then(r => r.data),
|
||||
get: (id: number) => client.get<Task>(`/api/tasks/${id}`).then(r => r.data),
|
||||
create: (t: Partial<Task>) => client.post<Task>('/api/tasks', t).then(r => r.data),
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface Tool {
|
||||
categorie?: string
|
||||
photo_url?: string
|
||||
video_url?: string
|
||||
notice_texte?: string
|
||||
notice_fichier_url?: string
|
||||
boutique_nom?: string
|
||||
boutique_url?: string
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
>
|
||||
<option :value="null">— Associer à une plante existante (optionnel)</option>
|
||||
<option v-for="p in plants" :key="p.id" :value="p.id">
|
||||
{{ p.nom_commun }}
|
||||
{{ formatPlantLabel(p) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { formatPlantLabel } from '@/utils/plants'
|
||||
|
||||
interface IdentifyResult {
|
||||
species: string
|
||||
@@ -122,6 +123,7 @@ interface IdentifyResult {
|
||||
interface Plant {
|
||||
id: number
|
||||
nom_commun: string
|
||||
variete?: string
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/// <reference types="../../node_modules/.vue-global-types/vue_3.5_0_0_0.d.ts" />
|
||||
import { ref, onMounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { formatPlantLabel } from '@/utils/plants';
|
||||
const emit = defineEmits();
|
||||
const previewUrl = ref(null);
|
||||
const imageFile = ref(null);
|
||||
@@ -186,7 +187,7 @@ else {
|
||||
key: (p.id),
|
||||
value: (p.id),
|
||||
});
|
||||
(p.nom_commun);
|
||||
(__VLS_ctx.formatPlantLabel(p));
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex gap-2" },
|
||||
@@ -345,6 +346,7 @@ var __VLS_dollars;
|
||||
const __VLS_self = (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
formatPlantLabel: formatPlantLabel,
|
||||
previewUrl: previewUrl,
|
||||
loading: loading,
|
||||
saving: saving,
|
||||
|
||||
@@ -6,7 +6,7 @@ export const useTasksStore = defineStore('tasks', () => {
|
||||
const tasks = ref<Task[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
async function fetchAll(params?: { statut?: string; garden_id?: number }) {
|
||||
async function fetchAll(params?: { statut?: string; garden_id?: number; planting_id?: number }) {
|
||||
loading.value = true
|
||||
tasks.value = await tasksApi.list(params)
|
||||
loading.value = false
|
||||
|
||||
6
frontend/src/utils/plants.js
Normal file
6
frontend/src/utils/plants.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export function formatPlantLabel(plant) {
|
||||
if (plant.variete && plant.variete.trim()) {
|
||||
return `${plant.nom_commun} — ${plant.variete.trim()}`;
|
||||
}
|
||||
return plant.nom_commun;
|
||||
}
|
||||
12
frontend/src/utils/plants.ts
Normal file
12
frontend/src/utils/plants.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface PlantLabelData {
|
||||
id?: number
|
||||
nom_commun: string
|
||||
variete?: string | null
|
||||
}
|
||||
|
||||
export function formatPlantLabel(plant: PlantLabelData): string {
|
||||
if (plant.variete && plant.variete.trim()) {
|
||||
return `${plant.nom_commun} — ${plant.variete.trim()}`
|
||||
}
|
||||
return plant.nom_commun
|
||||
}
|
||||
14
frontend/src/utils/uiSizeDefaults.js
Normal file
14
frontend/src/utils/uiSizeDefaults.js
Normal file
@@ -0,0 +1,14 @@
|
||||
export const UI_SIZE_DEFAULTS = {
|
||||
ui_font_size: 14,
|
||||
ui_menu_font_size: 13,
|
||||
ui_menu_icon_size: 18,
|
||||
ui_thumb_size: 96,
|
||||
};
|
||||
export function applyUiSizesToRoot(data) {
|
||||
const root = document.documentElement;
|
||||
for (const [key, def] of Object.entries(UI_SIZE_DEFAULTS)) {
|
||||
const val = Number(data[key]) || def;
|
||||
const prop = '--' + key.replace(/_/g, '-');
|
||||
root.style.setProperty(prop, `${val}px`);
|
||||
}
|
||||
}
|
||||
@@ -64,10 +64,13 @@
|
||||
🔗 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"
|
||||
@click="toggleAdventice(lightbox!)"
|
||||
:class="[
|
||||
'px-3 py-2 rounded-lg text-xs font-medium transition-colors',
|
||||
isAdventice(lightbox!) ? 'bg-red/20 text-red hover:bg-red/30' : 'bg-green/20 text-green hover:bg-green/30'
|
||||
]"
|
||||
>
|
||||
🌾 Marquer adventice
|
||||
{{ isAdventice(lightbox!) ? '🪓 Retirer adventice' : '🌾 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">
|
||||
@@ -87,7 +90,7 @@
|
||||
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green mb-4">
|
||||
<option :value="null">-- Choisir une plante --</option>
|
||||
<option v-for="p in plantsStore.plants" :key="p.id" :value="p.id">
|
||||
{{ p.nom_commun }}{{ p.variete ? ' — ' + p.variete : '' }}
|
||||
{{ formatPlantLabel(p) }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="flex gap-2 justify-end">
|
||||
@@ -110,6 +113,7 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import axios from 'axios'
|
||||
import PhotoIdentifyModal from '@/components/PhotoIdentifyModal.vue'
|
||||
import { usePlantsStore } from '@/stores/plants'
|
||||
import { formatPlantLabel } from '@/utils/plants'
|
||||
|
||||
interface Media {
|
||||
id: number; entity_type: string; entity_id: number
|
||||
@@ -152,7 +156,8 @@ function labelFor(type: string) {
|
||||
}
|
||||
|
||||
function plantName(id: number) {
|
||||
return plantsStore.plants.find(p => p.id === id)?.nom_commun ?? ''
|
||||
const plant = plantsStore.plants.find(p => p.id === id)
|
||||
return plant ? formatPlantLabel(plant) : ''
|
||||
}
|
||||
|
||||
function openLightbox(m: Media) { lightbox.value = m }
|
||||
@@ -191,6 +196,29 @@ async function markAsAdventice(m: Media) {
|
||||
}
|
||||
}
|
||||
|
||||
function isAdventice(m: Media) {
|
||||
return m.entity_type === 'adventice'
|
||||
}
|
||||
|
||||
async function toggleAdventice(m: Media) {
|
||||
if (isAdventice(m)) {
|
||||
await axios.patch(`/api/media/${m.id}`, {
|
||||
entity_type: 'bibliotheque',
|
||||
entity_id: 0,
|
||||
})
|
||||
const target = medias.value.find(x => x.id === m.id)
|
||||
if (target) {
|
||||
target.entity_type = 'bibliotheque'
|
||||
target.entity_id = 0
|
||||
}
|
||||
if (lightbox.value?.id === m.id) {
|
||||
lightbox.value = { ...lightbox.value, entity_type: 'bibliotheque', entity_id: 0 }
|
||||
}
|
||||
return
|
||||
}
|
||||
await markAsAdventice(m)
|
||||
}
|
||||
|
||||
async function deleteMedia(m: Media) {
|
||||
if (!confirm('Supprimer cette photo ?')) return
|
||||
await axios.delete(`/api/media/${m.id}`)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed, onMounted, ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
import PhotoIdentifyModal from '@/components/PhotoIdentifyModal.vue';
|
||||
import { usePlantsStore } from '@/stores/plants';
|
||||
import { formatPlantLabel } from '@/utils/plants';
|
||||
const medias = ref([]);
|
||||
const loading = ref(false);
|
||||
const lightbox = ref(null);
|
||||
@@ -30,7 +31,8 @@ function labelFor(type) {
|
||||
return map[type] ?? '📷';
|
||||
}
|
||||
function plantName(id) {
|
||||
return plantsStore.plants.find(p => p.id === id)?.nom_commun ?? '';
|
||||
const plant = plantsStore.plants.find(p => p.id === id);
|
||||
return plant ? formatPlantLabel(plant) : '';
|
||||
}
|
||||
function openLightbox(m) { lightbox.value = m; }
|
||||
function startLink(m) {
|
||||
@@ -68,6 +70,27 @@ async function markAsAdventice(m) {
|
||||
lightbox.value = { ...lightbox.value, entity_type: 'adventice', entity_id: 0 };
|
||||
}
|
||||
}
|
||||
function isAdventice(m) {
|
||||
return m.entity_type === 'adventice';
|
||||
}
|
||||
async function toggleAdventice(m) {
|
||||
if (isAdventice(m)) {
|
||||
await axios.patch(`/api/media/${m.id}`, {
|
||||
entity_type: 'bibliotheque',
|
||||
entity_id: 0,
|
||||
});
|
||||
const target = medias.value.find(x => x.id === m.id);
|
||||
if (target) {
|
||||
target.entity_type = 'bibliotheque';
|
||||
target.entity_id = 0;
|
||||
}
|
||||
if (lightbox.value?.id === m.id) {
|
||||
lightbox.value = { ...lightbox.value, entity_type: 'bibliotheque', entity_id: 0 };
|
||||
}
|
||||
return;
|
||||
}
|
||||
await markAsAdventice(m);
|
||||
}
|
||||
async function deleteMedia(m) {
|
||||
if (!confirm('Supprimer cette photo ?'))
|
||||
return;
|
||||
@@ -240,10 +263,14 @@ if (__VLS_ctx.lightbox) {
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.lightbox))
|
||||
return;
|
||||
__VLS_ctx.markAsAdventice(__VLS_ctx.lightbox);
|
||||
__VLS_ctx.toggleAdventice(__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" },
|
||||
...{ class: ([
|
||||
'px-3 py-2 rounded-lg text-xs font-medium transition-colors',
|
||||
__VLS_ctx.isAdventice(__VLS_ctx.lightbox) ? 'bg-red/20 text-red hover:bg-red/30' : 'bg-green/20 text-green hover:bg-green/30'
|
||||
]) },
|
||||
});
|
||||
(__VLS_ctx.isAdventice(__VLS_ctx.lightbox) ? '🪓 Retirer adventice' : '🌾 Marquer adventice');
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.lightbox))
|
||||
@@ -289,8 +316,7 @@ if (__VLS_ctx.linkMedia) {
|
||||
key: (p.id),
|
||||
value: (p.id),
|
||||
});
|
||||
(p.nom_commun);
|
||||
(p.variete ? ' — ' + p.variete : '');
|
||||
(__VLS_ctx.formatPlantLabel(p));
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex gap-2 justify-end" },
|
||||
@@ -451,15 +477,6 @@ if (__VLS_ctx.showIdentify) {
|
||||
/** @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']} */ ;
|
||||
@@ -526,6 +543,7 @@ const __VLS_self = (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
PhotoIdentifyModal: PhotoIdentifyModal,
|
||||
formatPlantLabel: formatPlantLabel,
|
||||
loading: loading,
|
||||
lightbox: lightbox,
|
||||
showIdentify: showIdentify,
|
||||
@@ -540,7 +558,8 @@ const __VLS_self = (await import('vue')).defineComponent({
|
||||
openLightbox: openLightbox,
|
||||
startLink: startLink,
|
||||
confirmLink: confirmLink,
|
||||
markAsAdventice: markAsAdventice,
|
||||
isAdventice: isAdventice,
|
||||
toggleAdventice: toggleAdventice,
|
||||
deleteMedia: deleteMedia,
|
||||
onIdentified: onIdentified,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="p-4 max-w-3xl mx-auto">
|
||||
<div class="p-4 max-w-5xl mx-auto">
|
||||
<button class="text-text-muted text-sm mb-4 hover:text-text" @click="router.back()">← Retour</button>
|
||||
|
||||
<div v-if="garden">
|
||||
|
||||
@@ -32,7 +32,7 @@ const __VLS_ctx = {};
|
||||
let __VLS_components;
|
||||
let __VLS_directives;
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "p-4 max-w-3xl mx-auto" },
|
||||
...{ class: "p-4 max-w-5xl mx-auto" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
@@ -147,7 +147,7 @@ else {
|
||||
});
|
||||
}
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-3xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-5xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="p-4 max-w-2xl mx-auto">
|
||||
<div class="p-4 max-w-5xl mx-auto">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold text-green">🪴 Jardins</h1>
|
||||
<button class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
|
||||
|
||||
@@ -113,7 +113,7 @@ 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-5xl mx-auto" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center justify-between mb-6" },
|
||||
@@ -486,7 +486,7 @@ if (__VLS_ctx.showForm) {
|
||||
});
|
||||
}
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-5xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
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>
|
||||
<p v-if="t.notice_texte" class="text-text-muted text-xs whitespace-pre-line">{{ t.notice_texte }}</p>
|
||||
<a v-else-if="t.notice_fichier_url" :href="t.notice_fichier_url" target="_blank" rel="noopener noreferrer"
|
||||
class="text-aqua text-xs hover:underline truncate">📄 Notice (fichier)</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"
|
||||
@@ -79,14 +80,8 @@
|
||||
<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>
|
||||
<textarea v-model="form.notice_texte" placeholder="Notice (texte libre)..."
|
||||
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-24" />
|
||||
<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">
|
||||
@@ -110,10 +105,8 @@ const showForm = ref(false)
|
||||
const editId = ref<number | null>(null)
|
||||
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: '',
|
||||
@@ -123,14 +116,10 @@ const form = reactive({
|
||||
prix_achat: undefined as number | undefined,
|
||||
photo_url: '',
|
||||
video_url: '',
|
||||
notice_texte: '',
|
||||
notice_fichier_url: '',
|
||||
})
|
||||
|
||||
function fileNameFromUrl(url: string) {
|
||||
if (!url) return ''
|
||||
return url.split('/').pop() || url
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
Object.assign(form, {
|
||||
nom: '',
|
||||
@@ -141,6 +130,7 @@ function resetForm() {
|
||||
prix_achat: undefined,
|
||||
photo_url: '',
|
||||
video_url: '',
|
||||
notice_texte: '',
|
||||
notice_fichier_url: '',
|
||||
})
|
||||
}
|
||||
@@ -150,10 +140,8 @@ function openCreate() {
|
||||
resetForm()
|
||||
photoFile.value = null
|
||||
videoFile.value = null
|
||||
noticeFile.value = null
|
||||
photoPreview.value = ''
|
||||
videoPreview.value = ''
|
||||
noticeFileName.value = ''
|
||||
showForm.value = true
|
||||
}
|
||||
|
||||
@@ -171,13 +159,6 @@ function onVideoSelected(event: Event) {
|
||||
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, {
|
||||
@@ -189,14 +170,13 @@ function startEdit(t: Tool) {
|
||||
prix_achat: t.prix_achat,
|
||||
photo_url: t.photo_url || '',
|
||||
video_url: t.video_url || '',
|
||||
notice_texte: t.notice_texte || '',
|
||||
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
|
||||
}
|
||||
|
||||
@@ -205,10 +185,8 @@ function closeForm() {
|
||||
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> {
|
||||
@@ -229,6 +207,7 @@ async function submitTool() {
|
||||
prix_achat: form.prix_achat,
|
||||
photo_url: form.photo_url || undefined,
|
||||
video_url: form.video_url || undefined,
|
||||
notice_texte: form.notice_texte || undefined,
|
||||
notice_fichier_url: form.notice_fichier_url || undefined,
|
||||
}
|
||||
|
||||
@@ -238,11 +217,10 @@ async function submitTool() {
|
||||
saved = await toolsStore.create(payload)
|
||||
}
|
||||
|
||||
if (saved.id && (photoFile.value || videoFile.value || noticeFile.value)) {
|
||||
if (saved.id && (photoFile.value || videoFile.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)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,8 @@ const showForm = ref(false);
|
||||
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: '',
|
||||
@@ -20,13 +18,9 @@ const form = reactive({
|
||||
prix_achat: undefined,
|
||||
photo_url: '',
|
||||
video_url: '',
|
||||
notice_texte: '',
|
||||
notice_fichier_url: '',
|
||||
});
|
||||
function fileNameFromUrl(url) {
|
||||
if (!url)
|
||||
return '';
|
||||
return url.split('/').pop() || url;
|
||||
}
|
||||
function resetForm() {
|
||||
Object.assign(form, {
|
||||
nom: '',
|
||||
@@ -37,6 +31,7 @@ function resetForm() {
|
||||
prix_achat: undefined,
|
||||
photo_url: '',
|
||||
video_url: '',
|
||||
notice_texte: '',
|
||||
notice_fichier_url: '',
|
||||
});
|
||||
}
|
||||
@@ -45,10 +40,8 @@ function openCreate() {
|
||||
resetForm();
|
||||
photoFile.value = null;
|
||||
videoFile.value = null;
|
||||
noticeFile.value = null;
|
||||
photoPreview.value = '';
|
||||
videoPreview.value = '';
|
||||
noticeFileName.value = '';
|
||||
showForm.value = true;
|
||||
}
|
||||
function onPhotoSelected(event) {
|
||||
@@ -65,12 +58,6 @@ function onVideoSelected(event) {
|
||||
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, {
|
||||
@@ -82,14 +69,13 @@ function startEdit(t) {
|
||||
prix_achat: t.prix_achat,
|
||||
photo_url: t.photo_url || '',
|
||||
video_url: t.video_url || '',
|
||||
notice_texte: t.notice_texte || '',
|
||||
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() {
|
||||
@@ -97,10 +83,8 @@ function closeForm() {
|
||||
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();
|
||||
@@ -119,6 +103,7 @@ async function submitTool() {
|
||||
prix_achat: form.prix_achat,
|
||||
photo_url: form.photo_url || undefined,
|
||||
video_url: form.video_url || undefined,
|
||||
notice_texte: form.notice_texte || undefined,
|
||||
notice_fichier_url: form.notice_fichier_url || undefined,
|
||||
};
|
||||
if (editId.value) {
|
||||
@@ -127,14 +112,12 @@ async function submitTool() {
|
||||
else {
|
||||
saved = await toolsStore.create(payload);
|
||||
}
|
||||
if (saved.id && (photoFile.value || videoFile.value || noticeFile.value)) {
|
||||
if (saved.id && (photoFile.value || videoFile.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);
|
||||
}
|
||||
@@ -244,7 +227,13 @@ for (const [t] of __VLS_getVForSourceType((__VLS_ctx.toolsStore.tools))) {
|
||||
...{ class: "text-aqua text-xs hover:underline truncate" },
|
||||
});
|
||||
}
|
||||
if (t.notice_fichier_url) {
|
||||
if (t.notice_texte) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-text-muted text-xs whitespace-pre-line" },
|
||||
});
|
||||
(t.notice_texte);
|
||||
}
|
||||
else if (t.notice_fichier_url) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.a, __VLS_intrinsicElements.a)({
|
||||
href: (t.notice_fichier_url),
|
||||
target: "_blank",
|
||||
@@ -382,22 +371,11 @@ if (__VLS_ctx.showForm) {
|
||||
...{ 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.textarea)({
|
||||
value: (__VLS_ctx.form.notice_texte),
|
||||
placeholder: "Notice (texte libre)...",
|
||||
...{ 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-24" },
|
||||
});
|
||||
__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" },
|
||||
});
|
||||
@@ -480,6 +458,9 @@ if (__VLS_ctx.showForm) {
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['truncate']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['whitespace-pre-line']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-aqua']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
|
||||
@@ -640,10 +621,6 @@ if (__VLS_ctx.showForm) {
|
||||
/** @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']} */ ;
|
||||
@@ -655,10 +632,8 @@ if (__VLS_ctx.showForm) {
|
||||
/** @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['resize-none']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['h-24']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['justify-end']} */ ;
|
||||
@@ -684,13 +659,10 @@ const __VLS_self = (await import('vue')).defineComponent({
|
||||
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,
|
||||
|
||||
@@ -2,32 +2,46 @@
|
||||
<div class="p-4 max-w-3xl mx-auto">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold text-green">📆 Planning</h1>
|
||||
<!-- Navigateur semaine -->
|
||||
<div class="flex items-center gap-3">
|
||||
<button @click="prevWeek" class="text-text-muted hover:text-text text-lg">◀</button>
|
||||
<span class="text-text text-sm font-medium">{{ weekLabel }}</span>
|
||||
<button @click="nextWeek" class="text-text-muted hover:text-text text-lg">▶</button>
|
||||
<button @click="goToday" class="text-xs text-green border border-green/30 rounded px-2 py-0.5 hover:bg-green/10">Auj.</button>
|
||||
<!-- Navigateur 4 semaines -->
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="prevPeriod"
|
||||
class="text-xs text-text-muted border border-bg-hard rounded px-2 py-1 hover:text-text hover:border-text-muted">
|
||||
Prev
|
||||
</button>
|
||||
<button @click="goToday"
|
||||
class="text-xs text-green border border-green/30 rounded px-2 py-1 hover:bg-green/10">
|
||||
Today
|
||||
</button>
|
||||
<button @click="nextPeriod"
|
||||
class="text-xs text-text-muted border border-bg-hard rounded px-2 py-1 hover:text-text hover:border-text-muted">
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-text text-sm font-medium mb-3">{{ periodLabel }}</div>
|
||||
|
||||
<!-- Grille semaine -->
|
||||
<!-- En-tête jours -->
|
||||
<div class="grid grid-cols-7 gap-1 mb-2">
|
||||
<div v-for="d in weekDays" :key="d.iso"
|
||||
:class="['text-center text-xs py-1 rounded',
|
||||
d.isToday ? 'text-green font-bold' : 'text-text-muted']">
|
||||
<div>{{ d.dayShort }}</div>
|
||||
<div :class="['text-sm font-semibold mt-0.5', d.isToday ? 'bg-green text-bg rounded-full w-6 h-6 flex items-center justify-center mx-auto' : '']">
|
||||
{{ d.dayNum }}
|
||||
</div>
|
||||
<div v-for="dayName in dayHeaders" :key="dayName" class="text-center text-xs py-1 rounded text-text-muted">
|
||||
{{ dayName }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tâches par jour -->
|
||||
<!-- Grille 4 semaines -->
|
||||
<div class="grid grid-cols-7 gap-1">
|
||||
<div v-for="d in weekDays" :key="d.iso"
|
||||
:class="['min-h-24 rounded-lg p-1 border transition-colors',
|
||||
d.isToday ? 'border-green/40 bg-green/5' : 'border-bg-hard bg-bg-soft']">
|
||||
<div v-for="d in periodDays" :key="d.iso"
|
||||
@click="selectDay(d.iso)"
|
||||
:class="['min-h-24 rounded-lg p-1 border transition-colors cursor-pointer',
|
||||
d.isToday ? 'border-green/40 bg-green/5' : 'border-bg-hard bg-bg-soft',
|
||||
selectedIso === d.iso ? 'ring-1 ring-yellow/60 border-yellow/40' : '']">
|
||||
<div class="text-[11px] text-text-muted mb-1">
|
||||
<span :class="d.isToday ? 'text-green font-bold' : ''">{{ d.dayNum }}</span>
|
||||
<span v-if="d.showMonth" class="ml-1">{{ d.monthShort }}</span>
|
||||
</div>
|
||||
<div v-if="todoTasksByDay[d.iso]?.length" class="flex items-center gap-1 flex-wrap mb-1">
|
||||
<span v-for="(t, i) in todoTasksByDay[d.iso].slice(0, 10)" :key="`${d.iso}-${t.id ?? i}`"
|
||||
:class="['w-1.5 h-1.5 rounded-full', dotClass(t.priorite)]"></span>
|
||||
</div>
|
||||
<div v-for="t in tasksByDay[d.iso] || []" :key="t.id"
|
||||
:class="['text-xs rounded px-1 py-0.5 mb-0.5 cursor-pointer hover:opacity-80 truncate',
|
||||
priorityClass(t.priorite)]"
|
||||
@@ -39,6 +53,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Détail jour sélectionné -->
|
||||
<div class="mt-4 bg-bg-soft rounded-lg p-3 border border-bg-hard">
|
||||
<div class="text-text text-sm font-semibold">{{ selectedLabel }}</div>
|
||||
<div class="text-text-muted text-xs mt-0.5">{{ selectedTasks.length }} tâche(s) planifiée(s)</div>
|
||||
<div v-if="!selectedTasks.length" class="text-text-muted text-xs mt-2">Aucune tâche planifiée ce jour.</div>
|
||||
<div v-else class="mt-2 space-y-1">
|
||||
<div v-for="t in selectedTasks" :key="t.id"
|
||||
class="bg-bg rounded px-2 py-1 border border-bg-hard flex items-center gap-2">
|
||||
<span :class="['w-2 h-2 rounded-full shrink-0', dotClass(t.priorite)]"></span>
|
||||
<span class="text-text text-xs flex-1 truncate">{{ t.titre }}</span>
|
||||
<span :class="['text-[10px] px-1.5 py-0.5 rounded shrink-0', statutClass(t.statut)]">{{ t.statut }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tâches sans date -->
|
||||
<div class="mt-6">
|
||||
<h2 class="text-text-muted text-xs uppercase tracking-widest mb-2">Sans date</h2>
|
||||
@@ -61,6 +90,8 @@ const store = useTasksStore()
|
||||
|
||||
const today = new Date()
|
||||
const weekStart = ref(getMonday(today))
|
||||
const selectedIso = ref(toIso(today))
|
||||
const dayHeaders = ['lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim']
|
||||
|
||||
function getMonday(d: Date) {
|
||||
const day = d.getDay()
|
||||
@@ -75,23 +106,26 @@ function toIso(d: Date) {
|
||||
return d.toISOString().slice(0, 10)
|
||||
}
|
||||
|
||||
const weekDays = computed(() => {
|
||||
const periodDays = computed(() => {
|
||||
const todayIso = toIso(today)
|
||||
return Array.from({ length: 7 }, (_, i) => {
|
||||
return Array.from({ length: 28 }, (_, i) => {
|
||||
const d = new Date(weekStart.value)
|
||||
d.setDate(d.getDate() + i)
|
||||
const dayNum = d.getDate()
|
||||
const monthShort = d.toLocaleDateString('fr-FR', { month: 'short' })
|
||||
return {
|
||||
iso: toIso(d),
|
||||
dayShort: d.toLocaleDateString('fr-FR', { weekday: 'short' }),
|
||||
dayNum: d.getDate(),
|
||||
dayNum,
|
||||
monthShort,
|
||||
showMonth: dayNum === 1 || i === 0,
|
||||
isToday: toIso(d) === todayIso,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const weekLabel = computed(() => {
|
||||
const start = weekDays.value[0]
|
||||
const end = weekDays.value[6]
|
||||
const periodLabel = computed(() => {
|
||||
const start = periodDays.value[0]
|
||||
const end = periodDays.value[27]
|
||||
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' })}`
|
||||
@@ -100,6 +134,7 @@ const weekLabel = computed(() => {
|
||||
const tasksByDay = computed(() => {
|
||||
const map: Record<string, typeof store.tasks> = {}
|
||||
for (const t of store.tasks) {
|
||||
if (t.statut === 'template') continue
|
||||
if (!t.echeance) continue
|
||||
const key = t.echeance.slice(0, 10)
|
||||
if (!map[key]) map[key] = []
|
||||
@@ -108,19 +143,47 @@ const tasksByDay = computed(() => {
|
||||
return map
|
||||
})
|
||||
|
||||
const unscheduled = computed(() => store.tasks.filter(t => !t.echeance && t.statut !== 'fait'))
|
||||
const todoTasksByDay = computed(() => {
|
||||
const map: Record<string, typeof store.tasks> = {}
|
||||
for (const [iso, tasks] of Object.entries(tasksByDay.value)) {
|
||||
map[iso] = tasks.filter(t => t.statut !== 'fait')
|
||||
}
|
||||
return map
|
||||
})
|
||||
|
||||
function prevWeek() {
|
||||
const selectedTasks = computed(() => tasksByDay.value[selectedIso.value] || [])
|
||||
const selectedLabel = computed(() => {
|
||||
if (!selectedIso.value) return 'Détail du jour'
|
||||
const d = new Date(selectedIso.value + 'T12:00:00')
|
||||
return d.toLocaleDateString('fr-FR', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
})
|
||||
|
||||
const unscheduled = computed(() => store.tasks.filter(t => !t.echeance && t.statut !== 'fait' && t.statut !== 'template'))
|
||||
|
||||
function prevPeriod() {
|
||||
const d = new Date(weekStart.value)
|
||||
d.setDate(d.getDate() - 7)
|
||||
d.setDate(d.getDate() - 28)
|
||||
weekStart.value = d
|
||||
selectedIso.value = toIso(d)
|
||||
}
|
||||
function nextWeek() {
|
||||
function nextPeriod() {
|
||||
const d = new Date(weekStart.value)
|
||||
d.setDate(d.getDate() + 7)
|
||||
d.setDate(d.getDate() + 28)
|
||||
weekStart.value = d
|
||||
selectedIso.value = toIso(d)
|
||||
}
|
||||
function goToday() {
|
||||
weekStart.value = getMonday(today)
|
||||
selectedIso.value = toIso(today)
|
||||
}
|
||||
function selectDay(iso: string) {
|
||||
selectedIso.value = iso
|
||||
}
|
||||
function goToday() { weekStart.value = getMonday(today) }
|
||||
|
||||
const priorityClass = (p: string) => ({
|
||||
haute: 'bg-red/20 text-red',
|
||||
|
||||
@@ -4,6 +4,8 @@ import { useTasksStore } from '@/stores/tasks';
|
||||
const store = useTasksStore();
|
||||
const today = new Date();
|
||||
const weekStart = ref(getMonday(today));
|
||||
const selectedIso = ref(toIso(today));
|
||||
const dayHeaders = ['lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim'];
|
||||
function getMonday(d) {
|
||||
const day = d.getDay();
|
||||
const diff = (day === 0 ? -6 : 1 - day);
|
||||
@@ -15,22 +17,25 @@ function getMonday(d) {
|
||||
function toIso(d) {
|
||||
return d.toISOString().slice(0, 10);
|
||||
}
|
||||
const weekDays = computed(() => {
|
||||
const periodDays = computed(() => {
|
||||
const todayIso = toIso(today);
|
||||
return Array.from({ length: 7 }, (_, i) => {
|
||||
return Array.from({ length: 28 }, (_, i) => {
|
||||
const d = new Date(weekStart.value);
|
||||
d.setDate(d.getDate() + i);
|
||||
const dayNum = d.getDate();
|
||||
const monthShort = d.toLocaleDateString('fr-FR', { month: 'short' });
|
||||
return {
|
||||
iso: toIso(d),
|
||||
dayShort: d.toLocaleDateString('fr-FR', { weekday: 'short' }),
|
||||
dayNum: d.getDate(),
|
||||
dayNum,
|
||||
monthShort,
|
||||
showMonth: dayNum === 1 || i === 0,
|
||||
isToday: toIso(d) === todayIso,
|
||||
};
|
||||
});
|
||||
});
|
||||
const weekLabel = computed(() => {
|
||||
const start = weekDays.value[0];
|
||||
const end = weekDays.value[6];
|
||||
const periodLabel = computed(() => {
|
||||
const start = periodDays.value[0];
|
||||
const end = periodDays.value[27];
|
||||
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' })}`;
|
||||
@@ -38,6 +43,8 @@ const weekLabel = computed(() => {
|
||||
const tasksByDay = computed(() => {
|
||||
const map = {};
|
||||
for (const t of store.tasks) {
|
||||
if (t.statut === 'template')
|
||||
continue;
|
||||
if (!t.echeance)
|
||||
continue;
|
||||
const key = t.echeance.slice(0, 10);
|
||||
@@ -47,18 +54,45 @@ const tasksByDay = computed(() => {
|
||||
}
|
||||
return map;
|
||||
});
|
||||
const unscheduled = computed(() => store.tasks.filter(t => !t.echeance && t.statut !== 'fait'));
|
||||
function prevWeek() {
|
||||
const todoTasksByDay = computed(() => {
|
||||
const map = {};
|
||||
for (const [iso, tasks] of Object.entries(tasksByDay.value)) {
|
||||
map[iso] = tasks.filter(t => t.statut !== 'fait');
|
||||
}
|
||||
return map;
|
||||
});
|
||||
const selectedTasks = computed(() => tasksByDay.value[selectedIso.value] || []);
|
||||
const selectedLabel = computed(() => {
|
||||
if (!selectedIso.value)
|
||||
return 'Détail du jour';
|
||||
const d = new Date(selectedIso.value + 'T12:00:00');
|
||||
return d.toLocaleDateString('fr-FR', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
});
|
||||
const unscheduled = computed(() => store.tasks.filter(t => !t.echeance && t.statut !== 'fait' && t.statut !== 'template'));
|
||||
function prevPeriod() {
|
||||
const d = new Date(weekStart.value);
|
||||
d.setDate(d.getDate() - 7);
|
||||
d.setDate(d.getDate() - 28);
|
||||
weekStart.value = d;
|
||||
selectedIso.value = toIso(d);
|
||||
}
|
||||
function nextWeek() {
|
||||
function nextPeriod() {
|
||||
const d = new Date(weekStart.value);
|
||||
d.setDate(d.getDate() + 7);
|
||||
d.setDate(d.getDate() + 28);
|
||||
weekStart.value = d;
|
||||
selectedIso.value = toIso(d);
|
||||
}
|
||||
function goToday() {
|
||||
weekStart.value = getMonday(today);
|
||||
selectedIso.value = toIso(today);
|
||||
}
|
||||
function selectDay(iso) {
|
||||
selectedIso.value = iso;
|
||||
}
|
||||
function goToday() { weekStart.value = getMonday(today); }
|
||||
const priorityClass = (p) => ({
|
||||
haute: 'bg-red/20 text-red',
|
||||
normale: 'bg-yellow/20 text-yellow',
|
||||
@@ -86,49 +120,71 @@ __VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1
|
||||
...{ class: "text-2xl font-bold text-green" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center gap-3" },
|
||||
...{ class: "flex items-center gap-2" },
|
||||
});
|
||||
__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" },
|
||||
...{ onClick: (__VLS_ctx.prevPeriod) },
|
||||
...{ class: "text-xs text-text-muted border border-bg-hard rounded px-2 py-1 hover:text-text hover:border-text-muted" },
|
||||
});
|
||||
__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" },
|
||||
...{ class: "text-xs text-green border border-green/30 rounded px-2 py-1 hover:bg-green/10" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.nextPeriod) },
|
||||
...{ class: "text-xs text-text-muted border border-bg-hard rounded px-2 py-1 hover:text-text hover:border-text-muted" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text text-sm font-medium mb-3" },
|
||||
});
|
||||
(__VLS_ctx.periodLabel);
|
||||
__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))) {
|
||||
for (const [dayName] of __VLS_getVForSourceType((__VLS_ctx.dayHeaders))) {
|
||||
__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']) },
|
||||
key: (dayName),
|
||||
...{ class: "text-center text-xs py-1 rounded 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);
|
||||
(dayName);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "grid grid-cols-7 gap-1" },
|
||||
});
|
||||
for (const [d] of __VLS_getVForSourceType((__VLS_ctx.weekDays))) {
|
||||
for (const [d] of __VLS_getVForSourceType((__VLS_ctx.periodDays))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
__VLS_ctx.selectDay(d.iso);
|
||||
} },
|
||||
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']) },
|
||||
...{ class: (['min-h-24 rounded-lg p-1 border transition-colors cursor-pointer',
|
||||
d.isToday ? 'border-green/40 bg-green/5' : 'border-bg-hard bg-bg-soft',
|
||||
__VLS_ctx.selectedIso === d.iso ? 'ring-1 ring-yellow/60 border-yellow/40' : '']) },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-[11px] text-text-muted mb-1" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: (d.isToday ? 'text-green font-bold' : '') },
|
||||
});
|
||||
(d.dayNum);
|
||||
if (d.showMonth) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "ml-1" },
|
||||
});
|
||||
(d.monthShort);
|
||||
}
|
||||
if (__VLS_ctx.todoTasksByDay[d.iso]?.length) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center gap-1 flex-wrap mb-1" },
|
||||
});
|
||||
for (const [t, i] of __VLS_getVForSourceType((__VLS_ctx.todoTasksByDay[d.iso].slice(0, 10)))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
key: (`${d.iso}-${t.id ?? i}`),
|
||||
...{ class: (['w-1.5 h-1.5 rounded-full', __VLS_ctx.dotClass(t.priorite)]) },
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const [t] of __VLS_getVForSourceType((__VLS_ctx.tasksByDay[d.iso] || []))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
key: (t.id),
|
||||
@@ -144,6 +200,44 @@ for (const [d] of __VLS_getVForSourceType((__VLS_ctx.weekDays))) {
|
||||
});
|
||||
}
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "mt-4 bg-bg-soft rounded-lg p-3 border border-bg-hard" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text text-sm font-semibold" },
|
||||
});
|
||||
(__VLS_ctx.selectedLabel);
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text-muted text-xs mt-0.5" },
|
||||
});
|
||||
(__VLS_ctx.selectedTasks.length);
|
||||
if (!__VLS_ctx.selectedTasks.length) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text-muted text-xs mt-2" },
|
||||
});
|
||||
}
|
||||
else {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "mt-2 space-y-1" },
|
||||
});
|
||||
for (const [t] of __VLS_getVForSourceType((__VLS_ctx.selectedTasks))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
key: (t.id),
|
||||
...{ class: "bg-bg rounded px-2 py-1 border border-bg-hard flex items-center gap-2" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: (['w-2 h-2 rounded-full shrink-0', __VLS_ctx.dotClass(t.priorite)]) },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-text text-xs flex-1 truncate" },
|
||||
});
|
||||
(t.titre);
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: (['text-[10px] px-1.5 py-0.5 rounded shrink-0', __VLS_ctx.statutClass(t.statut)]) },
|
||||
});
|
||||
(t.statut);
|
||||
}
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "mt-6" },
|
||||
});
|
||||
@@ -184,36 +278,93 @@ for (const [t] of __VLS_getVForSourceType((__VLS_ctx.unscheduled))) {
|
||||
/** @type {__VLS_StyleScopedClasses['text-green']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['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['hover:border-text-muted']} */ ;
|
||||
/** @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['py-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:bg-green/10']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:border-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-medium']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['grid-cols-7']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['grid-cols-7']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-[11px]']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['ml-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-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-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['p-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mt-0.5']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mt-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mt-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['space-y-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-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-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['truncate']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mt-6']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
@@ -240,13 +391,19 @@ var __VLS_dollars;
|
||||
const __VLS_self = (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
weekDays: weekDays,
|
||||
weekLabel: weekLabel,
|
||||
selectedIso: selectedIso,
|
||||
dayHeaders: dayHeaders,
|
||||
periodDays: periodDays,
|
||||
periodLabel: periodLabel,
|
||||
tasksByDay: tasksByDay,
|
||||
todoTasksByDay: todoTasksByDay,
|
||||
selectedTasks: selectedTasks,
|
||||
selectedLabel: selectedLabel,
|
||||
unscheduled: unscheduled,
|
||||
prevWeek: prevWeek,
|
||||
nextWeek: nextWeek,
|
||||
prevPeriod: prevPeriod,
|
||||
nextPeriod: nextPeriod,
|
||||
goToday: goToday,
|
||||
selectDay: selectDay,
|
||||
priorityClass: priorityClass,
|
||||
dotClass: dotClass,
|
||||
statutClass: statutClass,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="p-4 max-w-3xl mx-auto">
|
||||
<div class="p-4 max-w-5xl mx-auto">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold text-green">🌱 Plantations</h1>
|
||||
<button @click="showCreate = true"
|
||||
@@ -47,6 +47,12 @@
|
||||
openRecoltes === p.id ? 'bg-aqua/20 text-aqua' : 'bg-bg-hard text-text-muted hover:text-aqua']">
|
||||
🍅 Récoltes
|
||||
</button>
|
||||
<button
|
||||
class="text-xs px-2 py-1 rounded bg-blue/20 text-blue hover:bg-blue/30 transition-colors"
|
||||
@click="openTaskFromTemplate(p)"
|
||||
>
|
||||
➕ Tâche
|
||||
</button>
|
||||
<button @click="startEdit(p)" class="text-yellow text-xs hover:underline">Édit.</button>
|
||||
<button @click="store.remove(p.id!)" class="text-text-muted hover:text-red text-sm ml-1">✕</button>
|
||||
</div>
|
||||
@@ -93,6 +99,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal ajout tâche depuis template -->
|
||||
<div
|
||||
v-if="showTaskTemplateModal && taskTarget"
|
||||
class="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4"
|
||||
@click.self="closeTaskTemplateModal"
|
||||
>
|
||||
<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-1">Ajouter une tâche</h2>
|
||||
<p class="text-text-muted text-xs mb-4">
|
||||
Plantation: {{ plantName(taskTarget.variety_id) }} — {{ gardenName(taskTarget.garden_id) }}
|
||||
</p>
|
||||
<form @submit.prevent="createTaskFromTemplate" class="grid gap-3">
|
||||
<div>
|
||||
<label class="text-text-muted text-xs block mb-1">Template *</label>
|
||||
<select
|
||||
v-model.number="taskTemplateForm.template_id"
|
||||
required
|
||||
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green"
|
||||
>
|
||||
<option value="">Choisir un template</option>
|
||||
<option v-for="t in templates" :key="t.id" :value="t.id">
|
||||
{{ t.titre }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-text-muted text-xs block mb-1">Échéance (optionnel)</label>
|
||||
<input
|
||||
v-model="taskTemplateForm.echeance"
|
||||
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>
|
||||
<label class="text-text-muted text-xs block mb-1">Description complémentaire (optionnel)</label>
|
||||
<textarea
|
||||
v-model="taskTemplateForm.extra_description"
|
||||
rows="2"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<button type="button" @click="closeTaskTemplateModal" class="px-4 py-2 text-text-muted hover:text-text text-sm">
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
|
||||
>
|
||||
Créer la tâche
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal création / édition plantation -->
|
||||
<div v-if="showCreate" class="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4"
|
||||
@click.self="closeCreate">
|
||||
@@ -113,7 +175,7 @@
|
||||
class="bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green">
|
||||
<option value="">Choisir une plante</option>
|
||||
<option v-for="p in plantsStore.plants" :key="p.id" :value="p.id">
|
||||
{{ p.nom_commun }}{{ p.variete ? ' — ' + p.variete : '' }}
|
||||
{{ formatPlantLabel(p) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -181,7 +243,10 @@ import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { usePlantingsStore } from '@/stores/plantings'
|
||||
import { useGardensStore } from '@/stores/gardens'
|
||||
import { usePlantsStore } from '@/stores/plants'
|
||||
import type { Planting } from '@/api/plantings'
|
||||
import { recoltesApi, type Recolte } from '@/api/recoltes'
|
||||
import { tasksApi, type Task } from '@/api/tasks'
|
||||
import { formatPlantLabel } from '@/utils/plants'
|
||||
|
||||
const store = usePlantingsStore()
|
||||
const gardensStore = useGardensStore()
|
||||
@@ -193,6 +258,9 @@ const filterStatut = ref('')
|
||||
const openRecoltes = ref<number | null>(null)
|
||||
const recoltesList = ref<Recolte[]>([])
|
||||
const loadingRecoltes = ref(false)
|
||||
const templates = ref<Task[]>([])
|
||||
const showTaskTemplateModal = ref(false)
|
||||
const taskTarget = ref<Planting | null>(null)
|
||||
|
||||
const statuts = [
|
||||
{ val: '', label: 'Toutes' },
|
||||
@@ -213,13 +281,19 @@ const rForm = reactive({
|
||||
quantite: 1, unite: 'kg', date_recolte: new Date().toISOString().slice(0, 10)
|
||||
})
|
||||
|
||||
const taskTemplateForm = reactive({
|
||||
template_id: 0,
|
||||
echeance: '',
|
||||
extra_description: '',
|
||||
})
|
||||
|
||||
const filtered = computed(() =>
|
||||
filterStatut.value ? store.plantings.filter(p => p.statut === filterStatut.value) : store.plantings
|
||||
)
|
||||
|
||||
function plantName(id: number) {
|
||||
const p = plantsStore.plants.find(x => x.id === id)
|
||||
return p ? (p.variete ? `${p.nom_commun} (${p.variete})` : p.nom_commun) : `Plante #${id}`
|
||||
return p ? formatPlantLabel(p) : `Plante #${id}`
|
||||
}
|
||||
|
||||
function gardenName(id: number) {
|
||||
@@ -274,6 +348,44 @@ function startEdit(p: typeof store.plantings[0]) {
|
||||
|
||||
function closeCreate() { showCreate.value = false; editId.value = null }
|
||||
|
||||
async function loadTemplates() {
|
||||
templates.value = await tasksApi.list({ statut: 'template' })
|
||||
}
|
||||
|
||||
async function openTaskFromTemplate(planting: Planting) {
|
||||
if (!templates.value.length) {
|
||||
await loadTemplates()
|
||||
}
|
||||
taskTarget.value = planting
|
||||
Object.assign(taskTemplateForm, { template_id: 0, echeance: '', extra_description: '' })
|
||||
showTaskTemplateModal.value = true
|
||||
}
|
||||
|
||||
function closeTaskTemplateModal() {
|
||||
showTaskTemplateModal.value = false
|
||||
taskTarget.value = null
|
||||
}
|
||||
|
||||
async function createTaskFromTemplate() {
|
||||
if (!taskTarget.value || !taskTarget.value.id || !taskTemplateForm.template_id) return
|
||||
const tpl = templates.value.find(t => t.id === taskTemplateForm.template_id)
|
||||
if (!tpl) return
|
||||
const extra = taskTemplateForm.extra_description.trim()
|
||||
const description = [tpl.description || '', extra].filter(Boolean).join('\n\n')
|
||||
await tasksApi.create({
|
||||
titre: tpl.titre,
|
||||
description: description || undefined,
|
||||
garden_id: taskTarget.value.garden_id,
|
||||
planting_id: taskTarget.value.id,
|
||||
priorite: tpl.priorite || 'normale',
|
||||
echeance: taskTemplateForm.echeance || undefined,
|
||||
recurrence: tpl.recurrence ?? null,
|
||||
frequence_jours: tpl.frequence_jours ?? null,
|
||||
statut: 'a_faire',
|
||||
})
|
||||
closeTaskTemplateModal()
|
||||
}
|
||||
|
||||
async function createPlanting() {
|
||||
if (editId.value) {
|
||||
await store.update(editId.value, { ...cForm })
|
||||
@@ -292,5 +404,6 @@ onMounted(() => {
|
||||
store.fetchAll()
|
||||
gardensStore.fetchAll()
|
||||
plantsStore.fetchAll()
|
||||
loadTemplates()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { usePlantingsStore } from '@/stores/plantings';
|
||||
import { useGardensStore } from '@/stores/gardens';
|
||||
import { usePlantsStore } from '@/stores/plants';
|
||||
import { recoltesApi } from '@/api/recoltes';
|
||||
import { tasksApi } from '@/api/tasks';
|
||||
import { formatPlantLabel } from '@/utils/plants';
|
||||
const store = usePlantingsStore();
|
||||
const gardensStore = useGardensStore();
|
||||
const plantsStore = usePlantsStore();
|
||||
@@ -13,6 +15,9 @@ const filterStatut = ref('');
|
||||
const openRecoltes = ref(null);
|
||||
const recoltesList = ref([]);
|
||||
const loadingRecoltes = ref(false);
|
||||
const templates = ref([]);
|
||||
const showTaskTemplateModal = ref(false);
|
||||
const taskTarget = ref(null);
|
||||
const statuts = [
|
||||
{ val: '', label: 'Toutes' },
|
||||
{ val: 'prevu', label: '📋 Prévu' },
|
||||
@@ -29,10 +34,15 @@ const cForm = reactive({
|
||||
const rForm = reactive({
|
||||
quantite: 1, unite: 'kg', date_recolte: new Date().toISOString().slice(0, 10)
|
||||
});
|
||||
const taskTemplateForm = reactive({
|
||||
template_id: 0,
|
||||
echeance: '',
|
||||
extra_description: '',
|
||||
});
|
||||
const filtered = computed(() => filterStatut.value ? store.plantings.filter(p => p.statut === filterStatut.value) : store.plantings);
|
||||
function plantName(id) {
|
||||
const p = plantsStore.plants.find(x => x.id === id);
|
||||
return p ? (p.variete ? `${p.nom_commun} (${p.variete})` : p.nom_commun) : `Plante #${id}`;
|
||||
return p ? formatPlantLabel(p) : `Plante #${id}`;
|
||||
}
|
||||
function gardenName(id) {
|
||||
return gardensStore.gardens.find(g => g.id === id)?.nom ?? `Jardin #${id}`;
|
||||
@@ -89,6 +99,42 @@ function startEdit(p) {
|
||||
showCreate.value = true;
|
||||
}
|
||||
function closeCreate() { showCreate.value = false; editId.value = null; }
|
||||
async function loadTemplates() {
|
||||
templates.value = await tasksApi.list({ statut: 'template' });
|
||||
}
|
||||
async function openTaskFromTemplate(planting) {
|
||||
if (!templates.value.length) {
|
||||
await loadTemplates();
|
||||
}
|
||||
taskTarget.value = planting;
|
||||
Object.assign(taskTemplateForm, { template_id: 0, echeance: '', extra_description: '' });
|
||||
showTaskTemplateModal.value = true;
|
||||
}
|
||||
function closeTaskTemplateModal() {
|
||||
showTaskTemplateModal.value = false;
|
||||
taskTarget.value = null;
|
||||
}
|
||||
async function createTaskFromTemplate() {
|
||||
if (!taskTarget.value || !taskTarget.value.id || !taskTemplateForm.template_id)
|
||||
return;
|
||||
const tpl = templates.value.find(t => t.id === taskTemplateForm.template_id);
|
||||
if (!tpl)
|
||||
return;
|
||||
const extra = taskTemplateForm.extra_description.trim();
|
||||
const description = [tpl.description || '', extra].filter(Boolean).join('\n\n');
|
||||
await tasksApi.create({
|
||||
titre: tpl.titre,
|
||||
description: description || undefined,
|
||||
garden_id: taskTarget.value.garden_id,
|
||||
planting_id: taskTarget.value.id,
|
||||
priorite: tpl.priorite || 'normale',
|
||||
echeance: taskTemplateForm.echeance || undefined,
|
||||
recurrence: tpl.recurrence ?? null,
|
||||
frequence_jours: tpl.frequence_jours ?? null,
|
||||
statut: 'a_faire',
|
||||
});
|
||||
closeTaskTemplateModal();
|
||||
}
|
||||
async function createPlanting() {
|
||||
if (editId.value) {
|
||||
await store.update(editId.value, { ...cForm });
|
||||
@@ -107,13 +153,14 @@ onMounted(() => {
|
||||
store.fetchAll();
|
||||
gardensStore.fetchAll();
|
||||
plantsStore.fetchAll();
|
||||
loadTemplates();
|
||||
});
|
||||
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-3xl mx-auto" },
|
||||
...{ class: "p-4 max-w-5xl mx-auto" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center justify-between mb-6" },
|
||||
@@ -212,6 +259,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.openTaskFromTemplate(p);
|
||||
} },
|
||||
...{ class: "text-xs px-2 py-1 rounded bg-blue/20 text-blue hover:bg-blue/30 transition-colors" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
__VLS_ctx.startEdit(p);
|
||||
@@ -323,6 +376,76 @@ for (const [p] of __VLS_getVForSourceType((__VLS_ctx.filtered))) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (__VLS_ctx.showTaskTemplateModal && __VLS_ctx.taskTarget) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (__VLS_ctx.closeTaskTemplateModal) },
|
||||
...{ 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-1" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-text-muted text-xs mb-4" },
|
||||
});
|
||||
(__VLS_ctx.plantName(__VLS_ctx.taskTarget.variety_id));
|
||||
(__VLS_ctx.gardenName(__VLS_ctx.taskTarget.garden_id));
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
|
||||
...{ onSubmit: (__VLS_ctx.createTaskFromTemplate) },
|
||||
...{ 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.select, __VLS_intrinsicElements.select)({
|
||||
value: (__VLS_ctx.taskTemplateForm.template_id),
|
||||
required: true,
|
||||
...{ class: "bg-bg border border-bg-soft rounded-lg px-3 py-2 text-text text-sm w-full outline-none focus:border-green" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
|
||||
value: "",
|
||||
});
|
||||
for (const [t] of __VLS_getVForSourceType((__VLS_ctx.templates))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.option, __VLS_intrinsicElements.option)({
|
||||
key: (t.id),
|
||||
value: (t.id),
|
||||
});
|
||||
(t.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.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.taskTemplateForm.echeance);
|
||||
__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.taskTemplateForm.extra_description),
|
||||
rows: "2",
|
||||
...{ 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" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex gap-2 justify-end" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.closeTaskTemplateModal) },
|
||||
type: "button",
|
||||
...{ class: "px-4 py-2 text-text-muted hover:text-text text-sm" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
type: "submit",
|
||||
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
|
||||
});
|
||||
}
|
||||
if (__VLS_ctx.showCreate) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (__VLS_ctx.closeCreate) },
|
||||
@@ -375,8 +498,7 @@ if (__VLS_ctx.showCreate) {
|
||||
key: (p.id),
|
||||
value: (p.id),
|
||||
});
|
||||
(p.nom_commun);
|
||||
(p.variete ? ' — ' + p.variete : '');
|
||||
(__VLS_ctx.formatPlantLabel(p));
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "grid grid-cols-2 gap-3" },
|
||||
@@ -484,7 +606,7 @@ if (__VLS_ctx.showCreate) {
|
||||
(__VLS_ctx.editId ? 'Enregistrer' : 'Créer');
|
||||
}
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-3xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-5xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
@@ -539,6 +661,14 @@ if (__VLS_ctx.showCreate) {
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-blue/20']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-blue']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:bg-blue/30']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['transition-colors']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-yellow']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:underline']} */ ;
|
||||
@@ -652,6 +782,92 @@ if (__VLS_ctx.showCreate) {
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['block']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['block']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['block']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['resize-none']} */ ;
|
||||
/** @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['fixed']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['inset-0']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-black/60']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['z-50']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['justify-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-bg-hard']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['p-6']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-md']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-bg-soft']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-bold']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex-col']} */ ;
|
||||
@@ -833,6 +1049,7 @@ var __VLS_dollars;
|
||||
const __VLS_self = (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
formatPlantLabel: formatPlantLabel,
|
||||
store: store,
|
||||
gardensStore: gardensStore,
|
||||
plantsStore: plantsStore,
|
||||
@@ -842,9 +1059,13 @@ const __VLS_self = (await import('vue')).defineComponent({
|
||||
openRecoltes: openRecoltes,
|
||||
recoltesList: recoltesList,
|
||||
loadingRecoltes: loadingRecoltes,
|
||||
templates: templates,
|
||||
showTaskTemplateModal: showTaskTemplateModal,
|
||||
taskTarget: taskTarget,
|
||||
statuts: statuts,
|
||||
cForm: cForm,
|
||||
rForm: rForm,
|
||||
taskTemplateForm: taskTemplateForm,
|
||||
filtered: filtered,
|
||||
plantName: plantName,
|
||||
gardenName: gardenName,
|
||||
@@ -855,6 +1076,9 @@ const __VLS_self = (await import('vue')).defineComponent({
|
||||
deleteRecolte: deleteRecolte,
|
||||
startEdit: startEdit,
|
||||
closeCreate: closeCreate,
|
||||
openTaskFromTemplate: openTaskFromTemplate,
|
||||
closeTaskTemplateModal: closeTaskTemplateModal,
|
||||
createTaskFromTemplate: createTaskFromTemplate,
|
||||
createPlanting: createPlanting,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="p-4 max-w-4xl mx-auto">
|
||||
<div class="p-4 max-w-6xl mx-auto">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold text-green">🌱 Plantes</h1>
|
||||
<button @click="showForm = true" class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90">+ Ajouter</button>
|
||||
@@ -18,61 +18,63 @@
|
||||
<!-- Liste -->
|
||||
<div v-if="plantsStore.loading" class="text-text-muted text-sm">Chargement...</div>
|
||||
<div v-else-if="!filteredPlants.length" class="text-text-muted text-sm py-4">Aucune plante.</div>
|
||||
<div v-for="p in filteredPlants" :key="p.id"
|
||||
class="bg-bg-soft rounded-lg mb-2 border border-bg-hard overflow-hidden">
|
||||
<!-- En-tête cliquable -->
|
||||
<div class="p-4 flex items-start justify-between gap-4 cursor-pointer"
|
||||
@click="toggleDetail(p.id!)">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1 flex-wrap">
|
||||
<span class="text-text font-semibold">{{ p.nom_commun }}</span>
|
||||
<span v-if="p.variete" class="text-text-muted text-xs">— {{ p.variete }}</span>
|
||||
<span v-if="p.categorie" :class="['text-xs px-2 py-0.5 rounded-full font-medium', catClass(p.categorie)]">{{ catLabel(p.categorie) }}</span>
|
||||
</div>
|
||||
<div class="text-text-muted text-xs flex gap-3 flex-wrap">
|
||||
<span v-if="p.famille">🌿 {{ p.famille }}</span>
|
||||
<span v-if="p.espacement_cm">↔ {{ p.espacement_cm }}cm</span>
|
||||
<span v-if="p.besoin_eau">💧 {{ p.besoin_eau }}</span>
|
||||
<span v-if="p.plantation_mois">🌱 Plantation: mois {{ p.plantation_mois }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<span class="text-text-muted text-xs">{{ openId === p.id ? '▲' : '▼' }}</span>
|
||||
<button @click.stop="startEdit(p)" class="text-yellow text-xs hover:underline">Édit.</button>
|
||||
<button @click.stop="removePlant(p.id!)" class="text-red text-xs hover:underline">Suppr.</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panneau détail -->
|
||||
<div v-if="openId === p.id" class="border-t border-bg-hard px-4 pb-4 pt-3">
|
||||
<!-- Notes -->
|
||||
<p v-if="p.notes" class="text-text-muted text-sm mb-3 italic">{{ p.notes }}</p>
|
||||
|
||||
<!-- Galerie photos -->
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="text-text-muted text-xs font-medium uppercase tracking-wide">Photos</span>
|
||||
<button @click="openUpload(p)" class="text-green text-xs hover:underline">+ Ajouter une photo</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loadingPhotos" class="text-text-muted text-xs">Chargement...</div>
|
||||
<div v-else-if="!plantPhotos.length" class="text-text-muted text-xs mb-3">Aucune photo pour cette plante.</div>
|
||||
<div v-else class="grid grid-cols-4 gap-2 mb-3">
|
||||
<div v-for="m in plantPhotos" :key="m.id"
|
||||
class="aspect-square rounded overflow-hidden bg-bg-hard relative group cursor-pointer"
|
||||
@click="lightbox = m">
|
||||
<img :src="m.thumbnail_url || m.url" class="w-full h-full object-cover" />
|
||||
<div v-if="m.identified_common"
|
||||
class="absolute bottom-0 left-0 right-0 bg-black/70 text-xs text-green px-1 py-0.5 truncate">
|
||||
{{ m.identified_common }}
|
||||
<div v-else class="grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-3">
|
||||
<div v-for="p in filteredPlants" :key="p.id"
|
||||
class="bg-bg-soft rounded-lg border border-bg-hard overflow-hidden">
|
||||
<!-- En-tête cliquable -->
|
||||
<div class="p-4 flex items-start justify-between gap-4 cursor-pointer"
|
||||
@click="toggleDetail(p.id!)">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1 flex-wrap">
|
||||
<span class="text-text font-semibold">{{ p.nom_commun }}</span>
|
||||
<span v-if="p.variete" class="text-text-muted text-xs">— {{ p.variete }}</span>
|
||||
<span v-if="p.categorie" :class="['text-xs px-2 py-0.5 rounded-full font-medium', catClass(p.categorie)]">{{ catLabel(p.categorie) }}</span>
|
||||
</div>
|
||||
<button @click.stop="deletePhoto(m)" class="hidden group-hover:flex absolute top-1 right-1 bg-red/80 text-white text-xs rounded px-1">✕</button>
|
||||
<div class="text-text-muted text-xs flex gap-3 flex-wrap">
|
||||
<span v-if="p.famille">🌿 {{ p.famille }}</span>
|
||||
<span v-if="p.espacement_cm">↔ {{ p.espacement_cm }}cm</span>
|
||||
<span v-if="p.besoin_eau">💧 {{ p.besoin_eau }}</span>
|
||||
<span v-if="p.plantation_mois">🌱 Plantation: mois {{ p.plantation_mois }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<span class="text-text-muted text-xs">{{ openId === p.id ? '▲' : '▼' }}</span>
|
||||
<button @click.stop="startEdit(p)" class="text-yellow text-xs hover:underline">Édit.</button>
|
||||
<button @click.stop="removePlant(p.id!)" class="text-red text-xs hover:underline">Suppr.</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lier une photo existante de la bibliothèque -->
|
||||
<button @click="openLinkPhoto(p)" class="text-blue text-xs hover:underline">
|
||||
🔗 Lier une photo existante de la bibliothèque
|
||||
</button>
|
||||
<!-- Panneau détail -->
|
||||
<div v-if="openId === p.id" class="border-t border-bg-hard px-4 pb-4 pt-3">
|
||||
<!-- Notes -->
|
||||
<p v-if="p.notes" class="text-text-muted text-sm mb-3 italic">{{ p.notes }}</p>
|
||||
|
||||
<!-- Galerie photos -->
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="text-text-muted text-xs font-medium uppercase tracking-wide">Photos</span>
|
||||
<button @click="openUpload(p)" class="text-green text-xs hover:underline">+ Ajouter une photo</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loadingPhotos" class="text-text-muted text-xs">Chargement...</div>
|
||||
<div v-else-if="!plantPhotos.length" class="text-text-muted text-xs mb-3">Aucune photo pour cette plante.</div>
|
||||
<div v-else class="grid grid-cols-4 gap-2 mb-3">
|
||||
<div v-for="m in plantPhotos" :key="m.id"
|
||||
class="aspect-square rounded overflow-hidden bg-bg-hard relative group cursor-pointer"
|
||||
@click="lightbox = m">
|
||||
<img :src="m.thumbnail_url || m.url" class="w-full h-full object-cover" />
|
||||
<div v-if="m.identified_common"
|
||||
class="absolute bottom-0 left-0 right-0 bg-black/70 text-xs text-green px-1 py-0.5 truncate">
|
||||
{{ m.identified_common }}
|
||||
</div>
|
||||
<button @click.stop="deletePhoto(m)" class="hidden group-hover:flex absolute top-1 right-1 bg-red/80 text-white text-xs rounded px-1">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lier une photo existante de la bibliothèque -->
|
||||
<button @click="openLinkPhoto(p)" class="text-blue text-xs hover:underline">
|
||||
🔗 Lier une photo existante de la bibliothèque
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -193,7 +195,7 @@
|
||||
<!-- Modal upload photo pour une plante -->
|
||||
<div v-if="uploadTarget" class="fixed inset-0 bg-black/70 z-50 flex items-center justify-center p-4" @click.self="uploadTarget = null">
|
||||
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-sm border border-bg-soft">
|
||||
<h3 class="text-text font-bold mb-4">Photo pour "{{ uploadTarget.nom_commun }}"</h3>
|
||||
<h3 class="text-text font-bold mb-4">Photo pour "{{ formatPlantLabel(uploadTarget) }}"</h3>
|
||||
<label class="block border-2 border-dashed border-bg-soft rounded-lg p-6 text-center cursor-pointer hover:border-green transition-colors">
|
||||
<input type="file" accept="image/*" class="hidden" @change="uploadPhoto" />
|
||||
<div class="text-text-muted text-sm">📷 Choisir une image</div>
|
||||
@@ -205,7 +207,7 @@
|
||||
<!-- Modal lier photo existante -->
|
||||
<div v-if="linkTarget" class="fixed inset-0 bg-black/70 z-50 flex items-center justify-center p-4" @click.self="linkTarget = null">
|
||||
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-2xl border border-bg-soft max-h-[80vh] flex flex-col">
|
||||
<h3 class="text-text font-bold mb-3">Lier une photo à "{{ linkTarget.nom_commun }}"</h3>
|
||||
<h3 class="text-text font-bold mb-3">Lier une photo à "{{ formatPlantLabel(linkTarget) }}"</h3>
|
||||
<p class="text-text-muted text-xs mb-3">Sélectionne une photo de la bibliothèque (non liée à une plante)</p>
|
||||
<div v-if="!unlinkPhotos.length" class="text-text-muted text-sm py-4 text-center">Aucune photo disponible.</div>
|
||||
<div v-else class="grid grid-cols-4 gap-2 overflow-y-auto flex-1">
|
||||
@@ -247,6 +249,7 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { usePlantsStore } from '@/stores/plants'
|
||||
import type { Plant } from '@/api/plants'
|
||||
import { formatPlantLabel } from '@/utils/plants'
|
||||
|
||||
const plantsStore = usePlantsStore()
|
||||
const showForm = ref(false)
|
||||
@@ -285,9 +288,16 @@ const form = reactive({
|
||||
plantation_mois: '', recolte_mois: '', notes: '',
|
||||
})
|
||||
|
||||
const filteredPlants = computed(() =>
|
||||
selectedCat.value ? plantsStore.plants.filter(p => p.categorie === selectedCat.value) : plantsStore.plants
|
||||
)
|
||||
const filteredPlants = computed(() => {
|
||||
const source = selectedCat.value
|
||||
? plantsStore.plants.filter(p => p.categorie === selectedCat.value)
|
||||
: plantsStore.plants
|
||||
return [...source].sort((a, b) => {
|
||||
const byName = (a.nom_commun || '').localeCompare(b.nom_commun || '', 'fr', { sensitivity: 'base' })
|
||||
if (byName !== 0) return byName
|
||||
return (a.variete || '').localeCompare(b.variete || '', 'fr', { sensitivity: 'base' })
|
||||
})
|
||||
})
|
||||
|
||||
const catClass = (cat: string) => ({
|
||||
potager: 'bg-green/20 text-green',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { usePlantsStore } from '@/stores/plants';
|
||||
import { formatPlantLabel } from '@/utils/plants';
|
||||
const plantsStore = usePlantsStore();
|
||||
const showForm = ref(false);
|
||||
const editPlant = ref(null);
|
||||
@@ -29,7 +30,17 @@ const form = reactive({
|
||||
temp_min_c: undefined,
|
||||
plantation_mois: '', recolte_mois: '', notes: '',
|
||||
});
|
||||
const filteredPlants = computed(() => selectedCat.value ? plantsStore.plants.filter(p => p.categorie === selectedCat.value) : plantsStore.plants);
|
||||
const filteredPlants = computed(() => {
|
||||
const source = selectedCat.value
|
||||
? plantsStore.plants.filter(p => p.categorie === selectedCat.value)
|
||||
: plantsStore.plants;
|
||||
return [...source].sort((a, b) => {
|
||||
const byName = (a.nom_commun || '').localeCompare(b.nom_commun || '', 'fr', { sensitivity: 'base' });
|
||||
if (byName !== 0)
|
||||
return byName;
|
||||
return (a.variete || '').localeCompare(b.variete || '', 'fr', { sensitivity: 'base' });
|
||||
});
|
||||
});
|
||||
const catClass = (cat) => ({
|
||||
potager: 'bg-green/20 text-green',
|
||||
fleur: 'bg-orange/20 text-orange',
|
||||
@@ -148,7 +159,7 @@ 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" },
|
||||
...{ class: "p-4 max-w-6xl mx-auto" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center justify-between mb-6" },
|
||||
@@ -186,161 +197,194 @@ else if (!__VLS_ctx.filteredPlants.length) {
|
||||
...{ class: "text-text-muted text-sm py-4" },
|
||||
});
|
||||
}
|
||||
for (const [p] of __VLS_getVForSourceType((__VLS_ctx.filteredPlants))) {
|
||||
else {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
key: (p.id),
|
||||
...{ class: "bg-bg-soft rounded-lg mb-2 border border-bg-hard overflow-hidden" },
|
||||
...{ class: "grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-3" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
__VLS_ctx.toggleDetail(p.id);
|
||||
} },
|
||||
...{ class: "p-4 flex items-start justify-between gap-4 cursor-pointer" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex-1 min-w-0" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center gap-2 mb-1 flex-wrap" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-text font-semibold" },
|
||||
});
|
||||
(p.nom_commun);
|
||||
if (p.variete) {
|
||||
for (const [p] of __VLS_getVForSourceType((__VLS_ctx.filteredPlants))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
key: (p.id),
|
||||
...{ class: "bg-bg-soft rounded-lg border border-bg-hard overflow-hidden" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.plantsStore.loading))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.filteredPlants.length))
|
||||
return;
|
||||
__VLS_ctx.toggleDetail(p.id);
|
||||
} },
|
||||
...{ class: "p-4 flex items-start justify-between gap-4 cursor-pointer" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex-1 min-w-0" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center gap-2 mb-1 flex-wrap" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-text font-semibold" },
|
||||
});
|
||||
(p.nom_commun);
|
||||
if (p.variete) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-text-muted text-xs" },
|
||||
});
|
||||
(p.variete);
|
||||
}
|
||||
if (p.categorie) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: (['text-xs px-2 py-0.5 rounded-full font-medium', __VLS_ctx.catClass(p.categorie)]) },
|
||||
});
|
||||
(__VLS_ctx.catLabel(p.categorie));
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text-muted text-xs flex gap-3 flex-wrap" },
|
||||
});
|
||||
if (p.famille) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(p.famille);
|
||||
}
|
||||
if (p.espacement_cm) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(p.espacement_cm);
|
||||
}
|
||||
if (p.besoin_eau) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(p.besoin_eau);
|
||||
}
|
||||
if (p.plantation_mois) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(p.plantation_mois);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center gap-2 shrink-0" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-text-muted text-xs" },
|
||||
});
|
||||
(p.variete);
|
||||
}
|
||||
if (p.categorie) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: (['text-xs px-2 py-0.5 rounded-full font-medium', __VLS_ctx.catClass(p.categorie)]) },
|
||||
});
|
||||
(__VLS_ctx.catLabel(p.categorie));
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text-muted text-xs flex gap-3 flex-wrap" },
|
||||
});
|
||||
if (p.famille) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(p.famille);
|
||||
}
|
||||
if (p.espacement_cm) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(p.espacement_cm);
|
||||
}
|
||||
if (p.besoin_eau) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(p.besoin_eau);
|
||||
}
|
||||
if (p.plantation_mois) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({});
|
||||
(p.plantation_mois);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center gap-2 shrink-0" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-text-muted text-xs" },
|
||||
});
|
||||
(__VLS_ctx.openId === p.id ? '▲' : '▼');
|
||||
__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.removePlant(p.id);
|
||||
} },
|
||||
...{ class: "text-red text-xs hover:underline" },
|
||||
});
|
||||
if (__VLS_ctx.openId === p.id) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "border-t border-bg-hard px-4 pb-4 pt-3" },
|
||||
});
|
||||
if (p.notes) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-text-muted text-sm mb-3 italic" },
|
||||
});
|
||||
(p.notes);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "mb-2 flex items-center justify-between" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-text-muted text-xs font-medium uppercase tracking-wide" },
|
||||
(__VLS_ctx.openId === p.id ? '▲' : '▼');
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.plantsStore.loading))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.filteredPlants.length))
|
||||
return;
|
||||
__VLS_ctx.startEdit(p);
|
||||
} },
|
||||
...{ class: "text-yellow text-xs hover:underline" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.openId === p.id))
|
||||
if (!!(__VLS_ctx.plantsStore.loading))
|
||||
return;
|
||||
__VLS_ctx.openUpload(p);
|
||||
if (!!(!__VLS_ctx.filteredPlants.length))
|
||||
return;
|
||||
__VLS_ctx.removePlant(p.id);
|
||||
} },
|
||||
...{ class: "text-green text-xs hover:underline" },
|
||||
...{ class: "text-red text-xs hover:underline" },
|
||||
});
|
||||
if (__VLS_ctx.loadingPhotos) {
|
||||
if (__VLS_ctx.openId === p.id) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text-muted text-xs" },
|
||||
...{ class: "border-t border-bg-hard px-4 pb-4 pt-3" },
|
||||
});
|
||||
}
|
||||
else if (!__VLS_ctx.plantPhotos.length) {
|
||||
if (p.notes) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-text-muted text-sm mb-3 italic" },
|
||||
});
|
||||
(p.notes);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text-muted text-xs mb-3" },
|
||||
...{ class: "mb-2 flex items-center justify-between" },
|
||||
});
|
||||
}
|
||||
else {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "grid grid-cols-4 gap-2 mb-3" },
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-text-muted text-xs font-medium uppercase tracking-wide" },
|
||||
});
|
||||
for (const [m] of __VLS_getVForSourceType((__VLS_ctx.plantPhotos))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.plantsStore.loading))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.filteredPlants.length))
|
||||
return;
|
||||
if (!(__VLS_ctx.openId === p.id))
|
||||
return;
|
||||
__VLS_ctx.openUpload(p);
|
||||
} },
|
||||
...{ class: "text-green text-xs hover:underline" },
|
||||
});
|
||||
if (__VLS_ctx.loadingPhotos) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.openId === p.id))
|
||||
return;
|
||||
if (!!(__VLS_ctx.loadingPhotos))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.plantPhotos.length))
|
||||
return;
|
||||
__VLS_ctx.lightbox = m;
|
||||
} },
|
||||
key: (m.id),
|
||||
...{ class: "aspect-square rounded overflow-hidden bg-bg-hard relative group cursor-pointer" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
|
||||
src: (m.thumbnail_url || m.url),
|
||||
...{ 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.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.openId === p.id))
|
||||
return;
|
||||
if (!!(__VLS_ctx.loadingPhotos))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.plantPhotos.length))
|
||||
return;
|
||||
__VLS_ctx.deletePhoto(m);
|
||||
} },
|
||||
...{ class: "hidden group-hover:flex absolute top-1 right-1 bg-red/80 text-white text-xs rounded px-1" },
|
||||
...{ class: "text-text-muted text-xs" },
|
||||
});
|
||||
}
|
||||
else if (!__VLS_ctx.plantPhotos.length) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text-muted text-xs mb-3" },
|
||||
});
|
||||
}
|
||||
else {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "grid grid-cols-4 gap-2 mb-3" },
|
||||
});
|
||||
for (const [m] of __VLS_getVForSourceType((__VLS_ctx.plantPhotos))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.plantsStore.loading))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.filteredPlants.length))
|
||||
return;
|
||||
if (!(__VLS_ctx.openId === p.id))
|
||||
return;
|
||||
if (!!(__VLS_ctx.loadingPhotos))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.plantPhotos.length))
|
||||
return;
|
||||
__VLS_ctx.lightbox = m;
|
||||
} },
|
||||
key: (m.id),
|
||||
...{ class: "aspect-square rounded overflow-hidden bg-bg-hard relative group cursor-pointer" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.img)({
|
||||
src: (m.thumbnail_url || m.url),
|
||||
...{ 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.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.plantsStore.loading))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.filteredPlants.length))
|
||||
return;
|
||||
if (!(__VLS_ctx.openId === p.id))
|
||||
return;
|
||||
if (!!(__VLS_ctx.loadingPhotos))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.plantPhotos.length))
|
||||
return;
|
||||
__VLS_ctx.deletePhoto(m);
|
||||
} },
|
||||
...{ class: "hidden group-hover:flex absolute top-1 right-1 bg-red/80 text-white text-xs rounded px-1" },
|
||||
});
|
||||
}
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!!(__VLS_ctx.plantsStore.loading))
|
||||
return;
|
||||
if (!!(!__VLS_ctx.filteredPlants.length))
|
||||
return;
|
||||
if (!(__VLS_ctx.openId === p.id))
|
||||
return;
|
||||
__VLS_ctx.openLinkPhoto(p);
|
||||
} },
|
||||
...{ class: "text-blue text-xs hover:underline" },
|
||||
});
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(__VLS_ctx.openId === p.id))
|
||||
return;
|
||||
__VLS_ctx.openLinkPhoto(p);
|
||||
} },
|
||||
...{ class: "text-blue text-xs hover:underline" },
|
||||
});
|
||||
}
|
||||
}
|
||||
if (__VLS_ctx.showForm || __VLS_ctx.editPlant) {
|
||||
@@ -597,7 +641,7 @@ if (__VLS_ctx.uploadTarget) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.h3, __VLS_intrinsicElements.h3)({
|
||||
...{ class: "text-text font-bold mb-4" },
|
||||
});
|
||||
(__VLS_ctx.uploadTarget.nom_commun);
|
||||
(__VLS_ctx.formatPlantLabel(__VLS_ctx.uploadTarget));
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
|
||||
...{ class: "block border-2 border-dashed border-bg-soft rounded-lg p-6 text-center cursor-pointer hover:border-green transition-colors" },
|
||||
});
|
||||
@@ -634,7 +678,7 @@ if (__VLS_ctx.linkTarget) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.h3, __VLS_intrinsicElements.h3)({
|
||||
...{ class: "text-text font-bold mb-3" },
|
||||
});
|
||||
(__VLS_ctx.linkTarget.nom_commun);
|
||||
(__VLS_ctx.formatPlantLabel(__VLS_ctx.linkTarget));
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-text-muted text-xs mb-3" },
|
||||
});
|
||||
@@ -733,7 +777,7 @@ if (__VLS_ctx.lightbox) {
|
||||
});
|
||||
}
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-4xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-6xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
@@ -759,9 +803,13 @@ if (__VLS_ctx.lightbox) {
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['grid-cols-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['lg:grid-cols-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['2xl:grid-cols-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-bg-soft']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['overflow-hidden']} */ ;
|
||||
@@ -1274,6 +1322,7 @@ var __VLS_dollars;
|
||||
const __VLS_self = (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
formatPlantLabel: formatPlantLabel,
|
||||
plantsStore: plantsStore,
|
||||
showForm: showForm,
|
||||
editPlant: editPlant,
|
||||
|
||||
@@ -2,10 +2,80 @@
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { settingsApi } from '@/api/settings';
|
||||
import { meteoApi } from '@/api/meteo';
|
||||
import { UI_SIZE_DEFAULTS, applyUiSizesToRoot } from '@/utils/uiSizeDefaults';
|
||||
const debugMode = ref(false);
|
||||
const saving = ref(false);
|
||||
const savedMsg = ref('');
|
||||
const refreshingMeteo = ref(false);
|
||||
const downloadingBackup = ref(false);
|
||||
const backupMsg = ref('');
|
||||
const apiBaseUrl = detectApiBaseUrl();
|
||||
// --- UI Size settings ---
|
||||
const uiSizeSettings = [
|
||||
{ key: 'ui_font_size', label: 'Taille texte', min: 12, max: 20, step: 1, unit: 'px' },
|
||||
{ key: 'ui_menu_font_size', label: 'Texte menu latéral', min: 11, max: 18, step: 1, unit: 'px' },
|
||||
{ key: 'ui_menu_icon_size', label: 'Icônes menu', min: 14, max: 28, step: 1, unit: 'px' },
|
||||
{ key: 'ui_thumb_size', label: 'Miniatures images/vidéo', min: 60, max: 200, step: 4, unit: 'px' },
|
||||
];
|
||||
const uiSizes = ref({ ...UI_SIZE_DEFAULTS });
|
||||
const savingUi = ref(false);
|
||||
const uiSavedMsg = ref('');
|
||||
function applyUiSizes() {
|
||||
applyUiSizesToRoot(uiSizes.value);
|
||||
window.dispatchEvent(new CustomEvent('ui-sizes-updated', { detail: { ...uiSizes.value } }));
|
||||
}
|
||||
async function saveUiSettings() {
|
||||
savingUi.value = true;
|
||||
uiSavedMsg.value = '';
|
||||
try {
|
||||
const payload = {};
|
||||
for (const [k, v] of Object.entries(uiSizes.value))
|
||||
payload[k] = String(v);
|
||||
await settingsApi.update(payload);
|
||||
applyUiSizes();
|
||||
uiSavedMsg.value = 'Enregistré';
|
||||
setTimeout(() => { uiSavedMsg.value = ''; }, 1800);
|
||||
}
|
||||
catch {
|
||||
uiSavedMsg.value = 'Erreur lors de l\'enregistrement.';
|
||||
setTimeout(() => { uiSavedMsg.value = ''; }, 2200);
|
||||
}
|
||||
finally {
|
||||
savingUi.value = false;
|
||||
}
|
||||
}
|
||||
function resetUiSettings() {
|
||||
uiSizes.value = { ...UI_SIZE_DEFAULTS };
|
||||
applyUiSizes();
|
||||
}
|
||||
function detectApiBaseUrl() {
|
||||
const envBase = String(import.meta.env?.VITE_API_URL || '').trim();
|
||||
if (envBase) {
|
||||
if (envBase.startsWith('http://') || envBase.startsWith('https://')) {
|
||||
return envBase.replace(/\/$/, '');
|
||||
}
|
||||
if (envBase.startsWith('/')) {
|
||||
return window.location.origin;
|
||||
}
|
||||
}
|
||||
if (window.location.port === '8060') {
|
||||
return window.location.origin;
|
||||
}
|
||||
return `${window.location.protocol}//${window.location.hostname}:8060`;
|
||||
}
|
||||
function openInNewTab(path) {
|
||||
const url = `${apiBaseUrl}${path}`;
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
function openApiDocs() {
|
||||
openInNewTab('/docs');
|
||||
}
|
||||
function openApiRedoc() {
|
||||
openInNewTab('/redoc');
|
||||
}
|
||||
function openApiHealth() {
|
||||
openInNewTab('/api/health');
|
||||
}
|
||||
function toBool(value) {
|
||||
if (typeof value === 'boolean')
|
||||
return value;
|
||||
@@ -21,6 +91,12 @@ async function loadSettings() {
|
||||
const data = await settingsApi.get();
|
||||
debugMode.value = toBool(data.debug_mode);
|
||||
notifyDebugChanged(debugMode.value);
|
||||
for (const s of uiSizeSettings) {
|
||||
const v = data[s.key];
|
||||
if (v != null)
|
||||
uiSizes.value[s.key] = Number(v) || UI_SIZE_DEFAULTS[s.key];
|
||||
}
|
||||
applyUiSizes();
|
||||
}
|
||||
catch {
|
||||
// Laisse la valeur locale si l'API n'est pas disponible.
|
||||
@@ -48,6 +124,29 @@ async function refreshMeteo() {
|
||||
refreshingMeteo.value = false;
|
||||
}
|
||||
}
|
||||
async function downloadBackup() {
|
||||
downloadingBackup.value = true;
|
||||
backupMsg.value = '';
|
||||
try {
|
||||
const { blob, filename } = await settingsApi.downloadBackup();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
backupMsg.value = 'Téléchargement lancé.';
|
||||
}
|
||||
catch {
|
||||
backupMsg.value = 'Erreur lors de la sauvegarde.';
|
||||
}
|
||||
finally {
|
||||
downloadingBackup.value = false;
|
||||
window.setTimeout(() => { backupMsg.value = ''; }, 2200);
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
void loadSettings();
|
||||
});
|
||||
@@ -67,6 +166,61 @@ __VLS_asFunctionalElement(__VLS_intrinsicElements.section, __VLS_intrinsicElemen
|
||||
__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-4" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "grid grid-cols-1 gap-4" },
|
||||
});
|
||||
for (const [s] of __VLS_getVForSourceType((__VLS_ctx.uiSizeSettings))) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
key: (s.key),
|
||||
...{ class: "flex items-center gap-3" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.label, __VLS_intrinsicElements.label)({
|
||||
...{ class: "text-sm text-text w-44 shrink-0" },
|
||||
});
|
||||
(s.label);
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.input)({
|
||||
...{ onInput: (__VLS_ctx.applyUiSizes) },
|
||||
type: "range",
|
||||
min: (s.min),
|
||||
max: (s.max),
|
||||
step: (s.step),
|
||||
...{ class: "flex-1 accent-green" },
|
||||
});
|
||||
(__VLS_ctx.uiSizes[s.key]);
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-text-muted text-xs w-12 text-right" },
|
||||
});
|
||||
(__VLS_ctx.uiSizes[s.key]);
|
||||
(s.unit);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "mt-4 flex items-center gap-2" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.saveUiSettings) },
|
||||
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
|
||||
disabled: (__VLS_ctx.savingUi),
|
||||
});
|
||||
(__VLS_ctx.savingUi ? 'Enregistrement...' : 'Enregistrer');
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.resetUiSettings) },
|
||||
...{ class: "text-text-muted text-xs hover:text-text px-2" },
|
||||
});
|
||||
if (__VLS_ctx.uiSavedMsg) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-xs text-aqua" },
|
||||
});
|
||||
(__VLS_ctx.uiSavedMsg);
|
||||
}
|
||||
__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" },
|
||||
});
|
||||
@@ -108,19 +262,61 @@ __VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElement
|
||||
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 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-2" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-text-muted text-xs mb-3" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-text" },
|
||||
});
|
||||
(__VLS_ctx.apiBaseUrl);
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex flex-wrap items-center gap-2" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.openApiDocs) },
|
||||
...{ class: "bg-blue text-bg px-3 py-2 rounded-lg text-xs font-semibold hover:opacity-90" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.openApiRedoc) },
|
||||
...{ class: "bg-aqua text-bg px-3 py-2 rounded-lg text-xs font-semibold hover:opacity-90" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.openApiHealth) },
|
||||
...{ class: "bg-bg border border-bg-hard text-text px-3 py-2 rounded-lg text-xs font-semibold hover:border-text-muted" },
|
||||
});
|
||||
__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.p, __VLS_intrinsicElements.p)({
|
||||
...{ class: "text-text-muted text-sm mb-3" },
|
||||
});
|
||||
__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)({});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center gap-2" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.downloadBackup) },
|
||||
...{ class: "bg-aqua text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
|
||||
disabled: (__VLS_ctx.downloadingBackup),
|
||||
});
|
||||
(__VLS_ctx.downloadingBackup ? 'Préparation du ZIP...' : 'Télécharger la sauvegarde (.zip)');
|
||||
if (__VLS_ctx.backupMsg) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.span, __VLS_intrinsicElements.span)({
|
||||
...{ class: "text-xs text-aqua" },
|
||||
});
|
||||
(__VLS_ctx.backupMsg);
|
||||
}
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-3xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
|
||||
@@ -139,6 +335,52 @@ __VLS_asFunctionalElement(__VLS_intrinsicElements.li, __VLS_intrinsicElements.li
|
||||
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['grid']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['grid-cols-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-44']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['shrink-0']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['accent-green']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-12']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-right']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mt-4']} */ ;
|
||||
/** @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-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-2']} */ ;
|
||||
/** @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['inline-flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
@@ -185,12 +427,71 @@ __VLS_asFunctionalElement(__VLS_intrinsicElements.li, __VLS_intrinsicElements.li
|
||||
/** @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['space-y-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex-wrap']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-blue']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-aqua']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-bg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:opacity-90']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded-lg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['font-semibold']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['hover:border-text-muted']} */ ;
|
||||
/** @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['mb-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-aqua']} */ ;
|
||||
/** @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']} */ ;
|
||||
var __VLS_dollars;
|
||||
const __VLS_self = (await import('vue')).defineComponent({
|
||||
setup() {
|
||||
@@ -199,8 +500,22 @@ const __VLS_self = (await import('vue')).defineComponent({
|
||||
saving: saving,
|
||||
savedMsg: savedMsg,
|
||||
refreshingMeteo: refreshingMeteo,
|
||||
downloadingBackup: downloadingBackup,
|
||||
backupMsg: backupMsg,
|
||||
apiBaseUrl: apiBaseUrl,
|
||||
uiSizeSettings: uiSizeSettings,
|
||||
uiSizes: uiSizes,
|
||||
savingUi: savingUi,
|
||||
uiSavedMsg: uiSavedMsg,
|
||||
applyUiSizes: applyUiSizes,
|
||||
saveUiSettings: saveUiSettings,
|
||||
resetUiSettings: resetUiSettings,
|
||||
openApiDocs: openApiDocs,
|
||||
openApiRedoc: openApiRedoc,
|
||||
openApiHealth: openApiHealth,
|
||||
saveSettings: saveSettings,
|
||||
refreshMeteo: refreshMeteo,
|
||||
downloadBackup: downloadBackup,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="p-4 max-w-2xl mx-auto">
|
||||
<div class="p-4 max-w-5xl mx-auto">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold text-green">✅ Tâches</h1>
|
||||
<button class="bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90"
|
||||
@click="openCreate">+ Nouvelle</button>
|
||||
@click="openCreateTemplate">+ Nouveau template</button>
|
||||
</div>
|
||||
|
||||
<div v-for="[groupe, label] in groupes" :key="groupe" class="mb-6">
|
||||
@@ -18,17 +18,25 @@
|
||||
}">●</span>
|
||||
<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.description" class="text-text-muted text-xs">{{ t.description }}</div>
|
||||
<div v-if="t.echeance && t.statut !== 'template'" 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 v-if="t.planting_id && t.statut !== 'template'" class="text-text-muted text-xs">🌱 Plantation #{{ t.planting_id }}</div>
|
||||
</div>
|
||||
<div class="flex gap-1 items-center shrink-0">
|
||||
<button v-if="t.statut === 'a_faire'" class="text-xs text-blue hover:underline"
|
||||
@click="store.updateStatut(t.id!, 'en_cours')">→ En cours</button>
|
||||
<button v-if="t.statut === 'en_cours'" class="text-xs text-green hover:underline"
|
||||
@click="store.updateStatut(t.id!, 'fait')">✓ Fait</button>
|
||||
<button @click="startEdit(t)" class="text-xs text-yellow hover:underline ml-2">Édit.</button>
|
||||
<button
|
||||
v-if="t.statut === 'template'"
|
||||
@click="startEdit(t)"
|
||||
class="text-xs text-yellow hover:underline ml-2"
|
||||
>
|
||||
Édit.
|
||||
</button>
|
||||
<button class="text-xs text-text-muted hover:text-red ml-1" @click="store.remove(t.id!)">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,7 +45,7 @@
|
||||
<!-- Modal création / édition -->
|
||||
<div v-if="showForm" class="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4" @click.self="closeForm">
|
||||
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-md border border-bg-soft">
|
||||
<h2 class="text-text font-bold text-lg mb-4">{{ editId ? 'Modifier la tâche' : 'Nouvelle tâche' }}</h2>
|
||||
<h2 class="text-text font-bold text-lg mb-4">{{ editId ? 'Modifier la tâche' : 'Nouveau template' }}</h2>
|
||||
<form @submit.prevent="submit" class="grid gap-3">
|
||||
<div>
|
||||
<label class="text-text-muted text-xs block mb-1">Titre *</label>
|
||||
@@ -59,25 +67,17 @@
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-text-muted text-xs block mb-1">Statut</label>
|
||||
<select v-model="form.statut" class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm">
|
||||
<option value="a_faire">À faire</option>
|
||||
<option value="en_cours">En cours</option>
|
||||
<option value="fait">Terminé</option>
|
||||
</select>
|
||||
<label class="text-text-muted text-xs block mb-1">Type</label>
|
||||
<input value="Template" readonly
|
||||
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text-muted text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-text-muted text-xs block mb-1">Échéance</label>
|
||||
<input v-model="form.echeance" type="date"
|
||||
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" />
|
||||
</div>
|
||||
<div class="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>
|
||||
<p class="text-text-muted text-[11px] mt-1">Fréquence proposée quand la tâche est ajoutée depuis une plantation.</p>
|
||||
</div>
|
||||
<div v-if="form.repetition">
|
||||
<label class="text-text-muted text-xs block mb-1">Fréquence (jours)</label>
|
||||
@@ -93,7 +93,7 @@
|
||||
</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' }}
|
||||
{{ editId ? 'Enregistrer' : 'Créer le template' }}
|
||||
</button>
|
||||
<button type="button" class="text-text-muted text-sm px-4 py-2 hover:text-text" @click="closeForm">Annuler</button>
|
||||
</div>
|
||||
@@ -115,8 +115,7 @@ const form = reactive({
|
||||
titre: '',
|
||||
description: '',
|
||||
priorite: 'normale',
|
||||
statut: 'a_faire',
|
||||
echeance: '',
|
||||
statut: 'template',
|
||||
repetition: false,
|
||||
frequence_jours: undefined as number | undefined,
|
||||
})
|
||||
@@ -125,6 +124,7 @@ const groupes: [string, string][] = [
|
||||
['a_faire', 'À faire'],
|
||||
['en_cours', 'En cours'],
|
||||
['fait', 'Terminé'],
|
||||
['template', 'Templates'],
|
||||
]
|
||||
|
||||
const byStatut = (s: string) => store.tasks.filter(t => t.statut === s)
|
||||
@@ -133,33 +133,40 @@ function fmtDate(s: string) {
|
||||
return new Date(s + 'T12:00:00').toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' })
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
editId.value = null
|
||||
function resetForm() {
|
||||
Object.assign(form, {
|
||||
titre: '',
|
||||
description: '',
|
||||
priorite: 'normale',
|
||||
statut: 'a_faire',
|
||||
echeance: '',
|
||||
statut: 'template',
|
||||
repetition: false,
|
||||
frequence_jours: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
function openCreateTemplate() {
|
||||
editId.value = null
|
||||
resetForm()
|
||||
showForm.value = true
|
||||
}
|
||||
|
||||
function startEdit(t: Task) {
|
||||
editId.value = t.id!
|
||||
Object.assign(form, {
|
||||
titre: t.titre, description: (t as any).description || '',
|
||||
priorite: t.priorite, statut: t.statut,
|
||||
echeance: t.echeance ? t.echeance.slice(0, 10) : '',
|
||||
repetition: Boolean((t as any).recurrence || (t as any).frequence_jours),
|
||||
frequence_jours: (t as any).frequence_jours ?? undefined,
|
||||
titre: t.titre,
|
||||
description: t.description || '',
|
||||
priorite: t.priorite,
|
||||
statut: t.statut || 'template',
|
||||
repetition: Boolean(t.recurrence || t.frequence_jours),
|
||||
frequence_jours: t.frequence_jours ?? undefined,
|
||||
})
|
||||
showForm.value = true
|
||||
}
|
||||
|
||||
function closeForm() { showForm.value = false; editId.value = null }
|
||||
function closeForm() {
|
||||
showForm.value = false
|
||||
editId.value = null
|
||||
}
|
||||
|
||||
onMounted(() => store.fetchAll())
|
||||
|
||||
@@ -168,10 +175,11 @@ async function submit() {
|
||||
titre: form.titre,
|
||||
description: form.description,
|
||||
priorite: form.priorite,
|
||||
statut: form.statut,
|
||||
echeance: form.echeance || undefined,
|
||||
statut: 'template',
|
||||
recurrence: form.repetition ? 'jours' : null,
|
||||
frequence_jours: form.repetition ? (form.frequence_jours ?? 7) : null,
|
||||
echeance: undefined,
|
||||
planting_id: undefined,
|
||||
}
|
||||
if (editId.value) {
|
||||
await store.update(editId.value, payload)
|
||||
@@ -179,5 +187,6 @@ async function submit() {
|
||||
await store.create(payload)
|
||||
}
|
||||
closeForm()
|
||||
resetForm()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -8,8 +8,7 @@ const form = reactive({
|
||||
titre: '',
|
||||
description: '',
|
||||
priorite: 'normale',
|
||||
statut: 'a_faire',
|
||||
echeance: '',
|
||||
statut: 'template',
|
||||
repetition: false,
|
||||
frequence_jours: undefined,
|
||||
});
|
||||
@@ -17,46 +16,54 @@ const groupes = [
|
||||
['a_faire', 'À faire'],
|
||||
['en_cours', 'En cours'],
|
||||
['fait', 'Terminé'],
|
||||
['template', 'Templates'],
|
||||
];
|
||||
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;
|
||||
function resetForm() {
|
||||
Object.assign(form, {
|
||||
titre: '',
|
||||
description: '',
|
||||
priorite: 'normale',
|
||||
statut: 'a_faire',
|
||||
echeance: '',
|
||||
statut: 'template',
|
||||
repetition: false,
|
||||
frequence_jours: undefined,
|
||||
});
|
||||
}
|
||||
function openCreateTemplate() {
|
||||
editId.value = null;
|
||||
resetForm();
|
||||
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) : '',
|
||||
titre: t.titre,
|
||||
description: t.description || '',
|
||||
priorite: t.priorite,
|
||||
statut: t.statut || 'template',
|
||||
repetition: Boolean(t.recurrence || t.frequence_jours),
|
||||
frequence_jours: t.frequence_jours ?? undefined,
|
||||
});
|
||||
showForm.value = true;
|
||||
}
|
||||
function closeForm() { showForm.value = false; editId.value = null; }
|
||||
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,
|
||||
statut: 'template',
|
||||
recurrence: form.repetition ? 'jours' : null,
|
||||
frequence_jours: form.repetition ? (form.frequence_jours ?? 7) : null,
|
||||
echeance: undefined,
|
||||
planting_id: undefined,
|
||||
};
|
||||
if (editId.value) {
|
||||
await store.update(editId.value, payload);
|
||||
@@ -65,13 +72,14 @@ async function submit() {
|
||||
await store.create(payload);
|
||||
}
|
||||
closeForm();
|
||||
resetForm();
|
||||
}
|
||||
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-5xl mx-auto" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex items-center justify-between mb-6" },
|
||||
@@ -80,7 +88,7 @@ __VLS_asFunctionalElement(__VLS_intrinsicElements.h1, __VLS_intrinsicElements.h1
|
||||
...{ class: "text-2xl font-bold text-green" },
|
||||
});
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.openCreate) },
|
||||
...{ onClick: (__VLS_ctx.openCreateTemplate) },
|
||||
...{ class: "bg-green text-bg px-4 py-2 rounded-lg text-sm font-semibold hover:opacity-90" },
|
||||
});
|
||||
for (const [[groupe, label]] of __VLS_getVForSourceType((__VLS_ctx.groupes))) {
|
||||
@@ -116,7 +124,13 @@ for (const [[groupe, label]] of __VLS_getVForSourceType((__VLS_ctx.groupes))) {
|
||||
...{ class: "text-text text-sm" },
|
||||
});
|
||||
(t.titre);
|
||||
if (t.echeance) {
|
||||
if (t.description) {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text-muted text-xs" },
|
||||
});
|
||||
(t.description);
|
||||
}
|
||||
if (t.echeance && t.statut !== 'template') {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text-muted text-xs" },
|
||||
});
|
||||
@@ -128,6 +142,12 @@ for (const [[groupe, label]] of __VLS_getVForSourceType((__VLS_ctx.groupes))) {
|
||||
});
|
||||
(t.frequence_jours);
|
||||
}
|
||||
if (t.planting_id && t.statut !== 'template') {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "text-text-muted text-xs" },
|
||||
});
|
||||
(t.planting_id);
|
||||
}
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "flex gap-1 items-center shrink-0" },
|
||||
});
|
||||
@@ -151,12 +171,16 @@ 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" },
|
||||
});
|
||||
if (t.statut === 'template') {
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (...[$event]) => {
|
||||
if (!(t.statut === 'template'))
|
||||
return;
|
||||
__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);
|
||||
@@ -176,7 +200,7 @@ if (__VLS_ctx.showForm) {
|
||||
__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_ctx.editId ? 'Modifier la tâche' : 'Nouveau template');
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.form, __VLS_intrinsicElements.form)({
|
||||
...{ onSubmit: (__VLS_ctx.submit) },
|
||||
...{ class: "grid gap-3" },
|
||||
@@ -223,28 +247,11 @@ if (__VLS_ctx.showForm) {
|
||||
__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" },
|
||||
value: "Template",
|
||||
readonly: true,
|
||||
...{ class: "w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text-muted text-sm" },
|
||||
});
|
||||
(__VLS_ctx.form.echeance);
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.div, __VLS_intrinsicElements.div)({
|
||||
...{ class: "bg-bg rounded border border-bg-hard p-3" },
|
||||
});
|
||||
@@ -281,7 +288,7 @@ if (__VLS_ctx.showForm) {
|
||||
type: "submit",
|
||||
...{ class: "bg-green text-bg px-4 py-2 rounded text-sm font-semibold" },
|
||||
});
|
||||
(__VLS_ctx.editId ? 'Enregistrer' : 'Créer');
|
||||
(__VLS_ctx.editId ? 'Enregistrer' : 'Créer le template');
|
||||
__VLS_asFunctionalElement(__VLS_intrinsicElements.button, __VLS_intrinsicElements.button)({
|
||||
...{ onClick: (__VLS_ctx.closeForm) },
|
||||
type: "button",
|
||||
@@ -289,7 +296,7 @@ if (__VLS_ctx.showForm) {
|
||||
});
|
||||
}
|
||||
/** @type {__VLS_StyleScopedClasses['p-4']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-2xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['max-w-5xl']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mx-auto']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
@@ -333,6 +340,10 @@ if (__VLS_ctx.showForm) {
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @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-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['flex']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['gap-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['items-center']} */ ;
|
||||
@@ -430,23 +441,8 @@ if (__VLS_ctx.showForm) {
|
||||
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text-muted']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-xs']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['block']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['mb-1']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['w-full']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border-bg-hard']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['px-3']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['py-2']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-text']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['text-sm']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['focus:border-green']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['outline-none']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['bg-bg']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['rounded']} */ ;
|
||||
/** @type {__VLS_StyleScopedClasses['border']} */ ;
|
||||
@@ -502,7 +498,7 @@ const __VLS_self = (await import('vue')).defineComponent({
|
||||
groupes: groupes,
|
||||
byStatut: byStatut,
|
||||
fmtDate: fmtDate,
|
||||
openCreate: openCreate,
|
||||
openCreateTemplate: openCreateTemplate,
|
||||
startEdit: startEdit,
|
||||
closeForm: closeForm,
|
||||
submit: submit,
|
||||
|
||||
Reference in New Issue
Block a user