refactor: improve code formatting and organization across multiple components
- Enhanced readability by restructuring code formatting in various components, including API documentation, user management, and sales modules. - Standardized table and object definitions for better clarity in API documentation. - Improved layout and indentation in React components for better maintainability. - Updated unit tests to reflect changes in formatting and ensure consistency. These changes enhance the overall code quality and maintainability of the project, making it easier for developers to navigate and understand the codebase.
This commit is contained in:
+1
-3
@@ -29,9 +29,7 @@ describe('CustomersRemoteDataTransformer', () => {
|
||||
|
||||
it('includes named contacts on create and omits status', () => {
|
||||
const payload = transformer.transformCreatePayload(dto);
|
||||
expect(payload.contacts).toEqual([
|
||||
{ name: 'Andi Pratama', jobTitle: 'Manager', phone: '+6281111111111' },
|
||||
]);
|
||||
expect(payload.contacts).toEqual([{ name: 'Andi Pratama', jobTitle: 'Manager', phone: '+6281111111111' }]);
|
||||
expect(payload).not.toHaveProperty('status');
|
||||
expect(payload).not.toHaveProperty('id');
|
||||
});
|
||||
|
||||
+11
-2
@@ -13,7 +13,11 @@ import {
|
||||
Text,
|
||||
notifications,
|
||||
} from '@repo/ui/components';
|
||||
import { useDetailPageContext, useEnterpriseModuleConfigContext, useEnterpriseModuleTranslationContext } from '@repo/ui/foundations';
|
||||
import {
|
||||
useDetailPageContext,
|
||||
useEnterpriseModuleConfigContext,
|
||||
useEnterpriseModuleTranslationContext,
|
||||
} from '@repo/ui/foundations';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-react';
|
||||
@@ -131,7 +135,12 @@ export function DetailContacts() {
|
||||
<ActionIcon variant="subtle" onClick={() => openEdit(contact)} aria-label={t('edit_contact')}>
|
||||
<Pencil size={16} />
|
||||
</ActionIcon>
|
||||
<ActionIcon variant="subtle" color="red" onClick={() => handleDelete(contact)} aria-label={t('delete_contact')}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="red"
|
||||
onClick={() => handleDelete(contact)}
|
||||
aria-label={t('delete_contact')}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
+12
-1
@@ -1,4 +1,15 @@
|
||||
import { ActionIcon, Box, Button, FieldTextInput, FieldTextarea, Group, Paper, SimpleGrid, Stack, Text } from '@repo/ui/components';
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Button,
|
||||
FieldTextInput,
|
||||
FieldTextarea,
|
||||
Group,
|
||||
Paper,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@repo/ui/components';
|
||||
import { useEnterpriseModuleTranslationContext, useFormPageContext } from '@repo/ui/foundations';
|
||||
import { useFieldArray } from '@repo/ui/form';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
|
||||
+2
-8
@@ -28,14 +28,8 @@ export const createProductSchema = (t: (key: string) => string) => {
|
||||
return z.object({
|
||||
code: configCodeSchema(t, PRODUCT_CODE_MAX),
|
||||
name: productNameSchema(t),
|
||||
unit: z.preprocess(
|
||||
emptyToUndefined,
|
||||
compose(z.string(), maxLength(16, t('common:fields.unit'))).optional(),
|
||||
),
|
||||
unit: z.preprocess(emptyToUndefined, compose(z.string(), maxLength(16, t('common:fields.unit'))).optional()),
|
||||
price: optionalDecimalStringSchema(t, 'common:fields.price'),
|
||||
brand: z.preprocess(
|
||||
emptyToUndefined,
|
||||
compose(z.string(), maxLength(64, t('common:fields.brand'))).optional(),
|
||||
),
|
||||
brand: z.preprocess(emptyToUndefined, compose(z.string(), maxLength(64, t('common:fields.brand'))).optional()),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -40,9 +40,7 @@ describe('CyclesRemoteDataServices', () => {
|
||||
|
||||
it('uses PATCH when editing a cycle', async () => {
|
||||
await service.edit('cyc-1', { cycleNumber: 2 } as any);
|
||||
expect(httpClient.request).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ url: '/cycles/cyc-1', method: 'PATCH' }),
|
||||
);
|
||||
expect(httpClient.request).toHaveBeenCalledWith(expect.objectContaining({ url: '/cycles/cyc-1', method: 'PATCH' }));
|
||||
});
|
||||
|
||||
it('bulk-deletes via POST /cycles/bulk-delete', async () => {
|
||||
|
||||
@@ -51,7 +51,9 @@ export interface CycleDto {
|
||||
employeeId: string;
|
||||
purpose: FieldPurpose;
|
||||
cycleNumber: number;
|
||||
weekdays?: CycleWeekdayEntity[] | Record<string, { startBranchId: string; endBranchId: string; customerIds: string[] }>;
|
||||
weekdays?:
|
||||
| CycleWeekdayEntity[]
|
||||
| Record<string, { startBranchId: string; endBranchId: string; customerIds: string[] }>;
|
||||
status?: ConfigurationStatus;
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
|
||||
+1
-6
@@ -2,12 +2,7 @@ import { BaseDataTransformer } from '@repo/core-api/data-services';
|
||||
import { omitEmptyFields } from '../../../../../../../core/domain/configuration-field-validators';
|
||||
import { WEEKDAYS } from '../../../../../../../core/domain/configuration-field-validators';
|
||||
import type { FieldPurpose } from '../../../../../../../core/domain/field-purpose';
|
||||
import type {
|
||||
CycleEntity,
|
||||
CycleWeekdayEntity,
|
||||
CycleWeekdayRow,
|
||||
CycleWeekdayWrite,
|
||||
} from '../entities';
|
||||
import type { CycleEntity, CycleWeekdayEntity, CycleWeekdayRow, CycleWeekdayWrite } from '../entities';
|
||||
|
||||
function relationId(value: unknown): string | undefined {
|
||||
if (value && typeof value === 'object' && 'id' in value) {
|
||||
|
||||
@@ -44,7 +44,18 @@ export default function CyclePageForm({ formPageType }: { formPageType: FormPage
|
||||
formControl={formControl}
|
||||
formPageType={formPageType}
|
||||
ignoreKeyDuplicate={['id']}
|
||||
ignoreKeyUpdate={['status', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy', 'id', 'purpose', 'employeeId', 'weekdays', 'routeGeometry']}
|
||||
ignoreKeyUpdate={[
|
||||
'status',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'createdBy',
|
||||
'updatedBy',
|
||||
'id',
|
||||
'purpose',
|
||||
'employeeId',
|
||||
'weekdays',
|
||||
'routeGeometry',
|
||||
]}
|
||||
highlightDataKey="cycleNumber"
|
||||
pageHeaderProps={{
|
||||
title: title?.title,
|
||||
|
||||
@@ -29,7 +29,8 @@ export default function CyclePageIndex() {
|
||||
field: 'weekdayRows',
|
||||
headerName: t('section_weekdays'),
|
||||
minWidth: 140,
|
||||
valueGetter: ({ data }) => data?.weekdayRows?.filter((row) => row.enabled).length ?? data?.weekdays?.length ?? 0,
|
||||
valueGetter: ({ data }) =>
|
||||
data?.weekdayRows?.filter((row) => row.enabled).length ?? data?.weekdays?.length ?? 0,
|
||||
},
|
||||
];
|
||||
}, [t]);
|
||||
|
||||
+3
-1
@@ -65,7 +65,9 @@ export class PlansRemoteDataTransformer extends BaseDataTransformer<PlanEntity>
|
||||
if (this.purpose === 'sales') {
|
||||
payload.invoiceIds = relationIds(entity.invoices).length ? relationIds(entity.invoices) : entity.invoiceIds;
|
||||
} else {
|
||||
payload.packingSlipIds = relationIds(entity.packingSlips).length ? relationIds(entity.packingSlips) : entity.packingSlipIds;
|
||||
payload.packingSlipIds = relationIds(entity.packingSlips).length
|
||||
? relationIds(entity.packingSlips)
|
||||
: entity.packingSlipIds;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
+38
-6
@@ -1,6 +1,24 @@
|
||||
import { ActionIcon, Box, Button, FieldAsyncSelect, FieldValue, Group, Paper, RenderDate, SimpleGrid, Stack, StatusBadge, Text, notifications } from '@repo/ui/components';
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Button,
|
||||
FieldAsyncSelect,
|
||||
FieldValue,
|
||||
Group,
|
||||
Paper,
|
||||
RenderDate,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
StatusBadge,
|
||||
Text,
|
||||
notifications,
|
||||
} from '@repo/ui/components';
|
||||
import { RouteMap } from '@repo/ui/map';
|
||||
import { useDetailPageContext, useEnterpriseModuleTranslationContext, useEnterpriseModuleDataServiceContext } from '@repo/ui/foundations';
|
||||
import {
|
||||
useDetailPageContext,
|
||||
useEnterpriseModuleTranslationContext,
|
||||
useEnterpriseModuleDataServiceContext,
|
||||
} from '@repo/ui/foundations';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { relationLabel } from '../../../../shared/relation-label';
|
||||
@@ -39,10 +57,24 @@ export function DetailGeneral() {
|
||||
</Text>
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 4 }} spacing="md" verticalSpacing="xl">
|
||||
<FieldValue label={t('common:fields.employee')} value={relationLabel(data?.employee) || data?.employeeId} />
|
||||
<FieldValue label={t('common:fields.date')} value={data?.date} render={(val) => <RenderDate value={val as any} />} />
|
||||
<FieldValue label={t('common:fields.startBranch')} value={relationLabel(data?.startBranch) || data?.startBranchId} />
|
||||
<FieldValue label={t('common:fields.endBranch')} value={relationLabel(data?.endBranch) || data?.endBranchId} />
|
||||
<FieldValue label={t('common:fields.status')} value={data?.status} render={(val) => <StatusBadge status={String(val ?? '')} />} />
|
||||
<FieldValue
|
||||
label={t('common:fields.date')}
|
||||
value={data?.date}
|
||||
render={(val) => <RenderDate value={val as any} />}
|
||||
/>
|
||||
<FieldValue
|
||||
label={t('common:fields.startBranch')}
|
||||
value={relationLabel(data?.startBranch) || data?.startBranchId}
|
||||
/>
|
||||
<FieldValue
|
||||
label={t('common:fields.endBranch')}
|
||||
value={relationLabel(data?.endBranch) || data?.endBranchId}
|
||||
/>
|
||||
<FieldValue
|
||||
label={t('common:fields.status')}
|
||||
value={data?.status}
|
||||
render={(val) => <StatusBadge status={String(val ?? '')} />}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
|
||||
|
||||
+5
-1
@@ -1,5 +1,9 @@
|
||||
import { Box, FieldAsyncSelect, FieldDatePicker, Paper, SimpleGrid, Text } from '@repo/ui/components';
|
||||
import { useEnterpriseModuleTranslationContext, useFormPageContext, useEnterpriseModuleConfigContext } from '@repo/ui/foundations';
|
||||
import {
|
||||
useEnterpriseModuleTranslationContext,
|
||||
useFormPageContext,
|
||||
useEnterpriseModuleConfigContext,
|
||||
} from '@repo/ui/foundations';
|
||||
import { purposeFromModuleKey } from '../../../../../../../../core/domain/field-purpose';
|
||||
import { loadEmployeeOptions } from '../../../../shared/load-employee-options';
|
||||
import { loadBranchOptions } from '../../../../shared/load-branch-options';
|
||||
|
||||
@@ -39,7 +39,20 @@ export default function PlanPageForm({ formPageType }: { formPageType: FormPageT
|
||||
formControl={formControl}
|
||||
formPageType={formPageType}
|
||||
ignoreKeyDuplicate={['id']}
|
||||
ignoreKeyUpdate={['status', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy', 'id', 'purpose', 'employeeId', 'destinations', 'routeGeometry', 'invoiceIds', 'packingSlipIds']}
|
||||
ignoreKeyUpdate={[
|
||||
'status',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'createdBy',
|
||||
'updatedBy',
|
||||
'id',
|
||||
'purpose',
|
||||
'employeeId',
|
||||
'destinations',
|
||||
'routeGeometry',
|
||||
'invoiceIds',
|
||||
'packingSlipIds',
|
||||
]}
|
||||
highlightDataKey="date"
|
||||
pageHeaderProps={{
|
||||
title: title?.title,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
export function relationLabel(
|
||||
item: { code?: string | null; name?: string; id?: string | number } | null | undefined,
|
||||
) {
|
||||
export function relationLabel(item: { code?: string | null; name?: string; id?: string | number } | null | undefined) {
|
||||
if (!item) return '';
|
||||
if (item.code && item.name) return `${item.code} - ${item.name}`;
|
||||
return item.name || item.code || (item.id == null ? '' : String(item.id));
|
||||
|
||||
+3
-1
@@ -24,7 +24,9 @@ export default function SalesOrderPageDetail() {
|
||||
{ label: t('nav:sales-orders'), type: 'link', href: `${salesOrdersModuleConfig.webUrl}/index` },
|
||||
],
|
||||
}}
|
||||
customPageActions={(data, pageActions) => actions.detailStatusActions(data as SalesOrderEntity, pageActions ?? [])}
|
||||
customPageActions={(data, pageActions) =>
|
||||
actions.detailStatusActions(data as SalesOrderEntity, pageActions ?? [])
|
||||
}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<DetailGeneral salesRequestHref={(id) => `${salesRequestsModuleConfig.webUrl}/detail/${id}`} />
|
||||
|
||||
+10
-1
@@ -62,7 +62,16 @@ export default function SalesOrderPageForm({ formPageType }: { formPageType: For
|
||||
formControl={formControl}
|
||||
formPageType={formPageType}
|
||||
ignoreKeyDuplicate={['code']}
|
||||
ignoreKeyUpdate={['status', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy', 'id', 'salesRequestId', 'salesRequest']}
|
||||
ignoreKeyUpdate={[
|
||||
'status',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'createdBy',
|
||||
'updatedBy',
|
||||
'id',
|
||||
'salesRequestId',
|
||||
'salesRequest',
|
||||
]}
|
||||
highlightDataKey="code"
|
||||
pageHeaderProps={{
|
||||
title: title?.title,
|
||||
|
||||
@@ -8,7 +8,9 @@ export function DetailGeneral({
|
||||
}: {
|
||||
salesRequestHref?: (id: string) => string;
|
||||
} = {}) {
|
||||
const { detailData } = useDetailPageContext<SalesDocumentEntity & { salesRequestId?: string | null; salesRequest?: { id?: string; code?: string } | null }>();
|
||||
const { detailData } = useDetailPageContext<
|
||||
SalesDocumentEntity & { salesRequestId?: string | null; salesRequest?: { id?: string; code?: string } | null }
|
||||
>();
|
||||
const { t } = useEnterpriseModuleTranslationContext();
|
||||
const data = detailData;
|
||||
const salesRequestId = data?.salesRequestId ?? data?.salesRequest?.id;
|
||||
@@ -26,7 +28,10 @@ export function DetailGeneral({
|
||||
value={data?.date}
|
||||
render={(val) => <RenderDate value={val as any} />}
|
||||
/>
|
||||
<FieldValue label={t('common:fields.salesPerson')} value={relationLabel(data?.salesPerson) || data?.salesPersonId} />
|
||||
<FieldValue
|
||||
label={t('common:fields.salesPerson')}
|
||||
value={relationLabel(data?.salesPerson) || data?.salesPersonId}
|
||||
/>
|
||||
<FieldValue label={t('common:fields.branch')} value={relationLabel(data?.branch) || data?.branchId} />
|
||||
<FieldValue label={t('common:fields.division')} value={relationLabel(data?.division) || data?.divisionId} />
|
||||
<FieldValue label={t('common:fields.customer')} value={relationLabel(data?.customer) || data?.customerId} />
|
||||
@@ -36,9 +41,7 @@ export function DetailGeneral({
|
||||
value={salesRequestId}
|
||||
render={() =>
|
||||
salesRequestId ? (
|
||||
<Anchor href={salesRequestHref(salesRequestId)}>
|
||||
{data?.salesRequest?.code || salesRequestId}
|
||||
</Anchor>
|
||||
<Anchor href={salesRequestHref(salesRequestId)}>{data?.salesRequest?.code || salesRequestId}</Anchor>
|
||||
) : (
|
||||
'-'
|
||||
)
|
||||
|
||||
@@ -14,11 +14,7 @@ export function DetailLocation() {
|
||||
{t('section_location')}
|
||||
</Text>
|
||||
<Stack gap="md">
|
||||
<LocationMap
|
||||
latitude={data?.latitude}
|
||||
longitude={data?.longitude}
|
||||
emptyLabel={t('common:map.noLocation')}
|
||||
/>
|
||||
<LocationMap latitude={data?.latitude} longitude={data?.longitude} emptyLabel={t('common:map.noLocation')} />
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 4 }} spacing="md" verticalSpacing="xl">
|
||||
<FieldValue label={t('common:fields.address')} value={data?.address} />
|
||||
<FieldValue label={t('common:fields.latitude')} value={data?.latitude} />
|
||||
|
||||
@@ -32,7 +32,13 @@ export function FormGeneral() {
|
||||
placeholder="e.g. SR-20260826-0001"
|
||||
radius="md"
|
||||
/>
|
||||
<FieldDatePicker control={formControl.control} name="date" label={t('common:fields.date')} required radius="md" />
|
||||
<FieldDatePicker
|
||||
control={formControl.control}
|
||||
name="date"
|
||||
label={t('common:fields.date')}
|
||||
required
|
||||
radius="md"
|
||||
/>
|
||||
<FieldAsyncSelect<EmployeeEntity>
|
||||
control={formControl.control}
|
||||
name="salesPerson"
|
||||
|
||||
@@ -15,7 +15,12 @@ export function FormImages() {
|
||||
<Paper withBorder shadow="sm" radius="md" p="xl">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Text fw={600}>{t('section_images')}</Text>
|
||||
<Button variant="light" size="xs" leftSection={<Plus size={14} />} onClick={() => append({ url: '', description: '' })}>
|
||||
<Button
|
||||
variant="light"
|
||||
size="xs"
|
||||
leftSection={<Plus size={14} />}
|
||||
onClick={() => append({ url: '', description: '' })}
|
||||
>
|
||||
{t('add_image')}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { ActionIcon, Box, Button, FieldAsyncSelect, FieldTextInput, Group, Paper, Table, Text } from '@repo/ui/components';
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Button,
|
||||
FieldAsyncSelect,
|
||||
FieldTextInput,
|
||||
Group,
|
||||
Paper,
|
||||
Table,
|
||||
Text,
|
||||
} from '@repo/ui/components';
|
||||
import { useEnterpriseModuleTranslationContext, useFormPageContext } from '@repo/ui/foundations';
|
||||
import { useFieldArray, useWatch } from '@repo/ui/form';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
@@ -70,7 +80,12 @@ export function FormProducts() {
|
||||
</Table.Td>
|
||||
<Table.Td ta="right">{currency.format(lineTotal(line?.quantity, line?.price))}</Table.Td>
|
||||
<Table.Td>
|
||||
<ActionIcon variant="subtle" color="red" onClick={() => remove(index)} aria-label={t('remove_line')}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="red"
|
||||
onClick={() => remove(index)}
|
||||
aria-label={t('remove_line')}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</ActionIcon>
|
||||
</Table.Td>
|
||||
|
||||
@@ -95,7 +95,9 @@ export function toSalesWritePayload(
|
||||
};
|
||||
|
||||
if (options?.includeSalesRequestId) {
|
||||
payload.salesRequestId = relationId((entity as { salesRequest?: unknown }).salesRequest) ?? (entity as { salesRequestId?: string }).salesRequestId;
|
||||
payload.salesRequestId =
|
||||
relationId((entity as { salesRequest?: unknown }).salesRequest) ??
|
||||
(entity as { salesRequestId?: string }).salesRequestId;
|
||||
}
|
||||
|
||||
return payload;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import { compose, maxLength, required } from '@repo/ui/validators';
|
||||
import { configAddressSchema, optionalLatitudeSchema, optionalLongitudeSchema } from '../../../../../core/domain/configuration-field-validators';
|
||||
import {
|
||||
configAddressSchema,
|
||||
optionalLatitudeSchema,
|
||||
optionalLongitudeSchema,
|
||||
} from '../../../../../core/domain/configuration-field-validators';
|
||||
import { decimalStringSchema } from '../../../../../core/domain/decimal-string.schema';
|
||||
|
||||
const NOTES_MAX = 1024;
|
||||
|
||||
@@ -82,7 +82,10 @@ export function useSalesDocumentActions(documentType: SalesDocumentType) {
|
||||
icon: <Icon size={16} />,
|
||||
variant: 'light' as const,
|
||||
onClick: () => {
|
||||
void applyStatus(selectedRows.map((row) => String(row.id)), action.target);
|
||||
void applyStatus(
|
||||
selectedRows.map((row) => String(row.id)),
|
||||
action.target,
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
+1
-3
@@ -95,9 +95,7 @@ describe('PrivilegesRemoteDataTransformer', () => {
|
||||
|
||||
expect(payload).not.toHaveProperty('status');
|
||||
expect(payload).not.toHaveProperty('id');
|
||||
expect(payload.details).toEqual(
|
||||
expect.arrayContaining([{ privilegeKeyId: 'key-1', action: 'view', value: true }]),
|
||||
);
|
||||
expect(payload.details).toEqual(expect.arrayContaining([{ privilegeKeyId: 'key-1', action: 'view', value: true }]));
|
||||
});
|
||||
|
||||
it('maps an empty details list to an empty matrix', () => {
|
||||
|
||||
+1
-5
@@ -56,11 +56,7 @@ export function FormPermissions() {
|
||||
</Table.Td>
|
||||
{PRIVILEGE_ACTIONS.map((action) => (
|
||||
<Table.Td key={action} ta="center">
|
||||
<FieldCheckbox
|
||||
control={formControl.control}
|
||||
name={`matrix.${key.id}.${action}`}
|
||||
label=""
|
||||
/>
|
||||
<FieldCheckbox control={formControl.control} name={`matrix.${key.id}.${action}`} label="" />
|
||||
</Table.Td>
|
||||
))}
|
||||
</Table.Tr>
|
||||
|
||||
@@ -40,9 +40,7 @@ describe('UsersRemoteDataServices', () => {
|
||||
|
||||
it('uses PATCH when editing a user', async () => {
|
||||
await service.edit('user-1', { username: 'alice' } as any);
|
||||
expect(httpClient.request).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ url: '/users/user-1', method: 'PATCH' }),
|
||||
);
|
||||
expect(httpClient.request).toHaveBeenCalledWith(expect.objectContaining({ url: '/users/user-1', method: 'PATCH' }));
|
||||
});
|
||||
|
||||
it('bulk-deletes via POST /users/bulk-delete', async () => {
|
||||
|
||||
@@ -13,14 +13,10 @@ export const createUserSchema = (t: (key: string) => string, options?: { require
|
||||
: z.union([z.literal(''), compose(z.string(), rangeLength(8, 72, passwordLabel))]).optional();
|
||||
|
||||
return z.object({
|
||||
username: compose(
|
||||
z.string(),
|
||||
required(usernameLabel),
|
||||
rangeLength(3, 32, usernameLabel),
|
||||
(schema: z.ZodString) =>
|
||||
schema.regex(USERNAME_PATTERN, {
|
||||
message: JSON.stringify({ key: 'validation:invalid_format', values: { field: usernameLabel } }),
|
||||
}),
|
||||
username: compose(z.string(), required(usernameLabel), rangeLength(3, 32, usernameLabel), (schema: z.ZodString) =>
|
||||
schema.regex(USERNAME_PATTERN, {
|
||||
message: JSON.stringify({ key: 'validation:invalid_format', values: { field: usernameLabel } }),
|
||||
}),
|
||||
),
|
||||
password,
|
||||
privilege: z.any().nullable().optional(),
|
||||
|
||||
+11
-7
@@ -1,5 +1,13 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Box, FieldTextInput, FieldPasswordInput, FieldAsyncSelect, Paper, SimpleGrid, Text } from '@repo/ui/components';
|
||||
import {
|
||||
Box,
|
||||
FieldTextInput,
|
||||
FieldPasswordInput,
|
||||
FieldAsyncSelect,
|
||||
Paper,
|
||||
SimpleGrid,
|
||||
Text,
|
||||
} from '@repo/ui/components';
|
||||
import { useEnterpriseModuleTranslationContext, useFormPageContext } from '@repo/ui/foundations';
|
||||
import { loadPrivilegeOptions } from '../../load-privilege-options';
|
||||
import { loadEmployeeOptions } from '../../load-employee-options';
|
||||
@@ -34,15 +42,11 @@ export function FormGeneral({ requirePassword }: { requirePassword: boolean }) {
|
||||
const employee = formControl.watch('employee');
|
||||
|
||||
useEffect(() => {
|
||||
hydrateRelation(formControl, 'privilege', 'privilegeId', privilege, (id) =>
|
||||
privilegesDataService.getOne(id),
|
||||
);
|
||||
hydrateRelation(formControl, 'privilege', 'privilegeId', privilege, (id) => privilegesDataService.getOne(id));
|
||||
}, [privilege, formControl]);
|
||||
|
||||
useEffect(() => {
|
||||
hydrateRelation(formControl, 'employee', 'employeeId', employee, (id) =>
|
||||
employeesDataService.getOne(id),
|
||||
);
|
||||
hydrateRelation(formControl, 'employee', 'employeeId', employee, (id) => employeesDataService.getOne(id));
|
||||
}, [employee, formControl]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -21,11 +21,7 @@ registerModuleNamespace(usersModuleConfig.translationNamespace, {
|
||||
|
||||
export default function UsersModule() {
|
||||
return (
|
||||
<EnterpriseModuleProvider<UserEntity>
|
||||
config={usersModuleConfig}
|
||||
dataServices={usersDataService}
|
||||
store={usersStore}
|
||||
>
|
||||
<EnterpriseModuleProvider<UserEntity> config={usersModuleConfig} dataServices={usersDataService} store={usersStore}>
|
||||
<Routes>
|
||||
<Route path="/index" element={<IndexPage />} />
|
||||
<Route path="/detail/:dataId" element={<DetailPage />} />
|
||||
|
||||
@@ -33,13 +33,12 @@ function emptyToUndefinedNumber(value: unknown) {
|
||||
}
|
||||
|
||||
export function configCodeSchema(t: (key: string) => string, max = CONFIG_CODE_MAX) {
|
||||
return compose(
|
||||
z.string(),
|
||||
required(t('common:fields.code')),
|
||||
maxLength(max, t('common:fields.code')),
|
||||
).regex(CODE_PATTERN, {
|
||||
message: JSON.stringify({ key: 'validation:invalid_format', values: { field: t('common:fields.code') } }),
|
||||
});
|
||||
return compose(z.string(), required(t('common:fields.code')), maxLength(max, t('common:fields.code'))).regex(
|
||||
CODE_PATTERN,
|
||||
{
|
||||
message: JSON.stringify({ key: 'validation:invalid_format', values: { field: t('common:fields.code') } }),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function configNameSchema(t: (key: string) => string) {
|
||||
@@ -69,13 +68,20 @@ export function optionalPhoneSchema() {
|
||||
}
|
||||
|
||||
export function configAddressSchema(t: (key: string) => string) {
|
||||
return compose(z.string(), required(t('common:fields.address')), maxLength(CONFIG_ADDRESS_MAX, t('common:fields.address')));
|
||||
return compose(
|
||||
z.string(),
|
||||
required(t('common:fields.address')),
|
||||
maxLength(CONFIG_ADDRESS_MAX, t('common:fields.address')),
|
||||
);
|
||||
}
|
||||
|
||||
export function weekdaySchema(t: (key: string) => string) {
|
||||
return z.enum(WEEKDAYS, {
|
||||
required_error: JSON.stringify({ key: 'validation:required', values: { field: t('common:fields.weekday') } }),
|
||||
invalid_type_error: JSON.stringify({ key: 'validation:invalid_format', values: { field: t('common:fields.weekday') } }),
|
||||
invalid_type_error: JSON.stringify({
|
||||
key: 'validation:invalid_format',
|
||||
values: { field: t('common:fields.weekday') },
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,14 +92,19 @@ export function workingHoursSchema(t: (key: string) => string) {
|
||||
}
|
||||
|
||||
export function optionalNfcIdSchema(t: (key: string) => string) {
|
||||
return z.preprocess(emptyToUndefined, compose(z.string(), maxLength(CONFIG_NFC_ID_MAX, t('common:fields.nfcId'))).optional());
|
||||
return z.preprocess(
|
||||
emptyToUndefined,
|
||||
compose(z.string(), maxLength(CONFIG_NFC_ID_MAX, t('common:fields.nfcId'))).optional(),
|
||||
);
|
||||
}
|
||||
|
||||
export function optionalLatitudeSchema() {
|
||||
return z.preprocess(
|
||||
emptyToUndefinedNumber,
|
||||
compose(
|
||||
z.number({ invalid_type_error: JSON.stringify({ key: 'validation:invalid_format', values: { field: 'latitude' } }) }),
|
||||
z.number({
|
||||
invalid_type_error: JSON.stringify({ key: 'validation:invalid_format', values: { field: 'latitude' } }),
|
||||
}),
|
||||
minValue(-90, 'latitude'),
|
||||
maxValue(90, 'latitude'),
|
||||
).optional(),
|
||||
@@ -104,7 +115,9 @@ export function optionalLongitudeSchema() {
|
||||
return z.preprocess(
|
||||
emptyToUndefinedNumber,
|
||||
compose(
|
||||
z.number({ invalid_type_error: JSON.stringify({ key: 'validation:invalid_format', values: { field: 'longitude' } }) }),
|
||||
z.number({
|
||||
invalid_type_error: JSON.stringify({ key: 'validation:invalid_format', values: { field: 'longitude' } }),
|
||||
}),
|
||||
minValue(-180, 'longitude'),
|
||||
maxValue(180, 'longitude'),
|
||||
).optional(),
|
||||
|
||||
@@ -10,9 +10,7 @@ export function filterMenuByViewPrivilege<T extends { moduleKey?: string; childr
|
||||
}
|
||||
|
||||
return items.flatMap((item) => {
|
||||
const children = item.children
|
||||
? filterMenuByViewPrivilege(item.children, privileges, false)
|
||||
: undefined;
|
||||
const children = item.children ? filterMenuByViewPrivilege(item.children, privileges, false) : undefined;
|
||||
|
||||
if (item.moduleKey && privileges[item.moduleKey]?.ALLOW_VIEW !== true) {
|
||||
return [];
|
||||
|
||||
Reference in New Issue
Block a user