chore: build

This commit is contained in:
2026-06-14 15:17:15 +02:00
parent be7e3b3bd8
commit 8a503b544e
4 changed files with 232 additions and 196 deletions
+7 -13
View File
@@ -264,17 +264,6 @@ export const notifyClientToDescend = async (commandId: number) => {
};
};
export const confirmReceptionAdmin = async (commandId: number) => {
const { data } = await apiClient.post(
`${V2}/admin/protected/orders/${commandId}/confirm-reception`,
);
return {
success: true,
message: data.message,
points_earned: data.points_earned,
client_username: data.client_username,
};
};
// ============================================
@@ -935,13 +924,18 @@ export interface RewardCategoryConfig {
amount: number;
}
export interface RewardItem {
product_id: number;
quantity: number;
price: number;
}
export interface PointsReward {
threshold: number;
type: "free_product" | "half_price_product" | "custom";
description: string;
category_configs: RewardCategoryConfig[];
reward_product_id?: number;
reward_quantity?: number;
reward_items: RewardItem[];
}
export interface PointsPool {
@@ -23,7 +23,6 @@ import {
getCommandItems,
updateCommandStatus,
validateCommand,
confirmReceptionAdmin,
notifyClientToDescend,
getDeliveryPersonDetails,
deleteCommandItem,
@@ -206,20 +205,6 @@ export default function OrderDetailScreen() {
);
};
const handleConfirmReception = async () => {
try {
const res = await confirmReceptionAdmin(orderId);
showSuccess(
"Réception confirmée",
`${res.points_earned} point(s) attribués au client ${res.client_username}`,
);
const updated = await getCommandByID(orderId);
setCommand(updated.command);
} catch (e: any) {
showError("Erreur", e.message);
}
};
const handleDeleteCommand = () => {
Alert.alert(
"Supprimer la commande",
@@ -880,15 +865,7 @@ export default function OrderDetailScreen() {
style={{ marginTop: spacing.s }}
/>
)}
{!["approved", "cancelled"].includes(command.status) && (
<Button
title="Confirmer la commande"
onPress={handleConfirmReception}
variant="primary"
fullWidth
style={{ marginTop: spacing.s }}
/>
)}
<Button
title="Supprimer la commande"
onPress={handleDeleteCommand}
@@ -23,7 +23,7 @@ import {
updateCommandStatus,
getCommandItems,
notifyClientToDescend,
confirmReceptionAdmin,
validateCommand,
deleteCommand,
deleteCommandItem,
@@ -221,26 +221,6 @@ export default function OrdersScreen() {
}
};
const handleConfirmReception = (commandId: number) => {
showConfirm(
"Confirmer la commande",
`Confirmer la réception de la commande #${commandId} ?`,
async () => {
try {
const res = await confirmReceptionAdmin(commandId);
showSuccess(
"Réception confirmée",
`${res.points_earned} point(s) attribués au client ${res.client_username}`,
);
await loadData();
} catch (e: any) {
showError("Erreur", e.message);
}
},
"Confirmer",
);
};
const handleForceValidate = (commandId: number) => {
showConfirm(
"Finaliser la commande",
@@ -794,15 +774,7 @@ export default function OrdersScreen() {
},
condition: item.status === "assigned",
},
{
label: "Confirmer réception",
icon: "checkmark-circle-outline" as keyof typeof Ionicons.glyphMap,
onPress: () => {
setOpenMenuId(null);
handleConfirmReception(item.id);
},
condition: item.status === "livre",
},
{
label: "Finaliser commande",
icon: "trophy-outline" as keyof typeof Ionicons.glyphMap,
@@ -16,7 +16,7 @@ import { Ionicons } from "@expo/vector-icons";
import { spacing, fontSize, borderRadius } from "../../theme";
import { useTheme } from "../../context/ThemeContext";
import { getSettings, updateSettings, getCategories, getAvailableDeliveryPersons, getAllProductsAdmin, DEFAULT_DELIVERY_SCHEDULE, DEFAULT_POSTAL_ZONES } from "../../api/api_admin";
import type { AppSettings, Category, CategoryRoute, DeliveryModeConfig, PointsTier, PointsPool, PointsReward, RewardCategoryConfig, DaySchedule, DeliverySchedule, PostalZone } from "../../api/api_admin";
import type { AppSettings, Category, CategoryRoute, DeliveryModeConfig, PointsTier, PointsPool, PointsReward, RewardCategoryConfig, RewardItem, DaySchedule, DeliverySchedule, PostalZone } from "../../api/api_admin";
import type { Product } from "../../api/types";
import AlertModal from "../../components/ui/AlertModal";
import { useAlert } from "../../hooks/useAlert";
@@ -46,6 +46,51 @@ const DAYS: { key: keyof DeliverySchedule; label: string }[] = [
{ key: "sunday", label: "Dimanche" },
];
// ──────────────────────────────────────────────────────────────
// Composant section accordéon générique
// ──────────────────────────────────────────────────────────────
function AccordionSection({
title,
badge,
children,
colors,
s,
}: {
title: string;
badge?: React.ReactNode;
children: React.ReactNode;
colors: any;
s: any;
}) {
const [open, setOpen] = useState(false);
return (
<View style={s.section}>
<TouchableOpacity
onPress={() => setOpen((o) => !o)}
activeOpacity={0.7}
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: spacing.l,
paddingVertical: spacing.m,
}}
>
<Text style={[s.sectionTitle, { paddingHorizontal: 0, paddingTop: 0, paddingBottom: 0 }]}>{title}</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s }}>
{badge}
<Ionicons name={open ? "chevron-up" : "chevron-down"} size={18} color={colors.textMuted} />
</View>
</TouchableOpacity>
{open && (
<View style={{ borderTopWidth: 1, borderTopColor: colors.border }}>
{children}
</View>
)}
</View>
);
}
function DeliveryScheduleSection({
schedule,
onChange,
@@ -63,8 +108,7 @@ function DeliveryScheduleSection({
};
return (
<View style={s.section}>
<Text style={s.sectionTitle}>Horaires de livraison</Text>
<AccordionSection title="Horaires de livraison" colors={colors} s={s}>
<Text style={[s.hint, { paddingTop: spacing.s }]}>
Définissez les jours et horaires d'ouverture pour la livraison.
</Text>
@@ -133,7 +177,7 @@ function DeliveryScheduleSection({
</View>
);
})}
</View>
</AccordionSection>
);
}
@@ -200,8 +244,7 @@ function PostalZonesSection({
};
return (
<View style={s.section}>
<Text style={s.sectionTitle}>Zones de livraison</Text>
<AccordionSection title="Zones de livraison" colors={colors} s={s}>
<Text style={[s.hint, { paddingTop: spacing.s }]}>
Définissez les zones de livraison et leur montant minimum de commande.
</Text>
@@ -436,7 +479,7 @@ function PostalZonesSection({
</Text>
</TouchableOpacity>
)}
</View>
</AccordionSection>
);
}
@@ -547,24 +590,20 @@ function TiersSection({
onChange(tiers.filter((_, idx) => idx !== i));
};
const badge = (
<View style={{
paddingHorizontal: spacing.s, paddingVertical: 2, borderRadius: 10,
backgroundColor: active ? accentColor + "25" : colors.border + "40",
borderWidth: 1, borderColor: active ? accentColor : colors.border,
}}>
<Text style={{ fontSize: fontSize.xs, fontWeight: "700", color: active ? accentColor : colors.textMuted }}>
{active ? "Actif" : "Ignoré"}
</Text>
</View>
);
return (
<View style={s.section}>
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingRight: spacing.l }}>
<Text style={s.sectionTitle}>{title}</Text>
<View style={{
paddingHorizontal: spacing.s,
paddingVertical: 2,
borderRadius: 10,
backgroundColor: active ? accentColor + "25" : colors.border + "40",
borderWidth: 1,
borderColor: active ? accentColor : colors.border,
marginBottom: spacing.s,
}}>
<Text style={{ fontSize: fontSize.xs, fontWeight: "700", color: active ? accentColor : colors.textMuted }}>
{active ? "Actif" : "Ignoré"}
</Text>
</View>
</View>
<AccordionSection title={title} badge={badge} colors={colors} s={s}>
<Text style={s.hint}>
Définissez chaque palier : de X à Y = N points.{"\n"}
Max = 0 signifie illimité (pas de borne supérieure).
@@ -640,7 +679,7 @@ function TiersSection({
Ajouter un palier
</Text>
</TouchableOpacity>
</View>
</AccordionSection>
);
}
@@ -657,8 +696,7 @@ const EMPTY_REWARD: PointsReward = {
type: "free_product",
description: "",
category_configs: [],
reward_product_id: 0,
reward_quantity: 0,
reward_items: [],
};
// ──────────────────────────────────────────────────────────────
@@ -826,22 +864,20 @@ function CentralRewardSection({
});
};
return (
<View style={s.section}>
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingRight: spacing.l }}>
<Text style={s.sectionTitle}>Récompenses par palier</Text>
<View style={{
paddingHorizontal: spacing.s, paddingVertical: 2, borderRadius: 10,
backgroundColor: enabled ? REWARD_ACCENT + "25" : colors.border + "40",
borderWidth: 1, borderColor: enabled ? REWARD_ACCENT : colors.border,
marginBottom: spacing.s,
}}>
<Text style={{ fontSize: 10, fontWeight: "700", color: enabled ? REWARD_ACCENT : colors.textMuted }}>
{enabled ? "Activée" : "Désactivée"}
</Text>
</View>
</View>
const rewardBadge = (
<View style={{
paddingHorizontal: spacing.s, paddingVertical: 2, borderRadius: 10,
backgroundColor: enabled ? REWARD_ACCENT + "25" : colors.border + "40",
borderWidth: 1, borderColor: enabled ? REWARD_ACCENT : colors.border,
}}>
<Text style={{ fontSize: 10, fontWeight: "700", color: enabled ? REWARD_ACCENT : colors.textMuted }}>
{enabled ? "Activée" : "Désactivée"}
</Text>
</View>
);
return (
<AccordionSection title="Récompenses par palier" badge={rewardBadge} colors={colors} s={s}>
<View style={[s.row, s.rowFirst]}>
<View style={s.rowLeft}>
<Text style={s.rowLabel}>Récompense activée</Text>
@@ -920,72 +956,129 @@ function CentralRewardSection({
/>
</View>
{/* Produit récompense */}
{/* Produits récompense */}
<View>
<Text style={[s.rowLabel, { marginBottom: spacing.xs }]}>Produit ajouté au panier</Text>
<Text style={[s.rowLabel, { marginBottom: spacing.xs }]}>Produits ajoutés au panier</Text>
<Text style={[s.rowDesc, { marginBottom: spacing.m }]}>
Quand le client réclame sa récompense, ce produit est automatiquement ajouté à son panier (gratuit). Il doit commander au moins un produit normal.
Quand le client réclame sa récompense, ces produits sont automatiquement ajoutés à son panier (gratuits). Il doit commander au moins un produit normal.
</Text>
{/* Sélecteur produit */}
<View style={{ gap: spacing.s }}>
{r.reward_items.map((item, idx) => {
const allProds = Object.values(productsByCategory).flat();
const prod = allProds.find((p) => p.id === item.product_id);
return (
<View
key={idx}
style={{
borderWidth: 1, borderColor: REWARD_ACCENT + "44",
borderRadius: borderRadius.sm, padding: spacing.m,
backgroundColor: REWARD_ACCENT + "08", gap: spacing.s,
}}
>
{/* Sélecteur produit */}
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: spacing.xs }}>
{allProds.map((p) => {
const sel = item.product_id === p.id;
return (
<TouchableOpacity
key={p.id}
onPress={() => {
const updated = r.reward_items.map((it, i) =>
i === idx ? { ...it, product_id: p.id } : it
);
update({ reward_items: updated });
}}
style={{
flexDirection: "row", alignItems: "center", gap: 4,
paddingHorizontal: spacing.s, paddingVertical: 4,
borderRadius: borderRadius.sm, borderWidth: 1.5,
borderColor: sel ? REWARD_ACCENT : colors.border,
backgroundColor: sel ? REWARD_ACCENT + "22" : "transparent",
}}
>
{sel && <Ionicons name="checkmark" size={11} color={REWARD_ACCENT} />}
<Text style={{ fontSize: 12, fontWeight: sel ? "700" : "400", color: sel ? REWARD_ACCENT : colors.textMuted }}>
{p.name} ({p.category})
</Text>
</TouchableOpacity>
);
})}
</View>
{/* Quantité + Prix + Supprimer */}
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.m }}>
<View style={{ flex: 1, flexDirection: "row", alignItems: "center", gap: spacing.xs }}>
<Text style={{ fontSize: 12, color: colors.textMuted }}>Qté :</Text>
<TextInput
style={[s.thresholdInput, { width: 56, fontSize: 13 }]}
keyboardType="decimal-pad"
value={item.quantity > 0 ? String(item.quantity) : ""}
onChangeText={(v) => {
const n = parseFloat(v);
const updated = r.reward_items.map((it, i) =>
i === idx ? { ...it, quantity: isNaN(n) ? 0 : n } : it
);
update({ reward_items: updated });
}}
placeholder="1"
placeholderTextColor={colors.textMuted}
/>
</View>
<View style={{ flex: 1, flexDirection: "row", alignItems: "center", gap: spacing.xs }}>
<Text style={{ fontSize: 12, color: colors.textMuted }}>Prix :</Text>
<TextInput
style={[s.thresholdInput, { width: 56, fontSize: 13 }]}
keyboardType="decimal-pad"
value={item.price > 0 ? String(item.price) : ""}
onChangeText={(v) => {
const n = parseFloat(v);
const updated = r.reward_items.map((it, i) =>
i === idx ? { ...it, price: isNaN(n) ? 0 : n } : it
);
update({ reward_items: updated });
}}
placeholder="0"
placeholderTextColor={colors.textMuted}
/>
<Text style={{ fontSize: 12, color: colors.textMuted }}></Text>
</View>
<TouchableOpacity
onPress={() => update({ reward_items: r.reward_items.filter((_, i) => i !== idx) })}
style={{ padding: spacing.xs }}
>
<Ionicons name="trash-outline" size={18} color="#ef4444" />
</TouchableOpacity>
</View>
{prod && (
<Text style={{ fontSize: 11, color: REWARD_ACCENT, fontStyle: "italic" }}>
{prod.name}{item.quantity > 0 ? ` · x${item.quantity}` : ""}{item.price > 0 ? ` · ${item.price}` : ""}
</Text>
)}
{!prod && (
<Text style={{ fontSize: 11, color: colors.textMuted, fontStyle: "italic" }}>
Sélectionnez un produit ci-dessus
</Text>
)}
</View>
);
})}
{Object.values(productsByCategory).flat().length === 0 ? (
<Text style={[s.hint, { paddingHorizontal: 0 }]}>Aucun produit disponible</Text>
) : (
<View style={{ gap: spacing.xs }}>
<TouchableOpacity
onPress={() => update({ reward_product_id: 0, reward_quantity: 0 })}
style={{
flexDirection: "row", alignItems: "center", gap: spacing.xs,
paddingHorizontal: spacing.m, paddingVertical: spacing.s,
borderRadius: borderRadius.sm, borderWidth: 1.5,
borderColor: !r.reward_product_id ? colors.border : "transparent",
backgroundColor: !r.reward_product_id ? colors.border + "30" : "transparent",
}}
>
<Text style={{ fontSize: 13, color: !r.reward_product_id ? colors.textPrimary : colors.textMuted }}>
Aucun (désactivé)
</Text>
</TouchableOpacity>
{Object.values(productsByCategory).flat().map((prod) => {
const sel = r.reward_product_id === prod.id;
return (
<TouchableOpacity
key={prod.id}
onPress={() => update({ reward_product_id: prod.id, reward_quantity: r.reward_quantity && r.reward_quantity > 0 ? r.reward_quantity : 1 })}
style={{
flexDirection: "row", alignItems: "center", gap: spacing.xs,
paddingHorizontal: spacing.m, paddingVertical: spacing.s,
borderRadius: borderRadius.sm, borderWidth: 1.5,
borderColor: sel ? REWARD_ACCENT : colors.border,
backgroundColor: sel ? REWARD_ACCENT + "18" : "transparent",
}}
>
<Ionicons name="gift-outline" size={14} color={sel ? REWARD_ACCENT : colors.textMuted} />
<Text style={{ flex: 1, fontSize: 13, fontWeight: sel ? "700" : "400", color: sel ? REWARD_ACCENT : colors.textMuted }}>
{prod.name} ({prod.category})
</Text>
{sel && <Ionicons name="checkmark-circle" size={16} color={REWARD_ACCENT} />}
</TouchableOpacity>
);
})}
</View>
)}
{/* Quantité */}
{!!r.reward_product_id && (
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.m, marginTop: spacing.s }}>
<Text style={[s.rowLabel, { flex: 1 }]}>Quantité offerte</Text>
<TextInput
style={[s.thresholdInput, { width: 64 }]}
keyboardType="decimal-pad"
value={r.reward_quantity ? String(r.reward_quantity) : ""}
onChangeText={(v) => {
const n = parseFloat(v);
if (!isNaN(n) && n > 0) update({ reward_quantity: n });
}}
placeholder="1"
placeholderTextColor={colors.textMuted}
/>
</View>
<TouchableOpacity
onPress={() => update({ reward_items: [...r.reward_items, { product_id: 0, quantity: 1, price: 0 }] })}
style={{
flexDirection: "row", alignItems: "center", justifyContent: "center", gap: spacing.xs,
paddingHorizontal: spacing.m, paddingVertical: spacing.s,
borderRadius: borderRadius.sm, borderWidth: 1.5,
borderColor: REWARD_ACCENT + "66",
}}
>
<Ionicons name="add-circle-outline" size={16} color={REWARD_ACCENT} />
<Text style={{ fontSize: 13, color: REWARD_ACCENT, fontWeight: "600" }}>Ajouter un produit récompense</Text>
</TouchableOpacity>
)}
</View>
</View>
@@ -1046,7 +1139,7 @@ function CentralRewardSection({
</View>
{/* Récapitulatif */}
{r.category_configs.length > 0 && (
{(r.category_configs.length > 0 || r.reward_items.length > 0) && (
<View style={{ backgroundColor: REWARD_ACCENT + "12", borderRadius: borderRadius.sm, borderLeftWidth: 3, borderLeftColor: REWARD_ACCENT, padding: spacing.m, gap: 4 }}>
<Text style={{ fontSize: 13, fontWeight: "700", color: REWARD_ACCENT }}>Récapitulatif</Text>
<Text style={{ fontSize: 13, color: colors.textPrimary }}>
@@ -1058,14 +1151,22 @@ function CentralRewardSection({
)}
{r.category_configs.map((cfg) => (
<Text key={cfg.category} style={{ fontSize: 12, color: colors.textSecondary }}>
{cfg.category}{cfg.amount > 0 ? ` (${cfg.amount}€)` : ""} : {cfg.all_products ? "tous les produits" : `${cfg.product_ids.length} produit(s) sélectionné(s)`}
Éligible : {cfg.category}{cfg.amount > 0 ? ` (${cfg.amount}€)` : ""} {cfg.all_products ? "tous les produits" : `${cfg.product_ids.length} produit(s)`}
</Text>
))}
{r.reward_items.filter((it) => it.product_id > 0).map((it, idx) => {
const prod = Object.values(productsByCategory).flat().find((p) => p.id === it.product_id);
return (
<Text key={idx} style={{ fontSize: 12, color: colors.textSecondary }}>
🎁 {prod?.name ?? `Produit #${it.product_id}`}{it.quantity > 0 ? ` · x${it.quantity}` : ""}{it.price > 0 ? ` · ${it.price}` : ""}
</Text>
);
})}
</View>
)}
</View>
)}
</View>
</AccordionSection>
);
}
@@ -1167,7 +1268,7 @@ export default function SettingsScreen() {
category_routes: s.delivery_mode?.category_routes ?? [],
},
points_reward: s.points_reward
? { ...s.points_reward, category_configs: s.points_reward.category_configs ?? [] }
? { ...s.points_reward, category_configs: s.points_reward.category_configs ?? [], reward_items: s.points_reward.reward_items ?? [] }
: null,
});
}
@@ -1397,8 +1498,7 @@ export default function SettingsScreen() {
<View style={s.container}>
<ScrollView contentContainerStyle={s.content}>
{/* Personnalisation */}
<View style={s.section}>
<Text style={s.sectionTitle}>Personnalisation</Text>
<AccordionSection title="Personnalisation" colors={colors} s={s}>
<View style={[s.row, s.rowFirst]}>
<View style={s.rowLeft}>
<Text style={s.rowLabel}>Nom du shop</Text>
@@ -1415,11 +1515,10 @@ export default function SettingsScreen() {
autoCorrect={false}
/>
</View>
</View>
</AccordionSection>
{/* Amendes */}
<View style={s.section}>
<Text style={s.sectionTitle}>Amendes</Text>
<AccordionSection title="Amendes" colors={colors} s={s}>
<View style={[s.row, s.rowFirst]}>
<View style={s.rowLeft}>
<Text style={s.rowLabel}>Amendes activées</Text>
@@ -1522,11 +1621,10 @@ export default function SettingsScreen() {
</View>
))}
</View>
</View>
</AccordionSection>
{/* Parrainage */}
<View style={s.section}>
<Text style={s.sectionTitle}>Parrainage</Text>
<AccordionSection title="Parrainage" colors={colors} s={s}>
<View style={[s.row, s.rowFirst]}>
<View style={s.rowLeft}>
<Text style={s.rowLabel}>Parrainage activé</Text>
@@ -1544,11 +1642,10 @@ export default function SettingsScreen() {
thumbColor="#fff"
/>
</View>
</View>
</AccordionSection>
{/* Système de points */}
<View style={s.section}>
<Text style={s.sectionTitle}>Système de points</Text>
<AccordionSection title="Système de points" colors={colors} s={s}>
<View style={[s.row, s.rowFirst]}>
<View style={s.rowLeft}>
<Text style={s.rowLabel}>Points activés</Text>
@@ -1604,11 +1701,10 @@ export default function SettingsScreen() {
</Text>
</TouchableOpacity>
</View>
</View>
</AccordionSection>
{/* Attribution des catégories */}
<View style={s.section}>
<Text style={s.sectionTitle}>Attribution des catégories aux points</Text>
<AccordionSection title="Attribution des catégories aux points" colors={colors} s={s}>
<Text style={s.hint}>
Choisissez quel type de point est attribué pour chaque catégorie de produit.{"\n"}
Vous pouvez modifier les attributions à tout moment.
@@ -1695,7 +1791,7 @@ export default function SettingsScreen() {
Ajoutez au moins un type de point pour assigner des catégories.
</Text>
)}
</View>
</AccordionSection>
{/* Barèmes de points — un par pool */}
{pools.map((pool, i) => (
@@ -1738,8 +1834,7 @@ export default function SettingsScreen() {
/>
{/* Paiement crypto */}
<View style={s.section}>
<Text style={s.sectionTitle}>Paiement crypto</Text>
<AccordionSection title="Paiement crypto" colors={colors} s={s}>
{/* Toggle activation */}
<View style={[s.row, s.rowFirst]}>
@@ -1881,13 +1976,12 @@ export default function SettingsScreen() {
)}
</View>
</View>
</View>
</AccordionSection>
{/* ============================================ */}
{/* 🤖 TELEGRAM */}
{/* ============================================ */}
<View style={s.section}>
<Text style={s.sectionTitle}>Notifications Telegram</Text>
<AccordionSection title="Notifications Telegram" colors={colors} s={s}>
<View style={s.row}>
<View style={s.rowLeft}>
<Text style={s.rowLabel}>Notifications activées</Text>
@@ -1977,13 +2071,12 @@ export default function SettingsScreen() {
</View>
</View>
</View>
</AccordionSection>
{/* ============================================ */}
{/* 🚚 MODE DE LIVRAISON */}
{/* ============================================ */}
<View style={s.section}>
<Text style={s.sectionTitle}>Mode de livraison</Text>
<AccordionSection title="Mode de livraison" colors={colors} s={s}>
<Text style={[s.hint, { paddingTop: spacing.s, paddingHorizontal: spacing.l }]}>
Choisissez comment les commandes sont assignées aux livreurs.
</Text>
@@ -2102,7 +2195,7 @@ export default function SettingsScreen() {
</TouchableOpacity>
</View>
)}
</View>
</AccordionSection>
<TouchableOpacity
style={s.saveButton}