Fix CBT history react-markdown crash, fix test deletion cascade, fix cbt theme, add 180 NEET questions
This commit is contained in:
@@ -628,8 +628,6 @@ def create_manual_test(
|
||||
created_by=data.admin_id
|
||||
)
|
||||
session.add(test_obj)
|
||||
session.commit()
|
||||
session.refresh(test_obj)
|
||||
|
||||
# 2. Parse and create questions
|
||||
sections_dict = {}
|
||||
@@ -649,8 +647,6 @@ def create_manual_test(
|
||||
created_by=data.admin_id
|
||||
)
|
||||
session.add(q_obj)
|
||||
session.commit()
|
||||
session.refresh(q_obj)
|
||||
|
||||
sec_name = q_data.section
|
||||
if sec_name not in sections_dict:
|
||||
@@ -678,4 +674,69 @@ def toggle_test_status(test_id: UUID, session: Session = Depends(get_session)):
|
||||
session.add(test_obj)
|
||||
session.commit()
|
||||
|
||||
return {"status": "success", "is_active": test_obj.is_active}
|
||||
return {"status": "success", "is_active": test_obj.is_active}
|
||||
|
||||
@app.delete("/api/admin/tests/{test_id}", tags=["Admin"])
|
||||
def delete_test(test_id: UUID, session: Session = Depends(get_session)):
|
||||
from sqlmodel import select
|
||||
test_obj = session.get(models.Test, test_id)
|
||||
if not test_obj:
|
||||
raise HTTPException(status_code=404, detail="Test not found")
|
||||
|
||||
# 1. Identify all question IDs associated with the test
|
||||
question_ids = []
|
||||
if test_obj.sections:
|
||||
for section_name, section_data in test_obj.sections.items():
|
||||
question_ids.extend(section_data.get("questions", []))
|
||||
|
||||
# Convert question IDs to UUIDs
|
||||
import uuid
|
||||
q_uuids = []
|
||||
for q_id in question_ids:
|
||||
try:
|
||||
q_uuids.append(uuid.UUID(q_id) if isinstance(q_id, str) else q_id)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# 2. Identify all attempts associated with this test
|
||||
attempts_query = select(models.Attempt).where(
|
||||
models.Attempt.source_id == test_id
|
||||
)
|
||||
attempts = session.exec(attempts_query).all()
|
||||
attempt_ids = [att.id for att in attempts]
|
||||
|
||||
# 3. Delete Mistake records referencing these attempts
|
||||
if attempt_ids:
|
||||
mistakes_by_attempt_query = select(models.Mistake).where(
|
||||
models.Mistake.attempt_id.in_(attempt_ids)
|
||||
)
|
||||
mistakes_by_attempt = session.exec(mistakes_by_attempt_query).all()
|
||||
for mistake in mistakes_by_attempt:
|
||||
session.delete(mistake)
|
||||
|
||||
# 4. Delete Mistake records referencing these questions
|
||||
if q_uuids:
|
||||
mistakes_by_q_query = select(models.Mistake).where(
|
||||
models.Mistake.question_id.in_(q_uuids)
|
||||
)
|
||||
mistakes_by_q = session.exec(mistakes_by_q_query).all()
|
||||
for mistake in mistakes_by_q:
|
||||
try:
|
||||
session.delete(mistake)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 5. Delete the Attempt records
|
||||
for att in attempts:
|
||||
session.delete(att)
|
||||
|
||||
# 6. Delete the Question records
|
||||
for q_uuid in q_uuids:
|
||||
q_obj = session.get(models.Question, q_uuid)
|
||||
if q_obj:
|
||||
session.delete(q_obj)
|
||||
|
||||
# 7. Delete the Test itself
|
||||
session.delete(test_obj)
|
||||
session.commit()
|
||||
return {"status": "success", "message": "Test and all associated questions, attempts, and mistakes deleted successfully"}
|
||||
1832
jee_advanced_full_90q.json
Normal file
1832
jee_advanced_full_90q.json
Normal file
File diff suppressed because it is too large
Load Diff
3782
neet_180_questions.json
Normal file
3782
neet_180_questions.json
Normal file
File diff suppressed because it is too large
Load Diff
479
neet_sample_questions.md
Normal file
479
neet_sample_questions.md
Normal file
@@ -0,0 +1,479 @@
|
||||
# NEET Sample Questions
|
||||
|
||||
Here are some realistic sample questions for a NEET exam.
|
||||
|
||||
### Q1. The dimension of $\frac{1}{\sqrt{\mu_0 \varepsilon_0}}$ is that of:
|
||||
**Subject**: Physics | **Topic**: Units and Measurements | **Difficulty**: Medium
|
||||
|
||||
- **A**: Velocity
|
||||
- **B**: Time
|
||||
- **C**: Capacitance
|
||||
- **D**: Distance
|
||||
|
||||
**Correct Answer**: A
|
||||
|
||||
**Solution**:
|
||||
We know that $c = \frac{1}{\sqrt{\mu_0 \varepsilon_0}}$, where $c$ is the speed of light in vacuum. Therefore, the dimension of $\frac{1}{\sqrt{\mu_0 \varepsilon_0}}$ is that of velocity, which is $[L T^{-1}]$.
|
||||
|
||||
---
|
||||
|
||||
### Q2. Two projectiles are thrown with the same initial velocity at angles of $30^\circ$ and $60^\circ$ with the horizontal. The ratio of their maximum heights is:
|
||||
**Subject**: Physics | **Topic**: Kinematics | **Difficulty**: Easy
|
||||
|
||||
- **A**: 1:3
|
||||
- **B**: 3:1
|
||||
- **C**: 1:\sqrt{3}
|
||||
- **D**: 1:2
|
||||
|
||||
**Correct Answer**: A
|
||||
|
||||
**Solution**:
|
||||
Maximum height $H = \frac{u^2 \sin^2 \theta}{2g}$.
|
||||
For $\theta_1 = 30^\circ$, $H_1 = \frac{u^2 (1/2)^2}{2g} = \frac{u^2}{8g}$.
|
||||
For $\theta_2 = 60^\circ$, $H_2 = \frac{u^2 (\sqrt{3}/2)^2}{2g} = \frac{3u^2}{8g}$.
|
||||
Ratio $H_1 : H_2 = 1 : 3$.
|
||||
|
||||
---
|
||||
|
||||
### Q3. A Carnot engine takes heat from a reservoir at $627^\circ\text{C}$ and rejects heat to a sink at $27^\circ\text{C}$. Its efficiency is:
|
||||
**Subject**: Physics | **Topic**: Thermodynamics | **Difficulty**: Medium
|
||||
|
||||
- **A**: 2/3
|
||||
- **B**: 1/3
|
||||
- **C**: 3/4
|
||||
- **D**: 1/2
|
||||
|
||||
**Correct Answer**: A
|
||||
|
||||
**Solution**:
|
||||
Temperature of source, $T_1 = 627 + 273 = 900\text{ K}$.
|
||||
Temperature of sink, $T_2 = 27 + 273 = 300\text{ K}$.
|
||||
Efficiency $\eta = 1 - \frac{T_2}{T_1} = 1 - \frac{300}{900} = 1 - \frac{1}{3} = \frac{2}{3}$.
|
||||
|
||||
---
|
||||
|
||||
### Q4. A parallel plate capacitor with air between the plates has a capacitance of $8\text{ pF}$. What will be the capacitance if the distance between the plates is reduced by half, and the space between them is filled with a substance of dielectric constant $6$?
|
||||
**Subject**: Physics | **Topic**: Electrostatics | **Difficulty**: Hard
|
||||
|
||||
- **A**: 96 pF
|
||||
- **B**: 8 pF
|
||||
- **C**: 48 pF
|
||||
- **D**: 12 pF
|
||||
|
||||
**Correct Answer**: A
|
||||
|
||||
**Solution**:
|
||||
Initial capacitance $C_0 = \frac{\varepsilon_0 A}{d} = 8\text{ pF}$.
|
||||
New distance $d' = d/2$ and new dielectric constant $K = 6$.
|
||||
New capacitance $C' = \frac{K \varepsilon_0 A}{d'} = \frac{6 \varepsilon_0 A}{d/2} = 12 \left(\frac{\varepsilon_0 A}{d}\right) = 12 \times 8\text{ pF} = 96\text{ pF}$.
|
||||
|
||||
---
|
||||
|
||||
### Q5. In a meter bridge experiment, the null point is obtained at $20\text{ cm}$ from one end of the wire when resistance $X$ is balanced against another resistance $Y$. If $X < Y$, then what will be the new position of the null point from the same end, if one decides to balance a resistance of $4X$ against $Y$?
|
||||
**Subject**: Physics | **Topic**: Current Electricity | **Difficulty**: Medium
|
||||
|
||||
- **A**: 50 cm
|
||||
- **B**: 40 cm
|
||||
- **C**: 80 cm
|
||||
- **D**: 20 cm
|
||||
|
||||
**Correct Answer**: A
|
||||
|
||||
**Solution**:
|
||||
Initially, $\frac{X}{Y} = \frac{20}{100-20} = \frac{20}{80} = \frac{1}{4}$. So $Y = 4X$.
|
||||
For new resistance $4X$ against $Y$, ratio is $\frac{4X}{Y} = \frac{4X}{4X} = 1$.
|
||||
Let new null point be $l$, then $\frac{l}{100-l} = 1 \implies l = 100 - l \implies 2l = 100 \implies l = 50\text{ cm}$.
|
||||
|
||||
---
|
||||
|
||||
### Q6. The magnetic field at the centre of a circular current carrying coil of radius $R$ is $B_0$. If the radius is doubled keeping the current same, the new magnetic field at the centre will be:
|
||||
**Subject**: Physics | **Topic**: Magnetism | **Difficulty**: Easy
|
||||
|
||||
- **A**: $B_0/2$
|
||||
- **B**: $B_0/4$
|
||||
- **C**: $2B_0$
|
||||
- **D**: $B_0$
|
||||
|
||||
**Correct Answer**: A
|
||||
|
||||
**Solution**:
|
||||
Magnetic field at the centre of a circular coil is $B = \frac{\mu_0 I}{2R}$.
|
||||
Since $B \propto \frac{1}{R}$, doubling the radius will make the magnetic field half. Thus, new field is $B_0/2$.
|
||||
|
||||
---
|
||||
|
||||
### Q7. In a series LCR circuit, the voltage across the resistance, capacitance, and inductance is $10\text{ V}$ each. If the capacitance is short-circuited, the voltage across the inductance will be:
|
||||
**Subject**: Physics | **Topic**: Alternating Current | **Difficulty**: Medium
|
||||
|
||||
- **A**: 10 V
|
||||
- **B**: $10\sqrt{2}\text{ V}$
|
||||
- **C**: $10/\sqrt{2}\text{ V}$
|
||||
- **D**: Zero
|
||||
|
||||
**Correct Answer**: C
|
||||
|
||||
**Solution**:
|
||||
Initially, $V_R = 10\text{ V}$, $V_L = 10\text{ V}$, $V_C = 10\text{ V}$. Since $V_L = V_C$, the circuit is in resonance. The applied voltage $V = \sqrt{V_R^2 + (V_L - V_C)^2} = V_R = 10\text{ V}$.
|
||||
If capacitance is shorted, it becomes an LR circuit. Impedance $Z = \sqrt{R^2 + X_L^2}$. Since $V_R = V_L$ initially, $R = X_L$, so $Z = R\sqrt{2}$.
|
||||
New current $I' = \frac{V}{Z} = \frac{10}{R\sqrt{2}}$.
|
||||
New voltage across inductance $V_L' = I' X_L = \frac{10}{R\sqrt{2}} \times R = \frac{10}{\sqrt{2}}\text{ V}$.
|
||||
|
||||
---
|
||||
|
||||
### Q8. In Young's double slit experiment, if the separation between coherent sources is halved and the distance of the screen from the coherent sources is doubled, then the fringe width becomes:
|
||||
**Subject**: Physics | **Topic**: Optics | **Difficulty**: Medium
|
||||
|
||||
- **A**: Half
|
||||
- **B**: Four times
|
||||
- **C**: One-fourth
|
||||
- **D**: Double
|
||||
|
||||
**Correct Answer**: B
|
||||
|
||||
**Solution**:
|
||||
Fringe width $\beta = \frac{\lambda D}{d}$.
|
||||
New slit separation $d' = d/2$.
|
||||
New screen distance $D' = 2D$.
|
||||
New fringe width $\beta' = \frac{\lambda (2D)}{(d/2)} = 4 \frac{\lambda D}{d} = 4\beta$.
|
||||
|
||||
---
|
||||
|
||||
### Q9. When the light of frequency $2\nu_0$ (where $\nu_0$ is threshold frequency) is incident on a metal plate, the maximum velocity of electrons emitted is $v_1$. When the frequency of the incident radiation is increased to $5\nu_0$, the maximum velocity of electrons emitted from the same plate is $v_2$. The ratio of $v_1$ to $v_2$ is:
|
||||
**Subject**: Physics | **Topic**: Modern Physics | **Difficulty**: Hard
|
||||
|
||||
- **A**: 1:2
|
||||
- **B**: 1:4
|
||||
- **C**: 4:1
|
||||
- **D**: 2:1
|
||||
|
||||
**Correct Answer**: A
|
||||
|
||||
**Solution**:
|
||||
From Einstein's photoelectric equation: $K_{\max} = h\nu - h\nu_0 = \frac{1}{2} m v^2$.
|
||||
Case 1: $\frac{1}{2} m v_1^2 = h(2\nu_0) - h\nu_0 = h\nu_0$.
|
||||
Case 2: $\frac{1}{2} m v_2^2 = h(5\nu_0) - h\nu_0 = 4h\nu_0$.
|
||||
Dividing the two equations: $\frac{v_1^2}{v_2^2} = \frac{1}{4} \implies \frac{v_1}{v_2} = \frac{1}{2}$.
|
||||
|
||||
---
|
||||
|
||||
### Q10. Which logic gate is represented by the boolean expression $Y = \overline{A \cdot B}$?
|
||||
**Subject**: Physics | **Topic**: Semiconductors | **Difficulty**: Easy
|
||||
|
||||
- **A**: NOR
|
||||
- **B**: OR
|
||||
- **C**: NAND
|
||||
- **D**: AND
|
||||
|
||||
**Correct Answer**: C
|
||||
|
||||
**Solution**:
|
||||
The boolean expression $Y = \overline{A \cdot B}$ represents the NAND gate. It gives an output of 0 only when both inputs A and B are 1, and 1 otherwise.
|
||||
|
||||
---
|
||||
|
||||
### Q11. The number of water molecules is maximum in:
|
||||
**Subject**: Chemistry | **Topic**: Physical Chemistry | **Difficulty**: Easy
|
||||
|
||||
- **A**: 18 grams of water
|
||||
- **B**: 18 moles of water
|
||||
- **C**: 18 molecules of water
|
||||
- **D**: 1.8 grams of water
|
||||
|
||||
**Correct Answer**: B
|
||||
|
||||
**Solution**:
|
||||
18 grams of water = 1 mole = $N_A$ molecules.
|
||||
18 moles of water = $18 N_A$ molecules.
|
||||
1.8 grams of water = 0.1 mole = $0.1 N_A$ molecules.
|
||||
Thus, 18 moles of water contains the maximum number of molecules.
|
||||
|
||||
---
|
||||
|
||||
### Q12. The maximum number of electrons in an atom that can have the quantum numbers $n = 4$ and $m_l = +1$ is:
|
||||
**Subject**: Chemistry | **Topic**: Physical Chemistry | **Difficulty**: Medium
|
||||
|
||||
- **A**: 2
|
||||
- **B**: 4
|
||||
- **C**: 6
|
||||
- **D**: 8
|
||||
|
||||
**Correct Answer**: C
|
||||
|
||||
**Solution**:
|
||||
For $n = 4$, the possible values of $l$ are 0, 1, 2, 3.
|
||||
The condition $m_l = +1$ is possible for $l = 1$, $l = 2$, and $l = 3$.
|
||||
Each subshell ($l=1, 2, 3$) will have one orbital with $m_l = +1$.
|
||||
Since each orbital can hold 2 electrons, the total number of electrons is $3 \times 2 = 6$.
|
||||
|
||||
---
|
||||
|
||||
### Q13. The species having pyramidal shape is:
|
||||
**Subject**: Chemistry | **Topic**: Inorganic Chemistry | **Difficulty**: Medium
|
||||
|
||||
- **A**: $\text{SO}_3$
|
||||
- **B**: $\text{BrF}_3$
|
||||
- **C**: $\text{SiO}_3^{2-}$
|
||||
- **D**: $\text{OSF}_2$
|
||||
|
||||
**Correct Answer**: D
|
||||
|
||||
**Solution**:
|
||||
In $\text{OSF}_2$, the central sulfur atom has 4 electron domains (1 double bond to O, 2 single bonds to F, and 1 lone pair). This corresponds to an $sp^3$ hybridization with a trigonal pyramidal shape. $\text{SO}_3$ is trigonal planar, $\text{BrF}_3$ is T-shaped.
|
||||
|
||||
---
|
||||
|
||||
### Q14. For the reversible reaction $\text{N}_2(\text{g}) + 3\text{H}_2(\text{g}) \rightleftharpoons 2\text{NH}_3(\text{g}) + \text{Heat}$, the equilibrium shifts in forward direction:
|
||||
**Subject**: Chemistry | **Topic**: Physical Chemistry | **Difficulty**: Medium
|
||||
|
||||
- **A**: By increasing the concentration of $\text{NH}_3(\text{g})$
|
||||
- **B**: By decreasing the pressure
|
||||
- **C**: By decreasing the concentrations of $\text{N}_2(\text{g})$ and $\text{H}_2(\text{g})$
|
||||
- **D**: By increasing pressure and decreasing temperature
|
||||
|
||||
**Correct Answer**: D
|
||||
|
||||
**Solution**:
|
||||
According to Le Chatelier's principle, an exothermic reaction ($\Delta H < 0$) is favored by low temperatures. A reaction involving a decrease in the number of gaseous moles ($4 \rightarrow 2$) is favored by high pressures. Therefore, increasing pressure and decreasing temperature will shift the equilibrium in the forward direction.
|
||||
|
||||
---
|
||||
|
||||
### Q15. Given $E^\circ_{\text{Cr}^{3+}/\text{Cr}} = -0.74\text{ V}$; $E^\circ_{\text{MnO}_4^-/\text{Mn}^{2+}} = 1.51\text{ V}$; $E^\circ_{\text{Cr}_2\text{O}_7^{2-}/\text{Cr}^{3+}} = 1.33\text{ V}$; $E^\circ_{\text{Cl}/\text{Cl}^-} = 1.36\text{ V}$.
|
||||
Based on the data given above, strongest oxidizing agent will be:
|
||||
**Subject**: Chemistry | **Topic**: Physical Chemistry | **Difficulty**: Hard
|
||||
|
||||
- **A**: $\text{Cl}$
|
||||
- **B**: $\text{Cr}^{3+}$
|
||||
- **C**: $\text{Mn}^{2+}$
|
||||
- **D**: $\text{MnO}_4^-$
|
||||
|
||||
**Correct Answer**: D
|
||||
|
||||
**Solution**:
|
||||
Higher the standard reduction potential ($E^\circ$), stronger is the oxidizing power. The $E^\circ$ value for $\text{MnO}_4^-/\text{Mn}^{2+}$ is the highest ($+1.51\text{ V}$). Therefore, $\text{MnO}_4^-$ is the strongest oxidizing agent among the given species.
|
||||
|
||||
---
|
||||
|
||||
### Q16. Which of the following carbocations is most stable?
|
||||
**Subject**: Chemistry | **Topic**: Organic Chemistry | **Difficulty**: Medium
|
||||
|
||||
- **A**: Allyl carbocation
|
||||
- **B**: Primary alkyl carbocation
|
||||
- **C**: Tert-butyl carbocation
|
||||
- **D**: Triphenylmethyl carbocation
|
||||
|
||||
**Correct Answer**: D
|
||||
|
||||
**Solution**:
|
||||
Stability of carbocations is determined by delocalization of charge. The triphenylmethyl carbocation, $(\text{C}_6\text{H}_5)_3\text{C}^+$, has 3 phenyl rings providing extensive resonance stabilization, making it exceptionally stable compared to the others.
|
||||
|
||||
---
|
||||
|
||||
### Q17. Ozonolysis of an organic compound gives formaldehyde as one of the products. This confirms the presence of:
|
||||
**Subject**: Chemistry | **Topic**: Organic Chemistry | **Difficulty**: Hard
|
||||
|
||||
- **A**: Two isomeric double bonds
|
||||
- **B**: A vinyl group
|
||||
- **C**: An isopropyl group
|
||||
- **D**: An acetylenic triple bond
|
||||
|
||||
**Correct Answer**: B
|
||||
|
||||
**Solution**:
|
||||
Ozonolysis cleaves a carbon-carbon double bond and adds oxygen. Formaldehyde ($\text{HCHO}$) is produced if the original double bond was at the end of a chain, specifically involving a terminal $=\text{CH}_2$ group, which is characteristic of a vinyl group ($-\text{CH}=\text{CH}_2$).
|
||||
|
||||
---
|
||||
|
||||
### Q18. The IUPAC name of $[\text{Pt}(\text{NH}_3)_2\text{Cl}(\text{NO}_2)]$ is:
|
||||
**Subject**: Chemistry | **Topic**: Inorganic Chemistry | **Difficulty**: Medium
|
||||
|
||||
- **A**: Platinum diaminechloronitrite
|
||||
- **B**: Chloronitrito-N-ammineplatinum(II)
|
||||
- **C**: Diamminechloridonitrito-N-platinum(II)
|
||||
- **D**: Diamminechloronitrito-O-platinate(II)
|
||||
|
||||
**Correct Answer**: C
|
||||
|
||||
**Solution**:
|
||||
Ligands are named in alphabetical order: ammine, chlorido, nitrito-N (since it coordinates through nitrogen). The oxidation state of Pt is $x + 0 + (-1) + (-1) = 0 \implies x = +2$. Thus, the correct name is Diamminechloridonitrito-N-platinum(II).
|
||||
|
||||
---
|
||||
|
||||
### Q19. The unit of rate constant for a zero order reaction is:
|
||||
**Subject**: Chemistry | **Topic**: Physical Chemistry | **Difficulty**: Easy
|
||||
|
||||
- **A**: $\text{s}^{-1}$
|
||||
- **B**: $\text{mol}\cdot\text{L}^{-1}\cdot\text{s}^{-1}$
|
||||
- **C**: $\text{L}\cdot\text{mol}^{-1}\cdot\text{s}^{-1}$
|
||||
- **D**: $\text{L}^2\cdot\text{mol}^{-2}\cdot\text{s}^{-1}$
|
||||
|
||||
**Correct Answer**: B
|
||||
|
||||
**Solution**:
|
||||
For a zero-order reaction, Rate = $k[A]^0 = k$. The unit of rate is concentration per unit time ($\text{mol}\cdot\text{L}^{-1}\cdot\text{s}^{-1}$). Thus, the unit of the rate constant $k$ is also $\text{mol}\cdot\text{L}^{-1}\cdot\text{s}^{-1}$.
|
||||
|
||||
---
|
||||
|
||||
### Q20. Which of the following hormones is produced under the condition of stress which stimulates glycogenolysis in the liver of human beings?
|
||||
**Subject**: Chemistry | **Topic**: Organic Chemistry | **Difficulty**: Medium
|
||||
|
||||
- **A**: Thyroxin
|
||||
- **B**: Insulin
|
||||
- **C**: Adrenaline
|
||||
- **D**: Estradiol
|
||||
|
||||
**Correct Answer**: C
|
||||
|
||||
**Solution**:
|
||||
Adrenaline (epinephrine) is the 'fight or flight' hormone released during stress. It promotes the breakdown of glycogen into glucose (glycogenolysis) in the liver to increase blood glucose levels for energy.
|
||||
|
||||
---
|
||||
|
||||
### Q21. Crossing over occurs during which stage of meiosis?
|
||||
**Subject**: Biology | **Topic**: Cell Structure and Function | **Difficulty**: Medium
|
||||
|
||||
- **A**: Leptotene
|
||||
- **B**: Pachytene
|
||||
- **C**: Diplotene
|
||||
- **D**: Zygotene
|
||||
|
||||
**Correct Answer**: B
|
||||
|
||||
**Solution**:
|
||||
Crossing over, the exchange of genetic material between non-sister chromatids of homologous chromosomes, occurs during the pachytene stage of prophase I of meiosis.
|
||||
|
||||
---
|
||||
|
||||
### Q22. A cross between two tall plants resulted in offspring having few dwarf plants. What would be the genotypes of both the parents?
|
||||
**Subject**: Biology | **Topic**: Genetics and Evolution | **Difficulty**: Easy
|
||||
|
||||
- **A**: TT and Tt
|
||||
- **B**: Tt and Tt
|
||||
- **C**: TT and TT
|
||||
- **D**: Tt and tt
|
||||
|
||||
**Correct Answer**: B
|
||||
|
||||
**Solution**:
|
||||
Since both parents are tall but they produce some dwarf offspring, both parents must carry the recessive allele for dwarfness. Thus, their genotype must be heterozygous (Tt). A cross of Tt x Tt yields a 3:1 ratio of tall to dwarf.
|
||||
|
||||
---
|
||||
|
||||
### Q23. Which of the following RNAs should be most abundant in an animal cell?
|
||||
**Subject**: Biology | **Topic**: Genetics and Evolution | **Difficulty**: Medium
|
||||
|
||||
- **A**: tRNA
|
||||
- **B**: mRNA
|
||||
- **C**: miRNA
|
||||
- **D**: rRNA
|
||||
|
||||
**Correct Answer**: D
|
||||
|
||||
**Solution**:
|
||||
Ribosomal RNA (rRNA) constitutes about 80% of the total RNA in an actively growing cell. It forms the structural and catalytic core of ribosomes.
|
||||
|
||||
---
|
||||
|
||||
### Q24. Which of the following terms describes human dentition?
|
||||
**Subject**: Biology | **Topic**: Human Physiology | **Difficulty**: Hard
|
||||
|
||||
- **A**: Thecodont, Diphyodont, Homodont
|
||||
- **B**: Thecodont, Diphyodont, Heterodont
|
||||
- **C**: Pleurodont, Monophyodont, Homodont
|
||||
- **D**: Pleurodont, Diphyodont, Heterodont
|
||||
|
||||
**Correct Answer**: B
|
||||
|
||||
**Solution**:
|
||||
Human dentition is Thecodont (teeth embedded in jaw sockets), Diphyodont (two sets of teeth: deciduous and permanent), and Heterodont (different types of teeth: incisors, canines, premolars, molars).
|
||||
|
||||
---
|
||||
|
||||
### Q25. Which hormone is responsible for the ovulation in human females?
|
||||
**Subject**: Biology | **Topic**: Reproduction | **Difficulty**: Medium
|
||||
|
||||
- **A**: FSH (Follicle Stimulating Hormone)
|
||||
- **B**: Progesterone
|
||||
- **C**: LH (Luteinizing Hormone)
|
||||
- **D**: Estrogen
|
||||
|
||||
**Correct Answer**: C
|
||||
|
||||
**Solution**:
|
||||
A surge in LH (Luteinizing Hormone) mid-cycle triggers the rupture of the Graafian follicle and the subsequent release of the ovum, a process known as ovulation.
|
||||
|
||||
---
|
||||
|
||||
### Q26. The first stable product of CO2 fixation in C3 plants is:
|
||||
**Subject**: Biology | **Topic**: Plant Physiology | **Difficulty**: Medium
|
||||
|
||||
- **A**: Phosphoglyceraldehyde (PGAL)
|
||||
- **B**: Malic acid
|
||||
- **C**: Oxaloacetic acid (OAA)
|
||||
- **D**: 3-Phosphoglyceric acid (3-PGA)
|
||||
|
||||
**Correct Answer**: D
|
||||
|
||||
**Solution**:
|
||||
In the Calvin cycle of C3 plants, CO2 combines with RuBP to form a 6-carbon unstable intermediate that immediately splits into two molecules of 3-phosphoglyceric acid (3-PGA), the first stable product.
|
||||
|
||||
---
|
||||
|
||||
### Q27. The pyramid of energy is always:
|
||||
**Subject**: Biology | **Topic**: Ecology | **Difficulty**: Easy
|
||||
|
||||
- **A**: Upright
|
||||
- **B**: Inverted
|
||||
- **C**: Spindle shaped
|
||||
- **D**: Variable
|
||||
|
||||
**Correct Answer**: A
|
||||
|
||||
**Solution**:
|
||||
The pyramid of energy is always upright because energy is lost as heat at each trophic level (10% law), so energy always decreases as it flows from producers to higher trophic levels.
|
||||
|
||||
---
|
||||
|
||||
### Q28. The cutting of DNA at specific locations became possible with the discovery of:
|
||||
**Subject**: Biology | **Topic**: Biotechnology | **Difficulty**: Medium
|
||||
|
||||
- **A**: Selectable markers
|
||||
- **B**: Ligases
|
||||
- **C**: Restriction enzymes
|
||||
- **D**: Probes
|
||||
|
||||
**Correct Answer**: C
|
||||
|
||||
**Solution**:
|
||||
Restriction endonucleases (or restriction enzymes) cut DNA at specific recognition sequences, making recombinant DNA technology possible.
|
||||
|
||||
---
|
||||
|
||||
### Q29. Receptor sites for neurotransmitters are present on:
|
||||
**Subject**: Biology | **Topic**: Human Physiology | **Difficulty**: Hard
|
||||
|
||||
- **A**: Pre-synaptic membrane
|
||||
- **B**: Tips of axons
|
||||
- **C**: Post-synaptic membrane
|
||||
- **D**: Membrane of synaptic vesicles
|
||||
|
||||
**Correct Answer**: C
|
||||
|
||||
**Solution**:
|
||||
Neurotransmitters are released from synaptic vesicles in the pre-synaptic neuron and diffuse across the synaptic cleft to bind with specific receptor sites located on the post-synaptic membrane.
|
||||
|
||||
---
|
||||
|
||||
### Q30. Which of the following are found in extreme saline conditions?
|
||||
**Subject**: Biology | **Topic**: Diversity in Living World | **Difficulty**: Medium
|
||||
|
||||
- **A**: Eubacteria
|
||||
- **B**: Cyanobacteria
|
||||
- **C**: Mycobacteria
|
||||
- **D**: Archaebacteria
|
||||
|
||||
**Correct Answer**: D
|
||||
|
||||
**Solution**:
|
||||
Archaebacteria are known to live in some of the most harsh habitats. Specifically, halophiles live in extreme saline conditions (e.g., Dead Sea).
|
||||
|
||||
---
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -17,6 +17,7 @@
|
||||
"katex": "^0.17.0",
|
||||
"lucide-react": "^1.21.0",
|
||||
"next": "16.2.9",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.4",
|
||||
"react-datepicker": "^9.1.0",
|
||||
"react-dom": "19.2.4",
|
||||
@@ -27,6 +28,7 @@
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-math": "^6.0.0",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"zod": "^4.4.3",
|
||||
"zustand": "^5.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -6754,6 +6756,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-themes": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
||||
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
@@ -8888,7 +8900,6 @@
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
|
||||
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"katex": "^0.17.0",
|
||||
"lucide-react": "^1.21.0",
|
||||
"next": "16.2.9",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.4",
|
||||
"react-datepicker": "^9.1.0",
|
||||
"react-dom": "19.2.4",
|
||||
@@ -28,6 +29,7 @@
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-math": "^6.0.0",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"zod": "^4.4.3",
|
||||
"zustand": "^5.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
1872
real_jee_questions_rendering_test.json
Normal file
1872
real_jee_questions_rendering_test.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,24 +6,51 @@ import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Loader2, Upload, FileText, Clock, CalendarIcon } from "lucide-react"
|
||||
import { Loader2, Upload, FileText, Clock, CalendarIcon, CheckCircle2, AlertCircle, X, Trash2 } from "lucide-react"
|
||||
import DatePicker from "react-datepicker"
|
||||
import "react-datepicker/dist/react-datepicker.css"
|
||||
import { z } from "zod"
|
||||
|
||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||
|
||||
// Strict Zod schema for client-side validation
|
||||
const QuestionSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
section: z.string().default("General"),
|
||||
subject: z.string(),
|
||||
topic: z.string(),
|
||||
subtopic: z.string(),
|
||||
difficulty: z.string(),
|
||||
type: z.enum(["MCQ_Single", "MCQ_Multiple", "Numerical"]),
|
||||
question_text: z.string(),
|
||||
options: z.record(z.string(), z.any()).nullable().optional(),
|
||||
correct_answer: z.object({
|
||||
value: z.any()
|
||||
}),
|
||||
solution_text: z.string(),
|
||||
language: z.enum(["English", "Hindi", "Bilingual"])
|
||||
}).passthrough();
|
||||
|
||||
const TestSchema = z.array(QuestionSchema);
|
||||
|
||||
export default function AdminTestsPage() {
|
||||
const [tests, setTests] = useState<any[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const { userId } = useActiveUser()
|
||||
|
||||
// Upload Form State
|
||||
const [title, setTitle] = useState("")
|
||||
const [examPattern, setExamPattern] = useState("JEE_Main")
|
||||
const [timeLimit, setTimeLimit] = useState("180")
|
||||
const [scheduledDate, setScheduledDate] = useState<Date | null>(null)
|
||||
const [endDate, setEndDate] = useState<Date | null>(null)
|
||||
const [questionsJson, setQuestionsJson] = useState("")
|
||||
|
||||
// Drag and Drop State
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [validQuestions, setValidQuestions] = useState<Record<string, unknown>[] | null>(null)
|
||||
const [validationErrors, setValidationErrors] = useState<{ path: string, message: string }[]>([])
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const fetchTests = async () => {
|
||||
try {
|
||||
@@ -40,6 +67,7 @@ export default function AdminTestsPage() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
fetchTests()
|
||||
}, [])
|
||||
|
||||
@@ -56,28 +84,106 @@ export default function AdminTestsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (testId: string) => {
|
||||
if (!confirm("Are you sure you want to delete this test and all its questions? This action cannot be undone.")) return
|
||||
try {
|
||||
const res = await fetch(`/api/admin/tests/${testId}`, {
|
||||
method: "DELETE"
|
||||
})
|
||||
if (res.ok) {
|
||||
alert("Test deleted successfully!")
|
||||
fetchTests()
|
||||
} else {
|
||||
alert("Failed to delete test.")
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert("Error deleting test")
|
||||
}
|
||||
}
|
||||
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
setIsDragging(true)
|
||||
}
|
||||
|
||||
const handleDragLeave = (e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
setIsDragging(false)
|
||||
}
|
||||
|
||||
const handleDrop = (e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
setIsDragging(false)
|
||||
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
processFile(e.dataTransfer.files[0])
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files.length > 0) {
|
||||
processFile(e.target.files[0])
|
||||
}
|
||||
}
|
||||
|
||||
const processFile = (file: File) => {
|
||||
if (!file.name.endsWith('.json')) {
|
||||
setValidationErrors([{ path: "File", message: "Only .json files are allowed." }])
|
||||
setValidQuestions(null)
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const json = JSON.parse(e.target?.result as string)
|
||||
|
||||
// Zod validation
|
||||
const result = TestSchema.safeParse(json)
|
||||
if (result.success) {
|
||||
setValidQuestions(result.data)
|
||||
setValidationErrors([])
|
||||
} else {
|
||||
setValidQuestions(null)
|
||||
const errors = (result as any).error.errors.map((err: any) => ({
|
||||
path: err.path.join('.'),
|
||||
message: err.message
|
||||
}))
|
||||
setValidationErrors(errors)
|
||||
}
|
||||
} catch {
|
||||
setValidQuestions(null)
|
||||
setValidationErrors([{ path: "JSON Parsing", message: "Invalid JSON format. Check for syntax errors." }])
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}
|
||||
|
||||
const removeFile = () => {
|
||||
setValidQuestions(null)
|
||||
setValidationErrors([])
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = ""
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpload = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!questionsJson) return
|
||||
if (!validQuestions || validQuestions.length === 0) {
|
||||
alert("Please upload a valid JSON file with questions first.")
|
||||
return
|
||||
}
|
||||
if (!userId) return
|
||||
|
||||
setUploading(true)
|
||||
|
||||
let parsedQuestions = [];
|
||||
try {
|
||||
parsedQuestions = JSON.parse(questionsJson);
|
||||
} catch(err) {
|
||||
alert("Invalid JSON format. Please paste a valid JSON array.");
|
||||
setUploading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
title,
|
||||
exam_pattern: examPattern,
|
||||
time_limit: parseInt(timeLimit),
|
||||
admin_id: userId,
|
||||
questions_json: parsedQuestions,
|
||||
questions_json: validQuestions,
|
||||
scheduled_date: scheduledDate ? scheduledDate.toISOString() : null,
|
||||
end_date: endDate ? endDate.toISOString() : null,
|
||||
};
|
||||
@@ -92,10 +198,11 @@ export default function AdminTestsPage() {
|
||||
if (res.ok) {
|
||||
alert("Test created successfully!")
|
||||
setTitle("")
|
||||
setQuestionsJson("")
|
||||
removeFile()
|
||||
fetchTests()
|
||||
} else {
|
||||
alert("Creation failed")
|
||||
const err = await res.json()
|
||||
alert(`Creation failed: ${JSON.stringify(err)}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -115,13 +222,13 @@ export default function AdminTestsPage() {
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
|
||||
{/* Upload Form */}
|
||||
<Card className="lg:col-span-1 border-muted bg-card shadow-sm">
|
||||
<Card className="lg:col-span-1 border-muted bg-card shadow-sm h-fit">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Upload className="w-5 h-5 text-primary" />
|
||||
Create Manual Test
|
||||
</CardTitle>
|
||||
<CardDescription>Paste the structured JSON block of questions in LaTeX format to create a deterministic test.</CardDescription>
|
||||
<CardDescription>Upload a structured JSON array of questions in LaTeX format to create a deterministic test.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleUpload} className="space-y-4">
|
||||
@@ -150,7 +257,7 @@ export default function AdminTestsPage() {
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2 flex flex-col">
|
||||
<label className="text-sm font-medium">Scheduled Start (Optional)</label>
|
||||
<label className="text-sm font-medium">Scheduled Start</label>
|
||||
<DatePicker
|
||||
selected={scheduledDate}
|
||||
onChange={(date: Date | null) => setScheduledDate(date)}
|
||||
@@ -160,11 +267,11 @@ export default function AdminTestsPage() {
|
||||
timeCaption="time"
|
||||
dateFormat="MMMM d, yyyy h:mm aa"
|
||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||
placeholderText="Select start date & time"
|
||||
placeholderText="Optional"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2 flex flex-col">
|
||||
<label className="text-sm font-medium">Scheduled End (Optional)</label>
|
||||
<label className="text-sm font-medium">Scheduled End</label>
|
||||
<DatePicker
|
||||
selected={endDate}
|
||||
onChange={(date: Date | null) => setEndDate(date)}
|
||||
@@ -174,24 +281,66 @@ export default function AdminTestsPage() {
|
||||
timeCaption="time"
|
||||
dateFormat="MMMM d, yyyy h:mm aa"
|
||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||
placeholderText="Select end date & time"
|
||||
placeholderText="Optional"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Questions JSON (LaTeX Format)</label>
|
||||
<textarea
|
||||
required
|
||||
value={questionsJson}
|
||||
onChange={e => setQuestionsJson(e.target.value)}
|
||||
className="w-full min-h-[250px] p-3 text-sm font-mono border rounded-md bg-background focus:ring-primary focus:border-primary"
|
||||
placeholder='[{"section": "Physics", "text_en": "Question text...", "options_en": ["A", "B", "C", "D"], "correct_answer": "A"}]'
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Paste your questions as a JSON array.</p>
|
||||
<div className="space-y-2 pt-2">
|
||||
<label className="text-sm font-medium">Questions JSON File</label>
|
||||
|
||||
{validQuestions ? (
|
||||
<div className="flex items-center justify-between p-3 border border-green-500/50 bg-green-500/10 rounded-md">
|
||||
<div className="flex items-center gap-2 text-green-600 dark:text-green-400">
|
||||
<CheckCircle2 className="w-5 h-5" />
|
||||
<span className="text-sm font-medium">{validQuestions.length} questions validated</span>
|
||||
</div>
|
||||
<Button type="button" variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-foreground" onClick={removeFile}>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={`relative flex flex-col items-center justify-center p-6 border-2 border-dashed rounded-md transition-colors ${isDragging ? "border-primary bg-primary/5" : "border-muted-foreground/25 hover:border-primary/50"}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<Upload className="w-8 h-8 text-muted-foreground mb-3" />
|
||||
<p className="text-sm font-medium text-center">Drag and drop your .json file here</p>
|
||||
<p className="text-xs text-muted-foreground text-center mt-1">or click to browse files</p>
|
||||
<input
|
||||
type="file"
|
||||
accept=".json"
|
||||
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
||||
onChange={handleFileChange}
|
||||
ref={fileInputRef}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{validationErrors.length > 0 && (
|
||||
<div className="mt-2 p-3 border border-destructive/50 bg-destructive/10 rounded-md max-h-48 overflow-y-auto">
|
||||
<div className="flex items-center gap-2 text-destructive mb-2">
|
||||
<AlertCircle className="w-4 h-4" />
|
||||
<span className="text-sm font-bold">Validation Errors Found</span>
|
||||
</div>
|
||||
<ul className="space-y-1">
|
||||
{validationErrors.slice(0, 50).map((err, i) => (
|
||||
<li key={i} className="text-xs text-destructive/90 flex gap-2">
|
||||
<span className="font-mono bg-destructive/20 px-1 rounded">{err.path}</span>
|
||||
<span>{err.message}</span>
|
||||
</li>
|
||||
))}
|
||||
{validationErrors.length > 50 && (
|
||||
<li className="text-xs text-muted-foreground italic mt-2">...and {validationErrors.length - 50} more errors</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full" disabled={uploading}>
|
||||
<Button type="submit" className="w-full" disabled={uploading || !validQuestions}>
|
||||
{uploading ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <FileText className="w-4 h-4 mr-2" />}
|
||||
{uploading ? "Creating Test..." : "Create Test from JSON"}
|
||||
</Button>
|
||||
@@ -200,7 +349,7 @@ export default function AdminTestsPage() {
|
||||
</Card>
|
||||
|
||||
{/* Existing Tests List */}
|
||||
<Card className="lg:col-span-2 border-muted bg-card shadow-sm">
|
||||
<Card className="lg:col-span-2 border-muted bg-card shadow-sm h-fit">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<FileText className="w-5 h-5 text-primary" />
|
||||
@@ -240,9 +389,19 @@ export default function AdminTestsPage() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-medium">{test.is_active ? "Published" : "Hidden"}</span>
|
||||
<Switch checked={test.is_active} onCheckedChange={() => handleToggle(test.id)} />
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium">{test.is_active ? "Published" : "Hidden"}</span>
|
||||
<Switch checked={test.is_active} onCheckedChange={() => handleToggle(test.id)} />
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 text-destructive hover:bg-destructive/10"
|
||||
onClick={() => handleDelete(test.id)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -250,7 +409,6 @@ export default function AdminTestsPage() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Clock, Flag, ChevronLeft, ChevronRight, Pause as PauseIcon, MoreVertical, Calculator as CalculatorIcon, FlaskConical, Edit3, XCircle, Play, Shield, AlertTriangle, Moon, Sun } from "lucide-react"
|
||||
import Image from "next/image"
|
||||
import { useTheme } from "next-themes"
|
||||
import { useCBTStore, QuestionStatus } from "@/store/cbtStore"
|
||||
import { useExamTimer } from "@/hooks/useExamTimer"
|
||||
import ReactMarkdown from "react-markdown"
|
||||
@@ -38,7 +37,29 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
const [showPeriodicTable, setShowPeriodicTable] = useState(false);
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [showSubmitConfirm, setShowSubmitConfirm] = useState(false);
|
||||
const { theme, setTheme } = useTheme();
|
||||
const [localTheme, setLocalTheme] = useState<string>('light');
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
if (document.documentElement.classList.contains('dark')) {
|
||||
setLocalTheme('dark');
|
||||
} else {
|
||||
setLocalTheme('light');
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleThemeToggle = () => {
|
||||
if (document.documentElement.classList.contains('dark')) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('theme', 'light');
|
||||
setLocalTheme('light');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
setLocalTheme('dark');
|
||||
}
|
||||
};
|
||||
|
||||
// Anti-cheat state
|
||||
const [violationCount, setViolationCount] = useState(0);
|
||||
@@ -63,9 +84,6 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
resetExamState,
|
||||
status,
|
||||
attemptId,
|
||||
currentQuestionId,
|
||||
getQuestion,
|
||||
examState
|
||||
} = useCBTStore();
|
||||
|
||||
const { formattedTime, isTimedOut } = useExamTimer();
|
||||
@@ -243,8 +261,8 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
<p className="font-semibold">Preferred Theme</p>
|
||||
<p className="text-sm text-muted-foreground">Choose dark or light mode for the exam</p>
|
||||
</div>
|
||||
<Button variant="outline" size="icon" onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
|
||||
{theme === 'dark' ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
|
||||
<Button variant="outline" size="icon" onClick={handleThemeToggle}>
|
||||
{localTheme === 'dark' ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -267,13 +285,13 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
onRetake={examType === 'DPS' ? () => {
|
||||
resetExamState();
|
||||
window.location.reload();
|
||||
} : undefined}
|
||||
} : () => {}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const currentQuestion = getQuestion(currentQuestionId)
|
||||
const currentState = currentQuestion ? examState?.answers[currentQuestion.id] : null
|
||||
const currentQuestion = questions[currentQuestionIndex];
|
||||
const currentState = currentQuestion ? answers[currentQuestion.id] : null
|
||||
|
||||
const handleOptionSelect = (qId: string, letter: string, type: string) => {
|
||||
if (type === 'Multiple_Choice') {
|
||||
@@ -281,7 +299,11 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
const selectedArray = currentSelected.split(',').filter(Boolean);
|
||||
if (selectedArray.includes(letter)) {
|
||||
const newArray = selectedArray.filter((l: string) => l !== letter).sort();
|
||||
selectAnswer(qId, newArray.length > 0 ? newArray.join(',') : null);
|
||||
if (newArray.length > 0) {
|
||||
selectAnswer(qId, newArray.join(','));
|
||||
} else {
|
||||
clearResponse(qId);
|
||||
}
|
||||
} else {
|
||||
selectedArray.push(letter);
|
||||
selectAnswer(qId, selectedArray.sort().join(','));
|
||||
@@ -291,7 +313,8 @@ export default function CBTExamPage({ params }: { params: Promise<{ examId: stri
|
||||
}
|
||||
}
|
||||
|
||||
// Effect to close overlay tools on EscapealetteStats = Object.values(answers).reduce(
|
||||
// Effect to close overlay tools on Escape
|
||||
const paletteStats = Object.values(answers || {}).reduce(
|
||||
(acc, val) => {
|
||||
acc[val.status]++;
|
||||
return acc;
|
||||
|
||||
@@ -118,6 +118,37 @@ export default function AttemptHistoryPage({ params }: { params: Promise<{ attem
|
||||
const q = questions[ans.question_id];
|
||||
if (!q) return null;
|
||||
|
||||
const getUserAnswerText = () => {
|
||||
if (ans.is_subjective || q.type === 'Subjective' || q.type === 'Numerical') {
|
||||
if (Array.isArray(ans.selected_answer)) {
|
||||
return ans.selected_answer.join(", ");
|
||||
}
|
||||
return String(ans.selected_answer || 'Not Attempted');
|
||||
}
|
||||
if (Array.isArray(ans.selected_answer)) {
|
||||
return ans.selected_answer.map((val: any) => q.options && q.options[val] ? `**${val})** ${q.options[val]}` : val).join("\n\n");
|
||||
}
|
||||
return q.options && q.options[ans.selected_answer]
|
||||
? `**${ans.selected_answer})** ${q.options[ans.selected_answer]}`
|
||||
: String(ans.selected_answer || 'Not Attempted');
|
||||
};
|
||||
|
||||
const getCorrectAnswerText = () => {
|
||||
const rawCorrect = q.correct_answer?.correct || q.correct_answer?.value || q.correct_answer?.selected;
|
||||
if (ans.is_subjective || q.type === 'Subjective' || q.type === 'Numerical') {
|
||||
if (Array.isArray(rawCorrect)) {
|
||||
return rawCorrect.join(", ");
|
||||
}
|
||||
return String(rawCorrect || 'Subjective text');
|
||||
}
|
||||
if (Array.isArray(rawCorrect)) {
|
||||
return rawCorrect.map((val: any) => q.options && q.options[val] ? `**${val})** ${q.options[val]}` : val).join("\n\n");
|
||||
}
|
||||
return q.options && q.options[rawCorrect]
|
||||
? `**${rawCorrect})** ${q.options[rawCorrect]}`
|
||||
: String(rawCorrect || 'N/A');
|
||||
};
|
||||
|
||||
return (
|
||||
<Card key={ans.question_id} className={`border-l-4 ${ans.is_correct ? 'border-l-green-500' : 'border-l-red-500'}`}>
|
||||
<CardContent className="p-6">
|
||||
@@ -150,9 +181,7 @@ export default function AttemptHistoryPage({ params }: { params: Promise<{ attem
|
||||
<p className="text-sm font-bold text-muted-foreground uppercase">Your Answer</p>
|
||||
<div className={`p-3 rounded-lg border prose prose-sm dark:prose-invert ${ans.is_correct ? 'bg-green-500/10 border-green-500/30 text-green-500' : 'bg-red-500/10 border-red-500/30 text-red-500'}`}>
|
||||
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
|
||||
{ans.is_subjective || q.type === 'Subjective' || q.type === 'Numerical'
|
||||
? (ans.selected_answer || 'Not Attempted')
|
||||
: (q.options && q.options[ans.selected_answer] ? `${ans.selected_answer}) ${q.options[ans.selected_answer]}` : ans.selected_answer || 'Not Attempted')}
|
||||
{getUserAnswerText()}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,11 +189,7 @@ export default function AttemptHistoryPage({ params }: { params: Promise<{ attem
|
||||
<p className="text-sm font-bold text-muted-foreground uppercase">Correct Answer</p>
|
||||
<div className="p-3 rounded-lg border bg-green-500/10 border-green-500/30 text-green-500 prose prose-sm dark:prose-invert">
|
||||
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
|
||||
{ans.is_subjective || q.type === 'Subjective' || q.type === 'Numerical'
|
||||
? (q.correct_answer?.value || q.correct_answer?.correct || q.correct_answer?.selected || 'Subjective text')
|
||||
: (q.options && q.options[q.correct_answer?.correct || q.correct_answer?.value || q.correct_answer?.selected]
|
||||
? `${q.correct_answer?.correct || q.correct_answer?.value || q.correct_answer?.selected}) ${q.options[q.correct_answer?.correct || q.correct_answer?.value || q.correct_answer?.selected]}`
|
||||
: (q.correct_answer?.correct || q.correct_answer?.value || q.correct_answer?.selected || 'N/A'))}
|
||||
{getCorrectAnswerText()}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,6 @@ export interface UserProfile {
|
||||
state: string;
|
||||
tier: "Free" | "Pro" | "Al_Coach"; // Al_Coach in database schema
|
||||
streak_count: number;
|
||||
streak_count: number;
|
||||
longest_streak: number;
|
||||
active_session_token?: string;
|
||||
created_at?: string;
|
||||
|
||||
Reference in New Issue
Block a user