feat(i18n): add "save_changes" translations for English and Indonesian

fix(validation): improve Indonesian validation messages for clarity

refactor(ui): optimize useAsyncPaginate hook for better readability

fix(data-table): enhance error handling with detailed messages in EnterpriseDataTable

feat(enterprise-module): add form control to EnterpriseFormPageConfig and implement save confirmation modal

refactor(form-page): streamline form page context and improve save handling logic

feat(index-page): set document title based on config or translation key

refactor(module-provider): clean up comments and improve code organization
This commit is contained in:
Firman Ramdhani
2026-07-27 11:14:15 +07:00
parent b332da4808
commit e55c33069b
18 changed files with 776 additions and 363 deletions
@@ -12,22 +12,9 @@ import { compose, required, rangeLength } from '@repo/ui/validators';
export const createFullPageSchema = (t: any) => {
return z.object({
// Code: Required, length between 3 and 10 characters.
code: compose(z.string(), required(t.fields.code), rangeLength(3, 10, t.fields.code)),
code: compose(z.string(), required(t('fields.code'))),
// Name: Required, length between 3 and 50 characters.
name: compose(z.string(), required(t.fields.name), rangeLength(3, 50, t.fields.name)),
// Status: Required selection (typically from a dropdown/select).
status: compose(z.string(), required(t.fields.status)),
// Description: Optional text field.
description: z.string().optional(),
name: compose(z.string(), required(t('fields.name')), rangeLength(3, 50, t('fields.name'))),
});
};
/**
* Data Transfer Object (DTO) for the Full Page form.
* This type is automatically inferred from the Zod schema factory.
* Use this type as a generic for form initialization, e.g., `useForm<FullPageFormDTO>()`.
*/
export type FullPageFormDTO = z.infer<ReturnType<typeof createFullPageSchema>>;
@@ -37,9 +37,9 @@ export default function FullPageModule() {
<Routes>
<Route path="/index" element={<IndexPage />} />
<Route path="/detail/:dataId" element={<DetailPage />} />
<Route path="/edit/:dataId" element={<FormPage formPageType="edit" />} />
<Route path="/duplicate/:dataId" element={<FormPage formPageType="duplicate" />} />
<Route path="/create" element={<FormPage formPageType="create" />} />
<Route path="/edit/:dataId" element={<FormPage formPageType="EDIT" />} />
<Route path="/duplicate/:dataId" element={<FormPage formPageType="DUPLICATE" />} />
<Route path="/create" element={<FormPage formPageType="CREATE" />} />
<Route path="/" element={<Navigate to={`${fullPageModuleConfig.webUrl}/index`} replace={true} />} />
<Route path="*" element={<Navigate to={'/404'} replace={true} />} />
</Routes>
@@ -2,10 +2,12 @@
"title": "Full Page",
"detail_page_title": "Detail Full Page",
"create_page_title": "New Full Page",
"edit_page_title": "Edit Full Page",
"duplicate_page_title": "Duplicate Full Page",
"description": "An example module of a <1>full page layout</1> for detailed forms.",
"detail_page_description": "View and manage detailed information for this record.",
"create_page_description": "Fill out the form below to add a new record to the system.",
"edit_page_description": "Update and modify the information of the selected record.",
"duplicate_page_description": "Copy and modify information from an existing record to quickly create a new one.",
"fields": {
"code": "Code",
@@ -2,10 +2,12 @@
"title": "Halaman Penuh",
"detail_page_title": "Detail Halaman Penuh",
"create_page_title": "Buat Halaman Penuh Baru",
"edit_page_title": "Ubah Halaman Penuh",
"duplicate_page_title": "Duplikat Halaman Penuh",
"description": "Contoh penerapan <1>tata letak halaman penuh</1> untuk formulir detail.",
"detail_page_description": "Lihat dan kelola informasi terperinci terkait data ini.",
"create_page_description": "Lengkapi formulir di bawah ini untuk menambahkan data baru ke dalam sistem.",
"edit_page_description": "Perbarui dan ubah informasi pada data yang dipilih.",
"duplicate_page_description": "Salin dan sesuaikan informasi dari data yang sudah ada untuk mempercepat pembuatan data baru.",
"fields": {
"code": "Kode",
@@ -1,146 +1,69 @@
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';
import { Paper, Box, Stack } from '@repo/ui/components';
import { FieldTextInput } from '@repo/ui/form';
import { useEnterpriseModuleTranslationContext, EnterpriseFormPageProvider, FormPageType } from '@repo/ui/foundations';
import { useMemo } from 'react';
import { fullPageModuleConfig } from '../../domain/constants';
import { createFullPageSchema } from '../../domain/validators/full-page.validator';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
export default function FullPagePageForm({ formPageType }: { formPageType: 'edit' | 'create' | 'duplicate' }) {
export default function FullPagePageForm({ formPageType }: { formPageType: FormPageType }) {
const { t } = useEnterpriseModuleTranslationContext();
const title = useMemo(() => {
if (formPageType === 'CREATE') {
return { title: t('create_page_title'), description: t('create_page_description') };
} else if (formPageType === 'EDIT') {
return { title: t('edit_page_title'), description: t('edit_page_description') };
} else if (formPageType === 'DUPLICATE') {
return { title: t('duplicate_page_title'), description: t('duplicate_page_description') };
}
return { title: '', description: '' };
}, [formPageType, t]);
const validator = useMemo(() => {
return createFullPageSchema(t);
}, [t]);
const formControl = useForm({ resolver: zodResolver(validator) });
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 --- */}
<EnterpriseFormPageProvider
formControl={formControl}
formPageType={formPageType}
ignoreKeyDuplicate={['code']}
highlightDataKey="code"
pageHeaderProps={{
title: title?.title,
description: title?.description,
breadcrumbs: [
{ label: t('nav:example-module'), type: 'text' },
{ label: t('nav:example-full-page'), type: 'link', href: `${fullPageModuleConfig.webUrl}/index` },
],
}}
>
<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
<FieldTextInput
control={formControl.control}
name="code"
label={t('fields.code')}
placeholder="e.g. WID-001"
required
defaultValue={formPageType === 'edit' ? 'WID-001' : ''}
radius="md"
/>
<TextInput
<FieldTextInput
name="name"
control={formControl.control}
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>
</EnterpriseFormPageProvider>
);
}