diff --git a/.agents/skills/index-layout/SKILL.md b/.agents/skills/index-layout/SKILL.md index 14e5966..a9a6bc7 100644 --- a/.agents/skills/index-layout/SKILL.md +++ b/.agents/skills/index-layout/SKILL.md @@ -45,7 +45,11 @@ Do **not** redeclare: // GOOD const columnDefs = [ { field: 'username', headerName: t('common:fields.username'), minWidth: 160 }, - { field: 'privilege', headerName: t('common:fields.privilege'), valueGetter: ({ data }) => relationLabel(data?.privilege) }, + { + field: 'privilege', + headerName: t('common:fields.privilege'), + valueGetter: ({ data }) => relationLabel(data?.privilege), + }, ]; // BAD — duplicates shared chrome @@ -59,12 +63,12 @@ const columnDefs = [ API and transformers map: -| Entity field | Meaning | -|---|---| -| `createdBy` | actor string, `{ username }`, or uuid | -| `createdAt` | unix ms | -| `updatedBy` | actor string, `{ username }`, or uuid | -| `updatedAt` | unix ms | +| Entity field | Meaning | +| ------------ | ------------------------------------- | +| `createdBy` | actor string, `{ username }`, or uuid | +| `createdAt` | unix ms | +| `updatedBy` | actor string, `{ username }`, or uuid | +| `updatedAt` | unix ms | `EnterpriseDataTable` postfix columns bind those names and fall back to legacy snake_case (`creator_name`, `created_at`, `editor_name`, `updated_at`) for leftover local/Pouch rows. diff --git a/api.md b/api.md index 92d0964..ae2ced6 100644 --- a/api.md +++ b/api.md @@ -25,10 +25,10 @@ Base URL: `http://localhost:{PORT}` (default **3000**). There is **no** global p ### Tokens -| Token | Type | Default lifetime | Transport | -| ----- | ---- | ---------------- | --------- | -| Access | JWT HS256 | `15m` (`JWT_ACCESS_EXPIRES_IN`) | `Authorization: Bearer …` | -| Refresh | Opaque 64-char string | 7 days (`REFRESH_TOKEN_EXPIRES_IN_MS`) | JSON body `refreshToken` | +| Token | Type | Default lifetime | Transport | +| ------- | --------------------- | -------------------------------------- | ------------------------- | +| Access | JWT HS256 | `15m` (`JWT_ACCESS_EXPIRES_IN`) | `Authorization: Bearer …` | +| Refresh | Opaque 64-char string | 7 days (`REFRESH_TOKEN_EXPIRES_IN_MS`) | JSON body `refreshToken` | Refresh **rotates**: each successful `POST /auth/refresh` returns a new pair; the old refresh token is invalid. @@ -111,11 +111,11 @@ CSV import uses `multipart/form-data` with field name **`file`** (max 1 MiB). Query (all optional): -| Param | Rules | Default | -| ----- | ----- | ------- | -| `page` | integer ≥ 1 | `1` | -| `limit` | 1–**200** | `10` | -| `offset` | integer ≥ 0 | — | +| Param | Rules | Default | +| -------- | ----------- | ------- | +| `page` | integer ≥ 1 | `1` | +| `limit` | 1–**200** | `10` | +| `offset` | integer ≥ 0 | — | If both `page` and `offset` are sent, **`page` wins**. `offset` maps to `page = floor(offset / limit) + 1`. @@ -123,7 +123,9 @@ Public response: ```json { - "data": [ /* items */ ], + "data": [ + /* items */ + ], "meta": { "currentPage": 1, "itemCount": 10, @@ -140,15 +142,15 @@ Public response: Unwrapped resource object, or: -| Operation | Status | Body | -| --------- | ------ | ---- | -| Create | `201` | resource DTO (detail shape) | -| Update / status | `200` | resource DTO | -| Delete | `204` | empty | -| Bulk delete | `200` | `{ "deleted": number }` | -| Bulk status | `200` | `{ "updated": number }` | -| Import | `200` | `{ "imported": number }` | -| Generate plans | `200` | `{ "created": number, "skipped": number }` | +| Operation | Status | Body | +| --------------- | ------ | ------------------------------------------ | +| Create | `201` | resource DTO (detail shape) | +| Update / status | `200` | resource DTO | +| Delete | `204` | empty | +| Bulk delete | `200` | `{ "deleted": number }` | +| Bulk status | `200` | `{ "updated": number }` | +| Import | `200` | `{ "imported": number }` | +| Generate plans | `200` | `{ "created": number, "skipped": number }` | ### Errors (NestJS default) @@ -162,14 +164,14 @@ Unwrapped resource object, or: `message` is a string or an array of validation strings. -| Status | Typical cause | -| ------ | ------------- | -| `400` | Validation, extra fields, invalid VO (phone/date/status), `status` on PATCH | -| `401` | Missing/expired/revoked JWT, bad credentials, invalid refresh | -| `403` | `Insufficient privilege` | -| `404` | `{Resource} not found` (for example `Branch not found`) | -| `409` | Unique conflict (username, code, cycle/plan already exists) | -| `429` | Rate limit | +| Status | Typical cause | +| ------ | --------------------------------------------------------------------------- | +| `400` | Validation, extra fields, invalid VO (phone/date/status), `status` on PATCH | +| `401` | Missing/expired/revoked JWT, bad credentials, invalid refresh | +| `403` | `Insufficient privilege` | +| `404` | `{Resource} not found` (for example `Branch not found`) | +| `409` | Unique conflict (username, code, cycle/plan already exists) | +| `429` | Rate limit | CSV batch failure: @@ -189,12 +191,12 @@ Core (configuration, privileges, cycles, plans, settings): `draft` | `active` | Sales statuses (use **only** these on that resource): -| Resource | Allowed | -| -------- | ------- | -| Sales request | `draft`, `pending`, `approved`, `rejected` | -| Sales order / packing slip | `draft`, `processed`, `completed`, `cancelled` | -| Sales invoice | `draft`, `processed`, `partial`, `completed`, `cancelled` | -| Sales payment | `draft`, `pending`, `approved`, `rejected` | +| Resource | Allowed | +| -------------------------- | --------------------------------------------------------- | +| Sales request | `draft`, `pending`, `approved`, `rejected` | +| Sales order / packing slip | `draft`, `processed`, `completed`, `cancelled` | +| Sales invoice | `draft`, `processed`, `partial`, `completed`, `cancelled` | +| Sales payment | `draft`, `pending`, `approved`, `rejected` | ### Dates @@ -230,43 +232,43 @@ Actions: `view` | `create` | `update` | `delete` | `import`. HTTP mapping: -| Handler | Action | -| ------- | ------ | -| `GET` list / detail | `view` | -| `POST /` create, `POST /plans/generate` | `create` | +| Handler | Action | +| --------------------------------------------------------------------------------------------------- | -------- | +| `GET` list / detail | `view` | +| `POST /` create, `POST /plans/generate` | `create` | | `PATCH /:id`, `PATCH /:id/status`, `POST /bulk-status`, nested customer contacts, plan destinations | `update` | -| `DELETE /:id`, `POST /bulk-delete` | `delete` | -| `POST /import` | `import` | +| `DELETE /:id`, `POST /bulk-delete` | `delete` | +| `POST /import` | `import` | Catalog (`GET /privilege-keys`, needs `PRIVILEGES` `view`): -| code | label | -| ---- | ----- | -| `PRIVILEGES` | Privileges | -| `USERS` | Users | -| `CONFIGURATION.DIVISION` | Divisions | -| `CONFIGURATION.BRANCH` | Branches | -| `CONFIGURATION.CUSTOMER` | Customers | -| `CONFIGURATION.EMPLOYEE` | Employees | -| `CONFIGURATION.PRODUCT` | Products | -| `SALES.REQUEST` | Sales requests | -| `SALES.ORDER` | Sales orders | -| `SALES.PACKING_SLIP` | Packing slips | -| `SALES.INVOICE` | Sales invoices | -| `SALES.PAYMENT` | Sales payments | -| `CONFIGURATION.SETTING` | Company settings | -| `SALES.CYCLE` | Sales cycles | -| `SALES.PLAN` | Sales plans | -| `LOGISTICS.CYCLE` | Logistics cycles | -| `LOGISTICS.PLAN` | Logistics plans | +| code | label | +| ------------------------ | ---------------- | +| `PRIVILEGES` | Privileges | +| `USERS` | Users | +| `CONFIGURATION.DIVISION` | Divisions | +| `CONFIGURATION.BRANCH` | Branches | +| `CONFIGURATION.CUSTOMER` | Customers | +| `CONFIGURATION.EMPLOYEE` | Employees | +| `CONFIGURATION.PRODUCT` | Products | +| `SALES.REQUEST` | Sales requests | +| `SALES.ORDER` | Sales orders | +| `SALES.PACKING_SLIP` | Packing slips | +| `SALES.INVOICE` | Sales invoices | +| `SALES.PAYMENT` | Sales payments | +| `CONFIGURATION.SETTING` | Company settings | +| `SALES.CYCLE` | Sales cycles | +| `SALES.PLAN` | Sales plans | +| `LOGISTICS.CYCLE` | Logistics cycles | +| `LOGISTICS.PLAN` | Logistics plans | ### Field purpose Cycles and plans do **not** use a single key. Privilege is resolved from `purpose`: -| purpose | cycle key | plan key | -| ------- | --------- | -------- | -| `sales` | `SALES.CYCLE` | `SALES.PLAN` | +| purpose | cycle key | plan key | +| ----------- | ----------------- | ---------------- | +| `sales` | `SALES.CYCLE` | `SALES.PLAN` | | `logistics` | `LOGISTICS.CYCLE` | `LOGISTICS.PLAN` | `purpose` is read from **body** (writes) or **query** (lists). If omitted, the user may proceed if they have the action on **either** purpose; list results are filtered to purposes they can view. Superadmin bypasses. @@ -279,17 +281,17 @@ Sales plans may attach `invoiceIds` only. Logistics plans may attach `packingSli Unless a section says otherwise, each resource below implements: -| Method | Path | Status | Notes | -| ------ | ---- | ------ | ----- | -| `GET` | `/{resource}` | `200` | Paginated `{ data, meta }` | -| `GET` | `/{resource}/:id` | `200` | Detail (may include nested arrays list omits) | -| `POST` | `/{resource}` | `201` | Create | -| `PATCH` | `/{resource}/:id` | `200` | Update — **no** `status` | -| `PATCH` | `/{resource}/:id/status` | `200` | `{ status }` | -| `DELETE` | `/{resource}/:id` | `204` | Hard delete (cycles/plans: archive) | -| `POST` | `/{resource}/bulk-delete` | `200` | `{ ids }` → `{ deleted }` | -| `POST` | `/{resource}/bulk-status` | `200` | `{ ids, status }` → `{ updated }` | -| `POST` | `/{resource}/import` | `200` | multipart `file` → `{ imported }` | +| Method | Path | Status | Notes | +| -------- | ------------------------- | ------ | --------------------------------------------- | +| `GET` | `/{resource}` | `200` | Paginated `{ data, meta }` | +| `GET` | `/{resource}/:id` | `200` | Detail (may include nested arrays list omits) | +| `POST` | `/{resource}` | `201` | Create | +| `PATCH` | `/{resource}/:id` | `200` | Update — **no** `status` | +| `PATCH` | `/{resource}/:id/status` | `200` | `{ status }` | +| `DELETE` | `/{resource}/:id` | `204` | Hard delete (cycles/plans: archive) | +| `POST` | `/{resource}/bulk-delete` | `200` | `{ ids }` → `{ deleted }` | +| `POST` | `/{resource}/bulk-status` | `200` | `{ ids, status }` → `{ updated }` | +| `POST` | `/{resource}/import` | `200` | multipart `file` → `{ imported }` | Audit fields on primary DTOs: `createdAt`, `updatedAt` (unix ms), `createdBy`, `updatedBy` (user UUID). @@ -352,9 +354,7 @@ List filters: `name`, `code`, `status`, `search` (name/code). "name": "Sales Staff", "code": "SALES_STAFF", "status": "draft", - "details": [ - { "privilegeKeyId": "uuid", "action": "view", "value": true } - ] + "details": [{ "privilegeKeyId": "uuid", "action": "view", "value": true }] } ``` @@ -467,11 +467,11 @@ Contact create: `name` required; `jobTitle?`, `phone?`, `mobilePhone?`, `notes?` ### Nested contacts (privilege = customer **update**) -| Method | Path | Status | Body | Response | -| ------ | ---- | ------ | ---- | -------- | -| `POST` | `/customers/:id/contacts` | `200` | create contact | full `CustomerDto` | -| `PATCH` | `/customers/:id/contacts/:contactId` | `200` | partial contact | full `CustomerDto` | -| `DELETE` | `/customers/:id/contacts/:contactId` | `204` | — | empty | +| Method | Path | Status | Body | Response | +| -------- | ------------------------------------ | ------ | --------------- | ------------------ | +| `POST` | `/customers/:id/contacts` | `200` | create contact | full `CustomerDto` | +| `PATCH` | `/customers/:id/contacts/:contactId` | `200` | partial contact | full `CustomerDto` | +| `DELETE` | `/customers/:id/contacts/:contactId` | `204` | — | empty | List filters: `code`, `name`, `phone`, `address`, `nfcId`, `status`, `search` (code/name/address). @@ -574,12 +574,8 @@ Detail adds: ```json { - "products": [ - { "id": "uuid", "productId": "uuid", "quantity": "2.0000", "price": "12500.0000" } - ], - "images": [ - { "id": "uuid", "url": "https://…", "description": null } - ] + "products": [{ "id": "uuid", "productId": "uuid", "quantity": "2.0000", "price": "12500.0000" }], + "images": [{ "id": "uuid", "url": "https://…", "description": null }] } ``` @@ -659,10 +655,10 @@ CSV required: `date` (allocations empty on import). Key: `CONFIGURATION.SETTING`. Singleton — no list/CRUD. -| Method | Path | Action | Notes | -| ------ | ---- | ------ | ----- | -| `GET` | `/settings` | view | `404` `{ "message": "Settings not configured" }` if never patched | -| `PATCH` | `/settings` | update | upserts | +| Method | Path | Action | Notes | +| ------- | ----------- | ------ | ----------------------------------------------------------------- | +| `GET` | `/settings` | view | `404` `{ "message": "Settings not configured" }` if never patched | +| `PATCH` | `/settings` | update | upserts | **Patch body:** `{ "cycleStartDate": "2026-01-05" }` (`YYYY-MM-DD` only). @@ -730,11 +726,12 @@ No hard delete: `DELETE` / `bulk-delete` archive. **Has CSV import.** Standard l "endBranchId": "uuid", "routeGeometry": { "type": "LineString", - "coordinates": [[106.8456, -6.2088], [107.0, -6.3]] + "coordinates": [ + [106.8456, -6.2088], + [107.0, -6.3] + ] }, - "destinations": [ - { "id": "uuid", "customerId": "uuid", "sortOrder": 0 } - ] + "destinations": [{ "id": "uuid", "customerId": "uuid", "sortOrder": 0 }] } ], "status": "draft", @@ -759,19 +756,19 @@ Privilege: `RequireFieldPrivilege('plan', action)` → `SALES.PLAN` or `LOGISTIC **No CSV import.** Delete archives. Extra routes: generate, add/remove destinations. -| Method | Path | Action | Status | Body / response | -| ------ | ---- | ------ | ------ | --------------- | -| `GET` | `/plans` | view | 200 | paginated | -| `GET` | `/plans/:id` | view | 200 | `PlanDto` | -| `POST` | `/plans/generate` | create | 200 | `{ employeeId, purpose, from, to }` → `{ created, skipped }` | -| `POST` | `/plans` | create | 201 | see below | -| `PATCH` | `/plans/:id` | update | 200 | no status | -| `PATCH` | `/plans/:id/status` | update | 200 | `{ status }` | -| `POST` | `/plans/:id/destinations` | update | 200 | `{ customerId, afterDestinationId? }` → `PlanDto` | -| `DELETE` | `/plans/:id/destinations/:destinationId` | update | **200** | `PlanDto` (not 204) | -| `DELETE` | `/plans/:id` | delete | 204 | archives | -| `POST` | `/plans/bulk-delete` | delete | 200 | `{ deleted }` | -| `POST` | `/plans/bulk-status` | update | 200 | `{ updated }` | +| Method | Path | Action | Status | Body / response | +| -------- | ---------------------------------------- | ------ | ------- | ------------------------------------------------------------ | +| `GET` | `/plans` | view | 200 | paginated | +| `GET` | `/plans/:id` | view | 200 | `PlanDto` | +| `POST` | `/plans/generate` | create | 200 | `{ employeeId, purpose, from, to }` → `{ created, skipped }` | +| `POST` | `/plans` | create | 201 | see below | +| `PATCH` | `/plans/:id` | update | 200 | no status | +| `PATCH` | `/plans/:id/status` | update | 200 | `{ status }` | +| `POST` | `/plans/:id/destinations` | update | 200 | `{ customerId, afterDestinationId? }` → `PlanDto` | +| `DELETE` | `/plans/:id/destinations/:destinationId` | update | **200** | `PlanDto` (not 204) | +| `DELETE` | `/plans/:id` | delete | 204 | archives | +| `POST` | `/plans/bulk-delete` | delete | 200 | `{ deleted }` | +| `POST` | `/plans/bulk-status` | update | 200 | `{ updated }` | Register static paths (`generate`, `bulk-delete`, `bulk-status`) before `:id`. @@ -898,14 +895,7 @@ type UnixMs = number; type DecimalString = string; type CoreStatus = 'draft' | 'active' | 'archived'; type FieldPurpose = 'sales' | 'logistics'; -type Weekday = - | 'monday' - | 'tuesday' - | 'wednesday' - | 'thursday' - | 'friday' - | 'saturday' - | 'sunday'; +type Weekday = 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday'; type PrivilegeAction = 'view' | 'create' | 'update' | 'delete' | 'import'; type PaginationMeta = { @@ -932,10 +922,7 @@ type Me = { username: string; isSuperadmin: boolean; privilege: { id: Uuid; name: string; code: string } | null; - permissions: Record< - string, - Record - >; + permissions: Record>; }; type RouteGeometry = { diff --git a/apps/web/src/apps/main/modules/configuration/customers/domain/transformers/customer.remote.transformer.test.ts b/apps/web/src/apps/main/modules/configuration/customers/domain/transformers/customer.remote.transformer.test.ts index a5ebc21..1d3c329 100644 --- a/apps/web/src/apps/main/modules/configuration/customers/domain/transformers/customer.remote.transformer.test.ts +++ b/apps/web/src/apps/main/modules/configuration/customers/domain/transformers/customer.remote.transformer.test.ts @@ -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'); }); diff --git a/apps/web/src/apps/main/modules/configuration/customers/presentation/components/detail-component/detail-contacts.tsx b/apps/web/src/apps/main/modules/configuration/customers/presentation/components/detail-component/detail-contacts.tsx index 73ea453..a30d091 100644 --- a/apps/web/src/apps/main/modules/configuration/customers/presentation/components/detail-component/detail-contacts.tsx +++ b/apps/web/src/apps/main/modules/configuration/customers/presentation/components/detail-component/detail-contacts.tsx @@ -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() { openEdit(contact)} aria-label={t('edit_contact')}> - handleDelete(contact)} aria-label={t('delete_contact')}> + handleDelete(contact)} + aria-label={t('delete_contact')} + > diff --git a/apps/web/src/apps/main/modules/configuration/customers/presentation/components/form-component/form-contacts.tsx b/apps/web/src/apps/main/modules/configuration/customers/presentation/components/form-component/form-contacts.tsx index 9bb5981..b60cf0c 100644 --- a/apps/web/src/apps/main/modules/configuration/customers/presentation/components/form-component/form-contacts.tsx +++ b/apps/web/src/apps/main/modules/configuration/customers/presentation/components/form-component/form-contacts.tsx @@ -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'; diff --git a/apps/web/src/apps/main/modules/configuration/products/domain/validators/product.validator.ts b/apps/web/src/apps/main/modules/configuration/products/domain/validators/product.validator.ts index ed01c13..3d7cc89 100644 --- a/apps/web/src/apps/main/modules/configuration/products/domain/validators/product.validator.ts +++ b/apps/web/src/apps/main/modules/configuration/products/domain/validators/product.validator.ts @@ -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()), }); }; diff --git a/apps/web/src/apps/main/modules/field/cycles/data/cycle.remote.service.test.ts b/apps/web/src/apps/main/modules/field/cycles/data/cycle.remote.service.test.ts index 8e52fc9..a9f909e 100644 --- a/apps/web/src/apps/main/modules/field/cycles/data/cycle.remote.service.test.ts +++ b/apps/web/src/apps/main/modules/field/cycles/data/cycle.remote.service.test.ts @@ -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 () => { diff --git a/apps/web/src/apps/main/modules/field/cycles/domain/entities/cycle.entity.ts b/apps/web/src/apps/main/modules/field/cycles/domain/entities/cycle.entity.ts index 0b014ae..407001e 100644 --- a/apps/web/src/apps/main/modules/field/cycles/domain/entities/cycle.entity.ts +++ b/apps/web/src/apps/main/modules/field/cycles/domain/entities/cycle.entity.ts @@ -51,7 +51,9 @@ export interface CycleDto { employeeId: string; purpose: FieldPurpose; cycleNumber: number; - weekdays?: CycleWeekdayEntity[] | Record; + weekdays?: + | CycleWeekdayEntity[] + | Record; status?: ConfigurationStatus; createdAt?: number; updatedAt?: number; diff --git a/apps/web/src/apps/main/modules/field/cycles/domain/transformers/cycle.remote.transformer.ts b/apps/web/src/apps/main/modules/field/cycles/domain/transformers/cycle.remote.transformer.ts index bba0e69..7d8361a 100644 --- a/apps/web/src/apps/main/modules/field/cycles/domain/transformers/cycle.remote.transformer.ts +++ b/apps/web/src/apps/main/modules/field/cycles/domain/transformers/cycle.remote.transformer.ts @@ -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) { diff --git a/apps/web/src/apps/main/modules/field/cycles/presentation/pages/cycle.page.form.tsx b/apps/web/src/apps/main/modules/field/cycles/presentation/pages/cycle.page.form.tsx index 70b4aa2..7c4810b 100644 --- a/apps/web/src/apps/main/modules/field/cycles/presentation/pages/cycle.page.form.tsx +++ b/apps/web/src/apps/main/modules/field/cycles/presentation/pages/cycle.page.form.tsx @@ -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, diff --git a/apps/web/src/apps/main/modules/field/cycles/presentation/pages/cycle.page.index.tsx b/apps/web/src/apps/main/modules/field/cycles/presentation/pages/cycle.page.index.tsx index caa1041..60c4c3b 100644 --- a/apps/web/src/apps/main/modules/field/cycles/presentation/pages/cycle.page.index.tsx +++ b/apps/web/src/apps/main/modules/field/cycles/presentation/pages/cycle.page.index.tsx @@ -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]); diff --git a/apps/web/src/apps/main/modules/field/plans/domain/transformers/plan.remote.transformer.ts b/apps/web/src/apps/main/modules/field/plans/domain/transformers/plan.remote.transformer.ts index 7c12ead..a432b1d 100644 --- a/apps/web/src/apps/main/modules/field/plans/domain/transformers/plan.remote.transformer.ts +++ b/apps/web/src/apps/main/modules/field/plans/domain/transformers/plan.remote.transformer.ts @@ -65,7 +65,9 @@ export class PlansRemoteDataTransformer extends BaseDataTransformer 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; } diff --git a/apps/web/src/apps/main/modules/field/plans/presentation/components/detail-component/detail-general.tsx b/apps/web/src/apps/main/modules/field/plans/presentation/components/detail-component/detail-general.tsx index a86b337..12b9b0c 100644 --- a/apps/web/src/apps/main/modules/field/plans/presentation/components/detail-component/detail-general.tsx +++ b/apps/web/src/apps/main/modules/field/plans/presentation/components/detail-component/detail-general.tsx @@ -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() { - } /> - - - } /> + } + /> + + + } + /> diff --git a/apps/web/src/apps/main/modules/field/plans/presentation/components/form-component/form-general.tsx b/apps/web/src/apps/main/modules/field/plans/presentation/components/form-component/form-general.tsx index 6aec4b2..1887ba5 100644 --- a/apps/web/src/apps/main/modules/field/plans/presentation/components/form-component/form-general.tsx +++ b/apps/web/src/apps/main/modules/field/plans/presentation/components/form-component/form-general.tsx @@ -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'; diff --git a/apps/web/src/apps/main/modules/field/plans/presentation/pages/plan.page.form.tsx b/apps/web/src/apps/main/modules/field/plans/presentation/pages/plan.page.form.tsx index 2e3e7e8..2756b69 100644 --- a/apps/web/src/apps/main/modules/field/plans/presentation/pages/plan.page.form.tsx +++ b/apps/web/src/apps/main/modules/field/plans/presentation/pages/plan.page.form.tsx @@ -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, diff --git a/apps/web/src/apps/main/modules/field/shared/relation-label.ts b/apps/web/src/apps/main/modules/field/shared/relation-label.ts index 5bdf681..7ea956b 100644 --- a/apps/web/src/apps/main/modules/field/shared/relation-label.ts +++ b/apps/web/src/apps/main/modules/field/shared/relation-label.ts @@ -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)); diff --git a/apps/web/src/apps/main/modules/sales/orders/presentation/pages/sales-order.page.detail.tsx b/apps/web/src/apps/main/modules/sales/orders/presentation/pages/sales-order.page.detail.tsx index abac559..f9e8cf1 100644 --- a/apps/web/src/apps/main/modules/sales/orders/presentation/pages/sales-order.page.detail.tsx +++ b/apps/web/src/apps/main/modules/sales/orders/presentation/pages/sales-order.page.detail.tsx @@ -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 ?? []) + } > `${salesRequestsModuleConfig.webUrl}/detail/${id}`} /> diff --git a/apps/web/src/apps/main/modules/sales/orders/presentation/pages/sales-order.page.form.tsx b/apps/web/src/apps/main/modules/sales/orders/presentation/pages/sales-order.page.form.tsx index a0828ad..2666446 100644 --- a/apps/web/src/apps/main/modules/sales/orders/presentation/pages/sales-order.page.form.tsx +++ b/apps/web/src/apps/main/modules/sales/orders/presentation/pages/sales-order.page.form.tsx @@ -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, diff --git a/apps/web/src/apps/main/modules/sales/shared/detail-general.tsx b/apps/web/src/apps/main/modules/sales/shared/detail-general.tsx index aafcf03..cf76777 100644 --- a/apps/web/src/apps/main/modules/sales/shared/detail-general.tsx +++ b/apps/web/src/apps/main/modules/sales/shared/detail-general.tsx @@ -8,7 +8,9 @@ export function DetailGeneral({ }: { salesRequestHref?: (id: string) => string; } = {}) { - const { detailData } = useDetailPageContext(); + 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) => } /> - + @@ -36,9 +41,7 @@ export function DetailGeneral({ value={salesRequestId} render={() => salesRequestId ? ( - - {data?.salesRequest?.code || salesRequestId} - + {data?.salesRequest?.code || salesRequestId} ) : ( '-' ) diff --git a/apps/web/src/apps/main/modules/sales/shared/detail-location.tsx b/apps/web/src/apps/main/modules/sales/shared/detail-location.tsx index 0e400e3..e32f276 100644 --- a/apps/web/src/apps/main/modules/sales/shared/detail-location.tsx +++ b/apps/web/src/apps/main/modules/sales/shared/detail-location.tsx @@ -14,11 +14,7 @@ export function DetailLocation() { {t('section_location')} - + diff --git a/apps/web/src/apps/main/modules/sales/shared/form-general.tsx b/apps/web/src/apps/main/modules/sales/shared/form-general.tsx index 8473b70..d3453f1 100644 --- a/apps/web/src/apps/main/modules/sales/shared/form-general.tsx +++ b/apps/web/src/apps/main/modules/sales/shared/form-general.tsx @@ -32,7 +32,13 @@ export function FormGeneral() { placeholder="e.g. SR-20260826-0001" radius="md" /> - + control={formControl.control} name="salesPerson" diff --git a/apps/web/src/apps/main/modules/sales/shared/form-images.tsx b/apps/web/src/apps/main/modules/sales/shared/form-images.tsx index 54beb21..e24449a 100644 --- a/apps/web/src/apps/main/modules/sales/shared/form-images.tsx +++ b/apps/web/src/apps/main/modules/sales/shared/form-images.tsx @@ -15,7 +15,12 @@ export function FormImages() { {t('section_images')} - diff --git a/apps/web/src/apps/main/modules/sales/shared/form-products.tsx b/apps/web/src/apps/main/modules/sales/shared/form-products.tsx index 08cfc6c..371fc5d 100644 --- a/apps/web/src/apps/main/modules/sales/shared/form-products.tsx +++ b/apps/web/src/apps/main/modules/sales/shared/form-products.tsx @@ -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() { {currency.format(lineTotal(line?.quantity, line?.price))} - remove(index)} aria-label={t('remove_line')}> + remove(index)} + aria-label={t('remove_line')} + > diff --git a/apps/web/src/apps/main/modules/sales/shared/sales-document.mapper.ts b/apps/web/src/apps/main/modules/sales/shared/sales-document.mapper.ts index 29abc52..9658158 100644 --- a/apps/web/src/apps/main/modules/sales/shared/sales-document.mapper.ts +++ b/apps/web/src/apps/main/modules/sales/shared/sales-document.mapper.ts @@ -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; diff --git a/apps/web/src/apps/main/modules/sales/shared/sales-document.validator.ts b/apps/web/src/apps/main/modules/sales/shared/sales-document.validator.ts index 0820ee4..9473df3 100644 --- a/apps/web/src/apps/main/modules/sales/shared/sales-document.validator.ts +++ b/apps/web/src/apps/main/modules/sales/shared/sales-document.validator.ts @@ -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; diff --git a/apps/web/src/apps/main/modules/sales/shared/use-sales-document-actions.tsx b/apps/web/src/apps/main/modules/sales/shared/use-sales-document-actions.tsx index eb20dce..55c8677 100644 --- a/apps/web/src/apps/main/modules/sales/shared/use-sales-document-actions.tsx +++ b/apps/web/src/apps/main/modules/sales/shared/use-sales-document-actions.tsx @@ -82,7 +82,10 @@ export function useSalesDocumentActions(documentType: SalesDocumentType) { icon: , 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, + ); }, }; }); diff --git a/apps/web/src/apps/main/modules/system/privileges/domain/transformers/privilege.remote.transformer.test.ts b/apps/web/src/apps/main/modules/system/privileges/domain/transformers/privilege.remote.transformer.test.ts index 665998b..b4b6893 100644 --- a/apps/web/src/apps/main/modules/system/privileges/domain/transformers/privilege.remote.transformer.test.ts +++ b/apps/web/src/apps/main/modules/system/privileges/domain/transformers/privilege.remote.transformer.test.ts @@ -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', () => { diff --git a/apps/web/src/apps/main/modules/system/privileges/presentation/components/form-component/form-permissions.tsx b/apps/web/src/apps/main/modules/system/privileges/presentation/components/form-component/form-permissions.tsx index 996425b..a490a68 100644 --- a/apps/web/src/apps/main/modules/system/privileges/presentation/components/form-component/form-permissions.tsx +++ b/apps/web/src/apps/main/modules/system/privileges/presentation/components/form-component/form-permissions.tsx @@ -56,11 +56,7 @@ export function FormPermissions() { {PRIVILEGE_ACTIONS.map((action) => ( - + ))} diff --git a/apps/web/src/apps/main/modules/system/users/data/user.remote.service.test.ts b/apps/web/src/apps/main/modules/system/users/data/user.remote.service.test.ts index a95e9e5..b16bd7e 100644 --- a/apps/web/src/apps/main/modules/system/users/data/user.remote.service.test.ts +++ b/apps/web/src/apps/main/modules/system/users/data/user.remote.service.test.ts @@ -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 () => { diff --git a/apps/web/src/apps/main/modules/system/users/domain/validators/user.validator.ts b/apps/web/src/apps/main/modules/system/users/domain/validators/user.validator.ts index 149e084..888696f 100644 --- a/apps/web/src/apps/main/modules/system/users/domain/validators/user.validator.ts +++ b/apps/web/src/apps/main/modules/system/users/domain/validators/user.validator.ts @@ -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(), diff --git a/apps/web/src/apps/main/modules/system/users/presentation/components/form-component/form-general.tsx b/apps/web/src/apps/main/modules/system/users/presentation/components/form-component/form-general.tsx index c8ebede..34718a1 100644 --- a/apps/web/src/apps/main/modules/system/users/presentation/components/form-component/form-general.tsx +++ b/apps/web/src/apps/main/modules/system/users/presentation/components/form-component/form-general.tsx @@ -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 ( diff --git a/apps/web/src/apps/main/modules/system/users/presentation/factory/index.tsx b/apps/web/src/apps/main/modules/system/users/presentation/factory/index.tsx index 7e2013c..cf6a6e7 100644 --- a/apps/web/src/apps/main/modules/system/users/presentation/factory/index.tsx +++ b/apps/web/src/apps/main/modules/system/users/presentation/factory/index.tsx @@ -21,11 +21,7 @@ registerModuleNamespace(usersModuleConfig.translationNamespace, { export default function UsersModule() { return ( - - config={usersModuleConfig} - dataServices={usersDataService} - store={usersStore} - > + config={usersModuleConfig} dataServices={usersDataService} store={usersStore}> } /> } /> diff --git a/apps/web/src/core/domain/configuration-field-validators.ts b/apps/web/src/core/domain/configuration-field-validators.ts index b6f2011..3059e9c 100644 --- a/apps/web/src/core/domain/configuration-field-validators.ts +++ b/apps/web/src/core/domain/configuration-field-validators.ts @@ -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(), diff --git a/apps/web/src/core/lib/filter-menu-by-view-privilege.ts b/apps/web/src/core/lib/filter-menu-by-view-privilege.ts index 7b837ea..77c952d 100644 --- a/apps/web/src/core/lib/filter-menu-by-view-privilege.ts +++ b/apps/web/src/core/lib/filter-menu-by-view-privilege.ts @@ -10,9 +10,7 @@ export function filterMenuByViewPrivilege { - 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 []; diff --git a/packages/ui/src/components/map/location-map.test.tsx b/packages/ui/src/components/map/location-map.test.tsx index a10f23f..5b255ab 100644 --- a/packages/ui/src/components/map/location-map.test.tsx +++ b/packages/ui/src/components/map/location-map.test.tsx @@ -17,7 +17,9 @@ const { setView, panTo, invalidateSize } = vi.hoisted(() => ({ vi.mock('react-leaflet', () => ({ MapContainer: ({ children }: { children: React.ReactNode }) =>
{children}
, TileLayer: () =>
, - CircleMarker: ({ center }: { center: [number, number] }) =>
{`${center[0]},${center[1]}`}
, + CircleMarker: ({ center }: { center: [number, number] }) => ( +
{`${center[0]},${center[1]}`}
+ ), useMap: () => ({ setView, panTo, invalidateSize }), useMapEvents: (handlers: { click?: (event: LeafletClick) => void }) => { mapClick = handlers.click; diff --git a/packages/ui/src/components/map/location-map.tsx b/packages/ui/src/components/map/location-map.tsx index ace9562..3839826 100644 --- a/packages/ui/src/components/map/location-map.tsx +++ b/packages/ui/src/components/map/location-map.tsx @@ -123,10 +123,19 @@ export function LocationMap({ className="tg-map-viewport" style={{ cursor: interactive ? 'crosshair' : undefined }} > - + {position ? ( - + ) : null} {onChange ? : null} diff --git a/packages/ui/src/components/map/location-point.ts b/packages/ui/src/components/map/location-point.ts index bcca7d6..41502aa 100644 --- a/packages/ui/src/components/map/location-point.ts +++ b/packages/ui/src/components/map/location-point.ts @@ -43,7 +43,7 @@ export function clampLatitude(value: number): number { // which the form schemas reject. Keep 180 and -180 as authored instead of collapsing them. export function wrapLongitude(value: number): number { if (value >= -180 && value <= 180) return value; - const wrapped = ((value + 180) % 360 + 360) % 360 - 180; + const wrapped = ((((value + 180) % 360) + 360) % 360) - 180; return wrapped; } @@ -54,10 +54,7 @@ export function locationFromLatLng(lat: number, lng: number): { latitude: number }; } -export function mapViewForLocation( - latitude: unknown, - longitude: unknown, -): { center: [number, number]; zoom: number } { +export function mapViewForLocation(latitude: unknown, longitude: unknown): { center: [number, number]; zoom: number } { const point = toLocationLatLng(latitude, longitude); if (!point) { return { center: DEFAULT_MAP_CENTER, zoom: DEFAULT_MAP_ZOOM }; diff --git a/packages/ui/src/components/map/osm.ts b/packages/ui/src/components/map/osm.ts index 440194e..ef0da31 100644 --- a/packages/ui/src/components/map/osm.ts +++ b/packages/ui/src/components/map/osm.ts @@ -1,3 +1,3 @@ export const OSM_TILE_URL = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; -export const OSM_ATTRIBUTION = '© OpenStreetMap contributors'; - +export const OSM_ATTRIBUTION = + '© OpenStreetMap contributors'; diff --git a/packages/ui/src/components/map/route-geometry.ts b/packages/ui/src/components/map/route-geometry.ts index 7e3496c..4d6b1bb 100644 --- a/packages/ui/src/components/map/route-geometry.ts +++ b/packages/ui/src/components/map/route-geometry.ts @@ -7,7 +7,5 @@ export function toLeafletLatLngs(geometry?: RouteGeometry | null): Array<[number if (!geometry?.coordinates?.length) { return []; } - return geometry.coordinates - .filter((pair) => Array.isArray(pair) && pair.length >= 2) - .map(([lng, lat]) => [lat, lng]); + return geometry.coordinates.filter((pair) => Array.isArray(pair) && pair.length >= 2).map(([lng, lat]) => [lat, lng]); } diff --git a/packages/ui/src/components/map/route-map.tsx b/packages/ui/src/components/map/route-map.tsx index cc93570..08b51e1 100644 --- a/packages/ui/src/components/map/route-map.tsx +++ b/packages/ui/src/components/map/route-map.tsx @@ -44,7 +44,12 @@ export function RouteMap({ geometry, height = 280 }: RouteMapProps) { {positions.map((position, index) => ( - + {index + 1} ))} diff --git a/packages/ui/src/foundations/enterprise-module/components/data-table/audit-column.utils.test.ts b/packages/ui/src/foundations/enterprise-module/components/data-table/audit-column.utils.test.ts index fc3d135..905acf6 100644 --- a/packages/ui/src/foundations/enterprise-module/components/data-table/audit-column.utils.test.ts +++ b/packages/ui/src/foundations/enterprise-module/components/data-table/audit-column.utils.test.ts @@ -1,24 +1,17 @@ import { describe, expect, it } from 'vitest'; import { DateUtils } from '@repo/utils'; -import { - EMPTY_AUDIT_DISPLAY, - formatAuditActor, - formatAuditTimestamp, - resolveAuditValue, -} from './audit-column.utils'; +import { EMPTY_AUDIT_DISPLAY, formatAuditActor, formatAuditTimestamp, resolveAuditValue } from './audit-column.utils'; describe('resolveAuditValue', () => { it('prefers camelCase over snake_case', () => { - expect( - resolveAuditValue({ createdBy: 'alice', creator_name: 'legacy' }, 'createdBy', 'creator_name'), - ).toBe('alice'); + expect(resolveAuditValue({ createdBy: 'alice', creator_name: 'legacy' }, 'createdBy', 'creator_name')).toBe( + 'alice', + ); }); it('falls back to snake_case when camelCase is missing', () => { expect(resolveAuditValue({ creator_name: 'legacy' }, 'createdBy', 'creator_name')).toBe('legacy'); - expect(resolveAuditValue({ created_at: 1_700_000_000_000 }, 'createdAt', 'created_at')).toBe( - 1_700_000_000_000, - ); + expect(resolveAuditValue({ created_at: 1_700_000_000_000 }, 'createdAt', 'created_at')).toBe(1_700_000_000_000); }); it('returns undefined when neither field is present', () => { diff --git a/packages/ui/src/foundations/enterprise-module/components/data-table/components/bulk-actions.tsx b/packages/ui/src/foundations/enterprise-module/components/data-table/components/bulk-actions.tsx index b49981d..062dd07 100644 --- a/packages/ui/src/foundations/enterprise-module/components/data-table/components/bulk-actions.tsx +++ b/packages/ui/src/foundations/enterprise-module/components/data-table/components/bulk-actions.tsx @@ -63,7 +63,10 @@ export function BulkActionMenu({ const hasActive = selectedRows.some((row) => row[statusKey]?.toLowerCase() === 'active'); const hasInactive = selectedRows.some( - (row) => row[statusKey]?.toLowerCase() === 'inactive' || row[statusKey]?.toLowerCase() === 'draft' || row[statusKey]?.toLowerCase() === 'archived', + (row) => + row[statusKey]?.toLowerCase() === 'inactive' || + row[statusKey]?.toLowerCase() === 'draft' || + row[statusKey]?.toLowerCase() === 'archived', ); const defaultActions: PageActionProps[] = []; diff --git a/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx b/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx index 4f49218..ef86bdf 100644 --- a/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx +++ b/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx @@ -282,7 +282,7 @@ export function EnterpriseDataTable(props: EnterpriseDataT const filterKeys = filterConfig?.defaultValues ? Object.keys(filterConfig.defaultValues) : Object.keys(filterData).filter( - (key) => key !== searchKey && !['page', 'limit', 'order_by', 'order_type'].includes(key), + (key) => key !== searchKey && !['page', 'limit', 'orderBy', 'orderType'].includes(key), ); return filterKeys.filter((key) => { @@ -773,8 +773,8 @@ export function EnterpriseDataTable(props: EnterpriseDataT const requestParams: Record = { page, limit, - order_by: orderBy, - order_type: orderType, + orderBy: orderBy, + orderType: orderType, ...filterRef.current, }; 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 03b985c..37e2cef 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 @@ -504,7 +504,8 @@ export function EnterpriseDetailPageProvider( const isMasterData = moduleType === 'MASTER_DATA'; const isDataActive = detailData && ['active'].includes(detailData[statusKey]?.toLowerCase()); - const isDataInActive = detailData && ['inactive', 'draft', 'archived'].includes(detailData[statusKey]?.toLowerCase()); + const isDataInActive = + detailData && ['inactive', 'draft', 'archived'].includes(detailData[statusKey]?.toLowerCase()); // 1. Declare action with Privilege & Module Type conditions directly const rawActions = [