diff --git a/2016/scripts/build-database.js b/2016/scripts/build-database.js index c4b0306..10c4e24 100644 --- a/2016/scripts/build-database.js +++ b/2016/scripts/build-database.js @@ -26,6 +26,16 @@ const SCORE_PATTERNS = { const ALL_SCORE_FIELDS = Object.keys(SCORE_PATTERNS); +// Strip Vietnamese diacritics: "NGUYỄN BŨU LỘC" → "nguyen buu loc" +function toAscii(str) { + return str + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .replace(/đ/g, "d") + .replace(/Đ/g, "D") + .toLowerCase(); +} + // Parse score text "Toán: 3.75 Ngữ văn: 5.00 ..." into { toan: 3.75, ... } function parseScoreString(diemThi) { const scores = {}; @@ -85,6 +95,7 @@ function processSeparateScoresRow(row) { return { so_bao_danh: sbd, ho_ten: hoTen, + ho_ten_ascii: toAscii(hoTen), ngay_sinh: null, ten_cum_thi: null, gioi_tinh: null, @@ -126,6 +137,7 @@ function processMappedRow(row, map) { return { so_bao_danh: sbd, ho_ten: hoTen, + ho_ten_ascii: toAscii(hoTen), ngay_sinh: ngaySinh || null, ten_cum_thi: tenCumThi || null, gioi_tinh: gioiTinh || null, @@ -148,6 +160,7 @@ function main() { CREATE TABLE student ( so_bao_danh TEXT PRIMARY KEY, ho_ten TEXT NOT NULL, + ho_ten_ascii TEXT NOT NULL, ngay_sinh TEXT, ten_cum_thi TEXT, gioi_tinh TEXT, @@ -165,16 +178,17 @@ function main() { tieng_trung REAL ); CREATE INDEX idx_ho_ten ON student(ho_ten); + CREATE INDEX idx_ho_ten_ascii ON student(ho_ten_ascii); CREATE INDEX idx_ten_cum_thi ON student(ten_cum_thi); `); const insert = db.prepare(` INSERT OR REPLACE INTO student - (so_bao_danh, ho_ten, ngay_sinh, ten_cum_thi, gioi_tinh, + (so_bao_danh, ho_ten, ho_ten_ascii, ngay_sinh, ten_cum_thi, gioi_tinh, toan, ngu_van, vat_ly, hoa_hoc, sinh_hoc, lich_su, dia_ly, tieng_anh, tieng_phap, tieng_duc, tieng_nhat, tieng_trung) VALUES - (@so_bao_danh, @ho_ten, @ngay_sinh, @ten_cum_thi, @gioi_tinh, + (@so_bao_danh, @ho_ten, @ho_ten_ascii, @ngay_sinh, @ten_cum_thi, @gioi_tinh, @toan, @ngu_van, @vat_ly, @hoa_hoc, @sinh_hoc, @lich_su, @dia_ly, @tieng_anh, @tieng_phap, @tieng_duc, @tieng_nhat, @tieng_trung) `); diff --git a/2016/src/App.jsx b/2016/src/App.jsx index 2d9d3eb..f09e734 100644 --- a/2016/src/App.jsx +++ b/2016/src/App.jsx @@ -8,6 +8,20 @@ import "./App.css"; const DB_URL = import.meta.env.BASE_URL + "thptqg2016.db.gz"; const MAX_RESULTS = 100; +// Strip Vietnamese diacritics for search: "nguyễn bữu lộc" → "nguyen buu loc" +function toAscii(str) { + return str + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .replace(/đ/gi, "d") + .toLowerCase(); +} + +// Check if string contains only ASCII (no Vietnamese diacritics) +function isAsciiOnly(str) { + return /^[\x00-\x7F]*$/.test(str); +} + function App() { const { db, loading, error, progress } = useSqlite(DB_URL); const [results, setResults] = useState(null); @@ -28,11 +42,25 @@ function App() { "SELECT * FROM student WHERE so_bao_danh = $q LIMIT $limit", ); stmt.bind({ $q: query.toUpperCase(), $limit: MAX_RESULTS }); - } else { + } else if (isAsciiOnly(query)) { + // ASCII input: search against normalized column (diacritics-insensitive) stmt = db.prepare( - "SELECT * FROM student WHERE ho_ten LIKE $q LIMIT $limit", + "SELECT * FROM student WHERE ho_ten_ascii LIKE $q LIMIT $limit", ); - stmt.bind({ $q: `%${query}%`, $limit: MAX_RESULTS }); + stmt.bind({ $q: `%${toAscii(query)}%`, $limit: MAX_RESULTS }); + } else { + // Vietnamese input: search both original and normalized + const normalized = toAscii(query); + stmt = db.prepare( + `SELECT * FROM student + WHERE ho_ten LIKE $q OR ho_ten_ascii LIKE $qn + LIMIT $limit`, + ); + stmt.bind({ + $q: `%${query}%`, + $qn: `%${normalized}%`, + $limit: MAX_RESULTS, + }); } const rows = []; diff --git a/2016/src/components/search-form.jsx b/2016/src/components/search-form.jsx index 3087fe5..7fcbbe4 100644 --- a/2016/src/components/search-form.jsx +++ b/2016/src/components/search-form.jsx @@ -15,7 +15,7 @@ export function SearchForm({ onSearch, disabled }) { type="text" value={query} onChange={(e) => setQuery(e.target.value)} - placeholder="Nhập số báo danh hoặc họ tên..." + placeholder="Nhập SBD hoặc họ tên (VD: nguyen van a)..." disabled={disabled} autoFocus />