@checkstack/catalog-frontend 0.3.11 → 0.4.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
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @checkstack/catalog-frontend
|
|
2
2
|
|
|
3
|
+
## 0.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [f676e11]
|
|
8
|
+
- @checkstack/ui@1.0.0
|
|
9
|
+
- @checkstack/common@0.6.2
|
|
10
|
+
- @checkstack/auth-frontend@0.5.9
|
|
11
|
+
- @checkstack/auth-common@0.5.5
|
|
12
|
+
- @checkstack/catalog-common@1.2.7
|
|
13
|
+
- @checkstack/frontend-api@0.3.5
|
|
14
|
+
- @checkstack/notification-common@0.2.5
|
|
15
|
+
|
|
16
|
+
## 0.4.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- e5079e1: Add contacts management to system editor
|
|
21
|
+
|
|
22
|
+
- **catalog-frontend**: New `ContactsEditor` component allows adding/removing platform users and external mailboxes as system contacts directly from the system editor dialog
|
|
23
|
+
- **catalog-common**: Added `instanceAccess` override to contacts RPC endpoints for correct single-resource RLAC checking
|
|
24
|
+
- **ui**: Fixed Tabs component to use `type="button"` to prevent form submission when used inside forms
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Updated dependencies [e5079e1]
|
|
29
|
+
- Updated dependencies [9551fd7]
|
|
30
|
+
- @checkstack/catalog-common@1.2.6
|
|
31
|
+
- @checkstack/ui@0.5.3
|
|
32
|
+
- @checkstack/auth-frontend@0.5.8
|
|
33
|
+
|
|
3
34
|
## 0.3.11
|
|
4
35
|
|
|
5
36
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/catalog-frontend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.tsx",
|
|
6
6
|
"scripts": {
|
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
"lint:code": "eslint . --max-warnings 0"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@checkstack/
|
|
13
|
-
"@checkstack/
|
|
14
|
-
"@checkstack/
|
|
15
|
-
"@checkstack/
|
|
16
|
-
"@checkstack/
|
|
17
|
-
"@checkstack/
|
|
12
|
+
"@checkstack/auth-common": "0.5.4",
|
|
13
|
+
"@checkstack/catalog-common": "1.2.6",
|
|
14
|
+
"@checkstack/frontend-api": "0.3.4",
|
|
15
|
+
"@checkstack/auth-frontend": "0.5.8",
|
|
16
|
+
"@checkstack/common": "0.6.1",
|
|
17
|
+
"@checkstack/notification-common": "0.2.4",
|
|
18
|
+
"@checkstack/ui": "0.5.3",
|
|
18
19
|
"react": "^18.2.0",
|
|
19
20
|
"react-router-dom": "^6.22.0",
|
|
20
21
|
"lucide-react": "^0.344.0"
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"typescript": "^5.0.0",
|
|
24
25
|
"@types/react": "^18.2.0",
|
|
25
|
-
"@checkstack/tsconfig": "0.0.
|
|
26
|
-
"@checkstack/scripts": "0.1.
|
|
26
|
+
"@checkstack/tsconfig": "0.0.3",
|
|
27
|
+
"@checkstack/scripts": "0.1.1"
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Input,
|
|
5
|
+
Label,
|
|
6
|
+
useToast,
|
|
7
|
+
LoadingSpinner,
|
|
8
|
+
Select,
|
|
9
|
+
SelectContent,
|
|
10
|
+
SelectItem,
|
|
11
|
+
SelectTrigger,
|
|
12
|
+
SelectValue,
|
|
13
|
+
Tabs,
|
|
14
|
+
TabPanel,
|
|
15
|
+
} from "@checkstack/ui";
|
|
16
|
+
import {
|
|
17
|
+
usePluginClient,
|
|
18
|
+
useApi,
|
|
19
|
+
accessApiRef,
|
|
20
|
+
} from "@checkstack/frontend-api";
|
|
21
|
+
import { CatalogApi, type SystemContact } from "@checkstack/catalog-common";
|
|
22
|
+
import { AuthApi, authAccess } from "@checkstack/auth-common";
|
|
23
|
+
import { User, Mail, Trash2, Plus } from "lucide-react";
|
|
24
|
+
|
|
25
|
+
interface ContactsEditorProps {
|
|
26
|
+
systemId: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface UserDto {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
email: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const ContactsEditor: React.FC<ContactsEditorProps> = ({ systemId }) => {
|
|
36
|
+
const catalogClient = usePluginClient(CatalogApi);
|
|
37
|
+
const authClient = usePluginClient(AuthApi);
|
|
38
|
+
const accessApi = useApi(accessApiRef);
|
|
39
|
+
const toast = useToast();
|
|
40
|
+
|
|
41
|
+
// Check if user can search users
|
|
42
|
+
const { allowed: canSearchUsers, loading: accessLoading } =
|
|
43
|
+
accessApi.useAccess(authAccess.users.read);
|
|
44
|
+
|
|
45
|
+
// Form state
|
|
46
|
+
const [selectedUserId, setSelectedUserId] = useState("");
|
|
47
|
+
const [mailboxEmail, setMailboxEmail] = useState("");
|
|
48
|
+
const [label, setLabel] = useState("");
|
|
49
|
+
const [activeTab, setActiveTab] = useState("mailbox");
|
|
50
|
+
|
|
51
|
+
// Update active tab when permission loading completes
|
|
52
|
+
React.useEffect(() => {
|
|
53
|
+
if (!accessLoading && canSearchUsers) {
|
|
54
|
+
setActiveTab("user");
|
|
55
|
+
}
|
|
56
|
+
}, [accessLoading, canSearchUsers]);
|
|
57
|
+
|
|
58
|
+
// Fetch existing contacts
|
|
59
|
+
const {
|
|
60
|
+
data: contacts = [],
|
|
61
|
+
isLoading: contactsLoading,
|
|
62
|
+
refetch: refetchContacts,
|
|
63
|
+
} = catalogClient.getSystemContacts.useQuery({ systemId });
|
|
64
|
+
|
|
65
|
+
// Fetch users for selection (only if user has permission)
|
|
66
|
+
const { data: users = [] } = authClient.getUsers.useQuery(
|
|
67
|
+
{},
|
|
68
|
+
{ enabled: canSearchUsers },
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Add contact mutation
|
|
72
|
+
const addContactMutation = catalogClient.addSystemContact.useMutation({
|
|
73
|
+
onSuccess: () => {
|
|
74
|
+
toast.success("Contact added successfully");
|
|
75
|
+
setSelectedUserId("");
|
|
76
|
+
setMailboxEmail("");
|
|
77
|
+
setLabel("");
|
|
78
|
+
void refetchContacts();
|
|
79
|
+
},
|
|
80
|
+
onError: (error) => {
|
|
81
|
+
toast.error(
|
|
82
|
+
error instanceof Error ? error.message : "Failed to add contact",
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Remove contact mutation
|
|
88
|
+
const removeContactMutation = catalogClient.removeSystemContact.useMutation({
|
|
89
|
+
onSuccess: () => {
|
|
90
|
+
toast.success("Contact removed");
|
|
91
|
+
void refetchContacts();
|
|
92
|
+
},
|
|
93
|
+
onError: (error) => {
|
|
94
|
+
toast.error(
|
|
95
|
+
error instanceof Error ? error.message : "Failed to remove contact",
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const handleAddUserContact = () => {
|
|
101
|
+
if (!selectedUserId) {
|
|
102
|
+
toast.error("Please select a user");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
addContactMutation.mutate({
|
|
107
|
+
systemId,
|
|
108
|
+
type: "user",
|
|
109
|
+
userId: selectedUserId,
|
|
110
|
+
label: label.trim() || undefined,
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const handleAddMailboxContact = () => {
|
|
115
|
+
if (!mailboxEmail.trim()) {
|
|
116
|
+
toast.error("Please enter an email address");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Basic email validation
|
|
121
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(mailboxEmail.trim())) {
|
|
122
|
+
toast.error("Please enter a valid email address");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
addContactMutation.mutate({
|
|
127
|
+
systemId,
|
|
128
|
+
type: "mailbox",
|
|
129
|
+
email: mailboxEmail.trim(),
|
|
130
|
+
label: label.trim() || undefined,
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const handleRemoveContact = (contactId: string) => {
|
|
135
|
+
removeContactMutation.mutate(contactId);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Filter out users who are already contacts
|
|
139
|
+
const existingUserIds = new Set(
|
|
140
|
+
contacts
|
|
141
|
+
.filter((c): c is SystemContact & { type: "user" } => c.type === "user")
|
|
142
|
+
.map((c) => c.userId),
|
|
143
|
+
);
|
|
144
|
+
const availableUsers = (users as UserDto[]).filter(
|
|
145
|
+
(u) => !existingUserIds.has(u.id),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// Build tab items
|
|
149
|
+
const tabItems = [];
|
|
150
|
+
if (canSearchUsers) {
|
|
151
|
+
tabItems.push({ id: "user", label: "Add User" });
|
|
152
|
+
}
|
|
153
|
+
tabItems.push({ id: "mailbox", label: "Add Mailbox" });
|
|
154
|
+
|
|
155
|
+
if (contactsLoading || accessLoading) {
|
|
156
|
+
return (
|
|
157
|
+
<div className="flex justify-center py-4">
|
|
158
|
+
<LoadingSpinner />
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="space-y-4">
|
|
165
|
+
<Label>Contacts</Label>
|
|
166
|
+
|
|
167
|
+
{/* Existing Contacts List */}
|
|
168
|
+
{contacts.length > 0 && (
|
|
169
|
+
<div className="border rounded-lg divide-y">
|
|
170
|
+
{contacts.map((contact) => (
|
|
171
|
+
<div
|
|
172
|
+
key={contact.id}
|
|
173
|
+
className="flex items-center justify-between p-3"
|
|
174
|
+
>
|
|
175
|
+
<div className="flex items-center gap-2">
|
|
176
|
+
{contact.type === "user" ? (
|
|
177
|
+
<User className="h-4 w-4 text-muted-foreground" />
|
|
178
|
+
) : (
|
|
179
|
+
<Mail className="h-4 w-4 text-muted-foreground" />
|
|
180
|
+
)}
|
|
181
|
+
<div>
|
|
182
|
+
<span className="text-sm">
|
|
183
|
+
{contact.type === "user"
|
|
184
|
+
? (contact.userName ?? contact.userId)
|
|
185
|
+
: contact.email}
|
|
186
|
+
</span>
|
|
187
|
+
{contact.label && (
|
|
188
|
+
<span className="text-xs text-muted-foreground ml-2">
|
|
189
|
+
({contact.label})
|
|
190
|
+
</span>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
<Button
|
|
195
|
+
variant="ghost"
|
|
196
|
+
size="sm"
|
|
197
|
+
onClick={() => handleRemoveContact(contact.id)}
|
|
198
|
+
disabled={removeContactMutation.isPending}
|
|
199
|
+
>
|
|
200
|
+
<Trash2 className="h-4 w-4" />
|
|
201
|
+
</Button>
|
|
202
|
+
</div>
|
|
203
|
+
))}
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
|
|
207
|
+
{contacts.length === 0 && (
|
|
208
|
+
<p className="text-sm text-muted-foreground">
|
|
209
|
+
No contacts assigned yet
|
|
210
|
+
</p>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{/* Add Contact Section */}
|
|
214
|
+
<div className="border rounded-lg p-4 space-y-4">
|
|
215
|
+
<Tabs
|
|
216
|
+
items={tabItems}
|
|
217
|
+
activeTab={activeTab}
|
|
218
|
+
onTabChange={setActiveTab}
|
|
219
|
+
/>
|
|
220
|
+
|
|
221
|
+
{canSearchUsers && (
|
|
222
|
+
<TabPanel id="user" activeTab={activeTab} className="space-y-3 pt-3">
|
|
223
|
+
<div className="space-y-2">
|
|
224
|
+
<Label htmlFor="user-select">Platform User</Label>
|
|
225
|
+
<Select value={selectedUserId} onValueChange={setSelectedUserId}>
|
|
226
|
+
<SelectTrigger id="user-select">
|
|
227
|
+
<SelectValue placeholder="Select a user" />
|
|
228
|
+
</SelectTrigger>
|
|
229
|
+
<SelectContent>
|
|
230
|
+
{availableUsers.length === 0 ? (
|
|
231
|
+
<SelectItem value="_none" disabled>
|
|
232
|
+
No available users
|
|
233
|
+
</SelectItem>
|
|
234
|
+
) : (
|
|
235
|
+
availableUsers.map((user) => (
|
|
236
|
+
<SelectItem key={user.id} value={user.id}>
|
|
237
|
+
{user.name} ({user.email})
|
|
238
|
+
</SelectItem>
|
|
239
|
+
))
|
|
240
|
+
)}
|
|
241
|
+
</SelectContent>
|
|
242
|
+
</Select>
|
|
243
|
+
</div>
|
|
244
|
+
<div className="space-y-2">
|
|
245
|
+
<Label htmlFor="user-label">Label (optional)</Label>
|
|
246
|
+
<Input
|
|
247
|
+
id="user-label"
|
|
248
|
+
placeholder="e.g., Primary, On-Call"
|
|
249
|
+
value={label}
|
|
250
|
+
onChange={(e) => setLabel(e.target.value)}
|
|
251
|
+
/>
|
|
252
|
+
</div>
|
|
253
|
+
<Button
|
|
254
|
+
onClick={handleAddUserContact}
|
|
255
|
+
disabled={!selectedUserId || addContactMutation.isPending}
|
|
256
|
+
size="sm"
|
|
257
|
+
>
|
|
258
|
+
<Plus className="h-4 w-4 mr-1" />
|
|
259
|
+
Add User Contact
|
|
260
|
+
</Button>
|
|
261
|
+
</TabPanel>
|
|
262
|
+
)}
|
|
263
|
+
|
|
264
|
+
<TabPanel id="mailbox" activeTab={activeTab} className="space-y-3 pt-3">
|
|
265
|
+
<div className="space-y-2">
|
|
266
|
+
<Label htmlFor="mailbox-email">Email Address</Label>
|
|
267
|
+
<Input
|
|
268
|
+
id="mailbox-email"
|
|
269
|
+
type="email"
|
|
270
|
+
placeholder="team@example.com"
|
|
271
|
+
value={mailboxEmail}
|
|
272
|
+
onChange={(e) => setMailboxEmail(e.target.value)}
|
|
273
|
+
/>
|
|
274
|
+
</div>
|
|
275
|
+
<div className="space-y-2">
|
|
276
|
+
<Label htmlFor="mailbox-label">Label (optional)</Label>
|
|
277
|
+
<Input
|
|
278
|
+
id="mailbox-label"
|
|
279
|
+
placeholder="e.g., Support, Escalation"
|
|
280
|
+
value={label}
|
|
281
|
+
onChange={(e) => setLabel(e.target.value)}
|
|
282
|
+
/>
|
|
283
|
+
</div>
|
|
284
|
+
<Button
|
|
285
|
+
onClick={handleAddMailboxContact}
|
|
286
|
+
disabled={!mailboxEmail.trim() || addContactMutation.isPending}
|
|
287
|
+
size="sm"
|
|
288
|
+
>
|
|
289
|
+
<Plus className="h-4 w-4 mr-1" />
|
|
290
|
+
Add Mailbox Contact
|
|
291
|
+
</Button>
|
|
292
|
+
</TabPanel>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
);
|
|
296
|
+
};
|
|
@@ -24,7 +24,15 @@ import {
|
|
|
24
24
|
} from "@checkstack/ui";
|
|
25
25
|
import { authApiRef } from "@checkstack/auth-frontend/api";
|
|
26
26
|
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
Activity,
|
|
29
|
+
Info,
|
|
30
|
+
Users,
|
|
31
|
+
FileJson,
|
|
32
|
+
Calendar,
|
|
33
|
+
Mail,
|
|
34
|
+
User,
|
|
35
|
+
} from "lucide-react";
|
|
28
36
|
|
|
29
37
|
const CATALOG_PLUGIN_ID = "catalog";
|
|
30
38
|
|
|
@@ -57,6 +65,12 @@ export const SystemDetailPage: React.FC = () => {
|
|
|
57
65
|
const { data: groupsData, isLoading: groupsLoading } =
|
|
58
66
|
catalogClient.getGroups.useQuery({});
|
|
59
67
|
|
|
68
|
+
// Fetch contacts for this system
|
|
69
|
+
const { data: contactsData } = catalogClient.getSystemContacts.useQuery(
|
|
70
|
+
{ systemId: systemId ?? "" },
|
|
71
|
+
{ enabled: !!systemId },
|
|
72
|
+
);
|
|
73
|
+
|
|
60
74
|
// Find the system from the fetched data
|
|
61
75
|
const system = systemsData?.systems.find((s) => s.id === systemId);
|
|
62
76
|
const loading = systemsLoading || groupsLoading;
|
|
@@ -218,14 +232,6 @@ export const SystemDetailPage: React.FC = () => {
|
|
|
218
232
|
{system.description || "No description provided"}
|
|
219
233
|
</p>
|
|
220
234
|
</div>
|
|
221
|
-
<div>
|
|
222
|
-
<label className="text-sm font-medium text-muted-foreground">
|
|
223
|
-
Owner
|
|
224
|
-
</label>
|
|
225
|
-
<p className="mt-1 text-foreground">
|
|
226
|
-
{system.owner || "Not assigned"}
|
|
227
|
-
</p>
|
|
228
|
-
</div>
|
|
229
235
|
<div className="flex gap-6 text-sm">
|
|
230
236
|
<div className="flex items-center gap-2 text-muted-foreground">
|
|
231
237
|
<Calendar className="h-4 w-4" />
|
|
@@ -253,6 +259,51 @@ export const SystemDetailPage: React.FC = () => {
|
|
|
253
259
|
</CardContent>
|
|
254
260
|
</Card>
|
|
255
261
|
|
|
262
|
+
{/* Contacts Card */}
|
|
263
|
+
<Card className="border-border shadow-sm">
|
|
264
|
+
<CardHeader className="border-b border-border bg-muted/30">
|
|
265
|
+
<div className="flex items-center gap-2">
|
|
266
|
+
<Mail className="h-5 w-5 text-muted-foreground" />
|
|
267
|
+
<CardTitle className="text-lg font-semibold">Contacts</CardTitle>
|
|
268
|
+
</div>
|
|
269
|
+
</CardHeader>
|
|
270
|
+
<CardContent className="p-6">
|
|
271
|
+
{!contactsData || contactsData.length === 0 ? (
|
|
272
|
+
<p className="text-muted-foreground text-sm">
|
|
273
|
+
No contacts assigned to this system
|
|
274
|
+
</p>
|
|
275
|
+
) : (
|
|
276
|
+
<div className="space-y-2">
|
|
277
|
+
{contactsData.map((contact) => (
|
|
278
|
+
<div
|
|
279
|
+
key={contact.id}
|
|
280
|
+
className="flex items-center gap-2 text-sm"
|
|
281
|
+
>
|
|
282
|
+
{contact.type === "user" ? (
|
|
283
|
+
<User className="h-4 w-4 text-muted-foreground" />
|
|
284
|
+
) : (
|
|
285
|
+
<Mail className="h-4 w-4 text-muted-foreground" />
|
|
286
|
+
)}
|
|
287
|
+
<a
|
|
288
|
+
href={`mailto:${contact.type === "user" ? contact.userEmail : contact.email}`}
|
|
289
|
+
className="text-primary hover:underline"
|
|
290
|
+
>
|
|
291
|
+
{contact.type === "user"
|
|
292
|
+
? (contact.userName ?? contact.userId)
|
|
293
|
+
: contact.email}
|
|
294
|
+
</a>
|
|
295
|
+
{contact.label && (
|
|
296
|
+
<span className="text-muted-foreground">
|
|
297
|
+
({contact.label})
|
|
298
|
+
</span>
|
|
299
|
+
)}
|
|
300
|
+
</div>
|
|
301
|
+
))}
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
</CardContent>
|
|
305
|
+
</Card>
|
|
306
|
+
|
|
256
307
|
{/* Groups Card */}
|
|
257
308
|
<Card className="border-border shadow-sm">
|
|
258
309
|
<CardHeader className="border-b border-border bg-muted/30">
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
useToast,
|
|
13
13
|
} from "@checkstack/ui";
|
|
14
14
|
import { TeamAccessEditor } from "@checkstack/auth-frontend";
|
|
15
|
+
import { ContactsEditor } from "./ContactsEditor";
|
|
15
16
|
|
|
16
17
|
interface SystemEditorProps {
|
|
17
18
|
open: boolean;
|
|
@@ -28,7 +29,7 @@ export const SystemEditor: React.FC<SystemEditorProps> = ({
|
|
|
28
29
|
}) => {
|
|
29
30
|
const [name, setName] = useState(initialData?.name || "");
|
|
30
31
|
const [description, setDescription] = useState(
|
|
31
|
-
initialData?.description || ""
|
|
32
|
+
initialData?.description || "",
|
|
32
33
|
);
|
|
33
34
|
const [loading, setLoading] = useState(false);
|
|
34
35
|
const toast = useToast();
|
|
@@ -100,6 +101,9 @@ export const SystemEditor: React.FC<SystemEditorProps> = ({
|
|
|
100
101
|
/>
|
|
101
102
|
</div>
|
|
102
103
|
|
|
104
|
+
{/* Contacts Editor - only shown for existing systems */}
|
|
105
|
+
{initialData?.id && <ContactsEditor systemId={initialData.id} />}
|
|
106
|
+
|
|
103
107
|
{/* Team Access Editor - only shown for existing systems */}
|
|
104
108
|
{initialData?.id && (
|
|
105
109
|
<TeamAccessEditor
|
|
@@ -119,8 +123,8 @@ export const SystemEditor: React.FC<SystemEditorProps> = ({
|
|
|
119
123
|
{loading
|
|
120
124
|
? "Saving..."
|
|
121
125
|
: initialData
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
? "Save Changes"
|
|
127
|
+
: "Create System"}
|
|
124
128
|
</Button>
|
|
125
129
|
</DialogFooter>
|
|
126
130
|
</form>
|