chore: features category

This commit is contained in:
2026-03-07 15:10:18 +01:00
parent f66efee59c
commit dd31473ca1
13 changed files with 433 additions and 62 deletions
+3 -3
View File
@@ -15,7 +15,7 @@
"simulator": true "simulator": true
}, },
"env": { "env": {
"API_URL": "https://uber-stup.club" "API_URL": "http://5.181.0.112"
} }
}, },
"preview": { "preview": {
@@ -24,7 +24,7 @@
"buildType": "apk" "buildType": "apk"
}, },
"env": { "env": {
"API_URL": "https://uber-stup.club" "API_URL": "http://5.181.0.112"
} }
}, },
"production": { "production": {
@@ -34,7 +34,7 @@
"buildType": "apk" "buildType": "apk"
}, },
"env": { "env": {
"API_URL": "https://uber-stup.club" "API_URL": "http://5.181.0.112"
} }
} }
} }
+72 -2
View File
@@ -9,8 +9,8 @@ import type {
Alert, Alert,
} from "./types"; } from "./types";
const V2 = "https://uber-stup.club/api/v2"; const V2 = "http://5.181.0.112/api/v2";
const CABINE_URL = "https://uber-stup.club/api/v1/cabine"; const CABINE_URL = "http://5.181.0.112/api/v1/cabine";
// ============================================ // ============================================
// AUTH // AUTH
@@ -580,3 +580,73 @@ export const getCommandCountByStatus = async (
return 0; return 0;
} }
}; };
// ============================================
// CATÉGORIES
// ============================================
const V1_PUBLIC = "https://uber-stup.club/api/v1";
export interface Category {
id: number;
name: string;
created_at: string;
}
export const getCategories = async (): Promise<Category[]> => {
try {
const { data } = await apiClient.get(`${V1_PUBLIC}/categories`);
return data.categories || [];
} catch {
return [];
}
};
export const createCategoryAdmin = async (
name: string,
): Promise<{ success: boolean; category?: Category; error?: string }> => {
try {
const { data } = await apiClient.post(
`${V2}/admin/protected/categories`,
{ name },
);
return { success: true, category: data.category };
} catch (error: any) {
return {
success: false,
error: error.response?.data?.error || "Erreur",
};
}
};
export const updateCategoryAdmin = async (
id: number,
name: string,
): Promise<{ success: boolean; category?: Category; error?: string }> => {
try {
const { data } = await apiClient.put(
`${V2}/admin/protected/categories/${id}`,
{ name },
);
return { success: true, category: data.category };
} catch (error: any) {
return {
success: false,
error: error.response?.data?.error || "Erreur",
};
}
};
export const deleteCategoryAdmin = async (
id: number,
): Promise<{ success: boolean; error?: string }> => {
try {
await apiClient.delete(`${V2}/admin/protected/categories/${id}`);
return { success: true };
} catch (error: any) {
return {
success: false,
error: error.response?.data?.error || "Erreur",
};
}
};
+2 -2
View File
@@ -6,8 +6,8 @@ import type {
Alert, Alert,
} from "./types"; } from "./types";
const API = "https://uber-stup.club/api/v1/cabine"; const API = "http://5.181.0.112/api/v1/cabine";
const V2 = "https://uber-stup.club/api/v2"; const V2 = "http://5.181.0.112/api/v2";
// ============================================ // ============================================
// ITEMS // ITEMS
+1 -1
View File
@@ -7,7 +7,7 @@ import type {
Alert, Alert,
} from "./types"; } from "./types";
const API = "https://uber-stup.club/api/v1/livreur"; const API = "http://5.181.0.112/api/v1/livreur";
// ============================================ // ============================================
// STATUT // STATUT
+1 -1
View File
@@ -2,7 +2,7 @@ import axios from "axios";
import { getToken, getAdminToken } from "../auth/tokenStorage"; import { getToken, getAdminToken } from "../auth/tokenStorage";
// Change this to your server IP/domain // Change this to your server IP/domain
export const API_BASE_URL = "https://uber-stup.club"; export const API_BASE_URL = "http://5.181.0.112";
const apiClient = axios.create({ const apiClient = axios.create({
baseURL: API_BASE_URL, baseURL: API_BASE_URL,
@@ -14,6 +14,7 @@ import OrdersScreen from "../screens/admin/OrdersScreen";
import OrderDetailScreen from "../screens/admin/OrderDetailScreen"; import OrderDetailScreen from "../screens/admin/OrderDetailScreen";
import UsersScreen from "../screens/admin/UsersScreen"; import UsersScreen from "../screens/admin/UsersScreen";
import ProductsScreen from "../screens/admin/ProductsScreen"; import ProductsScreen from "../screens/admin/ProductsScreen";
import CategoriesScreen from "../screens/admin/CategoriesScreen";
import DeliveryScreen from "../screens/admin/DeliveryScreen"; import DeliveryScreen from "../screens/admin/DeliveryScreen";
import AlertsScreen from "../screens/admin/AlertsScreen"; import AlertsScreen from "../screens/admin/AlertsScreen";
import AddressScreen from "../screens/admin/AddressScreen"; import AddressScreen from "../screens/admin/AddressScreen";
@@ -128,6 +129,20 @@ function AdminTabs() {
), ),
}} }}
/> />
<Tab.Screen
name="Categories"
component={CategoriesScreen}
options={{
title: "Catégories",
tabBarIcon: ({ color, size }) => (
<Ionicons
name="pricetag-outline"
size={size}
color={color}
/>
),
}}
/>
<Tab.Screen <Tab.Screen
name="Delivery" name="Delivery"
component={DeliveryScreen} component={DeliveryScreen}
+1
View File
@@ -10,6 +10,7 @@ export type AdminTabParamList = {
Orders: undefined; Orders: undefined;
Users: undefined; Users: undefined;
Products: undefined; Products: undefined;
Categories: undefined;
Delivery: undefined; Delivery: undefined;
Alerts: undefined; Alerts: undefined;
Addresses: undefined; Addresses: undefined;
@@ -0,0 +1,280 @@
import React, { useState, useEffect, useCallback } from "react";
import {
View,
Text,
StyleSheet,
FlatList,
TextInput,
TouchableOpacity,
Modal,
KeyboardAvoidingView,
Platform,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize, borderRadius } from "../../theme";
import { useTheme } from "../../context/ThemeContext";
import {
getCategories,
createCategoryAdmin,
updateCategoryAdmin,
deleteCategoryAdmin,
} from "../../api/api_admin";
import type { Category } from "../../api/api_admin";
import LoadingSpinner from "../../components/ui/LoadingSpinner";
import Card from "../../components/ui/Card";
import AlertModal from "../../components/ui/AlertModal";
import { useAlert } from "../../hooks/useAlert";
export default function CategoriesScreen() {
const { colors } = useTheme();
const [categories, setCategories] = useState<Category[]>([]);
const [loading, setLoading] = useState(true);
const [modalVisible, setModalVisible] = useState(false);
const [editingCategory, setEditingCategory] = useState<Category | null>(null);
const [name, setName] = useState("");
const [saving, setSaving] = useState(false);
const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert();
const load = useCallback(async () => {
setLoading(true);
const data = await getCategories();
setCategories(data);
setLoading(false);
}, []);
useEffect(() => {
load();
}, [load]);
const openCreate = () => {
setEditingCategory(null);
setName("");
setModalVisible(true);
};
const openEdit = (cat: Category) => {
setEditingCategory(cat);
setName(cat.name);
setModalVisible(true);
};
const closeModal = () => {
setModalVisible(false);
setName("");
setEditingCategory(null);
};
const handleSave = async () => {
const trimmed = name.trim();
if (!trimmed) {
showError("Erreur", "Le nom est requis");
return;
}
setSaving(true);
if (editingCategory) {
const res = await updateCategoryAdmin(editingCategory.id, trimmed);
if (res.success) {
showSuccess("Succès", "Catégorie modifiée");
closeModal();
load();
} else {
showError("Erreur", res.error || "Erreur");
}
} else {
const res = await createCategoryAdmin(trimmed);
if (res.success) {
showSuccess("Succès", "Catégorie créée");
closeModal();
load();
} else {
showError("Erreur", res.error || "Erreur");
}
}
setSaving(false);
};
const handleDelete = (cat: Category) => {
showConfirm(
"Supprimer",
`Supprimer la catégorie "${cat.name}" ?`,
async () => {
const res = await deleteCategoryAdmin(cat.id);
if (res.success) {
showSuccess("Succès", "Catégorie supprimée");
load();
} else {
showError("Erreur", res.error || "Erreur");
}
},
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.background },
header: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
padding: spacing.m,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
title: { fontSize: fontSize.xl, fontWeight: "700", color: colors.text },
addBtn: {
flexDirection: "row",
alignItems: "center",
gap: 6,
backgroundColor: colors.accent,
paddingHorizontal: spacing.m,
paddingVertical: spacing.s,
borderRadius: borderRadius.m,
},
addBtnText: { color: "#fff", fontWeight: "600", fontSize: fontSize.s },
list: { padding: spacing.m, gap: spacing.s },
row: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
},
catName: { fontSize: fontSize.m, color: colors.text, fontWeight: "600", textTransform: "capitalize" },
catDate: { fontSize: fontSize.xs, color: colors.textSecondary, marginTop: 2 },
actions: { flexDirection: "row", gap: spacing.s },
actionBtn: { padding: 8 },
overlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.5)",
justifyContent: "center",
padding: spacing.l,
},
modal: {
backgroundColor: colors.surface,
borderRadius: borderRadius.l,
padding: spacing.l,
gap: spacing.m,
},
modalTitle: { fontSize: fontSize.l, fontWeight: "700", color: colors.text },
input: {
borderWidth: 1,
borderColor: colors.border,
borderRadius: borderRadius.m,
padding: spacing.m,
color: colors.text,
fontSize: fontSize.m,
backgroundColor: colors.background,
},
modalBtns: { flexDirection: "row", gap: spacing.s },
cancelBtn: {
flex: 1,
padding: spacing.m,
borderRadius: borderRadius.m,
borderWidth: 1,
borderColor: colors.border,
alignItems: "center",
},
cancelBtnText: { color: colors.text, fontWeight: "600" },
saveBtn: {
flex: 1,
padding: spacing.m,
borderRadius: borderRadius.m,
backgroundColor: colors.accent,
alignItems: "center",
},
saveBtnText: { color: "#fff", fontWeight: "600" },
empty: { textAlign: "center", color: colors.textSecondary, marginTop: spacing.xl },
});
if (loading) return <LoadingSpinner />;
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>Catégories</Text>
<TouchableOpacity style={styles.addBtn} onPress={openCreate}>
<Ionicons name="add" size={18} color="#fff" />
<Text style={styles.addBtnText}>Ajouter</Text>
</TouchableOpacity>
</View>
<FlatList
data={categories}
keyExtractor={(item) => String(item.id)}
contentContainerStyle={styles.list}
ListEmptyComponent={
<Text style={styles.empty}>Aucune catégorie. Créez-en une !</Text>
}
renderItem={({ item }) => (
<Card>
<View style={styles.row}>
<View>
<Text style={styles.catName}>{item.name}</Text>
<Text style={styles.catDate}>
Créée le {new Date(item.created_at).toLocaleDateString("fr-FR")}
</Text>
</View>
<View style={styles.actions}>
<TouchableOpacity
style={styles.actionBtn}
onPress={() => openEdit(item)}
>
<Ionicons name="pencil-outline" size={20} color={colors.info} />
</TouchableOpacity>
<TouchableOpacity
style={styles.actionBtn}
onPress={() => handleDelete(item)}
>
<Ionicons name="trash-outline" size={20} color={colors.danger} />
</TouchableOpacity>
</View>
</View>
</Card>
)}
/>
<Modal visible={modalVisible} transparent animationType="fade">
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : undefined}
style={styles.overlay}
>
<View style={styles.modal}>
<Text style={styles.modalTitle}>
{editingCategory ? "Modifier la catégorie" : "Nouvelle catégorie"}
</Text>
<TextInput
style={styles.input}
placeholder="Nom de la catégorie"
placeholderTextColor={colors.textSecondary}
value={name}
onChangeText={setName}
autoCapitalize="none"
/>
<View style={styles.modalBtns}>
<TouchableOpacity style={styles.cancelBtn} onPress={closeModal}>
<Text style={styles.cancelBtnText}>Annuler</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.saveBtn}
onPress={handleSave}
disabled={saving}
>
<Text style={styles.saveBtnText}>
{saving ? "..." : "Enregistrer"}
</Text>
</TouchableOpacity>
</View>
</View>
</KeyboardAvoidingView>
</Modal>
<AlertModal
visible={alert.visible}
type={alert.type}
title={alert.title}
message={alert.message}
onConfirm={alert.onConfirm || hideAlert}
onCancel={alert.type === "confirm" ? hideAlert : undefined}
confirmText={alert.type === "confirm" ? "Confirmer" : "OK"}
cancelText="Annuler"
/>
</View>
);
}
@@ -25,7 +25,9 @@ import {
deleteProductAdmin, deleteProductAdmin,
uploadProductMediaAdmin, uploadProductMediaAdmin,
deleteProductMediaAdmin, deleteProductMediaAdmin,
getCategories,
} from "../../api/api_admin"; } from "../../api/api_admin";
import type { Category } from "../../api/api_admin";
import type { Product } from "../../api/types"; import type { Product } from "../../api/types";
import LoadingSpinner from "../../components/ui/LoadingSpinner"; import LoadingSpinner from "../../components/ui/LoadingSpinner";
import Card from "../../components/ui/Card"; import Card from "../../components/ui/Card";
@@ -54,11 +56,7 @@ interface PendingMedia {
mediaType: "image" | "video"; mediaType: "image" | "video";
} }
const CATEGORIES = [ // Les catégories sont chargées dynamiquement depuis l'API
{ value: "weed&hash", label: "Weed & Hash" },
{ value: "zipette&co", label: "Zipette & Co" },
{ value: "gros&semi", label: "Gros & Semi" },
];
const UNITS = [ const UNITS = [
{ value: "u", label: "u" }, { value: "u", label: "u" },
@@ -79,9 +77,9 @@ interface FormState {
prices: PriceRow[]; prices: PriceRow[];
} }
const emptyForm = (): FormState => ({ const emptyForm = (firstCategory = ""): FormState => ({
name: "", name: "",
category: "weed&hash", category: firstCategory,
description: "", description: "",
stock: "", stock: "",
unit: "u", unit: "u",
@@ -94,6 +92,7 @@ const emptyForm = (): FormState => ({
export default function ProductsScreen() { export default function ProductsScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const [products, setProducts] = useState<Product[]>([]); const [products, setProducts] = useState<Product[]>([]);
const [categories, setCategories] = useState<Category[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
@@ -117,8 +116,12 @@ export default function ProductsScreen() {
// -------------------------------------------------- // --------------------------------------------------
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
try { try {
const result = await getAllProductsAdmin(); const [result, cats] = await Promise.all([
getAllProductsAdmin(),
getCategories(),
]);
setProducts(result.data); setProducts(result.data);
setCategories(cats);
} catch { } catch {
/* ignore */ /* ignore */
} }
@@ -139,7 +142,7 @@ export default function ProductsScreen() {
// -------------------------------------------------- // --------------------------------------------------
const openCreate = () => { const openCreate = () => {
setEditingProduct(null); setEditingProduct(null);
setForm(emptyForm()); setForm(emptyForm(categories[0]?.name || ""));
setExistingMedia([]); setExistingMedia([]);
setMediaToDelete([]); setMediaToDelete([]);
setPendingMedia([]); setPendingMedia([]);
@@ -896,29 +899,29 @@ export default function ProductsScreen() {
{/* Catégorie */} {/* Catégorie */}
<Text style={styles.label}>Catégorie *</Text> <Text style={styles.label}>Catégorie *</Text>
<View style={styles.catRow}> <View style={styles.catRow}>
{CATEGORIES.map((c) => ( {categories.map((c) => (
<TouchableOpacity <TouchableOpacity
key={c.value} key={c.id}
style={[ style={[
styles.catBtn, styles.catBtn,
form.category === c.value && form.category === c.name &&
styles.catBtnActive, styles.catBtnActive,
]} ]}
onPress={() => onPress={() =>
setForm((f) => ({ setForm((f) => ({
...f, ...f,
category: c.value, category: c.name,
})) }))
} }
> >
<Text <Text
style={[ style={[
styles.catBtnText, styles.catBtnText,
form.category === c.value && form.category === c.name &&
styles.catBtnTextActive, styles.catBtnTextActive,
]} ]}
> >
{c.label} {c.name}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
))} ))}
+3 -3
View File
@@ -15,7 +15,7 @@
"simulator": true "simulator": true
}, },
"env": { "env": {
"API_URL": "https://uber-stup.club" "API_URL": "http://5.181.0.112"
}, },
"channel": "development" "channel": "development"
}, },
@@ -25,7 +25,7 @@
"buildType": "apk" "buildType": "apk"
}, },
"env": { "env": {
"API_URL": "https://uber-stup.club" "API_URL": "http://5.181.0.112"
}, },
"channel": "preview" "channel": "preview"
}, },
@@ -36,7 +36,7 @@
"buildType": "apk" "buildType": "apk"
}, },
"env": { "env": {
"API_URL": "https://uber-stup.club" "API_URL": "http://5.181.0.112"
}, },
"channel": "production" "channel": "production"
} }
+16 -1
View File
@@ -11,7 +11,7 @@ import type {
import { getToken } from "../auth/tokenStorage"; import { getToken } from "../auth/tokenStorage";
import { extractUsernameFromToken } from "../auth/jwtUtils"; import { extractUsernameFromToken } from "../auth/jwtUtils";
const V1 = "https://uber-stup.club/api/v1"; const V1 = "http://5.181.0.112/api/v1";
export const getJwtUsername = async (): Promise<string | null> => { export const getJwtUsername = async (): Promise<string | null> => {
const token = await getToken(); const token = await getToken();
@@ -113,6 +113,21 @@ export const logoutUser = async (): Promise<void> => {
// PRODUCTS // PRODUCTS
// ============================================ // ============================================
export interface Category {
id: number;
name: string;
created_at: string;
}
export const getCategories = async (): Promise<Category[]> => {
try {
const { data } = await apiClient.get(`${V1}/categories`);
return data.categories || [];
} catch {
return [];
}
};
export const getAllProducts = async () => { export const getAllProducts = async () => {
try { try {
const { data } = await apiClient.get(`${V1}/products`); const { data } = await apiClient.get(`${V1}/products`);
+1 -1
View File
@@ -2,7 +2,7 @@ import axios from "axios";
import { getToken, getAdminToken } from "../auth/tokenStorage"; import { getToken, getAdminToken } from "../auth/tokenStorage";
// Change this to your server IP/domain // Change this to your server IP/domain
export const API_BASE_URL = "https://uber-stup.club"; export const API_BASE_URL = "http://5.181.0.112";
const apiClient = axios.create({ const apiClient = axios.create({
baseURL: API_BASE_URL, baseURL: API_BASE_URL,
+20 -33
View File
@@ -18,7 +18,8 @@ import {
} from "react-native"; } from "react-native";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { getAllProducts, getProductsByCategory } from "../../api/api"; import { getAllProducts, getProductsByCategory, getCategories } from "../../api/api";
import type { Category } from "../../api/api";
import type { Product } from "../../api/api_types"; import type { Product } from "../../api/api_types";
import ProductCard from "../../components/ProductCard"; import ProductCard from "../../components/ProductCard";
import CategoryPill from "../../components/CategoryPill"; import CategoryPill from "../../components/CategoryPill";
@@ -33,25 +34,14 @@ const logoGrosSemi = require("../../../assets/logo-gros-semi.png");
const { width: SCREEN_WIDTH } = Dimensions.get("window"); const { width: SCREEN_WIDTH } = Dimensions.get("window");
const CARD_WIDTH = SCREEN_WIDTH - 48; const CARD_WIDTH = SCREEN_WIDTH - 48;
const CATEGORIES = [ // Les catégories sont chargées dynamiquement depuis l'API
{ label: "Tous", value: "tous" },
{ label: "Weed&Hash", value: "weed&hash" },
{ label: "Zipette&Co", value: "zipette&co" },
{ label: "Gros&Semi", value: "gros&semi" },
];
const CATEGORY_TITLES: Record<string, string> = {
tous: "Tous les produits",
"weed&hash": "Weed & Hash",
"zipette&co": "Zipette & Co",
"gros&semi": "Gros & Semi",
};
type Nav = NativeStackNavigationProp<ClientStackParamList>; type Nav = NativeStackNavigationProp<ClientStackParamList>;
export default function ProductsScreen() { export default function ProductsScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const navigation = useNavigation<Nav>(); const navigation = useNavigation<Nav>();
const [categories, setCategories] = useState<Category[]>([]);
const [products, setProducts] = useState<Product[]>([]); const [products, setProducts] = useState<Product[]>([]);
const [selectedCategory, setSelectedCategory] = useState("tous"); const [selectedCategory, setSelectedCategory] = useState("tous");
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -60,6 +50,10 @@ export default function ProductsScreen() {
const carouselRef = useRef<FlatList>(null); const carouselRef = useRef<FlatList>(null);
const scaleAnim = useRef(new Animated.Value(1)).current; const scaleAnim = useRef(new Animated.Value(1)).current;
useEffect(() => {
getCategories().then(setCategories);
}, []);
useEffect(() => { useEffect(() => {
const pulse = Animated.loop( const pulse = Animated.loop(
Animated.sequence([ Animated.sequence([
@@ -190,7 +184,7 @@ export default function ProductsScreen() {
return ( return (
<ImageBackground <ImageBackground
source={selectedCategory === "gros&semi" ? logoGrosSemi : undefined} source={undefined}
style={styles.container} style={styles.container}
imageStyle={styles.bgImage} imageStyle={styles.bgImage}
> >
@@ -200,19 +194,24 @@ export default function ProductsScreen() {
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.filters} contentContainerStyle={styles.filters}
> >
{CATEGORIES.map((cat) => ( <CategoryPill
label="Tous"
active={selectedCategory === "tous"}
onPress={() => setSelectedCategory("tous")}
/>
{categories.map((cat) => (
<CategoryPill <CategoryPill
key={cat.value} key={cat.id}
label={cat.label} label={cat.name}
active={selectedCategory === cat.value} active={selectedCategory === cat.name}
onPress={() => setSelectedCategory(cat.value)} onPress={() => setSelectedCategory(cat.name)}
/> />
))} ))}
</ScrollView> </ScrollView>
</View> </View>
<View style={styles.categoryHeader}> <View style={styles.categoryHeader}>
<Text style={styles.categoryTitle}> <Text style={styles.categoryTitle}>
{CATEGORY_TITLES[selectedCategory]} {selectedCategory === "tous" ? "Tous les produits" : selectedCategory}
</Text> </Text>
</View> </View>
{error ? ( {error ? (
@@ -258,18 +257,6 @@ export default function ProductsScreen() {
)} )}
/> />
)} )}
{selectedCategory === "gros&semi" && (
<View style={styles.comingSoonWrapper}>
<Animated.Text
style={[
styles.comingSoonText,
{ transform: [{ scale: scaleAnim }] },
]}
>
Prochainement
</Animated.Text>
</View>
)}
</ImageBackground> </ImageBackground>
); );
} }