/** * BUS-Tickets - Providers Management Screen * Copyright (c) 2024-2026 IT Enterprise */ import { useState } from 'react'; import { View, Text, ScrollView, TouchableOpacity, TextInput, Switch, StyleSheet, Alert, Modal, ActivityIndicator, Image, } from 'react-native'; import { useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { useTheme } from '@/contexts/ThemeContext'; import { useLocale } from '@/contexts/LocaleContext'; import { useProviders, BusProvider } from '@/contexts/ProvidersContext'; export default function ProvidersScreen() { const router = useRouter(); const { colors } = useTheme(); const { t } = useLocale(); const { providers, addProvider, updateProvider, removeProvider, toggleProvider, setDefaultProvider, testConnection, syncAllProviders, } = useProviders(); const [showAddModal, setShowAddModal] = useState(false); const [editingProvider, setEditingProvider] = useState(null); const [isSyncing, setIsSyncing] = useState(false); const [testingId, setTestingId] = useState(null); // New provider form state const [formData, setFormData] = useState({ name: '', displayName: '', apiUrl: '', apiKey: '', logoUrl: '', primaryColor: '#e94560', }); const resetForm = () => { setFormData({ name: '', displayName: '', apiUrl: '', apiKey: '', logoUrl: '', primaryColor: '#e94560', }); setEditingProvider(null); }; const handleAddProvider = async () => { if (!formData.name || !formData.apiUrl) { Alert.alert(t.common.error, 'Name and API URL are required'); return; } try { if (editingProvider) { await updateProvider(editingProvider.id, { name: formData.name, displayName: formData.displayName || formData.name, apiUrl: formData.apiUrl, apiKey: formData.apiKey || undefined, logoUrl: formData.logoUrl || undefined, primaryColor: formData.primaryColor, }); } else { await addProvider({ name: formData.name, displayName: formData.displayName || formData.name, apiUrl: formData.apiUrl, apiKey: formData.apiKey || undefined, logoUrl: formData.logoUrl || undefined, primaryColor: formData.primaryColor, enabled: true, isDefault: providers.length === 0, supportsOnlinePayment: true, supportsSeatSelection: true, supportsRefunds: false, }); } setShowAddModal(false); resetForm(); } catch (error) { Alert.alert(t.common.error, 'Failed to save provider'); } }; const handleEditProvider = (provider: BusProvider) => { setEditingProvider(provider); setFormData({ name: provider.name, displayName: provider.displayName, apiUrl: provider.apiUrl, apiKey: provider.apiKey || '', logoUrl: provider.logoUrl || '', primaryColor: provider.primaryColor || '#e94560', }); setShowAddModal(true); }; const handleDeleteProvider = (provider: BusProvider) => { if (provider.isDefault) { Alert.alert(t.common.error, 'Cannot delete default provider'); return; } Alert.alert( t.common.delete, `Are you sure you want to remove ${provider.displayName}?`, [ { text: t.common.cancel, style: 'cancel' }, { text: t.common.delete, style: 'destructive', onPress: () => removeProvider(provider.id), }, ] ); }; const handleTestConnection = async (provider: BusProvider) => { setTestingId(provider.id); try { const success = await testConnection(provider); if (success) { Alert.alert(t.common.success, 'Connection successful!'); } else { Alert.alert(t.common.error, `Connection failed: ${provider.errorMessage || 'Unknown error'}`); } } catch (error) { Alert.alert(t.common.error, 'Connection test failed'); } finally { setTestingId(null); } }; const handleSyncAll = async () => { setIsSyncing(true); try { await syncAllProviders(); Alert.alert(t.common.success, 'All providers synced'); } catch (error) { Alert.alert(t.common.error, 'Sync failed'); } finally { setIsSyncing(false); } }; const styles = createStyles(colors); const ProviderCard = ({ provider }: { provider: BusProvider }) => ( {provider.logoUrl ? ( ) : ( {provider.displayName.charAt(0).toUpperCase()} )} {provider.displayName} {provider.isDefault && ( Default )} {provider.apiUrl} {provider.isConnected ? 'Connected' : provider.errorMessage || 'Not connected'} toggleProvider(provider.id, value)} trackColor={{ false: colors.border, true: colors.primary }} thumbColor="#fff" /> handleTestConnection(provider)} disabled={testingId === provider.id} > {testingId === provider.id ? ( ) : ( <> Test )} handleEditProvider(provider)} > {t.common.edit} {!provider.isDefault && ( <> setDefaultProvider(provider.id)} > Set Default handleDeleteProvider(provider)} > {t.common.delete} )} ); return ( {/* Header Actions */} {isSyncing ? ( ) : ( <> Sync All )} { resetForm(); setShowAddModal(true); }} > Add Provider {/* Info Card */} Connect multiple bus operators to search and book across different companies. Each provider needs an API URL to their Odoo backend. {/* Providers List */} Connected Providers ({providers.length}) {providers.map((provider) => ( ))} {providers.length === 0 && ( No providers configured Add your first bus operator to start searching for trips )} {/* Add/Edit Provider Modal */} {editingProvider ? 'Edit Provider' : 'Add New Provider'} { setShowAddModal(false); resetForm(); }}> Provider ID * setFormData({ ...formData, name: text })} autoCapitalize="none" /> Display Name * setFormData({ ...formData, displayName: text })} /> API URL * setFormData({ ...formData, apiUrl: text })} autoCapitalize="none" keyboardType="url" /> API Key (optional) setFormData({ ...formData, apiKey: text })} autoCapitalize="none" secureTextEntry /> Logo URL (optional) setFormData({ ...formData, logoUrl: text })} autoCapitalize="none" keyboardType="url" /> Brand Color {['#e94560', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#34495e'].map( (color) => ( setFormData({ ...formData, primaryColor: color })} > {formData.primaryColor === color && ( )} ) )} { setShowAddModal(false); resetForm(); }} > {t.common.cancel} {t.common.save} ); } const createStyles = (colors: any) => StyleSheet.create({ container: { flex: 1, backgroundColor: colors.background, }, content: { padding: 16, }, headerActions: { flexDirection: 'row', gap: 12, marginBottom: 16, }, syncButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, backgroundColor: colors.secondary, borderRadius: 12, padding: 14, }, syncButtonText: { color: '#fff', fontSize: 16, fontWeight: '600', }, addButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, backgroundColor: colors.primary, borderRadius: 12, padding: 14, }, addButtonText: { color: '#fff', fontSize: 16, fontWeight: '600', }, infoCard: { flexDirection: 'row', alignItems: 'flex-start', gap: 12, backgroundColor: colors.primary + '15', borderRadius: 12, padding: 16, marginBottom: 24, }, infoText: { flex: 1, fontSize: 14, color: colors.text, lineHeight: 20, }, sectionTitle: { fontSize: 16, fontWeight: '600', color: colors.text, marginBottom: 12, }, providerCard: { backgroundColor: colors.card, borderRadius: 12, padding: 16, marginBottom: 12, }, providerHeader: { flexDirection: 'row', alignItems: 'center', gap: 12, }, providerLogo: { width: 48, height: 48, borderRadius: 8, }, providerLogoPlaceholder: { width: 48, height: 48, borderRadius: 8, justifyContent: 'center', alignItems: 'center', }, providerLogoText: { color: '#fff', fontSize: 20, fontWeight: 'bold', }, providerInfo: { flex: 1, }, providerNameRow: { flexDirection: 'row', alignItems: 'center', gap: 8, }, providerName: { fontSize: 16, fontWeight: '600', color: colors.text, }, defaultBadge: { backgroundColor: colors.primary, borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2, }, defaultBadgeText: { color: '#fff', fontSize: 10, fontWeight: '600', }, providerUrl: { fontSize: 12, color: colors.textSecondary, marginTop: 2, }, statusRow: { flexDirection: 'row', alignItems: 'center', gap: 6, marginTop: 4, }, statusDot: { width: 8, height: 8, borderRadius: 4, }, statusText: { fontSize: 12, color: colors.textSecondary, }, providerActions: { flexDirection: 'row', gap: 8, marginTop: 12, paddingTop: 12, borderTopWidth: 1, borderTopColor: colors.border, }, actionBtn: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 12, paddingVertical: 6, borderRadius: 6, backgroundColor: colors.background, }, actionBtnDanger: { backgroundColor: colors.error + '15', }, actionBtnText: { fontSize: 12, color: colors.primary, fontWeight: '500', }, emptyState: { alignItems: 'center', padding: 48, }, emptyStateText: { fontSize: 18, fontWeight: '600', color: colors.text, marginTop: 16, }, emptyStateSubtext: { fontSize: 14, color: colors.textSecondary, textAlign: 'center', marginTop: 8, }, // Modal styles modalOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'flex-end', }, modalContent: { backgroundColor: colors.card, borderTopLeftRadius: 20, borderTopRightRadius: 20, maxHeight: '90%', }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 16, borderBottomWidth: 1, borderBottomColor: colors.border, }, modalTitle: { fontSize: 18, fontWeight: '600', color: colors.text, }, modalBody: { padding: 16, maxHeight: 400, }, modalFooter: { flexDirection: 'row', gap: 12, padding: 16, borderTopWidth: 1, borderTopColor: colors.border, }, formGroup: { marginBottom: 16, }, formLabel: { fontSize: 14, fontWeight: '500', color: colors.text, marginBottom: 8, }, input: { backgroundColor: colors.background, borderRadius: 8, padding: 12, fontSize: 16, color: colors.text, }, colorPicker: { flexDirection: 'row', gap: 12, }, colorOption: { width: 40, height: 40, borderRadius: 20, justifyContent: 'center', alignItems: 'center', }, colorOptionSelected: { borderWidth: 3, borderColor: '#fff', }, cancelButton: { flex: 1, padding: 14, borderRadius: 12, backgroundColor: colors.background, alignItems: 'center', }, cancelButtonText: { fontSize: 16, fontWeight: '600', color: colors.text, }, saveButton: { flex: 1, padding: 14, borderRadius: 12, backgroundColor: colors.primary, alignItems: 'center', }, saveButtonText: { fontSize: 16, fontWeight: '600', color: '#fff', }, });