feat: enhance data-table with search, filter/setting drawers, and updated UI controls
This commit is contained in:
@@ -40,8 +40,11 @@
|
|||||||
"collapseAll": "Collapse all menu",
|
"collapseAll": "Collapse all menu",
|
||||||
"searchMenu": "Search menu",
|
"searchMenu": "Search menu",
|
||||||
"searchData": "Search data",
|
"searchData": "Search data",
|
||||||
|
"searchPlaceholder": "Type to search & press Enter...",
|
||||||
"collapse": "Collapse",
|
"collapse": "Collapse",
|
||||||
"expandSidebar": "Expand Sidebar",
|
"expandSidebar": "Expand Sidebar",
|
||||||
|
"filterTitle": "Filter {{module}}",
|
||||||
|
"tableSettingTitle": "Table Setting {{module}}",
|
||||||
"actions": {
|
"actions": {
|
||||||
"create": "Create New",
|
"create": "Create New",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
|
|||||||
@@ -40,8 +40,11 @@
|
|||||||
"collapseAll": "Tutup semua menu",
|
"collapseAll": "Tutup semua menu",
|
||||||
"searchMenu": "Cari menu",
|
"searchMenu": "Cari menu",
|
||||||
"searchData": "Cari data",
|
"searchData": "Cari data",
|
||||||
|
"searchPlaceholder": "Ketik pencarian & tekan Enter...",
|
||||||
"collapse": "Tutup",
|
"collapse": "Tutup",
|
||||||
"expandSidebar": "Perluas Sidebar",
|
"expandSidebar": "Perluas Bilah Sisi",
|
||||||
|
"filterTitle": "Filter {{module}}",
|
||||||
|
"tableSettingTitle": "Pengaturan Tabel {{module}}",
|
||||||
"actions": {
|
"actions": {
|
||||||
"create": "Buat Baru",
|
"create": "Buat Baru",
|
||||||
"edit": "Ubah",
|
"edit": "Ubah",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { memo, Fragment } from 'react';
|
import { memo, Fragment } from 'react';
|
||||||
import { Group, Button, Menu, Divider, ActionIcon, Box, ButtonProps, Tooltip } from '@mantine/core';
|
import { Group, Button, Menu, Divider, ActionIcon, Box, ButtonProps, Tooltip, MantineSpacing } from '@mantine/core';
|
||||||
import { ChevronDown, MoreVertical } from 'lucide-react';
|
import { ChevronDown, MoreVertical } from 'lucide-react';
|
||||||
import { PageActionProps } from './types';
|
import { PageActionProps } from './types';
|
||||||
import { getIntentColor } from './utils';
|
import { getIntentColor } from './utils';
|
||||||
@@ -8,13 +8,16 @@ export interface PageActionsProps {
|
|||||||
/** Array of configured page-level actions. */
|
/** Array of configured page-level actions. */
|
||||||
actions?: PageActionProps[];
|
actions?: PageActionProps[];
|
||||||
customButtonProps?: (action: PageActionProps) => ButtonProps;
|
customButtonProps?: (action: PageActionProps) => ButtonProps;
|
||||||
|
gapActionDesktop?:MantineSpacing;
|
||||||
|
gapActionMobile?:MantineSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A responsive and flexible presentational component for page-level actions.
|
* A responsive and flexible presentational component for page-level actions.
|
||||||
* Automatically adapts layout based on screen size.
|
* Automatically adapts layout based on screen size.
|
||||||
*/
|
*/
|
||||||
export const PageActions = memo(function PageActions({ actions = [], customButtonProps }: PageActionsProps) {
|
export const PageActions = memo(function PageActions(props: PageActionsProps) {
|
||||||
|
const { actions = [], customButtonProps, gapActionDesktop='xs', gapActionMobile='sm' }=props;
|
||||||
if (!actions || actions?.length === 0) {
|
if (!actions || actions?.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -33,7 +36,7 @@ export const PageActions = memo(function PageActions({ actions = [], customButto
|
|||||||
return (
|
return (
|
||||||
<Box style={{ display: 'inline-flex' }}>
|
<Box style={{ display: 'inline-flex' }}>
|
||||||
{/* --- DESKTOP VIEW --- */}
|
{/* --- DESKTOP VIEW --- */}
|
||||||
<Group gap="xs" wrap="nowrap" visibleFrom="md">
|
<Group gap={gapActionDesktop} wrap="nowrap" visibleFrom="md">
|
||||||
{actions.map((action, index) => {
|
{actions.map((action, index) => {
|
||||||
if (action.type === 'divider') {
|
if (action.type === 'divider') {
|
||||||
return <Divider key={`divider-${index}`} orientation="vertical" mr="sm" ml="sm" />;
|
return <Divider key={`divider-${index}`} orientation="vertical" mr="sm" ml="sm" />;
|
||||||
@@ -139,7 +142,7 @@ export const PageActions = memo(function PageActions({ actions = [], customButto
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{/* --- MOBILE VIEW --- */}
|
{/* --- MOBILE VIEW --- */}
|
||||||
<Group gap="xs" wrap="nowrap" hiddenFrom="md">
|
<Group gap={gapActionMobile} wrap="nowrap" hiddenFrom="md">
|
||||||
<Menu position="bottom-end" withArrow withinPortal>
|
<Menu position="bottom-end" withArrow withinPortal>
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<ActionIcon variant="outline" size="md">
|
<ActionIcon variant="outline" size="md">
|
||||||
|
|||||||
+1
-6
@@ -6,7 +6,6 @@ import {
|
|||||||
} from '../../../hooks/use-module.context';
|
} from '../../../hooks/use-module.context';
|
||||||
import { Trash2, CheckCircle, XCircle, PauseCircle, RotateCcw, X, Check } from 'lucide-react';
|
import { Trash2, CheckCircle, XCircle, PauseCircle, RotateCcw, X, Check } from 'lucide-react';
|
||||||
import { PageActions, PageActionProps } from '../../../../../components';
|
import { PageActions, PageActionProps } from '../../../../../components';
|
||||||
import { Group } from '@mantine/core';
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Types
|
// Types
|
||||||
@@ -159,9 +158,5 @@ export function BulkActionMenu({
|
|||||||
|
|
||||||
if (actions.length === 0) return null;
|
if (actions.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return <PageActions actions={actions} />;
|
||||||
<Group justify="flex-end" mb="sm">
|
|
||||||
<PageActions actions={actions} />
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
import { Drawer, Text } from '@mantine/core';
|
||||||
|
|
||||||
|
export interface TableFilterDrawerProps {
|
||||||
|
opened: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TableFilterDrawer({ opened, onClose, title }: TableFilterDrawerProps) {
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title={<Text fw={600}>{title}</Text>}
|
||||||
|
position="right"
|
||||||
|
size="md"
|
||||||
|
padding="md"
|
||||||
|
>
|
||||||
|
<Text c="dimmed" size="sm">
|
||||||
|
Filter configuration will go here.
|
||||||
|
</Text>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
import { Drawer, Text } from '@mantine/core';
|
||||||
|
|
||||||
|
export interface TableSettingDrawerProps {
|
||||||
|
opened: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TableSettingDrawer({ opened, onClose, title }: TableSettingDrawerProps) {
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title={<Text fw={600}>{title}</Text>}
|
||||||
|
position="right"
|
||||||
|
size="md"
|
||||||
|
padding="md"
|
||||||
|
>
|
||||||
|
<Text c="dimmed" size="sm">
|
||||||
|
Table setting configuration will go here.
|
||||||
|
</Text>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -11,9 +11,11 @@ import {
|
|||||||
DefaultMenuItem,
|
DefaultMenuItem,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
} from 'ag-grid-community';
|
} from 'ag-grid-community';
|
||||||
import { Box } from '@mantine/core';
|
import { Box, Group, TextInput } from '@mantine/core';
|
||||||
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
import { Search, Filter, Settings } from 'lucide-react';
|
||||||
|
|
||||||
import { DataGrid, StatusBadge } from '../../../../components';
|
import { DataGrid, StatusBadge, PageActions } from '../../../../components';
|
||||||
import type { PageActionProps } from '../../../../components';
|
import type { PageActionProps } from '../../../../components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -37,6 +39,8 @@ import { RowActionMenu } from './components/row-actions';
|
|||||||
import { BulkActionMenu } from './components/bulk-actions';
|
import { BulkActionMenu } from './components/bulk-actions';
|
||||||
import { ActionConfirmationModal, ACTION_TRANSLATION_MAP } from '../action-confirmation-modal';
|
import { ActionConfirmationModal, ACTION_TRANSLATION_MAP } from '../action-confirmation-modal';
|
||||||
import { BulkActionConfirmationModal } from '../bulk-action-confirmation';
|
import { BulkActionConfirmationModal } from '../bulk-action-confirmation';
|
||||||
|
import { TableFilterDrawer } from './components/table-filter-drawer';
|
||||||
|
import { TableSettingDrawer } from './components/table-setting-drawer';
|
||||||
import { EntityId } from '../../../../../../core-api/src/data-services/types';
|
import { EntityId } from '../../../../../../core-api/src/data-services/types';
|
||||||
|
|
||||||
export * from 'ag-grid-community';
|
export * from 'ag-grid-community';
|
||||||
@@ -184,6 +188,9 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Local UI State
|
// Local UI State
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
const [openedFilter, { open: openFilter, close: closeFilter }] = useDisclosure(false);
|
||||||
|
const [openedSetting, { open: openSetting, close: closeSetting }] = useDisclosure(false);
|
||||||
|
const moduleTitle = config.tabTitle || t(`${config.translationNamespace}:title`);
|
||||||
|
|
||||||
// Reference to the AG Grid API for programmatic interaction
|
// Reference to the AG Grid API for programmatic interaction
|
||||||
const gridApiRef = useRef<GridApi<E> | null>(null);
|
const gridApiRef = useRef<GridApi<E> | null>(null);
|
||||||
@@ -690,6 +697,38 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
{/* TABLE HEADER (Search, Filter, Bulk Actions) */}
|
||||||
|
<Group justify="space-between" align="center" mb="sm">
|
||||||
|
<Group gap="sm">
|
||||||
|
<TextInput
|
||||||
|
size="sm"
|
||||||
|
w={{ base: '100%', sm: 350 }}
|
||||||
|
placeholder={t('common:searchPlaceholder')}
|
||||||
|
leftSection={<Search size={16} />}
|
||||||
|
/>
|
||||||
|
<PageActions
|
||||||
|
actions={[
|
||||||
|
{ type: 'divider', key: 'search-divider' },
|
||||||
|
{
|
||||||
|
key: 'filter',
|
||||||
|
icon: <Filter size={16} />,
|
||||||
|
variant: 'default',
|
||||||
|
showLabel: false,
|
||||||
|
tooltipLabel: t('common:actions.filter'),
|
||||||
|
onClick: openFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'setting',
|
||||||
|
icon: <Settings size={16} />,
|
||||||
|
variant: 'default',
|
||||||
|
showLabel: false,
|
||||||
|
tooltipLabel: t('common:actions.setting'),
|
||||||
|
onClick: openSetting,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
{/* BULK ACTION TOOLBAR — shown when rows are selected */}
|
{/* BULK ACTION TOOLBAR — shown when rows are selected */}
|
||||||
<BulkActionMenu
|
<BulkActionMenu
|
||||||
selectedRows={selectedRows as E[]}
|
selectedRows={selectedRows as E[]}
|
||||||
@@ -697,6 +736,7 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
|
|||||||
statusKey={statusKey}
|
statusKey={statusKey}
|
||||||
customBulkActions={customBulkActions}
|
customBulkActions={customBulkActions}
|
||||||
/>
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
{/* GRID CONTAINER */}
|
{/* GRID CONTAINER */}
|
||||||
<Box
|
<Box
|
||||||
@@ -745,6 +785,17 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
|
|||||||
t={t}
|
t={t}
|
||||||
batchSize={batchSize}
|
batchSize={batchSize}
|
||||||
/>
|
/>
|
||||||
|
<TableFilterDrawer
|
||||||
|
opened={openedFilter}
|
||||||
|
onClose={closeFilter}
|
||||||
|
title={t('common:filterTitle', { module: moduleTitle })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TableSettingDrawer
|
||||||
|
opened={openedSetting}
|
||||||
|
onClose={closeSetting}
|
||||||
|
title={t('common:tableSettingTitle', { module: moduleTitle })}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user