241 lines
8.5 KiB
TypeScript
241 lines
8.5 KiB
TypeScript
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
|
import { View, Text, StyleSheet, FlatList, RefreshControl } from "react-native";
|
|
import { spacing, fontSize } from "../../theme";
|
|
import { useTheme } from "../../context/ThemeContext";
|
|
import { getAllClients } from "../../api/api_admin";
|
|
import { applyClientPenalty, resetClientPenalties, resetClientPoints, getPublicSettings } from "../../api/api_cabine";
|
|
import type { PublicSettings } from "../../api/api_cabine";
|
|
import type { ClientResponse } from "../../api/types";
|
|
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
|
import Card from "../../components/ui/Card";
|
|
import Button from "../../components/ui/Button";
|
|
import AlertModal from "../../components/ui/AlertModal";
|
|
import { useAlert } from "../../hooks/useAlert";
|
|
|
|
export default function UsersScreen() {
|
|
const { colors } = useTheme();
|
|
const [clients, setClients] = useState<ClientResponse[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
const [appSettings, setAppSettings] = useState<PublicSettings>({
|
|
penalties_enabled: true,
|
|
points_enabled: true,
|
|
});
|
|
const [penaltyModal, setPenaltyModal] = useState<{
|
|
visible: boolean;
|
|
username: string;
|
|
}>({ visible: false, username: "" });
|
|
const { alert, showError, showSuccess, showConfirm, hideAlert } =
|
|
useAlert();
|
|
|
|
const loadData = useCallback(async () => {
|
|
try {
|
|
const [clientsData, settings] = await Promise.all([
|
|
getAllClients(),
|
|
getPublicSettings(),
|
|
]);
|
|
setClients(clientsData);
|
|
setAppSettings(settings);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
setLoading(false);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, [loadData]);
|
|
|
|
const onRefresh = async () => {
|
|
setRefreshing(true);
|
|
await loadData();
|
|
setRefreshing(false);
|
|
};
|
|
|
|
const handleReset = (username: string) => {
|
|
showConfirm(
|
|
"Reset pénalités",
|
|
`Réinitialiser les pénalités de ${username} ?`,
|
|
async () => {
|
|
try {
|
|
await resetClientPenalties(username);
|
|
await loadData();
|
|
} catch (e: any) {
|
|
showError("Erreur", e.message);
|
|
}
|
|
},
|
|
"Reset",
|
|
);
|
|
};
|
|
|
|
const handleResetPoints = (username: string) => {
|
|
showConfirm(
|
|
"Reset points",
|
|
`Réinitialiser les points de ${username} ?`,
|
|
async () => {
|
|
try {
|
|
await resetClientPoints(username);
|
|
await loadData();
|
|
} catch (e: any) {
|
|
showError("Erreur", e.message);
|
|
}
|
|
},
|
|
"Reset",
|
|
);
|
|
};
|
|
|
|
const handleResetZipette = (username: string) => {
|
|
showConfirm(
|
|
"Reset points zipette",
|
|
`Réinitialiser les points zipette de ${username} ?`,
|
|
async () => {
|
|
try {
|
|
await resetClientPoints(username, true);
|
|
await loadData();
|
|
} catch (e: any) {
|
|
showError("Erreur", e.message);
|
|
}
|
|
},
|
|
"Reset",
|
|
);
|
|
};
|
|
|
|
const styles = useMemo(
|
|
() =>
|
|
StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: colors.bgPrimary },
|
|
username: {
|
|
fontSize: fontSize.lg,
|
|
fontWeight: "bold",
|
|
color: colors.textWhite,
|
|
},
|
|
info: {
|
|
color: colors.textSecondary,
|
|
fontSize: fontSize.sm,
|
|
marginTop: 2,
|
|
},
|
|
statsRow: {
|
|
flexDirection: "row",
|
|
marginTop: spacing.m,
|
|
gap: spacing.l,
|
|
},
|
|
stat: { alignItems: "center" },
|
|
statValue: { fontSize: fontSize.lg, fontWeight: "bold" },
|
|
statLabel: { fontSize: fontSize.xs, color: colors.textMuted },
|
|
actions: {
|
|
flexDirection: "row",
|
|
gap: spacing.s,
|
|
marginTop: spacing.m,
|
|
flexWrap: "wrap",
|
|
},
|
|
empty: {
|
|
color: colors.textMuted,
|
|
textAlign: "center",
|
|
marginTop: spacing.xxl,
|
|
},
|
|
}),
|
|
[colors],
|
|
);
|
|
|
|
const renderClient = ({ item }: { item: ClientResponse }) => (
|
|
<Card style={{ marginBottom: spacing.m }}>
|
|
<Text style={styles.username}>{item.username}</Text>
|
|
<Text style={styles.info}>
|
|
{item.prenom} {item.nom} - {item.telephone}
|
|
</Text>
|
|
<View style={styles.statsRow}>
|
|
{appSettings.points_enabled && (
|
|
<>
|
|
<View style={styles.stat}>
|
|
<Text style={[styles.statValue, { color: colors.success }]}>
|
|
{item.point}
|
|
</Text>
|
|
<Text style={styles.statLabel}>Points</Text>
|
|
</View>
|
|
<View style={styles.stat}>
|
|
<Text style={[styles.statValue, { color: colors.info }]}>
|
|
{item.points_zipette}
|
|
</Text>
|
|
<Text style={styles.statLabel}>Zipette</Text>
|
|
</View>
|
|
</>
|
|
)}
|
|
{appSettings.penalties_enabled && (
|
|
<View style={styles.stat}>
|
|
<Text style={[styles.statValue, { color: colors.warning }]}>
|
|
{item.amende}
|
|
</Text>
|
|
<Text style={styles.statLabel}>Amendes</Text>
|
|
</View>
|
|
)}
|
|
<View style={styles.stat}>
|
|
<Text style={[styles.statValue, { color: colors.danger }]}>
|
|
{item.cancellations_count}
|
|
</Text>
|
|
<Text style={styles.statLabel}>Annul.</Text>
|
|
</View>
|
|
</View>
|
|
<View style={styles.actions}>
|
|
{appSettings.penalties_enabled && (
|
|
<Button
|
|
title="Reset pénalités"
|
|
onPress={() => handleReset(item.username)}
|
|
size="sm"
|
|
variant="outline"
|
|
/>
|
|
)}
|
|
{appSettings.points_enabled && (
|
|
<>
|
|
<Button
|
|
title="Reset points"
|
|
onPress={() => handleResetPoints(item.username)}
|
|
size="sm"
|
|
variant="outline"
|
|
/>
|
|
<Button
|
|
title="Reset zipette"
|
|
onPress={() => handleResetZipette(item.username)}
|
|
size="sm"
|
|
variant="outline"
|
|
/>
|
|
</>
|
|
)}
|
|
</View>
|
|
</Card>
|
|
);
|
|
|
|
if (loading) return <LoadingSpinner message="Chargement..." />;
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<FlatList
|
|
data={clients}
|
|
keyExtractor={(item) => item.id.toString()}
|
|
renderItem={renderClient}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={refreshing}
|
|
onRefresh={onRefresh}
|
|
tintColor={colors.info}
|
|
/>
|
|
}
|
|
contentContainerStyle={{ padding: spacing.l }}
|
|
ListEmptyComponent={
|
|
<Text style={styles.empty}>Aucun client</Text>
|
|
}
|
|
/>
|
|
|
|
<AlertModal
|
|
visible={alert.visible}
|
|
type={alert.type}
|
|
title={alert.title}
|
|
message={alert.message}
|
|
onClose={hideAlert}
|
|
onConfirm={alert.onConfirm}
|
|
confirmText={alert.confirmText}
|
|
cancelText={alert.cancelText}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|