feat: enhance data-table with search, filter/setting drawers, and updated UI controls

This commit is contained in:
Firman Ramdhani
2026-07-23 12:30:39 +07:00
parent 4e13423409
commit ddf11741e9
7 changed files with 123 additions and 20 deletions
@@ -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">
@@ -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>
);
} }
@@ -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>
);
}
@@ -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>
); );
} }