Files
meet-hub/src/pages/member/MemberProfit.tsx

240 lines
10 KiB
TypeScript

import { useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { AppLayout } from "@/components/AppLayout";
import { useAuth } from "@/hooks/useAuth";
import { supabase } from "@/integrations/supabase/client";
import { Card, CardContent, 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 { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/skeleton";
import { toast } from "@/hooks/use-toast";
import { formatIDR, formatDateTime } from "@/lib/format";
interface WalletData {
current_balance: number;
total_earned: number;
total_withdrawn: number;
pending_balance: number;
}
interface ProfitRow {
order_item_id: string;
order_id: string;
created_at: string;
product_title: string;
profit_share_percentage: number;
profit_amount: number;
profit_status: string | null;
wallet_transaction_id: string | null;
}
interface WithdrawalRow {
id: string;
amount: number;
status: string;
requested_at: string;
processed_at: string | null;
payment_reference: string | null;
admin_notes: string | null;
}
export default function MemberProfit() {
const { user, loading: authLoading } = useAuth();
const navigate = useNavigate();
const [loading, setLoading] = useState(true);
const [wallet, setWallet] = useState<WalletData | null>(null);
const [profits, setProfits] = useState<ProfitRow[]>([]);
const [withdrawals, setWithdrawals] = useState<WithdrawalRow[]>([]);
const [openWithdrawDialog, setOpenWithdrawDialog] = useState(false);
const [withdrawAmount, setWithdrawAmount] = useState("");
const [withdrawNotes, setWithdrawNotes] = useState("");
const [submitting, setSubmitting] = useState(false);
const [settings, setSettings] = useState<{ min_withdrawal_amount: number } | null>(null);
useEffect(() => {
if (!authLoading && !user) navigate("/auth");
if (user) fetchData();
}, [user, authLoading]);
const fetchData = async () => {
const [walletRes, profitRes, withdrawalRes, settingsRes] = await Promise.all([
supabase.rpc("get_collaborator_wallet", { p_user_id: user!.id }),
supabase
.from("collaborator_profits")
.select("*")
.eq("collaborator_user_id", user!.id)
.order("created_at", { ascending: false }),
supabase
.from("withdrawals")
.select("id, amount, status, requested_at, processed_at, payment_reference, admin_notes")
.eq("user_id", user!.id)
.order("requested_at", { ascending: false }),
supabase.rpc("get_collaboration_settings"),
]);
setWallet((walletRes.data?.[0] as WalletData) || {
current_balance: 0,
total_earned: 0,
total_withdrawn: 0,
pending_balance: 0,
});
setProfits((profitRes.data as ProfitRow[]) || []);
setWithdrawals((withdrawalRes.data as WithdrawalRow[]) || []);
setSettings({ min_withdrawal_amount: settingsRes.data?.[0]?.min_withdrawal_amount || 100000 });
setLoading(false);
};
const canSubmit = useMemo(() => {
const amount = Number(withdrawAmount || 0);
const min = settings?.min_withdrawal_amount || 100000;
const available = Number(wallet?.current_balance || 0);
return amount >= min && amount <= available;
}, [withdrawAmount, settings, wallet]);
const submitWithdrawal = async () => {
if (!canSubmit) {
toast({
title: "Nominal tidak valid",
description: "Periksa minimum penarikan dan saldo tersedia",
variant: "destructive",
});
return;
}
setSubmitting(true);
const { data, error } = await supabase.functions.invoke("create-withdrawal", {
body: {
amount: Number(withdrawAmount),
notes: withdrawNotes || null,
},
});
const response = data as { error?: string } | null;
if (error || response?.error) {
toast({
title: "Gagal membuat withdrawal",
description: response?.error || error?.message || "Unknown error",
variant: "destructive",
});
setSubmitting(false);
return;
}
toast({ title: "Berhasil", description: "Withdrawal request berhasil dibuat" });
setSubmitting(false);
setOpenWithdrawDialog(false);
setWithdrawAmount("");
setWithdrawNotes("");
fetchData();
};
if (authLoading || loading) {
return (
<AppLayout>
<div className="container mx-auto px-4 py-8">
<Skeleton className="h-10 w-1/3 mb-8" />
<Skeleton className="h-72 w-full" />
</div>
</AppLayout>
);
}
return (
<AppLayout>
<div className="container mx-auto px-4 py-8 space-y-6">
<div>
<h1 className="text-4xl font-bold mb-2">Profit</h1>
<p className="text-muted-foreground">Ringkasan pendapatan kolaborasi Anda</p>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card className="border-2 border-border"><CardContent className="pt-6"><p className="text-sm text-muted-foreground">Total Earnings</p><p className="text-2xl font-bold">{formatIDR(wallet?.total_earned || 0)}</p></CardContent></Card>
<Card className="border-2 border-border"><CardContent className="pt-6"><p className="text-sm text-muted-foreground">Available Balance</p><p className="text-2xl font-bold">{formatIDR(wallet?.current_balance || 0)}</p></CardContent></Card>
<Card className="border-2 border-border"><CardContent className="pt-6"><p className="text-sm text-muted-foreground">Total Withdrawn</p><p className="text-2xl font-bold">{formatIDR(wallet?.total_withdrawn || 0)}</p></CardContent></Card>
<Card className="border-2 border-border"><CardContent className="pt-6"><p className="text-sm text-muted-foreground">Pending Balance</p><p className="text-2xl font-bold">{formatIDR(wallet?.pending_balance || 0)}</p></CardContent></Card>
</div>
<Card className="border-2 border-border">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Withdrawal</CardTitle>
<Button onClick={() => setOpenWithdrawDialog(true)}>Request Withdrawal</Button>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p>Minimum withdrawal: {formatIDR(settings?.min_withdrawal_amount || 100000)}</p>
<p>Available balance: {formatIDR(wallet?.current_balance || 0)}</p>
</CardContent>
</Card>
<Card className="border-2 border-border">
<CardHeader><CardTitle>Profit History</CardTitle></CardHeader>
<CardContent className="p-0">
<Table>
<TableHeader><TableRow><TableHead>Date</TableHead><TableHead>Product</TableHead><TableHead>Share</TableHead><TableHead>Amount</TableHead><TableHead>Status</TableHead></TableRow></TableHeader>
<TableBody>
{profits.map((row) => (
<TableRow key={row.order_item_id}>
<TableCell>{formatDateTime(row.created_at)}</TableCell>
<TableCell>{row.product_title}</TableCell>
<TableCell>{row.profit_share_percentage}%</TableCell>
<TableCell>{formatIDR(row.profit_amount || 0)}</TableCell>
<TableCell><Badge variant="secondary">{row.profit_status || "-"}</Badge></TableCell>
</TableRow>
))}
{profits.length === 0 && (
<TableRow><TableCell colSpan={5} className="text-center text-muted-foreground py-8">Belum ada data profit</TableCell></TableRow>
)}
</TableBody>
</Table>
</CardContent>
</Card>
<Card className="border-2 border-border">
<CardHeader><CardTitle>Withdrawal History</CardTitle></CardHeader>
<CardContent className="p-0">
<Table>
<TableHeader><TableRow><TableHead>Date</TableHead><TableHead>Amount</TableHead><TableHead>Status</TableHead><TableHead>Reference</TableHead></TableRow></TableHeader>
<TableBody>
{withdrawals.map((w) => (
<TableRow key={w.id}>
<TableCell>{formatDateTime(w.requested_at)}</TableCell>
<TableCell>{formatIDR(w.amount || 0)}</TableCell>
<TableCell><Badge variant={w.status === "completed" ? "default" : w.status === "rejected" ? "destructive" : "secondary"}>{w.status}</Badge></TableCell>
<TableCell>{w.payment_reference || "-"}</TableCell>
</TableRow>
))}
{withdrawals.length === 0 && (
<TableRow><TableCell colSpan={4} className="text-center text-muted-foreground py-8">Belum ada withdrawal</TableCell></TableRow>
)}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
<Dialog open={openWithdrawDialog} onOpenChange={setOpenWithdrawDialog}>
<DialogContent className="border-2 border-border">
<DialogHeader><DialogTitle>Request Withdrawal</DialogTitle></DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label>Nominal (IDR)</Label>
<Input type="number" value={withdrawAmount} onChange={(e) => setWithdrawAmount(e.target.value)} />
</div>
<div className="space-y-2">
<Label>Notes</Label>
<Textarea value={withdrawNotes} onChange={(e) => setWithdrawNotes(e.target.value)} />
</div>
<Button onClick={submitWithdrawal} disabled={submitting} className="w-full">
{submitting ? "Submitting..." : "Submit Withdrawal"}
</Button>
</div>
</DialogContent>
</Dialog>
</AppLayout>
);
}