|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>301 Tariff Analysis Dashboard - US Auto Industry</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-chart-box-and-violin-plot@3.0.2/build/Chart.BoxPlot.js"></script> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.chart-container { |
|
|
transition: all 0.3s ease; |
|
|
cursor: pointer; |
|
|
} |
|
|
.chart-container:hover { |
|
|
transform: translateY(-5px); |
|
|
box-shadow: 0 10px 20px rgba(0,0,0,0.1); |
|
|
} |
|
|
.sidebar { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.data-table { |
|
|
max-height: 400px; |
|
|
overflow-y: auto; |
|
|
} |
|
|
.highlight-row:hover { |
|
|
background-color: #f0f9ff; |
|
|
} |
|
|
.tooltip { |
|
|
position: relative; |
|
|
display: inline-block; |
|
|
} |
|
|
.tooltip .tooltiptext { |
|
|
visibility: hidden; |
|
|
width: 200px; |
|
|
background-color: #333; |
|
|
color: #fff; |
|
|
text-align: center; |
|
|
border-radius: 6px; |
|
|
padding: 5px; |
|
|
position: absolute; |
|
|
z-index: 1; |
|
|
bottom: 125%; |
|
|
left: 50%; |
|
|
margin-left: -100px; |
|
|
opacity: 0; |
|
|
transition: opacity 0.3s; |
|
|
} |
|
|
.tooltip:hover .tooltiptext { |
|
|
visibility: visible; |
|
|
opacity: 1; |
|
|
} |
|
|
.active-filter { |
|
|
background-color: #3B82F6 !important; |
|
|
color: white !important; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50"> |
|
|
<div class="flex h-screen overflow-hidden"> |
|
|
|
|
|
<div class="sidebar bg-blue-800 text-white w-64 flex-shrink-0 hidden md:block"> |
|
|
<div class="p-4"> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<i class="fas fa-car text-2xl"></i> |
|
|
<h1 class="text-xl font-bold">301 Tariff Analyzer</h1> |
|
|
</div> |
|
|
<p class="text-blue-200 text-sm mt-2">US Auto Industry Focus</p> |
|
|
</div> |
|
|
<nav class="mt-6"> |
|
|
<div class="px-4 py-2 bg-blue-700"> |
|
|
<span class="font-medium">Dashboard</span> |
|
|
</div> |
|
|
<a href="#" class="block px-4 py-2 text-blue-200 hover:bg-blue-700 hover:text-white"> |
|
|
<i class="fas fa-chart-line mr-2"></i>Overview |
|
|
</a> |
|
|
<a href="#" class="block px-4 py-2 text-blue-200 hover:bg-blue-700 hover:text-white"> |
|
|
<i class="fas fa-file-import mr-2"></i>Import Data |
|
|
</a> |
|
|
<a href="#" class="block px-4 py-2 text-blue-200 hover:bg-blue-700 hover:text-white"> |
|
|
<i class="fas fa-search-dollar mr-2"></i>Tariff Analysis |
|
|
</a> |
|
|
<a href="#" class="block px-4 py-2 text-blue-200 hover:bg-blue-700 hover:text-white"> |
|
|
<i class="fas fa-industry mr-2"></i>Company Profiles |
|
|
</a> |
|
|
<a href="#" class="block px-4 py-2 text-blue-200 hover:bg-blue-700 hover:text-white"> |
|
|
<i class="fas fa-cog mr-2"></i>Settings |
|
|
</a> |
|
|
</nav> |
|
|
<div class="absolute bottom-0 w-full p-4"> |
|
|
<div class="bg-blue-700 p-3 rounded-lg"> |
|
|
<p class="text-sm">Need help with tariff analysis?</p> |
|
|
<button class="mt-2 bg-white text-blue-800 px-3 py-1 rounded text-sm font-medium w-full"> |
|
|
Contact Support |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex-1 overflow-auto"> |
|
|
|
|
|
<header class="bg-white shadow-sm"> |
|
|
<div class="flex justify-between items-center px-6 py-4"> |
|
|
<div class="flex items-center"> |
|
|
<button class="md:hidden mr-4 text-gray-500"> |
|
|
<i class="fas fa-bars text-xl"></i> |
|
|
</button> |
|
|
<h2 class="text-xl font-semibold text-gray-800">301 Tariff Dashboard</h2> |
|
|
</div> |
|
|
<div class="flex items-center space-x-4"> |
|
|
<div class="relative"> |
|
|
<input type="text" placeholder="Search..." class="pl-8 pr-4 py-2 border rounded-lg text-sm"> |
|
|
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> |
|
|
</div> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<div class="relative"> |
|
|
<i class="fas fa-bell text-gray-500 text-xl"></i> |
|
|
<span class="absolute top-0 right-0 h-2 w-2 rounded-full bg-red-500"></span> |
|
|
</div> |
|
|
<div class="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white"> |
|
|
<span>JD</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<main class="p-6"> |
|
|
|
|
|
<div class="bg-white rounded-lg shadow p-4 mb-6"> |
|
|
<div class="flex flex-wrap items-center justify-between gap-4"> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Time Period</label> |
|
|
<select id="timePeriod" class="border rounded-md px-3 py-2 text-sm"> |
|
|
<option value="12">Last 12 Months</option> |
|
|
<option value="6">Last 6 Months</option> |
|
|
<option value="3">Last 3 Months</option> |
|
|
<option value="custom">Custom Range</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Company</label> |
|
|
<select id="companyFilter" class="border rounded-md px-3 py-2 text-sm"> |
|
|
<option value="all">All Companies</option> |
|
|
<option value="General Motors">General Motors</option> |
|
|
<option value="Ford">Ford</option> |
|
|
<option value="Tesla">Tesla</option> |
|
|
<option value="Stellantis">Stellantis</option> |
|
|
<option value="Toyota USA">Toyota USA</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Product Category</label> |
|
|
<select id="productFilter" class="border rounded-md px-3 py-2 text-sm"> |
|
|
<option value="all">All Categories</option> |
|
|
<option value="Vehicle Parts">Vehicle Parts</option> |
|
|
<option value="Engines">Engines</option> |
|
|
<option value="Electronics">Electronics</option> |
|
|
<option value="Steel Components">Steel Components</option> |
|
|
<option value="Aluminum Components">Aluminum Components</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Country of Origin</label> |
|
|
<select id="countryFilter" class="border rounded-md px-3 py-2 text-sm"> |
|
|
<option value="all">All Countries</option> |
|
|
<option value="China">China</option> |
|
|
<option value="Mexico">Mexico</option> |
|
|
<option value="Canada">Canada</option> |
|
|
<option value="Germany">Germany</option> |
|
|
<option value="Japan">Japan</option> |
|
|
</select> |
|
|
</div> |
|
|
<button id="applyFilters" class="bg-blue-600 text-white px-4 py-2 rounded-md text-sm font-medium mt-6 hover:bg-blue-700"> |
|
|
Apply Filters |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6" id="keyMetrics"> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6"> |
|
|
|
|
|
<div class="bg-white rounded-lg shadow p-4 chart-container" id="monthlyChartContainer"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="font-medium text-gray-800">Monthly Tariff Payments</h3> |
|
|
<div class="flex space-x-2"> |
|
|
<button class="text-xs bg-gray-100 px-2 py-1 rounded chart-toggle active-filter" data-chart="monthlyTariffChart" data-type="USD">USD</button> |
|
|
<button class="text-xs bg-gray-100 px-2 py-1 rounded chart-toggle" data-chart="monthlyTariffChart" data-type="percent">% Change</button> |
|
|
</div> |
|
|
</div> |
|
|
<div class="h-64"> |
|
|
<canvas id="monthlyTariffChart"></canvas> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-lg shadow p-4 chart-container" id="companyChartContainer"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="font-medium text-gray-800">Tariff Impact by Company</h3> |
|
|
<div class="flex space-x-2"> |
|
|
<button class="text-xs bg-gray-100 px-2 py-1 rounded chart-toggle active-filter" data-chart="companyImpactChart" data-type="total">Total</button> |
|
|
<button class="text-xs bg-gray-100 px-2 py-1 rounded chart-toggle" data-chart="companyImpactChart" data-type="perShipment">Per Shipment</button> |
|
|
</div> |
|
|
</div> |
|
|
<div class="h-64"> |
|
|
<canvas id="companyImpactChart"></canvas> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-lg shadow p-4 chart-container" id="productChartContainer"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="font-medium text-gray-800">Product Category Breakdown</h3> |
|
|
<div class="flex space-x-2"> |
|
|
<button class="text-xs bg-gray-100 px-2 py-1 rounded chart-toggle active-filter" data-chart="productCategoryChart" data-type="value">Value</button> |
|
|
<button class="text-xs bg-gray-100 px-2 py-1 rounded chart-toggle" data-chart="productCategoryChart" data-type="volume">Volume</button> |
|
|
</div> |
|
|
</div> |
|
|
<div class="h-64"> |
|
|
<canvas id="productCategoryChart"></canvas> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-lg shadow p-4 chart-container" id="countryChartContainer"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="font-medium text-gray-800">Tariffs by Country of Origin</h3> |
|
|
<div class="flex space-x-2"> |
|
|
<button class="text-xs bg-gray-100 px-2 py-1 rounded chart-toggle active-filter" data-chart="countryOriginChart" data-type="absolute">Absolute</button> |
|
|
<button class="text-xs bg-gray-100 px-2 py-1 rounded chart-toggle" data-chart="countryOriginChart" data-type="relative">Relative</button> |
|
|
</div> |
|
|
</div> |
|
|
<div class="h-64"> |
|
|
<canvas id="countryOriginChart"></canvas> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6"> |
|
|
|
|
|
<div class="bg-white rounded-lg shadow p-4 chart-container" id="distributionChartContainer"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="font-medium text-gray-800">Tariff Rate Distribution</h3> |
|
|
<div class="tooltip"> |
|
|
<i class="fas fa-info-circle text-gray-400"></i> |
|
|
<span class="tooltiptext">Shows the frequency of different tariff rates applied to imports</span> |
|
|
</div> |
|
|
</div> |
|
|
<div class="h-64"> |
|
|
<canvas id="tariffRateDistributionChart"></canvas> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-lg shadow p-4 chart-container" id="exemptionChartContainer"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="font-medium text-gray-800">Exemption Status Analysis</h3> |
|
|
<div class="tooltip"> |
|
|
<i class="fas fa-info-circle text-gray-400"></i> |
|
|
<span class="tooltiptext">Breakdown of tariff exemption requests and their status</span> |
|
|
</div> |
|
|
</div> |
|
|
<div class="h-64"> |
|
|
<canvas id="exemptionStatusChart"></canvas> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-lg shadow overflow-hidden mb-6"> |
|
|
<div class="p-4 border-b"> |
|
|
<h3 class="font-medium text-gray-800">Detailed Tariff Data</h3> |
|
|
</div> |
|
|
<div class="data-table"> |
|
|
<table class="min-w-full divide-y divide-gray-200"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Company</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">HS Code</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Product</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Origin</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Value</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tariff Rate</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tariff Amt</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="bg-white divide-y divide-gray-200" id="tariffDataBody"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
<div class="bg-gray-50 px-4 py-3 flex items-center justify-between border-t border-gray-200"> |
|
|
<div class="flex-1 flex justify-between sm:hidden"> |
|
|
<a href="#" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"> |
|
|
Previous |
|
|
</a> |
|
|
<a href="#" class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"> |
|
|
Next |
|
|
</a> |
|
|
</div> |
|
|
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"> |
|
|
<div> |
|
|
<p class="text-sm text-gray-700"> |
|
|
Showing <span class="font-medium" id="startItem">1</span> to <span class="font-medium" id="endItem">10</span> of <span class="font-medium" id="totalItems">500</span> entries |
|
|
</p> |
|
|
</div> |
|
|
<div> |
|
|
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination"> |
|
|
<a href="#" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50" id="prevPage"> |
|
|
<span class="sr-only">Previous</span> |
|
|
<i class="fas fa-chevron-left"></i> |
|
|
</a> |
|
|
<a href="#" aria-current="page" class="z-10 bg-blue-50 border-blue-500 text-blue-600 relative inline-flex items-center px-4 py-2 border text-sm font-medium page-link active-page" data-page="1"> |
|
|
1 |
|
|
</a> |
|
|
<a href="#" class="bg-white border-gray-300 text-gray-500 hover:bg-gray-50 relative inline-flex items-center px-4 py-2 border text-sm font-medium page-link" data-page="2"> |
|
|
2 |
|
|
</a> |
|
|
<a href="#" class="bg-white border-gray-300 text-gray-500 hover:bg-gray-50 relative inline-flex items-center px-4 py-2 border text-sm font-medium page-link" data-page="3"> |
|
|
3 |
|
|
</a> |
|
|
<a href="#" class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50" id="nextPage"> |
|
|
<span class="sr-only">Next</span> |
|
|
<i class="fas fa-chevron-right"></i> |
|
|
</a> |
|
|
</nav> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-lg shadow p-6 mb-6"> |
|
|
<h3 class="font-medium text-gray-800 mb-4">Key Insights</h3> |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6" id="insightsContainer"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</main> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
function generateDemoData() { |
|
|
const companies = ['General Motors', 'Ford', 'Tesla', 'Stellantis', 'Toyota USA', 'Honda USA', 'BMW US', 'Mercedes-Benz USA']; |
|
|
const products = ['Vehicle Parts', 'Engines', 'Electronics', 'Steel Components', 'Aluminum Wheels', 'Sensors', 'Battery Components', 'Interior Trim']; |
|
|
const origins = ['China', 'Mexico', 'Canada', 'Germany', 'Japan', 'South Korea', 'Thailand', 'Vietnam']; |
|
|
const hsCodes = ['8708.99', '8708.50', '8483.10', '7326.90', '7616.99', '8536.50', '8507.60', '9401.90']; |
|
|
const exemptionStatuses = ['Approved', 'Pending', 'Denied', 'Not Requested']; |
|
|
|
|
|
const data = []; |
|
|
|
|
|
for (let i = 0; i < 500; i++) { |
|
|
const company = companies[Math.floor(Math.random() * companies.length)]; |
|
|
const product = products[Math.floor(Math.random() * products.length)]; |
|
|
const origin = origins[Math.floor(Math.random() * origins.length)]; |
|
|
const hsCode = hsCodes[Math.floor(Math.random() * hsCodes.length)]; |
|
|
const value = (Math.random() * 500000 + 10000).toFixed(2); |
|
|
const tariffRate = (Math.random() * 25 + 5).toFixed(2); |
|
|
const tariffAmount = (value * tariffRate / 100).toFixed(2); |
|
|
const date = new Date(2022, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1); |
|
|
const exemptionStatus = exemptionStatuses[Math.floor(Math.random() * exemptionStatuses.length)]; |
|
|
|
|
|
data.push({ |
|
|
id: i + 1, |
|
|
company, |
|
|
hsCode, |
|
|
product, |
|
|
origin, |
|
|
value, |
|
|
tariffRate, |
|
|
tariffAmount, |
|
|
date: date.toLocaleDateString(), |
|
|
exemptionStatus, |
|
|
weight: (Math.random() * 1000 + 100).toFixed(2), |
|
|
quantity: Math.floor(Math.random() * 1000) + 1, |
|
|
portOfEntry: ['Los Angeles', 'New York', 'Houston', 'Savannah', 'Seattle'][Math.floor(Math.random() * 5)], |
|
|
broker: ['FedEx Trade', 'DHL Global', 'UPS Supply', 'Customs Brokers Inc'][Math.floor(Math.random() * 4)], |
|
|
shipmentMethod: ['Air', 'Sea', 'Land'][Math.floor(Math.random() * 3)], |
|
|
processingTime: Math.floor(Math.random() * 10) + 1, |
|
|
inspectionRequired: Math.random() > 0.7 ? 'Yes' : 'No', |
|
|
|
|
|
}); |
|
|
} |
|
|
|
|
|
return data; |
|
|
} |
|
|
|
|
|
let demoData = generateDemoData(); |
|
|
let filteredData = [...demoData]; |
|
|
let currentPage = 1; |
|
|
const itemsPerPage = 10; |
|
|
|
|
|
|
|
|
let monthlyTariffChart, companyImpactChart, productCategoryChart, countryOriginChart, tariffRateDistributionChart, exemptionStatusChart; |
|
|
|
|
|
|
|
|
function filterData() { |
|
|
const company = document.getElementById('companyFilter').value; |
|
|
const product = document.getElementById('productFilter').value; |
|
|
const country = document.getElementById('countryFilter').value; |
|
|
const timePeriod = document.getElementById('timePeriod').value; |
|
|
|
|
|
filteredData = demoData.filter(item => { |
|
|
|
|
|
if (company !== 'all' && item.company !== company) return false; |
|
|
|
|
|
|
|
|
if (product !== 'all' && item.product !== product) return false; |
|
|
|
|
|
|
|
|
if (country !== 'all' && item.origin !== country) return false; |
|
|
|
|
|
|
|
|
if (timePeriod !== 'custom') { |
|
|
const months = parseInt(timePeriod); |
|
|
const itemDate = new Date(item.date); |
|
|
const cutoffDate = new Date(); |
|
|
cutoffDate.setMonth(cutoffDate.getMonth() - months); |
|
|
|
|
|
if (itemDate < cutoffDate) return false; |
|
|
} |
|
|
|
|
|
return true; |
|
|
}); |
|
|
|
|
|
currentPage = 1; |
|
|
updateTable(); |
|
|
updateCharts(); |
|
|
updateMetrics(); |
|
|
updateInsights(); |
|
|
updatePagination(); |
|
|
} |
|
|
|
|
|
|
|
|
function updateTable() { |
|
|
const tableBody = document.getElementById('tariffDataBody'); |
|
|
tableBody.innerHTML = ''; |
|
|
|
|
|
const startIndex = (currentPage - 1) * itemsPerPage; |
|
|
const endIndex = Math.min(startIndex + itemsPerPage, filteredData.length); |
|
|
|
|
|
for (let i = startIndex; i < endIndex; i++) { |
|
|
const item = filteredData[i]; |
|
|
const row = document.createElement('tr'); |
|
|
row.className = 'highlight-row'; |
|
|
row.innerHTML = ` |
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${item.company}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.hsCode}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.product}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.origin}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">$${parseFloat(item.value).toLocaleString()}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.tariffRate}%</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">$${parseFloat(item.tariffAmount).toLocaleString()}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.date}</td> |
|
|
`; |
|
|
tableBody.appendChild(row); |
|
|
} |
|
|
|
|
|
document.getElementById('startItem').textContent = startIndex + 1; |
|
|
document.getElementById('endItem').textContent = endIndex; |
|
|
document.getElementById('totalItems').textContent = filteredData.length; |
|
|
} |
|
|
|
|
|
|
|
|
function updateMetrics() { |
|
|
const totalTariff = filteredData.reduce((sum, item) => sum + parseFloat(item.tariffAmount), 0); |
|
|
const avgTariffRate = filteredData.reduce((sum, item) => sum + parseFloat(item.tariffRate), 0) / filteredData.length; |
|
|
|
|
|
|
|
|
const companyTotals = {}; |
|
|
filteredData.forEach(item => { |
|
|
if (!companyTotals[item.company]) { |
|
|
companyTotals[item.company] = 0; |
|
|
} |
|
|
companyTotals[item.company] += parseFloat(item.tariffAmount); |
|
|
}); |
|
|
|
|
|
let topCompany = ''; |
|
|
let topAmount = 0; |
|
|
for (const company in companyTotals) { |
|
|
if (companyTotals[company] > topAmount) { |
|
|
topCompany = company; |
|
|
topAmount = companyTotals[company]; |
|
|
} |
|
|
} |
|
|
|
|
|
const metricsContainer = document.getElementById('keyMetrics'); |
|
|
metricsContainer.innerHTML = ` |
|
|
<div class="bg-white rounded-lg shadow p-4"> |
|
|
<div class="flex justify-between items-start"> |
|
|
<div> |
|
|
<p class="text-sm text-gray-500">Total Tariff Payments</p> |
|
|
<p class="text-2xl font-bold mt-1">$${(totalTariff / 1000000).toFixed(1)}M</p> |
|
|
<p class="text-xs text-green-600 mt-1">+${(Math.random() * 10 + 5).toFixed(1)}% from last period</p> |
|
|
</div> |
|
|
<div class="bg-blue-100 p-3 rounded-full"> |
|
|
<i class="fas fa-dollar-sign text-blue-600"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white rounded-lg shadow p-4"> |
|
|
<div class="flex justify-between items-start"> |
|
|
<div> |
|
|
<p class="text-sm text-gray-500">Affected Shipments</p> |
|
|
<p class="text-2xl font-bold mt-1">${filteredData.length}</p> |
|
|
<p class="text-xs text-red-600 mt-1">-${(Math.random() * 5 + 3).toFixed(1)}% from last period</p> |
|
|
</div> |
|
|
<div class="bg-green-100 p-3 rounded-full"> |
|
|
<i class="fas fa-ship text-green-600"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white rounded-lg shadow p-4"> |
|
|
<div class="flex justify-between items-start"> |
|
|
<div> |
|
|
<p class="text-sm text-gray-500">Avg Tariff Rate</p> |
|
|
<p class="text-2xl font-bold mt-1">${avgTariffRate.toFixed(1)}%</p> |
|
|
<p class="text-xs text-green-600 mt-1">+${(Math.random() * 2 + 0.5).toFixed(1)}% from last period</p> |
|
|
</div> |
|
|
<div class="bg-purple-100 p-3 rounded-full"> |
|
|
<i class="fas fa-percent text-purple-600"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white rounded-lg shadow p-4"> |
|
|
<div class="flex justify-between items-start"> |
|
|
<div> |
|
|
<p class="text-sm text-gray-500">Top Impacted Company</p> |
|
|
<p class="text-xl font-bold mt-1">${topCompany || 'N/A'}</p> |
|
|
<p class="text-xs text-gray-600 mt-1">$${(topAmount / 1000000).toFixed(1)}M in tariffs</p> |
|
|
</div> |
|
|
<div class="bg-yellow-100 p-3 rounded-full"> |
|
|
<i class="fas fa-building text-yellow-600"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
function updateInsights() { |
|
|
|
|
|
const productBreakdown = {}; |
|
|
const exemptionStats = { Approved: 0, Pending: 0, Denied: 0, NotRequested: 0 }; |
|
|
const countryBreakdown = {}; |
|
|
|
|
|
filteredData.forEach(item => { |
|
|
|
|
|
if (!productBreakdown[item.product]) { |
|
|
productBreakdown[item.product] = 0; |
|
|
} |
|
|
productBreakdown[item.product] += parseFloat(item.tariffAmount); |
|
|
|
|
|
|
|
|
if (item.exemptionStatus === 'Not Requested') { |
|
|
exemptionStats.NotRequested++; |
|
|
} else { |
|
|
exemptionStats[item.exemptionStatus]++; |
|
|
} |
|
|
|
|
|
|
|
|
if (!countryBreakdown[item.origin]) { |
|
|
countryBreakdown[item.origin] = 0; |
|
|
} |
|
|
countryBreakdown[item.origin] += parseFloat(item.tariffAmount); |
|
|
}); |
|
|
|
|
|
|
|
|
let topProduct = ''; |
|
|
let topProductAmount = 0; |
|
|
let totalProductAmount = 0; |
|
|
for (const product in productBreakdown) { |
|
|
totalProductAmount += productBreakdown[product]; |
|
|
if (productBreakdown[product] > topProductAmount) { |
|
|
topProduct = product; |
|
|
topProductAmount = productBreakdown[product]; |
|
|
} |
|
|
} |
|
|
|
|
|
const productPercentage = (topProductAmount / totalProductAmount * 100).toFixed(0); |
|
|
|
|
|
|
|
|
const totalExemptionRequests = exemptionStats.Approved + exemptionStats.Pending + exemptionStats.Denied; |
|
|
const approvalRate = totalExemptionRequests > 0 |
|
|
? (exemptionStats.Approved / totalExemptionRequests * 100).toFixed(0) |
|
|
: 0; |
|
|
|
|
|
|
|
|
let topCountry = ''; |
|
|
let topCountryAmount = 0; |
|
|
for (const country in countryBreakdown) { |
|
|
if (countryBreakdown[country] > topCountryAmount) { |
|
|
topCountry = country; |
|
|
topCountryAmount = countryBreakdown[country]; |
|
|
} |
|
|
} |
|
|
|
|
|
const insightsContainer = document.getElementById('insightsContainer'); |
|
|
insightsContainer.innerHTML = ` |
|
|
<div class="border-l-4 border-blue-500 pl-4"> |
|
|
<h4 class="font-medium text-gray-700 mb-2">Top Impact</h4> |
|
|
<p class="text-sm text-gray-600">${topProduct || 'N/A'} account for ${productPercentage}% of all 301 tariff payments in the filtered data, with ${topCountry || 'N/A'} imports seeing the highest tariff amounts.</p> |
|
|
</div> |
|
|
<div class="border-l-4 border-green-500 pl-4"> |
|
|
<h4 class="font-medium text-gray-700 mb-2">Exemption Trends</h4> |
|
|
<p class="text-sm text-gray-600">Approximately ${totalExemptionRequests > 0 ? Math.round(totalExemptionRequests / filteredData.length * 100) : 0}% of tariff lines have exemption requests, with a ${approvalRate}% approval rate.</p> |
|
|
</div> |
|
|
<div class="border-l-4 border-purple-500 pl-4"> |
|
|
<h4 class="font-medium text-gray-700 mb-2">Company Strategies</h4> |
|
|
<p class="text-sm text-gray-600">Based on the filtered data, companies are facing an average tariff rate of ${(filteredData.reduce((sum, item) => sum + parseFloat(item.tariffRate), 0) / filteredData.length).toFixed(1)}% on affected imports.</p> |
|
|
</div> |
|
|
<div class="border-l-4 border-yellow-500 pl-4"> |
|
|
<h4 class="font-medium text-gray-700 mb-2">Cost Projections</h4> |
|
|
<p class="text-sm text-gray-600">If current rates persist, the filtered imports would result in $${(filteredData.reduce((sum, item) => sum + parseFloat(item.tariffAmount), 0) * 12).toLocaleString()} in additional tariffs over the next 12 months.</p> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
function updatePagination() { |
|
|
const totalPages = Math.ceil(filteredData.length / itemsPerPage); |
|
|
const paginationContainer = document.querySelector('.relative.z-0.inline-flex'); |
|
|
|
|
|
|
|
|
const existingLinks = document.querySelectorAll('.page-link'); |
|
|
existingLinks.forEach(link => { |
|
|
if (!link.id) { |
|
|
link.remove(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const prevPage = document.getElementById('prevPage'); |
|
|
const nextPage = document.getElementById('nextPage'); |
|
|
|
|
|
|
|
|
let startPage = Math.max(1, currentPage - 1); |
|
|
let endPage = Math.min(totalPages, currentPage + 1); |
|
|
|
|
|
|
|
|
if (currentPage <= 2) { |
|
|
endPage = Math.min(3, totalPages); |
|
|
} |
|
|
if (currentPage >= totalPages - 1) { |
|
|
startPage = Math.max(1, totalPages - 2); |
|
|
} |
|
|
|
|
|
|
|
|
for (let i = startPage; i <= endPage; i++) { |
|
|
const pageLink = document.createElement('a'); |
|
|
pageLink.href = '#'; |
|
|
pageLink.className = `bg-white border-gray-300 text-gray-500 hover:bg-gray-50 relative inline-flex items-center px-4 py-2 border text-sm font-medium page-link ${i === currentPage ? 'active-page bg-blue-50 border-blue-500 text-blue-600' : ''}`; |
|
|
pageLink.setAttribute('data-page', i); |
|
|
pageLink.textContent = i; |
|
|
|
|
|
if (i === 1) { |
|
|
prevPage.insertAdjacentElement('afterend', pageLink); |
|
|
} else { |
|
|
const lastPageLink = document.querySelector('.page-link:not(#prevPage):not(#nextPage):last-of-type'); |
|
|
lastPageLink.insertAdjacentElement('afterend', pageLink); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
prevPage.classList.toggle('opacity-50', currentPage === 1); |
|
|
prevPage.classList.toggle('cursor-not-allowed', currentPage === 1); |
|
|
nextPage.classList.toggle('opacity-50', currentPage === totalPages); |
|
|
nextPage.classList.toggle('cursor-not-allowed', currentPage === totalPages); |
|
|
} |
|
|
|
|
|
|
|
|
function updateCharts() { |
|
|
updateMonthlyTariffChart(); |
|
|
updateCompanyImpactChart(); |
|
|
updateProductCategoryChart(); |
|
|
updateCountryOriginChart(); |
|
|
updateTariffRateDistributionChart(); |
|
|
updateExemptionStatusChart(); |
|
|
} |
|
|
|
|
|
|
|
|
function updateMonthlyTariffChart() { |
|
|
const ctx = document.getElementById('monthlyTariffChart').getContext('2d'); |
|
|
|
|
|
|
|
|
const monthlyData = {}; |
|
|
filteredData.forEach(item => { |
|
|
const date = new Date(item.date); |
|
|
const month = date.getMonth(); |
|
|
|
|
|
if (!monthlyData[month]) { |
|
|
monthlyData[month] = { |
|
|
total: 0, |
|
|
count: 0 |
|
|
}; |
|
|
} |
|
|
|
|
|
monthlyData[month].total += parseFloat(item.tariffAmount); |
|
|
monthlyData[month].count++; |
|
|
}); |
|
|
|
|
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; |
|
|
const tariffData = []; |
|
|
const shipmentData = []; |
|
|
|
|
|
for (let i = 0; i < 12; i++) { |
|
|
tariffData.push(monthlyData[i] ? monthlyData[i].total / 1000000 : 0); |
|
|
shipmentData.push(monthlyData[i] ? monthlyData[i].count : 0); |
|
|
} |
|
|
|
|
|
if (monthlyTariffChart) { |
|
|
monthlyTariffChart.destroy(); |
|
|
} |
|
|
|
|
|
monthlyTariffChart = new Chart(ctx, { |
|
|
type: 'line', |
|
|
data: { |
|
|
labels: months, |
|
|
datasets: [ |
|
|
{ |
|
|
label: 'Tariff Payments ($M)', |
|
|
data: tariffData, |
|
|
borderColor: 'rgba(59, 130, 246, 1)', |
|
|
backgroundColor: 'rgba(59, 130, 246, 0.1)', |
|
|
borderWidth: 2, |
|
|
tension: 0.3, |
|
|
fill: true |
|
|
}, |
|
|
{ |
|
|
label: 'Shipments', |
|
|
data: shipmentData, |
|
|
borderColor: 'rgba(16, 185, 129, 1)', |
|
|
backgroundColor: 'rgba(16, 185, 129, 0.1)', |
|
|
borderWidth: 2, |
|
|
tension: 0.3, |
|
|
fill: true, |
|
|
yAxisID: 'y1' |
|
|
} |
|
|
] |
|
|
}, |
|
|
options: { |
|
|
responsive: true, |
|
|
maintainAspectRatio: false, |
|
|
scales: { |
|
|
y: { |
|
|
beginAtZero: true, |
|
|
title: { |
|
|
display: true, |
|
|
text: 'USD (Millions)' |
|
|
} |
|
|
}, |
|
|
y1: { |
|
|
beginAtZero: true, |
|
|
position: 'right', |
|
|
title: { |
|
|
display: true, |
|
|
text: 'Shipments' |
|
|
}, |
|
|
grid: { |
|
|
drawOnChartArea: false |
|
|
} |
|
|
} |
|
|
}, |
|
|
plugins: { |
|
|
tooltip: { |
|
|
mode: 'index', |
|
|
intersect: false |
|
|
}, |
|
|
legend: { |
|
|
position: 'top' |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('monthlyChartContainer').addEventListener('click', function(e) { |
|
|
if (e.target.tagName === 'CANVAS') { |
|
|
const activePoints = monthlyTariffChart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, true); |
|
|
if (activePoints.length > 0) { |
|
|
const monthIndex = activePoints[0].index; |
|
|
const monthName = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][monthIndex]; |
|
|
|
|
|
|
|
|
const originalFilteredData = [...filteredData]; |
|
|
filteredData = originalFilteredData.filter(item => { |
|
|
const date = new Date(item.date); |
|
|
return date.getMonth() === monthIndex; |
|
|
}); |
|
|
|
|
|
updateTable(); |
|
|
updateCharts(); |
|
|
updateMetrics(); |
|
|
updateInsights(); |
|
|
updatePagination(); |
|
|
|
|
|
|
|
|
alert(`Showing data for ${monthName} only. Click "Apply Filters" to reset.`); |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function updateCompanyImpactChart() { |
|
|
const ctx = document.getElementById('companyImpactChart').getContext('2d'); |
|
|
|
|
|
|
|
|
const companyData = {}; |
|
|
filteredData.forEach(item => { |
|
|
if (!companyData[item.company]) { |
|
|
companyData[item.company] = { |
|
|
total: 0, |
|
|
count: 0, |
|
|
avgRate: 0 |
|
|
}; |
|
|
} |
|
|
|
|
|
companyData[item.company].total += parseFloat(item.tariffAmount); |
|
|
companyData[item.company].count++; |
|
|
companyData[item.company].avgRate += parseFloat(item.tariffRate); |
|
|
}); |
|
|
|
|
|
|
|
|
for (const company in companyData) { |
|
|
companyData[company].avgRate = companyData[company].avgRate / companyData[company].count; |
|
|
} |
|
|
|
|
|
const companies = Object.keys(companyData); |
|
|
const totalData = companies.map(company => companyData[company].total / 1000000); |
|
|
const perShipmentData = companies.map(company => companyData[company].total / companyData[company].count); |
|
|
const avgRateData = companies.map(company => companyData[company].avgRate); |
|
|
|
|
|
if (companyImpactChart) { |
|
|
companyImpactChart.destroy(); |
|
|
} |
|
|
|
|
|
companyImpactChart = new Chart(ctx, { |
|
|
type: 'bar', |
|
|
data: { |
|
|
labels: companies, |
|
|
datasets: [ |
|
|
{ |
|
|
label: 'Total Tariff Payments ($M)', |
|
|
data: totalData, |
|
|
backgroundColor: 'rgba(99, 102, 241, 0.7)', |
|
|
borderColor: 'rgba(99, 102, 241, 1)', |
|
|
borderWidth: 1 |
|
|
}, |
|
|
{ |
|
|
label: 'Avg Tariff Rate (%)', |
|
|
data: avgRateData, |
|
|
backgroundColor: 'rgba(245, 158, 11, 0.7)', |
|
|
borderColor: 'rgba(245, 158, 11, 1)', |
|
|
borderWidth: 1, |
|
|
type: 'line', |
|
|
yAxisID: 'y1' |
|
|
} |
|
|
] |
|
|
}, |
|
|
options: { |
|
|
responsive: true, |
|
|
maintainAspectRatio: false, |
|
|
scales: { |
|
|
y: { |
|
|
beginAtZero: true, |
|
|
title: { |
|
|
display: true, |
|
|
text: 'USD (Millions)' |
|
|
} |
|
|
}, |
|
|
y1: { |
|
|
beginAtZero: true, |
|
|
position: 'right', |
|
|
title: { |
|
|
display: true, |
|
|
text: 'Percentage' |
|
|
}, |
|
|
grid: { |
|
|
drawOnChartArea: false |
|
|
} |
|
|
} |
|
|
}, |
|
|
plugins: { |
|
|
tooltip: { |
|
|
mode: 'index', |
|
|
intersect: false |
|
|
}, |
|
|
legend: { |
|
|
position: 'top' |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('companyChartContainer').addEventListener('click', function(e) { |
|
|
if (e.target.tagName === 'CANVAS') { |
|
|
const activePoints = companyImpactChart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, true); |
|
|
if (activePoints.length > 0) { |
|
|
const companyIndex = activePoints[0].index; |
|
|
const companyName = companyImpactChart.data.labels[companyIndex]; |
|
|
|
|
|
|
|
|
document.getElementById('companyFilter').value = companyName; |
|
|
filterData(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function updateProductCategoryChart() { |
|
|
const ctx = document.getElementById('productCategoryChart').getContext('2d'); |
|
|
|
|
|
|
|
|
const productData = {}; |
|
|
const productVolume = {}; |
|
|
filteredData.forEach(item => { |
|
|
if (!productData[item.product]) { |
|
|
productData[item.product] = 0; |
|
|
productVolume[item.product] = 0; |
|
|
} |
|
|
|
|
|
productData[item.product] += parseFloat(item.tariffAmount); |
|
|
productVolume[item.product]++; |
|
|
}); |
|
|
|
|
|
const products = Object.keys(productData); |
|
|
const valueData = products.map(product => productData[product] / 1000000); |
|
|
const volumeData = products.map(product => productVolume[product]); |
|
|
|
|
|
if (productCategoryChart) { |
|
|
productCategoryChart.destroy(); |
|
|
} |
|
|
|
|
|
productCategoryChart = new Chart(ctx, { |
|
|
type: 'doughnut', |
|
|
data: { |
|
|
labels: products, |
|
|
datasets: [{ |
|
|
label: 'Tariff Value ($M)', |
|
|
data: valueData, |
|
|
backgroundColor: [ |
|
|
'rgba(59, 130, 246, 0.7)', |
|
|
'rgba(16, 185, 129, 0.7)', |
|
|
'rgba(245, 158, 11, 0.7)', |
|
|
'rgba(239, 68, 68, 0.7)', |
|
|
'rgba(139, 92, 246, 0.7)', |
|
|
'rgba(20, 184, 166, 0.7)' |
|
|
], |
|
|
borderColor: [ |
|
|
'rgba(59, 130, 246, 1)', |
|
|
'rgba(16, 185, 129, 1)', |
|
|
'rgba(245, 158, 11, 1)', |
|
|
'rgba(239, 68, 68, 1)', |
|
|
'rgba(139, 92, 246, 1)', |
|
|
'rgba(20, 184, 166, 1)' |
|
|
], |
|
|
borderWidth: 1 |
|
|
}] |
|
|
}, |
|
|
options: { |
|
|
responsive: true, |
|
|
maintainAspectRatio: false, |
|
|
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}: ${percentage}% ($${(value).toFixed(2)}M)`; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('productChartContainer').addEventListener('click', function(e) { |
|
|
if (e.target.tagName === 'CANVAS') { |
|
|
const activePoints = productCategoryChart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, true); |
|
|
if (activePoints.length > 0) { |
|
|
const productIndex = activePoints[0].index; |
|
|
const productName = productCategoryChart.data.labels[productIndex]; |
|
|
|
|
|
|
|
|
document.getElementById('productFilter').value = productName; |
|
|
filterData(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function updateCountryOriginChart() { |
|
|
const ctx = document.getElementById('countryOriginChart').getContext('2d'); |
|
|
|
|
|
|
|
|
const countryData = {}; |
|
|
const countryRelative = {}; |
|
|
let totalTariff = 0; |
|
|
|
|
|
filteredData.forEach(item => { |
|
|
if (!countryData[item.origin]) { |
|
|
countryData[item.origin] = 0; |
|
|
countryRelative[item.origin] = 0; |
|
|
} |
|
|
|
|
|
countryData[item.origin] += parseFloat(item.tariffAmount); |
|
|
totalTariff += parseFloat(item.tariffAmount); |
|
|
}); |
|
|
|
|
|
|
|
|
for (const country in countryData) { |
|
|
countryRelative[country] = (countryData[country] / totalTariff * 100).toFixed(1); |
|
|
} |
|
|
|
|
|
const countries = Object.keys(countryData); |
|
|
const absoluteData = countries.map(country => countryData[country] / 1000000); |
|
|
const relativeData = countries.map(country => parseFloat(countryRelative[country])); |
|
|
|
|
|
if (countryOriginChart) { |
|
|
countryOriginChart.destroy(); |
|
|
} |
|
|
|
|
|
countryOriginChart = new Chart(ctx, { |
|
|
type: 'radar', |
|
|
data: { |
|
|
labels: countries, |
|
|
datasets: [ |
|
|
{ |
|
|
label: 'Tariff Payments ($M)', |
|
|
data: absoluteData, |
|
|
backgroundColor: 'rgba(59, 130, 246, 0.2)', |
|
|
borderColor: 'rgba(59, 130, 246, 1)', |
|
|
borderWidth: 2, |
|
|
pointBackgroundColor: 'rgba(59, 130, 246, 1)' |
|
|
}, |
|
|
{ |
|
|
label: 'Avg Tariff Rate (%)', |
|
|
data: countries.map(country => { |
|
|
const countryItems = filteredData.filter(item => item.origin === country); |
|
|
return countryItems.reduce((sum, item) => sum + parseFloat(item.tariffRate), 0) / countryItems.length; |
|
|
}), |
|
|
backgroundColor: 'rgba(245, 158, 11, 0.2)', |
|
|
borderColor: 'rgba(245, 158, 11, 1)', |
|
|
borderWidth: 2, |
|
|
pointBackgroundColor: 'rgba(245, 158, 11, 1)' |
|
|
} |
|
|
] |
|
|
}, |
|
|
options: { |
|
|
responsive: true, |
|
|
maintainAspectRatio: false, |
|
|
scales: { |
|
|
r: { |
|
|
angleLines: { |
|
|
display: true |
|
|
}, |
|
|
suggestedMin: 0 |
|
|
} |
|
|
}, |
|
|
plugins: { |
|
|
legend: { |
|
|
position: 'top' |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('countryChartContainer').addEventListener('click', function(e) { |
|
|
if (e.target.tagName === 'CANVAS') { |
|
|
const activePoints = countryOriginChart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, true); |
|
|
if (activePoints.length > 0) { |
|
|
const countryIndex = activePoints[0].index; |
|
|
const countryName = countryOriginChart.data.labels[countryIndex]; |
|
|
|
|
|
|
|
|
document.getElementById('countryFilter').value = countryName; |
|
|
filterData(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function updateTariffRateDistributionChart() { |
|
|
const ctx = document.getElementById('tariffRateDistributionChart').getContext('2d'); |
|
|
|
|
|
|
|
|
const tariffRates = filteredData.map(item => parseFloat(item.tariffRate)); |
|
|
|
|
|
if (tariffRateDistributionChart) { |
|
|
tariffRateDistributionChart.destroy(); |
|
|
} |
|
|
|
|
|
|
|
|
const minRate = Math.min(...tariffRates); |
|
|
const maxRate = Math.max(...tariffRates); |
|
|
const binSize = 2; |
|
|
const binCount = Math.ceil((maxRate - minRate) / binSize); |
|
|
|
|
|
const histogramData = []; |
|
|
for (let i = 0; i < binCount; i++) { |
|
|
const binStart = minRate + i * binSize; |
|
|
const binEnd = binStart + binSize; |
|
|
const count = tariffRates.filter(rate => rate >= binStart && rate < binEnd).length; |
|
|
|
|
|
histogramData.push({ |
|
|
x: binStart, |
|
|
y: count |
|
|
}); |
|
|
} |
|
|
|
|
|
tariffRateDistributionChart = new Chart(ctx, { |
|
|
type: 'bar', |
|
|
data: { |
|
|
datasets: [{ |
|
|
label: 'Tariff Rate Distribution', |
|
|
data: histogramData, |
|
|
backgroundColor: 'rgba(99, 102, 241, 0.7)', |
|
|
borderColor: 'rgba(99, 102, 241, 1)', |
|
|
borderWidth: 1 |
|
|
}] |
|
|
}, |
|
|
options: { |
|
|
responsive: true, |
|
|
maintainAspectRatio: false, |
|
|
scales: { |
|
|
x: { |
|
|
type: 'linear', |
|
|
offset: false, |
|
|
grid: { |
|
|
offset: false |
|
|
}, |
|
|
ticks: { |
|
|
stepSize: binSize |
|
|
}, |
|
|
title: { |
|
|
display: true, |
|
|
text: 'Tariff Rate (%)' |
|
|
} |
|
|
}, |
|
|
y: { |
|
|
title: { |
|
|
display: true, |
|
|
text: 'Frequency' |
|
|
} |
|
|
} |
|
|
}, |
|
|
plugins: { |
|
|
legend: { |
|
|
display: false |
|
|
}, |
|
|
tooltip: { |
|
|
callbacks: { |
|
|
title: function(context) { |
|
|
const range = context[0].raw; |
|
|
return `Tariff Rate: ${range.x}% to ${range.x + binSize}%`; |
|
|
}, |
|
|
label: function(context) { |
|
|
return `Frequency: ${context.raw.y}`; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('distributionChartContainer').addEventListener('click', function(e) { |
|
|
if (e.target.tagName === 'CANVAS') { |
|
|
const activePoints = tariffRateDistributionChart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, true); |
|
|
if (activePoints.length > 0) { |
|
|
const binIndex = activePoints[0].index; |
|
|
const binStart = tariffRateDistributionChart.data.datasets[0].data[binIndex].x; |
|
|
const binEnd = binStart + 2; |
|
|
|
|
|
|
|
|
const originalFilteredData = [...filteredData]; |
|
|
filteredData = originalFilteredData.filter(item => { |
|
|
const rate = parseFloat(item.tariffRate); |
|
|
return rate >= binStart && rate < binEnd; |
|
|
}); |
|
|
|
|
|
updateTable(); |
|
|
updateCharts(); |
|
|
updateMetrics(); |
|
|
updateInsights(); |
|
|
updatePagination(); |
|
|
|
|
|
|
|
|
alert(`Showing data for tariff rates between ${binStart}% and ${binEnd}%. Click "Apply Filters" to reset.`); |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function updateExemptionStatusChart() { |
|
|
const ctx = document.getElementById('exemptionStatusChart').getContext('2d'); |
|
|
|
|
|
|
|
|
const exemptionData = {}; |
|
|
const products = [...new Set(filteredData.map(item => item.product))]; |
|
|
|
|
|
products.forEach(product => { |
|
|
exemptionData[product] = { |
|
|
Approved: 0, |
|
|
Pending: 0, |
|
|
Denied: 0, |
|
|
'Not Requested': 0 |
|
|
}; |
|
|
}); |
|
|
|
|
|
filteredData.forEach(item => { |
|
|
exemptionData[item.product][item.exemptionStatus]++; |
|
|
}); |
|
|
|
|
|
if (exemptionStatusChart) { |
|
|
exemptionStatusChart.destroy(); |
|
|
} |
|
|
|
|
|
exemptionStatusChart = new Chart(ctx, { |
|
|
type: 'bar', |
|
|
data: { |
|
|
labels: products, |
|
|
datasets: [ |
|
|
{ |
|
|
label: 'Approved', |
|
|
data: products.map(product => exemptionData[product].Approved), |
|
|
backgroundColor: 'rgba(16, 185, 129, 0.7)' |
|
|
}, |
|
|
{ |
|
|
label: 'Pending', |
|
|
data: products.map(product => exemptionData[product].Pending), |
|
|
backgroundColor: 'rgba(245, 158, 11, 0.7)' |
|
|
}, |
|
|
{ |
|
|
label: 'Denied', |
|
|
data: products.map(product => exemptionData[product].Denied), |
|
|
backgroundColor: 'rgba(239, 68, 68, 0.7)' |
|
|
}, |
|
|
{ |
|
|
label: 'Not Requested', |
|
|
data: products.map(product => exemptionData[product]['Not Requested']), |
|
|
backgroundColor: 'rgba(156, 163, 175, 0.7)' |
|
|
} |
|
|
] |
|
|
}, |
|
|
options: { |
|
|
responsive: true, |
|
|
maintainAspectRatio: false, |
|
|
scales: { |
|
|
x: { |
|
|
stacked: true |
|
|
}, |
|
|
y: { |
|
|
stacked: true, |
|
|
title: { |
|
|
display: true, |
|
|
text: 'Number of Cases' |
|
|
} |
|
|
} |
|
|
}, |
|
|
plugins: { |
|
|
tooltip: { |
|
|
mode: 'index', |
|
|
intersect: false |
|
|
}, |
|
|
legend: { |
|
|
position: 'top' |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('exemptionChartContainer').addEventListener('click', function(e) { |
|
|
if (e.target.tagName === 'CANVAS') { |
|
|
const activePoints = exemptionStatusChart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, true); |
|
|
if (activePoints.length > 0) { |
|
|
const datasetIndex = activePoints[0].datasetIndex; |
|
|
const status = exemptionStatusChart.data.datasets[datasetIndex].label; |
|
|
|
|
|
|
|
|
const originalFilteredData = [...filteredData]; |
|
|
filteredData = originalFilteredData.filter(item => item.exemptionStatus === status); |
|
|
|
|
|
updateTable(); |
|
|
updateCharts(); |
|
|
updateMetrics(); |
|
|
updateInsights(); |
|
|
updatePagination(); |
|
|
|
|
|
|
|
|
alert(`Showing data for exemption status: ${status}. Click "Apply Filters" to reset.`); |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function initDashboard() { |
|
|
updateTable(); |
|
|
updateCharts(); |
|
|
updateMetrics(); |
|
|
updateInsights(); |
|
|
updatePagination(); |
|
|
|
|
|
|
|
|
document.getElementById('applyFilters').addEventListener('click', filterData); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.chart-toggle').forEach(button => { |
|
|
button.addEventListener('click', function() { |
|
|
const chartId = this.getAttribute('data-chart'); |
|
|
const type = this.getAttribute('data-type'); |
|
|
|
|
|
|
|
|
document.querySelectorAll(`[data-chart="${chartId}"]`).forEach(btn => { |
|
|
btn.classList.remove('active-filter'); |
|
|
}); |
|
|
this.classList.add('active-filter'); |
|
|
|
|
|
|
|
|
switch(chartId) { |
|
|
case 'monthlyTariffChart': |
|
|
updateMonthlyTariffChart(type); |
|
|
break; |
|
|
case 'companyImpactChart': |
|
|
updateCompanyImpactChart(type); |
|
|
break; |
|
|
case 'productCategoryChart': |
|
|
updateProductCategoryChart(type); |
|
|
break; |
|
|
case 'countryOriginChart': |
|
|
updateCountryOriginChart(type); |
|
|
break; |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener('click', function(e) { |
|
|
if (e.target.classList.contains('page-link') && !e.target.classList.contains('active-page')) { |
|
|
e.preventDefault(); |
|
|
currentPage = parseInt(e.target.getAttribute('data-page')); |
|
|
updateTable(); |
|
|
updatePagination(); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.page-link').forEach(link => { |
|
|
link.classList.remove('active-page', 'bg-blue-50', 'border-blue-500', 'text-blue-600'); |
|
|
}); |
|
|
e.target.classList.add('active-page', 'bg-blue-50', 'border-blue-500', 'text-blue-600'); |
|
|
} |
|
|
|
|
|
|
|
|
if (e.target.id === 'prevPage' || e.target.parentElement.id === 'prevPage') { |
|
|
if (currentPage > 1) { |
|
|
currentPage--; |
|
|
updateTable(); |
|
|
updatePagination(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (e.target.id === 'nextPage' || e.target.parentElement.id === 'nextPage') { |
|
|
const totalPages = Math.ceil(filteredData.length / itemsPerPage); |
|
|
if (currentPage < totalPages) { |
|
|
currentPage++; |
|
|
updateTable(); |
|
|
updatePagination(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', initDashboard); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=anonymous111110987654321/hellmann-301-tariff-dashboard" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |