import { json } from "@remix-run/node"; import { useLoaderData } from "@remix-run/react"; import { useState } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Badge } from "~/components/ui/badge"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"; import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar"; import { Separator } from "~/components/ui/separator"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "~/components/ui/dialog"; import { Search, MapPin, Phone, MessageSquare, Star, Truck, Package, Clock, Filter, Grid, List, Eye, Users, Calendar, Weight, Recycle, Leaf, Zap, ChevronRight, ImageOff } from "lucide-react"; // Component untuk Image dengan fallback const WasteImage = ({ src, alt, className = "", fallbackType = "waste" }: { src: string; alt: string; className?: string; fallbackType?: "waste" | "detail"; }) => { const [imageError, setImageError] = useState(false); const [imageLoading, setImageLoading] = useState(true); const handleImageError = () => { setImageError(true); setImageLoading(false); }; const handleImageLoad = () => { setImageLoading(false); }; // Fallback component const ImageFallback = () => (

No Image

); // Loading skeleton const ImageSkeleton = () => (
); if (imageError) { return ; } return (
{imageLoading && } {alt}
); }; // Interfaces interface WasteItem { id: string; title: string; category: | "organic" | "plastic" | "paper" | "metal" | "glass" | "electronic" | "mixed"; description: string; quantity: number; // in kg unit: "kg" | "ton" | "pieces"; pricePerUnit: number; // per kg condition: "fresh" | "sorted" | "processed" | "mixed"; availableUntil: string; images: string[]; supplier: { id: string; name: string; type: "individual" | "company" | "cooperative"; location: string; district: string; rating: number; reviewCount: number; avatar?: string; phone: string; verified: boolean; responseTime: string; // "< 1 jam", "2-4 jam", etc }; pickupAvailable: boolean; minimumOrder: number; // kg tags: string[]; postedAt: string; viewCount: number; } interface ExploreData { items: WasteItem[]; categories: Array<{ id: string; name: string; count: number; icon: string; }>; locations: string[]; stats: { totalSuppliers: number; totalVolume: number; // total kg available averagePrice: number; activeListings: number; }; } export const loader = async (): Promise => { // Mock data - dalam implementasi nyata, ambil dari database const exploreData: ExploreData = { stats: { totalSuppliers: 45, totalVolume: 15420, // kg averagePrice: 850, // per kg activeListings: 128 }, categories: [ { id: "plastic", name: "Plastik", count: 32, icon: "♻️" }, { id: "paper", name: "Kertas", count: 28, icon: "📄" }, { id: "organic", name: "Organik", count: 24, icon: "🌱" }, { id: "metal", name: "Logam", count: 18, icon: "🔩" }, { id: "glass", name: "Kaca", count: 12, icon: "🍶" }, { id: "electronic", name: "Elektronik", count: 8, icon: "📱" }, { id: "mixed", name: "Campuran", count: 6, icon: "📦" } ], locations: [ "Jakarta Utara", "Jakarta Selatan", "Jakarta Timur", "Jakarta Barat", "Jakarta Pusat", "Depok", "Tangerang", "Bekasi" ], items: [ { id: "item-001", title: "Plastik Botol PET Bersih", category: "plastic", description: "Botol plastik PET bekas air mineral yang sudah dicuci bersih dan dipisahkan berdasarkan warna. Kondisi sangat baik untuk daur ulang.", quantity: 250, unit: "kg", pricePerUnit: 2800, condition: "sorted", availableUntil: "2025-07-15", images: ["https://picsum.photos/300/200?random=1"], supplier: { id: "sup-001", name: "Koperasi Sukamaju", type: "cooperative", location: "Kelurahan Merdeka", district: "Jakarta Utara", rating: 4.8, reviewCount: 23, phone: "081234567890", verified: true, responseTime: "< 1 jam" }, pickupAvailable: true, minimumOrder: 50, tags: ["Bersih", "Tersortir", "Siap Proses"], postedAt: "2025-07-05", viewCount: 45 }, { id: "item-002", title: "Kertas Kardus Bekas Kemasan", category: "paper", description: "Kardus bekas kemasan elektronik dan makanan. Kondisi kering dan tidak basah. Sudah diratakan dan dibundle rapi.", quantity: 180, unit: "kg", pricePerUnit: 1200, condition: "sorted", availableUntil: "2025-07-12", images: ["https://picsum.photos/300/200?random=2"], supplier: { id: "sup-002", name: "Ahmad Wijaya", type: "individual", location: "Komplek Permata", district: "Jakarta Selatan", rating: 4.5, reviewCount: 12, phone: "081234567891", verified: true, responseTime: "2-4 jam" }, pickupAvailable: true, minimumOrder: 30, tags: ["Kering", "Bundle", "Kemasan"], postedAt: "2025-07-04", viewCount: 28 }, { id: "item-003", title: "Sampah Organik Pasar Segar", category: "organic", description: "Limbah organik dari pasar tradisional meliputi sisa sayuran, buah-buahan, dan daun. Cocok untuk kompos atau biogas.", quantity: 500, unit: "kg", pricePerUnit: 400, condition: "fresh", availableUntil: "2025-07-07", images: ["https://picsum.photos/300/200?random=3"], supplier: { id: "sup-003", name: "Pasar Tradisional Sentral", type: "company", location: "Pasar Sentral", district: "Jakarta Tengah", rating: 4.9, reviewCount: 67, phone: "081234567892", verified: true, responseTime: "< 30 menit" }, pickupAvailable: true, minimumOrder: 100, tags: ["Segar", "Organik", "Kompos"], postedAt: "2025-07-06", viewCount: 89 }, { id: "item-004", title: "Kaleng Aluminium Bekas Minuman", category: "metal", description: "Kaleng aluminium bekas minuman ringan dan bir. Sudah dibersihkan dan dipress untuk efisiensi transport.", quantity: 85, unit: "kg", pricePerUnit: 8500, condition: "processed", availableUntil: "2025-07-20", images: ["https://picsum.photos/300/200?random=4"], supplier: { id: "sup-004", name: "Sari Recycling", type: "company", location: "Industrial Park", district: "Jakarta Timur", rating: 4.7, reviewCount: 34, phone: "081234567893", verified: true, responseTime: "1-2 jam" }, pickupAvailable: false, minimumOrder: 20, tags: ["Aluminium", "Dipress", "Bersih"], postedAt: "2025-07-03", viewCount: 52 }, { id: "item-005", title: "Botol Kaca Bekas Wine & Bir", category: "glass", description: "Botol kaca bekas wine, bir, dan minuman lainnya. Berbagai ukuran dan warna. Kondisi utuh tanpa keretakan.", quantity: 120, unit: "kg", pricePerUnit: 650, condition: "sorted", availableUntil: "2025-07-18", images: ["https://picsum.photos/300/200?random=5"], supplier: { id: "sup-005", name: "Dedi Kurniawan", type: "individual", location: "Cluster Villa", district: "Jakarta Barat", rating: 4.3, reviewCount: 8, phone: "081234567894", verified: false, responseTime: "4-6 jam" }, pickupAvailable: true, minimumOrder: 25, tags: ["Utuh", "Beragam", "Bersih"], postedAt: "2025-07-02", viewCount: 19 }, { id: "item-006", title: "Komponen Elektronik Bekas", category: "electronic", description: "Komponen elektronik bekas dari perangkat komputer, TV, dan handphone. Mengandung logam mulia yang bisa diekstrak.", quantity: 45, unit: "kg", pricePerUnit: 12000, condition: "mixed", availableUntil: "2025-07-25", images: ["https://picsum.photos/300/200?random=6"], supplier: { id: "sup-006", name: "TechWaste Solutions", type: "company", location: "Industrial Zone", district: "Jakarta Timur", rating: 4.6, reviewCount: 15, phone: "081234567895", verified: true, responseTime: "< 2 jam" }, pickupAvailable: false, minimumOrder: 10, tags: ["E-waste", "Logam Mulia", "Komponen"], postedAt: "2025-07-01", viewCount: 73 } ] }; return json(exploreData); }; export default function ExploreWaste() { const data = useLoaderData(); const [searchQuery, setSearchQuery] = useState(""); const [selectedCategory, setSelectedCategory] = useState("all"); const [selectedLocation, setSelectedLocation] = useState("all"); const [priceRange, setPriceRange] = useState("all"); const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); const [selectedItem, setSelectedItem] = useState(null); // Filter items const filteredItems = data.items.filter((item) => { const matchesSearch = item.title.toLowerCase().includes(searchQuery.toLowerCase()) || item.description.toLowerCase().includes(searchQuery.toLowerCase()) || item.tags.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase()) ); const matchesCategory = selectedCategory === "all" || item.category === selectedCategory; const matchesLocation = selectedLocation === "all" || item.supplier.district === selectedLocation; let matchesPrice = true; if (priceRange === "low") matchesPrice = item.pricePerUnit < 1000; else if (priceRange === "medium") matchesPrice = item.pricePerUnit >= 1000 && item.pricePerUnit < 5000; else if (priceRange === "high") matchesPrice = item.pricePerUnit >= 5000; return matchesSearch && matchesCategory && matchesLocation && matchesPrice; }); const getCategoryIcon = (category: string) => { switch (category) { case "plastic": return ; case "organic": return ; case "electronic": return ; default: return ; } }; const getConditionBadge = (condition: string) => { const variants = { fresh: "default", sorted: "secondary", processed: "outline", mixed: "destructive" } as const; const labels = { fresh: "Segar", sorted: "Tersortir", processed: "Diproses", mixed: "Campuran" }; return ( {labels[condition as keyof typeof labels] || condition} ); }; const getSupplierTypeBadge = (type: string) => { const labels = { individual: "Individu", company: "Perusahaan", cooperative: "Koperasi" }; return ( {labels[type as keyof typeof labels] || type} ); }; return (
{/* Header */}

