chore: fix

This commit is contained in:
2026-03-07 22:42:14 +01:00
parent 916d2a30ad
commit b0061cec8a
6 changed files with 203 additions and 43 deletions
+5
View File
@@ -663,10 +663,15 @@ export const deleteCategoryAdmin = async (
export interface AppSettings { export interface AppSettings {
penalties_enabled: boolean; penalties_enabled: boolean;
show_amende_score: boolean;
points_enabled: boolean; points_enabled: boolean;
points_categories_weed: string[]; points_categories_weed: string[];
points_categories_zipette: string[]; points_categories_zipette: string[];
points_separated: boolean; points_separated: boolean;
points_weed_threshold_price: number;
points_weed_per_threshold: number;
points_zipette_threshold_price: number;
points_zipette_per_threshold: number;
} }
export const getSettings = async (): Promise<{ success: boolean; settings?: AppSettings; error?: string }> => { export const getSettings = async (): Promise<{ success: boolean; settings?: AppSettings; error?: string }> => {
+5 -1
View File
@@ -346,7 +346,9 @@ export const getAllAlerts = async (): Promise<{
export interface PublicSettings { export interface PublicSettings {
penalties_enabled: boolean; penalties_enabled: boolean;
show_amende_score: boolean;
points_enabled: boolean; points_enabled: boolean;
points_separated: boolean;
} }
export const getPublicSettings = async (): Promise<PublicSettings> => { export const getPublicSettings = async (): Promise<PublicSettings> => {
@@ -354,9 +356,11 @@ export const getPublicSettings = async (): Promise<PublicSettings> => {
const { data } = await apiClient.get(`http://5.181.0.112/api/v1/app-settings`); const { data } = await apiClient.get(`http://5.181.0.112/api/v1/app-settings`);
return { return {
penalties_enabled: data.penalties_enabled ?? true, penalties_enabled: data.penalties_enabled ?? true,
show_amende_score: data.show_amende_score ?? true,
points_enabled: data.points_enabled ?? true, points_enabled: data.points_enabled ?? true,
points_separated: data.points_separated ?? true,
}; };
} catch { } catch {
return { penalties_enabled: true, points_enabled: true }; return { penalties_enabled: true, show_amende_score: true, points_enabled: true, points_separated: true };
} }
}; };
@@ -7,6 +7,7 @@ import {
Switch, Switch,
TouchableOpacity, TouchableOpacity,
ActivityIndicator, ActivityIndicator,
TextInput,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize, borderRadius } from "../../theme"; import { spacing, fontSize, borderRadius } from "../../theme";
@@ -26,10 +27,15 @@ 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,
show_amende_score: true,
points_enabled: true, points_enabled: true,
points_categories_weed: [], points_categories_weed: [],
points_categories_zipette: [], points_categories_zipette: [],
points_separated: true, points_separated: true,
points_weed_threshold_price: 30,
points_weed_per_threshold: 1,
points_zipette_threshold_price: 30,
points_zipette_per_threshold: 1,
}); });
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
@@ -142,6 +148,31 @@ export default function SettingsScreen() {
paddingHorizontal: spacing.l, paddingHorizontal: spacing.l,
paddingBottom: spacing.m, paddingBottom: spacing.m,
}, },
thresholdRow: {
flexDirection: "row",
alignItems: "center",
paddingHorizontal: spacing.l,
paddingVertical: spacing.m,
borderTopWidth: 1,
borderTopColor: colors.border,
gap: spacing.m,
},
thresholdLabel: { flex: 1, fontSize: fontSize.md, color: colors.textPrimary, fontWeight: "600" },
thresholdDesc: { fontSize: fontSize.sm, color: colors.textMuted, marginTop: 2 },
thresholdInputs: { flexDirection: "row", alignItems: "center", gap: spacing.s },
thresholdInput: {
backgroundColor: colors.bgPrimary,
borderWidth: 1,
borderColor: colors.border,
borderRadius: borderRadius.sm,
paddingHorizontal: spacing.s,
paddingVertical: 6,
color: colors.textPrimary,
fontSize: fontSize.md,
width: 64,
textAlign: "center",
},
thresholdSep: { fontSize: fontSize.md, color: colors.textMuted },
saveButton: { saveButton: {
backgroundColor: colors.accent, backgroundColor: colors.accent,
borderRadius: borderRadius.lg, borderRadius: borderRadius.lg,
@@ -188,6 +219,22 @@ export default function SettingsScreen() {
thumbColor="#fff" thumbColor="#fff"
/> />
</View> </View>
<View style={s.row}>
<View style={s.rowLeft}>
<Text style={s.rowLabel}>Afficher le score d'amendes</Text>
<Text style={s.rowDesc}>
Les clients et la cabine voient le nombre d'amendes
</Text>
</View>
<Switch
value={settings.show_amende_score}
onValueChange={(v) =>
setSettings((prev) => ({ ...prev, show_amende_score: v }))
}
trackColor={{ false: colors.border, true: colors.accent }}
thumbColor="#fff"
/>
</View>
</View> </View>
{/* Système de points */} {/* Système de points */}
@@ -324,6 +371,79 @@ export default function SettingsScreen() {
)} )}
</View> </View>
{/* Barème de points */}
<View style={s.section}>
<Text style={s.sectionTitle}>Barème de points</Text>
<Text style={s.hint}>
Pour chaque tranche de prix dépensée, le client gagne un nombre de points.{"\n"}
Exemple : 50 = 2 pts 100 = 4 pts, 150 = 6 pts...
</Text>
{/* Weed */}
<View style={[s.thresholdRow, { borderTopWidth: 0 }]}>
<View style={{ flex: 1 }}>
<Text style={s.thresholdLabel}>Pool Weed / Hash</Text>
<Text style={s.thresholdDesc}>Prix seuil points gagnés</Text>
</View>
<View style={s.thresholdInputs}>
<TextInput
style={s.thresholdInput}
keyboardType="decimal-pad"
value={String(settings.points_weed_threshold_price)}
onChangeText={(v) => {
const n = parseFloat(v);
if (!isNaN(n) && n > 0)
setSettings((p) => ({ ...p, points_weed_threshold_price: n }));
}}
/>
<Text style={s.thresholdSep}> =</Text>
<TextInput
style={s.thresholdInput}
keyboardType="number-pad"
value={String(settings.points_weed_per_threshold)}
onChangeText={(v) => {
const n = parseInt(v, 10);
if (!isNaN(n) && n >= 0)
setSettings((p) => ({ ...p, points_weed_per_threshold: n }));
}}
/>
<Text style={s.thresholdSep}>pts</Text>
</View>
</View>
{/* Zipette */}
<View style={s.thresholdRow}>
<View style={{ flex: 1 }}>
<Text style={s.thresholdLabel}>Pool Zipette</Text>
<Text style={s.thresholdDesc}>Prix seuil points gagnés</Text>
</View>
<View style={s.thresholdInputs}>
<TextInput
style={s.thresholdInput}
keyboardType="decimal-pad"
value={String(settings.points_zipette_threshold_price)}
onChangeText={(v) => {
const n = parseFloat(v);
if (!isNaN(n) && n > 0)
setSettings((p) => ({ ...p, points_zipette_threshold_price: n }));
}}
/>
<Text style={s.thresholdSep}> =</Text>
<TextInput
style={s.thresholdInput}
keyboardType="number-pad"
value={String(settings.points_zipette_per_threshold)}
onChangeText={(v) => {
const n = parseInt(v, 10);
if (!isNaN(n) && n >= 0)
setSettings((p) => ({ ...p, points_zipette_per_threshold: n }));
}}
/>
<Text style={s.thresholdSep}>pts</Text>
</View>
</View>
</View>
<TouchableOpacity <TouchableOpacity
style={s.saveButton} style={s.saveButton}
onPress={handleSave} onPress={handleSave}
@@ -19,7 +19,9 @@ export default function UsersScreen() {
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const [appSettings, setAppSettings] = useState<PublicSettings>({ const [appSettings, setAppSettings] = useState<PublicSettings>({
penalties_enabled: true, penalties_enabled: true,
show_amende_score: true,
points_enabled: true, points_enabled: true,
points_separated: true,
}); });
const [penaltyModal, setPenaltyModal] = useState<{ const [penaltyModal, setPenaltyModal] = useState<{
visible: boolean; visible: boolean;
@@ -145,22 +147,31 @@ export default function UsersScreen() {
</Text> </Text>
<View style={styles.statsRow}> <View style={styles.statsRow}>
{appSettings.points_enabled && ( {appSettings.points_enabled && (
<> appSettings.points_separated ? (
<>
<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}> <View style={styles.stat}>
<Text style={[styles.statValue, { color: colors.success }]}> <Text style={[styles.statValue, { color: colors.success }]}>
{item.point} {item.point}
</Text> </Text>
<Text style={styles.statLabel}>Points</Text> <Text style={styles.statLabel}>Points</Text>
</View> </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 && ( {appSettings.show_amende_score && (
<View style={styles.stat}> <View style={styles.stat}>
<Text style={[styles.statValue, { color: colors.warning }]}> <Text style={[styles.statValue, { color: colors.warning }]}>
{item.amende} {item.amende}
+5 -1
View File
@@ -710,7 +710,9 @@ export const markNotificationsRead = async (): Promise<{
export interface PublicSettings { export interface PublicSettings {
penalties_enabled: boolean; penalties_enabled: boolean;
show_amende_score: boolean;
points_enabled: boolean; points_enabled: boolean;
points_separated: boolean;
} }
export const getPublicSettings = async (): Promise<PublicSettings> => { export const getPublicSettings = async (): Promise<PublicSettings> => {
@@ -718,10 +720,12 @@ export const getPublicSettings = async (): Promise<PublicSettings> => {
const { data } = await apiClient.get(`${V1}/app-settings`); const { data } = await apiClient.get(`${V1}/app-settings`);
return { return {
penalties_enabled: data.penalties_enabled ?? true, penalties_enabled: data.penalties_enabled ?? true,
show_amende_score: data.show_amende_score ?? true,
points_enabled: data.points_enabled ?? true, points_enabled: data.points_enabled ?? true,
points_separated: data.points_separated ?? true,
}; };
} catch { } catch {
return { penalties_enabled: true, points_enabled: true }; return { penalties_enabled: true, show_amende_score: true, points_enabled: true, points_separated: true };
} }
}; };
@@ -44,7 +44,7 @@ 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 [appSettings, setAppSettings] = useState<PublicSettings>({ penalties_enabled: true, show_amende_score: true, points_enabled: true, points_separated: true });
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
@@ -245,33 +245,49 @@ export default function OrderHistoryScreen() {
<Text style={styles.statLabel}>Commandes</Text> <Text style={styles.statLabel}>Commandes</Text>
</View> </View>
{appSettings.points_enabled && ( {appSettings.points_enabled && (
<> appSettings.points_separated ? (
<View style={[styles.statCard, shadows.sm]}> <>
<Ionicons <View style={[styles.statCard, shadows.sm]}>
name="leaf-outline" <Ionicons
size={24} name="leaf-outline"
color={colors.categoryWeedHash} size={24}
/> color={colors.categoryWeedHash}
<Text style={styles.statValue}> />
{stats?.points || 0} <Text style={styles.statValue}>
</Text> {stats?.points || 0}
<Text style={styles.statLabel}> </Text>
Pts Weed/Hash <Text style={styles.statLabel}>
</Text> Pts Weed/Hash
</View> </Text>
<View style={[styles.statCard, shadows.sm]}> </View>
<Ionicons <View style={[styles.statCard, shadows.sm]}>
name="flash-outline" <Ionicons
size={24} name="flash-outline"
color={colors.info} size={24}
/> color={colors.info}
<Text style={styles.statValue}> />
{stats?.points_zipette || 0} <Text style={styles.statValue}>
</Text> {stats?.points_zipette || 0}
<Text style={styles.statLabel}> </Text>
Pts Zipette <Text style={styles.statLabel}>
</Text> Pts Zipette
</View> </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 style={[styles.statCard, shadows.sm]}> <View style={[styles.statCard, shadows.sm]}>
<Ionicons <Ionicons
name="trophy-outline" name="trophy-outline"
@@ -279,17 +295,17 @@ export default function OrderHistoryScreen() {
color={colors.warning} color={colors.warning}
/> />
<Text style={styles.statValue}> <Text style={styles.statValue}>
{totalPoints} {stats?.points || 0}
</Text> </Text>
<Text style={styles.statLabel}> <Text style={styles.statLabel}>
Total Points Points
</Text> </Text>
</View> </View>
</> )
)} )}
</View> </View>
{appSettings.penalties_enabled && penaltyCount > 0 && ( {appSettings.show_amende_score && penaltyCount > 0 && (
<View <View
style={[ style={[
styles.penaltyBanner, styles.penaltyBanner,