import React, { useState, useEffect, useMemo, useCallback } from 'react';
import {
LayoutDashboard,
Boxes,
Users,
Lock,
ArrowDownLeft,
FileCheck2,
Truck,
Receipt,
BookOpen,
ShieldCheck,
ShieldAlert,
Crown,
Warehouse,
Plus,
Search,
Printer,
X,
CheckCircle2,
AlertTriangle,
Database,
LogOut,
ChevronRight,
Beaker,
FileText,
Building,
UserCheck,
Calendar,
DollarSign,
Copy,
ExternalLink,
KeyRound,
LogIn,
UserPlus,
HelpCircle,
Info
} from 'lucide-react';
const ROMAN_MONTHS = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII'];
const formatRp = (num) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
maximumFractionDigits: 0
}).format(num || 0);
};
const generateDocNumber = (prefix, partnerName) => {
const cleanName = (partnerName || 'PGS').split(' ')[1] || (partnerName || 'PGS').split(' ')[0] || 'PGS';
const now = new Date();
const rom = ROMAN_MONTHS[now.getMonth()];
const dateNum = String(now.getDate()).padStart(2, '0');
const year = now.getFullYear();
const seq = String(Math.floor(Math.random() * 899) + 100);
return `${prefix}-PGS/${cleanName.toUpperCase().replace(/[^A-Z]/g, '')}/${rom}-${dateNum}/${year}/${seq}`;
};
const DEFAULT_USERS = [
{
id: 'usr-mgmt-01',
email: 'direksi@pratachem.com',
password: 'password123',
name: 'Cia Liu',
title: 'Assistant Director & Management',
role: 'management'
},
{
id: 'usr-adm-01',
email: 'admin.sales@pratachem.com',
password: 'password123',
name: 'Siti Rahma',
title: 'Admin & Sales Office',
role: 'admin'
},
{
id: 'usr-gdg-01',
email: 'gudang@pratachem.com',
password: 'password123',
name: 'Budi Santoso',
title: 'Kepala Gudang Tangerang',
role: 'gudang'
}
];
const INITIAL_CUSTOMERS = [
{
id: 'cust-001',
name: 'PT. MAYORA INDAH TBK',
address: 'Kawasan Industri Jatake, Jl. Industri Raya No. 18, Tangerang, Banten',
npwp: '01.345.678.9-056.000',
pic: 'Herman Susanto (Procurement Head)',
responsible: 'Andre Atmadja (Direksi)',
top: 'TOP 30 Hari',
creditLimit: 500000000,
emailFinance: 'invoicing.jatake@mayora.co.id',
phoneFinance: '021-590-8888'
},
{
id: 'cust-002',
name: 'PT. INDOFOOD CBP SUKSES MAKMUR TBK',
address: 'Kawasan Industri Indotaisei Sektor IA, Kalihurip, Cikampek, Karawang',
npwp: '01.234.567.8-092.000',
pic: 'Bambang Irawan (Purchasing Manager)',
responsible: 'Axton Salim (Direksi)',
top: 'TOP 30 Hari',
creditLimit: 750000000,
emailFinance: 'ap.chemicals@icbp.indofood.co.id',
phoneFinance: '0264-351-234'
},
{
id: 'cust-003',
name: 'PT. WINGS SURYA',
address: 'Jl. Kalisosok Kidul No. 2, Krembangan, Kota Surabaya, Jawa Timur',
npwp: '01.111.222.3-604.000',
pic: 'Stefanus Wijaya (Raw Materials Buyer)',
responsible: 'Ferdinand Katuari (Direksi)',
top: 'TOP 14 Hari',
creditLimit: 300000000,
emailFinance: 'finance.wings@wingscorp.com',
phoneFinance: '031-353-3333'
}
];
const INITIAL_SUPPLIERS = [
{
id: 'supp-001',
name: 'SINOCHEM HEBEI CO., LTD.',
address: 'No. 88 Lianmeng Road, Shijiazhuang, Hebei 050061, China',
npwp: '02.888.777.6-033.000',
pic: 'Mr. Chen Wei (Overseas Sales Dir)',
responsible: 'Dr. Zhang Yong (President)',
top: 'TOP 30 Hari',
emailFinance: 'export.finance@sinochem.hebei.com',
phoneFinance: '+86 311 8899 1122'
},
{
id: 'supp-002',
name: 'ROQUETTE FRERES FRANCE',
address: '1 Rue de la Haute Loge, 62136 Lestrem, France',
npwp: '02.555.444.3-033.000',
pic: 'Jean-Paul Sartre (Asia Pacific Mgr)',
responsible: 'Edouard Roquette (Chairman)',
top: 'TOP 60 Hari (LC)',
emailFinance: 'ap.invoicing@roquette.com',
phoneFinance: '+33 3 21 63 36 00'
},
{
id: 'supp-003',
name: 'TTCA WEIFANG CO., LTD.',
address: 'Weifang Coastal Economic Development Zone, Shandong, China',
npwp: '02.333.222.1-033.000',
pic: 'Ms. Wang Lin (Export Division)',
responsible: 'Liu Baojun (General Manager)',
top: 'Cash Advance / T/T',
emailFinance: 'finance@ttca.cn',
phoneFinance: '+86 536 8877 665'
}
];
const INITIAL_CHEMICALS = [
{
id: 'chem-001',
sku: 'PRCF0001',
warehouse: 'C',
category: 'F',
name: 'Citric Acid Anhydrous',
origin: 'TTCA Weifang, China',
form: 'Powder',
packageType: 'Zak',
weightPerUnit: 25,
pricePerKg: 28500,
bookedKg: 500,
batches: [
{ id: 'b-01', lotNumber: 'LOT-TTCA-2026-08', origin: 'TTCA Weifang', inflowDate: '2026-08-10', expDate: '2028-08-09', pkgCount: 40, remainingKg: 1000 },
{ id: 'b-02', lotNumber: 'LOT-TTCA-2026-09', origin: 'TTCA Weifang', inflowDate: '2026-09-01', expDate: '2028-09-01', pkgCount: 60, remainingKg: 1500 }
]
},
{
id: 'chem-002',
sku: 'PRTF0002',
warehouse: 'T',
category: 'F',
name: 'High Fructose Corn Syrup 55%',
origin: 'Roquette Freres, France',
form: 'Liquid',
packageType: 'Jerigen',
weightPerUnit: 25,
pricePerKg: 32000,
bookedKg: 250,
batches: [
{ id: 'b-03', lotNumber: 'LOT-RQ-2026-014', origin: 'Roquette France', inflowDate: '2026-09-12', expDate: '2027-09-11', pkgCount: 30, remainingKg: 750 }
]
},
{
id: 'chem-003',
sku: 'PRTI0003',
warehouse: 'T',
category: 'I',
name: 'Trichloroisocyanuric Acid (TCCA 90%)',
origin: 'Sinochem Hebei, China',
form: 'Granular',
packageType: 'Drum',
weightPerUnit: 50,
pricePerKg: 46000,
bookedKg: 0,
batches: [
{ id: 'b-04', lotNumber: 'LOT-SINO-2026-55', origin: 'Sinochem Hebei', inflowDate: '2026-09-15', expDate: '2029-09-14', pkgCount: 40, remainingKg: 2000 }
]
}
];
export default function App() {
const [usersList, setUsersList] = useState(() => {
try {
const saved = localStorage.getItem('pratachem_users');
return saved ? JSON.parse(saved) : DEFAULT_USERS;
} catch {
return DEFAULT_USERS;
}
});
const [currentUser, setCurrentUser] = useState(() => {
try {
const saved = localStorage.getItem('pratachem_session');
return saved ? JSON.parse(saved) : null;
} catch {
return null;
}
});
const [activeTab, setActiveTab] = useState('dashboard');
const [authTab, setAuthTab] = useState('login');
const [toast, setToast] = useState(null);
const [customers, setCustomers] = useState(INITIAL_CUSTOMERS);
const [suppliers, setSuppliers] = useState(INITIAL_SUPPLIERS);
const [chemicals, setChemicals] = useState(INITIAL_CHEMICALS);
const [supplierPOs, setSupplierPOs] = useState([
{
id: 'po-1',
poNumber: 'PO-PGS/SINOCHEM/IX-20/2026/001',
date: '2026-09-20',
supplier: 'SINOCHEM HEBEI CO., LTD.',
chemId: 'chem-003',
chemName: 'Trichloroisocyanuric Acid (TCCA 90%)',
packageCount: 40,
totalKg: 2000,
pricePerKg: 42000,
subtotal: 84000000,
ppn: 9240000,
grandTotal: 93240000,
status: 'Received'
}
]);
const [quotations, setQuotations] = useState([
{
id: 'quo-1',
quoNumber: 'Quo-PGS/MAYORA/IX-24/2026/001',
date: '2026-09-24',
customer: 'PT. MAYORA INDAH TBK',
customerId: 'cust-001',
customerPO: 'PO-MYR/09/2026/881',
chemId: 'chem-002',
chemName: 'High Fructose Corn Syrup 55%',
packageCount: 10,
totalKg: 250,
pricePerKg: 34500,
terms: 'TOP 30 Hari',
address: 'Kawasan Industri Jatake, Tangerang',
status: 'Shipped',
dispatchId: 'sj-1'
}
]);
const [dispatches, setDispatches] = useState([
{
id: 'sj-1',
sjNumber: 'SJ-PGS/MAYORA/IX-26/2026/001',
quoId: 'quo-1',
customer: 'PT. MAYORA INDAH TBK',
customerPO: 'PO-MYR/09/2026/881',
chemId: 'chem-002',
chemName: 'High Fructose Corn Syrup 55%',
packageCount: 10,
packageType: 'Jerigen',
totalKg: 250,
address: 'Kawasan Industri Jatake, Tangerang',
driverName: 'Septian (Deliveree CDE)',
driverPhone: '0812-9988-7711',
nopol: 'B 1583 VOR',
pickupTime: '2026-09-26 10:00',
status: 'Delivered',
podReceiver: 'Herman Susanto (QC Pass)',
podCompletionTime: '2026-09-26 14:30',
podNotes: 'Segel 10 jerigen utuh dan lengkap',
invoiceId: 'inv-1'
}
]);
const [invoices, setInvoices] = useState([
{
id: 'inv-1',
invNumber: 'INV-PGS/MAYORA/IX-26/2026/001',
dispatchId: 'sj-1',
sjNumber: 'SJ-PGS/MAYORA/IX-26/2026/001',
customer: 'PT. MAYORA INDAH TBK',
customerPO: 'PO-MYR/09/2026/881',
date: '2026-09-26',
dueDate: '2026-10-26',
terms: 'TOP 30 Hari',
totalKg: 250,
pricePerKg: 34500,
subtotal: 8625000,
ppn: 948750,
grandTotal: 9573750,
fakturPajak: '010.002-26.12345678',
status: 'Unpaid'
}
]);
const [logbook, setLogbook] = useState([
{
id: 'log-1',
type: 'Stok in',
time: '21/09/2026, 09.15',
driverInfo: 'Andi (B 2020 TOR)',
chemInfo: 'Citric Acid Anhydrous (PRCF0001)',
qty: '40 Zak (1.000 Kg)',
condition: 'Segel karung utuh, bersih, lolos uji QC',
officer: 'Budi Santoso'
},
{
id: 'log-2',
type: 'Stok out',
time: '26/09/2026, 10.30',
driverInfo: 'Septian (B 1583 VOR)',
chemInfo: 'High Fructose Corn Syrup 55% (PRTF0002)',
qty: '10 Jerigen (250 Kg)',
condition: 'Segel drum rapat, muat selesai tanpa tumpah',
officer: 'Budi Santoso'
}
]);
const [modalType, setModalType] = useState(null);
const [selectedBatchChem, setSelectedBatchChem] = useState(null);
const [printDoc, setPrintDoc] = useState(null);
const [activeQuoForDispatch, setActiveQuoForDispatch] = useState(null);
const [activeDispatchForPod, setActiveDispatchForPod] = useState(null);
const [activeDispatchForInv, setActiveDispatchForInv] = useState(null);
const [appDialog, setAppDialog] = useState(null);
const [supabaseConfig, setSupabaseConfig] = useState({
url: '',
anonKey: '',
isConnected: true
});
const showToast = (message, type = 'success') => {
setToast({ message, type });
setTimeout(() => setToast(null), 3500);
};
const triggerDialog = (title, message, isConfirm = false, onConfirm = null) => {
setAppDialog({ title, message, isConfirm, onConfirm });
};
const closeDialog = () => {
setAppDialog(null);
};
const handleLoginSubmit = (e) => {
e.preventDefault();
const email = e.target.email.value.trim().toLowerCase();
const password = e.target.password.value;
const matched = usersList.find(
(u) => u.email.toLowerCase() === email && u.password === password
);
if (matched) {
setCurrentUser(matched);
localStorage.setItem('pratachem_session', JSON.stringify(matched));
showToast(`Login berhasil sebagai ${matched.name} (${matched.role.toUpperCase()})`);
if (matched.role !== 'management' && activeTab === 'suppliers') {
setActiveTab('dashboard');
}
} else {
triggerDialog(
'Gagal Masuk',
'Kombinasi email atau kata sandi tidak cocok. Silakan periksa kembali atau daftarkan akun baru Anda.'
);
}
};
const handleRegisterSubmit = (e) => {
e.preventDefault();
const name = e.target.regName.value.trim();
const email = e.target.regEmail.value.trim().toLowerCase();
const role = e.target.regRole.value;
const password = e.target.regPass.value;
if (!name || !email || !password) {
triggerDialog('Pendaftaran Tidak Lengkap', 'Harap isi semua kolom pendaftaran.');
return;
}
if (usersList.some((u) => u.email.toLowerCase() === email)) {
triggerDialog(
'Email Sudah Terdaftar',
'Email tersebut sudah terdaftar di sistem ERP Pratachem. Silakan gunakan menu Masuk (Login).'
);
setAuthTab('login');
return;
}
let title = 'Staf Operasional';
if (role === 'management') title = 'Direksi & Management Pratachem';
else if (role === 'admin') title = 'Admin & Sales Office';
else if (role === 'gudang') title = 'Kepala / Staf Gudang';
const newUser = {
id: 'usr-' + Date.now(),
name,
email,
role,
title,
password
};
const updatedUsers = [...usersList, newUser];
setUsersList(updatedUsers);
localStorage.setItem('pratachem_users', JSON.stringify(updatedUsers));
setCurrentUser(newUser);
localStorage.setItem('pratachem_session', JSON.stringify(newUser));
showToast(`Pendaftaran berhasil! Selamat datang, ${name}`);
};
const handleLogout = () => {
triggerDialog(
'Konfirmasi Keluar',
'Apakah Anda yakin ingin keluar dari sesi akun ERP Pratachem saat ini?',
true,
() => {
setCurrentUser(null);
localStorage.removeItem('pratachem_session');
setActiveTab('dashboard');
showToast('Anda telah berhasil keluar dari akun.');
}
);
};
const inventoryStats = useMemo(() => {
let totalPhysicalKg = 0;
let totalBookedKg = 0;
let tngCount = 0;
let ckrCount = 0;
chemicals.forEach((c) => {
const chemPhysical = (c.batches || []).reduce((acc, b) => acc + (Number(b.remainingKg) || 0), 0);
totalPhysicalKg += chemPhysical;
totalBookedKg += Number(c.bookedKg) || 0;
if (c.warehouse === 'T') tngCount++;
if (c.warehouse === 'C') ckrCount++;
});
const totalFreeKg = Math.max(0, totalPhysicalKg - totalBookedKg);
return {
skuCount: chemicals.length,
totalPhysicalKg,
totalBookedKg,
totalFreeKg,
tngCount,
ckrCount
};
}, [chemicals]);
if (!currentUser) {
return (
PT. PRATACHEM GLOBALINDO SUKSES
Pratachem Core System Apps, Version 2.0
setAuthTab('login')}
className={`flex-1 py-3.5 transition flex items-center justify-center space-x-1.5 ${
authTab === 'login'
? 'text-blue-600 border-b-2 border-blue-600 bg-white'
: 'text-slate-500 hover:text-slate-800'
}`}
>
Masuk (Login)
setAuthTab('register')}
className={`flex-1 py-3.5 transition flex items-center justify-center space-x-1.5 ${
authTab === 'register'
? 'text-emerald-600 border-b-2 border-emerald-600 bg-white'
: 'text-slate-500 hover:text-slate-800'
}`}
>
Daftar Akun Baru
{authTab === 'login' ? (
) : (
)}
{appDialog && (
{appDialog.title}
{appDialog.message}
OK
)}
);
}
return (
setActiveTab('dashboard')}>
PRATACHEM
ERP v5.2
PT. Pratachem Globalindo Sukses • Chemical Trading & Logistics
{currentUser.name.charAt(0).toUpperCase()}
{currentUser.name}
{currentUser.role.toUpperCase()}
setModalType('supabase')}
title="Status Database Cloud Supabase Tokyo"
className="p-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-emerald-400 border border-slate-700 transition"
>
Keluar
setActiveTab('dashboard')}
className={`flex items-center space-x-1.5 px-3 py-2 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'dashboard' ? 'bg-blue-600 text-white' : 'text-slate-300 hover:text-white hover:bg-slate-700/50'
}`}
>
Dashboard
setActiveTab('inventory')}
className={`flex items-center space-x-1.5 px-3 py-2 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'inventory' ? 'bg-blue-600 text-white' : 'text-slate-300 hover:text-white hover:bg-slate-700/50'
}`}
>
Master Barang & FIFO
{currentUser.role !== 'gudang' && (
setActiveTab('customers')}
className={`flex items-center space-x-1.5 px-3 py-2 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'customers' ? 'bg-blue-600 text-white' : 'text-slate-300 hover:text-white hover:bg-slate-700/50'
}`}
>
Master Pelanggan
)}
{currentUser.role === 'management' && (
setActiveTab('suppliers')}
className={`flex items-center space-x-1.5 px-3 py-2 rounded-lg transition-colors whitespace-nowrap border border-amber-500/40 ${
activeTab === 'suppliers' ? 'bg-amber-600 text-white' : 'text-amber-300 hover:bg-amber-900/40'
}`}
>
Master Supplier (Rahasia)
)}
setActiveTab('inbound')}
className={`flex items-center space-x-1.5 px-3 py-2 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'inbound' ? 'bg-blue-600 text-white' : 'text-slate-300 hover:text-white hover:bg-slate-700/50'
}`}
>
Inbound & PO
{currentUser.role !== 'gudang' && (
setActiveTab('sales')}
className={`flex items-center space-x-1.5 px-3 py-2 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'sales' ? 'bg-blue-600 text-white' : 'text-slate-300 hover:text-white hover:bg-slate-700/50'
}`}
>
Penawaran & Booking
)}
setActiveTab('deliveree')}
className={`flex items-center space-x-1.5 px-3 py-2 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'deliveree' ? 'bg-blue-600 text-white' : 'text-slate-300 hover:text-white hover:bg-slate-700/50'
}`}
>
Dispatch Deliveree
{currentUser.role !== 'gudang' && (
setActiveTab('invoices')}
className={`flex items-center space-x-1.5 px-3 py-2 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'invoices' ? 'bg-blue-600 text-white' : 'text-slate-300 hover:text-white hover:bg-slate-700/50'
}`}
>
Invoicing & Faktur
)}
setActiveTab('logbook')}
className={`flex items-center space-x-1.5 px-3 py-2 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'logbook' ? 'bg-blue-600 text-white' : 'text-slate-300 hover:text-white hover:bg-slate-700/50'
}`}
>
Logbook Gerbang
{}
Hak Akses: {currentUser.title}
Sistem ERP & WMS Bahan Kimia Terpadu
{currentUser.role === 'management'
? 'Mode Direksi: Akses penuh termasuk Direktori Rahasia Supplier & Approval Pembukuan.'
: currentUser.role === 'admin'
? 'Mode Admin: Input transaksi, PO & Invoice e-Faktur. Data Supplier Prinsipal TERBLOKIR TOTAL.'
: 'Mode Lapangan Gudang: Verifikasi armada supir Deliveree, potong batch FIFO riil, dan catat fisik gerbang.'}
{currentUser.role !== 'gudang' && (
setModalType('new-customer')}
className="bg-white text-slate-900 hover:bg-slate-100 px-3.5 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shadow"
>
Customer Baru
)}
setModalType('new-chemical')}
className="bg-emerald-600 hover:bg-emerald-700 text-white px-3.5 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shadow"
>
Komoditas Baru
{}
{activeTab === 'dashboard' && (
{inventoryStats.skuCount} SKU
{inventoryStats.tngCount} Tangerang
•
{inventoryStats.ckrCount} Cikarang
{inventoryStats.totalPhysicalKg.toLocaleString('id-ID')} Kg
Stok riil di atas rak & pallet
{inventoryStats.totalBookedKg.toLocaleString('id-ID')} Kg
Terkunci PO penawaran aktif
{inventoryStats.totalFreeKg.toLocaleString('id-ID')} Kg
Aman ditawarkan ke customer
Pengiriman Aktif Deliveree
Pencocokan armada fisik dan pemotongan stok FIFO otomatis
setActiveTab('deliveree')} className="text-xs font-bold text-blue-600 hover:text-blue-700 flex items-center">
Buka Modul
{dispatches.map((d) => (
{d.customer}
{d.status}
{d.sjNumber} • {d.chemName} ({d.totalKg} Kg)
Supir: {d.driverName} ({d.nopol})
setPrintDoc({ type: 'sj', data: d })}
className="px-2.5 py-1.5 rounded-lg border border-slate-300 bg-white hover:bg-slate-100 text-slate-700 text-xs font-semibold flex items-center space-x-1"
>
Cetak SJ
))}
Audit Logbook Gerbang
Pencatatan fisik harian
setActiveTab('logbook')} className="text-xs font-bold text-blue-600 hover:text-blue-700">Lihat Semua
{logbook.slice(0, 3).map((l) => (
{l.type.toUpperCase()}
{l.time}
{l.chemInfo}
{l.driverInfo} • {l.qty}
))}
)}
{}
{activeTab === 'inventory' && (
Master Barang & Struktur SKU Cerdas
SKU Matrix
Komoditas Bahan Baku Kimia Non-Pompa • Manajemen Lapisan Batch FIFO Strict
setModalType('new-chemical')}
className="bg-blue-600 hover:bg-blue-700 text-white px-3.5 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shadow"
>
Tambah Komoditas Baru
Kode SKU
Nama Bahan Baku
Asal & Bentuk
Kemasan
Stok Fisik
Booked
Free Stok
Batch Lots
{chemicals.map((chem) => {
const physicalKg = (chem.batches || []).reduce((acc, b) => acc + (Number(b.remainingKg) || 0), 0);
const freeKg = Math.max(0, physicalKg - (chem.bookedKg || 0));
return (
setSelectedBatchChem(chem)}
className="font-mono font-bold text-blue-600 hover:text-blue-800 underline flex items-center space-x-1"
>
{chem.sku}
{chem.name}
{chem.origin}
{chem.form}
{chem.packageType} (@ {chem.weightPerUnit} Kg)
{physicalKg.toLocaleString('id-ID')} Kg
{chem.bookedKg || 0} Kg
{freeKg.toLocaleString('id-ID')} Kg
setSelectedBatchChem(chem)}
className="px-2.5 py-1 rounded-lg bg-blue-50 text-blue-700 hover:bg-blue-100 font-semibold text-[11px]"
>
{(chem.batches || []).length} Layer FIFO
);
})}
)}
{}
{activeTab === 'customers' && currentUser.role !== 'gudang' && (
Master Data Customer (Pelanggan Resmi)
PRD Bab 08
Basis Data Tagihan e-Faktur 16 Digit • Pengawasan Plafon Piutang (Credit Limit)
setModalType('new-customer')}
className="bg-blue-600 hover:bg-blue-700 text-white px-3.5 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shadow"
>
Tambah Customer Baru
Direktori Rekanan Pembeli Terdaftar
Total: {customers.length} Customer
Nama Perusahaan
NPWP (16 Digit)
Alamat Penagihan & Drop
PIC & Penanggung Jawab
Terms
Plafon Piutang
Kontak Finance
{customers.map((c) => (
{c.name}
{c.npwp}
{c.address}
{c.pic}
P.J: {c.responsible}
{c.top}
{formatRp(c.creditLimit)}
{c.phoneFinance}
{c.emailFinance}
))}
)}
{}
{activeTab === 'suppliers' && (
currentUser.role === 'management' ? (
Master Data Supplier (Prinsipal / Pabrik)
Strictly Confidential
Rahasia Dagang Pratachem • Tersembunyi Total dari Akun Admin Kantor & Tim Gudang
setModalType('new-supplier')}
className="bg-amber-600 hover:bg-amber-700 text-white px-3.5 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shadow"
>
Tambah Supplier Rahasia
Ketentuan Proteksi Data Pemasok (PRD Bab 02 & Bab 09):
Data prinsipal, kontak representatif pabrik, dan rekening vendor adalah rahasia operasional manajemen. Pengguna dengan hak akses Admin Kantor dan Tim Gudang hanya dapat memilih nama vendor di formulir PO tanpa dapat melihat direktori atau profil lengkap ini.
Direktori Prinsipal & Pabrik Rekanan
Total: {suppliers.length} Supplier
Nama Pabrik / Supplier
NPWP Pemasok
Alamat Pabrik / Kantor
PIC Representative
TOP
Email & No. HP Finance
{suppliers.map((s) => (
{s.name}
{s.npwp}
{s.address}
{s.pic}
P.J: {s.responsible}
{s.top}
{s.phoneFinance}
{s.emailFinance}
))}
) : (
AKSES DITOLAK: STRICTLY CONFIDENTIAL
Berdasarkan ketentuan PRD Bab 02 & Bab 09, modul Master Supplier memuat rahasia dagang perusahaan dan hanya dapat diakses oleh akun Management (Direksi & Owner) .
)
)}
{}
{activeTab === 'inbound' && (
Inbound & Penerbitan PO Resmi Supplier
Format PO-PGS Cetak Canon MG2500 (A4) • Penerimaan Batch FIFO & Verifikasi Fisik
setModalType('receive-inflow')}
className="bg-emerald-600 hover:bg-emerald-700 text-white px-3.5 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shadow"
>
Terima Barang (FIFO Inflow)
{currentUser.role !== 'gudang' && (
setModalType('new-po')}
className="bg-blue-600 hover:bg-blue-700 text-white px-3.5 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shadow"
>
Buat PO Supplier Baru
)}
Daftar PO-PGS ke Pabrik Kimia
Rumus: PO-PGS/[Supplier]/[Romawi]-[Tgl]/[Tahun]/[No]
No. PO Resmi
Tanggal
Supplier
Barang & Volume
Total (+11% PPN)
Status
Aksi Dokumen
{supplierPOs.map((po) => (
{po.poNumber}
{po.date}
{po.supplier}
{po.chemName}
{po.packageCount} Kemasan ({po.totalKg} Kg)
{formatRp(po.grandTotal)}
{po.status}
setPrintDoc({ type: 'po', data: po })}
className="px-2.5 py-1.5 rounded-lg border border-slate-300 bg-white hover:bg-slate-100 text-slate-700 font-semibold text-xs flex items-center space-x-1 mx-auto"
>
Cetak A4
))}
)}
{}
{activeTab === 'sales' && currentUser.role !== 'gudang' && (
Penawaran Harga & Kunci Stok (Booked)
PO Customer Mengunci Free Stok • Mencegah Terjadinya Overselling Antartim Sales
setModalType('new-quotation')}
className="bg-blue-600 hover:bg-blue-700 text-white px-3.5 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shadow"
>
Buat Penawaran / Booking Baru
Daftar Dokumen Penawaran (Quo-PGS)
Rumus: Quo-PGS/[Customer]/[Romawi]-[Tgl]/[Tahun]/[No]
No. Penawaran
Customer & PO Ref
Komoditas Kimia
Kuantiti Booked
Status Stok
Tindakan Logistik
{quotations.map((quo) => (
{quo.quoNumber}
{quo.customer}
Ref: {quo.customerPO}
{quo.chemName}
{quo.packageCount} Wadah ({quo.totalKg} Kg)
{quo.totalKg} Kg
{quo.status === 'Shipped' ? 'Surat Jalan Terbit' : 'Terkunci (Booked)'}
{quo.dispatchId ? (
Surat Jalan Terbit
) : (
{
setActiveQuoForDispatch(quo);
setModalType('dispatch-deliveree');
}}
className="px-2.5 py-1.5 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-semibold text-xs inline-flex items-center space-x-1"
>
Dispatch Deliveree
)}
))}
)}
{}
{activeTab === 'deliveree' && (
Dispatch Deliveree & Surat Jalan Continuous Form
Potong Stok Fisik FIFO saat Validasi Muat • Rekap POD Manual Teks (Tanpa File Upload) • Cetak Khusus EPSON LX-300+
Antrean Surat Jalan & Ekspedisi Deliveree
Rumus: SJ-PGS/[Customer]/[Romawi]-[Tgl]/[Tahun]/[No]
No. Surat Jalan
Customer & PO
Armada & Supir
Barang & Volume
Status Kirim
Konfirmasi POD Manual
Aksi / Cetak
{dispatches.map((dsp) => (
{dsp.sjNumber}
{dsp.customer}
PO: {dsp.customerPO}
{dsp.driverName}
{dsp.nopol} • Telp: {dsp.driverPhone}
{dsp.chemName}
{dsp.packageCount} {dsp.packageType} ({dsp.totalKg} Kg)
{dsp.status}
{dsp.status === 'Delivered' ? (
Diterima: {dsp.podReceiver}
{dsp.podCompletionTime}
) : (
{
setActiveDispatchForPod(dsp);
setModalType('confirm-pod');
}}
className="px-2.5 py-1 bg-amber-50 text-amber-800 hover:bg-amber-100 border border-amber-300 rounded-lg text-xs font-semibold"
>
Rekap POD Teks
)}
setPrintDoc({ type: 'sj', data: dsp })}
className="px-2.5 py-1.5 rounded-lg border border-slate-300 bg-white hover:bg-slate-100 text-slate-700 font-semibold text-xs inline-flex items-center space-x-1"
>
Cetak NCR
{dsp.status === 'Delivered' && !dsp.invoiceId && currentUser.role !== 'gudang' && (
{
setActiveDispatchForInv(dsp);
setModalType('generate-invoice');
}}
className="px-2.5 py-1.5 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-semibold text-xs"
>
Terbitkan Invoice
)}
))}
)}
{}
{activeTab === 'invoices' && currentUser.role !== 'gudang' && (
Invoicing & Faktur Pajak Ditjen Pajak
Diterbitkan Otomatis dari Surat Jalan Selesai • PPN 11% • Rekening Resmi BCA 308-1962-888
Rekapitulasi Tagihan Customer (INV-PGS)
Rumus: INV-PGS/[Customer]/[Romawi]-[Tgl]/[Tahun]/[No]
No. Invoice Tagihan
Ref. Surat Jalan
Customer & Terms
No. Faktur Pajak (16 Digit)
Subtotal DPP
Total (+11% PPN)
Status
Aksi / Cetak
{invoices.map((inv) => (
{inv.invNumber}
{inv.sjNumber}
{inv.customer}
{inv.terms} • Jt. Tempo: {inv.dueDate}
{inv.fakturPajak}
{formatRp(inv.subtotal)}
{formatRp(inv.grandTotal)}
{inv.status}
setPrintDoc({ type: 'inv', data: inv })}
className="px-2.5 py-1.5 rounded-lg border border-slate-300 bg-white hover:bg-slate-100 text-slate-700 font-semibold text-xs flex items-center space-x-1 mx-auto"
>
Cetak A4
))}
)}
{}
{activeTab === 'logbook' && (
Buku Transaksi Harian Gudang (Audit Trail Gerbang)
Catatan Masuk & Keluar Fisik Gerbang Real-Time: Supir, Plat Nomor, dan Kondisi Kemasan
setModalType('manual-log')}
className="bg-blue-600 hover:bg-blue-700 text-white px-3.5 py-2 rounded-xl text-xs font-bold transition flex items-center space-x-1.5 shadow"
>
Catat Mutasi Gerbang
Tipe Mutasi
Waktu Transaksi
Armada & Supir
Nama & Kode Barang
Kuantiti Kemasan
Catatan Kondisi Fisik
Petugas
{logbook.map((lg) => (
{lg.type.toUpperCase()}
{lg.time}
{lg.driverInfo}
{lg.chemInfo}
{lg.qty}
{lg.condition}
{lg.officer}
))}
)}
{}
{selectedBatchChem && (
{selectedBatchChem.sku}
{selectedBatchChem.name}
Kemasan: {selectedBatchChem.packageType} @ {selectedBatchChem.weightPerUnit} Kg • Asal: {selectedBatchChem.origin}
setSelectedBatchChem(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
Prinsip Strict FIFO: Pemotongan barang saat pengiriman Deliveree secara otomatis diprioritaskan dari nomor batch dengan tanggal penerimaan tertua terlebih dahulu.
No. Batch / Lot
Tgl Masuk (Urutan FIFO)
Expired Date
Wadah
Sisa Stok (Kg)
{(selectedBatchChem.batches || []).map((b, idx) => (
{b.lotNumber}
{b.inflowDate} {idx === 0 && Prioritas #1 }
{b.expDate}
{b.pkgCount} Kemasan
{b.remainingKg} Kg
))}
setSelectedBatchChem(null)} className="px-4 py-2 bg-slate-800 text-white rounded-xl text-xs font-semibold hover:bg-slate-700">
Tutup Drill-Down
)}
{}
{modalType === 'new-chemical' && (
Tambah Komoditas Bahan Kimia Baru
Penomoran SKU Cerdas Otomatis sesuai Kaidah Pratachem
setModalType(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
)}
{}
{modalType === 'receive-inflow' && (
Penerimaan Barang Masuk (Stok In)
Pencatatan Batch, Kadaluwarsa, dan Audit Fisik Gerbang
setModalType(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
)}
{}
{modalType === 'new-po' && (
Penerbitan PO Resmi ke Supplier
Pilih Supplier Rekanan • Format PO-PGS Cetak Canon MG2500
setModalType(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
)}
{}
{modalType === 'new-quotation' && (
Penawaran & Penguncian Stok (Booked)
Mencegah Overselling • Otomatis Mengurangi Free Stok
setModalType(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
)}
{}
{modalType === 'dispatch-deliveree' && activeQuoForDispatch && (
Dispatch Deliveree & Potong Stok FIFO
Mencocokkan Identitas Armada dan Mengurangi Lapisan Batch Tertua
setModalType(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
)}
{}
{modalType === 'confirm-pod' && activeDispatchForPod && (
Rekap Selesai Pengiriman (POD Teks)
Aturan PRD: Murni Pencatatan Manual Tanpa Upload File
setModalType(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
)}
{}
{modalType === 'generate-invoice' && activeDispatchForInv && (
Terbitkan Invoice & Faktur Pajak 16 Digit
Standar Ditjen Pajak • Cetak Rekening Resmi BCA 308-1962-888
setModalType(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
)}
{}
{modalType === 'new-customer' && (
Registrasi Master Customer (Pelanggan)
Plafon Piutang & Identitas Resmi Faktur Pajak
setModalType(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
)}
{}
{modalType === 'new-supplier' && currentUser.role === 'management' && (
Tambah Supplier Baru (Rahasia Dagang)
Khusus Manajemen Pratachem
setModalType(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
{
e.preventDefault();
const form = e.target;
const newS = {
id: 'supp-' + Date.now(),
name: form.name.value.trim().toUpperCase(),
address: form.address.value.trim(),
npwp: form.npwp.value.trim(),
pic: form.pic.value.trim(),
responsible: form.responsible.value.trim(),
top: form.top.value,
emailFinance: form.emailFinance.value.trim(),
phoneFinance: form.phoneFinance.value.trim()
};
setSuppliers([...suppliers, newS]);
setModalType(null);
showToast(`Supplier ${newS.name} berhasil disimpan secara aman`);
}}
className="p-5 overflow-y-auto space-y-3.5 text-xs"
>
Nama Pabrik / Supplier (Uppercase)
Alamat Kantor / Pabrik
setModalType(null)} className="px-4 py-2 border border-slate-300 rounded-xl font-semibold hover:bg-slate-100">Batal
Simpan Supplier Rahasia
)}
{}
{modalType === 'manual-log' && (
)}
{}
{modalType === 'supabase' && (
Koneksi Supabase Tokyo (AWS ap-northeast-1)
PRD Bab 01 & 11 • Biaya Operasional Bulanan Rp0 (Gratis Selamanya)
setModalType(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
Status Database Cloud Tokyo:
Terhubung & Sinkron Aktif
Keamanan Data Terjamin:
Data database tersimpan aman di Supabase PostgreSQL Tokyo. Seluruh catatan transaksi perusahaan tidak akan pernah hilang meskipun domain web mengalami kendala.
{
setModalType(null);
showToast('Konfigurasi Supabase berhasil diperbarui');
}}
className="px-4 py-2 bg-blue-600 text-white rounded-xl text-xs font-semibold hover:bg-blue-700"
>
Tutup Pengaturan
)}
{}
{printDoc && (
{printDoc.type === 'sj'
? 'Format Cetak EPSON LX-300+ Continuous Form NCR (8.23 in x 5.47 in)'
: printDoc.type === 'po'
? 'Format Cetak Canon MG2500 HVS A4 - Purchase Order Resmi'
: 'Format Cetak Canon MG2500 HVS A4 - Invoice & Faktur Pajak'}
Hardware Target: EPSON LX-300+ & Canon MG2500
window.print()}
className="px-4 py-1.5 bg-blue-600 hover:bg-blue-700 text-white font-bold text-xs rounded-xl shadow flex items-center space-x-1"
>
Kirim ke Mesin Printer
setPrintDoc(null)} className="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg">
{printDoc.type === 'sj' && (
PT. PRATACHEM GLOBALINDO SUKSES
Chemical Raw Materials Trading & Distribution
Grand Slipi Tower Lt. 9, Jl. S. Parman, Jakarta Barat
Telp: (021) 5366-8888 • Email: sales@pratachem.com
SURAT JALAN PENGIRIMAN
NO: {printDoc.data.sjNumber}
Tgl: {new Date().toLocaleDateString('id-ID')}
KEPADA YTH:
{printDoc.data.customer}
Alamat Kirim: {printDoc.data.address}
Ref PO Cust: {printDoc.data.customerPO}
EKSPEDISI / ARMADA PENGIRIM:
Penyedia: Deliveree Online Trucking
Nama Supir: {printDoc.data.driverName}
No. Polisi Plat: {printDoc.data.nopol}
No. Telp Supir: {printDoc.data.driverPhone}
NO
NAMA BAHAN BAKU KIMIA
WADAH
BERAT (KG)
1
{printDoc.data.chemName}
{printDoc.data.packageCount} {printDoc.data.packageType}
{printDoc.data.totalKg} Kg
* Catatan: Mohon periksa segel dan kondisi fisik barang saat diterima. Klaim setelah supir meninggalkan lokasi tidak berlaku.
Pengirim (Gudang PGS):
({currentUser.name})
Supir Ekspedisi:
({printDoc.data.driverName})
Tanda Terima Customer:
(Nama Jelas & Cap Pabrik)
)}
{printDoc.type === 'po' && (
PT. PRATACHEM GLOBALINDO SUKSES
Industrial & Food Chemical Raw Materials
Grand Slipi Tower Lt. 9, Jakarta Barat • Telp: (021) 5366-8888
PURCHASE ORDER
{printDoc.data.poNumber}
Tanggal: {printDoc.data.date}
Pemasok / Vendor:
{printDoc.data.supplier}
Status: Vendor Rekanan Resmi
Alamat Pengiriman (Gudang):
Gudang Pratachem - Pondok Bahar
Jl. H. Mansyur, Tangerang, Banten
Deskripsi Komoditas
Kuantiti
Harga / Kg
Subtotal
{printDoc.data.chemName}
{printDoc.data.packageCount} Kemasan ({printDoc.data.totalKg} Kg)
{formatRp(printDoc.data.pricePerKg)}
{formatRp(printDoc.data.subtotal)}
Subtotal DPP:
{formatRp(printDoc.data.subtotal)}
PPN 11%:
{formatRp(printDoc.data.ppn)}
Grand Total:
{formatRp(printDoc.data.grandTotal)}
Catatan Operasional Gudang: Harap menyertakan COA (Certificate of Analysis) dan MSDS fisik pada saat serah terima barang di Gudang Tangerang.
Diterima & Disetujui Vendor:
({printDoc.data.supplier})
Hormat Kami,
PT. Pratachem Globalindo Sukses
Cia Liu (Assistant Director)
)}
{printDoc.type === 'inv' && (
PT. PRATACHEM GLOBALINDO SUKSES
Chemical Raw Materials Trading & Export
Grand Slipi Tower Lt. 9, Jakarta Barat • Telp: (021) 5366-8888
FAKTUR PENJUALAN
{printDoc.data.invNumber}
Tanggal: {printDoc.data.date}
Tagihan Ditujukan Kepada:
{printDoc.data.customer}
Ref PO Cust: {printDoc.data.customerPO}
Ref Surat Jalan: {printDoc.data.sjNumber}
Ketentuan Pajak & Pembayaran:
e-Faktur: {printDoc.data.fakturPajak}
Terms: {printDoc.data.terms}
Jatuh Tempo: {printDoc.data.dueDate}
Keterangan Barang
Volume (Kg)
Harga Satuan
Total DPP
{printDoc.data.chemName || 'Bahan Kimia Industri / Food Grade'}
{printDoc.data.totalKg} Kg
{formatRp(printDoc.data.pricePerKg)}
{formatRp(printDoc.data.subtotal)}
Dasar Pengenaan Pajak (DPP):
{formatRp(printDoc.data.subtotal)}
PPN Ditjen Pajak 11%:
{formatRp(printDoc.data.ppn)}
Total Tagihan:
{formatRp(printDoc.data.grandTotal)}
Instruksi Pembayaran Resmi:
Pembayaran mohon ditransfer penuh ke rekening resmi perusahaan:
Bank Central Asia (BCA) - No. Rekening: 308-1962-888
Atas Nama: PT. PRATACHEM GLOBALINDO SUKSES
Dokumen tagihan ini sah dan diproses otomatis oleh ERP Pratachem.
Jakarta, {printDoc.data.date}
PT. PRATACHEM GLOBALINDO SUKSES
(Finance & Accounting)
)}
)}
{}
{appDialog && (
{appDialog.isConfirm ? : }
{appDialog.title}
{appDialog.message}
{appDialog.isConfirm ? (
<>
Batal
{
if (typeof appDialog.onConfirm === 'function') {
appDialog.onConfirm();
}
closeDialog();
}}
className="px-3.5 py-1.5 bg-blue-600 hover:bg-blue-700 text-white rounded-xl text-xs font-semibold shadow"
>
Konfirmasi
>
) : (
OK
)}
)}
{toast && (
{toast.message}
)}
);
}