Files
projet_gestion_commande/frontend-admin/src/navigation/AdminNavigator.tsx
T
Xor290 1494f5e669
Frontend Admin - EAS Build / build (push) Failing after 1h0m49s
chore: build
2026-06-21 16:48:13 +02:00

434 lines
16 KiB
TypeScript

import React, { useState, useEffect, useRef, useCallback } from "react";
import {
TouchableOpacity,
View,
Modal,
Text,
ScrollView,
StyleSheet,
} from "react-native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { useNavigation } from "@react-navigation/native";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { Ionicons } from "@expo/vector-icons";
import { useAuth } from "../auth/AuthContext";
import { useTheme } from "../context/ThemeContext";
import {
logoutAdmin,
getAdminNotifications,
markAdminNotificationsRead,
} from "../api/api_admin";
import type { AppNotification } from "../api/api_admin";
import { fontSize, spacing } from "../theme";
import type { AdminTabParamList, AdminStackParamList } from "./types";
import DashboardScreen from "../screens/admin/DashboardScreen";
import StatsScreen from "../screens/admin/StatsScreen";
import OrdersScreen from "../screens/admin/OrdersScreen";
import OrderDetailScreen from "../screens/admin/OrderDetailScreen";
import UsersScreen from "../screens/admin/UsersScreen";
import ProductsScreen from "../screens/admin/ProductsScreen";
import CategoriesScreen from "../screens/admin/CategoriesScreen";
import DeliveryScreen from "../screens/admin/DeliveryScreen";
import AlertsScreen from "../screens/admin/AlertsScreen";
import AddressScreen from "../screens/admin/AddressScreen";
import SettingsScreen from "../screens/admin/SettingsScreen";
const Tab = createBottomTabNavigator<AdminTabParamList>();
const Stack = createNativeStackNavigator<AdminStackParamList>();
function AdminTabs() {
const { logout } = useAuth();
const { colors, isDark, toggleTheme } = useTheme();
const navigation = useNavigation<NativeStackNavigationProp<AdminStackParamList>>();
const [notifications, setNotifications] = useState<AppNotification[]>([]);
const [unreadCount, setUnreadCount] = useState(0);
const [modalVisible, setModalVisible] = useState(false);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const fetchNotifications = useCallback(async () => {
try {
const res = await getAdminNotifications();
setNotifications(res.notifications ?? []);
setUnreadCount(res.unread_count ?? 0);
} catch { /* ignore */ }
}, []);
useEffect(() => {
fetchNotifications();
intervalRef.current = setInterval(fetchNotifications, 15000);
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, [fetchNotifications]);
const openModal = async () => {
setModalVisible(true);
if (unreadCount > 0) {
try {
await markAdminNotificationsRead();
setUnreadCount(0);
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
} catch { /* ignore */ }
}
};
const handleLogout = async () => {
await logoutAdmin();
await logout();
};
const formatTime = (iso: string) => {
const diff = Math.floor((Date.now() - new Date(iso).getTime()) / 60000);
if (diff < 1) return "à l'instant";
if (diff < 60) return `il y a ${diff} min`;
const h = Math.floor(diff / 60);
if (h < 24) return `il y a ${h}h`;
return `il y a ${Math.floor(h / 24)}j`;
};
return (
<>
<Tab.Navigator
screenOptions={{
headerStyle: { backgroundColor: colors.secondary },
headerTintColor: colors.textWhite,
headerRight: () => (
<View
style={{
flexDirection: "row",
alignItems: "center",
marginRight: spacing.l,
}}
>
<TouchableOpacity
onPress={openModal}
style={{ marginRight: spacing.m }}
>
<View>
<Ionicons
name="notifications-outline"
size={22}
color={colors.textSecondary}
/>
{unreadCount > 0 && (
<View
style={[
styles.badge,
{ backgroundColor: colors.danger },
]}
>
<Text style={styles.badgeText}>
{unreadCount > 9 ? "9+" : unreadCount}
</Text>
</View>
)}
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={toggleTheme}
style={{ marginRight: spacing.m }}
>
<Ionicons
name={isDark ? "sunny-outline" : "moon-outline"}
size={22}
color={colors.textSecondary}
/>
</TouchableOpacity>
<TouchableOpacity
onPress={() => navigation.navigate("Settings")}
style={{ marginRight: spacing.m }}
>
<Ionicons
name="settings-outline"
size={22}
color={colors.textSecondary}
/>
</TouchableOpacity>
<TouchableOpacity onPress={handleLogout}>
<Ionicons
name="log-out-outline"
size={24}
color={colors.textSecondary}
/>
</TouchableOpacity>
</View>
),
tabBarStyle: {
backgroundColor: colors.secondary,
borderTopColor: colors.border,
borderTopWidth: 1,
},
tabBarActiveTintColor: colors.accent,
tabBarInactiveTintColor: colors.textMuted,
tabBarLabelStyle: { fontSize: fontSize.xs },
}}
>
<Tab.Screen
name="Dashboard"
component={DashboardScreen}
options={{
title: "Dashboard",
tabBarIcon: ({ color, size }) => (
<Ionicons name="grid-outline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Stats"
component={StatsScreen}
options={{
title: "Stats",
tabBarIcon: ({ color, size }) => (
<Ionicons name="bar-chart-outline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Orders"
component={OrdersScreen}
options={{
title: "Commandes",
tabBarIcon: ({ color, size }) => (
<Ionicons name="receipt-outline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Users"
component={UsersScreen}
options={{
title: "Clients",
tabBarIcon: ({ color, size }) => (
<Ionicons name="people-outline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Products"
component={ProductsScreen}
options={{
title: "Produits",
tabBarIcon: ({ color, size }) => (
<Ionicons name="cube-outline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Categories"
component={CategoriesScreen}
options={{
title: "Catégories",
tabBarIcon: ({ color, size }) => (
<Ionicons name="pricetag-outline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Delivery"
component={DeliveryScreen}
options={{
title: "Livreurs",
tabBarIcon: ({ color, size }) => (
<Ionicons name="bicycle-outline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Alerts"
component={AlertsScreen}
options={{
title: "Alertes",
tabBarIcon: ({ color, size }) => (
<Ionicons name="alert-circle-outline" size={size} color={color} />
),
}}
/>
<Tab.Screen
name="Addresses"
component={AddressScreen}
options={{
title: "Adresses",
tabBarIcon: ({ color, size }) => (
<Ionicons name="map-outline" size={size} color={color} />
),
}}
/>
</Tab.Navigator>
<Modal
visible={modalVisible}
transparent
animationType="slide"
onRequestClose={() => setModalVisible(false)}
>
<View style={styles.modalOverlay}>
<View
style={[
styles.modalContainer,
{ backgroundColor: colors.bgSecondary },
]}
>
<View style={styles.modalHeader}>
<Text
style={[styles.modalTitle, { color: colors.textWhite }]}
>
Notifications
</Text>
<TouchableOpacity onPress={() => setModalVisible(false)}>
<Ionicons
name="close"
size={24}
color={colors.textSecondary}
/>
</TouchableOpacity>
</View>
<ScrollView style={styles.notifList}>
{notifications.length === 0 ? (
<Text
style={[
styles.emptyText,
{ color: colors.textMuted },
]}
>
Aucune notification
</Text>
) : (
notifications.map((n, i) => (
<View
key={i}
style={[
styles.notifItem,
{
borderLeftColor: n.read
? colors.border
: colors.accent,
backgroundColor: n.read
? "transparent"
: colors.bgPrimary ?? colors.bgSecondary,
},
]}
>
<Text
style={[
styles.notifMessage,
{ color: colors.textWhite },
]}
>
{n.message}
</Text>
<Text
style={[
styles.notifTime,
{ color: colors.textMuted },
]}
>
{formatTime(n.created_at)}
</Text>
</View>
))
)}
</ScrollView>
</View>
</View>
</Modal>
</>
);
}
export default function AdminNavigator() {
const { colors } = useTheme();
return (
<Stack.Navigator
screenOptions={{
headerStyle: { backgroundColor: colors.secondary },
headerTintColor: colors.textWhite,
}}
>
<Stack.Screen
name="AdminTabs"
component={AdminTabs}
options={{ headerShown: false }}
/>
<Stack.Screen
name="OrderDetail"
component={OrderDetailScreen}
options={{ title: "Détail commande" }}
/>
<Stack.Screen
name="Settings"
component={SettingsScreen}
options={{ title: "Paramètres" }}
/>
</Stack.Navigator>
);
}
const styles = StyleSheet.create({
badge: {
position: "absolute",
top: -4,
right: -6,
minWidth: 16,
height: 16,
borderRadius: 8,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: 2,
},
badgeText: {
color: "#fff",
fontSize: 9,
fontWeight: "bold",
},
modalOverlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.5)",
justifyContent: "flex-end",
},
modalContainer: {
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
maxHeight: "70%",
paddingBottom: 32,
},
modalHeader: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
padding: 16,
borderBottomWidth: 1,
borderBottomColor: "rgba(255,255,255,0.1)",
},
modalTitle: {
fontSize: 18,
fontWeight: "bold",
},
notifList: {
padding: 12,
},
notifItem: {
borderLeftWidth: 3,
paddingLeft: 12,
paddingVertical: 10,
marginBottom: 8,
borderRadius: 4,
paddingRight: 8,
},
notifMessage: {
fontSize: 14,
},
notifTime: {
fontSize: 12,
marginTop: 4,
},
emptyText: {
textAlign: "center",
marginTop: 32,
fontSize: 14,
},
});