Explore Waste Marketplace

Temukan dan hubungi supplier sampah untuk kebutuhan operasional Anda

{/* Stats Cards */}

Total Supplier

{data.stats.totalSuppliers}

Volume Tersedia

{data.stats.totalVolume.toLocaleString()} kg

Listing Aktif

{data.stats.activeListings}

💰

Harga Rata-rata

Rp {data.stats.averagePrice}/kg

{/* Filters */}
{/* Search */}
setSearchQuery(e.target.value)} className="pl-10" />
{/* Category Filter */}
{/* Location Filter */}
{/* Price Filter */}
{filteredItems.length} item ditemukan {(searchQuery || selectedCategory !== "all" || selectedLocation !== "all" || priceRange !== "all") && ( )}
{/* Items Grid/List */}
{filteredItems.map((item) => (
{item.supplier.verified && ( Verified )} {item.pickupAvailable && ( Pickup Available )}
{getCategoryIcon(item.category)}
{/* Title & Condition */}

{item.title}

{getConditionBadge(item.condition)}
{/* Price & Quantity */}

Rp {item.pricePerUnit.toLocaleString()}

per {item.unit}

{item.quantity.toLocaleString()} {item.unit}

tersedia

{/* Supplier Info */}
{item.supplier.name .split(" ") .map((n) => n[0]) .join("")}

{item.supplier.name}

{getSupplierTypeBadge(item.supplier.type)}
{item.supplier.district}
{item.supplier.rating} ({item.supplier.reviewCount})
{/* Tags */}
{item.tags.slice(0, 3).map((tag) => ( {tag} ))} {item.tags.length > 3 && ( +{item.tags.length - 3} )}
{/* Meta Info */}
Available until{" "} {new Date(item.availableUntil).toLocaleDateString( "id-ID" )}
{item.viewCount} views
{/* Actions */}
{item.title} Detail lengkap stok sampah dari {item.supplier.name}

Informasi Produk

Kategori: {item.category}

Kondisi: {item.condition}

Quantity: {item.quantity}{" "} {item.unit}

Minimum Order:{" "} {item.minimumOrder} {item.unit}

Harga: Rp{" "} {item.pricePerUnit.toLocaleString()} per{" "} {item.unit}

Informasi Supplier

Nama: {item.supplier.name}

Lokasi:{" "} {item.supplier.location},{" "} {item.supplier.district}

Rating: {item.supplier.rating} /5 ({item.supplier.reviewCount} reviews)

Response Time:{" "} {item.supplier.responseTime}

Pickup:{" "} {item.pickupAvailable ? "Available" : "Self-pickup only"}

Deskripsi

{item.description}

))}
{/* Empty State */} {filteredItems.length === 0 && (

Tidak ada item ditemukan

Coba ubah filter pencarian atau kata kunci untuk menemukan stok sampah yang sesuai.

)}
); }