chore: fix

This commit is contained in:
2026-03-07 20:51:48 +01:00
parent 5c212e5f9c
commit 916d2a30ad
8 changed files with 316 additions and 194 deletions
+3 -1
View File
@@ -663,7 +663,9 @@ export const deleteCategoryAdmin = async (
export interface AppSettings { export interface AppSettings {
penalties_enabled: boolean; penalties_enabled: boolean;
points_categories: string[]; points_enabled: boolean;
points_categories_weed: string[];
points_categories_zipette: string[];
points_separated: boolean; points_separated: boolean;
} }
+21
View File
@@ -339,3 +339,24 @@ export const getAllAlerts = async (): Promise<{
return { success: false, alerts: [], count: 0 }; return { success: false, alerts: [], count: 0 };
} }
}; };
// ============================================
// PARAMÈTRES PUBLICS
// ============================================
export interface PublicSettings {
penalties_enabled: boolean;
points_enabled: boolean;
}
export const getPublicSettings = async (): Promise<PublicSettings> => {
try {
const { data } = await apiClient.get(`http://5.181.0.112/api/v1/app-settings`);
return {
penalties_enabled: data.penalties_enabled ?? true,
points_enabled: data.points_enabled ?? true,
};
} catch {
return { penalties_enabled: true, points_enabled: true };
}
};
@@ -2,6 +2,8 @@ import React from "react";
import { TouchableOpacity, View } from "react-native"; import { TouchableOpacity, View } from "react-native";
import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { useNavigation } from "@react-navigation/native";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { useAuth } from "../auth/AuthContext"; import { useAuth } from "../auth/AuthContext";
import { useTheme } from "../context/ThemeContext"; import { useTheme } from "../context/ThemeContext";
@@ -26,6 +28,7 @@ const Stack = createNativeStackNavigator<AdminStackParamList>();
function AdminTabs() { function AdminTabs() {
const { logout } = useAuth(); const { logout } = useAuth();
const { colors, isDark, toggleTheme } = useTheme(); const { colors, isDark, toggleTheme } = useTheme();
const navigation = useNavigation<NativeStackNavigationProp<AdminStackParamList>>();
const handleLogout = async () => { const handleLogout = async () => {
await logoutAdmin(); await logoutAdmin();
@@ -55,6 +58,16 @@ function AdminTabs() {
color={colors.textSecondary} color={colors.textSecondary}
/> />
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity
onPress={() => navigation.navigate("Settings")}
style={{ marginRight: spacing.m }}
>
<Ionicons
name="settings-outline"
size={22}
color={colors.textSecondary}
/>
</TouchableOpacity>
<TouchableOpacity onPress={handleLogout}> <TouchableOpacity onPress={handleLogout}>
<Ionicons <Ionicons
name="log-out-outline" name="log-out-outline"
@@ -186,20 +199,6 @@ function AdminTabs() {
), ),
}} }}
/> />
<Tab.Screen
name="Settings"
component={SettingsScreen}
options={{
title: "Paramètres",
tabBarIcon: ({ color, size }) => (
<Ionicons
name="settings-outline"
size={size}
color={color}
/>
),
}}
/>
</Tab.Navigator> </Tab.Navigator>
); );
} }
@@ -224,6 +223,11 @@ export default function AdminNavigator() {
component={OrderDetailScreen} component={OrderDetailScreen}
options={{ title: "Détail commande" }} options={{ title: "Détail commande" }}
/> />
<Stack.Screen
name="Settings"
component={SettingsScreen}
options={{ title: "Paramètres" }}
/>
</Stack.Navigator> </Stack.Navigator>
); );
} }
+1 -1
View File
@@ -14,12 +14,12 @@ export type AdminTabParamList = {
Delivery: undefined; Delivery: undefined;
Alerts: undefined; Alerts: undefined;
Addresses: undefined; Addresses: undefined;
Settings: undefined;
}; };
export type AdminStackParamList = { export type AdminStackParamList = {
AdminTabs: undefined; AdminTabs: undefined;
OrderDetail: { orderId: number }; OrderDetail: { orderId: number };
Settings: undefined;
}; };
export type CabineTabParamList = { export type CabineTabParamList = {
@@ -16,6 +16,8 @@ import type { AppSettings, Category } from "../../api/api_admin";
import AlertModal from "../../components/ui/AlertModal"; import AlertModal from "../../components/ui/AlertModal";
import { useAlert } from "../../hooks/useAlert"; import { useAlert } from "../../hooks/useAlert";
type PoolAssignment = "weed" | "zipette" | "none";
export default function SettingsScreen() { export default function SettingsScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const { alert, showError, showSuccess, hideAlert } = useAlert(); const { alert, showError, showSuccess, hideAlert } = useAlert();
@@ -24,7 +26,9 @@ export default function SettingsScreen() {
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [settings, setSettings] = useState<AppSettings>({ const [settings, setSettings] = useState<AppSettings>({
penalties_enabled: true, penalties_enabled: true,
points_categories: [], points_enabled: true,
points_categories_weed: [],
points_categories_zipette: [],
points_separated: true, points_separated: true,
}); });
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
@@ -38,7 +42,8 @@ export default function SettingsScreen() {
if (settingsRes.success && settingsRes.settings) { if (settingsRes.success && settingsRes.settings) {
setSettings({ setSettings({
...settingsRes.settings, ...settingsRes.settings,
points_categories: settingsRes.settings.points_categories ?? [], points_categories_weed: settingsRes.settings.points_categories_weed ?? [],
points_categories_zipette: settingsRes.settings.points_categories_zipette ?? [],
}); });
} }
if (categoriesRes) { if (categoriesRes) {
@@ -51,13 +56,19 @@ export default function SettingsScreen() {
loadData(); loadData();
}, [loadData]); }, [loadData]);
const toggleCategory = (name: string) => { const getPoolFor = (name: string): PoolAssignment => {
if ((settings.points_categories_weed ?? []).includes(name)) return "weed";
if ((settings.points_categories_zipette ?? []).includes(name)) return "zipette";
return "none";
};
const setPool = (name: string, pool: PoolAssignment) => {
setSettings((prev) => { setSettings((prev) => {
const cats = prev.points_categories ?? []; const weed = (prev.points_categories_weed ?? []).filter((c) => c !== name);
if (cats.includes(name)) { const zipette = (prev.points_categories_zipette ?? []).filter((c) => c !== name);
return { ...prev, points_categories: cats.filter((c) => c !== name) }; if (pool === "weed") weed.push(name);
} else if (pool === "zipette") zipette.push(name);
return { ...prev, points_categories: [...cats, name] }; return { ...prev, points_categories_weed: weed, points_categories_zipette: zipette };
}); });
}; };
@@ -73,14 +84,8 @@ export default function SettingsScreen() {
}; };
const s = StyleSheet.create({ const s = StyleSheet.create({
container: { container: { flex: 1, backgroundColor: colors.bgPrimary },
flex: 1, content: { padding: spacing.l, paddingBottom: spacing.xl * 2 },
backgroundColor: colors.bgPrimary,
},
content: {
padding: spacing.l,
paddingBottom: spacing.xl * 2,
},
section: { section: {
backgroundColor: colors.bgSecondary, backgroundColor: colors.bgSecondary,
borderRadius: borderRadius.lg, borderRadius: borderRadius.lg,
@@ -106,53 +111,31 @@ export default function SettingsScreen() {
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: colors.border, borderTopColor: colors.border,
}, },
rowFirst: { rowFirst: { borderTopWidth: 0 },
borderTopWidth: 0, rowLeft: { flex: 1, marginRight: spacing.m },
}, rowLabel: { fontSize: fontSize.md, color: colors.textPrimary, fontWeight: "600" },
rowLeft: { rowDesc: { fontSize: fontSize.sm, color: colors.textMuted, marginTop: 2 },
flex: 1, catRow: {
marginRight: spacing.m,
},
rowLabel: {
fontSize: fontSize.md,
color: colors.textPrimary,
fontWeight: "600",
},
rowDesc: {
fontSize: fontSize.sm,
color: colors.textMuted,
marginTop: 2,
},
categoryRow: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
paddingHorizontal: spacing.l, paddingHorizontal: spacing.l,
paddingVertical: spacing.m, paddingVertical: spacing.m,
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: colors.border, borderTopColor: colors.border,
gap: spacing.m, gap: spacing.s,
}, },
categoryRowFirst: { catRowFirst: { borderTopWidth: 0 },
borderTopWidth: 0, colorDot: { width: 10, height: 10, borderRadius: 5 },
catName: { flex: 1, fontSize: fontSize.md, color: colors.textPrimary },
chips: { flexDirection: "row", gap: spacing.xs },
chip: {
paddingHorizontal: spacing.s,
paddingVertical: 4,
borderRadius: borderRadius.sm,
borderWidth: 1,
}, },
checkBox: { chipText: { fontSize: fontSize.sm, fontWeight: "600" },
width: 22, hint: {
height: 22,
borderRadius: 6,
borderWidth: 2,
borderColor: colors.accent,
alignItems: "center",
justifyContent: "center",
},
checkBoxChecked: {
backgroundColor: colors.accent,
},
categoryName: {
fontSize: fontSize.md,
color: colors.textPrimary,
flex: 1,
},
allCatsHint: {
fontSize: fontSize.sm, fontSize: fontSize.sm,
color: colors.textMuted, color: colors.textMuted,
fontStyle: "italic", fontStyle: "italic",
@@ -169,11 +152,7 @@ export default function SettingsScreen() {
gap: spacing.s, gap: spacing.s,
marginTop: spacing.s, marginTop: spacing.s,
}, },
saveButtonText: { saveButtonText: { color: "#fff", fontSize: fontSize.md, fontWeight: "700" },
color: "#fff",
fontSize: fontSize.md,
fontWeight: "700",
},
}); });
if (loading) { if (loading) {
@@ -184,13 +163,13 @@ export default function SettingsScreen() {
); );
} }
const selectedCats = settings.points_categories ?? []; const WEED_COLOR = "#10b981";
const allSelected = selectedCats.length === 0; const ZIP_COLOR = "#9333ea";
return ( return (
<View style={s.container}> <View style={s.container}>
<ScrollView contentContainerStyle={s.content}> <ScrollView contentContainerStyle={s.content}>
{/* Section amendes */} {/* Amendes */}
<View style={s.section}> <View style={s.section}>
<Text style={s.sectionTitle}>Amendes</Text> <Text style={s.sectionTitle}>Amendes</Text>
<View style={[s.row, s.rowFirst]}> <View style={[s.row, s.rowFirst]}>
@@ -211,16 +190,32 @@ export default function SettingsScreen() {
</View> </View>
</View> </View>
{/* Section points */} {/* Système de points */}
<View style={s.section}> <View style={s.section}>
<Text style={s.sectionTitle}>Système de points</Text> <Text style={s.sectionTitle}>Système de points</Text>
<View style={[s.row, s.rowFirst]}> <View style={[s.row, s.rowFirst]}>
<View style={s.rowLeft}> <View style={s.rowLeft}>
<Text style={s.rowLabel}>Points séparés par catégorie</Text> <Text style={s.rowLabel}>Points activés</Text>
<Text style={s.rowDesc}> <Text style={s.rowDesc}>
Activé : weed point, zipette point_zipette{"\n"} Les clients voient leurs scores de points et point_zipette.{"\n"}
Désactivé : tous les points dans un seul compteur La cabine peut réinitialiser les points.
</Text>
</View>
<Switch
value={settings.points_enabled}
onValueChange={(v) =>
setSettings((prev) => ({ ...prev, points_enabled: v }))
}
trackColor={{ false: colors.border, true: colors.accent }}
thumbColor="#fff"
/>
</View>
<View style={s.row}>
<View style={s.rowLeft}>
<Text style={s.rowLabel}>Points séparés par pool</Text>
<Text style={s.rowDesc}>
Activé : Weed point / Zipette point_zipette{"\n"}
Désactivé : tout dans un seul compteur (point)
</Text> </Text>
</View> </View>
<Switch <Switch
@@ -234,50 +229,96 @@ export default function SettingsScreen() {
</View> </View>
</View> </View>
{/* Section catégories éligibles aux points */} {/* Attribution des catégories */}
<View style={s.section}> <View style={s.section}>
<Text style={s.sectionTitle}>Catégories éligibles aux points</Text> <Text style={s.sectionTitle}>Attribution des catégories aux points</Text>
{allSelected && ( <Text style={s.hint}>
<Text style={s.allCatsHint}> Pour chaque catégorie, choisis si elle génère des points Weed, Zipette, ou aucun.
Aucune sélection = comportement par défaut (toutes les catégories sauf gros&semi) </Text>
</Text>
)} {/* Légende */}
<View style={{ flexDirection: "row", gap: spacing.m, paddingHorizontal: spacing.l, paddingBottom: spacing.m }}>
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.xs }}>
<View style={[s.colorDot, { backgroundColor: WEED_COLOR }]} />
<Text style={{ fontSize: fontSize.sm, color: colors.textMuted }}>Weed</Text>
</View>
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.xs }}>
<View style={[s.colorDot, { backgroundColor: ZIP_COLOR }]} />
<Text style={{ fontSize: fontSize.sm, color: colors.textMuted }}>Zipette</Text>
</View>
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.xs }}>
<View style={[s.colorDot, { backgroundColor: colors.border }]} />
<Text style={{ fontSize: fontSize.sm, color: colors.textMuted }}>Aucun</Text>
</View>
</View>
{categories.map((cat, index) => { {categories.map((cat, index) => {
const checked = selectedCats.includes(cat.name); const pool = getPoolFor(cat.name);
return ( return (
<TouchableOpacity <View
key={cat.id} key={cat.id}
style={[ style={[s.catRow, index === 0 && s.catRowFirst]}
s.categoryRow,
index === 0 && s.categoryRowFirst,
]}
onPress={() => toggleCategory(cat.name)}
> >
<View <View
style={[ style={[
s.checkBox, s.colorDot,
checked && s.checkBoxChecked, { backgroundColor: cat.color || colors.accent },
{ borderColor: cat.color || colors.accent },
]} ]}
> />
{checked && ( <Text style={s.catName}>{cat.name}</Text>
<Ionicons name="checkmark" size={14} color="#fff" /> <View style={s.chips}>
{(["weed", "zipette", "none"] as PoolAssignment[]).map(
(p) => {
const active = pool === p;
const chipColor =
p === "weed"
? WEED_COLOR
: p === "zipette"
? ZIP_COLOR
: colors.textMuted;
const label =
p === "weed"
? "W"
: p === "zipette"
? "Z"
: "—";
return (
<TouchableOpacity
key={p}
style={[
s.chip,
{
borderColor: chipColor,
backgroundColor: active
? chipColor
: "transparent",
},
]}
onPress={() => setPool(cat.name, p)}
>
<Text
style={[
s.chipText,
{
color: active
? "#fff"
: chipColor,
},
]}
>
{label}
</Text>
</TouchableOpacity>
);
},
)} )}
</View> </View>
<View </View>
style={{
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: cat.color || colors.accent,
}}
/>
<Text style={s.categoryName}>{cat.name}</Text>
</TouchableOpacity>
); );
})} })}
{categories.length === 0 && ( {categories.length === 0 && (
<Text style={[s.allCatsHint, { paddingTop: 0 }]}> <Text style={[s.hint, { paddingTop: 0 }]}>
Aucune catégorie disponible Aucune catégorie disponible
</Text> </Text>
)} )}
@@ -3,7 +3,8 @@ import { View, Text, StyleSheet, FlatList, RefreshControl } from "react-native";
import { spacing, fontSize } from "../../theme"; import { spacing, fontSize } from "../../theme";
import { useTheme } from "../../context/ThemeContext"; import { useTheme } from "../../context/ThemeContext";
import { getAllClients } from "../../api/api_admin"; import { getAllClients } from "../../api/api_admin";
import { applyClientPenalty, resetClientPenalties, resetClientPoints } from "../../api/api_cabine"; import { applyClientPenalty, resetClientPenalties, resetClientPoints, getPublicSettings } from "../../api/api_cabine";
import type { PublicSettings } from "../../api/api_cabine";
import type { ClientResponse } from "../../api/types"; import type { ClientResponse } from "../../api/types";
import LoadingSpinner from "../../components/ui/LoadingSpinner"; import LoadingSpinner from "../../components/ui/LoadingSpinner";
import Card from "../../components/ui/Card"; import Card from "../../components/ui/Card";
@@ -16,6 +17,10 @@ export default function UsersScreen() {
const [clients, setClients] = useState<ClientResponse[]>([]); const [clients, setClients] = useState<ClientResponse[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const [appSettings, setAppSettings] = useState<PublicSettings>({
penalties_enabled: true,
points_enabled: true,
});
const [penaltyModal, setPenaltyModal] = useState<{ const [penaltyModal, setPenaltyModal] = useState<{
visible: boolean; visible: boolean;
username: string; username: string;
@@ -25,7 +30,12 @@ export default function UsersScreen() {
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
try { try {
setClients(await getAllClients()); const [clientsData, settings] = await Promise.all([
getAllClients(),
getPublicSettings(),
]);
setClients(clientsData);
setAppSettings(settings);
} catch { } catch {
/* ignore */ /* ignore */
} }
@@ -35,6 +45,7 @@ export default function UsersScreen() {
useEffect(() => { useEffect(() => {
loadData(); loadData();
}, [loadData]); }, [loadData]);
const onRefresh = async () => { const onRefresh = async () => {
setRefreshing(true); setRefreshing(true);
await loadData(); await loadData();
@@ -115,6 +126,7 @@ export default function UsersScreen() {
flexDirection: "row", flexDirection: "row",
gap: spacing.s, gap: spacing.s,
marginTop: spacing.m, marginTop: spacing.m,
flexWrap: "wrap",
}, },
empty: { empty: {
color: colors.textMuted, color: colors.textMuted,
@@ -132,24 +144,30 @@ export default function UsersScreen() {
{item.prenom} {item.nom} - {item.telephone} {item.prenom} {item.nom} - {item.telephone}
</Text> </Text>
<View style={styles.statsRow}> <View style={styles.statsRow}>
<View style={styles.stat}> {appSettings.points_enabled && (
<Text style={[styles.statValue, { color: colors.success }]}> <>
{item.point} <View style={styles.stat}>
</Text> <Text style={[styles.statValue, { color: colors.success }]}>
<Text style={styles.statLabel}>Points</Text> {item.point}
</View> </Text>
<View style={styles.stat}> <Text style={styles.statLabel}>Points</Text>
<Text style={[styles.statValue, { color: colors.info }]}> </View>
{item.points_zipette} <View style={styles.stat}>
</Text> <Text style={[styles.statValue, { color: colors.info }]}>
<Text style={styles.statLabel}>Zipette</Text> {item.points_zipette}
</View> </Text>
<View style={styles.stat}> <Text style={styles.statLabel}>Zipette</Text>
<Text style={[styles.statValue, { color: colors.warning }]}> </View>
{item.amende} </>
</Text> )}
<Text style={styles.statLabel}>Amendes</Text> {appSettings.penalties_enabled && (
</View> <View style={styles.stat}>
<Text style={[styles.statValue, { color: colors.warning }]}>
{item.amende}
</Text>
<Text style={styles.statLabel}>Amendes</Text>
</View>
)}
<View style={styles.stat}> <View style={styles.stat}>
<Text style={[styles.statValue, { color: colors.danger }]}> <Text style={[styles.statValue, { color: colors.danger }]}>
{item.cancellations_count} {item.cancellations_count}
@@ -158,24 +176,30 @@ export default function UsersScreen() {
</View> </View>
</View> </View>
<View style={styles.actions}> <View style={styles.actions}>
<Button {appSettings.penalties_enabled && (
title="Reset pénalités" <Button
onPress={() => handleReset(item.username)} title="Reset pénalités"
size="sm" onPress={() => handleReset(item.username)}
variant="outline" size="sm"
/> variant="outline"
<Button />
title="Reset points" )}
onPress={() => handleResetPoints(item.username)} {appSettings.points_enabled && (
size="sm" <>
variant="outline" <Button
/> title="Reset points"
<Button onPress={() => handleResetPoints(item.username)}
title="Reset zipette" size="sm"
onPress={() => handleResetZipette(item.username)} variant="outline"
size="sm" />
variant="outline" <Button
/> title="Reset zipette"
onPress={() => handleResetZipette(item.username)}
size="sm"
variant="outline"
/>
</>
)}
</View> </View>
</Card> </Card>
); );
+21
View File
@@ -704,6 +704,27 @@ export const markNotificationsRead = async (): Promise<{
} }
}; };
// ============================================
// PUBLIC SETTINGS
// ============================================
export interface PublicSettings {
penalties_enabled: boolean;
points_enabled: boolean;
}
export const getPublicSettings = async (): Promise<PublicSettings> => {
try {
const { data } = await apiClient.get(`${V1}/app-settings`);
return {
penalties_enabled: data.penalties_enabled ?? true,
points_enabled: data.points_enabled ?? true,
};
} catch {
return { penalties_enabled: true, points_enabled: true };
}
};
export const calculateOrderTotal = (order: any): number => { export const calculateOrderTotal = (order: any): number => {
if (typeof order.total === "number" && order.total > 0) return order.total; if (typeof order.total === "number" && order.total > 0) return order.total;
if (typeof order.total_prix === "number" && order.total_prix > 0) if (typeof order.total_prix === "number" && order.total_prix > 0)
@@ -13,9 +13,11 @@ import { Ionicons } from "@expo/vector-icons";
import { import {
getMyCompletedOrders, getMyCompletedOrders,
getMyPenalties, getMyPenalties,
getPublicSettings,
formatOrderDate, formatOrderDate,
formatPrice, formatPrice,
} from "../../api/api"; } from "../../api/api";
import type { PublicSettings } from "../../api/api";
import type { import type {
CompletedOrder, CompletedOrder,
ClientStats, ClientStats,
@@ -42,14 +44,16 @@ export default function OrderHistoryScreen() {
const [orders, setOrders] = useState<CompletedOrder[]>([]); const [orders, setOrders] = useState<CompletedOrder[]>([]);
const [stats, setStats] = useState<ClientStats | null>(null); const [stats, setStats] = useState<ClientStats | null>(null);
const [penalties, setPenalties] = useState<PenaltyInfo | null>(null); const [penalties, setPenalties] = useState<PenaltyInfo | null>(null);
const [appSettings, setAppSettings] = useState<PublicSettings>({ penalties_enabled: true, points_enabled: true });
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const fetchData = useCallback(async () => { const fetchData = useCallback(async () => {
try { try {
const [histRes, penRes] = await Promise.all([ const [histRes, penRes, settings] = await Promise.all([
getMyCompletedOrders(), getMyCompletedOrders(),
getMyPenalties(), getMyPenalties(),
getPublicSettings(),
]); ]);
if (histRes.success) { if (histRes.success) {
setOrders(histRes.commands || []); setOrders(histRes.commands || []);
@@ -58,6 +62,7 @@ export default function OrderHistoryScreen() {
if (penRes.success) { if (penRes.success) {
setPenalties(penRes.data || null); setPenalties(penRes.data || null);
} }
setAppSettings(settings);
} catch { } catch {
/* ignore */ /* ignore */
} finally { } finally {
@@ -239,48 +244,52 @@ export default function OrderHistoryScreen() {
</Text> </Text>
<Text style={styles.statLabel}>Commandes</Text> <Text style={styles.statLabel}>Commandes</Text>
</View> </View>
<View style={[styles.statCard, shadows.sm]}> {appSettings.points_enabled && (
<Ionicons <>
name="leaf-outline" <View style={[styles.statCard, shadows.sm]}>
size={24} <Ionicons
color={colors.categoryWeedHash} name="leaf-outline"
/> size={24}
<Text style={styles.statValue}> color={colors.categoryWeedHash}
{stats?.points || 0} />
</Text> <Text style={styles.statValue}>
<Text style={styles.statLabel}> {stats?.points || 0}
Pts Weed/Hash </Text>
</Text> <Text style={styles.statLabel}>
</View> Pts Weed/Hash
<View style={[styles.statCard, shadows.sm]}> </Text>
<Ionicons </View>
name="flash-outline" <View style={[styles.statCard, shadows.sm]}>
size={24} <Ionicons
color={colors.info} name="flash-outline"
/> size={24}
<Text style={styles.statValue}> color={colors.info}
{stats?.points_zipette || 0} />
</Text> <Text style={styles.statValue}>
<Text style={styles.statLabel}> {stats?.points_zipette || 0}
Pts Zipette </Text>
</Text> <Text style={styles.statLabel}>
</View> Pts Zipette
<View style={[styles.statCard, shadows.sm]}> </Text>
<Ionicons </View>
name="trophy-outline" <View style={[styles.statCard, shadows.sm]}>
size={24} <Ionicons
color={colors.warning} name="trophy-outline"
/> size={24}
<Text style={styles.statValue}> color={colors.warning}
{totalPoints} />
</Text> <Text style={styles.statValue}>
<Text style={styles.statLabel}> {totalPoints}
Total Points </Text>
</Text> <Text style={styles.statLabel}>
</View> Total Points
</Text>
</View>
</>
)}
</View> </View>
{penaltyCount > 0 && ( {appSettings.penalties_enabled && penaltyCount > 0 && (
<View <View
style={[ style={[
styles.penaltyBanner, styles.penaltyBanner,