From b332da4808d31d4b3632ae4d30b83e2c137fd905 Mon Sep 17 00:00:00 2001 From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:21:27 +0700 Subject: [PATCH 1/5] feat: enhance confirmation dialogs and form error handling; add new utility functions --- .../core-i18n/src/languages/en/common.json | 17 ++- .../core-i18n/src/languages/id/common.json | 17 ++- .../action-confirmation-modal/index.tsx | 5 + .../enterprise-module/entities/entity.ts | 119 ++++++++---------- .../providers/detail-page.provider.tsx | 8 +- .../providers/index-page.provider.tsx | 2 +- packages/utils/package.json | 4 +- packages/utils/src/index.ts | 1 + packages/utils/src/lodash/lodash.utils.ts | 3 + pnpm-lock.yaml | 14 +++ 10 files changed, 117 insertions(+), 73 deletions(-) create mode 100644 packages/utils/src/lodash/lodash.utils.ts diff --git a/packages/core-i18n/src/languages/en/common.json b/packages/core-i18n/src/languages/en/common.json index 31e0767..b0d0b88 100644 --- a/packages/core-i18n/src/languages/en/common.json +++ b/packages/core-i18n/src/languages/en/common.json @@ -97,13 +97,28 @@ "hold": { "title": "Hold Data", "description": "Are you sure you want to hold this data?" + }, + "save": { + "title": "Save Data", + "description": "Are you sure you want to save this data?" } }, "draft": { "recoveryTitle": "Draft Found", "recoveryMessage": "You have an unsaved draft from {{date}}. Would you like to continue editing?", "continueEditing": "Continue Editing", - "discardDraft": "Start Fresh" + "discardDraft": "Start Fresh", + "autoSaved": "Draft auto-saved", + "expired": "Previous draft has expired and was discarded" + }, + "formPage": { + "createTitle": "Create {{module}}", + "editTitle": "Edit {{module}}", + "duplicateTitle": "Duplicate {{module}}", + "loadingData": "Loading data...", + "loadError": "Failed to load data", + "saveError": "Failed to save data", + "validationError": "Please fix the form errors before saving" }, "bulkAction": { "totalData": "Total Data", diff --git a/packages/core-i18n/src/languages/id/common.json b/packages/core-i18n/src/languages/id/common.json index cbfa935..66eeb6d 100644 --- a/packages/core-i18n/src/languages/id/common.json +++ b/packages/core-i18n/src/languages/id/common.json @@ -97,13 +97,28 @@ "hold": { "title": "Hold Data", "description": "Apakah Anda yakin ingin menahan data ini?" + }, + "save": { + "title": "Simpan Data", + "description": "Apakah Anda yakin ingin menyimpan data ini?" } }, "draft": { "recoveryTitle": "Draf Ditemukan", "recoveryMessage": "Anda memiliki draf yang belum disimpan dari {{date}}. Apakah Anda ingin melanjutkan?", "continueEditing": "Lanjutkan", - "discardDraft": "Mulai Baru" + "discardDraft": "Mulai Baru", + "autoSaved": "Draf tersimpan otomatis", + "expired": "Draf sebelumnya telah kedaluwarsa dan dihapus" + }, + "formPage": { + "createTitle": "Buat {{module}}", + "editTitle": "Ubah {{module}}", + "duplicateTitle": "Duplikat {{module}}", + "loadingData": "Memuat data...", + "loadError": "Gagal memuat data", + "saveError": "Gagal menyimpan data", + "validationError": "Mohon perbaiki kesalahan formulir sebelum menyimpan" }, "bulkAction": { "totalData": "Total Data", diff --git a/packages/ui/src/foundations/enterprise-module/components/action-confirmation-modal/index.tsx b/packages/ui/src/foundations/enterprise-module/components/action-confirmation-modal/index.tsx index ad450a9..a668387 100644 --- a/packages/ui/src/foundations/enterprise-module/components/action-confirmation-modal/index.tsx +++ b/packages/ui/src/foundations/enterprise-module/components/action-confirmation-modal/index.tsx @@ -63,6 +63,11 @@ export const ACTION_TRANSLATION_MAP: Record) => string; } -// --------------------------------------------------------------------------- -// Page-Level Configurations -// --------------------------------------------------------------------------- - -/** - * Lifecycle interceptors for form processing. - * @template E The base database entity. - * @template TFormData The payload structure (defaults to Partial for DTOs). - */ -export interface EnterpriseFormLifecycleHooks> { - onValidate?: (data: TFormData) => Promise; - beforeSave?: (data: TFormData) => Promise; - /** Overrides the default repository save implementation. */ - save?: (data: TFormData) => Promise; - afterSave?: (result: E) => Promise; - afterGetData?: (data: E) => Promise; -} - -interface BasePageConfig { - children?: ReactNode; - px?: string | number; - py?: string | number; - pageHeaderProps?: Omit; -} -export interface EnterpriseIndexPageConfig extends BasePageConfig { - customPageActions?: (actions: PageActionsProps['actions']) => PageActionsProps['actions']; - onClickCreate?: (key: string) => void; -} - -export interface EnterpriseFormPageConfig extends EnterpriseFormLifecycleHooks { - children?: ReactNode; - showPageHeader?: boolean; - useDefaultPadding?: boolean; - customHiddenActions?: (data: Partial, defaultHidden: string[]) => string[]; - /** Strongly typed event handler for custom form interactions. */ - onCustomActionClick?: (key: ModuleActionType, data?: unknown) => void; - - draftConfig?: DraftConfig; - /** Type-safe array of entity keys to exclude during an update operation. */ - ignoreKeyUpdate?: (keyof E)[]; - /** Type-safe array of entity keys to exclude when duplicating a record. */ - ignoreKeyDuplicate?: (keyof E)[]; - initialValue?: Partial; - - presetDuplicate?: (data: E) => Promise>; -} - // --------------------------------------------------------------------------- // Action Confirmation Modal Configuration // --------------------------------------------------------------------------- @@ -336,10 +289,46 @@ export interface BulkActionResult { messages?: string[]; } +export interface PrivilegeEntity { + ALLOW_VIEW: boolean; + ALLOW_CREATE: boolean; + ALLOW_EDIT: boolean; + ALLOW_DELETE: boolean; + + ALLOW_PRINT: boolean; + ALLOW_PRINT_COPY: boolean; + + ALLOW_APPROVAL: boolean; + ALLOW_ACTIVATE: boolean; + ALLOW_DEACTIVATE: boolean; + + ALLOW_CONFIRM: boolean; + ALLOW_CANCEL: boolean; + ALLOW_ROLLBACK: boolean; + ALLOW_HOLD: boolean; + + ALLOW_LOGS: boolean; + ALLOW_NOTES: boolean; +} + +// --------------------------------------------------------------------------- +// Page-Level Configurations +// --------------------------------------------------------------------------- +interface BasePageConfig { + children?: ReactNode; + px?: string | number; + py?: string | number; + pageHeaderProps?: Omit; +} +export interface EnterpriseIndexPageConfig extends BasePageConfig { + customPageActions?: (actions: PageActionsProps['actions']) => PageActionsProps['actions']; + onClickCreate?: (key: string) => void; +} + export interface EnterpriseDetailPageConfig extends BasePageConfig { editMode?: 'FULL' | 'PARTIAL'; customPageActions?: (data: E, actions: PageActionsProps['actions']) => PageActionsProps['actions']; - onDetailLoaded?: (data: E) => void; + onDataLoaded?: (data: E) => void; showHighlightData?: boolean; showHighlightDataOnBreadcrumbs?: boolean; @@ -375,24 +364,24 @@ export interface EnterpriseDetailPageConfig e holdModalConfig?: ActionModalConfig; } -export interface PrivilegeEntity { - ALLOW_VIEW: boolean; - ALLOW_CREATE: boolean; - ALLOW_EDIT: boolean; - ALLOW_DELETE: boolean; +export interface EnterpriseFormPageConfig extends BasePageConfig { + formPageType: FormPageType; + customPageActions?: (data: E, actions: PageActionsProps['actions']) => PageActionsProps['actions']; + onDataLoaded?: (data: E) => void; - ALLOW_PRINT: boolean; - ALLOW_PRINT_COPY: boolean; + showHighlightData?: boolean; + showHighlightDataOnBreadcrumbs?: boolean; + highlightDataKey?: string; - ALLOW_APPROVAL: boolean; - ALLOW_ACTIVATE: boolean; - ALLOW_DEACTIVATE: boolean; + /** Key to reference status data in the entity, used to render the status badge automatically. @default 'status' */ + statusKey?: string; + /** Custom callback to provide dynamic status badge properties */ + getCustomStatusBadgeConfig?: (status: string) => Partial; - ALLOW_CONFIRM: boolean; - ALLOW_CANCEL: boolean; - ALLOW_ROLLBACK: boolean; - ALLOW_HOLD: boolean; - - ALLOW_LOGS: boolean; - ALLOW_NOTES: boolean; + /** Type-safe array of entity keys to exclude during an update operation. */ + ignoreKeyUpdate?: (keyof E)[]; + /** Type-safe array of entity keys to exclude when duplicating a record. */ + ignoreKeyDuplicate?: (keyof E)[]; + initialValueCreate?: Partial; + presetDuplicate?(payload: any): Promise; } diff --git a/packages/ui/src/foundations/enterprise-module/providers/detail-page.provider.tsx b/packages/ui/src/foundations/enterprise-module/providers/detail-page.provider.tsx index 3aee758..0ba877d 100644 --- a/packages/ui/src/foundations/enterprise-module/providers/detail-page.provider.tsx +++ b/packages/ui/src/foundations/enterprise-module/providers/detail-page.provider.tsx @@ -20,17 +20,17 @@ import { shortcutsData } from '../../../constants'; export function EnterpriseDetailPageProvider(props: EnterpriseDetailPageConfig) { const { children, - editMode = 'FULL', pageHeaderProps, px, py, + editMode = 'FULL', showHighlightData = true, showHighlightDataOnBreadcrumbs = true, highlightDataKey = 'code', statusKey = 'status', getCustomStatusBadgeConfig, - onDetailLoaded, + onDataLoaded, customPageActions, onClickCreate, @@ -77,7 +77,7 @@ export function EnterpriseDetailPageProvider( if (response && response.data) { const data = response.data?.data; setDetailData(data as E); - if (onDetailLoaded) onDetailLoaded(data as E); + if (onDataLoaded) onDataLoaded(data as E); } } catch (error: any) { notifications.show({ @@ -377,7 +377,7 @@ export function EnterpriseDetailPageProvider( /** Platform-aware shortcut label for the Create action. */ const CREATE_SHORTCUT_LABEL = useMemo(() => { - const shortcutData = shortcutsData.find((s) => s.key === 'collapse_sidebar'); + const shortcutData = shortcutsData.find((s) => s.key === 'create_data'); return IS_MACOS ? shortcutData?.macKeyIcons.join(' ') : shortcutData?.winKeyIcons.join(' '); }, [IS_MACOS]); diff --git a/packages/ui/src/foundations/enterprise-module/providers/index-page.provider.tsx b/packages/ui/src/foundations/enterprise-module/providers/index-page.provider.tsx index f29206a..77fb022 100644 --- a/packages/ui/src/foundations/enterprise-module/providers/index-page.provider.tsx +++ b/packages/ui/src/foundations/enterprise-module/providers/index-page.provider.tsx @@ -37,7 +37,7 @@ export function EnterpriseIndexPageProvider(props: EnterpriseIndexPageConfig) { /** Platform-aware shortcut label for the Create action. */ const CREATE_SHORTCUT_LABEL = useMemo(() => { - const shortcutData = shortcutsData.find((s) => s.key === 'collapse_sidebar'); + const shortcutData = shortcutsData.find((s) => s.key === 'create_data'); return IS_MACOS ? shortcutData?.macKeyIcons.join(' ') : shortcutData?.winKeyIcons.join(' '); }, [IS_MACOS]); diff --git a/packages/utils/package.json b/packages/utils/package.json index b9b2a8f..810c3d0 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -13,12 +13,14 @@ }, "dependencies": { "crypto-js": "^4.2.0", - "dayjs": "^1.11.19" + "dayjs": "^1.11.19", + "lodash": "^4.18.1" }, "devDependencies": { "@repo/eslint-config": "workspace:*", "@repo/typescript-config": "workspace:*", "@types/crypto-js": "^4.2.2", + "@types/lodash": "^4.17.24", "eslint": "^8.57.1", "typescript": "5.5.4", "vitest": "^4.0.17" diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 017eb39..89abad7 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -4,3 +4,4 @@ export * from './encryption/encryption.utils'; export * from './date/date.utils'; export * from './currency/currency.utils'; export * from './string/string.utils'; +export * from './lodash/lodash.utils'; diff --git a/packages/utils/src/lodash/lodash.utils.ts b/packages/utils/src/lodash/lodash.utils.ts new file mode 100644 index 0000000..8728c95 --- /dev/null +++ b/packages/utils/src/lodash/lodash.utils.ts @@ -0,0 +1,3 @@ +import lodash from 'lodash'; + +export { lodash }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd04a69..a833d8e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -670,6 +670,9 @@ importers: dayjs: specifier: ^1.11.19 version: 1.11.19 + lodash: + specifier: ^4.18.1 + version: 4.18.1 devDependencies: '@repo/eslint-config': specifier: workspace:* @@ -680,6 +683,9 @@ importers: '@types/crypto-js': specifier: ^4.2.2 version: 4.2.2 + '@types/lodash': + specifier: ^4.17.24 + version: 4.17.24 eslint: specifier: ^8.57.1 version: 8.57.1 @@ -3835,6 +3841,10 @@ packages: resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} dev: true + /@types/lodash@4.17.24: + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + dev: true + /@types/markdown-it@14.1.2: resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} dependencies: @@ -8815,6 +8825,10 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + /lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + dev: false + /log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} From e55c33069bf5a52c2489551c74c96b4ba56bc0a7 Mon Sep 17 00:00:00 2001 From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com> Date: Mon, 27 Jul 2026 11:14:15 +0700 Subject: [PATCH 2/5] 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 --- .../components/validation-bank-demo.tsx | 175 +++++--- .../domain/validators/full-page.validator.ts | 17 +- .../full-page/presentation/factory/index.tsx | 6 +- .../presentation/languages/en/full-page.json | 2 + .../presentation/languages/id/full-page.json | 2 + .../pages/full-page.page.form.tsx | 171 ++------ .../core-i18n/src/languages/en/common.json | 1 + .../core-i18n/src/languages/id/common.json | 1 + .../src/languages/id/validation.json | 10 +- .../custom/selects/hooks/useAsyncPaginate.ts | 14 +- .../components/data-table/index.tsx | 13 +- .../enterprise-module/entities/entity.ts | 6 +- .../hooks/use-form-page.context.ts | 5 +- .../foundations/enterprise-module/index.ts | 2 + .../providers/detail-page.provider.tsx | 271 +++++++----- .../providers/form-page.provider.tsx | 406 ++++++++++++++++++ .../providers/index-page.provider.tsx | 17 +- .../providers/module.provider.tsx | 20 +- 18 files changed, 776 insertions(+), 363 deletions(-) diff --git a/apps/showcase/src/pages/forms/components/form-demo/components/validation-bank-demo.tsx b/apps/showcase/src/pages/forms/components/form-demo/components/validation-bank-demo.tsx index 029c089..50d0a92 100644 --- a/apps/showcase/src/pages/forms/components/form-demo/components/validation-bank-demo.tsx +++ b/apps/showcase/src/pages/forms/components/form-demo/components/validation-bank-demo.tsx @@ -3,9 +3,13 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Button, Paper, Title, Group, Stack, Code, Divider } from '@repo/ui/components'; -import { - FieldTextInput, FieldPasswordInput, FieldNumberInput, - FieldLocalSelect, FieldAsyncSelect, FieldRichTextEditor +import { + FieldTextInput, + FieldPasswordInput, + FieldNumberInput, + FieldLocalSelect, + FieldAsyncSelect, + FieldRichTextEditor, } from '@repo/ui/form'; import type { LoadOptionsFn } from '@repo/ui/form'; @@ -30,16 +34,16 @@ const mockFetchUsers: LoadOptionsFn = async (search, page) => { await new Promise((resolve) => setTimeout(resolve, 500)); const allUsers = Array.from({ length: 20 }, (_, i) => ({ id: i + 1, - email: `user${i + 1}@company.com` + email: `user${i + 1}@company.com`, })); - const filtered = allUsers.filter(u => u.email.toLowerCase().includes(search.toLowerCase())); + const filtered = allUsers.filter((u) => u.email.toLowerCase().includes(search.toLowerCase())); const pageSize = 5; const start = (page - 1) * pageSize; const paginated = filtered.slice(start, start + pageSize); - + return { options: paginated, - hasMore: start + pageSize < filtered.length + hasMore: start + pageSize < filtered.length, }; }; @@ -50,34 +54,53 @@ const MOCK_VENDORS = [ const mockFetchVendors: LoadOptionsFn = async (search, _page) => { await new Promise((resolve) => setTimeout(resolve, 500)); - const filtered = MOCK_VENDORS.filter(v => v.name.toLowerCase().includes(search.toLowerCase()) || v.code.toLowerCase().includes(search.toLowerCase())); + const filtered = MOCK_VENDORS.filter( + (v) => v.name.toLowerCase().includes(search.toLowerCase()) || v.code.toLowerCase().includes(search.toLowerCase()), + ); return { options: filtered, hasMore: false }; }; -import { - compose, required, rangeLength, - positiveNumber, simplePassword, - complexPassword, phoneValidator, rangeValue +import { + compose, + required, + rangeLength, + positiveNumber, + simplePassword, + complexPassword, + phoneValidator, + rangeValue, } from '@repo/ui/validators'; import { useFormDemoTranslation } from '../i18n/useFormDemoTranslation'; export default function ValidationBankDemo() { const t = useFormDemoTranslation(); - + // Compose the Zod schema using the atomic validators - const validationSchema = useMemo(() => z.object({ - username: compose(z.string(), required(t.fields.customerName), rangeLength(3, 15, t.fields.customerName)), - simplePass: compose(z.string(), required(t.validation.simplePassword), simplePassword(6)), - complexPass: compose(z.string(), required(t.validation.complexPassword), complexPassword(8)), - age: compose(z.number(), required(t.fields.age), rangeValue(18, 65, t.fields.age)), - score: compose(z.number(), required(t.validation.score), positiveNumber(t.validation.score)), - phone: compose(z.string(), required(t.validation.phone), phoneValidator()), - department: z.object({ code: z.string(), name: z.string() }, { required_error: t.errors.departmentRequired }), - assignees: z.array(z.object({ id: z.number(), email: z.string() })).min(2, t.errors.min2Assignees), - prefilledVendor: z.object({ id: z.union([z.string(), z.number()]), code: z.string(), name: z.string() }, { required_error: t.errors.vendorRequired }), - emptyVendor: z.object({ id: z.union([z.string(), z.number()]), code: z.string(), name: z.string() }, { required_error: t.errors.vendorRequired }), - prefilledAsyncMulti: z.array(z.object({ id: z.union([z.string(), z.number()]), code: z.string(), name: z.string() })).min(1, t.errors.min1Vendor), - richTextNotes: z.string().min(15, t.errors.notesMin15), - }), [t]); + const validationSchema = useMemo( + () => + z.object({ + username: compose(z.string(), required(t.fields.customerName), rangeLength(3, 15, t.fields.customerName)), + simplePass: compose(z.string(), required(t.validation.simplePassword), simplePassword(6)), + complexPass: compose(z.string(), required(t.validation.complexPassword), complexPassword(8)), + age: compose(z.number(), required(t.fields.age), rangeValue(18, 65, t.fields.age)), + score: compose(z.number(), required(t.validation.score), positiveNumber(t.validation.score)), + phone: compose(z.string(), required(t.validation.phone), phoneValidator()), + department: z.object({ code: z.string(), name: z.string() }, { required_error: t.errors.departmentRequired }), + assignees: z.array(z.object({ id: z.number(), email: z.string() })).min(2, t.errors.min2Assignees), + prefilledVendor: z.object( + { id: z.union([z.string(), z.number()]), code: z.string(), name: z.string() }, + { required_error: t.errors.vendorRequired }, + ), + emptyVendor: z.object( + { id: z.union([z.string(), z.number()]), code: z.string(), name: z.string() }, + { required_error: t.errors.vendorRequired }, + ), + prefilledAsyncMulti: z + .array(z.object({ id: z.union([z.string(), z.number()]), code: z.string(), name: z.string() })) + .min(1, t.errors.min1Vendor), + richTextNotes: z.string().min(15, t.errors.notesMin15), + }), + [t], + ); type ValidationFormValues = z.infer; @@ -96,10 +119,10 @@ export default function ValidationBankDemo() { emptyVendor: null as any, prefilledAsyncMulti: [ { id: 'V1', code: 'VN-01', name: 'Vendor One' }, - { id: 'V2', code: 'VN-02', name: 'Vendor Two' } + { id: 'V2', code: 'VN-02', name: 'Vendor Two' }, ] as any, richTextNotes: '', - } + }, }); const onSubmit = (data: ValidationFormValues) => console.log('Validation Passed:', data); @@ -110,62 +133,66 @@ export default function ValidationBankDemo() {
- {t.sections.validationBankTitle} + + {t.sections.validationBankTitle} + - - - + - - - - - - {t.sections.objectLevelValidations} + + {t.sections.objectLevelValidations} + - + name="department" control={control as any} @@ -176,7 +203,7 @@ export default function ValidationBankDemo() { clearable withAsterisk /> - + multiple name="assignees" @@ -190,9 +217,11 @@ export default function ValidationBankDemo() { withAsterisk /> - {t.sections.validatedPrefilledObjects} + + {t.sections.validatedPrefilledObjects} + - + - {t.sections.richTextValidations} + + {t.sections.richTextValidations} + - +
- {t.common.submittedData} + + {t.common.submittedData} + {JSON.stringify(data, null, 2)} diff --git a/apps/web/src/apps/modules/example/full-page/domain/validators/full-page.validator.ts b/apps/web/src/apps/modules/example/full-page/domain/validators/full-page.validator.ts index a32d8e6..3d52662 100644 --- a/apps/web/src/apps/modules/example/full-page/domain/validators/full-page.validator.ts +++ b/apps/web/src/apps/modules/example/full-page/domain/validators/full-page.validator.ts @@ -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()`. - */ -export type FullPageFormDTO = z.infer>; diff --git a/apps/web/src/apps/modules/example/full-page/presentation/factory/index.tsx b/apps/web/src/apps/modules/example/full-page/presentation/factory/index.tsx index a1415d8..add31b8 100644 --- a/apps/web/src/apps/modules/example/full-page/presentation/factory/index.tsx +++ b/apps/web/src/apps/modules/example/full-page/presentation/factory/index.tsx @@ -37,9 +37,9 @@ export default function FullPageModule() { } /> } /> - } /> - } /> - } /> + } /> + } /> + } /> } /> } /> diff --git a/apps/web/src/apps/modules/example/full-page/presentation/languages/en/full-page.json b/apps/web/src/apps/modules/example/full-page/presentation/languages/en/full-page.json index 766c70d..c460661 100644 --- a/apps/web/src/apps/modules/example/full-page/presentation/languages/en/full-page.json +++ b/apps/web/src/apps/modules/example/full-page/presentation/languages/en/full-page.json @@ -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 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", diff --git a/apps/web/src/apps/modules/example/full-page/presentation/languages/id/full-page.json b/apps/web/src/apps/modules/example/full-page/presentation/languages/id/full-page.json index 2d2b36f..8e59d95 100644 --- a/apps/web/src/apps/modules/example/full-page/presentation/languages/id/full-page.json +++ b/apps/web/src/apps/modules/example/full-page/presentation/languages/id/full-page.json @@ -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 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", diff --git a/apps/web/src/apps/modules/example/full-page/presentation/pages/full-page.page.form.tsx b/apps/web/src/apps/modules/example/full-page/presentation/pages/full-page.page.form.tsx index a6f3f60..bc69850 100644 --- a/apps/web/src/apps/modules/example/full-page/presentation/pages/full-page.page.form.tsx +++ b/apps/web/src/apps/modules/example/full-page/presentation/pages/full-page.page.form.tsx @@ -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 ( - - {/* --- INLINED PAGE HEADER --- */} - - } - > - - Database Clusters - - - {formPageType === 'create' ? 'Create Cluster' : 'Edit Cluster'} - - - - - - - - {formPageType === 'create' ? 'Create New Cluster' : 'Edit Cluster Configuration'} - - - Configure and provision a new highly available database cluster. - - - - - - - - - - - - - {/* --- END INLINED PAGE HEADER --- */} - + - - {formPageType === 'create' ? 'Create Entity' : 'Edit Entity'} - - - Fill in the required information to configure your entity properly. Fields marked with * are required. - - - - - - -