chore: add color for category
This commit is contained in:
@@ -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<Category[]> => {
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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<Category[]>([]);
|
||||
@@ -32,6 +42,8 @@ export default function CategoriesScreen() {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [editingCategory, setEditingCategory] = useState<Category | null>(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 }) => (
|
||||
<Card>
|
||||
<View style={styles.row}>
|
||||
<View>
|
||||
<Text style={styles.catName}>{item.name}</Text>
|
||||
<Text style={styles.catDate}>
|
||||
Créée le{" "}
|
||||
{new Date(item.created_at).toLocaleDateString("fr-FR")}
|
||||
</Text>
|
||||
<View style={styles.catLeft}>
|
||||
<View
|
||||
style={[
|
||||
styles.colorDot,
|
||||
{ backgroundColor: item.color || "#7c3aed" },
|
||||
]}
|
||||
/>
|
||||
<View>
|
||||
<Text style={styles.catName}>{item.name}</Text>
|
||||
<Text style={styles.catDate}>
|
||||
Créée le{" "}
|
||||
{new Date(item.created_at).toLocaleDateString("fr-FR")}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.actions}>
|
||||
<TouchableOpacity
|
||||
@@ -253,10 +324,15 @@ export default function CategoriesScreen() {
|
||||
behavior={Platform.OS === "ios" ? "padding" : undefined}
|
||||
style={styles.overlay}
|
||||
>
|
||||
<View style={styles.modal}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.modal}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<Text style={styles.modalTitle}>
|
||||
{editingCategory ? "Modifier la catégorie" : "Nouvelle catégorie"}
|
||||
</Text>
|
||||
|
||||
<Text style={styles.label}>Nom</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Nom de la catégorie"
|
||||
@@ -265,6 +341,43 @@ export default function CategoriesScreen() {
|
||||
onChangeText={setName}
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
|
||||
<View style={styles.colorSection}>
|
||||
<Text style={styles.label}>Couleur du filtre</Text>
|
||||
<View style={styles.previewRow}>
|
||||
<View
|
||||
style={[styles.previewDot, { backgroundColor: color }]}
|
||||
/>
|
||||
<TextInput
|
||||
style={styles.hexInput}
|
||||
value={hexInput}
|
||||
onChangeText={handleHexInput}
|
||||
placeholder="#RRGGBB"
|
||||
placeholderTextColor={colors.textSecondary}
|
||||
autoCapitalize="none"
|
||||
maxLength={7}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.paletteGrid}>
|
||||
{PRESET_COLORS.map((c) => (
|
||||
<TouchableOpacity
|
||||
key={c}
|
||||
style={[
|
||||
styles.colorSwatch,
|
||||
{
|
||||
backgroundColor: c,
|
||||
borderColor:
|
||||
color === c
|
||||
? colors.textWhite
|
||||
: "transparent",
|
||||
},
|
||||
]}
|
||||
onPress={() => selectColor(c)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.modalBtns}>
|
||||
<TouchableOpacity style={styles.cancelBtn} onPress={closeModal}>
|
||||
<Text style={styles.cancelBtnText}>Annuler</Text>
|
||||
@@ -279,7 +392,7 @@ export default function CategoriesScreen() {
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</Modal>
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ export const logoutUser = async (): Promise<void> => {
|
||||
export interface Category {
|
||||
id: number;
|
||||
name: string;
|
||||
color: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
|
||||
@@ -205,6 +205,7 @@ export default function ProductsScreen() {
|
||||
label={cat.name}
|
||||
active={selectedCategory === cat.name}
|
||||
onPress={() => setSelectedCategory(cat.name)}
|
||||
color={cat.color}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
Reference in New Issue
Block a user