From 75161a1ae05ce776907008598e3cc634803203c8 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sun, 28 Jun 2026 12:28:46 +0200 Subject: [PATCH] chore: build --- frontend-admin/src/api/api_admin.ts | 12 + .../src/screens/admin/UsersScreen.tsx | 1029 +++++++++++++---- 2 files changed, 788 insertions(+), 253 deletions(-) diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index e8895c59..d43a68d5 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -653,6 +653,18 @@ export const setClientParrain = async ( return { success: false, error: e.response?.data?.error || "Erreur" }; } }; +export const getClientParrain = async ( + username: string, +): Promise<{ success: boolean; parrain?: string | null; error?: string }> => { + try { + const { data } = await apiClient.get( + `${V2}/admin/protected/client/${username}/parrain`, + ); + return { success: true, parrain: data.parrain ?? null }; + } catch (e: any) { + return { success: false, error: e.response?.data?.error || "Erreur" }; + } +}; export const creditClientReferral = async ( username: string, diff --git a/frontend-admin/src/screens/admin/UsersScreen.tsx b/frontend-admin/src/screens/admin/UsersScreen.tsx index ebd89d23..1a4b4ef6 100644 --- a/frontend-admin/src/screens/admin/UsersScreen.tsx +++ b/frontend-admin/src/screens/admin/UsersScreen.tsx @@ -24,6 +24,7 @@ import { createClientByAdmin, createUserByAdmin, setClientParrain, + getClientParrain, creditClientReferral, resetClientReferral, getClientCancelledOrders, @@ -147,6 +148,9 @@ export default function UsersScreen() { const [referralEnabled, setReferralEnabled] = useState(true); const [filter, setFilter] = useState("all"); const [search, setSearch] = useState(""); + const [parrainMap, setParrainMap] = useState>( + {}, + ); // Edit client modal const [editClientModal, setEditClientModal] = useState<{ @@ -180,7 +184,6 @@ export default function UsersScreen() { const [createTel, setCreateTel] = useState(""); const [createParrain, setCreateParrain] = useState(""); const [creating, setCreating] = useState(false); - // Referral credit modal const [referralModal, setReferralModal] = useState<{ visible: boolean; @@ -195,14 +198,24 @@ export default function UsersScreen() { visible: boolean; client: ClientResponse | null; }>({ visible: false, client: null }); - const [sanctionTab, setSanctionTab] = useState<"amende" | "points">("amende"); + 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>({}); - const [subtractInputs, setSubtractInputs] = useState>({}); - const [subtractLoading, setSubtractLoading] = useState>({}); + const [pointsInputs, setPointsInputs] = useState>( + {}, + ); + const [pointsLoading, setPointsLoading] = useState>( + {}, + ); + const [subtractInputs, setSubtractInputs] = useState< + Record + >({}); + const [subtractLoading, setSubtractLoading] = useState< + Record + >({}); // Cancelled orders modal const [cancelledModal, setCancelledModal] = useState<{ @@ -212,7 +225,8 @@ export default function UsersScreen() { loading: boolean; }>({ visible: false, username: "", orders: [], loading: false }); - const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert(); + const { alert, showError, showSuccess, showConfirm, hideAlert } = + useAlert(); // -------------------------------------------------- // Data @@ -231,8 +245,34 @@ export default function UsersScreen() { setPoolNames(pools.map((p) => p.name)); setPoolKeys(pools.map((p) => p.key)); setPointsEnabled(settingsRes.settings.points_enabled ?? true); - setAmendeEnabled(settingsRes.settings.show_amende_score ?? true); - setReferralEnabled(settingsRes.settings.referral_enabled ?? true); + setAmendeEnabled( + settingsRes.settings.show_amende_score ?? true, + ); + setReferralEnabled( + settingsRes.settings.referral_enabled ?? true, + ); + } + + // Charger le parrain de chaque client en parallèle (best-effort, + // une erreur sur un client n'empêche pas les autres de s'afficher). + const clientUsernames = (clientsData ?? []).map((c) => c.username); + if (clientUsernames.length > 0) { + const results = await Promise.all( + clientUsernames.map(async (username) => { + const res = await getClientParrain(username); + return [ + username, + res.success ? (res.parrain ?? null) : null, + ] as const; + }), + ); + const map: Record = {}; + for (const [username, parrain] of results) { + map[username] = parrain; + } + setParrainMap(map); + } else { + setParrainMap({}); } } catch { /* ignore */ @@ -332,7 +372,10 @@ export default function UsersScreen() { const handleSaveClient = async () => { if (!editClientModal.client) return; if (editClientPassword.trim() && editClientPassword.trim().length < 8) { - showError("Erreur", "Le mot de passe doit contenir au moins 8 caractères"); + showError( + "Erreur", + "Le mot de passe doit contenir au moins 8 caractères", + ); return; } try { @@ -366,7 +409,10 @@ export default function UsersScreen() { const handleSaveUser = async () => { if (!editUserModal.user) return; if (editUserPassword.trim() && editUserPassword.trim().length < 8) { - showError("Erreur", "Le mot de passe doit faire au moins 8 caractères"); + showError( + "Erreur", + "Le mot de passe doit faire au moins 8 caractères", + ); return; } try { @@ -439,7 +485,10 @@ export default function UsersScreen() { } setReferralLoading(true); try { - const res = await creditClientReferral(referralModal.username, amount); + const res = await creditClientReferral( + referralModal.username, + amount, + ); if (res.success) { setReferralModal((prev) => ({ ...prev, visible: false })); await loadData(); @@ -497,15 +546,28 @@ export default function UsersScreen() { `Ajouter ${pts} € d'amende à "${sanctionModal.client.username}" ?\nRaison : ${amendeReason.trim()}`, async () => { setAmendeLoading(true); - const res = await applyClientPenalty(sanctionModal.client!.username, pts, amendeReason.trim()); + const res = await applyClientPenalty( + sanctionModal.client!.username, + pts, + amendeReason.trim(), + ); setAmendeLoading(false); - if (!res.success) { showError("Erreur", res.error || "Echec"); return; } + 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"); + showSuccess( + "Compensation", + "Amende annulée — solde parrainage débité en compensation", + ); } else { - showSuccess("Validé", `Amende de ${pts} € appliquée à "${sanctionModal.client!.username}"`); + showSuccess( + "Validé", + `Amende de ${pts} € appliquée à "${sanctionModal.client!.username}"`, + ); } await loadData(); }, @@ -518,9 +580,18 @@ export default function UsersScreen() { "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; } - showSuccess("Validé", `Amende de "${sanctionModal.client!.username}" remise à zéro`); + const res = await resetClientPenalties( + sanctionModal.client!.username, + false, + ); + if (!res.success) { + showError("Erreur", res.error || "Echec"); + return; + } + showSuccess( + "Validé", + `Amende de "${sanctionModal.client!.username}" remise à zéro`, + ); await loadData(); }, "Remettre à zéro", @@ -539,11 +610,21 @@ export default function UsersScreen() { `Ajouter ${pts} points "${poolName}" à "${sanctionModal.client.username}" ?`, async () => { setPointsLoading((prev) => ({ ...prev, [poolKey]: true })); - const res = await addClientPoints(sanctionModal.client!.username, poolKey, pts); + const res = await addClientPoints( + sanctionModal.client!.username, + poolKey, + pts, + ); setPointsLoading((prev) => ({ ...prev, [poolKey]: false })); - if (!res.success) { showError("Erreur", res.error || "Echec"); return; } + if (!res.success) { + showError("Erreur", res.error || "Echec"); + return; + } setPointsInputs((prev) => ({ ...prev, [poolKey]: "" })); - showSuccess("Validé", `${pts} points "${poolName}" ajoutés à "${sanctionModal.client!.username}"`); + showSuccess( + "Validé", + `${pts} points "${poolName}" ajoutés à "${sanctionModal.client!.username}"`, + ); await loadData(); }, ); @@ -561,25 +642,48 @@ export default function UsersScreen() { `Retirer ${pts} points "${poolName}" à "${sanctionModal.client.username}" ?`, async () => { setSubtractLoading((prev) => ({ ...prev, [poolKey]: true })); - const res = await subtractClientPoints(sanctionModal.client!.username, poolKey, pts); + const res = await subtractClientPoints( + sanctionModal.client!.username, + poolKey, + pts, + ); setSubtractLoading((prev) => ({ ...prev, [poolKey]: false })); - if (!res.success) { showError("Erreur", res.error || "Echec"); return; } + if (!res.success) { + showError("Erreur", res.error || "Echec"); + return; + } setSubtractInputs((prev) => ({ ...prev, [poolKey]: "" })); - showSuccess("Validé", `${pts} points "${poolName}" retirés à "${sanctionModal.client!.username}"`); + showSuccess( + "Validé", + `${pts} points "${poolName}" retirés à "${sanctionModal.client!.username}"`, + ); await loadData(); }, ); }; - const handleResetPoints = (poolKey: string, poolName: string, poolIdx: number) => { + 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; } - showSuccess("Validé", `Points "${poolName}" de "${sanctionModal.client!.username}" remis à zéro`); + const res = await resetClientPoints( + sanctionModal.client!.username, + poolIdx, + ); + if (!res.success) { + showError("Erreur", res.error || "Echec"); + return; + } + showSuccess( + "Validé", + `Points "${poolName}" de "${sanctionModal.client!.username}" remis à zéro`, + ); await loadData(); }, "Remettre à zéro", @@ -590,9 +694,18 @@ export default function UsersScreen() { // Cancelled orders // -------------------------------------------------- const openCancelledOrders = async (client: ClientResponse) => { - setCancelledModal({ visible: true, username: client.username, orders: [], loading: true }); + setCancelledModal({ + visible: true, + username: client.username, + orders: [], + loading: true, + }); const res = await getClientCancelledOrders(client.username); - setCancelledModal((prev) => ({ ...prev, orders: res.orders, loading: false })); + setCancelledModal((prev) => ({ + ...prev, + orders: res.orders, + loading: false, + })); }; // -------------------------------------------------- @@ -646,7 +759,10 @@ export default function UsersScreen() { telephone: createTel.trim(), }); if (createParrain.trim()) { - await setClientParrain(usernameToCreate, createParrain.trim()); + await setClientParrain( + usernameToCreate, + createParrain.trim(), + ); } } else { await createUserByAdmin({ @@ -665,14 +781,22 @@ export default function UsersScreen() { if (createType === "client") { const freshClients = await getAllClients(); setClients(freshClients); - if (freshClients.find((c) => c.username === usernameToCreate)) { + if ( + freshClients.find( + (c) => c.username === usernameToCreate, + ) + ) { setCreateModal(false); return; } } else { const freshUsers = await getAllUsers(); setUsers(freshUsers); - if (freshUsers.find((u: any) => u.username === usernameToCreate)) { + if ( + freshUsers.find( + (u: any) => u.username === usernameToCreate, + ) + ) { setCreateModal(false); return; } @@ -698,7 +822,8 @@ export default function UsersScreen() { // Filter grid filterGrid: { flexDirection: "row", - paddingHorizontal: screenWidth < 380 ? spacing.s : spacing.m, + paddingHorizontal: + screenWidth < 380 ? spacing.s : spacing.m, paddingVertical: spacing.s, gap: screenWidth < 380 ? spacing.xs : spacing.s, }, @@ -777,6 +902,20 @@ export default function UsersScreen() { fontSize: fontSize.sm, marginTop: 2, }, + parrainRow: { + flexDirection: "row", + alignItems: "center", + gap: 4, + marginTop: 4, + }, + parrainText: { + color: colors.textMuted, + fontSize: fontSize.xs, + }, + parrainName: { + color: colors.success, + fontWeight: "600", + }, statsRow: { flexDirection: "row", flexWrap: "wrap", @@ -795,7 +934,11 @@ export default function UsersScreen() { color: colors.textWhite, textAlign: "center", }, - statLabel: { fontSize: fontSize.xs, color: colors.textMuted, textAlign: "center" }, + statLabel: { + fontSize: fontSize.xs, + color: colors.textMuted, + textAlign: "center", + }, // Non-client nonClientInfo: { @@ -940,7 +1083,9 @@ export default function UsersScreen() { {referralEnabled && ( openReferralModal(item.clientData!)} + onPress={() => + openReferralModal(item.clientData!) + } > )} - {referralEnabled && (item.clientData!.referral_balance ?? 0) > 0 && ( - handleResetReferral(item.clientData!.username)} - > - - - )} + {referralEnabled && + (item.clientData!.referral_balance ?? 0) > + 0 && ( + + handleResetReferral( + item.clientData!.username, + ) + } + > + + + )} openCancelledOrders(item.clientData!)} + onPress={() => + openCancelledOrders(item.clientData!) + } > openSanctionModal(item.clientData!)} + onPress={() => + openSanctionModal(item.clientData!) + } > openEditClient(item.clientData!)} + onPress={() => + openEditClient(item.clientData!) + } > {item.clientData.telephone} + {parrainMap[item.clientData.username] ? ( + + + + Parrainé par{" "} + + { + parrainMap[ + item.clientData.username + ] + } + + + + ) : null} - + {item.clientData.command} - Cmd + + Cmd + - {pointsEnabled && poolNames.map((name, i) => { - const key = poolKeys[i] ?? ""; - const value = key ? (item.clientData!.points_extra?.[key] ?? 0) : 0; - const color = i === 0 ? colors.success : i === 1 ? colors.info : colors.warning; - return ( - - {value} - {name} - - ); - })} + {pointsEnabled && + poolNames.map((name, i) => { + const key = poolKeys[i] ?? ""; + const value = key + ? (item.clientData!.points_extra?.[ + key + ] ?? 0) + : 0; + const color = + i === 0 + ? colors.success + : i === 1 + ? colors.info + : colors.warning; + return ( + + + {value} + + + {name} + + + ); + })} {amendeEnabled && ( {item.clientData.amende}€ - Amende + + Amende + )} {item.clientData.cancellations_count} - Annul. + + Annul. + - {referralEnabled && (item.clientData.referral_balance ?? 0) > 0 && ( - - - {(item.clientData.referral_balance ?? 0).toFixed(0)}€ - - Parrain - - )} + {referralEnabled && + (item.clientData.referral_balance ?? 0) > 0 && ( + + + {( + item.clientData + .referral_balance ?? 0 + ).toFixed(0)} + € + + + Parrain + + + )} )} @@ -1157,7 +1393,11 @@ export default function UsersScreen() { {/* Search bar */} - + {search.length > 0 && ( setSearch("")}> - + )} @@ -1379,7 +1623,13 @@ export default function UsersScreen() { value={createParrain} onChangeText={setCreateParrain} autoCapitalize="none" - icon={} + icon={ + + } /> )} @@ -1406,54 +1656,139 @@ export default function UsersScreen() { {/* Modal commandes annulées */} setCancelledModal((prev) => ({ ...prev, visible: false }))} + onClose={() => + setCancelledModal((prev) => ({ ...prev, visible: false })) + } title={`Annulations — ${cancelledModal.username}`} icon="ban-outline" iconColor={colors.danger} > {cancelledModal.loading ? ( - + ) : cancelledModal.orders.length === 0 ? ( - + Aucune commande annulée ) : ( - + {cancelledModal.orders.map((order, idx) => { const date = order.cancellation?.cancelled_at - ? new Date(order.cancellation.cancelled_at).toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric" }) - : new Date(order.updated_at).toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric" }); - const reason = order.cancel_reason || order.cancellation?.reason || ""; + ? new Date( + order.cancellation.cancelled_at, + ).toLocaleDateString("fr-FR", { + day: "2-digit", + month: "2-digit", + year: "numeric", + }) + : new Date(order.updated_at).toLocaleDateString( + "fr-FR", + { + day: "2-digit", + month: "2-digit", + year: "numeric", + }, + ); + const reason = + order.cancel_reason || + order.cancellation?.reason || + ""; return ( - - + + Commande #{order.id} - {date} - - - - {order.items_count} article{order.items_count > 1 ? "s" : ""} + + {date} - - {Number(order.total_prix).toFixed(2)} € + + + + {order.items_count} article + {order.items_count > 1 ? "s" : ""} + + + {Number(order.total_prix).toFixed( + 2, + )}{" "} + € {reason ? ( - + Raison : {reason} ) : ( - + Aucune raison fournie )} @@ -1467,13 +1802,21 @@ export default function UsersScreen() { {/* Modal points & amendes */} setSanctionModal({ visible: false, client: null })} + onClose={() => + setSanctionModal({ visible: false, client: null }) + } title={`Points & amendes — ${sanctionModal.client?.username ?? ""}`} icon="wallet-outline" iconColor={colors.warning} > {/* Onglets */} - + {(["amende", "points"] as const).map((tab) => ( - + {tab === "amende" ? "Amende" : "Points"} @@ -1521,19 +1878,32 @@ export default function UsersScreen() { {sanctionTab === "amende" && ( <> {/* Valeur actuelle */} - - + + Amende actuelle - + {sanctionModal.client?.amende ?? 0} € @@ -1544,16 +1914,30 @@ export default function UsersScreen() { value={amendeAmount} onChangeText={setAmendeAmount} keyboardType="decimal-pad" - icon={} + icon={ + + } /> } + icon={ + + } />