Files
jardin/frontend/src/views/TachesView.vue
2026-03-01 07:21:46 +01:00

645 lines
28 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="p-4 max-w-6xl mx-auto space-y-8">
<!-- En-tête -->
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-green tracking-tight">Gestion des Tâches</h1>
<p class="text-text-muted text-xs mt-1">Organisez vos travaux au jardin avec des listes et des modèles.</p>
</div>
<button class="btn-primary flex items-center gap-2" @click="openCreateTemplate">
<span class="text-lg leading-none">+</span> Template
</button>
</div>
<!-- Section "Créer rapidement" -->
<section class="bg-bg-soft/20 rounded-xl border border-bg-soft overflow-hidden">
<button
class="w-full px-5 py-3 flex items-center justify-between text-left hover:bg-bg-soft/30 transition-colors"
@click="showQuickSection = !showQuickSection"
>
<span class="text-text font-bold text-sm flex items-center gap-2">
<span class="text-yellow"></span> Créer rapidement
</span>
<span class="text-text-muted text-xs flex items-center gap-2">
<span>{{ totalTemplates }} template{{ totalTemplates > 1 ? 's' : '' }} disponible{{ totalTemplates > 1 ? 's' : '' }}</span>
<span :class="['transition-transform inline-block', showQuickSection ? 'rotate-180' : '']"></span>
</span>
</button>
<div v-show="showQuickSection" class="border-t border-bg-soft divide-y divide-bg-soft/50">
<!-- Mes templates personnalisés -->
<div v-if="byStatut('template').length" class="p-4 space-y-3">
<h3 class="text-text-muted text-[10px] font-bold uppercase tracking-widest">Mes templates</h3>
<div class="flex flex-wrap gap-2">
<button
v-for="t in byStatut('template')" :key="t.id"
class="flex items-center gap-2 px-3 py-1.5 rounded-lg border text-sm font-medium transition-all hover:scale-[1.02]"
:class="priorityChipClass(t.priorite)"
@click="openSchedule(t)"
>
<span>{{ priorityIcon(t.priorite) }}</span>
<span>{{ t.titre }}</span>
<span v-if="t.frequence_jours" class="text-[10px] opacity-70">🔁 {{ t.frequence_jours }}j</span>
<span class="ml-1 opacity-50 text-[10px]"> programmer</span>
</button>
</div>
</div>
<!-- Tâches courantes prédéfinies -->
<div class="p-4 space-y-3">
<h3 class="text-text-muted text-[10px] font-bold uppercase tracking-widest">Tâches courantes</h3>
<div class="flex flex-wrap gap-2">
<button
v-for="qt in quickTemplatesFiltered" :key="qt.titre"
class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-bg-soft bg-bg text-text-muted text-sm hover:text-text hover:border-text-muted transition-all hover:scale-[1.02]"
@click="openScheduleQuick(qt)"
>
<span>{{ qt.icone }}</span>
<span>{{ qt.titre }}</span>
</button>
</div>
</div>
</div>
</section>
<!-- Kanban : À faire / En cours / Terminé -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div v-for="[groupe, label] in listGroupes" :key="groupe" class="space-y-3">
<h2 class="text-text-muted text-xs font-bold uppercase tracking-widest flex items-center gap-2">
<span :class="['w-2 h-2 rounded-full', groupeColor(groupe)]"></span>
{{ label }}
<span class="ml-auto bg-bg-soft px-2 py-0.5 rounded text-[10px]">{{ byStatut(groupe).length }}</span>
</h2>
<div v-if="!byStatut(groupe).length" class="card-jardin text-center py-8 opacity-30 border-dashed">
<p class="text-text-muted text-xs uppercase tracking-widest font-bold">Aucune tâche</p>
</div>
<div v-for="t in byStatut(groupe)" :key="t.id"
class="card-jardin flex items-center gap-4 group relative overflow-hidden">
<!-- Barre priorité -->
<div :class="[
'absolute left-0 top-0 bottom-0 w-1',
t.priorite === 'haute' ? 'bg-red' : t.priorite === 'normale' ? 'bg-yellow' : 'bg-bg-hard'
]"></div>
<div class="flex-1 min-w-0 pl-1">
<div class="flex items-center gap-2 mb-1">
<span class="text-text font-bold text-sm">{{ t.titre }}</span>
<span v-if="t.frequence_jours" class="badge badge-aqua !text-[8px]">🔁 {{ freqLabel(t.frequence_jours) }}</span>
</div>
<div v-if="t.description" class="text-text-muted text-xs line-clamp-1 mb-1 italic">{{ t.description }}</div>
<div class="flex flex-wrap gap-3 text-[10px] font-bold uppercase tracking-tighter text-text-muted opacity-70">
<span v-if="t.echeance" class="flex items-center gap-1">📅 {{ fmtDate(t.echeance) }}</span>
<span v-if="t.planting_id" class="flex items-center gap-1">🌱 {{ plantingShortLabel(t.planting_id) }}</span>
</div>
</div>
<div class="flex gap-2 items-center shrink-0">
<button v-if="t.statut === 'a_faire'" class="btn-outline !py-1 !px-2 text-blue border-blue/20 hover:bg-blue/10 text-[10px] font-bold uppercase"
@click="updateStatut(t.id!, 'en_cours')">Démarrer</button>
<button v-if="t.statut === 'en_cours'" class="btn-outline !py-1 !px-2 text-green border-green/20 hover:bg-green/10 text-[10px] font-bold uppercase"
@click="updateStatut(t.id!, 'fait')">Terminer</button>
<button @click="removeTask(t.id!)"
class="p-1.5 text-text-muted hover:text-red transition-colors opacity-0 group-hover:opacity-100"></button>
</div>
</div>
</div>
</div>
<!-- Bibliothèque de templates -->
<section v-if="byStatut('template').length" class="space-y-3">
<h2 class="text-text-muted text-xs font-bold uppercase tracking-widest flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-aqua"></span>
Bibliothèque de templates
<span class="ml-auto bg-bg-soft px-2 py-0.5 rounded text-[10px]">{{ byStatut('template').length }}</span>
</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3">
<div v-for="t in byStatut('template')" :key="t.id"
class="card-jardin flex items-center gap-3 group relative overflow-hidden">
<div :class="[
'absolute left-0 top-0 bottom-0 w-1',
t.priorite === 'haute' ? 'bg-red' : t.priorite === 'normale' ? 'bg-yellow' : 'bg-bg-hard'
]"></div>
<div class="flex-1 min-w-0 pl-1">
<div class="flex items-center gap-2 mb-0.5">
<span class="text-text font-bold text-sm">{{ t.titre }}</span>
<span v-if="t.frequence_jours" class="badge badge-aqua !text-[8px]">🔁 {{ freqLabel(t.frequence_jours) }}</span>
</div>
<div v-if="t.description" class="text-text-muted text-xs line-clamp-1 italic">{{ t.description }}</div>
</div>
<div class="flex items-center gap-1 shrink-0">
<button
class="btn-outline !py-1 !px-2 text-aqua border-aqua/20 hover:bg-aqua/10 text-[10px] font-bold uppercase"
@click="openSchedule(t)"
>Programmer</button>
<button @click="startEdit(t)"
class="p-1.5 text-text-muted hover:text-yellow transition-colors opacity-0 group-hover:opacity-100"></button>
<button @click="removeTask(t.id!)"
class="p-1.5 text-text-muted hover:text-red transition-colors opacity-0 group-hover:opacity-100"></button>
</div>
</div>
</div>
</section>
<!-- Modal création / édition template -->
<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 le template' : 'Nouveau template' }}</h2>
<form @submit.prevent="submit" class="grid gap-3">
<div>
<label class="text-text-muted text-xs block mb-1">Titre *</label>
<input v-model="form.titre" required
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Description</label>
<textarea v-model="form.description" rows="1"
@input="autoResize"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none resize-none transition-all overflow-hidden" />
</div>
<div>
<label class="text-text-muted text-xs block mb-1">Priorité</label>
<select v-model="form.priorite" class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm">
<option value="basse">Basse</option>
<option value="normale">Normale</option>
<option value="haute">Haute</option>
</select>
</div>
<div class="bg-bg rounded border border-bg-hard p-3">
<label class="inline-flex items-center gap-2 text-sm text-text cursor-pointer">
<input v-model="form.repetition" type="checkbox" class="accent-green" />
Répétition périodique
</label>
<p class="text-text-muted text-[11px] mt-1">Fréquence proposée quand la tâche est programmée depuis un template.</p>
</div>
<div v-if="form.repetition" class="flex gap-2 items-center">
<input v-model.number="form.freq_nb" type="number" min="1" max="99" required placeholder="1"
class="w-20 bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none font-mono" />
<select v-model="form.freq_unite" class="flex-1 bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm">
<option value="jours">Jour(s)</option>
<option value="semaines">Semaine(s)</option>
<option value="mois">Mois</option>
</select>
<span class="text-text-muted text-xs whitespace-nowrap">= {{ formFreqEnJours }} j</span>
</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" :disabled="submitting">
{{ submitting ? 'Enregistrement…' : (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>
</form>
</div>
</div>
<!-- Modal "Programmer une tâche" -->
<div v-if="showScheduleModal" class="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4" @click.self="closeSchedule">
<div class="bg-bg-hard rounded-xl p-6 w-full max-w-sm border border-bg-soft max-h-[90vh] overflow-y-auto">
<h2 class="text-text font-bold text-lg mb-1">Programmer une tâche</h2>
<p class="text-text-muted text-xs mb-5 italic">
Crée une tâche <span class="text-blue font-bold">À faire</span> depuis ce template.
</p>
<form @submit.prevent="createScheduled" class="grid gap-3">
<!-- Titre -->
<div>
<label class="text-text-muted text-xs block mb-1">Titre *</label>
<input v-model="scheduleForm.titre" required
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none" />
</div>
<!-- Date de démarrage -->
<div>
<label class="text-text-muted text-xs block mb-1">
Date de démarrage
<span class="opacity-50">(par défaut : aujourd'hui)</span>
</label>
<input v-model="scheduleForm.date_debut" 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>
<!-- Priorité -->
<div>
<label class="text-text-muted text-xs block mb-1">Priorité</label>
<select v-model="scheduleForm.priorite" class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm">
<option value="basse">⚪ Basse</option>
<option value="normale">🟡 Normale</option>
<option value="haute">🔴 Haute</option>
</select>
</div>
<!-- Plantation liée -->
<div>
<label class="text-text-muted text-xs block mb-1">
Plantation liée
<span class="opacity-50">(optionnel)</span>
</label>
<select v-model="scheduleForm.planting_id"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none">
<option :value="null">— Aucune plantation —</option>
<optgroup v-for="g in plantingsByGarden" :key="g.gardenId" :label="g.gardenName">
<option v-for="p in g.plantings" :key="p.id" :value="p.id">
{{ plantingLabel(p) }}
</option>
</optgroup>
</select>
</div>
<!-- Répétition -->
<div class="bg-bg rounded border border-bg-hard p-3 space-y-2">
<label class="inline-flex items-center gap-2 text-sm text-text cursor-pointer">
<input v-model="scheduleForm.repetition" type="checkbox" class="accent-green" />
Répétition périodique
</label>
<div v-if="scheduleForm.repetition" class="flex gap-2 items-center pt-1">
<span class="text-text-muted text-xs shrink-0">Tous les</span>
<input v-model.number="scheduleForm.freq_nb" type="number" min="1" max="99"
class="w-16 bg-bg-soft border border-bg-soft rounded px-2 py-1.5 text-text text-sm focus:border-green outline-none font-mono text-center" />
<select v-model="scheduleForm.freq_unite"
class="flex-1 bg-bg-soft border border-bg-soft rounded px-2 py-1.5 text-text text-sm focus:border-green outline-none">
<option value="jours">Jour(s)</option>
<option value="semaines">Semaine(s)</option>
<option value="mois">Mois</option>
</select>
</div>
<p v-if="scheduleForm.repetition" class="text-text-muted text-[11px]">
→ Récurrence de {{ scheduleFreqEnJours }} jour{{ scheduleFreqEnJours > 1 ? 's' : '' }}
</p>
</div>
<!-- Notes -->
<div>
<label class="text-text-muted text-xs block mb-1">Notes <span class="opacity-50">(optionnel)</span></label>
<textarea v-model="scheduleForm.notes" rows="2" placeholder="Précisions sur cette occurrence…"
class="w-full bg-bg border border-bg-hard rounded px-3 py-2 text-text text-sm focus:border-green outline-none resize-none" />
</div>
<div class="flex gap-2 mt-1">
<button type="submit" class="bg-blue text-bg px-4 py-2 rounded text-sm font-semibold flex-1" :disabled="submitting">
{{ submitting ? 'Création' : 'Créer la tâche' }}
</button>
<button type="button" class="text-text-muted text-sm px-4 py-2 hover:text-text" @click="closeSchedule">Annuler</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { useTasksStore } from '@/stores/tasks'
import { usePlantingsStore } from '@/stores/plantings'
import { usePlantsStore } from '@/stores/plants'
import { useGardensStore } from '@/stores/gardens'
import type { Task } from '@/api/tasks'
import type { Planting } from '@/api/plantings'
import { useToast } from '@/composables/useToast'
// ── Types ──────────────────────────────────────────────────────────────────────
interface QuickTemplate {
titre: string
icone: string
priorite: string
description?: string
}
// ── Stores & composables ───────────────────────────────────────────────────────
const store = useTasksStore()
const plantingsStore = usePlantingsStore()
const plantsStore = usePlantsStore()
const gardensStore = useGardensStore()
const toast = useToast()
// ── État UI ────────────────────────────────────────────────────────────────────
const showForm = ref(false)
const showScheduleModal = ref(false)
const showQuickSection = ref(true)
const editId = ref<number | null>(null)
const submitting = ref(false)
// ── Formulaire template (création / édition) ───────────────────────────────────
const form = reactive({
titre: '',
description: '',
priorite: 'normale',
repetition: false,
freq_nb: 1 as number,
freq_unite: 'semaines' as 'jours' | 'semaines' | 'mois',
})
const formFreqEnJours = computed(() => {
const n = form.freq_nb || 1
if (form.freq_unite === 'semaines') return n * 7
if (form.freq_unite === 'mois') return n * 30
return n
})
// ── Formulaire "programmer" (instancier un template) ──────────────────────────
const scheduleForm = reactive({
titre: '',
date_debut: today(),
notes: '',
priorite: 'normale',
planting_id: null as number | null,
repetition: false,
freq_nb: 1 as number,
freq_unite: 'semaines' as 'jours' | 'semaines' | 'mois',
})
const scheduleFreqEnJours = computed(() => {
const n = scheduleForm.freq_nb || 1
if (scheduleForm.freq_unite === 'semaines') return n * 7
if (scheduleForm.freq_unite === 'mois') return n * 30
return n
})
// ── Templates prédéfinis jardinage ─────────────────────────────────────────────
const QUICK_TEMPLATES: QuickTemplate[] = [
{ titre: 'Arrosage', icone: '💧', priorite: 'normale' },
{ titre: 'Semis en intérieur', icone: '🌱', priorite: 'normale' },
{ titre: 'Semis en pleine terre', icone: '🌾', priorite: 'normale' },
{ titre: 'Repiquage / Transplantation',icone: '🪴', priorite: 'normale' },
{ titre: 'Récolte', icone: '🥕', priorite: 'normale' },
{ titre: 'Taille / Ébourgeonnage', icone: '', priorite: 'normale' },
{ titre: 'Désherbage', icone: '🌿', priorite: 'basse' },
{ titre: 'Fertilisation / Amendement', icone: '💊', priorite: 'normale' },
{ titre: 'Traitement phytosanitaire', icone: '🧪', priorite: 'haute' },
{ titre: 'Observation / Relevé', icone: '👁', priorite: 'basse' },
{ titre: 'Paillage', icone: '🍂', priorite: 'basse' },
{ titre: 'Compostage', icone: '', priorite: 'basse' },
{ titre: 'Buttage', icone: '', priorite: 'normale' },
{ titre: 'Protection gel / Voile', icone: '🌡', priorite: 'haute' },
{ titre: 'Tuteurage', icone: '🪵', priorite: 'normale' },
{ titre: 'Éclaircissage', icone: '🌞', priorite: 'normale' },
]
const quickTemplatesFiltered = computed(() => {
const existing = new Set(byStatut('template').map(t => t.titre.toLowerCase().trim()))
return QUICK_TEMPLATES.filter(qt => !existing.has(qt.titre.toLowerCase().trim()))
})
const totalTemplates = computed(
() => byStatut('template').length + quickTemplatesFiltered.value.length
)
// ── Plantations groupées par jardin pour le <optgroup> ─────────────────────────
const plantingsByGarden = computed(() => {
const gardens = gardensStore.gardens
const plantings = plantingsStore.plantings.filter(p => p.statut !== 'termine' && p.statut !== 'echoue')
const groups: { gardenId: number; gardenName: string; plantings: Planting[] }[] = []
for (const g of gardens) {
const gPlantings = plantings.filter(p => p.garden_id === g.id)
if (gPlantings.length) {
groups.push({ gardenId: g.id!, gardenName: g.nom, plantings: gPlantings })
}
}
// Plantations sans jardin reconnu
const knownGardenIds = new Set(gardens.map(g => g.id))
const orphans = plantings.filter(p => !knownGardenIds.has(p.garden_id))
if (orphans.length) {
groups.push({ gardenId: 0, gardenName: 'Autres', plantings: orphans })
}
return groups
})
function plantingLabel(p: Planting): string {
const plant = plantsStore.plants.find(pl => pl.id === p.variety_id)
const nom = plant
? [plant.nom_commun, plant.variete].filter(Boolean).join(' ')
: `Variété #${p.variety_id}`
const date = p.date_plantation ? ` (${fmtDate(p.date_plantation)})` : ''
return `${nom}${date}`
}
function plantingShortLabel(id: number): string {
const p = plantingsStore.plantings.find(x => x.id === id)
if (!p) return `#${id}`
const plant = plantsStore.plants.find(pl => pl.id === p.variety_id)
return plant?.nom_commun ?? `#${id}`
}
// ── Groupes Kanban ─────────────────────────────────────────────────────────────
const listGroupes: [string, string][] = [
['a_faire', 'À faire'],
['en_cours', 'En cours'],
['fait', 'Terminé'],
]
function groupeColor(g: string) {
const map: Record<string, string> = { a_faire: 'bg-blue', en_cours: 'bg-yellow', fait: 'bg-green' }
return map[g] ?? 'bg-bg-hard'
}
const byStatut = (s: string) => store.tasks.filter(t => t.statut === s)
// ── Helpers ────────────────────────────────────────────────────────────────────
function today() {
return new Date().toISOString().slice(0, 10)
}
function fmtDate(s: string) {
return new Date(s + 'T12:00:00').toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' })
}
function freqLabel(jours: number): string {
if (jours % 30 === 0 && jours >= 30) return `${jours / 30}mois`
if (jours % 7 === 0 && jours >= 7) return `${jours / 7}sem`
return `${jours}j`
}
function priorityIcon(p: string) {
return { haute: '🔴', normale: '🟡', basse: '' }[p] ?? ''
}
function priorityChipClass(p: string) {
const map: Record<string, string> = {
haute: 'border-red/30 bg-red/10 text-red hover:bg-red/20',
normale: 'border-yellow/30 bg-yellow/10 text-yellow hover:bg-yellow/20',
basse: 'border-bg-soft bg-bg text-text-muted hover:bg-bg-soft',
}
return map[p] ?? map.basse
}
function autoResize(event: Event) {
const el = event.target as HTMLTextAreaElement
el.style.height = 'auto'
el.style.height = el.scrollHeight + 'px'
}
// ── Gestion template (form création/édition) ───────────────────────────────────
function resetForm() {
Object.assign(form, {
titre: '', description: '', priorite: 'normale',
repetition: false, freq_nb: 1, freq_unite: 'semaines',
})
}
function openCreateTemplate() {
editId.value = null
resetForm()
showForm.value = true
}
function startEdit(t: Task) {
editId.value = t.id!
const jours = t.frequence_jours ?? 0
let freq_nb = jours
let freq_unite: 'jours' | 'semaines' | 'mois' = 'jours'
if (jours >= 30 && jours % 30 === 0) { freq_nb = jours / 30; freq_unite = 'mois' }
else if (jours >= 7 && jours % 7 === 0) { freq_nb = jours / 7; freq_unite = 'semaines' }
Object.assign(form, {
titre: t.titre,
description: t.description || '',
priorite: t.priorite,
repetition: Boolean(t.recurrence || t.frequence_jours),
freq_nb: freq_nb || 1,
freq_unite,
})
showForm.value = true
}
function closeForm() {
showForm.value = false
editId.value = null
}
async function submit() {
if (submitting.value) return
submitting.value = true
const freqJours = form.repetition ? formFreqEnJours.value : null
const payload = {
titre: form.titre,
description: form.description || undefined,
priorite: form.priorite,
statut: 'template',
recurrence: form.repetition ? 'jours' : null,
frequence_jours: freqJours,
}
try {
if (editId.value) {
await store.update(editId.value, payload)
toast.success('Template modifié')
} else {
await store.create(payload)
toast.success('Template créé')
}
closeForm()
resetForm()
} catch {
// L'intercepteur Axios affiche le message
} finally {
submitting.value = false
}
}
// ── Programmer une tâche depuis un template ────────────────────────────────────
function resetScheduleForm() {
Object.assign(scheduleForm, {
titre: '',
date_debut: today(),
notes: '',
priorite: 'normale',
planting_id: null,
repetition: false,
freq_nb: 1,
freq_unite: 'semaines',
})
}
function openSchedule(t: Task) {
resetScheduleForm()
const jours = t.frequence_jours ?? 0
let freq_nb = jours
let freq_unite: 'jours' | 'semaines' | 'mois' = 'jours'
if (jours >= 30 && jours % 30 === 0) { freq_nb = jours / 30; freq_unite = 'mois' }
else if (jours >= 7 && jours % 7 === 0) { freq_nb = jours / 7; freq_unite = 'semaines' }
Object.assign(scheduleForm, {
titre: t.titre,
notes: t.description || '',
priorite: t.priorite,
repetition: Boolean(t.frequence_jours),
freq_nb: freq_nb || 1,
freq_unite,
})
showScheduleModal.value = true
}
function openScheduleQuick(qt: QuickTemplate) {
resetScheduleForm()
Object.assign(scheduleForm, {
titre: `${qt.icone} ${qt.titre}`,
priorite: qt.priorite,
})
showScheduleModal.value = true
}
function closeSchedule() {
showScheduleModal.value = false
}
async function createScheduled() {
if (submitting.value) return
submitting.value = true
const freqJours = scheduleForm.repetition ? scheduleFreqEnJours.value : null
try {
await store.create({
titre: scheduleForm.titre,
description: scheduleForm.notes || undefined,
priorite: scheduleForm.priorite,
statut: 'a_faire',
echeance: scheduleForm.date_debut || today(),
planting_id: scheduleForm.planting_id ?? undefined,
recurrence: scheduleForm.repetition ? 'jours' : null,
frequence_jours: freqJours,
})
toast.success(`"${scheduleForm.titre}" ajoutée aux tâches à faire`)
closeSchedule()
} catch {
// L'intercepteur Axios affiche le message
} finally {
submitting.value = false
}
}
// ── Actions Kanban ─────────────────────────────────────────────────────────────
async function updateStatut(id: number, statut: string) {
try {
await store.updateStatut(id, statut)
} catch { /* L'intercepteur Axios affiche le message */ }
}
async function removeTask(id: number) {
try {
await store.remove(id)
toast.success('Tâche supprimée')
} catch { /* L'intercepteur Axios affiche le message */ }
}
// ── Initialisation ─────────────────────────────────────────────────────────────
onMounted(async () => {
try {
await Promise.all([
store.fetchAll(),
plantingsStore.fetchAll(),
plantsStore.fetchAll(),
gardensStore.fetchAll(),
])
} catch {
toast.error('Impossible de charger les données')
}
})
</script>