@object-ui/plugin-detail 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.turbo/turbo-build.log +45 -8
  2. package/CHANGELOG.md +9 -0
  3. package/dist/AddressField-C07oUOY6.js +96 -0
  4. package/dist/AutoNumberField-BxnFqllo.js +8 -0
  5. package/dist/AvatarField-VThNABzo.js +82 -0
  6. package/dist/BooleanField-CGHKBzAi.js +37 -0
  7. package/dist/CodeField-Co_muhRR.js +21 -0
  8. package/dist/ColorField-DLid_tFz.js +42 -0
  9. package/dist/CurrencyField-Bw-LqANM.js +43 -0
  10. package/dist/DateField-BNHAzMB2.js +21 -0
  11. package/dist/DateTimeField-DjAyn_DQ.js +28 -0
  12. package/dist/EmailField-xoNcSppb.js +31 -0
  13. package/dist/FileField-DbNJwjU2.js +133 -0
  14. package/dist/FormulaField-CJkkwIK8.js +9 -0
  15. package/dist/GeolocationField-C1AnS6VV.js +123 -0
  16. package/dist/GridField-DATAHIKf.js +30 -0
  17. package/dist/ImageField-CEKJpyJp.js +90 -0
  18. package/dist/LocationField-jDWXjlpx.js +31 -0
  19. package/dist/LookupField-DQ08L9UQ.js +96 -0
  20. package/dist/MasterDetailField-Dbk529Ea.js +108 -0
  21. package/dist/NumberField-BVroN9aV.js +26 -0
  22. package/dist/ObjectField-CT3l_IHW.js +48 -0
  23. package/dist/PasswordField-DweVLEE0.js +38 -0
  24. package/dist/PercentField-ZpWUK97K.js +63 -0
  25. package/dist/PhoneField-mw-9fqZ_.js +31 -0
  26. package/dist/QRCodeField-Cbb9ck59.js +77 -0
  27. package/dist/RatingField-CSqgLS6t.js +47 -0
  28. package/dist/RichTextField-BpfBOd99.js +38 -0
  29. package/dist/SelectField-B9Ei-5jl.js +26 -0
  30. package/dist/SignatureField-DgGpHnQ8.js +85 -0
  31. package/dist/SliderField-C6HvOHd8.js +30 -0
  32. package/dist/SummaryField-ugYPYxjP.js +9 -0
  33. package/dist/TextAreaField-BK3RgzY3.js +39 -0
  34. package/dist/TextField-Bvzx3atT.js +32 -0
  35. package/dist/TimeField-Cuz9-Uai.js +21 -0
  36. package/dist/UrlField-B6XHTV73.js +33 -0
  37. package/dist/UserField-ooTul2d6.js +49 -0
  38. package/dist/VectorField-CKg9jdGa.js +25 -0
  39. package/dist/index-CnlyRfY_.js +59461 -0
  40. package/dist/index.js +30 -55026
  41. package/dist/index.umd.cjs +41 -30
  42. package/dist/plugin-detail.css +1 -1
  43. package/dist/src/ActivityTimeline.d.ts +20 -0
  44. package/dist/src/ActivityTimeline.d.ts.map +1 -0
  45. package/dist/src/CommentAttachment.d.ts +25 -0
  46. package/dist/src/CommentAttachment.d.ts.map +1 -0
  47. package/dist/src/CommentInput.d.ts +24 -0
  48. package/dist/src/CommentInput.d.ts.map +1 -0
  49. package/dist/src/DetailSection.d.ts +6 -0
  50. package/dist/src/DetailSection.d.ts.map +1 -1
  51. package/dist/src/DetailView.d.ts +4 -0
  52. package/dist/src/DetailView.d.ts.map +1 -1
  53. package/dist/src/DetailView.stories.d.ts +8 -0
  54. package/dist/src/DetailView.stories.d.ts.map +1 -1
  55. package/dist/src/DiffView.d.ts +24 -0
  56. package/dist/src/DiffView.d.ts.map +1 -0
  57. package/dist/src/FieldChangeItem.d.ts +21 -0
  58. package/dist/src/FieldChangeItem.d.ts.map +1 -0
  59. package/dist/src/InlineCreateRelated.d.ts +32 -0
  60. package/dist/src/InlineCreateRelated.d.ts.map +1 -0
  61. package/dist/src/MentionAutocomplete.d.ts +43 -0
  62. package/dist/src/MentionAutocomplete.d.ts.map +1 -0
  63. package/dist/src/PointInTimeRestore.d.ts +28 -0
  64. package/dist/src/PointInTimeRestore.d.ts.map +1 -0
  65. package/dist/src/ReactionPicker.d.ts +25 -0
  66. package/dist/src/ReactionPicker.d.ts.map +1 -0
  67. package/dist/src/RecordActivityTimeline.d.ts +49 -0
  68. package/dist/src/RecordActivityTimeline.d.ts.map +1 -0
  69. package/dist/src/RecordChatterPanel.d.ts +48 -0
  70. package/dist/src/RecordChatterPanel.d.ts.map +1 -0
  71. package/dist/src/RecordComments.d.ts +20 -0
  72. package/dist/src/RecordComments.d.ts.map +1 -0
  73. package/dist/src/RecordNavigationEnhanced.d.ts +18 -0
  74. package/dist/src/RecordNavigationEnhanced.d.ts.map +1 -0
  75. package/dist/src/RelatedList.d.ts +4 -0
  76. package/dist/src/RelatedList.d.ts.map +1 -1
  77. package/dist/src/RelationshipGraph.d.ts +23 -0
  78. package/dist/src/RelationshipGraph.d.ts.map +1 -0
  79. package/dist/src/RichTextCommentInput.d.ts +24 -0
  80. package/dist/src/RichTextCommentInput.d.ts.map +1 -0
  81. package/dist/src/SubscriptionToggle.d.ts +22 -0
  82. package/dist/src/SubscriptionToggle.d.ts.map +1 -0
  83. package/dist/src/ThreadedReplies.d.ts +26 -0
  84. package/dist/src/ThreadedReplies.d.ts.map +1 -0
  85. package/dist/src/autoLayout.d.ts +34 -0
  86. package/dist/src/autoLayout.d.ts.map +1 -0
  87. package/dist/src/index.d.ts +36 -0
  88. package/dist/src/index.d.ts.map +1 -1
  89. package/dist/src/useDetailTranslation.d.ts +34 -0
  90. package/dist/src/useDetailTranslation.d.ts.map +1 -0
  91. package/package.json +8 -7
  92. package/src/ActivityTimeline.tsx +184 -0
  93. package/src/CommentAttachment.tsx +192 -0
  94. package/src/CommentInput.tsx +81 -0
  95. package/src/DetailSection.tsx +74 -9
  96. package/src/DetailView.stories.tsx +76 -0
  97. package/src/DetailView.tsx +270 -27
  98. package/src/DiffView.tsx +231 -0
  99. package/src/FieldChangeItem.tsx +46 -0
  100. package/src/InlineCreateRelated.tsx +291 -0
  101. package/src/MentionAutocomplete.tsx +123 -0
  102. package/src/PointInTimeRestore.tsx +261 -0
  103. package/src/ReactionPicker.tsx +106 -0
  104. package/src/RecordActivityTimeline.tsx +429 -0
  105. package/src/RecordChatterPanel.tsx +202 -0
  106. package/src/RecordComments.tsx +215 -0
  107. package/src/RecordNavigationEnhanced.tsx +211 -0
  108. package/src/RelatedList.tsx +37 -8
  109. package/src/RelationshipGraph.tsx +286 -0
  110. package/src/RichTextCommentInput.tsx +348 -0
  111. package/src/SubscriptionToggle.tsx +60 -0
  112. package/src/ThreadedReplies.tsx +161 -0
  113. package/src/__tests__/ActivityTimeline.test.tsx +119 -0
  114. package/src/__tests__/ActivityTimelineFiltering.test.tsx +143 -0
  115. package/src/__tests__/CommentInput.test.tsx +57 -0
  116. package/src/__tests__/DetailSection.test.tsx +320 -0
  117. package/src/__tests__/DetailView.test.tsx +415 -1
  118. package/src/__tests__/FieldChangeItem.test.tsx +119 -0
  119. package/src/__tests__/MentionAutocomplete.test.tsx +97 -0
  120. package/src/__tests__/ReactionPicker.test.tsx +113 -0
  121. package/src/__tests__/RecordActivityTimeline.test.tsx +395 -0
  122. package/src/__tests__/RecordChatterPanel.test.tsx +227 -0
  123. package/src/__tests__/RecordComments.test.tsx +96 -0
  124. package/src/__tests__/RecordCommentsPinSearch.test.tsx +133 -0
  125. package/src/__tests__/RelatedList.test.tsx +66 -0
  126. package/src/__tests__/SubscriptionToggle.test.tsx +84 -0
  127. package/src/__tests__/ThreadedReplies.test.tsx +212 -0
  128. package/src/__tests__/autoLayout.test.ts +184 -0
  129. package/src/__tests__/phase12-features.test.tsx +583 -0
  130. package/src/autoLayout.ts +111 -0
  131. package/src/index.tsx +46 -0
  132. package/src/useDetailTranslation.ts +103 -0
