refactor: restructure storage and environment modules; remove unused code

- Deleted unused PouchDB configuration and types from core storage.
- Removed Electron-related hooks and utilities that are no longer needed.
- Introduced new local storage management for application keys.
- Created a new environment wrapper for landing application.
- Added public HTTP client for landing application with minimal configuration.
- Implemented new PouchDB entities for items and POS configurations.
- Updated main application entry point to reflect new storage structure.
This commit is contained in:
Firman Ramdhani
2026-05-29 17:36:34 +07:00
parent b2e622c57e
commit 5d9f0f6d94
26 changed files with 233 additions and 197 deletions
@@ -1,6 +1,6 @@
import { useAppEvent } from '@repo/core-events';
import type { ProfileUpdatedPayload } from '@repo/core-events';
import { secureIndexedDB, AppStorageKey } from '../../../../core/storage';
import { secureIndexedDB, AppStorageKey } from '../../../../core/storage/local';
// ─── Props ──────────────────────────────────────────────────────
@@ -1,6 +1,6 @@
import { CommonRemoteDataServices } from '@repo/core-api/data-services';
import type { BaseEntity } from '@repo/core-api/data-services';
import { apiClient } from '../../../../../../lib/api-client';
import { apiClient } from '../../../../../../core/lib/api-client';
// ─── Domain Entity ──────────────────────────────────────────────
@@ -34,10 +34,7 @@ export interface BookingEntity extends BaseEntity {
* await bookingServices.confirmProcessTransaction('42');
* ```
*/
export const bookingServices = new CommonRemoteDataServices<BookingEntity>(
apiClient,
{
apiUrl: '/bookings',
moduleKey: 'BOOKING',
},
);
export const bookingServices = new CommonRemoteDataServices<BookingEntity>(apiClient, {
apiUrl: '/bookings',
moduleKey: 'BOOKING',
});
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import { useTranslation, changeLanguage, applyTenantOverrides, i18n } from '@repo/core-i18n';
import { secureIndexedDB, AppStorageKey } from '../../../../../../core/storage';
import { secureIndexedDB, AppStorageKey } from '../../../../../../core/storage/local';
// Decentralized locale imports
import bookingId from '../locales/id/booking.json';
@@ -1,5 +1,5 @@
import { useState, useCallback } from 'react';
import { secureStorage, secureIndexedDB, AppStorageKey } from '../../../../../../core/storage';
import { secureStorage, secureIndexedDB, AppStorageKey } from '../../../../../../core/storage/local';
// ─── Demo Data ──────────────────────────────────────────────────
+60 -68
View File
@@ -1,20 +1,12 @@
import { useEffect, useState, useCallback } from 'react';
import {
Button,
Card,
Group,
Stack,
Title,
Text,
Table,
Badge,
} from '@repo/ui/components';
import { itemDB, posConfigDB } from '../../core/db';
import type { Item, POSConfiguration } from '../../core/db/types';
import { Button, Card, Group, Stack, Title, Text, Table, Badge } from '@repo/ui/components';
import { itemDB, posConfigDB } from '../../core/storage/pouch-db';
import type { ItemEntity, POSConfigurationEntity } from '../../core/storage/pouch-db/entities';
export default function PouchSample() {
const [configs, setConfigs] = useState<POSConfiguration[]>([]);
const [items, setItems] = useState<Item[]>([]);
const [configs, setConfigs] = useState<POSConfigurationEntity[]>([]);
const [items, setItems] = useState<ItemEntity[]>([]);
// Load initial data
const loadData = useCallback(async () => {
@@ -24,7 +16,7 @@ export default function PouchSample() {
const allItems = await itemDB.find({ selector: {} });
setItems(allItems);
console.log({allConfigs, allItems})
console.log({ allConfigs, allItems });
} catch (err) {
console.error('Failed to load PouchDB data', err);
}
@@ -150,35 +142,35 @@ export default function PouchSample() {
<Table.Th className="sticky top-0 bg-white z-10 shadow-sm">Actions</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{items.length > 0 ? (
items.map((item) => (
<Table.Tr key={item._id}>
<Table.Td>{item._id}</Table.Td>
<Table.Td>{item.name}</Table.Td>
<Table.Td>
<Badge color="brand" variant="light">
{item.item_type}
</Badge>
</Table.Td>
<Table.Td>${Number(item.base_price).toFixed(2)}</Table.Td>
<Table.Td>{item.item_rates?.length || 0}</Table.Td>
<Table.Td>
<Button size="xs" color="error" variant="subtle" onClick={() => handleDeleteItem(item._id)}>
Delete
</Button>
<Table.Tbody>
{items.length > 0 ? (
items.map((item) => (
<Table.Tr key={item._id}>
<Table.Td>{item._id}</Table.Td>
<Table.Td>{item.name}</Table.Td>
<Table.Td>
<Badge color="brand" variant="light">
{item.item_type}
</Badge>
</Table.Td>
<Table.Td>${Number(item.base_price).toFixed(2)}</Table.Td>
<Table.Td>{item.item_rates?.length || 0}</Table.Td>
<Table.Td>
<Button size="xs" color="error" variant="subtle" onClick={() => handleDeleteItem(item._id)}>
Delete
</Button>
</Table.Td>
</Table.Tr>
))
) : (
<Table.Tr>
<Table.Td colSpan={6} align="center">
<Text c="dimmed">No items found.</Text>
</Table.Td>
</Table.Tr>
))
) : (
<Table.Tr>
<Table.Td colSpan={6} align="center">
<Text c="dimmed">No items found.</Text>
</Table.Td>
</Table.Tr>
)}
</Table.Tbody>
</Table>
)}
</Table.Tbody>
</Table>
</div>
</Card>
@@ -202,34 +194,34 @@ export default function PouchSample() {
<Table.Th className="sticky top-0 bg-white z-10 shadow-sm">Actions</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{configs.length > 0 ? (
configs.map((cfg) => (
<Table.Tr key={cfg._id}>
<Table.Td>{cfg._id}</Table.Td>
<Table.Td>{cfg.pos_name}</Table.Td>
<Table.Td>{cfg.pos_number}</Table.Td>
<Table.Td>
<Badge color="info" variant="outline">
{cfg.items?.length || 0} Items
</Badge>
</Table.Td>
<Table.Td>
<Button size="xs" color="error" variant="subtle" onClick={() => handleDeleteConfig(cfg._id)}>
Delete
</Button>
<Table.Tbody>
{configs.length > 0 ? (
configs.map((cfg) => (
<Table.Tr key={cfg._id}>
<Table.Td>{cfg._id}</Table.Td>
<Table.Td>{cfg.pos_name}</Table.Td>
<Table.Td>{cfg.pos_number}</Table.Td>
<Table.Td>
<Badge color="info" variant="outline">
{cfg.items?.length || 0} Items
</Badge>
</Table.Td>
<Table.Td>
<Button size="xs" color="error" variant="subtle" onClick={() => handleDeleteConfig(cfg._id)}>
Delete
</Button>
</Table.Td>
</Table.Tr>
))
) : (
<Table.Tr>
<Table.Td colSpan={5} align="center">
<Text c="dimmed">No configurations found.</Text>
</Table.Td>
</Table.Tr>
))
) : (
<Table.Tr>
<Table.Td colSpan={5} align="center">
<Text c="dimmed">No configurations found.</Text>
</Table.Td>
</Table.Tr>
)}
</Table.Tbody>
</Table>
)}
</Table.Tbody>
</Table>
</div>
</Card>
</Stack>
+9 -7
View File
@@ -1,7 +1,7 @@
// 1. Import hooks yang baru saja dibuat Opus
import { Button } from '@repo/ui/components';
import { useElectronPrinter } from '../../hooks/use-electron-printer';
import { useElectronUpdater } from '../../hooks/use-electron-updater';
import { useElectronPrinter } from '../../core/hooks/use-electron-printer';
import { useElectronUpdater } from '../../core/hooks/use-electron-updater';
export default function App() {
// 2. Panggil hooks-nya
@@ -11,13 +11,15 @@ export default function App() {
return (
<div style={{ padding: '20px', border: '2px solid blue', margin: '20px' }}>
<h2>🧪 Test Integrasi Electron</h2>
<p><strong>Status Auto-Update:</strong> {status}</p>
<p>
<strong>Status Auto-Update:</strong> {status}
</p>
<Button variant="filled" color="brand" onClick={refreshPrinters}>
Refresh Printer
</Button>
<h3>🖨 Daftar Printer di Komputer Ini:</h3>
<ul>
{printers.length === 0 ? (
@@ -32,4 +34,4 @@ export default function App() {
</ul>
</div>
);
}
}
+96 -45
View File
@@ -15,7 +15,6 @@ import {
NumberInput,
Textarea,
Switch,
Radio,
Table,
Badge,
Divider,
@@ -48,13 +47,20 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
const getSubtitle = () => {
switch (activeTab) {
case 'rbac': return 'Role-Based Access Control and Permissions';
case 'storage': return 'Offline-First PouchDB Synchronization';
case 'auth': return 'Authentication & Security Layers';
case 'ui-components': return 'Theme, Typography, Forms & Data Grids';
case 'events': return 'Global Event Bus Synchronization';
case 'hardware': return 'Hardware Integration & Printers';
default: return 'Architecture Showcase';
case 'rbac':
return 'Role-Based Access Control and Permissions';
case 'storage':
return 'Offline-First PouchDB Synchronization';
case 'auth':
return 'Authentication & Security Layers';
case 'ui-components':
return 'Theme, Typography, Forms & Data Grids';
case 'events':
return 'Global Event Bus Synchronization';
case 'hardware':
return 'Hardware Integration & Printers';
default:
return 'Architecture Showcase';
}
};
@@ -70,11 +76,11 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
className="h-screen"
styles={{
root: { display: 'flex', height: '100vh', overflow: 'hidden' },
list: {
minWidth: 260,
padding: '1rem',
borderRight: '1px solid var(--mantine-color-default-border)',
backgroundColor: 'var(--mantine-color-default-element-bg)'
list: {
minWidth: 260,
padding: '1rem',
borderRight: '1px solid var(--mantine-color-default-border)',
backgroundColor: 'var(--mantine-color-default-element-bg)',
},
panel: { flex: 1, display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden' },
tab: { justifyContent: 'flex-start' }, // Ensure the entire tab content aligns left
@@ -83,8 +89,12 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
>
<Tabs.List>
<Box mb="xl" px="sm">
<Title order={3} c="brand.7">Eigen ERP</Title>
<Text size="xs" c="dimmed">Architecture Showcase</Text>
<Title order={3} c="brand.7">
Eigen ERP
</Title>
<Text size="xs" c="dimmed">
Architecture Showcase
</Text>
</Box>
<Tabs.Tab value="ui-components" leftSection={<Layout size={18} />}>
@@ -109,22 +119,24 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
<Tabs.Panel value={activeTab as string}>
{/* Header */}
<Paper
p="md"
radius={0}
withBorder
style={{
borderTop: 0,
borderLeft: 0,
<Paper
p="md"
radius={0}
withBorder
style={{
borderTop: 0,
borderLeft: 0,
borderRight: 0,
zIndex: 10,
flexShrink: 0
zIndex: 10,
flexShrink: 0,
}}
>
<Group justify="space-between">
<Stack gap={0}>
<Title order={3}>Architecture Showcase</Title>
<Text size="sm" c="dimmed">{getSubtitle()}</Text>
<Text size="sm" c="dimmed">
{getSubtitle()}
</Text>
</Stack>
</Group>
</Paper>
@@ -132,25 +144,32 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{/* Scrollable Content Area */}
<Box className="flex-1 overflow-y-auto p-6" style={{ height: 'calc(100vh - 80px)' }}>
<Container size="xl" m={0} p={0}>
{/* --- UI COMPONENTS TAB --- */}
{activeTab === 'ui-components' && (
<Stack gap="xl">
{/* Control Panel */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">Theme Controls</Title>
<Title order={4} mb="md">
Theme Controls
</Title>
<Group grow align="flex-end">
<Select
label="Color Scheme"
value={colorScheme}
onChange={(val) => setColorScheme((val as ColorSchemeType) || 'light')}
data={[{ value: 'light', label: 'Light' }, { value: 'dark', label: 'Dark' }]}
data={[
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
]}
/>
<Select
label="Density (Spacing & Sizing)"
value={density}
onChange={(val) => setDensity((val as DensityType) || 'standard')}
data={[{ value: 'compact', label: 'Compact (ERP Mode)' }, { value: 'standard', label: 'Standard (UI Mode)' }]}
data={[
{ value: 'compact', label: 'Compact (ERP Mode)' },
{ value: 'standard', label: 'Standard (UI Mode)' },
]}
/>
</Group>
</Card>
@@ -159,22 +178,40 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
<Card withBorder shadow="sm" radius="md" p="md">
<Stack gap="lg">
<div>
<Title order={4} mb="xs">Typography & Badges</Title>
<Text size="sm" c="dimmed">This is dimmed small text indicating a subtitle.</Text>
<Title order={4} mb="xs">
Typography & Badges
</Title>
<Text size="sm" c="dimmed">
This is dimmed small text indicating a subtitle.
</Text>
<Group mt="md">
<Badge color="brand">Brand Badge</Badge>
<Badge color="success" variant="light">Success Status</Badge>
<Badge color="error" variant="outline">Error State</Badge>
<Badge color="success" variant="light">
Success Status
</Badge>
<Badge color="error" variant="outline">
Error State
</Badge>
</Group>
</div>
<Divider />
<div>
<Title order={4} mb="md">Buttons</Title>
<Title order={4} mb="md">
Buttons
</Title>
<Group>
<Button variant="filled" color="brand">Filled Button</Button>
<Button variant="outline" color="brand">Outline Button</Button>
<Button variant="light" color="info">Light Info</Button>
<Button variant="subtle" color="error">Cancel</Button>
<Button variant="filled" color="brand">
Filled Button
</Button>
<Button variant="outline" color="brand">
Outline Button
</Button>
<Button variant="light" color="info">
Light Info
</Button>
<Button variant="subtle" color="error">
Cancel
</Button>
</Group>
</div>
</Stack>
@@ -182,7 +219,9 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{/* Forms */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">Form Elements</Title>
<Title order={4} mb="md">
Form Elements
</Title>
<Stack gap="md">
<Group grow align="flex-start">
<TextInput label="First Name" placeholder="Enter your first name" withAsterisk />
@@ -202,7 +241,9 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{/* Data Grid */}
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">Data Grid</Title>
<Title order={4} mb="md">
Data Grid
</Title>
<Table striped highlightOnHover withTableBorder withColumnBorders>
<Table.Thead>
<Table.Tr>
@@ -218,7 +259,12 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
<Table.Td>{row.id}</Table.Td>
<Table.Td>{row.customer}</Table.Td>
<Table.Td>
<Badge size="sm" color={row.status === 'Delivered' ? 'success' : row.status === 'Shipped' ? 'info' : 'warning'}>
<Badge
size="sm"
color={
row.status === 'Delivered' ? 'success' : row.status === 'Shipped' ? 'info' : 'warning'
}
>
{row.status}
</Badge>
</Table.Td>
@@ -242,7 +288,9 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{activeTab === 'rbac' && (
<Stack gap="xl">
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">RBAC Engine</Title>
<Title order={4} mb="md">
RBAC Engine
</Title>
<Text c="dimmed">RBAC Demo Component Coming Soon...</Text>
</Card>
</Stack>
@@ -252,7 +300,9 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{activeTab === 'auth' && (
<Stack gap="xl">
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">Auth & Security</Title>
<Title order={4} mb="md">
Auth & Security
</Title>
<Text c="dimmed">Auth Demo Component Coming Soon...</Text>
</Card>
</Stack>
@@ -262,7 +312,9 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{activeTab === 'events' && (
<Stack gap="xl">
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">Nested Showcase Example</Title>
<Title order={4} mb="md">
Nested Showcase Example
</Title>
<Text mb="md">This is an example of a nested showcase component.</Text>
<ExamplePage />
</Card>
@@ -280,7 +332,6 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
</Card>
</Stack>
)}
</Container>
</Box>
</Tabs.Panel>
-47
View File
@@ -1,47 +0,0 @@
/**
* Enterprise ERP Data Domain Models
* These types reflect the actual schema of the underlying CouchDB instances.
*/
export interface ItemRate {
season_period?: string | null;
price: string | number;
}
export interface ItemCategory {
_id?: string;
name?: string;
[key: string]: any;
}
/**
* Represents a sellable product or service.
*/
export interface Item {
_id: string;
_rev?: string;
name: string;
base_price: string | number;
item_type: string;
usage_type?: string;
item_category?: ItemCategory[] | ItemCategory | string;
item_rates?: ItemRate[];
// Allow for other ERP-specific fields
[key: string]: any;
}
/**
* Represents the configuration and assigned data for a specific Point of Sale terminal.
*/
export interface POSConfiguration {
_id: string;
_rev?: string;
pos_number: string;
pos_name: string;
items: Item[];
payment_methods?: any[];
// Allow for other ERP-specific fields
[key: string]: any;
}
@@ -0,0 +1,2 @@
export * from './item.pouchdb.entity';
export * from './pos-configuration.pouchdb.entity';
@@ -0,0 +1,27 @@
interface ItemRateEntity {
season_period?: string | null;
price: string | number;
}
interface ItemCategoryEntity {
_id?: string;
name?: string;
[key: string]: any;
}
/**
* Represents a sellable product or service.
*/
export interface ItemEntity {
_id: string;
_rev?: string;
name: string;
base_price: string | number;
item_type: string;
usage_type?: string;
item_category?: ItemCategoryEntity[] | ItemCategoryEntity | string;
item_rates?: ItemRateEntity[];
// Allow for other ERP-specific fields
[key: string]: any;
}
@@ -0,0 +1,16 @@
import { ItemEntity } from './item.pouchdb.entity';
/**
* Represents the configuration and assigned data for a specific Point of Sale terminal.
*/
export interface POSConfigurationEntity {
_id: string;
_rev?: string;
pos_number: string;
pos_name: string;
items: ItemEntity[];
payment_methods?: any[];
// Allow for other ERP-specific fields
[key: string]: any;
}
@@ -5,9 +5,10 @@
* which databases to create and where they sync to. The core engine
* (`PouchDatabaseManager`) has zero knowledge of business domains.
*/
import { ENV } from '../../environment';
import { PouchDatabaseManager } from '@repo/core-storage';
import { ENV } from '../../environment/env';
import type { Item, POSConfiguration } from './types';
import { ItemEntity, POSConfigurationEntity } from './entities';
// ─── Manager Singleton ──────────────────────────────────────────
@@ -24,9 +25,7 @@ function buildRemoteUrl(dbName: string): string | undefined {
}
// 1. Tambahkan http:// secara otomatis jika DevOps hanya mengisi IP Address di .env
const safeBaseUrl = COUCHDB_BASE_URL.startsWith('http')
? COUCHDB_BASE_URL
: `http://${COUCHDB_BASE_URL}`;
const safeBaseUrl = COUCHDB_BASE_URL.startsWith('http') ? COUCHDB_BASE_URL : `http://${COUCHDB_BASE_URL}`;
try {
// 2. Gunakan URL parser yang aman dari karakter aneh pada password
@@ -45,13 +44,13 @@ function buildRemoteUrl(dbName: string): string | undefined {
// ─── Register Application Databases ─────────────────────────────
/** POS Configuration database — stores device settings, theme, etc. */
export const posConfigDB = dbManager.register<POSConfiguration>({
export const posConfigDB = dbManager.register<POSConfigurationEntity>({
localName: 'pos_configuration',
remoteUrl: buildRemoteUrl('pos_configuration'),
});
/** Items database — products available for sale in POS. */
export const itemDB = dbManager.register<Item>({
export const itemDB = dbManager.register<ItemEntity>({
localName: 'item',
remoteUrl: buildRemoteUrl('item'),
});
+1 -1
View File
@@ -16,7 +16,7 @@ import './main.css';
import { lazy, StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { setupI18n } from '@repo/core-i18n';
import { secureStorage, AppStorageKey } from './core/storage';
import { secureStorage, AppStorageKey } from './core/storage/local';
const App = lazy(() => import('./apps'));