feat: add full page index component with pagination and actions
- Implemented FullPagePageIndex component with mock data for database clusters. - Added pagination and bulk actions for managing database clusters. - Created navigation context hooks for detail, edit, duplicate, and create actions. - Introduced a new store for managing state in full-page and single-page modules. feat: add navigation localization files - Added English and Indonesian localization files for navigation menu items. - Included translations for various modules including CRM, Sales, Supply Chain, and more. feat: create system information shortcuts component - Developed Shortcut component to display keyboard shortcuts with search functionality. - Implemented System component to show placeholder information when system details are unavailable. - Added localization for shortcuts and system information in English and Indonesian. feat: implement global theme store - Created a Zustand store for managing theme color scheme with localStorage persistence. feat: add module page header component - Developed ModulePageHeader component for consistent page header across modules. - Included breadcrumb navigation, title, description, and action buttons. feat: define default privileges for enterprise module - Established default privileges for CRUD operations and other actions in the enterprise module.
This commit is contained in:
@@ -31,7 +31,8 @@
|
||||
"react-i18next": "^15.4.0",
|
||||
"react-router-dom": "^7.11.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"zod": "^3.25.36"
|
||||
"zod": "^3.25.36",
|
||||
"zustand": "^5.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { lazy, Suspense, useState } from 'react';
|
||||
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { ThemeProvider, ColorSchemeType, DensityType } from '@repo/ui/provider';
|
||||
import { ThemeProvider, DensityType } from '@repo/ui/provider';
|
||||
import { NotFound, Forbidden, Maintenance, ComingSoon } from '@repo/ui/components';
|
||||
import { LoadingScreen } from '../core/components/loading-screen';
|
||||
import { useThemeStore } from '../core/store/theme.store';
|
||||
|
||||
const AuthModule = lazy(() => import('./auth'));
|
||||
const AppModule = lazy(() => import('./modules'));
|
||||
@@ -10,7 +11,7 @@ const ShowcaseView = lazy(() => import('./showcase/showcase-view'));
|
||||
const ShellDemo = lazy(() => import('./showcase/shell-demo'));
|
||||
|
||||
export default function App() {
|
||||
const [colorScheme, setColorScheme] = useState<ColorSchemeType>('light');
|
||||
const colorScheme = useThemeStore((s) => s.colorScheme);
|
||||
const [density, setDensity] = useState<DensityType>('compact');
|
||||
|
||||
return (
|
||||
@@ -20,20 +21,10 @@ export default function App() {
|
||||
<Routes>
|
||||
<Route path="/auth/*" element={<AuthModule />} />
|
||||
<Route path="/app/*" element={<AppModule />} />
|
||||
<Route
|
||||
path="/showcase"
|
||||
element={
|
||||
<ShowcaseView
|
||||
colorScheme={colorScheme}
|
||||
setColorScheme={setColorScheme}
|
||||
density={density}
|
||||
setDensity={setDensity}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path="/showcase" element={<ShowcaseView density={density} setDensity={setDensity} />} />
|
||||
<Route path="/shell-demo" element={<ShellDemo />} />
|
||||
<Route path="/404" element={<NotFound />} />
|
||||
<Route path="/403" element={<Forbidden />} />
|
||||
<Route path="/404" element={<NotFound homeUrl="/app" />} />
|
||||
<Route path="/403" element={<Forbidden homeUrl="/app" />} />
|
||||
<Route path="/maintenance" element={<Maintenance />} />
|
||||
<Route path="/coming-soon" element={<ComingSoon />} />
|
||||
<Route path="/" element={<Navigate to="/showcase" />} />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"title": "Full Page Management",
|
||||
"title": "Full Page",
|
||||
"description": "An example module of a <1>full page layout</1> for detailed forms.",
|
||||
"fields": {
|
||||
"status": "Status",
|
||||
"name": "Name",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"title": "Manajemen Halaman Penuh",
|
||||
"title": "Halaman Penuh",
|
||||
"description": "Contoh modul <1>tata letak halaman penuh</1> untuk formulir detail.",
|
||||
"fields": {
|
||||
"status": "Status",
|
||||
"name": "Nama",
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
import { Paper, SimpleGrid, Box, Text, Group, Badge, Divider, Breadcrumbs, Anchor, Flex, Title, ActionIcon, Button } from '@repo/ui/components';
|
||||
import { useEnterpriseModuleTranslationContext } from '@repo/ui/foundations';
|
||||
import { ChevronRight, CheckCircle2, Trash2, Edit2, Play } from 'lucide-react';
|
||||
|
||||
export default function FullPagePageDetail() {
|
||||
const { t } = useEnterpriseModuleTranslationContext();
|
||||
|
||||
return (
|
||||
<Box pb="xl">
|
||||
{/* --- INLINED PAGE HEADER --- */}
|
||||
<Box mb="xl" mt="xs">
|
||||
<Breadcrumbs
|
||||
mb="md"
|
||||
separator={<ChevronRight size={12} strokeWidth={3} style={{ color: 'var(--mantine-color-gray-5)' }} />}
|
||||
>
|
||||
<Anchor href="#" c="dimmed" size="xs" fw={500} style={{ transition: 'color 0.2s ease', letterSpacing: '0.2px' }}>
|
||||
Acme Corp
|
||||
</Anchor>
|
||||
<Anchor href="#" c="dimmed" size="xs" fw={500} style={{ transition: 'color 0.2s ease', letterSpacing: '0.2px' }}>
|
||||
Infrastructure
|
||||
</Anchor>
|
||||
<Anchor href="#" c="dimmed" size="xs" fw={500} style={{ transition: 'color 0.2s ease', letterSpacing: '0.2px' }}>
|
||||
Database Clusters
|
||||
</Anchor>
|
||||
<Text size="xs" fw={600} style={{ color: 'light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0))', letterSpacing: '0.2px' }}>
|
||||
prod-db-01
|
||||
</Text>
|
||||
</Breadcrumbs>
|
||||
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Flex gap="md" align="center">
|
||||
<Box>
|
||||
<Group gap="xs" align="center" mb={4}>
|
||||
<Title
|
||||
order={2}
|
||||
fw={600}
|
||||
style={{
|
||||
color: 'light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0))',
|
||||
letterSpacing: '-0.3px',
|
||||
}}
|
||||
>
|
||||
prod-db-01
|
||||
</Title>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="teal"
|
||||
size="sm"
|
||||
radius="sm"
|
||||
fw={600}
|
||||
leftSection={<CheckCircle2 size={10} strokeWidth={3} />}
|
||||
style={{ textTransform: 'capitalize' }}
|
||||
>
|
||||
Running
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
<Text size="sm" c="dimmed" fw={400}>
|
||||
PostgreSQL v15.4 · <Text span fw={500} c="var(--mantine-color-text)">us-east-1</Text> · Created Oct 12, 2023
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Group gap="sm">
|
||||
<ActionIcon variant="light" color="red" size="lg" radius="md" aria-label="Delete">
|
||||
<Trash2 size={18} strokeWidth={2} />
|
||||
</ActionIcon>
|
||||
<Button
|
||||
variant="default"
|
||||
radius="md"
|
||||
leftSection={<Edit2 size={16} strokeWidth={2} style={{ color: 'var(--mantine-color-dimmed)' }} />}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
color="indigo"
|
||||
radius="md"
|
||||
leftSection={<Play size={16} strokeWidth={2.5} />}
|
||||
style={{ boxShadow: '0 4px 14px 0 rgba(76, 110, 245, 0.39)' }}
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Divider mt={28} mb={0} color="light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))" />
|
||||
</Box>
|
||||
{/* --- END INLINED PAGE HEADER --- */}
|
||||
|
||||
<Paper withBorder shadow="sm" radius="md" p="xl">
|
||||
<Text size="lg" fw={600} mb="xs" style={{ color: 'light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0))', letterSpacing: '-0.3px' }}>
|
||||
General Information
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" mb="xl">
|
||||
View the complete details and configuration for this entity.
|
||||
</Text>
|
||||
|
||||
<Divider mb="xl" color="light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))" />
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="xl" verticalSpacing="xl">
|
||||
<Box>
|
||||
<Text size="xs" tt="uppercase" fw={700} c="dimmed" mb={4} style={{ letterSpacing: '0.5px' }}>
|
||||
{t('fields.code')}
|
||||
</Text>
|
||||
<Text size="sm" fw={500}>WID-001</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" tt="uppercase" fw={700} c="dimmed" mb={4} style={{ letterSpacing: '0.5px' }}>
|
||||
{t('fields.name')}
|
||||
</Text>
|
||||
<Text size="sm" fw={500}>Dashboard Widget</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" tt="uppercase" fw={700} c="dimmed" mb={4} style={{ letterSpacing: '0.5px' }}>
|
||||
{t('fields.status')}
|
||||
</Text>
|
||||
<Badge variant="light" color="teal" size="sm" radius="sm" fw={600}>
|
||||
ACTIVE
|
||||
</Badge>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" tt="uppercase" fw={700} c="dimmed" mb={4} style={{ letterSpacing: '0.5px' }}>
|
||||
{t('fields.description')}
|
||||
</Text>
|
||||
<Text size="sm" fw={500}>Main dashboard widget</Text>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
import { Paper, Box, Text, Divider, TextInput, Select, Textarea, Stack, Breadcrumbs, Anchor, Flex, Title, Button, Group } from '@repo/ui/components';
|
||||
import { useEnterpriseModuleTranslationContext } from '@repo/ui/foundations';
|
||||
import { ChevronRight, Database } from 'lucide-react';
|
||||
|
||||
export default function FullPagePageForm({ formPageType }: { formPageType: 'edit' | 'create' | 'duplicate' }) {
|
||||
const { t } = useEnterpriseModuleTranslationContext();
|
||||
|
||||
return (
|
||||
<Box pb="xl">
|
||||
{/* --- INLINED PAGE HEADER --- */}
|
||||
<Box mb="xl" mt="xs">
|
||||
<Breadcrumbs
|
||||
mb="md"
|
||||
separator={<ChevronRight size={12} strokeWidth={3} style={{ color: 'var(--mantine-color-gray-5)' }} />}
|
||||
>
|
||||
<Anchor href="#" c="dimmed" size="xs" fw={500} style={{ transition: 'color 0.2s ease', letterSpacing: '0.2px' }}>
|
||||
Database Clusters
|
||||
</Anchor>
|
||||
<Text size="xs" fw={600} style={{ color: 'light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0))', letterSpacing: '0.2px' }}>
|
||||
{formPageType === 'create' ? 'Create Cluster' : 'Edit Cluster'}
|
||||
</Text>
|
||||
</Breadcrumbs>
|
||||
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Flex gap="md" align="center">
|
||||
<Box>
|
||||
<Title
|
||||
order={2}
|
||||
fw={600}
|
||||
mb={4}
|
||||
style={{
|
||||
color: 'light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0))',
|
||||
letterSpacing: '-0.3px',
|
||||
}}
|
||||
>
|
||||
{formPageType === 'create' ? 'Create New Cluster' : 'Edit Cluster Configuration'}
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed" fw={400}>
|
||||
Configure and provision a new highly available database cluster.
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Group gap="sm">
|
||||
<Button variant="default" radius="md">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
color="indigo"
|
||||
radius="md"
|
||||
leftSection={<Database size={16} strokeWidth={2.5} />}
|
||||
style={{ boxShadow: '0 4px 14px 0 rgba(76, 110, 245, 0.39)' }}
|
||||
>
|
||||
{formPageType === 'create' ? 'Deploy Cluster' : 'Save Changes'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Divider mt={28} mb={0} color="light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))" />
|
||||
</Box>
|
||||
{/* --- END INLINED PAGE HEADER --- */}
|
||||
|
||||
<Paper withBorder shadow="sm" radius="md" p="xl">
|
||||
<Text size="lg" fw={600} mb="xs" style={{ color: 'light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0))', letterSpacing: '-0.3px' }}>
|
||||
{formPageType === 'create' ? 'Create Entity' : 'Edit Entity'}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" mb="xl">
|
||||
Fill in the required information to configure your entity properly. Fields marked with * are required.
|
||||
</Text>
|
||||
|
||||
<Divider mb="xl" color="light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))" />
|
||||
|
||||
<Box maw={600}>
|
||||
<Stack gap="lg">
|
||||
<TextInput
|
||||
label={t('fields.code')}
|
||||
placeholder="e.g. WID-001"
|
||||
required
|
||||
defaultValue={formPageType === 'edit' ? 'WID-001' : ''}
|
||||
radius="md"
|
||||
/>
|
||||
<TextInput
|
||||
label={t('fields.name')}
|
||||
placeholder="e.g. Dashboard Widget"
|
||||
required
|
||||
defaultValue={formPageType === 'edit' ? 'Dashboard Widget' : ''}
|
||||
radius="md"
|
||||
/>
|
||||
<Select
|
||||
label={t('fields.status')}
|
||||
placeholder="Select status"
|
||||
data={['ACTIVE', 'INACTIVE']}
|
||||
defaultValue="ACTIVE"
|
||||
required
|
||||
radius="md"
|
||||
/>
|
||||
<Textarea
|
||||
label={t('fields.description')}
|
||||
placeholder="Enter a detailed description..."
|
||||
minRows={4}
|
||||
defaultValue={formPageType === 'edit' ? 'Main dashboard widget' : ''}
|
||||
radius="md"
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+424
@@ -0,0 +1,424 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import {
|
||||
Table,
|
||||
Box,
|
||||
Paper,
|
||||
Badge,
|
||||
Text,
|
||||
Breadcrumbs,
|
||||
Anchor,
|
||||
Group,
|
||||
Flex,
|
||||
ThemeIcon,
|
||||
Title,
|
||||
ActionIcon,
|
||||
Button,
|
||||
Divider,
|
||||
TextInput,
|
||||
Pagination,
|
||||
Select,
|
||||
ScrollArea,
|
||||
} from '@repo/ui/components';
|
||||
import { PageActions } from '@repo/ui/components';
|
||||
import { useEnterpriseModuleTranslationContext, useEnterpriseModuleNavigationContext } from '@repo/ui/foundations';
|
||||
import { FullPageEntity } from '../../domain/entities';
|
||||
import {
|
||||
Edit2,
|
||||
Eye,
|
||||
Copy,
|
||||
Trash2,
|
||||
ChevronRight,
|
||||
Database,
|
||||
Activity,
|
||||
MoreHorizontal,
|
||||
Settings,
|
||||
Play,
|
||||
Search,
|
||||
Filter,
|
||||
Plus,
|
||||
CheckCircle2,
|
||||
Ban,
|
||||
} from 'lucide-react';
|
||||
|
||||
// TODO: Replace this mock data with real API data loaded from DataService via useEnterpriseModuleDataServiceContext
|
||||
const MOCK_DATA: FullPageEntity[] = Array.from({ length: 50 }).map((_, i) => ({
|
||||
id: String(i + 1),
|
||||
name: `Database Cluster ${i + 1}`,
|
||||
code: `DB-PROD-${String(i + 1).padStart(3, '0')}`,
|
||||
status: i % 7 === 0 ? 'MAINTENANCE' : i % 4 === 0 ? 'INACTIVE' : 'ACTIVE',
|
||||
description: `Managed PostgreSQL cluster instance in region us-east-${(i % 3) + 1}`,
|
||||
}));
|
||||
|
||||
export default function FullPagePageIndex() {
|
||||
const { t } = useEnterpriseModuleTranslationContext();
|
||||
const { navigateToDetail, navigateToEdit, navigateToDuplicate, navigateToCreate } =
|
||||
useEnterpriseModuleNavigationContext();
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const totalRecords = MOCK_DATA.length;
|
||||
const totalPages = Math.ceil(totalRecords / pageSize);
|
||||
const paginatedData = MOCK_DATA.slice((page - 1) * pageSize, page * pageSize);
|
||||
|
||||
const getRowActions = useCallback(
|
||||
(row: FullPageEntity) => [
|
||||
{
|
||||
key: 'view',
|
||||
label: t('common:view'),
|
||||
icon: <Eye size={14} />,
|
||||
onClick: () => navigateToDetail(row.id as string),
|
||||
},
|
||||
{
|
||||
key: 'edit',
|
||||
label: t('common:edit'),
|
||||
icon: <Edit2 size={14} />,
|
||||
onClick: () => navigateToEdit(row.id as string),
|
||||
},
|
||||
{
|
||||
key: 'duplicate',
|
||||
label: t('common:duplicate'),
|
||||
icon: <Copy size={14} />,
|
||||
onClick: () => navigateToDuplicate(row.id as string),
|
||||
},
|
||||
{
|
||||
type: 'divider' as const,
|
||||
key: 'div-1',
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: t('common:delete'),
|
||||
icon: <Trash2 size={14} />,
|
||||
intent: 'destructive' as const,
|
||||
onClick: () => {
|
||||
// Mock delete action
|
||||
alert(`Delete ${row.name}`);
|
||||
},
|
||||
},
|
||||
],
|
||||
[t, navigateToDetail, navigateToEdit, navigateToDuplicate],
|
||||
);
|
||||
|
||||
const rows = paginatedData.map((item) => (
|
||||
<Table.Tr key={item.id}>
|
||||
<Table.Td>
|
||||
<Text
|
||||
size="sm"
|
||||
fw={600}
|
||||
style={{ color: 'light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0))' }}
|
||||
>
|
||||
{item.code}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm" fw={500}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge
|
||||
variant={item.status === 'ACTIVE' ? 'light' : item.status === 'MAINTENANCE' ? 'outline' : 'dot'}
|
||||
color={item.status === 'ACTIVE' ? 'teal' : item.status === 'MAINTENANCE' ? 'orange' : 'gray'}
|
||||
size="sm"
|
||||
radius="sm"
|
||||
fw={600}
|
||||
>
|
||||
{item.status}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm" c="dimmed">
|
||||
{item.description}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td ta="right">
|
||||
<PageActions actions={getRowActions(item)} />
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
));
|
||||
|
||||
return (
|
||||
<Box pb="xl">
|
||||
{/* --- INLINED PAGE HEADER --- */}
|
||||
<Box mb="xl" mt="xs">
|
||||
<Breadcrumbs
|
||||
mb="md"
|
||||
separator={<ChevronRight size={12} strokeWidth={3} style={{ color: 'var(--mantine-color-gray-5)' }} />}
|
||||
>
|
||||
<Anchor
|
||||
href="#"
|
||||
c="dimmed"
|
||||
size="xs"
|
||||
fw={500}
|
||||
style={{ transition: 'color 0.2s ease', letterSpacing: '0.2px' }}
|
||||
>
|
||||
Acme Corp
|
||||
</Anchor>
|
||||
<Anchor
|
||||
href="#"
|
||||
c="dimmed"
|
||||
size="xs"
|
||||
fw={500}
|
||||
style={{ transition: 'color 0.2s ease', letterSpacing: '0.2px' }}
|
||||
>
|
||||
Infrastructure
|
||||
</Anchor>
|
||||
<Text
|
||||
size="xs"
|
||||
fw={600}
|
||||
style={{
|
||||
color: 'light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0))',
|
||||
letterSpacing: '0.2px',
|
||||
}}
|
||||
>
|
||||
Database Clusters
|
||||
</Text>
|
||||
</Breadcrumbs>
|
||||
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Flex gap="md" align="center">
|
||||
<ThemeIcon
|
||||
size={54}
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="indigo"
|
||||
style={{
|
||||
border: '1px solid light-dark(var(--mantine-color-indigo-1), var(--mantine-color-indigo-9))',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.03)',
|
||||
}}
|
||||
>
|
||||
<Database size={26} strokeWidth={1.5} />
|
||||
</ThemeIcon>
|
||||
|
||||
<Box>
|
||||
<Group gap="xs" align="center" mb={4}>
|
||||
<Title
|
||||
order={2}
|
||||
fw={600}
|
||||
style={{
|
||||
color: 'light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0))',
|
||||
letterSpacing: '-0.3px',
|
||||
}}
|
||||
>
|
||||
PostgreSQL Primary
|
||||
</Title>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="teal"
|
||||
size="sm"
|
||||
radius="sm"
|
||||
fw={600}
|
||||
leftSection={<Activity size={10} strokeWidth={3} />}
|
||||
style={{ textTransform: 'capitalize' }}
|
||||
>
|
||||
Healthy
|
||||
</Badge>
|
||||
<Badge variant="outline" color="gray" size="sm" radius="sm" fw={600} style={{ textTransform: 'none' }}>
|
||||
v15.4
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
<Text size="sm" c="dimmed" fw={400}>
|
||||
Managed relational database cluster in{' '}
|
||||
<Text span fw={500} c="var(--mantine-color-text)">
|
||||
us-east-1
|
||||
</Text>{' '}
|
||||
region. Last automated backup was 2 hours ago.
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Group gap="sm">
|
||||
<ActionIcon variant="default" size="lg" radius="md" aria-label="More Options">
|
||||
<MoreHorizontal size={18} strokeWidth={2} style={{ color: 'var(--mantine-color-dimmed)' }} />
|
||||
</ActionIcon>
|
||||
<Button
|
||||
variant="default"
|
||||
radius="md"
|
||||
leftSection={<Settings size={16} strokeWidth={2} style={{ color: 'var(--mantine-color-dimmed)' }} />}
|
||||
>
|
||||
Configure
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
color="indigo"
|
||||
radius="md"
|
||||
leftSection={<Play size={16} strokeWidth={2.5} />}
|
||||
style={{ boxShadow: '0 4px 14px 0 rgba(76, 110, 245, 0.39)' }}
|
||||
>
|
||||
Start Cluster
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Divider mt={28} mb={0} color="light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))" />
|
||||
</Box>
|
||||
{/* --- END INLINED PAGE HEADER --- */}
|
||||
|
||||
<Paper withBorder shadow="sm" radius="md" style={{ overflow: 'hidden' }}>
|
||||
{/* --- TABLE TOOLBAR --- */}
|
||||
<Box
|
||||
p="md"
|
||||
style={{ borderBottom: '1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-7))' }}
|
||||
>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<TextInput
|
||||
placeholder="Search items..."
|
||||
leftSection={<Search size={16} strokeWidth={2} style={{ color: 'var(--mantine-color-dimmed)' }} />}
|
||||
size="sm"
|
||||
radius="md"
|
||||
w={280}
|
||||
/>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
radius="md"
|
||||
leftSection={<Filter size={16} strokeWidth={2} style={{ color: 'var(--mantine-color-dimmed)' }} />}
|
||||
>
|
||||
Filters
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Group gap="xs">
|
||||
<Text size="sm" c="dimmed" fw={500} mr="xs">
|
||||
Bulk Actions:
|
||||
</Text>
|
||||
<Button
|
||||
variant="light"
|
||||
color="teal"
|
||||
size="sm"
|
||||
radius="md"
|
||||
leftSection={<CheckCircle2 size={14} strokeWidth={2.5} />}
|
||||
>
|
||||
Activate
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
color="gray"
|
||||
size="sm"
|
||||
radius="md"
|
||||
leftSection={<Ban size={14} strokeWidth={2.5} />}
|
||||
>
|
||||
Deactivate
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
size="sm"
|
||||
radius="md"
|
||||
leftSection={<Trash2 size={14} strokeWidth={2.5} />}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Divider
|
||||
orientation="vertical"
|
||||
mx="xs"
|
||||
color="light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-6))"
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="filled"
|
||||
color="indigo"
|
||||
size="sm"
|
||||
radius="md"
|
||||
leftSection={<Plus size={16} strokeWidth={2.5} />}
|
||||
onClick={navigateToCreate}
|
||||
style={{ boxShadow: '0 4px 12px 0 rgba(76, 110, 245, 0.28)' }}
|
||||
>
|
||||
Create New
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
</Box>
|
||||
{/* --- END TABLE TOOLBAR --- */}
|
||||
|
||||
<ScrollArea.Autosize mah="calc(100vh - 350px)" offsetScrollbars>
|
||||
<Table
|
||||
stickyHeader
|
||||
stickyHeaderOffset={-1}
|
||||
striped
|
||||
highlightOnHover
|
||||
verticalSpacing="sm"
|
||||
horizontalSpacing="md"
|
||||
>
|
||||
<Table.Thead bg="light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-8))">
|
||||
<Table.Tr>
|
||||
<Table.Th fw={700} style={{ letterSpacing: '0.5px', textTransform: 'uppercase' }} fz="xs" c="dimmed">
|
||||
{t('fields.code')}
|
||||
</Table.Th>
|
||||
<Table.Th fw={700} style={{ letterSpacing: '0.5px', textTransform: 'uppercase' }} fz="xs" c="dimmed">
|
||||
{t('fields.name')}
|
||||
</Table.Th>
|
||||
<Table.Th fw={700} style={{ letterSpacing: '0.5px', textTransform: 'uppercase' }} fz="xs" c="dimmed">
|
||||
{t('fields.status')}
|
||||
</Table.Th>
|
||||
<Table.Th fw={700} style={{ letterSpacing: '0.5px', textTransform: 'uppercase' }} fz="xs" c="dimmed">
|
||||
{t('fields.description')}
|
||||
</Table.Th>
|
||||
<Table.Th
|
||||
fw={700}
|
||||
style={{ letterSpacing: '0.5px', textTransform: 'uppercase' }}
|
||||
fz="xs"
|
||||
c="dimmed"
|
||||
w={80}
|
||||
ta="right"
|
||||
>
|
||||
Actions
|
||||
</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>{rows}</Table.Tbody>
|
||||
</Table>
|
||||
</ScrollArea.Autosize>
|
||||
|
||||
{/* --- PAGINATION FOOTER --- */}
|
||||
<Box
|
||||
p="md"
|
||||
style={{ borderTop: '1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-7))' }}
|
||||
>
|
||||
<Group justify="space-between" align="center">
|
||||
<Text size="sm" c="dimmed" fw={500}>
|
||||
Showing{' '}
|
||||
<Text span fw={600} c="var(--mantine-color-text)">
|
||||
{(page - 1) * pageSize + 1}
|
||||
</Text>{' '}
|
||||
to{' '}
|
||||
<Text span fw={600} c="var(--mantine-color-text)">
|
||||
{Math.min(page * pageSize, totalRecords)}
|
||||
</Text>{' '}
|
||||
of{' '}
|
||||
<Text span fw={600} c="var(--mantine-color-text)">
|
||||
{totalRecords}
|
||||
</Text>{' '}
|
||||
entries
|
||||
</Text>
|
||||
<Group gap="md">
|
||||
<Group gap="xs">
|
||||
<Text size="sm" c="dimmed" fw={500}>
|
||||
Rows per page:
|
||||
</Text>
|
||||
<Select
|
||||
size="sm"
|
||||
radius="md"
|
||||
data={['10', '20', '50']}
|
||||
value={String(pageSize)}
|
||||
onChange={(val) => {
|
||||
setPageSize(Number(val));
|
||||
setPage(1);
|
||||
}}
|
||||
w={75}
|
||||
/>
|
||||
</Group>
|
||||
<Pagination total={totalPages} value={page} onChange={setPage} size="sm" radius="md" color="indigo" />
|
||||
</Group>
|
||||
</Group>
|
||||
</Box>
|
||||
{/* --- END PAGINATION FOOTER --- */}
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+123
-7
@@ -1,15 +1,131 @@
|
||||
import { Paper, SimpleGrid, Box, Text, Group, Badge, Divider, Breadcrumbs, Anchor, Flex, Title, ActionIcon, Button } from '@repo/ui/components';
|
||||
import { useEnterpriseModuleTranslationContext } from '@repo/ui/foundations';
|
||||
import { ChevronRight, CheckCircle2, Trash2, Edit2, Play } from 'lucide-react';
|
||||
|
||||
export default function FullPagePageDetail() {
|
||||
const { t } = useEnterpriseModuleTranslationContext();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>full-page.page.detail</div>
|
||||
<div>{t('fields.code')}</div>
|
||||
<div>{t('fields.name')}</div>
|
||||
<div>{t('fields.status')}</div>
|
||||
<div>{t('fields.description')}</div>
|
||||
</div>
|
||||
<Box pb="xl">
|
||||
{/* --- INLINED PAGE HEADER --- */}
|
||||
<Box mb="xl" mt="xs">
|
||||
<Breadcrumbs
|
||||
mb="md"
|
||||
separator={<ChevronRight size={12} strokeWidth={3} style={{ color: 'var(--mantine-color-gray-5)' }} />}
|
||||
>
|
||||
<Anchor href="#" c="dimmed" size="xs" fw={500} style={{ transition: 'color 0.2s ease', letterSpacing: '0.2px' }}>
|
||||
Acme Corp
|
||||
</Anchor>
|
||||
<Anchor href="#" c="dimmed" size="xs" fw={500} style={{ transition: 'color 0.2s ease', letterSpacing: '0.2px' }}>
|
||||
Infrastructure
|
||||
</Anchor>
|
||||
<Anchor href="#" c="dimmed" size="xs" fw={500} style={{ transition: 'color 0.2s ease', letterSpacing: '0.2px' }}>
|
||||
Database Clusters
|
||||
</Anchor>
|
||||
<Text size="xs" fw={600} style={{ color: 'light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0))', letterSpacing: '0.2px' }}>
|
||||
prod-db-01
|
||||
</Text>
|
||||
</Breadcrumbs>
|
||||
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Flex gap="md" align="center">
|
||||
<Box>
|
||||
<Group gap="xs" align="center" mb={4}>
|
||||
<Title
|
||||
order={2}
|
||||
fw={600}
|
||||
style={{
|
||||
color: 'light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0))',
|
||||
letterSpacing: '-0.3px',
|
||||
}}
|
||||
>
|
||||
prod-db-01
|
||||
</Title>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="teal"
|
||||
size="sm"
|
||||
radius="sm"
|
||||
fw={600}
|
||||
leftSection={<CheckCircle2 size={10} strokeWidth={3} />}
|
||||
style={{ textTransform: 'capitalize' }}
|
||||
>
|
||||
Running
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
<Text size="sm" c="dimmed" fw={400}>
|
||||
PostgreSQL v15.4 · <Text span fw={500} c="var(--mantine-color-text)">us-east-1</Text> · Created Oct 12, 2023
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Group gap="sm">
|
||||
<ActionIcon variant="light" color="red" size="lg" radius="md" aria-label="Delete">
|
||||
<Trash2 size={18} strokeWidth={2} />
|
||||
</ActionIcon>
|
||||
<Button
|
||||
variant="default"
|
||||
radius="md"
|
||||
leftSection={<Edit2 size={16} strokeWidth={2} style={{ color: 'var(--mantine-color-dimmed)' }} />}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
color="indigo"
|
||||
radius="md"
|
||||
leftSection={<Play size={16} strokeWidth={2.5} />}
|
||||
style={{ boxShadow: '0 4px 14px 0 rgba(76, 110, 245, 0.39)' }}
|
||||
>
|
||||
Restart
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Divider mt={28} mb={0} color="light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))" />
|
||||
</Box>
|
||||
{/* --- END INLINED PAGE HEADER --- */}
|
||||
|
||||
<Paper withBorder shadow="sm" radius="md" p="xl">
|
||||
<Text size="lg" fw={600} mb="xs" style={{ color: 'light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0))', letterSpacing: '-0.3px' }}>
|
||||
General Information
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" mb="xl">
|
||||
View the complete details and configuration for this entity.
|
||||
</Text>
|
||||
|
||||
<Divider mb="xl" color="light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))" />
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="xl" verticalSpacing="xl">
|
||||
<Box>
|
||||
<Text size="xs" tt="uppercase" fw={700} c="dimmed" mb={4} style={{ letterSpacing: '0.5px' }}>
|
||||
{t('fields.code')}
|
||||
</Text>
|
||||
<Text size="sm" fw={500}>WID-001</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" tt="uppercase" fw={700} c="dimmed" mb={4} style={{ letterSpacing: '0.5px' }}>
|
||||
{t('fields.name')}
|
||||
</Text>
|
||||
<Text size="sm" fw={500}>Dashboard Widget</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" tt="uppercase" fw={700} c="dimmed" mb={4} style={{ letterSpacing: '0.5px' }}>
|
||||
{t('fields.status')}
|
||||
</Text>
|
||||
<Badge variant="light" color="teal" size="sm" radius="sm" fw={600}>
|
||||
ACTIVE
|
||||
</Badge>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" tt="uppercase" fw={700} c="dimmed" mb={4} style={{ letterSpacing: '0.5px' }}>
|
||||
{t('fields.description')}
|
||||
</Text>
|
||||
<Text size="sm" fw={500}>Main dashboard widget</Text>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
+138
-10
@@ -1,18 +1,146 @@
|
||||
import {
|
||||
Paper,
|
||||
Box,
|
||||
Text,
|
||||
Divider,
|
||||
TextInput,
|
||||
Select,
|
||||
Textarea,
|
||||
Stack,
|
||||
Breadcrumbs,
|
||||
Anchor,
|
||||
Flex,
|
||||
Title,
|
||||
Button,
|
||||
Group,
|
||||
} from '@repo/ui/components';
|
||||
import { useEnterpriseModuleTranslationContext } from '@repo/ui/foundations';
|
||||
import { ChevronRight, Database } from 'lucide-react';
|
||||
|
||||
export default function FullPagePageForm({ formPageType }: { formPageType: 'edit' | 'create' | 'duplicate' }) {
|
||||
const { t } = useEnterpriseModuleTranslationContext();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>full-page.page.form</div>
|
||||
<div>Form Type: {formPageType}</div>
|
||||
<div>{t('fields.code')}</div>
|
||||
<div>{t('fields.name')}</div>
|
||||
<div>{t('fields.status')}</div>
|
||||
<div>{t('fields.description')}</div>
|
||||
<div>{t('validation:required')}</div>
|
||||
<div>{t('common:save')}</div>
|
||||
</div>
|
||||
<Box pb="xl">
|
||||
{/* --- INLINED PAGE HEADER --- */}
|
||||
<Box mb="xl" mt="xs">
|
||||
<Breadcrumbs
|
||||
mb="md"
|
||||
separator={<ChevronRight size={12} strokeWidth={3} style={{ color: 'var(--mantine-color-gray-5)' }} />}
|
||||
>
|
||||
<Anchor
|
||||
href="#"
|
||||
c="dimmed"
|
||||
size="xs"
|
||||
fw={500}
|
||||
style={{ transition: 'color 0.2s ease', letterSpacing: '0.2px' }}
|
||||
>
|
||||
Database Clusters
|
||||
</Anchor>
|
||||
<Text
|
||||
size="xs"
|
||||
fw={600}
|
||||
style={{
|
||||
color: 'light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0))',
|
||||
letterSpacing: '0.2px',
|
||||
}}
|
||||
>
|
||||
{formPageType === 'create' ? 'Create Cluster' : 'Edit Cluster'}
|
||||
</Text>
|
||||
</Breadcrumbs>
|
||||
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Flex gap="md" align="center">
|
||||
<Box>
|
||||
<Title
|
||||
order={2}
|
||||
fw={600}
|
||||
mb={4}
|
||||
style={{
|
||||
color: 'light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0))',
|
||||
letterSpacing: '-0.3px',
|
||||
}}
|
||||
>
|
||||
{formPageType === 'create' ? 'Create New Cluster' : 'Edit Cluster Configuration'}
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed" fw={400}>
|
||||
Configure and provision a new highly available database cluster.
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Group gap="sm">
|
||||
<Button variant="default" radius="md">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
color="indigo"
|
||||
radius="md"
|
||||
leftSection={<Database size={16} strokeWidth={2.5} />}
|
||||
style={{ boxShadow: '0 4px 14px 0 rgba(76, 110, 245, 0.39)' }}
|
||||
>
|
||||
{formPageType === 'create' ? 'Deploy Cluster' : 'Save Changes'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Divider mt={28} mb={0} color="light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))" />
|
||||
</Box>
|
||||
{/* --- END INLINED PAGE HEADER --- */}
|
||||
|
||||
<Paper withBorder shadow="sm" radius="md" p="xl">
|
||||
<Text
|
||||
size="lg"
|
||||
fw={600}
|
||||
mb="xs"
|
||||
style={{
|
||||
color: 'light-dark(var(--mantine-color-gray-9), var(--mantine-color-dark-0))',
|
||||
letterSpacing: '-0.3px',
|
||||
}}
|
||||
>
|
||||
{formPageType === 'create' ? 'Create Entity' : 'Edit Entity'}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" mb="xl">
|
||||
Fill in the required information to configure your entity properly. Fields marked with * are required.
|
||||
</Text>
|
||||
|
||||
<Divider mb="xl" color="light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))" />
|
||||
|
||||
<Box maw={600}>
|
||||
<Stack gap="lg">
|
||||
<TextInput
|
||||
label={t('fields.code')}
|
||||
placeholder="e.g. WID-001"
|
||||
required
|
||||
defaultValue={formPageType === 'edit' ? 'WID-001' : ''}
|
||||
radius="md"
|
||||
/>
|
||||
<TextInput
|
||||
label={t('fields.name')}
|
||||
placeholder="e.g. Dashboard Widget"
|
||||
required
|
||||
defaultValue={formPageType === 'edit' ? 'Dashboard Widget' : ''}
|
||||
radius="md"
|
||||
/>
|
||||
<Select
|
||||
label={t('fields.status')}
|
||||
placeholder="Select status"
|
||||
data={['ACTIVE', 'INACTIVE']}
|
||||
defaultValue="ACTIVE"
|
||||
required
|
||||
radius="md"
|
||||
/>
|
||||
<Textarea
|
||||
label={t('fields.description')}
|
||||
placeholder="Enter a detailed description..."
|
||||
minRows={4}
|
||||
defaultValue={formPageType === 'edit' ? 'Main dashboard widget' : ''}
|
||||
radius="md"
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
+380
-92
@@ -1,99 +1,387 @@
|
||||
import { Table, Box, Title, Paper } from '@repo/ui/components';
|
||||
import { PageActions } from '@repo/ui/components';
|
||||
import { useEnterpriseModuleTranslationContext, useEnterpriseModuleNavigationContext } from '@repo/ui/foundations';
|
||||
import { FullPageEntity } from '../../domain/entities';
|
||||
import { Edit2, Eye, Copy, Trash2 } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
// TODO: Replace this mock data with real API data loaded from DataService via useEnterpriseModuleDataServiceContext
|
||||
const MOCK_DATA: FullPageEntity[] = [
|
||||
{ id: '1', name: 'Dashboard Widget', code: 'WID-001', status: 'ACTIVE', description: 'Main dashboard widget' },
|
||||
{ id: '2', name: 'Report Generator', code: 'REP-002', status: 'INACTIVE', description: 'Generates monthly reports' },
|
||||
{
|
||||
id: '3',
|
||||
name: 'User Management',
|
||||
code: 'USR-003',
|
||||
status: 'ACTIVE',
|
||||
description: 'Manages user roles and permissions',
|
||||
},
|
||||
];
|
||||
import { EnterpriseIndexPageProvider, useEnterpriseModuleTranslationContext } from '@repo/ui/foundations';
|
||||
import { Trans } from '@repo/core-i18n';
|
||||
import { Badge, Text } from '@repo/ui/components';
|
||||
import { Activity, LayoutDashboard } from 'lucide-react';
|
||||
import { FullPageModuleConfig } from '../../domain/constants';
|
||||
|
||||
export default function FullPagePageIndex() {
|
||||
// Translation is scoped to ['FULL_PAGE', 'common'] — no prefix needed for module keys
|
||||
const { t } = useEnterpriseModuleTranslationContext();
|
||||
|
||||
const { navigateToDetail, navigateToEdit, navigateToDuplicate } = useEnterpriseModuleNavigationContext();
|
||||
|
||||
const getRowActions = useCallback(
|
||||
(row: FullPageEntity) => [
|
||||
{
|
||||
key: 'view',
|
||||
label: t('common:view'),
|
||||
icon: <Eye size={14} />,
|
||||
onClick: () => navigateToDetail(row.id as string),
|
||||
},
|
||||
{
|
||||
key: 'edit',
|
||||
label: t('common:edit'),
|
||||
icon: <Edit2 size={14} />,
|
||||
onClick: () => navigateToEdit(row.id as string),
|
||||
},
|
||||
{
|
||||
key: 'duplicate',
|
||||
label: t('common:duplicate'),
|
||||
icon: <Copy size={14} />,
|
||||
onClick: () => navigateToDuplicate(row.id as string),
|
||||
},
|
||||
{
|
||||
type: 'divider' as const,
|
||||
key: 'div-1',
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: t('common:delete'),
|
||||
icon: <Trash2 size={14} />,
|
||||
intent: 'destructive' as const,
|
||||
onClick: () => {
|
||||
// Mock delete action
|
||||
alert(`Delete ${row.name}`);
|
||||
},
|
||||
},
|
||||
],
|
||||
[t, navigateToDetail, navigateToEdit, navigateToDuplicate],
|
||||
);
|
||||
|
||||
const rows = MOCK_DATA.map((item) => (
|
||||
<Table.Tr key={item.id}>
|
||||
<Table.Td>{item.code}</Table.Td>
|
||||
<Table.Td>{item.name}</Table.Td>
|
||||
<Table.Td>{item.status}</Table.Td>
|
||||
<Table.Td>{item.description}</Table.Td>
|
||||
<Table.Td>
|
||||
<PageActions actions={getRowActions(item)} />
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
));
|
||||
|
||||
return (
|
||||
<Box p="md">
|
||||
<Title order={2} mb="md">
|
||||
{t('title')}
|
||||
</Title>
|
||||
|
||||
<Paper withBorder shadow="sm" radius="md">
|
||||
<Table striped highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>{t('fields.code')}</Table.Th>
|
||||
<Table.Th>{t('fields.name')}</Table.Th>
|
||||
<Table.Th>{t('fields.status')}</Table.Th>
|
||||
<Table.Th>{t('fields.description')}</Table.Th>
|
||||
<Table.Th w={200}>{t('common:edit')}</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>{rows}</Table.Tbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
</Box>
|
||||
<EnterpriseIndexPageProvider
|
||||
pageHeaderProps={{
|
||||
title: t('title'),
|
||||
description: (
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="description"
|
||||
components={{
|
||||
1: <Text span fw={500} c="var(--mantine-color-text)" />,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
badges: (
|
||||
<Badge
|
||||
variant="light"
|
||||
color="teal"
|
||||
size="sm"
|
||||
radius="sm"
|
||||
fw={600}
|
||||
leftSection={<Activity size={10} strokeWidth={3} />}
|
||||
style={{ textTransform: 'capitalize' }}
|
||||
>
|
||||
Healthy
|
||||
</Badge>
|
||||
),
|
||||
icon: LayoutDashboard,
|
||||
breadcrumbs: [
|
||||
{ label: t('nav:example-module'), type: 'text' },
|
||||
{ label: t('nav:example-full-page'), type: 'link', href: `${FullPageModuleConfig.webUrl}/index` },
|
||||
],
|
||||
}}
|
||||
>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
<div style={{ textAlign: 'justify' }}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro quae aut minus doloremque mollitia quis,
|
||||
consequatur enim dolorum sunt itaque quia perferendis cumque accusantium reprehenderit laboriosam, voluptate,
|
||||
voluptas voluptates neque! Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis dolorum
|
||||
excepturi sunt. Omnis eaque, laborum adipisci unde est architecto deserunt fugit eos pariatur dicta? Cumque
|
||||
autem repellat distinctio ullam necessitatibus! Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet
|
||||
placeat voluptas, aliquam non delectus ratione labore illo, ipsa voluptate exercitationem repellendus
|
||||
consequatur blanditiis ullam autem excepturi molestias porro velit facere.
|
||||
</div>
|
||||
</EnterpriseIndexPageProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Burger,
|
||||
Group,
|
||||
Select,
|
||||
Switch,
|
||||
Text,
|
||||
useCoreAppShell,
|
||||
ActionIcon,
|
||||
@@ -11,8 +12,9 @@ import {
|
||||
UnstyledButton,
|
||||
Box,
|
||||
} from '@repo/ui/components';
|
||||
import { Globe, Bell, HelpCircle, Settings, User, Briefcase, LogOut, ChevronDown } from 'lucide-react';
|
||||
import { Globe, Bell, HelpCircle, Settings, User, Briefcase, LogOut, ChevronDown, Sun, Moon } from 'lucide-react';
|
||||
import { AppStorageKey, secureStorage } from '../../../../core/storage/local';
|
||||
import { useThemeStore } from '../../../../core/store/theme.store';
|
||||
import { NotificationDropdown } from './notifications/notification-dropdown';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
@@ -35,16 +37,10 @@ const getInitials = (name: string): string => {
|
||||
export default function HeaderLayout() {
|
||||
const { i18n, t } = useTranslation();
|
||||
const { mobileOpened, toggleMobile } = useCoreAppShell();
|
||||
const { colorScheme, setColorScheme } = useThemeStore();
|
||||
|
||||
return (
|
||||
<Group
|
||||
h="100%"
|
||||
px="md"
|
||||
justify="space-between"
|
||||
bg="var(--mantine-color-body)"
|
||||
style={{ borderBottom: '1px solid var(--mantine-color-default-border)' }}
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group h="100%" px="md" justify="space-between" bg="var(--mantine-color-body)" wrap="nowrap">
|
||||
{/* 1. LEFT SECTION (Navigation & Context) */}
|
||||
<Group wrap="nowrap" gap="sm">
|
||||
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
|
||||
@@ -57,7 +53,14 @@ export default function HeaderLayout() {
|
||||
|
||||
{/* 3. RIGHT SECTION (Actions & Profile) */}
|
||||
<Group wrap="nowrap" gap="sm">
|
||||
<ActionIcon component={Link} to="/app/system/information" variant="subtle" color="gray" size="lg" aria-label="Help">
|
||||
<ActionIcon
|
||||
component={Link}
|
||||
to="/app/system/information"
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="lg"
|
||||
aria-label="Help"
|
||||
>
|
||||
<HelpCircle size={18} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -136,6 +139,7 @@ export default function HeaderLayout() {
|
||||
cursor: 'pointer',
|
||||
height: '22px',
|
||||
minHeight: '22px',
|
||||
fontSize: 'var(--mantine-font-size-sm)',
|
||||
},
|
||||
root: {
|
||||
marginTop: '-4px',
|
||||
@@ -145,11 +149,30 @@ export default function HeaderLayout() {
|
||||
/>
|
||||
</Group>
|
||||
|
||||
{/* Custom Theme Item */}
|
||||
<Group justify="space-between" wrap="nowrap" px="sm" py="xs">
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
{colorScheme === 'dark' ? <Moon size={14} /> : <Sun size={14} />}
|
||||
<Text size="sm">{t('common:theme')}</Text>
|
||||
</Group>
|
||||
<Switch
|
||||
checked={colorScheme === 'dark'}
|
||||
onChange={(event) => setColorScheme(event.currentTarget.checked ? 'dark' : 'light')}
|
||||
size="sm"
|
||||
color="brand"
|
||||
onLabel={<Moon size={12} strokeWidth={2.5} color="var(--mantine-color-white)" />}
|
||||
offLabel={<Sun size={12} strokeWidth={2.5} color="var(--mantine-color-brand-6)" />}
|
||||
styles={{ track: { cursor: 'pointer' } }}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Menu.Divider />
|
||||
|
||||
{/* Application Section */}
|
||||
<Menu.Label>{t('common:application')}</Menu.Label>
|
||||
<Menu.Item component={Link} to="/app/system/setting" leftSection={<Settings size={14} />}>{t('common:settings')}</Menu.Item>
|
||||
<Menu.Item component={Link} to="/app/system/setting" leftSection={<Settings size={14} />}>
|
||||
{t('common:settings')}
|
||||
</Menu.Item>
|
||||
<Menu.Item leftSection={<User size={14} />}>{t('common:myProfile')}</Menu.Item>
|
||||
|
||||
<Menu.Divider />
|
||||
|
||||
+11
-3
@@ -78,7 +78,7 @@ export function NotificationDropdown({ children }: NotificationDropdownProps) {
|
||||
justify="space-between"
|
||||
px="md"
|
||||
py="xs"
|
||||
style={{ borderBottom: '1px solid var(--mantine-color-default-border)' }}
|
||||
style={{ borderBottom: '1px solid var(--app-shell-border-color)' }}
|
||||
>
|
||||
<Text fw={600} size="sm">
|
||||
{t('common:notificationsTitle')}
|
||||
@@ -129,7 +129,7 @@ export function NotificationDropdown({ children }: NotificationDropdownProps) {
|
||||
active={!item.isRead}
|
||||
variant="light"
|
||||
style={{
|
||||
borderBottom: '1px solid var(--mantine-color-default-border)',
|
||||
borderBottom: '1px solid var(--app-shell-border-color)',
|
||||
}}
|
||||
/>
|
||||
))
|
||||
@@ -138,7 +138,15 @@ export function NotificationDropdown({ children }: NotificationDropdownProps) {
|
||||
|
||||
{notifications.length > 0 && (
|
||||
<Box p="xs">
|
||||
<Button component={Link} to="/app/system/notifications" variant="light" color="gray" fullWidth size="xs" radius="md">
|
||||
<Button
|
||||
component={Link}
|
||||
to="/app/system/notifications"
|
||||
variant="light"
|
||||
color="gray"
|
||||
fullWidth
|
||||
size="xs"
|
||||
radius="md"
|
||||
>
|
||||
{t('common:viewAll')}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -44,6 +44,7 @@ const MenuItemExpanded = memo(function MenuItemExpanded({
|
||||
expandVersion = 0,
|
||||
collapseVersion = 0,
|
||||
}: MenuItemExpandedProps) {
|
||||
const { t } = useTranslation();
|
||||
const Icon = item.icon;
|
||||
const isActive = activeKeys.has(item.key);
|
||||
const hasChildren = item.children && item.children.length > 0;
|
||||
@@ -66,7 +67,7 @@ const MenuItemExpanded = memo(function MenuItemExpanded({
|
||||
<NavLink
|
||||
component={hasChildren ? 'button' : (Link as any)}
|
||||
to={hasChildren ? undefined : item.path}
|
||||
label={item.label}
|
||||
label={t(item.label)}
|
||||
leftSection={<Icon size={18} />}
|
||||
active={isExactActive}
|
||||
opened={isOpened}
|
||||
@@ -75,7 +76,7 @@ const MenuItemExpanded = memo(function MenuItemExpanded({
|
||||
styles={{
|
||||
root: {
|
||||
borderRadius: 'var(--mantine-radius-md)',
|
||||
color: isParentActive ? 'var(--mantine-primary-color-filled)' : undefined,
|
||||
color: isParentActive ? 'light-dark(var(--mantine-color-brand-6), var(--mantine-color-brand-4))' : undefined,
|
||||
},
|
||||
label: {
|
||||
overflow: 'hidden',
|
||||
@@ -110,6 +111,7 @@ interface MenuItemFlyoutProps {
|
||||
}
|
||||
|
||||
const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot = true }: MenuItemFlyoutProps) {
|
||||
const { t } = useTranslation();
|
||||
const Icon = item.icon;
|
||||
const isActive = activeKeys.has(item.key);
|
||||
const hasChildren = item.children && item.children.length > 0;
|
||||
@@ -118,14 +120,14 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
|
||||
if (!hasChildren) {
|
||||
if (isRoot) {
|
||||
return (
|
||||
<Tooltip label={item.label} position="right" withArrow transitionProps={{ transition: 'fade-right' }}>
|
||||
<Tooltip label={t(item.label)} position="right" withArrow transitionProps={{ transition: 'fade-right' }}>
|
||||
<ActionIcon
|
||||
component={Link as any}
|
||||
to={item.path}
|
||||
variant={isActive ? 'light' : 'subtle'}
|
||||
color={isActive ? undefined : 'gray'}
|
||||
color={isActive ? undefined : 'var(--mantine-color-text)'}
|
||||
size="lg"
|
||||
aria-label={item.label}
|
||||
aria-label={t(item.label)}
|
||||
>
|
||||
<Icon size={20} />
|
||||
</ActionIcon>
|
||||
@@ -137,7 +139,7 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
|
||||
component={Link as any}
|
||||
to={item.path}
|
||||
leftSection={<Icon size={14} />}
|
||||
color={isActive ? 'var(--mantine-primary-color-filled)' : undefined}
|
||||
color={isActive ? 'brand' : undefined}
|
||||
styles={{
|
||||
itemLabel: {
|
||||
overflow: 'hidden',
|
||||
@@ -147,7 +149,7 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
|
||||
},
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
{t(item.label)}
|
||||
</Menu.Item>
|
||||
);
|
||||
}
|
||||
@@ -157,9 +159,9 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
|
||||
const Target = isRoot ? (
|
||||
<ActionIcon
|
||||
variant={isActive ? 'light' : 'subtle'}
|
||||
color={isActive ? undefined : 'gray'}
|
||||
color={isActive ? undefined : 'var(--mantine-color-text)'}
|
||||
size="lg"
|
||||
aria-label={item.label}
|
||||
aria-label={t(item.label)}
|
||||
>
|
||||
<Icon size={20} />
|
||||
</ActionIcon>
|
||||
@@ -167,7 +169,7 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
|
||||
<Menu.Item
|
||||
leftSection={<Icon size={14} />}
|
||||
rightSection={<ChevronsRight size={14} />}
|
||||
color={isActive ? 'var(--mantine-primary-color-filled)' : undefined}
|
||||
color={isActive ? 'brand' : undefined}
|
||||
closeMenuOnClick={false} // Mencegah parent tertutup saat memunculkan Level 3
|
||||
styles={{
|
||||
itemLabel: {
|
||||
@@ -178,7 +180,7 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
|
||||
},
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
{t(item.label)}
|
||||
</Menu.Item>
|
||||
);
|
||||
|
||||
@@ -197,7 +199,7 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
|
||||
<Menu.Dropdown>
|
||||
{isRoot && (
|
||||
<>
|
||||
<Menu.Label>{item.label}</Menu.Label>
|
||||
<Menu.Label>{t(item.label)}</Menu.Label>
|
||||
<Menu.Divider />
|
||||
</>
|
||||
)}
|
||||
@@ -295,7 +297,7 @@ export const SidebarMenu = memo(function SidebarMenu({
|
||||
const query = searchQuery.toLowerCase();
|
||||
|
||||
const filterItem = (item: MenuItemType): MenuItemType | null => {
|
||||
const isMatch = item.label.toLowerCase().includes(query);
|
||||
const isMatch = t(item.label).toLowerCase().includes(query);
|
||||
|
||||
if (item.children) {
|
||||
const filteredChildren = item.children.map(filterItem).filter((child): child is MenuItemType => child !== null);
|
||||
@@ -336,11 +338,13 @@ export const SidebarMenu = memo(function SidebarMenu({
|
||||
setIsMac(typeof window !== 'undefined' && navigator.userAgent.includes('Mac'));
|
||||
}, []);
|
||||
|
||||
const shortcutText = isMac ? '⌘K' : 'Ctrl+K';
|
||||
const filterMenuShortcutText = isMac ? '⇧⌘M' : 'Ctrl+Shift+M';
|
||||
const toggleShortcutText = isMac ? '⇧⌘B' : 'Ctrl+Shift+B';
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||
// Shortcut 1: Cmd/Ctrl + Shift + M for SEARCH MENU
|
||||
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === 'm') {
|
||||
e.preventDefault();
|
||||
if (isMini) {
|
||||
handleExpandAndSearch();
|
||||
@@ -348,6 +352,12 @@ export const SidebarMenu = memo(function SidebarMenu({
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Shortcut 2: Cmd/Ctrl + Shift + B for TOGGLE SIDEBAR
|
||||
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === 'b') {
|
||||
e.preventDefault();
|
||||
handleToggle();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
@@ -359,20 +369,27 @@ export const SidebarMenu = memo(function SidebarMenu({
|
||||
<Box h="100%" style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{withMenuFilter && (
|
||||
<Box
|
||||
p="xs"
|
||||
p="md"
|
||||
style={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 10,
|
||||
backgroundColor: 'color-mix(in srgb, var(--mantine-color-body) 80%, transparent)',
|
||||
backdropFilter: 'blur(8px)',
|
||||
borderBottom: '1px solid var(--mantine-color-default-border)',
|
||||
borderBottom: '1px solid var(--app-shell-border-color)',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<ActionIcon variant="subtle" size="lg" onClick={handleExpandAndSearch} aria-label="Search menu">
|
||||
<Search size={18} />
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="md"
|
||||
onClick={handleExpandAndSearch}
|
||||
aria-label="Search menu"
|
||||
color="var(--mantine-color-text)"
|
||||
radius="md"
|
||||
>
|
||||
<Search size={20} />
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
)}
|
||||
@@ -387,7 +404,7 @@ export const SidebarMenu = memo(function SidebarMenu({
|
||||
<>
|
||||
<Divider />
|
||||
<Stack align="center" p="xs">
|
||||
<Tooltip label={t('common:expandSidebar')} position="right" withArrow>
|
||||
<Tooltip label={`${t('common:expandSidebar')} (${toggleShortcutText})`} position="right" withArrow>
|
||||
<ActionIcon variant="subtle" color="gray" size="lg" onClick={handleToggle} aria-label="Expand sidebar">
|
||||
<ChevronsRight size={18} />
|
||||
</ActionIcon>
|
||||
@@ -411,7 +428,7 @@ export const SidebarMenu = memo(function SidebarMenu({
|
||||
zIndex: 10,
|
||||
backgroundColor: 'color-mix(in srgb, var(--mantine-color-body) 80%, transparent)',
|
||||
backdropFilter: 'blur(8px)',
|
||||
borderBottom: '1px solid var(--mantine-color-default-border)',
|
||||
borderBottom: '1px solid var(--app-shell-border-color)',
|
||||
}}
|
||||
>
|
||||
<Box style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
@@ -439,11 +456,11 @@ export const SidebarMenu = memo(function SidebarMenu({
|
||||
pointerEvents: 'none',
|
||||
padding: '2px 4px',
|
||||
color: 'var(--mantine-color-dimmed)',
|
||||
border: '1px solid var(--mantine-color-default-border)',
|
||||
border: '1px solid var(--app-shell-border-color)',
|
||||
borderRadius: 'var(--mantine-radius-sm)',
|
||||
}}
|
||||
>
|
||||
{shortcutText}
|
||||
{filterMenuShortcutText}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -485,7 +502,7 @@ export const SidebarMenu = memo(function SidebarMenu({
|
||||
<Divider />
|
||||
<Box p="xs">
|
||||
<NavLink
|
||||
label={t('common:collapse')}
|
||||
label={`${t('common:collapse')} (${toggleShortcutText})`}
|
||||
leftSection={<ChevronsLeft size={18} />}
|
||||
onClick={handleToggle}
|
||||
variant="subtle"
|
||||
|
||||
@@ -35,31 +35,31 @@ import type { MenuItemType } from '../types/menu.types';
|
||||
export const MENU_ITEMS: MenuItemType[] = [
|
||||
{
|
||||
key: 'dashboard',
|
||||
label: 'Dashboard',
|
||||
label: 'nav:dashboard',
|
||||
icon: LayoutDashboard,
|
||||
path: '/app/dashboard',
|
||||
},
|
||||
{
|
||||
key: 'crm',
|
||||
label: 'CRM',
|
||||
label: 'nav:crm',
|
||||
icon: Users,
|
||||
path: '/app/crm',
|
||||
children: [
|
||||
{
|
||||
key: 'crm-leads',
|
||||
label: 'Leads',
|
||||
label: 'nav:crm-leads',
|
||||
icon: Briefcase,
|
||||
path: '/app/crm/leads',
|
||||
},
|
||||
{
|
||||
key: 'crm-pipelines',
|
||||
label: 'Pipelines',
|
||||
label: 'nav:crm-pipelines',
|
||||
icon: Activity,
|
||||
path: '/app/crm/pipelines',
|
||||
},
|
||||
{
|
||||
key: 'crm-contacts',
|
||||
label: 'Contacts',
|
||||
label: 'nav:crm-contacts',
|
||||
icon: Phone,
|
||||
path: '/app/crm/contacts',
|
||||
},
|
||||
@@ -67,25 +67,25 @@ export const MENU_ITEMS: MenuItemType[] = [
|
||||
},
|
||||
{
|
||||
key: 'sales',
|
||||
label: 'Sales',
|
||||
label: 'nav:sales',
|
||||
icon: ShoppingCart,
|
||||
path: '/app/sales',
|
||||
children: [
|
||||
{
|
||||
key: 'sales-quotations',
|
||||
label: 'Quotations',
|
||||
label: 'nav:sales-quotations',
|
||||
icon: FileText,
|
||||
path: '/app/sales/quotations',
|
||||
},
|
||||
{
|
||||
key: 'sales-orders',
|
||||
label: 'Sales Orders',
|
||||
label: 'nav:sales-orders',
|
||||
icon: Box,
|
||||
path: '/app/sales/orders',
|
||||
},
|
||||
{
|
||||
key: 'sales-invoices',
|
||||
label: 'Invoices',
|
||||
label: 'nav:sales-invoices',
|
||||
icon: Receipt,
|
||||
path: '/app/sales/invoices',
|
||||
},
|
||||
@@ -93,31 +93,31 @@ export const MENU_ITEMS: MenuItemType[] = [
|
||||
},
|
||||
{
|
||||
key: 'supply-chain',
|
||||
label: 'Supply Chain',
|
||||
label: 'nav:supply-chain',
|
||||
icon: Truck,
|
||||
path: '/app/supply-chain',
|
||||
children: [
|
||||
{
|
||||
key: 'sc-inventory',
|
||||
label: 'Inventory Management',
|
||||
label: 'nav:sc-inventory',
|
||||
icon: Box,
|
||||
path: '/app/supply-chain/inventory',
|
||||
children: [
|
||||
{
|
||||
key: 'sc-inventory-products',
|
||||
label: 'Products',
|
||||
label: 'nav:sc-inventory-products',
|
||||
icon: Layers,
|
||||
path: '/app/supply-chain/inventory/products',
|
||||
},
|
||||
{
|
||||
key: 'sc-inventory-categories',
|
||||
label: 'Categories',
|
||||
label: 'nav:sc-inventory-categories',
|
||||
icon: Globe,
|
||||
path: '/app/supply-chain/inventory/categories',
|
||||
},
|
||||
{
|
||||
key: 'sc-inventory-adjustments',
|
||||
label: 'Stock Adjustments',
|
||||
label: 'nav:sc-inventory-adjustments',
|
||||
icon: FileSearch,
|
||||
path: '/app/supply-chain/inventory/adjustments',
|
||||
},
|
||||
@@ -125,13 +125,13 @@ export const MENU_ITEMS: MenuItemType[] = [
|
||||
},
|
||||
{
|
||||
key: 'sc-warehouses',
|
||||
label: 'Warehouses',
|
||||
label: 'nav:sc-warehouses',
|
||||
icon: Warehouse,
|
||||
path: '/app/supply-chain/warehouses',
|
||||
},
|
||||
{
|
||||
key: 'sc-logistics',
|
||||
label: 'Logistics',
|
||||
label: 'nav:sc-logistics',
|
||||
icon: Globe,
|
||||
path: '/app/supply-chain/logistics',
|
||||
},
|
||||
@@ -139,19 +139,19 @@ export const MENU_ITEMS: MenuItemType[] = [
|
||||
},
|
||||
{
|
||||
key: 'manufacturing',
|
||||
label: 'Manufacturing',
|
||||
label: 'nav:manufacturing',
|
||||
icon: Factory,
|
||||
path: '/app/manufacturing',
|
||||
children: [
|
||||
{
|
||||
key: 'mfg-bom',
|
||||
label: 'Bill of Materials',
|
||||
label: 'nav:mfg-bom',
|
||||
icon: Layers,
|
||||
path: '/app/manufacturing/bom',
|
||||
},
|
||||
{
|
||||
key: 'mfg-work-orders',
|
||||
label: 'Work Orders',
|
||||
label: 'nav:mfg-work-orders',
|
||||
icon: HardHat,
|
||||
path: '/app/manufacturing/work-orders',
|
||||
},
|
||||
@@ -159,31 +159,31 @@ export const MENU_ITEMS: MenuItemType[] = [
|
||||
},
|
||||
{
|
||||
key: 'hris',
|
||||
label: 'HRIS',
|
||||
label: 'nav:hris',
|
||||
icon: Briefcase,
|
||||
path: '/app/hris',
|
||||
children: [
|
||||
{
|
||||
key: 'hris-employees',
|
||||
label: 'Employees',
|
||||
label: 'nav:hris-employees',
|
||||
icon: Users,
|
||||
path: '/app/hris/employees',
|
||||
},
|
||||
{
|
||||
key: 'hris-attendance',
|
||||
label: 'Attendance',
|
||||
label: 'nav:hris-attendance',
|
||||
icon: Clock,
|
||||
path: '/app/hris/attendance',
|
||||
},
|
||||
{
|
||||
key: 'hris-payroll',
|
||||
label: 'Payroll',
|
||||
label: 'nav:hris-payroll',
|
||||
icon: CreditCard,
|
||||
path: '/app/hris/payroll',
|
||||
},
|
||||
{
|
||||
key: 'hris-calendar',
|
||||
label: 'Company Calendar',
|
||||
label: 'nav:hris-calendar',
|
||||
icon: Calendar,
|
||||
path: '/app/hris/calendar',
|
||||
},
|
||||
@@ -191,19 +191,19 @@ export const MENU_ITEMS: MenuItemType[] = [
|
||||
},
|
||||
{
|
||||
key: 'accounting',
|
||||
label: 'Accounting',
|
||||
label: 'nav:accounting',
|
||||
icon: Calculator,
|
||||
path: '/app/accounting',
|
||||
children: [
|
||||
{
|
||||
key: 'acc-gl',
|
||||
label: 'General Ledger',
|
||||
label: 'nav:acc-gl',
|
||||
icon: Database,
|
||||
path: '/app/accounting/general-ledger',
|
||||
},
|
||||
{
|
||||
key: 'acc-taxes',
|
||||
label: 'Taxes',
|
||||
label: 'nav:acc-taxes',
|
||||
icon: PiggyBank,
|
||||
path: '/app/accounting/taxes',
|
||||
},
|
||||
@@ -211,25 +211,25 @@ export const MENU_ITEMS: MenuItemType[] = [
|
||||
},
|
||||
{
|
||||
key: 'settings',
|
||||
label: 'Settings & Configuration',
|
||||
label: 'nav:settings',
|
||||
icon: Settings,
|
||||
path: '/app/settings',
|
||||
children: [
|
||||
{
|
||||
key: 'settings-general',
|
||||
label: 'General Settings',
|
||||
label: 'nav:settings-general',
|
||||
icon: Settings,
|
||||
path: '/app/settings/general',
|
||||
},
|
||||
{
|
||||
key: 'settings-security',
|
||||
label: 'Security',
|
||||
label: 'nav:settings-security',
|
||||
icon: Shield,
|
||||
path: '/app/settings/security',
|
||||
},
|
||||
{
|
||||
key: 'long-text-2',
|
||||
label: 'Extremely Long Menu Name To Test Text Truncation Handling Properly',
|
||||
label: 'nav:long-text-2',
|
||||
icon: FileText,
|
||||
path: '/app/settings/long-menu-test',
|
||||
},
|
||||
@@ -237,19 +237,19 @@ export const MENU_ITEMS: MenuItemType[] = [
|
||||
},
|
||||
{
|
||||
key: 'example-module',
|
||||
label: 'Example Module',
|
||||
label: 'nav:example-module',
|
||||
icon: Database,
|
||||
path: '/app/example-module',
|
||||
children: [
|
||||
{
|
||||
key: 'example-full-page',
|
||||
label: 'Example Full Page',
|
||||
label: 'nav:example-full-page',
|
||||
icon: LayoutDashboard,
|
||||
path: '/app/example/full-page/index',
|
||||
},
|
||||
{
|
||||
key: 'example-single-page',
|
||||
label: 'Example Single Page',
|
||||
label: 'nav:example-single-page',
|
||||
icon: FileText,
|
||||
path: '/app/example/single-page/index',
|
||||
},
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"dashboard": "Dashboard",
|
||||
"crm": "CRM",
|
||||
"crm-leads": "Leads",
|
||||
"crm-pipelines": "Pipelines",
|
||||
"crm-contacts": "Contacts",
|
||||
"sales": "Sales",
|
||||
"sales-quotations": "Quotations",
|
||||
"sales-orders": "Sales Orders",
|
||||
"sales-invoices": "Invoices",
|
||||
"supply-chain": "Supply Chain",
|
||||
"sc-inventory": "Inventory Management",
|
||||
"sc-inventory-products": "Products",
|
||||
"sc-inventory-categories": "Categories",
|
||||
"sc-inventory-adjustments": "Stock Adjustments",
|
||||
"sc-warehouses": "Warehouses",
|
||||
"sc-logistics": "Logistics",
|
||||
"manufacturing": "Manufacturing",
|
||||
"mfg-bom": "Bill of Materials",
|
||||
"mfg-work-orders": "Work Orders",
|
||||
"hris": "HRIS",
|
||||
"hris-employees": "Employees",
|
||||
"hris-attendance": "Attendance",
|
||||
"hris-payroll": "Payroll",
|
||||
"hris-calendar": "Company Calendar",
|
||||
"accounting": "Accounting",
|
||||
"acc-gl": "General Ledger",
|
||||
"acc-taxes": "Taxes",
|
||||
"settings": "Settings & Configuration",
|
||||
"settings-general": "General Settings",
|
||||
"settings-security": "Security",
|
||||
"long-text-2": "Extremely Long Menu Name To Test Text Truncation Handling Properly",
|
||||
"example-module": "Example Module",
|
||||
"example-full-page": "Example Full Page",
|
||||
"example-single-page": "Example Single Page",
|
||||
"system": "System"
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"dashboard": "Dasbor",
|
||||
"crm": "CRM",
|
||||
"crm-leads": "Prospek",
|
||||
"crm-pipelines": "Alur Penjualan",
|
||||
"crm-contacts": "Kontak",
|
||||
"sales": "Penjualan",
|
||||
"sales-quotations": "Penawaran",
|
||||
"sales-orders": "Pesanan Penjualan",
|
||||
"sales-invoices": "Faktur",
|
||||
"supply-chain": "Rantai Pasok",
|
||||
"sc-inventory": "Manajemen Inventaris",
|
||||
"sc-inventory-products": "Produk",
|
||||
"sc-inventory-categories": "Kategori",
|
||||
"sc-inventory-adjustments": "Penyesuaian Stok",
|
||||
"sc-warehouses": "Gudang",
|
||||
"sc-logistics": "Logistik",
|
||||
"manufacturing": "Manufaktur",
|
||||
"mfg-bom": "Daftar Material",
|
||||
"mfg-work-orders": "Perintah Kerja",
|
||||
"hris": "HRIS",
|
||||
"hris-employees": "Karyawan",
|
||||
"hris-attendance": "Kehadiran",
|
||||
"hris-payroll": "Penggajian",
|
||||
"hris-calendar": "Kalender Perusahaan",
|
||||
"accounting": "Akuntansi",
|
||||
"acc-gl": "Buku Besar",
|
||||
"acc-taxes": "Pajak",
|
||||
"settings": "Pengaturan & Konfigurasi",
|
||||
"settings-general": "Pengaturan Umum",
|
||||
"settings-security": "Keamanan",
|
||||
"long-text-2": "Nama Menu Sangat Panjang Untuk Menguji Penanganan Pemotongan Teks Dengan Baik",
|
||||
"example-module": "Modul Contoh",
|
||||
"example-full-page": "Contoh Halaman Penuh",
|
||||
"example-single-page": "Contoh Halaman Tunggal",
|
||||
"system": "Sistem"
|
||||
}
|
||||
@@ -1,8 +1,18 @@
|
||||
import { CoreAppShell, CoreAppShellConfig } from '@repo/ui/components';
|
||||
import { registerModuleNamespace } from '@repo/core-i18n';
|
||||
import HeaderLayout from './components/header.layout';
|
||||
import { SidebarMenu } from './components/sidebar';
|
||||
import { MENU_ITEMS } from './data/menu.data';
|
||||
|
||||
import navEn from './locales/en/nav.json';
|
||||
import navId from './locales/id/nav.json';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Namespace Registration (Module Scope)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Called once at import time — safe, idempotent, outside React render cycle.
|
||||
registerModuleNamespace('nav', { en: navEn, id: navId });
|
||||
|
||||
export default function ModuleLayout({ children }: { children: React.ReactNode }) {
|
||||
const configAppShell: CoreAppShellConfig = {
|
||||
variant: 'header-first',
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { LucideIcon } from 'lucide-react';
|
||||
export interface MenuItemType {
|
||||
/** Unique identifier for the menu item */
|
||||
key: string;
|
||||
/** Display label */
|
||||
/** i18n translation key (e.g. 'nav:dashboard'). Resolved via t() at render time. */
|
||||
label: string;
|
||||
/** Lucide icon component */
|
||||
icon: LucideIcon;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export const shortcuts = [
|
||||
{
|
||||
label: 'information:shortcuts.create.label',
|
||||
desc: 'information:shortcuts.create.desc',
|
||||
macKeys: ['⌘', '⇧', 'N'],
|
||||
winKeys: ['Ctrl', 'Shift', 'N'],
|
||||
},
|
||||
{
|
||||
label: 'information:shortcuts.searchMenu.label',
|
||||
desc: 'information:shortcuts.searchMenu.desc',
|
||||
macKeys: ['⌘', '⇧', 'M'],
|
||||
winKeys: ['Ctrl', 'Shift', 'M'],
|
||||
},
|
||||
{
|
||||
label: 'information:shortcuts.toggleSidebar.label',
|
||||
desc: 'information:shortcuts.toggleSidebar.desc',
|
||||
macKeys: ['⌘', '⇧', 'B'],
|
||||
winKeys: ['Ctrl', 'Shift', 'B'],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,103 @@
|
||||
import { useTranslation } from '@repo/core-i18n';
|
||||
import { Group, Kbd, Table, Text, TextInput, Box, Center, ScrollArea } from '@repo/ui/components';
|
||||
import { Search, Keyboard } from 'lucide-react';
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
import { shortcuts } from './data';
|
||||
|
||||
export function Shortcut() {
|
||||
const { t } = useTranslation();
|
||||
const [isMac, setIsMac] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
// OS Detection for rendering appropriate keyboard shortcuts
|
||||
useEffect(() => {
|
||||
setIsMac(typeof window !== 'undefined' && navigator.userAgent.includes('Mac'));
|
||||
}, []);
|
||||
|
||||
// Filter logika pencarian berdasarkan label ATAU deskripsi yang sudah diterjemahkan
|
||||
const filteredShortcuts = useMemo(() => {
|
||||
const query = searchQuery.toLowerCase().trim();
|
||||
if (!query) return shortcuts;
|
||||
|
||||
return shortcuts.filter((item) => {
|
||||
const labelMatch = t(item.label).toLowerCase().includes(query);
|
||||
const descMatch = t(item.desc).toLowerCase().includes(query);
|
||||
return labelMatch || descMatch;
|
||||
});
|
||||
}, [searchQuery, t]);
|
||||
|
||||
const rows = filteredShortcuts.map((element, index) => {
|
||||
const keys = isMac ? element.macKeys : element.winKeys;
|
||||
return (
|
||||
<Table.Tr key={index}>
|
||||
<Table.Td>
|
||||
<Text size="sm" fw={600} style={{ color: 'var(--mantine-color-text)' }}>
|
||||
{t(element.label)}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mt={4} style={{ maxWidth: '600px', lineHeight: 1.4 }}>
|
||||
{t(element.desc)}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td w={250}>
|
||||
<Group gap={6} wrap="nowrap">
|
||||
{keys.map((key, i) => (
|
||||
<Kbd key={i} size="md" style={{ fontSize: '12px' }}>
|
||||
{key}
|
||||
</Kbd>
|
||||
))}
|
||||
</Group>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box p="md" style={{ borderBottom: '1px solid var(--mantine-color-default-border)' }}>
|
||||
<TextInput
|
||||
placeholder={t('information:info.searchPlaceholder')}
|
||||
leftSection={<Search size={16} style={{ opacity: 0.5 }} />}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.currentTarget.value)}
|
||||
w={{ base: '100%', sm: 350 }}
|
||||
radius="md"
|
||||
size="xs"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<ScrollArea.Autosize mah="calc(100vh - 350px)" offsetScrollbars>
|
||||
<Table
|
||||
stickyHeader
|
||||
stickyHeaderOffset={-1}
|
||||
striped
|
||||
highlightOnHover
|
||||
verticalSpacing="sm"
|
||||
horizontalSpacing="md"
|
||||
>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>{t('information:info.table.action')}</Table.Th>
|
||||
<Table.Th>{t('information:info.table.shortcut')}</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{rows.length > 0 ? (
|
||||
rows
|
||||
) : (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={2}>
|
||||
<Center py={40} style={{ flexDirection: 'column', gap: '8px' }}>
|
||||
<Keyboard size={32} style={{ opacity: 0.2 }} />
|
||||
<Text c="dimmed" size="sm" fw={500}>
|
||||
{t('information:info.emptySearch')}
|
||||
</Text>
|
||||
</Center>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</ScrollArea.Autosize>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useTranslation } from '@repo/core-i18n';
|
||||
import { Group, Text } from '@repo/ui/components';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
export function System() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Group justify="center" align="center" style={{ minHeight: 200, flexDirection: 'column' }}>
|
||||
<Info size={48} color="var(--mantine-color-dimmed)" strokeWidth={1.5} />
|
||||
<Text size="lg" fw={500} mt="md">
|
||||
{t('information:info.placeholder.title')}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" ta="center" maw={400}>
|
||||
{t('information:info.placeholder.description')}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +1,58 @@
|
||||
import { Container, Paper, Title, Text } from '@repo/ui/components';
|
||||
import { useTranslation } from '@repo/core-i18n';
|
||||
import { Tabs, Card, CorePageContainer } from '@repo/ui/components';
|
||||
import { Keyboard, Info } from 'lucide-react';
|
||||
import { ModulePageHeader } from '@repo/ui/foundations';
|
||||
import { useTranslation, registerModuleNamespace } from '@repo/core-i18n';
|
||||
|
||||
import { Shortcut } from './components/shortcut';
|
||||
import { System } from './components/system';
|
||||
|
||||
import informationId from './locales/id/information.json';
|
||||
import informationEn from './locales/en/information.json';
|
||||
|
||||
registerModuleNamespace('information', {
|
||||
id: informationId,
|
||||
en: informationEn,
|
||||
});
|
||||
|
||||
export default function InformationPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Container fluid p="md">
|
||||
<Paper shadow="sm" p="md" radius="md">
|
||||
<Title order={2} mb="xs">
|
||||
{t('common:systemInformation')}
|
||||
</Title>
|
||||
<Text c="dimmed">{t('common:systemInformationDesc')}</Text>
|
||||
</Paper>
|
||||
</Container>
|
||||
<CorePageContainer>
|
||||
<ModulePageHeader
|
||||
icon={Info}
|
||||
title={t('information:info.pageTitle')}
|
||||
description={t('information:info.pageDescription')}
|
||||
disableMinimize={true}
|
||||
moduleKey="SYSTEM_INFORMATION"
|
||||
breadcrumbs={[
|
||||
{ label: t('nav:system'), type: 'text' },
|
||||
{ label: t('information:info.pageTitle'), type: 'link', href: '/app/system/information' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<Tabs defaultValue="shortcuts" variant="outline" radius="md">
|
||||
<Tabs.List mb="md">
|
||||
<Tabs.Tab value="shortcuts" leftSection={<Keyboard size={16} />}>
|
||||
{t('information:info.tabs.shortcuts')}
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="system" leftSection={<Info size={16} />}>
|
||||
{t('information:info.tabs.information')}
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="shortcuts">
|
||||
<Card shadow="sm" radius="md" withBorder padding={'xl'}>
|
||||
<Shortcut />
|
||||
</Card>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="system">
|
||||
<Card shadow="sm" radius="md" withBorder padding="xl">
|
||||
<System />
|
||||
</Card>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</CorePageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"info": {
|
||||
"pageTitle": "Information",
|
||||
"pageDescription": "Central hub for system details, configurations, and user references.",
|
||||
"tabs": {
|
||||
"shortcuts": "Keyboard Shortcuts",
|
||||
"information": "System Information"
|
||||
},
|
||||
"searchPlaceholder": "Search shortcuts...",
|
||||
"emptySearch": "No shortcuts found",
|
||||
"table": {
|
||||
"action": "Action",
|
||||
"shortcut": "Shortcut"
|
||||
},
|
||||
"placeholder": {
|
||||
"title": "System Details Unavailable",
|
||||
"description": "Detailed information regarding package versions, build environments, and core dependencies will be displayed here in a future update."
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"create": {
|
||||
"label": "Create / Add Data",
|
||||
"desc": "Works on index or detail pages containing an add data action button, provided you have the required access permissions."
|
||||
},
|
||||
"searchMenu": {
|
||||
"label": "Search Navigation Menu",
|
||||
"desc": "Works globally across all pages to quickly search for menus."
|
||||
},
|
||||
"toggleSidebar": {
|
||||
"label": "Toggle Sidebar",
|
||||
"desc": "Works globally to collapse or expand the main navigation panel to maximize workspace area."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"info": {
|
||||
"pageTitle": "Informasi",
|
||||
"pageDescription": "Pusat informasi untuk detail sistem, konfigurasi, dan referensi pengguna.",
|
||||
"tabs": {
|
||||
"shortcuts": "Pintasan Keyboard",
|
||||
"information": "Informasi Sistem"
|
||||
},
|
||||
"searchPlaceholder": "Cari pintasan...",
|
||||
"emptySearch": "Pintasan tidak ditemukan",
|
||||
"table": {
|
||||
"action": "Aksi",
|
||||
"shortcut": "Pintasan"
|
||||
},
|
||||
"placeholder": {
|
||||
"title": "Detail Sistem Belum Tersedia",
|
||||
"description": "Informasi detail mengenai versi paket, environment build, dan dependensi inti akan ditampilkan di sini pada pembaruan mendatang."
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"create": {
|
||||
"label": "Buat / Tambah Data",
|
||||
"desc": "Berfungsi di halaman indeks atau detail yang memiliki tombol aksi tambah data, serta memerlukan hak akses (permission) yang sah."
|
||||
},
|
||||
"searchMenu": {
|
||||
"label": "Cari Menu Navigasi",
|
||||
"desc": "Berfungsi secara global di seluruh halaman untuk mencari menu dengan cepat."
|
||||
},
|
||||
"toggleSidebar": {
|
||||
"label": "Buka/Tutup Sidebar",
|
||||
"desc": "Berfungsi secara global untuk melipat atau melebarkan panel navigasi utama agar ruang kerja menjadi lebih luas."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
import { Card, Title, Text, Table, Stack, Badge } from '@repo/ui/components';
|
||||
import { PageActions, RowActions, PageAction, RowAction } from '@repo/ui/components';
|
||||
import { PageActions, RowActions, PageActionProps, RowActionProps } from '@repo/ui/components';
|
||||
import { Save, Printer, Trash, MoreVertical, Edit, FileText, CheckCircle, Check } from 'lucide-react';
|
||||
|
||||
export default function ActionToolsShowcase() {
|
||||
const pageActions: PageAction[] = [
|
||||
const pageActions: PageActionProps[] = [
|
||||
{
|
||||
key: 'save',
|
||||
label: 'Save Changes',
|
||||
icon: <Save size={16} />,
|
||||
variant: 'filled',
|
||||
onClick: (key) => console.log('Clicked', key),
|
||||
},
|
||||
{
|
||||
key: 'save',
|
||||
label: 'Save Changes',
|
||||
@@ -65,7 +72,7 @@ export default function ActionToolsShowcase() {
|
||||
},
|
||||
];
|
||||
|
||||
const rowActions: RowAction[] = [
|
||||
const rowActions: RowActionProps[] = [
|
||||
{
|
||||
key: 'edit',
|
||||
tooltip: 'Edit Record',
|
||||
@@ -111,7 +118,7 @@ export default function ActionToolsShowcase() {
|
||||
<Text c="dimmed" mb="lg">
|
||||
Used in toolbars and page headers.
|
||||
</Text>
|
||||
<PageActions actions={pageActions} onClose={() => {}} />
|
||||
<PageActions actions={pageActions} />
|
||||
</Card>
|
||||
|
||||
<Card withBorder shadow="sm" radius="md" p="md">
|
||||
|
||||
@@ -87,12 +87,7 @@ export function LiveStockGrid() {
|
||||
■ Stop Feed
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="light"
|
||||
color="info"
|
||||
onClick={() => setShowAll((s) => !s)}
|
||||
size="sm"
|
||||
>
|
||||
<Button variant="light" color="info" onClick={() => setShowAll((s) => !s)} size="sm">
|
||||
{showAll ? `Show ${VISIBLE_ROWS} rows` : `Show all ${STOCK_COUNT} rows`}
|
||||
</Button>
|
||||
</Group>
|
||||
@@ -118,7 +113,7 @@ export function LiveStockGrid() {
|
||||
</Text>
|
||||
|
||||
{/* ── Data Grid ─────────────────────────────────────────── */}
|
||||
<div style={{ maxHeight: 500, overflow: 'auto', border: '1px solid var(--mantine-color-default-border)' }}>
|
||||
<div style={{ maxHeight: 500, overflow: 'auto', border: '1px solid var(--app-shell-border-color)' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr
|
||||
@@ -133,11 +128,51 @@ export function LiveStockGrid() {
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<th style={{ padding: '6px 8px', textAlign: 'left', borderBottom: '2px solid var(--mantine-color-default-border)' }}>Ticker</th>
|
||||
<th style={{ padding: '6px 8px', textAlign: 'right', borderBottom: '2px solid var(--mantine-color-default-border)' }}>Price</th>
|
||||
<th style={{ padding: '6px 8px', textAlign: 'right', borderBottom: '2px solid var(--mantine-color-default-border)' }}>Change</th>
|
||||
<th style={{ padding: '6px 8px', textAlign: 'right', borderBottom: '2px solid var(--mantine-color-default-border)' }}>Volume</th>
|
||||
<th style={{ padding: '6px 8px', textAlign: 'right', borderBottom: '2px solid var(--mantine-color-default-border)' }}>Renders</th>
|
||||
<th
|
||||
style={{
|
||||
padding: '6px 8px',
|
||||
textAlign: 'left',
|
||||
borderBottom: '2px solid var(--app-shell-border-color)',
|
||||
}}
|
||||
>
|
||||
Ticker
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
padding: '6px 8px',
|
||||
textAlign: 'right',
|
||||
borderBottom: '2px solid var(--app-shell-border-color)',
|
||||
}}
|
||||
>
|
||||
Price
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
padding: '6px 8px',
|
||||
textAlign: 'right',
|
||||
borderBottom: '2px solid var(--app-shell-border-color)',
|
||||
}}
|
||||
>
|
||||
Change
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
padding: '6px 8px',
|
||||
textAlign: 'right',
|
||||
borderBottom: '2px solid var(--app-shell-border-color)',
|
||||
}}
|
||||
>
|
||||
Volume
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
padding: '6px 8px',
|
||||
textAlign: 'right',
|
||||
borderBottom: '2px solid var(--app-shell-border-color)',
|
||||
}}
|
||||
>
|
||||
Renders
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -230,7 +230,7 @@ export default function ShellDemo() {
|
||||
px="md"
|
||||
justify="space-between"
|
||||
bg="gray.1"
|
||||
style={{ borderTop: '1px solid var(--mantine-color-default-border)' }}
|
||||
style={{ borderTop: '1px solid var(--app-shell-border-color)' }}
|
||||
>
|
||||
<Text size="sm" fw={600}>
|
||||
Mock Footer (bg="gray.1")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, Suspense } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ColorSchemeType, DensityType } from '@repo/ui/provider';
|
||||
import { useThemeStore } from '../../core/store/theme.store';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
@@ -34,13 +35,12 @@ import FormDemoView from './example/features/form-demo';
|
||||
import { COMPONENTS_REGISTRY } from './registry';
|
||||
|
||||
interface ShowcaseViewProps {
|
||||
colorScheme: ColorSchemeType;
|
||||
setColorScheme: (val: ColorSchemeType) => void;
|
||||
density: DensityType;
|
||||
setDensity: (val: DensityType) => void;
|
||||
}
|
||||
|
||||
export default function ShowcaseView({ colorScheme, setColorScheme, density, setDensity }: ShowcaseViewProps) {
|
||||
export default function ShowcaseView({ density, setDensity }: ShowcaseViewProps) {
|
||||
const { colorScheme, setColorScheme } = useThemeStore();
|
||||
const [activeTab, setActiveTab] = useState<string | null>('ui-components');
|
||||
const { i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
@@ -103,7 +103,7 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
|
||||
list: {
|
||||
minWidth: 260,
|
||||
padding: '1rem',
|
||||
borderRight: '1px solid var(--mantine-color-default-border)',
|
||||
borderRight: '1px solid var(--app-shell-border-color)',
|
||||
backgroundColor: 'var(--mantine-color-default-element-bg)',
|
||||
},
|
||||
panel: { flex: 1, display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden' },
|
||||
@@ -407,8 +407,6 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
|
||||
)
|
||||
);
|
||||
})}
|
||||
|
||||
|
||||
</Container>
|
||||
</Box>
|
||||
</Tabs.Panel>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createLocalStorage, createIndexedDB } from '@repo/core-storage';
|
||||
export const AppStorageKey = {
|
||||
USER_PROFILE: 'user_profile',
|
||||
LOCALE: 'app_locale',
|
||||
THEME: 'app_theme',
|
||||
ACCESS_TOKEN: 'access_token',
|
||||
REFRESH_TOKEN: 'refresh_token',
|
||||
MOCK_DB_COMPANY_A: 'mock_db_company_a',
|
||||
@@ -19,6 +20,7 @@ export const ENCRYPTED_KEYS = new Set<AppStorageKeyValue>([
|
||||
|
||||
export const PLAIN_KEYS = new Set<AppStorageKeyValue>([
|
||||
AppStorageKey.LOCALE,
|
||||
AppStorageKey.THEME,
|
||||
AppStorageKey.MOCK_DB_COMPANY_A,
|
||||
AppStorageKey.OFFLINE_DRAFT,
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import type { ColorSchemeType } from '@repo/ui/provider';
|
||||
|
||||
interface ThemeState {
|
||||
colorScheme: ColorSchemeType;
|
||||
setColorScheme: (scheme: ColorSchemeType) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global theme store with localStorage persistence.
|
||||
*
|
||||
* Uses the Zustand `persist` middleware so the chosen color scheme
|
||||
* survives page refreshes. The storage key (`app_theme`) is
|
||||
* intentionally kept in sync with `AppStorageKey.THEME` — both
|
||||
* write to the same localStorage entry.
|
||||
*/
|
||||
export const useThemeStore = create<ThemeState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
colorScheme: 'light',
|
||||
setColorScheme: (scheme) => set({ colorScheme: scheme }),
|
||||
}),
|
||||
{
|
||||
name: 'app_theme', // matches AppStorageKey.THEME
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
},
|
||||
),
|
||||
);
|
||||
Reference in New Issue
Block a user