StockPriceHistory.svelte 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <script>
  2. import { onMount } from 'svelte';
  3. import { Chart, registerables } from 'chart.js';
  4. import { getRequest } from '../utils/api.js';
  5. import { fade } from 'svelte/transition';
  6. import ChartDataLabels from 'chartjs-plugin-datalabels';
  7. Chart.register(...registerables, ChartDataLabels);
  8. export let code;
  9. let chartCanvas;
  10. let chartInstance;
  11. let selectedRange = '5d';
  12. let isLoading = true;
  13. let currentPrice = null;
  14. let currentCurrency = '';
  15. const ranges = ['5d', '30d', '6m', '1y'];
  16. async function fetchData(range) {
  17. try {
  18. const endpoint = `https://stocks-be.lhamacorp.com/api/stocks/${code}/history?range=${range}`;
  19. const res = await getRequest(endpoint, {}, null);
  20. if (!res.ok) {
  21. console.error(`Failed to fetch price history: ${res.status}`);
  22. return [];
  23. }
  24. return await res.json();
  25. } catch (err) {
  26. console.error('Error fetching stock history:', err);
  27. return [];
  28. } finally {
  29. isLoading = false;
  30. }
  31. }
  32. function renderChart(data) {
  33. const labels = data.map((item) => {
  34. const date = new Date(item.createdAt);
  35. const dd = String(date.getDate()).padStart(2, '0');
  36. const mm = String(date.getMonth() + 1).padStart(2, '0');
  37. const yyyy = date.getFullYear();
  38. return `${dd}/${mm}/${yyyy}`;
  39. });
  40. const prices = data.map((item) => item.price);
  41. if (chartInstance) {
  42. chartInstance.destroy();
  43. }
  44. chartInstance = new Chart(chartCanvas, {
  45. type: 'line',
  46. data: {
  47. labels,
  48. datasets: [
  49. {
  50. label: `Price (${data[0]?.currency || ''})`,
  51. data: prices,
  52. fill: false,
  53. borderWidth: 2
  54. }
  55. ]
  56. },
  57. options: {
  58. responsive: true,
  59. scales: {
  60. x: {
  61. title: {
  62. display: true,
  63. text: 'Date'
  64. }
  65. },
  66. y: {
  67. title: {
  68. display: true,
  69. text: 'Price'
  70. }
  71. }
  72. },
  73. plugins: {
  74. legend: {
  75. display: false
  76. },
  77. datalabels: {
  78. color: '#fff',
  79. anchor: 'end',
  80. align: 'top',
  81. font: {
  82. weight: 'bold'
  83. },
  84. formatter: (value) => value.toFixed(2)
  85. }
  86. }
  87. }
  88. });
  89. }
  90. async function updateChart(range) {
  91. selectedRange = range;
  92. isLoading = true;
  93. const history = await fetchData(range);
  94. isLoading = false;
  95. if (history.length > 0) {
  96. currentPrice = history[history.length - 1].price;
  97. currentCurrency = history[history.length - 1].currency || '';
  98. renderChart(history);
  99. }
  100. }
  101. onMount(() => {
  102. updateChart(selectedRange);
  103. });
  104. </script>
  105. {#if isLoading}
  106. <div in:fade class="flex justify-center items-center py-10">
  107. <svg
  108. class="animate-spin h-8 w-8 text-blue-500 dark:text-blue-300"
  109. xmlns="http://www.w3.org/2000/svg"
  110. fill="none"
  111. viewBox="0 0 24 24"
  112. >
  113. <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"
  114. ></circle>
  115. <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
  116. </svg>
  117. </div>
  118. {:else}
  119. <div class="w-full mt-10 max-w-6xl mx-auto bg-white dark:bg-gray-900 p-6 rounded-xl shadow-md">
  120. <h3 class="text-xl font-semibold text-gray-800 dark:text-gray-100 mb-4">
  121. Price History
  122. {#if currentPrice !== null}
  123. <span class="ml-2 text-blue-500 dark:text-blue-300 text-lg font-medium">
  124. ({currentCurrency} {currentPrice.toFixed(2)})
  125. </span>
  126. {/if}
  127. </h3>
  128. <canvas bind:this={chartCanvas}></canvas>
  129. <div class="flex justify-center gap-4 mt-6">
  130. {#each ranges as range}
  131. <button
  132. class="px-4 py-2 rounded-md text-sm font-medium transition cursor-pointer
  133. {selectedRange === range
  134. ? 'bg-blue-500 text-white'
  135. : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-100'}"
  136. on:click={() => updateChart(range)}
  137. >
  138. {range.toUpperCase()}
  139. </button>
  140. {/each}
  141. </div>
  142. </div>
  143. {/if}