import React, { useState } from 'react';
import { Calculator, DollarSign, Percent, Package, TrendingUp, AlertCircle, ShoppingBag, ArrowRight } from 'lucide-react';
const formatRp = (value) => {
if (isNaN(value) || value === null) return 'Rp 0';
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(value);
};
export default function App() {
const [activeTab, setActiveTab] = useState('ideal'); // 'ideal' | 'simulasi'
// Global Inputs
const [modal, setModal] = useState(50000);
const [packaging, setPackaging] = useState(2000);
// Fees (Persentase & Flat)
const [adminPercent, setAdminPercent] = useState(4.0); // Rata-rata biaya admin
const [adminFlat, setAdminFlat] = useState(2000); // Biaya tetap per pesanan (contoh Rp2000)
const [affiliatePercent, setAffiliatePercent] = useState(5.0); // Komisi kreator
const [xtraPercent, setXtraPercent] = useState(3.0); // Gratis ongkir xtra
// Tab 1 Specific: Hitung Harga Ideal
const [targetProfit, setTargetProfit] = useState(20000);
// Tab 2 Specific: Simulasi Keuntungan
const [sellingPrice, setSellingPrice] = useState(100000);
// --- CALCULATIONS FOR TAB 1 (Harga Ideal) ---
const calculateIdealPrice = () => {
const totalCost = Number(modal) + Number(packaging);
const totalPercentFee = (Number(adminPercent) + Number(affiliatePercent) + Number(xtraPercent)) / 100;
const targetGross = totalCost + Number(adminFlat) + Number(targetProfit);
// Rumus: Harga Jual = (Modal + Biaya Tetap + Target Profit) / (1 - Total Persentase Potongan)
let idealPrice = 0;
if (totalPercentFee < 1) {
idealPrice = targetGross / (1 - totalPercentFee);
}
const adminCut = idealPrice * (Number(adminPercent) / 100);
const affiliateCut = idealPrice * (Number(affiliatePercent) / 100);
const xtraCut = idealPrice * (Number(xtraPercent) / 100);
const totalPotongan = adminCut + affiliateCut + xtraCut + Number(adminFlat);
return { idealPrice, totalPotongan, adminCut, affiliateCut, xtraCut };
};
// --- CALCULATIONS FOR TAB 2 (Simulasi) ---
const calculateSimulasi = () => {
const price = Number(sellingPrice);
const totalCost = Number(modal) + Number(packaging);
const adminCut = price * (Number(adminPercent) / 100);
const affiliateCut = price * (Number(affiliatePercent) / 100);
const xtraCut = price * (Number(xtraPercent) / 100);
const totalPotongan = adminCut + affiliateCut + xtraCut + Number(adminFlat);
const netProfit = price - totalPotongan - totalCost;
const profitMargin = price > 0 ? (netProfit / price) * 100 : 0;
return { netProfit, totalPotongan, profitMargin, adminCut, affiliateCut, xtraCut };
};
const idealResult = calculateIdealPrice();
const simResult = calculateSimulasi();
const handleFocus = (e) => e.target.select();
return (
<div className="min-h-screen bg-slate-50 p-4 md:p-8 font-sans text-slate-800">
<div className="max-w-5xl mx-auto space-y-6">
{/* Header */}
<div className="text-center space-y-2">
<h1 className="text-3xl md:text-4xl font-extrabold flex items-center justify-center gap-3">
<ShoppingBag className="text-pink-600" size={36} />
<span className="bg-clip-text text-transparent bg-gradient-to-r from-pink-600 to-cyan-500">
Kalkulator Harga TikTok Shop
</span>
</h1>
<p className="text-slate-500 text-sm md:text-base max-w-2xl mx-auto">
Hitung harga jual yang tepat agar tidak boncos. Sesuaikan persentase biaya layanan, komisi affiliate, dan biaya admin sesuai dengan kategori produk Anda.
</p>
</div>
{/* Tabs */}
<div className="flex justify-center mb-6">
<div className="bg-white p-1 rounded-xl shadow-sm border border-slate-200 inline-flex">
<button
onClick={() => setActiveTab('ideal')}
className={`px-6 py-2.5 rounded-lg text-sm font-semibold transition-all ${
activeTab === 'ideal'
? 'bg-slate-800 text-white shadow-md'
: 'text-slate-500 hover:text-slate-800 hover:bg-slate-100'
}`}
>
Cari Harga Jual Ideal
</button>
<button
onClick={() => setActiveTab('simulasi')}
className={`px-6 py-2.5 rounded-lg text-sm font-semibold transition-all ${
activeTab === 'simulasi'
? 'bg-slate-800 text-white shadow-md'
: 'text-slate-500 hover:text-slate-800 hover:bg-slate-100'
}`}
>
Simulasi Keuntungan
</button>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Kolom Kiri: Input Biaya Modal & Variabel */}
<div className="lg:col-span-2 space-y-6">
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
<div className="bg-slate-800 px-6 py-4 border-b border-slate-200">
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
<Package size={20} className="text-cyan-400" />
Biaya Dasar Produk
</h2>
</div>
<div className="p-6 grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-sm font-medium text-slate-600">Harga Beli / Modal Produk (Rp)</label>
<input
type="number"
value={modal}
onChange={(e) => setModal(e.target.value)}
onFocus={handleFocus}
className="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-cyan-500 focus:border-cyan-500 transition-all outline-none"
/>
</div>
<div className="space-y-1">
<label className="text-sm font-medium text-slate-600">Biaya Packaging dll (Rp)</label>
<input
type="number"
value={packaging}
onChange={(e) => setPackaging(e.target.value)}
onFocus={handleFocus}
className="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-cyan-500 focus:border-cyan-500 transition-all outline-none"
/>
</div>
</div>
</div>
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
<div className="bg-slate-800 px-6 py-4 border-b border-slate-200">
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
<Percent size={20} className="text-pink-400" />
Potongan TikTok Shop
</h2>
</div>
<div className="p-6 grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-sm font-medium text-slate-600">Biaya Admin (%)</label>
<input
type="number"
step="0.1"
value={adminPercent}
onChange={(e) => setAdminPercent(e.target.value)}
onFocus={handleFocus}
className="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-pink-500 focus:border-pink-500 transition-all outline-none"
/>
</div>
<div className="space-y-1">
<label className="text-sm font-medium text-slate-600">Biaya Tetap Admin (Rp)</label>
<input
type="number"
value={adminFlat}
onChange={(e) => setAdminFlat(e.target.value)}
onFocus={handleFocus}
className="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-pink-500 focus:border-pink-500 transition-all outline-none"
/>
</div>
<div className="space-y-1">
<label className="text-sm font-medium text-slate-600">Komisi Affiliate (%)</label>
<input
type="number"
step="0.1"
value={affiliatePercent}
onChange={(e) => setAffiliatePercent(e.target.value)}
onFocus={handleFocus}
className="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-pink-500 focus:border-pink-500 transition-all outline-none"
/>
</div>
<div className="space-y-1">
<label className="text-sm font-medium text-slate-600">Layanan Gratis Ongkir (%)</label>
<input
type="number"
step="0.1"
value={xtraPercent}
onChange={(e) => setXtraPercent(e.target.value)}
onFocus={handleFocus}
className="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-pink-500 focus:border-pink-500 transition-all outline-none"
/>
</div>
</div>
</div>
{/* Input khusus berdasarkan tab */}
{activeTab === 'ideal' ? (
<div className="bg-cyan-50 rounded-2xl shadow-sm border border-cyan-200 p-6">
<h3 className="text-lg font-bold text-cyan-900 mb-3 flex items-center gap-2">
<TrendingUp size={20} />
Target Keuntungan Bersih
</h3>
<p className="text-sm text-cyan-800 mb-4">Berapa uang bersih yang ingin Anda dapatkan dari penjualan 1 produk ini?</p>
<div className="space-y-1">
<input
type="number"
value={targetProfit}
onChange={(e) => setTargetProfit(e.target.value)}
onFocus={handleFocus}
className="w-full px-4 py-3 text-lg font-semibold border border-cyan-300 rounded-lg focus:ring-2 focus:ring-cyan-600 focus:border-cyan-600 transition-all outline-none"
/>
</div>
</div>
) : (
<div className="bg-indigo-50 rounded-2xl shadow-sm border border-indigo-200 p-6">
<h3 className="text-lg font-bold text-indigo-900 mb-3 flex items-center gap-2">
<DollarSign size={20} />
Rencana Harga Jual
</h3>
<p className="text-sm text-indigo-800 mb-4">Masukkan harga jual yang ingin Anda pasang di etalase toko.</p>
<div className="space-y-1">
<input
type="number"
value={sellingPrice}
onChange={(e) => setSellingPrice(e.target.value)}
onFocus={handleFocus}
className="w-full px-4 py-3 text-lg font-semibold border border-indigo-300 rounded-lg focus:ring-2 focus:ring-indigo-600 focus:border-indigo-600 transition-all outline-none"
/>
</div>
</div>
)}
</div>
{/* Kolom Kanan: Hasil & Ringkasan */}
<div className="lg:col-span-1">
<div className="bg-white rounded-2xl shadow-xl border border-slate-200 sticky top-6 overflow-hidden">
{activeTab === 'ideal' ? (
// RESULT: IDEAL PRICE
<>
<div className="bg-gradient-to-br from-slate-800 to-slate-900 p-6 text-center text-white">
<p className="text-slate-300 text-sm font-medium mb-2 uppercase tracking-wider">Saran Harga Jual</p>
<h3 className="text-3xl font-extrabold text-cyan-400 mb-1">
{formatRp(idealResult.idealPrice)}
</h3>
<p className="text-slate-400 text-xs">Untuk mendapatkan untung bersih {formatRp(targetProfit)}</p>
</div>
<div className="p-6 space-y-4">
<h4 className="font-semibold text-slate-800 border-b pb-2">Rincian Potongan</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-slate-600">Biaya Admin ({adminPercent}%)</span>
<span className="font-medium text-red-500">-{formatRp(idealResult.adminCut)}</span>
</div>
<div className="flex justify-between">
<span className="text-slate-600">Biaya Tetap Admin</span>
<span className="font-medium text-red-500">-{formatRp(adminFlat)}</span>
</div>
<div className="flex justify-between">
<span className="text-slate-600">Komisi Affiliate ({affiliatePercent}%)</span>
<span className="font-medium text-red-500">-{formatRp(idealResult.affiliateCut)}</span>
</div>
<div className="flex justify-between">
<span className="text-slate-600">Gratis Ongkir Xtra ({xtraPercent}%)</span>
<span className="font-medium text-red-500">-{formatRp(idealResult.xtraCut)}</span>
</div>
</div>
<div className="pt-4 border-t border-slate-100">
<div className="flex justify-between items-center bg-red-50 p-3 rounded-lg border border-red-100">
<span className="font-semibold text-red-800 text-sm">Total Potongan</span>
<span className="font-bold text-red-600">{formatRp(idealResult.totalPotongan)}</span>
</div>
</div>
<div className="pt-2">
<div className="flex items-start gap-2 text-xs text-slate-500 bg-slate-50 p-3 rounded-lg">
<AlertCircle size={16} className="text-cyan-600 shrink-0 mt-0.5" />
<p>Rumus ini memasukkan potongan persentase dari harga jual akhir, sehingga Anda mendapatkan untung bulat sesuai target.</p>
</div>
</div>
</div>
</>
) : (
// RESULT: SIMULASI
<>
<div className={`p-6 text-center text-white ${simResult.netProfit >= 0 ? 'bg-gradient-to-br from-green-600 to-emerald-700' : 'bg-gradient-to-br from-red-600 to-rose-700'}`}>
<p className="text-white/80 text-sm font-medium mb-2 uppercase tracking-wider">Keuntungan Bersih</p>
<h3 className="text-3xl font-extrabold mb-1">
{formatRp(simResult.netProfit)}
</h3>
<p className="text-white/90 text-sm bg-black/20 inline-block px-3 py-1 rounded-full mt-2">
Margin: {simResult.profitMargin.toFixed(1)}%
</p>
</div>
<div className="p-6 space-y-4">
<h4 className="font-semibold text-slate-800 border-b pb-2">Rincian Perhitungan</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-slate-600">Harga Jual</span>
<span className="font-medium text-slate-800">{formatRp(sellingPrice)}</span>
</div>
<div className="flex justify-between">
<span className="text-slate-600">Total Modal (Produk + Pack)</span>
<span className="font-medium text-slate-800">-{formatRp(Number(modal) + Number(packaging))}</span>
</div>
<div className="pt-2 pb-1 text-xs font-semibold text-slate-400 uppercase tracking-wider">Potongan Aplikasi</div>
<div className="flex justify-between">
<span className="text-slate-600">Biaya Admin ({adminPercent}%) + Flat</span>
<span className="font-medium text-red-500">-{formatRp(simResult.adminCut + Number(adminFlat))}</span>
</div>
<div className="flex justify-between">
<span className="text-slate-600">Komisi Affiliate ({affiliatePercent}%)</span>
<span className="font-medium text-red-500">-{formatRp(simResult.affiliateCut)}</span>
</div>
<div className="flex justify-between">
<span className="text-slate-600">Gratis Ongkir Xtra ({xtraPercent}%)</span>
<span className="font-medium text-red-500">-{formatRp(simResult.xtraCut)}</span>
</div>
</div>
<div className="pt-4 border-t border-slate-100">
<div className="flex justify-between items-center bg-red-50 p-3 rounded-lg border border-red-100">
<span className="font-semibold text-red-800 text-sm">Total Potongan TikTok</span>
<span className="font-bold text-red-600">{formatRp(simResult.totalPotongan)}</span>
</div>
</div>
{simResult.netProfit < 0 && (
<div className="pt-2">
<div className="flex items-start gap-2 text-sm text-red-700 bg-red-100 p-3 rounded-lg border border-red-200">
<AlertCircle size={18} className="shrink-0 mt-0.5" />
<p><strong>Peringatan!</strong> Anda mengalami kerugian. Silakan naikkan harga jual atau turunkan biaya operasional.</p>
</div>
</div>
)}
</div>
</>
)}
</div>
</div>
</div>
</div>
</div>
);
}
Home ยป