739 lines
18 KiB
Markdown
739 lines
18 KiB
Markdown
# Frontend Helpers Module
|
|
|
|
## Overview
|
|
|
|
The Helpers module provides **utility functions** for common operations across the frontend application including data formatting, permission checking, notification handling, text transformation, and file saving.
|
|
|
|
**Location:** `frontend/src/helpers/`
|
|
|
|
**Total Files:** 6 files (~298 LOC)
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
frontend/src/helpers/
|
|
├── dataFormatter.js (176 LOC) # Entity data formatters for display/edit
|
|
├── userPermissions.ts (19 LOC) # RBAC permission checking
|
|
├── notifyStateHandler.ts (32 LOC) # Redux notification state handlers
|
|
├── textFormatters.ts (53 LOC) # Text transformation utilities
|
|
├── humanize.ts (12 LOC) # String humanization
|
|
└── fileSaver.ts (6 LOC) # File download trigger
|
|
```
|
|
|
|
---
|
|
|
|
## Helper Files
|
|
|
|
### 1. Data Formatter (`dataFormatter.js`)
|
|
|
|
**Purpose:** Format entity data for display in tables, lists, and edit forms.
|
|
|
|
**Lines:** ~210 LOC
|
|
|
|
**Dependencies:** `dayjs`, `dayjs/plugin/relativeTime`, `lodash`
|
|
|
|
#### Core Formatters
|
|
|
|
| Function | Input | Output | Description |
|
|
|----------|-------|--------|-------------|
|
|
| `filesFormatter(arr)` | File array | File array | Pass-through for file arrays |
|
|
| `imageFormatter(arr)` | Image array | `[{ publicUrl }]` | Extract public URLs from images |
|
|
| `oneImageFormatter(arr)` | Image array | `string` | Get first image's public URL |
|
|
| `dateFormatter(date)` | Date/string | `YYYY-MM-DD` | Format date for display |
|
|
| `dateTimeFormatter(date)` | Date/string | `YYYY-MM-DD HH:mm` | Format datetime for display |
|
|
| `relativeTimestamp(date)` | Date/string | Human-readable | Format as relative or absolute time |
|
|
| `booleanFormatter(val)` | boolean | `'Yes'` / `'No'` | Human-readable boolean |
|
|
| `dataGridEditFormatter(obj)` | Object | Object | Transform relations to IDs for DataGrid |
|
|
|
|
#### Entity Formatters Pattern
|
|
|
|
Each entity has 4 formatters following this naming convention:
|
|
|
|
```javascript
|
|
// Display formatters (for table cells/lists)
|
|
[entity]ManyListFormatter(val) // Array → display names array
|
|
[entity]OneListFormatter(val) // Object → display name string
|
|
|
|
// Edit formatters (for form selects)
|
|
[entity]ManyListFormatterEdit(val) // Array → [{ id, label }]
|
|
[entity]OneListFormatterEdit(val) // Object → { id, label }
|
|
```
|
|
|
|
#### Supported Entities
|
|
|
|
| Entity | Display Field | Formatters |
|
|
|--------|---------------|------------|
|
|
| `users` | `firstName` | 4 formatters |
|
|
| `roles` | `name` | 4 formatters |
|
|
| `permissions` | `name` | 4 formatters |
|
|
| `projects` | `name` | 4 formatters |
|
|
| `assets` | `name` | 4 formatters |
|
|
| `tour_pages` | `name` | 4 formatters |
|
|
| `transitions` | `name` | 4 formatters |
|
|
|
|
#### Usage Examples
|
|
|
|
```javascript
|
|
import dataFormatter from '@/helpers/dataFormatter';
|
|
|
|
// Display in table cell
|
|
const userName = dataFormatter.usersOneListFormatter(user);
|
|
// → "John"
|
|
|
|
// Display list of roles
|
|
const roleNames = dataFormatter.rolesManyListFormatter(roles);
|
|
// → ["Administrator", "Editor"]
|
|
|
|
// Format for edit dropdown
|
|
const roleOptions = dataFormatter.rolesManyListFormatterEdit(roles);
|
|
// → [{ id: "uuid-1", label: "Administrator" }, ...]
|
|
|
|
// Format date
|
|
const formattedDate = dataFormatter.dateFormatter(createdAt);
|
|
// → "2024-03-15"
|
|
|
|
// Transform object for DataGrid editing
|
|
const editData = dataFormatter.dataGridEditFormatter({
|
|
name: "Test",
|
|
project: { id: "uuid", name: "My Project" },
|
|
tags: [{ id: "1" }, { id: "2" }]
|
|
});
|
|
// → { name: "Test", project: "uuid", tags: ["1", "2"] }
|
|
```
|
|
|
|
#### relativeTimestamp Method
|
|
|
|
Formats dates as human-readable relative or absolute timestamps. Uses `dayjs` with the `relativeTime` plugin.
|
|
|
|
```javascript
|
|
import dayjs from 'dayjs';
|
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
|
dayjs.extend(relativeTime);
|
|
|
|
relativeTimestamp(date) {
|
|
if (!date) return '';
|
|
const d = dayjs(date);
|
|
const now = dayjs();
|
|
const diffMinutes = now.diff(d, 'minute');
|
|
const diffHours = now.diff(d, 'hour');
|
|
|
|
// Within last hour
|
|
if (diffMinutes < 60) {
|
|
return diffMinutes <= 1 ? 'Just now' : `${diffMinutes} min ago`;
|
|
}
|
|
|
|
// Within last 24 hours
|
|
if (diffHours < 24) {
|
|
return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
|
}
|
|
|
|
// Format as date + time
|
|
const isToday = d.isSame(now, 'day');
|
|
const isYesterday = d.isSame(now.subtract(1, 'day'), 'day');
|
|
const timeStr = d.format('HH:mm');
|
|
|
|
if (isToday) return `Today at ${timeStr}`;
|
|
if (isYesterday) return `Yesterday at ${timeStr}`;
|
|
return `${d.format('MMM D')} at ${timeStr}`;
|
|
}
|
|
```
|
|
|
|
**Output Examples:**
|
|
|
|
| Time Difference | Output |
|
|
|-----------------|--------|
|
|
| < 2 minutes | `"Just now"` |
|
|
| 5 minutes ago | `"5 min ago"` |
|
|
| 2 hours ago | `"2 hours ago"` |
|
|
| Same day | `"Today at 14:30"` |
|
|
| Yesterday | `"Yesterday at 09:15"` |
|
|
| Older dates | `"Apr 28 at 16:45"` |
|
|
|
|
**Usage:**
|
|
```javascript
|
|
import dataFormatter from '@/helpers/dataFormatter';
|
|
|
|
// In publish status buttons
|
|
dataFormatter.relativeTimestamp(lastPublishedAt);
|
|
// → "Just now" or "5 min ago" or "Today at 14:30"
|
|
|
|
// In constructor menu
|
|
dataFormatter.relativeTimestamp(page.updatedAt);
|
|
// → "2 hours ago"
|
|
```
|
|
|
|
---
|
|
|
|
#### dataGridEditFormatter Logic
|
|
|
|
Transforms nested objects/arrays to their IDs for DataGrid inline editing:
|
|
|
|
```javascript
|
|
dataGridEditFormatter(obj) {
|
|
return _.transform(obj, (result, value, key) => {
|
|
if (_.isArray(value)) {
|
|
result[key] = _.map(value, 'id'); // Array → array of IDs
|
|
} else if (_.isObject(value)) {
|
|
result[key] = value.id; // Object → ID string
|
|
} else {
|
|
result[key] = value; // Primitive → unchanged
|
|
}
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2. User Permissions (`userPermissions.ts`)
|
|
|
|
**Purpose:** Check if current user has required permission(s) for RBAC.
|
|
|
|
**Lines:** 19 LOC
|
|
|
|
**Used By:** 48 files (layouts, pages, components, factories)
|
|
|
|
#### Function Signature
|
|
|
|
```typescript
|
|
export function hasPermission(
|
|
user: User,
|
|
permission_name: string | string[]
|
|
): boolean
|
|
```
|
|
|
|
#### Parameters
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `user` | Object | Current user with `app_role` and `custom_permissions` |
|
|
| `permission_name` | `string` or `string[]` | Permission(s) to check |
|
|
|
|
#### Logic
|
|
|
|
1. Returns `false` if user has no role
|
|
2. Returns `true` if no permission specified (permission is optional)
|
|
3. Combines permissions from:
|
|
- `user.custom_permissions` (direct user permissions)
|
|
- `user.app_role.permissions` (role-based permissions)
|
|
4. Administrator role has implicit access to all permissions
|
|
5. For array of permissions, returns `true` if user has **any** of them
|
|
|
|
#### Implementation
|
|
|
|
```typescript
|
|
export function hasPermission(user, permission_name: string | string[]) {
|
|
if (!user?.app_role?.name) return false;
|
|
if (!permission_name) return true;
|
|
|
|
// Combine custom + role permissions
|
|
const permissions = new Set<string>([
|
|
...(user?.custom_permissions ?? []).map((p) => p.name),
|
|
...(user?.app_role?.permissions ?? []).map((p) => p.name),
|
|
]);
|
|
|
|
if (typeof permission_name === 'string') {
|
|
return permissions.has(permission_name) || user.app_role.name === 'Administrator';
|
|
} else {
|
|
return permission_name.some((permission) => permissions.has(permission));
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Usage Examples
|
|
|
|
```typescript
|
|
import { hasPermission } from '@/helpers/userPermissions';
|
|
|
|
// Check single permission
|
|
if (hasPermission(currentUser, 'READ_USERS')) {
|
|
// Show users list
|
|
}
|
|
|
|
// Check multiple permissions (OR logic)
|
|
if (hasPermission(currentUser, ['CREATE_PROJECTS', 'UPDATE_PROJECTS'])) {
|
|
// Show project form
|
|
}
|
|
|
|
// In layout guard
|
|
<LayoutAuthenticated permission="READ_ASSETS">
|
|
{page}
|
|
</LayoutAuthenticated>
|
|
|
|
// Conditional rendering
|
|
{hasPermission(user, 'DELETE_ASSETS') && (
|
|
<DeleteButton onClick={handleDelete} />
|
|
)}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Notify State Handler (`notifyStateHandler.ts`)
|
|
|
|
**Purpose:** Manage Redux notification state for async action feedback.
|
|
|
|
**Lines:** 32 LOC
|
|
|
|
**Used By:** `createEntitySlice.ts` (affects all 13 entity slices)
|
|
|
|
#### Functions
|
|
|
|
| Function | Purpose | Notification Type |
|
|
|----------|---------|-------------------|
|
|
| `resetNotify(state)` | Clear notification state | - |
|
|
| `fulfilledNotify(state, msg)` | Show success message | `'success'` |
|
|
| `rejectNotify(state, action)` | Show error message | `'error'` |
|
|
|
|
#### State Shape
|
|
|
|
```typescript
|
|
interface NotificationState {
|
|
showNotification: boolean;
|
|
typeNotification: '' | 'success' | 'error' | 'warn';
|
|
textNotification: string;
|
|
}
|
|
```
|
|
|
|
#### Implementation
|
|
|
|
```typescript
|
|
// Clear notification
|
|
export const resetNotify = (state) => {
|
|
state.notify.showNotification = false;
|
|
state.notify.typeNotification = '';
|
|
state.notify.textNotification = '';
|
|
};
|
|
|
|
// Show success notification
|
|
export const fulfilledNotify = (state, msg) => {
|
|
state.notify.textNotification = msg;
|
|
state.notify.typeNotification = 'success';
|
|
state.notify.showNotification = true;
|
|
};
|
|
|
|
// Show error notification (handles various error formats)
|
|
export const rejectNotify = (state, action) => {
|
|
if (typeof action.payload === 'string') {
|
|
state.notify.textNotification = action.payload;
|
|
} else if (typeof action === 'object') {
|
|
// Handle validation errors with field-level messages
|
|
const obj = { ...action.payload?.errors };
|
|
delete obj['_errors'];
|
|
|
|
let msg = '';
|
|
for (const key in obj) {
|
|
msg += `${key}: ${obj[key]['_errors']}; \n `;
|
|
}
|
|
state.notify.textNotification = msg;
|
|
} else {
|
|
state.notify.textNotification = 'Network error';
|
|
}
|
|
|
|
state.notify.textNotification = state.notify.textNotification || 'Network error';
|
|
state.notify.typeNotification = 'error';
|
|
state.notify.showNotification = true;
|
|
};
|
|
```
|
|
|
|
#### Usage in createEntitySlice
|
|
|
|
```typescript
|
|
import { resetNotify, fulfilledNotify, rejectNotify } from '../helpers/notifyStateHandler';
|
|
|
|
// In extraReducers
|
|
builder.addCase(create.pending, (state) => {
|
|
resetNotify(state);
|
|
});
|
|
|
|
builder.addCase(create.fulfilled, (state) => {
|
|
fulfilledNotify(state, 'User has been created');
|
|
});
|
|
|
|
builder.addCase(create.rejected, (state, action) => {
|
|
rejectNotify(state, action);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Text Formatters (`textFormatters.ts`)
|
|
|
|
**Purpose:** Common text transformation utilities for UI display.
|
|
|
|
**Lines:** 53 LOC
|
|
|
|
#### Functions
|
|
|
|
| Function | Input | Output | Example |
|
|
|----------|-------|--------|---------|
|
|
| `singularize(str)` | Plural string | Singular string | `'roles'` → `'role'` |
|
|
| `capitalize(str)` | String | Capitalized string | `'hello'` → `'Hello'` |
|
|
| `snakeToTitle(str)` | snake_case | Title Case | `'tour_pages'` → `'Tour Pages'` |
|
|
| `camelToTitle(str)` | camelCase | Title Case | `'tourPages'` → `'Tour Pages'` |
|
|
|
|
#### Implementation
|
|
|
|
```typescript
|
|
/**
|
|
* Convert a plural title to singular by removing the last character.
|
|
*/
|
|
export function singularize(pluralTitle: string): string {
|
|
return pluralTitle.slice(0, -1);
|
|
}
|
|
|
|
/**
|
|
* Capitalize the first letter of a string.
|
|
*/
|
|
export function capitalize(str: string): string {
|
|
if (!str) return '';
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|
|
|
|
/**
|
|
* Convert snake_case to Title Case.
|
|
*/
|
|
export function snakeToTitle(str: string): string {
|
|
return str.split('_').map(capitalize).join(' ');
|
|
}
|
|
|
|
/**
|
|
* Convert camelCase to Title Case.
|
|
*/
|
|
export function camelToTitle(str: string): string {
|
|
return str
|
|
.replace(/([A-Z])/g, ' $1')
|
|
.trim()
|
|
.split(' ')
|
|
.map(capitalize)
|
|
.join(' ');
|
|
}
|
|
```
|
|
|
|
#### Usage Examples
|
|
|
|
```typescript
|
|
import { singularize, capitalize, snakeToTitle, camelToTitle } from '@/helpers/textFormatters';
|
|
|
|
// Entity name transformations
|
|
singularize('View roles'); // 'View role'
|
|
singularize('users'); // 'user'
|
|
|
|
// String capitalization
|
|
capitalize('hello world'); // 'Hello world'
|
|
|
|
// Case conversions
|
|
snakeToTitle('tour_pages'); // 'Tour Pages'
|
|
snakeToTitle('project_audio_tracks'); // 'Project Audio Tracks'
|
|
|
|
camelToTitle('tourPages'); // 'Tour Pages'
|
|
camelToTitle('projectAudioTracks'); // 'Project Audio Tracks'
|
|
```
|
|
|
|
---
|
|
|
|
### 5. Humanize (`humanize.ts`)
|
|
|
|
**Purpose:** Convert programmatic strings to human-readable format.
|
|
|
|
**Lines:** 12 LOC
|
|
|
|
#### Function
|
|
|
|
```typescript
|
|
export function humanize(str: string): string
|
|
```
|
|
|
|
#### Transformations
|
|
|
|
1. Trim leading/trailing spaces and underscores
|
|
2. Replace underscores and multiple spaces with single space
|
|
3. Capitalize first letter
|
|
|
|
#### Implementation
|
|
|
|
```typescript
|
|
export function humanize(str: string) {
|
|
if (!str) return '';
|
|
return str
|
|
.toString()
|
|
.replace(/^[\s_]+|[\s_]+$/g, '') // Trim spaces/underscores
|
|
.replace(/[_\s]+/g, ' ') // Replace _/spaces with single space
|
|
.replace(/^[a-z]/, function (m) { // Capitalize first letter
|
|
return m.toUpperCase();
|
|
});
|
|
}
|
|
```
|
|
|
|
#### Usage Examples
|
|
|
|
```typescript
|
|
import { humanize } from '@/helpers/humanize';
|
|
|
|
humanize('user_name'); // 'User name'
|
|
humanize('_hello_world_'); // 'Hello world'
|
|
humanize('SOME_CONSTANT'); // 'SOME CONSTANT'
|
|
humanize(''); // ''
|
|
```
|
|
|
|
---
|
|
|
|
### 6. File Saver (`fileSaver.ts`)
|
|
|
|
**Purpose:** Trigger file download in browser.
|
|
|
|
**Lines:** 6 LOC
|
|
|
|
**Dependencies:** `file-saver` library
|
|
|
|
#### Function
|
|
|
|
```typescript
|
|
export const saveFile = (
|
|
e: Event,
|
|
url: string,
|
|
name: string
|
|
): void
|
|
```
|
|
|
|
#### Parameters
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `e` | Event | Click event (stopped for propagation) |
|
|
| `url` | string | File URL to download |
|
|
| `name` | string | Downloaded filename |
|
|
|
|
#### Implementation
|
|
|
|
```typescript
|
|
import { saveAs } from 'file-saver';
|
|
|
|
export const saveFile = (e, url: string, name: string) => {
|
|
e.stopPropagation();
|
|
saveAs(url, name);
|
|
};
|
|
```
|
|
|
|
#### Usage Examples
|
|
|
|
```typescript
|
|
import { saveFile } from '@/helpers/fileSaver';
|
|
|
|
// In a component
|
|
<button onClick={(e) => saveFile(e, asset.cdn_url, asset.name)}>
|
|
Download
|
|
</button>
|
|
|
|
// In a table action
|
|
const handleDownload = (e, file) => {
|
|
saveFile(e, file.publicUrl, file.originalName);
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Usage Patterns
|
|
|
|
### Permission Guard in Layout
|
|
|
|
```typescript
|
|
// layouts/Authenticated.tsx
|
|
import { hasPermission } from '../helpers/userPermissions';
|
|
|
|
if (!hasPermission(currentUser, permission)) {
|
|
router.push('/403');
|
|
return null;
|
|
}
|
|
```
|
|
|
|
### Data Formatting in Tables
|
|
|
|
```typescript
|
|
// In column configuration
|
|
import dataFormatter from '@/helpers/dataFormatter';
|
|
|
|
const columns = [
|
|
{
|
|
field: 'createdAt',
|
|
valueFormatter: ({ value }) => dataFormatter.dateTimeFormatter(value),
|
|
},
|
|
{
|
|
field: 'project',
|
|
valueFormatter: ({ value }) => dataFormatter.projectsOneListFormatter(value),
|
|
},
|
|
{
|
|
field: 'isActive',
|
|
valueFormatter: ({ value }) => dataFormatter.booleanFormatter(value),
|
|
},
|
|
];
|
|
```
|
|
|
|
### Notification Handling in Redux
|
|
|
|
```typescript
|
|
// In entity slice
|
|
import { resetNotify, fulfilledNotify, rejectNotify } from '../helpers/notifyStateHandler';
|
|
|
|
builder.addCase(deleteItem.pending, (state) => {
|
|
state.loading = true;
|
|
resetNotify(state);
|
|
});
|
|
|
|
builder.addCase(deleteItem.fulfilled, (state) => {
|
|
state.loading = false;
|
|
fulfilledNotify(state, 'Item has been deleted');
|
|
});
|
|
|
|
builder.addCase(deleteItem.rejected, (state, action) => {
|
|
state.loading = false;
|
|
rejectNotify(state, action);
|
|
});
|
|
```
|
|
|
|
### Text Transformation in UI
|
|
|
|
```typescript
|
|
import { snakeToTitle, singularize } from '@/helpers/textFormatters';
|
|
|
|
// Generate page title from entity name
|
|
const pageTitle = snakeToTitle(entityName);
|
|
// 'tour_pages' → 'Tour Pages'
|
|
|
|
// Generate singular form for messages
|
|
const message = `${singularize(entityName)} created`;
|
|
// 'Users' → 'User created'
|
|
```
|
|
|
|
---
|
|
|
|
## Helper Dependencies
|
|
|
|
```
|
|
dataFormatter.js
|
|
├── dayjs (date formatting)
|
|
└── lodash (object transformation)
|
|
|
|
fileSaver.ts
|
|
└── file-saver (browser download)
|
|
|
|
userPermissions.ts
|
|
├── Used by: layouts/Authenticated.tsx
|
|
├── Used by: factories/createListPage.tsx
|
|
├── Used by: components/DataGrid/configBuilderFactory.tsx
|
|
└── Used by: 48+ component files
|
|
|
|
notifyStateHandler.ts
|
|
└── Used by: stores/createEntitySlice.ts
|
|
└── Affects: All 13 entity slices
|
|
```
|
|
|
|
---
|
|
|
|
## File Inventory
|
|
|
|
| File | LOC | Category | Exports | Used By |
|
|
|------|-----|----------|---------|---------|
|
|
| `dataFormatter.js` | ~210 | Data | 1 default object | 30+ files |
|
|
| `userPermissions.ts` | 19 | Auth | `hasPermission` | 48 files |
|
|
| `notifyStateHandler.ts` | 32 | Redux | 3 functions | createEntitySlice |
|
|
| `textFormatters.ts` | 53 | Text | 4 functions | UI components |
|
|
| `humanize.ts` | 12 | Text | `humanize` | UI components |
|
|
| `fileSaver.ts` | 6 | File | `saveFile` | Download buttons |
|
|
| **Total** | **~332** | | **11 exports** | |
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Type-Safe Permission Checks
|
|
|
|
```typescript
|
|
// Good - explicit permission constant
|
|
import { Permission } from '@/types/permissions';
|
|
hasPermission(user, Permission.READ_USERS);
|
|
|
|
// Avoid - magic strings
|
|
hasPermission(user, 'READ_USERS');
|
|
```
|
|
|
|
### 2. Handle Empty Values in Formatters
|
|
|
|
```typescript
|
|
// dataFormatter handles null/undefined
|
|
const name = dataFormatter.usersOneListFormatter(null);
|
|
// → '' (empty string, not error)
|
|
```
|
|
|
|
### 3. Use Appropriate Formatter Type
|
|
|
|
```typescript
|
|
// For display (table cells, lists)
|
|
dataFormatter.rolesOneListFormatter(role); // → "Administrator"
|
|
|
|
// For edit forms (dropdowns)
|
|
dataFormatter.rolesOneListFormatterEdit(role); // → { id: "...", label: "Administrator" }
|
|
```
|
|
|
|
### 4. Consistent Notification Messages
|
|
|
|
```typescript
|
|
// In createEntitySlice
|
|
fulfilledNotify(state, `${displayName} has been created`);
|
|
fulfilledNotify(state, `${displayName} has been updated`);
|
|
fulfilledNotify(state, `${displayName} has been deleted`);
|
|
```
|
|
|
|
---
|
|
|
|
## Adding New Helpers
|
|
|
|
### Adding Entity Formatter
|
|
|
|
```javascript
|
|
// In dataFormatter.js
|
|
widgetsManyListFormatter(val) {
|
|
if (!val || !val.length) return [];
|
|
return val.map((item) => item.name);
|
|
},
|
|
widgetsOneListFormatter(val) {
|
|
if (!val) return '';
|
|
return val.name;
|
|
},
|
|
widgetsManyListFormatterEdit(val) {
|
|
if (!val || !val.length) return [];
|
|
return val.map((item) => {
|
|
return { id: item.id, label: item.name };
|
|
});
|
|
},
|
|
widgetsOneListFormatterEdit(val) {
|
|
if (!val) return '';
|
|
return { label: val.name, id: val.id };
|
|
},
|
|
```
|
|
|
|
### Adding New Helper File
|
|
|
|
```typescript
|
|
// helpers/newHelper.ts
|
|
/**
|
|
* Helper description
|
|
*/
|
|
|
|
export function helperFunction(input: InputType): OutputType {
|
|
// Implementation
|
|
}
|
|
|
|
// No index.ts - import directly from file
|
|
import { helperFunction } from '@/helpers/newHelper';
|
|
```
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- [factories-module.md](./factories-module.md) - Uses hasPermission in createListPage
|
|
- [stores-module.md](./stores-module.md) - Uses notifyStateHandler in createEntitySlice
|
|
- [types-module.md](./types-module.md) - Permission enum types
|
|
- [components-module.md](./components-module.md) - Uses dataFormatter in tables
|