import React, { useState } from 'react'; import { View, Text, ScrollView, TouchableOpacity, TextInput, StyleSheet, Alert, } from 'react-native'; import { useLocalSearchParams, useRouter } from 'expo-router'; import { useForm, Controller } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Ionicons } from '@expo/vector-icons'; import { SafeAreaView } from 'react-native-safe-area-context'; import { usePresetStore } from '@store/presetStore'; import { useFilamentStore } from '@store/filamentStore'; import { useAuthStore } from '@store/authStore'; import { deleteFilamentUseCase, updateFilamentUseCase } from '@infrastructure/container'; import { Input } from '@presentation/components/ui/Input'; import { Button } from '@presentation/components/ui/Button'; import { Card } from '@presentation/components/ui/Card'; import { calcNetWeight } from '@domain/Filament'; import { colors, typography, spacing, radius } from '@shared/theme'; import { MATERIALS, type Material } from '@shared/constants'; import { formatWeight, isValidHex, normalizeHex } from '@shared/utils/filament'; const schema = z.object({ brand: z.string().min(1, 'Marca obrigatória'), model: z.string().optional(), totalWeightG: z.coerce.number().min(1, 'Informe o peso total'), tempHotendC: z.coerce.number().optional(), tempBedC: z.coerce.number().optional(), flowFactorPct: z.coerce.number().optional(), notes: z.string().optional(), }); type FormData = z.infer; const PRESET_COLORS = ['#E05533', '#2563EB', '#FFFFFF', '#1E1B18', '#22C55E', '#EAB308', '#EC4899', '#A855F7', '#F97316']; /** * Tela de Edição de Filamento — 13O-0 */ export default function EditFilamentScreen(): React.ReactElement { const { id } = useLocalSearchParams<{ id: string }>(); const router = useRouter(); const { presets, systemPresets } = usePresetStore(); const { filaments, updateFilament, removeFilament } = useFilamentStore(); const filament = filaments.find((f) => f.id === id); const [selectedColor, setSelectedColor] = useState(filament?.colorHex ?? '#E05533'); const [hexInput, setHexInput] = useState(filament?.colorHex ?? '#E05533'); const [selectedMaterial, setSelectedMaterial] = useState((filament?.material as Material) ?? 'PLA'); const [selectedPresetId, setSelectedPresetId] = useState(filament?.spoolPresetId ?? systemPresets[0]?.id ?? null); const [isLoading, setIsLoading] = useState(false); const [isOpeningColorPicker, setIsOpeningColorPicker] = useState(false); const { control, handleSubmit, watch, formState: { errors } } = useForm({ resolver: zodResolver(schema), defaultValues: { brand: filament?.brand ?? '', model: filament?.model ?? '', totalWeightG: filament?.totalWeightG ?? 0, tempHotendC: filament?.tempHotendC ?? 210, tempBedC: filament?.tempBedC ?? 60, flowFactorPct: filament?.flowFactorPct ?? 100, notes: filament?.notes ?? '', }, }); const totalWeight = watch('totalWeightG'); const selectedPreset = presets.find((p) => p.id === selectedPresetId); const netWeight = selectedPreset && totalWeight ? calcNetWeight(Number(totalWeight), selectedPreset.spoolWeightG) : 0; const pct = Math.min(100, Math.round((netWeight / 1000) * 100)); if (!filament) { return ( Filamento não encontrado router.back()}> Voltar ); } const handleOpenColorPicker = (): void => { if (isOpeningColorPicker) return; setIsOpeningColorPicker(true); Alert.prompt( 'Cor Customizada', 'Digite um código de cor hexadecimal (ex: #FF5733)', [ { text: 'Cancelar', style: 'cancel', onPress: () => setIsOpeningColorPicker(false) }, { text: 'Confirmar', onPress: (value) => { setIsOpeningColorPicker(false); if (!value) return; const normalized = normalizeHex(value.trim()); if (isValidHex(normalized)) { setSelectedColor(normalized); setHexInput(normalized); } else { Alert.alert('Erro', 'Código hexadecimal inválido. Use o formato #RRGGBB ou #RGB.'); } }, }, ], 'plain-text', selectedColor, ); }; const handleDelete = async (): Promise => { Alert.alert( 'Confirmar Deleção', 'Tem certeza que deseja deletar este filamento?', [ { text: 'Cancelar', style: 'cancel' }, { text: 'Deletar', style: 'destructive', onPress: async () => { const user = useAuthStore.getState().user; if (!user) { Alert.alert('Erro', 'Usuário não autenticado.'); return; } setIsLoading(true); try { await deleteFilamentUseCase.execute(filament!.id, user.id); removeFilament(filament!.id); Alert.alert('Sucesso', 'Filamento deletado.'); router.back(); } catch (err) { console.error('delete filament error', err); Alert.alert('Erro', 'Falha ao deletar: ' + (err as Error).message); } finally { setIsLoading(false); } }, }, ], ); }; async function onSubmit(data: FormData): Promise { if (!selectedPresetId) { Alert.alert('Atenção', 'Selecione um preset de carretel.'); return; } setIsLoading(true); try { // TODO: UpdateFilamentUseCase via container DI const updated = { ...filament!, brand: data.brand, model: data.model ?? null, colorHex: selectedColor, material: selectedMaterial, spoolPresetId: selectedPresetId, totalWeightG: Number(data.totalWeightG), netWeightG: netWeight, tempHotendC: data.tempHotendC ?? null, tempBedC: data.tempBedC ?? null, flowFactorPct: data.flowFactorPct ?? null, notes: data.notes ?? null, updatedAt: new Date().toISOString(), }; updateFilament(updated); router.back(); } catch { Alert.alert('Erro', 'Não foi possível salvar as alterações.'); } finally { setIsLoading(false); } } return ( {/* Header */} router.back()}> Editar Filamento {/* Cor */} COR DO FILAMENTO {hexInput} {PRESET_COLORS.map((c) => ( { setSelectedColor(c); setHexInput(c); }} style={[styles.colorDot, { backgroundColor: c }, selectedColor === c && styles.colorDotActive]} /> ))} {/* Material */} MATERIAL {MATERIALS.map((m) => ( setSelectedMaterial(m)} style={[styles.chip, selectedMaterial === m && styles.chipActive]} > {m} ))} {/* Marca e Modelo */} ( )} /> ( )} /> {/* Parâmetros de impressão */} PARÂMETROS DE IMPRESSÃO ( } /> )} /> ( } /> )} /> ( } /> )} /> {/* Preset */} PRESET DO CARRETEL router.push('/(app)/config/presets/new' as never)}> + Personalizado {presets.map((p) => ( setSelectedPresetId(p.id)} style={[styles.presetItem, selectedPresetId === p.id && styles.presetItemActive]} > {p.name} {selectedPresetId === p.id && Carretel: {p.spoolWeightG}g} {p.spoolWeightG}g ))} {/* Calculadora */} CALCULADORA DE PESO Peso Total na Balança (g) ( g )} /> {netWeight > 0 && ( Filamento Disponível {totalWeight}g – {selectedPreset?.spoolWeightG}g {formatWeight(netWeight)} {pct}% )} {/* CTA fixo */}