From 74f3d4815e28d8b1e660963a3edfd6e8eadd30fc Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sat, 20 Jun 2026 20:14:18 +0200 Subject: [PATCH] chore: build backend frontend web --- backend/gestion/db/db_categories.go | 19 +++++++++++++++++-- backend/gestion/db/db_init.go | 17 +++++++++++++++++ backend/gestion/handlers/categories.go | 20 ++++++++++++++++++++ backend/gestion/routes/routes.go | 1 + frontend-prep/src/api/api.ts | 16 +++++++++------- 5 files changed, 64 insertions(+), 9 deletions(-) diff --git a/backend/gestion/db/db_categories.go b/backend/gestion/db/db_categories.go index 427b989f..45057b4a 100644 --- a/backend/gestion/db/db_categories.go +++ b/backend/gestion/db/db_categories.go @@ -13,6 +13,7 @@ type Category struct { Name string `json:"name" gorm:"column:name"` Color string `json:"color" gorm:"column:color"` IsComingSoon bool `json:"is_coming_soon" gorm:"column:is_coming_soon"` + Position int `json:"position" gorm:"column:position"` CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` } @@ -30,7 +31,7 @@ func ValidateCategoryColor(color string) error { func (d *Database) GetAllCategories() ([]Category, error) { var categories []Category - if err := d.GDB.Order("name ASC").Find(&categories).Error; err != nil { + if err := d.GDB.Order("position ASC, name ASC").Find(&categories).Error; err != nil { return nil, err } if categories == nil { @@ -43,7 +44,9 @@ func (d *Database) CreateCategory(name, color string, isComingSoon bool) (*Categ if color == "" { color = "#7c3aed" } - c := Category{Name: name, Color: color, IsComingSoon: isComingSoon} + var maxPos int + d.GDB.Model(&Category{}).Select("COALESCE(MAX(position), 0)").Scan(&maxPos) + c := Category{Name: name, Color: color, IsComingSoon: isComingSoon, Position: maxPos + 1} if err := d.GDB.Create(&c).Error; err != nil { return nil, err } @@ -82,6 +85,18 @@ func (d *Database) DeleteCategory(id int) error { return nil } +// ReorderCategories met à jour les positions selon l'ordre du tableau d'IDs fourni. +func (d *Database) ReorderCategories(ids []int) error { + tx := d.GDB.Begin() + for i, id := range ids { + if err := tx.Model(&Category{}).Where("id = ?", id).Update("position", i+1).Error; err != nil { + tx.Rollback() + return err + } + } + return tx.Commit().Error +} + func (d *Database) CategoryExists(name string) (bool, error) { var count int64 err := d.GDB.Model(&Category{}).Where("name = ?", name).Count(&count).Error diff --git a/backend/gestion/db/db_init.go b/backend/gestion/db/db_init.go index 5cb3dbec..b06e9c70 100644 --- a/backend/gestion/db/db_init.go +++ b/backend/gestion/db/db_init.go @@ -165,6 +165,23 @@ func InitDB() *Database { log.Fatalf("❌ Erreur migration categories.is_coming_soon: %v", err) } + // Migration: position d'affichage des catégories + if _, err = database.Exec(`ALTER TABLE categories ADD COLUMN IF NOT EXISTS position INTEGER NOT NULL DEFAULT 0`); err != nil { + log.Fatalf("❌ Erreur migration categories.position: %v", err) + } + // Backfill: attribuer des positions aux catégories existantes (ordre alphabétique) + if _, err = database.Exec(` + UPDATE categories c + SET position = sub.rn + FROM ( + SELECT id, ROW_NUMBER() OVER (ORDER BY name ASC) AS rn + FROM categories + ) sub + WHERE c.id = sub.id AND c.position = 0 + `); err != nil { + log.Fatalf("❌ Erreur backfill categories.position: %v", err) + } + // Migration: table paramètres globaux de l'application if _, err = database.Exec(`CREATE TABLE IF NOT EXISTS app_settings ( key VARCHAR(100) PRIMARY KEY, diff --git a/backend/gestion/handlers/categories.go b/backend/gestion/handlers/categories.go index 7f3c8801..ff92f9c2 100644 --- a/backend/gestion/handlers/categories.go +++ b/backend/gestion/handlers/categories.go @@ -109,6 +109,26 @@ func UpdateCategory(c *gin.Context) { }) } +func ReorderCategories(c *gin.Context) { + database := c.MustGet("database").(*db.Database) + + var req struct { + IDs []int `json:"ids" binding:"required"` + } + if err := c.ShouldBindJSON(&req); err != nil || len(req.IDs) == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "Liste d'IDs requise"}) + return + } + + if err := database.ReorderCategories(req.IDs); err != nil { + log.Printf("❌ [CATEGORIES] Reorder erreur: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur lors du réordonnancement"}) + return + } + + c.JSON(http.StatusOK, gin.H{"success": true}) +} + func DeleteCategory(c *gin.Context) { database := c.MustGet("database").(*db.Database) diff --git a/backend/gestion/routes/routes.go b/backend/gestion/routes/routes.go index d514fcd2..d0b9dddc 100644 --- a/backend/gestion/routes/routes.go +++ b/backend/gestion/routes/routes.go @@ -201,6 +201,7 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services // CATÉGORIES - GESTION ADMIN // ============================================ adminGroupV2.POST("/categories", handlers.CreateCategory) + adminGroupV2.PUT("/categories/reorder", handlers.ReorderCategories) adminGroupV2.PUT("/categories/:id", handlers.UpdateCategory) adminGroupV2.DELETE("/categories/:id", handlers.DeleteCategory) // ============================================ diff --git a/frontend-prep/src/api/api.ts b/frontend-prep/src/api/api.ts index 7d2ac84c..2267532c 100644 --- a/frontend-prep/src/api/api.ts +++ b/frontend-prep/src/api/api.ts @@ -92,9 +92,6 @@ export const extractUsernameFromToken = (): string | null => { } }; -/** - * ✅ Synchroniser sessionStorage avec JWT - */ export const syncUsernameFromJWT = (): string | null => { const jwtUsername = extractUsernameFromToken(); @@ -2198,12 +2195,14 @@ export const getMyPointsRewards = async (): Promise<{ reward: PointsRewardConfig | null; }> => { const token = getAuthToken(); - if (!token) return { success: false, enabled: false, pools: [], reward: null }; + if (!token) + return { success: false, enabled: false, pools: [], reward: null }; try { const response = await fetch(`${API_URL}/points/rewards`, { headers: { Authorization: `Bearer ${token}` }, }); - if (!response.ok) return { success: false, enabled: false, pools: [], reward: null }; + if (!response.ok) + return { success: false, enabled: false, pools: [], reward: null }; const data = await safeJson(response); return { success: true, @@ -2259,7 +2258,9 @@ export const getOrderRatingStatus = async ( } }; -export const claimMyReward = async (poolKey: string): Promise<{ +export const claimMyReward = async ( + poolKey: string, +): Promise<{ success: boolean; description?: string; remaining_rewards?: number; @@ -2279,7 +2280,8 @@ export const claimMyReward = async (poolKey: string): Promise<{ body: JSON.stringify({ pool_key: poolKey }), }); const data = await safeJson(response); - if (!response.ok) return { success: false, error: data.error || "Erreur" }; + if (!response.ok) + return { success: false, error: data.error || "Erreur" }; return { success: true, description: data.description,