Victoria Hall Booking for Events
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

547 lines
21 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. <!DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head th:replace="~{fragments/head-admin :: head}"></head>
  4. <body>
  5. <!-- Page Container -->
  6. <div id="page-container"
  7. class="sidebar-o sidebar-dark enable-page-overlay side-scroll page-header-fixed page-header-dark">
  8. <!-- Side Overlay aside (right)-->
  9. <aside th:replace="~{fragments/aside-right :: aside}"></aside>
  10. <!-- END Side Overlay (right)-->
  11. <!-- Sidebar Nav-->
  12. <nav th:replace="~{fragments/nav-admin :: nav}"></nav>
  13. <!-- END Sidebar -->
  14. <!-- Header -->
  15. <header th:replace="~{fragments/header-admin :: header}"></header>
  16. <!-- Main Container -->
  17. <main id="main-container">
  18. <!-- Page Content -->
  19. <!-- Card Container -->
  20. <div class="content">
  21. <div class="block block-rounded">
  22. <div class="block-header bg-primary text-white">
  23. <h3 class="block-title">Electricity Bill and Damage Details</h3>
  24. </div>
  25. <div class="block-content">
  26. <h4>User Details<span>*</span></h4>
  27. <div class="table-responsive">
  28. <table class="table table-bordered table-vcenter">
  29. <thead>
  30. <tr>
  31. <th>Booking Id</th>
  32. <th>Name</th>
  33. <th>Mobile Number</th>
  34. </tr>
  35. </thead>
  36. <tbody>
  37. <tr th:each="booking : ${booking}">
  38. <td> <span th:text="${booking.bookingId}"></span></td>
  39. <td><span th:text="${booking.users.name}"></span></td>
  40. <td><span th:text="${booking.users.mobileNo}"></span></td>
  41. </tr>
  42. </tbody>
  43. </table>
  44. </div>
  45. <form id="billForm" onsubmit="return submitBillForm()">
  46. <input type="hidden" th:value="${booking.bookingId}" name="bookingId" id="bookingId">
  47. <input type="hidden" th:value="${booking.ebDetails.ebId}" name="ebId" id="ebId">
  48. <!-- EB Details -->
  49. <h4>EB Details<span>*</span></h4>
  50. <div class="table-responsive">
  51. <table class="table table-bordered table-vcenter">
  52. <thead>
  53. <tr>
  54. <th colspan="4">Consumer Number</th>
  55. <th>Start Reading</th>
  56. <th>End Reading</th>
  57. <th>EB Cost Per Unit</th>
  58. <th>Total Units</th>
  59. <th>Total Amount</th>
  60. </tr>
  61. </thead>
  62. <tbody>
  63. <tr th:each="booking : ${booking}">
  64. <td colspan="4">
  65. <input type="text" class="form-control"
  66. th:value="${booking.hallDetails.ebNumber}"
  67. placeholder="Enter EB Number" required>
  68. </td>
  69. <td>
  70. <input type="number" id="startReading" name="startReading"
  71. class="form-control"
  72. th:value="${booking.ebDetails != null ? booking.ebDetails.startingReading : ''}"
  73. placeholder="Enter Starting Reading" required readonly>
  74. </td>
  75. <td>
  76. <input type="text" id="endReading" name="endReading"
  77. class="form-control"
  78. oninput="calculateEBAmount(); calculateUsedUnits();" required>
  79. </td>
  80. <td>
  81. <input type="number" id="ebCostPerUnit" name="ebCostPerUnit"
  82. class="form-control" th:value="${booking.hallDetails.ebCostPerUnit}"
  83. readonly>
  84. </td>
  85. <td>
  86. <span id="totalUsedUnits" class="form-control-static"
  87. style="display: none;"></span>
  88. </td>
  89. <td>
  90. <span id="totalEBAmount" class="form-control-static"
  91. style="display: none;"></span>
  92. </td>
  93. </tr>
  94. </tbody>
  95. </table>
  96. </div>
  97. <!-- Hidden input for Used Units -->
  98. <input type="hidden" id="hiddenTotalUsedUnits" name="totalUsedUnits">
  99. <input type="hidden" id="hiddenTotalEBAmount" name="totalEBAmount">
  100. <!-- Damage Details -->
  101. <h4>Damage Details</h4>
  102. <div class="table-responsive">
  103. <table class="table table-bordered table-vcenter" id="damageDetailsTable">
  104. <thead>
  105. <tr>
  106. <th>Materials</th>
  107. <th>No. of Quantity</th>
  108. <th>Per Material Amount</th>
  109. <th>Comments</th>
  110. <th>Damaged Item Photo</th>
  111. <th>Total Amount</th>
  112. </tr>
  113. </thead>
  114. <tbody id="damageDetailsbody">
  115. <!-- Initial Row will be here -->
  116. </tbody>
  117. </table>
  118. </div>
  119. <button type="button" class="btn btn-success mb-3" onclick="addMaterialRow()"><i
  120. class="fas fa-plus"></i>Add
  121. Material</button>
  122. <!-- Hidden input for total Damage Amount -->
  123. <input type="hidden" id="hiddenTotalDamageAmount" name="totalDamageAmount">
  124. <!-- Settlement Amount -->
  125. <div style="text-align: right;">
  126. <h4>Settlement Amount</h4>
  127. <h6>Total Settlement Amount: <span id="totalSettlementAmount"></span></h6>
  128. </div>
  129. <!-- Raise Bill Button -->
  130. <div style="text-align: center;">
  131. <button id="backButton" class="btn btn-secondary" onclick="goBack()">Back</button>
  132. <button id="submitButton" class="btn btn-primary" type="submit">Raise Bill</button>
  133. </div>
  134. <br>
  135. </form>
  136. </div>
  137. </div>
  138. </div>
  139. <!-- END Card Container -->
  140. <!-- END Page Content -->
  141. </main>
  142. <!-- END Main Container -->
  143. <!-- END Footer -->
  144. </div>
  145. <!-- END Page Container -->
  146. <!-- Scripts -->
  147. <div th:replace="~{fragments/resetpassword :: div}"></div>
  148. <footer th:replace="~{fragments/footer2 :: footer}"></footer>
  149. <script th:replace="~{fragments/script :: script}"></script>
  150. <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
  151. <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  152. <script type="text/javascript">
  153. document.addEventListener("DOMContentLoaded", function () {
  154. addMaterialRow(); // Add the first row automatically
  155. });
  156. let selectedMaterials = {}; // Object to track selected materials for each row
  157. var damageindex = 0;
  158. function addMaterialRow() {
  159. const tableBody = document.getElementById('damageDetailsTable').getElementsByTagName('tbody')[0];
  160. const rowIndex = tableBody.rows.length;
  161. selectedMaterials[rowIndex] = ""; // Initialize tracking for this row
  162. const row = tableBody.insertRow(rowIndex);
  163. // Set row's HTML with dropdown including a unique index
  164. row.innerHTML = generateRowHTML(rowIndex);
  165. updateAllDropdowns();
  166. calculateDamageAmount();
  167. }
  168. function generateRowHTML(index) {
  169. damageindex = index;
  170. return `
  171. <td>
  172. <select id="materials${index}" name="materials" class="form-control"
  173. onchange="materialSelectionChanged(this, ${index});">
  174. <option value="">Select</option>
  175. <option value="chair">Chair</option>
  176. <option value="table">Table</option>
  177. <option value="kitchenessentials">Kitchen Essentials</option>
  178. <option value="electronics">Electronics</option>
  179. <option value="Others">Electronics</option>
  180. </select>
  181. </td>
  182. <td><input type="number" id="quantity${index}" name="quantity[${index}]" class="form-control"
  183. oninput="calculateDamageAmount()"></td>
  184. <td><input type="number" id="perMaterialAmount${index}" name="perMaterialAmount[${index}]"
  185. class="form-control" oninput="calculateDamageAmount()"></td>
  186. <td><input type="text" id="comments${index}" name="comments" class="form-control"
  187. placeholder="Enter Comments"></td>
  188. <td><input type="file" id="damagePhoto${index}" name="damagePhoto${index}" class="form-control damage-image mb-1"></td>
  189. <td><span id="totalDamageAmount${index}" class="form-control-static"></span></td>
  190. <td><button type="button" class="btn btn-danger" onclick="removeMaterialRow(${index})"><i class="fas fa-minus"></i></button></td>
  191. `;
  192. }
  193. function removeMaterialRow(index) {
  194. const tableBody = document.getElementById('damageDetailsTable').getElementsByTagName('tbody')[0];
  195. tableBody.deleteRow(index);
  196. // Update selectedMaterials object
  197. delete selectedMaterials[index];
  198. // Reassign the indices for the remaining rows
  199. const rows = tableBody.rows;
  200. for (let i = index; i < rows.length; i++) {
  201. updateRowIndices(rows[i], i);
  202. selectedMaterials[i] = selectedMaterials[i + 1];
  203. }
  204. delete selectedMaterials[rows.length]; // Remove the last entry which is now undefined
  205. updateAllDropdowns();
  206. calculateDamageAmount();
  207. }
  208. function updateRowIndices(row, newIndex) {
  209. row.cells[0].querySelector('select').setAttribute('id', `materials${newIndex}`);
  210. row.cells[0].querySelector('select').setAttribute('onchange', `materialSelectionChanged(this, ${newIndex});`);
  211. row.cells[1].querySelector('input').setAttribute('id', `quantity${newIndex}`);
  212. row.cells[1].querySelector('input').setAttribute('name', `quantity[${newIndex}]`);
  213. row.cells[1].querySelector('input').setAttribute('oninput', `calculateDamageAmount()`);
  214. row.cells[2].querySelector('input').setAttribute('id', `perMaterialAmount${newIndex}`);
  215. row.cells[2].querySelector('input').setAttribute('name', `perMaterialAmount[${newIndex}]`);
  216. row.cells[2].querySelector('input').setAttribute('oninput', `calculateDamageAmount()`);
  217. row.cells[3].querySelector('input').setAttribute('id', `comments${newIndex}`);
  218. row.cells[4].querySelector('input').setAttribute('id', `damagePhoto${newIndex}`);
  219. row.cells[5].querySelector('span').setAttribute('id', `totalDamageAmount${newIndex}`);
  220. row.cells[6].querySelector('button').setAttribute('onclick', `removeMaterialRow(${newIndex})`);
  221. }
  222. function updateAllDropdowns() {
  223. const allMaterials = ["chair", "table", "kitchenessentials", "electronics", "others"];
  224. Object.keys(selectedMaterials).forEach(rowIndex => {
  225. const dropdown = document.getElementById(`materials${rowIndex}`);
  226. const selectedValue = selectedMaterials[rowIndex];
  227. dropdown.innerHTML = '<option value="">Select</option>';
  228. allMaterials.forEach(material => {
  229. if (!Object.values(selectedMaterials).includes(material) || material === selectedValue) {
  230. const option = new Option(material, material);
  231. dropdown.add(option);
  232. }
  233. });
  234. dropdown.value = selectedValue;
  235. });
  236. }
  237. function materialSelectionChanged(select, index) {
  238. // Update the selection tracking
  239. selectedMaterials[index] = select.value;
  240. updateAllDropdowns(); // Refresh all dropdowns to reflect the current selections
  241. calculateDamageAmount(); // Recalculate damage amount as materials selection might change the calculation
  242. }
  243. function calculateEBAmount() {
  244. let startReading = parseFloat(document.getElementById("startReading").value) || 0;
  245. let endReading = parseFloat(document.getElementById("endReading").value) || 0;
  246. let ebCostPerUnit = parseFloat(document.getElementById("ebCostPerUnit").value) || 0;
  247. let totalEBAmount = (endReading - startReading) * ebCostPerUnit; // Assuming rate per unit is 10
  248. if (!isNaN(totalEBAmount)) {
  249. document.getElementById("totalEBAmount").style.display = "block"; // Show the totalEBAmount
  250. document.getElementById("totalEBAmount").innerText = totalEBAmount.toFixed(2);
  251. document.getElementById("hiddenTotalEBAmount").value = totalEBAmount;
  252. calculateSettlementAmount();
  253. }
  254. }
  255. function calculateUsedUnits() {
  256. let startReading = parseFloat(document.getElementById("startReading").value) || 0;
  257. let endReading = parseFloat(document.getElementById("endReading").value) || 0;
  258. // Get the endReading input element
  259. let endReadingInput = document.getElementById("endReading");
  260. // Ensure end reading cannot be less than start reading
  261. if (endReading < startReading) {
  262. // Set custom validity message
  263. endReadingInput.setCustomValidity("End reading cannot be less than start reading.");
  264. endReadingInput.reportValidity();
  265. } else {
  266. // Clear any previous custom validity message
  267. endReadingInput.setCustomValidity("");
  268. }
  269. let usedUnits = endReading - startReading;
  270. // Show total used units
  271. document.getElementById("totalUsedUnits").style.display = "block";
  272. document.getElementById("totalUsedUnits").innerText = usedUnits.toFixed(2);
  273. document.getElementById("hiddenTotalUsedUnits").value = usedUnits.toFixed(2);
  274. let totalEBAmount = usedUnits * parseFloat(document.getElementById("ebCostPerUnit").value) || 0;
  275. // Show total EB amount
  276. document.getElementById("totalEBAmount").style.display = "block";
  277. document.getElementById("totalEBAmount").innerText = totalEBAmount.toFixed(2);
  278. document.getElementById("hiddenTotalEBAmount").value = totalEBAmount.toFixed(2);
  279. calculateSettlementAmount();
  280. }
  281. function calculateDamageAmount() {
  282. const rows = document.getElementById('damageDetailsTable').getElementsByTagName('tbody')[0].rows;
  283. let totalDamageAmount = 0;
  284. for (let i = 0; i < rows.length; i++) {
  285. const quantity = parseFloat(document.getElementById(`quantity${i}`).value) || 0;
  286. const perMaterialAmount = parseFloat(document.getElementById(`perMaterialAmount${i}`).value) || 0;
  287. const rowDamageAmount = quantity * perMaterialAmount;
  288. document.getElementById(`totalDamageAmount${i}`).innerText = rowDamageAmount.toFixed(2);
  289. totalDamageAmount += rowDamageAmount;
  290. }
  291. document.getElementById("hiddenTotalDamageAmount").value = totalDamageAmount.toFixed(2);
  292. calculateSettlementAmount();
  293. }
  294. function calculateSettlementAmount() {
  295. let totalEBAmount = parseFloat(document.getElementById("hiddenTotalEBAmount").value) || 0;
  296. let totalDamageAmount = parseFloat(document.getElementById("hiddenTotalDamageAmount").value) || 0;
  297. let totalSettlementAmount = totalEBAmount + totalDamageAmount;
  298. document.getElementById("totalSettlementAmount").innerText = totalSettlementAmount.toFixed(2);
  299. }
  300. function goBack() {
  301. window.history.back();
  302. }
  303. function submitBillForm() {
  304. var bookingId = $("#bookingId").val();
  305. var ebId = $("#ebId").val();
  306. var endReading = $("#endReading").val();
  307. var startReading = $("#startReading").val();
  308. var usedUnit = $("#totalUsedUnits").html();
  309. var ebAmount = $("#totalEBAmount").html();
  310. var formData = new FormData();
  311. formData.append('bookingId', bookingId);
  312. formData.append('ebId', ebId);
  313. formData.append('startingReading', startReading);
  314. formData.append('endingReading', endReading);
  315. formData.append('usedUnit', usedUnit);
  316. formData.append('ebAmount', ebAmount);
  317. var i = damageindex;
  318. $('#damageDetailsbody tr').each(function () {
  319. var assetName = $("#materials" + i).val();
  320. var comments = $("#comments" + i).val();
  321. var quantity = $("#quantity" + i).val();
  322. var assetAmount = $("#totalDamageAmount" + i).html();
  323. var assetImageInput = $("#damagePhoto" + i)[0].files[0]; // Assuming input type is file for assetImage
  324. formData.append('assetName', assetName);
  325. formData.append('comments', comments);
  326. formData.append('quantity', quantity);
  327. formData.append('assetAmount', assetAmount);
  328. formData.append('assetImage', assetImageInput);
  329. i = i - 1;
  330. });
  331. $.ajax({
  332. url: '/victoriahallbooking/careTaker/api/processEbDetailsAndDamage',
  333. type: 'POST',
  334. data: formData,
  335. contentType: false, // Let jQuery handle the content type
  336. processData: false, // Prevent jQuery from processing the data
  337. success: function (response) {
  338. Swal.fire({
  339. title: "Success!",
  340. text: "Form submitted successfully.",
  341. icon: "success",
  342. button: "OK",
  343. }).then((result) => {
  344. if (result.isConfirmed) {
  345. window.location.href = '/victoriahallbooking/admin/ongoingevents';
  346. }
  347. });
  348. },
  349. error: function (xhr, status, error) {
  350. console.error('Error:', error);
  351. Swal.fire({
  352. title: "Error!",
  353. text: "There was an error submitting the form.",
  354. icon: "error",
  355. button: "OK",
  356. }).then(() => {
  357. window.location.href = '/victoriahallbooking/admin/ongoingevents';
  358. });
  359. }
  360. });
  361. return false;
  362. }
  363. let isCompressing = false;
  364. // Attach the validation function to file input change event
  365. $(document).on('change', '.damage-image', function () {
  366. validateFile(this);
  367. });
  368. function validateFile(fileInput) {
  369. const allowedExtensions = /(\.jpg|\.jpeg|\.pdf)$/i;
  370. const file = fileInput.files[0];
  371. const filePath = fileInput.value;
  372. let errorMessage = '';
  373. // Clear any previous error message
  374. clearError(fileInput);
  375. // Check for duplicate files
  376. if (isDuplicateFile(file.name, fileInput)) {
  377. errorMessage = 'This file has already been selected. Please choose a different file.';
  378. fileInput.value = ''; // Clear the file input
  379. displayError(fileInput, errorMessage);
  380. return;
  381. }
  382. // Validate file type
  383. if (!allowedExtensions.exec(filePath)) {
  384. errorMessage = 'Only JPG, JPEG, and PDF files are allowed.';
  385. fileInput.value = ''; // Clear the file input
  386. displayError(fileInput, errorMessage);
  387. }
  388. // Handle image files separately
  389. else if (/(\.jpg|\.jpeg)$/i.test(filePath) && file.size > 2097152) {
  390. compressImage(file).then(compressedFile => {
  391. const dataTransfer = new DataTransfer();
  392. dataTransfer.items.add(compressedFile);
  393. fileInput.files = dataTransfer.files;
  394. // Validate file size after compression
  395. validateFileSize(fileInput);
  396. }).catch(error => {
  397. errorMessage = 'Error compressing image.';
  398. displayError(fileInput, errorMessage);
  399. });
  400. }
  401. // Directly validate PDF file size
  402. else if (/(\.pdf)$/i.test(filePath) && file.size > 2097152) {
  403. errorMessage = 'PDF file size exceeds the 2MB limit.';
  404. fileInput.value = ''; // Clear the file input
  405. displayError(fileInput, errorMessage);
  406. }
  407. else {
  408. // console.log('File size is within limit:', file.size, 'bytes');
  409. clearError(fileInput);
  410. }
  411. }
  412. function isDuplicateFile(fileName, currentInput) {
  413. const fileInputs = document.querySelectorAll('.file-upload-field');
  414. for (let input of fileInputs) {
  415. if (input !== currentInput && input.files.length > 0 && input.files[0].name === fileName) {
  416. return true;
  417. }
  418. }
  419. return false;
  420. }
  421. function clearError(fileInput) {
  422. const errorElements = fileInput.parentNode.getElementsByClassName('error-message');
  423. while (errorElements.length > 0) {
  424. errorElements[0].remove();
  425. }
  426. }
  427. // Function to compress image using canvas
  428. function compressImage(file) {
  429. return new Promise((resolve, reject) => {
  430. const reader = new FileReader();
  431. reader.onload = function (event) {
  432. const img = new Image();
  433. img.onload = function () {
  434. const canvas = document.createElement('canvas');
  435. const ctx = canvas.getContext('2d');
  436. const maxSize = 2097152; // 2MB in bytes
  437. let scale = 1;
  438. if (file.size > maxSize) {
  439. scale = Math.sqrt(maxSize / file.size);
  440. }
  441. canvas.width = img.width * scale;
  442. canvas.height = img.height * scale;
  443. ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  444. // Convert canvas content to blob
  445. canvas.toBlob(blob => {
  446. resolve(new File([blob], file.name, {type: 'image/jpeg', lastModified: Date.now()}));
  447. }, 'image/jpeg', 0.7); // Adjust JPEG quality as needed (0.7 is 70% quality)
  448. };
  449. img.src = event.target.result;
  450. };
  451. reader.readAsDataURL(file);
  452. });
  453. }
  454. // Function to validate file size after compression
  455. function validateFileSize(fileInput) {
  456. const file = fileInput.files[0];
  457. if (file && file.size > 2097152) {
  458. //console.log('File size is still greater than 2MB after compression.');
  459. // Handle the situation where the file size is still greater than 2MB
  460. displayError(fileInput, 'File size is still greater than 2MB after compression.');
  461. fileInput.value = ''; // Clear the file input
  462. } else {
  463. //console.log('File size is within limit after compression.');
  464. // Handle the situation where the file size is within the limit
  465. }
  466. }
  467. // Function to display error messages
  468. function displayError(fileInput, errorMessage) {
  469. // Example implementation: show error message near the file input
  470. let errorContainer = fileInput.nextElementSibling;
  471. if (!errorContainer || !errorContainer.classList.contains('error-message')) {
  472. errorContainer = document.createElement('div');
  473. errorContainer.classList.add('error-message');
  474. fileInput.parentNode.insertBefore(errorContainer, fileInput.nextSibling);
  475. }
  476. errorContainer.textContent = errorMessage;
  477. }
  478. </script>
  479. </body>
  480. </html>