This commit is contained in:
2026-03-22 11:42:57 +01:00
parent 2043a1b8b5
commit 7afca6ed04
14 changed files with 300 additions and 22 deletions

View File

@@ -45,4 +45,6 @@ export const settingsApi = {
}
return { blob: r.data as Blob, filename }
}),
backupSamba: () =>
client.post<{ ok: boolean; fichier: string; chemin: string }>('/api/settings/backup/samba').then(r => r.data),
}

View File

@@ -4,7 +4,7 @@
<span class="text-text-muted text-sm">{{ medias.length }} photo(s)</span>
<label class="cursor-pointer bg-bg-soft text-text-muted hover:text-text px-3 py-1 rounded-lg text-xs border border-bg-hard transition-colors">
+ Photo
<input type="file" accept="image/*" capture="environment" class="hidden" @change="onUpload" />
<input type="file" accept="image/*,.heic,.HEIC,.heif,.HEIF" capture="environment" class="hidden" @change="onUpload" />
</label>
</div>

View File

@@ -16,7 +16,7 @@
Choisir / Photographier
<input
type="file"
accept="image/*"
accept="image/*,.heic,.HEIC,.heif,.HEIF"
capture="environment"
class="hidden"
@change="onFileSelect"

View File

@@ -174,7 +174,7 @@
<div class="grid grid-cols-2 gap-4 pt-2">
<label class="btn-outline border-dashed border-bg-soft flex items-center justify-center gap-2 py-3 cursor-pointer hover:border-yellow hover:text-yellow text-[10px] font-black uppercase transition-all">
{{ uploadingPhotos ? '...' : '📸 Photos' }}
<input type="file" accept="image/*" multiple class="hidden" @change="uploadFiles($event, 'photo')" />
<input type="file" accept="image/*,.heic,.HEIC,.heif,.HEIF" multiple class="hidden" @change="uploadFiles($event, 'photo')" />
</label>
<label class="btn-outline border-dashed border-bg-soft flex items-center justify-center gap-2 py-3 cursor-pointer hover:border-aqua hover:text-aqua text-[10px] font-black uppercase transition-all">
{{ uploadingVideos ? '...' : '🎬 Vidéos' }}

View File

@@ -219,7 +219,7 @@
<div class="bg-bg-soft/30 rounded-3xl p-4 border border-bg-soft">
<label class="text-text-muted text-[10px] font-black uppercase tracking-widest block mb-3">Photo de l'espace</label>
<div class="relative group aspect-video rounded-2xl overflow-hidden bg-bg border-2 border-dashed border-bg-soft flex items-center justify-center cursor-pointer hover:border-green transition-all">
<input type="file" accept="image/*" @change="onPhotoSelected" class="absolute inset-0 opacity-0 cursor-pointer z-20" />
<input type="file" accept="image/*,.heic,.HEIC,.heif,.HEIF" @change="onPhotoSelected" class="absolute inset-0 opacity-0 cursor-pointer z-20" />
<img v-if="photoPreview" :src="photoPreview" class="absolute inset-0 w-full h-full object-cover" />
<div v-else class="text-center">
<span class="text-3xl block mb-2">📸</span>

View File

@@ -109,7 +109,7 @@
<!-- Upload Photo -->
<div class="bg-bg-soft/30 p-3 rounded-xl border border-bg-soft">
<label class="text-text-muted text-[9px] font-black uppercase tracking-widest block mb-2">Photo de l'outil</label>
<input type="file" accept="image/*" @change="onPhotoSelected" class="text-[10px] text-text-muted w-full" />
<input type="file" accept="image/*,.heic,.HEIC,.heif,.HEIF" @change="onPhotoSelected" class="text-[10px] text-text-muted w-full" />
<img v-if="photoPreview" :src="photoPreview" class="mt-2 w-full h-24 object-cover rounded border border-bg-hard shadow-lg" />
</div>

View File

@@ -495,7 +495,7 @@
</div>
<!-- Upload Photo -->
<input type="file" ref="fileInput" accept="image/*" class="hidden" @change="handleFileUpload" />
<input type="file" ref="fileInput" accept="image/*,.heic,.HEIC,.heif,.HEIF" class="hidden" @change="handleFileUpload" />
<!-- ====== POPUP FORMULAIRE VARIÉTÉ ====== -->
<div v-if="showFormVariety" class="fixed inset-0 bg-black/80 backdrop-blur-sm z-[70] flex items-center justify-center p-4" @click.self="closeFormVariety">

View File

