From 125d066dcb9d244602a9f50f7c42267537db4de1 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sat, 7 Mar 2026 15:48:27 +0100 Subject: [PATCH] chore: add color for category --- frontend-admin/src/api/api_admin.ts | 7 +- .../src/screens/admin/CategoriesScreen.tsx | 133 ++++++++++++++++-- mobile/src/api/api.ts | 1 + mobile/src/components/CategoryPill.tsx | 4 +- mobile/src/screens/client/ProductsScreen.tsx | 1 + 5 files changed, 133 insertions(+), 13 deletions(-) diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index 5461cb7e..e390e4a3 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -590,6 +590,7 @@ const V1_PUBLIC = "https://uber-stup.club/api/v1"; export interface Category { id: number; name: string; + color: string; created_at: string; } @@ -604,11 +605,12 @@ export const getCategories = async (): Promise => { export const createCategoryAdmin = async ( name: string, + color: string, ): Promise<{ success: boolean; category?: Category; error?: string }> => { try { const { data } = await apiClient.post( `${V2}/admin/protected/categories`, - { name }, + { name, color }, ); return { success: true, category: data.category }; } catch (error: any) { @@ -622,11 +624,12 @@ export const createCategoryAdmin = async ( export const updateCategoryAdmin = async ( id: number, name: string, + color: string, ): Promise<{ success: boolean; category?: Category; error?: string }> => { try { const { data } = await apiClient.put( `${V2}/admin/protected/categories/${id}`, - { name }, + { name, color }, ); return { success: true, category: data.category }; } catch (error: any) { diff --git a/frontend-admin/src/screens/admin/CategoriesScreen.tsx b/frontend-admin/src/screens/admin/CategoriesScreen.tsx index 872746ae..6bb6fad2 100644 --- a/frontend-admin/src/screens/admin/CategoriesScreen.tsx +++ b/frontend-admin/src/screens/admin/CategoriesScreen.tsx @@ -9,6 +9,7 @@ import { Modal, KeyboardAvoidingView, Platform, + ScrollView, } from "react-native"; import { Ionicons } from "@expo/vector-icons"; import { spacing, fontSize, borderRadius } from "../../theme"; @@ -25,6 +26,15 @@ import Card from "../../components/ui/Card"; import AlertModal from "../../components/ui/AlertModal"; import { useAlert } from "../../hooks/useAlert"; +const PRESET_COLORS = [ + "#7c3aed", "#9333ea", "#6366f1", + "#3b82f6", "#0ea5e9", "#06b6d4", + "#10b981", "#22c55e", "#84cc16", + "#f59e0b", "#f97316", "#ef4444", + "#ec4899", "#f472b6", "#ffffff", + "#a3a3a3", "#1a1a1a", "#000000", +]; + export default function CategoriesScreen() { const { colors } = useTheme(); const [categories, setCategories] = useState([]); @@ -32,6 +42,8 @@ export default function CategoriesScreen() { const [modalVisible, setModalVisible] = useState(false); const [editingCategory, setEditingCategory] = useState(null); const [name, setName] = useState(""); + const [color, setColor] = useState("#7c3aed"); + const [hexInput, setHexInput] = useState("#7c3aed"); const [saving, setSaving] = useState(false); const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert(); @@ -46,15 +58,29 @@ export default function CategoriesScreen() { load(); }, [load]); + const selectColor = (c: string) => { + setColor(c); + setHexInput(c); + }; + + const handleHexInput = (val: string) => { + setHexInput(val); + if (/^#[0-9A-Fa-f]{6}$/.test(val)) { + setColor(val); + } + }; + const openCreate = () => { setEditingCategory(null); setName(""); + selectColor("#7c3aed"); setModalVisible(true); }; const openEdit = (cat: Category) => { setEditingCategory(cat); setName(cat.name); + selectColor(cat.color || "#7c3aed"); setModalVisible(true); }; @@ -70,9 +96,13 @@ export default function CategoriesScreen() { showError("Erreur", "Le nom est requis"); return; } + if (!/^#[0-9A-Fa-f]{6}$/.test(color)) { + showError("Erreur", "Couleur invalide (format: #RRGGBB)"); + return; + } setSaving(true); if (editingCategory) { - const res = await updateCategoryAdmin(editingCategory.id, trimmed); + const res = await updateCategoryAdmin(editingCategory.id, trimmed, color); if (res.success) { showSuccess("Succès", "Catégorie modifiée"); closeModal(); @@ -81,7 +111,7 @@ export default function CategoriesScreen() { showError("Erreur", res.error || "Erreur"); } } else { - const res = await createCategoryAdmin(trimmed); + const res = await createCategoryAdmin(trimmed, color); if (res.success) { showSuccess("Succès", "Catégorie créée"); closeModal(); @@ -136,6 +166,8 @@ export default function CategoriesScreen() { alignItems: "center", justifyContent: "space-between", }, + catLeft: { flexDirection: "row", alignItems: "center", gap: spacing.s, flex: 1 }, + colorDot: { width: 16, height: 16, borderRadius: 8 }, catName: { fontSize: fontSize.md, color: colors.textPrimary, @@ -158,6 +190,7 @@ export default function CategoriesScreen() { gap: spacing.m, }, modalTitle: { fontSize: fontSize.lg, fontWeight: "700", color: colors.textPrimary }, + label: { fontSize: fontSize.sm, color: colors.textSecondary, marginBottom: 4 }, input: { borderWidth: 1, borderColor: colors.border, @@ -167,6 +200,36 @@ export default function CategoriesScreen() { fontSize: fontSize.md, backgroundColor: colors.bgInput, }, + colorSection: { gap: spacing.s }, + previewRow: { flexDirection: "row", alignItems: "center", gap: spacing.m }, + previewDot: { + width: 36, + height: 36, + borderRadius: 18, + borderWidth: 2, + borderColor: colors.border, + }, + hexInput: { + flex: 1, + borderWidth: 1, + borderColor: colors.border, + borderRadius: borderRadius.md, + padding: spacing.s, + color: colors.textPrimary, + fontSize: fontSize.md, + backgroundColor: colors.bgInput, + }, + paletteGrid: { + flexDirection: "row", + flexWrap: "wrap", + gap: spacing.s, + }, + colorSwatch: { + width: 32, + height: 32, + borderRadius: borderRadius.sm, + borderWidth: 2, + }, modalBtns: { flexDirection: "row", gap: spacing.s }, cancelBtn: { flex: 1, @@ -214,12 +277,20 @@ export default function CategoriesScreen() { renderItem={({ item }) => ( - - {item.name} - - Créée le{" "} - {new Date(item.created_at).toLocaleDateString("fr-FR")} - + + + + {item.name} + + Créée le{" "} + {new Date(item.created_at).toLocaleDateString("fr-FR")} + + - + {editingCategory ? "Modifier la catégorie" : "Nouvelle catégorie"} + + Nom + + + Couleur du filtre + + + + + + {PRESET_COLORS.map((c) => ( + selectColor(c)} + /> + ))} + + + Annuler @@ -279,7 +392,7 @@ export default function CategoriesScreen() { - + diff --git a/mobile/src/api/api.ts b/mobile/src/api/api.ts index 53f6a3c2..ed1e4de4 100644 --- a/mobile/src/api/api.ts +++ b/mobile/src/api/api.ts @@ -116,6 +116,7 @@ export const logoutUser = async (): Promise => { export interface Category { id: number; name: string; + color: string; created_at: string; } diff --git a/mobile/src/components/CategoryPill.tsx b/mobile/src/components/CategoryPill.tsx index b449043f..ce63c8f9 100644 --- a/mobile/src/components/CategoryPill.tsx +++ b/mobile/src/components/CategoryPill.tsx @@ -8,15 +8,17 @@ interface CategoryPillProps { label: string; active: boolean; onPress: () => void; + color?: string; } export default function CategoryPill({ label, active, onPress, + color, }: CategoryPillProps) { const { colors } = useTheme(); - const catColor = getCategoryColor(label, colors); + const catColor = color || getCategoryColor(label, colors); return ( setSelectedCategory(cat.name)} + color={cat.color} /> ))}