aegis-fraud-detector / frontend /src /components /ManualQueryForm.jsx
WizardCoder2007's picture
Fresh deploy with clean history
31b612e
Raw
History Blame Contribute Delete
10 kB
import React, { useState } from 'react';
import { Shield, ShieldAlert, ShieldCheck, RefreshCw, Zap } from 'lucide-react';
// Preset values for testing
const PRESETS = {
legitimate: {
F3888: '2026-06-08 10:00:00', // Date
F3886: 'Savings', // Account Type
F3887: '170', // Account Code
F3889: 'G365D', // Customer Code
F3890: 'R', // Account State
F3891: 'salaried', // Job Status
F3892: 'M', // Gender
F3893: 'RETAIL', // Channel
F3894: '30', // Tx Interval
F3895: '600', // Tx Amount
F3896: '600', // Daily Limit
F3897: '0'
},
suspicious: {
// For fraud, we set features that would tip off the model, e.g. very short interval, high amount, unemployed, etc.
F3888: '2026-06-08 23:45:00', // Late night
F3886: 'Savings',
F3887: '999', // Different account code
F3889: 'X999Z',
F3890: 'S', // Suspicious state
F3891: 'unemployed', // Unemployed job status
F3892: 'F',
F3893: 'INTERNET', // Internet channel
F3894: '1', // Tx Interval of 1 second (very high speed!)
F3895: '50000', // High transaction amount
F3896: '100000', // High limit
F3897: '1' // Alarm flag active
}
};
export default function ManualQueryForm({ onSubmit, modelName, threshold }) {
const [formData, setFormData] = useState(PRESETS.legitimate);
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
const handleInputChange = (key, value) => {
setFormData(prev => ({
...prev,
[key]: value
}));
};
const loadPreset = (type) => {
setFormData(PRESETS[type]);
setResult(null);
};
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setResult(null);
try {
// Map form string numbers to float/int where appropriate
const mappedData = {};
Object.keys(formData).forEach(key => {
const val = formData[key];
if (!isNaN(val) && val.trim() !== '') {
mappedData[key] = val.includes('.') ? parseFloat(val) : parseInt(val);
} else {
mappedData[key] = val;
}
});
const res = await onSubmit(mappedData);
setResult(res);
} catch (err) {
console.error(err);
alert('Prediction query failed. Ensure backend is running.');
} finally {
setLoading(false);
}
};
return (
<div className="glass-card animate-fade-in" style={{ height: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Shield style={{ color: 'var(--primary)' }} />
<h2 style={{ fontSize: '1.25rem', margin: 0 }}>Real-Time Inspector</h2>
</div>
<div style={{ display: 'flex', gap: '8px' }}>
<button
type="button"
className="btn btn-secondary"
style={{ padding: '6px 12px', fontSize: '0.75rem', borderColor: 'var(--success)' }}
onClick={() => loadPreset('legitimate')}
>
Safe Preset
</button>
<button
type="button"
className="btn btn-secondary"
style={{ padding: '6px 12px', fontSize: '0.75rem', borderColor: 'var(--danger)' }}
onClick={() => loadPreset('suspicious')}
>
Suspicious Preset
</button>
</div>
</div>
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
<div>
<label className="form-label">Date-Time (F3888)</label>
<input
type="text"
className="form-input"
value={formData.F3888}
onChange={(e) => handleInputChange('F3888', e.target.value)}
required
/>
</div>
<div>
<label className="form-label">Account Type (F3886)</label>
<select
className="form-input"
value={formData.F3886}
onChange={(e) => handleInputChange('F3886', e.target.value)}
>
<option value="Savings">Savings</option>
<option value="Current">Current</option>
<option value="Salary">Salary</option>
<option value="Corporate">Corporate</option>
</select>
</div>
<div>
<label className="form-label">Channel (F3893)</label>
<select
className="form-input"
value={formData.F3893}
onChange={(e) => handleInputChange('F3893', e.target.value)}
>
<option value="RETAIL">Retail / Branch</option>
<option value="INTERNET">Internet Banking</option>
<option value="MOBILE">Mobile App</option>
<option value="ATM">ATM Withdrawal</option>
</select>
</div>
<div>
<label className="form-label">Job Status (F3891)</label>
<select
className="form-input"
value={formData.F3891}
onChange={(e) => handleInputChange('F3891', e.target.value)}
>
<option value="salaried">Salaried Employee</option>
<option value="selfemployed">Self Employed</option>
<option value="student">Student</option>
<option value="unemployed">Unemployed</option>
</select>
</div>
<div>
<label className="form-label">Tx Interval Sec (F3894)</label>
<input
type="number"
className="form-input"
value={formData.F3894}
onChange={(e) => handleInputChange('F3894', e.target.value)}
required
/>
</div>
<div>
<label className="form-label">Tx Amount (F3895)</label>
<input
type="number"
className="form-input"
value={formData.F3895}
onChange={(e) => handleInputChange('F3895', e.target.value)}
required
/>
</div>
<div>
<label className="form-label">Daily Limit (F3896)</label>
<input
type="number"
className="form-input"
value={formData.F3896}
onChange={(e) => handleInputChange('F3896', e.target.value)}
required
/>
</div>
<div>
<label className="form-label">Account State (F3890)</label>
<input
type="text"
className="form-input"
value={formData.F3890}
onChange={(e) => handleInputChange('F3890', e.target.value)}
required
/>
</div>
</div>
<button
type="submit"
className="btn btn-primary"
style={{ width: '100%', padding: '12px', marginTop: '8px' }}
disabled={loading}
>
{loading ? (
<>
<RefreshCw size={18} style={{ animation: 'spin 1.5s linear infinite' }} />
Running AI Risk Check...
</>
) : (
<>
<Zap size={18} />
Evaluate Transaction
</>
)}
</button>
</form>
{/* Real-time result display */}
{result && (
<div className="animate-fade-in" style={{
marginTop: '20px',
padding: '16px',
borderRadius: '10px',
background: result.is_suspicious ? 'rgba(239, 68, 68, 0.08)' : 'rgba(16, 185, 129, 0.08)',
border: `1px solid ${result.is_suspicious ? 'rgba(239, 68, 68, 0.2)' : 'rgba(16, 185, 129, 0.2)'}`
}}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '10px' }}>
<span style={{ fontSize: '0.85rem', fontWeight: 600, color: 'var(--text-secondary)' }}>DECISION RESULT</span>
{result.is_suspicious ? (
<span className="badge badge-danger" style={{ display: 'flex', gap: '4px' }}>
<ShieldAlert size={12} /> SUSPICIOUS
</span>
) : (
<span className="badge badge-success" style={{ display: 'flex', gap: '4px' }}>
<ShieldCheck size={12} /> LEGITIMATE
</span>
)}
</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '8px', marginBottom: '8px' }}>
<span style={{ fontSize: '2rem', fontWeight: 800, color: result.is_suspicious ? 'var(--danger)' : 'var(--success)' }}>
{Math.round(result.probability * 100)}%
</span>
<span style={{ fontSize: '0.875rem', color: 'var(--text-secondary)' }}>Mule / Fraud Risk Score</span>
</div>
<div style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', lineHeight: '1.4' }}>
{result.is_suspicious ? (
<strong>⚠️ Threat Warning:</strong>
) : (
<strong>✅ Normal activity:</strong>
)}{' '}
The system evaluated this transaction as{' '}
<span style={{ color: result.is_suspicious ? 'var(--danger)' : 'var(--success)', fontWeight: 600 }}>
{result.risk_level} Risk
</span>{' '}
using the {modelName} model at threshold {threshold}.
</div>
</div>
)}
</div>
);
}