maj via codex

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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