@object-ui/plugin-detail 3.1.5 → 3.3.1
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.
- package/CHANGELOG.md +31 -0
- package/README.md +21 -1
- package/dist/AddressField-LgHnO2Lk.js +98 -0
- package/dist/AutoNumberField-xZCrU0eW.js +14 -0
- package/dist/{AvatarField-YGj51ozd.js → AvatarField-Dy2XGlPz.js} +16 -15
- package/dist/{BooleanField-CaA898Tk.js → BooleanField-C0Clfka5.js} +11 -10
- package/dist/CodeField-CHUa07B6.js +23 -0
- package/dist/ColorField-vxHqEhcS.js +38 -0
- package/dist/CurrencyField-DiWjYWDo.js +49 -0
- package/dist/DateField-DGaRPM4P.js +22 -0
- package/dist/DateTimeField-8QnpsI_h.js +30 -0
- package/dist/EmailField-CkVgMbpI.js +26 -0
- package/dist/FileField-5UPV7uek.js +149 -0
- package/dist/FormulaField-BUgt6-Pi.js +17 -0
- package/dist/GeolocationField-D9T_jgG6.js +118 -0
- package/dist/GridField-DE_HwiIN.js +49 -0
- package/dist/ImageField-Dswnqtzf.js +73 -0
- package/dist/LocationField-gjqbE6na.js +36 -0
- package/dist/LookupField-BcS3LRKc.js +901 -0
- package/dist/{MasterDetailField-I1A9oEGC.js → MasterDetailField-BF6_-X3A.js} +20 -19
- package/dist/NumberField-Dj2rYmrS.js +27 -0
- package/dist/ObjectField-BymIojwd.js +50 -0
- package/dist/{PasswordField-DBtluGJ1.js → PasswordField-ED_Xgqz-.js} +8 -7
- package/dist/PercentField-D-JKOxKC.js +61 -0
- package/dist/PhoneField-DSCaGYq7.js +26 -0
- package/dist/QRCodeField-CtcOUapi.js +73 -0
- package/dist/{RatingField-B_Mnr63i.js → RatingField-BDnyQFWy.js} +10 -9
- package/dist/RichTextField-CH6LVZQA.js +33 -0
- package/dist/SelectField-DE4dpkMV.js +36 -0
- package/dist/{SignatureField-CddhEK9u.js → SignatureField-B1wh3f5A.js} +18 -17
- package/dist/{SliderField-Df5hMzNc.js → SliderField-zoTCKh9n.js} +2 -1
- package/dist/SummaryField-BeBVT6VN.js +22 -0
- package/dist/TextAreaField-rfUGrRxh.js +37 -0
- package/dist/TextField-C_yM7ATQ.js +30 -0
- package/dist/TimeField-BcQmBZi9.js +22 -0
- package/dist/UrlField-BakaF6NI.js +31 -0
- package/dist/UserField-zS7y3eKb.js +76 -0
- package/dist/VectorField-CTZ4myDM.js +34 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1741 -1504
- package/dist/index.umd.cjs +43 -51
- package/dist/packages/plugin-detail/src/ActivityTimeline.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/CommentAttachment.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/CommentInput.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailSection.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailTabs.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailView.d.ts +47 -0
- package/dist/packages/plugin-detail/src/DetailView.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailView.stories.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DiffView.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/FieldChangeItem.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/HeaderHighlight.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/InlineCreateRelated.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/MentionAutocomplete.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/PointInTimeRestore.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/ReactionPicker.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordActivityTimeline.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordChatterPanel.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordComments.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordNavigationEnhanced.d.ts.map +1 -0
- package/dist/{src → packages/plugin-detail/src}/RelatedList.d.ts +8 -0
- package/dist/packages/plugin-detail/src/RelatedList.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RelationshipGraph.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RichTextCommentInput.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/SectionGroup.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/SubscriptionToggle.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/ThreadedReplies.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/autoLayout.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/index.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/useDetailTranslation.d.ts.map +1 -0
- package/dist/plugin-detail.css +1 -2
- package/dist/rolldown-runtime-DnwLefa7.js +23 -0
- package/dist/{src-CXr1-vVl.js → src-DyUKLvMN.js} +29788 -37711
- package/dist/useFieldTranslation-BRgjC1oq.js +9 -0
- package/package.json +34 -12
- package/.turbo/turbo-build.log +0 -61
- package/dist/AddressField-DBkEyMcG.js +0 -93
- package/dist/AutoNumberField-Baa191z-.js +0 -14
- package/dist/CodeField-BU51nl1L.js +0 -22
- package/dist/ColorField-Cnf6ZM7c.js +0 -37
- package/dist/CurrencyField-Wg-XOId2.js +0 -51
- package/dist/DateField-Cth1ky_m.js +0 -21
- package/dist/DateTimeField-B0m6FhHL.js +0 -32
- package/dist/EmailField-Do7qT_L_.js +0 -28
- package/dist/FileField-aRJAdbQb.js +0 -151
- package/dist/FormulaField-DTMkagFx.js +0 -14
- package/dist/GeolocationField-RqpHWTEv.js +0 -113
- package/dist/GridField-D4IH0cpo.js +0 -51
- package/dist/ImageField-BYCFajjr.js +0 -75
- package/dist/LocationField-Bi_ew9sd.js +0 -35
- package/dist/LookupField-BjwlDPtt.js +0 -902
- package/dist/NumberField-D_NucQlp.js +0 -26
- package/dist/ObjectField-CG-LaM65.js +0 -52
- package/dist/PercentField-B6sO_J3i.js +0 -63
- package/dist/PhoneField-CcQAWwR6.js +0 -28
- package/dist/QRCodeField-CEjWs-J5.js +0 -72
- package/dist/RichTextField-qOEJl5Ai.js +0 -32
- package/dist/SelectField-C8hWu3gm.js +0 -30
- package/dist/SummaryField-DgiFm-Cr.js +0 -19
- package/dist/TextAreaField-DuriTqsD.js +0 -36
- package/dist/TextField-CGNSl7RU.js +0 -29
- package/dist/TimeField-YO58ctFg.js +0 -21
- package/dist/UrlField-1-BMM1jn.js +0 -33
- package/dist/UserField-B6GqxP_S.js +0 -78
- package/dist/VectorField-BkEjbSt0.js +0 -36
- package/dist/src/ActivityTimeline.d.ts.map +0 -1
- package/dist/src/CommentAttachment.d.ts.map +0 -1
- package/dist/src/CommentInput.d.ts.map +0 -1
- package/dist/src/DetailSection.d.ts.map +0 -1
- package/dist/src/DetailTabs.d.ts.map +0 -1
- package/dist/src/DetailView.d.ts +0 -23
- package/dist/src/DetailView.d.ts.map +0 -1
- package/dist/src/DetailView.stories.d.ts.map +0 -1
- package/dist/src/DiffView.d.ts.map +0 -1
- package/dist/src/FieldChangeItem.d.ts.map +0 -1
- package/dist/src/HeaderHighlight.d.ts.map +0 -1
- package/dist/src/InlineCreateRelated.d.ts.map +0 -1
- package/dist/src/MentionAutocomplete.d.ts.map +0 -1
- package/dist/src/PointInTimeRestore.d.ts.map +0 -1
- package/dist/src/ReactionPicker.d.ts.map +0 -1
- package/dist/src/RecordActivityTimeline.d.ts.map +0 -1
- package/dist/src/RecordChatterPanel.d.ts.map +0 -1
- package/dist/src/RecordComments.d.ts.map +0 -1
- package/dist/src/RecordNavigationEnhanced.d.ts.map +0 -1
- package/dist/src/RelatedList.d.ts.map +0 -1
- package/dist/src/RelationshipGraph.d.ts.map +0 -1
- package/dist/src/RichTextCommentInput.d.ts.map +0 -1
- package/dist/src/SectionGroup.d.ts.map +0 -1
- package/dist/src/SubscriptionToggle.d.ts.map +0 -1
- package/dist/src/ThreadedReplies.d.ts.map +0 -1
- package/dist/src/autoLayout.d.ts.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/useDetailTranslation.d.ts.map +0 -1
- package/src/ActivityTimeline.tsx +0 -184
- package/src/CommentAttachment.tsx +0 -192
- package/src/CommentInput.tsx +0 -81
- package/src/DetailSection.tsx +0 -340
- package/src/DetailTabs.tsx +0 -73
- package/src/DetailView.stories.tsx +0 -334
- package/src/DetailView.tsx +0 -823
- package/src/DiffView.tsx +0 -231
- package/src/FieldChangeItem.tsx +0 -46
- package/src/HeaderHighlight.tsx +0 -88
- package/src/InlineCreateRelated.tsx +0 -291
- package/src/MentionAutocomplete.tsx +0 -123
- package/src/PointInTimeRestore.tsx +0 -261
- package/src/ReactionPicker.tsx +0 -106
- package/src/RecordActivityTimeline.tsx +0 -429
- package/src/RecordChatterPanel.tsx +0 -207
- package/src/RecordComments.tsx +0 -215
- package/src/RecordNavigationEnhanced.tsx +0 -211
- package/src/RelatedList.tsx +0 -413
- package/src/RelationshipGraph.tsx +0 -286
- package/src/RichTextCommentInput.tsx +0 -348
- package/src/SectionGroup.tsx +0 -101
- package/src/SubscriptionToggle.tsx +0 -60
- package/src/ThreadedReplies.tsx +0 -161
- package/src/__tests__/ActivityTimeline.test.tsx +0 -119
- package/src/__tests__/ActivityTimelineFiltering.test.tsx +0 -143
- package/src/__tests__/CommentInput.test.tsx +0 -57
- package/src/__tests__/DetailSection.test.tsx +0 -490
- package/src/__tests__/DetailView.test.tsx +0 -694
- package/src/__tests__/FieldChangeItem.test.tsx +0 -119
- package/src/__tests__/HeaderHighlight.test.tsx +0 -213
- package/src/__tests__/MentionAutocomplete.test.tsx +0 -97
- package/src/__tests__/ReactionPicker.test.tsx +0 -113
- package/src/__tests__/RecordActivityTimeline.test.tsx +0 -395
- package/src/__tests__/RecordChatterPanel.test.tsx +0 -265
- package/src/__tests__/RecordComments.test.tsx +0 -96
- package/src/__tests__/RecordCommentsPinSearch.test.tsx +0 -133
- package/src/__tests__/RelatedList.test.tsx +0 -160
- package/src/__tests__/SectionGroup.test.tsx +0 -101
- package/src/__tests__/SubscriptionToggle.test.tsx +0 -84
- package/src/__tests__/ThreadedReplies.test.tsx +0 -212
- package/src/__tests__/autoLayout.test.ts +0 -228
- package/src/__tests__/phase12-features.test.tsx +0 -583
- package/src/__tests__/roadmap-features.test.tsx +0 -478
- package/src/autoLayout.ts +0 -128
- package/src/index.tsx +0 -149
- package/src/useDetailTranslation.ts +0 -114
- package/tsconfig.json +0 -18
- package/vite.config.ts +0 -56
- package/vitest.config.ts +0 -13
- package/vitest.setup.ts +0 -1
- /package/dist/{src → packages/plugin-detail/src}/ActivityTimeline.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/CommentAttachment.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/CommentInput.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailSection.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailTabs.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailView.stories.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DiffView.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/FieldChangeItem.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/HeaderHighlight.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/InlineCreateRelated.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/MentionAutocomplete.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/PointInTimeRestore.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/ReactionPicker.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordActivityTimeline.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordChatterPanel.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordComments.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordNavigationEnhanced.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RelationshipGraph.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RichTextCommentInput.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/SectionGroup.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/SubscriptionToggle.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/ThreadedReplies.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/autoLayout.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/index.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/useDetailTranslation.d.ts +0 -0
package/src/DetailView.tsx
DELETED
|
@@ -1,823 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import * as React from 'react';
|
|
10
|
-
import {
|
|
11
|
-
cn,
|
|
12
|
-
Badge,
|
|
13
|
-
Button,
|
|
14
|
-
Skeleton,
|
|
15
|
-
DropdownMenu,
|
|
16
|
-
DropdownMenuContent,
|
|
17
|
-
DropdownMenuItem,
|
|
18
|
-
DropdownMenuSeparator,
|
|
19
|
-
DropdownMenuTrigger,
|
|
20
|
-
Tooltip,
|
|
21
|
-
TooltipContent,
|
|
22
|
-
TooltipProvider,
|
|
23
|
-
TooltipTrigger,
|
|
24
|
-
Tabs,
|
|
25
|
-
TabsList,
|
|
26
|
-
TabsTrigger,
|
|
27
|
-
TabsContent,
|
|
28
|
-
} from '@object-ui/components';
|
|
29
|
-
import {
|
|
30
|
-
ArrowLeft,
|
|
31
|
-
Edit,
|
|
32
|
-
Trash2,
|
|
33
|
-
MoreHorizontal,
|
|
34
|
-
Share2,
|
|
35
|
-
Copy,
|
|
36
|
-
Download,
|
|
37
|
-
History,
|
|
38
|
-
Star,
|
|
39
|
-
StarOff,
|
|
40
|
-
Check,
|
|
41
|
-
ChevronLeft,
|
|
42
|
-
ChevronRight,
|
|
43
|
-
} from 'lucide-react';
|
|
44
|
-
import { DetailSection } from './DetailSection';
|
|
45
|
-
import { DetailTabs } from './DetailTabs';
|
|
46
|
-
import { RelatedList } from './RelatedList';
|
|
47
|
-
import { SectionGroup } from './SectionGroup';
|
|
48
|
-
import { HeaderHighlight } from './HeaderHighlight';
|
|
49
|
-
import { RecordComments } from './RecordComments';
|
|
50
|
-
import { ActivityTimeline } from './ActivityTimeline';
|
|
51
|
-
import { SchemaRenderer } from '@object-ui/react';
|
|
52
|
-
import { buildExpandFields } from '@object-ui/core';
|
|
53
|
-
import type { DetailViewSchema, DataSource } from '@object-ui/types';
|
|
54
|
-
import { useDetailTranslation } from './useDetailTranslation';
|
|
55
|
-
|
|
56
|
-
/** Default page size for related lists in the detail view */
|
|
57
|
-
const DEFAULT_RELATED_PAGE_SIZE = 5;
|
|
58
|
-
|
|
59
|
-
export interface DetailViewProps {
|
|
60
|
-
schema: DetailViewSchema;
|
|
61
|
-
dataSource?: DataSource;
|
|
62
|
-
className?: string;
|
|
63
|
-
onEdit?: () => void;
|
|
64
|
-
onDelete?: () => void;
|
|
65
|
-
onBack?: () => void;
|
|
66
|
-
/** Enable inline editing toggle for detail fields */
|
|
67
|
-
inlineEdit?: boolean;
|
|
68
|
-
/** Callback when a field value is saved inline */
|
|
69
|
-
onFieldSave?: (field: string, value: any, record: any) => void | Promise<void>;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export const DetailView: React.FC<DetailViewProps> = ({
|
|
73
|
-
schema,
|
|
74
|
-
dataSource,
|
|
75
|
-
className,
|
|
76
|
-
onEdit,
|
|
77
|
-
onDelete,
|
|
78
|
-
onBack,
|
|
79
|
-
inlineEdit = false,
|
|
80
|
-
onFieldSave,
|
|
81
|
-
}) => {
|
|
82
|
-
const [data, setData] = React.useState<any>(schema.data);
|
|
83
|
-
const [loading, setLoading] = React.useState(!schema.data && !!((schema.api && schema.resourceId) || (dataSource && schema.objectName && schema.resourceId)));
|
|
84
|
-
const [isFavorite, setIsFavorite] = React.useState(false);
|
|
85
|
-
const [isInlineEditing, setIsInlineEditing] = React.useState(false);
|
|
86
|
-
const [editedValues, setEditedValues] = React.useState<Record<string, any>>({});
|
|
87
|
-
const [objectSchema, setObjectSchema] = React.useState<any>(null);
|
|
88
|
-
const { t } = useDetailTranslation();
|
|
89
|
-
|
|
90
|
-
// Fetch objectSchema + data with $expand when DataSource is provided
|
|
91
|
-
React.useEffect(() => {
|
|
92
|
-
let isMounted = true;
|
|
93
|
-
|
|
94
|
-
// If inline data provided, use it
|
|
95
|
-
if (schema.data) {
|
|
96
|
-
setData(schema.data);
|
|
97
|
-
setLoading(false);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (dataSource && schema.objectName && schema.resourceId) {
|
|
102
|
-
setLoading(true);
|
|
103
|
-
// Clear stale state when navigating between objects/records
|
|
104
|
-
setObjectSchema(null);
|
|
105
|
-
setData(null);
|
|
106
|
-
const objectName = schema.objectName;
|
|
107
|
-
const resourceId = schema.resourceId;
|
|
108
|
-
const prefix = `${objectName}-`;
|
|
109
|
-
|
|
110
|
-
// Collect all visible fields from sections and top-level fields
|
|
111
|
-
const allFields = [
|
|
112
|
-
...(schema.sections?.flatMap(s => s.fields) || []),
|
|
113
|
-
...(schema.fields || []),
|
|
114
|
-
];
|
|
115
|
-
|
|
116
|
-
// Load objectSchema first, then fetch data with $expand
|
|
117
|
-
const schemaPromise = dataSource.getObjectSchema
|
|
118
|
-
? dataSource.getObjectSchema(objectName).catch(() => null)
|
|
119
|
-
: Promise.resolve(null);
|
|
120
|
-
|
|
121
|
-
schemaPromise.then((resolvedSchema) => {
|
|
122
|
-
if (!isMounted) return;
|
|
123
|
-
setObjectSchema(resolvedSchema);
|
|
124
|
-
|
|
125
|
-
// Compute $expand from objectSchema
|
|
126
|
-
const expandFields = buildExpandFields(resolvedSchema?.fields, allFields);
|
|
127
|
-
const params = expandFields.length > 0 ? { $expand: expandFields } : undefined;
|
|
128
|
-
|
|
129
|
-
const findOnePromise = params
|
|
130
|
-
? dataSource.findOne(objectName, resourceId, params)
|
|
131
|
-
: dataSource.findOne(objectName, resourceId);
|
|
132
|
-
|
|
133
|
-
// Helper: try alternate ID format (strip or prepend objectName prefix)
|
|
134
|
-
const tryAltId = () => {
|
|
135
|
-
const resIdStr = String(resourceId);
|
|
136
|
-
const altId = resIdStr.startsWith(prefix)
|
|
137
|
-
? resIdStr.slice(prefix.length) // strip prefix
|
|
138
|
-
: `${prefix}${resIdStr}`; // prepend prefix
|
|
139
|
-
return (params
|
|
140
|
-
? dataSource.findOne(objectName, altId, params)
|
|
141
|
-
: dataSource.findOne(objectName, altId)
|
|
142
|
-
).then((fallbackResult) => {
|
|
143
|
-
if (isMounted) {
|
|
144
|
-
setData(fallbackResult);
|
|
145
|
-
setLoading(false);
|
|
146
|
-
}
|
|
147
|
-
}).catch(() => {
|
|
148
|
-
if (isMounted) {
|
|
149
|
-
setData(null);
|
|
150
|
-
setLoading(false);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
return findOnePromise
|
|
156
|
-
.catch(() => null) // Convert any error to null to trigger alternate ID fallback
|
|
157
|
-
.then((result) => {
|
|
158
|
-
if (!isMounted) return;
|
|
159
|
-
if (result) {
|
|
160
|
-
setData(result);
|
|
161
|
-
setLoading(false);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
// Fallback: try alternate ID format for backward compatibility
|
|
165
|
-
return tryAltId();
|
|
166
|
-
});
|
|
167
|
-
}).catch((err) => {
|
|
168
|
-
if (isMounted) {
|
|
169
|
-
console.error('Failed to fetch detail data:', err);
|
|
170
|
-
setLoading(false);
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
} else if (schema.api && schema.resourceId) {
|
|
174
|
-
setLoading(true);
|
|
175
|
-
fetch(`${schema.api}/${schema.resourceId}`)
|
|
176
|
-
.then(res => res.json())
|
|
177
|
-
.then(result => {
|
|
178
|
-
if (isMounted) {
|
|
179
|
-
setData(result?.data || result);
|
|
180
|
-
}
|
|
181
|
-
})
|
|
182
|
-
.catch(err => {
|
|
183
|
-
console.error('Failed to fetch detail data:', err);
|
|
184
|
-
})
|
|
185
|
-
.finally(() => { if (isMounted) setLoading(false); });
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return () => { isMounted = false; };
|
|
189
|
-
}, [schema.api, schema.resourceId, schema.objectName, dataSource, schema.sections, schema.fields]);
|
|
190
|
-
|
|
191
|
-
const handleBack = React.useCallback(() => {
|
|
192
|
-
if (onBack) {
|
|
193
|
-
onBack();
|
|
194
|
-
} else if (schema.onNavigate) {
|
|
195
|
-
// SPA-aware navigation
|
|
196
|
-
const backUrl = schema.backUrl || (schema.objectName ? `/${schema.objectName}` : '/');
|
|
197
|
-
schema.onNavigate(backUrl, { replace: true });
|
|
198
|
-
} else if (schema.backUrl) {
|
|
199
|
-
window.location.href = schema.backUrl;
|
|
200
|
-
} else {
|
|
201
|
-
window.history.back();
|
|
202
|
-
}
|
|
203
|
-
}, [onBack, schema]);
|
|
204
|
-
|
|
205
|
-
const handleEdit = React.useCallback(() => {
|
|
206
|
-
if (onEdit) {
|
|
207
|
-
onEdit();
|
|
208
|
-
} else if (schema.onNavigate && schema.editUrl) {
|
|
209
|
-
// SPA-aware navigation
|
|
210
|
-
schema.onNavigate(schema.editUrl);
|
|
211
|
-
} else if (schema.onNavigate && schema.objectName && schema.resourceId) {
|
|
212
|
-
// Build edit URL from object + resource
|
|
213
|
-
schema.onNavigate(`/${schema.objectName}/${schema.resourceId}/edit`);
|
|
214
|
-
} else if (schema.editUrl) {
|
|
215
|
-
window.location.href = schema.editUrl;
|
|
216
|
-
}
|
|
217
|
-
}, [onEdit, schema]);
|
|
218
|
-
|
|
219
|
-
const handleDelete = React.useCallback(() => {
|
|
220
|
-
const confirmMessage = schema.deleteConfirmation || t('detail.deleteConfirmation');
|
|
221
|
-
// Use window.confirm as fallback — the ActionProvider's onConfirm handler
|
|
222
|
-
// will intercept this if wired up via the action system.
|
|
223
|
-
if (window.confirm(confirmMessage)) {
|
|
224
|
-
onDelete?.();
|
|
225
|
-
// Navigate back after deletion if onNavigate available
|
|
226
|
-
if (schema.onNavigate && schema.objectName) {
|
|
227
|
-
schema.onNavigate(`/${schema.objectName}`, { replace: true });
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}, [onDelete, schema]);
|
|
231
|
-
|
|
232
|
-
const handleShare = React.useCallback(() => {
|
|
233
|
-
// Share functionality - could trigger share dialog or copy link
|
|
234
|
-
if (navigator.share && schema.objectName && schema.resourceId) {
|
|
235
|
-
navigator.share({
|
|
236
|
-
title: schema.title || t('detail.details'),
|
|
237
|
-
text: `${schema.objectName} #${schema.resourceId}`,
|
|
238
|
-
url: window.location.href,
|
|
239
|
-
}).catch((err) => console.log('Share failed:', err));
|
|
240
|
-
} else {
|
|
241
|
-
// Fallback: copy link to clipboard
|
|
242
|
-
navigator.clipboard.writeText(window.location.href).then(() => {
|
|
243
|
-
console.log('Link copied to clipboard');
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
}, [schema]);
|
|
247
|
-
|
|
248
|
-
const handleDuplicate = React.useCallback(() => {
|
|
249
|
-
// Duplicate functionality - could navigate to create page with prefilled data
|
|
250
|
-
console.log('Duplicate record:', data);
|
|
251
|
-
}, [data]);
|
|
252
|
-
|
|
253
|
-
const handleExport = React.useCallback(() => {
|
|
254
|
-
// Export functionality - could download as JSON, PDF, etc.
|
|
255
|
-
console.log('Export record:', data);
|
|
256
|
-
}, [data]);
|
|
257
|
-
|
|
258
|
-
const handleViewHistory = React.useCallback(() => {
|
|
259
|
-
// View history functionality
|
|
260
|
-
console.log('View history for record:', schema.resourceId);
|
|
261
|
-
}, [schema]);
|
|
262
|
-
|
|
263
|
-
const handleToggleFavorite = React.useCallback(() => {
|
|
264
|
-
setIsFavorite(!isFavorite);
|
|
265
|
-
}, [isFavorite]);
|
|
266
|
-
|
|
267
|
-
const handleInlineEditToggle = React.useCallback(() => {
|
|
268
|
-
if (isInlineEditing) {
|
|
269
|
-
// Save changes
|
|
270
|
-
const changes = Object.entries(editedValues);
|
|
271
|
-
if (changes.length > 0) {
|
|
272
|
-
const updatedData = { ...data, ...editedValues };
|
|
273
|
-
setData(updatedData);
|
|
274
|
-
changes.forEach(([field, value]) => {
|
|
275
|
-
onFieldSave?.(field, value, updatedData);
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
setEditedValues({});
|
|
279
|
-
}
|
|
280
|
-
setIsInlineEditing(!isInlineEditing);
|
|
281
|
-
}, [isInlineEditing, editedValues, data, onFieldSave]);
|
|
282
|
-
|
|
283
|
-
const handleInlineFieldChange = React.useCallback((field: string, value: any) => {
|
|
284
|
-
setEditedValues(prev => ({ ...prev, [field]: value }));
|
|
285
|
-
}, []);
|
|
286
|
-
|
|
287
|
-
// Keyboard shortcuts for prev/next record navigation (← / →)
|
|
288
|
-
React.useEffect(() => {
|
|
289
|
-
if (!schema.recordNavigation) return;
|
|
290
|
-
const nav = schema.recordNavigation;
|
|
291
|
-
const handler = (e: KeyboardEvent) => {
|
|
292
|
-
// Skip when focus is inside an input, textarea, or contenteditable
|
|
293
|
-
const tag = (e.target as HTMLElement)?.tagName;
|
|
294
|
-
if (tag === 'INPUT' || tag === 'TEXTAREA' || (e.target as HTMLElement)?.isContentEditable) return;
|
|
295
|
-
if (e.key === 'ArrowLeft' && nav.currentIndex > 0) {
|
|
296
|
-
e.preventDefault();
|
|
297
|
-
nav.onNavigate(nav.recordIds[nav.currentIndex - 1]);
|
|
298
|
-
} else if (e.key === 'ArrowRight' && nav.currentIndex < nav.recordIds.length - 1) {
|
|
299
|
-
e.preventDefault();
|
|
300
|
-
nav.onNavigate(nav.recordIds[nav.currentIndex + 1]);
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
document.addEventListener('keydown', handler);
|
|
304
|
-
return () => document.removeEventListener('keydown', handler);
|
|
305
|
-
}, [schema.recordNavigation]);
|
|
306
|
-
|
|
307
|
-
// Auto-discover related lists from objectSchema reference fields
|
|
308
|
-
const discoveredRelated = React.useMemo(() => {
|
|
309
|
-
if (!schema.autoDiscoverRelated || !objectSchema?.fields) return [];
|
|
310
|
-
// Only auto-discover when no explicit related config is provided
|
|
311
|
-
if (schema.related && schema.related.length > 0) return [];
|
|
312
|
-
const refs: Array<{ title: string; type: 'list' | 'grid' | 'table'; objectName: string; referenceField: string }> = [];
|
|
313
|
-
const fields = objectSchema.fields;
|
|
314
|
-
for (const [fieldName, fieldDef] of Object.entries<any>(fields)) {
|
|
315
|
-
const refTarget = fieldDef?.reference_to || fieldDef?.reference;
|
|
316
|
-
if (
|
|
317
|
-
fieldDef &&
|
|
318
|
-
(fieldDef.type === 'lookup' || fieldDef.type === 'master_detail') &&
|
|
319
|
-
refTarget
|
|
320
|
-
) {
|
|
321
|
-
refs.push({
|
|
322
|
-
title: fieldDef.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1),
|
|
323
|
-
type: 'table',
|
|
324
|
-
objectName: refTarget,
|
|
325
|
-
referenceField: fieldName,
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return refs;
|
|
330
|
-
}, [schema.autoDiscoverRelated, schema.related, objectSchema]);
|
|
331
|
-
|
|
332
|
-
// Merge explicit and auto-discovered related lists
|
|
333
|
-
const effectiveRelated: NonNullable<DetailViewSchema['related']> = React.useMemo(() => {
|
|
334
|
-
if (schema.related && schema.related.length > 0) return schema.related;
|
|
335
|
-
return discoveredRelated.map((r) => ({
|
|
336
|
-
title: r.title,
|
|
337
|
-
type: r.type,
|
|
338
|
-
api: r.objectName,
|
|
339
|
-
data: [] as any[],
|
|
340
|
-
}));
|
|
341
|
-
}, [schema.related, discoveredRelated]);
|
|
342
|
-
|
|
343
|
-
if (loading || schema.loading) {
|
|
344
|
-
return (
|
|
345
|
-
<div className={cn('space-y-4', className)}>
|
|
346
|
-
<Skeleton className="h-10 w-full" />
|
|
347
|
-
<Skeleton className="h-64 w-full" />
|
|
348
|
-
<Skeleton className="h-48 w-full" />
|
|
349
|
-
</div>
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (!data && !schema.data) {
|
|
354
|
-
return (
|
|
355
|
-
<div className={cn('flex flex-col items-center justify-center py-16 text-center', className)}>
|
|
356
|
-
<p className="text-lg font-semibold">{t('detail.recordNotFound')}</p>
|
|
357
|
-
<p className="text-sm text-muted-foreground mt-1">
|
|
358
|
-
{t('detail.recordNotFoundDescription')}
|
|
359
|
-
</p>
|
|
360
|
-
{(schema.showBack ?? true) && (
|
|
361
|
-
<Button variant="outline" size="sm" onClick={handleBack} className="mt-4 gap-2">
|
|
362
|
-
<ArrowLeft className="h-4 w-4" />
|
|
363
|
-
{t('detail.goBack')}
|
|
364
|
-
</Button>
|
|
365
|
-
)}
|
|
366
|
-
</div>
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return (
|
|
371
|
-
<TooltipProvider>
|
|
372
|
-
<div className={cn('space-y-6', className)}>
|
|
373
|
-
{/* Header - Airtable-inspired layout */}
|
|
374
|
-
<div className="flex flex-col sm:flex-row items-start justify-between gap-3 sm:gap-4 pb-4 border-b">
|
|
375
|
-
<div className="flex items-start gap-2 sm:gap-3 flex-1 min-w-0">
|
|
376
|
-
{(schema.showBack ?? true) && (
|
|
377
|
-
<Tooltip>
|
|
378
|
-
<TooltipTrigger asChild>
|
|
379
|
-
<Button variant="ghost" size="icon" onClick={handleBack} className="shrink-0 mt-1">
|
|
380
|
-
<ArrowLeft className="h-4 w-4" />
|
|
381
|
-
</Button>
|
|
382
|
-
</TooltipTrigger>
|
|
383
|
-
<TooltipContent>{t('detail.back')}</TooltipContent>
|
|
384
|
-
</Tooltip>
|
|
385
|
-
)}
|
|
386
|
-
<div className="flex-1 min-w-0">
|
|
387
|
-
<div className="flex items-center gap-2 flex-wrap">
|
|
388
|
-
<h1 className="text-xl sm:text-2xl font-bold truncate">
|
|
389
|
-
{(schema.primaryField && data?.[schema.primaryField]) || schema.title || t('detail.details')}
|
|
390
|
-
</h1>
|
|
391
|
-
{schema.summaryFields?.map((fieldName) => {
|
|
392
|
-
const val = data?.[fieldName];
|
|
393
|
-
if (val === null || val === undefined || val === '') return null;
|
|
394
|
-
return (
|
|
395
|
-
<Badge key={fieldName} variant="secondary" className="text-xs" aria-label={`${fieldName}: ${val}`}>
|
|
396
|
-
{String(val)}
|
|
397
|
-
</Badge>
|
|
398
|
-
);
|
|
399
|
-
})}
|
|
400
|
-
<Tooltip>
|
|
401
|
-
<TooltipTrigger asChild>
|
|
402
|
-
<Button
|
|
403
|
-
variant="ghost"
|
|
404
|
-
size="icon"
|
|
405
|
-
className="h-6 w-6 shrink-0"
|
|
406
|
-
onClick={handleToggleFavorite}
|
|
407
|
-
>
|
|
408
|
-
{isFavorite ? (
|
|
409
|
-
<Star className="h-4 w-4 fill-yellow-400 text-yellow-400" />
|
|
410
|
-
) : (
|
|
411
|
-
<StarOff className="h-4 w-4 text-muted-foreground" />
|
|
412
|
-
)}
|
|
413
|
-
</Button>
|
|
414
|
-
</TooltipTrigger>
|
|
415
|
-
<TooltipContent>
|
|
416
|
-
{isFavorite ? t('detail.removeFromFavorites') : t('detail.addToFavorites')}
|
|
417
|
-
</TooltipContent>
|
|
418
|
-
</Tooltip>
|
|
419
|
-
</div>
|
|
420
|
-
{schema.objectName && (
|
|
421
|
-
<p className="text-sm text-muted-foreground mt-1 flex items-center gap-1.5">
|
|
422
|
-
<span className="font-medium">{schema.objectName}</span>
|
|
423
|
-
<span className="text-muted-foreground/60">•</span>
|
|
424
|
-
<span>#{schema.resourceId}</span>
|
|
425
|
-
</p>
|
|
426
|
-
)}
|
|
427
|
-
</div>
|
|
428
|
-
</div>
|
|
429
|
-
|
|
430
|
-
<div className="flex flex-wrap items-center gap-1.5 shrink-0 w-full sm:w-auto">
|
|
431
|
-
{/* Prev/Next Record Navigation */}
|
|
432
|
-
{schema.recordNavigation && (
|
|
433
|
-
<div className="flex items-center gap-1 mr-2">
|
|
434
|
-
<Tooltip>
|
|
435
|
-
<TooltipTrigger asChild>
|
|
436
|
-
<Button
|
|
437
|
-
variant="outline"
|
|
438
|
-
size="icon"
|
|
439
|
-
className="h-8 w-8"
|
|
440
|
-
disabled={schema.recordNavigation.currentIndex <= 0}
|
|
441
|
-
onClick={() => {
|
|
442
|
-
const nav = schema.recordNavigation!;
|
|
443
|
-
if (nav.currentIndex > 0) {
|
|
444
|
-
nav.onNavigate(nav.recordIds[nav.currentIndex - 1]);
|
|
445
|
-
}
|
|
446
|
-
}}
|
|
447
|
-
>
|
|
448
|
-
<ChevronLeft className="h-4 w-4" />
|
|
449
|
-
</Button>
|
|
450
|
-
</TooltipTrigger>
|
|
451
|
-
<TooltipContent>{t('detail.previousRecord')}</TooltipContent>
|
|
452
|
-
</Tooltip>
|
|
453
|
-
<span className="text-xs text-muted-foreground whitespace-nowrap px-1">
|
|
454
|
-
{t('detail.recordOf', { current: schema.recordNavigation.currentIndex + 1, total: schema.recordNavigation.recordIds.length })}
|
|
455
|
-
</span>
|
|
456
|
-
<Tooltip>
|
|
457
|
-
<TooltipTrigger asChild>
|
|
458
|
-
<Button
|
|
459
|
-
variant="outline"
|
|
460
|
-
size="icon"
|
|
461
|
-
className="h-8 w-8"
|
|
462
|
-
disabled={schema.recordNavigation.currentIndex >= schema.recordNavigation.recordIds.length - 1}
|
|
463
|
-
onClick={() => {
|
|
464
|
-
const nav = schema.recordNavigation!;
|
|
465
|
-
if (nav.currentIndex < nav.recordIds.length - 1) {
|
|
466
|
-
nav.onNavigate(nav.recordIds[nav.currentIndex + 1]);
|
|
467
|
-
}
|
|
468
|
-
}}
|
|
469
|
-
>
|
|
470
|
-
<ChevronRight className="h-4 w-4" />
|
|
471
|
-
</Button>
|
|
472
|
-
</TooltipTrigger>
|
|
473
|
-
<TooltipContent>{t('detail.nextRecord')}</TooltipContent>
|
|
474
|
-
</Tooltip>
|
|
475
|
-
</div>
|
|
476
|
-
)}
|
|
477
|
-
|
|
478
|
-
{schema.actions?.map((action, index) => (
|
|
479
|
-
<SchemaRenderer key={index} schema={action} data={data} />
|
|
480
|
-
))}
|
|
481
|
-
|
|
482
|
-
{/* Inline Edit Toggle - hidden on mobile, accessible via more menu */}
|
|
483
|
-
{inlineEdit && (
|
|
484
|
-
<Tooltip>
|
|
485
|
-
<TooltipTrigger asChild>
|
|
486
|
-
<Button
|
|
487
|
-
variant={isInlineEditing ? 'default' : 'outline'}
|
|
488
|
-
size="sm"
|
|
489
|
-
onClick={handleInlineEditToggle}
|
|
490
|
-
className="gap-2 hidden sm:inline-flex"
|
|
491
|
-
>
|
|
492
|
-
{isInlineEditing ? (
|
|
493
|
-
<>
|
|
494
|
-
<Check className="h-4 w-4" />
|
|
495
|
-
<span className="hidden sm:inline">{t('detail.save')}</span>
|
|
496
|
-
</>
|
|
497
|
-
) : (
|
|
498
|
-
<>
|
|
499
|
-
<Edit className="h-4 w-4" />
|
|
500
|
-
<span className="hidden sm:inline">{t('detail.editInline')}</span>
|
|
501
|
-
</>
|
|
502
|
-
)}
|
|
503
|
-
</Button>
|
|
504
|
-
</TooltipTrigger>
|
|
505
|
-
<TooltipContent>
|
|
506
|
-
{isInlineEditing ? t('detail.saveChanges') : t('detail.editFieldsInline')}
|
|
507
|
-
</TooltipContent>
|
|
508
|
-
</Tooltip>
|
|
509
|
-
)}
|
|
510
|
-
|
|
511
|
-
{/* Share Button - hidden on mobile, accessible via more menu */}
|
|
512
|
-
<Tooltip>
|
|
513
|
-
<TooltipTrigger asChild>
|
|
514
|
-
<Button variant="outline" size="icon" onClick={handleShare} className="hidden sm:inline-flex">
|
|
515
|
-
<Share2 className="h-4 w-4" />
|
|
516
|
-
</Button>
|
|
517
|
-
</TooltipTrigger>
|
|
518
|
-
<TooltipContent>{t('detail.share')}</TooltipContent>
|
|
519
|
-
</Tooltip>
|
|
520
|
-
|
|
521
|
-
{/* Edit Button - hidden on mobile, accessible via more menu */}
|
|
522
|
-
{schema.showEdit && (
|
|
523
|
-
<Tooltip>
|
|
524
|
-
<TooltipTrigger asChild>
|
|
525
|
-
<Button variant="default" onClick={handleEdit} className="gap-2 hidden sm:inline-flex">
|
|
526
|
-
<Edit className="h-4 w-4" />
|
|
527
|
-
<span className="hidden sm:inline">{t('detail.edit')}</span>
|
|
528
|
-
</Button>
|
|
529
|
-
</TooltipTrigger>
|
|
530
|
-
<TooltipContent>{t('detail.editRecord')}</TooltipContent>
|
|
531
|
-
</Tooltip>
|
|
532
|
-
)}
|
|
533
|
-
|
|
534
|
-
{/* More Actions Menu */}
|
|
535
|
-
<DropdownMenu>
|
|
536
|
-
<Tooltip>
|
|
537
|
-
<TooltipTrigger asChild>
|
|
538
|
-
<DropdownMenuTrigger asChild>
|
|
539
|
-
<Button variant="ghost" size="icon">
|
|
540
|
-
<MoreHorizontal className="h-4 w-4" />
|
|
541
|
-
</Button>
|
|
542
|
-
</DropdownMenuTrigger>
|
|
543
|
-
</TooltipTrigger>
|
|
544
|
-
<TooltipContent>{t('detail.moreActions')}</TooltipContent>
|
|
545
|
-
</Tooltip>
|
|
546
|
-
<DropdownMenuContent align="end" className="w-[calc(100vw-2rem)] sm:w-48 max-h-[60vh] overflow-y-auto">
|
|
547
|
-
{/* Mobile-only: Share, Edit, Inline Edit */}
|
|
548
|
-
<DropdownMenuItem onClick={handleShare} className="sm:hidden">
|
|
549
|
-
<Share2 className="h-4 w-4 mr-2" />
|
|
550
|
-
{t('detail.share')}
|
|
551
|
-
</DropdownMenuItem>
|
|
552
|
-
{schema.showEdit && (
|
|
553
|
-
<DropdownMenuItem onClick={handleEdit} className="sm:hidden">
|
|
554
|
-
<Edit className="h-4 w-4 mr-2" />
|
|
555
|
-
{t('detail.edit')}
|
|
556
|
-
</DropdownMenuItem>
|
|
557
|
-
)}
|
|
558
|
-
{inlineEdit && (
|
|
559
|
-
<DropdownMenuItem onClick={handleInlineEditToggle} className="sm:hidden">
|
|
560
|
-
<Edit className="h-4 w-4 mr-2" />
|
|
561
|
-
{isInlineEditing ? t('detail.save') : t('detail.editInline')}
|
|
562
|
-
</DropdownMenuItem>
|
|
563
|
-
)}
|
|
564
|
-
<DropdownMenuSeparator className="sm:hidden" />
|
|
565
|
-
<DropdownMenuItem onClick={handleDuplicate}>
|
|
566
|
-
<Copy className="h-4 w-4 mr-2" />
|
|
567
|
-
{t('detail.duplicate')}
|
|
568
|
-
</DropdownMenuItem>
|
|
569
|
-
<DropdownMenuItem onClick={handleExport}>
|
|
570
|
-
<Download className="h-4 w-4 mr-2" />
|
|
571
|
-
{t('detail.export')}
|
|
572
|
-
</DropdownMenuItem>
|
|
573
|
-
<DropdownMenuItem onClick={handleViewHistory}>
|
|
574
|
-
<History className="h-4 w-4 mr-2" />
|
|
575
|
-
{t('detail.viewHistory')}
|
|
576
|
-
</DropdownMenuItem>
|
|
577
|
-
{schema.showDelete && (
|
|
578
|
-
<>
|
|
579
|
-
<DropdownMenuSeparator />
|
|
580
|
-
<DropdownMenuItem
|
|
581
|
-
onClick={handleDelete}
|
|
582
|
-
className="text-destructive focus:text-destructive"
|
|
583
|
-
>
|
|
584
|
-
<Trash2 className="h-4 w-4 mr-2" />
|
|
585
|
-
{t('detail.delete')}
|
|
586
|
-
</DropdownMenuItem>
|
|
587
|
-
</>
|
|
588
|
-
)}
|
|
589
|
-
</DropdownMenuContent>
|
|
590
|
-
</DropdownMenu>
|
|
591
|
-
</div>
|
|
592
|
-
</div>
|
|
593
|
-
|
|
594
|
-
{/* Custom Header */}
|
|
595
|
-
{schema.header && (
|
|
596
|
-
<div>
|
|
597
|
-
<SchemaRenderer schema={schema.header} data={data} />
|
|
598
|
-
</div>
|
|
599
|
-
)}
|
|
600
|
-
|
|
601
|
-
{/* Header Highlight Area */}
|
|
602
|
-
{schema.highlightFields && schema.highlightFields.length > 0 && (
|
|
603
|
-
<HeaderHighlight fields={schema.highlightFields} data={data} objectName={schema.objectName} objectSchema={objectSchema} />
|
|
604
|
-
)}
|
|
605
|
-
|
|
606
|
-
{/* Auto Tabs mode: wrap sections, related, activity into tabs */}
|
|
607
|
-
{schema.autoTabs && !schema.tabs?.length ? (
|
|
608
|
-
<Tabs defaultValue="details" className="w-full">
|
|
609
|
-
<TabsList className="w-full justify-start border-b rounded-none bg-transparent p-0">
|
|
610
|
-
<TabsTrigger
|
|
611
|
-
value="details"
|
|
612
|
-
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
|
|
613
|
-
>
|
|
614
|
-
{t('detail.details')}
|
|
615
|
-
</TabsTrigger>
|
|
616
|
-
{effectiveRelated.length > 0 && (
|
|
617
|
-
<TabsTrigger
|
|
618
|
-
value="related"
|
|
619
|
-
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
|
|
620
|
-
>
|
|
621
|
-
<span className="flex items-center gap-1.5">
|
|
622
|
-
{t('detail.related')}
|
|
623
|
-
<Badge variant="secondary" className="text-xs">{effectiveRelated.length}</Badge>
|
|
624
|
-
</span>
|
|
625
|
-
</TabsTrigger>
|
|
626
|
-
)}
|
|
627
|
-
{schema.activities && schema.activities.length > 0 && (
|
|
628
|
-
<TabsTrigger
|
|
629
|
-
value="activity"
|
|
630
|
-
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
|
|
631
|
-
>
|
|
632
|
-
<span className="flex items-center gap-1.5">
|
|
633
|
-
{t('detail.activity')}
|
|
634
|
-
<Badge variant="secondary" className="text-xs">{schema.activities.length}</Badge>
|
|
635
|
-
</span>
|
|
636
|
-
</TabsTrigger>
|
|
637
|
-
)}
|
|
638
|
-
</TabsList>
|
|
639
|
-
|
|
640
|
-
{/* Details Tab Content */}
|
|
641
|
-
<TabsContent value="details" className="mt-4">
|
|
642
|
-
<div className="space-y-3 sm:space-y-4">
|
|
643
|
-
{/* Section Groups */}
|
|
644
|
-
{schema.sectionGroups && schema.sectionGroups.length > 0 && (
|
|
645
|
-
schema.sectionGroups.map((group, index) => (
|
|
646
|
-
<SectionGroup
|
|
647
|
-
key={index}
|
|
648
|
-
group={group}
|
|
649
|
-
data={{ ...data, ...editedValues }}
|
|
650
|
-
objectSchema={objectSchema}
|
|
651
|
-
objectName={schema.objectName}
|
|
652
|
-
isEditing={isInlineEditing}
|
|
653
|
-
onFieldChange={handleInlineFieldChange}
|
|
654
|
-
/>
|
|
655
|
-
))
|
|
656
|
-
)}
|
|
657
|
-
{schema.sections && schema.sections.length > 0 && (
|
|
658
|
-
schema.sections.map((section, index) => (
|
|
659
|
-
<DetailSection
|
|
660
|
-
key={index}
|
|
661
|
-
section={section}
|
|
662
|
-
data={{ ...data, ...editedValues }}
|
|
663
|
-
objectSchema={objectSchema}
|
|
664
|
-
objectName={schema.objectName}
|
|
665
|
-
isEditing={isInlineEditing}
|
|
666
|
-
onFieldChange={handleInlineFieldChange}
|
|
667
|
-
/>
|
|
668
|
-
))
|
|
669
|
-
)}
|
|
670
|
-
{schema.fields && schema.fields.length > 0 && !schema.sections?.length && (
|
|
671
|
-
<DetailSection
|
|
672
|
-
section={{
|
|
673
|
-
fields: schema.fields,
|
|
674
|
-
columns: schema.columns,
|
|
675
|
-
}}
|
|
676
|
-
data={{ ...data, ...editedValues }}
|
|
677
|
-
objectSchema={objectSchema}
|
|
678
|
-
objectName={schema.objectName}
|
|
679
|
-
isEditing={isInlineEditing}
|
|
680
|
-
onFieldChange={handleInlineFieldChange}
|
|
681
|
-
/>
|
|
682
|
-
)}
|
|
683
|
-
{/* Comments in details tab */}
|
|
684
|
-
{schema.comments && (
|
|
685
|
-
<RecordComments
|
|
686
|
-
comments={schema.comments}
|
|
687
|
-
onAddComment={schema.onAddComment}
|
|
688
|
-
/>
|
|
689
|
-
)}
|
|
690
|
-
</div>
|
|
691
|
-
</TabsContent>
|
|
692
|
-
|
|
693
|
-
{/* Related Tab Content */}
|
|
694
|
-
{effectiveRelated.length > 0 && (
|
|
695
|
-
<TabsContent value="related" className="mt-4">
|
|
696
|
-
<div className="space-y-4">
|
|
697
|
-
{effectiveRelated.map((related, index) => (
|
|
698
|
-
<RelatedList
|
|
699
|
-
key={index}
|
|
700
|
-
title={related.title}
|
|
701
|
-
type={related.type}
|
|
702
|
-
api={related.api}
|
|
703
|
-
data={related.data}
|
|
704
|
-
columns={related.columns as any}
|
|
705
|
-
dataSource={dataSource}
|
|
706
|
-
objectName={related.api}
|
|
707
|
-
collapsible
|
|
708
|
-
pageSize={DEFAULT_RELATED_PAGE_SIZE}
|
|
709
|
-
/>
|
|
710
|
-
))}
|
|
711
|
-
</div>
|
|
712
|
-
</TabsContent>
|
|
713
|
-
)}
|
|
714
|
-
|
|
715
|
-
{/* Activity Tab Content */}
|
|
716
|
-
{schema.activities && schema.activities.length > 0 && (
|
|
717
|
-
<TabsContent value="activity" className="mt-4">
|
|
718
|
-
<ActivityTimeline activities={schema.activities} />
|
|
719
|
-
</TabsContent>
|
|
720
|
-
)}
|
|
721
|
-
</Tabs>
|
|
722
|
-
) : (
|
|
723
|
-
<>
|
|
724
|
-
{/* Section Groups */}
|
|
725
|
-
{schema.sectionGroups && schema.sectionGroups.length > 0 && (
|
|
726
|
-
<div className="space-y-3 sm:space-y-4">
|
|
727
|
-
{schema.sectionGroups.map((group, index) => (
|
|
728
|
-
<SectionGroup
|
|
729
|
-
key={index}
|
|
730
|
-
group={group}
|
|
731
|
-
data={{ ...data, ...editedValues }}
|
|
732
|
-
objectSchema={objectSchema}
|
|
733
|
-
objectName={schema.objectName}
|
|
734
|
-
isEditing={isInlineEditing}
|
|
735
|
-
onFieldChange={handleInlineFieldChange}
|
|
736
|
-
/>
|
|
737
|
-
))}
|
|
738
|
-
</div>
|
|
739
|
-
)}
|
|
740
|
-
|
|
741
|
-
{/* Sections */}
|
|
742
|
-
{schema.sections && schema.sections.length > 0 && (
|
|
743
|
-
<div className="space-y-3 sm:space-y-4">
|
|
744
|
-
{schema.sections.map((section, index) => (
|
|
745
|
-
<DetailSection
|
|
746
|
-
key={index}
|
|
747
|
-
section={section}
|
|
748
|
-
data={{ ...data, ...editedValues }}
|
|
749
|
-
objectSchema={objectSchema}
|
|
750
|
-
objectName={schema.objectName}
|
|
751
|
-
isEditing={isInlineEditing}
|
|
752
|
-
onFieldChange={handleInlineFieldChange}
|
|
753
|
-
/>
|
|
754
|
-
))}
|
|
755
|
-
</div>
|
|
756
|
-
)}
|
|
757
|
-
|
|
758
|
-
{/* Direct Fields (if no sections) */}
|
|
759
|
-
{schema.fields && schema.fields.length > 0 && !schema.sections?.length && (
|
|
760
|
-
<DetailSection
|
|
761
|
-
section={{
|
|
762
|
-
fields: schema.fields,
|
|
763
|
-
columns: schema.columns,
|
|
764
|
-
}}
|
|
765
|
-
data={{ ...data, ...editedValues }}
|
|
766
|
-
objectSchema={objectSchema}
|
|
767
|
-
objectName={schema.objectName}
|
|
768
|
-
isEditing={isInlineEditing}
|
|
769
|
-
onFieldChange={handleInlineFieldChange}
|
|
770
|
-
/>
|
|
771
|
-
)}
|
|
772
|
-
|
|
773
|
-
{/* Tabs */}
|
|
774
|
-
{schema.tabs && schema.tabs.length > 0 && (
|
|
775
|
-
<DetailTabs tabs={schema.tabs} data={data} />
|
|
776
|
-
)}
|
|
777
|
-
|
|
778
|
-
{/* Related Lists */}
|
|
779
|
-
{effectiveRelated.length > 0 && (
|
|
780
|
-
<div className="space-y-4">
|
|
781
|
-
<h2 className="text-xl font-semibold">{t('detail.related')}</h2>
|
|
782
|
-
{effectiveRelated.map((related, index) => (
|
|
783
|
-
<RelatedList
|
|
784
|
-
key={index}
|
|
785
|
-
title={related.title}
|
|
786
|
-
type={related.type}
|
|
787
|
-
api={related.api}
|
|
788
|
-
data={related.data}
|
|
789
|
-
columns={related.columns as any}
|
|
790
|
-
dataSource={dataSource}
|
|
791
|
-
objectName={related.api}
|
|
792
|
-
collapsible
|
|
793
|
-
pageSize={DEFAULT_RELATED_PAGE_SIZE}
|
|
794
|
-
/>
|
|
795
|
-
))}
|
|
796
|
-
</div>
|
|
797
|
-
)}
|
|
798
|
-
|
|
799
|
-
{/* Comments */}
|
|
800
|
-
{schema.comments && (
|
|
801
|
-
<RecordComments
|
|
802
|
-
comments={schema.comments}
|
|
803
|
-
onAddComment={schema.onAddComment}
|
|
804
|
-
/>
|
|
805
|
-
)}
|
|
806
|
-
|
|
807
|
-
{/* Activity Timeline */}
|
|
808
|
-
{schema.activities && schema.activities.length > 0 && (
|
|
809
|
-
<ActivityTimeline activities={schema.activities} />
|
|
810
|
-
)}
|
|
811
|
-
</>
|
|
812
|
-
)}
|
|
813
|
-
|
|
814
|
-
{/* Custom Footer */}
|
|
815
|
-
{schema.footer && (
|
|
816
|
-
<div>
|
|
817
|
-
<SchemaRenderer schema={schema.footer} data={data} />
|
|
818
|
-
</div>
|
|
819
|
-
)}
|
|
820
|
-
</div>
|
|
821
|
-
</TooltipProvider>
|
|
822
|
-
);
|
|
823
|
-
};
|