Files
menulio/apps/mobile/src/components/menu/AddItemModal.tsx
T
2026-08-20 01:51:59 +03:00

315 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import {
Alert,
Image,
KeyboardAvoidingView,
Modal,
Platform,
Pressable,
ScrollView,
Text,
TextInput,
View,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import * as Haptics from "expo-haptics";
import * as ImagePicker from "expo-image-picker";
interface AddItemModalProps {
visible: boolean;
categoryName: string;
categoryId: string;
onClose: () => void;
onAdd: (categoryId: string, name: string, price: number, description?: string, imageUrl?: string) => Promise<void>;
}
export function AddItemModal({
visible,
categoryName,
categoryId,
onClose,
onAdd,
}: AddItemModalProps) {
const [name, setName] = useState("");
const [price, setPrice] = useState("");
const [description, setDescription] = useState("");
const [imageUrl, setImageUrl] = useState<string | null>(null);
const [adding, setAdding] = useState(false);
async function handlePickImage() {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {});
const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!permission.granted) {
Alert.alert("İzin Gerekli", "Ürün görseli seçebilmek için fotoğraf galerisi erişim izni gereklidir.");
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ["images"],
allowsEditing: true,
aspect: [4, 3],
quality: 0.6,
base64: true,
});
if (!result.canceled && result.assets[0]) {
const asset = result.assets[0];
if (asset.base64) {
setImageUrl(`data:image/jpeg;base64,${asset.base64}`);
} else {
setImageUrl(asset.uri);
}
}
}
async function handleAdd() {
const parsedPrice = Number(price.replace(",", "."));
if (!name.trim()) {
Alert.alert("Eksik Bilgi", "Lütfen ürün adını giriniz.");
return;
}
if (Number.isNaN(parsedPrice) || parsedPrice < 0) {
Alert.alert("Geçersiz Fiyat", "Lütfen geçerli bir fiyat giriniz.");
return;
}
setAdding(true);
try {
await onAdd(
categoryId,
name.trim(),
parsedPrice,
description.trim() || undefined,
imageUrl || undefined,
);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
setName("");
setPrice("");
setDescription("");
setImageUrl(null);
onClose();
} catch (err) {
Alert.alert("Hata", err instanceof Error ? err.message : "Ürün eklenemedi.");
} finally {
setAdding(false);
}
}
return (
<Modal
visible={visible}
animationType="slide"
transparent
onRequestClose={onClose}
>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : undefined}
style={{ flex: 1, backgroundColor: "rgba(0,0,0,0.5)", justifyContent: "flex-end" }}
>
<Pressable style={{ flex: 1 }} onPress={onClose} />
<View
style={{
backgroundColor: "#FFFFFF",
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
maxHeight: "88%",
paddingBottom: Platform.OS === "ios" ? 34 : 20,
}}
>
<View style={{ alignItems: "center", paddingTop: 10, paddingBottom: 6 }}>
<View
style={{
width: 36,
height: 4,
borderRadius: 2,
backgroundColor: "#E7E5E4",
}}
/>
</View>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 20,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: "#F5F5F4",
}}
>
<View>
<Text style={{ fontSize: 18, fontWeight: "700", color: "#1C1917" }}>
Yeni Ürün Ekle
</Text>
<Text style={{ fontSize: 12, color: "#78716C", marginTop: 2 }}>
Kategori: <Text style={{ fontWeight: "700", color: "#C8A96B" }}>{categoryName}</Text>
</Text>
</View>
<Pressable
onPress={onClose}
style={{
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: "#F5F5F4",
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons name="close" size={20} color="#78716C" />
</Pressable>
</View>
<ScrollView contentContainerStyle={{ padding: 20, gap: 16 }}>
{/* Image Picker */}
<View>
<Text style={{ fontSize: 13, fontWeight: "700", color: "#44403C", marginBottom: 8 }}>
Ürün Fotoğrafı (Opsiyonel)
</Text>
{imageUrl ? (
<View style={{ position: "relative", width: "100%", height: 160, borderRadius: 14, overflow: "hidden", borderWidth: 1, borderColor: "#E7E5E4" }}>
<Image source={{ uri: imageUrl }} style={{ width: "100%", height: 160 }} resizeMode="cover" />
<Pressable
onPress={() => setImageUrl(null)}
style={{
position: "absolute",
top: 10,
right: 10,
backgroundColor: "rgba(0,0,0,0.65)",
borderRadius: 14,
padding: 6,
}}
>
<Ionicons name="trash-outline" size={18} color="#FFFFFF" />
</Pressable>
</View>
) : (
<Pressable
onPress={handlePickImage}
style={({ pressed }) => ({
height: 100,
borderRadius: 14,
borderWidth: 1.5,
borderStyle: "dashed",
borderColor: "#D6D3D1",
backgroundColor: "#FAF8F5",
alignItems: "center",
justifyContent: "center",
gap: 6,
opacity: pressed ? 0.8 : 1,
})}
>
<Ionicons name="camera-outline" size={26} color="#C8A96B" />
<Text style={{ fontSize: 13, fontWeight: "600", color: "#78716C" }}>
Galeriden Fotoğraf Seç
</Text>
</Pressable>
)}
</View>
<View>
<Text style={{ fontSize: 13, fontWeight: "700", color: "#44403C", marginBottom: 6 }}>
Ürün Adı *
</Text>
<TextInput
value={name}
onChangeText={setName}
placeholder="Örn: Mercimek Çorbası"
placeholderTextColor="#A8A29E"
style={{
backgroundColor: "#FAFAFA",
borderWidth: 1,
borderColor: "#E7E5E4",
borderRadius: 12,
padding: 14,
fontSize: 15,
fontWeight: "600",
color: "#1C1917",
}}
/>
</View>
<View>
<Text style={{ fontSize: 13, fontWeight: "700", color: "#44403C", marginBottom: 6 }}>
Fiyat () *
</Text>
<View
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: "#FAFAFA",
borderWidth: 1,
borderColor: "#E7E5E4",
borderRadius: 12,
paddingHorizontal: 14,
}}
>
<TextInput
value={price}
onChangeText={setPrice}
placeholder="120"
placeholderTextColor="#A8A29E"
keyboardType="decimal-pad"
style={{
flex: 1,
paddingVertical: 14,
fontSize: 16,
fontWeight: "700",
color: "#C8A96B",
}}
/>
<Text style={{ fontSize: 16, fontWeight: "700", color: "#1C1917" }}></Text>
</View>
</View>
<View>
<Text style={{ fontSize: 13, fontWeight: "700", color: "#44403C", marginBottom: 6 }}>
Açıklama (Opsiyonel)
</Text>
<TextInput
value={description}
onChangeText={setDescription}
placeholder="Kısa içerik veya porsiyon açıklaması..."
placeholderTextColor="#A8A29E"
multiline
numberOfLines={3}
style={{
backgroundColor: "#FAFAFA",
borderWidth: 1,
borderColor: "#E7E5E4",
borderRadius: 12,
padding: 14,
fontSize: 14,
color: "#1C1917",
minHeight: 70,
textAlignVertical: "top",
}}
/>
</View>
<Pressable
onPress={handleAdd}
disabled={adding || !name.trim() || !price.trim()}
style={({ pressed }) => ({
backgroundColor: !name.trim() || !price.trim() ? "#D6D3D1" : "#1C1917",
paddingVertical: 16,
borderRadius: 14,
alignItems: "center",
opacity: pressed ? 0.85 : 1,
marginTop: 8,
})}
>
<Text style={{ color: "#FFFFFF", fontSize: 15, fontWeight: "700" }}>
{adding ? "Ekleniyor..." : "Ürünü Menüye Ekle"}
</Text>
</Pressable>
</ScrollView>
</View>
</KeyboardAvoidingView>
</Modal>
);
}