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