@@ -112,7 +112,7 @@
</div>
</section>
<!-- Section Sauvegarde -->
<!-- Section Sauvegarde locale -->
<section class="card-jardin flex flex-col h-full border-aqua/20">
<div class="flex items-center gap-3 mb-6 border-b border-bg-hard pb-4">
<span class="text-2xl">📦</span>
@@ -126,7 +126,7 @@
<p class="text-xs text-text-muted leading-relaxed">
Génère une archive complète (.zip) incluant votre base de données SQLite et tous les médias (photos/vidéos) uploadés.
</p>
<button
class="btn-primary !bg-aqua !text-bg w-full py-4 flex flex-col items-center gap-2 shadow-lg hover:shadow-aqua/20"
:disabled="downloadingBackup"
@@ -135,11 +135,119 @@
<span class="text-xl">💾</span>
<span class="text-[10px] font-black uppercase tracking-widest">{{ downloadingBackup ? 'Préparation...' : 'Télécharger le Pack Complet' }}</span>
</button>
<div v-if="backupMsg" class="text-[10px] text-center font-bold text-aqua animate-bounce">{{ backupMsg }}</div>
</div>
</section>
<!-- Section Images -->
<section class="card-jardin flex flex-col h-full border-orange/20">
<div class="flex items-center gap-3 mb-6 border-b border-bg-hard pb-4">
<span class="text-2xl">🖼️</span>
<div>
<h2 class="text-text font-bold uppercase tracking-widest text-xs">Images</h2>
<p class="text-[10px] text-text-muted font-bold">Qualité et taille des photos importées.</p>
</div>
</div>
<div class="flex-1 space-y-6">
<div class="space-y-3">
<div class="flex justify-between items-center">
<label class="text-[10px] font-black uppercase tracking-widest text-text-muted">Largeur max des photos</label>
<span class="text-xs font-mono text-orange">{{ imageMaxWidthLabel }}</span>
</div>
<div class="grid grid-cols-3 gap-2">
<button
v-for="opt in imageWidthOptions"
:key="opt.value"
@click="imageMaxWidth = opt.value"
:class="[
'py-2 px-1 rounded-xl text-[10px] font-black uppercase tracking-widest border transition-all',
imageMaxWidth === opt.value
? 'bg-orange/20 border-orange text-orange'
: 'border-bg-soft text-text-muted hover:border-orange/40'
]"
>{{ opt.label }}</button>
</div>
<p class="text-[10px] text-text-muted leading-relaxed italic">
S'applique aux nouvelles photos importées. Les miniatures restent à 300 px.
</p>
</div>
</div>
<div class="mt-8 pt-4 border-t border-bg-hard flex items-center justify-end gap-3">
<span v-if="imageSavedMsg" class="text-[10px] font-bold text-aqua">{{ imageSavedMsg }}</span>
<button
class="btn-primary !bg-orange !text-bg !py-2 !px-6 text-xs"
:disabled="savingImage"
@click="saveImageSettings"
>{{ savingImage ? '...' : 'Enregistrer' }}</button>
</div>
</section>
<!-- Section Sauvegarde Samba -->
<section class="card-jardin flex flex-col h-full border-purple/20">
<div class="flex items-center gap-3 mb-6 border-b border-bg-hard pb-4">
<span class="text-2xl">🗄</span>
<div>
<h2 class="text-text font-bold uppercase tracking-widest text-xs">Sauvegarde Samba / NAS</h2>
<p class="text-[10px] text-text-muted font-bold">Envoi automatique vers un partage réseau.</p>
</div>
</div>
<div class="flex-1 space-y-4">
<div class="grid grid-cols-2 gap-3">
<div class="space-y-1 col-span-2 sm:col-span-1">
<label class="text-[10px] font-black uppercase tracking-widest text-text-muted">Serveur (IP ou nom)</label>
<input v-model="samba.serveur" type="text" placeholder="192.168.1.10"
class="input-jardin w-full text-xs" />
</div>
<div class="space-y-1 col-span-2 sm:col-span-1">
<label class="text-[10px] font-black uppercase tracking-widest text-text-muted">Partage</label>
<input v-model="samba.partage" type="text" placeholder="Jardin"
class="input-jardin w-full text-xs" />
</div>
<div class="space-y-1 col-span-2">
<label class="text-[10px] font-black uppercase tracking-widest text-text-muted">Sous-dossier (optionnel)</label>
<input v-model="samba.sous_dossier" type="text" placeholder="backups/jardin"
class="input-jardin w-full text-xs" />
</div>
<div class="space-y-1 col-span-2 sm:col-span-1">
<label class="text-[10px] font-black uppercase tracking-widest text-text-muted">Utilisateur</label>
<input v-model="samba.utilisateur" type="text" placeholder="user"
class="input-jardin w-full text-xs" />
</div>
<div class="space-y-1 col-span-2 sm:col-span-1">
<label class="text-[10px] font-black uppercase tracking-widest text-text-muted">Mot de passe</label>
<input v-model="samba.motdepasse" type="password" placeholder="••••••••"
class="input-jardin w-full text-xs" />
</div>
</div>
<div v-if="sambaSendMsg" :class="['text-[10px] text-center font-bold p-2 rounded-lg', sambaError ? 'text-red bg-red/10' : 'text-green bg-green/10']">
{{ sambaSendMsg }}
</div>
</div>
<div class="mt-6 pt-4 border-t border-bg-hard flex flex-col gap-3">
<div class="flex items-center justify-end gap-3">
<span v-if="sambaSavedMsg" class="text-[10px] font-bold text-aqua">{{ sambaSavedMsg }}</span>
<button class="btn-outline !py-2 !px-4 text-xs border-purple/40 text-purple hover:bg-purple/10"
:disabled="savingSamba" @click="saveSambaSettings">
{{ savingSamba ? '...' : 'Enregistrer la config' }}
</button>
</div>
<button
class="btn-primary w-full py-3 flex items-center justify-center gap-2 text-xs"
:disabled="sendingSamba || !samba.serveur || !samba.partage"
@click="sendSambaBackup"
>
<span>{{ sendingSamba ? '⏳' : '📤' }}</span>
<span class="font-black uppercase tracking-widest text-[10px]">{{ sendingSamba ? 'Envoi en cours...' : 'Envoyer un backup maintenant' }}</span>
</button>
</div>
</section>
<!-- Section API Docs (Largeur double sur XL+) -->
<section class="card-jardin xl:col-span-2 flex flex-col border-bg-soft/50">
<div class="flex items-center gap-3 mb-6 border-b border-bg-hard pb-4">
@@ -188,6 +296,32 @@ const downloadingBackup = ref(false)
const backupMsg = ref('')
const apiBaseUrl = detectApiBaseUrl()
// --- Image max width ---
const imageWidthOptions = [
{ value: 400, label: '400 px' },
{ value: 600, label: '600 px' },
{ value: 800, label: '800 px' },
{ value: 1200, label: '1200 px' },
{ value: 1600, label: '1600 px' },
{ value: 2400, label: '2400 px' },
{ value: 0, label: 'Originale' },
]
const imageMaxWidth = ref(1200)
const imageMaxWidthLabel = computed(() => {
const opt = imageWidthOptions.find(o => o.value === imageMaxWidth.value)
return opt ? opt.label : `${imageMaxWidth.value} px`
})
const savingImage = ref(false)
const imageSavedMsg = ref('')
// --- Samba ---
const samba = ref({ serveur: '', partage: '', sous_dossier: '', utilisateur: '', motdepasse: '' })
const savingSamba = ref(false)
const sambaSavedMsg = ref('')
const sendingSamba = ref(false)
const sambaSendMsg = ref('')
const sambaError = ref(false)
// --- UI Size settings ---
const uiSizeSettings = [
{ key: 'ui_font_size', label: 'Corps de texte', min: 12, max: 24, step: 1, unit: 'px' },
@@ -276,11 +410,69 @@ async function loadSettings() {
if (v != null) uiSizes.value[s.key] = Number(v) || UI_SIZE_DEFAULTS[s.key]
}
applyUiSizes()
if (data.image_max_width != null) imageMaxWidth.value = Number(data.image_max_width) || 1200
if (data.samba_serveur != null) samba.value.serveur = data.samba_serveur
if (data.samba_partage != null) samba.value.partage = data.samba_partage
if (data.samba_sous_dossier != null) samba.value.sous_dossier = data.samba_sous_dossier
if (data.samba_utilisateur != null) samba.value.utilisateur = data.samba_utilisateur
if (data.samba_motdepasse != null) samba.value.motdepasse = data.samba_motdepasse
} catch {
// Laisse la valeur locale
}
}
async function saveImageSettings() {
savingImage.value = true
imageSavedMsg.value = ''
try {
await settingsApi.update({ image_max_width: String(imageMaxWidth.value) })
imageSavedMsg.value = 'Enregistré'
setTimeout(() => { imageSavedMsg.value = '' }, 1800)
} catch {
imageSavedMsg.value = 'Erreur.'
setTimeout(() => { imageSavedMsg.value = '' }, 2200)
} finally {
savingImage.value = false
}
}
async function saveSambaSettings() {
savingSamba.value = true
sambaSavedMsg.value = ''
try {
await settingsApi.update({
samba_serveur: samba.value.serveur,
samba_partage: samba.value.partage,
samba_sous_dossier: samba.value.sous_dossier,
samba_utilisateur: samba.value.utilisateur,
samba_motdepasse: samba.value.motdepasse,
})
sambaSavedMsg.value = 'Enregistré'
setTimeout(() => { sambaSavedMsg.value = '' }, 1800)
} catch {
sambaSavedMsg.value = 'Erreur.'
setTimeout(() => { sambaSavedMsg.value = '' }, 2200)
} finally {
savingSamba.value = false
}
}
async function sendSambaBackup() {
sendingSamba.value = true
sambaSendMsg.value = ''
sambaError.value = false
try {
const res = await settingsApi.backupSamba()
sambaSendMsg.value = `Envoyé : ${res.fichier}`
} catch (err: any) {
sambaError.value = true
sambaSendMsg.value = err?.response?.data?.detail || 'Erreur lors de l\'envoi Samba.'
} finally {
sendingSamba.value = false
setTimeout(() => { sambaSendMsg.value = '' }, 4000)
}
}
async function saveSettings() {
saving.value = true
savedMsg.value = ''