stagent 0.6.3 → 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.
Files changed (139) hide show
  1. package/README.md +21 -2
  2. package/dist/cli.js +226 -1
  3. package/docs/.coverage-gaps.json +66 -16
  4. package/docs/.last-generated +1 -1
  5. package/docs/features/dashboard-kanban.md +13 -7
  6. package/docs/features/settings.md +15 -3
  7. package/docs/features/tables.md +122 -0
  8. package/docs/index.md +3 -2
  9. package/docs/journeys/developer.md +26 -16
  10. package/docs/journeys/personal-use.md +23 -9
  11. package/docs/journeys/power-user.md +40 -14
  12. package/docs/journeys/work-use.md +43 -15
  13. package/docs/manifest.json +27 -17
  14. package/package.json +3 -1
  15. package/src/app/api/chat/entities/search/route.ts +12 -3
  16. package/src/app/api/projects/[id]/route.ts +37 -0
  17. package/src/app/api/projects/__tests__/delete-project.test.ts +12 -0
  18. package/src/app/api/snapshots/[id]/restore/route.ts +62 -0
  19. package/src/app/api/snapshots/[id]/route.ts +44 -0
  20. package/src/app/api/snapshots/route.ts +54 -0
  21. package/src/app/api/snapshots/settings/route.ts +67 -0
  22. package/src/app/api/tables/[id]/charts/[chartId]/route.ts +89 -0
  23. package/src/app/api/tables/[id]/charts/route.ts +72 -0
  24. package/src/app/api/tables/[id]/columns/route.ts +70 -0
  25. package/src/app/api/tables/[id]/export/route.ts +94 -0
  26. package/src/app/api/tables/[id]/history/route.ts +15 -0
  27. package/src/app/api/tables/[id]/import/route.ts +111 -0
  28. package/src/app/api/tables/[id]/route.ts +86 -0
  29. package/src/app/api/tables/[id]/rows/[rowId]/history/route.ts +32 -0
  30. package/src/app/api/tables/[id]/rows/[rowId]/route.ts +51 -0
  31. package/src/app/api/tables/[id]/rows/route.ts +101 -0
  32. package/src/app/api/tables/[id]/triggers/[triggerId]/route.ts +65 -0
  33. package/src/app/api/tables/[id]/triggers/route.ts +122 -0
  34. package/src/app/api/tables/route.ts +65 -0
  35. package/src/app/api/tables/templates/route.ts +92 -0
  36. package/src/app/globals.css +14 -0
  37. package/src/app/settings/page.tsx +2 -0
  38. package/src/app/tables/[id]/page.tsx +67 -0
  39. package/src/app/tables/page.tsx +21 -0
  40. package/src/app/tables/templates/page.tsx +19 -0
  41. package/src/components/book/book-reader.tsx +62 -9
  42. package/src/components/book/content-blocks.tsx +6 -1
  43. package/src/components/chat/chat-table-result.tsx +139 -0
  44. package/src/components/documents/document-browser.tsx +1 -1
  45. package/src/components/projects/project-form-sheet.tsx +3 -27
  46. package/src/components/schedules/schedule-form.tsx +5 -27
  47. package/src/components/settings/data-management-section.tsx +17 -12
  48. package/src/components/settings/database-snapshots-section.tsx +469 -0
  49. package/src/components/shared/app-sidebar.tsx +2 -0
  50. package/src/components/shared/document-picker-sheet.tsx +214 -11
  51. package/src/components/tables/table-browser.tsx +234 -0
  52. package/src/components/tables/table-cell-editor.tsx +226 -0
  53. package/src/components/tables/table-chart-builder.tsx +288 -0
  54. package/src/components/tables/table-chart-view.tsx +146 -0
  55. package/src/components/tables/table-column-header.tsx +103 -0
  56. package/src/components/tables/table-column-sheet.tsx +331 -0
  57. package/src/components/tables/table-create-sheet.tsx +240 -0
  58. package/src/components/tables/table-detail-sheet.tsx +144 -0
  59. package/src/components/tables/table-detail-tabs.tsx +278 -0
  60. package/src/components/tables/table-grid.tsx +61 -0
  61. package/src/components/tables/table-history-tab.tsx +148 -0
  62. package/src/components/tables/table-import-wizard.tsx +542 -0
  63. package/src/components/tables/table-list-table.tsx +95 -0
  64. package/src/components/tables/table-relation-combobox.tsx +217 -0
  65. package/src/components/tables/table-row-sheet.tsx +271 -0
  66. package/src/components/tables/table-spreadsheet.tsx +394 -0
  67. package/src/components/tables/table-template-gallery.tsx +162 -0
  68. package/src/components/tables/table-template-preview.tsx +219 -0
  69. package/src/components/tables/table-toolbar.tsx +79 -0
  70. package/src/components/tables/table-triggers-tab.tsx +446 -0
  71. package/src/components/tables/types.ts +6 -0
  72. package/src/components/tables/use-spreadsheet-keys.ts +171 -0
  73. package/src/components/tables/utils.ts +29 -0
  74. package/src/components/tasks/task-create-panel.tsx +5 -31
  75. package/src/components/tasks/task-edit-dialog.tsx +5 -27
  76. package/src/components/workflows/workflow-form-view.tsx +11 -35
  77. package/src/components/workflows/workflow-status-view.tsx +1 -1
  78. package/src/instrumentation.ts +3 -0
  79. package/src/lib/agents/__tests__/claude-agent.test.ts +5 -1
  80. package/src/lib/agents/claude-agent.ts +3 -1
  81. package/src/lib/agents/profiles/builtins/document-writer/SKILL.md +23 -0
  82. package/src/lib/agents/profiles/builtins/technical-writer/SKILL.md +10 -0
  83. package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +1 -1
  84. package/src/lib/agents/runtime/anthropic-direct.ts +29 -0
  85. package/src/lib/agents/runtime/openai-direct.ts +29 -0
  86. package/src/lib/book/chapter-generator.ts +81 -5
  87. package/src/lib/book/chapter-mapping.ts +58 -24
  88. package/src/lib/book/content.ts +83 -47
  89. package/src/lib/book/markdown-parser.ts +1 -1
  90. package/src/lib/book/reading-paths.ts +8 -8
  91. package/src/lib/book/types.ts +1 -1
  92. package/src/lib/book/update-detector.ts +4 -1
  93. package/src/lib/chat/stagent-tools.ts +2 -0
  94. package/src/lib/chat/tool-catalog.ts +34 -0
  95. package/src/lib/chat/tools/table-tools.ts +955 -0
  96. package/src/lib/chat/tools/workflow-tools.ts +9 -1
  97. package/src/lib/constants/table-status.ts +68 -0
  98. package/src/lib/data/__tests__/clear.test.ts +1 -1
  99. package/src/lib/data/clear.ts +45 -0
  100. package/src/lib/data/seed-data/__tests__/profiles.test.ts +28 -23
  101. package/src/lib/data/seed-data/conversations.ts +350 -42
  102. package/src/lib/data/seed-data/documents.ts +564 -591
  103. package/src/lib/data/seed-data/learned-context.ts +101 -22
  104. package/src/lib/data/seed-data/notifications.ts +344 -70
  105. package/src/lib/data/seed-data/profile-test-results.ts +92 -11
  106. package/src/lib/data/seed-data/profiles.ts +144 -46
  107. package/src/lib/data/seed-data/projects.ts +50 -18
  108. package/src/lib/data/seed-data/repo-imports.ts +28 -13
  109. package/src/lib/data/seed-data/schedules.ts +208 -41
  110. package/src/lib/data/seed-data/table-templates.ts +234 -0
  111. package/src/lib/data/seed-data/tasks.ts +614 -116
  112. package/src/lib/data/seed-data/usage-ledger.ts +182 -103
  113. package/src/lib/data/seed-data/user-tables.ts +203 -0
  114. package/src/lib/data/seed-data/views.ts +52 -7
  115. package/src/lib/data/seed-data/workflows.ts +231 -84
  116. package/src/lib/data/seed.ts +55 -14
  117. package/src/lib/data/tables.ts +417 -0
  118. package/src/lib/db/bootstrap.ts +227 -0
  119. package/src/lib/db/index.ts +9 -0
  120. package/src/lib/db/migrations/0019_add_tables_feature.sql +160 -0
  121. package/src/lib/db/migrations/0020_add_table_triggers.sql +19 -0
  122. package/src/lib/db/migrations/0021_add_row_history.sql +15 -0
  123. package/src/lib/db/schema.ts +368 -0
  124. package/src/lib/snapshots/auto-backup.ts +132 -0
  125. package/src/lib/snapshots/retention.ts +64 -0
  126. package/src/lib/snapshots/snapshot-manager.ts +429 -0
  127. package/src/lib/tables/computed.ts +61 -0
  128. package/src/lib/tables/context-builder.ts +139 -0
  129. package/src/lib/tables/formula-engine.ts +415 -0
  130. package/src/lib/tables/history.ts +115 -0
  131. package/src/lib/tables/import.ts +343 -0
  132. package/src/lib/tables/query-builder.ts +152 -0
  133. package/src/lib/tables/trigger-evaluator.ts +146 -0
  134. package/src/lib/tables/types.ts +141 -0
  135. package/src/lib/tables/validation.ts +119 -0
  136. package/src/lib/utils/stagent-paths.ts +20 -0
  137. package/src/lib/workflows/types.ts +1 -1
  138. package/tsconfig.json +3 -1
  139. /package/docs/features/{playbook.md → user-guide.md} +0 -0
