chore: fix UI

This commit is contained in:
2026-04-26 18:23:50 +02:00
parent 322882d587
commit a120ad6391
11 changed files with 317 additions and 44 deletions
+19 -3
View File
@@ -564,14 +564,14 @@ export const applyClientPenalty = async (
username: string,
points: number,
reason: string,
): Promise<{ success: boolean; error?: string }> => {
): Promise<{ success: boolean; compensated?: boolean; error?: string }> => {
try {
await apiClient.post(`${V2}/admin/protected/penalty`, {
const { data } = await apiClient.post(`${V2}/admin/protected/penalty`, {
username,
points,
reason,
});
return { success: true };
return { success: true, compensated: data?.compensated === true };
} catch (e: any) {
return { success: false, error: e.response?.data?.error || "Erreur" };
}
@@ -623,6 +623,22 @@ export const addClientPoints = async (
}
};
export const subtractClientPoints = async (
username: string,
poolKey: string,
points: number,
): Promise<{ success: boolean; error?: string }> => {
try {
await apiClient.post(
`${V2}/admin/protected/client/${username}/points/subtract`,
{ pool_key: poolKey, points },
);
return { success: true };
} catch (e: any) {
return { success: false, error: e.response?.data?.error || "Erreur" };
}
};
// ============================================
// ALERTES
// ============================================
@@ -513,12 +513,13 @@ export default function ProductsScreen() {
backgroundColor: "rgba(0,0,0,0.85)",
justifyContent: "flex-end",
},
modalWrapper: { maxHeight: "94%" },
modalWrapper: { maxHeight: "94%", flex: 1 },
modal: {
backgroundColor: colors.bgSecondary,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
maxHeight: "100%",
flex: 1,
},
modalHeader: {
flexDirection: "row",
@@ -538,6 +539,7 @@ export default function ProductsScreen() {
modalBody: {
paddingHorizontal: spacing.xl,
paddingTop: spacing.l,
flex: 1,
},
modalFooter: {
flexDirection: "row",
@@ -30,6 +30,7 @@ import {
resetClientPenalties,
resetClientPoints,
addClientPoints,
subtractClientPoints,
} from "../../api/api_admin";
import type { CancelledOrder } from "../../api/api_admin";
import type { ClientResponse } from "../../api/types";
@@ -197,6 +198,8 @@ export default function UsersScreen() {
const [amendeLoading, setAmendeLoading] = useState(false);
const [pointsInputs, setPointsInputs] = useState<Record<string, string>>({});
const [pointsLoading, setPointsLoading] = useState<Record<string, boolean>>({});
const [subtractInputs, setSubtractInputs] = useState<Record<string, string>>({});
const [subtractLoading, setSubtractLoading] = useState<Record<string, boolean>>({});
// Cancelled orders modal
const [cancelledModal, setCancelledModal] = useState<{
@@ -206,7 +209,7 @@ export default function UsersScreen() {
loading: boolean;
}>({ visible: false, username: "", orders: [], loading: false });
const { alert, showError, showConfirm, hideAlert } = useAlert();
const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert();
// --------------------------------------------------
// Data
@@ -455,11 +458,13 @@ export default function UsersScreen() {
setAmendeReason("");
setPointsInputs({});
setPointsLoading({});
setSubtractInputs({});
setSubtractLoading({});
setSanctionTab("amende");
setSanctionModal({ visible: true, client });
};
const handleAddAmende = async () => {
const handleAddAmende = () => {
const pts = parseInt(amendeAmount, 10);
if (isNaN(pts) || pts <= 0) {
showError("Erreur", "Entrez un montant valide");
@@ -470,13 +475,22 @@ export default function UsersScreen() {
return;
}
if (!sanctionModal.client) return;
setAmendeLoading(true);
const res = await applyClientPenalty(sanctionModal.client.username, pts, amendeReason.trim());
setAmendeLoading(false);
if (!res.success) { showError("Erreur", res.error || "Echec"); return; }
setAmendeAmount("");
setAmendeReason("");
await loadData();
showConfirm(
"Confirmer l'amende",
`Ajouter ${pts} € d'amende à "${sanctionModal.client.username}" ?\nRaison : ${amendeReason.trim()}`,
async () => {
setAmendeLoading(true);
const res = await applyClientPenalty(sanctionModal.client!.username, pts, amendeReason.trim());
setAmendeLoading(false);
if (!res.success) { showError("Erreur", res.error || "Echec"); return; }
setAmendeAmount("");
setAmendeReason("");
if (res.compensated) {
showSuccess("Compensation", "Amende annulée — solde parrainage débité en compensation");
}
await loadData();
},
);
};
const handleResetAmende = () => {
@@ -493,19 +507,46 @@ export default function UsersScreen() {
);
};
const handleAddPoints = async (poolKey: string) => {
const handleAddPoints = (poolKey: string, poolName: string) => {
const pts = parseInt(pointsInputs[poolKey] || "", 10);
if (isNaN(pts) || pts <= 0) {
showError("Erreur", "Entrez un nombre de points valide");
return;
}
if (!sanctionModal.client) return;
setPointsLoading((prev) => ({ ...prev, [poolKey]: true }));
const res = await addClientPoints(sanctionModal.client.username, poolKey, pts);
setPointsLoading((prev) => ({ ...prev, [poolKey]: false }));
if (!res.success) { showError("Erreur", res.error || "Echec"); return; }
setPointsInputs((prev) => ({ ...prev, [poolKey]: "" }));
await loadData();
showConfirm(
"Confirmer l'ajout",
`Ajouter ${pts} points "${poolName}" à "${sanctionModal.client.username}" ?`,
async () => {
setPointsLoading((prev) => ({ ...prev, [poolKey]: true }));
const res = await addClientPoints(sanctionModal.client!.username, poolKey, pts);
setPointsLoading((prev) => ({ ...prev, [poolKey]: false }));
if (!res.success) { showError("Erreur", res.error || "Echec"); return; }
setPointsInputs((prev) => ({ ...prev, [poolKey]: "" }));
await loadData();
},
);
};
const handleSubtractPoints = (poolKey: string, poolName: string) => {
const pts = parseInt(subtractInputs[poolKey] || "", 10);
if (isNaN(pts) || pts <= 0) {
showError("Erreur", "Entrez un nombre de points valide");
return;
}
if (!sanctionModal.client) return;
showConfirm(
"Confirmer le retrait",
`Retirer ${pts} points "${poolName}" à "${sanctionModal.client.username}" ?`,
async () => {
setSubtractLoading((prev) => ({ ...prev, [poolKey]: true }));
const res = await subtractClientPoints(sanctionModal.client!.username, poolKey, pts);
setSubtractLoading((prev) => ({ ...prev, [poolKey]: false }));
if (!res.success) { showError("Erreur", res.error || "Echec"); return; }
setSubtractInputs((prev) => ({ ...prev, [poolKey]: "" }));
await loadData();
},
);
};
const handleResetPoints = (poolKey: string, poolName: string, poolIdx: number) => {
@@ -1538,7 +1579,7 @@ export default function UsersScreen() {
/>
</View>
<TouchableOpacity
onPress={() => handleAddPoints(key)}
onPress={() => handleAddPoints(key, name)}
disabled={pointsLoading[key]}
style={{
backgroundColor: color + "20",
@@ -1562,6 +1603,43 @@ export default function UsersScreen() {
</TouchableOpacity>
</View>
{/* Input + bouton enlever */}
<View style={{ flexDirection: "row", gap: spacing.s, alignItems: "center" }}>
<View style={{ flex: 1 }}>
<TextInput
placeholder="Points à enlever"
value={subtractInputs[key] ?? ""}
onChangeText={(v) =>
setSubtractInputs((prev) => ({ ...prev, [key]: v }))
}
keyboardType="numeric"
/>
</View>
<TouchableOpacity
onPress={() => handleSubtractPoints(key, name)}
disabled={subtractLoading[key]}
style={{
backgroundColor: colors.danger + "15",
borderWidth: 1,
borderColor: colors.danger,
borderRadius: borderRadius.md,
paddingHorizontal: spacing.m,
paddingVertical: spacing.m,
alignItems: "center",
justifyContent: "center",
minWidth: 72,
}}
>
{subtractLoading[key] ? (
<ActivityIndicator size="small" color={colors.danger} />
) : (
<Text style={{ color: colors.danger, fontWeight: "700", fontSize: fontSize.sm }}>
Enlever
</Text>
)}
</TouchableOpacity>
</View>
{/* Bouton reset */}
<TouchableOpacity
onPress={() => handleResetPoints(key, name, idx)}