feat: Support custom fields in calendar event detail panel (#15853)
## Overview This PR refactors the `CalendarEventDetails` component to dynamically display both standard and custom fields added via the metadata API, instead of using a hardcoded field list. ## Changes - Replaced hardcoded `fieldsToDisplay` array with dynamic field fetching using `useFieldListFieldMetadataItems` hook - Split fields into `standardFields` (maintaining original order) and `customFields` - Introduced `renderField` helper function to eliminate code duplication - Custom fields now automatically appear at the bottom of the detail panel after standard fields - Maintained exact field order and all existing functionality including participant response status display ## Technical Details - Uses existing `useFieldListFieldMetadataItems` pattern already established in the codebase - Standard field order explicitly defined: startsAt, endsAt, conferenceLink, location, description - Participant response status (Yes/Maybe/No) correctly positioned between first 2 and last 3 standard fields - All fields respect permissions and visibility settings from metadata ## Testing - ✅ Verified all standard fields display in correct order - ✅ Added custom field `mycustomfieldtest` via metadata API - displays correctly at bottom - ✅ Participant responses (Yes/Maybe/No) render correctly with avatars - ✅ Event title, creation date, and event chip display properly - ✅ Canceled event styling (strikethrough) works - ✅ No console errors or regressions detected - ✅ Read-only behavior maintained (calendar events sync from external sources) ## Screenshots <img width="1401" height="746" alt="CleanShot 2025-11-17 at 11 23 57" src="https://github.com/user-attachments/assets/3d967ec5-6d31-4fc3-b971-c73ea521c87d" /> ## Related This enables users to extend calendar events with custom metadata fields that will automatically display in the UI without code changes. --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
committed by
GitHub
parent
6fa75e1360
commit
f6cd51ba97
@@ -30,6 +30,18 @@ type ButtonProps = {}; // Component props suffix with 'Props'
|
||||
// ✅ Files and directories - kebab-case
|
||||
// user-profile.component.tsx
|
||||
// user-profile.styles.ts
|
||||
|
||||
// ❌ NEVER use abbreviations in variable names
|
||||
// Bad
|
||||
const users = data.map((u) => u.name);
|
||||
const field = items.find((f) => f.id === id);
|
||||
|
||||
// Good
|
||||
const users = data.map((user) => user.name);
|
||||
const field = items.find((item) => item.id === id);
|
||||
const fieldMetadata = inlineFields.find(
|
||||
(fieldMetadataItem) => fieldMetadataItem.name === fieldName,
|
||||
);
|
||||
```
|
||||
|
||||
## Import Organization
|
||||
@@ -59,11 +71,11 @@ const processUserData = (
|
||||
): ProcessedUser => {
|
||||
const processedUser = transformUserData(user);
|
||||
applyOptions(processedUser, options);
|
||||
|
||||
|
||||
if (callback) {
|
||||
callback(processedUser);
|
||||
}
|
||||
|
||||
|
||||
return processedUser;
|
||||
};
|
||||
```
|
||||
@@ -71,7 +83,7 @@ const processUserData = (
|
||||
## Comments
|
||||
```typescript
|
||||
// ✅ Use short-form comments, NOT JSDoc blocks
|
||||
// ✅ Explain business logic and non-obvious intentions
|
||||
// ✅ Explain business logic and non-obvious intentions (WHY, not WHAT)
|
||||
// Apply 15% discount for premium users with orders > $100
|
||||
const discount = isPremiumUser && orderTotal > 100 ? 0.15 : 0;
|
||||
|
||||
@@ -85,12 +97,55 @@ const calculateTotalPrice = (basePrice: number): number => {
|
||||
// Implementation
|
||||
};
|
||||
|
||||
// ❌ AVOID obvious comments that just describe what code does
|
||||
// Bad: Get all inline fields dynamically
|
||||
const { inlineFieldMetadataItems } = useFieldListFieldMetadataItems({...});
|
||||
|
||||
// Bad: Define standard fields in display order
|
||||
const standardFieldOrder = ['startsAt', 'endsAt', 'conferenceLink'];
|
||||
|
||||
// Bad: Split fields into standard and custom
|
||||
const standardFields = standardFieldOrder.map(...)
|
||||
|
||||
// ✅ GOOD: Only comment if explaining non-obvious business logic
|
||||
// Calendar events display standard fields first, then custom fields after participants
|
||||
// to maintain consistency with the legacy UI behavior
|
||||
const standardFields = standardFieldOrder.map(...)
|
||||
|
||||
// ❌ AVOID JSDoc blocks - use short comments instead
|
||||
/**
|
||||
* This style is NOT preferred in this codebase
|
||||
*/
|
||||
```
|
||||
|
||||
**Comment Guidelines:**
|
||||
- **DO** comment complex business rules or domain-specific logic
|
||||
- **DO** comment non-obvious algorithmic decisions
|
||||
- **DO** add TODOs for future improvements
|
||||
- **DON'T** comment obvious variable declarations or function calls
|
||||
- **DON'T** comment what is already clear from well-named variables/functions
|
||||
- **DON'T** add comments that just repeat what the code says
|
||||
|
||||
## Utility Helpers
|
||||
```typescript
|
||||
// ✅ Use existing utility helpers instead of manual checks
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isNonEmptyString, isNonEmptyArray } from '@sniptt/guards';
|
||||
|
||||
// ❌ Manual type guards
|
||||
const validItems = items.filter((item): item is Item => item !== undefined);
|
||||
const hasValue = value !== null && value !== undefined;
|
||||
|
||||
// ✅ Use utility helpers
|
||||
const validItems = items.filter(isDefined);
|
||||
const hasValue = isDefined(value);
|
||||
|
||||
// Other useful helpers:
|
||||
// - isDefined(value) - checks !== null && !== undefined
|
||||
// - isNonEmptyString(value) - checks string is defined and not empty
|
||||
// - isNonEmptyArray(value) - checks array is defined and has items
|
||||
```
|
||||
|
||||
## Security Patterns
|
||||
```typescript
|
||||
// ✅ CSV Export: Always apply security first, then formatting
|
||||
|
||||
Reference in New Issue
Block a user