'use client'
Browse filesimport { useState } from 'react'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Trophy, Zap, DollarSign, Shield, TrendingUp, Award } from 'lucide-react'
interface EquipmentRanking {
rank: number
brand: string
model: string
value: number
unit: string
series: string
price?: number
efficiency?: number
warranty?: number
}
interface EquipmentRankingTableProps {
panels: {
topPower: EquipmentRanking[]
bestPricePerWatt: EquipmentRanking[]
topEfficiency: EquipmentRanking[]
}
inverters: {
longestWarranty: EquipmentRanking[]
bestPricePerKw: EquipmentRanking[]
topEfficiency: EquipmentRanking[]
}
isLoading?: boolean
onInteraction?: (action: string, data?: any) => void
}
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(value)
}
const formatNumber = (value: number, decimals = 2) => {
return value.toFixed(decimals)
}
const getRankColor = (rank: number) => {
if (rank === 1) return 'bg-yellow-100 text-yellow-800 border-yellow-300'
if (rank === 2) return 'bg-gray-100 text-gray-800 border-gray-300'
if (rank === 3) return 'bg-orange-100 text-orange-800 border-orange-300'
return 'bg-blue-50 text-blue-800 border-blue-200'
}
const getRankIcon = (rank: number) => {
if (rank === 1) return <Trophy className="h-4 w-4 text-yellow-600" />
if (rank === 2) return <Award className="h-4 w-4 text-gray-600" />
if (rank === 3) return <Award className="h-4 w-4 text-orange-600" />
return <span className="text-sm font-bold text-blue-600">#{rank}</span>
}
function RankingTable({
title,
data,
icon,
valueLabel,
onInteraction
}: {
title: string
data: EquipmentRanking[]
icon: React.ReactNode
valueLabel: string
onInteraction?: (action: string, data?: any) => void
}) {
return (
<Card className="border-gradient">
<CardHeader>
<CardTitle className="flex items-center gap-2">
{icon}
{title}
</CardTitle>
<CardDescription>
Top 10 equipamentos ordenados por {valueLabel.toLowerCase()}
</CardDescription>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Posição</TableHead>
<TableHead>Marca</TableHead>
<TableHead>Modelo</TableHead>
<TableHead>Série</TableHead>
<TableHead>{valueLabel}</TableHead>
<TableHead>Preço Médio</TableHead>
<TableHead>Eficiência</TableHead>
<TableHead>Garantia</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item) => (
<TableRow
key={`${item.brand}-${item.model}`}
className="hover:bg-[var(--glass-light)] hover:backdrop-blur-sm transition-all duration-200 cursor-pointer"
onClick={() => onInteraction?.("equipment_click", { item })}
>
<TableCell>
<div className="flex items-center gap-2">
<Badge className={getRankColor(item.rank)}>
{getRankIcon(item.rank)}
</Badge>
</div>
</TableCell>
<TableCell className="font-medium">{item.brand}</TableCell>
<TableCell className="font-mono text-sm">{item.model}</TableCell>
<TableCell className="text-sm text-gray-600">{item.series}</TableCell>
<TableCell className="font-semibold text-blue-600">
{item.unit.includes('R$') ? formatCurrency(item.value) : `${formatNumber(item.value)} ${item.unit}`}
</TableCell>
<TableCell>
{item.price ? formatCurrency(item.price) : '-'}
</TableCell>
<TableCell>
{item.efficiency ? `${formatNumber(item.efficiency)}%` : '-'}
</TableCell>
<TableCell>
{item.warranty ? `${item.warranty} anos` : '-'}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
)
}
export function EquipmentRankingTable({
panels,
inverters,
isLoading,
onInteraction,
}: Readonly<EquipmentRankingTableProps>) {
const [activeTab, setActiveTab] = useState("panels")
if (isLoading) {
return (
<Card className="border-gradient">
<CardHeader>
<CardTitle>Carregando Rankings de Equipamentos...</CardTitle>
</CardHeader>
<CardContent>
<div className="animate-pulse space-y-4">
{[1, 2, 3, 4, 5].map((num) => (
<div
key={`loading-${num}`}
className="h-16 bg-gray-200 rounded"
></div>
))}
</div>
</CardContent>
</Card>
)
}
return (
<div className="space-y-6">
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="panels" className="flex items-center gap-2">
<Zap className="h-4 w-4" />
Painéis Solares
</TabsTrigger>
<TabsTrigger value="inverters" className="flex items-center gap-2">
<TrendingUp className="h-4 w-4" />
Inversores
</TabsTrigger>
</TabsList>
<TabsContent value="panels" className="space-y-6">
<RankingTable
title="Maior Potência - Painéis Solares"
data={panels.topPower}
icon={<Zap className="h-5 w-5 text-yellow-500" />}
valueLabel="Potência (Wp)"
onInteraction={onInteraction}
/>
<RankingTable
title="Melhor Preço por Watt - Painéis Solares"
data={panels.bestPricePerWatt}
icon={<DollarSign className="h-5 w-5 text-green-500" />}
valueLabel="Preço/W (R$/W)"
onInteraction={onInteraction}
/>
<RankingTable
title="Top Eficiência - Painéis Solares"
data={panels.topEfficiency}
icon={<TrendingUp className="h-5 w-5 text-blue-500" />}
valueLabel="Eficiência (%)"
onInteraction={onInteraction}
/>
</TabsContent>
<TabsContent value="inverters" className="space-y-6">
<RankingTable
title="Maior Garantia - Inversores"
data={inverters.longestWarranty}
icon={<Shield className="h-5 w-5 text-purple-500" />}
valueLabel="Garantia (anos)"
onInteraction={onInteraction}
/>
<RankingTable
title="Melhor Preço por kW - Inversores"
data={inverters.bestPricePerKw}
icon={<DollarSign className="h-5 w-5 text-green-500" />}
valueLabel="Preço/kW (R$/kW)"
onInteraction={onInteraction}
/>
<RankingTable
title="Top Eficiência - Inversores"
data={inverters.topEfficiency}
icon={<TrendingUp className="h-5 w-5 text-blue-500" />}
valueLabel="Eficiência (%)"
onInteraction={onInteraction}
/>
</TabsContent>
</Tabs>
</div>
)
}
- README.md +7 -4
- components/footer.js +97 -0
- components/header.js +76 -0
- index.html +59 -19
- script.js +172 -0
- style.css +24 -19
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title: Solar Equipment Showdown
|
| 3 |
-
emoji: 📉
|
| 4 |
colorFrom: pink
|
| 5 |
-
colorTo:
|
|
|
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Solar Equipment Showdown ⚡
|
|
|
|
| 3 |
colorFrom: pink
|
| 4 |
+
colorTo: red
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomFooter extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
footer {
|
| 7 |
+
background-color: #1e293b;
|
| 8 |
+
color: white;
|
| 9 |
+
padding: 2rem 0;
|
| 10 |
+
margin-top: 3rem;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
.container {
|
| 14 |
+
max-width: 1200px;
|
| 15 |
+
margin: 0 auto;
|
| 16 |
+
padding: 0 1rem;
|
| 17 |
+
display: grid;
|
| 18 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 19 |
+
gap: 2rem;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.footer-section h3 {
|
| 23 |
+
font-size: 1.125rem;
|
| 24 |
+
font-weight: 600;
|
| 25 |
+
margin-bottom: 1rem;
|
| 26 |
+
color: #e2e8f0;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.footer-section ul {
|
| 30 |
+
display: flex;
|
| 31 |
+
flex-direction: column;
|
| 32 |
+
gap: 0.5rem;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.footer-section a {
|
| 36 |
+
color: #94a3b8;
|
| 37 |
+
transition: color 0.2s;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.footer-section a:hover {
|
| 41 |
+
color: #e2e8f0;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.copyright {
|
| 45 |
+
text-align: center;
|
| 46 |
+
margin-top: 2rem;
|
| 47 |
+
padding-top: 2rem;
|
| 48 |
+
border-top: 1px solid #334155;
|
| 49 |
+
color: #94a3b8;
|
| 50 |
+
font-size: 0.875rem;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
@media (max-width: 640px) {
|
| 54 |
+
.container {
|
| 55 |
+
grid-template-columns: 1fr;
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
</style>
|
| 59 |
+
<footer>
|
| 60 |
+
<div class="container">
|
| 61 |
+
<div class="footer-section">
|
| 62 |
+
<h3>Solar Showdown</h3>
|
| 63 |
+
<p class="text-slate-400">Comparando os melhores equipamentos solares para você tomar a melhor decisão.</p>
|
| 64 |
+
</div>
|
| 65 |
+
<div class="footer-section">
|
| 66 |
+
<h3>Links</h3>
|
| 67 |
+
<ul>
|
| 68 |
+
<li><a href="#panels">Painéis Solares</a></li>
|
| 69 |
+
<li><a href="#inverters">Inversores</a></li>
|
| 70 |
+
<li><a href="#comparison">Comparador</a></li>
|
| 71 |
+
</ul>
|
| 72 |
+
</div>
|
| 73 |
+
<div class="footer-section">
|
| 74 |
+
<h3>Recursos</h3>
|
| 75 |
+
<ul>
|
| 76 |
+
<li><a href="#blog">Blog</a></li>
|
| 77 |
+
<li><a href="#faq">FAQ</a></li>
|
| 78 |
+
<li><a href="#contact">Contato</a></li>
|
| 79 |
+
</ul>
|
| 80 |
+
</div>
|
| 81 |
+
<div class="footer-section">
|
| 82 |
+
<h3>Legal</h3>
|
| 83 |
+
<ul>
|
| 84 |
+
<li><a href="#privacy">Privacidade</a></li>
|
| 85 |
+
<li><a href="#terms">Termos</a></li>
|
| 86 |
+
<li><a href="#cookies">Cookies</a></li>
|
| 87 |
+
</ul>
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
<div class="copyright">
|
| 91 |
+
© ${new Date().getFullYear()} Solar Showdown. Todos os direitos reservados.
|
| 92 |
+
</div>
|
| 93 |
+
</footer>
|
| 94 |
+
`;
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
customElements.define('custom-footer', CustomFooter);
|
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomHeader extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
header {
|
| 7 |
+
background: linear-gradient(135deg, #0ea5e9 0%, #8b5cf6 100%);
|
| 8 |
+
color: white;
|
| 9 |
+
padding: 1.5rem 0;
|
| 10 |
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
.container {
|
| 14 |
+
max-width: 1200px;
|
| 15 |
+
margin: 0 auto;
|
| 16 |
+
padding: 0 1rem;
|
| 17 |
+
display: flex;
|
| 18 |
+
justify-content: space-between;
|
| 19 |
+
align-items: center;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.logo {
|
| 23 |
+
font-size: 1.5rem;
|
| 24 |
+
font-weight: bold;
|
| 25 |
+
display: flex;
|
| 26 |
+
align-items: center;
|
| 27 |
+
gap: 0.5rem;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
nav ul {
|
| 31 |
+
display: flex;
|
| 32 |
+
gap: 1.5rem;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
nav a {
|
| 36 |
+
color: white;
|
| 37 |
+
font-weight: 500;
|
| 38 |
+
transition: opacity 0.2s;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
nav a:hover {
|
| 42 |
+
opacity: 0.8;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
@media (max-width: 768px) {
|
| 46 |
+
.container {
|
| 47 |
+
flex-direction: column;
|
| 48 |
+
gap: 1rem;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
nav ul {
|
| 52 |
+
flex-wrap: wrap;
|
| 53 |
+
justify-content: center;
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
</style>
|
| 57 |
+
<header>
|
| 58 |
+
<div class="container">
|
| 59 |
+
<a href="/" class="logo">
|
| 60 |
+
<i data-feather="zap"></i>
|
| 61 |
+
Solar Showdown
|
| 62 |
+
</a>
|
| 63 |
+
<nav>
|
| 64 |
+
<ul>
|
| 65 |
+
<li><a href="#panels">Painéis</a></li>
|
| 66 |
+
<li><a href="#inverters">Inversores</a></li>
|
| 67 |
+
<li><a href="#comparison">Comparar</a></li>
|
| 68 |
+
<li><a href="#about">Sobre</a></li>
|
| 69 |
+
</ul>
|
| 70 |
+
</nav>
|
| 71 |
+
</div>
|
| 72 |
+
</header>
|
| 73 |
+
`;
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
customElements.define('custom-header', CustomHeader);
|
|
@@ -1,19 +1,59 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Solar Equipment Showdown</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 10 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 11 |
+
<script>
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
theme: {
|
| 14 |
+
extend: {
|
| 15 |
+
colors: {
|
| 16 |
+
primary: {
|
| 17 |
+
50: '#f0f9ff',
|
| 18 |
+
100: '#e0f2fe',
|
| 19 |
+
200: '#bae6fd',
|
| 20 |
+
300: '#7dd3fc',
|
| 21 |
+
400: '#38bdf8',
|
| 22 |
+
500: '#0ea5e9',
|
| 23 |
+
600: '#0284c7',
|
| 24 |
+
700: '#0369a1',
|
| 25 |
+
800: '#075985',
|
| 26 |
+
900: '#0c4a6e',
|
| 27 |
+
},
|
| 28 |
+
secondary: {
|
| 29 |
+
50: '#f5f3ff',
|
| 30 |
+
100: '#ede9fe',
|
| 31 |
+
200: '#ddd6fe',
|
| 32 |
+
300: '#c4b5fd',
|
| 33 |
+
400: '#a78bfa',
|
| 34 |
+
500: '#8b5cf6',
|
| 35 |
+
600: '#7c3aed',
|
| 36 |
+
700: '#6d28d9',
|
| 37 |
+
800: '#5b21b6',
|
| 38 |
+
900: '#4c1d95',
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
</script>
|
| 45 |
+
</head>
|
| 46 |
+
<body class="bg-gray-50">
|
| 47 |
+
<custom-header></custom-header>
|
| 48 |
+
<main class="container mx-auto px-4 py-8">
|
| 49 |
+
<div id="app" class="space-y-8"></div>
|
| 50 |
+
</main>
|
| 51 |
+
<custom-footer></custom-footer>
|
| 52 |
+
|
| 53 |
+
<script src="components/header.js"></script>
|
| 54 |
+
<script src="components/footer.js"></script>
|
| 55 |
+
<script src="script.js"></script>
|
| 56 |
+
<script>feather.replace();</script>
|
| 57 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 58 |
+
</body>
|
| 59 |
+
</html>
|
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 2 |
+
// Mock data for demonstration
|
| 3 |
+
const mockData = {
|
| 4 |
+
panels: {
|
| 5 |
+
topPower: [
|
| 6 |
+
{ rank: 1, brand: "SunPower", model: "X22-370", value: 370, unit: "Wp", series: "X-Series", price: 899, efficiency: 22.8, warranty: 25 },
|
| 7 |
+
{ rank: 2, brand: "LG", model: "LG370Q1C-A5", value: 370, unit: "Wp", series: "NeON R", price: 799, efficiency: 21.7, warranty: 25 },
|
| 8 |
+
{ rank: 3, brand: "Panasonic", model: "VBHN330SA16", value: 330, unit: "Wp", series: "HIT", price: 699, efficiency: 19.7, warranty: 25 },
|
| 9 |
+
],
|
| 10 |
+
bestPricePerWatt: [
|
| 11 |
+
{ rank: 1, brand: "Canadian Solar", model: "CS3K-395MS", value: 2.15, unit: "R$/W", series: "HiKu", price: 850, efficiency: 19.9, warranty: 12 },
|
| 12 |
+
{ rank: 2, brand: "Trina Solar", model: "TSM-395DE15", value: 2.20, unit: "R$/W", series: "Vertex S", price: 869, efficiency: 20.3, warranty: 15 },
|
| 13 |
+
{ rank: 3, brand: "Jinko Solar", model: "JKM395M-72HL4", value: 2.25, unit: "R$/W", series: "Tiger Pro", price: 889, efficiency: 20.4, warranty: 15 },
|
| 14 |
+
],
|
| 15 |
+
topEfficiency: [
|
| 16 |
+
{ rank: 1, brand: "SunPower", model: "Maxeon 3", value: 22.6, unit: "%", series: "Maxeon", price: 999, efficiency: 22.6, warranty: 25 },
|
| 17 |
+
{ rank: 2, brand: "LG", model: "LG380Q1C-A5", value: 21.7, unit: "%", series: "NeON R", price: 849, efficiency: 21.7, warranty: 25 },
|
| 18 |
+
{ rank: 3, brand: "REC", model: "REC370AA", value: 21.3, unit: "%", series: "Alpha Pure", price: 799, efficiency: 21.3, warranty: 25 },
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
inverters: {
|
| 22 |
+
longestWarranty: [
|
| 23 |
+
{ rank: 1, brand: "Fronius", model: "Symo 10.0-3-M", value: 10, unit: "anos", series: "Symo", price: 12500, efficiency: 98.1 },
|
| 24 |
+
{ rank: 2, brand: "SMA", model: "Sunny Tripower 10.0", value: 10, unit: "anos", series: "Tripower", price: 11900, efficiency: 98.3 },
|
| 25 |
+
{ rank: 3, brand: "Huawei", model: "SUN2000-10KTL-M1", value: 10, unit: "anos", series: "SUN2000", price: 10900, efficiency: 98.4 },
|
| 26 |
+
],
|
| 27 |
+
bestPricePerKw: [
|
| 28 |
+
{ rank: 1, brand: "Growatt", model: "MIN 10000TL-X", value: 850, unit: "R$/kW", series: "MIN", price: 8500, efficiency: 97.8 },
|
| 29 |
+
{ rank: 2, brand: "Solis", model: "S6-GR1P10K", value: 900, unit: "R$/kW", series: "S6", price: 9000, efficiency: 98.0 },
|
| 30 |
+
{ rank: 3, brand: "GoodWe", model: "GW10KN-DT", value: 950, unit: "R$/kW", series: "DNS", price: 9500, efficiency: 98.2 },
|
| 31 |
+
],
|
| 32 |
+
topEfficiency: [
|
| 33 |
+
{ rank: 1, brand: "Huawei", model: "SUN2000-10KTL-M1", value: 98.4, unit: "%", series: "SUN2000", price: 10900, warranty: 10 },
|
| 34 |
+
{ rank: 2, brand: "SMA", model: "Sunny Tripower 10.0", value: 98.3, unit: "%", series: "Tripower", price: 11900, warranty: 10 },
|
| 35 |
+
{ rank: 3, brand: "Fronius", model: "Symo 10.0-3-M", value: 98.1, unit: "%", series: "Symo", price: 12500, warranty: 10 },
|
| 36 |
+
]
|
| 37 |
+
}
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
// Render the equipment ranking table
|
| 41 |
+
const app = document.getElementById('app');
|
| 42 |
+
app.innerHTML = `
|
| 43 |
+
<div class="space-y-6">
|
| 44 |
+
<div class="tabs">
|
| 45 |
+
<div class="tabs-list grid w-full grid-cols-2 gap-2 mb-6">
|
| 46 |
+
<button class="tab-trigger active flex items-center gap-2 py-3 px-4 rounded-lg bg-primary-500 text-white" data-tab="panels">
|
| 47 |
+
<i data-feather="zap"></i>
|
| 48 |
+
Painéis Solares
|
| 49 |
+
</button>
|
| 50 |
+
<button class="tab-trigger flex items-center gap-2 py-3 px-4 rounded-lg bg-gray-200 text-gray-700" data-tab="inverters">
|
| 51 |
+
<i data-feather="trending-up"></i>
|
| 52 |
+
Inversores
|
| 53 |
+
</button>
|
| 54 |
+
</div>
|
| 55 |
+
|
| 56 |
+
<div class="tab-content active" data-tab="panels">
|
| 57 |
+
${renderRankingTable("Maior Potência - Painéis Solares", mockData.panels.topPower, "zap", "Potência (Wp)")}
|
| 58 |
+
${renderRankingTable("Melhor Preço por Watt - Painéis Solares", mockData.panels.bestPricePerWatt, "dollar-sign", "Preço/W (R$/W)")}
|
| 59 |
+
${renderRankingTable("Top Eficiência - Painéis Solares", mockData.panels.topEfficiency, "trending-up", "Eficiência (%)")}
|
| 60 |
+
</div>
|
| 61 |
+
|
| 62 |
+
<div class="tab-content hidden" data-tab="inverters">
|
| 63 |
+
${renderRankingTable("Maior Garantia - Inversores", mockData.inverters.longestWarranty, "shield", "Garantia (anos)")}
|
| 64 |
+
${renderRankingTable("Melhor Preço por kW - Inversores", mockData.inverters.bestPricePerKw, "dollar-sign", "Preço/kW (R$/kW)")}
|
| 65 |
+
${renderRankingTable("Top Eficiência - Inversores", mockData.inverters.topEfficiency, "trending-up", "Eficiência (%)")}
|
| 66 |
+
</div>
|
| 67 |
+
</div>
|
| 68 |
+
</div>
|
| 69 |
+
`;
|
| 70 |
+
|
| 71 |
+
// Tab functionality
|
| 72 |
+
document.querySelectorAll('.tab-trigger').forEach(trigger => {
|
| 73 |
+
trigger.addEventListener('click', () => {
|
| 74 |
+
const tabId = trigger.dataset.tab;
|
| 75 |
+
|
| 76 |
+
// Update active tab button
|
| 77 |
+
document.querySelectorAll('.tab-trigger').forEach(t => {
|
| 78 |
+
t.classList.remove('active', 'bg-primary-500', 'text-white');
|
| 79 |
+
t.classList.add('bg-gray-200', 'text-gray-700');
|
| 80 |
+
});
|
| 81 |
+
trigger.classList.add('active', 'bg-primary-500', 'text-white');
|
| 82 |
+
trigger.classList.remove('bg-gray-200', 'text-gray-700');
|
| 83 |
+
|
| 84 |
+
// Show active tab content
|
| 85 |
+
document.querySelectorAll('.tab-content').forEach(content => {
|
| 86 |
+
content.classList.add('hidden');
|
| 87 |
+
content.classList.remove('active');
|
| 88 |
+
});
|
| 89 |
+
document.querySelector(`.tab-content[data-tab="${tabId}"]`).classList.remove('hidden');
|
| 90 |
+
document.querySelector(`.tab-content[data-tab="${tabId}"]`).classList.add('active');
|
| 91 |
+
|
| 92 |
+
feather.replace();
|
| 93 |
+
});
|
| 94 |
+
});
|
| 95 |
+
|
| 96 |
+
feather.replace();
|
| 97 |
+
});
|
| 98 |
+
|
| 99 |
+
function renderRankingTable(title, data, icon, valueLabel) {
|
| 100 |
+
return `
|
| 101 |
+
<div class="border-gradient hover-scale mb-6">
|
| 102 |
+
<div class="card-header p-6">
|
| 103 |
+
<h3 class="text-xl font-bold flex items-center gap-2">
|
| 104 |
+
<i data-feather="${icon}" class="text-primary-500"></i>
|
| 105 |
+
${title}
|
| 106 |
+
</h3>
|
| 107 |
+
<p class="text-gray-600 mt-1">Top 10 equipamentos ordenados por ${valueLabel.toLowerCase()}</p>
|
| 108 |
+
</div>
|
| 109 |
+
<div class="card-content px-6 pb-6 overflow-x-auto">
|
| 110 |
+
<table class="w-full">
|
| 111 |
+
<thead>
|
| 112 |
+
<tr class="text-left border-b">
|
| 113 |
+
<th class="pb-3">Posição</th>
|
| 114 |
+
<th class="pb-3">Marca</th>
|
| 115 |
+
<th class="pb-3">Modelo</th>
|
| 116 |
+
<th class="pb-3">Série</th>
|
| 117 |
+
<th class="pb-3">${valueLabel}</th>
|
| 118 |
+
<th class="pb-3">Preço Médio</th>
|
| 119 |
+
<th class="pb-3">Eficiência</th>
|
| 120 |
+
<th class="pb-3">Garantia</th>
|
| 121 |
+
</tr>
|
| 122 |
+
</thead>
|
| 123 |
+
<tbody>
|
| 124 |
+
${data.map(item => `
|
| 125 |
+
<tr class="border-b hover:bg-primary-50 transition-colors cursor-pointer" data-id="${item.brand}-${item.model}">
|
| 126 |
+
<td class="py-4">
|
| 127 |
+
<span class="rank-badge ${getRankColor(item.rank)}">
|
| 128 |
+
${getRankIcon(item.rank)}
|
| 129 |
+
</span>
|
| 130 |
+
</td>
|
| 131 |
+
<td class="font-medium">${item.brand}</td>
|
| 132 |
+
<td class="font-mono text-sm">${item.model}</td>
|
| 133 |
+
<td class="text-sm text-gray-600">${item.series}</td>
|
| 134 |
+
<td class="font-semibold text-primary-600">
|
| 135 |
+
${item.unit.includes('R$') ? formatCurrency(item.value) : `${formatNumber(item.value)} ${item.unit}`}
|
| 136 |
+
</td>
|
| 137 |
+
<td>${item.price ? formatCurrency(item.price) : '-'}</td>
|
| 138 |
+
<td>${item.efficiency ? `${formatNumber(item.efficiency)}%` : '-'}</td>
|
| 139 |
+
<td>${item.warranty ? `${item.warranty} anos` : '-'}</td>
|
| 140 |
+
</tr>
|
| 141 |
+
`).join('')}
|
| 142 |
+
</tbody>
|
| 143 |
+
</table>
|
| 144 |
+
</div>
|
| 145 |
+
</div>
|
| 146 |
+
`;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
function formatCurrency(value) {
|
| 150 |
+
return new Intl.NumberFormat('pt-BR', {
|
| 151 |
+
style: 'currency',
|
| 152 |
+
currency: 'BRL'
|
| 153 |
+
}).format(value);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
function formatNumber(value, decimals = 2) {
|
| 157 |
+
return value.toFixed(decimals);
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
function getRankColor(rank) {
|
| 161 |
+
if (rank === 1) return 'bg-yellow-100 text-yellow-800 border-yellow-300';
|
| 162 |
+
if (rank === 2) return 'bg-gray-100 text-gray-800 border-gray-300';
|
| 163 |
+
if (rank === 3) return 'bg-orange-100 text-orange-800 border-orange-300';
|
| 164 |
+
return 'bg-blue-50 text-blue-800 border-blue-200';
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
function getRankIcon(rank) {
|
| 168 |
+
if (rank === 1) return '<i data-feather="award" class="text-yellow-600"></i>';
|
| 169 |
+
if (rank === 2) return '<i data-feather="award" class="text-gray-600"></i>';
|
| 170 |
+
if (rank === 3) return '<i data-feather="award" class="text-orange-600"></i>';
|
| 171 |
+
return `<span class="text-sm font-bold text-blue-600">#${rank}</span>`;
|
| 172 |
+
}
|
|
@@ -1,28 +1,33 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
| 4 |
}
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
font-size: 15px;
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
}
|
| 17 |
|
| 18 |
-
.
|
| 19 |
-
|
| 20 |
-
margin: 0 auto;
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
}
|
| 25 |
|
| 26 |
-
.
|
| 27 |
-
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.border-gradient {
|
| 2 |
+
border: 1px solid transparent;
|
| 3 |
+
background: linear-gradient(white, white) padding-box,
|
| 4 |
+
linear-gradient(to right, #0ea5e9, #8b5cf6) border-box;
|
| 5 |
+
border-radius: 0.5rem;
|
| 6 |
}
|
| 7 |
|
| 8 |
+
.glass-effect {
|
| 9 |
+
background: rgba(255, 255, 255, 0.7);
|
| 10 |
+
backdrop-filter: blur(10px);
|
| 11 |
+
-webkit-backdrop-filter: blur(10px);
|
| 12 |
}
|
| 13 |
|
| 14 |
+
.hover-scale {
|
| 15 |
+
transition: transform 0.2s ease-in-out;
|
|
|
|
|
|
|
|
|
|
| 16 |
}
|
| 17 |
|
| 18 |
+
.hover-scale:hover {
|
| 19 |
+
transform: scale(1.02);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
+
.animate-pulse {
|
| 23 |
+
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
| 24 |
}
|
| 25 |
+
|
| 26 |
+
@keyframes pulse {
|
| 27 |
+
0%, 100% {
|
| 28 |
+
opacity: 1;
|
| 29 |
+
}
|
| 30 |
+
50% {
|
| 31 |
+
opacity: 0.5;
|
| 32 |
+
}
|
| 33 |
+
}
|