CVNSS commited on
Commit
984e562
·
verified ·
1 Parent(s): 866ac6c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +94 -53
index.html CHANGED
@@ -2,10 +2,25 @@
2
  <html lang="vi">
3
  <head>
4
  <meta charset="UTF-8">
5
- <title>Tra cứu Đơn vị Hành chính - Luat.ai API</title>
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
  <!-- Bootstrap 5 CDN -->
8
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  </head>
10
  <body class="bg-light">
11
  <div class="container py-5">
@@ -15,33 +30,34 @@
15
  <div class="row mb-3">
16
  <div class="col-md-4 mb-2">
17
  <label for="province" class="form-label">Tỉnh/Thành phố cũ</label>
18
- <select class="form-select" id="province" name="province" required>
19
- <option value="">-- Chọn tỉnh/thành phố --</option>
20
  </select>
21
  </div>
22
  <div class="col-md-4 mb-2">
23
  <label for="district" class="form-label">Quận/Huyện cũ</label>
24
- <select class="form-select" id="district" name="district">
25
- <option value="">-- Chọn quận/huyện --</option>
26
  </select>
27
  </div>
28
  <div class="col-md-4 mb-2">
29
  <label for="ward" class="form-label">Phường/Xã cũ</label>
30
- <select class="form-select" id="ward" name="ward">
31
- <option value="">-- Chọn phường/xã --</option>
32
  </select>
33
  </div>
34
  </div>
35
  <button class="btn btn-primary" type="submit" id="searchBtn">
36
- Tra cứu
37
  </button>
38
- <span id="loading" style="display:none;" class="ms-3 text-info">Đang xử lý...</span>
 
39
  </form>
40
  </div>
41
-
42
  <div id="resultArea"></div>
43
  </div>
44
-
 
45
  <script>
46
  const apiBase = "https://api.luat.ai/administrative";
47
  const provinceSel = document.getElementById("province");
@@ -50,49 +66,42 @@ const wardSel = document.getElementById("ward");
50
  const resultArea = document.getElementById("resultArea");
51
  const searchForm = document.getElementById("searchForm");
52
  const loading = document.getElementById("loading");
 
 
 
 
 
 
53
 
54
- // Load province list
55
  async function loadProvinces() {
56
  let res = await fetch(apiBase + '/provinces');
57
  let data = await res.json();
58
- data.forEach(p => {
59
- let opt = document.createElement("option");
60
- opt.value = p;
61
- opt.innerText = p;
62
- provinceSel.appendChild(opt);
63
- });
64
  }
65
 
66
- // Load districts when province changes
67
  provinceSel.addEventListener("change", async function() {
68
- districtSel.innerHTML = `<option value="">-- Chọn quận/huyện --</option>`;
69
- wardSel.innerHTML = `<option value="">-- Chọn phường/ --</option>`;
 
70
  if (!this.value) return;
71
  let res = await fetch(apiBase + '/districts?province=' + encodeURIComponent(this.value));
72
  let data = await res.json();
73
- data.forEach(d => {
74
- let opt = document.createElement("option");
75
- opt.value = d;
76
- opt.innerText = d;
77
- districtSel.appendChild(opt);
78
- });
79
  });
80
 
81
- // Load wards when district changes
82
  districtSel.addEventListener("change", async function() {
83
- wardSel.innerHTML = `<option value="">-- Chọn phường/xã --</option>`;
 
84
  if (!this.value || !provinceSel.value) return;
85
  let res = await fetch(apiBase + '/wards?province=' + encodeURIComponent(provinceSel.value) + '&district=' + encodeURIComponent(this.value));
86
  let data = await res.json();
87
- data.forEach(w => {
88
- let opt = document.createElement("option");
89
- opt.value = w;
90
- opt.innerText = w;
91
- wardSel.appendChild(opt);
92
- });
93
  });
94
 
95
- // Handle search
96
  searchForm.addEventListener("submit", async function(e) {
97
  e.preventDefault();
98
  resultArea.innerHTML = "";
@@ -104,44 +113,76 @@ searchForm.addEventListener("submit", async function(e) {
104
  let url = apiBase + '/search?' + params.join('&');
105
  try {
106
  let res = await fetch(url);
107
- if (!res.ok) throw await res.json();
108
  let data = await res.json();
109
- renderResult(data);
 
 
 
 
 
 
 
110
  } catch (err) {
111
- showError(err.message || "Không tìm thấy dữ liệu, vui lòng kiểm tra lại thông tin.");
112
  }
113
  loading.style.display = "none";
114
  });
115
 
116
- // Hiển thị kết quả
 
 
 
 
 
 
 
117
  function renderResult(data) {
118
  resultArea.innerHTML = `
119
  <div class="card shadow p-3">
120
  <h5 class="mb-3 text-success">Kết quả tra cứu:</h5>
121
  <table class="table table-bordered align-middle">
122
  <tbody>
123
- <tr><th scope="row">Tỉnh/TP cũ</th><td>${data.oldProvince || ''}</td></tr>
124
- <tr><th scope="row">Quận/Huyện cũ</th><td>${data.oldDistrict || ''}</td></tr>
125
- <tr><th scope="row">Phường/Xã cũ</th><td>${data.oldWard || ''}</td></tr>
126
- <tr class="table-secondary"><th scope="row">Tỉnh/TP mới</th><td>${data.newProvince || ''}</td></tr>
127
- <tr class="table-secondary"><th scope="row">Phường/Xã mới</th><td>${data.newWard || ''}</td></tr>
128
- <tr><th scope="row">Cấp hành chính mới</th><td>${data.newLevel || ''}</td></tr>
129
  </tbody>
130
  </table>
131
  </div>
132
  `;
133
  }
134
-
135
- // Hiển thị lỗi
136
- function showError(msg) {
137
  resultArea.innerHTML = `
138
- <div class="alert alert-danger">
139
- <b>Lỗi:</b> ${msg}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  </div>
141
  `;
142
  }
143
-
144
- // Initial load
 
145
  loadProvinces();
146
  </script>
147
  </body>
 
2
  <html lang="vi">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <title>Tra cứu Đơn vị Hành chính - Luat.AI API</title>
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
  <!-- Bootstrap 5 CDN -->
8
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <!-- Choices.js cho select tìm kiếm nhanh -->
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css" />
11
+ <style>
12
+ .choices__inner { min-height: 42px !important; }
13
+ .loading-spinner {
14
+ width: 1.5rem;
15
+ height: 1.5rem;
16
+ border: .25em solid #0d6efd;
17
+ border-right-color: transparent;
18
+ border-radius: 50%;
19
+ display: inline-block;
20
+ animation: spin 1s linear infinite;
21
+ }
22
+ @keyframes spin { 100% { transform: rotate(360deg); } }
23
+ </style>
24
  </head>
25
  <body class="bg-light">
26
  <div class="container py-5">
 
30
  <div class="row mb-3">
31
  <div class="col-md-4 mb-2">
32
  <label for="province" class="form-label">Tỉnh/Thành phố cũ</label>
33
+ <select class="form-select" id="province" required>
34
+ <option value="">-- Chọn/Tìm tỉnh/thành phố --</option>
35
  </select>
36
  </div>
37
  <div class="col-md-4 mb-2">
38
  <label for="district" class="form-label">Quận/Huyện cũ</label>
39
+ <select class="form-select" id="district">
40
+ <option value="">-- Chọn/Tìm quận/huyện --</option>
41
  </select>
42
  </div>
43
  <div class="col-md-4 mb-2">
44
  <label for="ward" class="form-label">Phường/Xã cũ</label>
45
+ <select class="form-select" id="ward">
46
+ <option value="">-- Chọn/Tìm phường/xã --</option>
47
  </select>
48
  </div>
49
  </div>
50
  <button class="btn btn-primary" type="submit" id="searchBtn">
51
+ <span id="searchIcon" class="me-1">🔎</span>Tra cứu
52
  </button>
53
+ <button class="btn btn-outline-secondary ms-2" type="button" id="resetBtn">Làm mới</button>
54
+ <span id="loading" style="display:none;" class="ms-3"><span class="loading-spinner"></span> Đang xử lý...</span>
55
  </form>
56
  </div>
 
57
  <div id="resultArea"></div>
58
  </div>
59
+ <!-- Choices.js: Tìm kiếm nhanh select -->
60
+ <script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
61
  <script>
62
  const apiBase = "https://api.luat.ai/administrative";
63
  const provinceSel = document.getElementById("province");
 
66
  const resultArea = document.getElementById("resultArea");
67
  const searchForm = document.getElementById("searchForm");
68
  const loading = document.getElementById("loading");
69
+ const resetBtn = document.getElementById("resetBtn");
70
+
71
+ // Choices.js: Giao diện và tìm nhanh select
72
+ let provinceChoices = new Choices(provinceSel, { searchEnabled: true, itemSelectText: '', shouldSort: false });
73
+ let districtChoices = new Choices(districtSel, { searchEnabled: true, itemSelectText: '', shouldSort: false });
74
+ let wardChoices = new Choices(wardSel, { searchEnabled: true, itemSelectText: '', shouldSort: false });
75
 
 
76
  async function loadProvinces() {
77
  let res = await fetch(apiBase + '/provinces');
78
  let data = await res.json();
79
+ let opts = data.map(p => ({ value: p, label: p }));
80
+ provinceChoices.setChoices([{ value: '', label: '-- Chọn/Tìm tỉnh/thành phố --', selected: true, disabled: true }, ...opts], 'value', 'label', true);
 
 
 
 
81
  }
82
 
 
83
  provinceSel.addEventListener("change", async function() {
84
+ districtChoices.clearStore(); wardChoices.clearStore();
85
+ districtChoices.setChoices([{ value: '', label: '-- Chọn/Tìm quận/huyện --', selected: true, disabled: true }], 'value', 'label', true);
86
+ wardChoices.setChoices([{ value: '', label: '-- Chọn/Tìm phường/xã --', selected: true, disabled: true }], 'value', 'label', true);
87
  if (!this.value) return;
88
  let res = await fetch(apiBase + '/districts?province=' + encodeURIComponent(this.value));
89
  let data = await res.json();
90
+ let opts = data.map(d => ({ value: d, label: d }));
91
+ districtChoices.setChoices([{ value: '', label: '-- Chọn/Tìm quận/huyện --', selected: true, disabled: true }, ...opts], 'value', 'label', true);
 
 
 
 
92
  });
93
 
 
94
  districtSel.addEventListener("change", async function() {
95
+ wardChoices.clearStore();
96
+ wardChoices.setChoices([{ value: '', label: '-- Chọn/Tìm phường/xã --', selected: true, disabled: true }], 'value', 'label', true);
97
  if (!this.value || !provinceSel.value) return;
98
  let res = await fetch(apiBase + '/wards?province=' + encodeURIComponent(provinceSel.value) + '&district=' + encodeURIComponent(this.value));
99
  let data = await res.json();
100
+ let opts = data.map(w => ({ value: w, label: w }));
101
+ wardChoices.setChoices([{ value: '', label: '-- Chọn/Tìm phường/xã --', selected: true, disabled: true }, ...opts], 'value', 'label', true);
 
 
 
 
102
  });
103
 
104
+ // Tra cứu
105
  searchForm.addEventListener("submit", async function(e) {
106
  e.preventDefault();
107
  resultArea.innerHTML = "";
 
113
  let url = apiBase + '/search?' + params.join('&');
114
  try {
115
  let res = await fetch(url);
 
116
  let data = await res.json();
117
+ // Nếu trả về mảng (nhiều kết quả)
118
+ if (Array.isArray(data)) {
119
+ if (data.length === 0) return showError("Không tìm thấy dữ liệu, vui lòng kiểm tra lại thông tin.");
120
+ renderMultiResult(data);
121
+ }
122
+ // Nếu trả về object (1 kết quả)
123
+ else if (data && data.id) renderResult(data);
124
+ else showError("Không tìm thấy dữ liệu, vui lòng kiểm tra lại thông tin.");
125
  } catch (err) {
126
+ showError(err.message || "Lỗi hệ thống hoặc API.");
127
  }
128
  loading.style.display = "none";
129
  });
130
 
131
+ resetBtn.addEventListener("click", function() {
132
+ provinceChoices.setChoiceByValue('');
133
+ districtChoices.clearStore(); districtChoices.setChoices([{ value: '', label: '-- Chọn/Tìm quận/huyện --', selected: true, disabled: true }], 'value', 'label', true);
134
+ wardChoices.clearStore(); wardChoices.setChoices([{ value: '', label: '-- Chọn/Tìm phường/xã --', selected: true, disabled: true }], 'value', 'label', true);
135
+ resultArea.innerHTML = "";
136
+ });
137
+
138
+ // Hiển thị kết quả 1 dòng
139
  function renderResult(data) {
140
  resultArea.innerHTML = `
141
  <div class="card shadow p-3">
142
  <h5 class="mb-3 text-success">Kết quả tra cứu:</h5>
143
  <table class="table table-bordered align-middle">
144
  <tbody>
145
+ <tr><th>Tỉnh/TP cũ</th><td>${data.oldProvince || ''}</td></tr>
146
+ <tr><th>Quận/Huyện cũ</th><td>${data.oldDistrict || ''}</td></tr>
147
+ <tr><th>Phường/Xã cũ</th><td>${data.oldWard || ''}</td></tr>
148
+ <tr class="table-secondary"><th>Tỉnh/TP mới</th><td>${data.newProvince || ''}</td></tr>
149
+ <tr class="table-secondary"><th>Phường/Xã mới</th><td>${data.newWard || ''}</td></tr>
150
+ <tr><th>Cấp hành chính mới</th><td>${data.newLevel || ''}</td></tr>
151
  </tbody>
152
  </table>
153
  </div>
154
  `;
155
  }
156
+ // Hiển thị nhiều kết quả
157
+ function renderMultiResult(list) {
 
158
  resultArea.innerHTML = `
159
+ <div class="card shadow p-3">
160
+ <h5 class="mb-3 text-success"> ${list.length} kết quả:</h5>
161
+ <div style="max-height:350px;overflow:auto;">
162
+ <table class="table table-bordered table-hover align-middle small">
163
+ <thead class="table-light"><tr>
164
+ <th>Tỉnh/TP cũ</th><th>Quận/Huyện cũ</th><th>Phường/Xã cũ</th>
165
+ <th>Tỉnh/TP mới</th><th>Phường/Xã mới</th><th>Cấp mới</th>
166
+ </tr></thead>
167
+ <tbody>
168
+ ${list.map(d=>`
169
+ <tr>
170
+ <td>${d.oldProvince||''}</td>
171
+ <td>${d.oldDistrict||''}</td>
172
+ <td>${d.oldWard||''}</td>
173
+ <td>${d.newProvince||''}</td>
174
+ <td>${d.newWard||''}</td>
175
+ <td>${d.newLevel||''}</td>
176
+ </tr>
177
+ `).join('')}
178
+ </tbody>
179
+ </table></div>
180
  </div>
181
  `;
182
  }
183
+ function showError(msg) {
184
+ resultArea.innerHTML = `<div class="alert alert-danger"><b>Lỗi:</b> ${msg}</div>`;
185
+ }
186
  loadProvinces();
187
  </script>
188
  </body>