chore: update point system

This commit is contained in:
2026-03-07 23:26:30 +01:00
parent b0061cec8a
commit 51c65d1179
2 changed files with 142 additions and 80 deletions
+8 -4
View File
@@ -661,6 +661,12 @@ export const deleteCategoryAdmin = async (
// PARAMÈTRES GLOBAUX
// ============================================
export interface PointsTier {
min: number;
max: number; // 0 = illimité
points: number;
}
export interface AppSettings {
penalties_enabled: boolean;
show_amende_score: boolean;
@@ -668,10 +674,8 @@ export interface AppSettings {
points_categories_weed: string[];
points_categories_zipette: string[];
points_separated: boolean;
points_weed_threshold_price: number;
points_weed_per_threshold: number;
points_zipette_threshold_price: number;
points_zipette_per_threshold: number;
points_weed_tiers: PointsTier[];
points_zipette_tiers: PointsTier[];
}
export const getSettings = async (): Promise<{ success: boolean; settings?: AppSettings; error?: string }> => {
@@ -13,12 +13,127 @@ import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize, borderRadius } from "../../theme";
import { useTheme } from "../../context/ThemeContext";
import { getSettings, updateSettings, getCategories } from "../../api/api_admin";
import type { AppSettings, Category } from "../../api/api_admin";
import type { AppSettings, Category, PointsTier } from "../../api/api_admin";
import AlertModal from "../../components/ui/AlertModal";
import { useAlert } from "../../hooks/useAlert";
type PoolAssignment = "weed" | "zipette" | "none";
// ──────────────────────────────────────────────────────────────
// Composant éditeur de paliers
// ──────────────────────────────────────────────────────────────
function TiersSection({
title,
tiers,
onChange,
colors,
s,
accentColor,
}: {
title: string;
tiers: PointsTier[];
onChange: (tiers: PointsTier[]) => void;
colors: any;
s: any;
accentColor: string;
}) {
const updateTier = (i: number, field: keyof PointsTier, raw: string) => {
const val = field === "points" ? parseInt(raw, 10) : parseFloat(raw);
if (isNaN(val)) return;
const next = tiers.map((t, idx) => (idx === i ? { ...t, [field]: val } : t));
onChange(next);
};
const addTier = () => {
onChange([...tiers, { min: 0, max: 0, points: 1 }]);
};
const removeTier = (i: number) => {
onChange(tiers.filter((_, idx) => idx !== i));
};
return (
<View style={s.section}>
<Text style={s.sectionTitle}>{title}</Text>
<Text style={s.hint}>
Définissez chaque palier : de X à Y = N points.{"\n"}
Max = 0 signifie illimité (pas de borne supérieure).
</Text>
{/* En-tête colonnes */}
<View style={{ flexDirection: "row", paddingHorizontal: spacing.l, paddingBottom: spacing.xs, gap: spacing.xs }}>
<Text style={[s.thresholdSep, { width: 56, textAlign: "center" }]}>Min </Text>
<Text style={[s.thresholdSep, { width: 56, textAlign: "center" }]}>Max </Text>
<Text style={[s.thresholdSep, { width: 44, textAlign: "center" }]}>Pts</Text>
</View>
{tiers.map((tier, i) => (
<View
key={i}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: spacing.l,
paddingVertical: spacing.s,
borderTopWidth: 1,
borderTopColor: colors.border,
gap: spacing.xs,
}}
>
<TextInput
style={[s.thresholdInput, { width: 56 }]}
keyboardType="decimal-pad"
value={String(tier.min)}
onChangeText={(v) => updateTier(i, "min", v)}
/>
<Text style={s.thresholdSep}></Text>
<TextInput
style={[s.thresholdInput, { width: 56 }]}
keyboardType="decimal-pad"
value={String(tier.max)}
onChangeText={(v) => updateTier(i, "max", v)}
placeholder="0=∞"
placeholderTextColor={colors.textMuted}
/>
<Text style={s.thresholdSep}>=</Text>
<TextInput
style={[s.thresholdInput, { width: 44 }]}
keyboardType="number-pad"
value={String(tier.points)}
onChangeText={(v) => updateTier(i, "points", v)}
/>
<Text style={[s.thresholdSep, { marginRight: spacing.xs }]}>pts</Text>
<TouchableOpacity onPress={() => removeTier(i)}>
<Ionicons name="trash-outline" size={18} color={colors.danger} />
</TouchableOpacity>
</View>
))}
{tiers.length === 0 && (
<Text style={[s.hint, { paddingTop: 0 }]}>Aucun palier défini</Text>
)}
<TouchableOpacity
onPress={addTier}
style={{
flexDirection: "row",
alignItems: "center",
gap: spacing.xs,
paddingHorizontal: spacing.l,
paddingVertical: spacing.m,
borderTopWidth: 1,
borderTopColor: colors.border,
}}
>
<Ionicons name="add-circle-outline" size={18} color={accentColor} />
<Text style={{ fontSize: fontSize.sm, color: accentColor, fontWeight: "600" }}>
Ajouter un palier
</Text>
</TouchableOpacity>
</View>
);
}
export default function SettingsScreen() {
const { colors } = useTheme();
const { alert, showError, showSuccess, hideAlert } = useAlert();
@@ -32,10 +147,8 @@ export default function SettingsScreen() {
points_categories_weed: [],
points_categories_zipette: [],
points_separated: true,
points_weed_threshold_price: 30,
points_weed_per_threshold: 1,
points_zipette_threshold_price: 30,
points_zipette_per_threshold: 1,
points_weed_tiers: [],
points_zipette_tiers: [],
});
const [categories, setCategories] = useState<Category[]>([]);
@@ -372,77 +485,22 @@ export default function SettingsScreen() {
</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>
<TiersSection
title="Barème Weed / Hash"
tiers={settings.points_weed_tiers ?? []}
onChange={(tiers) => setSettings((p) => ({ ...p, points_weed_tiers: tiers }))}
colors={colors}
s={s}
accentColor={WEED_COLOR}
/>
<TiersSection
title="Barème Zipette"
tiers={settings.points_zipette_tiers ?? []}
onChange={(tiers) => setSettings((p) => ({ ...p, points_zipette_tiers: tiers }))}
colors={colors}
s={s}
accentColor={ZIP_COLOR}
/>
<TouchableOpacity
style={s.saveButton}