Results.cshtml 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. @model LotteryWebApp.Models.TermResultHistoryModel
  2. @{
  3. ViewData["Title"] = "Millions - Results History";
  4. Layout = "~/Areas/Millions/Views/Shared/_Layout.cshtml";
  5. ViewData["ActiveTab"] = "Results";
  6. // Default to last 3 days if not specified
  7. if (string.IsNullOrEmpty(Model.fromDate)) {
  8. Model.fromDate = DateTime.Now.AddDays(-2).ToString("yyyy-MM-dd");
  9. }
  10. if (string.IsNullOrEmpty(Model.toDate)) {
  11. Model.toDate = DateTime.Now.ToString("yyyy-MM-dd");
  12. }
  13. }
  14. @using LotteryWebApp.Languages;
  15. @using LotteryWebApp.Common;
  16. @section Styles {
  17. <script src="https://cdn.tailwindcss.com"></script>
  18. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  19. <!-- Flatpickr for premium calendar -->
  20. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
  21. <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
  22. <link rel="stylesheet" href="/Millions/css/site.css" />
  23. <link rel="stylesheet" href="/Millions/css/results.css" />
  24. }
  25. <div class="main-container results-container animate__animated animate__fadeIn pb-40 relative mx-auto max-w-[414px] shadow-2xl min-h-screen bg-white">
  26. <!-- Header Group: Sticky at Top -->
  27. <div class="sticky top-0 w-full z-[100] bg-[#0062FF]">
  28. <!-- Header: Matches Figma (Right Image) -->
  29. <div class="results-top-header">
  30. <a href="javascript:history.back()" class="back-btn">
  31. <i class="fa-solid fa-arrow-left"></i>
  32. </a>
  33. <h1 class="font-bricolage">@Lang.results</h1>
  34. </div>
  35. <!-- Selection Bar: Date range and search -->
  36. <div class="selection-bar bg-white rounded-t-[30px] px-4 pt-4 pb-2">
  37. <div class="flex gap-3">
  38. <div class="date-range-input flex-1 cursor-pointer relative bg-white border border-gray-200 rounded-lg p-2 flex items-center gap-2">
  39. <img src="/Millions/img/icon_calendar.svg" class="w-5 h-5" />
  40. <input type="text" id="dateRange" class="bg-transparent border-none outline-none font-bold text-[13px] w-full ml-1 cursor-pointer" readonly />
  41. <input type="hidden" id="fromDate" value="@Model.fromDate" />
  42. <input type="hidden" id="toDate" value="@Model.toDate" />
  43. <i class="fas fa-caret-down text-gray-400"></i>
  44. </div>
  45. <button id="btnSearch" class="btn-search-red !w-12 !h-12 flex items-center justify-center bg-[#0062FF] text-white rounded-xl shadow-md" onclick="triggerSearch()">
  46. <i class="fas fa-search"></i>
  47. </button>
  48. </div>
  49. </div>
  50. <!-- Red Separator Line -->
  51. <div class="px-4 mt-1">
  52. <div class="h-[2px] bg-gradient-to-r from-transparent via-[#0062FF] to-transparent rounded-full opacity-40"></div>
  53. </div>
  54. </div>
  55. <!-- Result List Container -->
  56. <div id="results-list-container" class="results-items-list px-4 min-h-[300px]">
  57. <!-- Content will be loaded via JS for all 3 games -->
  58. <div class="flex flex-col items-center justify-center py-20 text-gray-400">
  59. <i class="fas fa-circle-notch fa-spin text-3xl mb-2"></i>
  60. <span>Loading results...</span>
  61. </div>
  62. </div>
  63. <!-- Bottom Promo Bar (Matches Bottom message with yellow text) -->
  64. <div class="fixed bottom-[82px] left-1/2 -translate-x-1/2 w-full min-[600px]:max-w-[414px] bg-[#0062FF] text-white py-2 text-center text-[12px] font-bold tracking-wide z-40 flex flex-col items-center justify-center overflow-hidden h-auto shadow-md">
  65. <div class="flex flex-col items-center w-full animate-slide-up">
  66. <div class="opacity-90">@Lang.have_chance_to_get</div>
  67. <div class="text-[18px] font-black text-[#FBF3A7] mt-0.5">@Lang.millions_jackpot_today_htg</div>
  68. </div>
  69. </div>
  70. <!-- Shared Bottom Navbar -->
  71. <div class="fixed bottom-0 left-0 w-full z-50">
  72. <div class="mx-auto max-w-[414px]">
  73. <partial name="_BottomNavbar" />
  74. </div>
  75. </div>
  76. <!-- Custom Notification Modal -->
  77. <div id="notificationModal" class="fixed inset-0 z-[300] flex items-center justify-center hidden px-6 font-bricolage" style="background: linear-gradient(135deg, rgba(26, 26, 46, 0.9) 0%, rgba(22, 33, 62, 0.9) 100%);">
  78. <div class="w-full max-w-[343px] min-h-[420px] bg-white rounded-[24px] overflow-hidden flex flex-col items-center p-8 animate__animated animate__zoomIn animate__faster shadow-2xl border border-white/50">
  79. <div class="w-full flex justify-center mb-8 mt-4">
  80. <img src="/Millions/img/modal/fail_icon.png" class="w-[160px] h-auto object-contain" alt="Notificaton icon" />
  81. </div>
  82. <div class="px-2 text-center mb-10 flex-1 flex items-center justify-center">
  83. <p id="notificationMessage" class="text-black font-[800] text-[20px] leading-snug"></p>
  84. </div>
  85. <div class="w-full">
  86. <button onclick="closeNotificationModal()" class="w-full bg-[#0062FF] text-white font-[800] text-[18px] py-[12px] rounded-[16px] shadow-lg active:scale-95 transition-all">
  87. @Lang.login
  88. </button>
  89. </div>
  90. </div>
  91. </div>
  92. </div>
  93. @section Scripts {
  94. <script>
  95. const gameConfigs = [
  96. { code: '@Constants.Millions_CODE', name: 'Millions', color: '#0062FF' }
  97. ];
  98. function formatDateForDisplay(date) {
  99. return flatpickr.formatDate(date, "d/m/Y");
  100. }
  101. function formatDateForRequest(date) {
  102. return flatpickr.formatDate(date, "Y-m-d");
  103. }
  104. function getDateFromRequestValue(value) {
  105. return flatpickr.parseDate(value, "Y-m-d");
  106. }
  107. function setRangeText(fromDate, toDate) {
  108. document.getElementById("dateRange").value = `${formatDateForDisplay(fromDate)} - ${formatDateForDisplay(toDate)}`;
  109. }
  110. function syncRangeFields(selectedDates) {
  111. if (selectedDates.length === 0) return;
  112. const fromDate = selectedDates[0];
  113. const toDate = selectedDates.length > 1 ? selectedDates[1] : selectedDates[0];
  114. document.getElementById("fromDate").value = formatDateForRequest(fromDate);
  115. document.getElementById("toDate").value = formatDateForRequest(toDate);
  116. setRangeText(fromDate, toDate);
  117. }
  118. function initBrandedRangePicker(selector, yearStart, yearEnd) {
  119. const inputEl = document.querySelector(selector);
  120. const wrapper = inputEl.closest('.date-range-input');
  121. const fromDate = getDateFromRequestValue(document.getElementById("fromDate").value);
  122. const toDate = getDateFromRequestValue(document.getElementById("toDate").value);
  123. setRangeText(fromDate, toDate);
  124. flatpickr(selector, {
  125. mode: "range",
  126. dateFormat: "Y-m-d",
  127. defaultDate: [fromDate, toDate],
  128. disableMobile: true,
  129. monthSelectorType: "dropdown",
  130. appendTo: wrapper,
  131. onChange: function (selectedDates) {
  132. syncRangeFields(selectedDates);
  133. },
  134. onClose: function (selectedDates, dateStr, instance) {
  135. syncRangeFields(selectedDates);
  136. if (selectedDates.length === 1) {
  137. instance.setDate([selectedDates[0], selectedDates[0]], false);
  138. setRangeText(selectedDates[0], selectedDates[0]);
  139. }
  140. },
  141. onReady: function (selectedDates, dateStr, instance) {
  142. instance.calendarContainer.style.zIndex = "9999";
  143. const yearInput = instance.calendarContainer.querySelector(".numInput.cur-year");
  144. if (yearInput) {
  145. const yearSelect = document.createElement("select");
  146. yearSelect.className = "flatpickr-monthDropdown-months cur-year-select";
  147. yearSelect.style.cssText = "appearance:none; font-weight:800; border:none; background:transparent; color:white; cursor:pointer; margin-left:4px; outline:none; font-family:inherit; font-size:inherit;";
  148. for (let y = yearEnd; y >= yearStart; y--) {
  149. const opt = document.createElement("option");
  150. opt.value = y;
  151. opt.text = y;
  152. opt.style.color = "#333";
  153. if (y === instance.currentYear) opt.selected = true;
  154. yearSelect.appendChild(opt);
  155. }
  156. yearSelect.addEventListener("change", function () {
  157. instance.changeYear(parseInt(this.value));
  158. });
  159. yearInput.style.display = "none";
  160. yearInput.parentNode.appendChild(yearSelect);
  161. }
  162. }
  163. });
  164. }
  165. document.addEventListener('DOMContentLoaded', function() {
  166. initBrandedRangePicker("#dateRange", 2020, 2030);
  167. triggerSearch(); // Initial load for all games
  168. });
  169. var systemUpgrading = false;
  170. function showNotification(message, code) {
  171. $("#notificationMessage").text(message);
  172. const $btn = $("#notificationModal button");
  173. if (code === "-2" || (message && message.includes("System is upgrading"))) {
  174. systemUpgrading = true;
  175. $btn.text("@Lang.login");
  176. } else {
  177. systemUpgrading = false;
  178. $btn.text("OK");
  179. }
  180. $("#notificationModal").removeClass("hidden").addClass("flex");
  181. }
  182. function closeNotificationModal() {
  183. $("#notificationModal").addClass("hidden").removeClass("flex");
  184. if (systemUpgrading) {
  185. window.location.href = subDomain + "/Account/Login";
  186. }
  187. }
  188. async function triggerSearch() {
  189. const fromDate = document.getElementById("fromDate").value;
  190. const toDate = document.getElementById("toDate").value;
  191. const btn = document.getElementById("btnSearch");
  192. const container = document.getElementById("results-list-container");
  193. btn.disabled = true;
  194. btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
  195. container.style.opacity = "0.7";
  196. try {
  197. // Call the new grouped endpoint
  198. const response = await fetch(`${subDomain}/Millions/Home/TermResultHistoryGrouped?fromDate=${fromDate}&toDate=${toDate}`);
  199. if (!response.ok) throw new Error('Network response was not ok');
  200. const html = await response.text();
  201. try {
  202. const json = JSON.parse(html);
  203. if (json.responseCode === "-2" || (json.responseMessage && json.responseMessage.includes("System is upgrading"))) {
  204. showNotification(json.responseMessage || "System is upgrading", "-2");
  205. return;
  206. }
  207. } catch (e) {
  208. // Not JSON, likely HTML partial
  209. }
  210. if (html.trim() === "") {
  211. container.innerHTML = `
  212. <div class="w-full py-20 flex flex-col items-center justify-center text-gray-400 opacity-60">
  213. <i class="fas fa-search text-5xl mb-4"></i>
  214. <p class="font-bold">@Lang.no_results_found</p>
  215. </div>
  216. `;
  217. } else {
  218. container.innerHTML = html;
  219. }
  220. } catch (error) {
  221. console.error("Error loading grouped results:", error);
  222. // Check if the error response is JSON and has code -2
  223. if (error.message && (error.message.includes("System is upgrading") || error.message.includes("-2"))) {
  224. showNotification(error.message, "-2");
  225. } else {
  226. container.innerHTML = `<div class="text-center py-10 text-red-500 font-bold">Error loading results. Please try again.</div>`;
  227. }
  228. }
  229. container.style.opacity = "1";
  230. btn.disabled = false;
  231. btn.innerHTML = '<i class="fas fa-search"></i>';
  232. }
  233. </script>
  234. }