chore: update point system
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user