import React, { useState, useCallback } from 'react'; import { View, StyleSheet, ScrollView, Alert, KeyboardAvoidingView, Platform, } from 'react-native'; import { TextInput, Button, Text, SegmentedButtons, Snackbar, } from 'react-native-paper'; import { useNavigation } from '@react-navigation/native'; import NetInfo from '@react-native-community/netinfo'; import { proposalsApi, type CreateProposalRequest, } from '../../lib/api/proposals'; import { FilePickerButton } from '../../components/FilePickerButton'; import { useOfflineDraft, queueOfflineSubmission, } from '../../hooks/useOfflineDraft'; import { SERVICE_CATEGORIES, PRIORITIES } from '../../constants'; interface DraftForm extends Record { workOrderNumber: string; customerName: string; customerAddress: string; scopeOfWork: string; serviceCategory: string; priority: string; notes: string; } const emptyDraft: DraftForm = { workOrderNumber: '', customerName: '', customerAddress: '', scopeOfWork: '', serviceCategory: 'General', priority: 'Standard', notes: '', }; export function NewProposalScreen() { const navigation = useNavigation(); const { draft, updateDraft, clearDraft, isLoaded } = useOfflineDraft(emptyDraft); const [submitting, setSubmitting] = useState(false); const [proposalId, setProposalId] = useState(null); const [snackbar, setSnackbar] = useState(''); const validate = (): string | null => { if (!draft.customerName.trim()) return 'Customer name is required'; if (!draft.customerAddress.trim()) return 'Customer address is required'; if (!draft.scopeOfWork.trim()) return 'Scope of work is required'; if (!draft.workOrderNumber.trim()) return 'Work order number is required'; return null; }; const handleSubmit = useCallback(async () => { const error = validate(); if (error) { Alert.alert('Validation', error); return; } setSubmitting(true); try { const payload: CreateProposalRequest = { workOrderNumber: draft.workOrderNumber.trim(), customerName: draft.customerName.trim(), customerAddress: draft.customerAddress.trim(), scopeOfWork: draft.scopeOfWork.trim(), serviceCategory: draft.serviceCategory as CreateProposalRequest['serviceCategory'], priority: draft.priority as CreateProposalRequest['priority'], notes: draft.notes.trim() || undefined, }; const netState = await NetInfo.fetch(); if (!netState.isConnected) { await queueOfflineSubmission(payload); await clearDraft(); Alert.alert( 'Saved Offline', 'Your proposal has been queued and will submit when you reconnect.', [{ text: 'OK', onPress: () => navigation.goBack() }], ); return; } const result = await proposalsApi.create(payload); setProposalId(result.id); await clearDraft(); setSnackbar(`Proposal ${result.proposalNumber} submitted!`); setTimeout(() => navigation.goBack(), 1500); } catch (err) { Alert.alert( 'Error', err instanceof Error ? err.message : 'Failed to submit proposal.', ); } finally { setSubmitting(false); } }, [draft, clearDraft, navigation]); if (!isLoaded) return null; return ( updateDraft({ workOrderNumber: v })} mode="outlined" style={styles.input} /> updateDraft({ customerName: v })} mode="outlined" style={styles.input} /> updateDraft({ customerAddress: v })} mode="outlined" multiline numberOfLines={2} style={styles.input} /> Service Category updateDraft({ serviceCategory: v })} buttons={SERVICE_CATEGORIES.map((c) => ({ value: c, label: c, }))} style={styles.segmented} density="small" /> Priority updateDraft({ priority: v })} buttons={PRIORITIES.map((p) => ({ value: p, label: p }))} style={styles.segmented} /> updateDraft({ scopeOfWork: v })} mode="outlined" multiline numberOfLines={6} placeholder="Describe the work to be done..." style={styles.input} /> Vendor Proposal (optional) {proposalId ? ( setSnackbar('Vendor PDF attached')} getUploadUrl={(fileName) => proposalsApi.uploadAttachment(proposalId, fileName) } /> ) : ( Submit the proposal first, then attach vendor PDFs )} updateDraft({ notes: v })} mode="outlined" multiline numberOfLines={3} style={styles.input} /> setSnackbar('')} duration={3000} > {snackbar} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#FAFAFA', }, scroll: { padding: 16, paddingBottom: 40, }, input: { marginBottom: 12, }, label: { marginBottom: 8, marginTop: 4, color: '#424242', }, segmented: { marginBottom: 16, }, hint: { color: '#9E9E9E', marginBottom: 12, }, submit: { marginTop: 16, borderRadius: 8, }, submitContent: { paddingVertical: 8, }, });