- wrap root layout with QueryProvider for React Query - replace market UnderConstruction placeholder with MarketClient and real data fetching - implement search, pagination, and sort via search params using getActiveListings - add necessary client components and queries - update dependencies (e.g., prisma, pkg-types)
228 lines
7.0 KiB
TypeScript
228 lines
7.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
import { ShoppingCart, Clock, CheckCircle2, XCircle, MessageSquare } from "lucide-react";
|
|
import { IntentionDetailModal } from "./IntentionDetailModal";
|
|
|
|
interface PurchaseIntention {
|
|
intention_id: number;
|
|
buyer_id: number;
|
|
payment_method: string;
|
|
status: "pending" | "accepted" | "rejected" | "expired" | "completed";
|
|
buyer_message: string | null;
|
|
seller_response: string | null;
|
|
created_at: string;
|
|
buyer?: {
|
|
user_id: number;
|
|
username: string;
|
|
email: string;
|
|
};
|
|
}
|
|
|
|
interface MyAdsIntentionListProps {
|
|
listingId: number;
|
|
sellerId: number;
|
|
}
|
|
|
|
function formatTimeAgo(date: Date): string {
|
|
const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
|
|
if (seconds < 60) return "há alguns segundos";
|
|
const minutes = Math.floor(seconds / 60);
|
|
if (minutes < 60) return `há ${minutes} ${minutes === 1 ? "minuto" : "minutos"}`;
|
|
const hours = Math.floor(minutes / 60);
|
|
if (hours < 24) return `há ${hours} ${hours === 1 ? "hora" : "horas"}`;
|
|
const days = Math.floor(hours / 24);
|
|
return `há ${days} ${days === 1 ? "dia" : "dias"}`;
|
|
}
|
|
|
|
function getStatusBadge(status: string) {
|
|
switch (status) {
|
|
case "pending":
|
|
return (
|
|
<Badge variant="secondary" className="bg-yellow-500/20 text-yellow-700 dark:text-yellow-400">
|
|
<Clock className="h-3 w-3 mr-1" />
|
|
Pendente
|
|
</Badge>
|
|
);
|
|
case "accepted":
|
|
return (
|
|
<Badge variant="secondary" className="bg-green-500/20 text-green-700 dark:text-green-400">
|
|
<CheckCircle2 className="h-3 w-3 mr-1" />
|
|
Aceita
|
|
</Badge>
|
|
);
|
|
case "rejected":
|
|
return (
|
|
<Badge variant="secondary" className="bg-red-500/20 text-red-700 dark:text-red-400">
|
|
<XCircle className="h-3 w-3 mr-1" />
|
|
Rejeitada
|
|
</Badge>
|
|
);
|
|
default:
|
|
return <Badge variant="secondary">{status}</Badge>;
|
|
}
|
|
}
|
|
|
|
function getPaymentMethodLabel(method: string): string {
|
|
switch (method) {
|
|
case "pix":
|
|
return "PIX";
|
|
case "tibia_coins":
|
|
return "Tibia Coins";
|
|
case "gold_coins":
|
|
return "Gold Coins";
|
|
case "trade":
|
|
return "Troca";
|
|
default:
|
|
return method.toUpperCase();
|
|
}
|
|
}
|
|
|
|
export function MyAdsIntentionList({ listingId, sellerId }: MyAdsIntentionListProps) {
|
|
const [intentions, setIntentions] = useState<PurchaseIntention[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [selectedIntention, setSelectedIntention] = useState<PurchaseIntention | null>(null);
|
|
const [filter, setFilter] = useState<"all" | "pending" | "accepted" | "rejected">("all");
|
|
|
|
useEffect(() => {
|
|
loadIntentions();
|
|
}, [listingId, filter]);
|
|
|
|
const loadIntentions = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const params = new URLSearchParams();
|
|
if (filter !== "all") {
|
|
params.set("status", filter);
|
|
}
|
|
const res = await fetch(`/api/listings/${listingId}/intentions?${params.toString()}`);
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
setIntentions(data.intentions || []);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao carregar intenções:", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const pendingCount = intentions.filter((i) => i.status === "pending").length;
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="space-y-2">
|
|
{Array.from({ length: 3 }).map((_, i) => (
|
|
<Skeleton key={i} className="h-20 w-full" />
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (intentions.length === 0) {
|
|
return (
|
|
<div className="text-center py-8 text-sm text-muted-foreground">
|
|
Nenhuma intenção de compra encontrada
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Filtros */}
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<Button
|
|
variant={filter === "all" ? "default" : "outline"}
|
|
size="sm"
|
|
onClick={() => setFilter("all")}
|
|
>
|
|
Todas ({intentions.length})
|
|
</Button>
|
|
<Button
|
|
variant={filter === "pending" ? "default" : "outline"}
|
|
size="sm"
|
|
onClick={() => setFilter("pending")}
|
|
>
|
|
Pendentes ({pendingCount})
|
|
</Button>
|
|
<Button
|
|
variant={filter === "accepted" ? "default" : "outline"}
|
|
size="sm"
|
|
onClick={() => setFilter("accepted")}
|
|
>
|
|
Aceitas
|
|
</Button>
|
|
<Button
|
|
variant={filter === "rejected" ? "default" : "outline"}
|
|
size="sm"
|
|
onClick={() => setFilter("rejected")}
|
|
>
|
|
Rejeitadas
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Lista de intenções */}
|
|
<div className="space-y-2">
|
|
{intentions.map((intention) => (
|
|
<div
|
|
key={intention.intention_id}
|
|
className="border rounded-lg p-4 hover:bg-muted/50 transition-colors cursor-pointer"
|
|
onClick={() => setSelectedIntention(intention)}
|
|
>
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div className="flex items-start gap-3 flex-1">
|
|
<Avatar className="h-10 w-10">
|
|
<AvatarFallback>
|
|
{intention.buyer?.username.charAt(0).toUpperCase() || "?"}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<span className="font-medium text-sm">
|
|
{intention.buyer?.username || "Usuário"}
|
|
</span>
|
|
{getStatusBadge(intention.status)}
|
|
</div>
|
|
<div className="flex items-center gap-2 text-xs text-muted-foreground mb-2">
|
|
<ShoppingCart className="h-3 w-3" />
|
|
<span>{getPaymentMethodLabel(intention.payment_method)}</span>
|
|
<span>•</span>
|
|
<Clock className="h-3 w-3" />
|
|
<span>{formatTimeAgo(new Date(intention.created_at))}</span>
|
|
</div>
|
|
{intention.buyer_message && (
|
|
<p className="text-xs text-muted-foreground line-clamp-1">
|
|
<MessageSquare className="h-3 w-3 inline mr-1" />
|
|
{intention.buyer_message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Modal de detalhes */}
|
|
{selectedIntention && (
|
|
<IntentionDetailModal
|
|
open={!!selectedIntention}
|
|
onOpenChange={(open) => {
|
|
if (!open) setSelectedIntention(null);
|
|
}}
|
|
intention={selectedIntention}
|
|
listingId={listingId}
|
|
onUpdate={loadIntentions}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|