feat: add example components and system pages for UI showcase
- Introduced HeaderExample component for header display. - Created StyleGuideOne, StyleGuideTwo, and StyleGuideThree components showcasing various UI elements and layouts. - Added system pages: ComingSoon, Forbidden, Maintenance, and NotFound for better user experience during different states. - Updated index.ts to export new components for easier access.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { NotFound, Forbidden, Maintenance, ComingSoon } from '@repo/ui';
|
||||
|
||||
const AuthModule = lazy(() => import('./auth'));
|
||||
const AppModule = lazy(() => import('./modules'));
|
||||
@@ -11,8 +12,10 @@ export default function App() {
|
||||
<Routes>
|
||||
<Route path="/auth/*" element={<AuthModule />} />
|
||||
<Route path="/app/*" element={<AppModule />} />
|
||||
{/* <Route path="/404" element={<NotFoundPage />} />
|
||||
<Route path="/403" element={<ForbiddenPage />} /> */}
|
||||
<Route path="/404" element={<NotFound />} />
|
||||
<Route path="/403" element={<Forbidden />} />
|
||||
<Route path="/maintenance" element={<Maintenance />} />
|
||||
<Route path="/coming-soon" element={<ComingSoon />} />
|
||||
<Route path="*" element={<Navigate to="/app" />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { DateServiceComponent } from '@repo/ui';
|
||||
import { DateServiceExample, StyleGuideOne, StyleGuideTree, StyleGuideTwo } from '@repo/ui';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
|
||||
export default function AppModule() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/example/*" element={<DateServiceComponent />} />
|
||||
<Route path="/example/*" element={<DateServiceExample />} />
|
||||
<Route path="/guide-1/*" element={<StyleGuideOne />} />
|
||||
<Route path="/guide-2/*" element={<StyleGuideTwo />} />
|
||||
<Route path="/guide-3/*" element={<StyleGuideTree />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export const Counter: React.FC = () => {
|
||||
export const CounterExample: React.FC = () => {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { DateService } from '@repo/utils';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
|
||||
export const DateServiceComponent: React.FC = () => {
|
||||
export const DateServiceExample: React.FC = () => {
|
||||
// --- 0. TIMEZONE CONFIGURATION (ROOT CONTROL) ---
|
||||
const [currentZone, setCurrentZone] = useState<string>(DateService.getGlobalTimezone());
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { EncryptionService } from '@repo/utils';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export const EncryptionDemo: React.FC = () => {
|
||||
export const EncryptionExample: React.FC = () => {
|
||||
// --- 1. SETUP INSTANCE ---
|
||||
// Kita gunakan Singleton agar hemat memory
|
||||
const cryptoService = EncryptionService.getInstance();
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
interface HeaderProps {
|
||||
interface HeaderExampleProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const Header: React.FC<HeaderProps> = ({ title }) => {
|
||||
export const HeaderExample: React.FC<HeaderExampleProps> = ({ title }) => {
|
||||
return (
|
||||
<header id="header">
|
||||
<h1>{title}</h1>
|
||||
@@ -0,0 +1,208 @@
|
||||
import React from 'react';
|
||||
|
||||
// --- SHARED SUB-COMPONENTS ---
|
||||
const Card = ({
|
||||
title,
|
||||
children,
|
||||
className = '',
|
||||
}: {
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) => (
|
||||
<div className={`bg-white border border-gray-200 rounded-lg shadow-sm ${className}`}>
|
||||
{title && (
|
||||
<div className="px-4 py-3 border-b border-gray-100 bg-gray-50/50">
|
||||
<h3 className="text-lg font-semibold text-gray-800">{title}</h3>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-5">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Badge = ({
|
||||
children,
|
||||
variant = 'info',
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
variant?: 'success' | 'error' | 'warning' | 'info';
|
||||
}) => {
|
||||
const styles = {
|
||||
success: 'bg-green-50 text-green-600 border-green-100',
|
||||
error: 'bg-red-50 text-red-600 border-red-100',
|
||||
warning: 'bg-amber-50 text-amber-600 border-amber-100',
|
||||
info: 'bg-brand-50 text-brand-600 border-brand-100',
|
||||
};
|
||||
return (
|
||||
<span className={`px-2 py-0.5 rounded-sm text-xs font-bold border ${styles[variant]} uppercase tracking-wider`}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
// --- MAIN PAGE COMPONENT ---
|
||||
export function StyleGuideOne() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 text-gray-700 font-sans">
|
||||
{/* 1. OBJECT HEADER (SAP Pattern) */}
|
||||
<div className="bg-white border-b border-gray-200 px-8 py-6 sticky top-0 z-10 shadow-sm">
|
||||
<div className="max-w-7xl mx-auto flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div>
|
||||
<nav className="text-xs font-bold text-brand-600 uppercase tracking-widest mb-1">
|
||||
Procurement / Purchase Orders
|
||||
</nav>
|
||||
<h1 className="text-xl font-bold text-gray-900 flex items-center gap-3">
|
||||
PO #4500019283
|
||||
<Badge variant="success">Released</Badge>
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button className="px-4 py-1.5 border border-gray-300 rounded-md bg-white hover:bg-gray-50 font-medium transition-all shadow-sm">
|
||||
Reject
|
||||
</button>
|
||||
<button className="px-4 py-1.5 bg-brand-500 text-white rounded-md hover:bg-brand-600 font-medium transition-all shadow-sm">
|
||||
Approve Order
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto p-6 space-y-8">
|
||||
{/* 2. ANALYTICS / KPI SECTION */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: 'Total Value', val: '$12,450.00', color: 'text-gray-900' },
|
||||
{ label: 'Items Count', val: '24 SKUs', color: 'text-gray-900' },
|
||||
{ label: 'Days Overdue', val: '5 Days', color: 'text-red-500' },
|
||||
{ label: 'Vendor Rating', val: '4.8 / 5.0', color: 'text-green-600' },
|
||||
].map((kpi, i) => (
|
||||
<div key={i} className="bg-white p-4 rounded-lg border border-gray-200 shadow-sm">
|
||||
<p className="text-xs font-bold text-gray-500 uppercase tracking-wider">{kpi.label}</p>
|
||||
<p className={`text-xl font-bold mt-1 ${kpi.color}`}>{kpi.val}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* LEFT: FORM & CONTROLS */}
|
||||
<div className="lg:col-span-1 space-y-6">
|
||||
<Card title="General Information">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-medium text-gray-500">Vendor Name</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
className="bg-gray-50 border border-gray-200 rounded-md px-3 py-1.5 text-gray-600 outline-none cursor-not-allowed"
|
||||
defaultValue="Steel Global Corp"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-medium text-gray-500">Shipping Method</label>
|
||||
<select className="border border-gray-300 rounded-md px-3 py-1.5 bg-white focus:ring-1 focus:ring-brand-500 outline-none">
|
||||
<option>Sea Freight - Standard</option>
|
||||
<option>Air Freight - Express</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-medium text-gray-500">Order Priority</label>
|
||||
<div className="flex gap-4 mt-1">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="prio" className="accent-brand-500" />{' '}
|
||||
<span className="text-base">Normal</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="prio" className="accent-brand-500" defaultChecked />{' '}
|
||||
<span className="text-base">High</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="Order Status Logs">
|
||||
<ul className="space-y-4">
|
||||
{[
|
||||
{ time: '10:00 AM', msg: 'Order Created', user: 'System' },
|
||||
{ time: '11:30 AM', msg: 'Manager Approved', user: 'Budi Santoso' },
|
||||
].map((log, i) => (
|
||||
<li key={i} className="flex gap-3 border-l-2 border-brand-200 pl-4 relative">
|
||||
<div className="absolute w-2.5 h-2.5 bg-brand-500 rounded-full -left-[6px] top-1"></div>
|
||||
<div>
|
||||
<p className="text-base font-medium text-gray-800">{log.msg}</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
{log.time} • By {log.user}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: DATA TABLE (The Core of ERP) */}
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="!p-0 overflow-hidden">
|
||||
<div className="p-4 border-b border-gray-100 flex justify-between items-center">
|
||||
<h3 className="text-lg font-semibold text-gray-800">Line Items</h3>
|
||||
<div className="flex gap-2">
|
||||
<button className="text-xs font-bold text-brand-600 hover:bg-brand-50 px-2 py-1 rounded">
|
||||
EXPORT CSV
|
||||
</button>
|
||||
<button className="text-xs font-bold text-brand-600 hover:bg-brand-50 px-2 py-1 rounded">
|
||||
PRINT PO
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-gray-100/80">
|
||||
<th className="px-4 py-2 text-xs font-bold text-gray-500 uppercase">Material</th>
|
||||
<th className="px-4 py-2 text-xs font-bold text-gray-500 uppercase">Description</th>
|
||||
<th className="px-4 py-2 text-xs font-bold text-gray-500 uppercase text-right">Qty</th>
|
||||
<th className="px-4 py-2 text-xs font-bold text-gray-500 uppercase text-right">Price</th>
|
||||
<th className="px-4 py-2 text-xs font-bold text-gray-500 uppercase text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-100 text-base">
|
||||
{[1, 2, 3, 4, 5].map((item) => (
|
||||
<tr key={item} className="hover:bg-brand-50/20 transition-all cursor-pointer group">
|
||||
<td className="px-4 py-2 font-mono text-brand-600 group-hover:underline">M-1000{item}</td>
|
||||
<td className="px-4 py-2 text-gray-800">
|
||||
Industrial Steel Pipe 2.5" - Grade {item === 2 ? 'B' : 'A'}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-right">100.00</td>
|
||||
<td className="px-4 py-2 text-right">150.00</td>
|
||||
<td className="px-4 py-2 text-right font-semibold text-gray-900">15,000.00</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
<tfoot className="bg-gray-50/80 font-bold border-t-2 border-gray-200">
|
||||
<tr>
|
||||
<td colSpan={4} className="px-4 py-3 text-right text-gray-500 uppercase text-xs tracking-widest">
|
||||
Grand Total (Net)
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right text-brand-600 text-lg">$ 75,000.00</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="mt-4 bg-amber-50 border border-amber-100 rounded-lg p-4 flex gap-3">
|
||||
<span className="text-xl">⚠️</span>
|
||||
<div>
|
||||
<p className="text-base font-bold text-amber-800">Budget Warning</p>
|
||||
<p className="text-sm text-amber-700">
|
||||
This purchase order exceeds the quarterly budget for Department "Steel Fabrication" by 12%.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
// ==========================================================
|
||||
// 1. ATOMIC COMPONENTS (Berdasar Config 13px & Burgundy)
|
||||
// ==========================================================
|
||||
|
||||
const Card = ({ title, children, className = '', footer }: any) => (
|
||||
<div className={`bg-white border border-gray-200 rounded-lg shadow-sm overflow-hidden ${className}`}>
|
||||
{title && (
|
||||
<div className="px-4 py-3 border-b border-gray-100 bg-gray-50/50 flex justify-between items-center">
|
||||
<h3 className="text-lg font-semibold text-gray-800 tracking-tight">{title}</h3>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-5 text-base">{children}</div>
|
||||
{footer && <div className="px-4 py-3 bg-gray-50 border-t border-gray-100">{footer}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Button = ({ children, variant = 'primary', className = '', ...props }: any) => {
|
||||
const styles: any = {
|
||||
primary: 'bg-brand-500 text-white hover:bg-brand-600 shadow-sm border border-brand-600',
|
||||
secondary: 'bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 shadow-xs',
|
||||
ghost: 'text-gray-600 hover:bg-gray-100',
|
||||
danger: 'bg-red-500 text-white hover:bg-red-600',
|
||||
};
|
||||
return (
|
||||
<button
|
||||
className={`px-3 py-1.5 rounded-md font-medium text-base transition-all active:scale-95 disabled:opacity-50 ${styles[variant]} ${className}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const Badge = ({ children, variant = 'info' }: any) => {
|
||||
const styles: any = {
|
||||
success: 'bg-green-50 text-green-600 border-green-100',
|
||||
error: 'bg-red-50 text-red-600 border-red-100',
|
||||
warning: 'bg-amber-50 text-amber-600 border-amber-100',
|
||||
info: 'bg-brand-50 text-brand-700 border-brand-100',
|
||||
};
|
||||
return (
|
||||
<span className={`px-2 py-0.5 rounded-sm text-xs font-bold border ${styles[variant]} uppercase tracking-wider`}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
// ==========================================================
|
||||
// 2. MAIN PAGE: ADVANCED STYLE GUIDE
|
||||
// ==========================================================
|
||||
|
||||
export function StyleGuideTwo() {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen bg-gray-50 font-sans text-gray-800">
|
||||
{/* SIDE NAVIGATION */}
|
||||
<aside className="w-64 bg-brand-950 text-white flex flex-col sticky top-0 h-screen shadow-xl shrink-0">
|
||||
<div className="p-6 border-b border-brand-900 flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-brand-500 rounded-lg flex items-center justify-center font-bold text-xl shadow-inner">
|
||||
B
|
||||
</div>
|
||||
<span className="font-bold text-lg tracking-tight">BURGUNDY_ERP</span>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 p-4 space-y-1">
|
||||
<p className="text-brand-300 text-[10px] font-bold uppercase tracking-widest px-2 mb-2 opacity-60">
|
||||
Finance Modules
|
||||
</p>
|
||||
<div className="flex items-center gap-3 px-3 py-2 rounded-md bg-brand-500 text-white shadow-lg cursor-pointer">
|
||||
<span>📓</span> <span className="text-base font-medium">General Ledger</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 px-3 py-2 rounded-md hover:bg-brand-900 text-brand-100 cursor-pointer transition-colors">
|
||||
<span>📊</span> <span className="text-base font-medium">Cost Analysis</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 px-3 py-2 rounded-md hover:bg-brand-900 text-brand-100 cursor-pointer transition-colors">
|
||||
<span>🏦</span> <span className="text-base font-medium">Bank Reconciliation</span>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
{/* MAIN CONTENT AREA */}
|
||||
<main className="flex-1 flex flex-col min-w-0">
|
||||
{/* TOP BAR */}
|
||||
<header className="h-14 bg-white border-b border-gray-200 flex items-center justify-between px-8 shrink-0 shadow-sm">
|
||||
<div className="text-sm text-gray-500 font-medium">
|
||||
Master Data <span className="mx-2">/</span>{' '}
|
||||
<span className="text-gray-900 font-bold">Chart of Accounts</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-8 h-8 rounded-full bg-brand-100 border border-brand-200 flex items-center justify-center text-brand-700 font-bold text-xs">
|
||||
JD
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="p-8 space-y-8 overflow-y-auto">
|
||||
{/* HEADER SECTION */}
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">Account Overview: 110200</h2>
|
||||
<p className="text-base text-gray-500 mt-1">
|
||||
Domestic Trade Accounts Receivable •{' '}
|
||||
<span className="text-brand-600 font-semibold italic">Local Currency (IDR)</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" onClick={() => window.print()}>
|
||||
Print Statement
|
||||
</Button>
|
||||
<Button onClick={() => setIsModalOpen(true)}>Create Entry</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
|
||||
{/* LEFT: MAIN DATA TABLE */}
|
||||
<div className="xl:col-span-2 space-y-6">
|
||||
<Card title="Recent Transactions">
|
||||
<div className="overflow-x-auto -mx-5 -my-5">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-gray-50 border-b border-gray-100">
|
||||
<th className="px-5 py-3 text-xs font-bold text-gray-500 uppercase">Document</th>
|
||||
<th className="px-5 py-3 text-xs font-bold text-gray-500 uppercase">Partner</th>
|
||||
<th className="px-5 py-3 text-xs font-bold text-gray-500 uppercase text-right">Debit</th>
|
||||
<th className="px-5 py-3 text-xs font-bold text-gray-500 uppercase text-right">Credit</th>
|
||||
<th className="px-5 py-3 text-xs font-bold text-gray-500 uppercase text-center">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-50">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<tr key={i} className="hover:bg-brand-50/20 text-base transition-colors group">
|
||||
<td className="px-5 py-3 font-mono text-brand-700 font-semibold">INV-2026-00{i}</td>
|
||||
<td className="px-5 py-3">
|
||||
<p className="font-medium text-gray-800">Global Tech Solutions</p>
|
||||
<p className="text-xs text-gray-400">Vendor ID: V-9920</p>
|
||||
</td>
|
||||
<td className="px-5 py-3 text-right font-semibold">$ {i * 125}.00</td>
|
||||
<td className="px-5 py-3 text-right text-gray-300">0.00</td>
|
||||
<td className="px-5 py-3 text-center">
|
||||
<Badge variant={i % 2 === 0 ? 'success' : 'warning'}>
|
||||
{i % 2 === 0 ? 'Cleared' : 'Pending'}
|
||||
</Badge>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Card title="Budget Utilization">
|
||||
<div className="space-y-4">
|
||||
<div className="w-full bg-gray-100 rounded-full h-3">
|
||||
<div className="bg-brand-500 h-3 rounded-full" style={{ width: '75%' }}></div>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-500 font-medium italic underline decoration-brand-200 underline-offset-4">
|
||||
Spent: $75,000.00
|
||||
</span>
|
||||
<span className="text-gray-900 font-bold">Total: $100,000.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card title="Quick Insight">
|
||||
<p className="text-gray-600 leading-relaxed italic">
|
||||
"Anomali terdeteksi pada baris INV-2026-003. Nilai transaksi melebihi rata-rata bulanan sebesar
|
||||
40%."
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: CONFIGURATION & ACTIONS */}
|
||||
<div className="xl:col-span-1 space-y-6">
|
||||
<Card title="Account Settings" footer={<Button className="w-full">Update Control</Button>}>
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-bold text-gray-500 uppercase tracking-tighter italic">
|
||||
Reconciliation Type
|
||||
</label>
|
||||
<select className="w-full border border-gray-300 rounded-md px-3 py-1.5 bg-white focus:ring-1 focus:ring-brand-500 outline-none">
|
||||
<option>Automatic Match</option>
|
||||
<option>Manual Review</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-sm font-bold text-gray-500 uppercase tracking-tighter italic">
|
||||
Tax Code
|
||||
</label>
|
||||
<input
|
||||
className="w-full border border-gray-300 rounded-md px-3 py-1.5 outline-none focus:border-brand-500 transition-colors"
|
||||
placeholder="e.g. VAT_11"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 py-2 border-t border-gray-50">
|
||||
<input type="checkbox" className="w-4 h-4 accent-brand-500" id="lock" />
|
||||
<label htmlFor="lock" className="text-sm font-semibold text-gray-700">
|
||||
Lock account for posting
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="bg-brand-900 text-white p-5 rounded-lg shadow-lg relative overflow-hidden">
|
||||
<div className="relative z-10">
|
||||
<h4 className="text-lg font-bold mb-1 italic">Premium Support</h4>
|
||||
<p className="text-sm text-brand-100 opacity-80">
|
||||
Butuh bantuan rekonsiliasi? Hubungi konsultan finansial kami.
|
||||
</p>
|
||||
<button className="mt-4 text-xs font-bold bg-white text-brand-900 px-3 py-1.5 rounded uppercase tracking-widest shadow-lg">
|
||||
Chat Konsultan
|
||||
</button>
|
||||
</div>
|
||||
<div className="absolute -right-4 -bottom-4 text-6xl opacity-10 font-bold">ERP</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* 3. SAMPLE MODAL (Overlay) */}
|
||||
{isModalOpen && (
|
||||
<div className="fixed inset-0 bg-brand-950/40 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white rounded-xl shadow-2xl w-full max-w-lg overflow-hidden border border-gray-200">
|
||||
<div className="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
|
||||
<h3 className="text-xl font-bold text-gray-900">New Journal Entry</h3>
|
||||
<button onClick={() => setIsModalOpen(false)} className="text-gray-400 hover:text-gray-600 text-2xl">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<p className="text-base text-gray-600">
|
||||
Silakan masukkan detail entri jurnal baru untuk tahun fiskal 2026.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="col-span-2 border border-brand-100 bg-brand-50/50 p-3 rounded-md italic text-xs text-brand-800">
|
||||
Info: Pastikan periode pembukuan Januari sudah dibuka sebelum menekan "Post".
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-6 py-4 bg-gray-50 border-t border-gray-100 flex justify-end gap-2">
|
||||
<Button variant="secondary" onClick={() => setIsModalOpen(false)}>
|
||||
Discard
|
||||
</Button>
|
||||
<Button onClick={() => setIsModalOpen(false)}>Post Entry</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import React from 'react';
|
||||
|
||||
// ==========================================================
|
||||
// COMPACT UI COMPONENTS
|
||||
// ==========================================================
|
||||
|
||||
const SectionTitle = ({ children }: { children: React.ReactNode }) => (
|
||||
<h3 className="text-[11px] font-bold text-gray-500 uppercase tracking-widest mb-2 border-b border-gray-200 pb-1 italic">
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
|
||||
const CompactCard = ({ title, children, footer }: any) => (
|
||||
<div className="bg-white border border-gray-200 rounded shadow-sm flex flex-col">
|
||||
{title && (
|
||||
<div className="px-3 py-1.5 border-b border-gray-100 bg-gray-50/50 flex justify-between items-center">
|
||||
<span className="text-sm font-bold text-gray-700 tracking-tight">{title}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-3 flex-1">{children}</div>
|
||||
{footer && <div className="px-3 py-2 bg-gray-50/80 border-t border-gray-100">{footer}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Badge = ({ children, variant = 'info' }: any) => {
|
||||
const styles: any = {
|
||||
success: 'bg-green-50 text-green-700 border-green-200',
|
||||
error: 'bg-red-50 text-red-700 border-red-200',
|
||||
warning: 'bg-amber-50 text-amber-700 border-amber-200',
|
||||
info: 'bg-brand-50 text-brand-700 border-brand-200',
|
||||
};
|
||||
return (
|
||||
<span
|
||||
className={`px-1.5 py-0.5 rounded-sm text-[10px] font-bold border ${styles[variant]} uppercase tracking-tighter`}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
// ==========================================================
|
||||
// MAIN STYLE SHOWCASE
|
||||
// ==========================================================
|
||||
|
||||
export function StyleGuideTree() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 p-3 space-y-3 font-sans text-gray-800">
|
||||
{/* GLOBAL ACTION BAR */}
|
||||
<div className="bg-brand-900 text-white p-3 rounded flex justify-between items-center shadow-md">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-white p-1 rounded text-brand-900 font-bold text-xs uppercase">ERP-SYSTEM</div>
|
||||
<h1 className="text-base font-bold tracking-tight">Financial Postings & Asset Management</h1>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button className="bg-brand-700 hover:bg-brand-600 px-3 py-1 rounded text-base font-medium border border-brand-600 transition-all">
|
||||
Simulate
|
||||
</button>
|
||||
<button className="bg-white text-brand-900 hover:bg-gray-100 px-3 py-1 rounded text-base font-bold shadow-sm transition-all">
|
||||
Post Document
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-12 gap-3">
|
||||
{/* ROW 1: ANALYTICS & STATS (Top Row) */}
|
||||
{[
|
||||
{ label: 'Total Debit', val: 'IDR 4.500.000.000', sub: '24 Entries Today', color: 'text-gray-900' },
|
||||
{ label: 'Total Credit', val: 'IDR 4.500.000.000', sub: 'Balanced', color: 'text-green-600' },
|
||||
{ label: 'Unposted Items', val: '12 Documents', sub: 'High Priority', color: 'text-amber-600' },
|
||||
{ label: 'System Health', val: '99.9%', sub: 'No Latency', color: 'text-brand-600' },
|
||||
].map((stat, i) => (
|
||||
<div key={i} className="col-span-12 md:col-span-3 bg-white p-3 rounded border border-gray-200 shadow-sm">
|
||||
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest">{stat.label}</p>
|
||||
<p className={`text-lg font-bold ${stat.color} tracking-tight`}>{stat.val}</p>
|
||||
<p className="text-[11px] text-gray-400 font-medium italic">{stat.sub}</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* ROW 2: FORMS & CONTROLS (Left Panel) */}
|
||||
<div className="col-span-12 lg:col-span-4 space-y-3">
|
||||
<CompactCard title="Document Header">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="col-span-1 flex flex-col">
|
||||
<label className="text-xs font-bold text-gray-400 uppercase italic mb-1 tracking-tight">
|
||||
Posting Date
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
className="border border-gray-300 rounded px-2 py-1 text-base outline-none focus:border-brand-500 bg-gray-50"
|
||||
defaultValue="2026-01-19"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-1 flex flex-col">
|
||||
<label className="text-xs font-bold text-gray-400 uppercase italic mb-1 tracking-tight">Period</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
className="border border-gray-200 rounded px-2 py-1 text-base bg-gray-100 text-gray-500"
|
||||
defaultValue="01 / 2026"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col">
|
||||
<label className="text-xs font-bold text-gray-400 uppercase italic mb-1 tracking-tight">
|
||||
Document Type
|
||||
</label>
|
||||
<select className="border border-gray-300 rounded px-2 py-1 text-base outline-none bg-white">
|
||||
<option>SA - GL Account Document</option>
|
||||
<option>DZ - Customer Payment</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col">
|
||||
<label className="text-xs font-bold text-gray-400 uppercase italic mb-1 tracking-tight">
|
||||
Header Text
|
||||
</label>
|
||||
<textarea
|
||||
className="border border-gray-300 rounded px-2 py-1 text-base h-16 outline-none focus:border-brand-500"
|
||||
placeholder="Monthly reconciliation..."
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</CompactCard>
|
||||
|
||||
<CompactCard title="Operational Controls">
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center gap-3 p-2 border border-gray-100 rounded hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<input type="checkbox" className="accent-brand-600 w-4 h-4" defaultChecked />
|
||||
<span className="text-base font-medium">Automatic Tax Calculation</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-3 p-2 border border-gray-100 rounded hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<input type="checkbox" className="accent-brand-600 w-4 h-4" />
|
||||
<span className="text-base font-medium">Post to All Ledgers</span>
|
||||
</label>
|
||||
</div>
|
||||
</CompactCard>
|
||||
</div>
|
||||
|
||||
{/* ROW 3: COMPLEX DATA TABLE (Center Panel) */}
|
||||
<div className="col-span-12 lg:col-span-8 space-y-3">
|
||||
<CompactCard className="!p-0 overflow-hidden">
|
||||
<div className="bg-gray-50/50 px-4 py-2 border-b border-gray-200 flex justify-between items-center">
|
||||
<SectionTitle>Line Item Entries (001 - 005)</SectionTitle>
|
||||
<div className="flex gap-2 mb-2">
|
||||
<button className="text-[10px] font-bold text-brand-700 bg-brand-50 px-2 py-0.5 rounded border border-brand-100">
|
||||
ADD ROW
|
||||
</button>
|
||||
<button className="text-[10px] font-bold text-gray-500 bg-white px-2 py-0.5 rounded border border-gray-200">
|
||||
CLEAR ALL
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b border-gray-200">
|
||||
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase">Item</th>
|
||||
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase">GL Account</th>
|
||||
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase">Cost Center</th>
|
||||
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase text-right">
|
||||
Amount (LC)
|
||||
</th>
|
||||
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase text-center">Ind.</th>
|
||||
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase text-center">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-100">
|
||||
{[10, 20, 30, 40, 50].map((item, idx) => (
|
||||
<tr key={item} className="hover:bg-brand-50/30 transition-colors group">
|
||||
<td className="px-3 py-1.5 text-center font-mono text-gray-400">{item}</td>
|
||||
<td className="px-3 py-1.5">
|
||||
<div className="font-bold text-brand-700 text-base leading-none">11000{idx}</div>
|
||||
<div className="text-[10px] text-gray-400 italic">Petty Cash - Main Office</div>
|
||||
</td>
|
||||
<td className="px-3 py-1.5">
|
||||
<div className="font-medium text-gray-700">CC-HEAD-01</div>
|
||||
</td>
|
||||
<td className="px-3 py-1.5 text-right font-mono font-bold text-gray-900">
|
||||
{(item * 1500000).toLocaleString('id-ID')}
|
||||
</td>
|
||||
<td className="px-3 py-1.5 text-center font-bold text-brand-600">{idx % 2 === 0 ? 'S' : 'H'}</td>
|
||||
<td className="px-3 py-1.5 text-center">
|
||||
<Badge variant={idx === 2 ? 'warning' : 'success'}>{idx === 2 ? 'Review' : 'Valid'}</Badge>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-3 flex justify-end gap-10 border-t border-gray-200">
|
||||
<div className="text-right">
|
||||
<p className="text-[10px] font-bold text-gray-400 uppercase">Total Debit</p>
|
||||
<p className="text-lg font-bold text-gray-900 tracking-tight">IDR 450.000.000</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-[10px] font-bold text-gray-400 uppercase">Total Credit</p>
|
||||
<p className="text-lg font-bold text-brand-600 tracking-tight">IDR 450.000.000</p>
|
||||
</div>
|
||||
</div>
|
||||
</CompactCard>
|
||||
|
||||
{/* MESSAGE LOGS / ACTIVITY */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<CompactCard title="System Activity">
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ t: '09:41', u: 'System', m: 'Auto-balancing completed successfully.' },
|
||||
{ t: '10:05', u: 'Admin', m: 'Modified Cost Center on Item 20.' },
|
||||
].map((log, i) => (
|
||||
<div key={i} className="text-base flex gap-2 border-b border-gray-50 pb-1">
|
||||
<span className="font-mono text-brand-600 text-xs">{log.t}</span>
|
||||
<span className="text-gray-600 leading-tight">
|
||||
<strong>{log.u}:</strong> {log.m}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CompactCard>
|
||||
<CompactCard title="Quick Attachments">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="border border-dashed border-gray-300 rounded p-2 text-center w-full hover:bg-gray-50 cursor-pointer">
|
||||
<span className="text-xs font-bold text-gray-400">+ DROP FILES HERE</span>
|
||||
</div>
|
||||
<div className="bg-gray-100 px-2 py-1 rounded text-[11px] font-medium flex items-center gap-2">
|
||||
📄 invoice_001.pdf <span className="text-red-500 font-bold cursor-pointer">×</span>
|
||||
</div>
|
||||
</div>
|
||||
</CompactCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,14 @@
|
||||
export * from './header';
|
||||
export * from './counter';
|
||||
export * from './date-example';
|
||||
export * from './encryption-demo';
|
||||
export * from './button';
|
||||
export * from './examples/header-example';
|
||||
export * from './examples/counter-example';
|
||||
export * from './examples/date-example';
|
||||
export * from './examples/encryption-example';
|
||||
export * from './examples/style-guide-1';
|
||||
export * from './examples/style-guide-2';
|
||||
export * from './examples/style-guide-3';
|
||||
|
||||
export * from './system-pages/coming-soon';
|
||||
export * from './system-pages/forbidden';
|
||||
export * from './system-pages/maintenance';
|
||||
export * from './system-pages/not-found';
|
||||
|
||||
export * from './button/button';
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
export function ComingSoon() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-white px-4">
|
||||
<div className="w-full max-w-md text-center">
|
||||
{/* Code */}
|
||||
<p className="text-sm font-medium uppercase tracking-widest text-gray-400">Coming Soon</p>
|
||||
|
||||
{/* Title */}
|
||||
<h1 className="mt-3 text-6xl font-extrabold text-gray-900">Feature in Progress</h1>
|
||||
|
||||
{/* Description */}
|
||||
<p className="mt-4 text-base text-gray-500">
|
||||
This feature is currently under development. Stay tuned! We'll have it ready for you very soon.
|
||||
</p>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="mt-8 flex justify-center gap-3">
|
||||
<a href="/" className="rounded-md bg-gray-900 px-5 py-2.5 text-sm font-medium text-white hover:bg-gray-800">
|
||||
Back to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
export function Forbidden() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-white px-4">
|
||||
<div className="w-full max-w-md text-center">
|
||||
{/* Code */}
|
||||
<p className="text-sm font-medium uppercase tracking-widest text-gray-400">403 Error</p>
|
||||
|
||||
{/* Title */}
|
||||
<h1 className="mt-3 text-6xl font-extrabold text-gray-900">Access Denied</h1>
|
||||
|
||||
{/* Description */}
|
||||
<p className="mt-4 text-base text-gray-500">
|
||||
Sorry, you don’t have permission to access this page. Please contact your administrator if you believe this is
|
||||
a mistake.
|
||||
</p>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="mt-8 flex justify-center gap-3">
|
||||
<button
|
||||
onClick={() => window.history.back()}
|
||||
className="rounded-md border border-gray-300 px-5 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-100"
|
||||
>
|
||||
Go Back
|
||||
</button>
|
||||
|
||||
<a href="/" className="rounded-md bg-gray-900 px-5 py-2.5 text-sm font-medium text-white hover:bg-gray-800">
|
||||
Back to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export function Maintenance() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-white px-4">
|
||||
<div className="w-full max-w-md text-center">
|
||||
{/* Code */}
|
||||
<p className="text-sm font-medium uppercase tracking-widest text-gray-400">Maintenance</p>
|
||||
|
||||
{/* Title */}
|
||||
<h1 className="mt-3 text-6xl font-extrabold text-gray-900">We'll Be Back Soon</h1>
|
||||
|
||||
{/* Description */}
|
||||
<p className="mt-4 text-base text-gray-500">
|
||||
Our system is currently undergoing scheduled maintenance. We are working hard to bring it back online as soon
|
||||
as possible.
|
||||
</p>
|
||||
|
||||
{/* Optional Actions */}
|
||||
<div className="mt-8 flex justify-center gap-3">
|
||||
<a href="/" className="rounded-md bg-gray-900 px-5 py-2.5 text-sm font-medium text-white hover:bg-gray-800">
|
||||
Back to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export function NotFound() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-white px-4">
|
||||
<div className="w-full max-w-md text-center">
|
||||
<p className="text-sm font-medium uppercase tracking-widest text-gray-400">404 Error</p>
|
||||
|
||||
<h1 className="mt-3 text-6xl font-extrabold text-gray-900">Page Not Found</h1>
|
||||
|
||||
<p className="mt-4 text-base text-gray-500">
|
||||
Sorry, the page you’re looking for doesn’t exist or has been moved.
|
||||
</p>
|
||||
|
||||
<div className="mt-8 flex justify-center gap-3">
|
||||
<button
|
||||
onClick={() => window.history.back()}
|
||||
className="rounded-md border border-gray-300 px-5 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-100"
|
||||
>
|
||||
Go Back
|
||||
</button>
|
||||
|
||||
<a href="/" className="rounded-md bg-gray-900 px-5 py-2.5 text-sm font-medium text-white hover:bg-gray-800">
|
||||
Back to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user