first commit

This commit is contained in:
AyrisAI
2026-08-20 01:51:59 +03:00
commit 97b83c7fd4
109 changed files with 21215 additions and 0 deletions
@@ -0,0 +1,433 @@
import { useEffect, useState } from "react";
import {
Alert,
Image,
KeyboardAvoidingView,
Modal,
Platform,
Pressable,
ScrollView,
Switch,
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";
import type { MenuItemData } from "./MenuItemCard";
interface ItemDetailSheetProps {
item: MenuItemData | null;
visible: boolean;
onClose: () => void;
onSave: (updatedItem: MenuItemData) => Promise<void>;
onDelete: (itemId: string) => Promise<void>;
}
export function ItemDetailSheet({
item,
visible,
onClose,
onSave,
onDelete,
}: ItemDetailSheetProps) {
const [name, setName] = useState("");
const [price, setPrice] = useState("");
const [description, setDescription] = useState("");
const [imageUrl, setImageUrl] = useState<string | null>(null);
const [isActive, setIsActive] = useState(true);
const [saving, setSaving] = useState(false);
const [deleting, setDeleting] = useState(false);
useEffect(() => {
if (item) {
setName(item.name);
setPrice(String(item.price));
setDescription(item.description ?? "");
setImageUrl(item.image_url ?? null);
setIsActive(item.is_active);
}
}, [item]);
if (!item) return null;
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 handleSave() {
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;
}
setSaving(true);
try {
await onSave({
...item!,
name: name.trim(),
price: parsedPrice,
description: description.trim() || null,
image_url: imageUrl || null,
is_active: isActive,
});
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
onClose();
} catch (err) {
Alert.alert("Hata", err instanceof Error ? err.message : "Kaydedilemedi.");
} finally {
setSaving(false);
}
}
function confirmDelete() {
Alert.alert(
"Ürünü Sil",
`"${item?.name}" menüden silinecek. Emin misiniz?`,
[
{ text: "Vazgeç", style: "cancel" },
{
text: "Sil",
style: "destructive",
onPress: async () => {
setDeleting(true);
try {
await onDelete(item!.id);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning).catch(() => {});
onClose();
} catch (err) {
Alert.alert("Hata", err instanceof Error ? err.message : "Silinemedi.");
} finally {
setDeleting(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: "90%",
paddingBottom: Platform.OS === "ios" ? 34 : 20,
}}
>
{/* Header Handle & Bar */}
<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",
}}
>
<Text style={{ fontSize: 18, fontWeight: "700", color: "#1C1917" }}>
Ürün Detayları
</Text>
<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 }}>
{/* Availability Toggle Box */}
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
backgroundColor: isActive ? "#F0FDF4" : "#FEF2F2",
borderWidth: 1,
borderColor: isActive ? "#BBF7D0" : "#FECACA",
borderRadius: 14,
padding: 14,
}}
>
<View>
<Text
style={{
fontSize: 15,
fontWeight: "700",
color: isActive ? "#15803D" : "#B91C1C",
}}
>
{isActive ? "Menüde Aktif & Siparişe Açık" : "Tükendi / Menüde Gizli"}
</Text>
<Text style={{ fontSize: 12, color: "#78716C", marginTop: 2 }}>
{isActive
? "Müşteriler bu ürünü QR menüde görebilir."
: "Müşteriler ürünü Tükendi olarak görür."}
</Text>
</View>
<Switch
value={isActive}
onValueChange={(val) => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {});
setIsActive(val);
}}
trackColor={{ false: "#D6D3D1", true: "#059669" }}
thumbColor="#FFFFFF"
/>
</View>
{/* Image Picker / Preview */}
<View>
<Text style={{ fontSize: 13, fontWeight: "700", color: "#44403C", marginBottom: 8 }}>
Ürün Fotoğrafı
</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>
<Pressable
onPress={handlePickImage}
style={{
position: "absolute",
bottom: 10,
right: 10,
backgroundColor: "#1C1917",
borderRadius: 8,
paddingHorizontal: 10,
paddingVertical: 6,
flexDirection: "row",
alignItems: "center",
gap: 4,
}}
>
<Ionicons name="camera-outline" size={14} color="#FFFFFF" />
<Text style={{ color: "#FFFFFF", fontSize: 11, fontWeight: "700" }}>Değiştir</Text>
</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>
{/* Name Input */}
<View>
<Text style={{ fontSize: 13, fontWeight: "700", color: "#44403C", marginBottom: 6 }}>
Ürün Adı *
</Text>
<TextInput
value={name}
onChangeText={setName}
placeholder="Örn: Izgara Levrek"
placeholderTextColor="#A8A29E"
style={{
backgroundColor: "#FAFAFA",
borderWidth: 1,
borderColor: "#E7E5E4",
borderRadius: 12,
padding: 14,
fontSize: 15,
fontWeight: "600",
color: "#1C1917",
}}
/>
</View>
{/* Price Input */}
<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="0.00"
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>
{/* Description Input */}
<View>
<Text style={{ fontSize: 13, fontWeight: "700", color: "#44403C", marginBottom: 6 }}>
Açıklama / Malzemeler
</Text>
<TextInput
value={description}
onChangeText={setDescription}
placeholder="İçindekiler, porsiyon bilgisi, pişirme tarzı..."
placeholderTextColor="#A8A29E"
multiline
numberOfLines={3}
style={{
backgroundColor: "#FAFAFA",
borderWidth: 1,
borderColor: "#E7E5E4",
borderRadius: 12,
padding: 14,
fontSize: 14,
color: "#1C1917",
minHeight: 80,
textAlignVertical: "top",
}}
/>
</View>
{/* Action Buttons */}
<View style={{ gap: 10, marginTop: 10 }}>
<Pressable
onPress={handleSave}
disabled={saving || deleting}
style={({ pressed }) => ({
backgroundColor: "#1C1917",
paddingVertical: 16,
borderRadius: 14,
alignItems: "center",
opacity: pressed || saving ? 0.85 : 1,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 6,
})}
>
<Text style={{ color: "#FFFFFF", fontSize: 15, fontWeight: "700" }}>
{saving ? "Kaydediliyor..." : "Değişiklikleri Kaydet"}
</Text>
</Pressable>
<Pressable
onPress={confirmDelete}
disabled={saving || deleting}
style={({ pressed }) => ({
backgroundColor: "#FEF2F2",
borderWidth: 1,
borderColor: "#FEE2E2",
paddingVertical: 14,
borderRadius: 14,
alignItems: "center",
opacity: pressed || deleting ? 0.85 : 1,
})}
>
<Text style={{ color: "#DC2626", fontSize: 14, fontWeight: "700" }}>
{deleting ? "Siliniyor..." : "Bu Ürünü Menüden Sil"}
</Text>
</Pressable>
</View>
</ScrollView>
</View>
</KeyboardAvoidingView>
</Modal>
);
}