components/activite-CommandeRowCompact-reference.tsx
Annotation
Ce composant React Native affiche une ligne compacte résumant une commande dans une liste d'activités. Il expose un composant `CommandeRowCompact` qui prend une commande et une fonction de rappel optionnelle pour le clic. Ce composant est utilisé pour représenter chaque commande dans une interface utilisateur mobile, en affichant des informations clés comme le nom du vendeur, le produit principal, le statut, la date et le total.
Concepts détectés — comprends la théorie
UI React Native
16 occurrencesComposants UI React Native (View, Text, etc.). Différents de l'HTML web — on rend du natif.
Voir l'article général
Hooks React
3 occurrencesCe fichier utilise des hooks React. Les hooks sont la façon moderne de gérer l'état et les effets dans React. Voir l'architecture mobile pour le pattern complet.
Voir l'article général
1 export
Code source· tsx
import React, { useMemo } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { Image } from 'expo-image';
import { useTheme } from '@/hooks/useTheme';
import { FONTS, RADIUS, SPACING } from '@/lib/constants';
import type { CommandeMobile } from '@/hooks/useActivite';
type Props = { commande: CommandeMobile; onPress?: () => void };
const STATUT_LABEL: Record<CommandeMobile['statut'], string> = {
EN_ATTENTE: 'En attente',
CONFIRMEE: 'Confirmée',
EN_PREPARATION: 'En préparation',
EXPEDIE: 'Expédiée',
LIVRE: 'Livrée',
ANNULE: 'Annulée',
};
function statutColor(s: CommandeMobile['statut'], colors: ReturnType<typeof useTheme>['colors']) {
if (s === 'EN_ATTENTE') return colors.warning;
if (s === 'CONFIRMEE') return colors.info;
if (s === 'EN_PREPARATION') return colors.info;
if (s === 'EXPEDIE') return colors.primary;
if (s === 'LIVRE') return colors.success;
return colors.error;
}
export default function CommandeRowCompact({ commande, onPress }: Props) {
const { colors } = useTheme();
const styles = useMemo(() => makeStyles(colors), [colors]);
const sc = statutColor(commande.statut, colors);
const date = new Date(commande.createdAt).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
return (
<TouchableOpacity style={styles.row} onPress={onPress} activeOpacity={0.75}>
<View style={styles.thumbWrap}>
{commande.premiereLigne?.imageUrl ? (
<Image source={{ uri: commande.premiereLigne.imageUrl }} style={styles.thumb} contentFit="cover" />
) : (
<View style={[styles.thumb, styles.thumbFallback]}>
<Text style={styles.thumbFallbackText}>{commande.tenant?.nom?.charAt(0).toUpperCase() ?? '?'}</Text>
</View>
)}
</View>
<View style={styles.content}>
<Text style={styles.tenant} numberOfLines={1}>{commande.tenant?.nom ?? 'Vitrine'}</Text>
<Text style={styles.product} numberOfLines={1}>
{commande.premiereLigne?.nom ?? 'Commande'}
{commande.nbLignes > 1 ? ` +${commande.nbLignes - 1}` : ''}
</Text>
<View style={styles.metaRow}>
<View style={[styles.statutDot, { backgroundColor: sc }]} />
<Text style={[styles.statut, { color: sc }]}>{STATUT_LABEL[commande.statut]}</Text>
<Text style={styles.dot}>·</Text>
<Text style={styles.date}>{date}</Text>
<Text style={styles.dot}>·</Text>
<Text style={styles.total}>{commande.total.toLocaleString('fr-FR')} {commande.devise}</Text>
</View>
</View>
</TouchableOpacity>
);
}
function makeStyles(colors: ReturnType<typeof useTheme>['colors']) {
return StyleSheet.create({
row: {
flexDirection: 'row',
gap: SPACING.md,
backgroundColor: colors.bgCard,
borderRadius: RADIUS.lg,
borderWidth: 1,
borderColor: colors.border,
padding: SPACING.sm,
},
thumbWrap: { width: 56, height: 56, borderRadius: RADIUS.md, overflow: 'hidden' },
thumb: { width: '100%', height: '100%' },
thumbFallback: { backgroundColor: colors.bgElevated, alignItems: 'center', justifyContent: 'center' },
thumbFallbackText: { fontSize: FONTS.lg, fontWeight: FONTS.bold, color: colors.primary },
content: { flex: 1, justifyContent: 'center', gap: 2 },
tenant: { fontSize: FONTS.xs, color: colors.textMuted, fontWeight: FONTS.medium },
product: { fontSize: FONTS.sm, color: colors.text, fontWeight: FONTS.semibold },
metaRow: { flexDirection: 'row', alignItems: 'center', gap: 4, marginTop: 2 },
statutDot: { width: 8, height: 8, borderRadius: 4 },
statut: { fontSize: FONTS.xs, fontWeight: FONTS.semibold },
dot: { color: colors.textMuted, fontSize: FONTS.xs },
date: { color: colors.textSecondary, fontSize: FONTS.xs },
total: { color: colors.text, fontSize: FONTS.xs, fontWeight: FONTS.semibold },
});
}
import React, { useMemo } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { Image } from 'expo-image';
import { useTheme } from '@/hooks/useTheme';
import { FONTS, RADIUS, SPACING } from '@/lib/constants';
import type { CommandeMobile } from '@/hooks/useActivite';
type Props = { commande: CommandeMobile; onPress?: () => void };
const STATUT_LABEL: Record<CommandeMobile['statut'], string> = {
EN_ATTENTE: 'En attente',
CONFIRMEE: 'Confirmée',
EN_PREPARATION: 'En préparation',
EXPEDIE: 'Expédiée',
LIVRE: 'Livrée',
ANNULE: 'Annulée',
};
function statutColor(s: CommandeMobile['statut'], colors: ReturnType<typeof useTheme>['colors']) {
if (s === 'EN_ATTENTE') return colors.warning;
if (s === 'CONFIRMEE') return colors.info;
if (s === 'EN_PREPARATION') return colors.info;
if (s === 'EXPEDIE') return colors.primary;
if (s === 'LIVRE') return colors.success;
return colors.error;
}
export default function CommandeRowCompact({ commande, onPress }: Props) {
const { colors } = useTheme();
const styles = useMemo(() => makeStyles(colors), [colors]);
const sc = statutColor(commande.statut, colors);
const date = new Date(commande.createdAt).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
return (
<TouchableOpacity style={styles.row} onPress={onPress} activeOpacity={0.75}>
<View style={styles.thumbWrap}>
{commande.premiereLigne?.imageUrl ? (
<Image source={{ uri: commande.premiereLigne.imageUrl }} style={styles.thumb} contentFit="cover" />
) : (
<View style={[styles.thumb, styles.thumbFallback]}>
<Text style={styles.thumbFallbackText}>{commande.tenant?.nom?.charAt(0).toUpperCase() ?? '?'}</Text>
</View>
)}
</View>
<View style={styles.content}>
<Text style={styles.tenant} numberOfLines={1}>{commande.tenant?.nom ?? 'Vitrine'}</Text>
<Text style={styles.product} numberOfLines={1}>
{commande.premiereLigne?.nom ?? 'Commande'}
{commande.nbLignes > 1 ? ` +${commande.nbLignes - 1}` : ''}
</Text>
<View style={styles.metaRow}>
<View style={[styles.statutDot, { backgroundColor: sc }]} />
<Text style={[styles.statut, { color: sc }]}>{STATUT_LABEL[commande.statut]}</Text>
<Text style={styles.dot}>·</Text>
<Text style={styles.date}>{date}</Text>
<Text style={styles.dot}>·</Text>
<Text style={styles.total}>{commande.total.toLocaleString('fr-FR')} {commande.devise}</Text>
</View>
</View>
</TouchableOpacity>
);
}
function makeStyles(colors: ReturnType<typeof useTheme>['colors']) {
return StyleSheet.create({
row: {
flexDirection: 'row',
gap: SPACING.md,
backgroundColor: colors.bgCard,
borderRadius: RADIUS.lg,
borderWidth: 1,
borderColor: colors.border,
padding: SPACING.sm,
},
thumbWrap: { width: 56, height: 56, borderRadius: RADIUS.md, overflow: 'hidden' },
thumb: { width: '100%', height: '100%' },
thumbFallback: { backgroundColor: colors.bgElevated, alignItems: 'center', justifyContent: 'center' },
thumbFallbackText: { fontSize: FONTS.lg, fontWeight: FONTS.bold, color: colors.primary },
content: { flex: 1, justifyContent: 'center', gap: 2 },
tenant: { fontSize: FONTS.xs, color: colors.textMuted, fontWeight: FONTS.medium },
product: { fontSize: FONTS.sm, color: colors.text, fontWeight: FONTS.semibold },
metaRow: { flexDirection: 'row', alignItems: 'center', gap: 4, marginTop: 2 },
statutDot: { width: 8, height: 8, borderRadius: 4 },
statut: { fontSize: FONTS.xs, fontWeight: FONTS.semibold },
dot: { color: colors.textMuted, fontSize: FONTS.xs },
date: { color: colors.textSecondary, fontSize: FONTS.xs },
total: { color: colors.text, fontSize: FONTS.xs, fontWeight: FONTS.semibold },
});
}