Merge pull request 'refactor: restructure storage and environment modules; remove unused code' (#14) from feat/layout-web into main

Reviewed-on: eigen/fe-monorepo-template#14
This commit is contained in:
2026-05-29 10:37:44 +00:00
26 changed files with 233 additions and 197 deletions
@@ -1,6 +1,6 @@
import { CommonRemoteDataServices } from '@repo/core-api/data-services'; import { CommonRemoteDataServices } from '@repo/core-api/data-services';
import type { BaseEntity } from '@repo/core-api/data-services'; import type { BaseEntity } from '@repo/core-api/data-services';
import { publicClient } from '../../../lib/api-client'; import { publicClient } from '../../../core/lib/api-client';
// ─── Domain Entity ────────────────────────────────────────────── // ─── Domain Entity ──────────────────────────────────────────────
@@ -31,10 +31,7 @@ export interface PublicContentEntity extends BaseEntity {
* const { data: post } = await publicContentServices.getOne('hello-world'); * const { data: post } = await publicContentServices.getOne('hello-world');
* ``` * ```
*/ */
export const publicContentServices = new CommonRemoteDataServices<PublicContentEntity>( export const publicContentServices = new CommonRemoteDataServices<PublicContentEntity>(publicClient, {
publicClient,
{
apiUrl: '/content', apiUrl: '/content',
moduleKey: 'PUBLIC_CONTENT', moduleKey: 'PUBLIC_CONTENT',
}, });
);
@@ -2,8 +2,8 @@ import { useEffect, useState } from 'react';
import { useTranslation, changeLanguage, i18n } from '@repo/core-i18n'; import { useTranslation, changeLanguage, i18n } from '@repo/core-i18n';
// Decentralized locale imports // Decentralized locale imports
import homeId from '../locales/id/home.json'; import homeId from '../core/locales/id/home.json';
import homeEn from '../locales/en/home.json'; import homeEn from '../core/locales/en/home.json';
// Bendera penanda statis di level modul (default: false) // Bendera penanda statis di level modul (default: false)
let isHomeDictLoaded = false; let isHomeDictLoaded = false;
+1 -1
View File
@@ -7,7 +7,7 @@ import homeEn from '../locales/en/home.json';
declare module 'react-i18next' { declare module 'react-i18next' {
interface CustomTypeOptions { interface CustomTypeOptions {
defaultNS: 'common'; defaultNS: 'common';
resources: typeof coreResources['en'] & { resources: (typeof coreResources)['en'] & {
home: typeof homeEn; home: typeof homeEn;
}; };
} }
@@ -1,6 +1,6 @@
import { useAppEvent } from '@repo/core-events'; import { useAppEvent } from '@repo/core-events';
import type { ProfileUpdatedPayload } 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 ────────────────────────────────────────────────────── // ─── Props ──────────────────────────────────────────────────────
@@ -1,6 +1,6 @@
import { CommonRemoteDataServices } from '@repo/core-api/data-services'; import { CommonRemoteDataServices } from '@repo/core-api/data-services';
import type { BaseEntity } 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 ────────────────────────────────────────────── // ─── Domain Entity ──────────────────────────────────────────────
@@ -34,10 +34,7 @@ export interface BookingEntity extends BaseEntity {
* await bookingServices.confirmProcessTransaction('42'); * await bookingServices.confirmProcessTransaction('42');
* ``` * ```
*/ */
export const bookingServices = new CommonRemoteDataServices<BookingEntity>( export const bookingServices = new CommonRemoteDataServices<BookingEntity>(apiClient, {
apiClient,
{
apiUrl: '/bookings', apiUrl: '/bookings',
moduleKey: 'BOOKING', moduleKey: 'BOOKING',
}, });
);
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useTranslation, changeLanguage, applyTenantOverrides, i18n } from '@repo/core-i18n'; 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 // Decentralized locale imports
import bookingId from '../locales/id/booking.json'; import bookingId from '../locales/id/booking.json';
@@ -1,5 +1,5 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { secureStorage, secureIndexedDB, AppStorageKey } from '../../../../../../core/storage'; import { secureStorage, secureIndexedDB, AppStorageKey } from '../../../../../../core/storage/local';
// ─── Demo Data ────────────────────────────────────────────────── // ─── Demo Data ──────────────────────────────────────────────────
+7 -15
View File
@@ -1,20 +1,12 @@
import { useEffect, useState, useCallback } from 'react'; import { useEffect, useState, useCallback } from 'react';
import { import { Button, Card, Group, Stack, Title, Text, Table, Badge } from '@repo/ui/components';
Button,
Card, import { itemDB, posConfigDB } from '../../core/storage/pouch-db';
Group, import type { ItemEntity, POSConfigurationEntity } from '../../core/storage/pouch-db/entities';
Stack,
Title,
Text,
Table,
Badge,
} from '@repo/ui/components';
import { itemDB, posConfigDB } from '../../core/db';
import type { Item, POSConfiguration } from '../../core/db/types';
export default function PouchSample() { export default function PouchSample() {
const [configs, setConfigs] = useState<POSConfiguration[]>([]); const [configs, setConfigs] = useState<POSConfigurationEntity[]>([]);
const [items, setItems] = useState<Item[]>([]); const [items, setItems] = useState<ItemEntity[]>([]);
// Load initial data // Load initial data
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
@@ -24,7 +16,7 @@ export default function PouchSample() {
const allItems = await itemDB.find({ selector: {} }); const allItems = await itemDB.find({ selector: {} });
setItems(allItems); setItems(allItems);
console.log({allConfigs, allItems}) console.log({ allConfigs, allItems });
} catch (err) { } catch (err) {
console.error('Failed to load PouchDB data', err); console.error('Failed to load PouchDB data', err);
} }
+5 -3
View File
@@ -1,7 +1,7 @@
// 1. Import hooks yang baru saja dibuat Opus // 1. Import hooks yang baru saja dibuat Opus
import { Button } from '@repo/ui/components'; import { Button } from '@repo/ui/components';
import { useElectronPrinter } from '../../hooks/use-electron-printer'; import { useElectronPrinter } from '../../core/hooks/use-electron-printer';
import { useElectronUpdater } from '../../hooks/use-electron-updater'; import { useElectronUpdater } from '../../core/hooks/use-electron-updater';
export default function App() { export default function App() {
// 2. Panggil hooks-nya // 2. Panggil hooks-nya
@@ -12,7 +12,9 @@ export default function App() {
<div style={{ padding: '20px', border: '2px solid blue', margin: '20px' }}> <div style={{ padding: '20px', border: '2px solid blue', margin: '20px' }}>
<h2>🧪 Test Integrasi Electron</h2> <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}> <Button variant="filled" color="brand" onClick={refreshPrinters}>
Refresh Printer Refresh Printer
+84 -33
View File
@@ -15,7 +15,6 @@ import {
NumberInput, NumberInput,
Textarea, Textarea,
Switch, Switch,
Radio,
Table, Table,
Badge, Badge,
Divider, Divider,
@@ -48,13 +47,20 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
const getSubtitle = () => { const getSubtitle = () => {
switch (activeTab) { switch (activeTab) {
case 'rbac': return 'Role-Based Access Control and Permissions'; case 'rbac':
case 'storage': return 'Offline-First PouchDB Synchronization'; return 'Role-Based Access Control and Permissions';
case 'auth': return 'Authentication & Security Layers'; case 'storage':
case 'ui-components': return 'Theme, Typography, Forms & Data Grids'; return 'Offline-First PouchDB Synchronization';
case 'events': return 'Global Event Bus Synchronization'; case 'auth':
case 'hardware': return 'Hardware Integration & Printers'; return 'Authentication & Security Layers';
default: return 'Architecture Showcase'; 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';
} }
}; };
@@ -74,7 +80,7 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
minWidth: 260, minWidth: 260,
padding: '1rem', padding: '1rem',
borderRight: '1px solid var(--mantine-color-default-border)', borderRight: '1px solid var(--mantine-color-default-border)',
backgroundColor: 'var(--mantine-color-default-element-bg)' backgroundColor: 'var(--mantine-color-default-element-bg)',
}, },
panel: { flex: 1, display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden' }, panel: { flex: 1, display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden' },
tab: { justifyContent: 'flex-start' }, // Ensure the entire tab content aligns left 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> <Tabs.List>
<Box mb="xl" px="sm"> <Box mb="xl" px="sm">
<Title order={3} c="brand.7">Eigen ERP</Title> <Title order={3} c="brand.7">
<Text size="xs" c="dimmed">Architecture Showcase</Text> Eigen ERP
</Title>
<Text size="xs" c="dimmed">
Architecture Showcase
</Text>
</Box> </Box>
<Tabs.Tab value="ui-components" leftSection={<Layout size={18} />}> <Tabs.Tab value="ui-components" leftSection={<Layout size={18} />}>
@@ -118,13 +128,15 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
borderLeft: 0, borderLeft: 0,
borderRight: 0, borderRight: 0,
zIndex: 10, zIndex: 10,
flexShrink: 0 flexShrink: 0,
}} }}
> >
<Group justify="space-between"> <Group justify="space-between">
<Stack gap={0}> <Stack gap={0}>
<Title order={3}>Architecture Showcase</Title> <Title order={3}>Architecture Showcase</Title>
<Text size="sm" c="dimmed">{getSubtitle()}</Text> <Text size="sm" c="dimmed">
{getSubtitle()}
</Text>
</Stack> </Stack>
</Group> </Group>
</Paper> </Paper>
@@ -132,25 +144,32 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{/* Scrollable Content Area */} {/* Scrollable Content Area */}
<Box className="flex-1 overflow-y-auto p-6" style={{ height: 'calc(100vh - 80px)' }}> <Box className="flex-1 overflow-y-auto p-6" style={{ height: 'calc(100vh - 80px)' }}>
<Container size="xl" m={0} p={0}> <Container size="xl" m={0} p={0}>
{/* --- UI COMPONENTS TAB --- */} {/* --- UI COMPONENTS TAB --- */}
{activeTab === 'ui-components' && ( {activeTab === 'ui-components' && (
<Stack gap="xl"> <Stack gap="xl">
{/* Control Panel */} {/* Control Panel */}
<Card withBorder shadow="sm" radius="md" p="md"> <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"> <Group grow align="flex-end">
<Select <Select
label="Color Scheme" label="Color Scheme"
value={colorScheme} value={colorScheme}
onChange={(val) => setColorScheme((val as ColorSchemeType) || 'light')} 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 <Select
label="Density (Spacing & Sizing)" label="Density (Spacing & Sizing)"
value={density} value={density}
onChange={(val) => setDensity((val as DensityType) || 'standard')} 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> </Group>
</Card> </Card>
@@ -159,22 +178,40 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
<Card withBorder shadow="sm" radius="md" p="md"> <Card withBorder shadow="sm" radius="md" p="md">
<Stack gap="lg"> <Stack gap="lg">
<div> <div>
<Title order={4} mb="xs">Typography & Badges</Title> <Title order={4} mb="xs">
<Text size="sm" c="dimmed">This is dimmed small text indicating a subtitle.</Text> Typography & Badges
</Title>
<Text size="sm" c="dimmed">
This is dimmed small text indicating a subtitle.
</Text>
<Group mt="md"> <Group mt="md">
<Badge color="brand">Brand Badge</Badge> <Badge color="brand">Brand Badge</Badge>
<Badge color="success" variant="light">Success Status</Badge> <Badge color="success" variant="light">
<Badge color="error" variant="outline">Error State</Badge> Success Status
</Badge>
<Badge color="error" variant="outline">
Error State
</Badge>
</Group> </Group>
</div> </div>
<Divider /> <Divider />
<div> <div>
<Title order={4} mb="md">Buttons</Title> <Title order={4} mb="md">
Buttons
</Title>
<Group> <Group>
<Button variant="filled" color="brand">Filled Button</Button> <Button variant="filled" color="brand">
<Button variant="outline" color="brand">Outline Button</Button> Filled Button
<Button variant="light" color="info">Light Info</Button> </Button>
<Button variant="subtle" color="error">Cancel</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> </Group>
</div> </div>
</Stack> </Stack>
@@ -182,7 +219,9 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{/* Forms */} {/* Forms */}
<Card withBorder shadow="sm" radius="md" p="md"> <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"> <Stack gap="md">
<Group grow align="flex-start"> <Group grow align="flex-start">
<TextInput label="First Name" placeholder="Enter your first name" withAsterisk /> <TextInput label="First Name" placeholder="Enter your first name" withAsterisk />
@@ -202,7 +241,9 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{/* Data Grid */} {/* Data Grid */}
<Card withBorder shadow="sm" radius="md" p="md"> <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 striped highlightOnHover withTableBorder withColumnBorders>
<Table.Thead> <Table.Thead>
<Table.Tr> <Table.Tr>
@@ -218,7 +259,12 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
<Table.Td>{row.id}</Table.Td> <Table.Td>{row.id}</Table.Td>
<Table.Td>{row.customer}</Table.Td> <Table.Td>{row.customer}</Table.Td>
<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} {row.status}
</Badge> </Badge>
</Table.Td> </Table.Td>
@@ -242,7 +288,9 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{activeTab === 'rbac' && ( {activeTab === 'rbac' && (
<Stack gap="xl"> <Stack gap="xl">
<Card withBorder shadow="sm" radius="md" p="md"> <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> <Text c="dimmed">RBAC Demo Component Coming Soon...</Text>
</Card> </Card>
</Stack> </Stack>
@@ -252,7 +300,9 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{activeTab === 'auth' && ( {activeTab === 'auth' && (
<Stack gap="xl"> <Stack gap="xl">
<Card withBorder shadow="sm" radius="md" p="md"> <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> <Text c="dimmed">Auth Demo Component Coming Soon...</Text>
</Card> </Card>
</Stack> </Stack>
@@ -262,7 +312,9 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
{activeTab === 'events' && ( {activeTab === 'events' && (
<Stack gap="xl"> <Stack gap="xl">
<Card withBorder shadow="sm" radius="md" p="md"> <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> <Text mb="md">This is an example of a nested showcase component.</Text>
<ExamplePage /> <ExamplePage />
</Card> </Card>
@@ -280,7 +332,6 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
</Card> </Card>
</Stack> </Stack>
)} )}
</Container> </Container>
</Box> </Box>
</Tabs.Panel> </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 * which databases to create and where they sync to. The core engine
* (`PouchDatabaseManager`) has zero knowledge of business domains. * (`PouchDatabaseManager`) has zero knowledge of business domains.
*/ */
import { ENV } from '../../environment';
import { PouchDatabaseManager } from '@repo/core-storage'; import { PouchDatabaseManager } from '@repo/core-storage';
import { ENV } from '../../environment/env';
import type { Item, POSConfiguration } from './types'; import { ItemEntity, POSConfigurationEntity } from './entities';
// ─── Manager Singleton ────────────────────────────────────────── // ─── 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 // 1. Tambahkan http:// secara otomatis jika DevOps hanya mengisi IP Address di .env
const safeBaseUrl = COUCHDB_BASE_URL.startsWith('http') const safeBaseUrl = COUCHDB_BASE_URL.startsWith('http') ? COUCHDB_BASE_URL : `http://${COUCHDB_BASE_URL}`;
? COUCHDB_BASE_URL
: `http://${COUCHDB_BASE_URL}`;
try { try {
// 2. Gunakan URL parser yang aman dari karakter aneh pada password // 2. Gunakan URL parser yang aman dari karakter aneh pada password
@@ -45,13 +44,13 @@ function buildRemoteUrl(dbName: string): string | undefined {
// ─── Register Application Databases ───────────────────────────── // ─── Register Application Databases ─────────────────────────────
/** POS Configuration database — stores device settings, theme, etc. */ /** POS Configuration database — stores device settings, theme, etc. */
export const posConfigDB = dbManager.register<POSConfiguration>({ export const posConfigDB = dbManager.register<POSConfigurationEntity>({
localName: 'pos_configuration', localName: 'pos_configuration',
remoteUrl: buildRemoteUrl('pos_configuration'), remoteUrl: buildRemoteUrl('pos_configuration'),
}); });
/** Items database — products available for sale in POS. */ /** Items database — products available for sale in POS. */
export const itemDB = dbManager.register<Item>({ export const itemDB = dbManager.register<ItemEntity>({
localName: 'item', localName: 'item',
remoteUrl: buildRemoteUrl('item'), remoteUrl: buildRemoteUrl('item'),
}); });
+1 -1
View File
@@ -16,7 +16,7 @@ import './main.css';
import { lazy, StrictMode } from 'react'; import { lazy, StrictMode } from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import { setupI18n } from '@repo/core-i18n'; import { setupI18n } from '@repo/core-i18n';
import { secureStorage, AppStorageKey } from './core/storage'; import { secureStorage, AppStorageKey } from './core/storage/local';
const App = lazy(() => import('./apps')); const App = lazy(() => import('./apps'));