| document.addEventListener('DOMContentLoaded', function() { |
| |
| const expenses = [ |
| { id: 1, software: 'CSI [Etabs, SAFE, SAP2000]', invoiceDate: 'July 9, 2020', cost: 12176.60, renewalDate: 'July 9, 2020' }, |
| { id: 2, software: 'Plaxis', invoiceDate: 'September 1, 2020', cost: 5361.00, renewalDate: 'September 1, 2020' }, |
| { id: 3, software: 'Tekla', invoiceDate: 'December 1, 2020', cost: 13503.40, renewalDate: 'December 1, 2020' }, |
| { id: 4, software: 'CSI [Etabs, SAFE, SAP2000]', invoiceDate: 'July 9, 2021', cost: 11877.00, renewalDate: 'July 9, 2021' }, |
| { id: 5, software: 'Plaxis', invoiceDate: 'September 1, 2021', cost: 5391.00, renewalDate: 'September 1, 2021' }, |
| { id: 6, software: 'Tekla', invoiceDate: 'December 1, 2021', cost: 6868.60, renewalDate: 'December 1, 2021' }, |
| { id: 7, software: 'CSI [Etabs, SAFE, SAP2000]', invoiceDate: 'August 10, 2022', cost: 13482.00, renewalDate: 'August 10, 2022' }, |
| { id: 8, software: 'Plaxis', invoiceDate: 'September 5, 2022', cost: 5796.00, renewalDate: 'September 5, 2022' }, |
| { id: 9, software: 'Tekla', invoiceDate: 'October 18, 2022', cost: 7130.37, renewalDate: 'December 1, 2022' }, |
| { id: 10, software: 'CSI [Etabs, SAFE, SAP2000]', invoiceDate: 'July 4, 2023', cost: 11664.00, renewalDate: 'July 4, 2023' }, |
| { id: 11, software: 'Plaxis', invoiceDate: 'September 21, 2023', cost: 6201.00, renewalDate: 'September 5, 2023' }, |
| { id: 12, software: 'Tekla', invoiceDate: 'October 18, 2023', cost: 7792.20, renewalDate: 'December 1, 2023' }, |
| { id: 13, software: 'CSI [Etabs, SAFE, SAP2000]', invoiceDate: 'July 4, 2024', cost: 11336.00, renewalDate: 'July 4, 2024' }, |
| { id: 14, software: 'Plaxis', invoiceDate: 'September 18, 2024', cost: 7003.40, renewalDate: '18/9/2024' }, |
| { id: 15, software: 'Tekla', invoiceDate: 'October 10, 2024', cost: 8316.00, renewalDate: '10/10/2024' }, |
| { id: 16, software: 'CSI [Etabs, SAFE, SAP2000]', invoiceDate: 'May 22, 2025', cost: 11336.00, renewalDate: 'May 22, 2025' }, |
| { id: 17, software: 'Unreal Engine', invoiceDate: '6-Sept-24', cost: 2727.18, renewalDate: '6-Sept-24' }, |
| { id: 18, software: 'Plaxis', invoiceDate: 'September 18, 2025', cost: 7752.84, renewalDate: '18/9/2025' } |
| ]; |
|
|
| |
| const tableBody = document.getElementById('expenseTable'); |
| expenses.forEach(expense => { |
| const row = document.createElement('tr'); |
| row.className = 'hover:bg-gray-50 fade-in'; |
| row.innerHTML = ` |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${expense.id}</td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${expense.software}</td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${expense.invoiceDate}</td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${expense.cost.toLocaleString('en-SG', {style: 'currency', currency: 'SGD'})}</td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${expense.renewalDate}</td> |
| `; |
| tableBody.appendChild(row); |
| }); |
|
|
| |
| const years = ['2020', '2021', '2022', '2023', '2024', '2025']; |
| const annualCosts = years.map(year => { |
| return expenses |
| .filter(expense => expense.renewalDate.includes(year)) |
| .reduce((sum, expense) => sum + expense.cost, 0); |
| }); |
|
|
| const softwareNames = [...new Set(expenses.map(expense => expense.software))]; |
| const softwareCosts = softwareNames.map(software => { |
| return expenses |
| .filter(expense => expense.software === software) |
| .reduce((sum, expense) => sum + expense.cost, 0); |
| }); |
|
|
| |
| createAnnualChart(years, annualCosts); |
| createSoftwareChart(softwareNames, softwareCosts); |
| }); |
|
|
| function createAnnualChart(years, costs) { |
| const ctx = document.getElementById('annualChart').getContext('2d'); |
| new Chart(ctx, { |
| type: 'bar', |
| data: { |
| labels: years, |
| datasets: [{ |
| label: 'Annual Software Costs (SGD)', |
| data: costs, |
| backgroundColor: '#3B82F6', |
| borderColor: '#2563EB', |
| borderWidth: 1 |
| }] |
| }, |
| options: { |
| responsive: true, |
| plugins: { |
| legend: { |
| position: 'top', |
| }, |
| tooltip: { |
| callbacks: { |
| label: function(context) { |
| return context.parsed.y.toLocaleString('en-SG', {style: 'currency', currency: 'SGD'}); |
| } |
| } |
| } |
| }, |
| scales: { |
| y: { |
| beginAtZero: true, |
| ticks: { |
| callback: function(value) { |
| return value.toLocaleString('en-SG', {style: 'currency', currency: 'SGD'}); |
| } |
| } |
| } |
| } |
| } |
| }); |
| } |
|
|
| function createSoftwareChart(softwareNames, costs) { |
| const ctx = document.getElementById('softwareChart').getContext('2d'); |
| new Chart(ctx, { |
| type: 'pie', |
| data: { |
| labels: softwareNames, |
| datasets: [{ |
| data: costs, |
| backgroundColor: [ |
| '#3B82F6', |
| '#10B981', |
| '#F59E0B', |
| '#EF4444', |
| '#8B5CF6' |
| ], |
| borderWidth: 1 |
| }] |
| }, |
| options: { |
| responsive: true, |
| plugins: { |
| legend: { |
| position: 'right', |
| }, |
| tooltip: { |
| callbacks: { |
| label: function(context) { |
| const label = context.label || ''; |
| const value = context.raw || 0; |
| const total = context.dataset.data.reduce((a, b) => a + b, 0); |
| const percentage = Math.round((value / total) * 100); |
| return `${label}: ${value.toLocaleString('en-SG', {style: 'currency', currency: 'SGD'})} (${percentage}%)`; |
| } |
| } |
| } |
| } |
| } |
| }); |
| } |