An error occurred while processing the template.
The following has evaluated to null or missing: ==> entries[entries?size - 1] [in template "29795641980326#20120#137911" at line 2, column 22] ---- Tip: It's the final [] step that caused this error, not those before it. ---- Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)?? ---- ---- FTL stack trace ("~" means nesting-related): - Failed at: #assign lastEntry = entries[entries?s... [in template "29795641980326#20120#137911" at line 2, column 1] ----
1<#assign entries = entries?reverse />
2<#assign lastEntry = entries[entries?size - 1] />
3<#assign
4 assetRenderer = lastEntry.getAssetRenderer()
5 ddmFormFieldValuesMap = assetRenderer.getDDMFormValuesReader().getDDMFormValues().getDDMFormFieldValuesMap()
6 TyGiaSo = ""
7/>
8<#list ddmFormFieldValuesMap["TyGiaSo"] as field>
9 <#assign TyGiaSo = field.getValue().getString(locale) />
10</#list>
11<#assign secondLastEntry = entries[entries?size - 2] />
12
13<#-- Giá trị mới nhất -->
14<#assign
15 lastRenderer = lastEntry.getAssetRenderer()
16 lastFields = lastRenderer.getDDMFormValuesReader().getDDMFormValues().getDDMFormFieldValuesMap()
17 lastValue = 0
18/>
19<#list lastFields["TyGiaSo"] as field>
20 <#assign lastValue = field.getValue().getString(locale)?number />
21</#list>
22
23<#-- Giá trị trước đó -->
24<#assign
25 secondRenderer = secondLastEntry.getAssetRenderer()
26 secondFields = secondRenderer.getDDMFormValuesReader().getDDMFormValues().getDDMFormFieldValuesMap()
27 prevValue = 0
28/>
29<#list secondFields["TyGiaSo"] as field>
30 <#assign prevValue = field.getValue().getString(locale)?number />
31</#list>
32 <#assign delta = lastValue - prevValue />
33<#assign percent = (delta / prevValue) * 100 />
34
35<#if delta gte 0>
36 <#assign sign = "+" />
37<#else>
38 <#assign sign = "" />
39</#if>
40
41<div class="max-[1170px]:w-[calc(100%/2-12px)] max-[768px]:w-[calc(100%/2-4px)] max-[768px]:p-2 max-[650px]:w-full max-[650px]:px-0 sbv-chart">
42 <div class="flex flex-col space-y-[1px] mb-3">
43 <a href="https://www.dttktt.sbv.gov.vn/TyGia/faces/TyGiaTrungTam.jspx" class="text-[#000000] text-[20px] leading-[1.15] font-bold uppercase">
44 tỷ giá
45 </a>
46 <span class="text-[#009953] leading-[21px] text-[18px]">
47 ${lastValue?string["#,##0.00"]} VND
48 <span class="inline-block text-[14px] leading-4 text-[#000000]">
49 ${sign}${delta?string["#,##0.00"]} (${sign}${percent?string["0.00"]}%)
50 </span>
51 </span>
52 </div>
53 <div class="w-[370px] h-[270px] max-[1170px]:w-full max-[1170px]:h-[370px] bg-white p-1">
54 <!-- Bar chart would be rendered here -->
55 <canvas id="TyGiaChart" width="400" height="300"></canvas>
56 </div>
57 <div class="text-end mt-2">
58 <span class="text-[#9D1B43] rounded-full flex justify-start items-center space-x-[6px]">
59 <a href="https://www.dttktt.sbv.gov.vn/TyGia/faces/TyGiaTrungTam.jspx" class="text-[#9D1B43] inline-block text-[14px] leading-[1.5] space-x-[6px]">
60 Xem chi tiết <i class="fa-solid fa-caret-right"></i>
61 </a>
62 </div>
63 </div>
64 <style>
65 .sbv-chart .text-end a{color: #9D1B43;}
66 .sbv-chart .text-end a:hover{color: var(--btn-link-hover-color);}
67</style>
68<script>
69const showFirstAndLastLabelPlugin = {
70 id: 'showFirstAndLastLabel',
71 afterBuildTicks: function(chart) {
72 const xAxis = chart.scales.x;
73 if (!xAxis) return;
74
75 const ticks = xAxis.ticks;
76 const labels = xAxis.getLabels();
77
78 if (ticks.length === 0 || labels.length === 0) return;
79
80 // Xác định index đang hiển thị trong viewport hiện tại
81 const minIndex = Math.ceil(xAxis.min); // chỉ số nhỏ nhất hiển thị
82 const maxIndex = Math.floor(xAxis.max); // chỉ số lớn nhất hiển thị
83
84 // Nếu mốc đầu đang nằm trong viewport, ép lại label
85 if (ticks[0] && minIndex === 0) {
86 ticks[0].label = labels[0];
87 }
88
89 // Nếu mốc cuối đang nằm trong viewport, ép lại label
90 if (ticks[ticks.length - 1] && maxIndex === labels.length - 1) {
91 ticks[ticks.length - 1].label = labels[labels.length - 1];
92 }
93 }
94};
95
96document.addEventListener("DOMContentLoaded", function() {
97var dates = [
98 <#list entries as curEntry>
99 <#assign
100 assetRenderer = curEntry.getAssetRenderer()
101 ddmFormFieldValuesMap = assetRenderer.getDDMFormValuesReader().getDDMFormValues().getDDMFormFieldValuesMap()
102 NgayBatDau = ""
103 />
104<#assign zoomStart = (entries?size > 10)?then(entries?size - 10, 0) />
105<#assign zoomEnd = entries?size - 1 />
106 <#list ddmFormFieldValuesMap["NgayBatDau"] as field>
107 <#assign NgayBatDau = field.getValue().getString(locale)?date("yyyy-MM-dd") />
108 </#list>
109 "${NgayBatDau?string("dd-MM-yyyy")}"<#if curEntry_has_next>,</#if>
110 </#list>
111];
112
113 var tyGiaValues = [
114 <#list entries as curEntry>
115 <#assign
116 assetRenderer = curEntry.getAssetRenderer()
117 ddmFormFieldValuesMap = assetRenderer.getDDMFormValuesReader().getDDMFormValues().getDDMFormFieldValuesMap()
118 TyGiaSo = ""
119 />
120 <#list ddmFormFieldValuesMap["TyGiaSo"] as field>
121 <#assign TyGiaSo = field.getValue().getString(locale) />
122 </#list>
123 ${TyGiaSo?number}<#if curEntry_has_next>,</#if>
124 </#list>
125 ];
126 const ctx = document.getElementById('TyGiaChart').getContext('2d');
127new Chart(ctx, {
128 type: 'line',
129 data: {
130 labels: dates,
131 datasets: [{
132 label: 'Tỷ giá (VND)',
133 data: tyGiaValues,
134 borderColor: '#A37B40',
135 backgroundColor: '#A37B40',
136 tension: 0.3,
137 fill: false,
138 pointBackgroundColor: '#A37B40'
139 }]
140 },
141 options: {
142 scales: {
143 y: {
144 ticks: {
145 color: '#000000'
146 },
147 title: {
148 display: false,
149 text: 'Tỷ giá',
150 font: { weight: 'bold' },
151 color: "#000000",
152 align: 'end'
153 },
154 beginAtZero: false
155 },
156 x: {
157 ticks: {
158 color: '#000000'
159 },
160 title: {
161 display: false,
162 text: 'Năm',
163 font: { weight: 'bold' },
164 align: 'end',
165 color: "#000000",
166 min: ${zoomStart},
167 max: ${zoomEnd},
168 }
169 }
170 },
171 plugins: {
172 legend: { display: false },
173 zoom: {
174 zoom: {
175 wheel: { enabled: true }, // Zoom bằng cuộn chuột
176 pinch: { enabled: true }, // Zoom bằng cảm ứng (mobile)
177 mode: 'x', // hoặc 'y' hoặc 'xy'
178 onZoomComplete: ({ chart }) => chart.update('none'),
179 // 👇 Zoom theo con trỏ chuột
180 center: {
181 x: 'mouse',
182 y: 'mouse'
183 }
184 },
185 pan: {
186 enabled: true,
187 mode: 'x' // cho phép kéo trục x
188 },
189 limits: {
190 x: { min: 'original', max: 'original' },
191 y: { min: 'original', max: 'original' }
192 }
193 }
194 }
195 },
196 plugins: [ChartZoom, showFirstAndLastLabelPlugin]
197});
198
199});
200</script>