-
-
-
- {loading && (
-
-
- Đang tải cơ sở dữ liệu{progress > 0 ? ` · ${progress}%` : "..."}
-
-
-
- )}
-
- {error && Lỗi: {error}
}
-
-
-
-
-
-
- {activeTab === "search" && (
- <>
-
-
- {searchError && (
- Lỗi truy vấn: {searchError}
- )}
-
-
-
- {results && results.length >= MAX_RESULTS && (
-
- Hiển thị tối đa {MAX_RESULTS} kết quả. Vui lòng tìm kiếm cụ
- thể hơn.
-
- )}
- >
- )}
-
- {activeTab === "sql" && (
-
- )}
-
-
-
-
- );
-}
-
-export default App;
diff --git a/2016/src/components/custom-query.jsx b/2016/src/components/custom-query.jsx
deleted file mode 100644
index 5b4ed01..0000000
--- a/2016/src/components/custom-query.jsx
+++ /dev/null
@@ -1,219 +0,0 @@
-import { useState, useCallback } from "react";
-
-const MAX_ROWS = 1000;
-
-const PRESET_QUERIES = [
- {
- label: "Top 10 điểm Toán cao nhất",
- sql: `SELECT so_bao_danh, ho_ten, ten_cum_thi, toan
-FROM student WHERE toan IS NOT NULL
-ORDER BY toan DESC LIMIT 10`,
- },
- {
- label: "Điểm trung bình theo cụm thi",
- sql: `SELECT ten_cum_thi,
- COUNT(*) AS so_luong,
- ROUND(AVG(toan), 2) AS tb_toan,
- ROUND(AVG(ngu_van), 2) AS tb_van,
- ROUND(AVG(tieng_anh), 2) AS tb_anh
-FROM student
-GROUP BY ten_cum_thi
-ORDER BY so_luong DESC
-LIMIT 20`,
- },
- {
- label: "Thống kê theo giới tính",
- sql: `SELECT gioi_tinh,
- COUNT(*) AS so_luong,
- ROUND(AVG(toan), 2) AS tb_toan,
- ROUND(AVG(ngu_van), 2) AS tb_van
-FROM student
-WHERE gioi_tinh IS NOT NULL
-GROUP BY gioi_tinh`,
- },
- {
- label: "Thí sinh đạt 9+ điểm Toán",
- sql: `SELECT so_bao_danh, ho_ten, ten_cum_thi, gioi_tinh, toan
-FROM student WHERE toan >= 9
-ORDER BY toan DESC LIMIT 50`,
- },
- {
- label: "Phân bố điểm Toán",
- sql: `SELECT
- CASE
- WHEN toan < 1 THEN '0-1'
- WHEN toan < 2 THEN '1-2'
- WHEN toan < 3 THEN '2-3'
- WHEN toan < 4 THEN '3-4'
- WHEN toan < 5 THEN '4-5'
- WHEN toan < 6 THEN '5-6'
- WHEN toan < 7 THEN '6-7'
- WHEN toan < 8 THEN '7-8'
- WHEN toan < 9 THEN '8-9'
- ELSE '9-10'
- END AS khoang_diem,
- COUNT(*) AS so_luong
-FROM student WHERE toan IS NOT NULL
-GROUP BY khoang_diem
-ORDER BY khoang_diem`,
- },
- {
- label: "Số thí sinh theo ngoại ngữ",
- sql: `SELECT
- SUM(CASE WHEN tieng_anh IS NOT NULL THEN 1 ELSE 0 END) AS tieng_anh,
- SUM(CASE WHEN tieng_phap IS NOT NULL THEN 1 ELSE 0 END) AS tieng_phap,
- SUM(CASE WHEN tieng_duc IS NOT NULL THEN 1 ELSE 0 END) AS tieng_duc,
- SUM(CASE WHEN tieng_nhat IS NOT NULL THEN 1 ELSE 0 END) AS tieng_nhat,
- SUM(CASE WHEN tieng_trung IS NOT NULL THEN 1 ELSE 0 END) AS tieng_trung
-FROM student`,
- },
- {
- label: "Schema bảng student",
- sql: `PRAGMA table_info(student)`,
- },
-];
-
-export function CustomQuery({ db, disabled }) {
- const [sql, setSql] = useState("");
- const [columns, setColumns] = useState([]);
- const [rows, setRows] = useState([]);
- const [queryError, setQueryError] = useState(null);
- const [execTime, setExecTime] = useState(null);
-
- const executeQuery = useCallback(
- (queryStr) => {
- if (!db) return;
- setQueryError(null);
- setColumns([]);
- setRows([]);
- setExecTime(null);
-
- const trimmed = queryStr.trim();
- if (!trimmed) return;
-
- // Safety: only allow read-only statements
- const upper = trimmed.toUpperCase();
- const allowed = ["SELECT", "PRAGMA", "EXPLAIN", "WITH"];
- if (!allowed.some((kw) => upper.startsWith(kw))) {
- setQueryError(
- "Chỉ hỗ trợ truy vấn đọc (SELECT, PRAGMA, EXPLAIN, WITH).",
- );
- return;
- }
-
- // Auto-add LIMIT if user forgot
- let finalSql = trimmed;
- if (
- upper.startsWith("SELECT") &&
- !upper.includes("LIMIT") &&
- !upper.includes("PRAGMA")
- ) {
- finalSql = `${trimmed.replace(/;$/, "")} LIMIT ${MAX_ROWS}`;
- }
-
- try {
- const start = performance.now();
- const stmt = db.prepare(finalSql);
- const colNames = stmt.getColumnNames();
- const resultRows = [];
-
- let count = 0;
- while (stmt.step() && count < MAX_ROWS) {
- resultRows.push(stmt.get());
- count++;
- }
- stmt.free();
-
- const elapsed = performance.now() - start;
- setColumns(colNames);
- setRows(resultRows);
- setExecTime(elapsed.toFixed(1));
- } catch (err) {
- setQueryError(err.message);
- }
- },
- [db],
- );
-
- function handleSubmit(e) {
- e.preventDefault();
- executeQuery(sql);
- }
-
- function handlePreset(presetSql) {
- setSql(presetSql);
- executeQuery(presetSql);
- }
-
- return (
-
-
- Mẫu truy vấn:
- {PRESET_QUERIES.map((p, i) => (
-
- ))}
-
-
-
-
- {queryError &&
Lỗi: {queryError}
}
-
- {columns.length > 0 && (
-
-
-
-
- {columns.map((col, i) => (
- | {col} |
- ))}
-
-
-
- {rows.map((row, ri) => (
-
- {row.map((cell, ci) => (
- |
- {cell === null ? "NULL" : String(cell)}
- |
- ))}
-
- ))}
-
-
- {rows.length >= MAX_ROWS && (
-
- Hiển thị tối đa {MAX_ROWS} kết quả. Thêm LIMIT để giới hạn.
-
- )}
-
- )}
-
- );
-}
diff --git a/2016/src/components/score-table.jsx b/2016/src/components/score-table.jsx
deleted file mode 100644
index e5258d6..0000000
--- a/2016/src/components/score-table.jsx
+++ /dev/null
@@ -1,74 +0,0 @@
-const SUBJECT_COLUMNS = [
- { key: "toan", label: "Toán" },
- { key: "ngu_van", label: "Ngữ văn" },
- { key: "vat_ly", label: "Vật lí" },
- { key: "hoa_hoc", label: "Hóa học" },
- { key: "sinh_hoc", label: "Sinh học" },
- { key: "lich_su", label: "Lịch sử" },
- { key: "dia_ly", label: "Địa lí" },
- { key: "tieng_anh", label: "T.Anh" },
- { key: "tieng_phap", label: "T.Pháp" },
- { key: "tieng_duc", label: "T.Đức" },
- { key: "tieng_nhat", label: "T.Nhật" },
- { key: "tieng_trung", label: "T.Trung" },
-];
-
-function formatScore(val) {
- if (val === null || val === undefined) return "—";
- return Number(val).toFixed(2);
-}
-
-function scoreClass(val) {
- if (val === null || val === undefined) return "score-cell score-empty";
- const num = Number(val);
- if (num >= 8) return "score-cell score-high";
- if (num < 4) return "score-cell score-low";
- return "score-cell";
-}
-
-export function ScoreTable({ results }) {
- if (!results) return null;
- if (results.length === 0) {
- return Không tìm thấy kết quả.
;
- }
-
- return (
- <>
-