diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index e250b7cb..a5efd21d 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -475,6 +475,21 @@ export const deleteClientAdmin = async (clientId: number) => { // PARRAINAGE ADMIN // ============================================ +export const setClientParrain = async ( + username: string, + parrain: string, +): Promise<{ success: boolean; error?: string }> => { + try { + await apiClient.post( + `${V2}/admin/protected/client/${username}/parrain/set`, + { parrain }, + ); + return { success: true }; + } catch (e: any) { + return { success: false, error: e.response?.data?.error || "Erreur" }; + } +}; + export const creditClientReferral = async ( username: string, amount: number, @@ -541,6 +556,73 @@ export const getClientCancelledOrders = async ( } }; +// ============================================ +// AMENDES & POINTS CLIENT (Admin) +// ============================================ + +export const applyClientPenalty = async ( + username: string, + points: number, + reason: string, +): Promise<{ success: boolean; error?: string }> => { + try { + await apiClient.post(`${V2}/admin/protected/penalty`, { + username, + points, + reason, + }); + return { success: true }; + } catch (e: any) { + return { success: false, error: e.response?.data?.error || "Erreur" }; + } +}; + +export const resetClientPenalties = async ( + username: string, + resetCancellationsCount = false, +): Promise<{ success: boolean; error?: string }> => { + try { + await apiClient.post( + `${V2}/admin/protected/client/${username}/penalties/reset`, + { reset_cancellations_count: resetCancellationsCount }, + ); + return { success: true }; + } catch (e: any) { + return { success: false, error: e.response?.data?.error || "Erreur" }; + } +}; + +export const resetClientPoints = async ( + username: string, + pool = -1, +): Promise<{ success: boolean; error?: string }> => { + try { + await apiClient.post( + `${V2}/admin/protected/client/${username}/point/reset`, + { pool }, + ); + return { success: true }; + } catch (e: any) { + return { success: false, error: e.response?.data?.error || "Erreur" }; + } +}; + +export const addClientPoints = async ( + username: string, + poolKey: string, + points: number, +): Promise<{ success: boolean; error?: string }> => { + try { + await apiClient.post( + `${V2}/admin/protected/client/${username}/points/add`, + { pool_key: poolKey, points }, + ); + return { success: true }; + } catch (e: any) { + return { success: false, error: e.response?.data?.error || "Erreur" }; + } +}; + // ============================================ // ALERTES // ============================================ diff --git a/frontend-admin/src/screens/admin/UsersScreen.tsx b/frontend-admin/src/screens/admin/UsersScreen.tsx index b661b87f..ebb40e15 100644 --- a/frontend-admin/src/screens/admin/UsersScreen.tsx +++ b/frontend-admin/src/screens/admin/UsersScreen.tsx @@ -22,9 +22,14 @@ import { deleteClientAdmin, createClientByAdmin, createUserByAdmin, + setClientParrain, creditClientReferral, getClientCancelledOrders, getSettings, + applyClientPenalty, + resetClientPenalties, + resetClientPoints, + addClientPoints, } from "../../api/api_admin"; import type { CancelledOrder } from "../../api/api_admin"; import type { ClientResponse } from "../../api/types"; @@ -169,6 +174,7 @@ export default function UsersScreen() { const [createNom, setCreateNom] = useState(""); const [createPrenom, setCreatePrenom] = useState(""); const [createTel, setCreateTel] = useState(""); + const [createParrain, setCreateParrain] = useState(""); const [creating, setCreating] = useState(false); // Referral credit modal @@ -180,6 +186,18 @@ export default function UsersScreen() { const [referralAmount, setReferralAmount] = useState(""); const [referralLoading, setReferralLoading] = useState(false); + // Points & amende modal + const [sanctionModal, setSanctionModal] = useState<{ + visible: boolean; + client: ClientResponse | null; + }>({ visible: false, client: null }); + const [sanctionTab, setSanctionTab] = useState<"amende" | "points">("amende"); + const [amendeAmount, setAmendeAmount] = useState(""); + const [amendeReason, setAmendeReason] = useState(""); + const [amendeLoading, setAmendeLoading] = useState(false); + const [pointsInputs, setPointsInputs] = useState>({}); + const [pointsLoading, setPointsLoading] = useState>({}); + // Cancelled orders modal const [cancelledModal, setCancelledModal] = useState<{ visible: boolean; @@ -429,6 +447,81 @@ export default function UsersScreen() { } }; + // -------------------------------------------------- + // Points & amende + // -------------------------------------------------- + const openSanctionModal = (client: ClientResponse) => { + setAmendeAmount(""); + setAmendeReason(""); + setPointsInputs({}); + setPointsLoading({}); + setSanctionTab("amende"); + setSanctionModal({ visible: true, client }); + }; + + const handleAddAmende = async () => { + const pts = parseInt(amendeAmount, 10); + if (isNaN(pts) || pts <= 0) { + showError("Erreur", "Entrez un montant valide"); + return; + } + if (!amendeReason.trim()) { + showError("Erreur", "La raison est obligatoire"); + 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(); + }; + + const handleResetAmende = () => { + if (!sanctionModal.client) return; + showConfirm( + "Remettre à zéro", + `Remettre l'amende de "${sanctionModal.client.username}" à 0 ?`, + async () => { + const res = await resetClientPenalties(sanctionModal.client!.username, false); + if (!res.success) { showError("Erreur", res.error || "Echec"); return; } + await loadData(); + }, + "Remettre à zéro", + ); + }; + + const handleAddPoints = async (poolKey: 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(); + }; + + const handleResetPoints = (poolKey: string, poolName: string, poolIdx: number) => { + if (!sanctionModal.client) return; + showConfirm( + "Remettre à zéro", + `Remettre les points "${poolName}" de "${sanctionModal.client.username}" à 0 ?`, + async () => { + const res = await resetClientPoints(sanctionModal.client!.username, poolIdx); + if (!res.success) { showError("Erreur", res.error || "Echec"); return; } + await loadData(); + }, + "Remettre à zéro", + ); + }; + // -------------------------------------------------- // Cancelled orders // -------------------------------------------------- @@ -448,6 +541,7 @@ export default function UsersScreen() { setCreateNom(""); setCreatePrenom(""); setCreateTel(""); + setCreateParrain(""); setCreateModal(true); }; @@ -487,6 +581,9 @@ export default function UsersScreen() { prenom: createPrenom.trim(), telephone: createTel.trim(), }); + if (createParrain.trim()) { + await setClientParrain(usernameToCreate, createParrain.trim()); + } } else { await createUserByAdmin({ username: usernameToCreate, @@ -790,6 +887,16 @@ export default function UsersScreen() { color={colors.danger} /> + openSanctionModal(item.clientData!)} + > + + openEditClient(item.clientData!)} @@ -1188,6 +1295,15 @@ export default function UsersScreen() { onChangeText={setCreateTel} keyboardType="phone-pad" /> + {referralEnabled && ( + } + /> + )} )}