Results.cshtml 12 KB

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