@@ -0,0 +1,217 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { Button } from "@/components/ui/button";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import {
7
+ Popover,
8
+ PopoverContent,
9
+ PopoverTrigger,
10
+ } from "@/components/ui/popover";
11
+ import {
12
+ Command,
13
+ CommandInput,
14
+ CommandList,
15
+ CommandEmpty,
16
+ CommandGroup,
17
+ CommandItem,
18
+ } from "@/components/ui/command";
19
+ import { Check, ChevronsUpDown, X } from "lucide-react";
20
+ import { cn } from "@/lib/utils";
21
+
22
+ interface TargetRow {
23
+ id: string;
24
+ displayValue: string;
25
+ }
26
+
27
+ interface TableRelationComboboxProps {
28
+ targetTableId: string;
29
+ displayColumn: string;
30
+ value: string | string[] | null;
31
+ multiple?: boolean;
32
+ onChange: (value: string | string[]) => void;
33
+ }
34
+
35
+ export function TableRelationCombobox({
36
+ targetTableId,
37
+ displayColumn,
38
+ value,
39
+ multiple = false,
40
+ onChange,
41
+ }: TableRelationComboboxProps) {
42
+ const [open, setOpen] = useState(false);
43
+ const [rows, setRows] = useState<TargetRow[]>([]);
44
+ const [loading, setLoading] = useState(false);
45
+ const [search, setSearch] = useState("");
46
+
47
+ const selectedIds = multiple
48
+ ? Array.isArray(value)
49
+ ? value
50
+ : value
51
+ ? [value]
52
+ : []
53
+ : value
54
+ ? [value as string]
55
+ : [];
56
+
57
+ const fetchRows = useCallback(
58
+ async (query?: string) => {
59
+ setLoading(true);
60
+ try {
61
+ const params = new URLSearchParams();
62
+ if (query) params.set("search", query);
63
+ params.set("limit", "50");
64
+
65
+ const res = await fetch(
66
+ `/api/tables/${targetTableId}/rows?${params.toString()}`
67
+ );
68
+ if (res.ok) {
69
+ const data = await res.json();
70
+ const items: TargetRow[] = (data.rows ?? data ?? []).map(
71
+ (row: Record<string, unknown>) => ({
72
+ id: String(row.id ?? row._id ?? ""),
73
+ displayValue: String(row[displayColumn] ?? row.id ?? ""),
74
+ })
75
+ );
76
+ setRows(items);
77
+ }
78
+ } catch {
79
+ // silent
80
+ } finally {
81
+ setLoading(false);
82
+ }
83
+ },
84
+ [targetTableId, displayColumn]
85
+ );
86
+
87
+ useEffect(() => {
88
+ if (open) {
89
+ fetchRows();
90
+ }
91
+ }, [open, fetchRows]);
92
+
93
+ useEffect(() => {
94
+ if (open && search) {
95
+ const timer = setTimeout(() => fetchRows(search), 300);
96
+ return () => clearTimeout(timer);
97
+ }
98
+ }, [search, open, fetchRows]);
99
+
100
+ function handleSelect(rowId: string) {
101
+ if (multiple) {
102
+ const current = [...selectedIds];
103
+ const idx = current.indexOf(rowId);
104
+ if (idx >= 0) {
105
+ current.splice(idx, 1);
106
+ } else {
107
+ current.push(rowId);
108
+ }
109
+ onChange(current);
110
+ } else {
111
+ onChange(rowId);
112
+ setOpen(false);
113
+ }
114
+ }
115
+
116
+ function handleRemove(rowId: string) {
117
+ if (multiple) {
118
+ onChange(selectedIds.filter((id) => id !== rowId));
119
+ } else {
120
+ onChange(multiple ? [] : "");
121
+ }
122
+ }
123
+
124
+ function getDisplayValue(rowId: string): string {
125
+ const row = rows.find((r) => r.id === rowId);
126
+ return row?.displayValue ?? rowId;
127
+ }
128
+
129
+ // Single value display
130
+ const singleDisplay =
131
+ !multiple && selectedIds.length > 0
132
+ ? getDisplayValue(selectedIds[0])
133
+ : null;
134
+
135
+ return (
136
+ <div className="space-y-1.5">
137
+ {/* Multi-select: show selected as badges */}
138
+ {multiple && selectedIds.length > 0 && (
139
+ <div className="flex flex-wrap gap-1">
140
+ {selectedIds.map((id) => (
141
+ <Badge key={id} variant="secondary" className="gap-1 pr-1">
142
+ <span className="truncate max-w-[120px]">
143
+ {getDisplayValue(id)}
144
+ </span>
145
+ <button
146
+ type="button"
147
+ onClick={(e) => {
148
+ e.stopPropagation();
149
+ handleRemove(id);
150
+ }}
151
+ className="ml-0.5 rounded-sm hover:bg-muted p-0.5"
152
+ >
153
+ <X className="h-3 w-3" />
154
+ </button>
155
+ </Badge>
156
+ ))}
157
+ </div>
158
+ )}
159
+
160
+ <Popover open={open} onOpenChange={setOpen}>
161
+ <PopoverTrigger asChild>
162
+ <Button
163
+ variant="outline"
164
+ role="combobox"
165
+ aria-expanded={open}
166
+ className="w-full justify-between h-9 text-sm font-normal"
167
+ >
168
+ <span className="truncate">
169
+ {singleDisplay ?? (multiple ? "Select rows..." : "Select a row...")}
170
+ </span>
171
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
172
+ </Button>
173
+ </PopoverTrigger>
174
+ <PopoverContent className="w-[280px] p-0" align="start">
175
+ <Command shouldFilter={false}>
176
+ <CommandInput
177
+ placeholder="Search..."
178
+ value={search}
179
+ onValueChange={setSearch}
180
+ />
181
+ <CommandList>
182
+ {loading ? (
183
+ <div className="py-6 text-center text-sm text-muted-foreground">
184
+ Loading...
185
+ </div>
186
+ ) : (
187
+ <>
188
+ <CommandEmpty>No rows found.</CommandEmpty>
189
+ <CommandGroup>
190
+ {rows.map((row) => {
191
+ const isSelected = selectedIds.includes(row.id);
192
+ return (
193
+ <CommandItem
194
+ key={row.id}
195
+ value={row.id}
196
+ onSelect={() => handleSelect(row.id)}
197
+ >
198
+ <Check
199
+ className={cn(
200
+ "mr-2 h-4 w-4",
201
+ isSelected ? "opacity-100" : "opacity-0"
202
+ )}
203
+ />
204
+ <span className="truncate">{row.displayValue}</span>
205
+ </CommandItem>
206
+ );
207
+ })}
208
+ </CommandGroup>
209
+ </>
210
+ )}
211
+ </CommandList>
212
+ </Command>
213
+ </PopoverContent>
214
+ </Popover>
215
+ </div>
216
+ );
217
+ }
@@ -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
+ }