chowdary1's picture
Fix: Remove ALL localhost:8000 hardcoded URLs - use relative paths for production
91be275
Raw History Blame Contribute Delete
49.1 kB
/**
* Questions Browser Panel - Admin Panel for Managing Interview Questions
*
* FLOW:
* 1. Click Questions → See all branches
* 2. Click Branch → See all domains for that branch
* 3. Click Domain → See all modes (friendly, balanced, strict, final_boss)
* 4. Click Mode → See questions with Add/Remove buttons
* 5. Add Question → Form with question and answer fields
*/
import React, { useState, useEffect, useCallback } from 'react';
import {
HelpCircle, Search, ChevronRight, ChevronDown,
Loader2, RefreshCw, AlertCircle, BookOpen,
Smile, Scale, Target, Skull, Plus, Trash2, X,
GitBranch, Briefcase, BarChart2, ChevronLeft, Save,
Cpu, Zap, Settings, Building, Layers, PenTool,
Database, Brain
} from 'lucide-react';
import { getToken } from '../../services/api';
import './QuestionsPanel.css';
const API_BASE = import.meta.env.VITE_API_URL || '';
// Branch icons mapping
const BRANCH_ICONS = {
computer_science: Cpu,
electronics: Zap,
electrical: Zap,
mechanical: Settings,
civil: Building,
architecture: PenTool,
};
// Interview modes with their metadata
const MODES = [
{
id: 'friendly',
name: 'Friendly',
icon: Smile,
color: '#10b981',
description: 'Easy conversational questions'
},
{
id: 'balanced',
name: 'Balanced',
icon: Scale,
color: '#3b82f6',
description: 'Mix of easy and moderate questions'
},
{
id: 'strict',
name: 'Strict',
icon: Target,
color: '#f59e0b',
description: 'Technical deep-dive questions'
},
{
id: 'final_boss',
name: 'Final Boss',
icon: Skull,
color: '#ef4444',
description: 'Most challenging questions'
},
];
function QuestionsPanel() {
// Navigation state - tracks current step
const [currentStep, setCurrentStep] = useState('branches'); // branches, domains, modes, source, questions
// Selection state
const [selectedBranch, setSelectedBranch] = useState(null);
const [selectedDomain, setSelectedDomain] = useState(null);
const [selectedMode, setSelectedMode] = useState(null);
const [selectedSource, setSelectedSource] = useState('collected'); // 'collected' or 'rag'
// Data state
const [branches, setBranches] = useState([]);
const [domains, setDomains] = useState([]);
const [questions, setQuestions] = useState([]);
const [stats, setStats] = useState(null);
// Loading states
const [branchesLoading, setBranchesLoading] = useState(true);
const [domainsLoading, setDomainsLoading] = useState(false);
const [questionsLoading, setQuestionsLoading] = useState(false);
const [statsLoading, setStatsLoading] = useState(true);
// Search and pagination
const [searchQuery, setSearchQuery] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [totalQuestions, setTotalQuestions] = useState(0);
const [totalPages, setTotalPages] = useState(1);
const pageSize = 20;
// UI state
const [error, setError] = useState(null);
const [expandedQuestion, setExpandedQuestion] = useState(null);
// Add Question Modal state
const [showAddModal, setShowAddModal] = useState(false);
const [newQuestion, setNewQuestion] = useState({ question: '', answer: '' });
const [addingQuestion, setAddingQuestion] = useState(false);
/**
* Fetch overall statistics
*/
const fetchStats = useCallback(async () => {
setStatsLoading(true);
try {
const token = getToken();
const response = await fetch(`${API_BASE}/api/admin/questions/stats`, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
if (data.success) {
setStats(data.stats);
}
}
} catch (err) {
console.error('Error fetching stats:', err);
} finally {
setStatsLoading(false);
}
}, []);
/**
* Fetch available branches
*/
const fetchBranches = useCallback(async () => {
setBranchesLoading(true);
setError(null);
try {
const token = getToken();
const response = await fetch(`${API_BASE}/api/admin/questions/branches`, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
if (data.success) {
setBranches(data.branches || []);
}
} else {
throw new Error('Failed to fetch branches');
}
} catch (err) {
console.error('Error fetching branches:', err);
setError('Failed to load branches');
} finally {
setBranchesLoading(false);
}
}, []);
/**
* Fetch domains for a specific branch
*/
const fetchDomains = useCallback(async (branchId) => {
setDomainsLoading(true);
setError(null);
try {
const token = getToken();
const url = `${API_BASE}/api/admin/questions/domains?branch=${branchId}`;
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
if (data.success) {
setDomains(data.domains || []);
}
} else {
throw new Error('Failed to fetch domains');
}
} catch (err) {
console.error('Error fetching domains:', err);
setError('Failed to load domains');
} finally {
setDomainsLoading(false);
}
}, []);
/**
* Fetch questions based on current selection
*/
const fetchQuestions = useCallback(async () => {
if (!selectedMode || !selectedBranch || !selectedDomain) return;
setQuestionsLoading(true);
setError(null);
try {
const token = getToken();
const params = new URLSearchParams({
mode: selectedMode,
page: currentPage.toString(),
limit: pageSize.toString(),
branch: selectedBranch,
domain: selectedDomain
});
if (searchQuery) params.append('search', searchQuery);
// Use RAG endpoint for RAG source, otherwise use collected questions
const endpoint = selectedSource === 'rag'
? `${API_BASE}/api/admin/questions/rag?${params}`
: `${API_BASE}/api/admin/questions/all?${params}`;
const response = await fetch(endpoint, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
if (data.success) {
// DEBUG: Log question sources
console.log('[QuestionsPanel] Received questions:', data.questions?.length);
console.log('[QuestionsPanel] Full first question:', data.questions?.[0]);
console.log('[QuestionsPanel] Sample sources:', data.questions?.slice(0, 3).map(q => ({
id: q.id,
source: q.source,
question: q.question?.substring(0, 50) + '...',
question_text: q.question_text?.substring(0, 50) + '...',
})));
setQuestions(data.questions || []);
setTotalQuestions(data.total || 0);
setTotalPages(data.total_pages || 1);
}
} else {
throw new Error('Failed to fetch questions');
}
} catch (err) {
console.error('Error fetching questions:', err);
setError('Failed to load questions');
setQuestions([]);
} finally {
setQuestionsLoading(false);
}
}, [selectedMode, selectedBranch, selectedDomain, searchQuery, currentPage, pageSize, selectedSource]);
// Initial load - fetch branches
useEffect(() => {
fetchStats();
fetchBranches();
}, [fetchStats, fetchBranches]);
// Fetch questions when on questions step
useEffect(() => {
if (currentStep === 'questions' && selectedMode) {
fetchQuestions();
}
}, [currentStep, selectedMode, fetchQuestions, currentPage, searchQuery]);
/**
* Handle branch selection
*/
const handleBranchSelect = (branch) => {
setSelectedBranch(branch.id);
setSelectedDomain(null);
setSelectedMode(null);
setQuestions([]);
fetchDomains(branch.id);
setCurrentStep('domains');
};
/**
* Handle domain selection
*/
const handleDomainSelect = (domain) => {
setSelectedDomain(domain.id);
setSelectedMode(null);
setQuestions([]);
setCurrentStep('modes');
};
/**
* Handle mode selection
*/
const handleModeSelect = (modeId) => {
setSelectedMode(modeId);
setCurrentPage(1);
setSearchQuery('');
// For strict and final_boss, show source selection
// For friendly and balanced, go directly to questions (collected only)
if (modeId === 'strict' || modeId === 'final_boss') {
setCurrentStep('source');
} else {
setSelectedSource('collected');
setCurrentStep('questions');
}
};
/**
* Handle source selection (Collected vs RAG)
*/
const handleSourceSelect = (source) => {
setSelectedSource(source);
setCurrentPage(1);
setSearchQuery('');
setCurrentStep('questions');
};
/**
* Go back to previous step
*/
const handleBack = () => {
if (currentStep === 'questions') {
// If came from source selection, go back there
if (selectedMode === 'strict' || selectedMode === 'final_boss') {
setCurrentStep('source');
} else {
setCurrentStep('modes');
setSelectedMode(null);
}
setQuestions([]);
} else if (currentStep === 'source') {
setCurrentStep('modes');
setSelectedMode(null);
setSelectedSource('collected');
} else if (currentStep === 'modes') {
setCurrentStep('domains');
setSelectedDomain(null);
} else if (currentStep === 'domains') {
setCurrentStep('branches');
setSelectedBranch(null);
setDomains([]);
}
};
/**
* Get breadcrumb path
*/
const getBreadcrumb = () => {
const parts = [];
if (selectedBranch) {
const branch = branches.find(b => b.id === selectedBranch);
parts.push({ label: branch?.shortName || branch?.name || selectedBranch, type: 'branch' });
}
if (selectedDomain) {
const domain = domains.find(d => d.id === selectedDomain);
parts.push({ label: domain?.name || selectedDomain, type: 'domain' });
}
if (selectedMode) {
const mode = MODES.find(m => m.id === selectedMode);
parts.push({ label: mode?.name || selectedMode, type: 'mode' });
}
if (currentStep === 'questions' && (selectedMode === 'strict' || selectedMode === 'final_boss')) {
parts.push({
label: selectedSource === 'rag' ? 'RAG Questions' : 'Collected Questions',
type: 'source'
});
}
return parts;
};
/**
* Handle add question
*/
const handleAddQuestion = async () => {
if (!newQuestion.question.trim()) {
alert('Please enter a question');
return;
}
setAddingQuestion(true);
try {
const token = getToken();
const response = await fetch(`${API_BASE}/api/admin/questions/add`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
question: newQuestion.question,
answer: newQuestion.answer,
branch: selectedBranch,
domain: selectedDomain,
mode: selectedMode,
difficulty: selectedMode === 'friendly' ? 'easy' :
selectedMode === 'final_boss' ? 'hard' : 'medium'
})
});
if (response.ok) {
setShowAddModal(false);
setNewQuestion({ question: '', answer: '' });
// Refresh questions
fetchQuestions();
} else {
const data = await response.json();
alert(data.detail || 'Failed to add question');
}
} catch (err) {
console.error('Error adding question:', err);
alert('Failed to add question');
} finally {
setAddingQuestion(false);
}
};
/**
* Handle delete question
*/
const handleDeleteQuestion = async (questionId) => {
if (!confirm('Are you sure you want to delete this question?')) {
return;
}
try {
const token = getToken();
const response = await fetch(`${API_BASE}/api/admin/questions/${questionId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
// Remove from local state
setQuestions(questions.filter(q => q.id !== questionId));
setTotalQuestions(prev => prev - 1);
} else {
const data = await response.json();
alert(data.detail || 'Failed to delete question');
}
} catch (err) {
console.error('Error deleting question:', err);
alert('Failed to delete question');
}
};
/**
* Render Step 1: Branch Selection
*/
const renderBranchSelection = () => (
<div className="selection-grid">
<h3 className="selection-title">
<GitBranch size={20} />
Select Branch
</h3>
<p className="selection-hint">Choose an engineering branch to view its domains</p>
{branchesLoading ? (
<div className="loading-state">
<Loader2 size={32} className="spin" />
<p>Loading branches...</p>
</div>
) : (
<div className="cards-grid branches-grid">
{branches.map((branch) => {
const Icon = BRANCH_ICONS[branch.id] || Layers;
return (
<button
key={branch.id}
className="selection-card branch-card"
onClick={() => handleBranchSelect(branch)}
>
<div className="card-icon">
<Icon size={28} />
</div>
<div className="card-info">
<span className="card-name">{branch.name}</span>
<span className="card-short">{branch.shortName}</span>
</div>
<ChevronRight size={20} className="card-arrow" />
</button>
);
})}
</div>
)}
{/* Stats Overview */}
{stats && !statsLoading && (
<div className="stats-overview">
<h4><BarChart2 size={16} /> Question Statistics</h4>
<div className="stats-grid">
<div className="stat-item highlight">
<span className="stat-value">{stats.total || 0}</span>
<span className="stat-label">Total Questions</span>
</div>
<div className="stat-item">
<span className="stat-value">{stats.collected?.by_difficulty?.easy || 0}</span>
<span className="stat-label">Easy</span>
</div>
<div className="stat-item">
<span className="stat-value">{stats.collected?.by_difficulty?.medium || 0}</span>
<span className="stat-label">Medium</span>
</div>
<div className="stat-item">
<span className="stat-value">{stats.collected?.by_difficulty?.hard || 0}</span>
<span className="stat-label">Hard</span>
</div>
</div>
</div>
)}
</div>
);
/**
* Render Step 2: Domain Selection
*/
const renderDomainSelection = () => (
<div className="selection-grid">
<h3 className="selection-title">
<Briefcase size={20} />
Select Domain
</h3>
<p className="selection-hint">Choose a specific role/domain</p>
{domainsLoading ? (
<div className="loading-state">
<Loader2 size={32} className="spin" />
<p>Loading domains...</p>
</div>
) : domains.length === 0 ? (
<div className="empty-state">
<Briefcase size={48} />
<p>No domains found for this branch</p>
</div>
) : (
<div className="cards-grid domains-grid">
{domains.map((domain) => (
<button
key={domain.id}
className="selection-card domain-card"
onClick={() => handleDomainSelect(domain)}
>
<div className="card-info">
<span className="card-name">{domain.name}</span>
<span className="card-id">{domain.id}</span>
</div>
<ChevronRight size={20} className="card-arrow" />
</button>
))}
</div>
)}
</div>
);
/**
* Render Step 3: Mode Selection
*/
const renderModeSelection = () => (
<div className="selection-grid">
<h3 className="selection-title">
<BookOpen size={20} />
Select Interview Mode
</h3>
<p className="selection-hint">Choose difficulty level for questions</p>
<div className="cards-grid modes-grid">
{MODES.map((mode) => {
const Icon = mode.icon;
return (
<button
key={mode.id}
className="selection-card mode-card"
onClick={() => handleModeSelect(mode.id)}
style={{ '--mode-color': mode.color }}
>
<div className="card-icon mode-icon">
<Icon size={28} />
</div>
<div className="card-info">
<span className="card-name">{mode.name}</span>
<span className="card-description">{mode.description}</span>
</div>
<ChevronRight size={20} className="card-arrow" />
</button>
);
})}
</div>
</div>
);
/**
* Render Step 3.5: Source Selection (for Strict/Final Boss)
*/
const renderSourceSelection = () => {
const currentMode = MODES.find(m => m.id === selectedMode);
return (
<div className="selection-grid">
<h3 className="selection-title">
<Database size={20} />
Select Question Source
</h3>
<p className="selection-hint">
Choose where to load questions from for <strong style={{ color: currentMode?.color }}>{currentMode?.name}</strong> mode
</p>
<div className="cards-grid source-grid">
{/* Collected Questions */}
<button
className="selection-card source-card collected"
onClick={() => handleSourceSelect('collected')}
>
<div className="card-icon source-icon">
<Database size={32} />
</div>
<div className="card-info">
<span className="card-name">Collected Questions</span>
<span className="card-description">
Pre-harvested questions from JSON files.
These are curated interview questions organized by domain.
</span>
</div>
<ChevronRight size={20} className="card-arrow" />
</button>
{/* RAG Questions */}
<button
className="selection-card source-card rag"
onClick={() => handleSourceSelect('rag')}
>
<div className="card-icon source-icon rag-icon">
<Brain size={32} />
</div>
<div className="card-info">
<span className="card-name">RAG Questions</span>
<span className="card-description">
Questions extracted from uploaded PDFs via AI training.
Auto-classified into branches and domains.
</span>
<span className="card-badge rag-badge">
<Brain size={12} /> AI-Trained
</span>
</div>
<ChevronRight size={20} className="card-arrow" />
</button>
</div>
<div className="source-info">
<AlertCircle size={16} />
<span>
RAG questions are generated when you upload PDFs and click "Train RAG" in the RAG Management panel.
The system automatically extracts and classifies questions into the correct branch and domain.
</span>
</div>
</div>
);
};
/**
* Render Step 4: Questions List
*/
const renderQuestionsList = () => {
const currentMode = MODES.find(m => m.id === selectedMode);
return (
<div className="questions-section">
{/* Action Bar */}
<div className="questions-action-bar">
<div className="search-box">
<Search size={18} />
<input
type="text"
placeholder="Search questions..."
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value);
setCurrentPage(1);
}}
/>
{searchQuery && (
<button
className="clear-search"
onClick={() => setSearchQuery('')}
>
<X size={16} />
</button>
)}
</div>
{/* Show RAG indicator when viewing RAG questions */}
{selectedSource === 'rag' && (
<span className="rag-indicator">
<Brain size={14} />
RAG Questions
</span>
)}
{/* Only show Add button for collected questions */}
{selectedSource !== 'rag' && (
<button
className="btn btn-primary add-question-btn"
onClick={() => setShowAddModal(true)}
>
<Plus size={18} />
Add Question
</button>
)}
</div>
{/* Questions count */}
<div className="questions-count">
<span>{totalQuestions} questions found</span>
{totalPages > 1 && (
<span className="page-info">
Page {currentPage} of {totalPages}
</span>
)}
</div>
{/* Loading state */}
{questionsLoading ? (
<div className="loading-state">
<Loader2 size={32} className="spin" />
<p>Loading questions...</p>
</div>
) : questions.length === 0 ? (
<div className="empty-state">
{selectedSource === 'rag' ? (
<>
<Brain size={48} />
<p>No RAG questions found</p>
<span>
Upload PDFs in the RAG Management panel and click "Train RAG" to generate questions.
Questions will be auto-classified into the correct branch and domain.
</span>
</>
) : (
<>
<HelpCircle size={48} />
<p>No questions found</p>
<span>Try adjusting your search or add new questions</span>
<button
className="btn btn-primary"
onClick={() => setShowAddModal(true)}
>
<Plus size={18} />
Add First Question
</button>
</>
)}
</div>
) : (
<>
{/* Questions list */}
<div className="questions-list">
{questions.map((question, index) => (
<div
key={question.id || index}
className={`question-card ${expandedQuestion === index ? 'expanded' : ''} ${selectedSource === 'rag' ? 'rag-question' : ''}`}
>
<div
className="question-header"
onClick={() => setExpandedQuestion(expandedQuestion === index ? null : index)}
>
{/* Source Badge: R=RAG, D=Database, A=API, F=Fallback - Always visible */}
{(() => {
// Determine badge class and text
let badgeClass = 'database';
let badgeText = 'D';
let badgeTitle = 'D = Database Question (collected)';
if (selectedSource === 'rag') {
badgeClass = 'rag';
badgeText = 'R';
badgeTitle = 'R = RAG Question (from trained PDFs)';
} else if (question.source === 'openai_api') {
badgeClass = 'api';
badgeText = 'A';
badgeTitle = 'A = API Generated Question (OpenAI)';
} else if (question.source === 'rag_engine') {
badgeClass = 'rag';
badgeText = 'R';
badgeTitle = 'R = RAG Question (from trained PDFs)';
} else if (question.source === 'fallback') {
badgeClass = 'fallback';
badgeText = 'F';
badgeTitle = 'F = Fallback Question (template)';
}
return (
<span
className={`source-badge source-${badgeClass}`}
title={badgeTitle}
data-source={question.source}
>
{badgeText}
</span>
);
})()}
<span className="question-number">#{(currentPage - 1) * pageSize + index + 1}</span>
<span className="question-text">
{question.question || question.question_text || '[No question text available]'}
</span>
<div className="question-actions">
{/* Only show delete for collected questions */}
{selectedSource !== 'rag' && (
<button
type="button"
className="action-btn delete-btn"
onClick={(e) => {
e.stopPropagation();
handleDeleteQuestion(question.id);
}}
title="Delete question"
>
<Trash2 size={16} />
</button>
)}
<button type="button" className="expand-btn">
{expandedQuestion === index ? <ChevronDown size={18} /> : <ChevronRight size={18} />}
</button>
</div>
</div>
{expandedQuestion === index && (
<div className="question-details">
{(question.answer || question.expected_answer_points?.length > 0) && (
<div className="detail-row">
<span className="detail-label">Expected Answer:</span>
<span className="detail-value">
{question.answer || question.expected_answer_points?.join(', ') || 'N/A'}
</span>
</div>
)}
<div className="question-meta">
{/* Source Badge in expanded view */}
{(() => {
let badgeClass = 'database';
let badgeText = 'D';
let badgeTitle = 'Database';
if (selectedSource === 'rag') {
badgeClass = 'rag';
badgeText = 'R';
badgeTitle = 'RAG';
} else if (question.source === 'openai_api') {
badgeClass = 'api';
badgeText = 'A';
badgeTitle = 'API Generated';
} else if (question.source === 'rag_engine') {
badgeClass = 'rag';
badgeText = 'R';
badgeTitle = 'RAG';
} else if (question.source === 'fallback') {
badgeClass = 'fallback';
badgeText = 'F';
badgeTitle = 'Fallback';
}
return (
<span
className={`source-badge source-${badgeClass}`}
title={badgeTitle}
>
{badgeText}
</span>
);
})()}
{question.difficulty && (
<span className={`meta-tag difficulty-${question.difficulty}`}>
{question.difficulty}
</span>
)}
{question.question_type && (
<span className="meta-tag type">
{question.question_type}
</span>
)}
{/* Show actual source value for debugging */}
<span className="meta-tag" style={{ fontSize: '10px', opacity: 0.7 }}>
src: {question.source || 'none'}
</span>
</div>
</div>
)}
</div>
))}
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="questions-pagination">
<button
className="pagination-btn"
disabled={currentPage === 1}
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
>
<ChevronLeft size={18} /> Previous
</button>
<div className="pagination-pages">
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
let pageNum;
if (totalPages <= 5) {
pageNum = i + 1;
} else if (currentPage <= 3) {
pageNum = i + 1;
} else if (currentPage >= totalPages - 2) {
pageNum = totalPages - 4 + i;
} else {
pageNum = currentPage - 2 + i;
}
return (
<button
key={pageNum}
className={`page-btn ${currentPage === pageNum ? 'active' : ''}`}
onClick={() => setCurrentPage(pageNum)}
>
{pageNum}
</button>
);
})}
</div>
<button
className="pagination-btn"
disabled={currentPage === totalPages}
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
>
Next <ChevronRight size={18} />
</button>
</div>
)}
</>
)}
</div>
);
};
/**
* Render Add Question Modal
*/
const renderAddModal = () => {
if (!showAddModal) return null;
return (
<div className="modal-overlay" onClick={() => setShowAddModal(false)}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
<div className="modal-header">
<h3>Add New Question</h3>
<button className="modal-close" onClick={() => setShowAddModal(false)}>
<X size={20} />
</button>
</div>
<div className="modal-body">
<div className="form-group">
<label>Question</label>
<textarea
placeholder="Enter the interview question..."
value={newQuestion.question}
onChange={(e) => setNewQuestion({ ...newQuestion, question: e.target.value })}
rows={4}
/>
</div>
<div className="form-group">
<label>Expected Answer / Key Points</label>
<textarea
placeholder="Enter expected answer or key points..."
value={newQuestion.answer}
onChange={(e) => setNewQuestion({ ...newQuestion, answer: e.target.value })}
rows={6}
/>
</div>
<div className="form-info">
<p><strong>Branch:</strong> {branches.find(b => b.id === selectedBranch)?.name}</p>
<p><strong>Domain:</strong> {domains.find(d => d.id === selectedDomain)?.name}</p>
<p><strong>Mode:</strong> {MODES.find(m => m.id === selectedMode)?.name}</p>
</div>
</div>
<div className="modal-footer">
<button
className="btn btn-secondary"
onClick={() => setShowAddModal(false)}
>
Cancel
</button>
<button
className="btn btn-primary"
onClick={handleAddQuestion}
disabled={addingQuestion || !newQuestion.question.trim()}
>
{addingQuestion ? (
<><Loader2 size={18} className="spin" /> Adding...</>
) : (
<><Save size={18} /> Add Question</>
)}
</button>
</div>
</div>
</div>
);
};
/**
* Render main content based on current step
*/
const renderContent = () => {
switch (currentStep) {
case 'branches':
return renderBranchSelection();
case 'domains':
return renderDomainSelection();
case 'modes':
return renderModeSelection();
case 'source':
return renderSourceSelection();
case 'questions':
return renderQuestionsList();
default:
return renderBranchSelection();
}
};
return (
<div className="questions-panel">
{/* Header */}
<header className="panel-header">
<div className="header-content">
<div className="header-title">
<div className="header-icon-wrapper">
<HelpCircle size={28} />
</div>
<div>
<h1>Question Browser</h1>
<p>Browse and manage interview questions</p>
</div>
</div>
<button
className="btn btn-secondary"
onClick={() => {
fetchStats();
if (currentStep === 'questions') fetchQuestions();
}}
>
<RefreshCw size={18} />
Refresh
</button>
</div>
{/* Breadcrumb Navigation */}
{currentStep !== 'branches' && (
<div className="breadcrumb-nav">
<button className="back-btn" onClick={handleBack}>
<ChevronLeft size={18} />
Back
</button>
<div className="breadcrumb-path">
<span
className="breadcrumb-item clickable"
onClick={() => {
setCurrentStep('branches');
setSelectedBranch(null);
setSelectedDomain(null);
setSelectedMode(null);
}}
>
All Branches
</span>
{getBreadcrumb().map((item, index) => (
<React.Fragment key={index}>
<ChevronRight size={14} />
<span className={`breadcrumb-item ${item.type}`}>
{item.label}
</span>
</React.Fragment>
))}
</div>
</div>
)}
</header>
{/* Error banner */}
{error && (
<div className="error-banner">
<AlertCircle size={18} />
<span>{error}</span>
<button onClick={() => setError(null)}>Dismiss</button>
</div>
)}
{/* Main content */}
<div className="panel-content">
{renderContent()}
</div>
{/* Add Question Modal */}
{renderAddModal()}
</div>
);
}
export default QuestionsPanel;