CVNSS commited on
Commit
0439ae0
·
verified ·
1 Parent(s): 7226c1a

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +177 -83
index.html CHANGED
@@ -2,109 +2,203 @@
2
  <html lang="vi">
3
  <head>
4
  <meta charset="UTF-8">
5
- <title>Tra cứu 2 chiều Xã/Phường Sáp Nhập 2025</title>
6
- <meta name="viewport" content="width=device-width, initial-scale=1">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8
  <style>
9
- body { background: #f6fafd; font-family: Arial, sans-serif;}
10
- .filter-box { background: #fff; padding: 16px; border-radius: 10px; box-shadow: 0 2px 8px #0001; margin-bottom: 18px; }
11
- .sticky-th th { position: sticky; top: 0; background: #f3f3f3; }
12
- .small { font-size: 13px; }
13
- .table-sm th, .table-sm td { padding: .4rem .5rem; }
14
- @media (max-width: 900px) {
15
- .table { font-size: 12px; }
16
- .filter-box { padding: 6px; }
 
 
 
 
 
 
17
  }
18
  </style>
19
  </head>
20
  <body>
21
- <div class="container py-4">
22
- <h3 class="mb-3 text-primary">🔎 Tra cứu 2 chiều Xã/Phường sáp nhập 2025</h3>
23
- <div class="filter-box row g-2 align-items-end mb-2">
24
- <div class="col-md-3 col-6">
25
- <label class="form-label small">Tỉnh/Thành mới</label>
26
- <select id="province_new" class="form-select form-select-sm"><option value="">-- Tất cả --</option></select>
27
- </div>
28
- <div class="col-md-3 col-6">
29
- <label class="form-label small">Xã/Phường mới</label>
30
- <input id="ward_new" type="text" class="form-control form-control-sm" placeholder="Tìm xã/phường mới">
 
 
 
 
 
 
 
 
 
 
31
  </div>
32
- <div class="col-md-3 col-6">
33
- <label class="form-label small">Tỉnh/Thành cũ</label>
34
- <select id="province_old" class="form-select form-select-sm"><option value="">-- Tất cả --</option></select>
 
 
 
35
  </div>
36
- <div class="col-md-3 col-6">
37
- <label class="form-label small">Xã/Phường cũ</label>
38
- <input id="ward_old" type="text" class="form-control form-control-sm" placeholder="Tìm xã/phường cũ">
 
 
 
39
  </div>
40
  </div>
41
- <div style="max-height:68vh;overflow:auto">
42
- <table class="table table-bordered table-hover table-sm align-middle mb-0">
43
- <thead class="sticky-th">
44
- <tr class="table-light small">
45
- <th>Tỉnh/Thành mới</th>
46
- <th>Xã/Phường mới</th>
47
- <th>Tỉnh/Thành cũ</th>
48
- <th>Xã/Phường cũ</th>
49
- </tr>
50
- </thead>
51
- <tbody id="result_tbody"></tbody>
52
- </table>
53
- </div>
54
- <div class="small text-secondary mt-2">Tìm kiếm 2 chiều, lọc theo bất kỳ trường nào (tỉnh/xã mới hoặc cũ).<br>Đảm bảo file <b>wards_2025_full.json</b> đã upload cùng thư mục.<br><b>Tip:</b> Bạn có thể tìm hoặc copy toàn bộ bảng để dùng cho nghiệp vụ đối chiếu.</div>
55
- </div>
56
  <script>
57
- let DATA = [];
58
- const dataUrl = "wards_2025_full.json"; // Đảm bảo upload file này cùng thư mục Spaces!
 
 
 
59
 
60
- function unique(arr) {
61
- return [...new Set(arr)].filter(Boolean).sort((a,b) => a.localeCompare(b));
 
62
  }
63
 
64
- fetch(dataUrl).then(r => r.json()).then(data => {
65
- DATA = data;
66
- initFilters();
67
- renderTable(DATA);
68
- });
69
-
70
- function initFilters() {
71
- const selNew = document.getElementById('province_new');
72
- const selOld = document.getElementById('province_old');
73
- const provinces_new = unique(DATA.map(d => d.province_new));
74
- const provinces_old = unique(DATA.map(d => d.province_old));
75
- provinces_new.forEach(p => selNew.appendChild(new Option(p, p)));
76
- provinces_old.forEach(p => selOld.appendChild(new Option(p, p)));
77
- ['province_new','province_old','ward_new','ward_old'].forEach(id => {
78
- document.getElementById(id).addEventListener('input', filterTable);
79
  });
80
  }
81
 
82
- function filterTable() {
83
- let p_new = document.getElementById('province_new').value.trim();
84
- let p_old = document.getElementById('province_old').value.trim();
85
- let w_new = document.getElementById('ward_new').value.trim().toLowerCase();
86
- let w_old = document.getElementById('ward_old').value.trim().toLowerCase();
87
- let rs = DATA.filter(d =>
88
- (!p_new || d.province_new === p_new) &&
89
- (!p_old || d.province_old === p_old) &&
90
- (!w_new || (d.ward_new && d.ward_new.toLowerCase().includes(w_new))) &&
91
- (!w_old || (d.ward_old && d.ward_old.toLowerCase().includes(w_old)))
92
- );
93
- renderTable(rs);
 
94
  }
95
 
96
- function renderTable(list) {
97
- const tbody = document.getElementById('result_tbody');
98
- tbody.innerHTML = list.length === 0
99
- ? `<tr><td colspan="4" class="text-center text-danger">Không tìm thấy kết quả</td></tr>`
100
- : list.map(d=>`
101
- <tr>
102
- <td>${d.province_new||""}</td>
103
- <td>${d.ward_new||""}</td>
104
- <td>${d.province_old||""}</td>
105
- <td>${d.ward_old||""}</td>
106
- </tr>`).join("");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  </script>
109
  </body>
110
  </html>
 
2
  <html lang="vi">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7
+ <title>Tra cứu phường/xã cũ mới sáp nhập | Tất cả tỉnh Việt Nam</title>
8
+ <meta name="description" content="Tìm kiếm thông tin phường/xã nhanh và chính xác tại Việt Nam. Lọc theo tỉnh/thành phố và từ khóa tên phường xã. Hiển thị 3321 kết quả." />
9
+ <meta name="keywords" content="tìm kiếm phường xã, danh sách phường xã, tra cứu địa danh, Tất cả tỉnh Việt Nam" />
10
+ <link rel="canonical" href="https://huggingface.co/spaces/CVNSS/sapnhap3321" />
11
+
12
+ <!-- Open Graph -->
13
+ <meta property="og:title" content="Tra cứu, tìm kiếm phường/xã cũ mới sau sáp nhập" />
14
+ <meta property="og:description" content="Tra cứu phường/xã trên toàn quốc theo tỉnh và từ khóa nhanh chóng." />
15
+ <meta property="og:type" content="website" />
16
+ <meta property="og:url" content="https://huggingface.co/spaces/CVNSS/sapnhap3321" />
17
+ <meta property="og:image" content="https://huggingface.co/spaces/CVNSS/sapnhap3321/thumbnail.png" />
18
+ <meta property="og:locale" content="vi_VN" />
19
+
20
+ <!-- Twitter Card -->
21
+ <meta name="twitter:card" content="summary_large_image" />
22
+ <meta name="twitter:title" content="Tìm kiếm phường/xã Việt Nam" />
23
+ <meta name="twitter:description" content="Tra cứu phường/xã trên toàn quốc theo tỉnh và từ khóa nhanh chóng." />
24
+ <meta name="twitter:image" content="https://huggingface.co/spaces/CVNSS/sapnhap3321/thumbnail.png" />
25
+
26
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
27
  <style>
28
+ body { padding-top: 2rem; background-color: #f8f9fa; }
29
+ .search-card { border-radius: 1rem; box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.1); }
30
+ .list-group-item { border: none; border-radius: 0.5rem; margin-bottom: 0.5rem; transition: background .3s; }
31
+ .list-group-item:hover { background-color: #e9ecef; }
32
+ .merge-list small { color: #6c757d; }
33
+ .footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #dee2e6; font-size: 0.85rem; color: #6c757d; }
34
+ .footer a { color: #0d6efd; text-decoration: none; }
35
+ .footer a:hover { text-decoration: underline; }
36
+ @media (max-width: 575.98px) {
37
+ .card-title { font-size: 1.4rem; }
38
+ .footer { font-size: 0.8rem; }
39
+ .list-group-item h6 { font-size: 0.95rem; }
40
+ .pagination .page-item { flex: 1 1 auto; }
41
+ .pagination .page-link { text-align: center; }
42
  }
43
  </style>
44
  </head>
45
  <body>
46
+ <div class="container-sm px-3 px-md-5 d-flex flex-column min-vh-100">
47
+ <div class="card search-card mb-4">
48
+ <div class="card-body">
49
+ <h1 class="card-title text-center mb-4">Tra cứu phường/xã sau sáp nhập</h1>
50
+ <form id="searchForm" class="mb-0">
51
+ <div class="row g-2">
52
+ <div class="col-md-3 col-12">
53
+ <select id="province" class="form-select">
54
+ <option value="all" selected>Tất cả tỉnh</option>
55
+ </select>
56
+ </div>
57
+ <div class="col-md-7 col-12">
58
+ <input id="q" type="text" class="form-control" placeholder="Nhập tên phường/xã mới hoặc cũ...(có thể để trống)" autofocus>
59
+ </div>
60
+ <div class="col-md-2 col-12">
61
+ <button class="btn btn-primary w-100">Tìm kiếm</button>
62
+ </div>
63
+ </div>
64
+ </form>
65
+ </div>
66
  </div>
67
+
68
+ <div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center gap-1 mb-2">
69
+ <h5 class="mb-0 text-secondary">
70
+ <span id="listTitle">Danh sách phường/xã</span>
71
+ </h5>
72
+ <small class="text-muted" id="counterInfo"></small>
73
  </div>
74
+ <ul class="list-group mt-1 mb-3" id="listGroup"></ul>
75
+ <nav aria-label="Page navigation">
76
+ <ul class="pagination justify-content-center flex-wrap flex-md-nowrap" id="pagination"></ul>
77
+ </nav>
78
+ <div class="footer text-center mt-auto mb-1">
79
+ <span>Phát triển bởi <a href="https://www.facebook.com/quyphamdev/" target="_blank">Phạm Vương Quý</a> | Dữ liệu & UX bởi CVNSS/ChatGPT 2025</span>
80
  </div>
81
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  <script>
83
+ // === CONFIG ===
84
+ const DATA_URL = "wards_merge_2025.json"; // Đảm bảo upload file này!
85
+ // ==============
86
+ let DATA = [], provinces = [], curPage = 1, perPage = 8, q = "", province = "all";
87
+ let lastQ = "", lastProvince = "all";
88
 
89
+ function uniq(a) { return [...new Set(a)]; }
90
+ function normalize(str) {
91
+ return (str||"").normalize("NFC").toLowerCase().replace(/[\u0300-\u036f]/g,"");
92
  }
93
 
94
+ function renderProvinces() {
95
+ let sel = document.getElementById("province");
96
+ provinces = uniq(DATA.map(d=>`${d.province_id}|${d.province}`));
97
+ provinces.sort((a,b) => a.split('|')[1].localeCompare(b.split('|')[1]));
98
+ provinces.forEach(s=>{
99
+ let [id, name] = s.split("|");
100
+ sel.innerHTML += `<option value="${id}">${name}</option>`;
 
 
 
 
 
 
 
 
101
  });
102
  }
103
 
104
+ function filterData() {
105
+ let qNorm = normalize(q);
106
+ let filtered = DATA.filter(d => {
107
+ let byProvince = (province==="all" || d.province_id===province);
108
+ if (!qNorm) return byProvince;
109
+ // Tìm trong phường xã mới, tỉnh, merged_from (tên cũ/quận/huyện cũ)
110
+ let haystack = [
111
+ d.ward_new, d.province,
112
+ ...(Array.isArray(d.merged_from)?d.merged_from: [d.merged_from])
113
+ ].join(" ").toLowerCase();
114
+ return byProvince && haystack.normalize("NFC").includes(qNorm);
115
+ });
116
+ return filtered;
117
  }
118
 
119
+ function renderList() {
120
+ let filtered = filterData();
121
+ let total = filtered.length;
122
+ let totalPage = Math.max(1, Math.ceil(total/perPage));
123
+ if (curPage > totalPage) curPage = totalPage;
124
+ let start = (curPage-1)*perPage, end = start+perPage;
125
+ let showing = filtered.slice(start, end);
126
+
127
+ let group = document.getElementById("listGroup");
128
+ group.innerHTML = showing.map(d=>{
129
+ let merged = (Array.isArray(d.merged_from) ? d.merged_from : [d.merged_from]).filter(Boolean).join(", ");
130
+ return `
131
+ <li class="list-group-item">
132
+ <div class="d-flex justify-content-between align-items-start">
133
+ <div>
134
+ <h6 class="mb-1">${d.ward_new||""}</h6>
135
+ <small class="text-muted">${d.province||""}</small>
136
+ <div class="merge-list mt-1"><small>Sáp nhập từ: ${merged||"<i>Không rõ</i>"}</small></div>
137
+ </div>
138
+ </div>
139
+ </li>`;
140
+ }).join("");
141
+
142
+ // Pagination
143
+ let pagi = document.getElementById("pagination");
144
+ pagi.innerHTML = "";
145
+ if (totalPage > 1) {
146
+ let html = "";
147
+ html += `<li class="page-item${curPage==1?' disabled':''}"><a class="page-link" href="#" data-page="1">« Đầu</a></li>`;
148
+ html += `<li class="page-item${curPage==1?' disabled':''}"><a class="page-link" href="#" data-page="${curPage-1}">‹ Trước</a></li>`;
149
+ let pageNums = [];
150
+ for (let i=Math.max(1,curPage-2); i<=Math.min(totalPage,curPage+2); ++i) pageNums.push(i);
151
+ if (pageNums[0] > 1) html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
152
+ pageNums.forEach(i=> html += `<li class="page-item${i==curPage?' active':''}"><a class="page-link" href="#" data-page="${i}">${i}</a></li>`);
153
+ if (pageNums[pageNums.length-1] < totalPage) html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
154
+ html += `<li class="page-item${curPage==totalPage?' disabled':''}"><a class="page-link" href="#" data-page="${curPage+1}">Tiếp ›</a></li>`;
155
+ html += `<li class="page-item${curPage==totalPage?' disabled':''}"><a class="page-link" href="#" data-page="${totalPage}">Cuối »</a></li>`;
156
+ pagi.innerHTML = html;
157
+ }
158
+ // Info
159
+ let info = document.getElementById("counterInfo");
160
+ info.innerHTML = `Đang hiển thị <strong>${start+1}–${Math.min(end,total)}</strong> trong tổng số <strong>${total}</strong>`;
161
+
162
+ // Update title
163
+ document.getElementById("listTitle").innerHTML =
164
+ `Danh sách phường/xã (Trang ${curPage} / ${Math.ceil(total/perPage)})`;
165
  }
166
+
167
+ // Event
168
+ document.addEventListener("DOMContentLoaded",()=>{
169
+ fetch(DATA_URL).then(r=>r.json()).then(data=>{
170
+ DATA = data;
171
+ renderProvinces();
172
+ renderList();
173
+ });
174
+ document.getElementById("searchForm").onsubmit = function(e) {
175
+ e.preventDefault();
176
+ q = document.getElementById("q").value.trim();
177
+ province = document.getElementById("province").value;
178
+ curPage = 1;
179
+ renderList();
180
+ };
181
+ document.getElementById("pagination").onclick = function(e) {
182
+ let t = e.target;
183
+ if (t.tagName === "A" && t.dataset.page) {
184
+ e.preventDefault();
185
+ let newPage = +t.dataset.page;
186
+ if (newPage > 0) { curPage = newPage; renderList(); }
187
+ }
188
+ };
189
+ document.getElementById("province").onchange = function() {
190
+ province = this.value;
191
+ curPage = 1;
192
+ renderList();
193
+ };
194
+ document.getElementById("q").oninput = function() {
195
+ if (this.value.length > 2 || this.value.length === 0) {
196
+ q = this.value.trim();
197
+ curPage = 1;
198
+ renderList();
199
+ }
200
+ }
201
+ });
202
  </script>
203
  </body>
204
  </html>