chore: fix UI

This commit is contained in:
2026-04-26 19:07:26 +02:00
parent a120ad6391
commit d39c49cce6
10 changed files with 172 additions and 147 deletions
@@ -8,6 +8,7 @@ import {
TouchableOpacity, TouchableOpacity,
Linking, Linking,
Alert, Alert,
useWindowDimensions,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize, borderRadius } from "../../theme"; import { spacing, fontSize, borderRadius } from "../../theme";
@@ -35,6 +36,7 @@ interface StatCard {
export default function DashboardScreen() { export default function DashboardScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const { username } = useAuth(); const { username } = useAuth();
const { width: screenWidth } = useWindowDimensions();
const [stats, setStats] = useState<StatCard[]>([]); const [stats, setStats] = useState<StatCard[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
@@ -137,54 +139,54 @@ export default function DashboardScreen() {
container: { container: {
flex: 1, flex: 1,
backgroundColor: colors.bgPrimary, backgroundColor: colors.bgPrimary,
padding: spacing.l, padding: screenWidth < 380 ? spacing.m : spacing.l,
}, },
welcome: { welcome: {
fontSize: fontSize.xl, fontSize: screenWidth < 380 ? fontSize.lg : fontSize.xl,
fontWeight: "bold", fontWeight: "bold",
color: colors.textWhite, color: colors.textWhite,
marginBottom: spacing.xs, marginBottom: spacing.xs,
}, },
subtitle: { subtitle: {
fontSize: fontSize.md, fontSize: screenWidth < 380 ? fontSize.sm : fontSize.md,
color: colors.textSecondary, color: colors.textSecondary,
marginBottom: spacing.xl, marginBottom: spacing.l,
}, },
grid: { grid: {
flexDirection: "row", flexDirection: "row",
flexWrap: "wrap", flexWrap: "wrap",
gap: spacing.m, gap: screenWidth < 380 ? spacing.s : spacing.m,
}, },
card: { card: {
width: "47%", width: screenWidth < 360 ? "100%" : "47%",
backgroundColor: colors.bgCard, backgroundColor: colors.bgCard,
borderRadius: borderRadius.md, borderRadius: borderRadius.md,
padding: spacing.l, padding: screenWidth < 380 ? spacing.m : spacing.l,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
alignItems: "center", alignItems: "center",
}, },
iconCircle: { iconCircle: {
width: 48, width: screenWidth < 380 ? 40 : 48,
height: 48, height: screenWidth < 380 ? 40 : 48,
borderRadius: 24, borderRadius: screenWidth < 380 ? 20 : 24,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
marginBottom: spacing.s, marginBottom: spacing.s,
}, },
cardValue: { cardValue: {
fontSize: fontSize.xxl, fontSize: screenWidth < 380 ? fontSize.xl : fontSize.xxl,
fontWeight: "bold", fontWeight: "bold",
color: colors.textWhite, color: colors.textWhite,
}, },
cardLabel: { cardLabel: {
fontSize: fontSize.sm, fontSize: screenWidth < 380 ? fontSize.xs : fontSize.sm,
color: colors.textSecondary, color: colors.textSecondary,
marginTop: spacing.xs, marginTop: spacing.xs,
textAlign: "center", textAlign: "center",
}, },
}), }),
[colors], [colors, screenWidth],
); );
if (loading) return <LoadingSpinner message="Chargement..." />; if (loading) return <LoadingSpinner message="Chargement..." />;
@@ -14,6 +14,7 @@ import {
TouchableOpacity, TouchableOpacity,
Modal, Modal,
StatusBar, StatusBar,
useWindowDimensions,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize, borderRadius } from "../../theme"; import { spacing, fontSize, borderRadius } from "../../theme";
@@ -34,10 +35,10 @@ import TomTomMap, {
TomTomMarker, TomTomMarker,
} from "../../components/TomTomMap"; } from "../../components/TomTomMap";
const MAP_HEIGHT = 280;
export default function DeliveryScreen() { export default function DeliveryScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
const MAP_HEIGHT = screenHeight < 700 ? 200 : screenWidth < 380 ? 220 : 280;
const statusColors = getStatusColors(colors); const statusColors = getStatusColors(colors);
const [livreurs, setLivreurs] = useState<DeliveryPerson[]>([]); const [livreurs, setLivreurs] = useState<DeliveryPerson[]>([]);
const [stats, setStats] = useState({ const [stats, setStats] = useState({
@@ -185,8 +186,8 @@ export default function DeliveryScreen() {
summaryRow: { summaryRow: {
flexDirection: "row", flexDirection: "row",
padding: spacing.l, padding: screenWidth < 380 ? spacing.m : spacing.l,
gap: spacing.s, gap: screenWidth < 380 ? spacing.xs : spacing.s,
}, },
summaryCard: { summaryCard: {
flex: 1, flex: 1,
@@ -197,13 +198,14 @@ export default function DeliveryScreen() {
alignItems: "center", alignItems: "center",
}, },
summaryValue: { summaryValue: {
fontSize: fontSize.xl, fontSize: screenWidth < 380 ? fontSize.lg : fontSize.xl,
fontWeight: "bold", fontWeight: "bold",
color: colors.textWhite, color: colors.textWhite,
}, },
summaryLabel: { summaryLabel: {
fontSize: fontSize.xs, fontSize: screenWidth < 380 ? 10 : fontSize.xs,
color: colors.textMuted, color: colors.textMuted,
textAlign: "center",
}, },
mapContainer: { mapContainer: {
@@ -311,7 +313,8 @@ export default function DeliveryScreen() {
}, },
statsRow: { statsRow: {
flexDirection: "row", flexDirection: "row",
gap: spacing.l, flexWrap: "wrap",
gap: screenWidth < 380 ? spacing.s : spacing.l,
marginTop: spacing.s, marginTop: spacing.s,
}, },
stat: { stat: {
@@ -425,8 +428,9 @@ export default function DeliveryScreen() {
}, },
legendRow: { legendRow: {
flexDirection: "row", flexDirection: "row",
flexWrap: "wrap",
justifyContent: "center", justifyContent: "center",
gap: spacing.l, gap: screenWidth < 380 ? spacing.m : spacing.l,
}, },
legendItem: { legendItem: {
flexDirection: "row", flexDirection: "row",
@@ -436,7 +440,7 @@ export default function DeliveryScreen() {
legendDot: { width: 10, height: 10, borderRadius: 5 }, legendDot: { width: 10, height: 10, borderRadius: 5 },
legendText: { color: colors.white, fontSize: fontSize.sm }, legendText: { color: colors.white, fontSize: fontSize.sm },
}), }),
[colors], [colors, screenWidth, screenHeight, MAP_HEIGHT],
); );
// -------------------------------------------------- // --------------------------------------------------
@@ -8,6 +8,7 @@ import {
Modal, Modal,
StatusBar, StatusBar,
Alert, Alert,
useWindowDimensions,
} from "react-native"; } from "react-native";
import { import {
useRoute, useRoute,
@@ -41,10 +42,10 @@ import { useAlert } from "../../hooks/useAlert";
type Route = RouteProp<AdminStackParamList, "OrderDetail">; type Route = RouteProp<AdminStackParamList, "OrderDetail">;
const MAP_HEIGHT = 240;
export default function OrderDetailScreen() { export default function OrderDetailScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const { height: screenHeight } = useWindowDimensions();
const MAP_HEIGHT = screenHeight < 700 ? 180 : 240;
const route = useRoute<Route>(); const route = useRoute<Route>();
const navigation = useNavigation(); const navigation = useNavigation();
const { orderId } = route.params; const { orderId } = route.params;
@@ -472,7 +473,7 @@ export default function OrderDetailScreen() {
fontWeight: "700", fontWeight: "700",
}, },
}), }),
[colors], [colors, MAP_HEIGHT],
); );
if (loading) return <LoadingSpinner message="Chargement..." />; if (loading) return <LoadingSpinner message="Chargement..." />;
@@ -9,6 +9,7 @@ import {
ActivityIndicator, ActivityIndicator,
TextInput, TextInput,
Keyboard, Keyboard,
useWindowDimensions,
} from "react-native"; } from "react-native";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
@@ -101,7 +102,7 @@ function DeliveryScheduleSection({
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, marginTop: spacing.xs }}> <View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, marginTop: spacing.xs }}>
<Text style={s.thresholdSep}>Ouverture</Text> <Text style={s.thresholdSep}>Ouverture</Text>
<TextInput <TextInput
style={[s.thresholdInput, { width: 72 }]} style={[s.thresholdInput, { width: screenWidth < 380 ? 58 : 72 }]}
value={day.open_time} value={day.open_time}
onChangeText={(v) => updateDay(key, { open_time: v })} onChangeText={(v) => updateDay(key, { open_time: v })}
placeholder="09:00" placeholder="09:00"
@@ -112,7 +113,7 @@ function DeliveryScheduleSection({
<Text style={s.thresholdSep}>→</Text> <Text style={s.thresholdSep}>→</Text>
<Text style={s.thresholdSep}>Fermeture</Text> <Text style={s.thresholdSep}>Fermeture</Text>
<TextInput <TextInput
style={[s.thresholdInput, { width: 72 }]} style={[s.thresholdInput, { width: screenWidth < 380 ? 58 : 72 }]}
value={day.close_time} value={day.close_time}
onChangeText={(v) => updateDay(key, { close_time: v })} onChangeText={(v) => updateDay(key, { close_time: v })}
placeholder="20:00" placeholder="20:00"
@@ -240,7 +241,7 @@ function PostalZonesSection({
<View style={{ paddingHorizontal: spacing.l, paddingBottom: spacing.m, gap: spacing.m }}> <View style={{ paddingHorizontal: spacing.l, paddingBottom: spacing.m, gap: spacing.m }}>
{/* Nom */} {/* Nom */}
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s }}> <View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s }}>
<Text style={[s.thresholdSep, { width: 48 }]}>Nom</Text> <Text style={[s.thresholdSep, { width: screenWidth < 380 ? 38 : 48 }]}>Nom</Text>
<TextInput <TextInput
style={[s.thresholdInput, { flex: 1, width: undefined, textAlign: "left", paddingHorizontal: spacing.m }]} style={[s.thresholdInput, { flex: 1, width: undefined, textAlign: "left", paddingHorizontal: spacing.m }]}
value={zone.name} value={zone.name}
@@ -251,9 +252,9 @@ function PostalZonesSection({
</View> </View>
{/* Minimum */} {/* Minimum */}
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s }}> <View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s }}>
<Text style={[s.thresholdSep, { width: 48 }]}>Min €</Text> <Text style={[s.thresholdSep, { width: screenWidth < 380 ? 38 : 48 }]}>Min €</Text>
<TextInput <TextInput
style={[s.thresholdInput, { width: 80 }]} style={[s.thresholdInput, { width: inputMd }]}
value={String(zone.min_amount)} value={String(zone.min_amount)}
onChangeText={(v) => { onChangeText={(v) => {
const n = parseFloat(v); const n = parseFloat(v);
@@ -332,7 +333,7 @@ function PostalZonesSection({
<View style={{ gap: 4 }}> <View style={{ gap: 4 }}>
<Text style={s.thresholdSep}>Montant minimum de commande (€) *</Text> <Text style={s.thresholdSep}>Montant minimum de commande (€) *</Text>
<TextInput <TextInput
style={[s.thresholdInput, { width: 100, textAlign: "left", paddingHorizontal: spacing.m }]} style={[s.thresholdInput, { width: inputLg, textAlign: "left", paddingHorizontal: spacing.m }]}
value={form.min_amount} value={form.min_amount}
onChangeText={(v) => { setForm((f) => ({ ...f, min_amount: v })); setFormError(""); }} onChangeText={(v) => { setForm((f) => ({ ...f, min_amount: v })); setFormError(""); }}
keyboardType="decimal-pad" keyboardType="decimal-pad"
@@ -564,9 +565,9 @@ function TiersSection({
{/* En-tête colonnes */} {/* En-tête colonnes */}
<View style={{ flexDirection: "row", paddingHorizontal: spacing.l, paddingBottom: spacing.xs, gap: spacing.xs }}> <View style={{ flexDirection: "row", paddingHorizontal: spacing.l, paddingBottom: spacing.xs, gap: spacing.xs }}>
<Text style={[s.thresholdSep, { width: 56, textAlign: "center" }]}>Min €</Text> <Text style={[s.thresholdSep, { width: inputSm, textAlign: "center" }]}>Min €</Text>
<Text style={[s.thresholdSep, { width: 56, textAlign: "center" }]}>Max €</Text> <Text style={[s.thresholdSep, { width: inputSm, textAlign: "center" }]}>Max €</Text>
<Text style={[s.thresholdSep, { width: 44, textAlign: "center" }]}>Pts</Text> <Text style={[s.thresholdSep, { width: screenWidth < 380 ? 36 : 44, textAlign: "center" }]}>Pts</Text>
</View> </View>
{tiers.map((tier, i) => ( {tiers.map((tier, i) => (
@@ -583,14 +584,14 @@ function TiersSection({
}} }}
> >
<TextInput <TextInput
style={[s.thresholdInput, { width: 56 }]} style={[s.thresholdInput, { width: inputSm }]}
keyboardType="decimal-pad" keyboardType="decimal-pad"
value={String(tier.min)} value={String(tier.min)}
onChangeText={(v) => updateTier(i, "min", v)} onChangeText={(v) => updateTier(i, "min", v)}
/> />
<Text style={s.thresholdSep}>→</Text> <Text style={s.thresholdSep}>→</Text>
<TextInput <TextInput
style={[s.thresholdInput, { width: 56 }]} style={[s.thresholdInput, { width: inputSm }]}
keyboardType="decimal-pad" keyboardType="decimal-pad"
value={String(tier.max)} value={String(tier.max)}
onChangeText={(v) => updateTier(i, "max", v)} onChangeText={(v) => updateTier(i, "max", v)}
@@ -599,7 +600,7 @@ function TiersSection({
/> />
<Text style={s.thresholdSep}>=</Text> <Text style={s.thresholdSep}>=</Text>
<TextInput <TextInput
style={[s.thresholdInput, { width: 44 }]} style={[s.thresholdInput, { width: screenWidth < 380 ? 36 : 44 }]}
keyboardType="number-pad" keyboardType="number-pad"
value={String(tier.points)} value={String(tier.points)}
onChangeText={(v) => updateTier(i, "points", v)} onChangeText={(v) => updateTier(i, "points", v)}
@@ -640,6 +641,10 @@ export default function SettingsScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const { alert, showError, showSuccess, hideAlert } = useAlert(); const { alert, showError, showSuccess, hideAlert } = useAlert();
const navigation = useNavigation(); const navigation = useNavigation();
const { width: screenWidth } = useWindowDimensions();
const inputSm = screenWidth < 380 ? 46 : 56;
const inputMd = screenWidth < 380 ? 64 : 80;
const inputLg = screenWidth < 380 ? 80 : 100;
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
@@ -811,7 +816,7 @@ export default function SettingsScreen() {
const s = StyleSheet.create({ const s = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.bgPrimary }, container: { flex: 1, backgroundColor: colors.bgPrimary },
content: { padding: spacing.l, paddingBottom: spacing.xl * 2 }, content: { padding: screenWidth < 380 ? spacing.m : spacing.l, paddingBottom: spacing.xl * 2 },
section: { section: {
backgroundColor: colors.bgSecondary, backgroundColor: colors.bgSecondary,
borderRadius: borderRadius.lg, borderRadius: borderRadius.lg,
@@ -988,7 +993,7 @@ export default function SettingsScreen() {
</View> </View>
<View style={{ flexDirection: "row", width: "100%", marginBottom: 2 }}> <View style={{ flexDirection: "row", width: "100%", marginBottom: 2 }}>
<Text style={[s.rowDesc, { flex: 1 }]}>À partir de (annul.)</Text> <Text style={[s.rowDesc, { flex: 1 }]}>À partir de (annul.)</Text>
<Text style={[s.rowDesc, { width: 80, textAlign: "center" }]}>Montant</Text> <Text style={[s.rowDesc, { width: inputMd, textAlign: "center" }]}>Montant</Text>
<View style={{ width: 28 }} /> <View style={{ width: 28 }} />
</View> </View>
{settings.penalty_tiers.map((tier, i) => ( {settings.penalty_tiers.map((tier, i) => (
@@ -1008,7 +1013,7 @@ export default function SettingsScreen() {
placeholderTextColor={colors.textMuted} placeholderTextColor={colors.textMuted}
/> />
<TextInput <TextInput
style={[s.thresholdInput, { width: 80, textAlign: "center" }]} style={[s.thresholdInput, { width: inputMd, textAlign: "center" }]}
value={String(tier.amount)} value={String(tier.amount)}
onChangeText={(v) => { onChangeText={(v) => {
const n = parseInt(v, 10); const n = parseInt(v, 10);
@@ -9,6 +9,7 @@ import {
TouchableOpacity, TouchableOpacity,
TextInput as RNTextInput, TextInput as RNTextInput,
ActivityIndicator, ActivityIndicator,
useWindowDimensions,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize, borderRadius } from "../../theme"; import { spacing, fontSize, borderRadius } from "../../theme";
@@ -58,6 +59,7 @@ type RoleFilter = "all" | "client" | "livreur" | "cabine" | "admin";
// ================================================== // ==================================================
export default function UsersScreen() { export default function UsersScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const { width: screenWidth } = useWindowDimensions();
const ROLE_TABS: { const ROLE_TABS: {
key: RoleFilter; key: RoleFilter;
@@ -487,6 +489,8 @@ export default function UsersScreen() {
setAmendeReason(""); setAmendeReason("");
if (res.compensated) { 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}"`);
} }
await loadData(); await loadData();
}, },
@@ -501,6 +505,7 @@ export default function UsersScreen() {
async () => { async () => {
const res = await resetClientPenalties(sanctionModal.client!.username, false); const res = await resetClientPenalties(sanctionModal.client!.username, false);
if (!res.success) { showError("Erreur", res.error || "Echec"); return; } if (!res.success) { showError("Erreur", res.error || "Echec"); return; }
showSuccess("Validé", `Amende de "${sanctionModal.client!.username}" remise à zéro`);
await loadData(); await loadData();
}, },
"Remettre à zéro", "Remettre à zéro",
@@ -523,6 +528,7 @@ export default function UsersScreen() {
setPointsLoading((prev) => ({ ...prev, [poolKey]: false })); 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]: "" })); setPointsInputs((prev) => ({ ...prev, [poolKey]: "" }));
showSuccess("Validé", `${pts} points "${poolName}" ajoutés à "${sanctionModal.client!.username}"`);
await loadData(); await loadData();
}, },
); );
@@ -544,6 +550,7 @@ export default function UsersScreen() {
setSubtractLoading((prev) => ({ ...prev, [poolKey]: false })); 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]: "" })); setSubtractInputs((prev) => ({ ...prev, [poolKey]: "" }));
showSuccess("Validé", `${pts} points "${poolName}" retirés à "${sanctionModal.client!.username}"`);
await loadData(); await loadData();
}, },
); );
@@ -557,6 +564,7 @@ export default function UsersScreen() {
async () => { async () => {
const res = await resetClientPoints(sanctionModal.client!.username, poolIdx); const res = await resetClientPoints(sanctionModal.client!.username, poolIdx);
if (!res.success) { showError("Erreur", res.error || "Echec"); return; } if (!res.success) { showError("Erreur", res.error || "Echec"); return; }
showSuccess("Validé", `Points "${poolName}" de "${sanctionModal.client!.username}" remis à zéro`);
await loadData(); await loadData();
}, },
"Remettre à zéro", "Remettre à zéro",
@@ -675,36 +683,38 @@ export default function UsersScreen() {
// Filter grid // Filter grid
filterGrid: { filterGrid: {
flexDirection: "row", flexDirection: "row",
paddingHorizontal: spacing.m, paddingHorizontal: screenWidth < 380 ? spacing.s : spacing.m,
paddingVertical: spacing.m, paddingVertical: spacing.s,
gap: spacing.s, gap: screenWidth < 380 ? spacing.xs : spacing.s,
}, },
filterCard: { filterCard: {
flex: 1, flex: 1,
alignItems: "center", alignItems: "center",
paddingVertical: spacing.m, paddingVertical: spacing.s,
paddingHorizontal: 2,
borderRadius: borderRadius.md, borderRadius: borderRadius.md,
borderWidth: 1, borderWidth: 1,
borderColor: colors.border, borderColor: colors.border,
backgroundColor: colors.bgSecondary, backgroundColor: colors.bgSecondary,
}, },
filterIconCircle: { filterIconCircle: {
width: 36, width: screenWidth < 380 ? 28 : 36,
height: 36, height: screenWidth < 380 ? 28 : 36,
borderRadius: 18, borderRadius: screenWidth < 380 ? 14 : 18,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
marginBottom: 4, marginBottom: 2,
}, },
filterCount: { filterCount: {
color: colors.textWhite, color: colors.textWhite,
fontSize: fontSize.lg, fontSize: screenWidth < 380 ? fontSize.sm : fontSize.md,
fontWeight: "700", fontWeight: "700",
}, },
filterLabel: { filterLabel: {
color: colors.textMuted, color: colors.textMuted,
fontSize: fontSize.sm, fontSize: screenWidth < 380 ? 10 : fontSize.xs,
fontWeight: "600", fontWeight: "600",
textAlign: "center",
}, },
// Card // Card
@@ -754,17 +764,23 @@ export default function UsersScreen() {
}, },
statsRow: { statsRow: {
flexDirection: "row", flexDirection: "row",
marginTop: spacing.m, flexWrap: "wrap",
gap: spacing.l, marginTop: spacing.s,
gap: spacing.s,
marginLeft: 52, marginLeft: 52,
}, },
stat: { alignItems: "center" }, stat: {
alignItems: "center",
minWidth: 52,
paddingVertical: spacing.xs,
},
statValue: { statValue: {
fontSize: fontSize.md, fontSize: fontSize.sm,
fontWeight: "bold", fontWeight: "bold",
color: colors.textWhite, color: colors.textWhite,
textAlign: "center",
}, },
statLabel: { fontSize: fontSize.xs, color: colors.textMuted }, statLabel: { fontSize: fontSize.xs, color: colors.textMuted, textAlign: "center" },
// Non-client // Non-client
nonClientInfo: { nonClientInfo: {
@@ -846,7 +862,7 @@ export default function UsersScreen() {
fontWeight: "600", fontWeight: "600",
}, },
}), }),
[colors], [colors, screenWidth],
); );
// -------------------------------------------------- // --------------------------------------------------
@@ -991,10 +1007,10 @@ export default function UsersScreen() {
</View> </View>
<View style={styles.statsRow}> <View style={styles.statsRow}>
<View style={styles.stat}> <View style={styles.stat}>
<Text style={styles.statValue}> <Text style={styles.statValue} numberOfLines={1}>
{item.clientData.command} {item.clientData.command}
</Text> </Text>
<Text style={styles.statLabel}>Cmd</Text> <Text style={styles.statLabel} numberOfLines={1}>Cmd</Text>
</View> </View>
{pointsEnabled && poolNames.map((name, i) => { {pointsEnabled && poolNames.map((name, i) => {
const key = poolKeys[i] ?? ""; const key = poolKeys[i] ?? "";
@@ -1002,46 +1018,40 @@ export default function UsersScreen() {
const color = i === 0 ? colors.success : i === 1 ? colors.info : colors.warning; const color = i === 0 ? colors.success : i === 1 ? colors.info : colors.warning;
return ( return (
<View key={i} style={styles.stat}> <View key={i} style={styles.stat}>
<Text style={[styles.statValue, { color }]}>{value}</Text> <Text style={[styles.statValue, { color }]} numberOfLines={1}>{value}</Text>
<Text style={styles.statLabel}>{name}</Text> <Text style={styles.statLabel} numberOfLines={1}>{name}</Text>
</View> </View>
); );
})} })}
{amendeEnabled && ( {amendeEnabled && (
<View style={styles.stat}> <View style={styles.stat}>
<Text <Text
style={[ style={[styles.statValue, { color: colors.warning }]}
styles.statValue, numberOfLines={1}
{ color: colors.warning },
]}
> >
{item.clientData.amende} {item.clientData.amende}
</Text> </Text>
<Text style={styles.statLabel}>Amende</Text> <Text style={styles.statLabel} numberOfLines={1}>Amende</Text>
</View> </View>
)} )}
<View style={styles.stat}> <View style={styles.stat}>
<Text <Text
style={[ style={[styles.statValue, { color: colors.danger }]}
styles.statValue, numberOfLines={1}
{ color: colors.danger },
]}
> >
{item.clientData.cancellations_count} {item.clientData.cancellations_count}
</Text> </Text>
<Text style={styles.statLabel}>Annul.</Text> <Text style={styles.statLabel} numberOfLines={1}>Annul.</Text>
</View> </View>
{referralEnabled && (item.clientData.referral_balance ?? 0) > 0 && ( {referralEnabled && (item.clientData.referral_balance ?? 0) > 0 && (
<View style={styles.stat}> <View style={styles.stat}>
<Text <Text
style={[ style={[styles.statValue, { color: colors.success }]}
styles.statValue, numberOfLines={1}
{ color: colors.success },
]}
> >
{(item.clientData.referral_balance ?? 0).toFixed(0)} {(item.clientData.referral_balance ?? 0).toFixed(0)}
</Text> </Text>
<Text style={styles.statLabel}>Parrain</Text> <Text style={styles.statLabel} numberOfLines={1}>Parrain</Text>
</View> </View>
)} )}
</View> </View>
@@ -8,7 +8,7 @@ import {
TouchableOpacity, TouchableOpacity,
TextInput as RNTextInput, TextInput as RNTextInput,
Animated, Animated,
Dimensions, useWindowDimensions,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
@@ -19,10 +19,9 @@ import { loginAdmin } from "../../api/api_admin";
import AlertModal from "../../components/ui/AlertModal"; import AlertModal from "../../components/ui/AlertModal";
import { useAlert } from "../../hooks/useAlert"; import { useAlert } from "../../hooks/useAlert";
const { width } = Dimensions.get("window");
export default function AdminLoginScreen() { export default function AdminLoginScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const { width: screenWidth } = useWindowDimensions();
const ACCENT = colors.accent; const ACCENT = colors.accent;
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
@@ -41,36 +40,36 @@ export default function AdminLoginScreen() {
accentBar: { height: 4, width: "100%" }, accentBar: { height: 4, width: "100%" },
heroSection: { heroSection: {
alignItems: "center", alignItems: "center",
paddingTop: 60, paddingTop: screenWidth < 380 ? 36 : 60,
paddingBottom: spacing.xl, paddingBottom: spacing.l,
}, },
heroBg: { heroBg: {
width: 120, width: screenWidth < 380 ? 96 : 120,
height: 120, height: screenWidth < 380 ? 96 : 120,
borderRadius: 60, borderRadius: screenWidth < 380 ? 48 : 60,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
}, },
heroInner: { heroInner: {
width: 88, width: screenWidth < 380 ? 68 : 88,
height: 88, height: screenWidth < 380 ? 68 : 88,
borderRadius: 44, borderRadius: screenWidth < 380 ? 34 : 44,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
}, },
heroTitle: { heroTitle: {
fontSize: fontSize.xxl, fontSize: screenWidth < 380 ? fontSize.xl : fontSize.xxl,
fontWeight: "800", fontWeight: "800",
color: colors.textWhite, color: colors.textWhite,
marginTop: spacing.l, marginTop: spacing.m,
letterSpacing: 0.5, letterSpacing: 0.5,
}, },
heroSubtitle: { heroSubtitle: {
fontSize: fontSize.md, fontSize: screenWidth < 380 ? fontSize.sm : fontSize.md,
color: colors.textMuted, color: colors.textMuted,
marginTop: spacing.xs, marginTop: spacing.xs,
}, },
formSection: { paddingHorizontal: spacing.xl, flex: 1 }, formSection: { paddingHorizontal: screenWidth < 380 ? spacing.l : spacing.xl, flex: 1 },
inputContainer: { inputContainer: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
@@ -121,7 +120,7 @@ export default function AdminLoginScreen() {
marginLeft: spacing.xs, marginLeft: spacing.xs,
}, },
}), }),
[colors], [colors, screenWidth],
); );
const handleLogin = async () => { const handleLogin = async () => {
@@ -8,7 +8,7 @@ import {
TouchableOpacity, TouchableOpacity,
TextInput as RNTextInput, TextInput as RNTextInput,
Animated, Animated,
Dimensions, useWindowDimensions,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
@@ -19,10 +19,9 @@ import { loginAdmin } from "../../api/api_admin";
import AlertModal from "../../components/ui/AlertModal"; import AlertModal from "../../components/ui/AlertModal";
import { useAlert } from "../../hooks/useAlert"; import { useAlert } from "../../hooks/useAlert";
const { width } = Dimensions.get("window");
export default function CabineLoginScreen() { export default function CabineLoginScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const { width: screenWidth } = useWindowDimensions();
const ACCENT = colors.info; const ACCENT = colors.info;
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
@@ -41,36 +40,36 @@ export default function CabineLoginScreen() {
accentBar: { height: 4, width: "100%" }, accentBar: { height: 4, width: "100%" },
heroSection: { heroSection: {
alignItems: "center", alignItems: "center",
paddingTop: 60, paddingTop: screenWidth < 380 ? 36 : 60,
paddingBottom: spacing.xl, paddingBottom: spacing.l,
}, },
heroBg: { heroBg: {
width: 120, width: screenWidth < 380 ? 96 : 120,
height: 120, height: screenWidth < 380 ? 96 : 120,
borderRadius: 60, borderRadius: screenWidth < 380 ? 48 : 60,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
}, },
heroInner: { heroInner: {
width: 88, width: screenWidth < 380 ? 68 : 88,
height: 88, height: screenWidth < 380 ? 68 : 88,
borderRadius: 44, borderRadius: screenWidth < 380 ? 34 : 44,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
}, },
heroTitle: { heroTitle: {
fontSize: fontSize.xxl, fontSize: screenWidth < 380 ? fontSize.xl : fontSize.xxl,
fontWeight: "800", fontWeight: "800",
color: colors.textWhite, color: colors.textWhite,
marginTop: spacing.l, marginTop: spacing.m,
letterSpacing: 0.5, letterSpacing: 0.5,
}, },
heroSubtitle: { heroSubtitle: {
fontSize: fontSize.md, fontSize: screenWidth < 380 ? fontSize.sm : fontSize.md,
color: colors.textMuted, color: colors.textMuted,
marginTop: spacing.xs, marginTop: spacing.xs,
}, },
formSection: { paddingHorizontal: spacing.xl, flex: 1 }, formSection: { paddingHorizontal: screenWidth < 380 ? spacing.l : spacing.xl, flex: 1 },
inputContainer: { inputContainer: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
@@ -121,7 +120,7 @@ export default function CabineLoginScreen() {
marginLeft: spacing.xs, marginLeft: spacing.xs,
}, },
}), }),
[colors], [colors, screenWidth],
); );
const handleLogin = async () => { const handleLogin = async () => {
@@ -8,7 +8,7 @@ import {
TouchableOpacity, TouchableOpacity,
TextInput as RNTextInput, TextInput as RNTextInput,
Animated, Animated,
Dimensions, useWindowDimensions,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
@@ -19,10 +19,9 @@ import { loginAdmin } from "../../api/api_admin";
import AlertModal from "../../components/ui/AlertModal"; import AlertModal from "../../components/ui/AlertModal";
import { useAlert } from "../../hooks/useAlert"; import { useAlert } from "../../hooks/useAlert";
const { width } = Dimensions.get("window");
export default function DeliveryLoginScreen() { export default function DeliveryLoginScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const { width: screenWidth } = useWindowDimensions();
const ACCENT = colors.success; const ACCENT = colors.success;
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
@@ -41,36 +40,36 @@ export default function DeliveryLoginScreen() {
accentBar: { height: 4, width: "100%" }, accentBar: { height: 4, width: "100%" },
heroSection: { heroSection: {
alignItems: "center", alignItems: "center",
paddingTop: 60, paddingTop: screenWidth < 380 ? 36 : 60,
paddingBottom: spacing.xl, paddingBottom: spacing.l,
}, },
heroBg: { heroBg: {
width: 120, width: screenWidth < 380 ? 96 : 120,
height: 120, height: screenWidth < 380 ? 96 : 120,
borderRadius: 60, borderRadius: screenWidth < 380 ? 48 : 60,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
}, },
heroInner: { heroInner: {
width: 88, width: screenWidth < 380 ? 68 : 88,
height: 88, height: screenWidth < 380 ? 68 : 88,
borderRadius: 44, borderRadius: screenWidth < 380 ? 34 : 44,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
}, },
heroTitle: { heroTitle: {
fontSize: fontSize.xxl, fontSize: screenWidth < 380 ? fontSize.xl : fontSize.xxl,
fontWeight: "800", fontWeight: "800",
color: colors.textWhite, color: colors.textWhite,
marginTop: spacing.l, marginTop: spacing.m,
letterSpacing: 0.5, letterSpacing: 0.5,
}, },
heroSubtitle: { heroSubtitle: {
fontSize: fontSize.md, fontSize: screenWidth < 380 ? fontSize.sm : fontSize.md,
color: colors.textMuted, color: colors.textMuted,
marginTop: spacing.xs, marginTop: spacing.xs,
}, },
formSection: { paddingHorizontal: spacing.xl, flex: 1 }, formSection: { paddingHorizontal: screenWidth < 380 ? spacing.l : spacing.xl, flex: 1 },
inputContainer: { inputContainer: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
@@ -121,7 +120,7 @@ export default function DeliveryLoginScreen() {
marginLeft: spacing.xs, marginLeft: spacing.xs,
}, },
}), }),
[colors], [colors, screenWidth],
); );
const handleLogin = async () => { const handleLogin = async () => {
@@ -8,6 +8,7 @@ import {
TouchableOpacity, TouchableOpacity,
Linking, Linking,
Alert, Alert,
useWindowDimensions,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize, borderRadius } from "../../theme"; import { spacing, fontSize, borderRadius } from "../../theme";
@@ -26,6 +27,7 @@ import LoadingSpinner from "../../components/ui/LoadingSpinner";
export default function DashboardScreen() { export default function DashboardScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const { username } = useAuth(); const { username } = useAuth();
const { width: screenWidth } = useWindowDimensions();
const [tgLinked, setTgLinked] = useState(false); const [tgLinked, setTgLinked] = useState(false);
const [tgEnabled, setTgEnabled] = useState(false); const [tgEnabled, setTgEnabled] = useState(false);
@@ -133,54 +135,54 @@ export default function DashboardScreen() {
container: { container: {
flex: 1, flex: 1,
backgroundColor: colors.bgPrimary, backgroundColor: colors.bgPrimary,
padding: spacing.l, padding: screenWidth < 380 ? spacing.m : spacing.l,
}, },
welcome: { welcome: {
fontSize: fontSize.xl, fontSize: screenWidth < 380 ? fontSize.lg : fontSize.xl,
fontWeight: "bold", fontWeight: "bold",
color: colors.textWhite, color: colors.textWhite,
marginBottom: spacing.xs, marginBottom: spacing.xs,
}, },
subtitle: { subtitle: {
fontSize: fontSize.md, fontSize: screenWidth < 380 ? fontSize.sm : fontSize.md,
color: colors.textSecondary, color: colors.textSecondary,
marginBottom: spacing.xl, marginBottom: spacing.l,
}, },
grid: { grid: {
flexDirection: "row", flexDirection: "row",
flexWrap: "wrap", flexWrap: "wrap",
gap: spacing.m, gap: screenWidth < 380 ? spacing.s : spacing.m,
}, },
card: { card: {
width: "47%", width: screenWidth < 360 ? "100%" : "47%",
backgroundColor: colors.bgCard, backgroundColor: colors.bgCard,
borderRadius: borderRadius.md, borderRadius: borderRadius.md,
padding: spacing.l, padding: screenWidth < 380 ? spacing.m : spacing.l,
borderWidth: 1, borderWidth: 1,
borderColor: colors.borderLight, borderColor: colors.borderLight,
alignItems: "center", alignItems: "center",
}, },
iconCircle: { iconCircle: {
width: 48, width: screenWidth < 380 ? 40 : 48,
height: 48, height: screenWidth < 380 ? 40 : 48,
borderRadius: 24, borderRadius: screenWidth < 380 ? 20 : 24,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
marginBottom: spacing.s, marginBottom: spacing.s,
}, },
cardValue: { cardValue: {
fontSize: fontSize.xxl, fontSize: screenWidth < 380 ? fontSize.xl : fontSize.xxl,
fontWeight: "bold", fontWeight: "bold",
color: colors.textWhite, color: colors.textWhite,
}, },
cardLabel: { cardLabel: {
fontSize: fontSize.sm, fontSize: screenWidth < 380 ? fontSize.xs : fontSize.sm,
color: colors.textSecondary, color: colors.textSecondary,
marginTop: spacing.xs, marginTop: spacing.xs,
textAlign: "center", textAlign: "center",
}, },
}), }),
[colors], [colors, screenWidth],
); );
if (loading) return <LoadingSpinner message="Chargement..." />; if (loading) return <LoadingSpinner message="Chargement..." />;
@@ -14,12 +14,12 @@ import {
RefreshControl, RefreshControl,
TouchableOpacity, TouchableOpacity,
AppState, AppState,
Dimensions,
Linking, Linking,
Modal, Modal,
StatusBar, StatusBar,
ScrollView, ScrollView,
TextInput, TextInput,
useWindowDimensions,
} from "react-native"; } from "react-native";
import Geolocation from "@react-native-community/geolocation"; import Geolocation from "@react-native-community/geolocation";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
@@ -55,8 +55,6 @@ import TomTomMap, {
} from "../../components/TomTomMap"; } from "../../components/TomTomMap";
import DetailsModal from "../../components/ui/Modal"; import DetailsModal from "../../components/ui/Modal";
const { width: SCREEN_WIDTH } = Dimensions.get("window");
const MAP_HEIGHT = 260;
const LOCATION_INTERVAL_MS = 15000; const LOCATION_INTERVAL_MS = 15000;
const STATUS_LABELS: Record<string, string> = { const STATUS_LABELS: Record<string, string> = {
@@ -79,6 +77,8 @@ interface EnrichedDelivery extends DeliveryItem {
export default function DashboardScreen() { export default function DashboardScreen() {
const { colors } = useTheme(); const { colors } = useTheme();
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
const MAP_HEIGHT = screenHeight < 700 ? 180 : screenWidth < 380 ? 200 : 260;
const [status, setStatus] = useState<DeliveryStatus | null>(null); const [status, setStatus] = useState<DeliveryStatus | null>(null);
const [deliveries, setDeliveries] = useState<EnrichedDelivery[]>([]); const [deliveries, setDeliveries] = useState<EnrichedDelivery[]>([]);
const [queue, setQueue] = useState<QueueInfo | null>(null); const [queue, setQueue] = useState<QueueInfo | null>(null);
@@ -1067,17 +1067,19 @@ export default function DashboardScreen() {
gap: spacing.m, gap: spacing.m,
}, },
instructionIconBox: { instructionIconBox: {
width: 42, width: screenWidth < 380 ? 34 : 42,
height: 42, height: screenWidth < 380 ? 34 : 42,
borderRadius: 12, borderRadius: 10,
backgroundColor: colors.accent, backgroundColor: colors.accent,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
flexShrink: 0,
}, },
instructionText: { instructionText: {
color: colors.textWhite, color: colors.textWhite,
fontSize: fontSize.md, fontSize: screenWidth < 380 ? fontSize.sm : fontSize.md,
fontWeight: "600", fontWeight: "600",
flex: 1,
}, },
instructionStreet: { instructionStreet: {
color: colors.textMuted, color: colors.textMuted,
@@ -1259,7 +1261,7 @@ export default function DashboardScreen() {
address: { address: {
flex: 1, flex: 1,
color: colors.textSecondary, color: colors.textSecondary,
fontSize: fontSize.sm, fontSize: screenWidth < 380 ? fontSize.xs : fontSize.sm,
}, },
itemsSection: { itemsSection: {
@@ -1285,10 +1287,12 @@ export default function DashboardScreen() {
paddingVertical: spacing.xs, paddingVertical: spacing.xs,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: colors.borderSubtle, borderBottomColor: colors.borderSubtle,
flexWrap: "nowrap",
}, },
itemName: { itemName: {
flex: 1,
color: colors.textWhite, color: colors.textWhite,
fontSize: fontSize.sm, fontSize: screenWidth < 380 ? fontSize.xs : fontSize.sm,
fontWeight: "600", fontWeight: "600",
}, },
itemQty: { itemQty: {
@@ -1547,7 +1551,7 @@ export default function DashboardScreen() {
fontSize: fontSize.md, fontSize: fontSize.md,
}, },
}), }),
[colors], [colors, screenWidth, screenHeight, MAP_HEIGHT],
); );
if (loading) return <LoadingSpinner message="Chargement..." />; if (loading) return <LoadingSpinner message="Chargement..." />;