stagent 0.7.0 → 0.8.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.
- package/package.json +1 -1
- package/src/app/globals.css +14 -0
- package/src/components/book/book-reader.tsx +62 -9
- package/src/components/book/content-blocks.tsx +6 -1
- package/src/components/tables/table-cell-editor.tsx +3 -3
- package/src/components/tables/table-row-sheet.tsx +271 -0
- package/src/components/tables/table-spreadsheet.tsx +55 -160
- package/src/components/workflows/workflow-form-view.tsx +6 -6
- package/src/lib/agents/profiles/builtins/document-writer/SKILL.md +23 -0
- package/src/lib/agents/profiles/builtins/technical-writer/SKILL.md +10 -0
- package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +1 -1
- package/src/lib/book/chapter-generator.ts +81 -5
- package/src/lib/book/chapter-mapping.ts +58 -24
- package/src/lib/book/content.ts +83 -47
- package/src/lib/book/markdown-parser.ts +1 -1
- package/src/lib/book/reading-paths.ts +8 -8
- package/src/lib/book/types.ts +1 -1
- package/src/lib/book/update-detector.ts +4 -1
- package/src/lib/chat/tools/workflow-tools.ts +9 -1
- package/src/lib/workflows/types.ts +1 -1
package/package.json
CHANGED
package/src/app/globals.css
CHANGED
|
@@ -810,6 +810,12 @@ body {
|
|
|
810
810
|
.book-callout-lesson .book-callout-icon { color: oklch(0.55 0.20 300); }
|
|
811
811
|
.book-callout-lesson .book-callout-title { color: oklch(0.45 0.16 300); }
|
|
812
812
|
|
|
813
|
+
.book-callout-case-study {
|
|
814
|
+
border-left-color: oklch(0.62 0.16 55);
|
|
815
|
+
background: oklch(0.62 0.16 55 / 0.05);
|
|
816
|
+
}
|
|
817
|
+
.book-callout-case-study .book-callout-icon { color: oklch(0.62 0.16 55); }
|
|
818
|
+
.book-callout-case-study .book-callout-title { color: oklch(0.50 0.12 55); }
|
|
813
819
|
|
|
814
820
|
/* callout body inline code */
|
|
815
821
|
.book-callout-body code {
|
|
@@ -821,6 +827,7 @@ body {
|
|
|
821
827
|
[data-book-theme="sepia"] .book-callout-warning { background: oklch(0.65 0.18 75 / 0.08); }
|
|
822
828
|
[data-book-theme="sepia"] .book-callout-info { background: oklch(0.55 0.20 260 / 0.08); }
|
|
823
829
|
[data-book-theme="sepia"] .book-callout-lesson { background: oklch(0.55 0.20 300 / 0.08); }
|
|
830
|
+
[data-book-theme="sepia"] .book-callout-case-study { background: oklch(0.62 0.16 55 / 0.08); }
|
|
824
831
|
[data-book-theme="sepia"] .book-callout-body code { background: oklch(0 0 0 / 0.06); }
|
|
825
832
|
|
|
826
833
|
/* dark callout overrides */
|
|
@@ -848,6 +855,11 @@ body {
|
|
|
848
855
|
[data-book-theme="dark"] .book-callout-lesson .book-callout-icon { color: oklch(0.70 0.18 300); }
|
|
849
856
|
[data-book-theme="dark"] .book-callout-lesson .book-callout-title { color: oklch(0.75 0.14 300); }
|
|
850
857
|
|
|
858
|
+
[data-book-theme="dark"] .book-callout-case-study {
|
|
859
|
+
background: oklch(0.62 0.16 55 / 0.10);
|
|
860
|
+
}
|
|
861
|
+
[data-book-theme="dark"] .book-callout-case-study .book-callout-icon { color: oklch(0.75 0.16 55); }
|
|
862
|
+
[data-book-theme="dark"] .book-callout-case-study .book-callout-title { color: oklch(0.80 0.12 55); }
|
|
851
863
|
|
|
852
864
|
[data-book-theme="dark"] .book-callout-body code { background: oklch(1 0 0 / 0.10); }
|
|
853
865
|
|
|
@@ -860,6 +872,8 @@ body {
|
|
|
860
872
|
.dark .book-callout-info .book-callout-title { color: oklch(0.75 0.14 260); }
|
|
861
873
|
.dark .book-callout-lesson .book-callout-icon { color: oklch(0.70 0.18 300); }
|
|
862
874
|
.dark .book-callout-lesson .book-callout-title { color: oklch(0.75 0.14 300); }
|
|
875
|
+
.dark .book-callout-case-study .book-callout-icon { color: oklch(0.75 0.16 55); }
|
|
876
|
+
.dark .book-callout-case-study .book-callout-title { color: oklch(0.80 0.12 55); }
|
|
863
877
|
.dark .book-callout-body code { background: oklch(1 0 0 / 0.10); }
|
|
864
878
|
|
|
865
879
|
/* ─── Book Interactive Blocks ─── */
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
BookOpen,
|
|
6
6
|
BookmarkPlus,
|
|
7
7
|
BookmarkMinus,
|
|
8
|
+
ChevronDown,
|
|
8
9
|
ChevronLeft,
|
|
9
10
|
ChevronRight,
|
|
10
11
|
List,
|
|
@@ -87,6 +88,7 @@ export function BookReader({ chapters: CHAPTERS }: { chapters: BookChapter[] })
|
|
|
87
88
|
const [tocOpen, setTocOpen] = useState(false);
|
|
88
89
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
89
90
|
const [tocTab, setTocTab] = useState<"chapters" | "bookmarks">("chapters");
|
|
91
|
+
const [collapsedParts, setCollapsedParts] = useState<Set<number>>(new Set());
|
|
90
92
|
const [activePath, setActivePath] = useState<string | null>(null);
|
|
91
93
|
const [recommendedPath, setRecommendedPath] = useState<string | null>(null);
|
|
92
94
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
@@ -160,6 +162,31 @@ export function BookReader({ chapters: CHAPTERS }: { chapters: BookChapter[] })
|
|
|
160
162
|
}
|
|
161
163
|
}, []);
|
|
162
164
|
|
|
165
|
+
const togglePart = useCallback((partNumber: number) => {
|
|
166
|
+
setCollapsedParts((prev) => {
|
|
167
|
+
const next = new Set(prev);
|
|
168
|
+
if (next.has(partNumber)) {
|
|
169
|
+
next.delete(partNumber);
|
|
170
|
+
} else {
|
|
171
|
+
next.add(partNumber);
|
|
172
|
+
}
|
|
173
|
+
return next;
|
|
174
|
+
});
|
|
175
|
+
}, []);
|
|
176
|
+
|
|
177
|
+
// Auto-expand the current chapter's part so it's always visible in TOC
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
const currentPart = currentChapter.part.number;
|
|
180
|
+
setCollapsedParts((prev) => {
|
|
181
|
+
if (prev.has(currentPart)) {
|
|
182
|
+
const next = new Set(prev);
|
|
183
|
+
next.delete(currentPart);
|
|
184
|
+
return next;
|
|
185
|
+
}
|
|
186
|
+
return prev;
|
|
187
|
+
});
|
|
188
|
+
}, [currentChapter]);
|
|
189
|
+
|
|
163
190
|
// Track scroll progress
|
|
164
191
|
useEffect(() => {
|
|
165
192
|
const el = contentRef.current;
|
|
@@ -431,15 +458,38 @@ export function BookReader({ chapters: CHAPTERS }: { chapters: BookChapter[] })
|
|
|
431
458
|
onSelectPath={handlePathChange}
|
|
432
459
|
/>
|
|
433
460
|
|
|
434
|
-
{PARTS.map((part) =>
|
|
461
|
+
{PARTS.map((part) => {
|
|
462
|
+
const isPartOpen = !collapsedParts.has(part.number);
|
|
463
|
+
return (
|
|
435
464
|
<div key={part.number}>
|
|
436
|
-
<
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
465
|
+
<button
|
|
466
|
+
onClick={() => togglePart(part.number)}
|
|
467
|
+
className="flex w-full items-center justify-between gap-2 text-left cursor-pointer group"
|
|
468
|
+
aria-expanded={isPartOpen}
|
|
469
|
+
>
|
|
470
|
+
<div>
|
|
471
|
+
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
472
|
+
Part {part.number}: {part.title}
|
|
473
|
+
</p>
|
|
474
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
475
|
+
{part.description}
|
|
476
|
+
</p>
|
|
477
|
+
</div>
|
|
478
|
+
<ChevronDown
|
|
479
|
+
className={cn(
|
|
480
|
+
"h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform duration-300 ease-in-out",
|
|
481
|
+
isPartOpen && "rotate-180"
|
|
482
|
+
)}
|
|
483
|
+
/>
|
|
484
|
+
</button>
|
|
485
|
+
<div
|
|
486
|
+
className={cn(
|
|
487
|
+
"grid transition-[grid-template-rows] duration-300 ease-in-out",
|
|
488
|
+
isPartOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
|
|
489
|
+
)}
|
|
490
|
+
>
|
|
491
|
+
<div className="overflow-hidden">
|
|
492
|
+
<div className="space-y-1 pt-2">
|
|
443
493
|
{(chaptersByPart.get(part.number) ?? []).map((ch) => {
|
|
444
494
|
const chProgress = progress[ch.id]?.progress ?? 0;
|
|
445
495
|
const chPct = Math.round(chProgress * 100);
|
|
@@ -488,8 +538,11 @@ export function BookReader({ chapters: CHAPTERS }: { chapters: BookChapter[] })
|
|
|
488
538
|
);
|
|
489
539
|
})}
|
|
490
540
|
</div>
|
|
541
|
+
</div>
|
|
542
|
+
</div>
|
|
491
543
|
</div>
|
|
492
|
-
|
|
544
|
+
);
|
|
545
|
+
})}
|
|
493
546
|
</div>
|
|
494
547
|
) : (
|
|
495
548
|
<div className="space-y-1">
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
AlertTriangle,
|
|
9
9
|
BookOpen,
|
|
10
10
|
PenLine,
|
|
11
|
+
Building2,
|
|
11
12
|
ArrowRight,
|
|
12
13
|
ChevronDown,
|
|
13
14
|
Copy,
|
|
@@ -203,6 +204,10 @@ const calloutConfig = {
|
|
|
203
204
|
icon: PenLine,
|
|
204
205
|
className: "book-callout-authors-note",
|
|
205
206
|
},
|
|
207
|
+
"case-study": {
|
|
208
|
+
icon: Building2,
|
|
209
|
+
className: "book-callout-case-study",
|
|
210
|
+
},
|
|
206
211
|
};
|
|
207
212
|
|
|
208
213
|
function CalloutBlockView({
|
|
@@ -213,7 +218,7 @@ function CalloutBlockView({
|
|
|
213
218
|
imageAlt,
|
|
214
219
|
defaultCollapsed,
|
|
215
220
|
}: {
|
|
216
|
-
variant: "tip" | "warning" | "info" | "lesson" | "authors-note";
|
|
221
|
+
variant: "tip" | "warning" | "info" | "lesson" | "authors-note" | "case-study";
|
|
217
222
|
title?: string;
|
|
218
223
|
markdown: string;
|
|
219
224
|
imageSrc?: string;
|
|
@@ -206,7 +206,7 @@ export function CellDisplay({ column, value, onToggleBoolean }: CellDisplayProps
|
|
|
206
206
|
return (
|
|
207
207
|
<a
|
|
208
208
|
href={`mailto:${value}`}
|
|
209
|
-
className="text-sm text-primary underline underline-offset-2 hover:text-primary/80"
|
|
209
|
+
className="text-sm text-primary underline underline-offset-2 hover:text-primary/80 truncate max-w-[200px] block"
|
|
210
210
|
onClick={(e) => e.stopPropagation()}
|
|
211
211
|
>
|
|
212
212
|
{String(value)}
|
|
@@ -215,12 +215,12 @@ export function CellDisplay({ column, value, onToggleBoolean }: CellDisplayProps
|
|
|
215
215
|
|
|
216
216
|
case "computed":
|
|
217
217
|
return (
|
|
218
|
-
<span className="text-sm text-muted-foreground italic">
|
|
218
|
+
<span className="text-sm text-muted-foreground italic truncate max-w-[200px] block">
|
|
219
219
|
{String(value)}
|
|
220
220
|
</span>
|
|
221
221
|
);
|
|
222
222
|
|
|
223
223
|
default:
|
|
224
|
-
return <span className="text-sm">{String(value)}</span>;
|
|
224
|
+
return <span className="text-sm truncate max-w-[200px] block">{String(value)}</span>;
|
|
225
225
|
}
|
|
226
226
|
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { Input } from "@/components/ui/input";
|
|
6
|
+
import { Label } from "@/components/ui/label";
|
|
7
|
+
import { Switch } from "@/components/ui/switch";
|
|
8
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
9
|
+
import {
|
|
10
|
+
Select,
|
|
11
|
+
SelectContent,
|
|
12
|
+
SelectItem,
|
|
13
|
+
SelectTrigger,
|
|
14
|
+
SelectValue,
|
|
15
|
+
} from "@/components/ui/select";
|
|
16
|
+
import {
|
|
17
|
+
Sheet,
|
|
18
|
+
SheetContent,
|
|
19
|
+
SheetHeader,
|
|
20
|
+
SheetTitle,
|
|
21
|
+
SheetFooter,
|
|
22
|
+
} from "@/components/ui/sheet";
|
|
23
|
+
import { toast } from "sonner";
|
|
24
|
+
import { TableRelationCombobox } from "./table-relation-combobox";
|
|
25
|
+
import type { ColumnDef } from "@/lib/tables/types";
|
|
26
|
+
|
|
27
|
+
interface ParsedRow {
|
|
28
|
+
id: string;
|
|
29
|
+
data: Record<string, unknown>;
|
|
30
|
+
position: number;
|
|
31
|
+
createdBy: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface TableRowSheetProps {
|
|
35
|
+
tableId: string;
|
|
36
|
+
columns: ColumnDef[];
|
|
37
|
+
row: ParsedRow;
|
|
38
|
+
open: boolean;
|
|
39
|
+
onOpenChange: (open: boolean) => void;
|
|
40
|
+
onRowUpdated: (rowId: string, data: Record<string, unknown>) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function TableRowSheet({
|
|
44
|
+
tableId,
|
|
45
|
+
columns,
|
|
46
|
+
row,
|
|
47
|
+
open,
|
|
48
|
+
onOpenChange,
|
|
49
|
+
onRowUpdated,
|
|
50
|
+
}: TableRowSheetProps) {
|
|
51
|
+
const [formData, setFormData] = useState<Record<string, unknown>>({});
|
|
52
|
+
const [saving, setSaving] = useState(false);
|
|
53
|
+
|
|
54
|
+
// Reset form data when sheet opens or row changes
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (open) {
|
|
57
|
+
setFormData({ ...row.data });
|
|
58
|
+
}
|
|
59
|
+
}, [open, row.id, row.data]);
|
|
60
|
+
|
|
61
|
+
const setField = (name: string, value: unknown) => {
|
|
62
|
+
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleSave = async () => {
|
|
66
|
+
setSaving(true);
|
|
67
|
+
try {
|
|
68
|
+
// Exclude computed columns from the payload
|
|
69
|
+
const payload: Record<string, unknown> = {};
|
|
70
|
+
for (const col of columns) {
|
|
71
|
+
if (col.dataType !== "computed") {
|
|
72
|
+
payload[col.name] = formData[col.name] ?? null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const res = await fetch(`/api/tables/${tableId}/rows/${row.id}`, {
|
|
77
|
+
method: "PATCH",
|
|
78
|
+
headers: { "Content-Type": "application/json" },
|
|
79
|
+
body: JSON.stringify({ data: payload }),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
toast.error("Failed to save row");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
onRowUpdated(row.id, { ...formData, ...payload });
|
|
88
|
+
onOpenChange(false);
|
|
89
|
+
toast.success("Row updated");
|
|
90
|
+
} catch {
|
|
91
|
+
toast.error("Failed to save row");
|
|
92
|
+
} finally {
|
|
93
|
+
setSaving(false);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const sortedColumns = [...columns].sort((a, b) => a.position - b.position);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
101
|
+
<SheetContent side="right" className="w-[480px] sm:max-w-[480px] flex flex-col">
|
|
102
|
+
<SheetHeader>
|
|
103
|
+
<SheetTitle>Edit Row</SheetTitle>
|
|
104
|
+
</SheetHeader>
|
|
105
|
+
<div className="px-6 pb-6 space-y-4 overflow-y-auto flex-1">
|
|
106
|
+
{sortedColumns.map((col) => (
|
|
107
|
+
<RowField
|
|
108
|
+
key={col.name}
|
|
109
|
+
column={col}
|
|
110
|
+
value={formData[col.name]}
|
|
111
|
+
onChange={(v) => setField(col.name, v)}
|
|
112
|
+
/>
|
|
113
|
+
))}
|
|
114
|
+
</div>
|
|
115
|
+
<SheetFooter className="px-6">
|
|
116
|
+
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
117
|
+
Cancel
|
|
118
|
+
</Button>
|
|
119
|
+
<Button onClick={handleSave} disabled={saving}>
|
|
120
|
+
{saving ? "Saving..." : "Save"}
|
|
121
|
+
</Button>
|
|
122
|
+
</SheetFooter>
|
|
123
|
+
</SheetContent>
|
|
124
|
+
</Sheet>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Per-field renderer ──────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
interface RowFieldProps {
|
|
131
|
+
column: ColumnDef;
|
|
132
|
+
value: unknown;
|
|
133
|
+
onChange: (value: unknown) => void;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function RowField({ column, value, onChange }: RowFieldProps) {
|
|
137
|
+
const strValue = value == null ? "" : String(value);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="space-y-2">
|
|
141
|
+
<Label className="flex items-center gap-1.5">
|
|
142
|
+
{column.displayName}
|
|
143
|
+
{column.required && <span className="text-destructive">*</span>}
|
|
144
|
+
</Label>
|
|
145
|
+
{renderInput(column, value, strValue, onChange)}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function renderInput(
|
|
151
|
+
column: ColumnDef,
|
|
152
|
+
value: unknown,
|
|
153
|
+
strValue: string,
|
|
154
|
+
onChange: (value: unknown) => void,
|
|
155
|
+
) {
|
|
156
|
+
switch (column.dataType) {
|
|
157
|
+
case "text":
|
|
158
|
+
return (
|
|
159
|
+
<Textarea
|
|
160
|
+
rows={3}
|
|
161
|
+
value={strValue}
|
|
162
|
+
onChange={(e) => onChange(e.target.value)}
|
|
163
|
+
placeholder={`Enter ${column.displayName.toLowerCase()}...`}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
case "number":
|
|
168
|
+
return (
|
|
169
|
+
<Input
|
|
170
|
+
type="number"
|
|
171
|
+
value={strValue}
|
|
172
|
+
onChange={(e) =>
|
|
173
|
+
onChange(e.target.value === "" ? null : Number(e.target.value))
|
|
174
|
+
}
|
|
175
|
+
placeholder="0"
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
case "date":
|
|
180
|
+
return (
|
|
181
|
+
<Input
|
|
182
|
+
type="date"
|
|
183
|
+
value={strValue}
|
|
184
|
+
onChange={(e) => onChange(e.target.value)}
|
|
185
|
+
/>
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
case "email":
|
|
189
|
+
return (
|
|
190
|
+
<Input
|
|
191
|
+
type="email"
|
|
192
|
+
value={strValue}
|
|
193
|
+
onChange={(e) => onChange(e.target.value)}
|
|
194
|
+
placeholder="name@example.com"
|
|
195
|
+
/>
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
case "url":
|
|
199
|
+
return (
|
|
200
|
+
<Input
|
|
201
|
+
type="url"
|
|
202
|
+
value={strValue}
|
|
203
|
+
onChange={(e) => onChange(e.target.value)}
|
|
204
|
+
placeholder="https://..."
|
|
205
|
+
/>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
case "boolean":
|
|
209
|
+
return (
|
|
210
|
+
<div className="flex items-center gap-2 pt-1">
|
|
211
|
+
<Switch
|
|
212
|
+
checked={!!value}
|
|
213
|
+
onCheckedChange={(checked) => onChange(checked)}
|
|
214
|
+
/>
|
|
215
|
+
<span className="text-sm text-muted-foreground">
|
|
216
|
+
{value ? "Yes" : "No"}
|
|
217
|
+
</span>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
case "select": {
|
|
222
|
+
const options = column.config?.options ?? [];
|
|
223
|
+
return (
|
|
224
|
+
<Select value={strValue} onValueChange={onChange}>
|
|
225
|
+
<SelectTrigger>
|
|
226
|
+
<SelectValue placeholder={`Select ${column.displayName.toLowerCase()}...`} />
|
|
227
|
+
</SelectTrigger>
|
|
228
|
+
<SelectContent>
|
|
229
|
+
{options.map((opt) => (
|
|
230
|
+
<SelectItem key={opt} value={opt}>
|
|
231
|
+
{opt}
|
|
232
|
+
</SelectItem>
|
|
233
|
+
))}
|
|
234
|
+
</SelectContent>
|
|
235
|
+
</Select>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
case "relation": {
|
|
240
|
+
const targetId = column.config?.targetTableId;
|
|
241
|
+
const dispCol = column.config?.displayColumn ?? "name";
|
|
242
|
+
if (!targetId) return <Input disabled value="No target table configured" />;
|
|
243
|
+
return (
|
|
244
|
+
<TableRelationCombobox
|
|
245
|
+
targetTableId={targetId}
|
|
246
|
+
displayColumn={dispCol}
|
|
247
|
+
value={strValue || null}
|
|
248
|
+
onChange={(v) => onChange(v)}
|
|
249
|
+
/>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
case "computed":
|
|
254
|
+
return (
|
|
255
|
+
<Input
|
|
256
|
+
disabled
|
|
257
|
+
value={strValue}
|
|
258
|
+
className="text-muted-foreground italic"
|
|
259
|
+
/>
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
default:
|
|
263
|
+
return (
|
|
264
|
+
<Input
|
|
265
|
+
value={strValue}
|
|
266
|
+
onChange={(e) => onChange(e.target.value)}
|
|
267
|
+
placeholder={`Enter ${column.displayName.toLowerCase()}...`}
|
|
268
|
+
/>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|