chore: build
This commit is contained in:
@@ -6,7 +6,6 @@ import {
|
|||||||
ScrollView,
|
ScrollView,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
useWindowDimensions,
|
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { spacing, fontSize } from "../../theme";
|
import { spacing, fontSize } from "../../theme";
|
||||||
@@ -18,23 +17,13 @@ import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
|||||||
import Card from "../../components/ui/Card";
|
import Card from "../../components/ui/Card";
|
||||||
|
|
||||||
type Period = "day" | "week" | "month";
|
type Period = "day" | "week" | "month";
|
||||||
type Metric = "count" | "revenue";
|
|
||||||
|
|
||||||
const BAR_MAX_HEIGHT = 110;
|
const BAR_MAX_HEIGHT = 110;
|
||||||
const BAR_WIDTH = 36;
|
const BAR_WIDTH = 36;
|
||||||
const BAR_GAP = 8;
|
const BAR_GAP = 8;
|
||||||
|
|
||||||
function BarChart({
|
function BarChart({ data, colors }: { data: StatPoint[]; colors: any }) {
|
||||||
data,
|
const maxVal = Math.max(...data.map((d) => d.count), 1);
|
||||||
metric,
|
|
||||||
colors,
|
|
||||||
}: {
|
|
||||||
data: StatPoint[];
|
|
||||||
metric: Metric;
|
|
||||||
colors: any;
|
|
||||||
}) {
|
|
||||||
const values = data.map((d) => (metric === "count" ? d.count : d.revenue));
|
|
||||||
const maxVal = Math.max(...values, 1);
|
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return (
|
return (
|
||||||
@@ -51,7 +40,7 @@ function BarChart({
|
|||||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={{ marginTop: spacing.m }}>
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={{ marginTop: spacing.m }}>
|
||||||
<View style={{ flexDirection: "row", alignItems: "flex-end", paddingBottom: spacing.s, paddingHorizontal: 4 }}>
|
<View style={{ flexDirection: "row", alignItems: "flex-end", paddingBottom: spacing.s, paddingHorizontal: 4 }}>
|
||||||
{data.map((point, i) => {
|
{data.map((point, i) => {
|
||||||
const val = metric === "count" ? point.count : point.revenue;
|
const val = point.count;
|
||||||
const barH = Math.max(4, (val / maxVal) * BAR_MAX_HEIGHT);
|
const barH = Math.max(4, (val / maxVal) * BAR_MAX_HEIGHT);
|
||||||
const isLast = i === data.length - 1;
|
const isLast = i === data.length - 1;
|
||||||
return (
|
return (
|
||||||
@@ -64,9 +53,7 @@ function BarChart({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={{ color: colors.textMuted, fontSize: 9, marginBottom: 3 }}>
|
<Text style={{ color: colors.textMuted, fontSize: 9, marginBottom: 3 }}>
|
||||||
{metric === "count"
|
{val > 0 ? String(val) : ""}
|
||||||
? val > 0 ? String(val) : ""
|
|
||||||
: val > 0 ? (val >= 1000 ? `${(val / 1000).toFixed(1)}k` : `${Math.round(val)}`) : ""}
|
|
||||||
</Text>
|
</Text>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -105,7 +92,6 @@ export default function StatsScreen() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
const [period, setPeriod] = useState<Period>("week");
|
const [period, setPeriod] = useState<Period>("week");
|
||||||
const [metric, setMetric] = useState<Metric>("count");
|
|
||||||
|
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -137,18 +123,13 @@ export default function StatsScreen() {
|
|||||||
const completed = deliveries.filter((d) => d.status === "livre" || d.status === "approved").length;
|
const completed = deliveries.filter((d) => d.status === "livre" || d.status === "approved").length;
|
||||||
const inProgress = deliveries.filter((d) => d.status === "en_route").length;
|
const inProgress = deliveries.filter((d) => d.status === "en_route").length;
|
||||||
const pending = deliveries.filter((d) => d.status === "assigned").length;
|
const pending = deliveries.filter((d) => d.status === "assigned").length;
|
||||||
const totalRevenue = deliveries
|
|
||||||
.filter((d) => d.status === "livre" || d.status === "approved")
|
|
||||||
.reduce((s, d) => s + (d.total_prix - (d.referral_used ?? 0)), 0);
|
|
||||||
|
|
||||||
const chartData = period === "day" ? byDay : period === "week" ? byWeek : byMonth;
|
const chartData = period === "day" ? byDay : period === "week" ? byWeek : byMonth;
|
||||||
|
|
||||||
const periodTotal = useMemo(() => {
|
const periodTotal = useMemo(
|
||||||
return chartData.reduce(
|
() => chartData.reduce((acc, p) => acc + p.count, 0),
|
||||||
(acc, p) => ({ count: acc.count + p.count, revenue: acc.revenue + p.revenue }),
|
[chartData],
|
||||||
{ count: 0, revenue: 0 },
|
|
||||||
);
|
);
|
||||||
}, [chartData]);
|
|
||||||
|
|
||||||
const periodLabels: Record<Period, string> = {
|
const periodLabels: Record<Period, string> = {
|
||||||
day: "30 derniers jours",
|
day: "30 derniers jours",
|
||||||
@@ -175,22 +156,6 @@ export default function StatsScreen() {
|
|||||||
marginTop: spacing.xs,
|
marginTop: spacing.xs,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
revenueCard: {
|
|
||||||
alignItems: "center",
|
|
||||||
paddingVertical: spacing.l,
|
|
||||||
marginTop: spacing.m,
|
|
||||||
},
|
|
||||||
revenueValue: {
|
|
||||||
fontSize: 28,
|
|
||||||
fontWeight: "800",
|
|
||||||
color: colors.success,
|
|
||||||
marginTop: spacing.s,
|
|
||||||
},
|
|
||||||
revenueLabel: {
|
|
||||||
color: colors.textMuted,
|
|
||||||
fontSize: fontSize.sm,
|
|
||||||
marginTop: spacing.xs,
|
|
||||||
},
|
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
color: colors.textWhite,
|
color: colors.textWhite,
|
||||||
fontSize: fontSize.lg,
|
fontSize: fontSize.lg,
|
||||||
@@ -222,26 +187,6 @@ export default function StatsScreen() {
|
|||||||
color: colors.textMuted,
|
color: colors.textMuted,
|
||||||
},
|
},
|
||||||
periodBtnTextActive: { color: colors.accent },
|
periodBtnTextActive: { color: colors.accent },
|
||||||
metricRow: {
|
|
||||||
flexDirection: "row",
|
|
||||||
gap: spacing.s,
|
|
||||||
marginBottom: spacing.s,
|
|
||||||
},
|
|
||||||
metricBtn: {
|
|
||||||
flex: 1,
|
|
||||||
paddingVertical: spacing.xs,
|
|
||||||
borderRadius: 6,
|
|
||||||
alignItems: "center",
|
|
||||||
backgroundColor: colors.bgCard,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: colors.border,
|
|
||||||
},
|
|
||||||
metricBtnActive: {
|
|
||||||
backgroundColor: colors.success + "22",
|
|
||||||
borderColor: colors.success,
|
|
||||||
},
|
|
||||||
metricBtnText: { fontSize: fontSize.xs, fontWeight: "600", color: colors.textMuted },
|
|
||||||
metricBtnTextActive: { color: colors.success },
|
|
||||||
chartCard: { paddingBottom: spacing.s },
|
chartCard: { paddingBottom: spacing.s },
|
||||||
summaryRow: {
|
summaryRow: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -287,15 +232,6 @@ export default function StatsScreen() {
|
|||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Revenu total all-time */}
|
|
||||||
<Card style={styles.revenueCard}>
|
|
||||||
<Ionicons name="cash-outline" size={28} color={colors.success} />
|
|
||||||
<Text style={styles.revenueValue}>
|
|
||||||
{totalRevenue.toLocaleString("fr-FR", { minimumFractionDigits: 2, maximumFractionDigits: 2 })} €
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.revenueLabel}>Revenu total généré</Text>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Section graphiques */}
|
{/* Section graphiques */}
|
||||||
<Text style={styles.sectionTitle}>Évolution</Text>
|
<Text style={styles.sectionTitle}>Évolution</Text>
|
||||||
|
|
||||||
@@ -314,44 +250,17 @@ export default function StatsScreen() {
|
|||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Sélecteur métrique */}
|
|
||||||
<View style={styles.metricRow}>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.metricBtn, metric === "count" && styles.metricBtnActive]}
|
|
||||||
onPress={() => setMetric("count")}
|
|
||||||
>
|
|
||||||
<Text style={[styles.metricBtnText, metric === "count" && styles.metricBtnTextActive]}>
|
|
||||||
Commandes
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.metricBtn, metric === "revenue" && styles.metricBtnActive]}
|
|
||||||
onPress={() => setMetric("revenue")}
|
|
||||||
>
|
|
||||||
<Text style={[styles.metricBtnText, metric === "revenue" && styles.metricBtnTextActive]}>
|
|
||||||
Revenus (€)
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Card style={styles.chartCard}>
|
<Card style={styles.chartCard}>
|
||||||
<Text style={styles.periodHint}>{periodLabels[period]}</Text>
|
<Text style={styles.periodHint}>{periodLabels[period]}</Text>
|
||||||
|
|
||||||
<BarChart data={chartData} metric={metric} colors={colors} />
|
<BarChart data={chartData} colors={colors} />
|
||||||
|
|
||||||
{/* Totaux de la période */}
|
|
||||||
{chartData.length > 0 && (
|
{chartData.length > 0 && (
|
||||||
<View style={styles.summaryRow}>
|
<View style={styles.summaryRow}>
|
||||||
<View style={styles.summaryItem}>
|
<View style={styles.summaryItem}>
|
||||||
<Text style={styles.summaryValue}>{periodTotal.count}</Text>
|
<Text style={styles.summaryValue}>{periodTotal}</Text>
|
||||||
<Text style={styles.summaryLabel}>livraisons</Text>
|
<Text style={styles.summaryLabel}>livraisons</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.summaryItem, { borderLeftWidth: 1, borderLeftColor: colors.border }]}>
|
|
||||||
<Text style={[styles.summaryValue, { color: colors.success }]}>
|
|
||||||
{periodTotal.revenue.toLocaleString("fr-FR", { minimumFractionDigits: 2, maximumFractionDigits: 2 })} €
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.summaryLabel}>revenus</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user