@@ -9,6 +9,7 @@
9
9
  import * as React from 'react';
10
10
  import {
11
11
  cn,
12
+ Badge,
12
13
  Button,
13
14
  Skeleton,
14
15
  DropdownMenu,
@@ -32,12 +33,19 @@ import {
32
33
  History,
33
34
  Star,
34
35
  StarOff,
36
+ Check,
37
+ ChevronLeft,
38
+ ChevronRight,
35
39
  } from 'lucide-react';
36
40
  import { DetailSection } from './DetailSection';
37
41
  import { DetailTabs } from './DetailTabs';
38
42
  import { RelatedList } from './RelatedList';
43
+ import { RecordComments } from './RecordComments';
44
+ import { ActivityTimeline } from './ActivityTimeline';
39
45
  import { SchemaRenderer } from '@object-ui/react';
46
+ import { buildExpandFields } from '@object-ui/core';
40
47
  import type { DetailViewSchema, DataSource } from '@object-ui/types';
48
+ import { useDetailTranslation } from './useDetailTranslation';
41
49
 
42
50
  export interface DetailViewProps {
43
51
  schema: DetailViewSchema;
@@ -46,6 +54,10 @@ export interface DetailViewProps {
46
54
  onEdit?: () => void;
47
55
  onDelete?: () => void;
48
56
  onBack?: () => void;
57
+ /** Enable inline editing toggle for detail fields */
58
+ inlineEdit?: boolean;
59
+ /** Callback when a field value is saved inline */
60
+ onFieldSave?: (field: string, value: any, record: any) => void | Promise<void>;
49
61
  }
50
62
 
51
63
  export const DetailView: React.FC<DetailViewProps> = ({
@@ -55,13 +67,21 @@ export const DetailView: React.FC<DetailViewProps> = ({
55
67
  onEdit,
56
68
  onDelete,
57
69
  onBack,
70
+ inlineEdit = false,
71
+ onFieldSave,
58
72
  }) => {
59
73
  const [data, setData] = React.useState<any>(schema.data);
60
74
  const [loading, setLoading] = React.useState(!schema.data && !!((schema.api && schema.resourceId) || (dataSource && schema.objectName && schema.resourceId)));
61
75
  const [isFavorite, setIsFavorite] = React.useState(false);
76
+ const [isInlineEditing, setIsInlineEditing] = React.useState(false);
77
+ const [editedValues, setEditedValues] = React.useState<Record<string, any>>({});
78
+ const [objectSchema, setObjectSchema] = React.useState<any>(null);
79
+ const { t } = useDetailTranslation();
62
80
 
63
- // Fetch data if API or DataSource provided
81
+ // Fetch objectSchema + data with $expand when DataSource is provided
64
82
  React.useEffect(() => {
83
+ let isMounted = true;
84
+
65
85
  // If inline data provided, use it
66
86
  if (schema.data) {
67
87
  setData(schema.data);
@@ -71,26 +91,86 @@ export const DetailView: React.FC<DetailViewProps> = ({
71
91
 
72
92
  if (dataSource && schema.objectName && schema.resourceId) {
73
93
  setLoading(true);
74
- dataSource.findOne(schema.objectName, schema.resourceId).then((result) => {
75
- setData(result);
76
- setLoading(false);
94
+ // Clear stale state when navigating between objects/records
95
+ setObjectSchema(null);
96
+ setData(null);
97
+ const objectName = schema.objectName;
98
+ const resourceId = schema.resourceId;
99
+ const prefix = `${objectName}-`;
100
+
101
+ // Collect all visible fields from sections and top-level fields
102
+ const allFields = [
103
+ ...(schema.sections?.flatMap(s => s.fields) || []),
104
+ ...(schema.fields || []),
105
+ ];
106
+
107
+ // Load objectSchema first, then fetch data with $expand
108
+ const schemaPromise = dataSource.getObjectSchema
109
+ ? dataSource.getObjectSchema(objectName).catch(() => null)
110
+ : Promise.resolve(null);
111
+
112
+ schemaPromise.then((resolvedSchema) => {
113
+ if (!isMounted) return;
114
+ setObjectSchema(resolvedSchema);
115
+
116
+ // Compute $expand from objectSchema
117
+ const expandFields = buildExpandFields(resolvedSchema?.fields, allFields);
118
+ const params = expandFields.length > 0 ? { $expand: expandFields } : undefined;
119
+
120
+ const findOnePromise = params
121
+ ? dataSource.findOne(objectName, resourceId, params)
122
+ : dataSource.findOne(objectName, resourceId);
123
+
124
+ return findOnePromise.then((result) => {
125
+ if (!isMounted) return;
126
+ if (result) {
127
+ setData(result);
128
+ setLoading(false);
129
+ return;
130
+ }
131
+ // Fallback: try alternate ID format for backward compatibility
132
+ const resIdStr = String(resourceId);
133
+ const altId = resIdStr.startsWith(prefix)
134
+ ? resIdStr.slice(prefix.length) // strip prefix
135
+ : `${prefix}${resIdStr}`; // prepend prefix
136
+ return (params
137
+ ? dataSource.findOne(objectName, altId, params)
138
+ : dataSource.findOne(objectName, altId)
139
+ ).then((fallbackResult) => {
140
+ if (isMounted) {
141
+ setData(fallbackResult);
142
+ setLoading(false);
143
+ }
144
+ }).catch(() => {
145
+ if (isMounted) {
146
+ setData(null);
147
+ setLoading(false);
148
+ }
149
+ });
150
+ });
77
151
  }).catch((err) => {
78
- console.error('Failed to fetch detail data:', err);
79
- setLoading(false);
152
+ if (isMounted) {
153
+ console.error('Failed to fetch detail data:', err);
154
+ setLoading(false);
155
+ }
80
156
  });
81
157
  } else if (schema.api && schema.resourceId) {
82
158
  setLoading(true);
83
159
  fetch(`${schema.api}/${schema.resourceId}`)
84
160
  .then(res => res.json())
85
161
  .then(result => {
86
- setData(result?.data || result);
162
+ if (isMounted) {
163
+ setData(result?.data || result);
164
+ }
87
165
  })
88
166
  .catch(err => {
89
167
  console.error('Failed to fetch detail data:', err);
90
168
  })
91
- .finally(() => setLoading(false));
169
+ .finally(() => { if (isMounted) setLoading(false); });
92
170
  }
93
- }, [schema.api, schema.resourceId]);
171
+
172
+ return () => { isMounted = false; };
173
+ }, [schema.api, schema.resourceId, schema.objectName, dataSource, schema.sections, schema.fields]);
94
174
 
95
175
  const handleBack = React.useCallback(() => {
96
176
  if (onBack) {
@@ -121,7 +201,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
121
201
  }, [onEdit, schema]);
122
202
 
123
203
  const handleDelete = React.useCallback(() => {
124
- const confirmMessage = schema.deleteConfirmation || 'Are you sure you want to delete this record?';
204
+ const confirmMessage = schema.deleteConfirmation || t('detail.deleteConfirmation');
125
205
  // Use window.confirm as fallback — the ActionProvider's onConfirm handler
126
206
  // will intercept this if wired up via the action system.
127
207
  if (window.confirm(confirmMessage)) {
@@ -137,7 +217,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
137
217
  // Share functionality - could trigger share dialog or copy link
138
218
  if (navigator.share && schema.objectName && schema.resourceId) {
139
219
  navigator.share({
140
- title: schema.title || 'Record Details',
220
+ title: schema.title || t('detail.details'),
141
221
  text: `${schema.objectName} #${schema.resourceId}`,
142
222
  url: window.location.href,
143
223
  }).catch((err) => console.log('Share failed:', err));
@@ -168,6 +248,46 @@ export const DetailView: React.FC<DetailViewProps> = ({
168
248
  setIsFavorite(!isFavorite);
169
249
  }, [isFavorite]);
170
250
 
251
+ const handleInlineEditToggle = React.useCallback(() => {
252
+ if (isInlineEditing) {
253
+ // Save changes
254
+ const changes = Object.entries(editedValues);
255
+ if (changes.length > 0) {
256
+ const updatedData = { ...data, ...editedValues };
257
+ setData(updatedData);
258
+ changes.forEach(([field, value]) => {
259
+ onFieldSave?.(field, value, updatedData);
260
+ });
261
+ }
262
+ setEditedValues({});
263
+ }
264
+ setIsInlineEditing(!isInlineEditing);
265
+ }, [isInlineEditing, editedValues, data, onFieldSave]);
266
+
267
+ const handleInlineFieldChange = React.useCallback((field: string, value: any) => {
268
+ setEditedValues(prev => ({ ...prev, [field]: value }));
269
+ }, []);
270
+
271
+ // Keyboard shortcuts for prev/next record navigation (← / →)
272
+ React.useEffect(() => {
273
+ if (!schema.recordNavigation) return;
274
+ const nav = schema.recordNavigation;
275
+ const handler = (e: KeyboardEvent) => {
276
+ // Skip when focus is inside an input, textarea, or contenteditable
277
+ const tag = (e.target as HTMLElement)?.tagName;
278
+ if (tag === 'INPUT' || tag === 'TEXTAREA' || (e.target as HTMLElement)?.isContentEditable) return;
279
+ if (e.key === 'ArrowLeft' && nav.currentIndex > 0) {
280
+ e.preventDefault();
281
+ nav.onNavigate(nav.recordIds[nav.currentIndex - 1]);
282
+ } else if (e.key === 'ArrowRight' && nav.currentIndex < nav.recordIds.length - 1) {
283
+ e.preventDefault();
284
+ nav.onNavigate(nav.recordIds[nav.currentIndex + 1]);
285
+ }
286
+ };
287
+ document.addEventListener('keydown', handler);
288
+ return () => document.removeEventListener('keydown', handler);
289
+ }, [schema.recordNavigation]);
290
+
171
291
  if (loading || schema.loading) {
172
292
  return (
173
293
  <div className={cn('space-y-4', className)}>
@@ -178,6 +298,23 @@ export const DetailView: React.FC<DetailViewProps> = ({
178
298
  );
179
299
  }
180
300
 
301
+ if (!data && !schema.data) {
302
+ return (
303
+ <div className={cn('flex flex-col items-center justify-center py-16 text-center', className)}>
304
+ <p className="text-lg font-semibold">{t('detail.recordNotFound')}</p>
305
+ <p className="text-sm text-muted-foreground mt-1">
306
+ {t('detail.recordNotFoundDescription')}
307
+ </p>
308
+ {(schema.showBack ?? true) && (
309
+ <Button variant="outline" size="sm" onClick={handleBack} className="mt-4 gap-2">
310
+ <ArrowLeft className="h-4 w-4" />
311
+ {t('detail.goBack')}
312
+ </Button>
313
+ )}
314
+ </div>
315
+ );
316
+ }
317
+
181
318
  return (
182
319
  <TooltipProvider>
183
320
  <div className={cn('space-y-6', className)}>
@@ -191,12 +328,23 @@ export const DetailView: React.FC<DetailViewProps> = ({
191
328
  <ArrowLeft className="h-4 w-4" />
192
329
  </Button>
193
330
  </TooltipTrigger>
194
- <TooltipContent>Back</TooltipContent>
331
+ <TooltipContent>{t('detail.back')}</TooltipContent>
195
332
  </Tooltip>
196
333
  )}
197
334
  <div className="flex-1 min-w-0">
198
- <div className="flex items-center gap-2">
199
- <h1 className="text-xl sm:text-2xl font-bold truncate">{schema.title || 'Details'}</h1>
335
+ <div className="flex items-center gap-2 flex-wrap">
336
+ <h1 className="text-xl sm:text-2xl font-bold truncate">
337
+ {(schema.primaryField && data?.[schema.primaryField]) || schema.title || t('detail.details')}
338
+ </h1>
339
+ {schema.summaryFields?.map((fieldName) => {
340
+ const val = data?.[fieldName];
341
+ if (val === null || val === undefined || val === '') return null;
342
+ return (
343
+ <Badge key={fieldName} variant="secondary" className="text-xs" aria-label={`${fieldName}: ${val}`}>
344
+ {String(val)}
345
+ </Badge>
346
+ );
347
+ })}
200
348
  <Tooltip>
201
349
  <TooltipTrigger asChild>
202
350
  <Button
@@ -213,7 +361,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
213
361
  </Button>
214
362
  </TooltipTrigger>
215
363
  <TooltipContent>
216
- {isFavorite ? 'Remove from favorites' : 'Add to favorites'}
364
+ {isFavorite ? t('detail.removeFromFavorites') : t('detail.addToFavorites')}
217
365
  </TooltipContent>
218
366
  </Tooltip>
219
367
  </div>
@@ -228,10 +376,86 @@ export const DetailView: React.FC<DetailViewProps> = ({
228
376
  </div>
229
377
 
230
378
  <div className="flex flex-wrap items-center gap-1.5 shrink-0 w-full sm:w-auto">
379
+ {/* Prev/Next Record Navigation */}
380
+ {schema.recordNavigation && (
381
+ <div className="flex items-center gap-1 mr-2">
382
+ <Tooltip>
383
+ <TooltipTrigger asChild>
384
+ <Button
385
+ variant="outline"
386
+ size="icon"
387
+ className="h-8 w-8"
388
+ disabled={schema.recordNavigation.currentIndex <= 0}
389
+ onClick={() => {
390
+ const nav = schema.recordNavigation!;
391
+ if (nav.currentIndex > 0) {
392
+ nav.onNavigate(nav.recordIds[nav.currentIndex - 1]);
393
+ }
394
+ }}
395
+ >
396
+ <ChevronLeft className="h-4 w-4" />
397
+ </Button>
398
+ </TooltipTrigger>
399
+ <TooltipContent>{t('detail.previousRecord')}</TooltipContent>
400
+ </Tooltip>
401
+ <span className="text-xs text-muted-foreground whitespace-nowrap px-1">
402
+ {t('detail.recordOf', { current: schema.recordNavigation.currentIndex + 1, total: schema.recordNavigation.recordIds.length })}
403
+ </span>
404
+ <Tooltip>
405
+ <TooltipTrigger asChild>
406
+ <Button
407
+ variant="outline"
408
+ size="icon"
409
+ className="h-8 w-8"
410
+ disabled={schema.recordNavigation.currentIndex >= schema.recordNavigation.recordIds.length - 1}
411
+ onClick={() => {
412
+ const nav = schema.recordNavigation!;
413
+ if (nav.currentIndex < nav.recordIds.length - 1) {
414
+ nav.onNavigate(nav.recordIds[nav.currentIndex + 1]);
415
+ }
416
+ }}
417
+ >
418
+ <ChevronRight className="h-4 w-4" />
419
+ </Button>
420
+ </TooltipTrigger>
421
+ <TooltipContent>{t('detail.nextRecord')}</TooltipContent>
422
+ </Tooltip>
423
+ </div>
424
+ )}
425
+
231
426
  {schema.actions?.map((action, index) => (
232
427
  <SchemaRenderer key={index} schema={action} data={data} />
233
428
  ))}
234
429
 
430
+ {/* Inline Edit Toggle */}
431
+ {inlineEdit && (
432
+ <Tooltip>
433
+ <TooltipTrigger asChild>
434
+ <Button
435
+ variant={isInlineEditing ? 'default' : 'outline'}
436
+ size="sm"
437
+ onClick={handleInlineEditToggle}
438
+ className="gap-2"
439
+ >
440
+ {isInlineEditing ? (
441
+ <>
442
+ <Check className="h-4 w-4" />
443
+ <span className="hidden sm:inline">{t('detail.save')}</span>
444
+ </>
445
+ ) : (
446
+ <>
447
+ <Edit className="h-4 w-4" />
448
+ <span className="hidden sm:inline">{t('detail.editInline')}</span>
449
+ </>
450
+ )}
451
+ </Button>
452
+ </TooltipTrigger>
453
+ <TooltipContent>
454
+ {isInlineEditing ? t('detail.saveChanges') : t('detail.editFieldsInline')}
455
+ </TooltipContent>
456
+ </Tooltip>
457
+ )}
458
+
235
459
  {/* Share Button */}
236
460
  <Tooltip>
237
461
  <TooltipTrigger asChild>
@@ -239,7 +463,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
239
463
  <Share2 className="h-4 w-4" />
240
464
  </Button>
241
465
  </TooltipTrigger>
242
- <TooltipContent>Share</TooltipContent>
466
+ <TooltipContent>{t('detail.share')}</TooltipContent>
243
467
  </Tooltip>
244
468
 
245
469
  {/* Edit Button */}
@@ -248,10 +472,10 @@ export const DetailView: React.FC<DetailViewProps> = ({
248
472
  <TooltipTrigger asChild>
249
473
  <Button variant="default" onClick={handleEdit} className="gap-2">
250
474
  <Edit className="h-4 w-4" />
251
- <span className="hidden sm:inline">Edit</span>
475
+ <span className="hidden sm:inline">{t('detail.edit')}</span>
252
476
  </Button>
253
477
  </TooltipTrigger>
254
- <TooltipContent>Edit record</TooltipContent>
478
+ <TooltipContent>{t('detail.editRecord')}</TooltipContent>
255
479
  </Tooltip>
256
480
  )}
257
481
 
@@ -265,20 +489,20 @@ export const DetailView: React.FC<DetailViewProps> = ({
265
489
  </Button>
266
490
  </DropdownMenuTrigger>
267
491
  </TooltipTrigger>
268
- <TooltipContent>More actions</TooltipContent>
492
+ <TooltipContent>{t('detail.moreActions')}</TooltipContent>
269
493
  </Tooltip>
270
494
  <DropdownMenuContent align="end" className="w-[calc(100vw-2rem)] sm:w-48 max-h-[60vh] overflow-y-auto">
271
495
  <DropdownMenuItem onClick={handleDuplicate}>
272
496
  <Copy className="h-4 w-4 mr-2" />
273
- Duplicate
497
+ {t('detail.duplicate')}
274
498
  </DropdownMenuItem>
275
499
  <DropdownMenuItem onClick={handleExport}>
276
500
  <Download className="h-4 w-4 mr-2" />
277
- Export
501
+ {t('detail.export')}
278
502
  </DropdownMenuItem>
279
503
  <DropdownMenuItem onClick={handleViewHistory}>
280
504
  <History className="h-4 w-4 mr-2" />
281
- View history
505
+ {t('detail.viewHistory')}
282
506
  </DropdownMenuItem>
283
507
  {schema.showDelete && (
284
508
  <>
@@ -288,7 +512,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
288
512
  className="text-destructive focus:text-destructive"
289
513
  >
290
514
  <Trash2 className="h-4 w-4 mr-2" />
291
- Delete
515
+ {t('detail.delete')}
292
516
  </DropdownMenuItem>
293
517
  </>
294
518
  )}
@@ -311,7 +535,10 @@ export const DetailView: React.FC<DetailViewProps> = ({
311
535
  <DetailSection
312
536
  key={index}
313
537
  section={section}
314
- data={data}
538
+ data={{ ...data, ...editedValues }}
539
+ objectSchema={objectSchema}
540
+ isEditing={isInlineEditing}
541
+ onFieldChange={handleInlineFieldChange}
315
542
  />
316
543
  ))}
317
544
  </div>
@@ -322,9 +549,12 @@ export const DetailView: React.FC<DetailViewProps> = ({
322
549
  <DetailSection
323
550
  section={{
324
551
  fields: schema.fields,
325
- columns: schema.columns || 2,
552
+ columns: schema.columns,
326
553
  }}
327
- data={data}
554
+ data={{ ...data, ...editedValues }}
555
+ objectSchema={objectSchema}
556
+ isEditing={isInlineEditing}
557
+ onFieldChange={handleInlineFieldChange}
328
558
  />
329
559
  )}
330
560
 
@@ -336,7 +566,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
336
566
  {/* Related Lists */}
337
567
  {schema.related && schema.related.length > 0 && (
338
568
  <div className="space-y-4">
339
- <h2 className="text-xl font-semibold">Related</h2>
569
+ <h2 className="text-xl font-semibold">{t('detail.related')}</h2>
340
570
  {schema.related.map((related, index) => (
341
571
  <RelatedList
342
572
  key={index}
@@ -351,6 +581,19 @@ export const DetailView: React.FC<DetailViewProps> = ({
351
581
  </div>
352
582
  )}
353
583
 
584
+ {/* Comments */}
585
+ {schema.comments && (
586
+ <RecordComments
587
+ comments={schema.comments}
588
+ onAddComment={schema.onAddComment}
589
+ />
590
+ )}
591
+
592
+ {/* Activity Timeline */}
593
+ {schema.activities && schema.activities.length > 0 && (
594
+ <ActivityTimeline activities={schema.activities} />
595
+ )}
596
+
354
597
  {/* Custom Footer */}
355
598
  {schema.footer && (
356
599
  <div>