MIF_E31222379_WEB/app/routes/pengelola.dashboard.transac...

442 lines
17 KiB
TypeScript

// app/routes/dashboard.pencatatan.tsx
import { json, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
import { Form, useLoaderData, useNavigation, useFetcher } from "@remix-run/react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { Textarea } from "~/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select";
import { Badge } from "~/components/ui/badge";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
import { Separator } from "~/components/ui/separator";
import {
Plus,
Search,
Filter,
Download,
Eye,
Edit,
Trash2,
Receipt,
Users,
Scale,
Calendar,
MapPin
} from "lucide-react";
import { useState } from "react";
// Loader untuk mengambil data
export async function loader({ request }: LoaderFunctionArgs) {
// Simulasi data - ganti dengan query database Anda
const wasteRecords = [
{
id: "WR-001",
date: "2024-07-10",
collectorName: "Budi Santoso",
collectorPhone: "+62 812 3456 7890",
wasteType: "Plastik PET",
weight: 150.5,
pricePerKg: 3500,
totalPrice: 526750,
location: "Kelurahan Kebayoran",
status: "completed",
notes: "Kondisi baik, sudah dipilah"
},
{
id: "WR-002",
date: "2024-07-09",
collectorName: "Siti Rahayu",
collectorPhone: "+62 813 9876 5432",
wasteType: "Kardus",
weight: 200.0,
pricePerKg: 2800,
totalPrice: 560000,
location: "Kelurahan Menteng",
status: "pending",
notes: "Perlu pengecekan kualitas"
},
{
id: "WR-003",
date: "2024-07-08",
collectorName: "Ahmad Wijaya",
collectorPhone: "+62 814 1122 3344",
wasteType: "Kaleng Aluminium",
weight: 75.2,
pricePerKg: 8500,
totalPrice: 639200,
location: "Kelurahan Cempaka Putih",
status: "completed",
notes: "Kualitas premium"
}
];
const wasteTypes = [
"Plastik PET",
"Kardus",
"Kaleng Aluminium",
"Kertas",
"Plastik HDPE",
"Besi/Logam",
"Kaca"
];
const collectors = [
{ name: "Budi Santoso", phone: "+62 812 3456 7890" },
{ name: "Siti Rahayu", phone: "+62 813 9876 5432" },
{ name: "Ahmad Wijaya", phone: "+62 814 1122 3344" }
];
return json({ wasteRecords, wasteTypes, collectors });
}
// Action untuk handle form submission
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const intent = formData.get("intent");
if (intent === "createRecord") {
const recordData = {
collectorName: formData.get("collectorName"),
collectorPhone: formData.get("collectorPhone"),
wasteType: formData.get("wasteType"),
weight: parseFloat(formData.get("weight") as string),
pricePerKg: parseFloat(formData.get("pricePerKg") as string),
location: formData.get("location"),
notes: formData.get("notes")
};
// Simulasi create - ganti dengan insert database Anda
console.log("Creating waste record:", recordData);
return json({ success: true, message: "Pencatatan sampah berhasil disimpan" });
}
return json({ success: false, message: "Invalid action" });
}
export default function WasteRecordingPage() {
const { wasteRecords, wasteTypes, collectors } = useLoaderData<typeof loader>();
const navigation = useNavigation();
const [searchTerm, setSearchTerm] = useState("");
const [selectedType, setSelectedType] = useState("all");
const [selectedStatus, setSelectedStatus] = useState("all");
const isSubmitting = navigation.state === "submitting";
// Filter records
const filteredRecords = wasteRecords.filter(record => {
const matchesSearch = record.collectorName.toLowerCase().includes(searchTerm.toLowerCase()) ||
record.wasteType.toLowerCase().includes(searchTerm.toLowerCase()) ||
record.id.toLowerCase().includes(searchTerm.toLowerCase());
const matchesType = selectedType === "all" || record.wasteType === selectedType;
const matchesStatus = selectedStatus === "all" || record.status === selectedStatus;
return matchesSearch && matchesType && matchesStatus;
});
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(amount);
};
const getStatusBadge = (status: string) => {
switch (status) {
case "completed":
return <Badge variant="default" className="bg-green-600">Selesai</Badge>;
case "pending":
return <Badge variant="secondary">Pending</Badge>;
default:
return <Badge variant="outline">{status}</Badge>;
}
};
return (
<div className="p-6 space-y-6">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold tracking-tight">Pencatatan Sampah</h1>
<p className="text-muted-foreground mt-2">
Catat pembelian sampah dari pengepul dan kelola transaksi
</p>
</div>
</div>
<div className="grid grid-cols-1 xl:grid-cols-4 gap-6">
{/* Left Column - Form Input */}
<div className="xl:col-span-1 space-y-6">
<Card>
<CardHeader>
<div className="flex items-center gap-2">
<Plus className="h-5 w-5 text-primary" />
<CardTitle>Tambah Pencatatan</CardTitle>
</div>
<CardDescription>
Input data pembelian sampah baru
</CardDescription>
</CardHeader>
<CardContent>
<Form method="post" className="space-y-4">
<input type="hidden" name="intent" value="createRecord" />
<div className="space-y-2">
<Label htmlFor="collectorName">Nama Pengepul</Label>
<Select name="collectorName">
<SelectTrigger>
<SelectValue placeholder="Pilih pengepul" />
</SelectTrigger>
<SelectContent>
{collectors.map((collector) => (
<SelectItem key={collector.name} value={collector.name}>
{collector.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="collectorPhone">No. Telepon</Label>
<Input
id="collectorPhone"
name="collectorPhone"
placeholder="+62 812 xxxx xxxx"
/>
</div>
<div className="space-y-2">
<Label htmlFor="wasteType">Jenis Sampah</Label>
<Select name="wasteType">
<SelectTrigger>
<SelectValue placeholder="Pilih jenis sampah" />
</SelectTrigger>
<SelectContent>
{wasteTypes.map((type) => (
<SelectItem key={type} value={type}>
{type}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="weight">Berat (kg)</Label>
<Input
id="weight"
name="weight"
type="number"
step="0.1"
placeholder="0.0"
/>
</div>
<div className="space-y-2">
<Label htmlFor="pricePerKg">Harga per Kg (Rp)</Label>
<Input
id="pricePerKg"
name="pricePerKg"
type="number"
placeholder="3500"
/>
</div>
<div className="space-y-2">
<Label htmlFor="location">Lokasi</Label>
<Input
id="location"
name="location"
placeholder="Kelurahan/Daerah"
/>
</div>
<div className="space-y-2">
<Label htmlFor="notes">Catatan</Label>
<Textarea
id="notes"
name="notes"
placeholder="Catatan kondisi atau kualitas sampah..."
rows={3}
/>
</div>
<Button type="submit" disabled={isSubmitting} className="w-full">
<Receipt className="h-4 w-4 mr-2" />
{isSubmitting ? "Menyimpan..." : "Simpan Pencatatan"}
</Button>
</Form>
</CardContent>
</Card>
{/* Summary Card */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Ringkasan Hari Ini</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Total Transaksi</span>
<span className="font-semibold">3</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Total Berat</span>
<span className="font-semibold">425.7 kg</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Total Nilai</span>
<span className="font-semibold text-green-600">Rp 1.725.950</span>
</div>
<Separator />
<div className="text-xs text-muted-foreground">
Data per {new Date().toLocaleDateString('id-ID')}
</div>
</CardContent>
</Card>
</div>
{/* Right Column - Records Table */}
<div className="xl:col-span-3 space-y-6">
{/* Filters */}
<Card>
<CardContent className="pt-6">
<div className="flex flex-col md:flex-row gap-4">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Cari berdasarkan pengepul, jenis sampah, atau ID..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
</div>
<div className="flex gap-2">
<Select value={selectedType} onValueChange={setSelectedType}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Jenis Sampah" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Semua Jenis</SelectItem>
{wasteTypes.map((type) => (
<SelectItem key={type} value={type}>
{type}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={selectedStatus} onValueChange={setSelectedStatus}>
<SelectTrigger className="w-[140px]">
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Semua Status</SelectItem>
<SelectItem value="completed">Selesai</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
</SelectContent>
</Select>
<Button variant="outline" size="icon">
<Download className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
{/* Records Table */}
<Card>
<CardHeader>
<div className="flex justify-between items-center">
<div>
<CardTitle>Riwayat Pencatatan</CardTitle>
<CardDescription>
Daftar transaksi pembelian sampah dari pengepul
</CardDescription>
</div>
<Badge variant="outline">
{filteredRecords.length} dari {wasteRecords.length} records
</Badge>
</div>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Tanggal</TableHead>
<TableHead>Pengepul</TableHead>
<TableHead>Jenis Sampah</TableHead>
<TableHead>Berat (kg)</TableHead>
<TableHead>Harga/kg</TableHead>
<TableHead>Total</TableHead>
<TableHead>Status</TableHead>
<TableHead>Aksi</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredRecords.length === 0 ? (
<TableRow>
<TableCell colSpan={9} className="text-center py-6 text-muted-foreground">
Tidak ada data yang ditemukan
</TableCell>
</TableRow>
) : (
filteredRecords.map((record) => (
<TableRow key={record.id}>
<TableCell className="font-medium">{record.id}</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Calendar className="h-4 w-4 text-muted-foreground" />
{new Date(record.date).toLocaleDateString('id-ID')}
</div>
</TableCell>
<TableCell>
<div>
<div className="font-medium">{record.collectorName}</div>
<div className="text-sm text-muted-foreground">{record.collectorPhone}</div>
</div>
</TableCell>
<TableCell>{record.wasteType}</TableCell>
<TableCell>
<div className="flex items-center gap-1">
<Scale className="h-4 w-4 text-muted-foreground" />
{record.weight}
</div>
</TableCell>
<TableCell>{formatCurrency(record.pricePerKg)}</TableCell>
<TableCell className="font-semibold text-green-600">
{formatCurrency(record.totalPrice)}
</TableCell>
<TableCell>{getStatusBadge(record.status)}</TableCell>
<TableCell>
<div className="flex gap-1">
<Button variant="ghost" size="icon" className="h-8 w-8">
<Eye className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8">
<Edit className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8 text-red-600">
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
);
}