Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
557 changes: 396 additions & 161 deletions src/components/compare/SupplierComparisonModal.tsx

Large diffs are not rendered by default.

17 changes: 10 additions & 7 deletions src/components/products/PriceFreshnessBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ function FreshnessTooltipBody({ freshness, priceUpdatedAt }: FreshnessTooltipPro
// Padrão único pt-BR: "em DD/MM/AAAA". A hora local e a forma por extenso
// ficam como detalhamento auxiliar, sem repetir a data curta.
const shortDate = isValidDate ? formatPriceDateShort(dateValue) : null;
const longDate = isValidDate ? formatPriceDateLong(dateValue) : null;
const _longDate = isValidDate ? formatPriceDateLong(dateValue) : null;
const _exactDateTime = isValidDate ? formatExactDateTime(dateValue) : null;
const statusLabel = STATUS_LABELS[freshness.status];
const rule = buildClassificationRule(freshness.thresholdDays);
Expand All @@ -128,12 +128,15 @@ function FreshnessTooltipBody({ freshness, priceUpdatedAt }: FreshnessTooltipPro
<div className="flex flex-col gap-1.5">
<div className="flex items-center gap-1.5">
<span className="font-semibold">{statusLabel}</span>
{freshness.status !== 'unknown' && (() => {
const stripped = freshness.label.match(/\(([^)]+)\)/)?.[1] || freshness.label.replace(/^(Atualizado|Próximo do limite|Possivelmente defasado)\s+/i, '').trim();
return (
<span className="text-muted-foreground">({stripped})</span>
);
})()}
{freshness.status !== 'unknown' &&
(() => {
const stripped =
freshness.label.match(/\(([^)]+)\)/)?.[1] ||
freshness.label
.replace(/^(Atualizado|Próximo do limite|Possivelmente defasado)\s+/i, '')
.trim();
return <span className="text-muted-foreground">({stripped})</span>;
})()}
</div>
{shortDate && (
<div className="leading-snug">
Expand Down
63 changes: 42 additions & 21 deletions src/components/products/SmartRecommendationsMock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import { useRef } from 'react';
import { Sparkles, ChevronLeft, ChevronRight, Trophy, TrendingUp, Star } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { QuickAddToQuote } from './QuickAddToQuote';
Expand Down Expand Up @@ -91,18 +90,28 @@ const MOCK_RECS: MockRec[] = [
},
];

function MockMiniCard({ rec, isBestChoice, badgeLabel }: { rec: MockRec; isBestChoice?: boolean; badgeLabel?: string }) {
function MockMiniCard({
rec,
isBestChoice,
badgeLabel,
}: {
rec: MockRec;
isBestChoice?: boolean;
badgeLabel?: string;
}) {
const scorePct = Math.round(rec.score * 100);
const isHighMatch = scorePct >= 95;

return (
<Card
className={cn(
'group/card min-w-[240px] max-w-[240px] shrink-0 overflow-hidden rounded-xl border-[1.5px] border-border',
'animate-fade-in relative transition-all duration-300',
'relative animate-fade-in transition-all duration-300',
'cursor-pointer hover:-translate-y-1 hover:border-primary/50 hover:shadow-lg',
isBestChoice && 'border-amber-400/60 shadow-[0_0_20px_-5px_rgba(251,191,36,0.3)]',
isHighMatch && !isBestChoice && 'border-primary/40 shadow-[0_0_15px_-5px_rgba(59,130,246,0.2)]',
isHighMatch &&
!isBestChoice &&
'border-primary/40 shadow-[0_0_15px_-5px_rgba(59,130,246,0.2)]',
)}
>
<div className="relative aspect-video w-full overflow-hidden bg-muted/30">
Expand All @@ -113,11 +122,11 @@ function MockMiniCard({ rec, isBestChoice, badgeLabel }: { rec: MockRec; isBestC
loading="lazy"
/>
{(isHighMatch || isBestChoice) && (
<div
<div
className={cn(
"absolute -inset-px opacity-0 transition-opacity duration-300 group-hover/card:opacity-100",
isBestChoice ? "bg-amber-400/10" : "bg-primary/5"
)}
'absolute -inset-px opacity-0 transition-opacity duration-300 group-hover/card:opacity-100',
isBestChoice ? 'bg-amber-400/10' : 'bg-primary/5',
)}
/>
)}
</div>
Expand All @@ -131,25 +140,31 @@ function MockMiniCard({ rec, isBestChoice, badgeLabel }: { rec: MockRec; isBestC
</div>
) : badgeLabel ? (
<div className="flex items-center gap-1 rounded-full bg-primary/10 px-2 py-0.5 text-[9px] font-bold text-primary">
{badgeLabel === 'Mais Pedido' ? <TrendingUp className="h-2.5 w-2.5" /> : <Star className="h-2.5 w-2.5" />}
{badgeLabel === 'Mais Pedido' ? (
<TrendingUp className="h-2.5 w-2.5" />
) : (
<Star className="h-2.5 w-2.5" />
)}
{badgeLabel.toUpperCase()}
</div>
) : <div className="h-4" />}
) : (
<div className="h-4" />
)}

<span
className={cn(
"shrink-0 rounded-full border-[1.5px] px-1.5 py-0.5 font-display text-[10px] font-bold",
isBestChoice
? "border-amber-400/50 bg-amber-50 text-amber-600"
: "border-primary/30 bg-primary/5 text-primary"
'shrink-0 rounded-full border-[1.5px] px-1.5 py-0.5 font-display text-[10px] font-bold',
isBestChoice
? 'border-amber-400/50 bg-amber-50 text-amber-600'
: 'border-primary/30 bg-primary/5 text-primary',
)}
>
{scorePct}%
</span>
</div>

<div className="space-y-1">
<h4 className="line-clamp-2 min-h-[2.5rem] font-display text-sm font-semibold leading-tight group-hover/card:text-primary transition-colors">
<h4 className="line-clamp-2 min-h-[2.5rem] font-display text-sm font-semibold leading-tight transition-colors group-hover/card:text-primary">
{rec.name}
</h4>
<p className="line-clamp-2 font-display text-[11px] leading-relaxed text-muted-foreground">
Expand All @@ -176,7 +191,10 @@ export function SmartRecommendationsMock() {
scrollerRef.current?.scrollBy({ left: delta, behavior: 'smooth' });

return (
<section className="animate-fade-in space-y-3 font-display" aria-label="Recomendações inteligentes (preview)">
<section
className="animate-fade-in space-y-3 font-display"
aria-label="Recomendações inteligentes (preview)"
>
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10">
Expand All @@ -187,7 +205,8 @@ export function SmartRecommendationsMock() {
Recomendações inteligentes para este produto
</h3>
<p className="text-xs text-muted-foreground">
Sugestões geradas por IA com base em similaridade, margem e perfil do cliente · <span className="font-medium text-primary">preview com dados mockados</span>
Sugestões geradas por IA com base em similaridade, margem e perfil do cliente ·{' '}
<span className="font-medium text-primary">preview com dados mockados</span>
</p>
</div>
</div>
Expand Down Expand Up @@ -222,10 +241,12 @@ export function SmartRecommendationsMock() {
>
{MOCK_RECS.map((rec, i) => (
<div key={rec.id} className="snap-start" role="listitem">
<MockMiniCard
rec={rec}
isBestChoice={i === 0}
badgeLabel={rec.score > 0.9 ? 'Mais Pedido' : rec.score > 0.7 ? 'Tendência' : undefined}
<MockMiniCard
rec={rec}
isBestChoice={i === 0}
badgeLabel={
rec.score > 0.9 ? 'Mais Pedido' : rec.score > 0.7 ? 'Tendência' : undefined
}
/>
</div>
))}
Expand Down
Loading