ai-helpdesk-api / MobileApp /src /screens /admin /AdminSettingsScreen.js
ritesh19180's picture
Upload folder using huggingface_hub
d96d2a2 verified
Raw
History Blame Contribute Delete
20.5 kB
import React, { useState, useEffect, useCallback } from 'react';
import {
StyleSheet, View, Text, TouchableOpacity, ScrollView,
ActivityIndicator, Switch, StatusBar, Alert
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { supabase } from '../../lib/supabase';
import { COLORS, SHADOWS } from '../../styles/theme';
import {
Cpu, Database, ShieldCheck, Save, RotateCcw,
Clock, Bell, Inbox
} from 'lucide-react-native';
import { useNotification } from '../../components/NotificationProvider';
import * as Haptics from 'expo-haptics';
const StepperControl = ({ label, value, onValueChange, min = 0.1, max = 1.0, step = 0.05, suffix = '%' }) => {
const handleDecrement = () => {
if (value > min) {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
onValueChange(Math.max(min, parseFloat((value - step).toFixed(2))));
}
};
const handleIncrement = () => {
if (value < max) {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
onValueChange(Math.min(max, parseFloat((value + step).toFixed(2))));
}
};
const displayValue = suffix === '%' ? `${(value * 100).toFixed(0)}%` : value.toFixed(0);
return (
<View style={styles.settingControlRow}>
<Text style={styles.controlLabel}>{label}</Text>
<View style={styles.stepperContainer}>
<TouchableOpacity
style={[styles.stepperBtn, value <= min && styles.disabledBtn]}
onPress={handleDecrement}
disabled={value <= min}
>
<Text style={styles.stepperBtnText}>-</Text>
</TouchableOpacity>
<View style={styles.stepperValueContainer}>
<Text style={styles.stepperValueText}>{displayValue}</Text>
</View>
<TouchableOpacity
style={[styles.stepperBtn, value >= max && styles.disabledBtn]}
onPress={handleIncrement}
disabled={value >= max}
>
<Text style={styles.stepperBtnText}>+</Text>
</TouchableOpacity>
</View>
</View>
);
};
const AdminSettingsScreen = () => {
const { success, error: toastError } = useNotification();
const [profile, setProfile] = useState(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
// Settings states
const [confidenceThreshold, setConfidenceThreshold] = useState(0.80);
const [duplicateSensitivity, setDuplicateSensitivity] = useState(0.85);
const [enableAutoResolve, setEnableAutoResolve] = useState(false);
const [autoCloseEnabled, setAutoCloseEnabled] = useState(true);
const [autoCloseDays, setAutoCloseDays] = useState(7);
const [emailNotifications, setEmailNotifications] = useState(true);
const [adminAlerts, setAdminAlerts] = useState(true);
const [digestFrequency, setDigestFrequency] = useState('daily'); // 'daily' | 'weekly'
const fetchSettings = useCallback(async () => {
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const { data: profileData } = await supabase
.from('profiles')
.select('*')
.eq('id', user.id)
.single();
if (profileData) {
setProfile(profileData);
const companyId = profileData.company_id;
if (companyId) {
// Fetch settings from system_settings
const { data: settings, error } = await supabase
.from('system_settings')
.select('*')
.eq('company_id', companyId)
.single();
if (!error && settings) {
setConfidenceThreshold(settings.ai_confidence_threshold ?? 0.80);
setDuplicateSensitivity(settings.duplicate_sensitivity ?? 0.85);
setEnableAutoResolve(settings.enable_auto_resolve ?? false);
setAutoCloseEnabled(settings.auto_close_enabled ?? true);
setAutoCloseDays(settings.auto_close_days ?? 7);
setEmailNotifications(settings.email_notifications ?? true);
setAdminAlerts(settings.admin_alerts ?? true);
setDigestFrequency(settings.digest_frequency ?? 'daily');
}
}
}
} catch (e) {
console.error('Fetch settings error:', e);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchSettings();
}, [fetchSettings]);
const handleSaveSettings = async () => {
const companyId = profile?.company_id;
if (!companyId) {
toastError("Link Error", "Your profile is not associated with any company.");
return;
}
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
setSaving(true);
try {
// Upsert into system_settings mapping to precise columns
const { error } = await supabase
.from('system_settings')
.upsert({
company_id: companyId,
ai_confidence_threshold: confidenceThreshold,
duplicate_sensitivity: duplicateSensitivity,
enable_auto_resolve: enableAutoResolve,
auto_close_enabled: autoCloseEnabled,
auto_close_days: autoCloseDays,
email_notifications: emailNotifications,
admin_alerts: adminAlerts,
digest_frequency: digestFrequency,
updated_at: new Date().toISOString()
});
if (error) {
console.error('Save settings error details:', error);
toastError("Save Failed", "Could not synchronize settings to database.");
} else {
success("Settings Saved", "AI thresholds and SLA guidelines synchronized successfully!");
}
} catch (e) {
console.error(e);
toastError("Error", "An unexpected error occurred during save.");
} finally {
setSaving(false);
}
};
const handleReset = () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
setConfidenceThreshold(0.80);
setDuplicateSensitivity(0.85);
setEnableAutoResolve(false);
setAutoCloseEnabled(true);
setAutoCloseDays(7);
setEmailNotifications(true);
setAdminAlerts(true);
setDigestFrequency('daily');
};
if (loading) {
return (
<View style={styles.loadingWrap}>
<ActivityIndicator size="large" color={COLORS.primary} />
</View>
);
}
return (
<SafeAreaView style={styles.container} edges={['top']}>
<StatusBar barStyle="dark-content" />
{/* Header */}
<View style={styles.header}>
<Text style={styles.title}>System Settings</Text>
</View>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={styles.scrollContent}>
{/* Section 1: AI Engine Benchmarks */}
<View style={styles.sectionCard}>
<View style={styles.sectionHeader}>
<Cpu size={20} color={COLORS.primary} />
<Text style={styles.sectionTitle}>AI Neural Benchmarks</Text>
</View>
<Text style={styles.sectionDesc}>
Configure standard confidence limits to bypass manual ticketing queues.
</Text>
<View style={styles.controlsList}>
<StepperControl
label="Routing Confidence"
value={confidenceThreshold}
onValueChange={setConfidenceThreshold}
min={0.1}
max={1.0}
suffix="%"
/>
<Text style={styles.helpText}>
Tickets with category classification confidence above this threshold will bypass manual triage.
</Text>
<View style={styles.separator} />
<View style={styles.settingControlRow}>
<View style={{ flex: 1, marginRight: 16 }}>
<Text style={styles.controlLabel}>AI Auto-Resolution</Text>
<Text style={styles.helpText}>
Allow the system to automatically close tickets when a verified solution RAG score exceeds 95%.
</Text>
</View>
<Switch
value={enableAutoResolve}
onValueChange={(val) => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
setEnableAutoResolve(val);
}}
trackColor={{ false: '#e5e7eb', true: COLORS.primaryLight }}
thumbColor={enableAutoResolve ? COLORS.primary : '#9ca3af'}
/>
</View>
</View>
</View>
{/* Section 2: Duplication Parameters */}
<View style={styles.sectionCard}>
<View style={styles.sectionHeader}>
<Database size={20} color="#8b5cf6" />
<Text style={styles.sectionTitle}>Duplicate Mitigation</Text>
</View>
<Text style={styles.sectionDesc}>
Adjust semantic limits to identify duplicate support inquiries automatically.
</Text>
<View style={styles.controlsList}>
<StepperControl
label="Sensitivity Threshold"
value={duplicateSensitivity}
onValueChange={setDuplicateSensitivity}
min={0.5}
max={1.0}
suffix="%"
/>
<Text style={styles.helpText}>
A higher percentage demands exact word matching, while a lower percentage flags conceptual duplicates.
</Text>
</View>
</View>
{/* Section 3: Auto-Close Tickets */}
<View style={styles.sectionCard}>
<View style={styles.sectionHeader}>
<Inbox size={20} color="#06b6d4" />
<Text style={styles.sectionTitle}>Auto-Close Settings</Text>
</View>
<Text style={styles.sectionDesc}>
Configure standard procedures for resolving ticket inactivity.
</Text>
<View style={styles.controlsList}>
<View style={styles.settingControlRow}>
<View style={{ flex: 1, marginRight: 16 }}>
<Text style={styles.controlLabel}>Auto-Close Inactive</Text>
<Text style={styles.helpText}>
Automatically archive resolved tickets after a period of user inactivity.
</Text>
</View>
<Switch
value={autoCloseEnabled}
onValueChange={(val) => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
setAutoCloseEnabled(val);
}}
trackColor={{ false: '#e5e7eb', true: COLORS.primaryLight }}
thumbColor={autoCloseEnabled ? COLORS.primary : '#9ca3af'}
/>
</View>
{autoCloseEnabled && (
<>
<View style={styles.separator} />
<StepperControl
label="Inactivity Days"
value={autoCloseDays}
onValueChange={setAutoCloseDays}
min={1}
max={30}
step={1}
suffix=""
/>
</>
)}
</View>
</View>
{/* Section 4: Notifications and Digests */}
<View style={styles.sectionCard}>
<View style={styles.sectionHeader}>
<Bell size={20} color="#f59e0b" />
<Text style={styles.sectionTitle}>Notifications & Digests</Text>
</View>
<Text style={styles.sectionDesc}>
Configure administrator alert guidelines and scheduled digest updates.
</Text>
<View style={styles.controlsList}>
<View style={styles.settingControlRow}>
<View style={{ flex: 1, marginRight: 16 }}>
<Text style={styles.controlLabel}>Email Notifications</Text>
<Text style={styles.helpText}>Receive summary digests and incident reports via work email.</Text>
</View>
<Switch
value={emailNotifications}
onValueChange={(val) => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
setEmailNotifications(val);
}}
trackColor={{ false: '#e5e7eb', true: COLORS.primaryLight }}
thumbColor={emailNotifications ? COLORS.primary : '#9ca3af'}
/>
</View>
<View style={styles.separator} />
<View style={styles.settingControlRow}>
<View style={{ flex: 1, marginRight: 16 }}>
<Text style={styles.controlLabel}>Critical Admin Alerts</Text>
<Text style={styles.helpText}>Enable push notifications for critical Priority 1 events.</Text>
</View>
<Switch
value={adminAlerts}
onValueChange={(val) => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
setAdminAlerts(val);
}}
trackColor={{ false: '#e5e7eb', true: COLORS.primaryLight }}
thumbColor={adminAlerts ? COLORS.primary : '#9ca3af'}
/>
</View>
{emailNotifications && (
<>
<View style={styles.separator} />
<View style={styles.settingControlRow}>
<Text style={styles.controlLabel}>Digest Frequency</Text>
<View style={styles.frequencyPicker}>
<TouchableOpacity
style={[styles.freqBtn, digestFrequency === 'daily' && styles.activeFreqBtn]}
onPress={() => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
setDigestFrequency('daily');
}}
>
<Text style={[styles.freqBtnText, digestFrequency === 'daily' && styles.activeFreqBtnText]}>Daily</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.freqBtn, digestFrequency === 'weekly' && styles.activeFreqBtn]}
onPress={() => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
setDigestFrequency('weekly');
}}
>
<Text style={[styles.freqBtnText, digestFrequency === 'weekly' && styles.activeFreqBtnText]}>Weekly</Text>
</TouchableOpacity>
</View>
</View>
</>
)}
</View>
</View>
{/* Section 5: SLA Guidelines Summary */}
<View style={styles.sectionCard}>
<View style={styles.sectionHeader}>
<ShieldCheck size={20} color="#ea580c" />
<Text style={styles.sectionTitle}>SLA Policies & Breach Targets</Text>
</View>
<Text style={styles.sectionDesc}>
Configure escalation times based on priority categories.
</Text>
<View style={styles.policyRow}>
<View style={[styles.policyDot, { backgroundColor: '#ef4444' }]} />
<Text style={styles.policyText}>Critical Priority SLA Breach target: <Text style={styles.boldText}>2 Hours</Text></Text>
</View>
<View style={styles.policyRow}>
<View style={[styles.policyDot, { backgroundColor: '#fbbf24' }]} />
<Text style={styles.policyText}>High Priority SLA Breach target: <Text style={styles.boldText}>8 Hours</Text></Text>
</View>
<View style={styles.policyRow}>
<View style={[styles.policyDot, { backgroundColor: '#3b82f6' }]} />
<Text style={styles.policyText}>Medium Priority SLA Breach target: <Text style={styles.boldText}>24 Hours</Text></Text>
</View>
<View style={[styles.policyRow, { marginBottom: 8 }]}>
<View style={[styles.policyDot, { backgroundColor: '#10b981' }]} />
<Text style={styles.policyText}>Low Priority SLA Breach target: <Text style={styles.boldText}>72 Hours</Text></Text>
</View>
</View>
{/* Action buttons */}
<View style={styles.btnRow}>
<TouchableOpacity
style={styles.resetBtn}
onPress={handleReset}
disabled={saving}
>
<RotateCcw size={18} color={COLORS.textLight} />
<Text style={styles.resetBtnText}>Reset Defaults</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.saveBtn}
onPress={handleSaveSettings}
disabled={saving}
>
{saving ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<>
<Save size={18} color="#fff" />
<Text style={styles.saveBtnText}>Save Settings</Text>
</>
)}
</TouchableOpacity>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: COLORS.background },
loadingWrap: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: COLORS.background },
header: { paddingHorizontal: 24, paddingTop: 16, paddingBottom: 16 },
title: { fontSize: 28, fontWeight: '900', color: COLORS.text, letterSpacing: -0.8 },
scrollContent: { paddingBottom: 120 },
// Section card
sectionCard: {
backgroundColor: '#fff',
borderRadius: 24,
marginHorizontal: 20,
padding: 20,
marginBottom: 16,
borderWidth: 1,
borderColor: 'rgba(0,0,0,0.03)',
...SHADOWS.soft,
},
sectionHeader: { flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 8 },
sectionTitle: { fontSize: 16, fontWeight: '900', color: COLORS.text },
sectionDesc: { fontSize: 12.5, color: COLORS.textMuted, fontWeight: '600', lineHeight: 18, marginBottom: 16 },
// Controls list
controlsList: { gap: 12 },
settingControlRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
controlLabel: { fontSize: 14, fontWeight: '800', color: COLORS.text },
// Steppers
stepperContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#f3f4f6', borderRadius: 12, padding: 4, height: 40 },
stepperBtn: { width: 32, height: 32, borderRadius: 8, backgroundColor: '#fff', justifyContent: 'center', alignItems: 'center', ...SHADOWS.soft },
disabledBtn: { opacity: 0.4 },
stepperBtnText: { fontSize: 16, fontWeight: '800', color: COLORS.text },
stepperValueContainer: { minWidth: 60, alignItems: 'center', justifyContent: 'center' },
stepperValueText: { fontSize: 13, fontWeight: '800', color: COLORS.text },
frequencyPicker: { flexDirection: 'row', backgroundColor: '#f3f4f6', padding: 3, borderRadius: 10 },
freqBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8 },
activeFreqBtn: { backgroundColor: '#fff', ...SHADOWS.soft },
freqBtnText: { fontSize: 11, fontWeight: '700', color: COLORS.textMuted },
activeFreqBtnText: { color: COLORS.text, fontWeight: '900' },
helpText: { fontSize: 11.5, color: COLORS.textMuted, fontWeight: '500', lineHeight: 16, marginTop: 4 },
separator: { height: 1, backgroundColor: 'rgba(0,0,0,0.05)', marginVertical: 8 },
policyRow: { flexDirection: 'row', alignItems: 'center', gap: 10, marginVertical: 6, paddingHorizontal: 4 },
policyDot: { width: 8, height: 8, borderRadius: 4 },
policyText: { fontSize: 13, color: COLORS.textLight, fontWeight: '500' },
boldText: { fontWeight: '800', color: COLORS.text },
// Buttons
btnRow: { flexDirection: 'row', gap: 12, marginHorizontal: 20, marginTop: 8 },
resetBtn: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 8,
backgroundColor: '#fff',
height: 52,
borderRadius: 18,
borderWidth: 1.5,
borderColor: 'rgba(0,0,0,0.08)',
...SHADOWS.soft
},
resetBtnText: { fontSize: 14, fontWeight: '800', color: COLORS.textLight },
saveBtn: {
flex: 1.5,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 8,
backgroundColor: COLORS.primary,
height: 52,
borderRadius: 18,
...SHADOWS.medium,
shadowColor: COLORS.primary
},
saveBtnText: { fontSize: 14, fontWeight: '800', color: '#fff' }
});
export default AdminSettingsScreen;