react-native-elearn-ui 0.1.25 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/module/components/Icon.js +30 -0
- package/lib/module/components/Icon.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/screens/index.js +1 -0
- package/lib/module/screens/index.js.map +1 -1
- package/lib/module/screens/qbank/QBankQuestionScreen.js +473 -0
- package/lib/module/screens/qbank/QBankQuestionScreen.js.map +1 -0
- package/lib/module/screens/qbank/QBankSubTopicScreen.js +20 -17
- package/lib/module/screens/qbank/QBankSubTopicScreen.js.map +1 -1
- package/lib/typescript/src/components/Icon.d.ts +1 -1
- package/lib/typescript/src/components/Icon.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/screens/index.d.ts +1 -0
- package/lib/typescript/src/screens/index.d.ts.map +1 -1
- package/lib/typescript/src/screens/qbank/QBankQuestionScreen.d.ts +14 -0
- package/lib/typescript/src/screens/qbank/QBankQuestionScreen.d.ts.map +1 -0
- package/lib/typescript/src/screens/qbank/QBankSubTopicScreen.d.ts +1 -0
- package/lib/typescript/src/screens/qbank/QBankSubTopicScreen.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +20 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Icon.tsx +12 -2
- package/src/index.tsx +1 -0
- package/src/screens/index.ts +1 -0
- package/src/screens/qbank/QBankQuestionScreen.tsx +448 -0
- package/src/screens/qbank/QBankSubTopicScreen.tsx +15 -13
- package/src/types/index.ts +23 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
ScrollView,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
StatusBar,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
10
|
+
import { useTheme } from '../../context/ThemeContext';
|
|
11
|
+
import { Typography, Icon, Button } from '../../components';
|
|
12
|
+
import type { QBankExamData, QBankExamOption } from '../../types';
|
|
13
|
+
|
|
14
|
+
interface QBankQuestionScreenProps {
|
|
15
|
+
examData: QBankExamData;
|
|
16
|
+
onFinish?: (results: Record<string, string>) => void;
|
|
17
|
+
onQuestionAttempt?: (questionId: string, selectedOptionId: string) => void;
|
|
18
|
+
onBack?: () => void;
|
|
19
|
+
onPressReport?: (questionId: string) => void;
|
|
20
|
+
onPressUsers?: (questionId: string) => void;
|
|
21
|
+
isProUser?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const formatTime = (totalSeconds: number) => {
|
|
25
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
26
|
+
const seconds = totalSeconds % 60;
|
|
27
|
+
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const QBankQuestionScreen: React.FC<QBankQuestionScreenProps> = ({
|
|
31
|
+
examData,
|
|
32
|
+
onFinish,
|
|
33
|
+
onQuestionAttempt,
|
|
34
|
+
onBack,
|
|
35
|
+
onPressReport,
|
|
36
|
+
onPressUsers,
|
|
37
|
+
isProUser = false,
|
|
38
|
+
}) => {
|
|
39
|
+
const theme = useTheme();
|
|
40
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
41
|
+
const [answers, setAnswers] = useState<Record<string, string>>({});
|
|
42
|
+
const [timeLeft, setTimeLeft] = useState(examData.timeLimitSeconds || 0);
|
|
43
|
+
|
|
44
|
+
const currentQuestion = examData.questions[currentIndex];
|
|
45
|
+
const totalQuestions = examData.questions.length;
|
|
46
|
+
const isAttempted = !!currentQuestion && !!answers[currentQuestion.id];
|
|
47
|
+
const selectedOptionId = currentQuestion ? answers[currentQuestion.id] : undefined;
|
|
48
|
+
const isLastQuestion = currentIndex === totalQuestions - 1;
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!examData.timeLimitSeconds) return;
|
|
52
|
+
|
|
53
|
+
if (timeLeft <= 0) {
|
|
54
|
+
onFinish?.(answers);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const timer = setInterval(() => {
|
|
59
|
+
setTimeLeft((prev) => prev - 1);
|
|
60
|
+
}, 1000);
|
|
61
|
+
|
|
62
|
+
return () => clearInterval(timer);
|
|
63
|
+
}, [timeLeft, examData.timeLimitSeconds, onFinish, answers]);
|
|
64
|
+
|
|
65
|
+
const handleOptionPress = (optionId: string) => {
|
|
66
|
+
if (!currentQuestion || isAttempted) return; // Prevent changing answer in Tutor mode
|
|
67
|
+
|
|
68
|
+
setAnswers((prev) => ({
|
|
69
|
+
...prev,
|
|
70
|
+
[currentQuestion.id]: optionId,
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
onQuestionAttempt?.(currentQuestion.id, optionId);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleNextOrFinish = () => {
|
|
77
|
+
if (isLastQuestion) {
|
|
78
|
+
onFinish?.(answers);
|
|
79
|
+
} else {
|
|
80
|
+
setCurrentIndex((prev) => prev + 1);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handlePrev = () => {
|
|
85
|
+
if (currentIndex > 0) {
|
|
86
|
+
setCurrentIndex((prev) => prev - 1);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const progressPercentage = ((currentIndex + 1) / totalQuestions) * 100;
|
|
91
|
+
|
|
92
|
+
const renderOption = (option: QBankExamOption, index: number) => {
|
|
93
|
+
const letters = ['A', 'B', 'C', 'D', 'E', 'F'];
|
|
94
|
+
const letter = letters[index] || '';
|
|
95
|
+
|
|
96
|
+
let borderColor = '#E5E7EB';
|
|
97
|
+
let backgroundColor = '#FFFFFF';
|
|
98
|
+
let iconName: 'check-circle' | 'x-circle' | undefined;
|
|
99
|
+
let iconColor = theme.textSecondary;
|
|
100
|
+
let statusText = '';
|
|
101
|
+
let statusColor = '';
|
|
102
|
+
|
|
103
|
+
const isSelected = selectedOptionId === option.id;
|
|
104
|
+
|
|
105
|
+
if (isAttempted) {
|
|
106
|
+
if (option.isCorrect) {
|
|
107
|
+
borderColor = theme.success;
|
|
108
|
+
backgroundColor = '#F0FDF4';
|
|
109
|
+
iconName = 'check-circle';
|
|
110
|
+
iconColor = theme.success;
|
|
111
|
+
statusText = 'Correct';
|
|
112
|
+
statusColor = theme.success;
|
|
113
|
+
} else if (isSelected && !option.isCorrect) {
|
|
114
|
+
borderColor = '#EF4444';
|
|
115
|
+
backgroundColor = '#FEF2F2';
|
|
116
|
+
iconName = 'x-circle';
|
|
117
|
+
iconColor = '#EF4444';
|
|
118
|
+
statusText = 'InCorrect';
|
|
119
|
+
statusColor = '#EF4444';
|
|
120
|
+
}
|
|
121
|
+
} else if (isSelected) {
|
|
122
|
+
// Very brief state before it evaluates (if we ever want a delay), but here it evaluates instantly
|
|
123
|
+
borderColor = theme.primary;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<TouchableOpacity
|
|
128
|
+
key={option.id}
|
|
129
|
+
style={[styles.optionContainer, { borderColor, backgroundColor }]}
|
|
130
|
+
onPress={() => handleOptionPress(option.id)}
|
|
131
|
+
activeOpacity={0.7}
|
|
132
|
+
>
|
|
133
|
+
<View style={styles.optionContent}>
|
|
134
|
+
{iconName ? (
|
|
135
|
+
<Icon name={iconName} size={20} color={iconColor} />
|
|
136
|
+
) : (
|
|
137
|
+
<View style={styles.emptyCircle} />
|
|
138
|
+
)}
|
|
139
|
+
<View style={styles.optionTextWrapper}>
|
|
140
|
+
<Typography variant="body" color={theme.textPrimary} style={styles.optionText}>
|
|
141
|
+
{letter}. {option.text}
|
|
142
|
+
</Typography>
|
|
143
|
+
{isAttempted && statusText ? (
|
|
144
|
+
<Typography variant="caption" color={statusColor} style={styles.optionStatusText}>
|
|
145
|
+
{statusText} {option.percentage !== undefined && <Typography variant="caption" color={theme.textSecondary}>[{option.percentage}%]</Typography>}
|
|
146
|
+
</Typography>
|
|
147
|
+
) : null}
|
|
148
|
+
{!statusText && option.percentage !== undefined && isAttempted && (
|
|
149
|
+
<Typography variant="caption" color={theme.textSecondary} style={styles.optionStatusText}>
|
|
150
|
+
[{option.percentage}%]
|
|
151
|
+
</Typography>
|
|
152
|
+
)}
|
|
153
|
+
</View>
|
|
154
|
+
</View>
|
|
155
|
+
</TouchableOpacity>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (!currentQuestion) return null;
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<SafeAreaView style={[styles.container, { backgroundColor: '#FFFFFF' }]} edges={['top', 'bottom']}>
|
|
163
|
+
<StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
|
|
164
|
+
|
|
165
|
+
{/* Header */}
|
|
166
|
+
<View style={styles.header}>
|
|
167
|
+
<TouchableOpacity onPress={onBack} style={styles.headerIconBtnLeft}>
|
|
168
|
+
<Icon name="chevron-left" size={24} color={theme.textPrimary} />
|
|
169
|
+
</TouchableOpacity>
|
|
170
|
+
<Typography variant="h3" color={theme.textPrimary} style={styles.headerTitle}>
|
|
171
|
+
{examData.title}
|
|
172
|
+
</Typography>
|
|
173
|
+
<View style={styles.headerIconBtnRight}>
|
|
174
|
+
{examData.accessType === 'pro' && !isProUser && (
|
|
175
|
+
<View style={styles.proBadge}>
|
|
176
|
+
<Typography variant="caption" bold color="#FFFFFF" style={{fontSize: 8}}>
|
|
177
|
+
PRO
|
|
178
|
+
</Typography>
|
|
179
|
+
</View>
|
|
180
|
+
)}
|
|
181
|
+
{examData.accessType === 'free' && (
|
|
182
|
+
<View style={styles.freeBadge}>
|
|
183
|
+
<Typography variant="caption" bold color={theme.success} style={{fontSize: 8}}>
|
|
184
|
+
FREE
|
|
185
|
+
</Typography>
|
|
186
|
+
</View>
|
|
187
|
+
)}
|
|
188
|
+
</View>
|
|
189
|
+
</View>
|
|
190
|
+
|
|
191
|
+
{/* Progress Bar */}
|
|
192
|
+
<View style={styles.progressTrack}>
|
|
193
|
+
<View style={[styles.progressFill, { width: `${progressPercentage}%`, backgroundColor: theme.success }]} />
|
|
194
|
+
</View>
|
|
195
|
+
|
|
196
|
+
<View style={{ flex: 1, backgroundColor: '#F9FAFB' }}>
|
|
197
|
+
<ScrollView
|
|
198
|
+
contentContainerStyle={styles.scrollContent}
|
|
199
|
+
showsVerticalScrollIndicator={false}
|
|
200
|
+
>
|
|
201
|
+
{/* Top Bar: Question Number & Timer */}
|
|
202
|
+
<View style={styles.topBar}>
|
|
203
|
+
<Typography variant="caption" bold color={theme.textSecondary} style={styles.questionNumber}>
|
|
204
|
+
QUESTION {currentIndex + 1}/{totalQuestions}
|
|
205
|
+
</Typography>
|
|
206
|
+
{!!examData.timeLimitSeconds && (
|
|
207
|
+
<View style={styles.timerWrapper}>
|
|
208
|
+
<Icon name="clock" size={14} color={theme.textSecondary} />
|
|
209
|
+
<Typography variant="caption" color={theme.textSecondary} style={styles.timerText}>
|
|
210
|
+
{formatTime(timeLeft)}
|
|
211
|
+
</Typography>
|
|
212
|
+
</View>
|
|
213
|
+
)}
|
|
214
|
+
</View>
|
|
215
|
+
|
|
216
|
+
{/* Question Text */}
|
|
217
|
+
<Typography variant="h3" bold color={theme.textPrimary} style={styles.questionText}>
|
|
218
|
+
{currentQuestion.questionText}
|
|
219
|
+
</Typography>
|
|
220
|
+
|
|
221
|
+
{/* Options */}
|
|
222
|
+
<View style={styles.optionsList}>
|
|
223
|
+
{currentQuestion.options.map((option, index) => renderOption(option, index))}
|
|
224
|
+
</View>
|
|
225
|
+
|
|
226
|
+
{/* Explanation Section (Only shown if attempted) */}
|
|
227
|
+
{isAttempted && currentQuestion.explanation && (
|
|
228
|
+
<View style={styles.explanationContainer}>
|
|
229
|
+
<Typography variant="caption" bold color={theme.textPrimary} style={styles.explanationTitle}>
|
|
230
|
+
EXPLANATION
|
|
231
|
+
</Typography>
|
|
232
|
+
<Typography variant="body" color={theme.textSecondary} style={styles.explanationText}>
|
|
233
|
+
{currentQuestion.explanation}
|
|
234
|
+
</Typography>
|
|
235
|
+
|
|
236
|
+
{currentQuestion.referenceId && (
|
|
237
|
+
<View style={styles.referenceContainer}>
|
|
238
|
+
<Typography variant="caption" color={theme.textSecondary} style={styles.referenceText}>
|
|
239
|
+
{currentQuestion.referenceId}
|
|
240
|
+
</Typography>
|
|
241
|
+
</View>
|
|
242
|
+
)}
|
|
243
|
+
</View>
|
|
244
|
+
)}
|
|
245
|
+
</ScrollView>
|
|
246
|
+
</View>
|
|
247
|
+
|
|
248
|
+
{/* Bottom Actions */}
|
|
249
|
+
<View style={styles.bottomActions}>
|
|
250
|
+
<View style={styles.leftActions}>
|
|
251
|
+
<TouchableOpacity style={styles.iconAction} onPress={() => onPressReport?.(currentQuestion.id)}>
|
|
252
|
+
<Icon name="alert-circle" size={20} color={theme.textSecondary} />
|
|
253
|
+
</TouchableOpacity>
|
|
254
|
+
<TouchableOpacity style={styles.iconAction} onPress={() => onPressUsers?.(currentQuestion.id)}>
|
|
255
|
+
<Icon name="users" size={20} color={theme.textSecondary} />
|
|
256
|
+
</TouchableOpacity>
|
|
257
|
+
</View>
|
|
258
|
+
<View style={styles.rightActions}>
|
|
259
|
+
<TouchableOpacity style={[styles.prevBtn, currentIndex === 0 && { opacity: 0.5 }]} onPress={handlePrev} disabled={currentIndex === 0}>
|
|
260
|
+
<Typography variant="body" bold color={theme.textPrimary}>
|
|
261
|
+
Prev
|
|
262
|
+
</Typography>
|
|
263
|
+
</TouchableOpacity>
|
|
264
|
+
<Button
|
|
265
|
+
title={isLastQuestion ? "Finish" : "Next"}
|
|
266
|
+
onPress={handleNextOrFinish}
|
|
267
|
+
style={styles.nextBtn}
|
|
268
|
+
textStyle={styles.nextBtnText}
|
|
269
|
+
/>
|
|
270
|
+
</View>
|
|
271
|
+
</View>
|
|
272
|
+
|
|
273
|
+
</SafeAreaView>
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const styles = StyleSheet.create({
|
|
278
|
+
container: {
|
|
279
|
+
flex: 1,
|
|
280
|
+
},
|
|
281
|
+
header: {
|
|
282
|
+
flexDirection: 'row',
|
|
283
|
+
alignItems: 'center',
|
|
284
|
+
paddingHorizontal: 16,
|
|
285
|
+
paddingVertical: 12,
|
|
286
|
+
},
|
|
287
|
+
headerIconBtnLeft: {
|
|
288
|
+
width: 40,
|
|
289
|
+
height: 40,
|
|
290
|
+
justifyContent: 'center',
|
|
291
|
+
alignItems: 'flex-start',
|
|
292
|
+
},
|
|
293
|
+
headerIconBtnRight: {
|
|
294
|
+
width: 40,
|
|
295
|
+
height: 40,
|
|
296
|
+
justifyContent: 'center',
|
|
297
|
+
alignItems: 'flex-end',
|
|
298
|
+
},
|
|
299
|
+
headerTitle: {
|
|
300
|
+
fontSize: 16,
|
|
301
|
+
fontWeight: '600',
|
|
302
|
+
flex: 1,
|
|
303
|
+
textAlign: 'center',
|
|
304
|
+
},
|
|
305
|
+
proBadge: {
|
|
306
|
+
paddingHorizontal: 6,
|
|
307
|
+
paddingVertical: 2,
|
|
308
|
+
borderRadius: 4,
|
|
309
|
+
backgroundColor: '#F59E0B',
|
|
310
|
+
},
|
|
311
|
+
freeBadge: {
|
|
312
|
+
paddingHorizontal: 6,
|
|
313
|
+
paddingVertical: 2,
|
|
314
|
+
borderRadius: 4,
|
|
315
|
+
borderWidth: 1,
|
|
316
|
+
borderColor: '#10B981',
|
|
317
|
+
},
|
|
318
|
+
progressTrack: {
|
|
319
|
+
height: 4,
|
|
320
|
+
backgroundColor: '#E5E7EB',
|
|
321
|
+
width: '100%',
|
|
322
|
+
},
|
|
323
|
+
progressFill: {
|
|
324
|
+
height: '100%',
|
|
325
|
+
},
|
|
326
|
+
scrollContent: {
|
|
327
|
+
padding: 20,
|
|
328
|
+
paddingBottom: 40,
|
|
329
|
+
},
|
|
330
|
+
topBar: {
|
|
331
|
+
flexDirection: 'row',
|
|
332
|
+
justifyContent: 'space-between',
|
|
333
|
+
alignItems: 'center',
|
|
334
|
+
marginBottom: 20,
|
|
335
|
+
},
|
|
336
|
+
questionNumber: {
|
|
337
|
+
fontSize: 12,
|
|
338
|
+
letterSpacing: 0.5,
|
|
339
|
+
},
|
|
340
|
+
timerWrapper: {
|
|
341
|
+
flexDirection: 'row',
|
|
342
|
+
alignItems: 'center',
|
|
343
|
+
},
|
|
344
|
+
timerText: {
|
|
345
|
+
fontSize: 12,
|
|
346
|
+
marginLeft: 6,
|
|
347
|
+
},
|
|
348
|
+
questionText: {
|
|
349
|
+
fontSize: 16,
|
|
350
|
+
lineHeight: 24,
|
|
351
|
+
marginBottom: 24,
|
|
352
|
+
},
|
|
353
|
+
optionsList: {
|
|
354
|
+
marginBottom: 32,
|
|
355
|
+
},
|
|
356
|
+
optionContainer: {
|
|
357
|
+
borderWidth: 1,
|
|
358
|
+
borderRadius: 8,
|
|
359
|
+
padding: 16,
|
|
360
|
+
marginBottom: 12,
|
|
361
|
+
backgroundColor: '#FFFFFF',
|
|
362
|
+
},
|
|
363
|
+
optionContent: {
|
|
364
|
+
flexDirection: 'row',
|
|
365
|
+
alignItems: 'flex-start',
|
|
366
|
+
},
|
|
367
|
+
emptyCircle: {
|
|
368
|
+
width: 20,
|
|
369
|
+
height: 20,
|
|
370
|
+
borderRadius: 10,
|
|
371
|
+
borderWidth: 1,
|
|
372
|
+
borderColor: '#D1D5DB',
|
|
373
|
+
},
|
|
374
|
+
optionTextWrapper: {
|
|
375
|
+
flex: 1,
|
|
376
|
+
marginLeft: 12,
|
|
377
|
+
},
|
|
378
|
+
optionText: {
|
|
379
|
+
fontSize: 14,
|
|
380
|
+
lineHeight: 20,
|
|
381
|
+
},
|
|
382
|
+
optionStatusText: {
|
|
383
|
+
marginTop: 6,
|
|
384
|
+
fontSize: 12,
|
|
385
|
+
},
|
|
386
|
+
explanationContainer: {
|
|
387
|
+
marginTop: 16,
|
|
388
|
+
},
|
|
389
|
+
explanationTitle: {
|
|
390
|
+
fontSize: 12,
|
|
391
|
+
marginBottom: 12,
|
|
392
|
+
},
|
|
393
|
+
explanationText: {
|
|
394
|
+
fontSize: 14,
|
|
395
|
+
lineHeight: 22,
|
|
396
|
+
},
|
|
397
|
+
referenceContainer: {
|
|
398
|
+
marginTop: 24,
|
|
399
|
+
paddingTop: 16,
|
|
400
|
+
borderTopWidth: 1,
|
|
401
|
+
borderTopColor: '#F3F4F6',
|
|
402
|
+
alignItems: 'center',
|
|
403
|
+
},
|
|
404
|
+
referenceText: {
|
|
405
|
+
fontSize: 12,
|
|
406
|
+
},
|
|
407
|
+
bottomActions: {
|
|
408
|
+
flexDirection: 'row',
|
|
409
|
+
justifyContent: 'space-between',
|
|
410
|
+
alignItems: 'center',
|
|
411
|
+
padding: 16,
|
|
412
|
+
borderTopWidth: 1,
|
|
413
|
+
borderTopColor: '#F3F4F6',
|
|
414
|
+
backgroundColor: '#FFFFFF',
|
|
415
|
+
},
|
|
416
|
+
leftActions: {
|
|
417
|
+
flexDirection: 'row',
|
|
418
|
+
alignItems: 'center',
|
|
419
|
+
},
|
|
420
|
+
iconAction: {
|
|
421
|
+
width: 36,
|
|
422
|
+
height: 36,
|
|
423
|
+
justifyContent: 'center',
|
|
424
|
+
alignItems: 'center',
|
|
425
|
+
marginRight: 8,
|
|
426
|
+
borderRadius: 18,
|
|
427
|
+
borderWidth: 1,
|
|
428
|
+
borderColor: '#E5E7EB',
|
|
429
|
+
},
|
|
430
|
+
rightActions: {
|
|
431
|
+
flexDirection: 'row',
|
|
432
|
+
alignItems: 'center',
|
|
433
|
+
},
|
|
434
|
+
prevBtn: {
|
|
435
|
+
marginRight: 20,
|
|
436
|
+
},
|
|
437
|
+
nextBtn: {
|
|
438
|
+
backgroundColor: '#10B981',
|
|
439
|
+
paddingHorizontal: 24,
|
|
440
|
+
paddingVertical: 10,
|
|
441
|
+
height: 'auto',
|
|
442
|
+
minWidth: 80,
|
|
443
|
+
},
|
|
444
|
+
nextBtnText: {
|
|
445
|
+
fontSize: 14,
|
|
446
|
+
fontWeight: '600',
|
|
447
|
+
},
|
|
448
|
+
});
|
|
@@ -19,6 +19,7 @@ interface QBankSubTopicScreenProps {
|
|
|
19
19
|
onPressModule?: (moduleId: string) => void;
|
|
20
20
|
onPressFilter?: (filter: string) => void;
|
|
21
21
|
onPressSearch?: () => void;
|
|
22
|
+
isProUser?: boolean;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export const QBankSubTopicScreen: React.FC<QBankSubTopicScreenProps> = ({
|
|
@@ -28,6 +29,7 @@ export const QBankSubTopicScreen: React.FC<QBankSubTopicScreenProps> = ({
|
|
|
28
29
|
onPressModule,
|
|
29
30
|
onPressFilter,
|
|
30
31
|
onPressSearch,
|
|
32
|
+
isProUser = false,
|
|
31
33
|
}) => {
|
|
32
34
|
const theme = useTheme();
|
|
33
35
|
const [activeFilter, setActiveFilter] = useState(params.activeFilter || params.filters[0]);
|
|
@@ -191,25 +193,27 @@ export const QBankSubTopicScreen: React.FC<QBankSubTopicScreenProps> = ({
|
|
|
191
193
|
</Typography>
|
|
192
194
|
</View>
|
|
193
195
|
)}
|
|
194
|
-
{moduleItem.status === 'locked' && (
|
|
195
|
-
<View style={styles.
|
|
196
|
-
<
|
|
196
|
+
{moduleItem.status === 'locked' && !isProUser && (
|
|
197
|
+
<View style={styles.proBadge}>
|
|
198
|
+
<Typography variant="caption" bold color="#FFFFFF" style={{fontSize: 7}}>
|
|
199
|
+
PRO
|
|
200
|
+
</Typography>
|
|
197
201
|
</View>
|
|
198
202
|
)}
|
|
199
203
|
{moduleItem.attemptStatus === 'resume' && (
|
|
200
|
-
<Typography variant="caption" bold color="#F59E0B" style={{fontSize:
|
|
204
|
+
<Typography variant="caption" bold color="#F59E0B" style={{fontSize: 9}}>
|
|
201
205
|
RESUME
|
|
202
206
|
</Typography>
|
|
203
207
|
)}
|
|
204
208
|
{moduleItem.attemptStatus === 'completed' && (
|
|
205
209
|
<View style={{ alignItems: 'flex-end' }}>
|
|
206
210
|
<View style={styles.completedBadge}>
|
|
207
|
-
<Typography variant="caption" bold color="#FFFFFF" style={{fontSize:
|
|
211
|
+
<Typography variant="caption" bold color="#FFFFFF" style={{fontSize: 7}}>
|
|
208
212
|
COMPLETED
|
|
209
213
|
</Typography>
|
|
210
214
|
</View>
|
|
211
215
|
{moduleItem.correctPercentage !== undefined && (
|
|
212
|
-
<Typography variant="caption" bold color={theme.success} style={{fontSize:
|
|
216
|
+
<Typography variant="caption" bold color={theme.success} style={{fontSize: 10, marginTop: 4}}>
|
|
213
217
|
{moduleItem.correctPercentage}% Correct
|
|
214
218
|
</Typography>
|
|
215
219
|
)}
|
|
@@ -427,13 +431,11 @@ const styles = StyleSheet.create({
|
|
|
427
431
|
borderColor: '#10B981',
|
|
428
432
|
backgroundColor: '#FFFFFF',
|
|
429
433
|
},
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
borderRadius:
|
|
434
|
-
backgroundColor: '#
|
|
435
|
-
justifyContent: 'center',
|
|
436
|
-
alignItems: 'center',
|
|
434
|
+
proBadge: {
|
|
435
|
+
paddingHorizontal: 6,
|
|
436
|
+
paddingVertical: 2,
|
|
437
|
+
borderRadius: 4,
|
|
438
|
+
backgroundColor: '#F59E0B',
|
|
437
439
|
},
|
|
438
440
|
completedBadge: {
|
|
439
441
|
paddingHorizontal: 6,
|
package/src/types/index.ts
CHANGED
|
@@ -239,6 +239,29 @@ export interface QBankSubTopicSection {
|
|
|
239
239
|
modules: QBankSubTopicModule[];
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
export interface QBankExamOption {
|
|
243
|
+
id: string;
|
|
244
|
+
text: string;
|
|
245
|
+
isCorrect: boolean;
|
|
246
|
+
percentage?: number; // e.g. 20 for [20%]
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface QBankExamQuestion {
|
|
250
|
+
id: string;
|
|
251
|
+
questionText: string;
|
|
252
|
+
options: QBankExamOption[];
|
|
253
|
+
explanation?: string;
|
|
254
|
+
referenceId?: string;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface QBankExamData {
|
|
258
|
+
id: string;
|
|
259
|
+
title: string;
|
|
260
|
+
accessType: 'free' | 'pro';
|
|
261
|
+
timeLimitSeconds?: number;
|
|
262
|
+
questions: QBankExamQuestion[];
|
|
263
|
+
}
|
|
264
|
+
|
|
242
265
|
export interface QBankSubTopicParams {
|
|
243
266
|
header: {
|
|
244
267
|
title: string;
|